Letzte erfolgreiche Ping-Prüfung im System-Monitor-Modal anzeigen (Issue #57)

Die per PING ueberwachten lokalen Server (check_fds.sh -> status.txt ->
LegacyStatusFileReader -> SystemRepository) landen bereits als Kacheln im
System-Monitor, dem mittleren der drei Monitore an der Buero-Wand - das ist
exakt der "alte Monitor" aus dem Issue. GET /api/v1/systems liefert
last_check_at dafuer schon mit, das Klick-Modal hat den Wert aber bisher
weder abgeholt noch angezeigt.

buildSystemModalContent() (office-scene.js) formatiert last_check_at jetzt
ueber die neue formatSystemCheckTime() - sie kommt sowohl mit ISO-8601
(API-gemeldete Systeme) als auch mit dem "YYYY-MM-DD HH:MM"-Format der
Legacy-status.txt klar. renderSystemModalBody() (office-render.js) zeigt den
Zeitpunkt nur bei Status "ok" an ("zuletzt erfolgreich geprüft: ..."), da nur
dann Erreichbarkeit UND Ping-Erfolg zusammenfallen - bei "Fehler" bliebe eine
Zeitangabe ohne Aenderung an check_fds.sh irrefuehrend.

Bewusst nicht Teil dieser Aenderung: Die im Issue zusaetzlich genannte
Portscan-Liste (Detailseite fuer weitere, nur manuell zu pruefende Server)
ist als "koennten" markiert und ohne konkrete Vorgabe zu unscharf fuer eine
automatisierte Umsetzung. Die sechs konkret genannten Hosts/IPs gehoeren in
die lokale, nicht versionierte hosts.conf (Analogon zu credentials.conf) und
damit auf den Produktivserver, nicht in dieses oeffentliche Repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
fds2610-bot 2026-08-20 20:49:33 +02:00
parent 0228ee4ae6
commit c16c4b1e46
3 changed files with 54 additions and 2 deletions

View file

@ -1254,7 +1254,11 @@ function renderSystemModalBody(body, content) {
for (const item of content.items) {
const li = document.createElement("li");
li.className = `office-modal-item status-${item.status}`;
li.textContent = `${item.name} (${item.type}) – ${item.status === "ok" ? "OK" : "Fehler"}${item.detail ? `: ${item.detail}` : ""}`;
const isReachable = item.status === "ok";
const lastCheckText = isReachable && item.lastCheckAt
? ` · zuletzt erfolgreich geprüft: ${item.lastCheckAt}`
: "";
li.textContent = `${item.name} (${item.type}) – ${isReachable ? "OK" : "Fehler"}${item.detail ? `: ${item.detail}` : ""}${lastCheckText}`;
list.appendChild(li);
}
body.appendChild(list);

View file

@ -329,11 +329,38 @@ export function buildSystemModalContent(systems = []) {
status: system.status,
type: system.type || "host",
detail: system.detail || "",
lastCheckAt: system.last_check_at,
lastCheckAt: formatSystemCheckTime(system.last_check_at),
})),
};
}
/**
* Formatiert last_check_at eines Systems (GET /api/v1/systems, Issue #57: "wann der letzte
* erfolgreiche Ping war") fuers Modal. Zwei Quellformate kommen vor: ISO-8601 UTC (per API
* gemeldete Systeme) und "YYYY-MM-DD HH:MM" ohne Zeitzone (Legacy-Import aus status.txt,
* geschrieben von check_fds.sh in Serverzeit) - beide werden von `Date` geparst, fehlende/
* ungueltige Werte liefern `null` statt eines irrefuehrenden Zeitstempels.
*
* @param {string|null|undefined} value
* @returns {string|null}
*/
export function formatSystemCheckTime(value) {
if (!value) {
return null;
}
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return null;
}
return date.toLocaleString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
}
/**
* Erzeugt die Punkte-Liste (SVG <polyline points="...">) fuer eine einfache
* KPI-Sparkline - bewusst ohne Chart-Lib gezeichnet (F10). Die API liefert die Historie

View file

@ -20,6 +20,7 @@ import {
EVENT_LINE_CHAR_WIDTH,
buildEventModalContent,
buildSystemModalContent,
formatSystemCheckTime,
buildKpiSparklinePoints,
buildKpiTiles,
buildKpiModalContent,
@ -335,6 +336,26 @@ test("buildSystemModalContent uebernimmt Status/Detail je System", () => {
]);
assert.equal(content.items[0].status, "ok");
assert.equal(content.items[0].detail, "");
assert.equal(content.items[0].lastCheckAt, null);
});
test("buildSystemModalContent formatiert einen gueltigen last_check_at (Issue #57)", () => {
const content = buildSystemModalContent([
{ id: "srv1", name: "Webserver", status: "ok", type: "host", detail: "1.2.3.4", last_check_at: "2026-08-20 18:40" },
]);
assert.match(content.items[0].lastCheckAt, /^\d{2}\.\d{2}\.\d{2}, \d{2}:\d{2}$/);
});
test("formatSystemCheckTime akzeptiert ISO-8601 (API) und 'YYYY-MM-DD HH:MM' (Legacy-status.txt)", () => {
assert.match(formatSystemCheckTime("2026-07-27T10:00:00Z"), /^\d{2}\.\d{2}\.\d{2}, \d{2}:\d{2}$/);
assert.match(formatSystemCheckTime("2026-08-20 18:40"), /^\d{2}\.\d{2}\.\d{2}, \d{2}:\d{2}$/);
});
test("formatSystemCheckTime liefert null fuer fehlende/ungueltige Werte", () => {
assert.equal(formatSystemCheckTime(null), null);
assert.equal(formatSystemCheckTime(undefined), null);
assert.equal(formatSystemCheckTime(""), null);
assert.equal(formatSystemCheckTime("nicht-valide"), null);
});
test("buildKpiSparklinePoints liefert leeren String ohne Historie und einen Punkt pro Messwert sonst", () => {