-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat(console): improve observer monitor formatting and readability #1193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,6 +111,8 @@ const elements = { | |
| systemBtn: document.getElementById("systemBtn"), | ||
| observerBtn: document.getElementById("observerBtn"), | ||
| monitorResults: document.getElementById("monitorResults"), | ||
| monitorDashboard: document.getElementById("monitorDashboard"), | ||
| monitorRefreshBtn: document.getElementById("monitorRefreshBtn"), | ||
| navToggleBtn: document.getElementById("navToggleBtn"), | ||
| resultToggleBtn: document.getElementById("resultToggleBtn"), | ||
| clearOutputBtn: document.getElementById("clearOutputBtn"), | ||
|
|
@@ -2431,31 +2433,179 @@ function bindTenants() { | |
| }); | ||
| } | ||
|
|
||
| function parseAsciiTable(text) { | ||
| if (!text || typeof text !== "string") return null; | ||
| const lines = text.split("\n").filter(l => l.trim()); | ||
| const dataLines = lines.filter(l => !l.match(/^[+\-]+$/)); | ||
| if (dataLines.length < 2) return null; | ||
| const splitRow = row => | ||
| row.split("|").slice(1, -1).map(c => c.trim()); | ||
| const headers = splitRow(dataLines[0]); | ||
| const rows = dataLines.slice(1).map(splitRow); | ||
| return { headers, rows }; | ||
| } | ||
|
|
||
| function renderMonitorTable(parsed) { | ||
| if (!parsed) return document.createDocumentFragment(); | ||
| const table = document.createElement("table"); | ||
| const thead = table.createTHead(); | ||
| const headerRow = thead.insertRow(); | ||
| for (const h of parsed.headers) { | ||
| const th = document.createElement("th"); | ||
| th.textContent = h; | ||
| headerRow.appendChild(th); | ||
| } | ||
| const tbody = table.createTBody(); | ||
| for (const row of parsed.rows) { | ||
| const tr = tbody.insertRow(); | ||
| const isTotal = row.some(c => /^TOTAL$/i.test(c)); | ||
| if (isTotal) tr.className = "total-row"; | ||
| for (const cell of row) { | ||
| const td = tr.insertCell(); | ||
| td.textContent = cell; | ||
| } | ||
| } | ||
| return table; | ||
| } | ||
|
|
||
| function renderComponentCard(name, comp) { | ||
| const healthy = comp.is_healthy !== false; | ||
| const friendlyNames = { | ||
| queue: "Processing Queue", | ||
| vikingdb: "Vector Database", | ||
| vlm: "Language Model", | ||
| lock: "Active Locks", | ||
| retrieval: "Retrieval Stats", | ||
| }; | ||
|
|
||
| const card = document.createElement("div"); | ||
| card.className = "monitor-card"; | ||
|
|
||
| const header = document.createElement("div"); | ||
| header.className = "monitor-card-header"; | ||
| const dot = document.createElement("span"); | ||
| dot.className = `health-dot ${healthy ? "ok" : "error"}`; | ||
| const h3 = document.createElement("h3"); | ||
| h3.textContent = friendlyNames[name] || name; | ||
| header.appendChild(dot); | ||
| header.appendChild(h3); | ||
| card.appendChild(header); | ||
|
|
||
| const statusText = comp.status || ""; | ||
| const tables = statusText.split("\n\n").filter(Boolean); | ||
| for (const tableText of tables) { | ||
| const parsed = parseAsciiTable(tableText); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Bug] (blocking) |
||
| if (parsed) { | ||
| card.appendChild(renderMonitorTable(parsed)); | ||
| } else { | ||
| const plain = document.createElement("div"); | ||
| plain.className = "plain-text"; | ||
| plain.textContent = tableText; | ||
| card.appendChild(plain); | ||
| } | ||
| } | ||
| return card; | ||
| } | ||
|
|
||
| function renderSystemStatus(result) { | ||
| const dashboard = elements.monitorDashboard; | ||
| dashboard.replaceChildren(); | ||
| if (!result) return; | ||
|
|
||
| const card = document.createElement("div"); | ||
| card.className = "monitor-card"; | ||
|
|
||
| const header = document.createElement("div"); | ||
| header.className = "monitor-card-header"; | ||
| const dot = document.createElement("span"); | ||
| dot.className = `health-dot ${result.initialized ? "ok" : "error"}`; | ||
| const h3 = document.createElement("h3"); | ||
| h3.textContent = "System"; | ||
| header.appendChild(dot); | ||
| header.appendChild(h3); | ||
| card.appendChild(header); | ||
|
|
||
| const grid = document.createElement("div"); | ||
| grid.className = "kv-grid"; | ||
| for (const [k, v] of Object.entries(result)) { | ||
| const label = document.createElement("span"); | ||
| label.className = "kv-label"; | ||
| label.textContent = k; | ||
| const value = document.createElement("span"); | ||
| value.className = "kv-value"; | ||
| value.textContent = typeof v === "string" ? v : JSON.stringify(v); | ||
| grid.appendChild(label); | ||
| grid.appendChild(value); | ||
| } | ||
| card.appendChild(grid); | ||
| dashboard.appendChild(card); | ||
| } | ||
|
|
||
| function renderObserverDashboard(result) { | ||
| const dashboard = elements.monitorDashboard; | ||
| dashboard.replaceChildren(); | ||
| if (!result?.components) return; | ||
|
|
||
| const summary = document.createElement("div"); | ||
| summary.style.cssText = "margin-bottom:12px;display:flex;align-items:center;gap:12px;font-size:13px;color:var(--muted)"; | ||
| for (const [name, comp] of Object.entries(result.components)) { | ||
| const dot = document.createElement("span"); | ||
| dot.className = `health-dot ${comp.is_healthy !== false ? "ok" : "error"}`; | ||
| const label = document.createTextNode(` ${name} `); | ||
| summary.appendChild(dot); | ||
| summary.appendChild(label); | ||
| } | ||
| dashboard.appendChild(summary); | ||
|
|
||
| const order = ["queue", "vikingdb", "vlm", "retrieval", "lock"]; | ||
| const sorted = order.filter(n => result.components[n]); | ||
| for (const name of Object.keys(result.components)) { | ||
| if (!sorted.includes(name)) sorted.push(name); | ||
| } | ||
| for (const name of sorted) { | ||
| dashboard.appendChild(renderComponentCard(name, result.components[name])); | ||
| } | ||
| } | ||
|
|
||
| let monitorRefreshInterval = null; | ||
|
|
||
| function bindMonitor() { | ||
| elements.systemBtn.addEventListener("click", async () => { | ||
| try { | ||
| const payload = await callConsole("/ov/system/status", { method: "GET" }); | ||
| const rows = Object.entries(payload.result || {}).map(([key, value]) => ({ | ||
| label: `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`, | ||
| })); | ||
| renderList(elements.monitorResults, rows); | ||
| elements.monitorResults.innerHTML = ""; | ||
| renderSystemStatus(payload.result); | ||
| setOutput(payload); | ||
| } catch (error) { | ||
| setOutput(error.message); | ||
| } | ||
| }); | ||
|
|
||
| elements.observerBtn.addEventListener("click", async () => { | ||
| async function loadObserver() { | ||
| try { | ||
| const payload = await callConsole("/ov/observer/system", { method: "GET" }); | ||
| const rows = Object.entries(payload.result?.components || {}).map(([name, value]) => ({ | ||
| label: `${name}: ${value?.status || JSON.stringify(value)}`, | ||
| })); | ||
| renderList(elements.monitorResults, rows); | ||
| elements.monitorResults.innerHTML = ""; | ||
| renderObserverDashboard(payload.result); | ||
| setOutput(payload); | ||
| } catch (error) { | ||
| setOutput(error.message); | ||
| } | ||
| } | ||
|
|
||
| elements.observerBtn.addEventListener("click", loadObserver); | ||
|
|
||
| elements.monitorRefreshBtn.addEventListener("click", () => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Bug] (blocking) Once auto-refresh is enabled, nothing turns it off when the user leaves the Monitor panel or switches to the System Status view. |
||
| if (monitorRefreshInterval) { | ||
| clearInterval(monitorRefreshInterval); | ||
| monitorRefreshInterval = null; | ||
| elements.monitorRefreshBtn.textContent = "Auto-refresh: OFF"; | ||
| elements.monitorRefreshBtn.classList.remove("active"); | ||
| } else { | ||
| loadObserver(); | ||
| monitorRefreshInterval = setInterval(loadObserver, 10000); | ||
| elements.monitorRefreshBtn.textContent = "Auto-refresh: ON"; | ||
| elements.monitorRefreshBtn.classList.add("active"); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Bug] (blocking) This code assumes the backend component key is
vlm, butObserverService.system()actually returnsmodels(queue,vikingdb,models,lock,retrieval). As a result, this card keeps the rawmodelslabel and also falls out of the intended order because bothfriendlyNamesand theorderarray are keyed onvlm. Please align the frontend mapping with the actual response contract.