import type { HttpContext } from '@adonisjs/core/http' import AuditLog from '#models/audit_log' import UserError from '#exceptions/user_error' import jobRunner, { type JobLine } from '#services/job_runner' import { openSse } from '#services/sse' export default class JobsController { /** * Replays the output of a job, then follows it until the end. Jobs * finished a while ago are read from the action log. */ async stream(ctx: HttpContext) { const id = Number(ctx.params.id) const job = jobRunner.get(id) const entry = job ? null : await AuditLog.find(id) if (!job && !entry) throw new UserError('Action introuvable.', 404) const channel = openSse(ctx) if (!job) { const lines = (entry!.output ?? '') .split('\n') .map((text) => ({ ts: 0, stream: 'out', text })) channel.send('meta', { id, action: entry!.action, target: entry!.target }) channel.send('lines', lines) channel.send('end', { status: entry!.status }) channel.close() return } channel.send('meta', { id, action: job.meta.action, target: job.meta.target }) channel.send('lines', job.lines) if (job.status !== 'running') { channel.send('end', { status: job.status }) channel.close() return } const onLine = (line: JobLine) => channel.send('lines', [line]) const onEnd = (status: string) => { channel.send('end', { status }) channel.close() } job.on('line', onLine) job.once('end', onEnd) channel.onClose(() => { job.off('line', onLine) job.off('end', onEnd) }) } }