126 lines
4.1 KiB
TypeScript
126 lines
4.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { fetchOverview, fetchHistory, type ModuleStatus, type HistoryEntry } from "@/lib/api";
|
|
|
|
const POLL_INTERVAL_MS = 5000;
|
|
|
|
export default function Page() {
|
|
const [modules, setModules] = useState<ModuleStatus[]>([]);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [selected, setSelected] = useState<string | null>(null);
|
|
const [history, setHistory] = useState<HistoryEntry[] | null>(null);
|
|
const [historyError, setHistoryError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
async function poll() {
|
|
try {
|
|
const data = await fetchOverview();
|
|
if (!cancelled) {
|
|
setModules(data);
|
|
setError(null);
|
|
}
|
|
} catch (e: any) {
|
|
// Ein nicht antwortendes Backend darf die zuletzt bekannte Ansicht
|
|
// nicht loeschen und die Seite nicht unbedienbar machen
|
|
// (Akzeptanzkriterium 2) — nur eine Fehlermeldung anzeigen, alte
|
|
// Daten bleiben sichtbar.
|
|
if (!cancelled) {
|
|
setError(e.message ?? "Unbekannter Fehler beim Laden der Uebersicht");
|
|
}
|
|
}
|
|
}
|
|
|
|
poll();
|
|
const id = setInterval(poll, POLL_INTERVAL_MS);
|
|
return () => {
|
|
cancelled = true;
|
|
clearInterval(id);
|
|
};
|
|
}, []);
|
|
|
|
async function openHistory(name: string) {
|
|
setSelected(name);
|
|
setHistoryError(null);
|
|
try {
|
|
const data = await fetchHistory(name);
|
|
setHistory(data);
|
|
} catch (e: any) {
|
|
setHistoryError(e.message ?? "Verlauf konnte nicht geladen werden");
|
|
setHistory(null);
|
|
}
|
|
}
|
|
|
|
const anyDown = modules.some((m) => m.status === "down");
|
|
|
|
return (
|
|
<main style={{ maxWidth: 800, margin: "0 auto", padding: "2rem 1rem" }}>
|
|
<h1>Systemstatus</h1>
|
|
|
|
{error && (
|
|
<p style={{ color: "#c62828", background: "#fdecea", padding: "0.5rem", borderRadius: 4 }} role="alert">
|
|
Uebersicht konnte gerade nicht aktualisiert werden: {error}. Zuletzt bekannter Stand wird weiter angezeigt.
|
|
</p>
|
|
)}
|
|
|
|
{!error && anyDown && (
|
|
<p style={{ color: "#c62828", background: "#fdecea", padding: "0.5rem", borderRadius: 4 }}>
|
|
Mindestens ein Modul ist derzeit nicht erreichbar.
|
|
</p>
|
|
)}
|
|
|
|
<ul style={{ listStyle: "none", padding: 0 }}>
|
|
{modules.map((m) => (
|
|
<li
|
|
key={m.name}
|
|
style={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
alignItems: "center",
|
|
background: "white",
|
|
padding: "0.75rem 1rem",
|
|
marginBottom: "0.5rem",
|
|
borderRadius: 8,
|
|
borderLeft: `6px solid ${m.status === "up" ? "#2e7d32" : "#c62828"}`,
|
|
}}
|
|
>
|
|
<div>
|
|
<strong>{m.name}</strong>
|
|
<div style={{ fontSize: "0.85rem", color: "#666" }}>
|
|
{m.status === "up" ? "Verfügbar" : "Nicht verfügbar"}
|
|
{m.last_checked && ` — zuletzt geprüft ${new Date(m.last_checked).toLocaleString("de-DE")}`}
|
|
</div>
|
|
</div>
|
|
<button onClick={() => openHistory(m.name)}>Verlauf</button>
|
|
</li>
|
|
))}
|
|
{modules.length === 0 && !error && <li>Lade Modulstatus…</li>}
|
|
</ul>
|
|
|
|
{selected && (
|
|
<section style={{ background: "white", padding: "1rem", borderRadius: 8, marginTop: "1.5rem" }}>
|
|
<h2>Verlauf: {selected}</h2>
|
|
{historyError && (
|
|
<p style={{ color: "#c62828" }} role="alert">
|
|
{historyError}
|
|
</p>
|
|
)}
|
|
{history && (
|
|
<ul>
|
|
{history.map((h, i) => (
|
|
<li key={i}>
|
|
{new Date(h.changed_at).toLocaleString("de-DE")} — {h.status === "up" ? "verfügbar" : "nicht verfügbar"}
|
|
</li>
|
|
))}
|
|
{history.length === 0 && <li>Keine Statusänderungen bisher.</li>}
|
|
</ul>
|
|
)}
|
|
<button onClick={() => setSelected(null)}>Schließen</button>
|
|
</section>
|
|
)}
|
|
</main>
|
|
);
|
|
}
|