private restExtractor: RestExtractor
) {}
- getJobs (jobState: JobStateClient, jobType: JobTypeClient, pagination: RestPagination, sort: SortMeta): Observable<ResultList<Job>> {
+ getJobs (options: {
+ jobState?: JobStateClient,
+ jobType: JobTypeClient,
+ pagination: RestPagination,
+ sort: SortMeta
+ }): Observable<ResultList<Job>> {
+ const { jobState, jobType, pagination, sort } = options
+
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
if (jobType !== 'all') params = params.append('jobType', jobType)
- return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL + '/' + jobState, { params })
+ return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL + `/${jobState ? jobState : ''}`, { params })
.pipe(
map(res => {
return this.restExtractor.convertResultListDateToHuman(res, [ 'createdAt', 'processedOn', 'finishedOn' ])
[clearable]="false"
[searchable]="false"
>
+ <ng-option value="all">
+ <span i18n="Selector for the list displaying jobs, filtering by their state">any</span>
+ </ng-option>
<ng-option *ngFor="let state of jobStates" [value]="state">
<span class="badge" [ngClass]="getJobStateClass(state)">{{ state }}</span>
</ng-option>
<th style="width: 40px"></th>
<th style="width: calc(100% - 390px)" class="job-id" i18n>ID</th>
<th style="width: 200px" class="job-type" i18n>Type</th>
+ <th style="width: 200px" class="job-type" i18n *ngIf="jobState === 'all'">State</th>
<th style="width: 150px" class="job-date" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-expanded="expanded" let-job>
<tr>
- <td class="expand-cell" [pRowToggler]="job" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
+ <td class="expand-cell c-hand" [pRowToggler]="job" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
<span class="expander">
<i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
</span>
</td>
- <td class="job-id" [pRowToggler]="job" [title]="job.id">{{ job.id }}</td>
- <td class="job-type" [pRowToggler]="job">{{ job.type }}</td>
- <td class="job-date" [pRowToggler]="job">{{ job.createdAt | date: 'short' }}</td>
+ <td class="job-id c-hand" [pRowToggler]="job" [title]="job.id">{{ job.id }}</td>
+ <td class="job-type c-hand" [pRowToggler]="job">{{ job.type }}</td>
+ <td class="job-type c-hand" [pRowToggler]="job" *ngIf="jobState === 'all'">
+ <span class="badge" [ngClass]="getJobStateClass(job.state)">{{ job.state }}</span>
+ </td>
+ <td class="job-date c-hand" [pRowToggler]="job">{{ job.createdAt | date: 'short' }}</td>
</tr>
</ng-template>
<ng-template pTemplate="rowexpansion" let-job>
<tr>
- <td colspan="4">
+ <td [attr.colspan]="getColspan()">
<pre>{{ [
'Job: ' + job.id,
'Type: ' + job.type,
</td>
</tr>
<tr>
- <td colspan="4">
+ <td [attr.colspan]="getColspan()">
<pre>{{ job.data }}</pre>
</td>
</tr>
<tr class="job-error" *ngIf="job.error">
- <td colspan="4">
+ <td [attr.colspan]="getColspan()">
<pre>{{ job.error }}</pre>
</td>
</tr>
<ng-template pTemplate="emptymessage">
<tr>
- <td colspan="4">
+ <td [attr.colspan]="getColspan()">
<div class="no-results">
<div class="d-block">
- <ng-container *ngIf="jobType === 'all'" i18n>No <span class="badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span> jobs found.</ng-container>
- <ng-container *ngIf="jobType !== 'all'" i18n>No <code>{{ jobType }}</code> jobs found that are <span class="badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span>.</ng-container>
+ <ng-container *ngIf="jobState === 'all'">
+ <ng-container *ngIf="jobType === 'all'" i18n>No jobs found.</ng-container>
+ <ng-container *ngIf="jobType !== 'all'" i18n>No <code>{{ jobType }}</code> jobs found.</ng-container>
+ </ng-container>
+ <ng-container *ngIf="jobState !== 'all'">
+ <ng-container *ngIf="jobType === 'all'" i18n>No <span class="badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span> jobs found.</ng-container>
+ <ng-container *ngIf="jobType !== 'all'" i18n>No <code>{{ jobType }}</code> jobs found that are <span class="badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span>.</ng-container>
+ </ng-container>
</div>
</div>
</td>
private static LOCAL_STORAGE_STATE = 'jobs-list-state'
private static LOCAL_STORAGE_TYPE = 'jobs-list-type'
- jobState: JobStateClient = 'waiting'
+ jobState?: JobStateClient | 'all'
jobStates: JobStateClient[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed' ]
jobType: JobTypeClient = 'all'
}
}
+ getColspan () {
+ return this.jobState === 'all' ? 5 : 4
+ }
+
onJobStateOrTypeChanged () {
this.pagination.start = 0
}
protected loadData () {
+ let jobState = this.jobState as JobState
+ if (this.jobState === 'all') jobState = null
+
this.jobsService
- .getJobs(this.jobState, this.jobType, this.pagination, this.sort)
+ .getJobs({
+ jobState,
+ jobType: this.jobType,
+ pagination: this.pagination,
+ sort: this.sort
+ })
.subscribe(
resultList => {
this.jobs = resultList.data
setDefaultSort
} from '../../middlewares'
import { paginationValidator } from '../../middlewares/validators'
-import { listJobsValidator } from '../../middlewares/validators/jobs'
+import { listJobsStateValidator, listJobsValidator } from '../../middlewares/validators/jobs'
import { isArray } from '../../helpers/custom-validators/misc'
+import { jobStates } from '@server/helpers/custom-validators/jobs'
const jobsRouter = express.Router()
+jobsRouter.get('/',
+ authenticate,
+ ensureUserHasRight(UserRight.MANAGE_JOBS),
+ paginationValidator,
+ jobsSortValidator,
+ setDefaultSort,
+ setDefaultPagination,
+ listJobsValidator,
+ asyncMiddleware(listJobs)
+)
+
jobsRouter.get('/:state',
authenticate,
ensureUserHasRight(UserRight.MANAGE_JOBS),
setDefaultSort,
setDefaultPagination,
listJobsValidator,
+ listJobsStateValidator,
asyncMiddleware(listJobs)
)
// ---------------------------------------------------------------------------
async function listJobs (req: express.Request, res: express.Response) {
- const state = req.params.state as JobState
+ const state = req.params.state as JobState || jobStates
const asc = req.query.sort === 'createdAt'
const jobType = req.query.jobType
const result: ResultList<Job> = {
total,
- data: jobs.map(j => formatJob(j, state))
+ data: Array.isArray(state)
+ ? await Promise.all(
+ jobs.map(async j => formatJob(j, await j.getState() as JobState))
+ )
+ : jobs.map(j => formatJob(j, state))
}
return res.json(result)
}
// ---------------------------------------------------------------------------
export {
+ jobStates,
isValidJobState,
isValidJobType
}
}
async listForApi (options: {
- state: JobState
+ state: JobState | JobState[]
start: number
count: number
asc?: boolean
jobType: JobType
}): Promise<Bull.Job[]> {
- const { state, start, count, asc, jobType } = options
+ const { state = Array.isArray(options.state) ? options.state : [ options.state ], start, count, asc, jobType } = options
let results: Bull.Job[] = []
const filteredJobTypes = this.filterJobTypes(jobType)
continue
}
- const jobs = await queue.getJobs([ state ], 0, start + count, asc)
+ const jobs = await queue.getJobs(state as Bull.JobStatus[], 0, start + count, asc)
results = results.concat(jobs)
}
return results.slice(start, start + count)
}
- async count (state: JobState, jobType?: JobType): Promise<number> {
+ async count (state: JobState | JobState[], jobType?: JobType): Promise<number> {
+ const states = Array.isArray(state) ? state : [ state ]
let total = 0
const filteredJobTypes = this.filterJobTypes(jobType)
const counts = await queue.getJobCounts()
- total += counts[state]
+ for (const s of states) {
+ total += counts[s]
+ }
}
return total
import { areValidationErrors } from './utils'
const listJobsValidator = [
- param('state')
- .custom(isValidJobState).not().isEmpty().withMessage('Should have a valid job state'),
query('jobType')
.optional()
.custom(isValidJobType).withMessage('Should have a valid job state'),
}
]
+const listJobsStateValidator = [
+ param('state')
+ .custom(isValidJobState).not().isEmpty().withMessage('Should have a valid job state'),
+
+ (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking listJobsValidator parameters.', { parameters: req.params })
+
+ if (areValidationErrors(req, res)) return
+
+ return next()
+ }
+]
+
// ---------------------------------------------------------------------------
export {
- listJobsValidator
+ listJobsValidator,
+ listJobsStateValidator
}
- name: state
in: path
required: true
- description: The state of the job
+ description: The state of the job ('' for for no filter)
schema:
type: string
enum:
+ - ''
- active
- completed
- failed
- waiting
- delayed
+ - $ref: '#/components/parameters/jobType'
- $ref: '#/components/parameters/start'
- $ref: '#/components/parameters/count'
- $ref: '#/components/parameters/sort'
schema:
type: string
example: peertube-plugin-auth-ldap
+ jobType:
+ name: jobType
+ in: query
+ required: false
+ description: job type
+ schema:
+ type: string
+ enum:
+ - activitypub-follow
+ - activitypub-http-broadcast
+ - activitypub-http-fetcher
+ - activitypub-http-unicast
+ - email
+ - video-transcoding
+ - video-file-import
+ - video-import
+ - videos-views
+ - activitypub-refresher
+ - video-redundancy
+ - video-live-ending
securitySchemes:
OAuth2:
description: >