mirror of
https://github.com/myronblair/kino-app
synced 2026-07-27 21:18:43 -05:00
Add admin-only service status panel with restart, wired to Docker directly
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import api from "../lib/api";
|
||||
import { useAuth } from "../lib/auth";
|
||||
import Hero from "../components/Hero";
|
||||
import Row from "../components/Row";
|
||||
import MovieDetailModal from "../components/MovieDetailModal";
|
||||
import ServiceStatus from "../components/ServiceStatus";
|
||||
|
||||
export default function Browse() {
|
||||
const nav = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const [featured, setFeatured] = useState(null);
|
||||
const [movies, setMovies] = useState([]);
|
||||
const [genres, setGenres] = useState([]);
|
||||
@@ -57,6 +60,7 @@ export default function Browse() {
|
||||
)}
|
||||
|
||||
<div className="-mt-20 relative z-10 pb-24">
|
||||
{user?.is_admin && <ServiceStatus />}
|
||||
{continueWatching.length > 0 && (
|
||||
<Row title="Resume" movies={continueWatching} onCardClick={handleMore} progressMap={progressMap} />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user