@@ -111,6 +111,8 @@ const elements = {
111111 systemBtn : document . getElementById ( "systemBtn" ) ,
112112 observerBtn : document . getElementById ( "observerBtn" ) ,
113113 monitorResults : document . getElementById ( "monitorResults" ) ,
114+ monitorDashboard : document . getElementById ( "monitorDashboard" ) ,
115+ monitorRefreshBtn : document . getElementById ( "monitorRefreshBtn" ) ,
114116 navToggleBtn : document . getElementById ( "navToggleBtn" ) ,
115117 resultToggleBtn : document . getElementById ( "resultToggleBtn" ) ,
116118 clearOutputBtn : document . getElementById ( "clearOutputBtn" ) ,
@@ -2431,31 +2433,139 @@ function bindTenants() {
24312433 } ) ;
24322434}
24332435
2436+ function parseAsciiTable ( text ) {
2437+ if ( ! text || typeof text !== "string" ) return null ;
2438+ const lines = text . split ( "\n" ) . filter ( l => l . trim ( ) ) ;
2439+ const dataLines = lines . filter ( l => ! l . match ( / ^ [ + \- ] + $ / ) ) ;
2440+ if ( dataLines . length < 2 ) return null ;
2441+ const splitRow = row =>
2442+ row . split ( "|" ) . slice ( 1 , - 1 ) . map ( c => c . trim ( ) ) ;
2443+ const headers = splitRow ( dataLines [ 0 ] ) ;
2444+ const rows = dataLines . slice ( 1 ) . map ( splitRow ) ;
2445+ return { headers, rows } ;
2446+ }
2447+
2448+ function renderMonitorTable ( parsed ) {
2449+ if ( ! parsed ) return "" ;
2450+ let html = "<table><thead><tr>" ;
2451+ for ( const h of parsed . headers ) html += `<th>${ h } </th>` ;
2452+ html += "</tr></thead><tbody>" ;
2453+ for ( const row of parsed . rows ) {
2454+ const isTotal = row . some ( c => / ^ T O T A L $ / i. test ( c ) ) ;
2455+ html += `<tr${ isTotal ? ' class="total-row"' : "" } >` ;
2456+ for ( const cell of row ) html += `<td>${ cell } </td>` ;
2457+ html += "</tr>" ;
2458+ }
2459+ html += "</tbody></table>" ;
2460+ return html ;
2461+ }
2462+
2463+ function renderComponentCard ( name , comp ) {
2464+ const healthy = comp . is_healthy !== false ;
2465+ const dotClass = healthy ? "ok" : "error" ;
2466+ const friendlyNames = {
2467+ queue : "Processing Queue" ,
2468+ vikingdb : "Vector Database" ,
2469+ vlm : "Language Model" ,
2470+ lock : "Active Locks" ,
2471+ retrieval : "Retrieval Stats" ,
2472+ } ;
2473+ const title = friendlyNames [ name ] || name ;
2474+ let bodyHtml = "" ;
2475+ const statusText = comp . status || "" ;
2476+ const tables = statusText . split ( "\n\n" ) . filter ( Boolean ) ;
2477+ for ( const tableText of tables ) {
2478+ const parsed = parseAsciiTable ( tableText ) ;
2479+ if ( parsed ) {
2480+ bodyHtml += renderMonitorTable ( parsed ) ;
2481+ } else {
2482+ bodyHtml += `<div class="plain-text">${ tableText } </div>` ;
2483+ }
2484+ }
2485+ return `<div class="monitor-card">
2486+ <div class="monitor-card-header">
2487+ <span class="health-dot ${ dotClass } "></span>
2488+ <h3>${ title } </h3>
2489+ </div>
2490+ ${ bodyHtml }
2491+ </div>` ;
2492+ }
2493+
2494+ function renderSystemStatus ( result ) {
2495+ const dashboard = elements . monitorDashboard ;
2496+ if ( ! result ) { dashboard . innerHTML = "" ; return ; }
2497+ let html = `<div class="monitor-card">
2498+ <div class="monitor-card-header">
2499+ <span class="health-dot ${ result . initialized ? "ok" : "error" } "></span>
2500+ <h3>System</h3>
2501+ </div>
2502+ <div class="kv-grid">` ;
2503+ for ( const [ k , v ] of Object . entries ( result ) ) {
2504+ const display = typeof v === "string" ? v : JSON . stringify ( v ) ;
2505+ html += `<span class="kv-label">${ k } </span><span class="kv-value">${ display } </span>` ;
2506+ }
2507+ html += "</div></div>" ;
2508+ dashboard . innerHTML = html ;
2509+ }
2510+
2511+ function renderObserverDashboard ( result ) {
2512+ const dashboard = elements . monitorDashboard ;
2513+ if ( ! result ?. components ) { dashboard . innerHTML = "" ; return ; }
2514+ const healthSummary = Object . entries ( result . components ) . map ( ( [ name , comp ] ) => {
2515+ const healthy = comp . is_healthy !== false ;
2516+ return `<span class="health-dot ${ healthy ? "ok" : "error" } "></span> ${ name } ` ;
2517+ } ) . join ( " " ) ;
2518+ let html = `<div style="margin-bottom:12px;display:flex;align-items:center;gap:12px;font-size:13px;color:var(--muted)">${ healthSummary } </div>` ;
2519+ const order = [ "queue" , "vikingdb" , "vlm" , "retrieval" , "lock" ] ;
2520+ const sorted = order . filter ( n => result . components [ n ] ) ;
2521+ for ( const miss of Object . keys ( result . components ) ) {
2522+ if ( ! sorted . includes ( miss ) ) sorted . push ( miss ) ;
2523+ }
2524+ for ( const name of sorted ) {
2525+ html += renderComponentCard ( name , result . components [ name ] ) ;
2526+ }
2527+ dashboard . innerHTML = html ;
2528+ }
2529+
2530+ let monitorRefreshInterval = null ;
2531+
24342532function bindMonitor ( ) {
24352533 elements . systemBtn . addEventListener ( "click" , async ( ) => {
24362534 try {
24372535 const payload = await callConsole ( "/ov/system/status" , { method : "GET" } ) ;
2438- const rows = Object . entries ( payload . result || { } ) . map ( ( [ key , value ] ) => ( {
2439- label : `${ key } : ${ typeof value === "string" ? value : JSON . stringify ( value ) } ` ,
2440- } ) ) ;
2441- renderList ( elements . monitorResults , rows ) ;
2536+ elements . monitorResults . innerHTML = "" ;
2537+ renderSystemStatus ( payload . result ) ;
24422538 setOutput ( payload ) ;
24432539 } catch ( error ) {
24442540 setOutput ( error . message ) ;
24452541 }
24462542 } ) ;
24472543
2448- elements . observerBtn . addEventListener ( "click" , async ( ) => {
2544+ async function loadObserver ( ) {
24492545 try {
24502546 const payload = await callConsole ( "/ov/observer/system" , { method : "GET" } ) ;
2451- const rows = Object . entries ( payload . result ?. components || { } ) . map ( ( [ name , value ] ) => ( {
2452- label : `${ name } : ${ value ?. status || JSON . stringify ( value ) } ` ,
2453- } ) ) ;
2454- renderList ( elements . monitorResults , rows ) ;
2547+ elements . monitorResults . innerHTML = "" ;
2548+ renderObserverDashboard ( payload . result ) ;
24552549 setOutput ( payload ) ;
24562550 } catch ( error ) {
24572551 setOutput ( error . message ) ;
24582552 }
2553+ }
2554+
2555+ elements . observerBtn . addEventListener ( "click" , loadObserver ) ;
2556+
2557+ elements . monitorRefreshBtn . addEventListener ( "click" , ( ) => {
2558+ if ( monitorRefreshInterval ) {
2559+ clearInterval ( monitorRefreshInterval ) ;
2560+ monitorRefreshInterval = null ;
2561+ elements . monitorRefreshBtn . textContent = "Auto-refresh: OFF" ;
2562+ elements . monitorRefreshBtn . classList . remove ( "active" ) ;
2563+ } else {
2564+ loadObserver ( ) ;
2565+ monitorRefreshInterval = setInterval ( loadObserver , 10000 ) ;
2566+ elements . monitorRefreshBtn . textContent = "Auto-refresh: ON" ;
2567+ elements . monitorRefreshBtn . classList . add ( "active" ) ;
2568+ }
24592569 } ) ;
24602570}
24612571
0 commit comments