import { useState } from "react"; import { useOpsBumpGeneration, useOpsHttpStart, useOpsHttpStop, useOpsPause, useOpsReload, useOpsRestart, useOpsResume, useOpsScale, useOpsStatus, } from "../api/hooks.js"; import { errorMessage } from "../api/client.js"; function Stat({ label, value }) { return (
{label} {value}
); } export function OpsPage() { const status = useOpsStatus(true); const pause = useOpsPause(); const resume = useOpsResume(); const reload = useOpsReload(); const restart = useOpsRestart(); const scale = useOpsScale(); const httpStart = useOpsHttpStart(); const httpStop = useOpsHttpStop(); const bump = useOpsBumpGeneration(); const [workerCount, setWorkerCount] = useState(""); const [msg, setMsg] = useState(null); const [err, setErr] = useState(null); const unavailable = status.isError && (status.error?.code === "ERR_NETWORK" || status.error?.response?.status === 404 || status.error?.message?.includes("Network")); function run(label, mutateAsync, args) { setMsg(null); setErr(null); mutateAsync(args) .then((data) => { setMsg(`${label} ok`); return data; }) .catch((e) => setErr(errorMessage(e, `${label} failed`))); } if (unavailable) { return (

Ops

Control plane is not reachable on /ops. Use{" "} pnpm dev:pm2 (Vite :8500, control :8600, HTTP :8700) to manage processes. Plain pnpm dev runs the monolith and has no control API.
); } const data = status.data; const desired = data?.desired; const queue = data?.queue; const children = data?.children; return (

Ops

Process control via PM2. Pause stops workers from opening jobs; cron/HTTP can still enqueue. Restart drains active jobs unless forced.

{desired?.restartNeeded ? (
Restart needed (generation {desired.generation}) {desired.restartReason ? `: ${desired.restartReason}` : ""}. Use Drain restart after install/config changes.
) : null} {msg ? (
{msg}
) : null} {err ? (
{err}
) : null} {status.isLoading && !data ? ( ) : ( <>

Queue

HTTP process

Workers

setWorkerCount(e.target.value)} />

Restart HTTP + workers

Pauses the queue, waits until active jobs are 0, migrates DB, then recreates PM2 processes. Force skips the wait (may interrupt runs).

Heartbeats

{(data?.heartbeats ?? []).map((h) => ( ))} {!data?.heartbeats?.length ? ( ) : null}
Role PID Generation Age (ms)
{h.role} {h.pid} {h.generation} {Math.max(0, Date.now() - h.ts)}
No live heartbeats yet

PM2 children

              {JSON.stringify(children ?? {}, null, 2)}
            
)}
); }