diff --git a/packages/api/src/handlers/pauseAll.ts b/packages/api/src/handlers/pauseAll.ts new file mode 100644 index 000000000..d0343f3de --- /dev/null +++ b/packages/api/src/handlers/pauseAll.ts @@ -0,0 +1,13 @@ +import { BullBoardRequest, ControllerHandlerReturnType } from '../../typings/app'; + +async function pauseAll(req: BullBoardRequest): Promise { + req.queues.forEach(async (queue) => { + const isPaused = await queue.isPaused(); + if (!isPaused) { + queue.pause(); + } + }); + return { status: 200, body: { message: 'All queues paused' } }; +} + +export const pauseAllHandler = pauseAll; diff --git a/packages/api/src/handlers/resumeAll.ts b/packages/api/src/handlers/resumeAll.ts new file mode 100644 index 000000000..7a9f2a7f6 --- /dev/null +++ b/packages/api/src/handlers/resumeAll.ts @@ -0,0 +1,13 @@ +import { BullBoardRequest, ControllerHandlerReturnType } from '../../typings/app'; + +async function resumeAll(req: BullBoardRequest): Promise { + req.queues.forEach(async (queue) => { + const isPaused = await queue.isPaused(); + if (isPaused) { + await queue.resume(); + } + }); + return { status: 200, body: { message: 'All queues resumed' } }; +} + +export const resumeAllHandler = resumeAll; diff --git a/packages/api/src/routes.ts b/packages/api/src/routes.ts index bb33fd5da..141ebf31a 100644 --- a/packages/api/src/routes.ts +++ b/packages/api/src/routes.ts @@ -15,6 +15,8 @@ import { retryAllHandler } from './handlers/retryAll'; import { retryJobHandler } from './handlers/retryJob'; import { promoteAllHandler } from './handlers/promoteAll'; import { updateJobDataHandler } from './handlers/updateJobData'; +import { pauseAllHandler } from './handlers/pauseAll'; +import { resumeAllHandler } from './handlers/resumeAll'; export const appRoutes: AppRouteDefs = { entryPoint: { @@ -25,6 +27,8 @@ export const appRoutes: AppRouteDefs = { api: [ { method: 'get', route: '/api/redis/stats', handler: redisStatsHandler }, { method: 'get', route: '/api/queues', handler: queuesHandler }, + { method: 'put', route: '/api/queues/pause', handler: pauseAllHandler }, + { method: 'put', route: '/api/queues/resume', handler: resumeAllHandler }, { method: 'get', route: '/api/queues/:queueName/:jobId/logs', diff --git a/packages/ui/src/components/OverviewDropDownActions/OverviewDropDownActions.tsx b/packages/ui/src/components/OverviewDropDownActions/OverviewDropDownActions.tsx new file mode 100644 index 000000000..b6e010064 --- /dev/null +++ b/packages/ui/src/components/OverviewDropDownActions/OverviewDropDownActions.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Item, Portal, Root, Trigger } from '@radix-ui/react-dropdown-menu'; +import { Button } from '../Button/Button'; +import { DropdownContent } from '../DropdownContent/DropdownContent'; +import { EllipsisVerticalIcon } from '../Icons/EllipsisVertical'; +import { PauseIcon } from '../Icons/Pause'; +import { PlayIcon } from '../Icons/Play'; +import { useTranslation } from 'react-i18next'; +import { useQueues } from '../../hooks/useQueues'; + +export const OverviewActions = ({ + actions, +}: { + actions: ReturnType['actions']; +}) => { + const { t } = useTranslation(); + + return ( + + + + + + + + + + {t('QUEUE.ACTIONS.PAUSE_ALL')} + + + + {t('QUEUE.ACTIONS.RESUME_ALL')} + + + + + ); +}; + +export default OverviewActions; diff --git a/packages/ui/src/hooks/useQueues.ts b/packages/ui/src/hooks/useQueues.ts index 9f811c271..f0e704b43 100644 --- a/packages/ui/src/hooks/useQueues.ts +++ b/packages/ui/src/hooks/useQueues.ts @@ -115,10 +115,23 @@ export function useQueues(): Omit & { actions: Queu jobOptions: Record ) => withConfirmAndUpdate(() => api.addJob(queueName, jobName, jobData, jobOptions), '', false); + const pauseAll = withConfirmAndUpdate( + () => api.pauseAllQueues(), + t('QUEUE.ACTIONS.CONFIRM.PAUSE_ALL'), + confirmQueueActions + ); + const resumeAll = withConfirmAndUpdate( + () => api.resumeAllQueues(), + t('QUEUE.ACTIONS.CONFIRM.RESUME_ALL'), + confirmQueueActions + ); + return { queues, loading, actions: { + pauseAll, + resumeAll, updateQueues, pollQueues, retryAll, diff --git a/packages/ui/src/pages/OverviewPage/OverviewPage.module.css b/packages/ui/src/pages/OverviewPage/OverviewPage.module.css index bac24fe63..2300ac68c 100644 --- a/packages/ui/src/pages/OverviewPage/OverviewPage.module.css +++ b/packages/ui/src/pages/OverviewPage/OverviewPage.module.css @@ -21,3 +21,9 @@ } } } + +.header { + display: flex; + justify-content: space-between; + align-items: center; +} diff --git a/packages/ui/src/pages/OverviewPage/OverviewPage.tsx b/packages/ui/src/pages/OverviewPage/OverviewPage.tsx index 56068b045..3dc0b7961 100644 --- a/packages/ui/src/pages/OverviewPage/OverviewPage.tsx +++ b/packages/ui/src/pages/OverviewPage/OverviewPage.tsx @@ -8,6 +8,7 @@ import { useQuery } from '../../hooks/useQuery'; import { useQueues } from '../../hooks/useQueues'; import { links } from '../../utils/links'; import s from './OverviewPage.module.css'; +import OverviewDropDownActions from '../../components/OverviewDropDownActions/OverviewDropDownActions'; export const OverviewPage = () => { const { t } = useTranslation(); @@ -20,8 +21,10 @@ export const OverviewPage = () => { queues?.filter((queue) => !selectedStatus || queue.counts[selectedStatus] > 0) || []; return (
- - +
+ + +
{queuesToView.length > 0 && (
    {queuesToView.map((queue) => ( diff --git a/packages/ui/src/services/Api.ts b/packages/ui/src/services/Api.ts index 13896dd98..77676839d 100644 --- a/packages/ui/src/services/Api.ts +++ b/packages/ui/src/services/Api.ts @@ -111,6 +111,14 @@ export class Api { return this.axios.put(`/queues/${encodeURIComponent(queueName)}/resume`); } + public pauseAllQueues() { + return this.axios.put(`/queues/pause`); + } + + public resumeAllQueues() { + return this.axios.put(`/queues/resume`); + } + public emptyQueue(queueName: string) { return this.axios.put(`/queues/${encodeURIComponent(queueName)}/empty`); } diff --git a/packages/ui/src/static/locales/en-US/messages.json b/packages/ui/src/static/locales/en-US/messages.json index 420f052b1..7a9a1dfdd 100644 --- a/packages/ui/src/static/locales/en-US/messages.json +++ b/packages/ui/src/static/locales/en-US/messages.json @@ -64,6 +64,8 @@ "CLEAN_ALL": "Clean all", "RESUME": "Resume", "PAUSE": "Pause", + "RESUME_ALL": "Resume all", + "PAUSE_ALL": "Pause all", "EMPTY": "Empty", "ADD_JOB": "Add job", "CONFIRM": { @@ -72,7 +74,9 @@ "PROMOTE_ALL": "Are you sure that you want to promote all delayed jobs?", "PAUSE_QUEUE": "Are you sure that you want to pause queue processing?", "EMPTY_QUEUE": "Are you sure that you want to empty the queue?", - "RESUME_QUEUE": "Are you sure that you want to resume queue processing?" + "RESUME_QUEUE": "Are you sure that you want to resume queue processing?", + "PAUSE_ALL": "Are you sure that you want to pause all queues?", + "RESUME_ALL": "Are you sure that you want to resume all queues?" } }, "STATUS": { diff --git a/packages/ui/typings/app.d.ts b/packages/ui/typings/app.d.ts index 3acd255c4..723535feb 100644 --- a/packages/ui/typings/app.d.ts +++ b/packages/ui/typings/app.d.ts @@ -11,6 +11,8 @@ export { Status } from '@bull-board/api/typings/app'; export type SelectedStatuses = Record; export interface QueueActions { + pauseAll: () => Promise; + resumeAll: () => Promise; retryAll: (queueName: string, status: JobRetryStatus) => () => Promise; promoteAll: (queueName: string) => () => Promise; cleanAll: (queueName: string, status: JobCleanStatus) => () => Promise;