diff --git a/public/dashboard.js b/public/dashboard.js index 268abb5c..eecb4244 100644 --- a/public/dashboard.js +++ b/public/dashboard.js @@ -210,38 +210,60 @@ $(document).ready(() => { queueState, }; - $bulkActionContainer.each((index, value) => { - const isChecked = $(value).find('[name=jobChecked]').is(':checked'); - const id = encodeURIComponent($(value).find('[name=jobId]').val()); + if (action !== 'clean') { + $bulkActionContainer.each((index, value) => { + const isChecked = $(value).find('[name=jobChecked]').is(':checked'); + const id = encodeURIComponent($(value).find('[name=jobId]').val()); + + if (isChecked) { + data.jobs.push(id); + } + }); + } - if (isChecked) { - data.jobs.push(id); - } - }); + const count = action === 'clean' ? 1000 : data.jobs.length; const r = window.confirm( - `${capitalize(action)} ${data.jobs.length} ${ - data.jobs.length > 1 ? 'jobs' : 'job' + `${capitalize(action)} ${count} ${ + count > 1 ? 'jobs' : 'job' } in queue "${queueHost}/${queueName}"?` ); if (r) { - $.ajax({ - method: action === 'remove' ? 'POST' : 'PATCH', - url: `${basePath}/api/queue/${encodeURIComponent( - queueHost - )}/${encodeURIComponent(queueName)}/${ - action === 'promote' ? 'delayed/' : '' - }job/bulk`, - data: JSON.stringify(data), - contentType: 'application/json', - }) - .done(() => { - window.location.reload(); + if (action === 'clean') { + $.ajax({ + method: 'DELETE', + url: `${basePath}/api/queue/${encodeURIComponent( + queueHost + )}/${encodeURIComponent(queueName)}/jobs/bulk`, + data: JSON.stringify(data), + contentType: 'application/json', }) - .fail((jqXHR) => { - window.alert(`Request failed, check console for error.`); - console.error(jqXHR.responseText); - }); + .done(() => { + window.location.reload(); + }) + .fail((jqXHR) => { + window.alert(`Request failed, check console for error.`); + console.error(jqXHR.responseText); + }); + } else { + $.ajax({ + method: action === 'remove' ? 'POST' : 'PATCH', + url: `${basePath}/api/queue/${encodeURIComponent( + queueHost + )}/${encodeURIComponent(queueName)}/${ + action === 'promote' ? 'delayed/' : '' + }job/bulk`, + data: JSON.stringify(data), + contentType: 'application/json', + }) + .done(() => { + window.location.reload(); + }) + .fail((jqXHR) => { + window.alert(`Request failed, check console for error.`); + console.error(jqXHR.responseText); + }); + } } else { $(this).prop('disabled', false); } diff --git a/src/server/views/api/bulkAction.js b/src/server/views/api/bulkAction.js index ae8ae206..3b42153c 100644 --- a/src/server/views/api/bulkAction.js +++ b/src/server/views/api/bulkAction.js @@ -1,6 +1,6 @@ const _ = require('lodash'); -const ACTIONS = ['remove', 'retry', 'promote']; +const ACTIONS = ['clean', 'remove', 'retry', 'promote']; function bulkAction(action) { return async function handler(req, res) { @@ -19,7 +19,7 @@ function bulkAction(action) { const {jobs, queueState} = req.body; try { - if (!_.isEmpty(jobs)) { + if (!_.isEmpty(jobs) && job.length > 0) { const jobsPromises = jobs.map((id) => queue.getJob(decodeURIComponent(id)) ); @@ -39,6 +39,9 @@ function bulkAction(action) { : fetchedJobs.map((job) => job[action]()); await Promise.all(actionPromises); return res.sendStatus(200); + } else if (action === 'clean') { + await queue.clean(1000, queueState); + return res.sendStatus(200); } } catch (e) { const body = { diff --git a/src/server/views/api/bulkJobsClean.js b/src/server/views/api/bulkJobsClean.js new file mode 100644 index 00000000..92c5241a --- /dev/null +++ b/src/server/views/api/bulkJobsClean.js @@ -0,0 +1,3 @@ +const bulkAction = require('./bulkAction'); + +module.exports = bulkAction('clean'); diff --git a/src/server/views/api/index.js b/src/server/views/api/index.js index 12b53987..ef4b6350 100644 --- a/src/server/views/api/index.js +++ b/src/server/views/api/index.js @@ -8,6 +8,7 @@ const jobRetry = require('./jobRetry'); const jobRemove = require('./jobRemove'); const jobDataUpdate = require('./jobDataUpdate'); const repeatableJobRemove = require('./repeatableJobRemove'); +const bulkJobsClean = require('./bulkJobsClean'); const bulkJobsPromote = require('./bulkJobsPromote'); const bulkJobsRemove = require('./bulkJobsRemove'); const bulkJobsRetry = require('./bulkJobsRetry'); @@ -30,5 +31,6 @@ router.patch('/queue/:queueHost/:queueName/job/:id', jobRetry); router.put('/queue/:queueHost/:queueName/pause', queuePause); router.put('/queue/:queueHost/:queueName/resume', queueResume); router.delete('/queue/:queueHost/:queueName/job/:id', jobRemove); +router.delete('/queue/:queueHost/:queueName/jobs/bulk', bulkJobsClean); module.exports = router; diff --git a/src/server/views/dashboard/queueJobsByState.js b/src/server/views/dashboard/queueJobsByState.js index c9272724..8b25ea49 100644 --- a/src/server/views/dashboard/queueJobsByState.js +++ b/src/server/views/dashboard/queueJobsByState.js @@ -153,6 +153,11 @@ async function _html(req, res) { state === 'failed' || (state === 'delayed' && !queue.IS_BEE) ); + const disableClean = !( + state === 'failed' || + state === 'completed' || + !queue.IS_BULL + ); return res.render('dashboard/templates/queueJobsByState', { basePath, @@ -164,6 +169,7 @@ async function _html(req, res) { disablePagination: queue.IS_BEE && (state === 'succeeded' || state === 'failed'), disableOrdering: queue.IS_BEE, + disableClean, disablePromote, disableRetry, currentPage: page, diff --git a/src/server/views/dashboard/templates/queueJobsByState.hbs b/src/server/views/dashboard/templates/queueJobsByState.hbs index ca7a757a..d41dfa8d 100644 --- a/src/server/views/dashboard/templates/queueJobsByState.hbs +++ b/src/server/views/dashboard/templates/queueJobsByState.hbs @@ -50,6 +50,12 @@ data-queue-state="{{ state }}" class="js-bulk-action btn btn-danger"> Remove Jobs + {{#unless disableClean}} + + {{/unless}} {{#unless disableRetry}}