diff --git a/dashboard/css/app.css b/dashboard/css/app.css index 135772f..594949a 100644 --- a/dashboard/css/app.css +++ b/dashboard/css/app.css @@ -187,6 +187,7 @@ body{background:var(--bg-1);color:var(--ink-1);font-family:var(--font-ui);font-s .chart-cur{font-family:var(--font-mono);font-size:16px;font-weight:700} .chart-svg{width:100%;height:52px;display:block} .chart-axis{display:flex;justify-content:space-between;margin-top:2px;font-family:var(--font-terminal);font-size:9px;color:var(--ink-4)} +.chart-minmax{display:flex;justify-content:space-between;margin-top:3px;font-family:var(--font-mono);font-size:9px;color:var(--ink-4)} .smart-btn{display:inline-flex;align-items:center;gap:8px;padding:7px 12px;border-radius:8px; border:1px solid var(--border-2);background:var(--bg-3);cursor:pointer; transition:background .12s,border-color .12s,transform .08s;font-family:var(--font-terminal);font-size:11px} diff --git a/dashboard/js/app.js b/dashboard/js/app.js index 016e650..1bb79a3 100644 --- a/dashboard/js/app.js +++ b/dashboard/js/app.js @@ -87,6 +87,8 @@ const App = (() => { updateServerStats(msg.data); } else if (msg.type === 'status_update') { Grid.updateStatus(msg.agent_id, msg.data.status); + } else if (msg.type === 'agent_removed') { + Grid.removeAgent(msg.agent_id); } } catch {} }; diff --git a/dashboard/js/grid.js b/dashboard/js/grid.js index 7ccb64b..1bd187c 100644 --- a/dashboard/js/grid.js +++ b/dashboard/js/grid.js @@ -111,8 +111,24 @@ const Grid = (() => { } function update(agentId, metrics) { - const entry = _agents.get(agentId); - if (!entry) return; + let entry = _agents.get(agentId); + if (!entry) { + // Nouvel agent découvert via WebSocket — on crée la tuile à la volée + const agent = { + id: agentId, + hostname: metrics.hostname || agentId, + ip: metrics.ip || '', + status: metrics.status || 'online', + }; + _agents.set(agentId, { agent, metrics }); + const grid = document.getElementById('agents-grid'); + if (grid) grid.insertAdjacentHTML('beforeend', renderTile(agent, metrics)); + updateStats(); + return; + } + // Mettre à jour ip/status depuis les métriques fraîches + if (metrics.ip) entry.agent.ip = metrics.ip; + if (metrics.status) entry.agent.status = metrics.status; // Conserver les valeurs lentes (disque, smart) quand le paquet ne les contient pas if (entry.metrics) { for (const k of Object.keys(entry.metrics)) { diff --git a/dashboard/js/popups.js b/dashboard/js/popups.js index 51d86f8..b95a21c 100644 --- a/dashboard/js/popups.js +++ b/dashboard/js/popups.js @@ -69,6 +69,17 @@ const Popups = (() => { const cpuPts = Charts.historyToCpuPts(history); const memPts = Charts.historyToMemPts(history); + let ramMin = null, ramMax = null; + for (const h of history) { + if (h.memory_used != null) { + if (ramMin === null || h.memory_used < ramMin) ramMin = h.memory_used; + if (ramMax === null || h.memory_used > ramMax) ramMax = h.memory_used; + } + } + const ramMinMax = ramMin !== null + ? `
min ${Grid.fmt(ramMin)}max ${Grid.fmt(ramMax)}
` + : ''; + const smartBtn = metrics?.smart ? `
@@ -118,6 +129,7 @@ const Popups = (() => {
−30min−15minnow
+ ${ramMinMax} diff --git a/server/handlers/agents.go b/server/handlers/agents.go index 1708320..6566414 100644 --- a/server/handlers/agents.go +++ b/server/handlers/agents.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/user/nanometrics/server/db" + "github.com/user/nanometrics/server/models" ) func AgentsHandler(database *db.DB) http.HandlerFunc { @@ -23,7 +24,7 @@ func AgentsHandler(database *db.DB) http.HandlerFunc { } } -func DeleteAgentHandler(database *db.DB) http.HandlerFunc { +func DeleteAgentHandler(database *db.DB, broadcast func(interface{})) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") if len(parts) < 3 { @@ -35,6 +36,7 @@ func DeleteAgentHandler(database *db.DB) http.HandlerFunc { http.Error(w, err.Error(), 500) return } + broadcast(models.WSMessage{Type: "agent_removed", AgentID: agentID}) w.WriteHeader(http.StatusNoContent) } } diff --git a/server/main.go b/server/main.go index 161ac2d..b0f9acc 100644 --- a/server/main.go +++ b/server/main.go @@ -111,7 +111,7 @@ func main() { case endsWith(r.URL.Path, "/icon") && r.Method == http.MethodGet: handlers.IconGetHandler(database)(w, r) case r.Method == http.MethodDelete: - handlers.DeleteAgentHandler(database)(w, r) + handlers.DeleteAgentHandler(database, hub.Broadcast)(w, r) default: http.NotFound(w, r) }