UNPKG

agendash

Version:

Dashboard for Agenda job scheduler

251 lines 9.77 kB
import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { AgendashController } from '../AgendashController.js'; import { cspHeader } from '../csp.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); /** * Create Hapi plugin for Agendash * * @example * ```typescript * import Hapi from '@hapi/hapi'; * import Inert from '@hapi/inert'; * import { Agenda } from 'agenda'; * import { createHapiPlugin } from 'agendash'; * * const server = Hapi.server({ port: 3000 }); * const agenda = new Agenda({ db: { address: 'mongodb://localhost/agenda' } }); * * await server.register(Inert); * await server.register({ * plugin: createHapiPlugin(agenda), * options: { auth: false }, * routes: { prefix: '/dash' } * }); * ``` */ export function createHapiPlugin(agenda) { const controller = new AgendashController(agenda); return { name: 'agendash', version: '6.0.0', register: async (server, options) => { const authConfig = options.auth ?? false; // CSP header on all responses server.ext('onPreResponse', (request, h) => { const response = request.response; if ('header' in response && typeof response.header === 'function') { response.header('Content-Security-Policy', cspHeader); } return h.continue; }); // Static files server.route({ method: 'GET', path: '/{param*}', handler: { directory: { path: join(__dirname, '../../public') } }, options: { auth: authConfig } }); // API routes server.route({ method: 'GET', path: '/api', handler: async (request, h) => { try { const query = request.query; const params = { name: query.job, state: query.state, search: query.q, property: query.property, isObjectId: query.isObjectId, skip: query.skip ? parseInt(query.skip, 10) : 0, limit: query.limit ? parseInt(query.limit, 10) : 50 }; return await controller.getJobs(params); } catch (error) { return h .response({ error: error instanceof Error ? error.message : 'Unknown error' }) .code(400); } }, options: { auth: authConfig } }); server.route({ method: 'POST', path: '/api/jobs/requeue', handler: async (request, h) => { try { const { jobIds } = request.payload; return await controller.requeueJobs(jobIds); } catch (error) { return h .response({ error: error instanceof Error ? error.message : 'Unknown error' }) .code(404); } }, options: { auth: authConfig } }); server.route({ method: 'POST', path: '/api/jobs/retry', handler: async (request, h) => { try { const { jobIds } = request.payload; return await controller.retryJobs(jobIds); } catch (error) { return h .response({ error: error instanceof Error ? error.message : 'Unknown error' }) .code(404); } }, options: { auth: authConfig } }); server.route({ method: 'POST', path: '/api/jobs/delete', handler: async (request, h) => { try { const { jobIds } = request.payload; const result = await controller.deleteJobs(jobIds); if (result.deleted) { return h.response(result); } return h.response({ message: 'Jobs not deleted' }).code(404); } catch (error) { return h .response({ error: error instanceof Error ? error.message : 'Unknown error' }) .code(404); } }, options: { auth: authConfig } }); server.route({ method: 'POST', path: '/api/jobs/create', handler: async (request, h) => { try { const options = request.payload; return await controller.createJob(options); } catch (error) { return h .response({ error: error instanceof Error ? error.message : 'Unknown error' }) .code(400); } }, options: { auth: authConfig } }); server.route({ method: 'POST', path: '/api/jobs/pause', handler: async (request, h) => { try { const { jobIds } = request.payload; return await controller.pauseJobs(jobIds); } catch (error) { return h .response({ error: error instanceof Error ? error.message : 'Unknown error' }) .code(400); } }, options: { auth: authConfig } }); server.route({ method: 'POST', path: '/api/jobs/resume', handler: async (request, h) => { try { const { jobIds } = request.payload; return await controller.resumeJobs(jobIds); } catch (error) { return h .response({ error: error instanceof Error ? error.message : 'Unknown error' }) .code(400); } }, options: { auth: authConfig } }); server.route({ method: 'GET', path: '/api/stats', handler: async (request, h) => { try { const fullDetails = request.query.fullDetails === 'true'; return await controller.getStats(fullDetails); } catch (error) { return h .response({ error: error instanceof Error ? error.message : 'Unknown error' }) .code(500); } }, options: { auth: authConfig } }); // SSE endpoint for real-time job state notifications server.route({ method: 'GET', path: '/api/events', handler: (request, h) => { // Check if state notifications are available if (!controller.hasStateNotifications()) { return h .response({ error: 'State notifications not available. Configure a notification channel that supports state subscriptions.' }) .code(501); } // Create a PassThrough stream for SSE const { PassThrough } = require('stream'); const stream = new PassThrough(); // Send initial connection message stream.write('event: connected\ndata: {"connected":true}\n\n'); // Subscribe to state notifications const unsubscribe = controller.createStateStream((notification) => { stream.write(`data: ${JSON.stringify(notification)}\n\n`); }); // Clean up on client disconnect request.raw.req.on('close', () => { unsubscribe(); stream.end(); }); return h.response(stream) .type('text/event-stream') .header('Cache-Control', 'no-cache') .header('Connection', 'keep-alive') .header('X-Accel-Buffering', 'no'); }, options: { auth: authConfig } }); } }; } //# sourceMappingURL=hapi.js.map