diff --git a/html/js/office-render.js b/html/js/office-render.js
index 0674439..c32a0e3 100644
--- a/html/js/office-render.js
+++ b/html/js/office-render.js
@@ -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);
diff --git a/html/js/office-scene.js b/html/js/office-scene.js
index d686766..ff531ee 100644
--- a/html/js/office-scene.js
+++ b/html/js/office-scene.js
@@ -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 ) fuer eine einfache
* KPI-Sparkline - bewusst ohne Chart-Lib gezeichnet (F10). Die API liefert die Historie
diff --git a/tests/frontend/office-scene.test.js b/tests/frontend/office-scene.test.js
index 431eb06..8620fc8 100644
--- a/tests/frontend/office-scene.test.js
+++ b/tests/frontend/office-scene.test.js
@@ -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", () => {