Add Network Design Studio with multi-U Dell rack designer.
Includes rack catalog/API, Visio-style drag-and-drop, faceplate assets, readable nameplates, and theme/RDP UI updates so multi-U placement stays correct during design. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -757,6 +757,7 @@
|
||||
<div class="kpi-actions">
|
||||
<button type="button" class="btn primary" data-kpi-act="focus">Focus on map</button>
|
||||
<button type="button" class="btn conn-link" data-kpi-act="ssh" ${node.ip ? "" : "disabled"}>SSH terminal</button>
|
||||
<button type="button" class="btn conn-link" data-kpi-act="rdp" ${node.ip ? "" : "disabled"}>RDP popout</button>
|
||||
<button type="button" class="btn" data-kpi-act="connect">Quick Connect</button>
|
||||
<button type="button" class="btn" data-kpi-act="inventory">Inspector + inventory</button>
|
||||
<button type="button" class="btn" data-kpi-act="chat">Ask Cockpit chat</button>
|
||||
@@ -1480,7 +1481,13 @@
|
||||
<span class="ct-t">SSH terminal</span>
|
||||
<span class="ct-s">Login with your username · ${escapeHtml(ip)}</span>
|
||||
</button>` : ""}
|
||||
${ip ? `<button type="button" class="connect-tile conn-link" id="btn-modal-rdp">
|
||||
<span class="ct-ico">▭</span>
|
||||
<span class="ct-t">RDP popout</span>
|
||||
<span class="ct-s">${node.rdp_host ? `OS ${escapeHtml(node.rdp_host)}` : node.os_hostname ? `Resolve ${escapeHtml(node.os_hostname)}` : `Edit host · ${escapeHtml(ip)}`}${node.is_windows ? " · Windows" : ""}</span>
|
||||
</button>` : ""}
|
||||
<button type="button" class="connect-tile" id="btn-modal-ai">
|
||||
|
||||
<span class="ct-ico">✦</span>
|
||||
<span class="ct-t">Ask AI</span>
|
||||
<span class="ct-s">OpenManage AI about this node</span>
|
||||
@@ -1512,6 +1519,11 @@
|
||||
window.cockpitSsh?.open(node);
|
||||
return;
|
||||
}
|
||||
if (e.target.closest("#btn-modal-rdp")) {
|
||||
closeConnect();
|
||||
window.cockpitRdp?.open(node);
|
||||
return;
|
||||
}
|
||||
if (e.target.closest("#btn-modal-ai")) {
|
||||
closeConnect();
|
||||
openAi(node);
|
||||
@@ -1583,7 +1595,47 @@
|
||||
updateFocusContext();
|
||||
}
|
||||
|
||||
async function loadWarrantyCompliance(deviceId) {
|
||||
async function loadExpansionPreview(deviceId) {
|
||||
const mount = $("#warranty-compliance-mount");
|
||||
if (!mount) return;
|
||||
try {
|
||||
const r = await fetch(`/api/devices/${deviceId}/expansion`);
|
||||
if (!r.ok) return;
|
||||
const data = await r.json();
|
||||
const x = data.expansion || {};
|
||||
const mem = x.memory || {};
|
||||
const spec = x.spec || {};
|
||||
const findings = (x.findings || []).filter((f) => f.severity === "warning").slice(0, 2);
|
||||
const box = document.createElement("div");
|
||||
box.className = "inv-section";
|
||||
box.innerHTML = `<h3>Memory / expansion</h3>
|
||||
<div class="inv-line">${mem.total_gb ?? "—"} GB in ${mem.populated ?? 0} DIMMs · ${mem.empty ?? 0} empty slots · Dell max ${spec.max_memory_tb != null ? spec.max_memory_tb + " TB" : "—"}</div>
|
||||
<div class="inv-line">Disks ${x.disks?.installed ?? "—"} / ${spec.drive_bays_max ?? "—"} bays · PCIe max ${spec.pcie_slots_max ?? "—"}</div>
|
||||
${findings.map((f) => `<div class="inv-line">${escapeHtml(f.text)}</div>`).join("")}
|
||||
<div class="inv-line"><button type="button" class="btn compact" id="btn-open-hw">Open full DIMM map & Dell guidance</button></div>`;
|
||||
mount.appendChild(box);
|
||||
$("#btn-open-hw")?.addEventListener("click", () => {
|
||||
window.cockpitReports?.open?.();
|
||||
// slight delay for drawer open
|
||||
setTimeout(() => {
|
||||
if (window.cockpitReports) {
|
||||
window.cockpitReports.state = window.cockpitReports.state || {};
|
||||
}
|
||||
const st = document.querySelector('#reports-tabs [data-tab="hardware"]');
|
||||
st?.click();
|
||||
// set device after tab loads
|
||||
setTimeout(() => {
|
||||
const btn = document.querySelector(`[data-hw-id="${deviceId}"]`);
|
||||
btn?.click();
|
||||
}, 400);
|
||||
}, 200);
|
||||
});
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
async function loadWarrantyCompliance(deviceId) {
|
||||
const mount = $("#warranty-compliance-mount");
|
||||
if (!mount) return;
|
||||
try {
|
||||
@@ -1681,6 +1733,7 @@
|
||||
<button type="button" class="btn primary" id="btn-quick-connect">Quick Connect · iDRAC / SSH</button>
|
||||
${node.idrac_url ? `<a class="btn conn-link" href="${escapeAttr(node.idrac_url)}" target="_blank" rel="noopener">iDRAC Web</a>` : ""}
|
||||
${node.ip ? `<button type="button" class="btn conn-link" id="btn-ssh-term">SSH terminal · ${escapeHtml(node.ip)}</button>` : ""}
|
||||
${node.ip ? `<button type="button" class="btn conn-link" id="btn-rdp-term">RDP popout · ${escapeHtml(node.rdp_host || node.os_hostname || node.ip)}</button>` : ""}
|
||||
<button type="button" class="btn" id="btn-detail">Load full inventory + app landscape</button>
|
||||
<button type="button" class="btn" id="btn-ask-ai">Ask OpenManage AI</button>
|
||||
<button type="button" class="btn ghost" id="btn-focus-model">Filter this model</button>
|
||||
@@ -1715,8 +1768,10 @@
|
||||
updateFocusContext();
|
||||
$("#btn-quick-connect")?.addEventListener("click", () => openConnect(node));
|
||||
$("#btn-ssh-term")?.addEventListener("click", () => window.cockpitSsh?.open(node));
|
||||
$("#btn-rdp-term")?.addEventListener("click", () => window.cockpitRdp?.open(node));
|
||||
$("#btn-ask-ai")?.addEventListener("click", () => openAi(node));
|
||||
loadWarrantyCompliance(node.id);
|
||||
loadExpansionPreview(node.id);
|
||||
const cachedInv = state.inventoryCache[node.id];
|
||||
if (cachedInv) {
|
||||
const mount = $("#inv-mount");
|
||||
@@ -2061,6 +2116,8 @@
|
||||
showToast("Focused " + (node.name || ""));
|
||||
} else if (a === "ssh") {
|
||||
window.cockpitSsh?.open(node);
|
||||
} else if (a === "rdp") {
|
||||
window.cockpitRdp?.open(node);
|
||||
} else if (a === "connect") {
|
||||
closeKpiPopup();
|
||||
openConnect(node);
|
||||
@@ -2238,6 +2295,25 @@
|
||||
}
|
||||
});
|
||||
|
||||
function applyTheme(theme) {
|
||||
const t = theme === "dark" ? "dark" : "light";
|
||||
document.documentElement.setAttribute("data-theme", t);
|
||||
try {
|
||||
localStorage.setItem("cockpit_theme", t);
|
||||
} catch (_) {}
|
||||
const btn = $("#btn-theme");
|
||||
if (btn) {
|
||||
btn.dataset.theme = t;
|
||||
btn.title = t === "light" ? "Switch to dark theme" : "Switch to light theme";
|
||||
btn.setAttribute("aria-label", btn.title);
|
||||
}
|
||||
}
|
||||
applyTheme(localStorage.getItem("cockpit_theme") || "light");
|
||||
$("#btn-theme")?.addEventListener("click", () => {
|
||||
const cur = document.documentElement.getAttribute("data-theme") || "light";
|
||||
applyTheme(cur === "light" ? "dark" : "light");
|
||||
});
|
||||
|
||||
$("#btn-reset-view").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
Reference in New Issue
Block a user