mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 13:59:40 +00:00
feat(eye-remote): Add radar sweep animation dashboard
- New radar.html for Eye Remote HyperPixel Round display - Classic radar sweep animation with green phosphor aesthetic - 6 module blips (AUTH/WALL/BOOT/MIND/ROOT/MESH) at 60° intervals - Blips illuminate when radar sweep passes with fade effect - Color-coded status: green=ok, yellow=warn, red=critical - Concentric grid rings and 12 radial lines - Center: time/date/hostname/status with glow effects - Mini metrics bar: CPU/MEM/DSK - Transport auto-detection: OTG/WiFi/SIM - Alert ticker at bottom - Simulation mode for offline demo - Threshold alerts per module Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ad76f624ab
commit
47372e8601
|
|
@ -4,6 +4,34 @@
|
|||
---
|
||||
## 2026-05-08
|
||||
|
||||
### Session 129 — Eye Remote Radar Animation + SOC Fixes
|
||||
|
||||
**Radar Dashboard Created (remote-ui/round/radar.html):**
|
||||
- New radar-style visualization for Eye Remote HyperPixel 2.1 Round
|
||||
- Classic radar sweep animation with green phosphor aesthetic
|
||||
- 6 module blips (AUTH/WALL/BOOT/MIND/ROOT/MESH) at 60° intervals
|
||||
- Blips light up when radar sweep passes them
|
||||
- Color-coded status: green=ok, yellow=warn, red=critical
|
||||
- Concentric grid rings (60, 100, 140, 180, 210px)
|
||||
- 12 radial grid lines
|
||||
- Center displays: time, date, hostname, system status
|
||||
- Top mini-metrics: CPU%, MEM%, DSK%
|
||||
- Transport badge: OTG/WiFi/SIM auto-detection
|
||||
- Ticker bar for alerts
|
||||
- Simulation mode for offline testing
|
||||
- Module thresholds: AUTH(CPU 70/85), WALL(MEM 75/90), BOOT(DISK 80/95), MIND(LOAD 2/4), ROOT(TEMP 65/75), MESH(WiFi -70/-80dBm)
|
||||
|
||||
**SOC Dashboard Fixes (earlier this session):**
|
||||
- Added shared sidebar injection to SOC page
|
||||
- Fixed CrowdSec health detection (direct LAPI HTTP + sudo for cscli)
|
||||
- Applied SecuBox Six-Stack colorimetry to stat cards
|
||||
- Implemented tiered auto-ban in CrowdSec profiles.yaml
|
||||
|
||||
**Files Created:**
|
||||
- `remote-ui/round/radar.html` — v1.0 Radar dashboard
|
||||
|
||||
---
|
||||
|
||||
### Session 128 — LED Tooltips + Kernel nftables Fix
|
||||
|
||||
**Sidebar v2.32.0:**
|
||||
|
|
|
|||
450
remote-ui/round/radar.html
Normal file
450
remote-ui/round/radar.html
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=480,height=480,initial-scale=1,user-scalable=no">
|
||||
<title>SecuBox Radar</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{background:#000;display:flex;justify-content:center;align-items:center;
|
||||
min-height:100vh;overflow:hidden;font-family:'JetBrains Mono','SF Mono','Consolas',monospace}
|
||||
#screen{position:relative;width:480px;height:480px;border-radius:50%;
|
||||
overflow:hidden;background:radial-gradient(circle,#041008 0%,#020804 60%,#000 100%);
|
||||
border:2px solid #0a2818}
|
||||
#radar-canvas{position:absolute;top:0;left:0;width:480px;height:480px}
|
||||
#overlay-canvas{position:absolute;top:0;left:0;width:480px;height:480px;pointer-events:none}
|
||||
#center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
|
||||
text-align:center;pointer-events:none;z-index:10;width:160px}
|
||||
#time{font-size:28px;font-weight:700;color:#00ff41;text-shadow:0 0 10px #00ff41,0 0 20px #00ff4188;
|
||||
letter-spacing:2px}
|
||||
#date{font-size:9px;color:#00ff4199;letter-spacing:2px;margin-top:2px}
|
||||
#hostname{font-size:8px;color:#00ff4166;letter-spacing:1px;margin-top:4px}
|
||||
#status{font-size:10px;font-weight:700;margin-top:8px;letter-spacing:1px;
|
||||
transition:color .3s,text-shadow .3s}
|
||||
#status.nominal{color:#00ff41;text-shadow:0 0 8px #00ff41}
|
||||
#status.warning{color:#ffc107;text-shadow:0 0 8px #ffc107}
|
||||
#status.critical{color:#ff4444;text-shadow:0 0 8px #ff4444;animation:pulse 1s infinite}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
|
||||
.blip-label{position:absolute;font-size:7px;letter-spacing:1px;
|
||||
text-shadow:0 0 4px currentColor;opacity:0;transition:opacity .3s;pointer-events:none}
|
||||
.blip-label.active{opacity:1}
|
||||
.blip-label.ok{color:#00ff41}
|
||||
.blip-label.warn{color:#ffc107}
|
||||
.blip-label.crit{color:#ff4444}
|
||||
#transport{position:absolute;top:15px;right:50%;transform:translateX(50%);
|
||||
font-size:8px;letter-spacing:1px;z-index:10}
|
||||
#transport.otg{color:#00ff41}
|
||||
#transport.wifi{color:#104A88}
|
||||
#transport.sim{color:#444}
|
||||
#version{position:absolute;bottom:12px;left:50%;transform:translateX(-50%);
|
||||
font-size:7px;color:#00ff4133;letter-spacing:1px}
|
||||
/* Alert ticker */
|
||||
#ticker{position:absolute;bottom:38px;left:50%;transform:translateX(-50%);
|
||||
font-size:8px;color:#00ff4188;letter-spacing:1px;max-width:200px;
|
||||
text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
||||
/* Metrics mini-readout */
|
||||
#metrics{position:absolute;top:50px;left:50%;transform:translateX(-50%);
|
||||
display:flex;gap:12px;font-size:8px;color:#00ff4166;z-index:10}
|
||||
.metric-item{text-align:center}
|
||||
.metric-val{font-size:10px;font-weight:700;color:#00ff41}
|
||||
/* Module icons around the radar */
|
||||
.module-icon{position:absolute;width:28px;height:28px;opacity:.4;
|
||||
transition:opacity .3s,filter .3s;pointer-events:none}
|
||||
.module-icon.active{opacity:1;filter:drop-shadow(0 0 6px currentColor)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="screen">
|
||||
<canvas id="radar-canvas" width="480" height="480"></canvas>
|
||||
<canvas id="overlay-canvas" width="480" height="480"></canvas>
|
||||
<div id="transport" class="otg">● OTG</div>
|
||||
<div id="metrics">
|
||||
<div class="metric-item"><div class="metric-val" id="m-cpu">--</div>CPU</div>
|
||||
<div class="metric-item"><div class="metric-val" id="m-mem">--</div>MEM</div>
|
||||
<div class="metric-item"><div class="metric-val" id="m-disk">--</div>DSK</div>
|
||||
</div>
|
||||
<div id="center">
|
||||
<div id="time">--:--:--</div>
|
||||
<div id="date">--- -- ---</div>
|
||||
<div id="hostname">secubox</div>
|
||||
<div id="status" class="nominal">SCANNING</div>
|
||||
</div>
|
||||
<div id="ticker">SYSTEM NOMINAL</div>
|
||||
<div id="version">SECUBOX RADAR v1.0</div>
|
||||
|
||||
<!-- Module labels positioned at their blip angles -->
|
||||
<div class="blip-label" id="lbl-AUTH" style="top:58px;left:50%;transform:translateX(-50%)">AUTH</div>
|
||||
<div class="blip-label" id="lbl-WALL" style="top:130px;right:65px">WALL</div>
|
||||
<div class="blip-label" id="lbl-MESH" style="bottom:130px;right:65px">MESH</div>
|
||||
<div class="blip-label" id="lbl-ROOT" style="bottom:58px;left:50%;transform:translateX(-50%)">ROOT</div>
|
||||
<div class="blip-label" id="lbl-MIND" style="bottom:130px;left:65px">MIND</div>
|
||||
<div class="blip-label" id="lbl-BOOT" style="top:130px;left:65px">BOOT</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// SecuBox Radar v1.0 - CyberMind
|
||||
// Real-time radar sweep with module status blips
|
||||
|
||||
const CFG = {
|
||||
API_OTG_BASE: 'http://10.55.0.1:8000',
|
||||
API_WIFI_BASE: 'http://secubox.local:8000',
|
||||
ENDPOINT_METRICS: '/api/v1/system/metrics',
|
||||
ENDPOINT_HEALTH: '/api/v1/health',
|
||||
REFRESH_INTERVAL: 2000,
|
||||
SWEEP_SPEED: 4000, // ms per full rotation
|
||||
SIMULATE: true,
|
||||
};
|
||||
|
||||
// Module definitions: angle (0=top, clockwise), color, metric key
|
||||
const MODULES = {
|
||||
AUTH: { angle: 0, color: '#C04E24', metric: 'cpu', warn: 70, crit: 85 },
|
||||
WALL: { angle: 60, color: '#9A6010', metric: 'mem', warn: 75, crit: 90 },
|
||||
MESH: { angle: 120, color: '#104A88', metric: 'wifi', warn: -70, crit: -80 },
|
||||
ROOT: { angle: 180, color: '#0A5840', metric: 'temp', warn: 65, crit: 75 },
|
||||
MIND: { angle: 240, color: '#3D35A0', metric: 'load', warn: 2, crit: 4 },
|
||||
BOOT: { angle: 300, color: '#803018', metric: 'disk', warn: 80, crit: 95 },
|
||||
};
|
||||
|
||||
const CX = 240, CY = 240;
|
||||
const BLIP_RADIUS = 170;
|
||||
const GRID_RINGS = [60, 100, 140, 180, 210];
|
||||
|
||||
// Canvas setup
|
||||
const radarCanvas = document.getElementById('radar-canvas');
|
||||
const radarCtx = radarCanvas.getContext('2d');
|
||||
const overlayCanvas = document.getElementById('overlay-canvas');
|
||||
const overlayCtx = overlayCanvas.getContext('2d');
|
||||
|
||||
// State
|
||||
let sweepAngle = 0;
|
||||
let lastFrame = 0;
|
||||
let metrics = null;
|
||||
let blipStates = {};
|
||||
|
||||
// Initialize blip states
|
||||
Object.keys(MODULES).forEach(m => {
|
||||
blipStates[m] = { brightness: 0, lastHit: 0, status: 'ok' };
|
||||
});
|
||||
|
||||
// Draw static grid (only once needed, but we redraw for glow effect)
|
||||
function drawGrid() {
|
||||
radarCtx.clearRect(0, 0, 480, 480);
|
||||
|
||||
// Background gradient
|
||||
const grad = radarCtx.createRadialGradient(CX, CY, 0, CX, CY, 240);
|
||||
grad.addColorStop(0, '#041008');
|
||||
grad.addColorStop(0.6, '#020804');
|
||||
grad.addColorStop(1, '#000');
|
||||
radarCtx.fillStyle = grad;
|
||||
radarCtx.beginPath();
|
||||
radarCtx.arc(CX, CY, 240, 0, Math.PI * 2);
|
||||
radarCtx.fill();
|
||||
|
||||
// Concentric circles
|
||||
radarCtx.strokeStyle = '#00ff4122';
|
||||
radarCtx.lineWidth = 1;
|
||||
GRID_RINGS.forEach(r => {
|
||||
radarCtx.beginPath();
|
||||
radarCtx.arc(CX, CY, r, 0, Math.PI * 2);
|
||||
radarCtx.stroke();
|
||||
});
|
||||
|
||||
// Cross lines (4 quadrants)
|
||||
radarCtx.beginPath();
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const a = (i / 12) * Math.PI * 2 - Math.PI / 2;
|
||||
radarCtx.moveTo(CX, CY);
|
||||
radarCtx.lineTo(CX + 220 * Math.cos(a), CY + 220 * Math.sin(a));
|
||||
}
|
||||
radarCtx.strokeStyle = '#00ff4111';
|
||||
radarCtx.stroke();
|
||||
|
||||
// Outer rim
|
||||
radarCtx.beginPath();
|
||||
radarCtx.arc(CX, CY, 220, 0, Math.PI * 2);
|
||||
radarCtx.strokeStyle = '#00ff4144';
|
||||
radarCtx.lineWidth = 2;
|
||||
radarCtx.stroke();
|
||||
}
|
||||
|
||||
// Draw radar sweep
|
||||
function drawSweep(angle) {
|
||||
overlayCtx.clearRect(0, 0, 480, 480);
|
||||
|
||||
// Sweep trail (gradient arc)
|
||||
const trailLength = Math.PI * 0.6; // 108 degrees trail
|
||||
const sweepGrad = overlayCtx.createConicGradient(angle - Math.PI/2, CX, CY);
|
||||
sweepGrad.addColorStop(0, '#00ff4100');
|
||||
sweepGrad.addColorStop(0.15, '#00ff4108');
|
||||
sweepGrad.addColorStop(0.3, '#00ff4120');
|
||||
sweepGrad.addColorStop(0.5, '#00ff4140');
|
||||
sweepGrad.addColorStop(1, '#00ff4100');
|
||||
|
||||
overlayCtx.beginPath();
|
||||
overlayCtx.moveTo(CX, CY);
|
||||
overlayCtx.arc(CX, CY, 220, angle - trailLength, angle);
|
||||
overlayCtx.closePath();
|
||||
overlayCtx.fillStyle = sweepGrad;
|
||||
overlayCtx.fill();
|
||||
|
||||
// Sweep line (bright)
|
||||
overlayCtx.beginPath();
|
||||
overlayCtx.moveTo(CX, CY);
|
||||
overlayCtx.lineTo(CX + 220 * Math.cos(angle), CY + 220 * Math.sin(angle));
|
||||
overlayCtx.strokeStyle = '#00ff41cc';
|
||||
overlayCtx.lineWidth = 2;
|
||||
overlayCtx.shadowColor = '#00ff41';
|
||||
overlayCtx.shadowBlur = 15;
|
||||
overlayCtx.stroke();
|
||||
overlayCtx.shadowBlur = 0;
|
||||
|
||||
// Draw blips
|
||||
Object.entries(MODULES).forEach(([name, mod]) => {
|
||||
const blipAngle = (mod.angle - 90) * Math.PI / 180; // Convert to radians, offset for top=0
|
||||
const bx = CX + BLIP_RADIUS * Math.cos(blipAngle);
|
||||
const by = CY + BLIP_RADIUS * Math.sin(blipAngle);
|
||||
const state = blipStates[name];
|
||||
|
||||
// Check if sweep just passed this blip
|
||||
const sweepDeg = ((angle + Math.PI/2) * 180 / Math.PI + 360) % 360;
|
||||
const diff = Math.abs(sweepDeg - mod.angle);
|
||||
const hit = diff < 15 || diff > 345;
|
||||
|
||||
if (hit && Date.now() - state.lastHit > 200) {
|
||||
state.brightness = 1;
|
||||
state.lastHit = Date.now();
|
||||
// Update label
|
||||
const lbl = document.getElementById('lbl-' + name);
|
||||
if (lbl) {
|
||||
lbl.classList.add('active');
|
||||
lbl.className = 'blip-label active ' + state.status;
|
||||
}
|
||||
}
|
||||
|
||||
// Fade blip
|
||||
state.brightness = Math.max(0, state.brightness - 0.015);
|
||||
|
||||
if (state.brightness > 0) {
|
||||
// Blip glow
|
||||
const alpha = state.brightness;
|
||||
let color = '#00ff41';
|
||||
if (state.status === 'warn') color = '#ffc107';
|
||||
if (state.status === 'crit') color = '#ff4444';
|
||||
|
||||
// Outer glow
|
||||
overlayCtx.beginPath();
|
||||
overlayCtx.arc(bx, by, 12 + state.brightness * 8, 0, Math.PI * 2);
|
||||
overlayCtx.fillStyle = color.slice(0,7) + Math.floor(alpha * 30).toString(16).padStart(2,'0');
|
||||
overlayCtx.fill();
|
||||
|
||||
// Core
|
||||
overlayCtx.beginPath();
|
||||
overlayCtx.arc(bx, by, 4 + state.brightness * 3, 0, Math.PI * 2);
|
||||
overlayCtx.fillStyle = color.slice(0,7) + Math.floor(alpha * 200).toString(16).padStart(2,'0');
|
||||
overlayCtx.shadowColor = color;
|
||||
overlayCtx.shadowBlur = 10 * alpha;
|
||||
overlayCtx.fill();
|
||||
overlayCtx.shadowBlur = 0;
|
||||
} else {
|
||||
// Dim blip marker
|
||||
overlayCtx.beginPath();
|
||||
overlayCtx.arc(bx, by, 3, 0, Math.PI * 2);
|
||||
overlayCtx.fillStyle = '#00ff4133';
|
||||
overlayCtx.fill();
|
||||
|
||||
// Fade label
|
||||
const lbl = document.getElementById('lbl-' + name);
|
||||
if (lbl) lbl.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Animation loop
|
||||
function animate(timestamp) {
|
||||
if (!lastFrame) lastFrame = timestamp;
|
||||
const delta = timestamp - lastFrame;
|
||||
lastFrame = timestamp;
|
||||
|
||||
// Update sweep angle
|
||||
sweepAngle += (delta / CFG.SWEEP_SPEED) * Math.PI * 2;
|
||||
if (sweepAngle > Math.PI * 2) sweepAngle -= Math.PI * 2;
|
||||
|
||||
drawGrid();
|
||||
drawSweep(sweepAngle);
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// Update clock
|
||||
function updateClock() {
|
||||
const n = new Date();
|
||||
const p = v => String(v).padStart(2, '0');
|
||||
document.getElementById('time').textContent = p(n.getHours()) + ':' + p(n.getMinutes()) + ':' + p(n.getSeconds());
|
||||
const D = ['DIM', 'LUN', 'MAR', 'MER', 'JEU', 'VEN', 'SAM'];
|
||||
const M = ['JAN', 'FEB', 'MAR', 'AVR', 'MAI', 'JUN', 'JUL', 'AOU', 'SEP', 'OCT', 'NOV', 'DEC'];
|
||||
document.getElementById('date').textContent = D[n.getDay()] + ' ' + p(n.getDate()) + ' ' + M[n.getMonth()];
|
||||
}
|
||||
|
||||
// Simulation
|
||||
const SIM = {
|
||||
cpu: 25, mem: 40, disk: 30, temp: 44, load: 0.3, wifi: -55,
|
||||
tick() {
|
||||
this.cpu = Math.max(5, Math.min(95, this.cpu + (Math.random() - 0.48) * 8));
|
||||
this.mem = Math.max(20, Math.min(90, this.mem + (Math.random() - 0.5) * 3));
|
||||
this.disk = Math.max(10, Math.min(95, this.disk + (Math.random() - 0.5) * 0.5));
|
||||
this.temp = Math.max(35, Math.min(80, this.temp + (Math.random() - 0.5) * 2));
|
||||
this.load = Math.max(0.1, Math.min(5, this.load + (Math.random() - 0.48) * 0.3));
|
||||
this.wifi = Math.max(-85, Math.min(-30, this.wifi + (Math.random() - 0.5) * 5));
|
||||
return {
|
||||
cpu: Math.round(this.cpu),
|
||||
mem: Math.round(this.mem),
|
||||
disk: Math.round(this.disk),
|
||||
temp: Math.round(this.temp),
|
||||
load: Math.round(this.load * 10) / 10,
|
||||
wifi: Math.round(this.wifi),
|
||||
hostname: 'secubox-demo',
|
||||
uptime: Math.floor(Date.now() / 1000) % 86400
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Update metrics display
|
||||
function updateMetrics(m) {
|
||||
metrics = m;
|
||||
document.getElementById('m-cpu').textContent = m.cpu + '%';
|
||||
document.getElementById('m-mem').textContent = m.mem + '%';
|
||||
document.getElementById('m-disk').textContent = m.disk + '%';
|
||||
document.getElementById('hostname').textContent = m.hostname || 'secubox';
|
||||
|
||||
// Update blip statuses based on thresholds
|
||||
let hasWarning = false, hasCritical = false;
|
||||
|
||||
Object.entries(MODULES).forEach(([name, mod]) => {
|
||||
let val;
|
||||
switch (mod.metric) {
|
||||
case 'cpu': val = m.cpu; break;
|
||||
case 'mem': val = m.mem; break;
|
||||
case 'disk': val = m.disk; break;
|
||||
case 'temp': val = m.temp; break;
|
||||
case 'load': val = m.load; break;
|
||||
case 'wifi': val = m.wifi; break;
|
||||
default: val = 0;
|
||||
}
|
||||
|
||||
let status = 'ok';
|
||||
// For wifi, lower (more negative) is worse
|
||||
if (mod.metric === 'wifi') {
|
||||
if (val < mod.crit) status = 'crit';
|
||||
else if (val < mod.warn) status = 'warn';
|
||||
} else {
|
||||
if (val > mod.crit) status = 'crit';
|
||||
else if (val > mod.warn) status = 'warn';
|
||||
}
|
||||
|
||||
blipStates[name].status = status;
|
||||
if (status === 'warn') hasWarning = true;
|
||||
if (status === 'crit') hasCritical = true;
|
||||
});
|
||||
|
||||
// Update global status
|
||||
const statusEl = document.getElementById('status');
|
||||
const tickerEl = document.getElementById('ticker');
|
||||
statusEl.classList.remove('nominal', 'warning', 'critical');
|
||||
|
||||
if (hasCritical) {
|
||||
statusEl.textContent = 'CRITICAL';
|
||||
statusEl.classList.add('critical');
|
||||
tickerEl.textContent = 'ALERT: THRESHOLD EXCEEDED';
|
||||
tickerEl.style.color = '#ff444488';
|
||||
} else if (hasWarning) {
|
||||
statusEl.textContent = 'WARNING';
|
||||
statusEl.classList.add('warning');
|
||||
tickerEl.textContent = 'CAUTION: ELEVATED METRICS';
|
||||
tickerEl.style.color = '#ffc10788';
|
||||
} else {
|
||||
statusEl.textContent = 'NOMINAL';
|
||||
statusEl.classList.add('nominal');
|
||||
tickerEl.textContent = 'ALL SYSTEMS OPERATIONAL';
|
||||
tickerEl.style.color = '#00ff4188';
|
||||
}
|
||||
}
|
||||
|
||||
// Transport manager (simplified)
|
||||
const TM = {
|
||||
active: 'SIM',
|
||||
async probe() {
|
||||
// Try OTG first
|
||||
try {
|
||||
const r = await fetch(CFG.API_OTG_BASE + CFG.ENDPOINT_HEALTH, {
|
||||
signal: AbortSignal.timeout(2000)
|
||||
});
|
||||
if (r.ok) { this.active = 'OTG'; return; }
|
||||
} catch (e) {}
|
||||
|
||||
// Try WiFi
|
||||
try {
|
||||
const r = await fetch(CFG.API_WIFI_BASE + CFG.ENDPOINT_HEALTH, {
|
||||
signal: AbortSignal.timeout(3000)
|
||||
});
|
||||
if (r.ok) { this.active = 'WiFi'; return; }
|
||||
} catch (e) {}
|
||||
|
||||
this.active = 'SIM';
|
||||
},
|
||||
async fetchMetrics() {
|
||||
if (CFG.SIMULATE || this.active === 'SIM') {
|
||||
return SIM.tick();
|
||||
}
|
||||
|
||||
const base = this.active === 'OTG' ? CFG.API_OTG_BASE : CFG.API_WIFI_BASE;
|
||||
try {
|
||||
const r = await fetch(base + CFG.ENDPOINT_METRICS, {
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
if (r.ok) {
|
||||
const d = await r.json();
|
||||
return {
|
||||
cpu: Math.round(d.cpu_percent || 0),
|
||||
mem: Math.round(d.mem_percent || 0),
|
||||
disk: Math.round(d.disk_percent || 0),
|
||||
temp: Math.round(d.cpu_temp || 40),
|
||||
load: d.load_avg_1 || 0,
|
||||
wifi: d.wifi_rssi || -50,
|
||||
hostname: d.hostname || 'secubox',
|
||||
uptime: d.uptime_seconds || 0
|
||||
};
|
||||
}
|
||||
} catch (e) {}
|
||||
return SIM.tick();
|
||||
}
|
||||
};
|
||||
|
||||
// Update transport badge
|
||||
function updateTransportBadge() {
|
||||
const el = document.getElementById('transport');
|
||||
el.className = TM.active.toLowerCase();
|
||||
el.textContent = '● ' + TM.active;
|
||||
}
|
||||
|
||||
// Main tick
|
||||
async function tick() {
|
||||
const m = await TM.fetchMetrics();
|
||||
updateMetrics(m);
|
||||
updateTransportBadge();
|
||||
}
|
||||
|
||||
// Init
|
||||
async function init() {
|
||||
await TM.probe();
|
||||
updateTransportBadge();
|
||||
updateClock();
|
||||
setInterval(updateClock, 1000);
|
||||
tick();
|
||||
setInterval(tick, CFG.REFRESH_INTERVAL);
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user