Replace stale Grafana hero with live operations panel
This commit is contained in:
+25
-4
@@ -119,11 +119,32 @@
|
||||
|
||||
<div class="operations-window">
|
||||
<div class="window-bar">
|
||||
<span>GRAFANA / BYTEGEIST OPERATIONS</span>
|
||||
<a href="assets/images/portfolio-grafana-operations.png" target="_blank">FULL RES ↗</a>
|
||||
<span><i class="live-dot"></i> BYTEGEIST / LIVE OPERATIONS</span>
|
||||
<small id="ops-source">PROMETHEUS / NODE EXPORTER / CADVISOR</small>
|
||||
</div>
|
||||
<img src="assets/images/portfolio-grafana-operations.png" alt="Grafana operations dashboard snapshot from the ByteGeist monitoring stack" />
|
||||
<p class="evidence-note">CAPTURED OCT 2026 · GRAFANA OPERATIONS SNAPSHOT · LIVE VALUES ABOVE REFRESH EVERY 15s</p>
|
||||
<div class="live-ops-panel" aria-label="Live ByteGeist operations telemetry">
|
||||
<div class="ops-chart">
|
||||
<div class="ops-chart-head">
|
||||
<span>CPU LOAD</span>
|
||||
<strong id="ops-cpu-value">--%</strong>
|
||||
</div>
|
||||
<div id="ops-cpu-bars" class="ops-bars" aria-hidden="true"></div>
|
||||
</div>
|
||||
<div class="ops-chart">
|
||||
<div class="ops-chart-head">
|
||||
<span>MEMORY</span>
|
||||
<strong id="ops-memory-value">--%</strong>
|
||||
</div>
|
||||
<div id="ops-memory-bars" class="ops-bars" aria-hidden="true"></div>
|
||||
</div>
|
||||
<div class="ops-status-grid">
|
||||
<div><span>HOST UPTIME</span><strong id="ops-uptime-value">--</strong></div>
|
||||
<div><span>CONTAINERS</span><strong id="ops-containers-value">--</strong></div>
|
||||
<div><span>SCRAPE TARGETS</span><strong id="ops-targets-value">--</strong></div>
|
||||
<div><span>REFRESH</span><strong>15s</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="evidence-note">LIVE OPERATIONS VIEW · VALUES COME FROM THE SAME SANITIZED /API/STATUS TELEMETRY FEED</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,6 +8,16 @@ const fields = {
|
||||
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"),
|
||||
};
|
||||
|
||||
const telemetryHistory = {
|
||||
cpu: [],
|
||||
memory: [],
|
||||
};
|
||||
|
||||
let lastSuccessfulUpdate = null;
|
||||
@@ -41,17 +51,56 @@ function setUnavailable() {
|
||||
}
|
||||
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 = "--";
|
||||
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 renderStatus(data) {
|
||||
fields.cpu.textContent = Number(data.host.cpu_percent).toFixed(1);
|
||||
fields.memory.textContent = Number(data.host.memory_percent).toFixed(1);
|
||||
fields.uptime.textContent = formatUptime(data.host.uptime_seconds);
|
||||
fields.containers.textContent = String(data.containers.running);
|
||||
fields.targets.textContent = data.prometheus.up + "/" + data.prometheus.total;
|
||||
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;
|
||||
|
||||
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");
|
||||
|
||||
|
||||
@@ -460,6 +460,94 @@ h1 {
|
||||
border: 1px solid #20232a;
|
||||
}
|
||||
|
||||
.live-ops-panel {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1px;
|
||||
border: 1px solid #20232a;
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
.ops-chart,
|
||||
.ops-status-grid {
|
||||
background: #070a0f;
|
||||
}
|
||||
|
||||
.ops-chart {
|
||||
min-height: 150px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.ops-chart-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ops-chart-head span,
|
||||
.ops-status-grid span {
|
||||
color: var(--dim);
|
||||
font: .56rem var(--mono);
|
||||
letter-spacing: .08em;
|
||||
}
|
||||
|
||||
.ops-chart-head strong {
|
||||
color: var(--green);
|
||||
font: 500 1.1rem var(--mono);
|
||||
}
|
||||
|
||||
.ops-bars {
|
||||
height: 86px;
|
||||
margin-top: 18px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
border-bottom: 1px solid #26313c;
|
||||
background:
|
||||
linear-gradient(to top, transparent 24%, #121820 25%, transparent 26%, transparent 49%, #121820 50%, transparent 51%, transparent 74%, #121820 75%, transparent 76%);
|
||||
}
|
||||
|
||||
.ops-bars i {
|
||||
flex: 1 1 0;
|
||||
min-width: 3px;
|
||||
height: var(--bar-height, 4%);
|
||||
background: var(--green);
|
||||
opacity: .72;
|
||||
transition: height .25s ease;
|
||||
}
|
||||
|
||||
.ops-chart:nth-child(2) .ops-bars i {
|
||||
background: var(--cyan);
|
||||
}
|
||||
|
||||
.ops-status-grid {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1px;
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
.ops-status-grid div {
|
||||
min-height: 70px;
|
||||
padding: 13px 16px;
|
||||
background: #080c12;
|
||||
}
|
||||
|
||||
.ops-status-grid strong {
|
||||
display: block;
|
||||
margin-top: 9px;
|
||||
color: var(--muted);
|
||||
font: 500 .82rem var(--mono);
|
||||
}
|
||||
|
||||
.window-bar small {
|
||||
color: var(--dim);
|
||||
font: .52rem var(--mono);
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
|
||||
.activity-panel {
|
||||
padding: 24px 18px;
|
||||
border-left: 1px solid var(--line);
|
||||
@@ -1191,6 +1279,19 @@ footer {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.live-ops-panel {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.ops-status-grid {
|
||||
grid-column: 1;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.window-bar small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.metric {
|
||||
min-height: 112px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user