Add admin-only service status panel with restart, wired to Docker directly

This commit is contained in:
Myron Blair
2026-07-26 17:53:19 -05:00
parent 19374b694e
commit 4d77a36f99
6 changed files with 137 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
import { useEffect, useState, useCallback } from "react";
import api from "../lib/api";
import { toast } from "sonner";
import { RotateCw } from "lucide-react";
export default function ServiceStatus() {
const [services, setServices] = useState([]);
const [restarting, setRestarting] = useState(null);
const load = useCallback(async () => {
try {
const { data } = await api.get("/services");
setServices(data);
} catch {
setServices([]);
}
}, []);
useEffect(() => {
load();
const t = setInterval(load, 15000);
return () => clearInterval(t);
}, [load]);
const restart = async (key, label) => {
setRestarting(key);
try {
await api.post(`/services/${key}/restart`);
toast.success(`${label} restarting…`);
setTimeout(load, 4000);
} catch (err) {
toast.error(err.response?.data?.detail || `Could not restart ${label}`);
} finally {
setRestarting(null);
}
};
if (!services.length) return null;
return (
<div className="px-6 md:px-12 pt-6" data-testid="service-status">
<div className="flex flex-wrap gap-2">
{services.map((s) => (
<div
key={s.key}
className="flex items-center gap-2 bg-[#0F0F0F] border border-[#222] px-3 py-1.5 text-xs"
data-testid={`service-${s.key}`}
>
<span
className={`inline-block w-2 h-2 rounded-full ${s.running ? "bg-[#86efac]" : "bg-[#fca5a5]"}`}
/>
<span className="text-[#8A8A8A]">{s.label}</span>
{!s.running && (
<button
onClick={() => restart(s.key, s.label)}
disabled={restarting === s.key}
className="ml-1 flex items-center gap-1 text-[#D9381E] hover:text-[#ED4B32] disabled:opacity-50"
data-testid={`service-restart-${s.key}`}
>
<RotateCw size={12} className={restarting === s.key ? "animate-spin" : ""} />
Restart
</button>
)}
</div>
))}
</div>
</div>
);
}