From 118626c8752bee7b05c4e0b668852e1aba2416f1 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 21 Apr 2023 15:04:52 +0200 Subject: Implement runner in client side --- client/src/app/+admin/system/index.ts | 1 + client/src/app/+admin/system/runners/index.ts | 5 + .../+admin/system/runners/runner-job-list/index.ts | 1 + .../runner-job-list/runner-job-list.component.html | 101 ++++++++++++++++++ .../runner-job-list/runner-job-list.component.ts | 76 +++++++++++++ .../app/+admin/system/runners/runner-list/index.ts | 1 + .../runners/runner-list/runner-list.component.html | 61 +++++++++++ .../runners/runner-list/runner-list.component.ts | 76 +++++++++++++ .../runner-registration-token-list/index.ts | 1 + .../runner-registration-token-list.component.html | 65 ++++++++++++ .../runner-registration-token-list.component.ts | 88 ++++++++++++++++ .../app/+admin/system/runners/runner.service.ts | 117 +++++++++++++++++++++ .../app/+admin/system/runners/runners.routes.ts | 53 ++++++++++ client/src/app/+admin/system/system.routes.ts | 5 +- 14 files changed, 650 insertions(+), 1 deletion(-) create mode 100644 client/src/app/+admin/system/runners/index.ts create mode 100644 client/src/app/+admin/system/runners/runner-job-list/index.ts create mode 100644 client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.html create mode 100644 client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.ts create mode 100644 client/src/app/+admin/system/runners/runner-list/index.ts create mode 100644 client/src/app/+admin/system/runners/runner-list/runner-list.component.html create mode 100644 client/src/app/+admin/system/runners/runner-list/runner-list.component.ts create mode 100644 client/src/app/+admin/system/runners/runner-registration-token-list/index.ts create mode 100644 client/src/app/+admin/system/runners/runner-registration-token-list/runner-registration-token-list.component.html create mode 100644 client/src/app/+admin/system/runners/runner-registration-token-list/runner-registration-token-list.component.ts create mode 100644 client/src/app/+admin/system/runners/runner.service.ts create mode 100644 client/src/app/+admin/system/runners/runners.routes.ts (limited to 'client/src/app/+admin/system') diff --git a/client/src/app/+admin/system/index.ts b/client/src/app/+admin/system/index.ts index b398832cc..6d84dafa8 100644 --- a/client/src/app/+admin/system/index.ts +++ b/client/src/app/+admin/system/index.ts @@ -1,4 +1,5 @@ export * from './debug' export * from './jobs' export * from './logs' +export * from './runners' export * from './system.routes' diff --git a/client/src/app/+admin/system/runners/index.ts b/client/src/app/+admin/system/runners/index.ts new file mode 100644 index 000000000..dbbf32fb5 --- /dev/null +++ b/client/src/app/+admin/system/runners/index.ts @@ -0,0 +1,5 @@ +export * from './runner.service' +export * from './runner-job-list' +export * from './runner-list' +export * from './runner-registration-token-list' +export * from './runners.routes' diff --git a/client/src/app/+admin/system/runners/runner-job-list/index.ts b/client/src/app/+admin/system/runners/runner-job-list/index.ts new file mode 100644 index 000000000..bdcf93234 --- /dev/null +++ b/client/src/app/+admin/system/runners/runner-job-list/index.ts @@ -0,0 +1 @@ +export * from './runner-job-list.component' diff --git a/client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.html b/client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.html new file mode 100644 index 000000000..7858b4bca --- /dev/null +++ b/client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.html @@ -0,0 +1,101 @@ +

+ + + Runner jobs + + + + + Remote runners + +

