agendash
Version:
Dashboard for Agenda job scheduler
251 lines • 9.77 kB
JavaScript
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