|
|
|
|
@ -28,6 +28,75 @@ export async function handleMetricsRequest(request: Request, db: Database): Prom
|
|
|
|
|
Metrics.set('allowed_groups_total_blocked', blocked);
|
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
|
|
// Métricas de tareas (gauges derivadas desde BD)
|
|
|
|
|
try {
|
|
|
|
|
const row = db
|
|
|
|
|
.prepare(`
|
|
|
|
|
SELECT
|
|
|
|
|
COUNT(*) AS total,
|
|
|
|
|
SUM(CASE WHEN COALESCE(completed, 0) = 1 THEN 1 ELSE 0 END) AS completed,
|
|
|
|
|
SUM(CASE WHEN COALESCE(completed, 0) = 0 THEN 1 ELSE 0 END) AS active,
|
|
|
|
|
SUM(
|
|
|
|
|
CASE
|
|
|
|
|
WHEN COALESCE(completed, 0) = 0
|
|
|
|
|
AND due_date IS NOT NULL
|
|
|
|
|
AND TRIM(due_date) <> ''
|
|
|
|
|
AND due_date < strftime('%Y-%m-%d','now')
|
|
|
|
|
THEN 1 ELSE 0
|
|
|
|
|
END
|
|
|
|
|
) AS overdue
|
|
|
|
|
FROM tasks;
|
|
|
|
|
`)
|
|
|
|
|
.get() as any;
|
|
|
|
|
|
|
|
|
|
if (row) {
|
|
|
|
|
const total = Number(row.total ?? 0);
|
|
|
|
|
const completed = Number(row.completed ?? 0);
|
|
|
|
|
const active = Number(row.active ?? 0);
|
|
|
|
|
const overdue = Number(row.overdue ?? 0);
|
|
|
|
|
|
|
|
|
|
Metrics.set('tasks_created_total', total);
|
|
|
|
|
Metrics.set('tasks_completed_total', completed);
|
|
|
|
|
Metrics.set('tasks_active', active);
|
|
|
|
|
Metrics.set('tasks_overdue', overdue);
|
|
|
|
|
}
|
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
|
|
// Métricas de cola de respuestas (gauges derivadas desde BD)
|
|
|
|
|
try {
|
|
|
|
|
const row = db
|
|
|
|
|
.prepare(`
|
|
|
|
|
SELECT
|
|
|
|
|
COUNT(*) AS pending,
|
|
|
|
|
MIN(strftime('%s', created_at)) AS oldest_ts,
|
|
|
|
|
strftime('%s','now') AS now_ts
|
|
|
|
|
FROM response_queue
|
|
|
|
|
WHERE status = 'queued';
|
|
|
|
|
`)
|
|
|
|
|
.get() as any;
|
|
|
|
|
|
|
|
|
|
if (row) {
|
|
|
|
|
const pending = Number(row.pending ?? 0);
|
|
|
|
|
Metrics.set('response_queue_pending', pending);
|
|
|
|
|
|
|
|
|
|
let ageSeconds = -1;
|
|
|
|
|
const oldestTs = row.oldest_ts != null ? Number(row.oldest_ts) : null;
|
|
|
|
|
const nowTs = row.now_ts != null ? Number(row.now_ts) : null;
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
pending > 0 &&
|
|
|
|
|
oldestTs != null &&
|
|
|
|
|
Number.isFinite(oldestTs) &&
|
|
|
|
|
nowTs != null &&
|
|
|
|
|
Number.isFinite(nowTs)
|
|
|
|
|
) {
|
|
|
|
|
ageSeconds = Math.max(0, nowTs - oldestTs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Metrics.set('response_queue_oldest_age_seconds', ageSeconds);
|
|
|
|
|
}
|
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
|
|
// Exponer métrica con el tiempo restante hasta el próximo group sync (o -1 si scheduler inactivo)
|
|
|
|
|
try {
|
|
|
|
|
const secs = GroupSyncService.getSecondsUntilNextGroupSync();
|
|
|
|
|
|