Refine dashboard card icons and storage layout
This commit is contained in:
@@ -1,84 +1,69 @@
|
||||
export function renderNetworkHealth(state) {
|
||||
_renderAdGuard(state.adguard || {});
|
||||
_renderScrutiny(state.scrutiny || {});
|
||||
renderAdGuard(state.adguard || {});
|
||||
renderScrutiny(state.scrutiny || {});
|
||||
}
|
||||
|
||||
function _renderAdGuard(d) {
|
||||
const online = d.source_status === "online";
|
||||
function renderAdGuard(data) {
|
||||
const online = data.source_status === "online";
|
||||
setPill("adguard-pill", online ? "ONLINE" : "OFFLINE", online ? "pill-online" : "pill-offline");
|
||||
|
||||
const pill = document.getElementById("adguard-pill");
|
||||
if (pill) {
|
||||
pill.textContent = online ? "ONLINE" : "OFFLINE";
|
||||
pill.className = "status-pill " + (online ? "pill-online" : "pill-offline");
|
||||
}
|
||||
setText("adguard-total", online ? fmtCompact(data.total_queries) : "\u2014");
|
||||
setText("adguard-blocked", online ? fmtCompact(data.blocked_queries) : "\u2014");
|
||||
setText("adguard-blocked-pct", online ? `${Math.round(data.blocked_percent ?? 0)}%` : "\u2014");
|
||||
setText("adguard-latency", online ? `${Math.round(data.avg_processing_ms ?? 0)}ms` : "\u2014");
|
||||
|
||||
const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
|
||||
|
||||
if (online) {
|
||||
set("adguard-total", fmtK(d.total_queries));
|
||||
set("adguard-blocked", fmtK(d.blocked_queries));
|
||||
set("adguard-blocked-pct", `${d.blocked_percent ?? 0}%`);
|
||||
set("adguard-latency", `${d.avg_processing_ms ?? 0}ms`);
|
||||
|
||||
const bar = document.getElementById("adguard-bar-fill");
|
||||
if (bar) bar.style.width = `${Math.min(d.blocked_percent ?? 0, 100)}%`;
|
||||
} else {
|
||||
["adguard-total", "adguard-blocked", "adguard-blocked-pct", "adguard-latency"].forEach(id => set(id, "—"));
|
||||
const bar = document.getElementById("adguard-bar-fill");
|
||||
if (bar) bar.style.width = "0%";
|
||||
const fill = document.getElementById("adguard-bar-fill");
|
||||
if (fill) {
|
||||
fill.style.width = `${online ? Math.min(data.blocked_percent ?? 0, 100) : 0}%`;
|
||||
}
|
||||
}
|
||||
|
||||
function _renderScrutiny(d) {
|
||||
const online = d.source_status === "online";
|
||||
function renderScrutiny(data) {
|
||||
const online = data.source_status === "online";
|
||||
setPill("scrutiny-pill", online ? "ONLINE" : "OFFLINE", online ? "pill-online" : "pill-offline");
|
||||
|
||||
const pill = document.getElementById("scrutiny-pill");
|
||||
if (pill) {
|
||||
pill.textContent = online ? "ONLINE" : "OFFLINE";
|
||||
pill.className = "status-pill " + (online ? "pill-online" : "pill-offline");
|
||||
}
|
||||
const total = data.total_count ?? 0;
|
||||
const failed = data.failed_count ?? 0;
|
||||
const passed = Math.max(total - failed, 0);
|
||||
|
||||
// stat blocks
|
||||
const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
|
||||
if (online) {
|
||||
set("scrutiny-total", d.total_count ?? 0);
|
||||
set("scrutiny-failed", d.failed_count ?? 0);
|
||||
const passedEl = document.getElementById("scrutiny-passed");
|
||||
if (passedEl) {
|
||||
passedEl.textContent = (d.total_count ?? 0) - (d.failed_count ?? 0);
|
||||
}
|
||||
} else {
|
||||
set("scrutiny-total", "—");
|
||||
set("scrutiny-failed", "—");
|
||||
set("scrutiny-passed", "—");
|
||||
}
|
||||
setText("scrutiny-total", online ? total : "\u2014");
|
||||
setText("scrutiny-passed", online ? passed : "\u2014");
|
||||
setText("scrutiny-failed", online ? failed : "\u2014");
|
||||
|
||||
// disk list
|
||||
const list = document.getElementById("scrutiny-list");
|
||||
if (!list) return;
|
||||
|
||||
if (!online || !d.devices || d.devices.length === 0) {
|
||||
list.innerHTML = `<div class="scrutiny-offline">— ${online ? "no devices" : "offline"}</div>`;
|
||||
const devices = Array.isArray(data.devices) ? data.devices.slice(0, 3) : [];
|
||||
if (!online || devices.length === 0) {
|
||||
list.innerHTML = `<div class="scrutiny-offline">\u2014 ${online ? "no disks" : "offline"}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = d.devices.map(dev => {
|
||||
const ok = dev.status === "passed";
|
||||
const icon = ok ? "✓" : dev.status === "failed" ? "✗" : "?";
|
||||
const cls = ok ? "disk-ok" : dev.status === "failed" ? "disk-fail" : "disk-unk";
|
||||
const temp = dev.temperature != null ? `<span class="disk-temp">${dev.temperature}°C</span>` : "";
|
||||
return `
|
||||
<div class="scrutiny-row">
|
||||
<span class="disk-icon ${cls}">${icon}</span>
|
||||
<span class="disk-name">${dev.name}</span>
|
||||
<span class="disk-model">${dev.model}</span>
|
||||
${temp}
|
||||
</div>`;
|
||||
}).join("");
|
||||
list.innerHTML = `<div class="scrutiny-strip">${devices.map((device) => {
|
||||
const status = device.status || "unknown";
|
||||
const cls = status === "passed" ? "ok" : status === "failed" ? "fail" : "unk";
|
||||
const token = status === "passed" ? "OK" : status === "failed" ? "ER" : "--";
|
||||
const name = device.name || device.device || "disk";
|
||||
|
||||
return `<span class="scrutiny-chip ${cls}"><strong>${token}</strong>${name}</span>`;
|
||||
}).join("")}</div>`;
|
||||
}
|
||||
|
||||
function fmtK(n) {
|
||||
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M";
|
||||
if (n >= 1_000) return (n / 1_000).toFixed(0) + "K";
|
||||
return String(n ?? 0);
|
||||
function setText(id, value) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = value;
|
||||
}
|
||||
|
||||
function setPill(id, label, cls) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.textContent = label;
|
||||
el.className = `status-pill ${cls}`;
|
||||
}
|
||||
|
||||
function fmtCompact(value) {
|
||||
const num = Number(value ?? 0);
|
||||
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`;
|
||||
if (num >= 1_000) return `${Math.round(num / 1_000)}K`;
|
||||
return `${num}`;
|
||||
}
|
||||
|
||||
@@ -3,26 +3,84 @@ export function renderStorage(state) {
|
||||
const grid = document.getElementById("storage-grid");
|
||||
if (!grid) return;
|
||||
|
||||
const root = storage.root || storage.disks?.[0] || null;
|
||||
const disks = storage.disks || [];
|
||||
if (!disks.length) {
|
||||
grid.innerHTML = '<div class="card" style="opacity:0.5; font-family:var(--font-mono); font-size:10px; color:var(--text-dim); padding:12px;">No disk data</div>';
|
||||
return;
|
||||
}
|
||||
const rootPct = root?.usage_percent ?? 0;
|
||||
const rootTone = pickTone(rootPct);
|
||||
const warningCount = disks.filter((disk) => between(disk.usage_percent, 70, 85)).length;
|
||||
const criticalCount = disks.filter((disk) => (disk.usage_percent ?? 0) > 85).length;
|
||||
const highest = disks.length
|
||||
? disks.reduce((current, disk) => ((disk.usage_percent ?? 0) > (current.usage_percent ?? 0) ? disk : current), disks[0])
|
||||
: null;
|
||||
const matrixPill = criticalCount > 0 ? "pill-offline" : warningCount > 0 ? "pill-degraded" : "pill-online";
|
||||
const strip = disks.length
|
||||
? disks.slice(0, 4).map((disk) => {
|
||||
const pct = disk.usage_percent ?? 0;
|
||||
return `<span class="storage-chip" style="color:${pickColor(pct)}">${disk.name || disk.mount} ${pct.toFixed(0)}%</span>`;
|
||||
}).join("")
|
||||
: '<span class="storage-chip">No disk data</span>';
|
||||
|
||||
grid.innerHTML = disks.map(disk => {
|
||||
const pct = disk.usage_percent ?? 0;
|
||||
const fillClass = pct > 85 ? "danger" : pct > 70 ? "warn" : "";
|
||||
const statusColor = disk.status === "critical" ? "var(--red)" : disk.status === "warning" ? "var(--yellow)" : "var(--teal)";
|
||||
return `
|
||||
<div class="card">
|
||||
<div class="disk-header">
|
||||
<span class="disk-name">${disk.name || disk.mount}</span>
|
||||
<span class="disk-usage" style="color:${statusColor}">${pct.toFixed(1)}%</span>
|
||||
grid.innerHTML = `
|
||||
<div class="card service-card storage-card storage-primary">
|
||||
<div class="card-title">
|
||||
<div class="card-title-left">
|
||||
<span class="service-icon icon-storage"><span class="icon-glyph glyph-storage"></span></span>
|
||||
<span class="service-name">ROOT STORAGE</span>
|
||||
</div>
|
||||
<div class="disk-sub">${disk.mount} · ${disk.used_gb?.toFixed(1)}/${disk.total_gb?.toFixed(1)} GB</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill ${fillClass}" style="width:${Math.min(pct,100)}%"></div>
|
||||
<span class="status-pill ${statusPill(root?.status)}">${(root?.status || "stable").toUpperCase()}</span>
|
||||
</div>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-block"><div class="stat-num ${rootTone}">${root ? `${rootPct.toFixed(1)}%` : "\u2014"}</div><div class="stat-label">Usage</div></div>
|
||||
<div class="stat-block"><div class="stat-num dim">${root ? fmtNum(root.used_gb) : "\u2014"}</div><div class="stat-label">Used GB</div></div>
|
||||
<div class="stat-block"><div class="stat-num dim">${root ? fmtNum(root.free_gb) : "\u2014"}</div><div class="stat-label">Free GB</div></div>
|
||||
</div>
|
||||
<div class="progress-wrap">
|
||||
<div class="progress-meta"><span>${root?.mount || "/"}</span><span>${root ? `${rootPct.toFixed(1)}%` : "0%"}</span></div>
|
||||
<div class="progress-bar"><div class="progress-fill ${rootTone}" style="width:${Math.min(100, rootPct)}%"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card service-card storage-card storage-matrix-card">
|
||||
<div class="card-title">
|
||||
<div class="card-title-left">
|
||||
<span class="service-icon icon-matrix"><span class="icon-glyph glyph-matrix"></span></span>
|
||||
<span class="service-name">DISK MATRIX</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join("");
|
||||
<span class="status-pill ${matrixPill}">${disks.length}</span>
|
||||
</div>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-block"><div class="stat-num">${disks.length || "\u2014"}</div><div class="stat-label">Volumes</div></div>
|
||||
<div class="stat-block"><div class="stat-num warn">${warningCount}</div><div class="stat-label">Warning</div></div>
|
||||
<div class="stat-block"><div class="stat-num danger">${criticalCount}</div><div class="stat-label">Critical</div></div>
|
||||
<div class="stat-block"><div class="stat-num ${pickTone(highest?.usage_percent ?? 0)}">${highest ? `${(highest.usage_percent ?? 0).toFixed(0)}%` : "\u2014"}</div><div class="stat-label">Peak</div></div>
|
||||
</div>
|
||||
<div class="service-footer"><span>Mounted Volumes</span><div class="storage-strip">${strip}</div></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function fmtNum(value) {
|
||||
return value == null ? "\u2014" : Number(value).toFixed(1);
|
||||
}
|
||||
|
||||
function between(value, min, max) {
|
||||
const num = value ?? 0;
|
||||
return num > min && num <= max;
|
||||
}
|
||||
|
||||
function pickTone(pct) {
|
||||
if (pct > 85) return "danger";
|
||||
if (pct > 70) return "warn";
|
||||
return "";
|
||||
}
|
||||
|
||||
function pickColor(pct) {
|
||||
if (pct > 85) return "var(--red)";
|
||||
if (pct > 70) return "var(--yellow)";
|
||||
return "var(--teal-bright)";
|
||||
}
|
||||
|
||||
function statusPill(status) {
|
||||
if (status === "critical") return "pill-offline";
|
||||
if (status === "warning") return "pill-degraded";
|
||||
return "pill-online";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user