+ + + + + + + UUID + Type + State + Priority + Progress + Runner + Created + + + + +
+
+ + + +
+
+
+ + + + + + + + + + + + {{ runnerJob.uuid }} + {{ runnerJob.type }} + {{ runnerJob.state.label }} + {{ runnerJob.priority }} + {{ runnerJob.progress }} + {{ runnerJob.runner?.name }} + {{ runnerJob.createdAt | date: 'short' }} + + + + + + +
+ Parent job: {{ runnerJob.parent?.uuid || '-' }}
+ Processed on {{ (runnerJob.startedAt || '-') }}
+ Finished on {{ (runnerJob.finishedAt || '-') }}
+
+ +
+ Payload: +
{{ runnerJob.payload }}
+
+ +
+ Private payload: +
{{ runnerJob.privatePayload }}
+
+ +
{{ runnerJob.error }}
+ + +
+ + + + +
+ No runner jobs found. +
+ + +
+
diff --git a/client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.ts b/client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.ts new file mode 100644 index 000000000..ea889f0f7 --- /dev/null +++ b/client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.ts @@ -0,0 +1,76 @@ +import { SortMeta } from 'primeng/api' +import { Component, OnInit } from '@angular/core' +import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' +import { DropdownAction } from '@app/shared/shared-main' +import { RunnerJob } from '@shared/models' +import { RunnerJobFormatted, RunnerService } from '../runner.service' + +@Component({ + selector: 'my-runner-job-list', + templateUrl: './runner-job-list.component.html' +}) +export class RunnerJobListComponent extends RestTable implements OnInit { + runnerJobs: RunnerJobFormatted[] = [] + totalRecords = 0 + + sort: SortMeta = { field: 'createdAt', order: -1 } + pagination: RestPagination = { count: this.rowsPerPage, start: 0 } + + actions: DropdownAction[][] = [] + + constructor ( + private runnerService: RunnerService, + private notifier: Notifier, + private confirmService: ConfirmService + ) { + super() + } + + ngOnInit () { + this.actions = [ + [ + { + label: $localize`Cancel this job`, + handler: job => this.cancelJob(job) + } + ] + ] + + this.initialize() + } + + getIdentifier () { + return 'RunnerJobListComponent' + } + + async cancelJob (job: RunnerJob) { + const res = await this.confirmService.confirm( + $localize`Do you really want to cancel this job? Children won't be processed.`, + $localize`Cancel job` + ) + + if (res === false) return + + this.runnerService.cancelJob(job) + .subscribe({ + next: () => { + this.reloadData() + this.notifier.success($localize`Job cancelled.`) + }, + + error: err => this.notifier.error(err.message) + }) + } + + protected reloadDataInternal () { + this.runnerService.listRunnerJobs({ pagination: this.pagination, sort: this.sort, search: this.search }) + .subscribe({ + next: resultList => { + this.runnerJobs = resultList.data + this.totalRecords = resultList.total + }, + + error: err => this.notifier.error(err.message) + }) + } +} diff --git a/client/src/app/+admin/system/runners/runner-list/index.ts b/client/src/app/+admin/system/runners/runner-list/index.ts new file mode 100644 index 000000000..5c12bb6d6 --- /dev/null +++ b/client/src/app/+admin/system/runners/runner-list/index.ts @@ -0,0 +1 @@ +export * from './runner-list.component' diff --git a/client/src/app/+admin/system/runners/runner-list/runner-list.component.html b/client/src/app/+admin/system/runners/runner-list/runner-list.component.html new file mode 100644 index 000000000..606eb9afd --- /dev/null +++ b/client/src/app/+admin/system/runners/runner-list/runner-list.component.html @@ -0,0 +1,61 @@ +

+ + + Remote runners + + + + + Runner registration tokens + +

+ + + + + + Name + Description + IP + Last contact + Created + + + + + + + + + + {{ runner.name }} + + {{ runner.description }} + + {{ runner.ip }} + + {{ runner.lastContact | date: 'short' }} + + {{ runner.createdAt | date: 'short' }} + + + + + + +
+ No remote runners found. +
+ + +
+
diff --git a/client/src/app/+admin/system/runners/runner-list/runner-list.component.ts b/client/src/app/+admin/system/runners/runner-list/runner-list.component.ts new file mode 100644 index 000000000..7566f967e --- /dev/null +++ b/client/src/app/+admin/system/runners/runner-list/runner-list.component.ts @@ -0,0 +1,76 @@ +import { SortMeta } from 'primeng/api' +import { Component, OnInit } from '@angular/core' +import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' +import { DropdownAction } from '@app/shared/shared-main' +import { Runner } from '@shared/models' +import { RunnerService } from '../runner.service' + +@Component({ + selector: 'my-runner-list', + templateUrl: './runner-list.component.html' +}) +export class RunnerListComponent extends RestTable implements OnInit { + runners: Runner[] = [] + totalRecords = 0 + + sort: SortMeta = { field: 'createdAt', order: -1 } + pagination: RestPagination = { count: this.rowsPerPage, start: 0 } + + actions: DropdownAction[][] = [] + + constructor ( + private runnerService: RunnerService, + private notifier: Notifier, + private confirmService: ConfirmService + ) { + super() + } + + ngOnInit () { + this.actions = [ + [ + { + label: $localize`Remove`, + handler: runer => this.deleteRunner(runer) + } + ] + ] + + this.initialize() + } + + getIdentifier () { + return 'RunnerListComponent' + } + + async deleteRunner (runner: Runner) { + const res = await this.confirmService.confirm( + $localize`Do you really want to delete this runner? It won't be able to process jobs anymore.`, + $localize`Remove ${runner.name}` + ) + + if (res === false) return + + this.runnerService.deleteRunner(runner) + .subscribe({ + next: () => { + this.reloadData() + this.notifier.success($localize`Runner removed.`) + }, + + error: err => this.notifier.error(err.message) + }) + } + + protected reloadDataInternal () { + this.runnerService.listRunners({ pagination: this.pagination, sort: this.sort }) + .subscribe({ + next: resultList => { + this.runners = resultList.data + this.totalRecords = resultList.total + }, + + error: err => this.notifier.error(err.message) + }) + } +} diff --git a/client/src/app/+admin/system/runners/runner-registration-token-list/index.ts b/client/src/app/+admin/system/runners/runner-registration-token-list/index.ts new file mode 100644 index 000000000..8e77978b3 --- /dev/null +++ b/client/src/app/+admin/system/runners/runner-registration-token-list/index.ts @@ -0,0 +1 @@ +export * from './runner-registration-token-list.component' diff --git a/client/src/app/+admin/system/runners/runner-registration-token-list/runner-registration-token-list.component.html b/client/src/app/+admin/system/runners/runner-registration-token-list/runner-registration-token-list.component.html new file mode 100644 index 000000000..2fd23e2fc --- /dev/null +++ b/client/src/app/+admin/system/runners/runner-registration-token-list/runner-registration-token-list.component.html @@ -0,0 +1,65 @@ +

