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
status.refetch()}
>
Refresh
{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}
/>
>
)}
);
}