+
+ );
+}
+
+function NodeResourceCard({node}){
+ const ramPct=node.ram_total_mb>0?(node.ram_used_mb/node.ram_total_mb)*100:0;
+ const ramUsed=(node.ram_used_mb/1024).toFixed(1);
+ const ramTotal=(node.ram_total_mb/1024).toFixed(0);
+ return (
+
+
{node.hostname}
+
+
+
RAM {ramUsed}/{ramTotal} GB
+
85?'var(--warning)':'var(--text-2)'}/>
+
+ {(node.gpus||[]).map((gpu,i)=>{
+ const vramPct=gpu.memory_total_mb>0?(gpu.memory_used_mb/gpu.memory_total_mb)*100:0;
+ const vramUsed=(gpu.memory_used_mb/1024).toFixed(1);
+ const vramTotal=(gpu.memory_total_mb/1024).toFixed(0);
+ const lbl=(node.gpus||[]).length>1?'GPU '+(i+1):'GPU';
+ return (
+
+
+
+
{lbl} VRAM {vramUsed}/{vramTotal} GB
+
85?'var(--warning)':'var(--purple)'}/>
+
+
+ );
+ })}
+
+ );
+}
+
+function ClusterResources(){
+ const {data,usingMock}=useClusterMetrics();
+ if(!data)return
Loading resource metrics...
;
+ return (
+
+ {usingMock&&(
+
⚠ Metrics API unavailable - showing mock data
+ )}
+
+ {data.nodes.map(n=>)}
+
+
+ );
+}
+
+window.ClusterResources=ClusterResources;
diff --git a/services/web-ui/public/styles-fixes.css b/services/web-ui/public/styles-fixes.css
index 13e0cea..675c064 100644
--- a/services/web-ui/public/styles-fixes.css
+++ b/services/web-ui/public/styles-fixes.css
@@ -678,3 +678,84 @@
z-index: 100;
pointer-events: none;
}
+
+/* ── Resource utilization cards (screens-resources.jsx) ── */
+.res-nodes-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 12px;
+}
+.res-node-card {
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+.res-node-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-1);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-bottom: 2px;
+}
+.res-node-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--success);
+ box-shadow: 0 0 0 3px var(--success-soft);
+ flex-shrink: 0;
+}
+.res-metric {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+.res-metric-label {
+ font-size: 11px;
+ font-weight: 500;
+ color: var(--text-3);
+ font-family: var(--font-mono);
+ display: flex;
+ align-items: baseline;
+ gap: 6px;
+}
+.res-metric-sub {
+ color: var(--text-4);
+ font-weight: 400;
+}
+.res-bar-wrap {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.res-bar {
+ flex: 1;
+ height: 6px;
+ background: var(--bg-4);
+ border-radius: 99px;
+ overflow: hidden;
+}
+.res-bar-fill {
+ height: 100%;
+ border-radius: 99px;
+ transition: width 0.6s ease;
+}
+.res-bar-pct {
+ font-size: 11px;
+ font-family: var(--font-mono);
+ color: var(--text-3);
+ min-width: 32px;
+ text-align: right;
+}
+.res-mock-note {
+ font-size: 11px;
+ color: var(--warning);
+ background: var(--warning-soft);
+ border-radius: var(--r-sm);
+ padding: 6px 10px;
+ margin-bottom: 10px;
+ font-family: var(--font-mono);
+}