103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
|
|
/**
|
||
|
|
* GET /api/status/workers
|
||
|
|
* Heartbeat snapshot for the three in-process workers:
|
||
|
|
* • analyzer — analyzer_jobs
|
||
|
|
* • rmm — rmm_executions
|
||
|
|
* • sync — sync_schedules / sync_history (proxy for the scheduler)
|
||
|
|
*
|
||
|
|
* For each: last activity timestamp, in-flight count, last-1h success/
|
||
|
|
* failure totals. Cheap — just SELECT COUNT(*) FILTER queries. */
|
||
|
|
|
||
|
|
import { NextResponse } from 'next/server';
|
||
|
|
import { requireAuth } from '@/lib/auth-utils';
|
||
|
|
import postgresClient from '@/lib/services/postgres-client';
|
||
|
|
|
||
|
|
export interface WorkerSnapshot {
|
||
|
|
name: string;
|
||
|
|
lastActivity: string | null;
|
||
|
|
inFlight: number;
|
||
|
|
oneHour: { success: number; failure: number };
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function GET() {
|
||
|
|
const { error } = await requireAuth();
|
||
|
|
if (error) return error;
|
||
|
|
|
||
|
|
const [analyzerRes, rmmRes, syncRes] = await Promise.all([
|
||
|
|
postgresClient.query<{
|
||
|
|
last_activity: string | null;
|
||
|
|
in_flight: string;
|
||
|
|
ok_1h: string;
|
||
|
|
fail_1h: string;
|
||
|
|
}>(
|
||
|
|
`SELECT
|
||
|
|
GREATEST(MAX(queued_at), MAX(started_at), MAX(finished_at))::text AS last_activity,
|
||
|
|
COUNT(*) FILTER (WHERE status IN ('queued','fetching','triaging','itglue','analyzing','deep_review'))::text AS in_flight,
|
||
|
|
COUNT(*) FILTER (WHERE status = 'complete' AND finished_at >= NOW() - INTERVAL '1 hour')::text AS ok_1h,
|
||
|
|
COUNT(*) FILTER (WHERE status = 'failed' AND finished_at >= NOW() - INTERVAL '1 hour')::text AS fail_1h
|
||
|
|
FROM analyzer_jobs`,
|
||
|
|
),
|
||
|
|
postgresClient.query<{
|
||
|
|
last_activity: string | null;
|
||
|
|
in_flight: string;
|
||
|
|
ok_1h: string;
|
||
|
|
fail_1h: string;
|
||
|
|
}>(
|
||
|
|
`SELECT
|
||
|
|
GREATEST(MAX(queued_at), MAX(started_at), MAX(completed_at))::text AS last_activity,
|
||
|
|
COUNT(*) FILTER (WHERE status IN ('queued','running'))::text AS in_flight,
|
||
|
|
COUNT(*) FILTER (WHERE status = 'complete' AND completed_at >= NOW() - INTERVAL '1 hour')::text AS ok_1h,
|
||
|
|
COUNT(*) FILTER (WHERE status IN ('failed','timeout') AND completed_at >= NOW() - INTERVAL '1 hour')::text AS fail_1h
|
||
|
|
FROM rmm_executions`,
|
||
|
|
),
|
||
|
|
postgresClient.query<{
|
||
|
|
last_run: string | null;
|
||
|
|
ok_1h: string;
|
||
|
|
fail_1h: string;
|
||
|
|
}>(
|
||
|
|
`SELECT
|
||
|
|
MAX(last_run)::text AS last_run,
|
||
|
|
COUNT(*) FILTER (WHERE last_status = 'success' AND last_run >= NOW() - INTERVAL '1 hour')::text AS ok_1h,
|
||
|
|
COUNT(*) FILTER (WHERE last_status = 'failed' AND last_run >= NOW() - INTERVAL '1 hour')::text AS fail_1h
|
||
|
|
FROM sync_schedules
|
||
|
|
WHERE is_enabled = true`,
|
||
|
|
),
|
||
|
|
]);
|
||
|
|
|
||
|
|
const a = analyzerRes.rows[0];
|
||
|
|
const r = rmmRes.rows[0];
|
||
|
|
const s = syncRes.rows[0];
|
||
|
|
|
||
|
|
const workers: WorkerSnapshot[] = [
|
||
|
|
{
|
||
|
|
name: 'Analyzer',
|
||
|
|
lastActivity: a?.last_activity ?? null,
|
||
|
|
inFlight: parseInt(a?.in_flight ?? '0', 10),
|
||
|
|
oneHour: {
|
||
|
|
success: parseInt(a?.ok_1h ?? '0', 10),
|
||
|
|
failure: parseInt(a?.fail_1h ?? '0', 10),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'RMM Overshell',
|
||
|
|
lastActivity: r?.last_activity ?? null,
|
||
|
|
inFlight: parseInt(r?.in_flight ?? '0', 10),
|
||
|
|
oneHour: {
|
||
|
|
success: parseInt(r?.ok_1h ?? '0', 10),
|
||
|
|
failure: parseInt(r?.fail_1h ?? '0', 10),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'Sync scheduler',
|
||
|
|
lastActivity: s?.last_run ?? null,
|
||
|
|
inFlight: 0,
|
||
|
|
oneHour: {
|
||
|
|
success: parseInt(s?.ok_1h ?? '0', 10),
|
||
|
|
failure: parseInt(s?.fail_1h ?? '0', 10),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
return NextResponse.json({ workers });
|
||
|
|
}
|