feat(v0.1.5): SMART multi-disques — collecte tous les disques détectés
Agent: - SmartMetrics + champ device (nom du disque ex: sda, nvme0) - smart: Option<Vec<SmartMetrics>> — tous les disques, pas seulement le 1er - collect() itère /sys/block, accumule les résultats de tous les disques valides Serveur: - SmartMetrics.Device + Smart []SmartMetrics dans AgentMetrics - InsertMetrics: stocke smart_json (JSON array) au lieu de colonnes plates - GetLastMetrics: désérialise smart_json - Migration: smart_json TEXT ajoutée Dashboard: - Tuile: une icône shield/triangle par disque avec tooltip incluant le nom - Popup détail: un bouton SMART par disque (couleur ok/err) - showSmart(agentId, diskIdx): affiche le disque sélectionné Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+14
-32
@@ -67,6 +67,7 @@ func (d *DB) migrate() error {
|
||||
_, _ = d.conn.Exec(`ALTER TABLE metrics ADD COLUMN smart_realloc INTEGER`)
|
||||
_, _ = d.conn.Exec(`ALTER TABLE metrics ADD COLUMN smart_hours INTEGER`)
|
||||
_, _ = d.conn.Exec(`ALTER TABLE metrics ADD COLUMN smart_wear INTEGER`)
|
||||
_, _ = d.conn.Exec(`ALTER TABLE metrics ADD COLUMN smart_json TEXT`)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -86,30 +87,24 @@ func (d *DB) UpsertAgent(m *models.AgentMetrics) error {
|
||||
|
||||
func (d *DB) InsertMetrics(m *models.AgentMetrics) error {
|
||||
ts := time.Now().Unix()
|
||||
var smartPassed, smartTemp, smartRealloc, smartHours, smartWear interface{}
|
||||
if m.Smart != nil {
|
||||
b := 0
|
||||
if m.Smart.Passed {
|
||||
b = 1
|
||||
var smartJSON interface{}
|
||||
if len(m.Smart) > 0 {
|
||||
if b, err := json.Marshal(m.Smart); err == nil {
|
||||
smartJSON = string(b)
|
||||
}
|
||||
smartPassed = b
|
||||
smartTemp = m.Smart.Temperature
|
||||
smartRealloc = m.Smart.ReallocatedSectors
|
||||
smartHours = m.Smart.PowerOnHours
|
||||
smartWear = m.Smart.WearLevel
|
||||
}
|
||||
_, err := d.conn.Exec(`
|
||||
INSERT INTO metrics (agent_id, ts,
|
||||
cpu_percent, memory_used, memory_free, memory_total,
|
||||
hdd_used, hdd_free, hdd_total,
|
||||
uptime, network_rx, network_tx, temperature,
|
||||
smart_passed, smart_temp, smart_realloc, smart_hours, smart_wear)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
smart_json)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
m.Hostname, ts,
|
||||
m.CPUPercent, m.MemoryUsed, m.MemoryFree, m.MemoryTotal,
|
||||
m.HDDUsed, m.HDDFree, m.HDDTotal,
|
||||
m.Uptime, m.NetworkRX, m.NetworkTX, m.Temperature,
|
||||
smartPassed, smartTemp, smartRealloc, smartHours, smartWear)
|
||||
smartJSON)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -137,11 +132,8 @@ func (d *DB) GetLastMetrics(agentID string) (*models.AgentMetrics, error) {
|
||||
var cpu, temperature *float64
|
||||
var memUsed, memFree, memTotal, hddUsed, hddFree, hddTotal *int64
|
||||
var uptime, netRX, netTX *int64
|
||||
var smartPassed, smartTemp, smartRealloc, smartHours, smartWear *int64
|
||||
var smartJSON *string
|
||||
|
||||
// Chaque sous-requête prend la dernière valeur NON NULL de sa colonne.
|
||||
// Nécessaire car les paquets rapides (2s) ne contiennent pas les métriques
|
||||
// lentes (disque, smart) qui sont envoyées toutes les 60s.
|
||||
err := d.conn.QueryRow(`
|
||||
SELECT
|
||||
(SELECT cpu_percent FROM metrics WHERE agent_id=? AND cpu_percent IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||
@@ -155,19 +147,15 @@ func (d *DB) GetLastMetrics(agentID string) (*models.AgentMetrics, error) {
|
||||
(SELECT network_rx FROM metrics WHERE agent_id=? AND network_rx IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||
(SELECT network_tx FROM metrics WHERE agent_id=? AND network_tx IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||
(SELECT temperature FROM metrics WHERE agent_id=? AND temperature IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||
(SELECT smart_passed FROM metrics WHERE agent_id=? AND smart_passed IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||
(SELECT smart_temp FROM metrics WHERE agent_id=? AND smart_passed IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||
(SELECT smart_realloc FROM metrics WHERE agent_id=? AND smart_passed IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||
(SELECT smart_hours FROM metrics WHERE agent_id=? AND smart_passed IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||
(SELECT smart_wear FROM metrics WHERE agent_id=? AND smart_passed IS NOT NULL ORDER BY ts DESC LIMIT 1)`,
|
||||
(SELECT smart_json FROM metrics WHERE agent_id=? AND smart_json IS NOT NULL ORDER BY ts DESC LIMIT 1)`,
|
||||
agentID, agentID, agentID, agentID,
|
||||
agentID, agentID, agentID,
|
||||
agentID, agentID, agentID, agentID,
|
||||
agentID, agentID, agentID, agentID, agentID).
|
||||
agentID).
|
||||
Scan(&cpu, &memUsed, &memFree, &memTotal,
|
||||
&hddUsed, &hddFree, &hddTotal,
|
||||
&uptime, &netRX, &netTX, &temperature,
|
||||
&smartPassed, &smartTemp, &smartRealloc, &smartHours, &smartWear)
|
||||
&smartJSON)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -187,14 +175,8 @@ func (d *DB) GetLastMetrics(agentID string) (*models.AgentMetrics, error) {
|
||||
NetworkTX: netTX,
|
||||
Temperature: temperature,
|
||||
}
|
||||
if smartPassed != nil {
|
||||
m.Smart = &models.SmartMetrics{
|
||||
Passed: *smartPassed == 1,
|
||||
Temperature: smartTemp,
|
||||
ReallocatedSectors: smartRealloc,
|
||||
PowerOnHours: smartHours,
|
||||
WearLevel: smartWear,
|
||||
}
|
||||
if smartJSON != nil {
|
||||
_ = json.Unmarshal([]byte(*smartJSON), &m.Smart)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@ type AgentMetrics struct {
|
||||
NetworkRX *int64 `json:"network_rx"`
|
||||
NetworkTX *int64 `json:"network_tx"`
|
||||
Temperature *float64 `json:"temperature"`
|
||||
Smart *SmartMetrics `json:"smart"`
|
||||
Smart []SmartMetrics `json:"smart"`
|
||||
}
|
||||
|
||||
type SmartMetrics struct {
|
||||
Device string `json:"device"`
|
||||
Passed bool `json:"passed"`
|
||||
Temperature *int64 `json:"temperature"`
|
||||
ReallocatedSectors *int64 `json:"reallocated_sectors"`
|
||||
|
||||
Reference in New Issue
Block a user