76 lines
5.0 KiB
JavaScript
76 lines
5.0 KiB
JavaScript
// Optional large Overview charts: real status schema, counter resets, no polling.
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import vm from 'node:vm';
|
|
const source = fs.readFileSync('src/admin/web/app/overview-wide-metrics.js','utf8');
|
|
const registry = JSON.parse(fs.readFileSync('src/admin/web/dashboard-widgets.json','utf8'));
|
|
const ids = registry.filter(w => w.id.startsWith('wide_')).map(w => w.id);
|
|
assert.equal(ids.length, 7);
|
|
assert(ids.every(id => registry.find(w => w.id === id).default_hidden));
|
|
const nodes = new Map(ids.map(id => [id,{ hidden: false }]));
|
|
const fields = new Map();
|
|
function $(id) {
|
|
if (!fields.has(id)) fields.set(id,{ textContent:'-', d:'', setAttribute(name,value) {this[name]=value;} });
|
|
return fields.get(id);
|
|
}
|
|
let now = 1000;
|
|
const ctx = {window:{},currentPanel:'overview',performance:{now:()=>now},fmt:(n,places=0)=>Number(n).toFixed(places),fmtBytes:n=>`${n} B`,$,
|
|
document:{querySelector:selector=>nodes.get(selector.match(/data-dashboard-widget="([^"]+)"/)?.[1])||null}};
|
|
vm.createContext(ctx);vm.runInContext(source,ctx);
|
|
const render=ctx.window.rustProxyWideOverview.render;
|
|
const text=id=>$(id).textContent;
|
|
const baseline = { revision:1,
|
|
metrics:{ http1_requests:50,http2_requests:30,http3_requests:20,active_connections:9,websocket_active:3,mean_duration_ms:15 },
|
|
system:{cpu_percent:45,process_cpu_percent:16,process_rss_bytes:1500},
|
|
cache:{hits:20,misses:10,resident_bytes:220},
|
|
backend_pools:[{members:[{status:'up',active:1,last_probe_ms:100,latency_ms:12,health_mode:'active'},{status:'down',active:0}]}]};
|
|
render(baseline);
|
|
assert.equal(text('overview-wide-wide_protocol_rates-summary'),'-');
|
|
assert.equal(text('overview-wide-wide_cpu-system-value'),'45.0%');
|
|
assert.equal(text('overview-wide-wide_memory-process-value'),'1500 B');
|
|
assert.equal(text('overview-wide-wide_backend_health-down-value'),'1');
|
|
assert.equal(text('overview-wide-wide_latency-probe-value'),'12.0 ms');
|
|
now = 3000;
|
|
const second=structuredClone(baseline);
|
|
second.metrics.http1_requests=70; second.metrics.http2_requests=40; second.metrics.http3_requests=22;
|
|
second.cache.hits=30; second.cache.misses=18;
|
|
render(second);
|
|
assert.equal(text('overview-wide-wide_protocol_rates-h1-value'),'10.0/s');
|
|
assert.equal(text('overview-wide-wide_protocol_rates-h2-value'),'5.0/s');
|
|
assert.equal(text('overview-wide-wide_protocol_rates-h3-value'),'1.0/s');
|
|
assert.equal(text('overview-wide-wide_cache_activity-hits-value'),'5.0/s');
|
|
assert.equal(text('overview-wide-wide_cache_activity-misses-value'),'4.0/s');
|
|
assert.equal(text('overview-wide-wide_connections-open-value'),'9');
|
|
now = 4000;
|
|
render({...second,metrics:{...second.metrics,http1_requests:0,http2_requests:0,http3_requests:0},cache:{...second.cache,hits:0,misses:0}});
|
|
assert.equal(text('overview-wide-wide_protocol_rates-h1-value'),'-', 'counter resets must not create negative traffic');
|
|
assert.equal(text('overview-wide-wide_cache_activity-hits-value'),'-');
|
|
nodes.get('wide_cpu').hidden=true;
|
|
now=5000;render({...second, system:{}});
|
|
assert.equal(text('overview-wide-wide_cpu-system-value'),'45.0%', 'hidden chart should not re-render');
|
|
nodes.get('wide_cpu').hidden=false;
|
|
now=6000;render({metrics:{},system:{},cache:{}});
|
|
assert.equal(text('overview-wide-wide_cpu-system-value'),'-', 'unavailable metrics are not zero');
|
|
assert.equal(text('overview-wide-wide_backend_health-up-value'),'-', 'missing backend array is no data');
|
|
ctx.window.rustProxyWideOverview.reset();
|
|
for(let i=0;i<75;i++){now+=1000;render({...baseline,system:{...baseline.system,cpu_percent:i}});}
|
|
assert(source.includes('const limit = 60'));
|
|
assert(source.includes('if (!node || node.hidden)'));
|
|
assert(!/fetch\s*\(|setInterval\s*\(/.test(source),'no extra API calls or polling');
|
|
const css=fs.readFileSync('src/admin/web/style.css','utf8');
|
|
const aligned = fs.readFileSync('src/admin/web/app/overview-masonry.js', 'utf8');
|
|
assert.match(css, /\.dashboard-overview-grid\.is-aligned\s*\{[^}]*grid-auto-rows:\s*auto;/);
|
|
assert.match(css, /\.dashboard-overview-grid\.is-aligned\s*\{[^}]*grid-auto-flow:\s*row;/);
|
|
assert.match(css, /\.dashboard-overview-grid\.is-aligned\s*\{[^}]*gap:\s*var\(--ui-gap\);/);
|
|
assert.match(css, /\.dashboard-overview-grid\.is-aligned\s*\{[^}]*align-items:\s*stretch;/);
|
|
assert.match(css, /#panel-overview\s*\{\s*container:\s*overview-canvas \/ inline-size;/);
|
|
assert.match(css, /@container overview-canvas \(max-width:\s*700px\)/);
|
|
assert.match(css, /\.dashboard-overview-grid\.is-aligned\s*>\s*\[data-dashboard-widget\]\s*\{[^}]*align-self:\s*stretch;/);
|
|
assert.match(css, /\.dashboard-overview-grid\.is-aligned\s*>\s*\[data-dashboard-widget\]\.overview-row-start\s*\{[^}]*grid-column-start:\s*1;/);
|
|
assert.match(aligned, /node\.classList\.remove\("overview-row-start"\)/);
|
|
assert.match(aligned, /node\.classList\.add\("overview-row-start"\)/);
|
|
assert.match(aligned, /grid\.classList\.remove\("is-packed"\)/);
|
|
assert(!/style\.position\s*=\s*["']absolute["']/.test(aligned));
|
|
assert(!aligned.includes('fetch(') && !aligned.includes('setInterval('));
|
|
console.log('PASS: 7 optional wide charts, regular rows, no uneven masonry');
|