feat(widgets): implémentation Phase 4 — widgets Glance complets
- widget-network-scan : liste équipements avec état (online/offline), hostname, IP, vendor, badges services, tri online en premier - widget-agent-metrics : barres CPU/RAM/disque/température par agent, code couleur ok (vert) / warn (orange) / crit (rouge) - sentinelmesh.css : styles custom (points statut, badges, barres de progression animées) compatibles thèmes Glance - glance-page-example.yaml : page Infrastructure prête à l'emploi - Backend widgets enrichi : mac, ports, offline count, net_rx/tx_bps - ROADMAP Phase 4 marquée complète Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,45 +13,52 @@ pub struct NetworkWidget {
|
||||
pub last_scan_at: Option<String>,
|
||||
pub total: usize,
|
||||
pub online: usize,
|
||||
pub offline: usize,
|
||||
pub devices: Vec<DeviceSummary>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DeviceSummary {
|
||||
pub ip: String,
|
||||
pub hostname: Option<String>,
|
||||
pub vendor: Option<String>,
|
||||
pub state: String,
|
||||
pub services: Value,
|
||||
pub ip: String,
|
||||
pub hostname: Option<String>,
|
||||
pub vendor: Option<String>,
|
||||
pub mac: Option<String>,
|
||||
pub state: String,
|
||||
pub services: Value,
|
||||
pub open_ports: Value,
|
||||
pub last_seen: String,
|
||||
}
|
||||
|
||||
#[oapath(get, path = "/api/v1/widgets/network",
|
||||
responses((status = 200, description = "Données widget réseau pour Glance")))]
|
||||
pub async fn network(State(db): State<SqlitePool>) -> Result<Json<NetworkWidget>> {
|
||||
let devices = sqlx::query_as::<_, Device>("SELECT * FROM devices ORDER BY state, ip")
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
// Online en premier, puis tri IP
|
||||
let devices = sqlx::query_as::<_, Device>(
|
||||
"SELECT * FROM devices ORDER BY CASE state WHEN 'online' THEN 0 ELSE 1 END, ip"
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
|
||||
let online = devices.iter().filter(|d| d.state == "online").count();
|
||||
let offline = devices.len() - online;
|
||||
|
||||
let summary = devices
|
||||
.iter()
|
||||
.map(|d| DeviceSummary {
|
||||
ip: d.ip.clone(),
|
||||
hostname: d.hostname.clone(),
|
||||
vendor: d.vendor.clone(),
|
||||
state: d.state.clone(),
|
||||
services: serde_json::from_str(&d.services).unwrap_or(Value::Array(vec![])),
|
||||
ip: d.ip.clone(),
|
||||
hostname: d.hostname.clone(),
|
||||
vendor: d.vendor.clone(),
|
||||
mac: d.mac.clone(),
|
||||
state: d.state.clone(),
|
||||
services: serde_json::from_str(&d.services).unwrap_or(Value::Array(vec![])),
|
||||
open_ports: serde_json::from_str(&d.open_ports).unwrap_or(Value::Array(vec![])),
|
||||
last_seen: d.last_seen.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let last_scan = devices.iter().map(|d| d.last_seen.clone()).max();
|
||||
|
||||
Ok(Json(NetworkWidget {
|
||||
last_scan_at: last_scan,
|
||||
total: devices.len(),
|
||||
online,
|
||||
devices: summary,
|
||||
}))
|
||||
Ok(Json(NetworkWidget { last_scan_at: last_scan, total: devices.len(), online, offline, devices: summary }))
|
||||
}
|
||||
|
||||
// --- Widget métriques ---
|
||||
@@ -66,10 +73,12 @@ pub struct AgentMetricSummary {
|
||||
pub agent_id: String,
|
||||
pub hostname: String,
|
||||
pub status: String,
|
||||
pub cpu_percent: Option<f64>,
|
||||
pub ram_percent: Option<f64>,
|
||||
pub disk_percent: Option<f64>,
|
||||
pub temperature_c: Option<f64>,
|
||||
pub cpu_percent: f64,
|
||||
pub ram_percent: f64,
|
||||
pub disk_percent: f64,
|
||||
pub temperature_c: f64,
|
||||
pub net_rx_bps: i64,
|
||||
pub net_tx_bps: i64,
|
||||
pub last_seen: String,
|
||||
}
|
||||
|
||||
@@ -77,10 +86,15 @@ pub struct AgentMetricSummary {
|
||||
responses((status = 200, description = "Données widget métriques pour Glance")))]
|
||||
pub async fn metrics(State(db): State<SqlitePool>) -> Result<Json<MetricsWidget>> {
|
||||
let rows = sqlx::query_as::<_, (
|
||||
String, String, String, Option<f64>, Option<f64>, Option<f64>, Option<f64>, String,
|
||||
String, String, String,
|
||||
Option<f64>, Option<f64>, Option<f64>, Option<f64>,
|
||||
Option<i64>, Option<i64>,
|
||||
String,
|
||||
)>(
|
||||
"SELECT a.id, a.hostname, a.status, m.cpu_percent, m.ram_percent,
|
||||
m.disk_percent, m.temperature_c, a.last_seen
|
||||
"SELECT a.id, a.hostname, a.status,
|
||||
m.cpu_percent, m.ram_percent, m.disk_percent, m.temperature_c,
|
||||
m.net_rx_bps, m.net_tx_bps,
|
||||
a.last_seen
|
||||
FROM agents a LEFT JOIN metrics m ON m.agent_id = a.id
|
||||
WHERE a.agent_type = 'metric'
|
||||
ORDER BY a.hostname",
|
||||
@@ -90,9 +104,17 @@ pub async fn metrics(State(db): State<SqlitePool>) -> Result<Json<MetricsWidget>
|
||||
|
||||
let agents = rows
|
||||
.into_iter()
|
||||
.map(|(id, hostname, status, cpu, ram, disk, temp, last_seen)| AgentMetricSummary {
|
||||
agent_id: id, hostname, status, cpu_percent: cpu, ram_percent: ram,
|
||||
disk_percent: disk, temperature_c: temp, last_seen,
|
||||
.map(|(id, hostname, status, cpu, ram, disk, temp, rx, tx, last_seen)| AgentMetricSummary {
|
||||
agent_id: id,
|
||||
hostname,
|
||||
status,
|
||||
cpu_percent: cpu.unwrap_or(0.0),
|
||||
ram_percent: ram.unwrap_or(0.0),
|
||||
disk_percent: disk.unwrap_or(0.0),
|
||||
temperature_c: temp.unwrap_or(0.0),
|
||||
net_rx_bps: rx.unwrap_or(0),
|
||||
net_tx_bps: tx.unwrap_or(0),
|
||||
last_seen,
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user