]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
add display of logs matching any state
authorRigel Kent <sendmemail@rigelk.eu>
Sun, 13 Dec 2020 18:27:25 +0000 (19:27 +0100)
committerRigel Kent <sendmemail@rigelk.eu>
Sun, 13 Dec 2020 18:27:25 +0000 (19:27 +0100)
client/src/app/+admin/system/jobs/job.service.ts
client/src/app/+admin/system/jobs/jobs.component.html
client/src/app/+admin/system/jobs/jobs.component.ts
server/controllers/api/jobs.ts
server/helpers/custom-validators/jobs.ts
server/lib/job-queue/job-queue.ts
server/middlewares/validators/jobs.ts
support/doc/api/openapi.yaml

index 1ac50f050f9f3f39ef5cca0ed965f1d74a7e4f58..4b4a8914fdd5543776351be93853e790ffc5624a 100644 (file)
@@ -19,13 +19,20 @@ export class JobService {
     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' ])
index e06156a9e35db4eeb8980a8fbfde065521776a1b..2d60e7b9ede0fdf2d97517c801e8583060325e6f 100644 (file)
@@ -17,6 +17,9 @@
       [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>
index f8e12d1b6108ed2966391832d83ac5fd45251880..b7f18067b807e230b18d9b5ab065ee9a80739794 100644 (file)
@@ -16,7 +16,7 @@ export class JobsComponent extends RestTable implements OnInit {
   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'
@@ -73,6 +73,10 @@ export class JobsComponent extends RestTable implements OnInit {
     }
   }
 
+  getColspan () {
+    return this.jobState === 'all' ? 5 : 4
+  }
+
   onJobStateOrTypeChanged () {
     this.pagination.start = 0
 
@@ -81,8 +85,16 @@ export class JobsComponent extends RestTable implements OnInit {
   }
 
   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
index ed6c945335cebb7f7894aaeb3bd79e82a5045c1b..1131a44d605800da9cc41b571bfb4f6fa19b8f56 100644 (file)
@@ -12,11 +12,23 @@ import {
   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),
@@ -25,6 +37,7 @@ jobsRouter.get('/:state',
   setDefaultSort,
   setDefaultPagination,
   listJobsValidator,
+  listJobsStateValidator,
   asyncMiddleware(listJobs)
 )
 
@@ -37,7 +50,7 @@ export {
 // ---------------------------------------------------------------------------
 
 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
 
@@ -52,7 +65,11 @@ async function listJobs (req: express.Request, res: express.Response) {
 
   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)
 }
index dd33e85a39a4d28c64054ec9996ac157dec8942b..72dc73ee442703fb39fa96ae5626345a6b283549 100644 (file)
@@ -15,6 +15,7 @@ function isValidJobType (value: any) {
 // ---------------------------------------------------------------------------
 
 export {
+  jobStates,
   isValidJobState,
   isValidJobType
 }
index 8d97434ac59b954d33510dd32780a051516ec955..49f06584dcda093b69eb5e365ce10af8822c8324 100644 (file)
@@ -154,13 +154,13 @@ class JobQueue {
   }
 
   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)
@@ -172,7 +172,7 @@ class JobQueue {
         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)
     }
 
@@ -188,7 +188,8 @@ class JobQueue {
     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)
@@ -202,7 +203,9 @@ class JobQueue {
 
       const counts = await queue.getJobCounts()
 
-      total += counts[state]
+      for (const s of states) {
+        total += counts[s]
+      }
     }
 
     return total
index b57615dbcb48e1261513e40fa372819b0bbd02bc..0fc183c1af9267b4c05d9c7ea6a40c11c65b572e 100644 (file)
@@ -5,8 +5,6 @@ import { logger } from '../../helpers/logger'
 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'),
@@ -20,8 +18,22 @@ const listJobsValidator = [
   }
 ]
 
+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
 }
index ba420b4a96fc8297ed8827e2824ded1b445025b5..c06bffb0a1ac550a84018520492a0565d4f72600 100644 (file)
@@ -356,15 +356,17 @@ paths:
         - 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'
@@ -3780,6 +3782,26 @@ components:
       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: >