Implement runner in client side

pull/5593/head
Chocobozzz 2023-04-21 15:04:52 +02:00 committed by Chocobozzz
parent e592df48c7
commit 118626c875
23 changed files with 750 additions and 11 deletions

View File

@ -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
}
}

View File

@ -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 { }

View File

@ -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: {

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -1,4 +1,5 @@
export * from './debug'
export * from './jobs'
export * from './logs'
export * from './runners'
export * from './system.routes'

View 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'

View File

@ -0,0 +1 @@
export * from './runner-job-list.component'

View File

@ -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>

View File

@ -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)
})
}
}

View File

@ -0,0 +1 @@
export * from './runner-list.component'

View File

@ -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>

View File

@ -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)
})
}
}

View File

@ -0,0 +1 @@
export * from './runner-registration-token-list.component'

View File

@ -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>

View File

@ -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)
})
}
}

View 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)))
}
}

View 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`
}
}
}
]
}
]

View File

@ -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
]
}
]

View File

@ -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]

View File

@ -35,3 +35,7 @@
.peertube-radio-container {
@include peertube-radio-container;
}
.peertube-button-icon {
@include button-with-icon(18px, 3px, -1px);
}

View File

@ -6,3 +6,7 @@
.fs-5-5 {
@include font-size(18px);
}
.fs-7 {
@include font-size(14px);
}