Files
2026-10-02 11:20:13 -04:00

195 lines
6.6 KiB
JavaScript

const livePanelStyles=document.createElement("link");livePanelStyles.rel="stylesheet";livePanelStyles.href="live-panels.css";document.head.appendChild(livePanelStyles);
const timeElement = document.querySelector("#local-time");
const refreshElement = document.querySelector("#refresh-indicator");
const fields = {
cpu: document.querySelector("#cpu-value"),
memory: document.querySelector("#memory-value"),
uptime: document.querySelector("#uptime-value"),
containers: document.querySelector("#containers-value"),
targets: document.querySelector("#targets-value"),
telemetry: document.querySelector("#telemetry-value"),
opsCpu: document.querySelector("#ops-cpu-value"),
opsMemory: document.querySelector("#ops-memory-value"),
opsUptime: document.querySelector("#ops-uptime-value"),
opsContainers: document.querySelector("#ops-containers-value"),
opsTargets: document.querySelector("#ops-targets-value"),
healthGitea: document.querySelector("#health-gitea"),
healthWoodpecker: document.querySelector("#health-woodpecker"),
healthKuma: document.querySelector("#health-kuma"),
healthPrometheus: document.querySelector("#health-prometheus"),
pipelineGiteaState: document.querySelector("#pipeline-gitea-state"),
pipelineWoodpeckerState: document.querySelector("#pipeline-woodpecker-state"),
deployedCommit: document.querySelector("#deployed-commit"),
};
const telemetryHistory = {
cpu: [],
memory: [],
};
let lastSampledAt = null;
let requestInFlight = false;
function updateClock() {
const now = new Date();
timeElement.textContent = new Intl.DateTimeFormat("en-US", {
timeZone: "America/Detroit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}).format(now);
}
function formatUptime(totalSeconds) {
const seconds = Math.max(0, Number(totalSeconds) || 0);
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) return days + "d " + hours + "h";
if (hours > 0) return hours + "h " + minutes + "m";
return minutes + "m";
}
function setUnavailable() {
for (const key of ["cpu", "memory", "uptime", "containers", "targets"]) {
fields[key].textContent = "--";
}
fields.telemetry.textContent = "UNAVAILABLE";
fields.telemetry.classList.add("metric-error");
fields.opsCpu.textContent = "--%";
fields.opsMemory.textContent = "--%";
fields.opsUptime.textContent = "--";
fields.opsContainers.textContent = "--";
fields.opsTargets.textContent = "--";
for (const element of [
fields.healthGitea,
fields.healthWoodpecker,
fields.healthKuma,
fields.healthPrometheus,
fields.pipelineGiteaState,
fields.pipelineWoodpeckerState,
]) {
if (element) {
element.textContent = "UNAVAILABLE";
element.classList.add("status-down");
}
}
fields.deployedCommit.textContent = "-------";
refreshElement.textContent = "TELEMETRY UNAVAILABLE";
refreshElement.classList.add("telemetry-error");
}
function renderOpsBars(elementId, values) {
const container = document.querySelector(elementId);
if (!container) return;
container.replaceChildren();
values.forEach((value) => {
const bar = document.createElement("i");
bar.style.setProperty("--bar-height", Math.max(4, Math.min(100, value)) + "%");
container.appendChild(bar);
});
}
function pushHistory(series, value) {
series.push(value);
if (series.length > 24) series.shift();
}
function setServiceState(element, service) {
if (!element) return;
const up = Boolean(service && service.up);
element.textContent = up ? "ONLINE" : "UNAVAILABLE";
element.classList.toggle("status-down", !up);
}
function renderStatus(data) {
const cpu = Number(data.host.cpu_percent);
const memory = Number(data.host.memory_percent);
const uptime = formatUptime(data.host.uptime_seconds);
const containers = String(data.containers.running);
const targets = data.prometheus.up + "/" + data.prometheus.total;
fields.cpu.textContent = cpu.toFixed(1);
fields.memory.textContent = memory.toFixed(1);
fields.uptime.textContent = uptime;
fields.containers.textContent = containers;
fields.targets.textContent = targets;
fields.telemetry.textContent = "LIVE";
fields.opsCpu.textContent = cpu.toFixed(1) + "%";
fields.opsMemory.textContent = memory.toFixed(1) + "%";
fields.opsUptime.textContent = uptime;
fields.opsContainers.textContent = containers;
fields.opsTargets.textContent = targets;
const services = data.services || {};
setServiceState(fields.healthGitea, services.gitea);
setServiceState(fields.healthWoodpecker, services.woodpecker);
setServiceState(fields.healthKuma, services.uptime_kuma);
setServiceState(fields.healthPrometheus, services.prometheus);
setServiceState(fields.pipelineGiteaState, services.gitea);
setServiceState(fields.pipelineWoodpeckerState, services.woodpecker);
fields.deployedCommit.textContent = data.deployment && data.deployment.commit
? data.deployment.commit
: "-------";
pushHistory(telemetryHistory.cpu, cpu);
pushHistory(telemetryHistory.memory, memory);
renderOpsBars("#ops-cpu-bars", telemetryHistory.cpu);
renderOpsBars("#ops-memory-bars", telemetryHistory.memory);
fields.telemetry.classList.remove("metric-error");
refreshElement.classList.remove("telemetry-error");
lastSampledAt = Number(data.sampled_at) * 1000;
updateRefreshLabel();
}
function updateRefreshLabel() {
if (!lastSampledAt) return;
const ageSeconds = Math.max(0, Math.floor((Date.now() - lastSampledAt) / 1000));
const stale = ageSeconds > 45;
refreshElement.textContent = (stale ? "TELEMETRY STALE | SAMPLE " : "LIVE TELEMETRY | SAMPLE ")
+ String(ageSeconds).padStart(2, "0") + "s AGO";
refreshElement.classList.toggle("telemetry-error", stale);
fields.telemetry.textContent = stale ? "STALE" : "LIVE";
fields.telemetry.classList.toggle("metric-error", stale);
}
async function refreshTelemetry() {
if (requestInFlight) return;
requestInFlight = true;
try {
const response = await fetch("/api/status", {
cache: "no-store",
headers: { "Accept": "application/json" },
});
if (!response.ok) throw new Error("status " + response.status);
const data = await response.json();
if (!data.ok) throw new Error("telemetry unavailable");
renderStatus(data);
} catch (error) {
console.error("ByteGeist telemetry request failed:", error);
setUnavailable();
} finally {
requestInFlight = false;
}
}
updateClock();
refreshTelemetry();
window.setInterval(updateClock, 1000);
window.setInterval(updateRefreshLabel, 1000);
window.setInterval(refreshTelemetry, 15000);