+ + + Runner registration tokens + + + +

+ + + + + + Token + Created + Associated runners + + + + +
+
+ +
+
+
+ + + + + + + + {{ registrationToken.registrationToken }} + + {{ registrationToken.createdAt | date: 'short' }} + + {{ registrationToken.registeredRunnersCount }} + + + + + + +
+ No registration token found for remote runners. +
+ + +
+
diff --git a/client/src/app/+admin/system/runners/runner-registration-token-list/runner-registration-token-list.component.ts b/client/src/app/+admin/system/runners/runner-registration-token-list/runner-registration-token-list.component.ts new file mode 100644 index 000000000..f03aab189 --- /dev/null +++ b/client/src/app/+admin/system/runners/runner-registration-token-list/runner-registration-token-list.component.ts @@ -0,0 +1,88 @@ +import { SortMeta } from 'primeng/api' +import { Component, OnInit } from '@angular/core' +import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' +import { DropdownAction } from '@app/shared/shared-main' +import { RunnerRegistrationToken } from '@shared/models' +import { RunnerService } from '../runner.service' + +@Component({ + selector: 'my-runner-registration-token-list', + templateUrl: './runner-registration-token-list.component.html' +}) +export class RunnerRegistrationTokenListComponent extends RestTable implements OnInit { + registrationTokens: RunnerRegistrationToken[] = [] + totalRecords = 0 + + sort: SortMeta = { field: 'createdAt', order: -1 } + pagination: RestPagination = { count: this.rowsPerPage, start: 0 } + + actions: DropdownAction[][] = [] + + constructor ( + private runnerService: RunnerService, + private notifier: Notifier, + private confirmService: ConfirmService + ) { + super() + } + + ngOnInit () { + this.actions = [ + [ + { + label: $localize`Remove this token`, + handler: token => this.removeToken(token) + } + ] + ] + + this.initialize() + } + + getIdentifier () { + return 'RunnerRegistrationTokenListComponent' + } + + generateToken () { + this.runnerService.generateToken() + .subscribe({ + next: () => { + this.reloadData() + this.notifier.success($localize`Registration token generated.`) + }, + + error: err => this.notifier.error(err.message) + }) + } + + async removeToken (token: RunnerRegistrationToken) { + const res = await this.confirmService.confirm( + $localize`Do you really want to remove this registration token? All associated runners will also be removed.`, + $localize`Remove registration token` + ) + + if (res === false) return + + this.runnerService.removeToken(token) + .subscribe({ + next: () => { + this.reloadData() + this.notifier.success($localize`Registration token removed.`) + }, + + error: err => this.notifier.error(err.message) + }) + } + + protected reloadDataInternal () { + this.runnerService.listRegistrationTokens({ pagination: this.pagination, sort: this.sort }) + .subscribe({ + next: resultList => { + this.registrationTokens = resultList.data + this.totalRecords = resultList.total + }, + + error: err => this.notifier.error(err.message) + }) + } +} diff --git a/client/src/app/+admin/system/runners/runner.service.ts b/client/src/app/+admin/system/runners/runner.service.ts new file mode 100644 index 000000000..05083318c --- /dev/null +++ b/client/src/app/+admin/system/runners/runner.service.ts @@ -0,0 +1,117 @@ + +import { SortMeta } from 'primeng/api' +import { catchError, forkJoin, map } from 'rxjs' +import { HttpClient, HttpParams } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { RestExtractor, RestPagination, RestService, ServerService } from '@app/core' +import { peertubeTranslate } from '@shared/core-utils' +import { ResultList } from '@shared/models/common' +import { Runner, RunnerJob, RunnerJobAdmin, RunnerRegistrationToken } from '@shared/models/runners' +import { environment } from '../../../../environments/environment' + +export type RunnerJobFormatted = RunnerJob & { + payload: string + privatePayload: string +} + +@Injectable() +export class RunnerService { + private static BASE_RUNNER_URL = environment.apiUrl + '/api/v1/runners' + + constructor ( + private authHttp: HttpClient, + private server: ServerService, + private restService: RestService, + private restExtractor: RestExtractor + ) {} + + listRegistrationTokens (options: { + pagination: RestPagination + sort: SortMeta + }) { + const { pagination, sort } = options + + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + return this.authHttp.get>(RunnerService.BASE_RUNNER_URL + '/registration-tokens', { params }) + .pipe(catchError(res => this.restExtractor.handleError(res))) + } + + generateToken () { + return this.authHttp.post(RunnerService.BASE_RUNNER_URL + '/registration-tokens/generate', {}) + .pipe(catchError(res => this.restExtractor.handleError(res))) + } + + removeToken (token: RunnerRegistrationToken) { + return this.authHttp.delete(RunnerService.BASE_RUNNER_URL + '/registration-tokens/' + token.id) + .pipe(catchError(res => this.restExtractor.handleError(res))) + } + + // --------------------------------------------------------------------------- + + listRunnerJobs (options: { + pagination: RestPagination + sort: SortMeta + search?: string + }) { + const { pagination, sort, search } = options + + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + if (search) params = params.append('search', search) + + return forkJoin([ + this.authHttp.get>(RunnerService.BASE_RUNNER_URL + '/jobs', { params }), + this.server.getServerLocale() + ]).pipe( + map(([ res, translations ]) => { + const newData = res.data.map(job => { + return { + ...job, + + state: { + id: job.state.id, + label: peertubeTranslate(job.state.label, translations) + }, + payload: JSON.stringify(job.payload, null, 2), + privatePayload: JSON.stringify(job.privatePayload, null, 2) + } as RunnerJobFormatted + }) + + return { + total: res.total, + data: newData + } + }), + map(res => this.restExtractor.convertResultListDateToHuman(res, [ 'createdAt', 'startedAt', 'finishedAt' ], 'precise')), + catchError(res => this.restExtractor.handleError(res)) + ) + } + + cancelJob (job: RunnerJob) { + return this.authHttp.post(RunnerService.BASE_RUNNER_URL + '/jobs/' + job.uuid + '/cancel', {}) + .pipe(catchError(res => this.restExtractor.handleError(res))) + } + + // --------------------------------------------------------------------------- + + listRunners (options: { + pagination: RestPagination + sort: SortMeta + }) { + const { pagination, sort } = options + + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + return this.authHttp.get>(RunnerService.BASE_RUNNER_URL + '/', { params }) + .pipe(catchError(res => this.restExtractor.handleError(res))) + } + + deleteRunner (runner: Runner) { + return this.authHttp.delete(RunnerService.BASE_RUNNER_URL + '/' + runner.id) + .pipe(catchError(res => this.restExtractor.handleError(res))) + } +} diff --git a/client/src/app/+admin/system/runners/runners.routes.ts b/client/src/app/+admin/system/runners/runners.routes.ts new file mode 100644 index 000000000..fabe687d6 --- /dev/null +++ b/client/src/app/+admin/system/runners/runners.routes.ts @@ -0,0 +1,53 @@ +import { Routes } from '@angular/router' +import { UserRightGuard } from '@app/core' +import { UserRight } from '@shared/models' +import { RunnerJobListComponent } from './runner-job-list' +import { RunnerListComponent } from './runner-list' +import { RunnerRegistrationTokenListComponent } from './runner-registration-token-list' + +export const RunnersRoutes: Routes = [ + { + path: 'runners', + canActivate: [ UserRightGuard ], + data: { + userRight: UserRight.MANAGE_RUNNERS + }, + children: [ + { + path: '', + redirectTo: 'jobs-list', + pathMatch: 'full' + }, + + { + path: 'jobs-list', + component: RunnerJobListComponent, + data: { + meta: { + title: $localize`List runner jobs` + } + } + }, + + { + path: 'runners-list', + component: RunnerListComponent, + data: { + meta: { + title: $localize`List remote runners` + } + } + }, + + { + path: 'registration-tokens-list', + component: RunnerRegistrationTokenListComponent, + data: { + meta: { + title: $localize`List registration runner tokens` + } + } + } + ] + } +] diff --git a/client/src/app/+admin/system/system.routes.ts b/client/src/app/+admin/system/system.routes.ts index d180aa3b9..87e4b25b3 100644 --- a/client/src/app/+admin/system/system.routes.ts +++ b/client/src/app/+admin/system/system.routes.ts @@ -4,6 +4,7 @@ import { UserRight } from '@shared/models' import { DebugComponent } from './debug' import { JobsComponent } from './jobs/jobs.component' import { LogsComponent } from './logs' +import { RunnersRoutes } from './runners' export const SystemRoutes: Routes = [ { @@ -46,7 +47,9 @@ export const SystemRoutes: Routes = [ title: $localize`Debug` } } - } + }, + + ...RunnersRoutes ] } ] -- cgit v1.2.3