From 4869b8049551845446a94d52118d445aa991aec8 Mon Sep 17 00:00:00 2001 From: Towsty Date: Sat, 29 Aug 2026 18:20:14 -0500 Subject: [PATCH] Expose a monitor endpoint for MyMonitor status and job progress. Co-authored-by: Cursor --- .env.example | 3 ++ nuxt.config.ts | 1 + server/api/monitor.get.ts | 58 +++++++++++++++++++++++++++++++++++++++ server/middleware/auth.ts | 1 + 4 files changed, 63 insertions(+) create mode 100644 server/api/monitor.get.ts diff --git a/.env.example b/.env.example index 7e3ffd3..6f321fd 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,9 @@ COMFY_LAUNCH_CMD= # Windows host agent (scripts/comfy-host-agent.mjs) so Coolify can start Comfy remotely COMFY_CONTROL_URL=http://192.168.77.7:8199 COMFY_CONTROL_TOKEN= +# Optional. If set, MyMonitor must send it as X-Monitor-Token (or ?token=). +# MONITOR_TOKEN= + # Idle-stop lives on the Beast host agent only (default 30 min). Set on the desktop, not here. # COMFY_IDLE_MS=1800000 diff --git a/nuxt.config.ts b/nuxt.config.ts index a4ea7b3..70595d4 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -32,6 +32,7 @@ export default defineNuxtConfig({ comfyLaunchCmd: process.env.COMFY_LAUNCH_CMD || '', comfyControlUrl: process.env.COMFY_CONTROL_URL || '', comfyControlToken: process.env.COMFY_CONTROL_TOKEN || '', + monitorToken: process.env.MONITOR_TOKEN || '', imageComfyHost: process.env.IMAGE_COMFY_HOST || '', imageComfyPort: process.env.IMAGE_COMFY_PORT || '', imageComfyPrimaryHost: process.env.IMAGE_COMFY_PRIMARY_HOST || '', diff --git a/server/api/monitor.get.ts b/server/api/monitor.get.ts new file mode 100644 index 0000000..40eb222 --- /dev/null +++ b/server/api/monitor.get.ts @@ -0,0 +1,58 @@ +import { checkComfyHttp, fetchQueue, fetchComfyControlStatus } from '~/server/utils/comfyLifecycle' +import { comfyConfigured } from '~/server/utils/comfy' +import { listJobs, type Job } from '~/server/utils/jobs' + +const ACTIVE: Job['status'][] = ['queued', 'uploading', 'running'] + +function monitorToken() { + return String(useRuntimeConfig().monitorToken || process.env.MONITOR_TOKEN || '').trim() +} + +function activeProgress(jobs: Job[]) { + const active = jobs.filter(job => ACTIVE.includes(job.status)) + if (!active.length) { + return { running: false, percent: 0 } + } + const percent = Math.max(0, Math.min(100, Math.round(Math.max(...active.map(job => Number(job.progress) || 0))))) + return { running: true, percent } +} + +export default defineEventHandler(async (event) => { + const expected = monitorToken() + if (expected) { + const given = String(getHeader(event, 'x-monitor-token') || getQuery(event).token || '').trim() + if (given !== expected) { + throw createError({ statusCode: 401, statusMessage: 'Monitor token required' }) + } + } + + setHeader(event, 'Cache-Control', 'no-store') + + const videoConfigured = comfyConfigured() + const [comfy, queue, control] = await Promise.all([ + videoConfigured + ? checkComfyHttp().catch(() => ({ ok: false })) + : Promise.resolve({ ok: false }), + videoConfigured ? fetchQueue() : Promise.resolve({ running: 0, pending: 0, ok: false }), + fetchComfyControlStatus().catch(() => null) + ]) + const comfyReachable = Boolean(comfy.ok || queue.ok) + const hostAgentOk = Boolean(control && control.ok !== false) + const busy = Boolean(comfyReachable && queue.running > 0) + const progress = activeProgress(listJobs()) + + return { + ok: true, + instance: String(useRuntimeConfig().public.instanceName || 'aigen'), + comfy: { + ok: comfyReachable, + busy, + asleep: Boolean(control?.asleep) && !comfyReachable, + processUp: Boolean(control?.process ?? control?.processUp) + }, + hostAgent: { + ok: hostAgentOk + }, + progress + } +}) diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts index e3cc38d..4f613f1 100644 --- a/server/middleware/auth.ts +++ b/server/middleware/auth.ts @@ -7,6 +7,7 @@ export default defineEventHandler((event) => { path.startsWith('/api/auth/logout') || path.startsWith('/api/auth/me') || path.startsWith('/api/health') || + path.startsWith('/api/monitor') || path.startsWith('/_nuxt') || path.startsWith('/favicon') || path === '/login'