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