Implement runner in client side
This commit is contained in:
parent
e592df48c7
commit
118626c875
23 changed files with 750 additions and 11 deletions
|
@ -1,5 +1,5 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
import { AuthService, ScreenService } from '@app/core'
|
||||
import { AuthService, ScreenService, ServerService } from '@app/core'
|
||||
import { ListOverflowItem } from '@app/shared/shared-main'
|
||||
import { TopMenuDropdownParam } from '@app/shared/shared-main/misc/top-menu-dropdown.component'
|
||||
import { UserRight } from '@shared/models'
|
||||
|
@ -14,7 +14,8 @@ export class AdminComponent implements OnInit {
|
|||
|
||||
constructor (
|
||||
private auth: AuthService,
|
||||
private screen: ScreenService
|
||||
private screen: ScreenService,
|
||||
private server: ServerService
|
||||
) { }
|
||||
|
||||
get isBroadcastMessageDisplayed () {
|
||||
|
@ -22,6 +23,14 @@ export class AdminComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.server.configReloaded.subscribe(() => this.buildMenu())
|
||||
|
||||
this.buildMenu()
|
||||
}
|
||||
|
||||
private buildMenu () {
|
||||
this.menuEntries = []
|
||||
|
||||
this.buildOverviewItems()
|
||||
this.buildFederationItems()
|
||||
this.buildModerationItems()
|
||||
|
@ -157,9 +166,23 @@ export class AdminComponent implements OnInit {
|
|||
children: []
|
||||
}
|
||||
|
||||
if (this.isRemoteRunnersEnabled() && this.hasRunnersRight()) {
|
||||
systemItems.children.push({
|
||||
label: $localize`Remote runners`,
|
||||
iconName: 'codesandbox',
|
||||
routerLink: '/admin/system/runners/runners-list'
|
||||
})
|
||||
|
||||
systemItems.children.push({
|
||||
label: $localize`Runner jobs`,
|
||||
iconName: 'globe',
|
||||
routerLink: '/admin/system/runners/jobs-list'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasJobsRight()) {
|
||||
systemItems.children.push({
|
||||
label: $localize`Jobs`,
|
||||
label: $localize`Local jobs`,
|
||||
iconName: 'circle-tick',
|
||||
routerLink: '/admin/system/jobs'
|
||||
})
|
||||
|
@ -226,6 +249,10 @@ export class AdminComponent implements OnInit {
|
|||
return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS)
|
||||
}
|
||||
|
||||
private hasRunnersRight () {
|
||||
return this.auth.getUser().hasRight(UserRight.MANAGE_RUNNERS)
|
||||
}
|
||||
|
||||
private hasDebugRight () {
|
||||
return this.auth.getUser().hasRight(UserRight.MANAGE_DEBUG)
|
||||
}
|
||||
|
@ -241,4 +268,10 @@ export class AdminComponent implements OnInit {
|
|||
private hasRegistrationsRight () {
|
||||
return this.auth.getUser().hasRight(UserRight.MANAGE_REGISTRATIONS)
|
||||
}
|
||||
|
||||
private isRemoteRunnersEnabled () {
|
||||
const config = this.server.getHTMLConfig()
|
||||
|
||||
return config.transcoding.remoteRunners.enabled || config.live.transcoding.remoteRunners.enabled
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,9 +56,17 @@ import {
|
|||
PluginShowInstalledComponent
|
||||
} from './plugins'
|
||||
import { SharedAdminModule } from './shared'
|
||||
import { JobService, LogsComponent, LogsService } from './system'
|
||||
import {
|
||||
JobService,
|
||||
LogsComponent,
|
||||
LogsService,
|
||||
RunnerJobListComponent,
|
||||
RunnerListComponent,
|
||||
RunnerRegistrationTokenListComponent,
|
||||
RunnerService
|
||||
} from './system'
|
||||
import { DebugComponent, DebugService } from './system/debug'
|
||||
import { JobsComponent } from './system/jobs/jobs.component'
|
||||
import { JobsComponent } from './system/jobs'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -125,7 +133,11 @@ import { JobsComponent } from './system/jobs/jobs.component'
|
|||
EditHomepageComponent,
|
||||
|
||||
RegistrationListComponent,
|
||||
ProcessRegistrationModalComponent
|
||||
ProcessRegistrationModalComponent,
|
||||
|
||||
RunnerRegistrationTokenListComponent,
|
||||
RunnerListComponent,
|
||||
RunnerJobListComponent
|
||||
],
|
||||
|
||||
exports: [
|
||||
|
@ -140,7 +152,8 @@ import { JobsComponent } from './system/jobs/jobs.component'
|
|||
PluginApiService,
|
||||
EditConfigurationService,
|
||||
VideoAdminService,
|
||||
AdminRegistrationService
|
||||
AdminRegistrationService,
|
||||
RunnerService
|
||||
]
|
||||
})
|
||||
export class AdminModule { }
|
||||
|
|
|
@ -190,6 +190,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
},
|
||||
webtorrent: {
|
||||
enabled: null
|
||||
},
|
||||
remoteRunners: {
|
||||
enabled: null
|
||||
}
|
||||
},
|
||||
live: {
|
||||
|
@ -208,7 +211,10 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
threads: TRANSCODING_THREADS_VALIDATOR,
|
||||
profile: null,
|
||||
resolutions: {},
|
||||
alwaysTranscodeOriginalResolution: null
|
||||
alwaysTranscodeOriginalResolution: null,
|
||||
remoteRunners: {
|
||||
enabled: null
|
||||
}
|
||||
}
|
||||
},
|
||||
videoStudio: {
|
||||
|
|
|
@ -110,6 +110,20 @@
|
|||
</my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="form-group" formGroupName="remoteRunners" [ngClass]="getDisabledLiveTranscodingClass()">
|
||||
<my-peertube-checkbox
|
||||
inputName="transcodingRemoteRunnersEnabled" formControlName="enabled"
|
||||
i18n-labelText labelText="Enable remote runners"
|
||||
>
|
||||
<ng-container ngProjectAs="description">
|
||||
<span i18n>
|
||||
Use <a routerLink="/admin/system/runners/runners-list">remote runners</a> to process live transcoding.
|
||||
Remote runners has to register on your instance first.
|
||||
</span>
|
||||
</ng-container>
|
||||
</my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="form-group" [ngClass]="getDisabledLiveTranscodingClass()">
|
||||
<label i18n for="liveTranscodingThreads">Live resolutions to generate</label>
|
||||
|
||||
|
|
|
@ -37,6 +37,20 @@
|
|||
|
||||
<ng-container ngProjectAs="extra">
|
||||
|
||||
<div class="form-group" formGroupName="remoteRunners" [ngClass]="getTranscodingDisabledClass()">
|
||||
<my-peertube-checkbox
|
||||
inputName="transcodingRemoteRunnersEnabled" formControlName="enabled"
|
||||
i18n-labelText labelText="Enable remote runners"
|
||||
>
|
||||
<ng-container ngProjectAs="description">
|
||||
<span i18n>
|
||||
Use <a routerLink="/admin/system/runners/runners-list">remote runners</a> to process VOD transcoding.
|
||||
Remote runners has to register on your instance first.
|
||||
</span>
|
||||
</ng-container>
|
||||
</my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="callout callout-light pt-2 pb-0">
|
||||
<label i18n>Input formats</label>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<p-table
|
||||
[value]="following" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
|
||||
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
|
||||
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
||||
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} hosts"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export * from './debug'
|
||||
export * from './jobs'
|
||||
export * from './logs'
|
||||
export * from './runners'
|
||||
export * from './system.routes'
|
||||
|
|
5
client/src/app/+admin/system/runners/index.ts
Normal file
5
client/src/app/+admin/system/runners/index.ts
Normal file
|
@ -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'
|
|
@ -0,0 +1 @@
|
|||
export * from './runner-job-list.component'
|
|
@ -0,0 +1,101 @@
|
|||
<h1 class="d-flex justify-content-between">
|
||||
<span class="text-nowrap me-2">
|
||||
<my-global-icon iconName="globe" aria-hidden="true"></my-global-icon>
|
||||
<span i18n>Runner jobs</span>
|
||||
</span>
|
||||
|
||||
<a routerLink="/admin/system/runners/runners-list" class="peertube-button-link peertube-button-icon grey-button">
|
||||
<my-global-icon iconName="codesandbox" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Remote runners</ng-container>
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
<p-table
|
||||
[value]="runnerJobs" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
|
||||
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
|
||||
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
||||
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} runner jobs"
|
||||
[expandedRowKeys]="expandedRows" dataKey="uuid"
|
||||
>
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th style="width: 40px"></th>
|
||||
<th style="width: 120px;"></th>
|
||||
<th i18n>UUID</th>
|
||||
<th i18n>Type</th>
|
||||
<th i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
|
||||
<th style="width: 100px" i18n pSortableColumn="priority">Priority <p-sortIcon field="priority"></p-sortIcon></th>
|
||||
<th style="width: 100px" i18n pSortableColumn="progress">Progress <p-sortIcon field="progress"></p-sortIcon></th>
|
||||
<th i18n>Runner</th>
|
||||
<th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="caption">
|
||||
<div class="caption">
|
||||
<div class="ms-auto d-flex">
|
||||
<my-advanced-input-filter class="me-2" (search)="onSearch($event)"></my-advanced-input-filter>
|
||||
|
||||
<my-button i18n-label label="Refresh" icon="refresh" (click)="reloadData()"></my-button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="body" let-expanded="expanded" let-runnerJob>
|
||||
<tr>
|
||||
<td class="expand-cell" [pRowToggler]="runnerJob">
|
||||
<my-table-expander-icon [expanded]="expanded"></my-table-expander-icon>
|
||||
</td>
|
||||
|
||||
<td class="action-cell">
|
||||
<my-action-dropdown
|
||||
placement="bottom-right top-right left auto" container="body"
|
||||
i18n-label label="Actions" [actions]="actions" [entry]="runnerJob"
|
||||
></my-action-dropdown>
|
||||
</td>
|
||||
|
||||
<td>{{ runnerJob.uuid }}</td>
|
||||
<td>{{ runnerJob.type }}</td>
|
||||
<td>{{ runnerJob.state.label }}</td>
|
||||
<td>{{ runnerJob.priority }}</td>
|
||||
<td>{{ runnerJob.progress }}</td>
|
||||
<td>{{ runnerJob.runner?.name }}</td>
|
||||
<td>{{ runnerJob.createdAt | date: 'short' }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="rowexpansion" let-runnerJob>
|
||||
<tr>
|
||||
<td colspan="9">
|
||||
<div class="mt-2 fs-7 font-monospace">
|
||||
Parent job: {{ runnerJob.parent?.uuid || '-' }} <br />
|
||||
Processed on {{ (runnerJob.startedAt || '-') }} <br />
|
||||
Finished on {{ (runnerJob.finishedAt || '-') }} <br />
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<strong i18n>Payload:</strong>
|
||||
<pre>{{ runnerJob.payload }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<strong i18n>Private payload:</strong>
|
||||
<pre>{{ runnerJob.privatePayload }}</pre>
|
||||
</div>
|
||||
|
||||
<pre *ngIf="runnerJob.error" class=".text-danger" >{{ runnerJob.error }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr>
|
||||
<td colspan="9">
|
||||
<div class="no-results">
|
||||
<ng-container i18n>No runner jobs found.</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
|
@ -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 <RunnerJob> implements OnInit {
|
||||
runnerJobs: RunnerJobFormatted[] = []
|
||||
totalRecords = 0
|
||||
|
||||
sort: SortMeta = { field: 'createdAt', order: -1 }
|
||||
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||
|
||||
actions: DropdownAction<RunnerJob>[][] = []
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './runner-list.component'
|
|
@ -0,0 +1,61 @@
|
|||
<h1 class="d-flex justify-content-between">
|
||||
<span class="text-nowrap me-2">
|
||||
<my-global-icon iconName="codesandbox" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Remote runners</ng-container>
|
||||
</span>
|
||||
|
||||
<a routerLink="/admin/system/runners/registration-tokens-list" class="peertube-button-link peertube-button-icon grey-button">
|
||||
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Runner registration tokens</ng-container>
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
<p-table
|
||||
[value]="runners" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
|
||||
[lazy]="true" (onLazyLoad)="loadLazy($event)"
|
||||
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
||||
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} remote runners"
|
||||
>
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th style="width: 120px;"></th>
|
||||
<th i18n>Name</th>
|
||||
<th i18n>Description</th>
|
||||
<th i18n>IP</th>
|
||||
<th i18n>Last contact</th>
|
||||
<th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="body" let-runner>
|
||||
<tr>
|
||||
<td class="action-cell">
|
||||
<my-action-dropdown
|
||||
placement="bottom-right top-right left auto" container="body"
|
||||
i18n-label label="Actions" [actions]="actions" [entry]="runner"
|
||||
></my-action-dropdown>
|
||||
</td>
|
||||
|
||||
<td>{{ runner.name }}</td>
|
||||
|
||||
<td>{{ runner.description }}</td>
|
||||
|
||||
<td>{{ runner.ip }}</td>
|
||||
|
||||
<td>{{ runner.lastContact | date: 'short' }}</td>
|
||||
|
||||
<td>{{ runner.createdAt | date: 'short' }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<div class="no-results">
|
||||
<ng-container i18n>No remote runners found.</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
|
@ -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 <Runner> implements OnInit {
|
||||
runners: Runner[] = []
|
||||
totalRecords = 0
|
||||
|
||||
sort: SortMeta = { field: 'createdAt', order: -1 }
|
||||
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||
|
||||
actions: DropdownAction<Runner>[][] = []
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './runner-registration-token-list.component'
|
|
@ -0,0 +1,65 @@
|
|||
<h1 class="d-flex justify-content-between">
|
||||
<span class="text-nowrap me-2">
|
||||
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Runner registration tokens</ng-container>
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<a routerLink="/admin/system/runners/runners-list" class="peertube-button-link peertube-button-icon grey-button">
|
||||
<my-global-icon iconName="codesandbox" aria-hidden="true"></my-global-icon>
|
||||
<ng-container i18n>Remote runners</ng-container>
|
||||
</a>
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<p-table
|
||||
[value]="registrationTokens" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
|
||||
[lazy]="true" (onLazyLoad)="loadLazy($event)"
|
||||
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
|
||||
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} registration tokens"
|
||||
>
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th style="width: 120px;"></th>
|
||||
<th i18n>Token</th>
|
||||
<th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
|
||||
<th style="width: 160px;" i18n>Associated runners</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="caption">
|
||||
<div class="caption">
|
||||
<div class="left-buttons">
|
||||
<my-button className="orange-button" i18n-label label="Generate token" icon="add" (click)="generateToken()"></my-button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="body" let-registrationToken>
|
||||
<tr>
|
||||
<td class="action-cell">
|
||||
<my-action-dropdown
|
||||
placement="bottom-right top-right left auto" container="body"
|
||||
i18n-label label="Actions" [actions]="actions" [entry]="registrationToken"
|
||||
></my-action-dropdown>
|
||||
</td>
|
||||
|
||||
<td>{{ registrationToken.registrationToken }}</td>
|
||||
|
||||
<td>{{ registrationToken.createdAt | date: 'short' }}</td>
|
||||
|
||||
<td>{{ registrationToken.registeredRunnersCount }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<div class="no-results">
|
||||
<ng-container i18n>No registration token found for remote runners.</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
|
@ -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 <RunnerRegistrationToken> implements OnInit {
|
||||
registrationTokens: RunnerRegistrationToken[] = []
|
||||
totalRecords = 0
|
||||
|
||||
sort: SortMeta = { field: 'createdAt', order: -1 }
|
||||
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||
|
||||
actions: DropdownAction<RunnerRegistrationToken>[][] = []
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
117
client/src/app/+admin/system/runners/runner.service.ts
Normal file
117
client/src/app/+admin/system/runners/runner.service.ts
Normal file
|
@ -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<ResultList<RunnerRegistrationToken>>(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<ResultList<RunnerJobAdmin>>(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<ResultList<Runner>>(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)))
|
||||
}
|
||||
}
|
53
client/src/app/+admin/system/runners/runners.routes.ts
Normal file
53
client/src/app/+admin/system/runners/runners.routes.ts
Normal file
|
@ -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`
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -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
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -43,7 +43,9 @@ export class LiveStreamInformationComponent {
|
|||
[LiveVideoError.BLACKLISTED]: $localize`Live blacklisted`,
|
||||
[LiveVideoError.DURATION_EXCEEDED]: $localize`Max duration exceeded`,
|
||||
[LiveVideoError.FFMPEG_ERROR]: $localize`Server error`,
|
||||
[LiveVideoError.QUOTA_EXCEEDED]: $localize`Quota exceeded`
|
||||
[LiveVideoError.QUOTA_EXCEEDED]: $localize`Quota exceeded`,
|
||||
[LiveVideoError.RUNNER_JOB_CANCEL]: $localize`Runner job cancelled`,
|
||||
[LiveVideoError.RUNNER_JOB_ERROR]: $localize`Error in runner job`
|
||||
}
|
||||
|
||||
return errors[session.error]
|
||||
|
|
|
@ -35,3 +35,7 @@
|
|||
.peertube-radio-container {
|
||||
@include peertube-radio-container;
|
||||
}
|
||||
|
||||
.peertube-button-icon {
|
||||
@include button-with-icon(18px, 3px, -1px);
|
||||
}
|
||||
|
|
|
@ -6,3 +6,7 @@
|
|||
.fs-5-5 {
|
||||
@include font-size(18px);
|
||||
}
|
||||
|
||||
.fs-7 {
|
||||
@include font-size(14px);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue