From 9f127d797042e036c5731184241a19ed00c904da Mon Sep 17 00:00:00 2001 From: rob Date: Wed, 8 Jun 2022 17:57:27 -0400 Subject: [PATCH] integration of updates from Venue/Soapbox --- app/controllers/admin/job-queue.js | 56 +++++++++++++++++-- app/views/admin/job-queue/job-view.pug | 76 ++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 app/views/admin/job-queue/job-view.pug diff --git a/app/controllers/admin/job-queue.js b/app/controllers/admin/job-queue.js index 7d25dac..1074b86 100644 --- a/app/controllers/admin/job-queue.js +++ b/app/controllers/admin/job-queue.js @@ -1,13 +1,13 @@ -// admin/job-queue.js +// admin/domain.js // Copyright (C) 2022 DTP Technologies, LLC -// License: Apache-2.0 +// All Rights Reserved 'use strict'; const DTP_COMPONENT_NAME = 'admin:job-queue'; const express = require('express'); -const { /*SiteError,*/ SiteController } = require('../../../lib/site-lib'); +const { SiteController, SiteError } = require('../../../lib/site-lib'); class JobQueueController extends SiteController { @@ -24,7 +24,11 @@ class JobQueueController extends SiteController { }); router.param('jobQueueName', this.populateJobQueueName.bind(this)); + router.param('jobId', this.populateJob.bind(this)); + router.post('/:jobQueueName/:jobId/action', this.postJobAction.bind(this)); + + router.get('/:jobQueueName/:jobId', this.getJobView.bind(this)); router.get('/:jobQueueName', this.getJobQueueView.bind(this)); router.get('/', this.getHomeView.bind(this)); @@ -36,6 +40,9 @@ class JobQueueController extends SiteController { try { res.locals.queueName = jobQueueName; res.locals.queue = await jobQueueService.getJobQueue(jobQueueName); + if (!res.locals.queue) { + throw new SiteError(404, 'Job queue not found'); + } return next(); } catch (error) { this.log.error('failed to populate job queue', { jobQueueName, error }); @@ -43,6 +50,46 @@ class JobQueueController extends SiteController { } } + async populateJob (req, res, next, jobId) { + try { + res.locals.job = await res.locals.queue.getJob(jobId); + if (!res.locals.job) { + throw new SiteError(404, 'Job not found'); + } + return next(); + } catch (error) { + this.log.error('failed to populate job', { jobId, error }); + return next(error); + } + } + + async postJobAction (req, res) { + try { + await res.locals.job[req.body.action](); + res.status(200).json({ success: true }); + } catch (error) { + this.log.error('failed to execute job action', { + jobId: res.locals.job.id, + action: req.body.action, + error, + }); + res.status(error.statusCode || 500).json({ + success: false, + message: error.message, + }); + } + } + + async getJobView (req, res, next) { + try { + res.locals.jobLogs = await res.locals.queue.getJobLogs(res.locals.job.id); + res.render('admin/job-queue/job-view'); + } catch (error) { + this.log.error('failed to render job view', { error }); + return next(error); + } + } + async getJobQueueView (req, res, next) { try { res.locals.jobCounts = await res.locals.queue.getJobCounts(); @@ -62,7 +109,8 @@ class JobQueueController extends SiteController { async getHomeView (req, res, next) { const { jobQueue: jobQueueService } = this.dtp.services; try { - res.locals.queues = await jobQueueService.discoverJobQueues('soapy:*:id'); + const prefix = process.env.REDIS_KEY_PREFIX || 'dtp'; + res.locals.queues = await jobQueueService.discoverJobQueues(`${prefix}:*:id`); res.render('admin/job-queue/index'); } catch (error) { this.log.error('failed to populate job queues view', { error }); diff --git a/app/views/admin/job-queue/job-view.pug b/app/views/admin/job-queue/job-view.pug new file mode 100644 index 0000000..65bd061 --- /dev/null +++ b/app/views/admin/job-queue/job-view.pug @@ -0,0 +1,76 @@ +extends ../layouts/main +block content + + .uk-margin + .uk-card.uk-card-secondary.uk-card-small + .uk-card-header + h1.uk-card-title #{job.name} id: #{job.id} + + .uk-card-body + .uk-margin + progress(value= job.progress, max= 100).uk-progress + .uk-margin + div(uk-grid) + .uk-width-auto attempt: #{job.attemptsMade} + .uk-width-auto attempts: #{job.opts.attempts} + .uk-width-auto ts: #{moment(job.timestamp).format('YYYY-MM-DD hh:mm:ss a')} + .uk-width-auto proc: #{moment(job.processedOn).format('YYYY-MM-DD hh:mm:ss a')} + + if job.finishedOn + .uk-width-auto fin: #{job.finishedOn} + + if job.delay > 0 + .uk-width-auto delay: #{job.delay} + + if job.opts.removeOnComplete + .uk-width-auto.uk-text-success remove on complete + + if job.opts.removeOnFail + .uk-width-auto.uk-text-danger remove on fail + + if job.data + h4 Job data + pre= JSON.stringify(job.data, null, 2) + + if job.failedReason + h4.uk-text-danger Failed reason + div= job.failedReason + + if Array.isArray(jobLogs) && (jobLogs.length > 0) + h4 Log + each log in jobLogs + div= log + + if Array.isArray(job.stacktrace) && (job.stacktrace.length > 0) + h4 Stacktrace + each trace in job.stacktrace + pre= trace + + .uk-card-footer + div(uk-grid) + .uk-width-expand + div(uk-grid) + .uk-width-auto + button( + type="button", + data-job-queue= queue.name, + data-job-id= job.id, + data-job-action= 'remove', + onclick="return dtp.adminApp.jobQueueAction(event);", + ).uk-button.dtp-button-danger Remove + .uk-width-auto + button( + type="button", + data-job-queue= queue.name, + data-job-id= job.id, + data-job-action= 'discard', + onclick="return dtp.adminApp.jobQueueAction(event);", + ).uk-button.dtp-button-danger Discard + .uk-width-auto + button( + type="button", + data-job-queue= queue.name, + data-job-id= job.id, + data-job-action= 'retry', + onclick="return dtp.adminApp.jobQueueAction(event);", + ).uk-button.dtp-button-primary Retry \ No newline at end of file