agendash
Version:
Dashboard for Agenda job scheduler
178 lines • 6.57 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 Koa middleware array for Agendash (sync version)
* Note: This returns only the CSP middleware. Use createKoaMiddlewareAsync for full setup.
*
* @deprecated Use createKoaMiddlewareAsync instead for complete middleware setup
* @example
* ```typescript
* import Koa from 'koa';
* import { Agenda } from 'agenda';
* import { createKoaMiddlewareAsync } from 'agendash';
*
* const app = new Koa();
* const agenda = new Agenda({ db: { address: 'mongodb://localhost/agenda' } });
*
* const middlewares = await createKoaMiddlewareAsync(agenda);
* middlewares.forEach(mw => app.use(mw));
* ```
*/
export function createKoaMiddleware(_agenda) {
const middlewares = [];
// CSP header middleware
middlewares.push(async (ctx, next) => {
await next();
ctx.set('Content-Security-Policy', cspHeader);
});
// Return initial middleware; use createKoaMiddlewareAsync for full setup
return middlewares;
}
/**
* Async version that fully sets up all middlewares
*/
export async function createKoaMiddlewareAsync(agenda) {
const controller = new AgendashController(agenda);
const middlewares = [];
// CSP header middleware
middlewares.push(async (ctx, next) => {
await next();
ctx.set('Content-Security-Policy', cspHeader);
});
const [{ default: koaStatic }, { default: bodyParser }, { default: Router }] = await Promise.all([
import('koa-static'),
import('koa-bodyparser'),
import('koa-router')
]);
// Static files (deferred to run after routes)
middlewares.push(koaStatic(join(__dirname, '../../public'), { defer: true }));
// Body parser
middlewares.push(bodyParser());
// API routes
const router = new Router();
router.get('/api', async (ctx) => {
const query = ctx.query;
try {
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
};
ctx.body = await controller.getJobs(params);
}
catch (error) {
ctx.status = 400;
ctx.body = { error: error instanceof Error ? error.message : 'Unknown error' };
}
});
router.post('/api/jobs/requeue', async (ctx) => {
try {
const { jobIds } = ctx.request.body;
ctx.body = await controller.requeueJobs(jobIds);
}
catch (error) {
ctx.status = 404;
ctx.body = { error: error instanceof Error ? error.message : 'Unknown error' };
}
});
router.post('/api/jobs/retry', async (ctx) => {
try {
const { jobIds } = ctx.request.body;
ctx.body = await controller.retryJobs(jobIds);
}
catch (error) {
ctx.status = 404;
ctx.body = { error: error instanceof Error ? error.message : 'Unknown error' };
}
});
router.post('/api/jobs/delete', async (ctx) => {
try {
const { jobIds } = ctx.request.body;
ctx.body = await controller.deleteJobs(jobIds);
}
catch (error) {
ctx.status = 404;
ctx.body = { error: error instanceof Error ? error.message : 'Unknown error' };
}
});
router.post('/api/jobs/create', async (ctx) => {
try {
const options = ctx.request.body;
ctx.body = await controller.createJob(options);
}
catch (error) {
ctx.status = 400;
ctx.body = { error: error instanceof Error ? error.message : 'Unknown error' };
}
});
router.post('/api/jobs/pause', async (ctx) => {
try {
const { jobIds } = ctx.request.body;
ctx.body = await controller.pauseJobs(jobIds);
}
catch (error) {
ctx.status = 400;
ctx.body = { error: error instanceof Error ? error.message : 'Unknown error' };
}
});
router.post('/api/jobs/resume', async (ctx) => {
try {
const { jobIds } = ctx.request.body;
ctx.body = await controller.resumeJobs(jobIds);
}
catch (error) {
ctx.status = 400;
ctx.body = { error: error instanceof Error ? error.message : 'Unknown error' };
}
});
router.get('/api/stats', async (ctx) => {
try {
const fullDetails = ctx.query.fullDetails === 'true';
ctx.body = await controller.getStats(fullDetails);
}
catch (error) {
ctx.status = 500;
ctx.body = { error: error instanceof Error ? error.message : 'Unknown error' };
}
});
// SSE endpoint for real-time job state notifications
router.get('/api/events', async (ctx) => {
// Check if state notifications are available
if (!controller.hasStateNotifications()) {
ctx.status = 501;
ctx.body = { error: 'State notifications not available. Configure a notification channel that supports state subscriptions.' };
return;
}
// Set up SSE headers
ctx.set('Content-Type', 'text/event-stream');
ctx.set('Cache-Control', 'no-cache');
ctx.set('Connection', 'keep-alive');
ctx.set('X-Accel-Buffering', 'no'); // Disable nginx buffering
// Use a passthrough stream for SSE
const { PassThrough } = await import('stream');
const stream = new PassThrough();
ctx.body = stream;
// 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
ctx.req.on('close', () => {
unsubscribe();
stream.end();
});
});
middlewares.push(router.routes());
middlewares.push(router.allowedMethods());
return middlewares;
}
//# sourceMappingURL=koa.js.map