feat(v0.1.4): SMART tile icon, IP locale robuste, copier HTTP, nettoyage UI

- Agent: détection IP via server_ip en priorité (fallback 8.8.8.8) — résout 0.0.0.0 sur LAN sans internet
- Agent: détection auto des disques /sys/block (sd*, nvme*) + fix continue dans la boucle smartctl
- Agent: SupplementaryGroups=disk dans le service systemd pour accès smartctl
- Dashboard: icône SMART (shield-check/triangle-exclamation) dans la ligne disque de la tuile
- Dashboard: bouton Copier compatible HTTP (fallback execCommand si clipboard API indisponible)
- Dashboard: suppression du texte redondant dans la section INSTALLATION AGENT

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gilles Soulier
2026-05-22 22:46:52 +02:00
parent e65770407c
commit a2060a1713
7 changed files with 59 additions and 16 deletions
+1 -1
View File
@@ -248,7 +248,7 @@ dependencies = [
[[package]]
name = "nanometrics-agent"
version = "0.1.2"
version = "0.1.4"
dependencies = [
"libc",
"rumqttc",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nanometrics-agent"
version = "0.1.3"
version = "0.1.4"
edition = "2021"
[lib]
+12 -6
View File
@@ -10,12 +10,18 @@ extern "C" fn handle_signal(_: libc::c_int) {
RUNNING.store(false, Ordering::Relaxed);
}
fn get_local_ip() -> String {
fn get_local_ip(server_ip: &str) -> String {
use std::net::UdpSocket;
if let Ok(s) = UdpSocket::bind("0.0.0.0:0") {
if s.connect("8.8.8.8:80").is_ok() {
if let Ok(addr) = s.local_addr() {
return addr.ip().to_string();
// Try server IP first (always reachable), then internet fallback
for target in &[format!("{}:80", server_ip), "8.8.8.8:80".to_string()] {
if let Ok(s) = UdpSocket::bind("0.0.0.0:0") {
if s.connect(target.as_str()).is_ok() {
if let Ok(addr) = s.local_addr() {
let ip = addr.ip().to_string();
if ip != "0.0.0.0" {
return ip;
}
}
}
}
}
@@ -37,7 +43,7 @@ fn main() {
.expect("Impossible de charger config.toml");
let hostname = System::host_name().unwrap_or_else(|| "unknown".to_string());
let ip = get_local_ip();
let ip = get_local_ip(&cfg.server.ip);
let mut sys = System::new();
let mut networks = Networks::new_with_refreshed_list();
+14 -4
View File
@@ -84,11 +84,21 @@ pub fn collect() -> Option<SmartMetrics> {
if !is_available() {
return None;
}
for dev in &["/dev/sda", "/dev/nvme0"] {
let output = std::process::Command::new("smartctl")
// Détecter les disques réels depuis /sys/block (sd*, nvme*)
let mut devs: Vec<String> = std::fs::read_dir("/sys/block")
.into_iter()
.flatten()
.flatten()
.map(|e| e.file_name().into_string().unwrap_or_default())
.filter(|n| n.starts_with("sd") || n.starts_with("nvme"))
.map(|n| format!("/dev/{}", n))
.collect();
devs.sort();
for dev in &devs {
let Ok(output) = std::process::Command::new("smartctl")
.args(["-j", dev])
.output()
.ok()?;
.output() else { continue };
let json = String::from_utf8_lossy(&output.stdout);
if let Ok(metrics) = parse_json(&json) {
return Some(metrics);