Add custom homelab dashboard stack

This commit is contained in:
2026-04-05 13:43:03 +02:00
parent 450a04a7d3
commit 89b9173c25
38 changed files with 3539 additions and 0 deletions
@@ -0,0 +1,87 @@
function formatTime(now) {
return new Intl.DateTimeFormat("de-DE", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
}).format(now);
}
function formatDate(now) {
return new Intl.DateTimeFormat("de-DE", {
weekday: "long",
day: "2-digit",
month: "short",
year: "numeric",
}).format(now).toUpperCase();
}
function statusTone(status) {
if (status === "offline") return "offline";
if (status === "degraded") return "warning";
return "online";
}
function statusLabel(status) {
if (status === "offline") return "OFFLINE";
if (status === "degraded") return "DEGRADED";
return "NOMINAL";
}
export function renderHeader(state) {
const now = new Date();
const { overview, services } = state.data;
const refreshNode = document.getElementById("last-refresh");
const overallTile = document.getElementById("overall-status-tile");
const overallLabel = document.getElementById("overall-status-label");
const overallSummary = document.getElementById("overall-status-summary");
const haTile = document.getElementById("home-assistant-tile");
const haLabel = document.getElementById("ha-status-label");
const haSummary = document.getElementById("ha-status-summary");
const heroCopy = document.getElementById("hero-copy");
document.getElementById("clock-time").textContent = formatTime(now);
document.getElementById("clock-date").textContent = formatDate(now);
if (refreshNode) {
if (state.lastRefreshAt) {
refreshNode.textContent = `LAST REFRESH ${new Intl.DateTimeFormat("de-DE", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}).format(state.lastRefreshAt)} CET`;
} else {
refreshNode.textContent = "LAST REFRESH PENDING";
}
refreshNode.classList.toggle("api-error", Boolean(state.error));
}
const overallTone = statusTone(overview.overall_status);
overallTile.className = "status-tile";
overallTile.style.borderColor = "";
overallLabel.textContent = statusLabel(overview.overall_status);
overallLabel.className = "";
overallSummary.textContent =
`${overview.services.online} services online, ${overview.services.degraded} degraded, ${overview.services.offline} offline`;
overallTile.classList.toggle("api-error", Boolean(state.error));
overallLabel.classList.add(`value-${overallTone === "warning" ? "warning" : overallTone === "offline" ? "danger" : "online"}`);
const haTone = statusTone(overview.home_assistant.status);
haLabel.textContent = overview.home_assistant.status === "online" ? "ONLINE" : "OFFLINE";
haLabel.className = "";
haLabel.classList.add(`value-${haTone === "warning" ? "warning" : haTone === "offline" ? "danger" : "online"}`);
haSummary.textContent =
overview.home_assistant.status === "online"
? `Version ${overview.home_assistant.version ?? "unknown"} / ${overview.home_assistant.response_time_ms ?? "n/a"} ms`
: "Basisstatus derzeit nicht erreichbar";
haTile.className = "status-tile";
if (state.error) {
heroCopy.textContent = `Aggregator API zuletzt fehlerhaft erreichbar. Letzte gueltige Daten bleiben sichtbar. Fehler: ${state.error.message}`;
} else {
heroCopy.textContent =
`Live angebundenes MVP-v1 Dashboard. ${services.summary.docker.running} Container running, ` +
`${services.summary.uptime_kuma.monitors_up} Kuma Monitors up, Home Assistant ${overview.home_assistant.status}.`;
}
}