import { useEffect, useState } from "react"; import { useOpsHttpStart, useOpsHttpStop, useOpsPause, useOpsProcessRestart, useOpsReload, useOpsRestart, useOpsResume, useOpsScale, useOpsStatus, } from "../api/hooks.js"; import { errorMessage } from "../api/client.js"; import { HttpProcessCard } from "../components/HttpProcessCard.jsx"; import { QueueCard } from "../components/QueueCard.jsx"; import { ProcessResourcesCard } from "../components/ProcessResourcesCard.jsx"; import { HeartbeatsAndPm2Card } from "../components/HeartbeatsAndPm2Card.jsx"; import { useNotifications } from "../notifications.jsx"; const MAX_WORKERS = 32; 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 processRestart = useOpsProcessRestart(); const { notify } = useNotifications(); const [workerCount, setWorkerCount] = useState(1); const [workerInit, setWorkerInit] = useState(false); const unavailable = status.isError && (status.error?.code === "ERR_NETWORK" || status.error?.response?.status === 404 || status.error?.message?.includes("Network")); const data = status.data; const desired = data?.desired; const queue = data?.queue; const children = data?.children; const httpOnline = Boolean(children?.httpOnline); useEffect(() => { if (!workerInit && desired?.workers != null) { setWorkerCount(desired.workers); setWorkerInit(true); } }, [desired?.workers, workerInit]); function run(label, mutateAsync, args) { const call = args === undefined ? mutateAsync() : mutateAsync(args); call .then((result) => { notify.success(`${label} ok`); return result; }) .catch((e) => notify.error(errorMessage(e, `${label} failed`))); } function scaleWorkers() { run("Scale", scale.mutateAsync, { workers: workerCount, force: false, }); } if (unavailable) { return (

Manage

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.
); } return (

Manage

{desired?.restartNeeded ? (
Restart needed (generation {desired.generation}) {desired.restartReason ? `: ${desired.restartReason}` : ""}. Use Drain restart after install/config changes.
) : null} {status.isLoading && !data ? ( ) : ( <>
run("HTTP start", httpStart.mutateAsync)} onStop={() => run("HTTP stop", httpStop.mutateAsync)} /> run("Pause", pause.mutateAsync)} onResume={() => run("Resume", resume.mutateAsync)} />
setWorkerCount((n) => Math.max(0, n - 1))} onIncreaseWorkers={() => setWorkerCount((n) => Math.min(MAX_WORKERS, n + 1)) } onScale={scaleWorkers} scalePending={scale.isPending} onReload={() => run("Reload", reload.mutateAsync)} reloadPending={reload.isPending} /> )}
); }