agendash
Version:
Dashboard for Agenda job scheduler
167 lines • 6.74 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 Fastify plugin for Agendash
*
* @example
* ```typescript
* import Fastify from 'fastify';
* import { Agenda } from 'agenda';
* import { createFastifyPlugin } from 'agendash';
*
* const fastify = Fastify();
* const agenda = new Agenda({ db: { address: 'mongodb://localhost/agenda' } });
*
* fastify.register(createFastifyPlugin(agenda), { prefix: '/dash' });
* ```
*/
export function createFastifyPlugin(agenda) {
const controller = new AgendashController(agenda);
return async (instance) => {
// Register static file serving
const fastifyStatic = await import('@fastify/static');
await instance.register(fastifyStatic.default, {
root: join(__dirname, '../../public')
});
// Index route with CSP header
instance.get('/', async (_request, reply) => {
reply.header('Content-Security-Policy', cspHeader);
return reply.sendFile('index.html');
});
// API routes
instance.get('/api', async (request, reply) => {
try {
const { job, state, q, property, isObjectId, skip, limit } = request.query;
const params = {
name: job,
state,
search: q,
property,
isObjectId,
skip: skip ? parseInt(skip, 10) : 0,
limit: limit ? parseInt(limit, 10) : 50
};
const result = await controller.getJobs(params);
return reply.send(result);
}
catch (error) {
return reply
.status(400)
.send({ error: error instanceof Error ? error.message : 'Unknown error' });
}
});
instance.post('/api/jobs/requeue', async (request, reply) => {
try {
const { jobIds } = request.body;
const result = await controller.requeueJobs(jobIds);
return reply.send(result);
}
catch (error) {
return reply
.status(404)
.send({ error: error instanceof Error ? error.message : 'Unknown error' });
}
});
instance.post('/api/jobs/retry', async (request, reply) => {
try {
const { jobIds } = request.body;
const result = await controller.retryJobs(jobIds);
return reply.send(result);
}
catch (error) {
return reply
.status(404)
.send({ error: error instanceof Error ? error.message : 'Unknown error' });
}
});
instance.post('/api/jobs/delete', async (request, reply) => {
try {
const { jobIds } = request.body;
const result = await controller.deleteJobs(jobIds);
return reply.send(result);
}
catch (error) {
return reply
.status(404)
.send({ error: error instanceof Error ? error.message : 'Unknown error' });
}
});
instance.post('/api/jobs/create', async (request, reply) => {
try {
const result = await controller.createJob(request.body);
return reply.send(result);
}
catch (error) {
return reply
.status(400)
.send({ error: error instanceof Error ? error.message : 'Unknown error' });
}
});
instance.post('/api/jobs/pause', async (request, reply) => {
try {
const { jobIds } = request.body;
const result = await controller.pauseJobs(jobIds);
return reply.send(result);
}
catch (error) {
return reply
.status(400)
.send({ error: error instanceof Error ? error.message : 'Unknown error' });
}
});
instance.post('/api/jobs/resume', async (request, reply) => {
try {
const { jobIds } = request.body;
const result = await controller.resumeJobs(jobIds);
return reply.send(result);
}
catch (error) {
return reply
.status(400)
.send({ error: error instanceof Error ? error.message : 'Unknown error' });
}
});
instance.get('/api/stats', async (request, reply) => {
try {
const fullDetails = request.query.fullDetails === 'true';
const result = await controller.getStats(fullDetails);
return reply.send(result);
}
catch (error) {
return reply
.status(500)
.send({ error: error instanceof Error ? error.message : 'Unknown error' });
}
});
// SSE endpoint for real-time job state notifications
instance.get('/api/events', async (request, reply) => {
// Check if state notifications are available
if (!controller.hasStateNotifications()) {
return reply
.status(501)
.send({ error: 'State notifications not available. Configure a notification channel that supports state subscriptions.' });
}
// Set up SSE headers
reply.raw.setHeader('Content-Type', 'text/event-stream');
reply.raw.setHeader('Cache-Control', 'no-cache');
reply.raw.setHeader('Connection', 'keep-alive');
reply.raw.setHeader('X-Accel-Buffering', 'no'); // Disable nginx buffering
// Send initial connection message
reply.raw.write('event: connected\ndata: {"connected":true}\n\n');
// Subscribe to state notifications
const unsubscribe = controller.createStateStream((notification) => {
reply.raw.write(`data: ${JSON.stringify(notification)}\n\n`);
});
// Clean up on client disconnect
request.raw.on('close', () => {
unsubscribe();
});
// Don't return anything - keep connection open
return reply;
});
};
}
//# sourceMappingURL=fastify.js.map