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:
2026-05-19 06:21:57 +02:00
parent 6bda1a2b59
commit 8a8641e9cd
6 changed files with 424 additions and 36 deletions
+140
View File
@@ -0,0 +1,140 @@
# Exemple de page Glance complète avec les deux widgets SentinelMesh
# À intégrer dans votre glance.yml
#
# Installation :
# 1. Copier sentinelmesh.css dans votre répertoire assets Glance
# 2. Référencer le CSS dans server: custom-css-file: /assets/sentinelmesh.css
# 3. Ajouter cette page dans votre glance.yml
pages:
- name: Infrastructure
slug: infra
width: wide
columns:
- size: small
widgets:
# --- Métriques système ---
- type: custom-api
title: Métriques systèmes
cache: 5s
url: http://sentinelmesh:8080/api/v1/widgets/metrics
template: |
<ul class="list list-gap-15">
{{ range .JSON.Array "agents" }}
{{- $cpu := .Float "cpu_percent" -}}
{{- $ram := .Float "ram_percent" -}}
{{- $disk := .Float "disk_percent" -}}
{{- $temp := .Float "temperature_c" -}}
{{- $up := eq (.String "status") "online" -}}
<li class="sm-agent">
<div class="flex justify-between items-center margin-bottom-6">
<span class="size-h4 {{ if $up }}color-highlight{{ else }}color-negative{{ end }}">
{{ .String "hostname" }}
</span>
<span class="size-h6 color-paragraph"
{{ .String "last_seen" | parseTime "rfc3339" | toRelativeTime }}></span>
</div>
{{ if $up }}
<div class="sm-metric-row">
<span class="sm-metric-label size-h6 color-paragraph">CPU</span>
<div class="sm-bar-track">
<div class="sm-bar {{ if ge $cpu 85.0 }}sm-bar-crit{{ else if ge $cpu 60.0 }}sm-bar-warn{{ else }}sm-bar-ok{{ end }}"
style="width: {{ printf "%.0f" $cpu }}%"></div>
</div>
<span class="sm-metric-val size-h6">{{ printf "%.0f" $cpu }}%</span>
</div>
<div class="sm-metric-row">
<span class="sm-metric-label size-h6 color-paragraph">RAM</span>
<div class="sm-bar-track">
<div class="sm-bar {{ if ge $ram 90.0 }}sm-bar-crit{{ else if ge $ram 70.0 }}sm-bar-warn{{ else }}sm-bar-ok{{ end }}"
style="width: {{ printf "%.0f" $ram }}%"></div>
</div>
<span class="sm-metric-val size-h6">{{ printf "%.0f" $ram }}%</span>
</div>
<div class="sm-metric-row">
<span class="sm-metric-label size-h6 color-paragraph">Disque</span>
<div class="sm-bar-track">
<div class="sm-bar {{ if ge $disk 90.0 }}sm-bar-crit{{ else if ge $disk 75.0 }}sm-bar-warn{{ else }}sm-bar-ok{{ end }}"
style="width: {{ printf "%.0f" $disk }}%"></div>
</div>
<span class="sm-metric-val size-h6">{{ printf "%.0f" $disk }}%</span>
</div>
{{ if gt $temp 0.0 }}
<div class="sm-metric-row">
<span class="sm-metric-label size-h6 color-paragraph">Temp</span>
<div class="sm-bar-track">
<div class="sm-bar {{ if ge $temp 85.0 }}sm-bar-crit{{ else if ge $temp 70.0 }}sm-bar-warn{{ else }}sm-bar-ok{{ end }}"
style="width: {{ printf "%.0f" (mul (div $temp 100.0) 100.0) }}%"></div>
</div>
<span class="sm-metric-val size-h6">{{ printf "%.0f" $temp }}°C</span>
</div>
{{ end }}
{{ else }}
<p class="size-h6 color-negative">Agent hors ligne</p>
{{ end }}
</li>
{{ end }}
</ul>
- size: full
widgets:
# --- Découverte réseau ---
- type: custom-api
title: Réseau local — 10.0.0.0/22
cache: 30s
url: http://sentinelmesh:8080/api/v1/widgets/network
template: |
{{- $online := .JSON.Int "online" -}}
{{- $offline := .JSON.Int "offline" -}}
{{- $total := .JSON.Int "total" -}}
<div class="sm-net-header flex justify-between margin-bottom-10">
<span class="size-h5 color-paragraph">
<span class="color-positive">{{ $online }} en ligne</span>
{{ if gt $offline 0 }}&nbsp;·&nbsp;<span class="color-negative">{{ $offline }} hors ligne</span>{{ end }}
&nbsp;·&nbsp; {{ $total }} total
</span>
{{ if .JSON.Exists "last_scan_at" }}
<span class="size-h6 color-paragraph"
{{ .JSON.String "last_scan_at" | parseTime "rfc3339" | toRelativeTime }}></span>
{{ end }}
</div>
<ul class="list list-gap-10 collapsible-container" data-collapse-after="15">
{{ range .JSON.Array "devices" }}
{{- $state := .String "state" -}}
{{- $host := .String "hostname" -}}
{{- $ip := .String "ip" -}}
{{- $vendor := .String "vendor" -}}
<li class="sm-device flex gap-10 items-center">
<div class="sm-state-dot {{ if eq $state "online" }}sm-dot-online{{ else }}sm-dot-offline{{ end }}"></div>
<div class="grow min-width-0">
<div class="flex justify-between">
<span class="size-h4 {{ if eq $state "online" }}color-highlight{{ else }}color-paragraph{{ end }} text-truncate">
{{- if ne $host "" }}{{ $host }}{{ else }}{{ $ip }}{{ end -}}
</span>
<span class="size-h6 color-paragraph shrink-0 margin-left-10">{{ $ip }}</span>
</div>
<div class="flex gap-5 flex-wrap margin-top-3">
{{ if ne $vendor "" }}
<span class="sm-badge sm-badge-vendor size-h6">{{ $vendor }}</span>
{{ end }}
{{ range .Array "services" }}
<span class="sm-badge size-h6">{{ .String "" }}</span>
{{ end }}
</div>
</div>
</li>
{{ end }}
</ul>
+87
View File
@@ -0,0 +1,87 @@
/* ============================================================
SentinelMesh — CSS custom pour widgets Glance
Copier dans le répertoire assets de Glance et référencer :
custom-css-file: /assets/sentinelmesh.css
============================================================ */
/* --- Widget réseau --- */
.sm-net-header {
align-items: center;
}
.sm-device {
align-items: flex-start;
padding: 4px 0;
}
/* Point de statut coloré */
.sm-state-dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-top: 5px;
flex-shrink: 0;
}
.sm-dot-online { background: var(--color-positive, #4caf50); }
.sm-dot-offline { background: var(--color-negative, #f44336); opacity: 0.5; }
/* Badges services / vendor */
.sm-badge {
display: inline-block;
padding: 1px 6px;
border-radius: 4px;
background: var(--color-widget-content-background, rgba(255,255,255,0.06));
color: var(--color-text-subdue, #aaa);
line-height: 1.6;
}
.sm-badge-vendor {
color: var(--color-primary, #7ca9d4);
}
/* --- Widget métriques --- */
.sm-agent {
padding-bottom: 6px;
border-bottom: 1px solid var(--color-widget-content-background, rgba(255,255,255,0.06));
}
.sm-agent:last-child { border-bottom: none; }
.sm-metric-row {
display: flex;
align-items: center;
gap: 8px;
margin-top: 4px;
}
.sm-metric-label {
width: 40px;
flex-shrink: 0;
text-align: right;
}
.sm-metric-val {
width: 36px;
flex-shrink: 0;
text-align: right;
}
/* Barre de progression */
.sm-bar-track {
flex: 1;
height: 4px;
border-radius: 2px;
background: var(--color-widget-content-background, rgba(255,255,255,0.08));
overflow: hidden;
}
.sm-bar {
height: 100%;
border-radius: 2px;
min-width: 2px;
transition: width 0.4s ease;
}
.sm-bar-ok { background: var(--color-positive, #4caf50); }
.sm-bar-warn { background: var(--color-base-500, #ff9800); }
.sm-bar-crit { background: var(--color-negative, #f44336); }
+76
View File
@@ -0,0 +1,76 @@
# Widget SentinelMesh — Métriques système
# À copier dans votre glance.yml
#
# Prérequis :
# - Backend SentinelMesh démarré (port 8080)
# - agent-metric en cours d'exécution sur chaque machine
# - Fichier CSS custom : assets/sentinelmesh.css
- type: custom-api
title: Métriques systèmes
cache: 5s
url: http://sentinelmesh:8080/api/v1/widgets/metrics
template: |
<ul class="list list-gap-15">
{{ range .JSON.Array "agents" }}
{{- $cpu := .Float "cpu_percent" -}}
{{- $ram := .Float "ram_percent" -}}
{{- $disk := .Float "disk_percent" -}}
{{- $temp := .Float "temperature_c" -}}
{{- $up := eq (.String "status") "online" -}}
<li class="sm-agent">
<div class="flex justify-between items-center margin-bottom-6">
<span class="size-h4 {{ if $up }}color-highlight{{ else }}color-negative{{ end }}">
{{ .String "hostname" }}
</span>
<span class="size-h6 color-paragraph"
{{ .String "last_seen" | parseTime "rfc3339" | toRelativeTime }}></span>
</div>
{{ if $up }}
<div class="sm-metric-row">
<span class="sm-metric-label size-h6 color-paragraph">CPU</span>
<div class="sm-bar-track">
<div class="sm-bar {{ if ge $cpu 85.0 }}sm-bar-crit{{ else if ge $cpu 60.0 }}sm-bar-warn{{ else }}sm-bar-ok{{ end }}"
style="width: {{ printf "%.0f" $cpu }}%"></div>
</div>
<span class="sm-metric-val size-h6">{{ printf "%.0f" $cpu }}%</span>
</div>
<div class="sm-metric-row">
<span class="sm-metric-label size-h6 color-paragraph">RAM</span>
<div class="sm-bar-track">
<div class="sm-bar {{ if ge $ram 90.0 }}sm-bar-crit{{ else if ge $ram 70.0 }}sm-bar-warn{{ else }}sm-bar-ok{{ end }}"
style="width: {{ printf "%.0f" $ram }}%"></div>
</div>
<span class="sm-metric-val size-h6">{{ printf "%.0f" $ram }}%</span>
</div>
<div class="sm-metric-row">
<span class="sm-metric-label size-h6 color-paragraph">Disque</span>
<div class="sm-bar-track">
<div class="sm-bar {{ if ge $disk 90.0 }}sm-bar-crit{{ else if ge $disk 75.0 }}sm-bar-warn{{ else }}sm-bar-ok{{ end }}"
style="width: {{ printf "%.0f" $disk }}%"></div>
</div>
<span class="sm-metric-val size-h6">{{ printf "%.0f" $disk }}%</span>
</div>
{{ if gt $temp 0.0 }}
<div class="sm-metric-row">
<span class="sm-metric-label size-h6 color-paragraph">Temp</span>
<div class="sm-bar-track">
<div class="sm-bar {{ if ge $temp 85.0 }}sm-bar-crit{{ else if ge $temp 70.0 }}sm-bar-warn{{ else }}sm-bar-ok{{ end }}"
style="width: {{ printf "%.0f" (mul (div $temp 100.0) 100.0) }}%"></div>
</div>
<span class="sm-metric-val size-h6">{{ printf "%.0f" $temp }}°C</span>
</div>
{{ end }}
{{ else }}
<p class="size-h6 color-negative">Agent hors ligne</p>
{{ end }}
</li>
{{ end }}
</ul>
+61
View File
@@ -0,0 +1,61 @@
# Widget SentinelMesh — Découverte réseau
# À copier dans votre glance.yml
#
# Prérequis :
# - Backend SentinelMesh démarré (port 8080)
# - agent-scan-network en cours d'exécution
# - Fichier CSS custom copié dans votre répertoire assets Glance
# et référencé dans glance.yml : custom-css-file: /assets/sentinelmesh.css
- type: custom-api
title: Réseau local
cache: 30s
url: http://sentinelmesh:8080/api/v1/widgets/network
template: |
{{- $online := .JSON.Int "online" -}}
{{- $offline := .JSON.Int "offline" -}}
{{- $total := .JSON.Int "total" -}}
<div class="sm-net-header flex justify-between margin-bottom-10">
<span class="size-h5 color-paragraph">
{{ $online }} en ligne
{{ if gt $offline 0 }}&nbsp;·&nbsp;<span class="color-negative">{{ $offline }} hors ligne</span>{{ end }}
&nbsp;·&nbsp; {{ $total }} total
</span>
{{ if .JSON.Exists "last_scan_at" }}
<span class="size-h6 color-paragraph"
{{ .JSON.String "last_scan_at" | parseTime "rfc3339" | toRelativeTime }}></span>
{{ end }}
</div>
<ul class="list list-gap-10 collapsible-container" data-collapse-after="8">
{{ range .JSON.Array "devices" }}
{{- $state := .String "state" -}}
{{- $host := .String "hostname" -}}
{{- $ip := .String "ip" -}}
{{- $vendor := .String "vendor" -}}
<li class="sm-device flex gap-10 items-center">
<div class="sm-state-dot {{ if eq $state "online" }}sm-dot-online{{ else }}sm-dot-offline{{ end }}"></div>
<div class="grow min-width-0">
<div class="flex justify-between">
<span class="size-h4 color-highlight text-truncate">
{{- if ne $host "" }}{{ $host }}{{ else }}{{ $ip }}{{ end -}}
</span>
<span class="size-h6 color-paragraph shrink-0 margin-left-10">{{ $ip }}</span>
</div>
<div class="flex gap-5 flex-wrap margin-top-3">
{{ if ne $vendor "" }}
<span class="sm-badge sm-badge-vendor size-h6">{{ $vendor }}</span>
{{ end }}
{{ range .Array "services" }}
<span class="sm-badge size-h6">{{ .String "" }}</span>
{{ end }}
</div>
</div>
</li>
{{ end }}
</ul>