Add VLAN inventory, Present decks, and network fabric mapping.

Seed all ATC/FDE VLANs with live host inventory and editable notes, fix cluster membership on the map, and ship Present/network UI polish.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
root
2026-07-18 21:56:30 +02:00
parent 91222faffe
commit 1e87f22006
15 changed files with 7587 additions and 382 deletions
+469 -36
View File
@@ -375,41 +375,153 @@
return [...map.entries()].sort((a, b) => b[1] - a[1]);
}
function barRows(entries, maxN, fillClass) {
function barRows(entries, maxN, fillClass, kind) {
const top = entries.slice(0, maxN);
const max = Math.max(1, ...top.map(([, n]) => n));
return top
.map(
([label, n]) => `<div class="an-bar-row">
<div class="an-bar-label" title="${escapeHtml(label)}">${escapeHtml(label)}</div>
([label, n]) => `<button type="button" class="an-bar-row an-clickable" data-an-kind="${escapeHtml(kind || "bar")}" data-an-key="${escapeHtml(label)}" data-an-count="${n}" title="Open context for ${escapeHtml(label)}">
<div class="an-bar-label">${escapeHtml(label)}</div>
<div class="an-bar-track"><div class="an-bar-fill ${fillClass || ""}" style="width:${Math.round((n / max) * 100)}%"></div></div>
<div class="an-bar-val">${n}</div>
</div>`
</button>`
)
.join("");
}
function donut(slices) {
function donut(slices, chartId) {
const total = slices.reduce((s, x) => s + x.value, 0) || 1;
let acc = 0;
const parts = slices.map((s) => {
const parts = [];
const meta = [];
slices.forEach((s) => {
const start = (acc / total) * 100;
acc += s.value;
const end = (acc / total) * 100;
return `${s.color} ${start}% ${end}%`;
parts.push(`${s.color} ${start}% ${end}%`);
meta.push({
key: s.key || s.label,
label: s.label,
value: s.value,
color: s.color,
startPct: start,
endPct: end,
});
});
const legend = slices
const legend = meta
.map(
(s) =>
`<li><span class="an-swatch" style="background:${s.color}"></span>${escapeHtml(s.label)} <strong>${s.value}</strong></li>`
`<li><button type="button" class="an-legend-btn an-clickable" data-an-kind="${escapeHtml(chartId || "donut")}" data-an-key="${escapeHtml(s.key)}" data-an-count="${s.value}" title="Open context: ${escapeHtml(s.label)}">
<span class="an-swatch" style="background:${s.color}"></span>
<span class="an-legend-text">${escapeHtml(s.label)}</span>
<strong>${s.value}</strong>
</button></li>`
)
.join("");
return `<div class="an-donut-wrap">
<div class="an-donut" style="--slices:${parts.join(", ")}"></div>
return `<div class="an-donut-wrap" data-an-chart="${escapeHtml(chartId || "")}" data-an-meta="${encodeURIComponent(JSON.stringify(meta))}">
<button type="button" class="an-donut an-donut-hit an-clickable" data-an-kind="${escapeHtml(chartId || "donut")}" data-an-donut="1" style="--slices:${parts.join(", ")}" title="Click a colored segment for context" aria-label="Chart ${escapeHtml(chartId || "")}"></button>
<ul class="an-legend">${legend}</ul>
</div>`;
}
function closeAnContext() {
const modal = $("#an-context-modal");
if (!modal) return;
modal.classList.add("hidden");
modal.setAttribute("aria-hidden", "true");
const scrim = $("#scrim");
if (scrim?.dataset.mode === "an-context") {
// keep reports drawer open underneath
if ($("#reports-drawer")?.classList.contains("open")) {
scrim.dataset.mode = "reports-drawer";
scrim.classList.add("open");
} else {
scrim.classList.remove("open");
delete scrim.dataset.mode;
}
}
}
function openAnContext({ title, sub, pills, rows, actions }) {
const modal = $("#an-context-modal");
if (!modal) return;
$("#an-ctx-title").textContent = title || "Context";
$("#an-ctx-sub").textContent = sub || "";
$("#an-ctx-pills").innerHTML = (pills || [])
.map((p) => `<span class="pill">${escapeHtml(p)}</span>`)
.join("");
const list = $("#an-ctx-list");
if (!rows?.length) {
list.innerHTML = `<p class="hint">No matching records in the current OME snapshot.</p>`;
} else {
list.innerHTML = `
<table class="an-ctx-table">
<thead><tr>${(rows[0].cols || []).map((c) => `<th>${escapeHtml(c)}</th>`).join("")}</tr></thead>
<tbody>
${rows
.map((r) => {
const cells = (r.cells || []).map((c) => `<td>${c}</td>`).join("");
const attr = r.jumpHw ? ` class="an-row-click" data-jump-hw="${r.jumpHw}"` : "";
return `<tr${attr}>${cells}</tr>`;
})
.join("")}
</tbody>
</table>
${rows.length >= 80 ? `<p class="an-note">Showing first ${rows.length} rows.</p>` : ""}`;
}
$("#an-ctx-actions").innerHTML = (actions || [])
.map(
(a) =>
`<button type="button" class="btn ${a.primary ? "primary" : "compact"}" data-an-action="${escapeHtml(a.id)}">${escapeHtml(a.label)}</button>`
)
.join("");
modal.classList.remove("hidden");
modal.setAttribute("aria-hidden", "false");
const scrim = $("#scrim");
if (scrim) {
scrim.classList.add("open");
scrim.dataset.mode = "an-context";
}
$("#an-ctx-close")?.addEventListener("click", closeAnContext, { once: true });
$("#an-ctx-actions")?.querySelectorAll("[data-an-action]").forEach((btn) => {
btn.addEventListener("click", () => {
const id = btn.dataset.anAction;
const act = (actions || []).find((x) => x.id === id);
if (act?.run) act.run();
closeAnContext();
});
});
list.querySelectorAll("[data-jump-hw]").forEach((el) => {
el.addEventListener("click", () => {
const id = Number(el.dataset.jumpHw);
if (!id) return;
closeAnContext();
state.hwDeviceId = id;
loadTab("hardware");
});
});
}
function sliceFromDonutClick(btn, evt) {
const wrap = btn.closest(".an-donut-wrap");
let meta = [];
try {
meta = JSON.parse(decodeURIComponent(wrap?.dataset.anMeta || "%5B%5D"));
} catch (_) {
meta = [];
}
if (!meta.length) return null;
const rect = btn.getBoundingClientRect();
const cx = rect.left + rect.width / 2;
const cy = rect.top + rect.height / 2;
const dx = evt.clientX - cx;
const dy = evt.clientY - cy;
let deg = (Math.atan2(dx, -dy) * 180) / Math.PI;
if (deg < 0) deg += 360;
const pct = (deg / 360) * 100;
return meta.find((s) => pct >= s.startPct && pct < s.endPct) || meta[meta.length - 1];
}
function renderAnalytics(mount) {
const fleet = state.fleet?.rows || [];
const sum = state.fleet?.summary || state.brief?.summary || {};
@@ -501,45 +613,55 @@
<div class="an-grid">
<section class="an-card">
<h3 title="Share of component compliance statuses from the Dell Online Baseline">Compliance mix</h3>
${donut([
{ label: "Critical", value: critComps || Number(byStatus.find(([k]) => k === "CRITICAL")?.[1] || 0), color: "#ff5c5c" },
{ label: "Warning", value: warnComps || Number(byStatus.find(([k]) => k === "WARNING")?.[1] || 0), color: "#ffb84d" },
{
label: "Other / OK",
value: Math.max(0, comps.length - critComps - warnComps),
color: "#3dffe0",
},
])}
<p class="an-note">${upgradeComps} components marked UPGRADE against Dell catalog</p>
${donut(
[
{ key: "CRITICAL", label: "Critical", value: critComps || Number(byStatus.find(([k]) => k === "CRITICAL")?.[1] || 0), color: "#ff5c5c" },
{ key: "WARNING", label: "Warning", value: warnComps || Number(byStatus.find(([k]) => k === "WARNING")?.[1] || 0), color: "#ffb84d" },
{
key: "OK",
label: "Other / OK",
value: Math.max(0, comps.length - critComps - warnComps),
color: "#3dffa0",
},
],
"compliance"
)}
<p class="an-note an-clickable" data-an-kind="compliance" data-an-key="UPGRADE" data-an-count="${upgradeComps}" title="Show all UPGRADE components">${upgradeComps} components marked UPGRADE against Dell catalog — click for list</p>
</section>
<section class="an-card">
<h3 title="Which firmware families need the most upgrades">Outdated by component</h3>
<div class="an-bars">${barRows(byComponent, 8, "crit") || '<p class="an-note">No upgrade components</p>'}</div>
<div class="an-bars">${barRows(byComponent, 8, "crit", "component") || '<p class="an-note">No upgrade components</p>'}</div>
</section>
<section class="an-card">
<h3 title="Which server models appear most often in the non-compliant set">Outdated by model</h3>
<div class="an-bars">${barRows(byModel, 8, "warn") || '<p class="an-note">No outdated models</p>'}</div>
<div class="an-bars">${barRows(byModel, 8, "warn", "model") || '<p class="an-note">No outdated models</p>'}</div>
</section>
<section class="an-card">
<h3 title="OME ConnectionState across the fleet snapshot">Connection state</h3>
${donut([
{ label: "Connected", value: connected, color: "#3dffe0" },
{ label: "Offline", value: offline, color: "#5a6a7a" },
])}
${donut(
[
{ key: "connected", label: "Connected", value: connected, color: "#3dffa0" },
{ key: "offline", label: "Offline", value: offline, color: "#e53935" },
],
"connection"
)}
<p class="an-note">${escapeHtml(String(sum.total_watts != null ? Math.round(sum.total_watts) + " W sampled" : "Power samples from live poll"))}</p>
</section>
<section class="an-card">
<h3 title="Days remaining buckets from OME WarrantyService">Warranty health</h3>
${donut([
{ label: "Expired", value: expired, color: "#ff5c5c" },
{ label: "≤ 30 days", value: d30, color: "#ffb84d" },
{ label: "3190 days", value: d90, color: "#00a8e8" },
{ label: "> 90 days", value: okW, color: "#3dffe0" },
])}
${donut(
[
{ key: "expired", label: "Expired", value: expired, color: "#ff5c5c" },
{ key: "d30", label: "≤ 30 days", value: d30, color: "#ffb84d" },
{ key: "d90", label: "3190 days", value: d90, color: "#ff9a3c" },
{ key: "ok", label: "> 90 days", value: okW, color: "#3dffa0" },
],
"warranty"
)}
<p class="an-note">${warranties.length} warranty records from OME</p>
</section>
@@ -579,9 +701,318 @@
state.filters.severity = "critical";
loadTab("firmware");
});
function ctxRowsFromComps(list) {
return list.slice(0, 80).map((c) => ({
cols: ["ST", "Device", "Component", "Current → Catalog", "Status"],
cells: [
`<code class="st">${escapeHtml(c.service_tag || "—")}</code>`,
escapeHtml(c.device_name || "—"),
escapeHtml(shortComponent(c.component)),
`<span class="mono">${escapeHtml(c.current_version || "?")}${escapeHtml(c.catalog_version || "?")}</span>`,
badge(c.compliance_status || c.update_action),
],
jumpHw: c.device_id || null,
}));
}
function ctxRowsFromDevices(list) {
return list.slice(0, 80).map((d) => ({
cols: ["ST", "Name", "Model", "Status"],
cells: [
`<code class="st">${escapeHtml(d.service_tag || "—")}</code>`,
escapeHtml(d.name || d.device_name || "—"),
escapeHtml(d.model || "—"),
badge(d.compliance_status || d.firmware_status || (d.connected ? "connected" : "offline")),
],
jumpHw: d.device_id || d.id || null,
}));
}
function ctxRowsFromFleet(list) {
return list.slice(0, 80).map((d) => ({
cols: ["ST", "Name", "Model", "iDRAC", "State"],
cells: [
`<code class="st">${escapeHtml(d.service_tag || "—")}</code>`,
escapeHtml(d.name || "—"),
escapeHtml(d.model || "—"),
`<span class="mono">${escapeHtml(d.idrac_ip || d.ip || "—")}</span>`,
d.connected
? `<span class="rpt-badge ok">online</span>`
: `<span class="rpt-badge bad">offline</span>`,
],
jumpHw: d.id || d.device_id || null,
}));
}
function ctxRowsFromWarranty(list) {
return list.slice(0, 80).map((w) => ({
cols: ["ST", "Device", "Level", "Ends", "Days left"],
cells: [
`<code class="st">${escapeHtml(w.service_tag || "—")}</code>`,
escapeHtml(w.device_name || "—"),
escapeHtml(w.service_level || "—"),
escapeHtml(w.end_date || "—"),
String(intOr(w.days_remaining)),
],
jumpHw: w.device_id || null,
}));
}
function handleAnContext(kind, key, count, evt) {
let title = key;
let sub = "";
let pills = [`${count ?? "?"} items`];
let rows = [];
let actions = [];
if (kind === "compliance") {
if (key === "UPGRADE") {
const list = comps.filter((c) => String(c.update_action || "").toUpperCase() === "UPGRADE");
title = "UPGRADE components";
sub = "Dell Online Baseline · update_action = UPGRADE";
rows = ctxRowsFromComps(list);
pills = [`${list.length} components`, `baseline ${cs.baseline_name || "—"}`];
actions = [
{
id: "fw",
label: "Open Firmware / BIOS",
primary: true,
run: () => {
state.outdatedOnly = true;
loadTab("firmware");
},
},
];
} else {
const list =
key === "OK"
? comps.filter((c) => {
const s = String(c.compliance_status || "").toUpperCase();
return s !== "CRITICAL" && s !== "WARNING";
})
: comps.filter((c) => String(c.compliance_status || "").toUpperCase() === key);
title = `Compliance · ${key}`;
sub = "Component compliance from Dell catalog baseline";
rows = ctxRowsFromComps(list);
pills = [`${list.length} components`, key];
actions = [
{
id: "fw",
label: "Open Firmware tab",
primary: true,
run: () => {
state.outdatedOnly = key !== "OK";
state.filters.severity = key === "CRITICAL" ? "critical" : key === "WARNING" ? "warning" : "all";
loadTab("firmware");
},
},
];
}
} else if (kind === "component") {
const list = comps.filter(
(c) =>
String(c.update_action || "").toUpperCase() === "UPGRADE" && shortComponent(c.component) === key
);
title = `Outdated · ${key}`;
sub = "Components marked UPGRADE in this firmware family";
rows = ctxRowsFromComps(list);
pills = [`${list.length} components`, key];
actions = [
{
id: "fw",
label: "Open Firmware filtered",
primary: true,
run: () => {
state.outdatedOnly = true;
state.filters.component = key;
loadTab("firmware");
},
},
];
} else if (kind === "model") {
const list = devices.filter(
(d) =>
(d.model || "Unknown") === key &&
(String(d.compliance_status || "").toUpperCase() === "CRITICAL" ||
String(d.firmware_status || "").toLowerCase().includes("non-compliant"))
);
title = `Outdated · ${key}`;
sub = "Non-compliant systems for this model";
rows = ctxRowsFromDevices(list);
pills = [`${list.length} systems`, key];
actions = [
{
id: "fw",
label: "Open Firmware / BIOS",
primary: true,
run: () => {
state.outdatedOnly = true;
state.filters.model = key;
loadTab("firmware");
},
},
];
} else if (kind === "connection") {
const list = fleet.filter((d) => (key === "connected" ? d.connected : !d.connected));
title = key === "connected" ? "Connected systems" : "Offline systems";
sub = "OME ConnectionState from live fleet snapshot";
rows = ctxRowsFromFleet(list);
pills = [`${list.length} devices`, key === "connected" ? "online" : "offline"];
actions = [
{
id: "fleet",
label: "Open Fleet inventory",
primary: true,
run: () => {
state.filters.connected = key === "connected" ? "yes" : "no";
loadTab("fleet");
},
},
];
} else if (kind === "warranty") {
const list = warranties.filter((w) => {
const d = intOr(w.days_remaining);
if (key === "expired") return d <= 0;
if (key === "d30") return d > 0 && d <= 30;
if (key === "d90") return d > 30 && d <= 90;
return d > 90;
});
const labels = { expired: "Expired", d30: "≤ 30 days", d90: "3190 days", ok: "> 90 days" };
title = `Warranty · ${labels[key] || key}`;
sub = "OME WarrantyService days_remaining buckets";
rows = ctxRowsFromWarranty(list);
pills = [`${list.length} records`, labels[key] || key];
actions = [
{
id: "war",
label: "Open Warranty tab",
primary: true,
run: () => {
state.filters.warranty =
key === "expired" ? "expired" : key === "d30" ? "d30" : key === "d90" ? "d90" : "ok";
loadTab("warranty");
},
},
];
} else if (kind === "kpi") {
// handled separately
return;
} else {
title = `${kind} · ${key}`;
sub = "Analytics selection";
}
openAnContext({ title, sub, pills, rows, actions });
}
mount.querySelectorAll("[data-an-kind]").forEach((el) => {
el.addEventListener("click", (evt) => {
evt.preventDefault();
evt.stopPropagation();
let kind = el.dataset.anKind;
let key = el.dataset.anKey;
let count = el.dataset.anCount;
if (el.dataset.anDonut === "1") {
const hit = sliceFromDonutClick(el, evt);
if (!hit) return;
key = hit.key;
count = hit.value;
}
if (!key) return;
handleAnContext(kind, key, count, evt);
});
});
mount.querySelectorAll("[data-jump]").forEach((el) => {
el.addEventListener("click", () => {
el.addEventListener("click", (evt) => {
evt.preventDefault();
const jump = el.dataset.jump;
// KPI → context popup first
if (el.classList.contains("an-kpi")) {
let kind = "connection";
let key = "connected";
if (el.dataset.conn === "all") {
openAnContext({
title: "Fleet devices",
sub: "Full OME fleet snapshot",
pills: [`${fleet.length} devices`, `${connected} online`, `${offline} offline`],
rows: ctxRowsFromFleet(fleet),
actions: [
{
id: "fleet",
label: "Open Fleet inventory",
primary: true,
run: () => {
state.filters.connected = "all";
loadTab("fleet");
},
},
],
});
return;
}
if (el.dataset.conn === "yes") {
handleAnContext("connection", "connected", connected);
return;
}
if (el.dataset.conn === "no") {
handleAnContext("connection", "offline", offline);
return;
}
if (el.dataset.sev === "critical") {
const label = (el.querySelector("span")?.textContent || "").toLowerCase();
if (label.includes("component")) {
handleAnContext("compliance", "CRITICAL", critComps);
} else {
const list = devices.filter(
(d) =>
String(d.compliance_status || "").toUpperCase() === "CRITICAL" ||
String(d.firmware_status || "").toLowerCase().includes("non-compliant")
);
openAnContext({
title: "Outdated vs Dell",
sub: "Systems non-compliant against Dell Online Baseline",
pills: [`${list.length} systems`, `baseline ${cs.baseline_name || "—"}`],
rows: ctxRowsFromDevices(list),
actions: [
{
id: "fw",
label: "Open Firmware / BIOS",
primary: true,
run: () => {
state.outdatedOnly = true;
state.filters.severity = "critical";
loadTab("firmware");
},
},
],
});
}
return;
}
if (el.dataset.war) {
const list = warranties.filter((w) => {
const d = intOr(w.days_remaining);
return d <= 30;
});
openAnContext({
title: "Warranty risk (≤30d / expired)",
sub: "OME warranty records needing attention",
pills: [`${list.length} records`, `${expired} expired`, `${d30} ≤30d`],
rows: ctxRowsFromWarranty(list),
actions: [
{
id: "war",
label: "Open Warranty tab",
primary: true,
run: () => {
state.filters.warranty = "expired";
loadTab("warranty");
},
},
],
});
return;
}
}
if (el.dataset.conn) state.filters.connected = el.dataset.conn;
if (el.dataset.sev) {
state.filters.severity = el.dataset.sev;
@@ -1386,13 +1817,15 @@
loadTab(chip.dataset.tab);
});
$("#scrim")?.addEventListener("click", () => {
if ($("#scrim")?.dataset.mode === "reports-drawer") closeReports();
const mode = $("#scrim")?.dataset.mode;
if (mode === "an-context") closeAnContext();
else if (mode === "reports-drawer") closeReports();
});
// Extend ops closeDrawers awareness via custom event
window.addEventListener("cockpit-close-drawers", closeReports);
}
window.cockpitReports = { open: openReports, close: closeReports, loadTab };
window.cockpitReports = { open: openReports, close: closeReports, loadTab, closeAnContext };
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
else init();
})();