feat: métriques réseau+hardware serveur+dashboard + API /agents/{id} + iperf3
Serveur:
- Modèles Go: NetworkInterface, HardwareInfo dans Agent + AgentMetrics
- DB: migrations network_info_json + hardware_info_json dans agents
- UpsertAgent: stocke les données lentes si présentes dans le payload
- GetAgents: désérialise network_info_json + hardware_info_json
- GET /api/agents/{id}: endpoint single agent
- docker-compose: service iperf3 (port 5201)
Dashboard:
- Popup détail: section RÉSEAU (tableau interfaces: type, vitesse, MAC, WoL, iperf3)
- Popup détail: section HARDWARE (carte mère, CPU, RAM slots/type/vitesse)
- CSS: .net-table/.net-row pour le tableau réseau
- Font-size global appliqué sur html root (au lieu de body)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+31
-6
@@ -68,6 +68,8 @@ func (d *DB) migrate() error {
|
||||
_, _ = 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`)
|
||||
_, _ = d.conn.Exec(`ALTER TABLE agents ADD COLUMN network_info_json TEXT`)
|
||||
_, _ = d.conn.Exec(`ALTER TABLE agents ADD COLUMN hardware_info_json TEXT`)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -75,13 +77,27 @@ func (d *DB) Close() { _ = d.conn.Close() }
|
||||
|
||||
func (d *DB) UpsertAgent(m *models.AgentMetrics) error {
|
||||
ts := time.Now().Unix()
|
||||
var netJSON, hwJSON interface{}
|
||||
if len(m.NetworkInfo) > 0 {
|
||||
if b, err := json.Marshal(m.NetworkInfo); err == nil {
|
||||
netJSON = string(b)
|
||||
}
|
||||
}
|
||||
if m.HardwareInfo != nil {
|
||||
if b, err := json.Marshal(m.HardwareInfo); err == nil {
|
||||
hwJSON = string(b)
|
||||
}
|
||||
}
|
||||
_, err := d.conn.Exec(`
|
||||
INSERT INTO agents (id, hostname, ip, status, last_seen, version)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO agents (id, hostname, ip, status, last_seen, version,
|
||||
network_info_json, hardware_info_json)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
ip=excluded.ip, status=excluded.status, last_seen=excluded.last_seen,
|
||||
version=CASE WHEN excluded.version != '' THEN excluded.version ELSE version END`,
|
||||
m.Hostname, m.Hostname, m.IP, m.Status, ts, m.Version)
|
||||
version=CASE WHEN excluded.version != '' THEN excluded.version ELSE version END,
|
||||
network_info_json=CASE WHEN excluded.network_info_json IS NOT NULL THEN excluded.network_info_json ELSE network_info_json END,
|
||||
hardware_info_json=CASE WHEN excluded.hardware_info_json IS NOT NULL THEN excluded.hardware_info_json ELSE hardware_info_json END`,
|
||||
m.Hostname, m.Hostname, m.IP, m.Status, ts, m.Version, netJSON, hwJSON)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -109,7 +125,8 @@ func (d *DB) InsertMetrics(m *models.AgentMetrics) error {
|
||||
}
|
||||
|
||||
func (d *DB) GetAgents() ([]models.Agent, error) {
|
||||
rows, err := d.conn.Query(`SELECT id, hostname, ip, status, last_seen, version FROM agents`)
|
||||
rows, err := d.conn.Query(`SELECT id, hostname, ip, status, last_seen, version,
|
||||
network_info_json, hardware_info_json FROM agents`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -117,9 +134,17 @@ func (d *DB) GetAgents() ([]models.Agent, error) {
|
||||
var agents []models.Agent
|
||||
for rows.Next() {
|
||||
var a models.Agent
|
||||
if err := rows.Scan(&a.ID, &a.Hostname, &a.IP, &a.Status, &a.LastSeen, &a.Version); err != nil {
|
||||
var netJSON, hwJSON *string
|
||||
if err := rows.Scan(&a.ID, &a.Hostname, &a.IP, &a.Status, &a.LastSeen, &a.Version,
|
||||
&netJSON, &hwJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if netJSON != nil {
|
||||
_ = json.Unmarshal([]byte(*netJSON), &a.NetworkInfo)
|
||||
}
|
||||
if hwJSON != nil {
|
||||
_ = json.Unmarshal([]byte(*hwJSON), &a.HardwareInfo)
|
||||
}
|
||||
agents = append(agents, a)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
|
||||
@@ -29,5 +29,14 @@ services:
|
||||
depends_on:
|
||||
- server
|
||||
|
||||
iperf3:
|
||||
image: ${IPERF3_IMAGE:-public.ecr.aws/docker/library/networkstatic/iperf3:latest}
|
||||
pull_policy: if_not_present
|
||||
restart: unless-stopped
|
||||
command: ["-s"]
|
||||
ports:
|
||||
- "5201:5201/tcp"
|
||||
- "5201:5201/udp"
|
||||
|
||||
volumes:
|
||||
nanometrics_data:
|
||||
|
||||
@@ -24,6 +24,31 @@ func AgentsHandler(database *db.DB) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func AgentDetailHandler(database *db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
||||
if len(parts) < 3 {
|
||||
http.Error(w, "invalid path", 400)
|
||||
return
|
||||
}
|
||||
agentID := parts[2]
|
||||
agents, err := database.GetAgents()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
for _, a := range agents {
|
||||
if a.ID == agentID {
|
||||
a.LastMetrics, _ = database.GetLastMetrics(agentID)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(a)
|
||||
return
|
||||
}
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
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, "/"), "/")
|
||||
|
||||
@@ -115,6 +115,8 @@ func main() {
|
||||
handlers.IconGetHandler(database)(w, r)
|
||||
case r.Method == http.MethodDelete:
|
||||
handlers.DeleteAgentHandler(database, hub.Broadcast)(w, r)
|
||||
case r.Method == http.MethodGet:
|
||||
handlers.AgentDetailHandler(database)(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
+46
-23
@@ -1,22 +1,43 @@
|
||||
package models
|
||||
|
||||
type AgentMetrics struct {
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version"`
|
||||
CPUPercent *float64 `json:"cpu_percent"`
|
||||
MemoryUsed *int64 `json:"memory_used"`
|
||||
MemoryFree *int64 `json:"memory_free"`
|
||||
MemoryTotal *int64 `json:"memory_total"`
|
||||
HDDUsed *int64 `json:"hdd_used"`
|
||||
HDDFree *int64 `json:"hdd_free"`
|
||||
HDDTotal *int64 `json:"hdd_total"`
|
||||
Uptime *int64 `json:"uptime"`
|
||||
NetworkRX *int64 `json:"network_rx"`
|
||||
NetworkTX *int64 `json:"network_tx"`
|
||||
Temperature *float64 `json:"temperature"`
|
||||
Smart []SmartMetrics `json:"smart"`
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version"`
|
||||
CPUPercent *float64 `json:"cpu_percent"`
|
||||
MemoryUsed *int64 `json:"memory_used"`
|
||||
MemoryFree *int64 `json:"memory_free"`
|
||||
MemoryTotal *int64 `json:"memory_total"`
|
||||
HDDUsed *int64 `json:"hdd_used"`
|
||||
HDDFree *int64 `json:"hdd_free"`
|
||||
HDDTotal *int64 `json:"hdd_total"`
|
||||
Uptime *int64 `json:"uptime"`
|
||||
NetworkRX *int64 `json:"network_rx"`
|
||||
NetworkTX *int64 `json:"network_tx"`
|
||||
Temperature *float64 `json:"temperature"`
|
||||
Smart []SmartMetrics `json:"smart"`
|
||||
NetworkInfo []NetworkInterface `json:"network_info"`
|
||||
HardwareInfo *HardwareInfo `json:"hardware_info"`
|
||||
}
|
||||
|
||||
type NetworkInterface struct {
|
||||
Name string `json:"name"`
|
||||
IfType string `json:"if_type"`
|
||||
SpeedMbps *int64 `json:"speed_mbps"`
|
||||
MAC string `json:"mac"`
|
||||
WoL *bool `json:"wol"`
|
||||
IperfMbps *float64 `json:"iperf_mbps"`
|
||||
}
|
||||
|
||||
type HardwareInfo struct {
|
||||
MotherboardVendor *string `json:"motherboard_vendor"`
|
||||
MotherboardModel *string `json:"motherboard_model"`
|
||||
CPUModel *string `json:"cpu_model"`
|
||||
RAMType *string `json:"ram_type"`
|
||||
RAMSpeedMHz *int64 `json:"ram_speed_mhz"`
|
||||
RAMSlotsUsed *int64 `json:"ram_slots_used"`
|
||||
RAMSlotsTotal *int64 `json:"ram_slots_total"`
|
||||
}
|
||||
|
||||
type SmartMetrics struct {
|
||||
@@ -29,13 +50,15 @@ type SmartMetrics struct {
|
||||
}
|
||||
|
||||
type Agent struct {
|
||||
ID string `json:"id"`
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
Status string `json:"status"`
|
||||
LastSeen int64 `json:"last_seen"`
|
||||
Version string `json:"version,omitempty"`
|
||||
LastMetrics *AgentMetrics `json:"last_metrics,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
Status string `json:"status"`
|
||||
LastSeen int64 `json:"last_seen"`
|
||||
Version string `json:"version,omitempty"`
|
||||
LastMetrics *AgentMetrics `json:"last_metrics,omitempty"`
|
||||
NetworkInfo []NetworkInterface `json:"network_info,omitempty"`
|
||||
HardwareInfo *HardwareInfo `json:"hardware_info,omitempty"`
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
|
||||
Reference in New Issue
Block a user