feat: add plans, design system, CONSIGNE and brainstorm assets
Ajoute les trois plans d'implémentation (agent Rust, serveur Go, dashboard), les consignes de design, les fichiers de brainstorming et le .gitignore. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
repo.md
|
||||
@@ -0,0 +1,44 @@
|
||||
<h2>Approches architecturales</h2>
|
||||
<p class="subtitle">Base de données + protocole temps réel — choisir une approche</p>
|
||||
|
||||
<div class="options">
|
||||
<div class="option" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="letter">A</div>
|
||||
<div class="content">
|
||||
<h3>SQLite + SSE</h3>
|
||||
<p><strong>SQLite</strong> — fichier unique, pas de conteneur supplémentaire, SQL classique.<br>
|
||||
<strong>Server-Sent Events</strong> — flux unidirectionnel serveur→browser, plus simple que WebSocket.</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>Avantages</h4><ul><li>Zéro dépendance supplémentaire</li><li>Déploiement ultra-simple</li><li>SSE natif dans les browsers modernes</li></ul></div>
|
||||
<div class="cons"><h4>Limites</h4><ul><li>Performances sur gros volumes de séries temporelles</li><li>SSE unidirectionnel (pas d'envoi de commandes futures)</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="letter">B</div>
|
||||
<div class="content">
|
||||
<h3>InfluxDB + WebSocket</h3>
|
||||
<p><strong>InfluxDB</strong> — time-series DB optimisée, requêtes temporelles puissantes.<br>
|
||||
<strong>WebSocket</strong> — bidirectionnel, extensible pour des commandes futures vers les agents.</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>Avantages</h4><ul><li>Optimisé pour les métriques temporelles</li><li>WebSocket = extensible (alertes, commandes)</li><li>Grafana compatible nativement</li></ul></div>
|
||||
<div class="cons"><h4>Limites</h4>
|
||||
<ul><li>Conteneur InfluxDB supplémentaire dans le compose</li><li>Courbe d'apprentissage Flux/InfluxQL</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="letter">C</div>
|
||||
<div class="content">
|
||||
<h3>SQLite + WebSocket ⭐ Recommandé</h3>
|
||||
<p><strong>SQLite</strong> — simplicité opérationnelle, suffisant pour 20+ agents avec rétention configurable.<br>
|
||||
<strong>WebSocket</strong> — bidirectionnel dès le départ, sans surcoût opérationnel.</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>Avantages</h4><ul><li>Pas de conteneur DB supplémentaire</li><li>WebSocket prêt pour extensions futures</li><li>Simple à debugger et sauvegarder</li></ul></div>
|
||||
<div class="cons"><h4>Limites</h4><ul><li>Requêtes temporelles moins expressives qu'InfluxDB</li><li>Scalabilité limitée au-delà de ~100 agents</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh;flex-direction:column;gap:16px">
|
||||
<p style="font-size:2em">⚙️</p>
|
||||
<p class="subtitle">Brainstorming Nanometrics — questions en cours dans le terminal...</p>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
{"type":"server-started","port":60787,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:60787","screen_dir":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599106-1779425616/content","state_dir":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599106-1779425616/state"}
|
||||
@@ -0,0 +1,44 @@
|
||||
<h2>Approches architecturales</h2>
|
||||
<p class="subtitle">Base de données + protocole temps réel — choisir une approche</p>
|
||||
|
||||
<div class="options">
|
||||
<div class="option" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="letter">A</div>
|
||||
<div class="content">
|
||||
<h3>SQLite + SSE</h3>
|
||||
<p><strong>SQLite</strong> — fichier unique, pas de conteneur supplémentaire, SQL classique.<br>
|
||||
<strong>Server-Sent Events</strong> — flux unidirectionnel serveur→browser, plus simple que WebSocket.</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>Avantages</h4><ul><li>Zéro dépendance supplémentaire</li><li>Déploiement ultra-simple</li><li>SSE natif dans les browsers modernes</li></ul></div>
|
||||
<div class="cons"><h4>Limites</h4><ul><li>Performances sur gros volumes de séries temporelles</li><li>SSE unidirectionnel (pas d'envoi de commandes futures)</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="letter">B</div>
|
||||
<div class="content">
|
||||
<h3>InfluxDB + WebSocket</h3>
|
||||
<p><strong>InfluxDB</strong> — time-series DB optimisée, requêtes temporelles puissantes.<br>
|
||||
<strong>WebSocket</strong> — bidirectionnel, extensible pour des commandes futures vers les agents.</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>Avantages</h4><ul><li>Optimisé pour les métriques temporelles</li><li>WebSocket = extensible (alertes, commandes)</li><li>Grafana compatible nativement</li></ul></div>
|
||||
<div class="cons"><h4>Limites</h4>
|
||||
<ul><li>Conteneur InfluxDB supplémentaire dans le compose</li><li>Courbe d'apprentissage Flux/InfluxQL</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="letter">C</div>
|
||||
<div class="content">
|
||||
<h3>SQLite + WebSocket ⭐ Recommandé</h3>
|
||||
<p><strong>SQLite</strong> — simplicité opérationnelle, suffisant pour 20+ agents avec rétention configurable.<br>
|
||||
<strong>WebSocket</strong> — bidirectionnel dès le départ, sans surcoût opérationnel.</p>
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>Avantages</h4><ul><li>Pas de conteneur DB supplémentaire</li><li>WebSocket prêt pour extensions futures</li><li>Simple à debugger et sauvegarder</li></ul></div>
|
||||
<div class="cons"><h4>Limites</h4><ul><li>Requêtes temporelles moins expressives qu'InfluxDB</li><li>Scalabilité limitée au-delà de ~100 agents</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,646 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nanometrics — Layout A v2</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script src="/helper.js"></script>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--accent:#fe8019; --accent-soft:#d65d0e; --accent-glow:rgba(254,128,25,0.3);
|
||||
--bg-0:#1d1813; --bg-1:#2a231d; --bg-2:#32291f; --bg-3:#3c332a; --bg-4:#4a3f33; --bg-5:#57493c;
|
||||
--ink-1:#f2e5c7; --ink-2:#d5c4a1; --ink-3:#a89984; --ink-4:#7c6f64;
|
||||
--ok:#4dbb26; --warn:#fabd2f; --err:#fb4934; --info:#83a598; --blue:#3db0d1;
|
||||
--border-1:rgba(255,255,255,0.06); --border-2:rgba(255,255,255,0.12); --border-3:rgba(255,255,255,0.22);
|
||||
--shadow-2:0 4px 16px rgba(0,0,0,0.5);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,0.08) inset,0 -1px 0 rgba(0,0,0,0.3) inset,0 4px 16px rgba(0,0,0,0.5);
|
||||
--font-ui:'Inter',system-ui,sans-serif; --font-mono:'JetBrains Mono',monospace; --font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
:root[data-theme="light"] {
|
||||
--accent:#af3a03; --accent-soft:#d65d0e; --accent-glow:rgba(175,58,3,0.2);
|
||||
--bg-0:#d5c4a1; --bg-1:#ebdbb2; --bg-2:#d5c4a1; --bg-3:#bdae93; --bg-4:#a89984; --bg-5:#928374;
|
||||
--ink-1:#3c3836; --ink-2:#504945; --ink-3:#665c54; --ink-4:#7c6f64;
|
||||
--ok:#3c911c; --warn:#b57614; --err:#9d0006; --info:#427b58; --blue:#2d82a3;
|
||||
--border-1:rgba(0,0,0,0.08); --border-2:rgba(0,0,0,0.15); --border-3:rgba(0,0,0,0.25);
|
||||
--shadow-2:0 4px 16px rgba(0,0,0,0.15);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,0.5) inset,0 -1px 0 rgba(0,0,0,0.1) inset,0 4px 16px rgba(0,0,0,0.12);
|
||||
--font-ui:'Inter',system-ui,sans-serif; --font-mono:'JetBrains Mono',monospace; --font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
|
||||
body { background:var(--bg-1); color:var(--ink-1); font-family:var(--font-ui); font-size:13px; height:100vh; display:flex; flex-direction:column; overflow:hidden; transition:background .2s, color .2s; }
|
||||
|
||||
/* ══ HEADER ══ */
|
||||
.header {
|
||||
background:var(--bg-2); border-bottom:1px solid var(--border-2);
|
||||
padding:0 20px; height:48px; display:flex; align-items:center; gap:14px; flex-shrink:0;
|
||||
}
|
||||
.header-logo { display:flex; align-items:center; gap:8px; }
|
||||
.header-logo .led { width:9px; height:9px; border-radius:50%; background:var(--accent); box-shadow:0 0 8px var(--accent-glow); animation:pulse 2s infinite; }
|
||||
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.5} }
|
||||
.header-logo .name { font-weight:700; font-size:14px; letter-spacing:.04em; color:var(--ink-1); font-family:var(--font-terminal); }
|
||||
.header-logo .version { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); }
|
||||
|
||||
.header-spacer { flex:1; }
|
||||
|
||||
.header-stats { display:flex; gap:16px; align-items:center; }
|
||||
.h-stat { display:flex; align-items:center; gap:6px; }
|
||||
.h-stat .s-label { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.h-stat .s-count { font-family:var(--font-mono); font-size:13px; font-weight:600; }
|
||||
.h-stat .s-count.ok { color:var(--ok); }
|
||||
.h-stat .s-count.warn { color:var(--warn); }
|
||||
.h-stat .s-count.err { color:var(--err); }
|
||||
|
||||
.header-sep { width:1px; height:24px; background:var(--border-2); }
|
||||
|
||||
.icon-btn {
|
||||
width:34px; height:34px; border-radius:8px; border:1px solid var(--border-2);
|
||||
background:var(--bg-3); color:var(--ink-2); font-size:14px;
|
||||
display:flex; align-items:center; justify-content:center; cursor:pointer;
|
||||
transition:background .12s, color .12s;
|
||||
position:relative;
|
||||
}
|
||||
.icon-btn:hover { background:var(--bg-4); color:var(--accent); }
|
||||
.icon-btn:active { background:var(--bg-5); box-shadow:inset 0 2px 4px rgba(0,0,0,0.3); }
|
||||
.icon-btn .tooltip {
|
||||
position:absolute; top:calc(100% + 6px); right:0;
|
||||
background:var(--bg-0); color:var(--ink-1); font-size:11px; font-family:var(--font-ui);
|
||||
padding:4px 8px; border-radius:5px; border:1px solid var(--border-2);
|
||||
white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .12s;
|
||||
}
|
||||
.icon-btn:hover .tooltip { opacity:1; }
|
||||
|
||||
/* ══ MAIN / GRID ══ */
|
||||
.main { flex:1; padding:16px; overflow-y:auto; }
|
||||
|
||||
.agents-grid {
|
||||
display:grid;
|
||||
grid-template-columns:repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap:12px;
|
||||
}
|
||||
|
||||
.tile {
|
||||
background:var(--bg-3); border-radius:10px; padding:14px 16px;
|
||||
border:1px solid var(--border-1); box-shadow:var(--tile-3d);
|
||||
cursor:pointer; transition:border-color .15s, box-shadow .15s;
|
||||
display:flex; flex-direction:column; gap:10px;
|
||||
}
|
||||
.tile:hover { border-color:var(--border-3); box-shadow:var(--tile-3d), 0 0 0 1px var(--border-2); }
|
||||
.tile.warn-tile { border-color:rgba(250,189,47,.3); }
|
||||
.tile.err-tile { border-color:rgba(251,73,52,.35); box-shadow:var(--tile-3d),0 0 18px rgba(251,73,52,.12); }
|
||||
.tile.offline-tile { opacity:.55; }
|
||||
|
||||
/* tile header */
|
||||
.tile-head { display:flex; align-items:center; gap:8px; }
|
||||
.t-icon-wrap { width:28px; height:28px; border-radius:6px; background:var(--bg-4); display:flex; align-items:center; justify-content:center; color:var(--accent); font-size:13px; }
|
||||
.t-info { flex:1; min-width:0; }
|
||||
.t-hostname { font-weight:600; font-size:13px; color:var(--ink-1); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.t-ip { font-family:var(--font-mono); font-size:10px; color:var(--ink-4); }
|
||||
.t-led { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
|
||||
.t-led.ok { background:var(--ok); box-shadow:0 0 6px var(--ok); }
|
||||
.t-led.warn { background:var(--warn); box-shadow:0 0 6px var(--warn); animation:pulse 1.5s infinite; }
|
||||
.t-led.err { background:var(--err); box-shadow:0 0 8px var(--err); animation:pulse 1s infinite; }
|
||||
.t-led.off { background:var(--ink-4); }
|
||||
|
||||
/* gauges */
|
||||
.tile-gauges { display:flex; flex-direction:column; gap:5px; }
|
||||
.gauge-row { display:flex; align-items:center; gap:8px; }
|
||||
.g-label { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.04em; width:28px; }
|
||||
.g-bar { flex:1; height:5px; border-radius:3px; background:var(--bg-1); overflow:hidden; }
|
||||
.g-fill { height:100%; border-radius:3px; background:var(--ok); transition:width .4s ease; }
|
||||
.g-fill.warn { background:var(--warn); }
|
||||
.g-fill.err { background:var(--err); }
|
||||
.g-val { font-family:var(--font-mono); font-size:11px; color:var(--ink-2); width:32px; text-align:right; }
|
||||
|
||||
/* tile footer */
|
||||
.tile-foot { display:flex; align-items:center; justify-content:space-between; }
|
||||
.t-uptime { font-family:var(--font-terminal); font-size:10px; color:var(--ink-4); }
|
||||
.t-uptime i { margin-right:3px; }
|
||||
|
||||
/* ══ FOOTER ══ */
|
||||
.footer {
|
||||
background:var(--bg-0); border-top:1px solid var(--border-2);
|
||||
height:28px; display:flex; align-items:center;
|
||||
font-family:var(--font-terminal); font-size:11px; color:var(--ink-4);
|
||||
flex-shrink:0;
|
||||
}
|
||||
.f-mode { background:var(--accent); color:var(--bg-0); padding:0 12px; height:100%; display:flex; align-items:center; font-weight:700; font-size:11px; }
|
||||
.f-cell { padding:0 14px; border-right:1px solid var(--border-1); display:flex; align-items:center; gap:6px; height:100%; }
|
||||
.f-cell i { font-size:10px; }
|
||||
.f-cell .f-val { font-family:var(--font-mono); color:var(--ink-2); }
|
||||
.f-cell .f-val.warn { color:var(--warn); }
|
||||
.f-cell .f-bar { width:40px; height:4px; border-radius:2px; background:var(--bg-3); overflow:hidden; display:inline-block; vertical-align:middle; margin-left:4px; }
|
||||
.f-cell .f-bar-fill { height:100%; border-radius:2px; background:var(--ok); }
|
||||
.f-cell .f-bar-fill.warn { background:var(--warn); }
|
||||
.f-spacer { flex:1; }
|
||||
.f-right { padding:0 14px; display:flex; align-items:center; gap:6px; height:100%; }
|
||||
.f-right .f-update { color:var(--ink-3); }
|
||||
|
||||
/* ══ POPUP ══ */
|
||||
.popup-overlay {
|
||||
position:fixed; inset:0; background:rgba(0,0,0,0.65); z-index:100;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
backdrop-filter:blur(2px);
|
||||
}
|
||||
.popup {
|
||||
background:var(--bg-2); border:1px solid var(--border-3); border-radius:12px;
|
||||
width:480px; max-width:95vw; box-shadow:0 24px 64px rgba(0,0,0,0.7);
|
||||
display:flex; flex-direction:column; overflow:hidden;
|
||||
}
|
||||
.popup-header {
|
||||
background:var(--bg-3); padding:14px 18px; border-bottom:1px solid var(--border-2);
|
||||
display:flex; align-items:center; gap:10px;
|
||||
}
|
||||
.popup-header .ph-icon { width:32px; height:32px; border-radius:8px; background:var(--bg-4); display:flex; align-items:center; justify-content:center; color:var(--accent); font-size:15px; }
|
||||
.popup-header .ph-info { flex:1; }
|
||||
.popup-header .ph-host { font-weight:700; font-size:14px; color:var(--ink-1); }
|
||||
.popup-header .ph-ip { font-family:var(--font-mono); font-size:11px; color:var(--ink-4); }
|
||||
.popup-header .ph-led { width:10px; height:10px; border-radius:50%; background:var(--ok); box-shadow:0 0 8px var(--ok); }
|
||||
.popup-header .close-btn { width:28px; height:28px; border-radius:6px; background:var(--bg-5); color:var(--ink-3); display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:12px; }
|
||||
.popup-header .close-btn:hover { background:var(--err); color:white; }
|
||||
|
||||
.popup-body { padding:18px; display:flex; flex-direction:column; gap:16px; }
|
||||
|
||||
.popup-kpis { display:grid; grid-template-columns:repeat(4,1fr); gap:8px; }
|
||||
.kpi-card {
|
||||
background:var(--bg-3); border-radius:8px; padding:10px 12px;
|
||||
border:1px solid var(--border-1); box-shadow:var(--tile-3d);
|
||||
}
|
||||
.kpi-label { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; margin-bottom:4px; }
|
||||
.kpi-value { font-family:var(--font-mono); font-size:20px; font-weight:700; color:var(--ink-1); line-height:1; }
|
||||
.kpi-value .unit { font-size:11px; color:var(--ink-3); font-weight:400; }
|
||||
.kpi-value.ok { color:var(--ok); }
|
||||
.kpi-value.warn { color:var(--warn); }
|
||||
.kpi-value.err { color:var(--err); }
|
||||
.kpi-sub { font-size:10px; color:var(--ink-4); font-family:var(--font-mono); margin-top:3px; }
|
||||
|
||||
.popup-section-title { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.08em; margin-bottom:8px; }
|
||||
|
||||
.popup-gauges { display:flex; flex-direction:column; gap:7px; }
|
||||
.pg-row { display:flex; align-items:center; gap:10px; }
|
||||
.pg-label { font-size:11px; color:var(--ink-3); width:80px; font-family:var(--font-terminal); }
|
||||
.pg-bar { flex:1; height:7px; border-radius:4px; background:var(--bg-1); overflow:hidden; }
|
||||
.pg-fill { height:100%; border-radius:4px; background:var(--ok); }
|
||||
.pg-fill.warn { background:var(--warn); }
|
||||
.pg-fill.err { background:var(--err); }
|
||||
.pg-val { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); width:60px; text-align:right; }
|
||||
|
||||
.popup-meta { display:grid; grid-template-columns:1fr 1fr; gap:6px; }
|
||||
.meta-item { background:var(--bg-3); border-radius:6px; padding:8px 10px; border:1px solid var(--border-1); }
|
||||
.meta-label { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.meta-value { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); margin-top:2px; }
|
||||
|
||||
.popup-footer {
|
||||
padding:12px 18px; border-top:1px solid var(--border-2);
|
||||
display:flex; align-items:center; gap:10px;
|
||||
background:var(--bg-3);
|
||||
}
|
||||
.pf-uptime { font-family:var(--font-terminal); font-size:11px; color:var(--ink-4); flex:1; }
|
||||
.pf-btn {
|
||||
padding:6px 14px; border-radius:8px; border:1px solid var(--border-2);
|
||||
background:var(--bg-4); color:var(--ink-2); font-size:12px; font-family:var(--font-ui);
|
||||
cursor:pointer; display:flex; align-items:center; gap:6px;
|
||||
}
|
||||
.pf-btn:hover { background:var(--bg-5); }
|
||||
.pf-btn.primary { background:var(--accent); color:var(--bg-0); border-color:var(--accent-soft); font-weight:600; }
|
||||
.pf-btn.primary:hover { background:var(--accent-soft); }
|
||||
|
||||
/* ══ CONFIG PANEL ══ */
|
||||
.config-overlay {
|
||||
position:fixed; inset:0; background:rgba(0,0,0,0.65); z-index:100;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
backdrop-filter:blur(2px);
|
||||
}
|
||||
.config-panel {
|
||||
background:var(--bg-2); border:1px solid var(--border-3); border-radius:12px;
|
||||
width:380px; box-shadow:0 24px 64px rgba(0,0,0,0.7); overflow:hidden;
|
||||
}
|
||||
.config-header {
|
||||
background:var(--bg-3); padding:14px 18px; border-bottom:1px solid var(--border-2);
|
||||
display:flex; align-items:center; gap:10px;
|
||||
}
|
||||
.config-header i { color:var(--accent); font-size:16px; }
|
||||
.config-header .ch-title { flex:1; font-weight:600; font-size:14px; }
|
||||
.config-header .close-btn { width:28px; height:28px; border-radius:6px; background:var(--bg-5); color:var(--ink-3); display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:12px; }
|
||||
.config-body { padding:18px; display:flex; flex-direction:column; gap:14px; }
|
||||
.config-group { display:flex; flex-direction:column; gap:6px; }
|
||||
.cg-label { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.08em; }
|
||||
.cg-row { display:flex; align-items:center; gap:10px; }
|
||||
.cg-row span { font-size:12px; color:var(--ink-3); min-width:80px; }
|
||||
.cg-slider { flex:1; accent-color:var(--accent); }
|
||||
.cg-val { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); width:40px; text-align:right; }
|
||||
.cg-select {
|
||||
flex:1; background:var(--bg-3); border:1px solid var(--border-2); border-radius:6px;
|
||||
color:var(--ink-1); padding:6px 10px; font-size:12px; font-family:var(--font-ui);
|
||||
}
|
||||
.config-footer {
|
||||
padding:12px 18px; border-top:1px solid var(--border-2); background:var(--bg-3);
|
||||
display:flex; gap:8px; justify-content:flex-end;
|
||||
}
|
||||
|
||||
/* ══ NOTICE BAR ══ */
|
||||
.notice { background:var(--bg-3); border-bottom:1px solid var(--border-1); padding:6px 20px; display:flex; align-items:center; gap:8px; font-size:11px; color:var(--ink-3); }
|
||||
.notice i { color:var(--accent); }
|
||||
|
||||
/* scrollbar */
|
||||
::-webkit-scrollbar{width:6px} ::-webkit-scrollbar-track{background:var(--bg-1)} ::-webkit-scrollbar-thumb{background:var(--bg-4);border-radius:3px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ══ HEADER ══ -->
|
||||
<div class="header">
|
||||
<div class="header-logo">
|
||||
<div class="led"></div>
|
||||
<span class="name">NANOMETRICS</span>
|
||||
<span class="version">v1.0</span>
|
||||
</div>
|
||||
|
||||
<div class="header-spacer"></div>
|
||||
|
||||
<div class="header-stats">
|
||||
<div class="h-stat">
|
||||
<span class="s-label">AGENTS</span>
|
||||
<span class="s-count" style="color:var(--ink-2)">8</span>
|
||||
</div>
|
||||
<div class="h-stat">
|
||||
<span class="s-label">OK</span>
|
||||
<span class="s-count ok">5</span>
|
||||
</div>
|
||||
<div class="h-stat">
|
||||
<span class="s-label">WARN</span>
|
||||
<span class="s-count warn">2</span>
|
||||
</div>
|
||||
<div class="h-stat">
|
||||
<span class="s-label">ERR</span>
|
||||
<span class="s-count err">1</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header-sep"></div>
|
||||
|
||||
<!-- Thème clair/sombre -->
|
||||
<div class="icon-btn" onclick="toggleTheme()" id="theme-btn">
|
||||
<i class="fa-solid fa-moon" id="theme-icon"></i>
|
||||
<span class="tooltip">Basculer thème</span>
|
||||
</div>
|
||||
|
||||
<!-- Configuration -->
|
||||
<div class="icon-btn" onclick="document.getElementById('config-overlay').style.display='flex'">
|
||||
<i class="fa-solid fa-sliders"></i>
|
||||
<span class="tooltip">Configuration serveur</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ MAIN ══ -->
|
||||
<div class="main">
|
||||
<div class="agents-grid" id="agents-grid">
|
||||
|
||||
<!-- tuile ok -->
|
||||
<div class="tile" onclick="document.getElementById('popup-overlay').style.display='flex'">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon-wrap"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-info">
|
||||
<div class="t-hostname">srv-prod-01</div>
|
||||
<div class="t-ip">10.0.0.11</div>
|
||||
</div>
|
||||
<div class="t-led ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:42%"></div></div><span class="g-val">42%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:58%"></div></div><span class="g-val">58%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DISK</span><div class="g-bar"><div class="g-fill" style="width:31%"></div></div><span class="g-val">31%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot">
|
||||
<span class="t-uptime"><i class="fa-solid fa-clock"></i>14j 6h</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tuile warn -->
|
||||
<div class="tile warn-tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon-wrap"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-info">
|
||||
<div class="t-hostname">srv-backup-02</div>
|
||||
<div class="t-ip">10.0.0.12</div>
|
||||
</div>
|
||||
<div class="t-led warn"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill warn" style="width:78%"></div></div><span class="g-val">78%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill warn" style="width:72%"></div></div><span class="g-val">72%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DISK</span><div class="g-bar"><div class="g-fill" style="width:20%"></div></div><span class="g-val">20%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot">
|
||||
<span class="t-uptime"><i class="fa-solid fa-clock"></i>3j 14h</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tuile err -->
|
||||
<div class="tile err-tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon-wrap" style="color:var(--err)"><i class="fa-solid fa-microchip"></i></div>
|
||||
<div class="t-info">
|
||||
<div class="t-hostname">rpi-sensor-03</div>
|
||||
<div class="t-ip">10.0.0.23</div>
|
||||
</div>
|
||||
<div class="t-led err"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:12%"></div></div><span class="g-val">12%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:38%"></div></div><span class="g-val">38%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DISK</span><div class="g-bar"><div class="g-fill err" style="width:94%"></div></div><span class="g-val">94%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot">
|
||||
<span class="t-uptime"><i class="fa-solid fa-clock"></i>62j 1h</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tuile ok -->
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon-wrap"><i class="fa-solid fa-hard-drive"></i></div>
|
||||
<div class="t-info">
|
||||
<div class="t-hostname">nas-storage-04</div>
|
||||
<div class="t-ip">10.0.0.30</div>
|
||||
</div>
|
||||
<div class="t-led ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:8%"></div></div><span class="g-val">8%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:45%"></div></div><span class="g-val">45%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DISK</span><div class="g-bar"><div class="g-fill" style="width:66%"></div></div><span class="g-val">66%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot">
|
||||
<span class="t-uptime"><i class="fa-solid fa-clock"></i>128j 3h</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tuile ok -->
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon-wrap"><i class="fa-solid fa-database"></i></div>
|
||||
<div class="t-info">
|
||||
<div class="t-hostname">db-primary-05</div>
|
||||
<div class="t-ip">10.0.0.40</div>
|
||||
</div>
|
||||
<div class="t-led ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:25%"></div></div><span class="g-val">25%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:61%"></div></div><span class="g-val">61%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DISK</span><div class="g-bar"><div class="g-fill" style="width:48%"></div></div><span class="g-val">48%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot">
|
||||
<span class="t-uptime"><i class="fa-solid fa-clock"></i>7j 22h</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tuile warn -->
|
||||
<div class="tile warn-tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon-wrap"><i class="fa-solid fa-globe"></i></div>
|
||||
<div class="t-info">
|
||||
<div class="t-hostname">web-front-06</div>
|
||||
<div class="t-ip">10.0.0.51</div>
|
||||
</div>
|
||||
<div class="t-led warn"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill warn" style="width:71%"></div></div><span class="g-val">71%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:55%"></div></div><span class="g-val">55%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DISK</span><div class="g-bar"><div class="g-fill" style="width:28%"></div></div><span class="g-val">28%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot">
|
||||
<span class="t-uptime"><i class="fa-solid fa-clock"></i>21j 8h</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tuile offline -->
|
||||
<div class="tile offline-tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon-wrap" style="color:var(--ink-4)"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-info">
|
||||
<div class="t-hostname">srv-dev-07</div>
|
||||
<div class="t-ip">10.0.0.60</div>
|
||||
</div>
|
||||
<div class="t-led off"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:0%"></div></div><span class="g-val" style="color:var(--ink-4)">—</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:0%"></div></div><span class="g-val" style="color:var(--ink-4)">—</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DISK</span><div class="g-bar"><div class="g-fill" style="width:0%"></div></div><span class="g-val" style="color:var(--ink-4)">—</span></div>
|
||||
</div>
|
||||
<div class="tile-foot">
|
||||
<span class="t-uptime" style="color:var(--err)"><i class="fa-solid fa-circle-xmark"></i>Hors ligne · vu il y a 2h</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tuile ok -->
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon-wrap"><i class="fa-solid fa-network-wired"></i></div>
|
||||
<div class="t-info">
|
||||
<div class="t-hostname">gateway-08</div>
|
||||
<div class="t-ip">10.0.0.1</div>
|
||||
</div>
|
||||
<div class="t-led ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:5%"></div></div><span class="g-val">5%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:22%"></div></div><span class="g-val">22%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DISK</span><div class="g-bar"><div class="g-fill" style="width:15%"></div></div><span class="g-val">15%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot">
|
||||
<span class="t-uptime"><i class="fa-solid fa-clock"></i>365j 0h</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ FOOTER ══ -->
|
||||
<div class="footer">
|
||||
<div class="f-mode">LIVE</div>
|
||||
<div class="f-cell">
|
||||
<i class="fa-solid fa-server"></i>
|
||||
<span>SERVEUR</span>
|
||||
</div>
|
||||
<div class="f-cell">
|
||||
<i class="fa-solid fa-microchip"></i>
|
||||
<span>CPU</span>
|
||||
<span class="f-val">18%</span>
|
||||
<span class="f-bar"><span class="f-bar-fill" style="width:18%"></span></span>
|
||||
</div>
|
||||
<div class="f-cell">
|
||||
<i class="fa-solid fa-memory"></i>
|
||||
<span>MEM</span>
|
||||
<span class="f-val warn">71%</span>
|
||||
<span class="f-bar"><span class="f-bar-fill warn" style="width:71%"></span></span>
|
||||
</div>
|
||||
<div class="f-spacer"></div>
|
||||
<div class="f-right">
|
||||
<i class="fa-solid fa-rotate"></i>
|
||||
<span class="f-update">Dernière actualisation : <span style="color:var(--ink-2);font-family:var(--font-mono)">22:14:07</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ POPUP DÉTAIL (visible par défaut pour la maquette) ══ -->
|
||||
<div class="popup-overlay" id="popup-overlay" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" onclick="event.stopPropagation()">
|
||||
<div class="popup-header">
|
||||
<div class="ph-icon"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="ph-info">
|
||||
<div class="ph-host">srv-prod-01</div>
|
||||
<div class="ph-ip">10.0.0.11</div>
|
||||
</div>
|
||||
<div class="ph-led"></div>
|
||||
<div class="close-btn" onclick="document.getElementById('popup-overlay').style.display='none'"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="popup-body">
|
||||
<!-- KPIs -->
|
||||
<div>
|
||||
<div class="popup-section-title">MÉTRIQUES ACTUELLES</div>
|
||||
<div class="popup-kpis">
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">CPU</div>
|
||||
<div class="kpi-value ok">42<span class="unit">%</span></div>
|
||||
<div class="kpi-sub">4 cœurs</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">MÉMOIRE</div>
|
||||
<div class="kpi-value">3.7<span class="unit">Go</span></div>
|
||||
<div class="kpi-sub">/ 8 Go · 46%</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">DISQUE</div>
|
||||
<div class="kpi-value">62<span class="unit">Go</span></div>
|
||||
<div class="kpi-sub">/ 200 Go · 31%</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">UPTIME</div>
|
||||
<div class="kpi-value" style="font-size:15px;color:var(--ink-1)">14j 6h</div>
|
||||
<div class="kpi-sub">depuis dernier boot</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Jauges détaillées -->
|
||||
<div>
|
||||
<div class="popup-section-title">UTILISATION DÉTAILLÉE</div>
|
||||
<div class="popup-gauges">
|
||||
<div class="pg-row"><span class="pg-label">CPU total</span><div class="pg-bar"><div class="pg-fill" style="width:42%"></div></div><span class="pg-val">42% / 100%</span></div>
|
||||
<div class="pg-row"><span class="pg-label">RAM utilisée</span><div class="pg-bar"><div class="pg-fill" style="width:46%"></div></div><span class="pg-val">3.7 / 8 Go</span></div>
|
||||
<div class="pg-row"><span class="pg-label">Disque utilisé</span><div class="pg-bar"><div class="pg-fill" style="width:31%"></div></div><span class="pg-val">62 / 200 Go</span></div>
|
||||
<div class="pg-row"><span class="pg-label">Disque libre</span><div class="pg-bar"><div class="pg-fill" style="width:69%"></div></div><span class="pg-val">138 Go libre</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Métadonnées -->
|
||||
<div>
|
||||
<div class="popup-section-title">INFORMATIONS SYSTÈME</div>
|
||||
<div class="popup-meta">
|
||||
<div class="meta-item"><div class="meta-label">HOSTNAME</div><div class="meta-value">srv-prod-01</div></div>
|
||||
<div class="meta-item"><div class="meta-label">ADRESSE IP</div><div class="meta-value" style="font-family:var(--font-mono)">10.0.0.11</div></div>
|
||||
<div class="meta-item"><div class="meta-label">DERNIER CONTACT</div><div class="meta-value" style="font-family:var(--font-mono)">22:14:07</div></div>
|
||||
<div class="meta-item"><div class="meta-label">FRÉQUENCE</div><div class="meta-value" style="font-family:var(--font-mono)">CPU 2s · Disk 60s</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="popup-footer">
|
||||
<span class="pf-uptime"><i class="fa-solid fa-clock" style="margin-right:5px"></i>En ligne depuis 14 jours 6 heures</span>
|
||||
<button class="pf-btn"><i class="fa-solid fa-chart-line"></i> Historique</button>
|
||||
<button class="pf-btn primary"><i class="fa-solid fa-xmark"></i> Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ CONFIG PANEL ══ -->
|
||||
<div class="config-overlay" id="config-overlay" style="display:none" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="config-panel" onclick="event.stopPropagation()">
|
||||
<div class="config-header">
|
||||
<i class="fa-solid fa-sliders"></i>
|
||||
<span class="ch-title">Configuration interface</span>
|
||||
<div class="close-btn" onclick="document.getElementById('config-overlay').style.display='none'"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="config-body">
|
||||
<div class="config-group">
|
||||
<div class="cg-label">TAILLE DES TUILES</div>
|
||||
<div class="cg-row">
|
||||
<span>Largeur min.</span>
|
||||
<input type="range" class="cg-slider" min="160" max="400" value="240">
|
||||
<span class="cg-val">240px</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-group">
|
||||
<div class="cg-label">TAILLE DU TEXTE</div>
|
||||
<div class="cg-row">
|
||||
<span>Interface</span>
|
||||
<input type="range" class="cg-slider" min="10" max="18" value="13">
|
||||
<span class="cg-val">13px</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-group">
|
||||
<div class="cg-label">SEUILS D'ALERTE (défaut)</div>
|
||||
<div class="cg-row">
|
||||
<span>Warning CPU/RAM</span>
|
||||
<input type="range" class="cg-slider" min="50" max="95" value="70">
|
||||
<span class="cg-val">70%</span>
|
||||
</div>
|
||||
<div class="cg-row">
|
||||
<span>Erreur CPU/RAM</span>
|
||||
<input type="range" class="cg-slider" min="60" max="100" value="85">
|
||||
<span class="cg-val">85%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-group">
|
||||
<div class="cg-label">AGENTS HORS LIGNE</div>
|
||||
<div class="cg-row">
|
||||
<span>Affichage</span>
|
||||
<select class="cg-select">
|
||||
<option>Visible (grisé)</option>
|
||||
<option>Masqué</option>
|
||||
<option>Dernière position</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-group">
|
||||
<div class="cg-label">RÉTENTION HISTORIQUE</div>
|
||||
<div class="cg-row">
|
||||
<span>Durée</span>
|
||||
<select class="cg-select">
|
||||
<option>7 jours</option>
|
||||
<option selected>30 jours</option>
|
||||
<option>90 jours</option>
|
||||
<option>1 an</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-footer">
|
||||
<button class="pf-btn" onclick="document.getElementById('config-overlay').style.display='none'">Annuler</button>
|
||||
<button class="pf-btn primary"><i class="fa-solid fa-floppy-disk"></i> Sauvegarder</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement;
|
||||
const icon = document.getElementById('theme-icon');
|
||||
if (html.dataset.theme === 'dark') {
|
||||
html.dataset.theme = 'light';
|
||||
icon.className = 'fa-solid fa-sun';
|
||||
} else {
|
||||
html.dataset.theme = 'dark';
|
||||
icon.className = 'fa-solid fa-moon';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,602 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nanometrics — Layout v3</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script src="/helper.js"></script>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--accent:#fe8019; --accent-soft:#d65d0e; --accent-glow:rgba(254,128,25,0.3);
|
||||
--bg-0:#1d1813; --bg-1:#2a231d; --bg-2:#32291f; --bg-3:#3c332a; --bg-4:#4a3f33; --bg-5:#57493c;
|
||||
--ink-1:#f2e5c7; --ink-2:#d5c4a1; --ink-3:#a89984; --ink-4:#7c6f64;
|
||||
--ok:#4dbb26; --warn:#fabd2f; --err:#fb4934; --info:#83a598; --blue:#3db0d1;
|
||||
--border-1:rgba(255,255,255,0.06); --border-2:rgba(255,255,255,0.12); --border-3:rgba(255,255,255,0.22);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,0.08) inset,0 -1px 0 rgba(0,0,0,0.3) inset,0 4px 16px rgba(0,0,0,0.5);
|
||||
--font-ui:'Inter',system-ui,sans-serif; --font-mono:'JetBrains Mono',monospace; --font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
:root[data-theme="light"] {
|
||||
--accent:#af3a03; --accent-soft:#d65d0e; --accent-glow:rgba(175,58,3,0.2);
|
||||
--bg-0:#d5c4a1; --bg-1:#ebdbb2; --bg-2:#d5c4a1; --bg-3:#bdae93; --bg-4:#a89984; --bg-5:#928374;
|
||||
--ink-1:#3c3836; --ink-2:#504945; --ink-3:#665c54; --ink-4:#7c6f64;
|
||||
--ok:#3c911c; --warn:#b57614; --err:#9d0006; --info:#427b58; --blue:#2d82a3;
|
||||
--border-1:rgba(0,0,0,0.08); --border-2:rgba(0,0,0,0.15); --border-3:rgba(0,0,0,0.25);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,0.5) inset,0 -1px 0 rgba(0,0,0,0.1) inset,0 4px 16px rgba(0,0,0,0.12);
|
||||
--font-ui:'Inter',system-ui,sans-serif; --font-mono:'JetBrains Mono',monospace; --font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
|
||||
body { background:var(--bg-1); color:var(--ink-1); font-family:var(--font-ui); font-size:13px; height:100vh; display:flex; flex-direction:column; overflow:hidden; transition:background .2s, color .2s; }
|
||||
|
||||
/* ══ HEADER ══ */
|
||||
.header { background:var(--bg-2); border-bottom:1px solid var(--border-2); padding:0 20px; height:48px; display:flex; align-items:center; gap:14px; flex-shrink:0; }
|
||||
.header-logo { display:flex; align-items:center; gap:8px; }
|
||||
.logo-led { width:9px; height:9px; border-radius:50%; background:var(--accent); box-shadow:0 0 8px var(--accent-glow); animation:blink 2s infinite; }
|
||||
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:.4} }
|
||||
.logo-name { font-weight:700; font-size:14px; letter-spacing:.05em; color:var(--ink-1); font-family:var(--font-terminal); }
|
||||
.logo-ver { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); }
|
||||
.h-sep { width:1px; height:24px; background:var(--border-2); }
|
||||
.h-spacer { flex:1; }
|
||||
.h-stats { display:flex; gap:14px; align-items:center; }
|
||||
.h-stat { display:flex; align-items:center; gap:5px; font-size:12px; }
|
||||
.h-stat .lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.h-stat .val { font-family:var(--font-mono); font-weight:700; }
|
||||
.val-ok{color:var(--ok)} .val-warn{color:var(--warn)} .val-err{color:var(--err)} .val-n{color:var(--ink-2)}
|
||||
|
||||
/* icon buttons header */
|
||||
.hbtn {
|
||||
width:34px; height:34px; border-radius:8px; border:1px solid var(--border-2);
|
||||
background:var(--bg-3); color:var(--ink-2); font-size:14px;
|
||||
display:flex; align-items:center; justify-content:center; cursor:pointer;
|
||||
position:relative; transition:background .12s, color .12s;
|
||||
}
|
||||
.hbtn:hover { background:var(--bg-4); color:var(--accent); }
|
||||
.hbtn:active { box-shadow:inset 0 2px 4px rgba(0,0,0,.35); }
|
||||
.hbtn .tip {
|
||||
position:absolute; top:calc(100% + 6px); right:0; z-index:50;
|
||||
background:var(--bg-0); color:var(--ink-1); font-size:11px;
|
||||
padding:4px 8px; border-radius:5px; border:1px solid var(--border-2);
|
||||
white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .12s;
|
||||
font-family:var(--font-ui);
|
||||
}
|
||||
.hbtn:hover .tip { opacity:1; }
|
||||
|
||||
/* ══ GRID ══ */
|
||||
.main { flex:1; padding:14px 16px; overflow-y:auto; }
|
||||
.agents-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:10px; }
|
||||
|
||||
/* ══ TILE ══ */
|
||||
.tile {
|
||||
background:var(--bg-3); border-radius:10px; padding:12px 14px;
|
||||
border:1px solid var(--border-1); box-shadow:var(--tile-3d);
|
||||
cursor:pointer; transition:border-color .15s, box-shadow .15s;
|
||||
display:flex; flex-direction:column; gap:9px;
|
||||
}
|
||||
.tile:hover { border-color:var(--border-3); }
|
||||
.tile.t-warn { border-color:rgba(250,189,47,.28); }
|
||||
.tile.t-err { border-color:rgba(251,73,52,.32); box-shadow:var(--tile-3d),0 0 16px rgba(251,73,52,.1); }
|
||||
.tile.t-off { opacity:.5; cursor:default; }
|
||||
|
||||
.tile-head { display:flex; align-items:center; gap:8px; }
|
||||
.t-icon { width:28px; height:28px; border-radius:7px; background:var(--bg-4); display:flex; align-items:center; justify-content:center; color:var(--accent); font-size:13px; flex-shrink:0; }
|
||||
.t-names { flex:1; min-width:0; }
|
||||
.t-host { font-weight:600; font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.t-ip { font-family:var(--font-mono); font-size:10px; color:var(--ink-4); }
|
||||
.t-status { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
|
||||
.s-ok { background:var(--ok); box-shadow:0 0 6px var(--ok); }
|
||||
.s-warn { background:var(--warn); box-shadow:0 0 6px var(--warn); animation:blink 1.5s infinite; }
|
||||
.s-err { background:var(--err); box-shadow:0 0 8px var(--err); animation:blink 1s infinite; }
|
||||
.s-off { background:var(--ink-4); }
|
||||
|
||||
/* gauges avec icône + tooltip */
|
||||
.tile-gauges { display:flex; flex-direction:column; gap:5px; }
|
||||
.g-row { display:flex; align-items:center; gap:7px; }
|
||||
|
||||
.g-icon-wrap {
|
||||
width:18px; height:18px; display:flex; align-items:center; justify-content:center;
|
||||
font-size:11px; color:var(--ink-3); flex-shrink:0;
|
||||
position:relative; cursor:help;
|
||||
}
|
||||
.g-icon-wrap .g-tip {
|
||||
position:absolute; bottom:calc(100% + 5px); left:50%; transform:translateX(-50%);
|
||||
background:var(--bg-0); color:var(--ink-1); font-size:10px;
|
||||
padding:3px 7px; border-radius:4px; border:1px solid var(--border-2);
|
||||
white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .12s;
|
||||
z-index:10; font-family:var(--font-ui);
|
||||
}
|
||||
.g-icon-wrap:hover .g-tip { opacity:1; }
|
||||
|
||||
.g-bar { flex:1; height:5px; border-radius:3px; background:var(--bg-1); overflow:hidden; }
|
||||
.g-fill { height:100%; border-radius:3px; background:var(--ok); transition:width .4s; }
|
||||
.g-fill.w { background:var(--warn); }
|
||||
.g-fill.e { background:var(--err); }
|
||||
.g-val { font-family:var(--font-mono); font-size:11px; color:var(--ink-2); width:34px; text-align:right; flex-shrink:0; }
|
||||
|
||||
.tile-foot { display:flex; align-items:center; gap:5px; font-family:var(--font-terminal); font-size:10px; color:var(--ink-4); }
|
||||
.tile-foot i { font-size:9px; }
|
||||
.tile-foot.offline { color:var(--err); }
|
||||
|
||||
/* ══ FOOTER ══ */
|
||||
.footer { background:var(--bg-0); border-top:1px solid var(--border-2); height:26px; display:flex; align-items:center; font-family:var(--font-terminal); font-size:11px; color:var(--ink-4); flex-shrink:0; }
|
||||
.f-mode { background:var(--accent); color:var(--bg-0); padding:0 12px; height:100%; display:flex; align-items:center; font-weight:700; font-size:11px; letter-spacing:.04em; }
|
||||
.f-cell { padding:0 12px; border-right:1px solid var(--border-1); display:flex; align-items:center; gap:5px; height:100%; }
|
||||
.f-icon { font-size:10px; color:var(--ink-4); }
|
||||
.f-val { font-family:var(--font-mono); color:var(--ink-2); }
|
||||
.f-val.w { color:var(--warn); }
|
||||
.f-minibar { width:36px; height:4px; border-radius:2px; background:var(--bg-3); overflow:hidden; }
|
||||
.f-minifill { height:100%; border-radius:2px; background:var(--ok); }
|
||||
.f-minifill.w { background:var(--warn); }
|
||||
.f-spacer { flex:1; }
|
||||
.f-right { padding:0 12px; display:flex; align-items:center; gap:6px; color:var(--ink-3); }
|
||||
.f-right .f-time { font-family:var(--font-mono); color:var(--ink-2); }
|
||||
|
||||
/* ══ POPUP DÉTAIL ══ */
|
||||
.overlay { position:fixed; inset:0; background:rgba(0,0,0,.65); z-index:100; display:flex; align-items:center; justify-content:center; backdrop-filter:blur(2px); }
|
||||
.popup { background:var(--bg-2); border:1px solid var(--border-3); border-radius:12px; width:500px; max-width:96vw; box-shadow:0 24px 64px rgba(0,0,0,.7); display:flex; flex-direction:column; overflow:hidden; }
|
||||
|
||||
.pop-head { background:var(--bg-3); padding:14px 18px; border-bottom:1px solid var(--border-2); display:flex; align-items:center; gap:10px; }
|
||||
.pop-head-icon { width:32px; height:32px; border-radius:8px; background:var(--bg-4); display:flex; align-items:center; justify-content:center; color:var(--accent); font-size:15px; }
|
||||
.pop-head-info { flex:1; }
|
||||
.pop-host { font-weight:700; font-size:14px; }
|
||||
.pop-ip { font-family:var(--font-mono); font-size:11px; color:var(--ink-4); }
|
||||
.pop-led { width:10px; height:10px; border-radius:50%; background:var(--ok); box-shadow:0 0 8px var(--ok); }
|
||||
.pop-close { width:28px; height:28px; border-radius:6px; background:var(--bg-5); color:var(--ink-3); display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:12px; }
|
||||
.pop-close:hover { background:var(--err); color:#fff; }
|
||||
|
||||
.pop-body { padding:16px 18px; display:flex; flex-direction:column; gap:14px; }
|
||||
|
||||
.sec-title { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.08em; margin-bottom:7px; }
|
||||
|
||||
/* KPI cards */
|
||||
.kpi-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:7px; }
|
||||
.kpi { background:var(--bg-3); border-radius:8px; padding:10px 12px; border:1px solid var(--border-1); box-shadow:var(--tile-3d); }
|
||||
.kpi-lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.kpi-val { font-family:var(--font-mono); font-size:20px; font-weight:700; line-height:1.1; margin-top:2px; }
|
||||
.kpi-val .u { font-size:10px; color:var(--ink-3); font-weight:400; }
|
||||
.kpi-sub { font-size:10px; color:var(--ink-4); font-family:var(--font-mono); margin-top:2px; }
|
||||
|
||||
/* gauges détail popup */
|
||||
.det-gauges { display:flex; flex-direction:column; gap:7px; }
|
||||
.dg-row { display:flex; align-items:center; gap:10px; }
|
||||
.dg-icon { width:22px; text-align:center; font-size:13px; position:relative; cursor:help; }
|
||||
.dg-icon .dg-tip {
|
||||
position:absolute; bottom:calc(100%+5px); left:50%; transform:translateX(-50%);
|
||||
background:var(--bg-0); color:var(--ink-1); font-size:10px;
|
||||
padding:3px 8px; border-radius:4px; border:1px solid var(--border-2);
|
||||
white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .1s; z-index:10;
|
||||
font-family:var(--font-ui);
|
||||
}
|
||||
.dg-icon:hover .dg-tip { opacity:1; }
|
||||
.dg-bar { flex:1; height:7px; border-radius:4px; background:var(--bg-1); overflow:hidden; }
|
||||
.dg-fill { height:100%; border-radius:4px; background:var(--ok); }
|
||||
.dg-fill.w { background:var(--warn); }
|
||||
.dg-fill.e { background:var(--err); }
|
||||
.dg-val { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); width:90px; text-align:right; }
|
||||
|
||||
/* meta */
|
||||
.meta-grid { display:grid; grid-template-columns:1fr 1fr; gap:6px; }
|
||||
.meta { background:var(--bg-3); border-radius:6px; padding:8px 10px; border:1px solid var(--border-1); }
|
||||
.meta-lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.meta-val { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); margin-top:2px; }
|
||||
|
||||
.pop-foot { padding:12px 18px; border-top:1px solid var(--border-2); background:var(--bg-3); display:flex; align-items:center; gap:8px; }
|
||||
.pop-uptime { font-family:var(--font-terminal); font-size:11px; color:var(--ink-4); flex:1; }
|
||||
.btn { padding:6px 14px; border-radius:8px; border:1px solid var(--border-2); background:var(--bg-4); color:var(--ink-2); font-size:12px; font-family:var(--font-ui); cursor:pointer; display:flex; align-items:center; gap:6px; transition:background .1s; }
|
||||
.btn:hover { background:var(--bg-5); }
|
||||
.btn.primary { background:var(--accent); color:var(--bg-0); border-color:var(--accent-soft); font-weight:600; }
|
||||
.btn.primary:hover { background:var(--accent-soft); }
|
||||
|
||||
/* ══ CONFIG PANEL ══ */
|
||||
.cfg { background:var(--bg-2); border:1px solid var(--border-3); border-radius:12px; width:380px; box-shadow:0 24px 64px rgba(0,0,0,.7); overflow:hidden; }
|
||||
.cfg-head { background:var(--bg-3); padding:14px 18px; border-bottom:1px solid var(--border-2); display:flex; align-items:center; gap:10px; }
|
||||
.cfg-head i { color:var(--accent); font-size:15px; }
|
||||
.cfg-title { flex:1; font-weight:600; }
|
||||
.cfg-body { padding:18px; display:flex; flex-direction:column; gap:13px; }
|
||||
.cfg-group { display:flex; flex-direction:column; gap:6px; }
|
||||
.cfg-lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.08em; }
|
||||
.cfg-row { display:flex; align-items:center; gap:10px; }
|
||||
.cfg-row > span { font-size:12px; color:var(--ink-3); min-width:90px; }
|
||||
.cfg-slider { flex:1; accent-color:var(--accent); }
|
||||
.cfg-v { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); width:44px; text-align:right; }
|
||||
.cfg-select { flex:1; background:var(--bg-3); border:1px solid var(--border-2); border-radius:6px; color:var(--ink-1); padding:6px 10px; font-size:12px; }
|
||||
.cfg-foot { padding:12px 18px; border-top:1px solid var(--border-2); background:var(--bg-3); display:flex; gap:8px; justify-content:flex-end; }
|
||||
|
||||
::-webkit-scrollbar{width:6px} ::-webkit-scrollbar-track{background:var(--bg-1)} ::-webkit-scrollbar-thumb{background:var(--bg-4);border-radius:3px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="header-logo">
|
||||
<div class="logo-led"></div>
|
||||
<span class="logo-name">NANOMETRICS</span>
|
||||
<span class="logo-ver">v1.0</span>
|
||||
</div>
|
||||
<div class="h-sep"></div>
|
||||
<div class="h-stats">
|
||||
<div class="h-stat"><span class="lbl">AGENTS</span><span class="val val-n">8</span></div>
|
||||
<div class="h-stat"><span class="lbl">OK</span><span class="val val-ok">5</span></div>
|
||||
<div class="h-stat"><span class="lbl">WARN</span><span class="val val-warn">2</span></div>
|
||||
<div class="h-stat"><span class="lbl">ERR</span><span class="val val-err">1</span></div>
|
||||
</div>
|
||||
<div class="h-spacer"></div>
|
||||
<div class="hbtn" onclick="toggleTheme()">
|
||||
<i class="fa-solid fa-moon" id="theme-icon"></i>
|
||||
<span class="tip">Thème clair / sombre</span>
|
||||
</div>
|
||||
<div class="hbtn" onclick="document.getElementById('cfg-overlay').style.display='flex'">
|
||||
<i class="fa-solid fa-sliders"></i>
|
||||
<span class="tip">Configuration interface</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GRID -->
|
||||
<div class="main">
|
||||
<div class="agents-grid">
|
||||
|
||||
<!-- srv-prod-01 · ok -->
|
||||
<div class="tile" onclick="showPopup()">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-names"><div class="t-host">srv-prod-01</div><div class="t-ip">10.0.0.11</div></div>
|
||||
<div class="t-status s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-microchip"></i><span class="g-tip">Processeur (CPU)</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:42%"></div></div>
|
||||
<span class="g-val">42%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-memory"></i><span class="g-tip">Mémoire RAM</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:58%"></div></div>
|
||||
<span class="g-val">58%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Espace disque</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:31%"></div></div>
|
||||
<span class="g-val">31%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>14j 6h</div>
|
||||
</div>
|
||||
|
||||
<!-- srv-backup-02 · warn -->
|
||||
<div class="tile t-warn">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-names"><div class="t-host">srv-backup-02</div><div class="t-ip">10.0.0.12</div></div>
|
||||
<div class="t-status s-warn"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap" style="color:var(--warn)"><i class="fa-solid fa-microchip"></i><span class="g-tip">Processeur (CPU) — élevé</span></div>
|
||||
<div class="g-bar"><div class="g-fill w" style="width:78%"></div></div>
|
||||
<span class="g-val" style="color:var(--warn)">78%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap" style="color:var(--warn)"><i class="fa-solid fa-memory"></i><span class="g-tip">Mémoire RAM — élevée</span></div>
|
||||
<div class="g-bar"><div class="g-fill w" style="width:72%"></div></div>
|
||||
<span class="g-val" style="color:var(--warn)">72%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Espace disque</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:20%"></div></div>
|
||||
<span class="g-val">20%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>3j 14h</div>
|
||||
</div>
|
||||
|
||||
<!-- rpi-sensor-03 · err -->
|
||||
<div class="tile t-err">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon" style="color:var(--err)"><i class="fa-solid fa-microchip"></i></div>
|
||||
<div class="t-names"><div class="t-host">rpi-sensor-03</div><div class="t-ip">10.0.0.23</div></div>
|
||||
<div class="t-status s-err"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-microchip"></i><span class="g-tip">Processeur (CPU)</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:12%"></div></div>
|
||||
<span class="g-val">12%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-memory"></i><span class="g-tip">Mémoire RAM</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:38%"></div></div>
|
||||
<span class="g-val">38%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap" style="color:var(--err)"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Disque critique !</span></div>
|
||||
<div class="g-bar"><div class="g-fill e" style="width:94%"></div></div>
|
||||
<span class="g-val" style="color:var(--err)">94%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>62j 1h</div>
|
||||
</div>
|
||||
|
||||
<!-- nas-04 · ok -->
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-hard-drive"></i></div>
|
||||
<div class="t-names"><div class="t-host">nas-storage-04</div><div class="t-ip">10.0.0.30</div></div>
|
||||
<div class="t-status s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-microchip"></i><span class="g-tip">Processeur (CPU)</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:8%"></div></div>
|
||||
<span class="g-val">8%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-memory"></i><span class="g-tip">Mémoire RAM</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:45%"></div></div>
|
||||
<span class="g-val">45%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Espace disque</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:66%"></div></div>
|
||||
<span class="g-val">66%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>128j 3h</div>
|
||||
</div>
|
||||
|
||||
<!-- db-05 · ok -->
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-database"></i></div>
|
||||
<div class="t-names"><div class="t-host">db-primary-05</div><div class="t-ip">10.0.0.40</div></div>
|
||||
<div class="t-status s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-microchip"></i><span class="g-tip">Processeur (CPU)</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:25%"></div></div>
|
||||
<span class="g-val">25%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-memory"></i><span class="g-tip">Mémoire RAM</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:61%"></div></div>
|
||||
<span class="g-val">61%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Espace disque</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:48%"></div></div>
|
||||
<span class="g-val">48%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>7j 22h</div>
|
||||
</div>
|
||||
|
||||
<!-- web-06 · warn -->
|
||||
<div class="tile t-warn">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-globe"></i></div>
|
||||
<div class="t-names"><div class="t-host">web-front-06</div><div class="t-ip">10.0.0.51</div></div>
|
||||
<div class="t-status s-warn"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap" style="color:var(--warn)"><i class="fa-solid fa-microchip"></i><span class="g-tip">Processeur (CPU) — élevé</span></div>
|
||||
<div class="g-bar"><div class="g-fill w" style="width:71%"></div></div>
|
||||
<span class="g-val" style="color:var(--warn)">71%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-memory"></i><span class="g-tip">Mémoire RAM</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:55%"></div></div>
|
||||
<span class="g-val">55%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Espace disque</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:28%"></div></div>
|
||||
<span class="g-val">28%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>21j 8h</div>
|
||||
</div>
|
||||
|
||||
<!-- srv-dev-07 · offline -->
|
||||
<div class="tile t-off">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon" style="color:var(--ink-4)"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-names"><div class="t-host">srv-dev-07</div><div class="t-ip">10.0.0.60</div></div>
|
||||
<div class="t-status s-off"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-microchip"></i><span class="g-tip">Processeur (CPU)</span></div>
|
||||
<div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-memory"></i><span class="g-tip">Mémoire RAM</span></div>
|
||||
<div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Espace disque</span></div>
|
||||
<div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-foot offline"><i class="fa-solid fa-circle-xmark"></i>Hors ligne · vu il y a 2h</div>
|
||||
</div>
|
||||
|
||||
<!-- gateway-08 · ok -->
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-network-wired"></i></div>
|
||||
<div class="t-names"><div class="t-host">gateway-08</div><div class="t-ip">10.0.0.1</div></div>
|
||||
<div class="t-status s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-microchip"></i><span class="g-tip">Processeur (CPU)</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:5%"></div></div>
|
||||
<span class="g-val">5%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-memory"></i><span class="g-tip">Mémoire RAM</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:22%"></div></div>
|
||||
<span class="g-val">22%</span>
|
||||
</div>
|
||||
<div class="g-row">
|
||||
<div class="g-icon-wrap"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Espace disque</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:15%"></div></div>
|
||||
<span class="g-val">15%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>365j 0h</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
<div class="f-mode">LIVE</div>
|
||||
<div class="f-cell"><i class="fa-solid fa-server f-icon"></i><span>SERVEUR</span></div>
|
||||
<div class="f-cell">
|
||||
<i class="fa-solid fa-microchip f-icon"></i>
|
||||
<span class="f-val">18%</span>
|
||||
<div class="f-minibar"><div class="f-minifill" style="width:18%"></div></div>
|
||||
</div>
|
||||
<div class="f-cell">
|
||||
<i class="fa-solid fa-memory f-icon"></i>
|
||||
<span class="f-val w">71%</span>
|
||||
<div class="f-minibar"><div class="f-minifill w" style="width:71%"></div></div>
|
||||
</div>
|
||||
<div class="f-spacer"></div>
|
||||
<div class="f-right">
|
||||
<i class="fa-solid fa-rotate"></i>
|
||||
<span>Actualisation : <span class="f-time">22:14:07</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- POPUP DÉTAIL -->
|
||||
<div class="overlay" id="popup-overlay" style="display:none" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" onclick="event.stopPropagation()">
|
||||
<div class="pop-head">
|
||||
<div class="pop-head-icon"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="pop-head-info">
|
||||
<div class="pop-host">srv-prod-01</div>
|
||||
<div class="pop-ip">10.0.0.11</div>
|
||||
</div>
|
||||
<div class="pop-led"></div>
|
||||
<div class="pop-close" onclick="document.getElementById('popup-overlay').style.display='none'"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="pop-body">
|
||||
<div>
|
||||
<div class="sec-title">MÉTRIQUES ACTUELLES</div>
|
||||
<div class="kpi-grid">
|
||||
<div class="kpi"><div class="kpi-lbl">CPU</div><div class="kpi-val" style="color:var(--ok)">42<span class="u">%</span></div><div class="kpi-sub">4 cœurs</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">MÉMOIRE</div><div class="kpi-val">3.7<span class="u">Go</span></div><div class="kpi-sub">/ 8 Go · 46%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">DISQUE</div><div class="kpi-val">62<span class="u">Go</span></div><div class="kpi-sub">/ 200 Go · 31%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">UPTIME</div><div class="kpi-val" style="font-size:15px;color:var(--ink-1)">14j 6h</div><div class="kpi-sub">depuis boot</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">UTILISATION DÉTAILLÉE</div>
|
||||
<div class="det-gauges">
|
||||
<div class="dg-row">
|
||||
<div class="dg-icon" style="color:var(--ok)"><i class="fa-solid fa-microchip"></i><span class="dg-tip">Processeur (CPU)</span></div>
|
||||
<div class="dg-bar"><div class="dg-fill" style="width:42%"></div></div>
|
||||
<span class="dg-val">42% / 100%</span>
|
||||
</div>
|
||||
<div class="dg-row">
|
||||
<div class="dg-icon"><i class="fa-solid fa-memory"></i><span class="dg-tip">Mémoire RAM utilisée</span></div>
|
||||
<div class="dg-bar"><div class="dg-fill" style="width:46%"></div></div>
|
||||
<span class="dg-val">3.7 / 8 Go</span>
|
||||
</div>
|
||||
<div class="dg-row">
|
||||
<div class="dg-icon"><i class="fa-solid fa-hard-drive"></i><span class="dg-tip">Disque utilisé</span></div>
|
||||
<div class="dg-bar"><div class="dg-fill" style="width:31%"></div></div>
|
||||
<span class="dg-val">62 / 200 Go</span>
|
||||
</div>
|
||||
<div class="dg-row">
|
||||
<div class="dg-icon" style="color:var(--blue)"><i class="fa-solid fa-floppy-disk"></i><span class="dg-tip">Espace libre</span></div>
|
||||
<div class="dg-bar"><div class="dg-fill" style="width:69%;background:var(--blue)"></div></div>
|
||||
<span class="dg-val">138 Go libre</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">INFORMATIONS</div>
|
||||
<div class="meta-grid">
|
||||
<div class="meta"><div class="meta-lbl">HOSTNAME</div><div class="meta-val">srv-prod-01</div></div>
|
||||
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">10.0.0.11</div></div>
|
||||
<div class="meta"><div class="meta-lbl">DERNIER CONTACT</div><div class="meta-val">22:14:07</div></div>
|
||||
<div class="meta"><div class="meta-lbl">FRÉQUENCE</div><div class="meta-val">CPU 2s · Disk 60s</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pop-foot">
|
||||
<span class="pop-uptime"><i class="fa-solid fa-clock" style="margin-right:4px"></i>En ligne depuis 14 jours 6 heures</span>
|
||||
<button class="btn"><i class="fa-solid fa-chart-line"></i> Historique</button>
|
||||
<button class="btn primary" onclick="document.getElementById('popup-overlay').style.display='none'"><i class="fa-solid fa-xmark"></i> Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONFIG PANEL -->
|
||||
<div class="overlay" id="cfg-overlay" style="display:none" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="cfg" onclick="event.stopPropagation()">
|
||||
<div class="cfg-head">
|
||||
<i class="fa-solid fa-sliders"></i>
|
||||
<span class="cfg-title">Configuration interface</span>
|
||||
<div class="pop-close" onclick="document.getElementById('cfg-overlay').style.display='none'"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="cfg-body">
|
||||
<div class="cfg-group">
|
||||
<div class="cfg-lbl">TAILLE DES TUILES</div>
|
||||
<div class="cfg-row"><span>Largeur min.</span><input type="range" class="cfg-slider" min="160" max="400" value="220"><span class="cfg-v">220px</span></div>
|
||||
</div>
|
||||
<div class="cfg-group">
|
||||
<div class="cfg-lbl">TAILLE DU TEXTE</div>
|
||||
<div class="cfg-row"><span>Interface</span><input type="range" class="cfg-slider" min="10" max="18" value="13"><span class="cfg-v">13px</span></div>
|
||||
</div>
|
||||
<div class="cfg-group">
|
||||
<div class="cfg-lbl">SEUILS D'ALERTE</div>
|
||||
<div class="cfg-row"><span>Warning CPU/RAM</span><input type="range" class="cfg-slider" min="50" max="95" value="70"><span class="cfg-v">70%</span></div>
|
||||
<div class="cfg-row"><span>Erreur CPU/RAM</span><input type="range" class="cfg-slider" min="60" max="100" value="85"><span class="cfg-v">85%</span></div>
|
||||
</div>
|
||||
<div class="cfg-group">
|
||||
<div class="cfg-lbl">AGENTS HORS LIGNE</div>
|
||||
<div class="cfg-row"><span>Affichage</span>
|
||||
<select class="cfg-select"><option>Visible (grisé)</option><option>Masqué</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cfg-group">
|
||||
<div class="cfg-lbl">RÉTENTION HISTORIQUE</div>
|
||||
<div class="cfg-row"><span>Durée</span>
|
||||
<select class="cfg-select"><option>7 jours</option><option selected>30 jours</option><option>90 jours</option><option>1 an</option></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cfg-foot">
|
||||
<button class="btn" onclick="document.getElementById('cfg-overlay').style.display='none'">Annuler</button>
|
||||
<button class="btn primary"><i class="fa-solid fa-floppy-disk"></i> Sauvegarder</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleTheme() {
|
||||
const h = document.documentElement;
|
||||
const i = document.getElementById('theme-icon');
|
||||
if (h.dataset.theme === 'dark') {
|
||||
h.dataset.theme = 'light';
|
||||
i.className = 'fa-solid fa-sun';
|
||||
} else {
|
||||
h.dataset.theme = 'dark';
|
||||
i.className = 'fa-solid fa-moon';
|
||||
}
|
||||
}
|
||||
function showPopup() {
|
||||
document.getElementById('popup-overlay').style.display = 'flex';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,541 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nanometrics — Layout v4</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script src="/helper.js"></script>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--accent:#fe8019; --accent-soft:#d65d0e; --accent-glow:rgba(254,128,25,0.3);
|
||||
--bg-0:#1d1813; --bg-1:#2a231d; --bg-2:#32291f; --bg-3:#3c332a; --bg-4:#4a3f33; --bg-5:#57493c;
|
||||
--ink-1:#f2e5c7; --ink-2:#d5c4a1; --ink-3:#a89984; --ink-4:#7c6f64;
|
||||
--ok:#4dbb26; --warn:#fabd2f; --err:#fb4934; --info:#83a598; --blue:#3db0d1; --purple:#c882c8;
|
||||
--border-1:rgba(255,255,255,0.06); --border-2:rgba(255,255,255,0.12); --border-3:rgba(255,255,255,0.22);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,0.08) inset,0 -1px 0 rgba(0,0,0,0.3) inset,0 4px 16px rgba(0,0,0,0.5);
|
||||
--font-ui:'Inter',system-ui,sans-serif; --font-mono:'JetBrains Mono',monospace; --font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
:root[data-theme="light"] {
|
||||
--accent:#af3a03; --accent-soft:#d65d0e; --accent-glow:rgba(175,58,3,0.2);
|
||||
--bg-0:#d5c4a1; --bg-1:#ebdbb2; --bg-2:#d5c4a1; --bg-3:#bdae93; --bg-4:#a89984; --bg-5:#928374;
|
||||
--ink-1:#3c3836; --ink-2:#504945; --ink-3:#665c54; --ink-4:#7c6f64;
|
||||
--ok:#3c911c; --warn:#b57614; --err:#9d0006; --blue:#2d82a3; --purple:#8c468c;
|
||||
--border-1:rgba(0,0,0,0.08); --border-2:rgba(0,0,0,0.15); --border-3:rgba(0,0,0,0.25);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,0.5) inset,0 -1px 0 rgba(0,0,0,0.1) inset,0 4px 16px rgba(0,0,0,0.12);
|
||||
--font-ui:'Inter',system-ui,sans-serif; --font-mono:'JetBrains Mono',monospace; --font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
|
||||
body { background:var(--bg-1); color:var(--ink-1); font-family:var(--font-ui); font-size:13px; height:100vh; display:flex; flex-direction:column; overflow:hidden; transition:background .2s,color .2s; }
|
||||
|
||||
/* HEADER */
|
||||
.header { background:var(--bg-2); border-bottom:1px solid var(--border-2); padding:0 20px; height:48px; display:flex; align-items:center; gap:14px; flex-shrink:0; }
|
||||
.logo { display:flex; align-items:center; gap:8px; }
|
||||
.logo-led { width:9px; height:9px; border-radius:50%; background:var(--accent); box-shadow:0 0 8px var(--accent-glow); animation:blink 2s infinite; }
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.logo-name { font-weight:700; font-size:14px; letter-spacing:.05em; font-family:var(--font-terminal); }
|
||||
.logo-ver { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); }
|
||||
.h-sep { width:1px; height:24px; background:var(--border-2); }
|
||||
.h-spacer { flex:1; }
|
||||
.h-stats { display:flex; gap:14px; }
|
||||
.h-stat { display:flex; align-items:center; gap:5px; }
|
||||
.h-stat .lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.h-stat .val { font-family:var(--font-mono); font-weight:700; font-size:13px; }
|
||||
.c-ok{color:var(--ok)}.c-warn{color:var(--warn)}.c-err{color:var(--err)}.c-n{color:var(--ink-2)}
|
||||
.hbtn { width:34px; height:34px; border-radius:8px; border:1px solid var(--border-2); background:var(--bg-3); color:var(--ink-2); font-size:14px; display:flex; align-items:center; justify-content:center; cursor:pointer; position:relative; transition:background .12s,color .12s; }
|
||||
.hbtn:hover { background:var(--bg-4); color:var(--accent); }
|
||||
.hbtn .tip { position:absolute; top:calc(100%+6px); right:0; z-index:50; background:var(--bg-0); color:var(--ink-1); font-size:11px; padding:4px 8px; border-radius:5px; border:1px solid var(--border-2); white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .12s; font-family:var(--font-ui); }
|
||||
.hbtn:hover .tip { opacity:1; }
|
||||
|
||||
/* GRID */
|
||||
.main { flex:1; padding:14px 16px; overflow-y:auto; }
|
||||
.agents-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:10px; }
|
||||
|
||||
/* TILE */
|
||||
.tile { background:var(--bg-3); border-radius:10px; padding:12px 14px; border:1px solid var(--border-1); box-shadow:var(--tile-3d); cursor:pointer; transition:border-color .15s; display:flex; flex-direction:column; gap:9px; }
|
||||
.tile:hover { border-color:var(--border-3); }
|
||||
.tile.t-warn { border-color:rgba(250,189,47,.28); }
|
||||
.tile.t-err { border-color:rgba(251,73,52,.32); box-shadow:var(--tile-3d),0 0 16px rgba(251,73,52,.1); }
|
||||
.tile.t-off { opacity:.5; cursor:default; }
|
||||
.tile-head { display:flex; align-items:center; gap:8px; }
|
||||
.t-icon { width:28px; height:28px; border-radius:7px; background:var(--bg-4); display:flex; align-items:center; justify-content:center; color:var(--accent); font-size:13px; flex-shrink:0; overflow:hidden; }
|
||||
.t-icon img { width:100%; height:100%; object-fit:cover; border-radius:7px; }
|
||||
.t-names { flex:1; min-width:0; }
|
||||
.t-host { font-weight:600; font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.t-ip { font-family:var(--font-mono); font-size:10px; color:var(--ink-4); }
|
||||
.t-led { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
|
||||
.s-ok{background:var(--ok);box-shadow:0 0 6px var(--ok)}.s-warn{background:var(--warn);box-shadow:0 0 6px var(--warn);animation:blink 1.5s infinite}.s-err{background:var(--err);box-shadow:0 0 8px var(--err);animation:blink 1s infinite}.s-off{background:var(--ink-4)}
|
||||
.tile-gauges { display:flex; flex-direction:column; gap:5px; }
|
||||
.g-row { display:flex; align-items:center; gap:7px; }
|
||||
.g-ico { width:18px; height:18px; display:flex; align-items:center; justify-content:center; font-size:11px; color:var(--ink-3); flex-shrink:0; position:relative; cursor:help; }
|
||||
.g-ico .g-tip { position:absolute; bottom:calc(100%+5px); left:50%; transform:translateX(-50%); background:var(--bg-0); color:var(--ink-1); font-size:10px; padding:3px 7px; border-radius:4px; border:1px solid var(--border-2); white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .12s; z-index:10; font-family:var(--font-ui); }
|
||||
.g-ico:hover .g-tip { opacity:1; }
|
||||
.g-bar { flex:1; height:5px; border-radius:3px; background:var(--bg-1); overflow:hidden; }
|
||||
.g-fill { height:100%; border-radius:3px; background:var(--ok); transition:width .4s; }
|
||||
.g-fill.w{background:var(--warn)}.g-fill.e{background:var(--err)}
|
||||
.g-val { font-family:var(--font-mono); font-size:11px; color:var(--ink-2); width:34px; text-align:right; flex-shrink:0; }
|
||||
.tile-foot { font-family:var(--font-terminal); font-size:10px; color:var(--ink-4); display:flex; align-items:center; gap:5px; }
|
||||
.tile-foot i { font-size:9px; }
|
||||
.tile-foot.offline { color:var(--err); }
|
||||
|
||||
/* FOOTER */
|
||||
.footer { background:var(--bg-0); border-top:1px solid var(--border-2); height:26px; display:flex; align-items:center; font-family:var(--font-terminal); font-size:11px; color:var(--ink-4); flex-shrink:0; }
|
||||
.f-mode { background:var(--accent); color:var(--bg-0); padding:0 12px; height:100%; display:flex; align-items:center; font-weight:700; letter-spacing:.04em; }
|
||||
.f-cell { padding:0 12px; border-right:1px solid var(--border-1); display:flex; align-items:center; gap:5px; height:100%; }
|
||||
.f-val { font-family:var(--font-mono); color:var(--ink-2); }
|
||||
.f-val.w { color:var(--warn); }
|
||||
.f-minibar { width:36px; height:4px; border-radius:2px; background:var(--bg-3); overflow:hidden; }
|
||||
.f-minifill { height:100%; border-radius:2px; background:var(--ok); }
|
||||
.f-minifill.w { background:var(--warn); }
|
||||
.f-spacer { flex:1; }
|
||||
.f-right { padding:0 12px; display:flex; align-items:center; gap:6px; color:var(--ink-3); }
|
||||
.f-time { font-family:var(--font-mono); color:var(--ink-2); }
|
||||
|
||||
/* ══ POPUP ══ */
|
||||
.overlay { position:fixed; inset:0; background:rgba(0,0,0,.65); z-index:100; display:flex; align-items:center; justify-content:center; backdrop-filter:blur(2px); }
|
||||
.popup { background:var(--bg-2); border:1px solid var(--border-3); border-radius:12px; width:540px; max-width:96vw; max-height:92vh; box-shadow:0 24px 64px rgba(0,0,0,.7); display:flex; flex-direction:column; overflow:hidden; }
|
||||
|
||||
/* popup header */
|
||||
.pop-head { background:var(--bg-3); padding:14px 18px; border-bottom:1px solid var(--border-2); display:flex; align-items:center; gap:12px; }
|
||||
|
||||
/* agent icon — cliquable pour upload */
|
||||
.agent-icon-wrap {
|
||||
position:relative; width:44px; height:44px; border-radius:10px; flex-shrink:0;
|
||||
cursor:pointer; overflow:hidden;
|
||||
background:var(--bg-4); display:flex; align-items:center; justify-content:center;
|
||||
color:var(--accent); font-size:18px;
|
||||
border:2px solid var(--border-2); transition:border-color .15s;
|
||||
}
|
||||
.agent-icon-wrap:hover { border-color:var(--accent); }
|
||||
.agent-icon-wrap img { width:100%; height:100%; object-fit:cover; }
|
||||
.agent-icon-overlay {
|
||||
position:absolute; inset:0; background:rgba(0,0,0,.6);
|
||||
display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
|
||||
opacity:0; transition:opacity .15s;
|
||||
font-size:10px; color:#fff; font-family:var(--font-ui);
|
||||
}
|
||||
.agent-icon-overlay i { font-size:14px; }
|
||||
.agent-icon-wrap:hover .agent-icon-overlay { opacity:1; }
|
||||
#icon-upload { display:none; }
|
||||
|
||||
.pop-head-info { flex:1; }
|
||||
.pop-host { font-weight:700; font-size:15px; }
|
||||
.pop-ip { font-family:var(--font-mono); font-size:11px; color:var(--ink-4); }
|
||||
.pop-led { width:10px; height:10px; border-radius:50%; background:var(--ok); box-shadow:0 0 8px var(--ok); flex-shrink:0; }
|
||||
.pop-close { width:28px; height:28px; border-radius:6px; background:var(--bg-5); color:var(--ink-3); display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:12px; transition:background .12s,color .12s; }
|
||||
.pop-close:hover { background:var(--err); color:#fff; }
|
||||
|
||||
/* upload feedback */
|
||||
.upload-hint { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); margin-top:3px; }
|
||||
|
||||
/* popup body */
|
||||
.pop-body { padding:16px 18px; display:flex; flex-direction:column; gap:14px; overflow-y:auto; }
|
||||
.sec-title { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.08em; margin-bottom:8px; }
|
||||
|
||||
/* KPI row */
|
||||
.kpi-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:7px; }
|
||||
.kpi { background:var(--bg-3); border-radius:8px; padding:10px 12px; border:1px solid var(--border-1); box-shadow:var(--tile-3d); }
|
||||
.kpi-lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.kpi-val { font-family:var(--font-mono); font-size:20px; font-weight:700; line-height:1.1; margin-top:2px; }
|
||||
.kpi-val .u { font-size:10px; color:var(--ink-3); font-weight:400; }
|
||||
.kpi-sub { font-size:10px; color:var(--ink-4); font-family:var(--font-mono); margin-top:2px; }
|
||||
|
||||
/* ── COURBES HISTORIQUES ── */
|
||||
.charts-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
|
||||
.chart-card { background:var(--bg-3); border-radius:8px; padding:10px 12px; border:1px solid var(--border-1); box-shadow:var(--tile-3d); }
|
||||
.chart-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; }
|
||||
.chart-label { display:flex; align-items:center; gap:6px; font-size:10px; font-family:var(--font-terminal); letter-spacing:.06em; color:var(--ink-3); }
|
||||
.chart-label i { font-size:11px; }
|
||||
.chart-cur { font-family:var(--font-mono); font-size:16px; font-weight:700; }
|
||||
.chart-svg { width:100%; height:56px; display:block; }
|
||||
.chart-axis { display:flex; justify-content:space-between; margin-top:3px; font-family:var(--font-terminal); font-size:9px; color:var(--ink-4); }
|
||||
|
||||
/* disk jauge conservée */
|
||||
.det-gauges { display:flex; flex-direction:column; gap:7px; }
|
||||
.dg-row { display:flex; align-items:center; gap:10px; }
|
||||
.dg-ico { width:22px; text-align:center; font-size:13px; position:relative; cursor:help; }
|
||||
.dg-ico .dg-tip { position:absolute; bottom:calc(100%+5px); left:50%; transform:translateX(-50%); background:var(--bg-0); color:var(--ink-1); font-size:10px; padding:3px 8px; border-radius:4px; border:1px solid var(--border-2); white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .1s; z-index:10; font-family:var(--font-ui); }
|
||||
.dg-ico:hover .dg-tip { opacity:1; }
|
||||
.dg-bar { flex:1; height:7px; border-radius:4px; background:var(--bg-1); overflow:hidden; }
|
||||
.dg-fill { height:100%; border-radius:4px; background:var(--ok); }
|
||||
.dg-fill.w{background:var(--warn)}.dg-fill.e{background:var(--err)}
|
||||
.dg-val { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); width:90px; text-align:right; }
|
||||
|
||||
/* meta */
|
||||
.meta-grid { display:grid; grid-template-columns:1fr 1fr; gap:6px; }
|
||||
.meta { background:var(--bg-3); border-radius:6px; padding:8px 10px; border:1px solid var(--border-1); }
|
||||
.meta-lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.meta-val { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); margin-top:2px; }
|
||||
|
||||
/* popup footer — pas de bouton Fermer */
|
||||
.pop-foot { padding:10px 18px; border-top:1px solid var(--border-2); background:var(--bg-3); display:flex; align-items:center; gap:8px; }
|
||||
.pop-uptime { font-family:var(--font-terminal); font-size:11px; color:var(--ink-4); flex:1; }
|
||||
.btn { padding:6px 14px; border-radius:8px; border:1px solid var(--border-2); background:var(--bg-4); color:var(--ink-2); font-size:12px; font-family:var(--font-ui); cursor:pointer; display:flex; align-items:center; gap:6px; transition:background .1s; }
|
||||
.btn:hover { background:var(--bg-5); }
|
||||
.btn.primary { background:var(--accent); color:var(--bg-0); border-color:var(--accent-soft); font-weight:600; }
|
||||
.btn.primary:hover { background:var(--accent-soft); }
|
||||
|
||||
/* scrollbar */
|
||||
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:var(--bg-1)}::-webkit-scrollbar-thumb{background:var(--bg-4);border-radius:3px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="logo">
|
||||
<div class="logo-led"></div>
|
||||
<span class="logo-name">NANOMETRICS</span>
|
||||
<span class="logo-ver">v1.0</span>
|
||||
</div>
|
||||
<div class="h-sep"></div>
|
||||
<div class="h-stats">
|
||||
<div class="h-stat"><span class="lbl">AGENTS</span><span class="val c-n">8</span></div>
|
||||
<div class="h-stat"><span class="lbl">OK</span><span class="val c-ok">5</span></div>
|
||||
<div class="h-stat"><span class="lbl">WARN</span><span class="val c-warn">2</span></div>
|
||||
<div class="h-stat"><span class="lbl">ERR</span><span class="val c-err">1</span></div>
|
||||
</div>
|
||||
<div class="h-spacer"></div>
|
||||
<div class="hbtn" onclick="toggleTheme()">
|
||||
<i class="fa-solid fa-moon" id="theme-icon"></i>
|
||||
<span class="tip">Thème clair / sombre</span>
|
||||
</div>
|
||||
<div class="hbtn" onclick="alert('Config panel')">
|
||||
<i class="fa-solid fa-sliders"></i>
|
||||
<span class="tip">Configuration interface</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GRID -->
|
||||
<div class="main">
|
||||
<div class="agents-grid">
|
||||
<div class="tile" onclick="showPopup()">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-names"><div class="t-host">srv-prod-01</div><div class="t-ip">10.0.0.11</div></div>
|
||||
<div class="t-led s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i><span class="g-tip">Processeur (CPU)</span></div><div class="g-bar"><div class="g-fill" style="width:42%"></div></div><span class="g-val">42%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i><span class="g-tip">Mémoire RAM</span></div><div class="g-bar"><div class="g-fill" style="width:58%"></div></div><span class="g-val">58%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Espace disque</span></div><div class="g-bar"><div class="g-fill" style="width:31%"></div></div><span class="g-val">31%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>14j 6h</div>
|
||||
</div>
|
||||
<div class="tile t-warn">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-names"><div class="t-host">srv-backup-02</div><div class="t-ip">10.0.0.12</div></div>
|
||||
<div class="t-led s-warn"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" style="color:var(--warn)"><i class="fa-solid fa-microchip"></i><span class="g-tip">CPU — élevé</span></div><div class="g-bar"><div class="g-fill w" style="width:78%"></div></div><span class="g-val" style="color:var(--warn)">78%</span></div>
|
||||
<div class="g-row"><div class="g-ico" style="color:var(--warn)"><i class="fa-solid fa-memory"></i><span class="g-tip">RAM — élevée</span></div><div class="g-bar"><div class="g-fill w" style="width:72%"></div></div><span class="g-val" style="color:var(--warn)">72%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Espace disque</span></div><div class="g-bar"><div class="g-fill" style="width:20%"></div></div><span class="g-val">20%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>3j 14h</div>
|
||||
</div>
|
||||
<div class="tile t-err">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon" style="color:var(--err)"><i class="fa-solid fa-microchip"></i></div>
|
||||
<div class="t-names"><div class="t-host">rpi-sensor-03</div><div class="t-ip">10.0.0.23</div></div>
|
||||
<div class="t-led s-err"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i><span class="g-tip">CPU</span></div><div class="g-bar"><div class="g-fill" style="width:12%"></div></div><span class="g-val">12%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i><span class="g-tip">RAM</span></div><div class="g-bar"><div class="g-fill" style="width:38%"></div></div><span class="g-val">38%</span></div>
|
||||
<div class="g-row"><div class="g-ico" style="color:var(--err)"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Disque critique !</span></div><div class="g-bar"><div class="g-fill e" style="width:94%"></div></div><span class="g-val" style="color:var(--err)">94%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>62j 1h</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-hard-drive"></i></div>
|
||||
<div class="t-names"><div class="t-host">nas-storage-04</div><div class="t-ip">10.0.0.30</div></div>
|
||||
<div class="t-led s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i><span class="g-tip">CPU</span></div><div class="g-bar"><div class="g-fill" style="width:8%"></div></div><span class="g-val">8%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i><span class="g-tip">RAM</span></div><div class="g-bar"><div class="g-fill" style="width:45%"></div></div><span class="g-val">45%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Disque</span></div><div class="g-bar"><div class="g-fill" style="width:66%"></div></div><span class="g-val">66%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>128j 3h</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-database"></i></div>
|
||||
<div class="t-names"><div class="t-host">db-primary-05</div><div class="t-ip">10.0.0.40</div></div>
|
||||
<div class="t-led s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i><span class="g-tip">CPU</span></div><div class="g-bar"><div class="g-fill" style="width:25%"></div></div><span class="g-val">25%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i><span class="g-tip">RAM</span></div><div class="g-bar"><div class="g-fill" style="width:61%"></div></div><span class="g-val">61%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Disque</span></div><div class="g-bar"><div class="g-fill" style="width:48%"></div></div><span class="g-val">48%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>7j 22h</div>
|
||||
</div>
|
||||
<div class="tile t-warn">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-globe"></i></div>
|
||||
<div class="t-names"><div class="t-host">web-front-06</div><div class="t-ip">10.0.0.51</div></div>
|
||||
<div class="t-led s-warn"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" style="color:var(--warn)"><i class="fa-solid fa-microchip"></i><span class="g-tip">CPU — élevé</span></div><div class="g-bar"><div class="g-fill w" style="width:71%"></div></div><span class="g-val" style="color:var(--warn)">71%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i><span class="g-tip">RAM</span></div><div class="g-bar"><div class="g-fill" style="width:55%"></div></div><span class="g-val">55%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Disque</span></div><div class="g-bar"><div class="g-fill" style="width:28%"></div></div><span class="g-val">28%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>21j 8h</div>
|
||||
</div>
|
||||
<div class="tile t-off">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon" style="color:var(--ink-4)"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-names"><div class="t-host">srv-dev-07</div><div class="t-ip">10.0.0.60</div></div>
|
||||
<div class="t-led s-off"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div>
|
||||
</div>
|
||||
<div class="tile-foot offline"><i class="fa-solid fa-circle-xmark"></i>Hors ligne · vu il y a 2h</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-network-wired"></i></div>
|
||||
<div class="t-names"><div class="t-host">gateway-08</div><div class="t-ip">10.0.0.1</div></div>
|
||||
<div class="t-led s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i><span class="g-tip">CPU</span></div><div class="g-bar"><div class="g-fill" style="width:5%"></div></div><span class="g-val">5%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i><span class="g-tip">RAM</span></div><div class="g-bar"><div class="g-fill" style="width:22%"></div></div><span class="g-val">22%</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i><span class="g-tip">Disque</span></div><div class="g-bar"><div class="g-fill" style="width:15%"></div></div><span class="g-val">15%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>365j 0h</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
<div class="f-mode">LIVE</div>
|
||||
<div class="f-cell"><i class="fa-solid fa-server" style="font-size:10px"></i><span>SERVEUR</span></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-microchip" style="font-size:10px"></i><span class="f-val">18%</span><div class="f-minibar"><div class="f-minifill" style="width:18%"></div></div></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-memory" style="font-size:10px"></i><span class="f-val w">71%</span><div class="f-minibar"><div class="f-minifill w" style="width:71%"></div></div></div>
|
||||
<div class="f-spacer"></div>
|
||||
<div class="f-right"><i class="fa-solid fa-rotate"></i><span>Actualisation : <span class="f-time">22:14:07</span></span></div>
|
||||
</div>
|
||||
|
||||
<!-- ══ POPUP ══ -->
|
||||
<div class="overlay" id="popup-overlay" style="display:flex" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" onclick="event.stopPropagation()">
|
||||
|
||||
<!-- HEADER POPUP -->
|
||||
<div class="pop-head">
|
||||
|
||||
<!-- Icône agent — cliquer pour uploader -->
|
||||
<div class="agent-icon-wrap" onclick="document.getElementById('icon-upload').click()" title="Changer l'icône">
|
||||
<i class="fa-solid fa-server" id="agent-icon-fa"></i>
|
||||
<img id="agent-icon-img" src="" style="display:none">
|
||||
<div class="agent-icon-overlay">
|
||||
<i class="fa-solid fa-camera"></i>
|
||||
<span>Changer</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" id="icon-upload" accept=".svg,.jpg,.jpeg,.png,.webp" onchange="handleIconUpload(event)">
|
||||
|
||||
<div class="pop-head-info">
|
||||
<div class="pop-host">srv-prod-01</div>
|
||||
<div class="pop-ip">10.0.0.11</div>
|
||||
<div class="upload-hint" id="upload-hint">Cliquer sur l'icône pour personnaliser · SVG, JPG, PNG, WEBP · max 128×128 px</div>
|
||||
</div>
|
||||
<div class="pop-led"></div>
|
||||
<div class="pop-close" onclick="document.getElementById('popup-overlay').style.display='none'">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BODY -->
|
||||
<div class="pop-body">
|
||||
|
||||
<!-- KPIs actuels -->
|
||||
<div>
|
||||
<div class="sec-title">MÉTRIQUES ACTUELLES</div>
|
||||
<div class="kpi-grid">
|
||||
<div class="kpi"><div class="kpi-lbl">CPU</div><div class="kpi-val c-ok">42<span class="u">%</span></div><div class="kpi-sub">4 cœurs</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">MÉMOIRE</div><div class="kpi-val">3.7<span class="u">Go</span></div><div class="kpi-sub">/ 8 Go · 46%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">DISQUE</div><div class="kpi-val">62<span class="u">Go</span></div><div class="kpi-sub">/ 200 Go · 31%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">UPTIME</div><div class="kpi-val" style="font-size:15px;color:var(--ink-1)">14j 6h</div><div class="kpi-sub">depuis boot</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COURBES HISTORIQUES CPU + RAM -->
|
||||
<div>
|
||||
<div class="sec-title">HISTORIQUE — 30 DERNIÈRES MINUTES</div>
|
||||
<div class="charts-grid">
|
||||
|
||||
<!-- CPU chart -->
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<div class="chart-label" style="color:var(--accent)"><i class="fa-solid fa-microchip"></i>CPU</div>
|
||||
<span class="chart-cur c-ok">42%</span>
|
||||
</div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 56" preserveAspectRatio="none" id="cpu-chart"></svg>
|
||||
<div class="chart-axis"><span>-30min</span><span>-15min</span><span>maintenant</span></div>
|
||||
</div>
|
||||
|
||||
<!-- RAM chart -->
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<div class="chart-label" style="color:var(--blue)"><i class="fa-solid fa-memory"></i>RAM</div>
|
||||
<span class="chart-cur" style="color:var(--blue)">46%</span>
|
||||
</div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 56" preserveAspectRatio="none" id="ram-chart"></svg>
|
||||
<div class="chart-axis"><span>-30min</span><span>-15min</span><span>maintenant</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DISQUE — jauge barre conservée (pas de série temporelle pertinente) -->
|
||||
<div>
|
||||
<div class="sec-title">STOCKAGE</div>
|
||||
<div class="det-gauges">
|
||||
<div class="dg-row">
|
||||
<div class="dg-ico"><i class="fa-solid fa-hard-drive"></i><span class="dg-tip">Disque utilisé</span></div>
|
||||
<div class="dg-bar"><div class="dg-fill" style="width:31%"></div></div>
|
||||
<span class="dg-val">62 / 200 Go</span>
|
||||
</div>
|
||||
<div class="dg-row">
|
||||
<div class="dg-ico" style="color:var(--blue)"><i class="fa-solid fa-floppy-disk"></i><span class="dg-tip">Espace libre</span></div>
|
||||
<div class="dg-bar"><div class="dg-fill" style="width:69%;background:var(--blue)"></div></div>
|
||||
<span class="dg-val">138 Go libre</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- INFOS -->
|
||||
<div>
|
||||
<div class="sec-title">INFORMATIONS</div>
|
||||
<div class="meta-grid">
|
||||
<div class="meta"><div class="meta-lbl">HOSTNAME</div><div class="meta-val">srv-prod-01</div></div>
|
||||
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">10.0.0.11</div></div>
|
||||
<div class="meta"><div class="meta-lbl">DERNIER CONTACT</div><div class="meta-val">22:14:07</div></div>
|
||||
<div class="meta"><div class="meta-lbl">FRÉQUENCE</div><div class="meta-val">CPU 2s · Disk 60s</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- FOOTER popup — pas de bouton Fermer -->
|
||||
<div class="pop-foot">
|
||||
<span class="pop-uptime"><i class="fa-solid fa-clock" style="margin-right:4px"></i>En ligne depuis 14 jours 6 heures</span>
|
||||
<button class="btn"><i class="fa-solid fa-chart-line"></i> Historique complet</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ── thème ── */
|
||||
function toggleTheme() {
|
||||
const h = document.documentElement;
|
||||
const i = document.getElementById('theme-icon');
|
||||
h.dataset.theme = h.dataset.theme === 'dark' ? 'light' : 'dark';
|
||||
i.className = h.dataset.theme === 'dark' ? 'fa-solid fa-moon' : 'fa-solid fa-sun';
|
||||
drawCharts();
|
||||
}
|
||||
|
||||
/* ── upload icône ── */
|
||||
function handleIconUpload(e) {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
const allowed = ['image/svg+xml','image/jpeg','image/png','image/webp'];
|
||||
if (!allowed.includes(file.type)) { alert('Format non supporté'); return; }
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(ev) {
|
||||
if (file.type === 'image/svg+xml') {
|
||||
// SVG : afficher directement
|
||||
showAgentIcon(ev.target.result, true);
|
||||
} else {
|
||||
// Raster : redimensionner canvas max 128×128 en respectant le ratio
|
||||
const img = new Image();
|
||||
img.onload = function() {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ratio = Math.min(128 / img.width, 128 / img.height, 1);
|
||||
canvas.width = Math.round(img.width * ratio);
|
||||
canvas.height = Math.round(img.height * ratio);
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
showAgentIcon(canvas.toDataURL(file.type === 'image/png' ? 'image/png' : 'image/jpeg', 0.9), false);
|
||||
};
|
||||
img.src = ev.target.result;
|
||||
}
|
||||
document.getElementById('upload-hint').textContent = '✓ Icône mise à jour — sauvegardée sur le serveur';
|
||||
document.getElementById('upload-hint').style.color = 'var(--ok)';
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
function showAgentIcon(src, isSvg) {
|
||||
document.getElementById('agent-icon-fa').style.display = 'none';
|
||||
const img = document.getElementById('agent-icon-img');
|
||||
img.src = src;
|
||||
img.style.display = 'block';
|
||||
}
|
||||
|
||||
/* ── courbes historiques ── */
|
||||
function makeCurve(pts, color, fillColor, w, h) {
|
||||
if (!pts.length) return '';
|
||||
const maxV = 100, minV = 0;
|
||||
const xs = pts.map((_, i) => (i / (pts.length - 1)) * w);
|
||||
const ys = pts.map(v => h - ((v - minV) / (maxV - minV)) * (h - 4) - 2);
|
||||
|
||||
// zone de seuil warn (70%) — ligne pointillée
|
||||
const warnY = h - ((70 - minV) / (maxV - minV)) * (h - 4) - 2;
|
||||
|
||||
let d = `M ${xs[0]} ${ys[0]}`;
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
const cx = (xs[i-1] + xs[i]) / 2;
|
||||
d += ` C ${cx} ${ys[i-1]}, ${cx} ${ys[i]}, ${xs[i]} ${ys[i]}`;
|
||||
}
|
||||
|
||||
const fillD = d + ` L ${xs[xs.length-1]} ${h} L ${xs[0]} ${h} Z`;
|
||||
|
||||
return `
|
||||
<defs>
|
||||
<linearGradient id="grad-${color.replace('#','')}" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="${fillColor}" stop-opacity="0.35"/>
|
||||
<stop offset="100%" stop-color="${fillColor}" stop-opacity="0.02"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<line x1="0" y1="${warnY}" x2="${w}" y2="${warnY}"
|
||||
stroke="var(--warn)" stroke-width="0.8" stroke-dasharray="3,3" opacity="0.5"/>
|
||||
<path d="${fillD}" fill="url(#grad-${color.replace('#','')})" />
|
||||
<path d="${d}" fill="none" stroke="${color}" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="${xs[xs.length-1]}" cy="${ys[ys.length-1]}" r="2.5" fill="${color}"/>
|
||||
`;
|
||||
}
|
||||
|
||||
function drawCharts() {
|
||||
const cpuPts = [38,41,35,40,44,42,39,45,50,48,43,42,44,41,40,43,47,45,42,44,41,43,42,40,43,41,44,43,41,42];
|
||||
const ramPts = [44,44,45,45,46,46,47,47,46,46,45,46,46,47,47,46,46,45,46,46,46,46,46,46,46,46,46,46,46,46];
|
||||
|
||||
const accent = getComputedStyle(document.documentElement).getPropertyValue('--accent').trim();
|
||||
const blue = getComputedStyle(document.documentElement).getPropertyValue('--blue').trim();
|
||||
|
||||
document.getElementById('cpu-chart').innerHTML = makeCurve(cpuPts, accent, accent, 200, 56);
|
||||
document.getElementById('ram-chart').innerHTML = makeCurve(ramPts, blue, blue, 200, 56);
|
||||
}
|
||||
|
||||
window.addEventListener('load', drawCharts);
|
||||
|
||||
function showPopup() {
|
||||
document.getElementById('popup-overlay').style.display = 'flex';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,577 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nanometrics — Layout v5</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script src="/helper.js"></script>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--accent:#fe8019; --accent-soft:#d65d0e; --accent-glow:rgba(254,128,25,0.28);
|
||||
--bg-0:#1d1813; --bg-1:#2a231d; --bg-2:#32291f; --bg-3:#3c332a; --bg-4:#4a3f33; --bg-5:#57493c;
|
||||
--ink-1:#f2e5c7; --ink-2:#d5c4a1; --ink-3:#a89984; --ink-4:#7c6f64;
|
||||
--ok:#4dbb26; --warn:#fabd2f; --err:#fb4934; --info:#83a598; --blue:#3db0d1; --purple:#c882c8;
|
||||
--border-1:rgba(255,255,255,0.06); --border-2:rgba(255,255,255,0.12); --border-3:rgba(255,255,255,0.26);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,0.08) inset,0 -1px 0 rgba(0,0,0,0.3) inset,0 6px 20px rgba(0,0,0,0.5);
|
||||
--tile-press:inset 0 2px 8px rgba(0,0,0,0.5),inset 0 1px 3px rgba(0,0,0,0.4);
|
||||
--hover-glow:0 0 0 1px var(--accent-soft),0 0 24px var(--accent-glow),0 6px 20px rgba(0,0,0,0.5);
|
||||
--font-ui:'Inter',system-ui,sans-serif; --font-mono:'JetBrains Mono',monospace; --font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
:root[data-theme="light"] {
|
||||
--accent:#af3a03; --accent-soft:#d65d0e; --accent-glow:rgba(175,58,3,0.18);
|
||||
--bg-0:#d5c4a1; --bg-1:#ebdbb2; --bg-2:#d5c4a1; --bg-3:#bdae93; --bg-4:#a89984; --bg-5:#928374;
|
||||
--ink-1:#3c3836; --ink-2:#504945; --ink-3:#665c54; --ink-4:#7c6f64;
|
||||
--ok:#3c911c; --warn:#b57614; --err:#9d0006; --blue:#2d82a3; --purple:#8c468c;
|
||||
--border-1:rgba(0,0,0,0.08); --border-2:rgba(0,0,0,0.15); --border-3:rgba(0,0,0,0.3);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,0.55) inset,0 -1px 0 rgba(0,0,0,0.08) inset,0 4px 14px rgba(0,0,0,0.13);
|
||||
--tile-press:inset 0 2px 6px rgba(0,0,0,0.2),inset 0 1px 3px rgba(0,0,0,0.15);
|
||||
--hover-glow:0 0 0 1px var(--accent-soft),0 0 18px var(--accent-glow),0 4px 14px rgba(0,0,0,0.13);
|
||||
--font-ui:'Inter',system-ui,sans-serif; --font-mono:'JetBrains Mono',monospace; --font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
|
||||
body { background:var(--bg-1); color:var(--ink-1); font-family:var(--font-ui); font-size:13px; height:100vh; display:flex; flex-direction:column; overflow:hidden; transition:background .2s,color .2s; }
|
||||
|
||||
/* ══ TOOLTIP SYSTÈME GLOBAL ══
|
||||
Chaque élément avec [data-tip] affiche un tooltip centré via JS,
|
||||
hors du flux — plus de problème d'offset ni de clip. */
|
||||
#tooltip {
|
||||
position:fixed; z-index:9999; pointer-events:none;
|
||||
background:var(--bg-0); color:var(--ink-1);
|
||||
border:1px solid var(--border-3); border-radius:5px;
|
||||
padding:4px 9px; font-size:11px; font-family:var(--font-ui);
|
||||
white-space:nowrap; opacity:0; transition:opacity .12s;
|
||||
box-shadow:0 4px 12px rgba(0,0,0,.4);
|
||||
}
|
||||
#tooltip.show { opacity:1; }
|
||||
|
||||
/* HEADER */
|
||||
.header { background:var(--bg-2); border-bottom:1px solid var(--border-2); padding:0 20px; height:48px; display:flex; align-items:center; gap:12px; flex-shrink:0; }
|
||||
.logo { display:flex; align-items:center; gap:8px; }
|
||||
.logo-led { width:9px; height:9px; border-radius:50%; background:var(--accent); box-shadow:0 0 8px var(--accent-glow); animation:blink 2s infinite; }
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.logo-name { font-weight:700; font-size:14px; letter-spacing:.05em; font-family:var(--font-terminal); }
|
||||
.logo-ver { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); }
|
||||
.h-sep { width:1px; height:24px; background:var(--border-2); }
|
||||
.h-spacer { flex:1; }
|
||||
.h-stats { display:flex; gap:14px; }
|
||||
.h-stat { display:flex; align-items:center; gap:5px; }
|
||||
.h-stat .lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.h-stat .val { font-family:var(--font-mono); font-weight:700; font-size:13px; }
|
||||
.c-ok{color:var(--ok)}.c-warn{color:var(--warn)}.c-err{color:var(--err)}.c-n{color:var(--ink-2)}
|
||||
|
||||
/* Header icon buttons */
|
||||
.hbtn {
|
||||
width:34px; height:34px; border-radius:8px;
|
||||
border:1px solid var(--border-2); background:var(--bg-3); color:var(--ink-2);
|
||||
font-size:14px; display:flex; align-items:center; justify-content:center;
|
||||
cursor:pointer; user-select:none;
|
||||
transition:background .12s, color .12s, box-shadow .08s, transform .08s;
|
||||
}
|
||||
.hbtn:hover { background:var(--bg-4); color:var(--accent); }
|
||||
.hbtn:active {
|
||||
transform:translateY(1px) scale(.96);
|
||||
box-shadow:var(--tile-press);
|
||||
background:var(--bg-5);
|
||||
}
|
||||
|
||||
/* GRID */
|
||||
.main { flex:1; padding:14px 16px; overflow-y:auto; }
|
||||
.agents-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:10px; }
|
||||
|
||||
/* TILE */
|
||||
.tile {
|
||||
background:var(--bg-3); border-radius:10px; padding:12px 14px;
|
||||
border:1px solid var(--border-1); box-shadow:var(--tile-3d);
|
||||
cursor:pointer; user-select:none;
|
||||
display:flex; flex-direction:column; gap:9px;
|
||||
transition:box-shadow .15s, transform .08s, border-color .15s;
|
||||
}
|
||||
.tile:hover {
|
||||
box-shadow:var(--hover-glow);
|
||||
border-color:var(--accent-soft);
|
||||
}
|
||||
.tile:active {
|
||||
transform:translateY(2px) scale(.99);
|
||||
box-shadow:var(--tile-press);
|
||||
border-color:var(--accent);
|
||||
}
|
||||
.tile.t-warn { border-color:rgba(250,189,47,.3); }
|
||||
.tile.t-warn:hover { border-color:var(--warn); box-shadow:0 0 0 1px var(--warn),0 0 22px rgba(250,189,47,.22),0 6px 20px rgba(0,0,0,.5); }
|
||||
.tile.t-err { border-color:rgba(251,73,52,.35); box-shadow:var(--tile-3d),0 0 16px rgba(251,73,52,.12); }
|
||||
.tile.t-err:hover { border-color:var(--err); box-shadow:0 0 0 1px var(--err),0 0 22px rgba(251,73,52,.25),0 6px 20px rgba(0,0,0,.5); }
|
||||
.tile.t-off { opacity:.5; cursor:default; }
|
||||
.tile.t-off:hover { box-shadow:var(--tile-3d); border-color:var(--border-1); }
|
||||
.tile.t-off:active { transform:none; box-shadow:var(--tile-3d); }
|
||||
|
||||
.tile-head { display:flex; align-items:center; gap:8px; }
|
||||
.t-icon { width:28px; height:28px; border-radius:7px; background:var(--bg-4); display:flex; align-items:center; justify-content:center; color:var(--accent); font-size:13px; flex-shrink:0; overflow:hidden; }
|
||||
.t-icon img { width:100%; height:100%; object-fit:cover; }
|
||||
.t-names { flex:1; min-width:0; }
|
||||
.t-host { font-weight:600; font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.t-ip { font-family:var(--font-mono); font-size:10px; color:var(--ink-4); }
|
||||
.t-led { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
|
||||
.s-ok {background:var(--ok);box-shadow:0 0 6px var(--ok)}
|
||||
.s-warn{background:var(--warn);box-shadow:0 0 6px var(--warn);animation:blink 1.5s infinite}
|
||||
.s-err {background:var(--err);box-shadow:0 0 8px var(--err);animation:blink 1s infinite}
|
||||
.s-off {background:var(--ink-4)}
|
||||
|
||||
.tile-gauges { display:flex; flex-direction:column; gap:5px; }
|
||||
.g-row { display:flex; align-items:center; gap:7px; }
|
||||
.g-ico { width:18px; height:18px; display:flex; align-items:center; justify-content:center; font-size:11px; color:var(--ink-3); flex-shrink:0; cursor:help; }
|
||||
.g-bar { flex:1; height:5px; border-radius:3px; background:var(--bg-1); overflow:hidden; }
|
||||
.g-fill { height:100%; border-radius:3px; background:var(--ok); transition:width .4s; }
|
||||
.g-fill.w{background:var(--warn)}.g-fill.e{background:var(--err)}
|
||||
.g-val { font-family:var(--font-mono); font-size:11px; color:var(--ink-2); width:34px; text-align:right; flex-shrink:0; }
|
||||
.tile-foot { font-family:var(--font-terminal); font-size:10px; color:var(--ink-4); display:flex; align-items:center; gap:5px; }
|
||||
.tile-foot i { font-size:9px; }
|
||||
.tile-foot.offline { color:var(--err); }
|
||||
|
||||
/* FOOTER */
|
||||
.footer { background:var(--bg-0); border-top:1px solid var(--border-2); height:26px; display:flex; align-items:center; font-family:var(--font-terminal); font-size:11px; color:var(--ink-4); flex-shrink:0; }
|
||||
.f-mode { background:var(--accent); color:var(--bg-0); padding:0 12px; height:100%; display:flex; align-items:center; font-weight:700; letter-spacing:.04em; }
|
||||
.f-cell { padding:0 12px; border-right:1px solid var(--border-1); display:flex; align-items:center; gap:5px; height:100%; }
|
||||
.f-val { font-family:var(--font-mono); color:var(--ink-2); }
|
||||
.f-val.w { color:var(--warn); }
|
||||
.f-minibar { width:36px; height:4px; border-radius:2px; background:var(--bg-3); overflow:hidden; }
|
||||
.f-minifill { height:100%; border-radius:2px; background:var(--ok); }
|
||||
.f-minifill.w { background:var(--warn); }
|
||||
.f-spacer { flex:1; }
|
||||
.f-right { padding:0 12px; display:flex; align-items:center; gap:6px; color:var(--ink-3); }
|
||||
.f-time { font-family:var(--font-mono); color:var(--ink-2); }
|
||||
|
||||
/* ══ POPUP ══ */
|
||||
.overlay { position:fixed; inset:0; background:rgba(0,0,0,.65); z-index:100; display:flex; align-items:center; justify-content:center; backdrop-filter:blur(2px); }
|
||||
.popup { background:var(--bg-2); border:1px solid var(--border-3); border-radius:12px; width:540px; max-width:96vw; max-height:92vh; box-shadow:0 24px 64px rgba(0,0,0,.7); display:flex; flex-direction:column; overflow:hidden; }
|
||||
.pop-head { background:var(--bg-3); padding:14px 18px; border-bottom:1px solid var(--border-2); display:flex; align-items:center; gap:12px; }
|
||||
.agent-icon-wrap { position:relative; width:44px; height:44px; border-radius:10px; flex-shrink:0; cursor:pointer; overflow:hidden; background:var(--bg-4); display:flex; align-items:center; justify-content:center; color:var(--accent); font-size:18px; border:2px solid var(--border-2); transition:border-color .15s; }
|
||||
.agent-icon-wrap:hover { border-color:var(--accent); }
|
||||
.agent-icon-wrap img { width:100%; height:100%; object-fit:cover; }
|
||||
.agent-icon-overlay { position:absolute; inset:0; background:rgba(0,0,0,.6); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px; opacity:0; transition:opacity .15s; font-size:10px; color:#fff; font-family:var(--font-ui); }
|
||||
.agent-icon-overlay i { font-size:14px; }
|
||||
.agent-icon-wrap:hover .agent-icon-overlay { opacity:1; }
|
||||
#icon-upload { display:none; }
|
||||
.pop-head-info { flex:1; }
|
||||
.pop-host { font-weight:700; font-size:15px; }
|
||||
.pop-ip { font-family:var(--font-mono); font-size:11px; color:var(--ink-4); }
|
||||
.upload-hint { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); margin-top:3px; }
|
||||
.pop-led { width:10px; height:10px; border-radius:50%; background:var(--ok); box-shadow:0 0 8px var(--ok); flex-shrink:0; }
|
||||
.pop-close {
|
||||
width:28px; height:28px; border-radius:6px; background:var(--bg-5); color:var(--ink-3);
|
||||
display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:12px;
|
||||
transition:background .12s, color .12s, transform .08s, box-shadow .08s;
|
||||
border:1px solid var(--border-1); user-select:none;
|
||||
}
|
||||
.pop-close:hover { background:var(--err); color:#fff; }
|
||||
.pop-close:active { transform:translateY(1px) scale(.93); box-shadow:var(--tile-press); }
|
||||
|
||||
.pop-body { padding:16px 18px; display:flex; flex-direction:column; gap:14px; overflow-y:auto; }
|
||||
.sec-title { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.08em; margin-bottom:8px; }
|
||||
.kpi-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:7px; }
|
||||
.kpi { background:var(--bg-3); border-radius:8px; padding:10px 12px; border:1px solid var(--border-1); box-shadow:var(--tile-3d); }
|
||||
.kpi-lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.kpi-val { font-family:var(--font-mono); font-size:20px; font-weight:700; line-height:1.1; margin-top:2px; }
|
||||
.kpi-val .u { font-size:10px; color:var(--ink-3); font-weight:400; }
|
||||
.kpi-sub { font-size:10px; color:var(--ink-4); font-family:var(--font-mono); margin-top:2px; }
|
||||
.charts-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
|
||||
.chart-card { background:var(--bg-3); border-radius:8px; padding:10px 12px; border:1px solid var(--border-1); box-shadow:var(--tile-3d); }
|
||||
.chart-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; }
|
||||
.chart-label { display:flex; align-items:center; gap:6px; font-size:10px; font-family:var(--font-terminal); letter-spacing:.06em; color:var(--ink-3); }
|
||||
.chart-cur { font-family:var(--font-mono); font-size:16px; font-weight:700; }
|
||||
.chart-svg { width:100%; height:56px; display:block; }
|
||||
.chart-axis { display:flex; justify-content:space-between; margin-top:3px; font-family:var(--font-terminal); font-size:9px; color:var(--ink-4); }
|
||||
.det-gauges { display:flex; flex-direction:column; gap:7px; }
|
||||
.dg-row { display:flex; align-items:center; gap:10px; }
|
||||
.dg-ico { width:22px; text-align:center; font-size:13px; cursor:help; }
|
||||
.dg-bar { flex:1; height:7px; border-radius:4px; background:var(--bg-1); overflow:hidden; }
|
||||
.dg-fill { height:100%; border-radius:4px; background:var(--ok); }
|
||||
.dg-fill.w{background:var(--warn)}.dg-fill.e{background:var(--err)}
|
||||
.dg-val { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); width:90px; text-align:right; }
|
||||
.meta-grid { display:grid; grid-template-columns:1fr 1fr; gap:6px; }
|
||||
.meta { background:var(--bg-3); border-radius:6px; padding:8px 10px; border:1px solid var(--border-1); }
|
||||
.meta-lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.meta-val { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); margin-top:2px; }
|
||||
.pop-foot { padding:10px 18px; border-top:1px solid var(--border-2); background:var(--bg-3); display:flex; align-items:center; gap:8px; }
|
||||
.pop-uptime { font-family:var(--font-terminal); font-size:11px; color:var(--ink-4); flex:1; }
|
||||
|
||||
/* Boutons génériques */
|
||||
.btn {
|
||||
padding:6px 14px; border-radius:8px; border:1px solid var(--border-2);
|
||||
background:var(--bg-4); color:var(--ink-2); font-size:12px; font-family:var(--font-ui);
|
||||
cursor:pointer; display:flex; align-items:center; gap:6px; user-select:none;
|
||||
transition:background .1s, transform .08s, box-shadow .08s;
|
||||
}
|
||||
.btn:hover { background:var(--bg-5); }
|
||||
.btn:active { transform:translateY(1px) scale(.97); box-shadow:var(--tile-press); }
|
||||
.btn.primary { background:var(--accent); color:var(--bg-0); border-color:var(--accent-soft); font-weight:600; }
|
||||
.btn.primary:hover { background:var(--accent-soft); }
|
||||
|
||||
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:var(--bg-1)}::-webkit-scrollbar-thumb{background:var(--bg-4);border-radius:3px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- TOOLTIP GLOBAL -->
|
||||
<div id="tooltip"></div>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="logo">
|
||||
<div class="logo-led"></div>
|
||||
<span class="logo-name">NANOMETRICS</span>
|
||||
<span class="logo-ver">v1.0</span>
|
||||
</div>
|
||||
<div class="h-sep"></div>
|
||||
<div class="h-stats">
|
||||
<div class="h-stat"><span class="lbl">AGENTS</span><span class="val c-n">8</span></div>
|
||||
<div class="h-stat"><span class="lbl">OK</span><span class="val c-ok">5</span></div>
|
||||
<div class="h-stat"><span class="lbl">WARN</span><span class="val c-warn">2</span></div>
|
||||
<div class="h-stat"><span class="lbl">ERR</span><span class="val c-err">1</span></div>
|
||||
</div>
|
||||
<div class="h-spacer"></div>
|
||||
<div class="hbtn" onclick="toggleTheme()" data-tip="Thème clair / sombre">
|
||||
<i class="fa-solid fa-moon" id="theme-icon"></i>
|
||||
</div>
|
||||
<div class="hbtn" data-tip="Configuration interface">
|
||||
<i class="fa-solid fa-sliders"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GRID -->
|
||||
<div class="main">
|
||||
<div class="agents-grid">
|
||||
|
||||
<div class="tile" onclick="showPopup()">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-names"><div class="t-host">srv-prod-01</div><div class="t-ip">10.0.0.11</div></div>
|
||||
<div class="t-led s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="Processeur (CPU)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:42%"></div></div><span class="g-val">42%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Mémoire RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:58%"></div></div><span class="g-val">58%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Espace disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:31%"></div></div><span class="g-val">31%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>14j 6h</div>
|
||||
</div>
|
||||
|
||||
<div class="tile t-warn">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-names"><div class="t-host">srv-backup-02</div><div class="t-ip">10.0.0.12</div></div>
|
||||
<div class="t-led s-warn"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="CPU — élevé" style="color:var(--warn)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill w" style="width:78%"></div></div><span class="g-val" style="color:var(--warn)">78%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="RAM — élevée" style="color:var(--warn)"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill w" style="width:72%"></div></div><span class="g-val" style="color:var(--warn)">72%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Espace disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:20%"></div></div><span class="g-val">20%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>3j 14h</div>
|
||||
</div>
|
||||
|
||||
<div class="tile t-err">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon" style="color:var(--err)"><i class="fa-solid fa-microchip"></i></div>
|
||||
<div class="t-names"><div class="t-host">rpi-sensor-03</div><div class="t-ip">10.0.0.23</div></div>
|
||||
<div class="t-led s-err"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="Processeur (CPU)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:12%"></div></div><span class="g-val">12%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Mémoire RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:38%"></div></div><span class="g-val">38%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Disque critique !" style="color:var(--err)"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill e" style="width:94%"></div></div><span class="g-val" style="color:var(--err)">94%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>62j 1h</div>
|
||||
</div>
|
||||
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-hard-drive"></i></div>
|
||||
<div class="t-names"><div class="t-host">nas-storage-04</div><div class="t-ip">10.0.0.30</div></div>
|
||||
<div class="t-led s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="Processeur (CPU)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:8%"></div></div><span class="g-val">8%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Mémoire RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:45%"></div></div><span class="g-val">45%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Espace disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:66%"></div></div><span class="g-val">66%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>128j 3h</div>
|
||||
</div>
|
||||
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-database"></i></div>
|
||||
<div class="t-names"><div class="t-host">db-primary-05</div><div class="t-ip">10.0.0.40</div></div>
|
||||
<div class="t-led s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="Processeur (CPU)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:25%"></div></div><span class="g-val">25%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Mémoire RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:61%"></div></div><span class="g-val">61%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Espace disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:48%"></div></div><span class="g-val">48%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>7j 22h</div>
|
||||
</div>
|
||||
|
||||
<div class="tile t-warn">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-globe"></i></div>
|
||||
<div class="t-names"><div class="t-host">web-front-06</div><div class="t-ip">10.0.0.51</div></div>
|
||||
<div class="t-led s-warn"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="CPU — élevé" style="color:var(--warn)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill w" style="width:71%"></div></div><span class="g-val" style="color:var(--warn)">71%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Mémoire RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:55%"></div></div><span class="g-val">55%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Espace disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:28%"></div></div><span class="g-val">28%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>21j 8h</div>
|
||||
</div>
|
||||
|
||||
<div class="tile t-off">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon" style="color:var(--ink-4)"><i class="fa-solid fa-server"></i></div>
|
||||
<div class="t-names"><div class="t-host">srv-dev-07</div><div class="t-ip">10.0.0.60</div></div>
|
||||
<div class="t-led s-off"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div>
|
||||
<div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div>
|
||||
</div>
|
||||
<div class="tile-foot offline"><i class="fa-solid fa-circle-xmark"></i>Hors ligne · vu il y a 2h</div>
|
||||
</div>
|
||||
|
||||
<div class="tile">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon"><i class="fa-solid fa-network-wired"></i></div>
|
||||
<div class="t-names"><div class="t-host">gateway-08</div><div class="t-ip">10.0.0.1</div></div>
|
||||
<div class="t-led s-ok"></div>
|
||||
</div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="Processeur (CPU)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:5%"></div></div><span class="g-val">5%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Mémoire RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:22%"></div></div><span class="g-val">22%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Espace disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:15%"></div></div><span class="g-val">15%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>365j 0h</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
<div class="f-mode">LIVE</div>
|
||||
<div class="f-cell"><i class="fa-solid fa-server" style="font-size:10px"></i><span>SERVEUR</span></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-microchip" style="font-size:10px"></i><span class="f-val">18%</span><div class="f-minibar"><div class="f-minifill" style="width:18%"></div></div></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-memory" style="font-size:10px"></i><span class="f-val w">71%</span><div class="f-minibar"><div class="f-minifill w" style="width:71%"></div></div></div>
|
||||
<div class="f-spacer"></div>
|
||||
<div class="f-right"><i class="fa-solid fa-rotate"></i><span>Actualisation : <span class="f-time">22:14:07</span></span></div>
|
||||
</div>
|
||||
|
||||
<!-- POPUP -->
|
||||
<div class="overlay" id="popup-overlay" style="display:flex" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" onclick="event.stopPropagation()">
|
||||
<div class="pop-head">
|
||||
<div class="agent-icon-wrap" onclick="document.getElementById('icon-upload').click()" data-tip="Changer l'icône de l'agent">
|
||||
<i class="fa-solid fa-server" id="agent-icon-fa"></i>
|
||||
<img id="agent-icon-img" src="" style="display:none">
|
||||
<div class="agent-icon-overlay"><i class="fa-solid fa-camera"></i><span>Changer</span></div>
|
||||
</div>
|
||||
<input type="file" id="icon-upload" accept=".svg,.jpg,.jpeg,.png,.webp" onchange="handleIconUpload(event)">
|
||||
<div class="pop-head-info">
|
||||
<div class="pop-host">srv-prod-01</div>
|
||||
<div class="pop-ip">10.0.0.11</div>
|
||||
<div class="upload-hint" id="upload-hint">Cliquer sur l'icône pour personnaliser · SVG JPG PNG WEBP · redim. max 128×128 px</div>
|
||||
</div>
|
||||
<div class="pop-led"></div>
|
||||
<div class="pop-close" onclick="document.getElementById('popup-overlay').style.display='none'" data-tip="Fermer">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pop-body">
|
||||
<div>
|
||||
<div class="sec-title">MÉTRIQUES ACTUELLES</div>
|
||||
<div class="kpi-grid">
|
||||
<div class="kpi"><div class="kpi-lbl">CPU</div><div class="kpi-val c-ok">42<span class="u">%</span></div><div class="kpi-sub">4 cœurs</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">MÉMOIRE</div><div class="kpi-val">3.7<span class="u">Go</span></div><div class="kpi-sub">/ 8 Go · 46%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">DISQUE</div><div class="kpi-val">62<span class="u">Go</span></div><div class="kpi-sub">/ 200 Go · 31%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">UPTIME</div><div class="kpi-val" style="font-size:15px;color:var(--ink-1)">14j 6h</div><div class="kpi-sub">depuis boot</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">HISTORIQUE — 30 DERNIÈRES MINUTES</div>
|
||||
<div class="charts-grid">
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<div class="chart-label" style="color:var(--accent)"><i class="fa-solid fa-microchip"></i>CPU</div>
|
||||
<span class="chart-cur c-ok">42%</span>
|
||||
</div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 56" preserveAspectRatio="none" id="cpu-chart"></svg>
|
||||
<div class="chart-axis"><span>−30min</span><span>−15min</span><span>now</span></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<div class="chart-label" style="color:var(--blue)"><i class="fa-solid fa-memory"></i>RAM</div>
|
||||
<span class="chart-cur" style="color:var(--blue)">46%</span>
|
||||
</div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 56" preserveAspectRatio="none" id="ram-chart"></svg>
|
||||
<div class="chart-axis"><span>−30min</span><span>−15min</span><span>now</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">STOCKAGE</div>
|
||||
<div class="det-gauges">
|
||||
<div class="dg-row">
|
||||
<div class="dg-ico" data-tip="Disque utilisé"><i class="fa-solid fa-hard-drive"></i></div>
|
||||
<div class="dg-bar"><div class="dg-fill" style="width:31%"></div></div>
|
||||
<span class="dg-val">62 / 200 Go</span>
|
||||
</div>
|
||||
<div class="dg-row">
|
||||
<div class="dg-ico" data-tip="Espace libre" style="color:var(--blue)"><i class="fa-solid fa-floppy-disk"></i></div>
|
||||
<div class="dg-bar"><div class="dg-fill" style="width:69%;background:var(--blue)"></div></div>
|
||||
<span class="dg-val">138 Go libre</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">INFORMATIONS</div>
|
||||
<div class="meta-grid">
|
||||
<div class="meta"><div class="meta-lbl">HOSTNAME</div><div class="meta-val">srv-prod-01</div></div>
|
||||
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">10.0.0.11</div></div>
|
||||
<div class="meta"><div class="meta-lbl">DERNIER CONTACT</div><div class="meta-val">22:14:07</div></div>
|
||||
<div class="meta"><div class="meta-lbl">FRÉQUENCE</div><div class="meta-val">CPU 2s · Disk 60s</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pop-foot">
|
||||
<span class="pop-uptime"><i class="fa-solid fa-clock" style="margin-right:4px"></i>En ligne depuis 14 jours 6 heures</span>
|
||||
<button class="btn"><i class="fa-solid fa-chart-line"></i> Historique complet</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ══ TOOLTIP GLOBAL — fixé sur le document, jamais clippé ══ */
|
||||
const tip = document.getElementById('tooltip');
|
||||
let tipTimer;
|
||||
|
||||
document.addEventListener('mouseover', e => {
|
||||
const el = e.target.closest('[data-tip]');
|
||||
if (!el) return;
|
||||
clearTimeout(tipTimer);
|
||||
tipTimer = setTimeout(() => {
|
||||
tip.textContent = el.dataset.tip;
|
||||
tip.classList.add('show');
|
||||
moveTip(e);
|
||||
}, 120);
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', e => {
|
||||
if (tip.classList.contains('show')) moveTip(e);
|
||||
});
|
||||
|
||||
document.addEventListener('mouseout', e => {
|
||||
const el = e.target.closest('[data-tip]');
|
||||
if (!el) return;
|
||||
clearTimeout(tipTimer);
|
||||
tip.classList.remove('show');
|
||||
});
|
||||
|
||||
function moveTip(e) {
|
||||
const tw = tip.offsetWidth, th = tip.offsetHeight;
|
||||
let x = e.clientX - tw / 2;
|
||||
let y = e.clientY - th - 10;
|
||||
/* garder dans le viewport */
|
||||
x = Math.max(6, Math.min(x, window.innerWidth - tw - 6));
|
||||
if (y < 6) y = e.clientY + 18;
|
||||
tip.style.left = x + 'px';
|
||||
tip.style.top = y + 'px';
|
||||
}
|
||||
|
||||
/* ══ THÈME ══ */
|
||||
function toggleTheme() {
|
||||
const h = document.documentElement;
|
||||
h.dataset.theme = h.dataset.theme === 'dark' ? 'light' : 'dark';
|
||||
document.getElementById('theme-icon').className =
|
||||
h.dataset.theme === 'dark' ? 'fa-solid fa-moon' : 'fa-solid fa-sun';
|
||||
drawCharts();
|
||||
}
|
||||
|
||||
/* ══ UPLOAD ICÔNE ══ */
|
||||
function handleIconUpload(e) {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
const allowed = ['image/svg+xml','image/jpeg','image/png','image/webp'];
|
||||
if (!allowed.includes(file.type)) { alert('Format non supporté'); return; }
|
||||
const reader = new FileReader();
|
||||
reader.onload = ev => {
|
||||
if (file.type === 'image/svg+xml') {
|
||||
applyIcon(ev.target.result);
|
||||
} else {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const r = Math.min(128 / img.width, 128 / img.height, 1);
|
||||
canvas.width = Math.round(img.width * r);
|
||||
canvas.height = Math.round(img.height * r);
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
applyIcon(canvas.toDataURL(file.type === 'image/png' ? 'image/png' : 'image/jpeg', 0.9));
|
||||
};
|
||||
img.src = ev.target.result;
|
||||
}
|
||||
const hint = document.getElementById('upload-hint');
|
||||
hint.textContent = '✓ Icône mise à jour — sauvegardée sur le serveur';
|
||||
hint.style.color = 'var(--ok)';
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
function applyIcon(src) {
|
||||
document.getElementById('agent-icon-fa').style.display = 'none';
|
||||
const img = document.getElementById('agent-icon-img');
|
||||
img.src = src; img.style.display = 'block';
|
||||
}
|
||||
|
||||
/* ══ COURBES ══ */
|
||||
function makeCurve(pts, stroke, fill, w, h) {
|
||||
const xs = pts.map((_, i) => (i / (pts.length - 1)) * w);
|
||||
const ys = pts.map(v => h - (v / 100) * (h - 6) - 3);
|
||||
const warnY = h - (70 / 100) * (h - 6) - 3;
|
||||
let d = `M${xs[0]} ${ys[0]}`;
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
const cx = (xs[i-1] + xs[i]) / 2;
|
||||
d += ` C${cx} ${ys[i-1]},${cx} ${ys[i]},${xs[i]} ${ys[i]}`;
|
||||
}
|
||||
const uid = Math.random().toString(36).slice(2);
|
||||
return `
|
||||
<defs>
|
||||
<linearGradient id="g${uid}" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="${fill}" stop-opacity=".4"/>
|
||||
<stop offset="100%" stop-color="${fill}" stop-opacity=".02"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<line x1="0" y1="${warnY}" x2="${w}" y2="${warnY}" stroke="var(--warn)" stroke-width=".8" stroke-dasharray="3,3" opacity=".5"/>
|
||||
<path d="${d} L${xs.at(-1)} ${h} L${xs[0]} ${h}Z" fill="url(#g${uid})"/>
|
||||
<path d="${d}" fill="none" stroke="${stroke}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="${xs.at(-1)}" cy="${ys.at(-1)}" r="2.5" fill="${stroke}"/>
|
||||
`;
|
||||
}
|
||||
|
||||
function drawCharts() {
|
||||
const cpu = [38,41,35,40,44,42,39,45,50,48,43,42,44,41,40,43,47,45,42,44,41,43,42,40,43,41,44,43,41,42];
|
||||
const ram = [44,44,45,45,46,46,47,47,46,46,45,46,46,47,47,46,46,45,46,46,46,46,46,46,46,46,46,46,46,46];
|
||||
const cs = getComputedStyle(document.documentElement);
|
||||
const accent = cs.getPropertyValue('--accent').trim();
|
||||
const blue = cs.getPropertyValue('--blue').trim();
|
||||
document.getElementById('cpu-chart').innerHTML = makeCurve(cpu, accent, accent, 200, 56);
|
||||
document.getElementById('ram-chart').innerHTML = makeCurve(ram, blue, blue, 200, 56);
|
||||
}
|
||||
|
||||
function showPopup() { document.getElementById('popup-overlay').style.display='flex'; }
|
||||
window.addEventListener('load', drawCharts);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,551 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nanometrics — Layout v6 — Config agent</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script src="/helper.js"></script>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing:border-box; margin:0; padding:0; }
|
||||
:root[data-theme="dark"] {
|
||||
--accent:#fe8019; --accent-soft:#d65d0e; --accent-glow:rgba(254,128,25,0.28);
|
||||
--bg-0:#1d1813; --bg-1:#2a231d; --bg-2:#32291f; --bg-3:#3c332a; --bg-4:#4a3f33; --bg-5:#57493c;
|
||||
--ink-1:#f2e5c7; --ink-2:#d5c4a1; --ink-3:#a89984; --ink-4:#7c6f64;
|
||||
--ok:#4dbb26; --warn:#fabd2f; --err:#fb4934; --info:#83a598; --blue:#3db0d1; --purple:#c882c8;
|
||||
--border-1:rgba(255,255,255,0.06); --border-2:rgba(255,255,255,0.12); --border-3:rgba(255,255,255,0.26);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,0.08) inset,0 -1px 0 rgba(0,0,0,0.3) inset,0 6px 20px rgba(0,0,0,0.5);
|
||||
--tile-press:inset 0 2px 8px rgba(0,0,0,0.5),inset 0 1px 3px rgba(0,0,0,0.4);
|
||||
--hover-glow:0 0 0 1px var(--accent-soft),0 0 24px var(--accent-glow),0 6px 20px rgba(0,0,0,0.5);
|
||||
--font-ui:'Inter',system-ui,sans-serif; --font-mono:'JetBrains Mono',monospace; --font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
:root[data-theme="light"] {
|
||||
--accent:#af3a03; --accent-soft:#d65d0e; --accent-glow:rgba(175,58,3,0.18);
|
||||
--bg-0:#d5c4a1; --bg-1:#ebdbb2; --bg-2:#d5c4a1; --bg-3:#bdae93; --bg-4:#a89984; --bg-5:#928374;
|
||||
--ink-1:#3c3836; --ink-2:#504945; --ink-3:#665c54; --ink-4:#7c6f64;
|
||||
--ok:#3c911c; --warn:#b57614; --err:#9d0006; --blue:#2d82a3; --purple:#8c468c;
|
||||
--border-1:rgba(0,0,0,0.08); --border-2:rgba(0,0,0,0.15); --border-3:rgba(0,0,0,0.3);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,0.55) inset,0 -1px 0 rgba(0,0,0,0.08) inset,0 4px 14px rgba(0,0,0,0.13);
|
||||
--tile-press:inset 0 2px 6px rgba(0,0,0,0.2);
|
||||
--hover-glow:0 0 0 1px var(--accent-soft),0 0 18px var(--accent-glow),0 4px 14px rgba(0,0,0,0.13);
|
||||
--font-ui:'Inter',system-ui,sans-serif; --font-mono:'JetBrains Mono',monospace; --font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
body { background:var(--bg-1); color:var(--ink-1); font-family:var(--font-ui); font-size:13px; height:100vh; display:flex; flex-direction:column; overflow:hidden; transition:background .2s,color .2s; }
|
||||
|
||||
/* TOOLTIP */
|
||||
#tooltip { position:fixed; z-index:9999; pointer-events:none; background:var(--bg-0); color:var(--ink-1); border:1px solid var(--border-3); border-radius:5px; padding:4px 9px; font-size:11px; font-family:var(--font-ui); white-space:nowrap; opacity:0; transition:opacity .12s; box-shadow:0 4px 12px rgba(0,0,0,.4); }
|
||||
#tooltip.show { opacity:1; }
|
||||
|
||||
/* HEADER */
|
||||
.header { background:var(--bg-2); border-bottom:1px solid var(--border-2); padding:0 20px; height:48px; display:flex; align-items:center; gap:12px; flex-shrink:0; }
|
||||
.logo { display:flex; align-items:center; gap:8px; }
|
||||
.logo-led { width:9px; height:9px; border-radius:50%; background:var(--accent); box-shadow:0 0 8px var(--accent-glow); animation:blink 2s infinite; }
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.logo-name { font-weight:700; font-size:14px; letter-spacing:.05em; font-family:var(--font-terminal); }
|
||||
.logo-ver { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); }
|
||||
.h-sep { width:1px; height:24px; background:var(--border-2); }
|
||||
.h-spacer { flex:1; }
|
||||
.h-stats { display:flex; gap:14px; }
|
||||
.h-stat { display:flex; align-items:center; gap:5px; }
|
||||
.h-stat .lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.h-stat .val { font-family:var(--font-mono); font-weight:700; font-size:13px; }
|
||||
.c-ok{color:var(--ok)}.c-warn{color:var(--warn)}.c-err{color:var(--err)}.c-n{color:var(--ink-2)}
|
||||
.hbtn { width:34px; height:34px; border-radius:8px; border:1px solid var(--border-2); background:var(--bg-3); color:var(--ink-2); font-size:14px; display:flex; align-items:center; justify-content:center; cursor:pointer; user-select:none; transition:background .12s,color .12s,transform .08s,box-shadow .08s; }
|
||||
.hbtn:hover { background:var(--bg-4); color:var(--accent); }
|
||||
.hbtn:active { transform:translateY(1px) scale(.96); box-shadow:var(--tile-press); }
|
||||
|
||||
/* GRID */
|
||||
.main { flex:1; padding:14px 16px; overflow-y:auto; }
|
||||
.agents-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:10px; }
|
||||
.tile { background:var(--bg-3); border-radius:10px; padding:12px 14px; border:1px solid var(--border-1); box-shadow:var(--tile-3d); cursor:pointer; user-select:none; display:flex; flex-direction:column; gap:9px; transition:box-shadow .15s,transform .08s,border-color .15s; }
|
||||
.tile:hover { box-shadow:var(--hover-glow); border-color:var(--accent-soft); }
|
||||
.tile:active { transform:translateY(2px) scale(.99); box-shadow:var(--tile-press); }
|
||||
.tile.t-warn { border-color:rgba(250,189,47,.3); }
|
||||
.tile.t-warn:hover { border-color:var(--warn); box-shadow:0 0 0 1px var(--warn),0 0 22px rgba(250,189,47,.22),0 6px 20px rgba(0,0,0,.5); }
|
||||
.tile.t-err { border-color:rgba(251,73,52,.35); }
|
||||
.tile.t-err:hover { border-color:var(--err); box-shadow:0 0 0 1px var(--err),0 0 22px rgba(251,73,52,.25),0 6px 20px rgba(0,0,0,.5); }
|
||||
.tile.t-off { opacity:.5; cursor:default; }
|
||||
.tile.t-off:hover,.tile.t-off:active { box-shadow:var(--tile-3d); border-color:var(--border-1); transform:none; }
|
||||
.tile-head { display:flex; align-items:center; gap:8px; }
|
||||
.t-icon { width:28px; height:28px; border-radius:7px; background:var(--bg-4); display:flex; align-items:center; justify-content:center; color:var(--accent); font-size:13px; flex-shrink:0; overflow:hidden; }
|
||||
.t-names { flex:1; min-width:0; }
|
||||
.t-host { font-weight:600; font-size:13px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.t-ip { font-family:var(--font-mono); font-size:10px; color:var(--ink-4); }
|
||||
.t-led { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
|
||||
.s-ok{background:var(--ok);box-shadow:0 0 6px var(--ok)}.s-warn{background:var(--warn);box-shadow:0 0 6px var(--warn);animation:blink 1.5s infinite}.s-err{background:var(--err);box-shadow:0 0 8px var(--err);animation:blink 1s infinite}.s-off{background:var(--ink-4)}
|
||||
.tile-gauges { display:flex; flex-direction:column; gap:5px; }
|
||||
.g-row { display:flex; align-items:center; gap:7px; }
|
||||
.g-ico { width:18px; height:18px; display:flex; align-items:center; justify-content:center; font-size:11px; color:var(--ink-3); flex-shrink:0; cursor:help; }
|
||||
.g-bar { flex:1; height:5px; border-radius:3px; background:var(--bg-1); overflow:hidden; }
|
||||
.g-fill { height:100%; border-radius:3px; background:var(--ok); }
|
||||
.g-fill.w{background:var(--warn)}.g-fill.e{background:var(--err)}
|
||||
.g-val { font-family:var(--font-mono); font-size:11px; color:var(--ink-2); width:34px; text-align:right; }
|
||||
.tile-foot { font-family:var(--font-terminal); font-size:10px; color:var(--ink-4); display:flex; align-items:center; gap:5px; }
|
||||
.tile-foot i { font-size:9px; }
|
||||
|
||||
/* FOOTER */
|
||||
.footer { background:var(--bg-0); border-top:1px solid var(--border-2); height:26px; display:flex; align-items:center; font-family:var(--font-terminal); font-size:11px; color:var(--ink-4); flex-shrink:0; }
|
||||
.f-mode { background:var(--accent); color:var(--bg-0); padding:0 12px; height:100%; display:flex; align-items:center; font-weight:700; letter-spacing:.04em; }
|
||||
.f-cell { padding:0 12px; border-right:1px solid var(--border-1); display:flex; align-items:center; gap:5px; height:100%; }
|
||||
.f-val { font-family:var(--font-mono); color:var(--ink-2); }
|
||||
.f-val.w { color:var(--warn); }
|
||||
.f-minibar { width:36px; height:4px; border-radius:2px; background:var(--bg-3); overflow:hidden; }
|
||||
.f-minifill { height:100%; border-radius:2px; background:var(--ok); }
|
||||
.f-minifill.w { background:var(--warn); }
|
||||
.f-spacer { flex:1; }
|
||||
.f-right { padding:0 12px; display:flex; align-items:center; gap:6px; color:var(--ink-3); }
|
||||
.f-time { font-family:var(--font-mono); color:var(--ink-2); }
|
||||
|
||||
/* ══ OVERLAY / POPUP BASE ══ */
|
||||
.overlay { position:fixed; inset:0; background:rgba(0,0,0,.65); z-index:100; display:flex; align-items:center; justify-content:center; backdrop-filter:blur(2px); }
|
||||
.popup { background:var(--bg-2); border:1px solid var(--border-3); border-radius:12px; box-shadow:0 24px 64px rgba(0,0,0,.7); display:flex; flex-direction:column; overflow:hidden; }
|
||||
.pop-close { width:28px; height:28px; border-radius:6px; background:var(--bg-5); color:var(--ink-3); display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:12px; border:1px solid var(--border-1); transition:background .12s,color .12s,transform .08s; user-select:none; }
|
||||
.pop-close:hover { background:var(--err); color:#fff; }
|
||||
.pop-close:active { transform:translateY(1px) scale(.93); box-shadow:var(--tile-press); }
|
||||
.sec-title { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.08em; margin-bottom:8px; }
|
||||
.btn { padding:6px 14px; border-radius:8px; border:1px solid var(--border-2); background:var(--bg-4); color:var(--ink-2); font-size:12px; font-family:var(--font-ui); cursor:pointer; display:flex; align-items:center; gap:6px; user-select:none; transition:background .1s,transform .08s,box-shadow .08s; }
|
||||
.btn:hover { background:var(--bg-5); }
|
||||
.btn:active { transform:translateY(1px) scale(.97); box-shadow:var(--tile-press); }
|
||||
.btn.primary { background:var(--accent); color:var(--bg-0); border-color:var(--accent-soft); font-weight:600; }
|
||||
.btn.primary:hover { background:var(--accent-soft); }
|
||||
.btn.danger { background:rgba(251,73,52,.15); color:var(--err); border-color:rgba(251,73,52,.3); }
|
||||
.btn.danger:hover { background:rgba(251,73,52,.25); }
|
||||
|
||||
/* ══ POPUP DÉTAIL AGENT ══ */
|
||||
#popup-detail { width:540px; max-width:96vw; max-height:92vh; }
|
||||
.pop-head { background:var(--bg-3); padding:14px 18px; border-bottom:1px solid var(--border-2); display:flex; align-items:center; gap:12px; }
|
||||
.agent-icon-wrap { position:relative; width:44px; height:44px; border-radius:10px; flex-shrink:0; cursor:pointer; overflow:hidden; background:var(--bg-4); display:flex; align-items:center; justify-content:center; color:var(--accent); font-size:18px; border:2px solid var(--border-2); transition:border-color .15s; }
|
||||
.agent-icon-wrap:hover { border-color:var(--accent); }
|
||||
.agent-icon-overlay { position:absolute; inset:0; background:rgba(0,0,0,.6); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px; opacity:0; transition:opacity .15s; font-size:10px; color:#fff; }
|
||||
.agent-icon-overlay i { font-size:14px; }
|
||||
.agent-icon-wrap:hover .agent-icon-overlay { opacity:1; }
|
||||
#icon-upload { display:none; }
|
||||
.pop-head-info { flex:1; }
|
||||
.pop-host { font-weight:700; font-size:15px; }
|
||||
.pop-ip { font-family:var(--font-mono); font-size:11px; color:var(--ink-4); }
|
||||
.upload-hint { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); margin-top:2px; }
|
||||
.pop-led { width:10px; height:10px; border-radius:50%; background:var(--ok); box-shadow:0 0 8px var(--ok); flex-shrink:0; }
|
||||
.pop-body { padding:16px 18px; display:flex; flex-direction:column; gap:14px; overflow-y:auto; }
|
||||
.kpi-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:7px; }
|
||||
.kpi { background:var(--bg-3); border-radius:8px; padding:10px 12px; border:1px solid var(--border-1); box-shadow:var(--tile-3d); }
|
||||
.kpi-lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.kpi-val { font-family:var(--font-mono); font-size:20px; font-weight:700; line-height:1.1; margin-top:2px; }
|
||||
.kpi-val .u { font-size:10px; color:var(--ink-3); font-weight:400; }
|
||||
.kpi-sub { font-size:10px; color:var(--ink-4); font-family:var(--font-mono); margin-top:2px; }
|
||||
.charts-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
|
||||
.chart-card { background:var(--bg-3); border-radius:8px; padding:10px 12px; border:1px solid var(--border-1); box-shadow:var(--tile-3d); }
|
||||
.chart-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; }
|
||||
.chart-label { display:flex; align-items:center; gap:6px; font-size:10px; font-family:var(--font-terminal); letter-spacing:.06em; color:var(--ink-3); }
|
||||
.chart-cur { font-family:var(--font-mono); font-size:16px; font-weight:700; }
|
||||
.chart-svg { width:100%; height:52px; display:block; }
|
||||
.chart-axis { display:flex; justify-content:space-between; margin-top:2px; font-family:var(--font-terminal); font-size:9px; color:var(--ink-4); }
|
||||
.det-gauges { display:flex; flex-direction:column; gap:7px; }
|
||||
.dg-row { display:flex; align-items:center; gap:10px; }
|
||||
.dg-ico { width:22px; text-align:center; font-size:13px; cursor:help; }
|
||||
.dg-bar { flex:1; height:7px; border-radius:4px; background:var(--bg-1); overflow:hidden; }
|
||||
.dg-fill { height:100%; border-radius:4px; background:var(--ok); }
|
||||
.dg-fill.b { background:var(--blue); }
|
||||
.dg-val { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); width:90px; text-align:right; }
|
||||
.meta-grid { display:grid; grid-template-columns:1fr 1fr; gap:6px; }
|
||||
.meta { background:var(--bg-3); border-radius:6px; padding:8px 10px; border:1px solid var(--border-1); }
|
||||
.meta-lbl { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.06em; }
|
||||
.meta-val { font-family:var(--font-mono); font-size:12px; color:var(--ink-2); margin-top:2px; }
|
||||
|
||||
/* badge protocoles dans meta */
|
||||
.proto-badges { display:flex; gap:5px; margin-top:4px; flex-wrap:wrap; }
|
||||
.proto-badge { display:inline-flex; align-items:center; gap:4px; padding:2px 7px; border-radius:999px; font-size:10px; font-family:var(--font-terminal); font-weight:600; }
|
||||
.proto-badge.udp { background:rgba(61,176,209,.15); color:var(--blue); border:1px solid rgba(61,176,209,.3); }
|
||||
.proto-badge.mqtt { background:rgba(200,130,200,.15); color:var(--purple); border:1px solid rgba(200,130,200,.3); }
|
||||
|
||||
/* footer popup détail — bouton config agent en bas à droite */
|
||||
.pop-foot { padding:10px 18px; border-top:1px solid var(--border-2); background:var(--bg-3); display:flex; align-items:center; gap:8px; }
|
||||
.pop-uptime { font-family:var(--font-terminal); font-size:11px; color:var(--ink-4); flex:1; }
|
||||
/* bouton config agent — icon service */
|
||||
.btn-agent-cfg {
|
||||
width:34px; height:34px; border-radius:8px; border:1px solid var(--border-2);
|
||||
background:var(--bg-4); color:var(--ink-3); font-size:15px;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
cursor:pointer; user-select:none;
|
||||
transition:background .12s,color .12s,transform .08s,box-shadow .08s;
|
||||
}
|
||||
.btn-agent-cfg:hover { background:var(--bg-5); color:var(--accent); }
|
||||
.btn-agent-cfg:active { transform:translateY(1px) scale(.93); box-shadow:var(--tile-press); }
|
||||
|
||||
/* ══ POPUP CONFIG AGENT ══ */
|
||||
#popup-agentcfg { width:480px; max-width:96vw; max-height:90vh; z-index:200; }
|
||||
.cfg-head { background:var(--bg-3); padding:14px 18px; border-bottom:1px solid var(--border-2); display:flex; align-items:center; gap:10px; }
|
||||
.cfg-head-icon { width:32px; height:32px; border-radius:8px; background:var(--bg-4); display:flex; align-items:center; justify-content:center; color:var(--accent); font-size:15px; }
|
||||
.cfg-head-info { flex:1; }
|
||||
.cfg-head-title { font-weight:700; font-size:14px; }
|
||||
.cfg-head-sub { font-size:11px; color:var(--ink-4); font-family:var(--font-terminal); }
|
||||
.cfg-body { padding:18px; display:flex; flex-direction:column; gap:18px; overflow-y:auto; max-height:60vh; }
|
||||
.cfg-section { display:flex; flex-direction:column; gap:10px; }
|
||||
.cfg-sec-title { font-size:9px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.08em; padding-bottom:6px; border-bottom:1px solid var(--border-1); }
|
||||
|
||||
/* checkboxes custom */
|
||||
.check-row { display:flex; align-items:center; gap:10px; padding:8px 10px; border-radius:8px; background:var(--bg-3); border:1px solid var(--border-1); cursor:pointer; transition:background .12s,border-color .12s; }
|
||||
.check-row:hover { background:var(--bg-4); border-color:var(--border-2); }
|
||||
.check-row.active { border-color:var(--accent-soft); background:rgba(254,128,25,.08); }
|
||||
.check-row.mqtt-active { border-color:rgba(200,130,200,.4); background:rgba(200,130,200,.07); }
|
||||
.chk-box { width:18px; height:18px; border-radius:4px; border:2px solid var(--border-3); background:var(--bg-1); display:flex; align-items:center; justify-content:center; font-size:11px; color:var(--accent); flex-shrink:0; transition:background .12s,border-color .12s; }
|
||||
.check-row.active .chk-box { background:var(--accent); border-color:var(--accent); color:var(--bg-0); }
|
||||
.check-row.mqtt-active .chk-box { background:var(--purple); border-color:var(--purple); color:var(--bg-0); }
|
||||
.chk-label { flex:1; }
|
||||
.chk-name { font-weight:600; font-size:13px; }
|
||||
.chk-desc { font-size:11px; color:var(--ink-4); font-family:var(--font-terminal); margin-top:1px; }
|
||||
.chk-badge { font-size:10px; font-family:var(--font-terminal); font-weight:700; padding:1px 7px; border-radius:999px; }
|
||||
.chk-badge.udp { background:rgba(61,176,209,.15); color:var(--blue); }
|
||||
.chk-badge.mqtt { background:rgba(200,130,200,.15); color:var(--purple); }
|
||||
|
||||
/* MQTT sub-options */
|
||||
.mqtt-opts { background:var(--bg-3); border-radius:8px; border:1px solid rgba(200,130,200,.2); padding:12px 14px; display:flex; flex-direction:column; gap:10px; }
|
||||
.mqtt-field { display:flex; align-items:center; gap:10px; }
|
||||
.mqtt-field label { font-size:11px; color:var(--ink-3); font-family:var(--font-terminal); width:90px; flex-shrink:0; }
|
||||
.mqtt-input { flex:1; background:var(--bg-1); border:1px solid var(--border-2); border-radius:6px; color:var(--ink-1); padding:6px 10px; font-size:12px; font-family:var(--font-mono); }
|
||||
.mqtt-input:focus { outline:none; border-color:var(--purple); }
|
||||
.mqtt-check-row { display:flex; align-items:center; justify-content:space-between; padding:4px 0; }
|
||||
.mqtt-check-row label { font-size:12px; color:var(--ink-2); display:flex; align-items:center; gap:7px; cursor:pointer; }
|
||||
.mqtt-check-row label i { color:var(--purple); font-size:11px; }
|
||||
/* toggle switch */
|
||||
.toggle { position:relative; width:34px; height:18px; flex-shrink:0; }
|
||||
.toggle input { opacity:0; width:0; height:0; }
|
||||
.toggle-slider { position:absolute; inset:0; border-radius:9px; background:var(--bg-4); border:1px solid var(--border-2); cursor:pointer; transition:background .2s; }
|
||||
.toggle-slider::before { content:''; position:absolute; width:12px; height:12px; border-radius:50%; background:var(--ink-4); top:2px; left:2px; transition:transform .2s, background .2s; }
|
||||
.toggle input:checked + .toggle-slider { background:rgba(200,130,200,.3); border-color:var(--purple); }
|
||||
.toggle input:checked + .toggle-slider::before { transform:translateX(16px); background:var(--purple); }
|
||||
|
||||
/* Métriques toggles */
|
||||
.metrics-grid { display:grid; grid-template-columns:1fr 1fr; gap:6px; }
|
||||
.metric-row { display:flex; align-items:center; gap:8px; padding:7px 10px; border-radius:7px; background:var(--bg-3); border:1px solid var(--border-1); }
|
||||
.metric-ico { width:22px; text-align:center; font-size:12px; color:var(--ink-3); }
|
||||
.metric-name { flex:1; font-size:12px; color:var(--ink-2); font-family:var(--font-terminal); }
|
||||
.tog-ok { }
|
||||
.tog-ok input:checked + .toggle-slider { background:rgba(77,187,38,.3); border-color:var(--ok); }
|
||||
.tog-ok input:checked + .toggle-slider::before { background:var(--ok); }
|
||||
|
||||
/* Commandes futures */
|
||||
.cmds-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:6px; }
|
||||
.cmd-btn { display:flex; flex-direction:column; align-items:center; gap:4px; padding:10px 8px; border-radius:8px; background:var(--bg-3); border:1px solid var(--border-1); cursor:not-allowed; opacity:.45; }
|
||||
.cmd-btn i { font-size:16px; color:var(--ink-3); }
|
||||
.cmd-btn span { font-size:10px; color:var(--ink-4); font-family:var(--font-terminal); }
|
||||
.cmd-btn.enabled { cursor:pointer; opacity:1; }
|
||||
.cmd-btn.enabled:hover { background:var(--bg-4); border-color:var(--border-2); }
|
||||
.cmd-btn.enabled:hover i { color:var(--accent); }
|
||||
.cmd-btn.danger-cmd { }
|
||||
.cmd-btn.danger-cmd.enabled:hover { background:rgba(251,73,52,.12); border-color:rgba(251,73,52,.3); }
|
||||
.cmd-btn.danger-cmd.enabled:hover i { color:var(--err); }
|
||||
.soon-tag { font-size:8px; color:var(--ink-4); font-family:var(--font-terminal); letter-spacing:.04em; }
|
||||
|
||||
/* cfg footer */
|
||||
.cfg-foot { padding:12px 18px; border-top:1px solid var(--border-2); background:var(--bg-3); display:flex; align-items:center; gap:8px; }
|
||||
.cfg-status { flex:1; display:flex; align-items:center; gap:6px; font-family:var(--font-terminal); font-size:11px; color:var(--ink-4); }
|
||||
.cfg-status .dot { width:6px; height:6px; border-radius:50%; background:var(--ok); }
|
||||
|
||||
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:var(--bg-1)}::-webkit-scrollbar-thumb{background:var(--bg-4);border-radius:3px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="tooltip"></div>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="logo"><div class="logo-led"></div><span class="logo-name">NANOMETRICS</span><span class="logo-ver">v1.0</span></div>
|
||||
<div class="h-sep"></div>
|
||||
<div class="h-stats">
|
||||
<div class="h-stat"><span class="lbl">AGENTS</span><span class="val c-n">8</span></div>
|
||||
<div class="h-stat"><span class="lbl">OK</span><span class="val c-ok">5</span></div>
|
||||
<div class="h-stat"><span class="lbl">WARN</span><span class="val c-warn">2</span></div>
|
||||
<div class="h-stat"><span class="lbl">ERR</span><span class="val c-err">1</span></div>
|
||||
</div>
|
||||
<div class="h-spacer"></div>
|
||||
<div class="hbtn" onclick="toggleTheme()" data-tip="Thème clair / sombre"><i class="fa-solid fa-moon" id="theme-icon"></i></div>
|
||||
<div class="hbtn" data-tip="Configuration interface"><i class="fa-solid fa-sliders"></i></div>
|
||||
</div>
|
||||
|
||||
<!-- GRID -->
|
||||
<div class="main">
|
||||
<div class="agents-grid">
|
||||
<div class="tile" onclick="showDetail()">
|
||||
<div class="tile-head"><div class="t-icon"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-prod-01</div><div class="t-ip">10.0.0.11</div></div><div class="t-led s-ok"></div></div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="Processeur (CPU)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:42%"></div></div><span class="g-val">42%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Mémoire RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:58%"></div></div><span class="g-val">58%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Espace disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:31%"></div></div><span class="g-val">31%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>14j 6h</div>
|
||||
</div>
|
||||
<div class="tile t-warn">
|
||||
<div class="tile-head"><div class="t-icon"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-backup-02</div><div class="t-ip">10.0.0.12</div></div><div class="t-led s-warn"></div></div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="CPU — élevé" style="color:var(--warn)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill w" style="width:78%"></div></div><span class="g-val" style="color:var(--warn)">78%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="RAM — élevée" style="color:var(--warn)"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill w" style="width:72%"></div></div><span class="g-val" style="color:var(--warn)">72%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Espace disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:20%"></div></div><span class="g-val">20%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>3j 14h</div>
|
||||
</div>
|
||||
<div class="tile t-err">
|
||||
<div class="tile-head"><div class="t-icon" style="color:var(--err)"><i class="fa-solid fa-microchip"></i></div><div class="t-names"><div class="t-host">rpi-sensor-03</div><div class="t-ip">10.0.0.23</div></div><div class="t-led s-err"></div></div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:12%"></div></div><span class="g-val">12%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:38%"></div></div><span class="g-val">38%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Disque critique !" style="color:var(--err)"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill e" style="width:94%"></div></div><span class="g-val" style="color:var(--err)">94%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>62j 1h</div>
|
||||
</div>
|
||||
<div class="tile"><div class="tile-head"><div class="t-icon"><i class="fa-solid fa-hard-drive"></i></div><div class="t-names"><div class="t-host">nas-storage-04</div><div class="t-ip">10.0.0.30</div></div><div class="t-led s-ok"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:8%"></div></div><span class="g-val">8%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:45%"></div></div><span class="g-val">45%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:66%"></div></div><span class="g-val">66%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>128j 3h</div></div>
|
||||
<div class="tile"><div class="tile-head"><div class="t-icon"><i class="fa-solid fa-database"></i></div><div class="t-names"><div class="t-host">db-primary-05</div><div class="t-ip">10.0.0.40</div></div><div class="t-led s-ok"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:25%"></div></div><span class="g-val">25%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:61%"></div></div><span class="g-val">61%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:48%"></div></div><span class="g-val">48%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>7j 22h</div></div>
|
||||
<div class="tile t-off"><div class="tile-head"><div class="t-icon" style="color:var(--ink-4)"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-dev-07</div><div class="t-ip">10.0.0.60</div></div><div class="t-led s-off"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div><div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div><div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div></div><div class="tile-foot" style="color:var(--err)"><i class="fa-solid fa-circle-xmark"></i>Hors ligne · vu il y a 2h</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
<div class="f-mode">LIVE</div>
|
||||
<div class="f-cell"><i class="fa-solid fa-server" style="font-size:10px"></i><span>SERVEUR</span></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-microchip" style="font-size:10px"></i><span class="f-val">18%</span><div class="f-minibar"><div class="f-minifill" style="width:18%"></div></div></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-memory" style="font-size:10px"></i><span class="f-val w">71%</span><div class="f-minibar"><div class="f-minifill w" style="width:71%"></div></div></div>
|
||||
<div class="f-spacer"></div>
|
||||
<div class="f-right"><i class="fa-solid fa-rotate"></i><span>Actualisation : <span class="f-time">22:14:07</span></span></div>
|
||||
</div>
|
||||
|
||||
<!-- ══ POPUP DÉTAIL AGENT ══ -->
|
||||
<div class="overlay" id="overlay-detail" style="display:flex" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-detail" onclick="event.stopPropagation()">
|
||||
<div class="pop-head">
|
||||
<div class="agent-icon-wrap" onclick="document.getElementById('icon-upload').click()" data-tip="Changer l'icône">
|
||||
<i class="fa-solid fa-server"></i>
|
||||
<div class="agent-icon-overlay"><i class="fa-solid fa-camera"></i><span>Changer</span></div>
|
||||
</div>
|
||||
<input type="file" id="icon-upload" accept=".svg,.jpg,.jpeg,.png,.webp">
|
||||
<div class="pop-head-info">
|
||||
<div class="pop-host">srv-prod-01</div>
|
||||
<div class="pop-ip">10.0.0.11</div>
|
||||
<div class="upload-hint">Cliquer sur l'icône pour personnaliser · SVG JPG PNG WEBP · max 128×128 px</div>
|
||||
</div>
|
||||
<div class="pop-led"></div>
|
||||
<div class="pop-close" onclick="document.getElementById('overlay-detail').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="pop-body">
|
||||
<div>
|
||||
<div class="sec-title">MÉTRIQUES ACTUELLES</div>
|
||||
<div class="kpi-grid">
|
||||
<div class="kpi"><div class="kpi-lbl">CPU</div><div class="kpi-val c-ok">42<span class="u">%</span></div><div class="kpi-sub">4 cœurs</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">MÉMOIRE</div><div class="kpi-val">3.7<span class="u">Go</span></div><div class="kpi-sub">/ 8 Go · 46%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">DISQUE</div><div class="kpi-val">62<span class="u">Go</span></div><div class="kpi-sub">/ 200 Go · 31%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">UPTIME</div><div class="kpi-val" style="font-size:15px;color:var(--ink-1)">14j 6h</div><div class="kpi-sub">depuis boot</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">HISTORIQUE — 30 DERNIÈRES MINUTES</div>
|
||||
<div class="charts-grid">
|
||||
<div class="chart-card">
|
||||
<div class="chart-header"><div class="chart-label" style="color:var(--accent)"><i class="fa-solid fa-microchip"></i>CPU</div><span class="chart-cur c-ok">42%</span></div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 52" preserveAspectRatio="none" id="cpu-chart"></svg>
|
||||
<div class="chart-axis"><span>−30min</span><span>−15min</span><span>now</span></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<div class="chart-header"><div class="chart-label" style="color:var(--blue)"><i class="fa-solid fa-memory"></i>RAM</div><span class="chart-cur" style="color:var(--blue)">46%</span></div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 52" preserveAspectRatio="none" id="ram-chart"></svg>
|
||||
<div class="chart-axis"><span>−30min</span><span>−15min</span><span>now</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">STOCKAGE</div>
|
||||
<div class="det-gauges">
|
||||
<div class="dg-row"><div class="dg-ico" data-tip="Utilisé"><i class="fa-solid fa-hard-drive"></i></div><div class="dg-bar"><div class="dg-fill" style="width:31%"></div></div><span class="dg-val">62 / 200 Go</span></div>
|
||||
<div class="dg-row"><div class="dg-ico" data-tip="Libre" style="color:var(--blue)"><i class="fa-solid fa-floppy-disk"></i></div><div class="dg-bar"><div class="dg-fill b" style="width:69%"></div></div><span class="dg-val">138 Go libre</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">INFORMATIONS</div>
|
||||
<div class="meta-grid">
|
||||
<div class="meta"><div class="meta-lbl">HOSTNAME</div><div class="meta-val">srv-prod-01</div></div>
|
||||
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">10.0.0.11</div></div>
|
||||
<div class="meta"><div class="meta-lbl">PROTOCOLES ACTIFS</div>
|
||||
<div class="proto-badges">
|
||||
<span class="proto-badge udp"><i class="fa-solid fa-arrow-up"></i>UDP</span>
|
||||
<span class="proto-badge mqtt"><i class="fa-brands fa-mqtt" style="font-family:var(--font-terminal)">M</i>MQTT</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta"><div class="meta-lbl">DERNIER CONTACT</div><div class="meta-val">22:14:07</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- footer — bouton config agent en bas à droite -->
|
||||
<div class="pop-foot">
|
||||
<span class="pop-uptime"><i class="fa-solid fa-clock" style="margin-right:4px"></i>En ligne depuis 14 jours 6 heures</span>
|
||||
<button class="btn"><i class="fa-solid fa-chart-line"></i> Historique</button>
|
||||
<div class="btn-agent-cfg" onclick="showAgentCfg()" data-tip="Configurer l'agent">
|
||||
<i class="fa-solid fa-gears"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ POPUP CONFIG AGENT ══ -->
|
||||
<div class="overlay" id="overlay-agentcfg" style="display:none;z-index:200" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-agentcfg" onclick="event.stopPropagation()">
|
||||
|
||||
<div class="cfg-head">
|
||||
<div class="cfg-head-icon"><i class="fa-solid fa-gears"></i></div>
|
||||
<div class="cfg-head-info">
|
||||
<div class="cfg-head-title">Configuration de l'agent</div>
|
||||
<div class="cfg-head-sub">srv-prod-01 · 10.0.0.11 · config récupérée à 22:14:05</div>
|
||||
</div>
|
||||
<div class="pop-close" onclick="document.getElementById('overlay-agentcfg').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="cfg-body">
|
||||
|
||||
<!-- PROTOCOLES -->
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">PROTOCOLES DE TRANSPORT</div>
|
||||
|
||||
<!-- UDP -->
|
||||
<div class="check-row active" id="row-udp" onclick="toggleProto('udp')">
|
||||
<div class="chk-box" id="chk-udp"><i class="fa-solid fa-check"></i></div>
|
||||
<div class="chk-label">
|
||||
<div class="chk-name">UDP <span class="chk-badge udp">UDP</span></div>
|
||||
<div class="chk-desc">Fire-and-forget · serveur 10.0.0.50 · port 9999</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MQTT -->
|
||||
<div class="check-row mqtt-active" id="row-mqtt" onclick="toggleProto('mqtt')">
|
||||
<div class="chk-box" id="chk-mqtt" style="background:var(--purple);border-color:var(--purple);color:var(--bg-0)"><i class="fa-solid fa-check"></i></div>
|
||||
<div class="chk-label">
|
||||
<div class="chk-name">MQTT <span class="chk-badge mqtt">MQTT</span></div>
|
||||
<div class="chk-desc">Bidirectionnel · broker 10.0.0.3 · port 1883</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MQTT sub-options -->
|
||||
<div class="mqtt-opts" id="mqtt-opts">
|
||||
<div class="mqtt-field">
|
||||
<label>Broker</label>
|
||||
<input class="mqtt-input" type="text" value="10.0.0.3">
|
||||
</div>
|
||||
<div class="mqtt-field">
|
||||
<label>Port</label>
|
||||
<input class="mqtt-input" type="number" value="1883" style="width:90px;flex:none">
|
||||
</div>
|
||||
<div class="mqtt-field">
|
||||
<label>Topic base</label>
|
||||
<input class="mqtt-input" type="text" value="nanometrics/agents">
|
||||
</div>
|
||||
<div style="border-top:1px solid var(--border-1);padding-top:8px;display:flex;flex-direction:column;gap:6px;">
|
||||
<div class="mqtt-check-row">
|
||||
<label><i class="fa-solid fa-satellite-dish"></i> Auto-discovery (Home Assistant)</label>
|
||||
<label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
|
||||
</div>
|
||||
<div class="mqtt-check-row">
|
||||
<label><i class="fa-solid fa-arrow-right-to-bracket"></i> Birth message (connexion)</label>
|
||||
<label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
|
||||
</div>
|
||||
<div class="mqtt-check-row">
|
||||
<label><i class="fa-solid fa-skull"></i> Last Will message (déconnexion)</label>
|
||||
<label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MÉTRIQUES -->
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">MÉTRIQUES ACTIVES</div>
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-microchip"></i></div><span class="metric-name">cpu</span><label class="toggle tog-ok"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-memory"></i></div><span class="metric-name">memory</span><label class="toggle tog-ok"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-hard-drive"></i></div><span class="metric-name">disk</span><label class="toggle tog-ok"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-network-wired"></i></div><span class="metric-name">network</span><label class="toggle tog-ok"><input type="checkbox"><span class="toggle-slider"></span></label></div>
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-clock"></i></div><span class="metric-name">uptime</span><label class="toggle tog-ok"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-thermometer-half"></i></div><span class="metric-name">temperature</span><label class="toggle tog-ok"><input type="checkbox"><span class="toggle-slider"></span></label></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COMMANDES -->
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">COMMANDES DISTANTES <span style="color:var(--ink-4);font-size:8px;margin-left:6px">— BIENTÔT DISPONIBLE</span></div>
|
||||
<div class="cmds-grid">
|
||||
<div class="cmd-btn"><i class="fa-solid fa-rotate-right"></i><span>reboot</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-power-off"></i><span>shutdown</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-display"></i><span>screen off</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-arrow-up-from-bracket"></i><span>update</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-arrow-up-right-dots"></i><span>upgrade</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-terminal"></i><span>shell cmd</span><span class="soon-tag">bientôt</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="cfg-foot">
|
||||
<div class="cfg-status">
|
||||
<div class="dot"></div>
|
||||
<span>Config synchronisée avec l'agent</span>
|
||||
</div>
|
||||
<button class="btn" onclick="document.getElementById('overlay-agentcfg').style.display='none'">Annuler</button>
|
||||
<button class="btn primary"><i class="fa-solid fa-paper-plane"></i> Envoyer à l'agent</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* tooltip */
|
||||
const tip=document.getElementById('tooltip');let tt;
|
||||
document.addEventListener('mouseover',e=>{const el=e.target.closest('[data-tip]');if(!el)return;clearTimeout(tt);tt=setTimeout(()=>{tip.textContent=el.dataset.tip;tip.classList.add('show');mv(e);},120);});
|
||||
document.addEventListener('mousemove',e=>{if(tip.classList.contains('show'))mv(e);});
|
||||
document.addEventListener('mouseout',e=>{const el=e.target.closest('[data-tip]');if(!el)return;clearTimeout(tt);tip.classList.remove('show');});
|
||||
function mv(e){const w=tip.offsetWidth,h=tip.offsetHeight;let x=e.clientX-w/2,y=e.clientY-h-10;x=Math.max(6,Math.min(x,window.innerWidth-w-6));if(y<6)y=e.clientY+18;tip.style.left=x+'px';tip.style.top=y+'px';}
|
||||
|
||||
/* thème */
|
||||
function toggleTheme(){const h=document.documentElement;h.dataset.theme=h.dataset.theme==='dark'?'light':'dark';document.getElementById('theme-icon').className=h.dataset.theme==='dark'?'fa-solid fa-moon':'fa-solid fa-sun';drawCharts();}
|
||||
|
||||
/* proto toggle */
|
||||
function toggleProto(p){
|
||||
const row=document.getElementById('row-'+p);
|
||||
const chk=document.getElementById('chk-'+p);
|
||||
const active=row.classList.contains('active')||row.classList.contains('mqtt-active');
|
||||
if(p==='udp'){row.classList.toggle('active',!active);chk.innerHTML=active?'':'';}
|
||||
if(p==='mqtt'){
|
||||
row.classList.toggle('mqtt-active',!active);
|
||||
chk.innerHTML=active?'':'<i class="fa-solid fa-check"></i>';
|
||||
if(!active){chk.style.cssText='background:var(--purple);border-color:var(--purple);color:var(--bg-0)';}
|
||||
else{chk.style.cssText='background:var(--bg-1);border-color:var(--border-3);color:var(--accent)';}
|
||||
document.getElementById('mqtt-opts').style.display=active?'none':'flex';
|
||||
}
|
||||
}
|
||||
|
||||
/* popups */
|
||||
function showDetail(){document.getElementById('overlay-detail').style.display='flex';drawCharts();}
|
||||
function showAgentCfg(){document.getElementById('overlay-agentcfg').style.display='flex';}
|
||||
|
||||
/* courbes */
|
||||
function makeCurve(pts,stroke,fill,w,h){
|
||||
const xs=pts.map((_,i)=>(i/(pts.length-1))*w);
|
||||
const ys=pts.map(v=>h-(v/100)*(h-6)-3);
|
||||
const wy=h-(70/100)*(h-6)-3;
|
||||
let d=`M${xs[0]} ${ys[0]}`;
|
||||
for(let i=1;i<pts.length;i++){const cx=(xs[i-1]+xs[i])/2;d+=` C${cx} ${ys[i-1]},${cx} ${ys[i]},${xs[i]} ${ys[i]}`;}
|
||||
const uid=Math.random().toString(36).slice(2);
|
||||
return `<defs><linearGradient id="g${uid}" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="${fill}" stop-opacity=".4"/><stop offset="100%" stop-color="${fill}" stop-opacity=".02"/></linearGradient></defs>
|
||||
<line x1="0" y1="${wy}" x2="${w}" y2="${wy}" stroke="var(--warn)" stroke-width=".8" stroke-dasharray="3,3" opacity=".5"/>
|
||||
<path d="${d} L${xs.at(-1)} ${h} L${xs[0]} ${h}Z" fill="url(#g${uid})"/>
|
||||
<path d="${d}" fill="none" stroke="${stroke}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="${xs.at(-1)}" cy="${ys.at(-1)}" r="2.5" fill="${stroke}"/>`;
|
||||
}
|
||||
function drawCharts(){
|
||||
const cpu=[38,41,35,40,44,42,39,45,50,48,43,42,44,41,40,43,47,45,42,44,41,43,42,40,43,41,44,43,41,42];
|
||||
const ram=[44,44,45,45,46,46,47,47,46,46,45,46,46,47,47,46,46,45,46,46,46,46,46,46,46,46,46,46,46,46];
|
||||
const cs=getComputedStyle(document.documentElement);
|
||||
const ac=cs.getPropertyValue('--accent').trim(),bl=cs.getPropertyValue('--blue').trim();
|
||||
document.getElementById('cpu-chart').innerHTML=makeCurve(cpu,ac,ac,200,52);
|
||||
document.getElementById('ram-chart').innerHTML=makeCurve(ram,bl,bl,200,52);
|
||||
}
|
||||
window.addEventListener('load',()=>{drawCharts();});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,634 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nanometrics — v7</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script src="/helper.js"></script>
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root[data-theme="dark"]{
|
||||
--accent:#fe8019;--accent-soft:#d65d0e;--accent-glow:rgba(254,128,25,.28);
|
||||
--bg-0:#1d1813;--bg-1:#2a231d;--bg-2:#32291f;--bg-3:#3c332a;--bg-4:#4a3f33;--bg-5:#57493c;
|
||||
--ink-1:#f2e5c7;--ink-2:#d5c4a1;--ink-3:#a89984;--ink-4:#7c6f64;
|
||||
--ok:#4dbb26;--warn:#fabd2f;--err:#fb4934;--blue:#3db0d1;--purple:#c882c8;
|
||||
--border-1:rgba(255,255,255,.06);--border-2:rgba(255,255,255,.12);--border-3:rgba(255,255,255,.26);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,.08) inset,0 -1px 0 rgba(0,0,0,.3) inset,0 6px 20px rgba(0,0,0,.5);
|
||||
--tile-press:inset 0 2px 8px rgba(0,0,0,.5),inset 0 1px 3px rgba(0,0,0,.4);
|
||||
--hover-glow:0 0 0 1px var(--accent-soft),0 0 24px var(--accent-glow),0 6px 20px rgba(0,0,0,.5);
|
||||
--font-ui:'Inter',system-ui,sans-serif;--font-mono:'JetBrains Mono',monospace;--font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
:root[data-theme="light"]{
|
||||
--accent:#af3a03;--accent-soft:#d65d0e;--accent-glow:rgba(175,58,3,.18);
|
||||
--bg-0:#d5c4a1;--bg-1:#ebdbb2;--bg-2:#d5c4a1;--bg-3:#bdae93;--bg-4:#a89984;--bg-5:#928374;
|
||||
--ink-1:#3c3836;--ink-2:#504945;--ink-3:#665c54;--ink-4:#7c6f64;
|
||||
--ok:#3c911c;--warn:#b57614;--err:#9d0006;--blue:#2d82a3;--purple:#8c468c;
|
||||
--border-1:rgba(0,0,0,.08);--border-2:rgba(0,0,0,.15);--border-3:rgba(0,0,0,.3);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,.55) inset,0 -1px 0 rgba(0,0,0,.08) inset,0 4px 14px rgba(0,0,0,.13);
|
||||
--tile-press:inset 0 2px 6px rgba(0,0,0,.2);
|
||||
--hover-glow:0 0 0 1px var(--accent-soft),0 0 18px var(--accent-glow),0 4px 14px rgba(0,0,0,.13);
|
||||
--font-ui:'Inter',system-ui,sans-serif;--font-mono:'JetBrains Mono',monospace;--font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
body{background:var(--bg-1);color:var(--ink-1);font-family:var(--font-ui);font-size:13px;height:100vh;display:flex;flex-direction:column;overflow:hidden;transition:background .2s,color .2s}
|
||||
|
||||
/* TOOLTIP */
|
||||
#tooltip{position:fixed;z-index:9999;pointer-events:none;background:var(--bg-0);color:var(--ink-1);border:1px solid var(--border-3);border-radius:5px;padding:4px 9px;font-size:11px;font-family:var(--font-ui);white-space:nowrap;opacity:0;transition:opacity .12s;box-shadow:0 4px 12px rgba(0,0,0,.4)}
|
||||
#tooltip.show{opacity:1}
|
||||
|
||||
/* HEADER */
|
||||
.header{background:var(--bg-2);border-bottom:1px solid var(--border-2);padding:0 20px;height:48px;display:flex;align-items:center;gap:12px;flex-shrink:0}
|
||||
.logo{display:flex;align-items:center;gap:8px}
|
||||
.logo-led{width:9px;height:9px;border-radius:50%;background:var(--accent);box-shadow:0 0 8px var(--accent-glow);animation:blink 2s infinite}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.logo-name{font-weight:700;font-size:14px;letter-spacing:.05em;font-family:var(--font-terminal)}
|
||||
.logo-ver{font-size:10px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.h-sep{width:1px;height:24px;background:var(--border-2)}
|
||||
.h-spacer{flex:1}
|
||||
.h-stats{display:flex;gap:14px}
|
||||
.h-stat{display:flex;align-items:center;gap:5px}
|
||||
.h-stat .lbl{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.h-stat .val{font-family:var(--font-mono);font-weight:700;font-size:13px}
|
||||
.c-ok{color:var(--ok)}.c-warn{color:var(--warn)}.c-err{color:var(--err)}.c-n{color:var(--ink-2)}
|
||||
.hbtn{width:34px;height:34px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-3);color:var(--ink-2);font-size:14px;display:flex;align-items:center;justify-content:center;cursor:pointer;user-select:none;transition:background .12s,color .12s,transform .08s,box-shadow .08s}
|
||||
.hbtn:hover{background:var(--bg-4);color:var(--accent)}
|
||||
.hbtn:active{transform:translateY(1px) scale(.96);box-shadow:var(--tile-press)}
|
||||
|
||||
/* GRID */
|
||||
.main{flex:1;padding:14px 16px;overflow-y:auto}
|
||||
.agents-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px}
|
||||
.tile{background:var(--bg-3);border-radius:10px;padding:12px 14px;border:1px solid var(--border-1);box-shadow:var(--tile-3d);cursor:pointer;user-select:none;display:flex;flex-direction:column;gap:9px;transition:box-shadow .15s,transform .08s,border-color .15s}
|
||||
.tile:hover{box-shadow:var(--hover-glow);border-color:var(--accent-soft)}
|
||||
.tile:active{transform:translateY(2px) scale(.99);box-shadow:var(--tile-press)}
|
||||
.tile.t-warn{border-color:rgba(250,189,47,.3)}.tile.t-warn:hover{border-color:var(--warn);box-shadow:0 0 0 1px var(--warn),0 0 22px rgba(250,189,47,.22),0 6px 20px rgba(0,0,0,.5)}
|
||||
.tile.t-err{border-color:rgba(251,73,52,.35)}.tile.t-err:hover{border-color:var(--err);box-shadow:0 0 0 1px var(--err),0 0 22px rgba(251,73,52,.25),0 6px 20px rgba(0,0,0,.5)}
|
||||
.tile.t-off{opacity:.5;cursor:default}.tile.t-off:hover,.tile.t-off:active{box-shadow:var(--tile-3d);border-color:var(--border-1);transform:none}
|
||||
.tile-head{display:flex;align-items:center;gap:8px}
|
||||
.t-icon{width:28px;height:28px;border-radius:7px;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--accent);font-size:13px;flex-shrink:0;overflow:hidden}
|
||||
.t-names{flex:1;min-width:0}
|
||||
.t-host{font-weight:600;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.t-ip{font-family:var(--font-mono);font-size:10px;color:var(--ink-4)}
|
||||
.t-led{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
||||
.s-ok{background:var(--ok);box-shadow:0 0 6px var(--ok)}.s-warn{background:var(--warn);box-shadow:0 0 6px var(--warn);animation:blink 1.5s infinite}.s-err{background:var(--err);box-shadow:0 0 8px var(--err);animation:blink 1s infinite}.s-off{background:var(--ink-4)}
|
||||
.tile-gauges{display:flex;flex-direction:column;gap:5px}
|
||||
.g-row{display:flex;align-items:center;gap:7px}
|
||||
.g-ico{width:18px;height:18px;display:flex;align-items:center;justify-content:center;font-size:11px;color:var(--ink-3);flex-shrink:0;cursor:help}
|
||||
.g-bar{flex:1;height:5px;border-radius:3px;background:var(--bg-1);overflow:hidden}
|
||||
.g-fill{height:100%;border-radius:3px;background:var(--ok)}
|
||||
.g-fill.w{background:var(--warn)}.g-fill.e{background:var(--err)}
|
||||
.g-val{font-family:var(--font-mono);font-size:11px;color:var(--ink-2);width:34px;text-align:right}
|
||||
.tile-foot{font-family:var(--font-terminal);font-size:10px;color:var(--ink-4);display:flex;align-items:center;gap:5px}
|
||||
.tile-foot i{font-size:9px}
|
||||
|
||||
/* FOOTER */
|
||||
.footer{background:var(--bg-0);border-top:1px solid var(--border-2);height:26px;display:flex;align-items:center;font-family:var(--font-terminal);font-size:11px;color:var(--ink-4);flex-shrink:0}
|
||||
.f-mode{background:var(--accent);color:var(--bg-0);padding:0 12px;height:100%;display:flex;align-items:center;font-weight:700;letter-spacing:.04em}
|
||||
.f-cell{padding:0 12px;border-right:1px solid var(--border-1);display:flex;align-items:center;gap:5px;height:100%}
|
||||
.f-val{font-family:var(--font-mono);color:var(--ink-2)}.f-val.w{color:var(--warn)}
|
||||
.f-minibar{width:36px;height:4px;border-radius:2px;background:var(--bg-3);overflow:hidden}
|
||||
.f-minifill{height:100%;border-radius:2px;background:var(--ok)}.f-minifill.w{background:var(--warn)}
|
||||
.f-spacer{flex:1}.f-right{padding:0 12px;display:flex;align-items:center;gap:6px;color:var(--ink-3)}
|
||||
.f-time{font-family:var(--font-mono);color:var(--ink-2)}
|
||||
|
||||
/* OVERLAY */
|
||||
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.65);z-index:100;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px)}
|
||||
.popup{background:var(--bg-2);border:1px solid var(--border-3);border-radius:12px;box-shadow:0 24px 64px rgba(0,0,0,.7);display:flex;flex-direction:column;overflow:hidden}
|
||||
|
||||
/* POPUP DETAIL — redimensionnable */
|
||||
#popup-detail{
|
||||
width:540px;max-width:96vw;max-height:92vh;
|
||||
/* redimensionnable par l'utilisateur */
|
||||
resize:both;overflow:hidden;
|
||||
min-width:380px;min-height:300px;
|
||||
}
|
||||
/* on met overflow:auto sur le body pour que resize fonctionne */
|
||||
#popup-detail .pop-body{overflow-y:auto;flex:1}
|
||||
|
||||
.pop-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:12px;flex-shrink:0}
|
||||
.agent-icon-wrap{position:relative;width:44px;height:44px;border-radius:10px;flex-shrink:0;cursor:pointer;overflow:hidden;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--accent);font-size:18px;border:2px solid var(--border-2);transition:border-color .15s}
|
||||
.agent-icon-wrap:hover{border-color:var(--accent)}
|
||||
.agent-icon-overlay{position:absolute;inset:0;background:rgba(0,0,0,.6);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;opacity:0;transition:opacity .15s;font-size:10px;color:#fff}
|
||||
.agent-icon-overlay i{font-size:14px}
|
||||
.agent-icon-wrap:hover .agent-icon-overlay{opacity:1}
|
||||
#icon-upload{display:none}
|
||||
.pop-head-info{flex:1}
|
||||
.pop-host{font-weight:700;font-size:15px}
|
||||
.pop-ip{font-family:var(--font-mono);font-size:11px;color:var(--ink-4)}
|
||||
.upload-hint{font-size:10px;color:var(--ink-4);font-family:var(--font-terminal);margin-top:2px}
|
||||
.pop-led{width:10px;height:10px;border-radius:50%;background:var(--ok);box-shadow:0 0 8px var(--ok);flex-shrink:0}
|
||||
.pop-close{width:28px;height:28px;border-radius:6px;background:var(--bg-5);color:var(--ink-3);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;border:1px solid var(--border-1);transition:background .12s,color .12s,transform .08s;user-select:none}
|
||||
.pop-close:hover{background:var(--err);color:#fff}
|
||||
.pop-close:active{transform:translateY(1px) scale(.93);box-shadow:var(--tile-press)}
|
||||
|
||||
.pop-body{padding:16px 18px;display:flex;flex-direction:column;gap:14px}
|
||||
.sec-title{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.08em;margin-bottom:8px}
|
||||
.kpi-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:7px}
|
||||
.kpi{background:var(--bg-3);border-radius:8px;padding:10px 12px;border:1px solid var(--border-1);box-shadow:var(--tile-3d)}
|
||||
.kpi-lbl{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.kpi-val{font-family:var(--font-mono);font-size:20px;font-weight:700;line-height:1.1;margin-top:2px}
|
||||
.kpi-val .u{font-size:10px;color:var(--ink-3);font-weight:400}
|
||||
.kpi-sub{font-size:10px;color:var(--ink-4);font-family:var(--font-mono);margin-top:2px}
|
||||
.charts-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||
.chart-card{background:var(--bg-3);border-radius:8px;padding:10px 12px;border:1px solid var(--border-1);box-shadow:var(--tile-3d)}
|
||||
.chart-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
|
||||
.chart-label{display:flex;align-items:center;gap:6px;font-size:10px;font-family:var(--font-terminal);letter-spacing:.06em;color:var(--ink-3)}
|
||||
.chart-cur{font-family:var(--font-mono);font-size:16px;font-weight:700}
|
||||
.chart-svg{width:100%;height:52px;display:block}
|
||||
.chart-axis{display:flex;justify-content:space-between;margin-top:2px;font-family:var(--font-terminal);font-size:9px;color:var(--ink-4)}
|
||||
|
||||
/* STOCKAGE + SMART */
|
||||
.storage-block{display:flex;flex-direction:column;gap:8px}
|
||||
.det-gauges{display:flex;flex-direction:column;gap:7px}
|
||||
.dg-row{display:flex;align-items:center;gap:10px}
|
||||
.dg-ico{width:22px;text-align:center;font-size:13px;cursor:help}
|
||||
.dg-bar{flex:1;height:7px;border-radius:4px;background:var(--bg-1);overflow:hidden}
|
||||
.dg-fill{height:100%;border-radius:4px;background:var(--ok)}
|
||||
.dg-fill.b{background:var(--blue)}
|
||||
.dg-val{font-family:var(--font-mono);font-size:12px;color:var(--ink-2);width:90px;text-align:right}
|
||||
|
||||
/* bouton SMART */
|
||||
.smart-btn{
|
||||
display:inline-flex;align-items:center;gap:8px;padding:7px 12px;border-radius:8px;
|
||||
border:1px solid var(--border-2);background:var(--bg-3);cursor:pointer;user-select:none;
|
||||
transition:background .12s,border-color .12s,transform .08s,box-shadow .08s;
|
||||
font-family:var(--font-terminal);font-size:11px;
|
||||
}
|
||||
.smart-btn:hover{background:var(--bg-4);border-color:var(--border-3)}
|
||||
.smart-btn:active{transform:translateY(1px);box-shadow:var(--tile-press)}
|
||||
.smart-btn.ok{border-color:rgba(77,187,38,.3);color:var(--ok)}
|
||||
.smart-btn.ok .smart-dot{background:var(--ok);box-shadow:0 0 5px var(--ok)}
|
||||
.smart-btn.warn{border-color:rgba(250,189,47,.3);color:var(--warn)}
|
||||
.smart-btn.warn .smart-dot{background:var(--warn);box-shadow:0 0 5px var(--warn)}
|
||||
.smart-btn.err{border-color:rgba(251,73,52,.3);color:var(--err)}
|
||||
.smart-btn.err .smart-dot{background:var(--err);box-shadow:0 0 5px var(--err)}
|
||||
.smart-dot{width:7px;height:7px;border-radius:50%}
|
||||
.smart-label{font-weight:600;letter-spacing:.04em}
|
||||
.smart-temp{font-family:var(--font-mono);font-size:10px;color:var(--ink-3);margin-left:4px}
|
||||
.smart-chevron{font-size:10px;color:var(--ink-4);margin-left:auto}
|
||||
|
||||
.meta-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px}
|
||||
.meta{background:var(--bg-3);border-radius:6px;padding:8px 10px;border:1px solid var(--border-1)}
|
||||
.meta-lbl{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.meta-val{font-family:var(--font-mono);font-size:12px;color:var(--ink-2);margin-top:2px}
|
||||
.proto-badges{display:flex;gap:5px;margin-top:4px;flex-wrap:wrap}
|
||||
.proto-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:999px;font-size:10px;font-family:var(--font-terminal);font-weight:600}
|
||||
.proto-badge.udp{background:rgba(61,176,209,.15);color:var(--blue);border:1px solid rgba(61,176,209,.3)}
|
||||
.proto-badge.mqtt{background:rgba(200,130,200,.15);color:var(--purple);border:1px solid rgba(200,130,200,.3)}
|
||||
|
||||
.pop-foot{padding:10px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;align-items:center;gap:8px;flex-shrink:0}
|
||||
.pop-uptime{font-family:var(--font-terminal);font-size:11px;color:var(--ink-4);flex:1}
|
||||
.resize-hint{font-family:var(--font-terminal);font-size:9px;color:var(--ink-4);display:flex;align-items:center;gap:4px}
|
||||
|
||||
.btn{padding:6px 14px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-4);color:var(--ink-2);font-size:12px;font-family:var(--font-ui);cursor:pointer;display:flex;align-items:center;gap:6px;user-select:none;transition:background .1s,transform .08s,box-shadow .08s}
|
||||
.btn:hover{background:var(--bg-5)}.btn:active{transform:translateY(1px) scale(.97);box-shadow:var(--tile-press)}
|
||||
.btn.primary{background:var(--accent);color:var(--bg-0);border-color:var(--accent-soft);font-weight:600}.btn.primary:hover{background:var(--accent-soft)}
|
||||
.btn-agent-cfg{width:34px;height:34px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-4);color:var(--ink-3);font-size:15px;display:flex;align-items:center;justify-content:center;cursor:pointer;user-select:none;transition:background .12s,color .12s,transform .08s,box-shadow .08s}
|
||||
.btn-agent-cfg:hover{background:var(--bg-5);color:var(--accent)}.btn-agent-cfg:active{transform:translateY(1px) scale(.93);box-shadow:var(--tile-press)}
|
||||
|
||||
/* ══ POPUP SMART ══ */
|
||||
#overlay-smart{z-index:300}
|
||||
#popup-smart{width:500px;max-width:96vw;max-height:88vh}
|
||||
.smart-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:10px}
|
||||
.smart-head-icon{width:32px;height:32px;border-radius:8px;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--ok);font-size:15px}
|
||||
.smart-head-info{flex:1}
|
||||
.smart-head-title{font-weight:700;font-size:14px}
|
||||
.smart-head-sub{font-size:11px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.smart-body{padding:18px;display:flex;flex-direction:column;gap:16px;overflow-y:auto}
|
||||
|
||||
/* verdict global */
|
||||
.smart-verdict{display:flex;align-items:center;gap:14px;background:rgba(77,187,38,.1);border:1px solid rgba(77,187,38,.3);border-radius:10px;padding:14px 18px}
|
||||
.verdict-icon{font-size:28px;color:var(--ok)}
|
||||
.verdict-text .v-title{font-size:16px;font-weight:700;color:var(--ok)}
|
||||
.verdict-text .v-sub{font-size:12px;color:var(--ink-3);margin-top:3px}
|
||||
|
||||
/* indicateurs simplifiés */
|
||||
.smart-indicators{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||||
.smart-ind{background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1);box-shadow:var(--tile-3d)}
|
||||
.si-header{display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
||||
.si-icon{font-size:14px;width:22px;text-align:center}
|
||||
.si-title{font-weight:600;font-size:12px;flex:1}
|
||||
.si-status{font-size:10px;font-family:var(--font-terminal);font-weight:700;padding:1px 7px;border-radius:999px}
|
||||
.si-status.ok{background:rgba(77,187,38,.15);color:var(--ok)}
|
||||
.si-status.warn{background:rgba(250,189,47,.15);color:var(--warn)}
|
||||
.si-val{font-family:var(--font-mono);font-size:18px;font-weight:700;color:var(--ink-1)}
|
||||
.si-val .u{font-size:11px;color:var(--ink-3);font-weight:400}
|
||||
.si-desc{font-size:11px;color:var(--ink-3);margin-top:4px;line-height:1.4}
|
||||
|
||||
/* attributs détail */
|
||||
.smart-attrs{display:flex;flex-direction:column;gap:5px}
|
||||
.attr-row{display:flex;align-items:center;gap:10px;padding:6px 10px;border-radius:6px;background:var(--bg-3);border:1px solid var(--border-1)}
|
||||
.attr-id{font-family:var(--font-mono);font-size:10px;color:var(--ink-4);width:28px;flex-shrink:0}
|
||||
.attr-name{flex:1;font-size:11px;color:var(--ink-2)}
|
||||
.attr-val{font-family:var(--font-mono);font-size:11px;font-weight:600;width:40px;text-align:right}
|
||||
.attr-explain{font-size:10px;color:var(--ink-4);width:130px;text-align:right;font-family:var(--font-terminal)}
|
||||
.attr-ok{color:var(--ok)}.attr-warn{color:var(--warn)}.attr-err{color:var(--err)}
|
||||
|
||||
.smart-footer{padding:12px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;align-items:center;gap:8px}
|
||||
.smart-footer-note{flex:1;font-size:10px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
|
||||
/* CONFIG AGENT */
|
||||
#overlay-agentcfg{z-index:200}
|
||||
#popup-agentcfg{width:480px;max-width:96vw;max-height:90vh}
|
||||
.cfg-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:10px}
|
||||
.cfg-head-icon{width:32px;height:32px;border-radius:8px;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--accent);font-size:15px}
|
||||
.cfg-head-info{flex:1}.cfg-head-title{font-weight:700;font-size:14px}.cfg-head-sub{font-size:11px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.cfg-body{padding:18px;display:flex;flex-direction:column;gap:16px;overflow-y:auto;max-height:58vh}
|
||||
.cfg-section{display:flex;flex-direction:column;gap:8px}
|
||||
.cfg-sec-title{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.08em;padding-bottom:6px;border-bottom:1px solid var(--border-1)}
|
||||
.check-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;background:var(--bg-3);border:1px solid var(--border-1);cursor:pointer;transition:background .12s,border-color .12s}
|
||||
.check-row.active{border-color:var(--accent-soft);background:rgba(254,128,25,.08)}
|
||||
.check-row.mqtt-active{border-color:rgba(200,130,200,.4);background:rgba(200,130,200,.07)}
|
||||
.chk-box{width:18px;height:18px;border-radius:4px;border:2px solid var(--border-3);background:var(--bg-1);display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0}
|
||||
.check-row.active .chk-box{background:var(--accent);border-color:var(--accent);color:var(--bg-0)}
|
||||
.check-row.mqtt-active .chk-box{background:var(--purple);border-color:var(--purple);color:var(--bg-0)}
|
||||
.chk-label{flex:1}.chk-name{font-weight:600;font-size:13px}.chk-desc{font-size:11px;color:var(--ink-4);font-family:var(--font-terminal);margin-top:1px}
|
||||
.chk-badge{font-size:10px;font-family:var(--font-terminal);font-weight:700;padding:1px 7px;border-radius:999px}
|
||||
.chk-badge.udp{background:rgba(61,176,209,.15);color:var(--blue)}.chk-badge.mqtt{background:rgba(200,130,200,.15);color:var(--purple)}
|
||||
.mqtt-opts{background:var(--bg-3);border-radius:8px;border:1px solid rgba(200,130,200,.2);padding:12px 14px;display:flex;flex-direction:column;gap:10px}
|
||||
.mqtt-field{display:flex;align-items:center;gap:10px}
|
||||
.mqtt-field label{font-size:11px;color:var(--ink-3);font-family:var(--font-terminal);width:90px;flex-shrink:0}
|
||||
.mqtt-input{flex:1;background:var(--bg-1);border:1px solid var(--border-2);border-radius:6px;color:var(--ink-1);padding:6px 10px;font-size:12px;font-family:var(--font-mono)}
|
||||
.mqtt-input:focus{outline:none;border-color:var(--purple)}
|
||||
.mqtt-check-row{display:flex;align-items:center;justify-content:space-between;padding:4px 0}
|
||||
.mqtt-check-row label{font-size:12px;color:var(--ink-2);display:flex;align-items:center;gap:7px;cursor:pointer}
|
||||
.mqtt-check-row label i{color:var(--purple);font-size:11px}
|
||||
.toggle{position:relative;width:34px;height:18px;flex-shrink:0}
|
||||
.toggle input{opacity:0;width:0;height:0}
|
||||
.toggle-slider{position:absolute;inset:0;border-radius:9px;background:var(--bg-4);border:1px solid var(--border-2);cursor:pointer;transition:background .2s}
|
||||
.toggle-slider::before{content:'';position:absolute;width:12px;height:12px;border-radius:50%;background:var(--ink-4);top:2px;left:2px;transition:transform .2s,background .2s}
|
||||
.toggle input:checked+.toggle-slider{background:rgba(200,130,200,.3);border-color:var(--purple)}
|
||||
.toggle input:checked+.toggle-slider::before{transform:translateX(16px);background:var(--purple)}
|
||||
.metrics-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px}
|
||||
.metric-row{display:flex;align-items:center;gap:8px;padding:7px 10px;border-radius:7px;background:var(--bg-3);border:1px solid var(--border-1)}
|
||||
.metric-ico{width:22px;text-align:center;font-size:12px;color:var(--ink-3)}
|
||||
.metric-name{flex:1;font-size:12px;color:var(--ink-2);font-family:var(--font-terminal)}
|
||||
.tog-ok input:checked+.toggle-slider{background:rgba(77,187,38,.3);border-color:var(--ok)}
|
||||
.tog-ok input:checked+.toggle-slider::before{background:var(--ok)}
|
||||
.cmds-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
|
||||
.cmd-btn{display:flex;flex-direction:column;align-items:center;gap:4px;padding:10px 8px;border-radius:8px;background:var(--bg-3);border:1px solid var(--border-1);cursor:not-allowed;opacity:.4}
|
||||
.cmd-btn i{font-size:16px;color:var(--ink-3)}.cmd-btn span{font-size:10px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.soon-tag{font-size:8px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.cfg-foot{padding:12px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;align-items:center;gap:8px}
|
||||
.cfg-status{flex:1;display:flex;align-items:center;gap:6px;font-family:var(--font-terminal);font-size:11px;color:var(--ink-4)}
|
||||
.cfg-status .dot{width:6px;height:6px;border-radius:50%;background:var(--ok)}
|
||||
|
||||
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:var(--bg-1)}::-webkit-scrollbar-thumb{background:var(--bg-4);border-radius:3px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="tooltip"></div>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="logo"><div class="logo-led"></div><span class="logo-name">NANOMETRICS</span><span class="logo-ver">v1.0</span></div>
|
||||
<div class="h-sep"></div>
|
||||
<div class="h-stats">
|
||||
<div class="h-stat"><span class="lbl">AGENTS</span><span class="val c-n">8</span></div>
|
||||
<div class="h-stat"><span class="lbl">OK</span><span class="val c-ok">5</span></div>
|
||||
<div class="h-stat"><span class="lbl">WARN</span><span class="val c-warn">2</span></div>
|
||||
<div class="h-stat"><span class="lbl">ERR</span><span class="val c-err">1</span></div>
|
||||
</div>
|
||||
<div class="h-spacer"></div>
|
||||
<div class="hbtn" onclick="toggleTheme()" data-tip="Thème clair / sombre"><i class="fa-solid fa-moon" id="theme-icon"></i></div>
|
||||
<div class="hbtn" data-tip="Configuration interface"><i class="fa-solid fa-sliders"></i></div>
|
||||
</div>
|
||||
|
||||
<!-- GRID -->
|
||||
<div class="main">
|
||||
<div class="agents-grid">
|
||||
<div class="tile" onclick="showDetail()">
|
||||
<div class="tile-head"><div class="t-icon"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-prod-01</div><div class="t-ip">10.0.0.11</div></div><div class="t-led s-ok"></div></div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:42%"></div></div><span class="g-val">42%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:58%"></div></div><span class="g-val">58%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:31%"></div></div><span class="g-val">31%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>14j 6h</div>
|
||||
</div>
|
||||
<div class="tile t-warn"><div class="tile-head"><div class="t-icon"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-backup-02</div><div class="t-ip">10.0.0.12</div></div><div class="t-led s-warn"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU élevé" style="color:var(--warn)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill w" style="width:78%"></div></div><span class="g-val" style="color:var(--warn)">78%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM élevée" style="color:var(--warn)"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill w" style="width:72%"></div></div><span class="g-val" style="color:var(--warn)">72%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:20%"></div></div><span class="g-val">20%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>3j 14h</div></div>
|
||||
<div class="tile t-err"><div class="tile-head"><div class="t-icon" style="color:var(--err)"><i class="fa-solid fa-microchip"></i></div><div class="t-names"><div class="t-host">rpi-sensor-03</div><div class="t-ip">10.0.0.23</div></div><div class="t-led s-err"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:12%"></div></div><span class="g-val">12%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:38%"></div></div><span class="g-val">38%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque critique !" style="color:var(--err)"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill e" style="width:94%"></div></div><span class="g-val" style="color:var(--err)">94%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>62j 1h</div></div>
|
||||
<div class="tile"><div class="tile-head"><div class="t-icon"><i class="fa-solid fa-hard-drive"></i></div><div class="t-names"><div class="t-host">nas-storage-04</div><div class="t-ip">10.0.0.30</div></div><div class="t-led s-ok"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:8%"></div></div><span class="g-val">8%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:45%"></div></div><span class="g-val">45%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:66%"></div></div><span class="g-val">66%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>128j 3h</div></div>
|
||||
<div class="tile t-off"><div class="tile-head"><div class="t-icon" style="color:var(--ink-4)"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-dev-07</div><div class="t-ip">10.0.0.60</div></div><div class="t-led s-off"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div><div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div><div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div></div><div class="tile-foot" style="color:var(--err)"><i class="fa-solid fa-circle-xmark"></i>Hors ligne · vu il y a 2h</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
<div class="f-mode">LIVE</div>
|
||||
<div class="f-cell"><i class="fa-solid fa-server" style="font-size:10px"></i><span>SERVEUR</span></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-microchip" style="font-size:10px"></i><span class="f-val">18%</span><div class="f-minibar"><div class="f-minifill" style="width:18%"></div></div></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-memory" style="font-size:10px"></i><span class="f-val w">71%</span><div class="f-minibar"><div class="f-minifill w" style="width:71%"></div></div></div>
|
||||
<div class="f-spacer"></div>
|
||||
<div class="f-right"><i class="fa-solid fa-rotate"></i><span>Actualisation : <span class="f-time">22:14:07</span></span></div>
|
||||
</div>
|
||||
|
||||
<!-- ══ POPUP DÉTAIL — redimensionnable ══ -->
|
||||
<div class="overlay" id="overlay-detail" style="display:flex" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-detail" onclick="event.stopPropagation()">
|
||||
<div class="pop-head">
|
||||
<div class="agent-icon-wrap" onclick="document.getElementById('icon-upload').click()" data-tip="Changer l'icône">
|
||||
<i class="fa-solid fa-server"></i>
|
||||
<div class="agent-icon-overlay"><i class="fa-solid fa-camera"></i><span>Changer</span></div>
|
||||
</div>
|
||||
<input type="file" id="icon-upload" accept=".svg,.jpg,.jpeg,.png,.webp">
|
||||
<div class="pop-head-info">
|
||||
<div class="pop-host">srv-prod-01</div>
|
||||
<div class="pop-ip">10.0.0.11</div>
|
||||
<div class="upload-hint">Cliquer sur l'icône pour personnaliser · SVG JPG PNG WEBP · max 128×128 px</div>
|
||||
</div>
|
||||
<div class="pop-led"></div>
|
||||
<div class="pop-close" onclick="document.getElementById('overlay-detail').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="pop-body">
|
||||
<div>
|
||||
<div class="sec-title">MÉTRIQUES ACTUELLES</div>
|
||||
<div class="kpi-grid">
|
||||
<div class="kpi"><div class="kpi-lbl">CPU</div><div class="kpi-val c-ok">42<span class="u">%</span></div><div class="kpi-sub">4 cœurs</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">MÉMOIRE</div><div class="kpi-val">3.7<span class="u">Go</span></div><div class="kpi-sub">/ 8 Go · 46%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">DISQUE</div><div class="kpi-val">62<span class="u">Go</span></div><div class="kpi-sub">/ 200 Go · 31%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">UPTIME</div><div class="kpi-val" style="font-size:15px;color:var(--ink-1)">14j 6h</div><div class="kpi-sub">depuis boot</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">HISTORIQUE — 30 MIN</div>
|
||||
<div class="charts-grid">
|
||||
<div class="chart-card">
|
||||
<div class="chart-header"><div class="chart-label" style="color:var(--accent)"><i class="fa-solid fa-microchip"></i>CPU</div><span class="chart-cur c-ok">42%</span></div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 52" preserveAspectRatio="none" id="cpu-chart"></svg>
|
||||
<div class="chart-axis"><span>−30min</span><span>−15min</span><span>now</span></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<div class="chart-header"><div class="chart-label" style="color:var(--blue)"><i class="fa-solid fa-memory"></i>RAM</div><span class="chart-cur" style="color:var(--blue)">46%</span></div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 52" preserveAspectRatio="none" id="ram-chart"></svg>
|
||||
<div class="chart-axis"><span>−30min</span><span>−15min</span><span>now</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">STOCKAGE</div>
|
||||
<div class="storage-block">
|
||||
<div class="det-gauges">
|
||||
<div class="dg-row"><div class="dg-ico" data-tip="Utilisé"><i class="fa-solid fa-hard-drive"></i></div><div class="dg-bar"><div class="dg-fill" style="width:31%"></div></div><span class="dg-val">62 / 200 Go</span></div>
|
||||
<div class="dg-row"><div class="dg-ico" data-tip="Libre" style="color:var(--blue)"><i class="fa-solid fa-floppy-disk"></i></div><div class="dg-bar"><div class="dg-fill b" style="width:69%"></div></div><span class="dg-val">138 Go libre</span></div>
|
||||
</div>
|
||||
<!-- Bouton SMART — affiché si smartctl disponible -->
|
||||
<div class="smart-btn ok" onclick="showSmart();event.stopPropagation()" data-tip="Voir l'état de santé complet du disque">
|
||||
<div class="smart-dot"></div>
|
||||
<span class="smart-label">SMART</span>
|
||||
<span style="font-size:10px;color:var(--ink-3)">·</span>
|
||||
<span style="font-size:11px;color:var(--ok);font-family:var(--font-terminal)">PASSED</span>
|
||||
<span class="smart-temp"><i class="fa-solid fa-temperature-half"></i> 34°C</span>
|
||||
<i class="fa-solid fa-chevron-right smart-chevron"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">INFORMATIONS</div>
|
||||
<div class="meta-grid">
|
||||
<div class="meta"><div class="meta-lbl">HOSTNAME</div><div class="meta-val">srv-prod-01</div></div>
|
||||
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">10.0.0.11</div></div>
|
||||
<div class="meta"><div class="meta-lbl">PROTOCOLES ACTIFS</div><div class="proto-badges"><span class="proto-badge udp"><i class="fa-solid fa-arrow-up"></i>UDP</span><span class="proto-badge mqtt">M MQTT</span></div></div>
|
||||
<div class="meta"><div class="meta-lbl">DERNIER CONTACT</div><div class="meta-val">22:14:07</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pop-foot">
|
||||
<span class="pop-uptime"><i class="fa-solid fa-clock" style="margin-right:4px"></i>En ligne depuis 14 jours 6 heures</span>
|
||||
<span class="resize-hint"><i class="fa-solid fa-up-right-and-down-left-from-center"></i>Redimensionnable</span>
|
||||
<div class="btn-agent-cfg" onclick="showAgentCfg()" data-tip="Configurer l'agent"><i class="fa-solid fa-gears"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ POPUP SMART ══ -->
|
||||
<div class="overlay" id="overlay-smart" style="display:none" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-smart" onclick="event.stopPropagation()">
|
||||
<div class="smart-head">
|
||||
<div class="smart-head-icon"><i class="fa-solid fa-shield-heart"></i></div>
|
||||
<div class="smart-head-info">
|
||||
<div class="smart-head-title">Santé du disque dur</div>
|
||||
<div class="smart-head-sub">srv-prod-01 · /dev/sda · Samsung SSD 870 EVO 250GB</div>
|
||||
</div>
|
||||
<div class="pop-close" onclick="document.getElementById('overlay-smart').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="smart-body">
|
||||
<!-- Verdict global en français, compréhensible par tous -->
|
||||
<div class="smart-verdict">
|
||||
<div class="verdict-icon"><i class="fa-solid fa-circle-check"></i></div>
|
||||
<div class="verdict-text">
|
||||
<div class="v-title">Disque en bonne santé</div>
|
||||
<div class="v-sub">Aucun problème détecté. Le disque fonctionne normalement et peut être utilisé en toute confiance.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Indicateurs simplifiés -->
|
||||
<div>
|
||||
<div class="sec-title">POINTS DE CONTRÔLE PRINCIPAUX</div>
|
||||
<div class="smart-indicators">
|
||||
<div class="smart-ind">
|
||||
<div class="si-header">
|
||||
<div class="si-icon" style="color:var(--warn)"><i class="fa-solid fa-temperature-half"></i></div>
|
||||
<span class="si-title">Température</span>
|
||||
<span class="si-status ok">Normale</span>
|
||||
</div>
|
||||
<div class="si-val">34<span class="u">°C</span></div>
|
||||
<div class="si-desc">La température idéale est entre 20°C et 50°C. Au-delà de 60°C le disque risque de s'abîmer prématurément.</div>
|
||||
</div>
|
||||
<div class="smart-ind">
|
||||
<div class="si-header">
|
||||
<div class="si-icon" style="color:var(--ok)"><i class="fa-solid fa-circle-check"></i></div>
|
||||
<span class="si-title">Secteurs défectueux</span>
|
||||
<span class="si-status ok">Aucun</span>
|
||||
</div>
|
||||
<div class="si-val">0<span class="u"> sect.</span></div>
|
||||
<div class="si-desc">Les secteurs défectueux sont des zones endommagées sur le disque. S'ils apparaissent en grand nombre, une panne est imminente.</div>
|
||||
</div>
|
||||
<div class="smart-ind">
|
||||
<div class="si-header">
|
||||
<div class="si-icon" style="color:var(--blue)"><i class="fa-solid fa-clock-rotate-left"></i></div>
|
||||
<span class="si-title">Heures de fonctionnement</span>
|
||||
<span class="si-status ok">Jeune</span>
|
||||
</div>
|
||||
<div class="si-val">4<span class="u">213 h</span></div>
|
||||
<div class="si-desc">Environ 175 jours de fonctionnement. Un disque dur de bureau dure en moyenne 3 à 5 ans (26 000 – 43 000 heures).</div>
|
||||
</div>
|
||||
<div class="smart-ind">
|
||||
<div class="si-header">
|
||||
<div class="si-icon" style="color:var(--ok)"><i class="fa-solid fa-battery-full"></i></div>
|
||||
<span class="si-title">Durée de vie SSD</span>
|
||||
<span class="si-status ok">Excellente</span>
|
||||
</div>
|
||||
<div class="si-val">98<span class="u">%</span></div>
|
||||
<div class="si-desc">Indique la durée de vie restante des cellules flash (spécifique aux SSD). 100% = neuf, 0% = fin de vie recommandée.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attributs détaillés -->
|
||||
<div>
|
||||
<div class="sec-title">ATTRIBUTS SMART DÉTAILLÉS</div>
|
||||
<div class="smart-attrs">
|
||||
<div class="attr-row">
|
||||
<span class="attr-id">001</span>
|
||||
<span class="attr-name">Taux d'erreurs de lecture brutes</span>
|
||||
<span class="attr-val attr-ok">0</span>
|
||||
<span class="attr-explain attr-ok">✓ Parfait</span>
|
||||
</div>
|
||||
<div class="attr-row">
|
||||
<span class="attr-id">005</span>
|
||||
<span class="attr-name">Secteurs réalloués (remplacés)</span>
|
||||
<span class="attr-val attr-ok">0</span>
|
||||
<span class="attr-explain attr-ok">✓ Aucun dommage</span>
|
||||
</div>
|
||||
<div class="attr-row">
|
||||
<span class="attr-id">009</span>
|
||||
<span class="attr-name">Heures de fonctionnement</span>
|
||||
<span class="attr-val attr-ok">4213</span>
|
||||
<span class="attr-explain">175 jours</span>
|
||||
</div>
|
||||
<div class="attr-row">
|
||||
<span class="attr-id">012</span>
|
||||
<span class="attr-name">Nombre de démarrages</span>
|
||||
<span class="attr-val attr-ok">312</span>
|
||||
<span class="attr-explain">Normale</span>
|
||||
</div>
|
||||
<div class="attr-row">
|
||||
<span class="attr-id">177</span>
|
||||
<span class="attr-name">Usure des cellules d'écriture (SSD)</span>
|
||||
<span class="attr-val attr-ok">2</span>
|
||||
<span class="attr-explain attr-ok">✓ Très faible</span>
|
||||
</div>
|
||||
<div class="attr-row">
|
||||
<span class="attr-id">190</span>
|
||||
<span class="attr-name">Température du disque</span>
|
||||
<span class="attr-val attr-ok">34°C</span>
|
||||
<span class="attr-explain attr-ok">✓ Normale</span>
|
||||
</div>
|
||||
<div class="attr-row">
|
||||
<span class="attr-id">197</span>
|
||||
<span class="attr-name">Secteurs instables en attente</span>
|
||||
<span class="attr-val attr-ok">0</span>
|
||||
<span class="attr-explain attr-ok">✓ Aucun</span>
|
||||
</div>
|
||||
<div class="attr-row">
|
||||
<span class="attr-id">198</span>
|
||||
<span class="attr-name">Secteurs non corrigeables</span>
|
||||
<span class="attr-val attr-ok">0</span>
|
||||
<span class="attr-explain attr-ok">✓ Aucun</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="smart-footer">
|
||||
<span class="smart-footer-note"><i class="fa-solid fa-circle-info" style="margin-right:4px"></i>Données collectées via smartctl · mise à jour à chaque actualisation de l'agent</span>
|
||||
<button class="btn primary" onclick="document.getElementById('overlay-smart').style.display='none'"><i class="fa-solid fa-xmark"></i> Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONFIG AGENT (inchangé) -->
|
||||
<div class="overlay" id="overlay-agentcfg" style="display:none;z-index:200" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-agentcfg" onclick="event.stopPropagation()">
|
||||
<div class="cfg-head">
|
||||
<div class="cfg-head-icon"><i class="fa-solid fa-gears"></i></div>
|
||||
<div class="cfg-head-info"><div class="cfg-head-title">Configuration de l'agent</div><div class="cfg-head-sub">srv-prod-01 · 10.0.0.11 · config récupérée à 22:14:05</div></div>
|
||||
<div class="pop-close" onclick="document.getElementById('overlay-agentcfg').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="cfg-body">
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">PROTOCOLES DE TRANSPORT</div>
|
||||
<div class="check-row active"><div class="chk-box"><i class="fa-solid fa-check"></i></div><div class="chk-label"><div class="chk-name">UDP <span class="chk-badge udp">UDP</span></div><div class="chk-desc">Fire-and-forget · serveur 10.0.0.50 · port 9999</div></div></div>
|
||||
<div class="check-row mqtt-active"><div class="chk-box" style="background:var(--purple);border-color:var(--purple);color:var(--bg-0)"><i class="fa-solid fa-check"></i></div><div class="chk-label"><div class="chk-name">MQTT <span class="chk-badge mqtt">MQTT</span></div><div class="chk-desc">Bidirectionnel · broker 10.0.0.3 · port 1883</div></div></div>
|
||||
<div class="mqtt-opts">
|
||||
<div class="mqtt-field"><label>Broker</label><input class="mqtt-input" type="text" value="10.0.0.3"></div>
|
||||
<div class="mqtt-field"><label>Port</label><input class="mqtt-input" type="number" value="1883" style="width:90px;flex:none"></div>
|
||||
<div class="mqtt-field"><label>Topic base</label><input class="mqtt-input" type="text" value="nanometrics/agents"></div>
|
||||
<div style="border-top:1px solid var(--border-1);padding-top:8px;display:flex;flex-direction:column;gap:6px">
|
||||
<div class="mqtt-check-row"><label><i class="fa-solid fa-satellite-dish"></i> Auto-discovery (Home Assistant)</label><label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="mqtt-check-row"><label><i class="fa-solid fa-arrow-right-to-bracket"></i> Birth message (connexion)</label><label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="mqtt-check-row"><label><i class="fa-solid fa-skull"></i> Last Will message (déconnexion)</label><label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">MÉTRIQUES ACTIVES</div>
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-microchip"></i></div><span class="metric-name">cpu</span><label class="toggle tog-ok"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-memory"></i></div><span class="metric-name">memory</span><label class="toggle tog-ok"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-hard-drive"></i></div><span class="metric-name">disk</span><label class="toggle tog-ok"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-shield-heart"></i></div><span class="metric-name">smart</span><label class="toggle tog-ok"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-network-wired"></i></div><span class="metric-name">network</span><label class="toggle tog-ok"><input type="checkbox"><span class="toggle-slider"></span></label></div>
|
||||
<div class="metric-row"><div class="metric-ico"><i class="fa-solid fa-thermometer-half"></i></div><span class="metric-name">temperature</span><label class="toggle tog-ok"><input type="checkbox"><span class="toggle-slider"></span></label></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">COMMANDES DISTANTES <span style="color:var(--ink-4);font-size:8px;margin-left:6px">— BIENTÔT</span></div>
|
||||
<div class="cmds-grid">
|
||||
<div class="cmd-btn"><i class="fa-solid fa-rotate-right"></i><span>reboot</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-power-off"></i><span>shutdown</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-display"></i><span>screen off</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-arrow-up-from-bracket"></i><span>update</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-arrow-up-right-dots"></i><span>upgrade</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-terminal"></i><span>shell cmd</span><span class="soon-tag">bientôt</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cfg-foot">
|
||||
<div class="cfg-status"><div class="dot"></div><span>Config synchronisée</span></div>
|
||||
<button class="btn" onclick="document.getElementById('overlay-agentcfg').style.display='none'">Annuler</button>
|
||||
<button class="btn primary"><i class="fa-solid fa-paper-plane"></i> Envoyer à l'agent</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const tip=document.getElementById('tooltip');let tt;
|
||||
document.addEventListener('mouseover',e=>{const el=e.target.closest('[data-tip]');if(!el)return;clearTimeout(tt);tt=setTimeout(()=>{tip.textContent=el.dataset.tip;tip.classList.add('show');mv(e);},120);});
|
||||
document.addEventListener('mousemove',e=>{if(tip.classList.contains('show'))mv(e);});
|
||||
document.addEventListener('mouseout',e=>{const el=e.target.closest('[data-tip]');if(!el)return;clearTimeout(tt);tip.classList.remove('show');});
|
||||
function mv(e){const w=tip.offsetWidth,h=tip.offsetHeight;let x=e.clientX-w/2,y=e.clientY-h-10;x=Math.max(6,Math.min(x,window.innerWidth-w-6));if(y<6)y=e.clientY+18;tip.style.left=x+'px';tip.style.top=y+'px';}
|
||||
|
||||
function toggleTheme(){const h=document.documentElement;h.dataset.theme=h.dataset.theme==='dark'?'light':'dark';document.getElementById('theme-icon').className=h.dataset.theme==='dark'?'fa-solid fa-moon':'fa-solid fa-sun';drawCharts();}
|
||||
function showDetail(){document.getElementById('overlay-detail').style.display='flex';drawCharts();}
|
||||
function showAgentCfg(){document.getElementById('overlay-agentcfg').style.display='flex';}
|
||||
function showSmart(){document.getElementById('overlay-smart').style.display='flex';}
|
||||
|
||||
/* Sauvegarde taille popup au resize */
|
||||
const pd=document.getElementById('popup-detail');
|
||||
const ro=new ResizeObserver(()=>{
|
||||
localStorage.setItem('popup-detail-size',JSON.stringify({w:pd.offsetWidth,h:pd.offsetHeight}));
|
||||
});
|
||||
ro.observe(pd);
|
||||
const saved=JSON.parse(localStorage.getItem('popup-detail-size')||'null');
|
||||
if(saved){pd.style.width=saved.w+'px';pd.style.height=saved.h+'px';}
|
||||
|
||||
function makeCurve(pts,stroke,fill,w,h){
|
||||
const xs=pts.map((_,i)=>(i/(pts.length-1))*w);
|
||||
const ys=pts.map(v=>h-(v/100)*(h-6)-3);
|
||||
const wy=h-(70/100)*(h-6)-3;
|
||||
let d=`M${xs[0]} ${ys[0]}`;
|
||||
for(let i=1;i<pts.length;i++){const cx=(xs[i-1]+xs[i])/2;d+=` C${cx} ${ys[i-1]},${cx} ${ys[i]},${xs[i]} ${ys[i]}`;}
|
||||
const uid=Math.random().toString(36).slice(2);
|
||||
return `<defs><linearGradient id="g${uid}" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="${fill}" stop-opacity=".4"/><stop offset="100%" stop-color="${fill}" stop-opacity=".02"/></linearGradient></defs>
|
||||
<line x1="0" y1="${wy}" x2="${w}" y2="${wy}" stroke="var(--warn)" stroke-width=".8" stroke-dasharray="3,3" opacity=".5"/>
|
||||
<path d="${d} L${xs.at(-1)} ${h} L${xs[0]} ${h}Z" fill="url(#g${uid})"/>
|
||||
<path d="${d}" fill="none" stroke="${stroke}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="${xs.at(-1)}" cy="${ys.at(-1)}" r="2.5" fill="${stroke}"/>`;
|
||||
}
|
||||
function drawCharts(){
|
||||
const cpu=[38,41,35,40,44,42,39,45,50,48,43,42,44,41,40,43,47,45,42,44,41,43,42,40,43,41,44,43,41,42];
|
||||
const ram=[44,44,45,45,46,46,47,47,46,46,45,46,46,47,47,46,46,45,46,46,46,46,46,46,46,46,46,46,46,46];
|
||||
const cs=getComputedStyle(document.documentElement);
|
||||
const ac=cs.getPropertyValue('--accent').trim(),bl=cs.getPropertyValue('--blue').trim();
|
||||
document.getElementById('cpu-chart').innerHTML=makeCurve(cpu,ac,ac,200,52);
|
||||
document.getElementById('ram-chart').innerHTML=makeCurve(ram,bl,bl,200,52);
|
||||
}
|
||||
window.addEventListener('load',drawCharts);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,672 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nanometrics — v8</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script src="/helper.js"></script>
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root[data-theme="dark"]{
|
||||
--accent:#fe8019;--accent-soft:#d65d0e;--accent-glow:rgba(254,128,25,.28);
|
||||
--bg-0:#1d1813;--bg-1:#2a231d;--bg-2:#32291f;--bg-3:#3c332a;--bg-4:#4a3f33;--bg-5:#57493c;
|
||||
--ink-1:#f2e5c7;--ink-2:#d5c4a1;--ink-3:#a89984;--ink-4:#7c6f64;
|
||||
--ok:#4dbb26;--warn:#fabd2f;--err:#fb4934;--blue:#3db0d1;--purple:#c882c8;
|
||||
--border-1:rgba(255,255,255,.06);--border-2:rgba(255,255,255,.12);--border-3:rgba(255,255,255,.26);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,.08) inset,0 -1px 0 rgba(0,0,0,.3) inset,0 6px 20px rgba(0,0,0,.5);
|
||||
--tile-press:inset 0 2px 8px rgba(0,0,0,.5),inset 0 1px 3px rgba(0,0,0,.4);
|
||||
--hover-glow:0 0 0 1px var(--accent-soft),0 0 24px var(--accent-glow),0 6px 20px rgba(0,0,0,.5);
|
||||
--font-ui:'Inter',system-ui,sans-serif;--font-mono:'JetBrains Mono',monospace;--font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
:root[data-theme="light"]{
|
||||
--accent:#af3a03;--accent-soft:#d65d0e;--accent-glow:rgba(175,58,3,.18);
|
||||
--bg-0:#d5c4a1;--bg-1:#ebdbb2;--bg-2:#d5c4a1;--bg-3:#bdae93;--bg-4:#a89984;--bg-5:#928374;
|
||||
--ink-1:#3c3836;--ink-2:#504945;--ink-3:#665c54;--ink-4:#7c6f64;
|
||||
--ok:#3c911c;--warn:#b57614;--err:#9d0006;--blue:#2d82a3;--purple:#8c468c;
|
||||
--border-1:rgba(0,0,0,.08);--border-2:rgba(0,0,0,.15);--border-3:rgba(0,0,0,.3);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,.55) inset,0 -1px 0 rgba(0,0,0,.08) inset,0 4px 14px rgba(0,0,0,.13);
|
||||
--tile-press:inset 0 2px 6px rgba(0,0,0,.2);
|
||||
--hover-glow:0 0 0 1px var(--accent-soft),0 0 18px var(--accent-glow),0 4px 14px rgba(0,0,0,.13);
|
||||
--font-ui:'Inter',system-ui,sans-serif;--font-mono:'JetBrains Mono',monospace;--font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
body{background:var(--bg-1);color:var(--ink-1);font-family:var(--font-ui);font-size:13px;height:100vh;display:flex;flex-direction:column;overflow:hidden;transition:background .2s,color .2s}
|
||||
#tooltip{position:fixed;z-index:9999;pointer-events:none;background:var(--bg-0);color:var(--ink-1);border:1px solid var(--border-3);border-radius:5px;padding:4px 9px;font-size:11px;font-family:var(--font-ui);white-space:nowrap;opacity:0;transition:opacity .12s;box-shadow:0 4px 12px rgba(0,0,0,.4)}
|
||||
#tooltip.show{opacity:1}
|
||||
|
||||
/* HEADER */
|
||||
.header{background:var(--bg-2);border-bottom:1px solid var(--border-2);padding:0 20px;height:48px;display:flex;align-items:center;gap:12px;flex-shrink:0}
|
||||
.logo{display:flex;align-items:center;gap:8px}
|
||||
.logo-led{width:9px;height:9px;border-radius:50%;background:var(--accent);box-shadow:0 0 8px var(--accent-glow);animation:blink 2s infinite}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.logo-name{font-weight:700;font-size:14px;letter-spacing:.05em;font-family:var(--font-terminal)}
|
||||
.logo-ver{font-size:10px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.h-sep{width:1px;height:24px;background:var(--border-2)}
|
||||
.h-spacer{flex:1}
|
||||
.h-stats{display:flex;gap:14px}
|
||||
.h-stat{display:flex;align-items:center;gap:5px}
|
||||
.h-stat .lbl{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.h-stat .val{font-family:var(--font-mono);font-weight:700;font-size:13px}
|
||||
.c-ok{color:var(--ok)}.c-warn{color:var(--warn)}.c-err{color:var(--err)}.c-n{color:var(--ink-2)}
|
||||
.hbtn{width:34px;height:34px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-3);color:var(--ink-2);font-size:14px;display:flex;align-items:center;justify-content:center;cursor:pointer;user-select:none;transition:background .12s,color .12s,transform .08s,box-shadow .08s}
|
||||
.hbtn:hover{background:var(--bg-4);color:var(--accent)}
|
||||
.hbtn:active{transform:translateY(1px) scale(.96);box-shadow:var(--tile-press)}
|
||||
.hbtn.active-btn{background:var(--accent);color:var(--bg-0);border-color:var(--accent-soft)}
|
||||
|
||||
/* GRID */
|
||||
.main{flex:1;padding:14px 16px;overflow-y:auto}
|
||||
.agents-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px}
|
||||
.tile{background:var(--bg-3);border-radius:10px;padding:12px 14px;border:1px solid var(--border-1);box-shadow:var(--tile-3d);cursor:pointer;user-select:none;display:flex;flex-direction:column;gap:9px;transition:box-shadow .15s,transform .08s,border-color .15s}
|
||||
.tile:hover{box-shadow:var(--hover-glow);border-color:var(--accent-soft)}
|
||||
.tile:active{transform:translateY(2px) scale(.99);box-shadow:var(--tile-press)}
|
||||
.tile.t-warn{border-color:rgba(250,189,47,.3)}.tile.t-warn:hover{border-color:var(--warn);box-shadow:0 0 0 1px var(--warn),0 0 22px rgba(250,189,47,.22),0 6px 20px rgba(0,0,0,.5)}
|
||||
.tile.t-err{border-color:rgba(251,73,52,.35)}.tile.t-err:hover{border-color:var(--err);box-shadow:0 0 0 1px var(--err),0 0 22px rgba(251,73,52,.25),0 6px 20px rgba(0,0,0,.5)}
|
||||
.tile.t-off{opacity:.5;cursor:default}.tile.t-off:hover,.tile.t-off:active{box-shadow:var(--tile-3d);border-color:var(--border-1);transform:none}
|
||||
.tile-head{display:flex;align-items:center;gap:8px}
|
||||
.t-icon{width:28px;height:28px;border-radius:7px;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--accent);font-size:13px;flex-shrink:0;overflow:hidden}
|
||||
.t-names{flex:1;min-width:0}
|
||||
.t-host{font-weight:600;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.t-ip{font-family:var(--font-mono);font-size:10px;color:var(--ink-4)}
|
||||
.t-led{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
||||
.s-ok{background:var(--ok);box-shadow:0 0 6px var(--ok)}.s-warn{background:var(--warn);box-shadow:0 0 6px var(--warn);animation:blink 1.5s infinite}.s-err{background:var(--err);box-shadow:0 0 8px var(--err);animation:blink 1s infinite}.s-off{background:var(--ink-4)}
|
||||
.tile-gauges{display:flex;flex-direction:column;gap:5px}
|
||||
.g-row{display:flex;align-items:center;gap:7px}
|
||||
.g-ico{width:18px;height:18px;display:flex;align-items:center;justify-content:center;font-size:11px;color:var(--ink-3);flex-shrink:0;cursor:help}
|
||||
.g-bar{flex:1;height:5px;border-radius:3px;background:var(--bg-1);overflow:hidden}
|
||||
.g-fill{height:100%;border-radius:3px;background:var(--ok)}
|
||||
.g-fill.w{background:var(--warn)}.g-fill.e{background:var(--err)}
|
||||
.g-val{font-family:var(--font-mono);font-size:11px;color:var(--ink-2);width:34px;text-align:right}
|
||||
.tile-foot{font-family:var(--font-terminal);font-size:10px;color:var(--ink-4);display:flex;align-items:center;gap:5px}
|
||||
.tile-foot i{font-size:9px}
|
||||
|
||||
/* FOOTER */
|
||||
.footer{background:var(--bg-0);border-top:1px solid var(--border-2);height:26px;display:flex;align-items:center;font-family:var(--font-terminal);font-size:11px;color:var(--ink-4);flex-shrink:0}
|
||||
.f-mode{background:var(--accent);color:var(--bg-0);padding:0 12px;height:100%;display:flex;align-items:center;font-weight:700;letter-spacing:.04em}
|
||||
.f-cell{padding:0 12px;border-right:1px solid var(--border-1);display:flex;align-items:center;gap:5px;height:100%}
|
||||
.f-val{font-family:var(--font-mono);color:var(--ink-2)}.f-val.w{color:var(--warn)}
|
||||
.f-minibar{width:36px;height:4px;border-radius:2px;background:var(--bg-3);overflow:hidden}
|
||||
.f-minifill{height:100%;border-radius:2px;background:var(--ok)}.f-minifill.w{background:var(--warn)}
|
||||
.f-spacer{flex:1}.f-right{padding:0 12px;display:flex;align-items:center;gap:6px;color:var(--ink-3)}
|
||||
.f-time{font-family:var(--font-mono);color:var(--ink-2)}
|
||||
|
||||
/* OVERLAY */
|
||||
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.65);z-index:100;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px)}
|
||||
|
||||
/* POPUP BASE */
|
||||
.popup{background:var(--bg-2);border:1px solid var(--border-3);border-radius:12px;box-shadow:0 24px 64px rgba(0,0,0,.7);display:flex;flex-direction:column;overflow:hidden}
|
||||
.pop-close{width:28px;height:28px;border-radius:6px;background:var(--bg-5);color:var(--ink-3);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;border:1px solid var(--border-1);transition:background .12s,color .12s,transform .08s;user-select:none}
|
||||
.pop-close:hover{background:var(--err);color:#fff}
|
||||
.pop-close:active{transform:translateY(1px) scale(.93);box-shadow:var(--tile-press)}
|
||||
.sec-title{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.08em;margin-bottom:8px}
|
||||
.btn{padding:6px 14px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-4);color:var(--ink-2);font-size:12px;font-family:var(--font-ui);cursor:pointer;display:flex;align-items:center;gap:6px;user-select:none;transition:background .1s,transform .08s,box-shadow .08s}
|
||||
.btn:hover{background:var(--bg-5)}.btn:active{transform:translateY(1px) scale(.97);box-shadow:var(--tile-press)}
|
||||
.btn.primary{background:var(--accent);color:var(--bg-0);border-color:var(--accent-soft);font-weight:600}.btn.primary:hover{background:var(--accent-soft)}
|
||||
|
||||
/* ══ CONFIG SERVEUR (header) ══ */
|
||||
#overlay-srvcfg{z-index:150}
|
||||
#popup-srvcfg{width:400px;max-width:96vw;max-height:88vh}
|
||||
.scfg-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:10px}
|
||||
.scfg-icon{width:32px;height:32px;border-radius:8px;background:var(--accent);display:flex;align-items:center;justify-content:center;color:var(--bg-0);font-size:15px}
|
||||
.scfg-title{flex:1;font-weight:700;font-size:14px}
|
||||
.scfg-body{padding:18px;display:flex;flex-direction:column;gap:14px;overflow-y:auto}
|
||||
.scfg-section{display:flex;flex-direction:column;gap:8px}
|
||||
.scfg-sec-title{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.08em;padding-bottom:6px;border-bottom:1px solid var(--border-1)}
|
||||
.scfg-row{display:flex;align-items:center;gap:10px}
|
||||
.scfg-row > label{font-size:12px;color:var(--ink-3);width:110px;flex-shrink:0;font-family:var(--font-terminal)}
|
||||
.scfg-slider{flex:1;accent-color:var(--accent)}
|
||||
.scfg-val{font-family:var(--font-mono);font-size:12px;color:var(--ink-2);width:48px;text-align:right}
|
||||
.scfg-select{flex:1;background:var(--bg-3);border:1px solid var(--border-2);border-radius:6px;color:var(--ink-1);padding:6px 10px;font-size:12px;font-family:var(--font-ui)}
|
||||
.scfg-toggle-row{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;background:var(--bg-3);border-radius:7px;border:1px solid var(--border-1)}
|
||||
.scfg-toggle-row label{font-size:12px;color:var(--ink-2);display:flex;align-items:center;gap:7px;cursor:pointer}
|
||||
.scfg-toggle-row label i{color:var(--accent);font-size:12px}
|
||||
.scfg-foot{padding:12px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;gap:8px;justify-content:flex-end}
|
||||
.toggle{position:relative;width:34px;height:18px;flex-shrink:0}
|
||||
.toggle input{opacity:0;width:0;height:0}
|
||||
.toggle-slider{position:absolute;inset:0;border-radius:9px;background:var(--bg-4);border:1px solid var(--border-2);cursor:pointer;transition:background .2s}
|
||||
.toggle-slider::before{content:'';position:absolute;width:12px;height:12px;border-radius:50%;background:var(--ink-4);top:2px;left:2px;transition:transform .2s,background .2s}
|
||||
.toggle input:checked+.toggle-slider{background:rgba(254,128,25,.3);border-color:var(--accent)}
|
||||
.toggle input:checked+.toggle-slider::before{transform:translateX(16px);background:var(--accent)}
|
||||
.tog-ok input:checked+.toggle-slider{background:rgba(77,187,38,.3);border-color:var(--ok)}
|
||||
.tog-ok input:checked+.toggle-slider::before{background:var(--ok)}
|
||||
|
||||
/* ══ POPUP DÉTAIL AGENT — redimensionnable ══ */
|
||||
#popup-detail{width:560px;max-width:96vw;max-height:92vh;resize:both;overflow:hidden;min-width:400px;min-height:320px}
|
||||
#popup-detail .pop-body{overflow-y:auto;flex:1}
|
||||
.pop-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:12px;flex-shrink:0}
|
||||
.agent-icon-wrap{position:relative;width:44px;height:44px;border-radius:10px;flex-shrink:0;cursor:pointer;overflow:hidden;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--accent);font-size:18px;border:2px solid var(--border-2);transition:border-color .15s}
|
||||
.agent-icon-wrap:hover{border-color:var(--accent)}
|
||||
.agent-icon-overlay{position:absolute;inset:0;background:rgba(0,0,0,.6);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;opacity:0;transition:opacity .15s;font-size:10px;color:#fff}
|
||||
.agent-icon-overlay i{font-size:14px}
|
||||
.agent-icon-wrap:hover .agent-icon-overlay{opacity:1}
|
||||
#icon-upload{display:none}
|
||||
.pop-head-info{flex:1}
|
||||
.pop-host{font-weight:700;font-size:15px}
|
||||
.pop-ip{font-family:var(--font-mono);font-size:11px;color:var(--ink-4)}
|
||||
.upload-hint{font-size:10px;color:var(--ink-4);font-family:var(--font-terminal);margin-top:2px}
|
||||
.pop-led{width:10px;height:10px;border-radius:50%;background:var(--ok);box-shadow:0 0 8px var(--ok);flex-shrink:0}
|
||||
.pop-body{padding:16px 18px;display:flex;flex-direction:column;gap:14px}
|
||||
.kpi-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:7px}
|
||||
.kpi{background:var(--bg-3);border-radius:8px;padding:10px 12px;border:1px solid var(--border-1);box-shadow:var(--tile-3d)}
|
||||
.kpi-lbl{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.kpi-val{font-family:var(--font-mono);font-size:20px;font-weight:700;line-height:1.1;margin-top:2px}
|
||||
.kpi-val .u{font-size:10px;color:var(--ink-3);font-weight:400}
|
||||
.kpi-sub{font-size:10px;color:var(--ink-4);font-family:var(--font-mono);margin-top:2px}
|
||||
.charts-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||
.chart-card{background:var(--bg-3);border-radius:8px;padding:10px 12px;border:1px solid var(--border-1);box-shadow:var(--tile-3d)}
|
||||
.chart-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
|
||||
.chart-label{display:flex;align-items:center;gap:6px;font-size:10px;font-family:var(--font-terminal);letter-spacing:.06em;color:var(--ink-3)}
|
||||
.chart-cur{font-family:var(--font-mono);font-size:16px;font-weight:700}
|
||||
.chart-svg{width:100%;height:52px;display:block}
|
||||
.chart-axis{display:flex;justify-content:space-between;margin-top:2px;font-family:var(--font-terminal);font-size:9px;color:var(--ink-4)}
|
||||
.storage-block{display:flex;flex-direction:column;gap:8px}
|
||||
.det-gauges{display:flex;flex-direction:column;gap:7px}
|
||||
.dg-row{display:flex;align-items:center;gap:10px}
|
||||
.dg-ico{width:22px;text-align:center;font-size:13px;cursor:help}
|
||||
.dg-bar{flex:1;height:7px;border-radius:4px;background:var(--bg-1);overflow:hidden}
|
||||
.dg-fill{height:100%;border-radius:4px;background:var(--ok)}.dg-fill.b{background:var(--blue)}
|
||||
.dg-val{font-family:var(--font-mono);font-size:12px;color:var(--ink-2);width:90px;text-align:right}
|
||||
.smart-btn{display:inline-flex;align-items:center;gap:8px;padding:7px 12px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-3);cursor:pointer;user-select:none;transition:background .12s,border-color .12s,transform .08s;font-family:var(--font-terminal);font-size:11px}
|
||||
.smart-btn:hover{background:var(--bg-4);border-color:var(--border-3)}.smart-btn:active{transform:translateY(1px);box-shadow:var(--tile-press)}
|
||||
.smart-btn.ok{border-color:rgba(77,187,38,.3);color:var(--ok)}.smart-dot{width:7px;height:7px;border-radius:50%;background:var(--ok);box-shadow:0 0 5px var(--ok)}
|
||||
.meta-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px}
|
||||
.meta{background:var(--bg-3);border-radius:6px;padding:8px 10px;border:1px solid var(--border-1)}
|
||||
.meta-lbl{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.meta-val{font-family:var(--font-mono);font-size:12px;color:var(--ink-2);margin-top:2px}
|
||||
.proto-badges{display:flex;gap:5px;margin-top:4px}
|
||||
.proto-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:999px;font-size:10px;font-family:var(--font-terminal);font-weight:600}
|
||||
.proto-badge.udp{background:rgba(61,176,209,.15);color:var(--blue);border:1px solid rgba(61,176,209,.3)}
|
||||
.proto-badge.mqtt{background:rgba(200,130,200,.15);color:var(--purple);border:1px solid rgba(200,130,200,.3)}
|
||||
.pop-foot{padding:10px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;align-items:center;gap:8px;flex-shrink:0}
|
||||
.pop-uptime{font-family:var(--font-terminal);font-size:11px;color:var(--ink-4);flex:1}
|
||||
.resize-hint{font-family:var(--font-terminal);font-size:9px;color:var(--ink-4);display:flex;align-items:center;gap:4px}
|
||||
.btn-agent-cfg{width:34px;height:34px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-4);color:var(--ink-3);font-size:15px;display:flex;align-items:center;justify-content:center;cursor:pointer;user-select:none;transition:background .12s,color .12s,transform .08s,box-shadow .08s}
|
||||
.btn-agent-cfg:hover{background:var(--bg-5);color:var(--accent)}.btn-agent-cfg:active{transform:translateY(1px) scale(.93);box-shadow:var(--tile-press)}
|
||||
|
||||
/* ══ CONFIG AGENT ══ */
|
||||
#overlay-agentcfg{z-index:200}
|
||||
#popup-agentcfg{width:520px;max-width:96vw;max-height:90vh}
|
||||
.cfg-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:10px}
|
||||
.cfg-head-icon{width:32px;height:32px;border-radius:8px;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--accent);font-size:15px}
|
||||
.cfg-head-info{flex:1}.cfg-head-title{font-weight:700;font-size:14px}.cfg-head-sub{font-size:11px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.cfg-body{padding:18px;display:flex;flex-direction:column;gap:16px;overflow-y:auto;max-height:60vh}
|
||||
.cfg-section{display:flex;flex-direction:column;gap:8px}
|
||||
.cfg-sec-title{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.08em;padding-bottom:6px;border-bottom:1px solid var(--border-1)}
|
||||
/* protocoles */
|
||||
.check-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;background:var(--bg-3);border:1px solid var(--border-1);cursor:pointer;transition:background .12s,border-color .12s}
|
||||
.check-row.active{border-color:var(--accent-soft);background:rgba(254,128,25,.08)}
|
||||
.check-row.mqtt-active{border-color:rgba(200,130,200,.4);background:rgba(200,130,200,.07)}
|
||||
.chk-box{width:18px;height:18px;border-radius:4px;border:2px solid var(--border-3);background:var(--bg-1);display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0}
|
||||
.check-row.active .chk-box{background:var(--accent);border-color:var(--accent);color:var(--bg-0)}
|
||||
.check-row.mqtt-active .chk-box{background:var(--purple);border-color:var(--purple);color:var(--bg-0)}
|
||||
.chk-label{flex:1}.chk-name{font-weight:600;font-size:13px}.chk-desc{font-size:11px;color:var(--ink-4);font-family:var(--font-terminal);margin-top:1px}
|
||||
.chk-badge{font-size:10px;font-family:var(--font-terminal);font-weight:700;padding:1px 7px;border-radius:999px}
|
||||
.chk-badge.udp{background:rgba(61,176,209,.15);color:var(--blue)}.chk-badge.mqtt{background:rgba(200,130,200,.15);color:var(--purple)}
|
||||
.mqtt-opts{background:var(--bg-3);border-radius:8px;border:1px solid rgba(200,130,200,.2);padding:12px 14px;display:flex;flex-direction:column;gap:10px}
|
||||
.mqtt-field{display:flex;align-items:center;gap:10px}
|
||||
.mqtt-field label{font-size:11px;color:var(--ink-3);font-family:var(--font-terminal);width:90px;flex-shrink:0}
|
||||
.mqtt-input{flex:1;background:var(--bg-1);border:1px solid var(--border-2);border-radius:6px;color:var(--ink-1);padding:6px 10px;font-size:12px;font-family:var(--font-mono)}
|
||||
.mqtt-input:focus{outline:none;border-color:var(--purple)}
|
||||
.mqtt-check-row{display:flex;align-items:center;justify-content:space-between;padding:3px 0}
|
||||
.mqtt-check-row label{font-size:12px;color:var(--ink-2);display:flex;align-items:center;gap:7px;cursor:pointer}
|
||||
.mqtt-check-row label i{color:var(--purple);font-size:11px}
|
||||
.tog-mqtt input:checked+.toggle-slider{background:rgba(200,130,200,.3);border-color:var(--purple)}
|
||||
.tog-mqtt input:checked+.toggle-slider::before{background:var(--purple)}
|
||||
|
||||
/* ══ MÉTRIQUES — 2 cases par métrique (UDP + MQTT) ══ */
|
||||
.metrics-table{display:flex;flex-direction:column;gap:0;border:1px solid var(--border-1);border-radius:8px;overflow:hidden}
|
||||
.metrics-header{display:grid;grid-template-columns:1fr 60px 60px;background:var(--bg-4);padding:7px 12px;gap:8px;align-items:center}
|
||||
.mh-label{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.mh-proto{font-size:9px;font-family:var(--font-terminal);font-weight:700;letter-spacing:.04em;text-align:center}
|
||||
.mh-proto.udp{color:var(--blue)}.mh-proto.mqtt{color:var(--purple)}
|
||||
.metric-row{display:grid;grid-template-columns:1fr 60px 60px;padding:7px 12px;gap:8px;align-items:center;background:var(--bg-3);border-top:1px solid var(--border-1);transition:background .1s}
|
||||
.metric-row:hover{background:var(--bg-4)}
|
||||
.metric-cell{display:flex;align-items:center;gap:7px}
|
||||
.metric-ico{font-size:12px;color:var(--ink-3);width:16px;text-align:center}
|
||||
.metric-name{font-size:12px;color:var(--ink-2);font-family:var(--font-terminal)}
|
||||
.metric-chk{display:flex;justify-content:center}
|
||||
/* checkbox custom compact */
|
||||
.cbox{width:18px;height:18px;border-radius:4px;border:2px solid var(--border-2);background:var(--bg-1);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:10px;color:transparent;transition:background .12s,border-color .12s,color .12s;user-select:none;flex-shrink:0}
|
||||
.cbox.udp-on{background:rgba(61,176,209,.2);border-color:var(--blue);color:var(--blue)}
|
||||
.cbox.mqtt-on{background:rgba(200,130,200,.2);border-color:var(--purple);color:var(--purple)}
|
||||
|
||||
/* commandes */
|
||||
.cmds-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
|
||||
.cmd-btn{display:flex;flex-direction:column;align-items:center;gap:4px;padding:10px 8px;border-radius:8px;background:var(--bg-3);border:1px solid var(--border-1);cursor:not-allowed;opacity:.4}
|
||||
.cmd-btn i{font-size:16px;color:var(--ink-3)}.cmd-btn span{font-size:10px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.soon-tag{font-size:8px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.cfg-foot{padding:12px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;align-items:center;gap:8px}
|
||||
.cfg-status{flex:1;display:flex;align-items:center;gap:6px;font-family:var(--font-terminal);font-size:11px;color:var(--ink-4)}
|
||||
.cfg-status .dot{width:6px;height:6px;border-radius:50%;background:var(--ok)}
|
||||
|
||||
/* SMART */
|
||||
#overlay-smart{z-index:300}
|
||||
#popup-smart{width:500px;max-width:96vw;max-height:88vh}
|
||||
.smart-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:10px}
|
||||
.smart-head-icon{width:32px;height:32px;border-radius:8px;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--ok);font-size:15px}
|
||||
.smart-head-info{flex:1}.smart-head-title{font-weight:700;font-size:14px}.smart-head-sub{font-size:11px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.smart-body{padding:18px;display:flex;flex-direction:column;gap:16px;overflow-y:auto;max-height:70vh}
|
||||
.smart-verdict{display:flex;align-items:center;gap:14px;background:rgba(77,187,38,.1);border:1px solid rgba(77,187,38,.3);border-radius:10px;padding:14px 18px}
|
||||
.verdict-icon{font-size:28px;color:var(--ok)}
|
||||
.verdict-text .v-title{font-size:16px;font-weight:700;color:var(--ok)}
|
||||
.verdict-text .v-sub{font-size:12px;color:var(--ink-3);margin-top:3px}
|
||||
.smart-indicators{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||||
.smart-ind{background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1);box-shadow:var(--tile-3d)}
|
||||
.si-header{display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
||||
.si-icon{font-size:14px;width:22px;text-align:center}
|
||||
.si-title{font-weight:600;font-size:12px;flex:1}
|
||||
.si-status{font-size:10px;font-family:var(--font-terminal);font-weight:700;padding:1px 7px;border-radius:999px}
|
||||
.si-status.ok{background:rgba(77,187,38,.15);color:var(--ok)}
|
||||
.si-val{font-family:var(--font-mono);font-size:18px;font-weight:700;color:var(--ink-1)}
|
||||
.si-val .u{font-size:11px;color:var(--ink-3);font-weight:400}
|
||||
.si-desc{font-size:11px;color:var(--ink-3);margin-top:4px;line-height:1.4}
|
||||
.smart-attrs{display:flex;flex-direction:column;gap:5px}
|
||||
.attr-row{display:flex;align-items:center;gap:10px;padding:6px 10px;border-radius:6px;background:var(--bg-3);border:1px solid var(--border-1)}
|
||||
.attr-id{font-family:var(--font-mono);font-size:10px;color:var(--ink-4);width:28px;flex-shrink:0}
|
||||
.attr-name{flex:1;font-size:11px;color:var(--ink-2)}
|
||||
.attr-val{font-family:var(--font-mono);font-size:11px;font-weight:600;width:40px;text-align:right}
|
||||
.attr-explain{font-size:10px;color:var(--ink-4);width:130px;text-align:right;font-family:var(--font-terminal)}
|
||||
.attr-ok{color:var(--ok)}
|
||||
.smart-footer{padding:12px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;align-items:center;gap:8px}
|
||||
.smart-footer-note{flex:1;font-size:10px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
|
||||
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:var(--bg-1)}::-webkit-scrollbar-thumb{background:var(--bg-4);border-radius:3px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="tooltip"></div>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="logo"><div class="logo-led"></div><span class="logo-name">NANOMETRICS</span><span class="logo-ver">v1.0</span></div>
|
||||
<div class="h-sep"></div>
|
||||
<div class="h-stats">
|
||||
<div class="h-stat"><span class="lbl">AGENTS</span><span class="val c-n">8</span></div>
|
||||
<div class="h-stat"><span class="lbl">OK</span><span class="val c-ok">5</span></div>
|
||||
<div class="h-stat"><span class="lbl">WARN</span><span class="val c-warn">2</span></div>
|
||||
<div class="h-stat"><span class="lbl">ERR</span><span class="val c-err">1</span></div>
|
||||
</div>
|
||||
<div class="h-spacer"></div>
|
||||
<div class="hbtn" onclick="toggleTheme()" data-tip="Thème clair / sombre"><i class="fa-solid fa-moon" id="theme-icon"></i></div>
|
||||
<!-- ← bouton config serveur fonctionnel -->
|
||||
<div class="hbtn" id="btn-srvcfg" onclick="showSrvCfg()" data-tip="Configuration serveur / interface"><i class="fa-solid fa-sliders"></i></div>
|
||||
</div>
|
||||
|
||||
<!-- GRID -->
|
||||
<div class="main">
|
||||
<div class="agents-grid">
|
||||
<div class="tile" onclick="showDetail()">
|
||||
<div class="tile-head"><div class="t-icon"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-prod-01</div><div class="t-ip">10.0.0.11</div></div><div class="t-led s-ok"></div></div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:42%"></div></div><span class="g-val">42%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:58%"></div></div><span class="g-val">58%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:31%"></div></div><span class="g-val">31%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>14j 6h</div>
|
||||
</div>
|
||||
<div class="tile t-warn"><div class="tile-head"><div class="t-icon"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-backup-02</div><div class="t-ip">10.0.0.12</div></div><div class="t-led s-warn"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU élevé" style="color:var(--warn)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill w" style="width:78%"></div></div><span class="g-val" style="color:var(--warn)">78%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM élevée" style="color:var(--warn)"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill w" style="width:72%"></div></div><span class="g-val" style="color:var(--warn)">72%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:20%"></div></div><span class="g-val">20%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>3j 14h</div></div>
|
||||
<div class="tile t-err"><div class="tile-head"><div class="t-icon" style="color:var(--err)"><i class="fa-solid fa-microchip"></i></div><div class="t-names"><div class="t-host">rpi-sensor-03</div><div class="t-ip">10.0.0.23</div></div><div class="t-led s-err"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:12%"></div></div><span class="g-val">12%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:38%"></div></div><span class="g-val">38%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque critique !" style="color:var(--err)"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill e" style="width:94%"></div></div><span class="g-val" style="color:var(--err)">94%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>62j 1h</div></div>
|
||||
<div class="tile"><div class="tile-head"><div class="t-icon"><i class="fa-solid fa-hard-drive"></i></div><div class="t-names"><div class="t-host">nas-storage-04</div><div class="t-ip">10.0.0.30</div></div><div class="t-led s-ok"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:8%"></div></div><span class="g-val">8%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:45%"></div></div><span class="g-val">45%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:66%"></div></div><span class="g-val">66%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>128j 3h</div></div>
|
||||
<div class="tile t-off"><div class="tile-head"><div class="t-icon" style="color:var(--ink-4)"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-dev-07</div><div class="t-ip">10.0.0.60</div></div><div class="t-led s-off"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div><div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div><div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div></div><div class="tile-foot" style="color:var(--err)"><i class="fa-solid fa-circle-xmark"></i>Hors ligne · vu il y a 2h</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
<div class="f-mode">LIVE</div>
|
||||
<div class="f-cell"><i class="fa-solid fa-server" style="font-size:10px"></i><span>SERVEUR</span></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-microchip" style="font-size:10px"></i><span class="f-val">18%</span><div class="f-minibar"><div class="f-minifill" style="width:18%"></div></div></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-memory" style="font-size:10px"></i><span class="f-val w">71%</span><div class="f-minibar"><div class="f-minifill w" style="width:71%"></div></div></div>
|
||||
<div class="f-spacer"></div>
|
||||
<div class="f-right"><i class="fa-solid fa-rotate"></i><span>Actualisation : <span class="f-time">22:14:07</span></span></div>
|
||||
</div>
|
||||
|
||||
<!-- ══ CONFIG SERVEUR (header sliders) ══ -->
|
||||
<div class="overlay" id="overlay-srvcfg" style="display:none" onclick="if(event.target===this)hideSrvCfg()">
|
||||
<div class="popup" id="popup-srvcfg" onclick="event.stopPropagation()">
|
||||
<div class="scfg-head">
|
||||
<div class="scfg-icon"><i class="fa-solid fa-sliders"></i></div>
|
||||
<span class="scfg-title">Configuration interface</span>
|
||||
<div class="pop-close" onclick="hideSrvCfg()" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="scfg-body">
|
||||
<div class="scfg-section">
|
||||
<div class="scfg-sec-title">AFFICHAGE DES TUILES</div>
|
||||
<div class="scfg-row">
|
||||
<label>Largeur min.</label>
|
||||
<input type="range" class="scfg-slider" min="160" max="420" value="220" oninput="this.nextElementSibling.textContent=this.value+'px'">
|
||||
<span class="scfg-val">220px</span>
|
||||
</div>
|
||||
<div class="scfg-row">
|
||||
<label>Taille du texte</label>
|
||||
<input type="range" class="scfg-slider" min="10" max="18" value="13" oninput="this.nextElementSibling.textContent=this.value+'px'">
|
||||
<span class="scfg-val">13px</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scfg-section">
|
||||
<div class="scfg-sec-title">SEUILS D'ALERTE PAR DÉFAUT</div>
|
||||
<div class="scfg-row">
|
||||
<label>Warning CPU/RAM</label>
|
||||
<input type="range" class="scfg-slider" min="50" max="95" value="70" oninput="this.nextElementSibling.textContent=this.value+'%'">
|
||||
<span class="scfg-val">70%</span>
|
||||
</div>
|
||||
<div class="scfg-row">
|
||||
<label>Erreur CPU/RAM</label>
|
||||
<input type="range" class="scfg-slider" min="60" max="100" value="85" oninput="this.nextElementSibling.textContent=this.value+'%'">
|
||||
<span class="scfg-val">85%</span>
|
||||
</div>
|
||||
<div class="scfg-row">
|
||||
<label>Warning Disque</label>
|
||||
<input type="range" class="scfg-slider" min="50" max="95" value="75" oninput="this.nextElementSibling.textContent=this.value+'%'">
|
||||
<span class="scfg-val">75%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scfg-section">
|
||||
<div class="scfg-sec-title">DONNÉES & RÉTENTION</div>
|
||||
<div class="scfg-row">
|
||||
<label>Historique</label>
|
||||
<select class="scfg-select"><option>7 jours</option><option selected>30 jours</option><option>90 jours</option><option>1 an</option></select>
|
||||
</div>
|
||||
<div class="scfg-row">
|
||||
<label>Courbes (durée)</label>
|
||||
<select class="scfg-select"><option>15 min</option><option selected>30 min</option><option>1 heure</option><option>6 heures</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scfg-section">
|
||||
<div class="scfg-sec-title">COMPORTEMENT</div>
|
||||
<div class="scfg-toggle-row">
|
||||
<label><i class="fa-solid fa-eye-slash"></i> Masquer les agents hors ligne</label>
|
||||
<label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label>
|
||||
</div>
|
||||
<div class="scfg-toggle-row">
|
||||
<label><i class="fa-solid fa-bell"></i> Notifications navigateur</label>
|
||||
<label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
|
||||
</div>
|
||||
<div class="scfg-toggle-row">
|
||||
<label><i class="fa-solid fa-up-right-and-down-left-from-center"></i> Mémoriser taille des popups</label>
|
||||
<label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scfg-foot">
|
||||
<button class="btn" onclick="hideSrvCfg()">Annuler</button>
|
||||
<button class="btn primary" onclick="hideSrvCfg()"><i class="fa-solid fa-floppy-disk"></i> Sauvegarder</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ POPUP DÉTAIL AGENT ══ -->
|
||||
<div class="overlay" id="overlay-detail" style="display:flex" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-detail" onclick="event.stopPropagation()">
|
||||
<div class="pop-head">
|
||||
<div class="agent-icon-wrap" onclick="document.getElementById('icon-upload').click()" data-tip="Changer l'icône">
|
||||
<i class="fa-solid fa-server"></i>
|
||||
<div class="agent-icon-overlay"><i class="fa-solid fa-camera"></i><span>Changer</span></div>
|
||||
</div>
|
||||
<input type="file" id="icon-upload" accept=".svg,.jpg,.jpeg,.png,.webp">
|
||||
<div class="pop-head-info">
|
||||
<div class="pop-host">srv-prod-01</div>
|
||||
<div class="pop-ip">10.0.0.11</div>
|
||||
<div class="upload-hint">Cliquer sur l'icône pour personnaliser · SVG JPG PNG WEBP · max 128×128 px</div>
|
||||
</div>
|
||||
<div class="pop-led"></div>
|
||||
<div class="pop-close" onclick="document.getElementById('overlay-detail').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="pop-body">
|
||||
<div>
|
||||
<div class="sec-title">MÉTRIQUES ACTUELLES</div>
|
||||
<div class="kpi-grid">
|
||||
<div class="kpi"><div class="kpi-lbl">CPU</div><div class="kpi-val c-ok">42<span class="u">%</span></div><div class="kpi-sub">4 cœurs</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">MÉMOIRE</div><div class="kpi-val">3.7<span class="u">Go</span></div><div class="kpi-sub">/ 8 Go · 46%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">DISQUE</div><div class="kpi-val">62<span class="u">Go</span></div><div class="kpi-sub">/ 200 Go · 31%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">UPTIME</div><div class="kpi-val" style="font-size:15px;color:var(--ink-1)">14j 6h</div><div class="kpi-sub">depuis boot</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">HISTORIQUE — 30 MIN</div>
|
||||
<div class="charts-grid">
|
||||
<div class="chart-card">
|
||||
<div class="chart-header"><div class="chart-label" style="color:var(--accent)"><i class="fa-solid fa-microchip"></i>CPU</div><span class="chart-cur c-ok">42%</span></div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 52" preserveAspectRatio="none" id="cpu-chart"></svg>
|
||||
<div class="chart-axis"><span>−30min</span><span>−15min</span><span>now</span></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<div class="chart-header"><div class="chart-label" style="color:var(--blue)"><i class="fa-solid fa-memory"></i>RAM</div><span class="chart-cur" style="color:var(--blue)">46%</span></div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 52" preserveAspectRatio="none" id="ram-chart"></svg>
|
||||
<div class="chart-axis"><span>−30min</span><span>−15min</span><span>now</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">STOCKAGE</div>
|
||||
<div class="storage-block">
|
||||
<div class="det-gauges">
|
||||
<div class="dg-row"><div class="dg-ico" data-tip="Utilisé"><i class="fa-solid fa-hard-drive"></i></div><div class="dg-bar"><div class="dg-fill" style="width:31%"></div></div><span class="dg-val">62 / 200 Go</span></div>
|
||||
<div class="dg-row"><div class="dg-ico" data-tip="Libre" style="color:var(--blue)"><i class="fa-solid fa-floppy-disk"></i></div><div class="dg-bar"><div class="dg-fill b" style="width:69%"></div></div><span class="dg-val">138 Go libre</span></div>
|
||||
</div>
|
||||
<div class="smart-btn ok" onclick="showSmart();event.stopPropagation()" data-tip="Voir la santé complète du disque">
|
||||
<div class="smart-dot"></div>
|
||||
<span style="font-weight:600;letter-spacing:.04em">SMART</span>
|
||||
<span style="font-size:10px;color:var(--ink-3)">·</span>
|
||||
<span style="font-size:11px;font-family:var(--font-terminal)">PASSED</span>
|
||||
<span style="font-family:var(--font-mono);font-size:10px;color:var(--ink-3);margin-left:4px"><i class="fa-solid fa-temperature-half"></i> 34°C</span>
|
||||
<i class="fa-solid fa-chevron-right" style="font-size:10px;color:var(--ink-4);margin-left:auto"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">INFORMATIONS</div>
|
||||
<div class="meta-grid">
|
||||
<div class="meta"><div class="meta-lbl">HOSTNAME</div><div class="meta-val">srv-prod-01</div></div>
|
||||
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">10.0.0.11</div></div>
|
||||
<div class="meta"><div class="meta-lbl">PROTOCOLES ACTIFS</div><div class="proto-badges"><span class="proto-badge udp"><i class="fa-solid fa-arrow-up"></i>UDP</span><span class="proto-badge mqtt">M MQTT</span></div></div>
|
||||
<div class="meta"><div class="meta-lbl">DERNIER CONTACT</div><div class="meta-val">22:14:07</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pop-foot">
|
||||
<span class="pop-uptime"><i class="fa-solid fa-clock" style="margin-right:4px"></i>En ligne depuis 14 jours 6 heures</span>
|
||||
<span class="resize-hint" data-tip="Taille sauvegardée sur le serveur"><i class="fa-solid fa-up-right-and-down-left-from-center"></i>Redimensionnable</span>
|
||||
<div class="btn-agent-cfg" onclick="showAgentCfg()" data-tip="Configurer l'agent"><i class="fa-solid fa-gears"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ CONFIG AGENT ══ -->
|
||||
<div class="overlay" id="overlay-agentcfg" style="display:none;z-index:200" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-agentcfg" onclick="event.stopPropagation()">
|
||||
<div class="cfg-head">
|
||||
<div class="cfg-head-icon"><i class="fa-solid fa-gears"></i></div>
|
||||
<div class="cfg-head-info"><div class="cfg-head-title">Configuration de l'agent</div><div class="cfg-head-sub">srv-prod-01 · 10.0.0.11 · config récupérée à 22:14:05</div></div>
|
||||
<div class="pop-close" onclick="this.closest('.overlay').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="cfg-body">
|
||||
|
||||
<!-- PROTOCOLES -->
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">PROTOCOLES DE TRANSPORT</div>
|
||||
<div class="check-row active"><div class="chk-box"><i class="fa-solid fa-check"></i></div><div class="chk-label"><div class="chk-name">UDP <span class="chk-badge udp">UDP</span></div><div class="chk-desc">Fire-and-forget · 10.0.0.50:9999</div></div></div>
|
||||
<div class="check-row mqtt-active"><div class="chk-box" style="background:var(--purple);border-color:var(--purple);color:var(--bg-0)"><i class="fa-solid fa-check"></i></div><div class="chk-label"><div class="chk-name">MQTT <span class="chk-badge mqtt">MQTT</span></div><div class="chk-desc">Bidirectionnel · 10.0.0.3:1883</div></div></div>
|
||||
<div class="mqtt-opts">
|
||||
<div class="mqtt-field"><label>Broker</label><input class="mqtt-input" type="text" value="10.0.0.3"></div>
|
||||
<div class="mqtt-field"><label>Port</label><input class="mqtt-input" type="number" value="1883" style="width:90px;flex:none"></div>
|
||||
<div class="mqtt-field"><label>Topic base</label><input class="mqtt-input" type="text" value="nanometrics/agents"></div>
|
||||
<div style="border-top:1px solid var(--border-1);padding-top:8px;display:flex;flex-direction:column;gap:5px">
|
||||
<div class="mqtt-check-row"><label><i class="fa-solid fa-satellite-dish"></i> Auto-discovery (Home Assistant)</label><label class="toggle tog-mqtt"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="mqtt-check-row"><label><i class="fa-solid fa-arrow-right-to-bracket"></i> Birth message</label><label class="toggle tog-mqtt"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="mqtt-check-row"><label><i class="fa-solid fa-skull"></i> Last Will message</label><label class="toggle tog-mqtt"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MÉTRIQUES — 2 cases par ligne : UDP + MQTT -->
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">MÉTRIQUES ACTIVES PAR PROTOCOLE</div>
|
||||
<div class="metrics-table">
|
||||
<div class="metrics-header">
|
||||
<span class="mh-label">MÉTRIQUE</span>
|
||||
<span class="mh-proto udp"><i class="fa-solid fa-arrow-up"></i> UDP</span>
|
||||
<span class="mh-proto mqtt">M MQTT</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-microchip"></i></div><span class="metric-name">cpu</span></div>
|
||||
<div class="metric-chk"><div class="cbox udp-on" onclick="toggleCbox(this,'udp')" data-tip="CPU via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox mqtt-on" onclick="toggleCbox(this,'mqtt')" data-tip="CPU via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-memory"></i></div><span class="metric-name">memory</span></div>
|
||||
<div class="metric-chk"><div class="cbox udp-on" onclick="toggleCbox(this,'udp')" data-tip="RAM via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox mqtt-on" onclick="toggleCbox(this,'mqtt')" data-tip="RAM via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-hard-drive"></i></div><span class="metric-name">disk</span></div>
|
||||
<div class="metric-chk"><div class="cbox udp-on" onclick="toggleCbox(this,'udp')" data-tip="Disque via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox mqtt-on" onclick="toggleCbox(this,'mqtt')" data-tip="Disque via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-shield-heart"></i></div><span class="metric-name">smart</span></div>
|
||||
<div class="metric-chk"><div class="cbox udp-on" onclick="toggleCbox(this,'udp')" data-tip="SMART via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox" onclick="toggleCbox(this,'mqtt')" data-tip="SMART via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-clock"></i></div><span class="metric-name">uptime</span></div>
|
||||
<div class="metric-chk"><div class="cbox udp-on" onclick="toggleCbox(this,'udp')" data-tip="Uptime via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox mqtt-on" onclick="toggleCbox(this,'mqtt')" data-tip="Uptime via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-network-wired"></i></div><span class="metric-name">network</span></div>
|
||||
<div class="metric-chk"><div class="cbox" onclick="toggleCbox(this,'udp')" data-tip="Réseau via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox" onclick="toggleCbox(this,'mqtt')" data-tip="Réseau via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-thermometer-half"></i></div><span class="metric-name">temperature</span></div>
|
||||
<div class="metric-chk"><div class="cbox" onclick="toggleCbox(this,'udp')" data-tip="Température via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox mqtt-on" onclick="toggleCbox(this,'mqtt')" data-tip="Température via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COMMANDES -->
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">COMMANDES DISTANTES <span style="color:var(--ink-4);font-size:8px;margin-left:6px">— BIENTÔT</span></div>
|
||||
<div class="cmds-grid">
|
||||
<div class="cmd-btn"><i class="fa-solid fa-rotate-right"></i><span>reboot</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-power-off"></i><span>shutdown</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-display"></i><span>screen off</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-arrow-up-from-bracket"></i><span>update</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-arrow-up-right-dots"></i><span>upgrade</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-terminal"></i><span>shell cmd</span><span class="soon-tag">bientôt</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cfg-foot">
|
||||
<div class="cfg-status"><div class="dot"></div><span>Config synchronisée avec l'agent</span></div>
|
||||
<button class="btn" onclick="this.closest('.overlay').style.display='none'">Annuler</button>
|
||||
<button class="btn primary"><i class="fa-solid fa-paper-plane"></i> Envoyer à l'agent</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SMART -->
|
||||
<div class="overlay" id="overlay-smart" style="display:none;z-index:300" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-smart" onclick="event.stopPropagation()">
|
||||
<div class="smart-head">
|
||||
<div class="smart-head-icon"><i class="fa-solid fa-shield-heart"></i></div>
|
||||
<div class="smart-head-info"><div class="smart-head-title">Santé du disque dur</div><div class="smart-head-sub">srv-prod-01 · /dev/sda · Samsung SSD 870 EVO 250GB</div></div>
|
||||
<div class="pop-close" onclick="this.closest('.overlay').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="smart-body">
|
||||
<div class="smart-verdict">
|
||||
<div class="verdict-icon"><i class="fa-solid fa-circle-check"></i></div>
|
||||
<div class="verdict-text"><div class="v-title">Disque en bonne santé</div><div class="v-sub">Aucun problème détecté. Le disque fonctionne normalement et peut être utilisé en toute confiance.</div></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">POINTS DE CONTRÔLE</div>
|
||||
<div class="smart-indicators">
|
||||
<div class="smart-ind"><div class="si-header"><div class="si-icon" style="color:var(--warn)"><i class="fa-solid fa-temperature-half"></i></div><span class="si-title">Température</span><span class="si-status ok">Normale</span></div><div class="si-val">34<span class="u">°C</span></div><div class="si-desc">Idéal : 20-50°C. Au-delà de 60°C le disque risque de s'abîmer prématurément.</div></div>
|
||||
<div class="smart-ind"><div class="si-header"><div class="si-icon" style="color:var(--ok)"><i class="fa-solid fa-circle-check"></i></div><span class="si-title">Secteurs défectueux</span><span class="si-status ok">Aucun</span></div><div class="si-val">0<span class="u"> sect.</span></div><div class="si-desc">S'ils apparaissent en grand nombre, une panne est imminente.</div></div>
|
||||
<div class="smart-ind"><div class="si-header"><div class="si-icon" style="color:var(--blue)"><i class="fa-solid fa-clock-rotate-left"></i></div><span class="si-title">Heures de fonctionnement</span><span class="si-status ok">Jeune</span></div><div class="si-val">4<span class="u">213h</span></div><div class="si-desc">≈175 jours. Un disque dure en moyenne 3 à 5 ans.</div></div>
|
||||
<div class="smart-ind"><div class="si-header"><div class="si-icon" style="color:var(--ok)"><i class="fa-solid fa-battery-full"></i></div><span class="si-title">Durée de vie SSD</span><span class="si-status ok">Excellente</span></div><div class="si-val">98<span class="u">%</span></div><div class="si-desc">100% = neuf · 0% = fin de vie recommandée.</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">ATTRIBUTS DÉTAILLÉS</div>
|
||||
<div class="smart-attrs">
|
||||
<div class="attr-row"><span class="attr-id">001</span><span class="attr-name">Taux d'erreurs de lecture</span><span class="attr-val attr-ok">0</span><span class="attr-explain attr-ok">✓ Parfait</span></div>
|
||||
<div class="attr-row"><span class="attr-id">005</span><span class="attr-name">Secteurs réalloués</span><span class="attr-val attr-ok">0</span><span class="attr-explain attr-ok">✓ Aucun dommage</span></div>
|
||||
<div class="attr-row"><span class="attr-id">009</span><span class="attr-name">Heures de fonctionnement</span><span class="attr-val attr-ok">4213</span><span class="attr-explain">175 jours</span></div>
|
||||
<div class="attr-row"><span class="attr-id">177</span><span class="attr-name">Usure cellules SSD</span><span class="attr-val attr-ok">2</span><span class="attr-explain attr-ok">✓ Très faible</span></div>
|
||||
<div class="attr-row"><span class="attr-id">190</span><span class="attr-name">Température</span><span class="attr-val attr-ok">34°C</span><span class="attr-explain attr-ok">✓ Normale</span></div>
|
||||
<div class="attr-row"><span class="attr-id">197</span><span class="attr-name">Secteurs instables</span><span class="attr-val attr-ok">0</span><span class="attr-explain attr-ok">✓ Aucun</span></div>
|
||||
<div class="attr-row"><span class="attr-id">198</span><span class="attr-name">Secteurs non corrigeables</span><span class="attr-val attr-ok">0</span><span class="attr-explain attr-ok">✓ Aucun</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="smart-footer">
|
||||
<span class="smart-footer-note"><i class="fa-solid fa-circle-info" style="margin-right:4px"></i>Données via smartctl · actualisées à chaque cycle de l'agent</span>
|
||||
<button class="btn primary" onclick="this.closest('.overlay').style.display='none'"><i class="fa-solid fa-xmark"></i> Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* tooltip */
|
||||
const tip=document.getElementById('tooltip');let tt;
|
||||
document.addEventListener('mouseover',e=>{const el=e.target.closest('[data-tip]');if(!el)return;clearTimeout(tt);tt=setTimeout(()=>{tip.textContent=el.dataset.tip;tip.classList.add('show');mv(e);},120);});
|
||||
document.addEventListener('mousemove',e=>{if(tip.classList.contains('show'))mv(e);});
|
||||
document.addEventListener('mouseout',e=>{const el=e.target.closest('[data-tip]');if(!el)return;clearTimeout(tt);tip.classList.remove('show');});
|
||||
function mv(e){const w=tip.offsetWidth,h=tip.offsetHeight;let x=e.clientX-w/2,y=e.clientY-h-10;x=Math.max(6,Math.min(x,window.innerWidth-w-6));if(y<6)y=e.clientY+18;tip.style.left=x+'px';tip.style.top=y+'px';}
|
||||
|
||||
/* thème */
|
||||
function toggleTheme(){const h=document.documentElement;h.dataset.theme=h.dataset.theme==='dark'?'light':'dark';document.getElementById('theme-icon').className=h.dataset.theme==='dark'?'fa-solid fa-moon':'fa-solid fa-sun';drawCharts();}
|
||||
|
||||
/* config serveur */
|
||||
function showSrvCfg(){document.getElementById('overlay-srvcfg').style.display='flex';document.getElementById('btn-srvcfg').classList.add('active-btn');}
|
||||
function hideSrvCfg(){document.getElementById('overlay-srvcfg').style.display='none';document.getElementById('btn-srvcfg').classList.remove('active-btn');}
|
||||
|
||||
/* popups */
|
||||
function showDetail(){document.getElementById('overlay-detail').style.display='flex';drawCharts();}
|
||||
function showAgentCfg(){document.getElementById('overlay-agentcfg').style.display='flex';}
|
||||
function showSmart(){document.getElementById('overlay-smart').style.display='flex';}
|
||||
|
||||
/* checkbox métrique par protocole */
|
||||
function toggleCbox(el,proto){
|
||||
const isOn=el.classList.contains(proto+'-on');
|
||||
el.classList.toggle(proto+'-on',!isOn);
|
||||
el.style.color=isOn?'transparent':'';
|
||||
}
|
||||
|
||||
/* courbes */
|
||||
function makeCurve(pts,stroke,fill,w,h){
|
||||
const xs=pts.map((_,i)=>(i/(pts.length-1))*w);
|
||||
const ys=pts.map(v=>h-(v/100)*(h-6)-3);
|
||||
const wy=h-(70/100)*(h-6)-3;
|
||||
let d=`M${xs[0]} ${ys[0]}`;
|
||||
for(let i=1;i<pts.length;i++){const cx=(xs[i-1]+xs[i])/2;d+=` C${cx} ${ys[i-1]},${cx} ${ys[i]},${xs[i]} ${ys[i]}`;}
|
||||
const uid=Math.random().toString(36).slice(2);
|
||||
return `<defs><linearGradient id="g${uid}" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="${fill}" stop-opacity=".4"/><stop offset="100%" stop-color="${fill}" stop-opacity=".02"/></linearGradient></defs>
|
||||
<line x1="0" y1="${wy}" x2="${w}" y2="${wy}" stroke="var(--warn)" stroke-width=".8" stroke-dasharray="3,3" opacity=".5"/>
|
||||
<path d="${d} L${xs.at(-1)} ${h} L${xs[0]} ${h}Z" fill="url(#g${uid})"/>
|
||||
<path d="${d}" fill="none" stroke="${stroke}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="${xs.at(-1)}" cy="${ys.at(-1)}" r="2.5" fill="${stroke}"/>`;
|
||||
}
|
||||
function drawCharts(){
|
||||
const cpu=[38,41,35,40,44,42,39,45,50,48,43,42,44,41,40,43,47,45,42,44,41,43,42,40,43,41,44,43,41,42];
|
||||
const ram=[44,44,45,45,46,46,47,47,46,46,45,46,46,47,47,46,46,45,46,46,46,46,46,46,46,46,46,46,46,46];
|
||||
const cs=getComputedStyle(document.documentElement);
|
||||
const ac=cs.getPropertyValue('--accent').trim(),bl=cs.getPropertyValue('--blue').trim();
|
||||
document.getElementById('cpu-chart').innerHTML=makeCurve(cpu,ac,ac,200,52);
|
||||
document.getElementById('ram-chart').innerHTML=makeCurve(ram,bl,bl,200,52);
|
||||
}
|
||||
|
||||
/* resize popup → sauvegarde serveur */
|
||||
const pd=document.getElementById('popup-detail');
|
||||
new ResizeObserver(()=>{
|
||||
/* en prod : fetch('/api/config', {method:'PUT', body: JSON.stringify({popup_detail_size:{w:pd.offsetWidth,h:pd.offsetHeight}})}) */
|
||||
console.log('Taille sauvegardée sur serveur:',pd.offsetWidth,'x',pd.offsetHeight);
|
||||
}).observe(pd);
|
||||
|
||||
window.addEventListener('load',drawCharts);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,673 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nanometrics — v9</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script src="/helper.js"></script>
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root[data-theme="dark"]{
|
||||
--accent:#fe8019;--accent-soft:#d65d0e;--accent-glow:rgba(254,128,25,.28);
|
||||
--bg-0:#1d1813;--bg-1:#2a231d;--bg-2:#32291f;--bg-3:#3c332a;--bg-4:#4a3f33;--bg-5:#57493c;
|
||||
--ink-1:#f2e5c7;--ink-2:#d5c4a1;--ink-3:#a89984;--ink-4:#7c6f64;
|
||||
--ok:#4dbb26;--warn:#fabd2f;--err:#fb4934;--blue:#3db0d1;--purple:#c882c8;
|
||||
--border-1:rgba(255,255,255,.06);--border-2:rgba(255,255,255,.12);--border-3:rgba(255,255,255,.26);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,.08) inset,0 -1px 0 rgba(0,0,0,.3) inset,0 6px 20px rgba(0,0,0,.5);
|
||||
--tile-press:inset 0 2px 8px rgba(0,0,0,.5),inset 0 1px 3px rgba(0,0,0,.4);
|
||||
--hover-glow:0 0 0 1px var(--accent-soft),0 0 24px var(--accent-glow),0 6px 20px rgba(0,0,0,.5);
|
||||
--font-ui:'Inter',system-ui,sans-serif;--font-mono:'JetBrains Mono',monospace;--font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
:root[data-theme="light"]{
|
||||
--accent:#af3a03;--accent-soft:#d65d0e;--accent-glow:rgba(175,58,3,.18);
|
||||
--bg-0:#d5c4a1;--bg-1:#ebdbb2;--bg-2:#d5c4a1;--bg-3:#bdae93;--bg-4:#a89984;--bg-5:#928374;
|
||||
--ink-1:#3c3836;--ink-2:#504945;--ink-3:#665c54;--ink-4:#7c6f64;
|
||||
--ok:#3c911c;--warn:#b57614;--err:#9d0006;--blue:#2d82a3;--purple:#8c468c;
|
||||
--border-1:rgba(0,0,0,.08);--border-2:rgba(0,0,0,.15);--border-3:rgba(0,0,0,.3);
|
||||
--tile-3d:0 1px 0 rgba(255,255,255,.55) inset,0 -1px 0 rgba(0,0,0,.08) inset,0 4px 14px rgba(0,0,0,.13);
|
||||
--tile-press:inset 0 2px 6px rgba(0,0,0,.2);
|
||||
--hover-glow:0 0 0 1px var(--accent-soft),0 0 18px var(--accent-glow),0 4px 14px rgba(0,0,0,.13);
|
||||
--font-ui:'Inter',system-ui,sans-serif;--font-mono:'JetBrains Mono',monospace;--font-terminal:'Share Tech Mono',monospace;
|
||||
}
|
||||
body{background:var(--bg-1);color:var(--ink-1);font-family:var(--font-ui);font-size:13px;height:100vh;display:flex;flex-direction:column;overflow:hidden;transition:background .2s,color .2s}
|
||||
#tooltip{position:fixed;z-index:9999;pointer-events:none;background:var(--bg-0);color:var(--ink-1);border:1px solid var(--border-3);border-radius:5px;padding:4px 9px;font-size:11px;font-family:var(--font-ui);white-space:nowrap;opacity:0;transition:opacity .12s;box-shadow:0 4px 12px rgba(0,0,0,.4)}
|
||||
#tooltip.show{opacity:1}
|
||||
|
||||
/* HEADER */
|
||||
.header{background:var(--bg-2);border-bottom:1px solid var(--border-2);padding:0 20px;height:48px;display:flex;align-items:center;gap:12px;flex-shrink:0}
|
||||
.logo{display:flex;align-items:center;gap:8px}
|
||||
.logo-led{width:9px;height:9px;border-radius:50%;background:var(--accent);box-shadow:0 0 8px var(--accent-glow);animation:blink 2s infinite}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.logo-name{font-weight:700;font-size:14px;letter-spacing:.05em;font-family:var(--font-terminal)}
|
||||
.logo-ver{font-size:10px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.h-sep{width:1px;height:24px;background:var(--border-2)}
|
||||
.h-spacer{flex:1}
|
||||
.h-stats{display:flex;gap:14px}
|
||||
.h-stat{display:flex;align-items:center;gap:5px}
|
||||
.h-stat .lbl{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.h-stat .val{font-family:var(--font-mono);font-weight:700;font-size:13px}
|
||||
.c-ok{color:var(--ok)}.c-warn{color:var(--warn)}.c-err{color:var(--err)}.c-n{color:var(--ink-2)}
|
||||
/* user-select:none uniquement sur les éléments interactifs qui ne doivent pas être sélectionnés */
|
||||
.hbtn{width:34px;height:34px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-3);color:var(--ink-2);font-size:14px;display:flex;align-items:center;justify-content:center;cursor:pointer;user-select:none;transition:background .12s,color .12s,transform .08s,box-shadow .08s}
|
||||
.hbtn:hover{background:var(--bg-4);color:var(--accent)}
|
||||
.hbtn:active{transform:translateY(1px) scale(.96);box-shadow:var(--tile-press)}
|
||||
.hbtn.active-btn{background:var(--accent);color:var(--bg-0);border-color:var(--accent-soft)}
|
||||
|
||||
/* GRID */
|
||||
.main{flex:1;padding:14px 16px;overflow-y:auto}
|
||||
.agents-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px}
|
||||
/* user-select retiré du .tile — le texte (hostname, IP, valeurs) doit rester sélectionnable */
|
||||
.tile{background:var(--bg-3);border-radius:10px;padding:12px 14px;border:1px solid var(--border-1);box-shadow:var(--tile-3d);cursor:pointer;display:flex;flex-direction:column;gap:9px;transition:box-shadow .15s,transform .08s,border-color .15s}
|
||||
.tile:hover{box-shadow:var(--hover-glow);border-color:var(--accent-soft)}
|
||||
.tile:active{transform:translateY(2px) scale(.99);box-shadow:var(--tile-press)}
|
||||
.tile.t-warn{border-color:rgba(250,189,47,.3)}.tile.t-warn:hover{border-color:var(--warn);box-shadow:0 0 0 1px var(--warn),0 0 22px rgba(250,189,47,.22),0 6px 20px rgba(0,0,0,.5)}
|
||||
.tile.t-err{border-color:rgba(251,73,52,.35)}.tile.t-err:hover{border-color:var(--err);box-shadow:0 0 0 1px var(--err),0 0 22px rgba(251,73,52,.25),0 6px 20px rgba(0,0,0,.5)}
|
||||
.tile.t-off{opacity:.5;cursor:default}.tile.t-off:hover,.tile.t-off:active{box-shadow:var(--tile-3d);border-color:var(--border-1);transform:none}
|
||||
.tile-head{display:flex;align-items:center;gap:8px;user-select:none}
|
||||
.t-icon{width:28px;height:28px;border-radius:7px;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--accent);font-size:13px;flex-shrink:0;overflow:hidden}
|
||||
.t-names{flex:1;min-width:0}
|
||||
.t-host{font-weight:600;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.t-ip{font-family:var(--font-mono);font-size:10px;color:var(--ink-4)}
|
||||
.t-led{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
||||
.s-ok{background:var(--ok);box-shadow:0 0 6px var(--ok)}.s-warn{background:var(--warn);box-shadow:0 0 6px var(--warn);animation:blink 1.5s infinite}.s-err{background:var(--err);box-shadow:0 0 8px var(--err);animation:blink 1s infinite}.s-off{background:var(--ink-4)}
|
||||
.tile-gauges{display:flex;flex-direction:column;gap:5px}
|
||||
.g-row{display:flex;align-items:center;gap:7px}
|
||||
.g-ico{width:18px;height:18px;display:flex;align-items:center;justify-content:center;font-size:11px;color:var(--ink-3);flex-shrink:0;cursor:help}
|
||||
.g-bar{flex:1;height:5px;border-radius:3px;background:var(--bg-1);overflow:hidden}
|
||||
.g-fill{height:100%;border-radius:3px;background:var(--ok)}
|
||||
.g-fill.w{background:var(--warn)}.g-fill.e{background:var(--err)}
|
||||
.g-val{font-family:var(--font-mono);font-size:11px;color:var(--ink-2);width:34px;text-align:right}
|
||||
.tile-foot{font-family:var(--font-terminal);font-size:10px;color:var(--ink-4);display:flex;align-items:center;gap:5px;user-select:none}
|
||||
.tile-foot i{font-size:9px}
|
||||
|
||||
/* FOOTER */
|
||||
.footer{background:var(--bg-0);border-top:1px solid var(--border-2);height:26px;display:flex;align-items:center;font-family:var(--font-terminal);font-size:11px;color:var(--ink-4);flex-shrink:0}
|
||||
.f-mode{background:var(--accent);color:var(--bg-0);padding:0 12px;height:100%;display:flex;align-items:center;font-weight:700;letter-spacing:.04em;user-select:none}
|
||||
.f-cell{padding:0 12px;border-right:1px solid var(--border-1);display:flex;align-items:center;gap:5px;height:100%}
|
||||
.f-val{font-family:var(--font-mono);color:var(--ink-2)}.f-val.w{color:var(--warn)}
|
||||
.f-minibar{width:36px;height:4px;border-radius:2px;background:var(--bg-3);overflow:hidden}
|
||||
.f-minifill{height:100%;border-radius:2px;background:var(--ok)}.f-minifill.w{background:var(--warn)}
|
||||
.f-spacer{flex:1}.f-right{padding:0 12px;display:flex;align-items:center;gap:6px;color:var(--ink-3)}
|
||||
.f-time{font-family:var(--font-mono);color:var(--ink-2)}
|
||||
|
||||
/* OVERLAY */
|
||||
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.65);z-index:100;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px)}
|
||||
|
||||
/* POPUP BASE */
|
||||
.popup{background:var(--bg-2);border:1px solid var(--border-3);border-radius:12px;box-shadow:0 24px 64px rgba(0,0,0,.7);display:flex;flex-direction:column;overflow:hidden}
|
||||
.pop-close{width:28px;height:28px;border-radius:6px;background:var(--bg-5);color:var(--ink-3);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:12px;border:1px solid var(--border-1);user-select:none;transition:background .12s,color .12s,transform .08s}
|
||||
.pop-close:hover{background:var(--err);color:#fff}
|
||||
.pop-close:active{transform:translateY(1px) scale(.93);box-shadow:var(--tile-press)}
|
||||
.sec-title{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.08em;margin-bottom:8px}
|
||||
.btn{padding:6px 14px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-4);color:var(--ink-2);font-size:12px;font-family:var(--font-ui);cursor:pointer;display:flex;align-items:center;gap:6px;user-select:none;transition:background .1s,transform .08s,box-shadow .08s}
|
||||
.btn:hover{background:var(--bg-5)}.btn:active{transform:translateY(1px) scale(.97);box-shadow:var(--tile-press)}
|
||||
.btn.primary{background:var(--accent);color:var(--bg-0);border-color:var(--accent-soft);font-weight:600}.btn.primary:hover{background:var(--accent-soft)}
|
||||
|
||||
/* ══ CONFIG SERVEUR (header) ══ */
|
||||
#overlay-srvcfg{z-index:150}
|
||||
#popup-srvcfg{width:400px;max-width:96vw;max-height:88vh}
|
||||
.scfg-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:10px}
|
||||
.scfg-icon{width:32px;height:32px;border-radius:8px;background:var(--accent);display:flex;align-items:center;justify-content:center;color:var(--bg-0);font-size:15px}
|
||||
.scfg-title{flex:1;font-weight:700;font-size:14px}
|
||||
.scfg-body{padding:18px;display:flex;flex-direction:column;gap:14px;overflow-y:auto}
|
||||
.scfg-section{display:flex;flex-direction:column;gap:8px}
|
||||
.scfg-sec-title{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.08em;padding-bottom:6px;border-bottom:1px solid var(--border-1)}
|
||||
.scfg-row{display:flex;align-items:center;gap:10px}
|
||||
.scfg-row > label{font-size:12px;color:var(--ink-3);width:110px;flex-shrink:0;font-family:var(--font-terminal)}
|
||||
.scfg-slider{flex:1;accent-color:var(--accent)}
|
||||
.scfg-val{font-family:var(--font-mono);font-size:12px;color:var(--ink-2);width:48px;text-align:right}
|
||||
.scfg-select{flex:1;background:var(--bg-3);border:1px solid var(--border-2);border-radius:6px;color:var(--ink-1);padding:6px 10px;font-size:12px;font-family:var(--font-ui)}
|
||||
.scfg-toggle-row{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;background:var(--bg-3);border-radius:7px;border:1px solid var(--border-1)}
|
||||
.scfg-toggle-row label{font-size:12px;color:var(--ink-2);display:flex;align-items:center;gap:7px;cursor:pointer}
|
||||
.scfg-toggle-row label i{color:var(--accent);font-size:12px}
|
||||
.scfg-foot{padding:12px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;gap:8px;justify-content:flex-end}
|
||||
.toggle{position:relative;width:34px;height:18px;flex-shrink:0}
|
||||
.toggle input{opacity:0;width:0;height:0}
|
||||
.toggle-slider{position:absolute;inset:0;border-radius:9px;background:var(--bg-4);border:1px solid var(--border-2);cursor:pointer;transition:background .2s}
|
||||
.toggle-slider::before{content:'';position:absolute;width:12px;height:12px;border-radius:50%;background:var(--ink-4);top:2px;left:2px;transition:transform .2s,background .2s}
|
||||
.toggle input:checked+.toggle-slider{background:rgba(254,128,25,.3);border-color:var(--accent)}
|
||||
.toggle input:checked+.toggle-slider::before{transform:translateX(16px);background:var(--accent)}
|
||||
.tog-ok input:checked+.toggle-slider{background:rgba(77,187,38,.3);border-color:var(--ok)}
|
||||
.tog-ok input:checked+.toggle-slider::before{background:var(--ok)}
|
||||
|
||||
/* ══ POPUP DÉTAIL AGENT — redimensionnable ══ */
|
||||
#popup-detail{width:560px;max-width:96vw;max-height:92vh;resize:both;overflow:hidden;min-width:400px;min-height:320px}
|
||||
#popup-detail .pop-body{overflow-y:auto;flex:1}
|
||||
.pop-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:12px;flex-shrink:0}
|
||||
.agent-icon-wrap{position:relative;width:44px;height:44px;border-radius:10px;flex-shrink:0;cursor:pointer;overflow:hidden;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--accent);font-size:18px;border:2px solid var(--border-2);transition:border-color .15s}
|
||||
.agent-icon-wrap:hover{border-color:var(--accent)}
|
||||
.agent-icon-overlay{position:absolute;inset:0;background:rgba(0,0,0,.6);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;opacity:0;transition:opacity .15s;font-size:10px;color:#fff}
|
||||
.agent-icon-overlay i{font-size:14px}
|
||||
.agent-icon-wrap:hover .agent-icon-overlay{opacity:1}
|
||||
#icon-upload{display:none}
|
||||
.pop-head-info{flex:1}
|
||||
.pop-host{font-weight:700;font-size:15px}
|
||||
.pop-ip{font-family:var(--font-mono);font-size:11px;color:var(--ink-4)}
|
||||
.upload-hint{font-size:10px;color:var(--ink-4);font-family:var(--font-terminal);margin-top:2px}
|
||||
.pop-led{width:10px;height:10px;border-radius:50%;background:var(--ok);box-shadow:0 0 8px var(--ok);flex-shrink:0}
|
||||
.pop-body{padding:16px 18px;display:flex;flex-direction:column;gap:14px}
|
||||
.kpi-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:7px}
|
||||
.kpi{background:var(--bg-3);border-radius:8px;padding:10px 12px;border:1px solid var(--border-1);box-shadow:var(--tile-3d)}
|
||||
.kpi-lbl{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.kpi-val{font-family:var(--font-mono);font-size:20px;font-weight:700;line-height:1.1;margin-top:2px}
|
||||
.kpi-val .u{font-size:10px;color:var(--ink-3);font-weight:400}
|
||||
.kpi-sub{font-size:10px;color:var(--ink-4);font-family:var(--font-mono);margin-top:2px}
|
||||
.charts-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||||
.chart-card{background:var(--bg-3);border-radius:8px;padding:10px 12px;border:1px solid var(--border-1);box-shadow:var(--tile-3d)}
|
||||
.chart-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
|
||||
.chart-label{display:flex;align-items:center;gap:6px;font-size:10px;font-family:var(--font-terminal);letter-spacing:.06em;color:var(--ink-3)}
|
||||
.chart-cur{font-family:var(--font-mono);font-size:16px;font-weight:700}
|
||||
.chart-svg{width:100%;height:52px;display:block}
|
||||
.chart-axis{display:flex;justify-content:space-between;margin-top:2px;font-family:var(--font-terminal);font-size:9px;color:var(--ink-4)}
|
||||
.storage-block{display:flex;flex-direction:column;gap:8px}
|
||||
.det-gauges{display:flex;flex-direction:column;gap:7px}
|
||||
.dg-row{display:flex;align-items:center;gap:10px}
|
||||
.dg-ico{width:22px;text-align:center;font-size:13px;cursor:help}
|
||||
.dg-bar{flex:1;height:7px;border-radius:4px;background:var(--bg-1);overflow:hidden}
|
||||
.dg-fill{height:100%;border-radius:4px;background:var(--ok)}.dg-fill.b{background:var(--blue)}
|
||||
.dg-val{font-family:var(--font-mono);font-size:12px;color:var(--ink-2);width:90px;text-align:right}
|
||||
/* user-select retiré du smart-btn — "SMART PASSED 34°C" doit être sélectionnable */
|
||||
.smart-btn{display:inline-flex;align-items:center;gap:8px;padding:7px 12px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-3);cursor:pointer;transition:background .12s,border-color .12s,transform .08s;font-family:var(--font-terminal);font-size:11px}
|
||||
.smart-btn:hover{background:var(--bg-4);border-color:var(--border-3)}.smart-btn:active{transform:translateY(1px);box-shadow:var(--tile-press)}
|
||||
.smart-btn.ok{border-color:rgba(77,187,38,.3);color:var(--ok)}.smart-dot{width:7px;height:7px;border-radius:50%;background:var(--ok);box-shadow:0 0 5px var(--ok)}
|
||||
.meta-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px}
|
||||
.meta{background:var(--bg-3);border-radius:6px;padding:8px 10px;border:1px solid var(--border-1)}
|
||||
.meta-lbl{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.meta-val{font-family:var(--font-mono);font-size:12px;color:var(--ink-2);margin-top:2px}
|
||||
.proto-badges{display:flex;gap:5px;margin-top:4px}
|
||||
.proto-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:999px;font-size:10px;font-family:var(--font-terminal);font-weight:600}
|
||||
.proto-badge.udp{background:rgba(61,176,209,.15);color:var(--blue);border:1px solid rgba(61,176,209,.3)}
|
||||
.proto-badge.mqtt{background:rgba(200,130,200,.15);color:var(--purple);border:1px solid rgba(200,130,200,.3)}
|
||||
.pop-foot{padding:10px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;align-items:center;gap:8px;flex-shrink:0}
|
||||
.pop-uptime{font-family:var(--font-terminal);font-size:11px;color:var(--ink-4);flex:1}
|
||||
.resize-hint{font-family:var(--font-terminal);font-size:9px;color:var(--ink-4);display:flex;align-items:center;gap:4px}
|
||||
.btn-agent-cfg{width:34px;height:34px;border-radius:8px;border:1px solid var(--border-2);background:var(--bg-4);color:var(--ink-3);font-size:15px;display:flex;align-items:center;justify-content:center;cursor:pointer;user-select:none;transition:background .12s,color .12s,transform .08s,box-shadow .08s}
|
||||
.btn-agent-cfg:hover{background:var(--bg-5);color:var(--accent)}.btn-agent-cfg:active{transform:translateY(1px) scale(.93);box-shadow:var(--tile-press)}
|
||||
|
||||
/* ══ CONFIG AGENT ══ */
|
||||
#overlay-agentcfg{z-index:200}
|
||||
#popup-agentcfg{width:520px;max-width:96vw;max-height:90vh}
|
||||
.cfg-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:10px}
|
||||
.cfg-head-icon{width:32px;height:32px;border-radius:8px;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--accent);font-size:15px}
|
||||
.cfg-head-info{flex:1}.cfg-head-title{font-weight:700;font-size:14px}.cfg-head-sub{font-size:11px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.cfg-body{padding:18px;display:flex;flex-direction:column;gap:16px;overflow-y:auto;max-height:62vh}
|
||||
.cfg-section{display:flex;flex-direction:column;gap:8px}
|
||||
.cfg-sec-title{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.08em;padding-bottom:6px;border-bottom:1px solid var(--border-1)}
|
||||
/* paramètres MQTT */
|
||||
.mqtt-opts{background:var(--bg-3);border-radius:8px;border:1px solid rgba(200,130,200,.2);padding:12px 14px;display:flex;flex-direction:column;gap:10px}
|
||||
.mqtt-field{display:flex;align-items:center;gap:10px}
|
||||
.mqtt-field label{font-size:11px;color:var(--ink-3);font-family:var(--font-terminal);width:90px;flex-shrink:0}
|
||||
.mqtt-input{flex:1;background:var(--bg-1);border:1px solid var(--border-2);border-radius:6px;color:var(--ink-1);padding:6px 10px;font-size:12px;font-family:var(--font-mono)}
|
||||
.mqtt-input:focus{outline:none;border-color:var(--purple)}
|
||||
.mqtt-check-row{display:flex;align-items:center;justify-content:space-between;padding:3px 0}
|
||||
.mqtt-check-row label{font-size:12px;color:var(--ink-2);display:flex;align-items:center;gap:7px;cursor:pointer}
|
||||
.mqtt-check-row label i{color:var(--purple);font-size:11px}
|
||||
.tog-mqtt input:checked+.toggle-slider{background:rgba(200,130,200,.3);border-color:var(--purple)}
|
||||
.tog-mqtt input:checked+.toggle-slider::before{background:var(--purple)}
|
||||
|
||||
/* ══ MÉTRIQUES — tableau 3 colonnes : métrique / UDP / MQTT ══ */
|
||||
.metrics-table{display:flex;flex-direction:column;gap:0;border:1px solid var(--border-2);border-radius:8px;overflow:hidden}
|
||||
.metrics-header{display:grid;grid-template-columns:1fr 56px 56px;background:var(--bg-4);padding:8px 12px;gap:4px;align-items:center}
|
||||
.mh-label{font-size:9px;color:var(--ink-4);font-family:var(--font-terminal);letter-spacing:.06em}
|
||||
.mh-proto{font-size:9px;font-family:var(--font-terminal);font-weight:700;letter-spacing:.04em;text-align:center;display:flex;flex-direction:column;align-items:center;gap:2px}
|
||||
.mh-proto.udp{color:var(--blue)}.mh-proto.mqtt{color:var(--purple)}
|
||||
.mh-proto .proto-dot{width:5px;height:5px;border-radius:50%;margin-top:1px}
|
||||
.mh-proto.udp .proto-dot{background:var(--blue)}.mh-proto.mqtt .proto-dot{background:var(--purple)}
|
||||
.metric-row{display:grid;grid-template-columns:1fr 56px 56px;padding:8px 12px;gap:4px;align-items:center;background:var(--bg-3);border-top:1px solid var(--border-1);transition:background .1s}
|
||||
.metric-row:hover{background:var(--bg-4)}
|
||||
.metric-cell{display:flex;align-items:center;gap:8px}
|
||||
.metric-ico{font-size:13px;color:var(--ink-3);width:16px;text-align:center}
|
||||
.metric-name{font-size:12px;color:var(--ink-2);font-family:var(--font-terminal)}
|
||||
.metric-chk{display:flex;justify-content:center}
|
||||
/* checkbox compact — user-select:none pour éviter la sélection au clic rapide */
|
||||
.cbox{width:20px;height:20px;border-radius:5px;border:2px solid var(--border-2);background:var(--bg-1);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:11px;color:transparent;transition:background .12s,border-color .12s,color .12s,transform .08s;user-select:none;flex-shrink:0}
|
||||
.cbox:hover{border-color:var(--border-3);transform:scale(1.08)}
|
||||
.cbox:active{transform:scale(.93)}
|
||||
.cbox.udp-on{background:rgba(61,176,209,.18);border-color:var(--blue);color:var(--blue)}
|
||||
.cbox.mqtt-on{background:rgba(200,130,200,.18);border-color:var(--purple);color:var(--purple)}
|
||||
|
||||
/* commandes */
|
||||
.cmds-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
|
||||
.cmd-btn{display:flex;flex-direction:column;align-items:center;gap:4px;padding:10px 8px;border-radius:8px;background:var(--bg-3);border:1px solid var(--border-1);cursor:not-allowed;opacity:.4}
|
||||
.cmd-btn i{font-size:16px;color:var(--ink-3)}.cmd-btn span{font-size:10px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.soon-tag{font-size:8px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.cfg-foot{padding:12px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;align-items:center;gap:8px}
|
||||
.cfg-status{flex:1;display:flex;align-items:center;gap:6px;font-family:var(--font-terminal);font-size:11px;color:var(--ink-4)}
|
||||
.cfg-status .dot{width:6px;height:6px;border-radius:50%;background:var(--ok)}
|
||||
|
||||
/* SMART */
|
||||
#overlay-smart{z-index:300}
|
||||
#popup-smart{width:500px;max-width:96vw;max-height:88vh}
|
||||
.smart-head{background:var(--bg-3);padding:14px 18px;border-bottom:1px solid var(--border-2);display:flex;align-items:center;gap:10px}
|
||||
.smart-head-icon{width:32px;height:32px;border-radius:8px;background:var(--bg-4);display:flex;align-items:center;justify-content:center;color:var(--ok);font-size:15px}
|
||||
.smart-head-info{flex:1}.smart-head-title{font-weight:700;font-size:14px}.smart-head-sub{font-size:11px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
.smart-body{padding:18px;display:flex;flex-direction:column;gap:16px;overflow-y:auto;max-height:70vh}
|
||||
.smart-verdict{display:flex;align-items:center;gap:14px;background:rgba(77,187,38,.1);border:1px solid rgba(77,187,38,.3);border-radius:10px;padding:14px 18px}
|
||||
.verdict-icon{font-size:28px;color:var(--ok)}
|
||||
.verdict-text .v-title{font-size:16px;font-weight:700;color:var(--ok)}
|
||||
.verdict-text .v-sub{font-size:12px;color:var(--ink-3);margin-top:3px}
|
||||
.smart-indicators{display:grid;grid-template-columns:1fr 1fr;gap:8px}
|
||||
.smart-ind{background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1);box-shadow:var(--tile-3d)}
|
||||
.si-header{display:flex;align-items:center;gap:8px;margin-bottom:6px}
|
||||
.si-icon{font-size:14px;width:22px;text-align:center}
|
||||
.si-title{font-weight:600;font-size:12px;flex:1}
|
||||
.si-status{font-size:10px;font-family:var(--font-terminal);font-weight:700;padding:1px 7px;border-radius:999px}
|
||||
.si-status.ok{background:rgba(77,187,38,.15);color:var(--ok)}
|
||||
.si-val{font-family:var(--font-mono);font-size:18px;font-weight:700;color:var(--ink-1)}
|
||||
.si-val .u{font-size:11px;color:var(--ink-3);font-weight:400}
|
||||
.si-desc{font-size:11px;color:var(--ink-3);margin-top:4px;line-height:1.4}
|
||||
.smart-attrs{display:flex;flex-direction:column;gap:5px}
|
||||
.attr-row{display:flex;align-items:center;gap:10px;padding:6px 10px;border-radius:6px;background:var(--bg-3);border:1px solid var(--border-1)}
|
||||
.attr-id{font-family:var(--font-mono);font-size:10px;color:var(--ink-4);width:28px;flex-shrink:0}
|
||||
.attr-name{flex:1;font-size:11px;color:var(--ink-2)}
|
||||
.attr-val{font-family:var(--font-mono);font-size:11px;font-weight:600;width:40px;text-align:right}
|
||||
.attr-explain{font-size:10px;color:var(--ink-4);width:130px;text-align:right;font-family:var(--font-terminal)}
|
||||
.attr-ok{color:var(--ok)}
|
||||
.smart-footer{padding:12px 18px;border-top:1px solid var(--border-2);background:var(--bg-3);display:flex;align-items:center;gap:8px}
|
||||
.smart-footer-note{flex:1;font-size:10px;color:var(--ink-4);font-family:var(--font-terminal)}
|
||||
|
||||
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:var(--bg-1)}::-webkit-scrollbar-thumb{background:var(--bg-4);border-radius:3px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="tooltip"></div>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="logo"><div class="logo-led"></div><span class="logo-name">NANOMETRICS</span><span class="logo-ver">v1.0</span></div>
|
||||
<div class="h-sep"></div>
|
||||
<div class="h-stats">
|
||||
<div class="h-stat"><span class="lbl">AGENTS</span><span class="val c-n">8</span></div>
|
||||
<div class="h-stat"><span class="lbl">OK</span><span class="val c-ok">5</span></div>
|
||||
<div class="h-stat"><span class="lbl">WARN</span><span class="val c-warn">2</span></div>
|
||||
<div class="h-stat"><span class="lbl">ERR</span><span class="val c-err">1</span></div>
|
||||
</div>
|
||||
<div class="h-spacer"></div>
|
||||
<div class="hbtn" onclick="toggleTheme()" data-tip="Thème clair / sombre"><i class="fa-solid fa-moon" id="theme-icon"></i></div>
|
||||
<div class="hbtn" id="btn-srvcfg" onclick="showSrvCfg()" data-tip="Configuration serveur / interface"><i class="fa-solid fa-sliders"></i></div>
|
||||
</div>
|
||||
|
||||
<!-- GRID -->
|
||||
<div class="main">
|
||||
<div class="agents-grid">
|
||||
<div class="tile" onclick="showDetail()">
|
||||
<div class="tile-head"><div class="t-icon"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-prod-01</div><div class="t-ip">10.0.0.11</div></div><div class="t-led s-ok"></div></div>
|
||||
<div class="tile-gauges">
|
||||
<div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:42%"></div></div><span class="g-val">42%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:58%"></div></div><span class="g-val">58%</span></div>
|
||||
<div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:31%"></div></div><span class="g-val">31%</span></div>
|
||||
</div>
|
||||
<div class="tile-foot"><i class="fa-solid fa-clock"></i>14j 6h</div>
|
||||
</div>
|
||||
<div class="tile t-warn"><div class="tile-head"><div class="t-icon"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-backup-02</div><div class="t-ip">10.0.0.12</div></div><div class="t-led s-warn"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU élevé" style="color:var(--warn)"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill w" style="width:78%"></div></div><span class="g-val" style="color:var(--warn)">78%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM élevée" style="color:var(--warn)"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill w" style="width:72%"></div></div><span class="g-val" style="color:var(--warn)">72%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:20%"></div></div><span class="g-val">20%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>3j 14h</div></div>
|
||||
<div class="tile t-err"><div class="tile-head"><div class="t-icon" style="color:var(--err)"><i class="fa-solid fa-microchip"></i></div><div class="t-names"><div class="t-host">rpi-sensor-03</div><div class="t-ip">10.0.0.23</div></div><div class="t-led s-err"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:12%"></div></div><span class="g-val">12%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:38%"></div></div><span class="g-val">38%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque critique !" style="color:var(--err)"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill e" style="width:94%"></div></div><span class="g-val" style="color:var(--err)">94%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>62j 1h</div></div>
|
||||
<div class="tile"><div class="tile-head"><div class="t-icon"><i class="fa-solid fa-hard-drive"></i></div><div class="t-names"><div class="t-host">nas-storage-04</div><div class="t-ip">10.0.0.30</div></div><div class="t-led s-ok"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico" data-tip="CPU"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"><div class="g-fill" style="width:8%"></div></div><span class="g-val">8%</span></div><div class="g-row"><div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div><div class="g-bar"><div class="g-fill" style="width:45%"></div></div><span class="g-val">45%</span></div><div class="g-row"><div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"><div class="g-fill" style="width:66%"></div></div><span class="g-val">66%</span></div></div><div class="tile-foot"><i class="fa-solid fa-clock"></i>128j 3h</div></div>
|
||||
<div class="tile t-off"><div class="tile-head"><div class="t-icon" style="color:var(--ink-4)"><i class="fa-solid fa-server"></i></div><div class="t-names"><div class="t-host">srv-dev-07</div><div class="t-ip">10.0.0.60</div></div><div class="t-led s-off"></div></div><div class="tile-gauges"><div class="g-row"><div class="g-ico"><i class="fa-solid fa-microchip"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div><div class="g-row"><div class="g-ico"><i class="fa-solid fa-memory"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div><div class="g-row"><div class="g-ico"><i class="fa-solid fa-hard-drive"></i></div><div class="g-bar"></div><span class="g-val" style="color:var(--ink-4)">—</span></div></div><div class="tile-foot" style="color:var(--err)"><i class="fa-solid fa-circle-xmark"></i>Hors ligne · vu il y a 2h</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
<div class="f-mode">LIVE</div>
|
||||
<div class="f-cell"><i class="fa-solid fa-server" style="font-size:10px"></i><span>SERVEUR</span></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-microchip" style="font-size:10px"></i><span class="f-val">18%</span><div class="f-minibar"><div class="f-minifill" style="width:18%"></div></div></div>
|
||||
<div class="f-cell"><i class="fa-solid fa-memory" style="font-size:10px"></i><span class="f-val w">71%</span><div class="f-minibar"><div class="f-minifill w" style="width:71%"></div></div></div>
|
||||
<div class="f-spacer"></div>
|
||||
<div class="f-right"><i class="fa-solid fa-rotate"></i><span>Actualisation : <span class="f-time">22:14:07</span></span></div>
|
||||
</div>
|
||||
|
||||
<!-- ══ CONFIG SERVEUR ══ -->
|
||||
<div class="overlay" id="overlay-srvcfg" style="display:none" onclick="if(event.target===this)hideSrvCfg()">
|
||||
<div class="popup" id="popup-srvcfg" onclick="event.stopPropagation()">
|
||||
<div class="scfg-head">
|
||||
<div class="scfg-icon"><i class="fa-solid fa-sliders"></i></div>
|
||||
<span class="scfg-title">Configuration interface</span>
|
||||
<div class="pop-close" onclick="hideSrvCfg()" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="scfg-body">
|
||||
<div class="scfg-section">
|
||||
<div class="scfg-sec-title">AFFICHAGE DES TUILES</div>
|
||||
<div class="scfg-row">
|
||||
<label>Largeur min.</label>
|
||||
<input type="range" class="scfg-slider" min="160" max="420" value="220" oninput="this.nextElementSibling.textContent=this.value+'px'">
|
||||
<span class="scfg-val">220px</span>
|
||||
</div>
|
||||
<div class="scfg-row">
|
||||
<label>Taille du texte</label>
|
||||
<input type="range" class="scfg-slider" min="10" max="18" value="13" oninput="this.nextElementSibling.textContent=this.value+'px'">
|
||||
<span class="scfg-val">13px</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scfg-section">
|
||||
<div class="scfg-sec-title">SEUILS D'ALERTE PAR DÉFAUT</div>
|
||||
<div class="scfg-row">
|
||||
<label>Warning CPU/RAM</label>
|
||||
<input type="range" class="scfg-slider" min="50" max="95" value="70" oninput="this.nextElementSibling.textContent=this.value+'%'">
|
||||
<span class="scfg-val">70%</span>
|
||||
</div>
|
||||
<div class="scfg-row">
|
||||
<label>Erreur CPU/RAM</label>
|
||||
<input type="range" class="scfg-slider" min="60" max="100" value="85" oninput="this.nextElementSibling.textContent=this.value+'%'">
|
||||
<span class="scfg-val">85%</span>
|
||||
</div>
|
||||
<div class="scfg-row">
|
||||
<label>Warning Disque</label>
|
||||
<input type="range" class="scfg-slider" min="50" max="95" value="75" oninput="this.nextElementSibling.textContent=this.value+'%'">
|
||||
<span class="scfg-val">75%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scfg-section">
|
||||
<div class="scfg-sec-title">DONNÉES & RÉTENTION</div>
|
||||
<div class="scfg-row">
|
||||
<label>Historique</label>
|
||||
<select class="scfg-select"><option>7 jours</option><option selected>30 jours</option><option>90 jours</option><option>1 an</option></select>
|
||||
</div>
|
||||
<div class="scfg-row">
|
||||
<label>Courbes (durée)</label>
|
||||
<select class="scfg-select"><option>15 min</option><option selected>30 min</option><option>1 heure</option><option>6 heures</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scfg-section">
|
||||
<div class="scfg-sec-title">COMPORTEMENT</div>
|
||||
<div class="scfg-toggle-row">
|
||||
<label><i class="fa-solid fa-eye-slash"></i> Masquer les agents hors ligne</label>
|
||||
<label class="toggle"><input type="checkbox"><span class="toggle-slider"></span></label>
|
||||
</div>
|
||||
<div class="scfg-toggle-row">
|
||||
<label><i class="fa-solid fa-bell"></i> Notifications navigateur</label>
|
||||
<label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
|
||||
</div>
|
||||
<div class="scfg-toggle-row">
|
||||
<label><i class="fa-solid fa-up-right-and-down-left-from-center"></i> Mémoriser taille des popups</label>
|
||||
<label class="toggle"><input type="checkbox" checked><span class="toggle-slider"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scfg-foot">
|
||||
<button class="btn" onclick="hideSrvCfg()">Annuler</button>
|
||||
<button class="btn primary" onclick="hideSrvCfg()"><i class="fa-solid fa-floppy-disk"></i> Sauvegarder</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ POPUP DÉTAIL AGENT ══ -->
|
||||
<div class="overlay" id="overlay-detail" style="display:flex" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-detail" onclick="event.stopPropagation()">
|
||||
<div class="pop-head">
|
||||
<div class="agent-icon-wrap" onclick="document.getElementById('icon-upload').click()" data-tip="Changer l'icône">
|
||||
<i class="fa-solid fa-server"></i>
|
||||
<div class="agent-icon-overlay"><i class="fa-solid fa-camera"></i><span>Changer</span></div>
|
||||
</div>
|
||||
<input type="file" id="icon-upload" accept=".svg,.jpg,.jpeg,.png,.webp">
|
||||
<div class="pop-head-info">
|
||||
<div class="pop-host">srv-prod-01</div>
|
||||
<div class="pop-ip">10.0.0.11</div>
|
||||
<div class="upload-hint">Cliquer sur l'icône pour personnaliser · SVG JPG PNG WEBP · max 128×128 px</div>
|
||||
</div>
|
||||
<div class="pop-led"></div>
|
||||
<div class="pop-close" onclick="document.getElementById('overlay-detail').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="pop-body">
|
||||
<div>
|
||||
<div class="sec-title">MÉTRIQUES ACTUELLES</div>
|
||||
<div class="kpi-grid">
|
||||
<div class="kpi"><div class="kpi-lbl">CPU</div><div class="kpi-val c-ok">42<span class="u">%</span></div><div class="kpi-sub">4 cœurs</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">MÉMOIRE</div><div class="kpi-val">3.7<span class="u">Go</span></div><div class="kpi-sub">/ 8 Go · 46%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">DISQUE</div><div class="kpi-val">62<span class="u">Go</span></div><div class="kpi-sub">/ 200 Go · 31%</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">UPTIME</div><div class="kpi-val" style="font-size:15px;color:var(--ink-1)">14j 6h</div><div class="kpi-sub">depuis boot</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">HISTORIQUE — 30 MIN</div>
|
||||
<div class="charts-grid">
|
||||
<div class="chart-card">
|
||||
<div class="chart-header"><div class="chart-label" style="color:var(--accent)"><i class="fa-solid fa-microchip"></i>CPU</div><span class="chart-cur c-ok">42%</span></div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 52" preserveAspectRatio="none" id="cpu-chart"></svg>
|
||||
<div class="chart-axis"><span>−30min</span><span>−15min</span><span>now</span></div>
|
||||
</div>
|
||||
<div class="chart-card">
|
||||
<div class="chart-header"><div class="chart-label" style="color:var(--blue)"><i class="fa-solid fa-memory"></i>RAM</div><span class="chart-cur" style="color:var(--blue)">46%</span></div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 52" preserveAspectRatio="none" id="ram-chart"></svg>
|
||||
<div class="chart-axis"><span>−30min</span><span>−15min</span><span>now</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">STOCKAGE</div>
|
||||
<div class="storage-block">
|
||||
<div class="det-gauges">
|
||||
<div class="dg-row"><div class="dg-ico" data-tip="Utilisé"><i class="fa-solid fa-hard-drive"></i></div><div class="dg-bar"><div class="dg-fill" style="width:31%"></div></div><span class="dg-val">62 / 200 Go</span></div>
|
||||
<div class="dg-row"><div class="dg-ico" data-tip="Libre" style="color:var(--blue)"><i class="fa-solid fa-floppy-disk"></i></div><div class="dg-bar"><div class="dg-fill b" style="width:69%"></div></div><span class="dg-val">138 Go libre</span></div>
|
||||
</div>
|
||||
<div class="smart-btn ok" onclick="showSmart();event.stopPropagation()" data-tip="Voir la santé complète du disque">
|
||||
<div class="smart-dot"></div>
|
||||
<span style="font-weight:600;letter-spacing:.04em">SMART</span>
|
||||
<span style="font-size:10px;color:var(--ink-3)">·</span>
|
||||
<span style="font-size:11px;font-family:var(--font-terminal)">PASSED</span>
|
||||
<span style="font-family:var(--font-mono);font-size:10px;color:var(--ink-3);margin-left:4px"><i class="fa-solid fa-temperature-half"></i> 34°C</span>
|
||||
<i class="fa-solid fa-chevron-right" style="font-size:10px;color:var(--ink-4);margin-left:auto"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">INFORMATIONS</div>
|
||||
<div class="meta-grid">
|
||||
<div class="meta"><div class="meta-lbl">HOSTNAME</div><div class="meta-val">srv-prod-01</div></div>
|
||||
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">10.0.0.11</div></div>
|
||||
<div class="meta"><div class="meta-lbl">PROTOCOLES ACTIFS</div><div class="proto-badges"><span class="proto-badge udp"><i class="fa-solid fa-arrow-up"></i>UDP</span><span class="proto-badge mqtt">M MQTT</span></div></div>
|
||||
<div class="meta"><div class="meta-lbl">DERNIER CONTACT</div><div class="meta-val">22:14:07</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pop-foot">
|
||||
<span class="pop-uptime"><i class="fa-solid fa-clock" style="margin-right:4px"></i>En ligne depuis 14 jours 6 heures</span>
|
||||
<span class="resize-hint" data-tip="Taille sauvegardée sur le serveur"><i class="fa-solid fa-up-right-and-down-left-from-center"></i>Redimensionnable</span>
|
||||
<div class="btn-agent-cfg" onclick="showAgentCfg()" data-tip="Configurer l'agent"><i class="fa-solid fa-gears"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ CONFIG AGENT ══ -->
|
||||
<div class="overlay" id="overlay-agentcfg" style="display:none;z-index:200" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-agentcfg" onclick="event.stopPropagation()">
|
||||
<div class="cfg-head">
|
||||
<div class="cfg-head-icon"><i class="fa-solid fa-gears"></i></div>
|
||||
<div class="cfg-head-info"><div class="cfg-head-title">Configuration de l'agent</div><div class="cfg-head-sub">srv-prod-01 · 10.0.0.11 · config récupérée à 22:14:05</div></div>
|
||||
<div class="pop-close" onclick="this.closest('.overlay').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="cfg-body">
|
||||
|
||||
<!-- MÉTRIQUES PAR PROTOCOLE — tableau 3 colonnes sans activation générale -->
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">MÉTRIQUES PAR PROTOCOLE</div>
|
||||
<div class="metrics-table">
|
||||
<div class="metrics-header">
|
||||
<span class="mh-label">MÉTRIQUE</span>
|
||||
<span class="mh-proto udp">
|
||||
<i class="fa-solid fa-arrow-up"></i> UDP
|
||||
<span class="proto-dot"></span>
|
||||
</span>
|
||||
<span class="mh-proto mqtt">
|
||||
<i class="fa-solid fa-tower-broadcast" style="font-size:8px"></i> MQTT
|
||||
<span class="proto-dot"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-microchip"></i></div><span class="metric-name">cpu</span></div>
|
||||
<div class="metric-chk"><div class="cbox udp-on" onclick="toggleCbox(this,'udp')" data-tip="CPU via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox mqtt-on" onclick="toggleCbox(this,'mqtt')" data-tip="CPU via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-memory"></i></div><span class="metric-name">memory</span></div>
|
||||
<div class="metric-chk"><div class="cbox udp-on" onclick="toggleCbox(this,'udp')" data-tip="RAM via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox mqtt-on" onclick="toggleCbox(this,'mqtt')" data-tip="RAM via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-hard-drive"></i></div><span class="metric-name">disk</span></div>
|
||||
<div class="metric-chk"><div class="cbox udp-on" onclick="toggleCbox(this,'udp')" data-tip="Disque via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox mqtt-on" onclick="toggleCbox(this,'mqtt')" data-tip="Disque via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-shield-heart"></i></div><span class="metric-name">smart</span></div>
|
||||
<div class="metric-chk"><div class="cbox udp-on" onclick="toggleCbox(this,'udp')" data-tip="SMART via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox" onclick="toggleCbox(this,'mqtt')" data-tip="SMART via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-clock"></i></div><span class="metric-name">uptime</span></div>
|
||||
<div class="metric-chk"><div class="cbox udp-on" onclick="toggleCbox(this,'udp')" data-tip="Uptime via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox mqtt-on" onclick="toggleCbox(this,'mqtt')" data-tip="Uptime via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-network-wired"></i></div><span class="metric-name">network</span></div>
|
||||
<div class="metric-chk"><div class="cbox" onclick="toggleCbox(this,'udp')" data-tip="Réseau via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox" onclick="toggleCbox(this,'mqtt')" data-tip="Réseau via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric-cell"><div class="metric-ico"><i class="fa-solid fa-thermometer-half"></i></div><span class="metric-name">temperature</span></div>
|
||||
<div class="metric-chk"><div class="cbox" onclick="toggleCbox(this,'udp')" data-tip="Température via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox mqtt-on" onclick="toggleCbox(this,'mqtt')" data-tip="Température via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PARAMÈTRES MQTT -->
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">PARAMÈTRES MQTT</div>
|
||||
<div class="mqtt-opts">
|
||||
<div class="mqtt-field"><label>Broker</label><input class="mqtt-input" type="text" value="10.0.0.3"></div>
|
||||
<div class="mqtt-field"><label>Port</label><input class="mqtt-input" type="number" value="1883" style="width:90px;flex:none"></div>
|
||||
<div class="mqtt-field"><label>Topic base</label><input class="mqtt-input" type="text" value="nanometrics/agents"></div>
|
||||
<div style="border-top:1px solid var(--border-1);padding-top:8px;display:flex;flex-direction:column;gap:5px">
|
||||
<div class="mqtt-check-row"><label><i class="fa-solid fa-satellite-dish"></i> Auto-discovery (Home Assistant)</label><label class="toggle tog-mqtt"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="mqtt-check-row"><label><i class="fa-solid fa-arrow-right-to-bracket"></i> Birth message</label><label class="toggle tog-mqtt"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
<div class="mqtt-check-row"><label><i class="fa-solid fa-skull"></i> Last Will message</label><label class="toggle tog-mqtt"><input type="checkbox" checked><span class="toggle-slider"></span></label></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COMMANDES -->
|
||||
<div class="cfg-section">
|
||||
<div class="cfg-sec-title">COMMANDES DISTANTES <span style="color:var(--ink-4);font-size:8px;margin-left:6px">— BIENTÔT</span></div>
|
||||
<div class="cmds-grid">
|
||||
<div class="cmd-btn"><i class="fa-solid fa-rotate-right"></i><span>reboot</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-power-off"></i><span>shutdown</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-display"></i><span>screen off</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-arrow-up-from-bracket"></i><span>update</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-arrow-up-right-dots"></i><span>upgrade</span><span class="soon-tag">bientôt</span></div>
|
||||
<div class="cmd-btn"><i class="fa-solid fa-terminal"></i><span>shell cmd</span><span class="soon-tag">bientôt</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cfg-foot">
|
||||
<div class="cfg-status"><div class="dot"></div><span>Config synchronisée avec l'agent</span></div>
|
||||
<button class="btn" onclick="this.closest('.overlay').style.display='none'">Annuler</button>
|
||||
<button class="btn primary"><i class="fa-solid fa-paper-plane"></i> Envoyer à l'agent</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SMART -->
|
||||
<div class="overlay" id="overlay-smart" style="display:none;z-index:300" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="popup" id="popup-smart" onclick="event.stopPropagation()">
|
||||
<div class="smart-head">
|
||||
<div class="smart-head-icon"><i class="fa-solid fa-shield-heart"></i></div>
|
||||
<div class="smart-head-info"><div class="smart-head-title">Santé du disque dur</div><div class="smart-head-sub">srv-prod-01 · /dev/sda · Samsung SSD 870 EVO 250GB</div></div>
|
||||
<div class="pop-close" onclick="this.closest('.overlay').style.display='none'" data-tip="Fermer"><i class="fa-solid fa-xmark"></i></div>
|
||||
</div>
|
||||
<div class="smart-body">
|
||||
<div class="smart-verdict">
|
||||
<div class="verdict-icon"><i class="fa-solid fa-circle-check"></i></div>
|
||||
<div class="verdict-text"><div class="v-title">Disque en bonne santé</div><div class="v-sub">Aucun problème détecté. Le disque fonctionne normalement et peut être utilisé en toute confiance.</div></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">POINTS DE CONTRÔLE</div>
|
||||
<div class="smart-indicators">
|
||||
<div class="smart-ind"><div class="si-header"><div class="si-icon" style="color:var(--warn)"><i class="fa-solid fa-temperature-half"></i></div><span class="si-title">Température</span><span class="si-status ok">Normale</span></div><div class="si-val">34<span class="u">°C</span></div><div class="si-desc">Idéal : 20-50°C. Au-delà de 60°C le disque risque de s'abîmer prématurément.</div></div>
|
||||
<div class="smart-ind"><div class="si-header"><div class="si-icon" style="color:var(--ok)"><i class="fa-solid fa-circle-check"></i></div><span class="si-title">Secteurs défectueux</span><span class="si-status ok">Aucun</span></div><div class="si-val">0<span class="u"> sect.</span></div><div class="si-desc">S'ils apparaissent en grand nombre, une panne est imminente.</div></div>
|
||||
<div class="smart-ind"><div class="si-header"><div class="si-icon" style="color:var(--blue)"><i class="fa-solid fa-clock-rotate-left"></i></div><span class="si-title">Heures de fonctionnement</span><span class="si-status ok">Jeune</span></div><div class="si-val">4<span class="u">213h</span></div><div class="si-desc">≈175 jours. Un disque dure en moyenne 3 à 5 ans.</div></div>
|
||||
<div class="smart-ind"><div class="si-header"><div class="si-icon" style="color:var(--ok)"><i class="fa-solid fa-battery-full"></i></div><span class="si-title">Durée de vie SSD</span><span class="si-status ok">Excellente</span></div><div class="si-val">98<span class="u">%</span></div><div class="si-desc">100% = neuf · 0% = fin de vie recommandée.</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">ATTRIBUTS DÉTAILLÉS</div>
|
||||
<div class="smart-attrs">
|
||||
<div class="attr-row"><span class="attr-id">001</span><span class="attr-name">Taux d'erreurs de lecture</span><span class="attr-val attr-ok">0</span><span class="attr-explain attr-ok">✓ Parfait</span></div>
|
||||
<div class="attr-row"><span class="attr-id">005</span><span class="attr-name">Secteurs réalloués</span><span class="attr-val attr-ok">0</span><span class="attr-explain attr-ok">✓ Aucun dommage</span></div>
|
||||
<div class="attr-row"><span class="attr-id">009</span><span class="attr-name">Heures de fonctionnement</span><span class="attr-val attr-ok">4213</span><span class="attr-explain">175 jours</span></div>
|
||||
<div class="attr-row"><span class="attr-id">177</span><span class="attr-name">Usure cellules SSD</span><span class="attr-val attr-ok">2</span><span class="attr-explain attr-ok">✓ Très faible</span></div>
|
||||
<div class="attr-row"><span class="attr-id">190</span><span class="attr-name">Température</span><span class="attr-val attr-ok">34°C</span><span class="attr-explain attr-ok">✓ Normale</span></div>
|
||||
<div class="attr-row"><span class="attr-id">197</span><span class="attr-name">Secteurs instables</span><span class="attr-val attr-ok">0</span><span class="attr-explain attr-ok">✓ Aucun</span></div>
|
||||
<div class="attr-row"><span class="attr-id">198</span><span class="attr-name">Secteurs non corrigeables</span><span class="attr-val attr-ok">0</span><span class="attr-explain attr-ok">✓ Aucun</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="smart-footer">
|
||||
<span class="smart-footer-note"><i class="fa-solid fa-circle-info" style="margin-right:4px"></i>Données via smartctl · actualisées à chaque cycle de l'agent</span>
|
||||
<button class="btn primary" onclick="this.closest('.overlay').style.display='none'"><i class="fa-solid fa-xmark"></i> Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* tooltip */
|
||||
const tip=document.getElementById('tooltip');let tt;
|
||||
document.addEventListener('mouseover',e=>{const el=e.target.closest('[data-tip]');if(!el)return;clearTimeout(tt);tt=setTimeout(()=>{tip.textContent=el.dataset.tip;tip.classList.add('show');mv(e);},120);});
|
||||
document.addEventListener('mousemove',e=>{if(tip.classList.contains('show'))mv(e);});
|
||||
document.addEventListener('mouseout',e=>{const el=e.target.closest('[data-tip]');if(!el)return;clearTimeout(tt);tip.classList.remove('show');});
|
||||
function mv(e){const w=tip.offsetWidth,h=tip.offsetHeight;let x=e.clientX-w/2,y=e.clientY-h-10;x=Math.max(6,Math.min(x,window.innerWidth-w-6));if(y<6)y=e.clientY+18;tip.style.left=x+'px';tip.style.top=y+'px';}
|
||||
|
||||
/* thème */
|
||||
function toggleTheme(){const h=document.documentElement;h.dataset.theme=h.dataset.theme==='dark'?'light':'dark';document.getElementById('theme-icon').className=h.dataset.theme==='dark'?'fa-solid fa-moon':'fa-solid fa-sun';drawCharts();}
|
||||
|
||||
/* config serveur */
|
||||
function showSrvCfg(){document.getElementById('overlay-srvcfg').style.display='flex';document.getElementById('btn-srvcfg').classList.add('active-btn');}
|
||||
function hideSrvCfg(){document.getElementById('overlay-srvcfg').style.display='none';document.getElementById('btn-srvcfg').classList.remove('active-btn');}
|
||||
|
||||
/* popups */
|
||||
function showDetail(){document.getElementById('overlay-detail').style.display='flex';drawCharts();}
|
||||
function showAgentCfg(){document.getElementById('overlay-agentcfg').style.display='flex';}
|
||||
function showSmart(){document.getElementById('overlay-smart').style.display='flex';}
|
||||
|
||||
/* checkbox métrique par protocole */
|
||||
function toggleCbox(el,proto){
|
||||
const isOn=el.classList.contains(proto+'-on');
|
||||
el.classList.toggle(proto+'-on',!isOn);
|
||||
el.style.color=isOn?'transparent':'';
|
||||
}
|
||||
|
||||
/* courbes */
|
||||
function makeCurve(pts,stroke,fill,w,h){
|
||||
const xs=pts.map((_,i)=>(i/(pts.length-1))*w);
|
||||
const ys=pts.map(v=>h-(v/100)*(h-6)-3);
|
||||
const wy=h-(70/100)*(h-6)-3;
|
||||
let d=`M${xs[0]} ${ys[0]}`;
|
||||
for(let i=1;i<pts.length;i++){const cx=(xs[i-1]+xs[i])/2;d+=` C${cx} ${ys[i-1]},${cx} ${ys[i]},${xs[i]} ${ys[i]}`;}
|
||||
const uid=Math.random().toString(36).slice(2);
|
||||
return `<defs><linearGradient id="g${uid}" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="${fill}" stop-opacity=".4"/><stop offset="100%" stop-color="${fill}" stop-opacity=".02"/></linearGradient></defs>
|
||||
<line x1="0" y1="${wy}" x2="${w}" y2="${wy}" stroke="var(--warn)" stroke-width=".8" stroke-dasharray="3,3" opacity=".5"/>
|
||||
<path d="${d} L${xs.at(-1)} ${h} L${xs[0]} ${h}Z" fill="url(#g${uid})"/>
|
||||
<path d="${d}" fill="none" stroke="${stroke}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="${xs.at(-1)}" cy="${ys.at(-1)}" r="2.5" fill="${stroke}"/>`;
|
||||
}
|
||||
function drawCharts(){
|
||||
const cpu=[38,41,35,40,44,42,39,45,50,48,43,42,44,41,40,43,47,45,42,44,41,43,42,40,43,41,44,43,41,42];
|
||||
const ram=[44,44,45,45,46,46,47,47,46,46,45,46,46,47,47,46,46,45,46,46,46,46,46,46,46,46,46,46,46,46];
|
||||
const cs=getComputedStyle(document.documentElement);
|
||||
const ac=cs.getPropertyValue('--accent').trim(),bl=cs.getPropertyValue('--blue').trim();
|
||||
document.getElementById('cpu-chart').innerHTML=makeCurve(cpu,ac,ac,200,52);
|
||||
document.getElementById('ram-chart').innerHTML=makeCurve(ram,bl,bl,200,52);
|
||||
}
|
||||
|
||||
/* resize popup → sauvegarde serveur */
|
||||
const pd=document.getElementById('popup-detail');
|
||||
new ResizeObserver(()=>{
|
||||
/* en prod : fetch('/api/config', {method:'PUT', body: JSON.stringify({popup_detail_size:{w:pd.offsetWidth,h:pd.offsetHeight}})}) */
|
||||
console.log('Taille sauvegardée sur serveur:',pd.offsetWidth,'x',pd.offsetHeight);
|
||||
}).observe(pd);
|
||||
|
||||
window.addEventListener('load',drawCharts);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,360 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nanometrics — Layout du dashboard</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
<script src="/helper.js"></script>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--accent: #fe8019; --accent-soft: #d65d0e; --accent-glow: rgba(254,128,25,0.35);
|
||||
--bg-0: #1d1813; --bg-1: #2a231d; --bg-2: #32291f; --bg-3: #3c332a; --bg-4: #4a3f33; --bg-5: #57493c;
|
||||
--ink-1: #f2e5c7; --ink-2: #d5c4a1; --ink-3: #a89984; --ink-4: #7c6f64;
|
||||
--ok: #4dbb26; --warn: #fabd2f; --err: #fb4934; --info: #83a598; --blue: #3db0d1; --purple: #c882c8;
|
||||
--border-1: rgba(255,255,255,0.06); --border-2: rgba(255,255,255,0.12); --border-3: rgba(255,255,255,0.22);
|
||||
--shadow-1: 0 1px 4px rgba(0,0,0,0.4); --shadow-2: 0 4px 16px rgba(0,0,0,0.5); --shadow-3: 0 8px 32px rgba(0,0,0,0.6);
|
||||
--tile-3d: 0 1px 0 rgba(255,255,255,0.08) inset, 0 -1px 0 rgba(0,0,0,0.3) inset, 0 4px 16px rgba(0,0,0,0.5);
|
||||
--font-ui: 'Inter', system-ui, sans-serif; --font-mono: 'JetBrains Mono', monospace; --font-terminal: 'Share Tech Mono', monospace;
|
||||
}
|
||||
|
||||
body { background: var(--bg-1); color: var(--ink-1); font-family: var(--font-ui); font-size: 13px; min-height: 100vh; }
|
||||
|
||||
/* ── HEADER PAGE ── */
|
||||
.page-header {
|
||||
background: var(--bg-0); border-bottom: 1px solid var(--border-2);
|
||||
padding: 14px 24px; text-align: center;
|
||||
}
|
||||
.page-header h1 { font-size: 15px; color: var(--ink-2); font-weight: 500; }
|
||||
.page-header p { font-size: 12px; color: var(--ink-3); margin-top: 4px; }
|
||||
|
||||
/* ── CARDS LAYOUT ── */
|
||||
.cards-wrap { display: flex; gap: 24px; padding: 28px 24px; flex-wrap: wrap; justify-content: center; }
|
||||
|
||||
.card-opt {
|
||||
background: var(--bg-2); border: 2px solid var(--border-2); border-radius: 12px;
|
||||
width: 320px; cursor: pointer; transition: border-color .15s, box-shadow .15s;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-opt:hover { border-color: var(--accent-soft); }
|
||||
.card-opt.selected { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
||||
|
||||
.card-label {
|
||||
background: var(--bg-3); padding: 10px 16px;
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
border-bottom: 1px solid var(--border-1);
|
||||
}
|
||||
.card-letter {
|
||||
width: 26px; height: 26px; border-radius: 6px;
|
||||
background: var(--accent); color: #1d1813; font-weight: 700; font-size: 13px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.card-name { font-weight: 600; font-size: 13px; color: var(--ink-1); }
|
||||
.card-sub { font-size: 11px; color: var(--ink-3); margin-top: 1px; }
|
||||
|
||||
.card-mock { padding: 14px; }
|
||||
|
||||
/* ── INDICATOR ── */
|
||||
#selection-bar {
|
||||
position: fixed; bottom: 0; left: 0; right: 0;
|
||||
background: var(--bg-0); border-top: 1px solid var(--border-2);
|
||||
padding: 10px 24px; display: flex; align-items: center; justify-content: space-between;
|
||||
font-family: var(--font-terminal); font-size: 12px; color: var(--ink-3);
|
||||
}
|
||||
#sel-text { color: var(--accent); }
|
||||
|
||||
/* ── MINI MAQUETTES ── */
|
||||
/* couleurs communes */
|
||||
.m { background: var(--bg-1); border-radius: 8px; overflow: hidden; }
|
||||
.mh { background: var(--bg-2); padding: 6px 10px; display: flex; align-items: center; gap: 6px; border-bottom: 1px solid var(--border-1); font-size: 10px; color: var(--ink-2); font-weight: 600; }
|
||||
.mh .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
|
||||
.mh .title { flex:1; }
|
||||
.mh .clock { font-family: var(--font-terminal); color: var(--ink-4); font-size: 9px; }
|
||||
|
||||
/* layout A — grille uniforme */
|
||||
.grid-a { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; padding: 8px; }
|
||||
.tile-a {
|
||||
background: var(--bg-3); border-radius: 6px; padding: 7px 8px;
|
||||
border: 1px solid var(--border-1); box-shadow: var(--tile-3d);
|
||||
display: flex; flex-direction: column; gap: 3px;
|
||||
}
|
||||
.tile-a .t-header { display: flex; align-items: center; gap: 4px; }
|
||||
.tile-a .t-icon { font-size: 9px; color: var(--accent); }
|
||||
.tile-a .t-name { font-size: 9px; font-weight: 600; color: var(--ink-2); }
|
||||
.tile-a .t-led { width: 5px; height: 5px; border-radius: 50%; background: var(--ok); margin-left: auto; }
|
||||
.tile-a .t-led.warn { background: var(--warn); }
|
||||
.tile-a .t-led.err { background: var(--err); }
|
||||
.tile-a .gauge-row { display: flex; align-items: center; gap: 4px; }
|
||||
.tile-a .g-label { font-size: 8px; color: var(--ink-4); font-family: var(--font-terminal); width: 20px; }
|
||||
.tile-a .g-bar { flex:1; height: 4px; border-radius: 2px; background: var(--bg-4); overflow: hidden; }
|
||||
.tile-a .g-fill { height: 100%; border-radius: 2px; background: var(--ok); }
|
||||
.tile-a .g-fill.warn { background: var(--warn); }
|
||||
.tile-a .g-fill.err { background: var(--err); }
|
||||
.tile-a .g-val { font-size: 8px; color: var(--ink-3); font-family: var(--font-mono); width: 20px; text-align: right; }
|
||||
|
||||
/* layout B — sidebar + grille */
|
||||
.layout-b { display: flex; gap: 0; }
|
||||
.sidebar-b {
|
||||
width: 72px; background: var(--bg-2); padding: 6px 4px;
|
||||
display: flex; flex-direction: column; gap: 2px;
|
||||
border-right: 1px solid var(--border-1);
|
||||
}
|
||||
.sb-item {
|
||||
padding: 5px 4px; border-radius: 5px; display: flex; align-items: center; gap: 4px;
|
||||
font-size: 8px; color: var(--ink-3); cursor: pointer;
|
||||
}
|
||||
.sb-item.active { background: var(--bg-4); color: var(--accent); }
|
||||
.sb-item .sb-led { width: 4px; height: 4px; border-radius: 50%; background: var(--ok); flex-shrink: 0; }
|
||||
.sb-item .sb-led.warn { background: var(--warn); }
|
||||
.sb-item .sb-led.err { background: var(--err); }
|
||||
.sb-item .sb-label { font-family: var(--font-terminal); font-size: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.grid-b { flex: 1; display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; padding: 6px; }
|
||||
.tile-b {
|
||||
background: var(--bg-3); border-radius: 6px; padding: 7px 8px;
|
||||
border: 1px solid var(--border-1); box-shadow: var(--tile-3d);
|
||||
display: flex; flex-direction: column; gap: 3px;
|
||||
}
|
||||
.tile-b .t-header { display: flex; align-items: center; gap: 4px; }
|
||||
.tile-b .t-icon { font-size: 9px; color: var(--accent); }
|
||||
.tile-b .t-name { font-size: 9px; font-weight: 600; color: var(--ink-2); }
|
||||
.tile-b .t-led { width: 5px; height: 5px; border-radius: 50%; background: var(--ok); margin-left: auto; }
|
||||
.tile-b .gauge-row { display: flex; align-items: center; gap: 4px; }
|
||||
.tile-b .g-label { font-size: 8px; color: var(--ink-4); font-family: var(--font-terminal); width: 20px; }
|
||||
.tile-b .g-bar { flex:1; height: 4px; border-radius: 2px; background: var(--bg-4); overflow: hidden; }
|
||||
.tile-b .g-fill { height: 100%; border-radius: 2px; background: var(--ok); }
|
||||
.tile-b .g-fill.warn { background: var(--warn); }
|
||||
.tile-b .g-val { font-size: 8px; color: var(--ink-3); font-family: var(--font-mono); width: 20px; text-align: right; }
|
||||
|
||||
/* layout C — 3 colonnes */
|
||||
.layout-c { display: flex; gap: 0; height: 160px; }
|
||||
.tree-c {
|
||||
width: 68px; background: var(--bg-2); padding: 5px 4px;
|
||||
display: flex; flex-direction: column; gap: 1px;
|
||||
border-right: 1px solid var(--border-1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.tc-group { font-size: 8px; color: var(--accent); font-family: var(--font-terminal); padding: 2px 3px; letter-spacing: .05em; }
|
||||
.tc-item { display: flex; align-items: center; gap: 3px; padding: 2px 6px; border-radius: 3px; }
|
||||
.tc-item.active { background: var(--bg-4); }
|
||||
.tc-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--ok); flex-shrink: 0; }
|
||||
.tc-dot.warn { background: var(--warn); }
|
||||
.tc-dot.err { background: var(--err); }
|
||||
.tc-label { font-size: 8px; color: var(--ink-3); font-family: var(--font-terminal); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.cockpit-c {
|
||||
flex: 1; padding: 6px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.kpi-c {
|
||||
background: var(--bg-3); border-radius: 5px; padding: 6px 8px;
|
||||
border: 1px solid var(--border-1); box-shadow: var(--tile-3d);
|
||||
display: flex; flex-direction: column; gap: 3px;
|
||||
}
|
||||
.kpi-c .k-label { font-size: 8px; color: var(--ink-4); font-family: var(--font-terminal); letter-spacing: .06em; }
|
||||
.kpi-c .k-val { font-family: var(--font-mono); font-size: 16px; font-weight: 700; color: var(--accent); line-height: 1; }
|
||||
.kpi-c .k-sub { font-size: 8px; color: var(--ink-3); }
|
||||
.kpi-c .g-bar { height: 3px; border-radius: 2px; background: var(--bg-4); overflow: hidden; margin-top: 2px; }
|
||||
.kpi-c .g-fill { height: 100%; border-radius: 2px; background: var(--ok); }
|
||||
.kpi-c .g-fill.warn { background: var(--warn); }
|
||||
.logs-c {
|
||||
width: 90px; background: var(--bg-2); padding: 5px;
|
||||
border-left: 1px solid var(--border-1); overflow: hidden;
|
||||
display: flex; flex-direction: column; gap: 2px;
|
||||
}
|
||||
.log-line { font-family: var(--font-terminal); font-size: 7px; color: var(--ink-4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.log-line.ok { color: var(--ok); }
|
||||
.log-line.warn { color: var(--warn); }
|
||||
.log-line.err { color: var(--err); }
|
||||
|
||||
/* status bar commune */
|
||||
.statusbar {
|
||||
background: var(--bg-0); border-top: 1px solid var(--border-1);
|
||||
display: flex; font-family: var(--font-terminal); font-size: 8px; color: var(--ink-4);
|
||||
}
|
||||
.sb-mode { background: var(--accent); color: var(--bg-0); padding: 2px 8px; font-weight: 700; }
|
||||
.sb-cell { padding: 2px 8px; border-right: 1px solid var(--border-1); }
|
||||
.sb-right { margin-left: auto; padding: 2px 8px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<h1>Layout du dashboard Nanometrics</h1>
|
||||
<p>Clique sur le layout qui te correspond le mieux</p>
|
||||
</div>
|
||||
|
||||
<div class="cards-wrap">
|
||||
|
||||
<!-- OPTION A -->
|
||||
<div class="card-opt" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="card-label">
|
||||
<div class="card-letter">A</div>
|
||||
<div>
|
||||
<div class="card-name">Grille uniforme</div>
|
||||
<div class="card-sub">Toutes les tuiles identiques, adaptatives</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-mock">
|
||||
<div class="m">
|
||||
<div class="mh"><div class="dot"></div><span class="title">NANOMETRICS</span><span class="clock">22:14:05</span></div>
|
||||
<div class="grid-a">
|
||||
<div class="tile-a">
|
||||
<div class="t-header"><span class="t-icon">⬡</span><span class="t-name">srv-01</span><span class="t-led"></span></div>
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:42%"></div></div><span class="g-val">42%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill warn" style="width:74%"></div></div><span class="g-val">74%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DSK</span><div class="g-bar"><div class="g-fill" style="width:31%"></div></div><span class="g-val">31%</span></div>
|
||||
</div>
|
||||
<div class="tile-a">
|
||||
<div class="t-header"><span class="t-icon">⬡</span><span class="t-name">srv-02</span><span class="t-led warn"></span></div>
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill warn" style="width:78%"></div></div><span class="g-val">78%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:55%"></div></div><span class="g-val">55%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DSK</span><div class="g-bar"><div class="g-fill" style="width:20%"></div></div><span class="g-val">20%</span></div>
|
||||
</div>
|
||||
<div class="tile-a">
|
||||
<div class="t-header"><span class="t-icon">⬡</span><span class="t-name">rpi-03</span><span class="t-led"></span></div>
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:12%"></div></div><span class="g-val">12%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:38%"></div></div><span class="g-val">38%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DSK</span><div class="g-bar"><div class="g-fill err" style="width:91%"></div></div><span class="g-val">91%</span></div>
|
||||
</div>
|
||||
<div class="tile-a">
|
||||
<div class="t-header"><span class="t-icon">⬡</span><span class="t-name">nas-04</span><span class="t-led"></span></div>
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:8%"></div></div><span class="g-val">8%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:60%"></div></div><span class="g-val">60%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DSK</span><div class="g-bar"><div class="g-fill" style="width:44%"></div></div><span class="g-val">44%</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statusbar">
|
||||
<div class="sb-mode">LIVE</div>
|
||||
<div class="sb-cell">4 agents</div>
|
||||
<div class="sb-cell">3 ok · 1 warn</div>
|
||||
<div class="sb-right">22:14:05</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OPTION B -->
|
||||
<div class="card-opt" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="card-label">
|
||||
<div class="card-letter">B</div>
|
||||
<div>
|
||||
<div class="card-name">Sidebar + grille</div>
|
||||
<div class="card-sub">Liste des agents à gauche, tuiles à droite</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-mock">
|
||||
<div class="m">
|
||||
<div class="mh"><div class="dot"></div><span class="title">NANOMETRICS</span><span class="clock">22:14:05</span></div>
|
||||
<div class="layout-b">
|
||||
<div class="sidebar-b">
|
||||
<div class="sb-item active"><div class="sb-led"></div><span class="sb-label">srv-01</span></div>
|
||||
<div class="sb-item"><div class="sb-led warn"></div><span class="sb-label">srv-02</span></div>
|
||||
<div class="sb-item"><div class="sb-led err"></div><span class="sb-label">rpi-03</span></div>
|
||||
<div class="sb-item"><div class="sb-led"></div><span class="sb-label">nas-04</span></div>
|
||||
<div class="sb-item"><div class="sb-led"></div><span class="sb-label">db-05</span></div>
|
||||
<div class="sb-item"><div class="sb-led"></div><span class="sb-label">web-06</span></div>
|
||||
</div>
|
||||
<div class="grid-b">
|
||||
<div class="tile-b">
|
||||
<div class="t-header"><span class="t-icon">⬡</span><span class="t-name">srv-01</span><span class="t-led"></span></div>
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:42%"></div></div><span class="g-val">42%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill warn" style="width:74%"></div></div><span class="g-val">74%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DSK</span><div class="g-bar"><div class="g-fill" style="width:31%"></div></div><span class="g-val">31%</span></div>
|
||||
</div>
|
||||
<div class="tile-b">
|
||||
<div class="t-header"><span class="t-icon">⬡</span><span class="t-name">srv-02</span><span class="t-led" style="background:var(--warn)"></span></div>
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill warn" style="width:78%"></div></div><span class="g-val">78%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:55%"></div></div><span class="g-val">55%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DSK</span><div class="g-bar"><div class="g-fill" style="width:20%"></div></div><span class="g-val">20%</span></div>
|
||||
</div>
|
||||
<div class="tile-b">
|
||||
<div class="t-header"><span class="t-icon">⬡</span><span class="t-name">rpi-03</span><span class="t-led" style="background:var(--err)"></span></div>
|
||||
<div class="gauge-row"><span class="g-label">CPU</span><div class="g-bar"><div class="g-fill" style="width:12%"></div></div><span class="g-val">12%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">RAM</span><div class="g-bar"><div class="g-fill" style="width:38%"></div></div><span class="g-val">38%</span></div>
|
||||
<div class="gauge-row"><span class="g-label">DSK</span><div class="g-bar"><div class="g-fill" style="width:91%;background:var(--err)"></div></div><span class="g-val">91%</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statusbar">
|
||||
<div class="sb-mode">LIVE</div>
|
||||
<div class="sb-cell">6 agents</div>
|
||||
<div class="sb-cell">4 ok · 1 warn · 1 err</div>
|
||||
<div class="sb-right">22:14:05</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OPTION C -->
|
||||
<div class="card-opt" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="card-label">
|
||||
<div class="card-letter">C</div>
|
||||
<div>
|
||||
<div class="card-name">3 colonnes (ops-style)</div>
|
||||
<div class="card-sub">Tree nav · cockpit KPI · logs live</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-mock">
|
||||
<div class="m">
|
||||
<div class="mh"><div class="dot"></div><span class="title">NANOMETRICS</span><span class="clock">22:14:05</span></div>
|
||||
<div class="layout-c">
|
||||
<div class="tree-c">
|
||||
<div class="tc-group">AGENTS</div>
|
||||
<div class="tc-item active"><div class="tc-dot"></div><span class="tc-label">srv-01</span></div>
|
||||
<div class="tc-item"><div class="tc-dot warn"></div><span class="tc-label">srv-02</span></div>
|
||||
<div class="tc-item"><div class="tc-dot err"></div><span class="tc-label">rpi-03</span></div>
|
||||
<div class="tc-item"><div class="tc-dot"></div><span class="tc-label">nas-04</span></div>
|
||||
<div class="tc-item"><div class="tc-dot"></div><span class="tc-label">db-05</span></div>
|
||||
</div>
|
||||
<div class="cockpit-c">
|
||||
<div class="kpi-c">
|
||||
<div class="k-label">CPU</div>
|
||||
<div class="k-val">42<span style="font-size:10px;color:var(--ink-3)">%</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:42%"></div></div>
|
||||
</div>
|
||||
<div class="kpi-c">
|
||||
<div class="k-label">MÉMOIRE</div>
|
||||
<div class="k-val">74<span style="font-size:10px;color:var(--ink-3)">%</span></div>
|
||||
<div class="g-bar"><div class="g-fill warn" style="width:74%"></div></div>
|
||||
</div>
|
||||
<div class="kpi-c">
|
||||
<div class="k-label">DISQUE</div>
|
||||
<div class="k-val">31<span style="font-size:10px;color:var(--ink-3)">%</span></div>
|
||||
<div class="g-bar"><div class="g-fill" style="width:31%"></div></div>
|
||||
</div>
|
||||
<div class="kpi-c">
|
||||
<div class="k-label">UPTIME</div>
|
||||
<div class="k-val" style="font-size:11px;color:var(--ink-1)">14j 6h</div>
|
||||
<div class="k-sub">depuis dernier boot</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="logs-c">
|
||||
<div class="log-line ok">22:14:05 srv-01 online</div>
|
||||
<div class="log-line warn">22:13:58 srv-02 cpu>75%</div>
|
||||
<div class="log-line err">22:13:41 rpi-03 disk>90%</div>
|
||||
<div class="log-line">22:13:30 nas-04 online</div>
|
||||
<div class="log-line">22:13:22 db-05 online</div>
|
||||
<div class="log-line warn">22:12:55 srv-02 cpu>70%</div>
|
||||
<div class="log-line">22:12:40 web-06 online</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statusbar">
|
||||
<div class="sb-mode">LIVE</div>
|
||||
<div class="sb-cell">srv-01 sélectionné</div>
|
||||
<div class="sb-cell">5 ok · 1 warn · 1 err</div>
|
||||
<div class="sb-right">22:14:05</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="selection-bar">
|
||||
<span>Nanometrics — Layout du dashboard</span>
|
||||
<span id="sel-text">Clique sur un layout pour le sélectionner</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
{"type":"server-started","port":55731,"host":"0.0.0.0","url_host":"10.0.0.50","url":"http://10.0.0.50:55731","screen_dir":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content","state_dir":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/state"}
|
||||
@@ -0,0 +1,26 @@
|
||||
{"type":"server-started","port":55731,"host":"0.0.0.0","url_host":"10.0.0.50","url":"http://10.0.0.50:55731","screen_dir":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content","state_dir":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/state"}
|
||||
{"type":"screen-added","file":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content/approaches.html"}
|
||||
{"source":"user-event","type":"click","text":"C\n \n SQLite + WebSocket ⭐ Recommandé\n SQLite — simplicité opérationnelle, suffisant pour 20+ agents avec rétention configurable.\n WebSocket — bidirectionnel dès le départ, sans surcoût opérationnel.\n \n AvantagesPas de conteneur DB supplémentaireWebSocket prêt pour extensions futuresSimple à debugger et sauvegarder\n LimitesRequêtes temporelles moins expressives qu'InfluxDBScalabilité limitée au-delà de ~100 agents","choice":"c","id":null,"timestamp":1779426035162}
|
||||
{"source":"user-event","type":"click","text":"C\n \n SQLite + WebSocket ⭐ Recommandé\n SQLite — simplicité opérationnelle, suffisant pour 20+ agents avec rétention configurable.\n WebSocket — bidirectionnel dès le départ, sans surcoût opérationnel.\n \n AvantagesPas de conteneur DB supplémentaireWebSocket prêt pour extensions futuresSimple à debugger et sauvegarder\n LimitesRequêtes temporelles moins expressives qu'InfluxDBScalabilité limitée au-delà de ~100 agents","choice":"c","id":null,"timestamp":1779426056446}
|
||||
{"source":"user-event","type":"click","text":"C\n \n SQLite + WebSocket ⭐ Recommandé\n SQLite — simplicité opérationnelle, suffisant pour 20+ agents avec rétention configurable.\n WebSocket — bidirectionnel dès le départ, sans surcoût opérationnel.\n \n AvantagesPas de conteneur DB supplémentaireWebSocket prêt pour extensions futuresSimple à debugger et sauvegarder\n LimitesRequêtes temporelles moins expressives qu'InfluxDBScalabilité limitée au-delà de ~100 agents","choice":"c","id":null,"timestamp":1779426057055}
|
||||
{"type":"screen-added","file":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content/layout.html"}
|
||||
{"source":"user-event","type":"click","text":"A\n \n Grille uniforme\n Toutes les tuiles identiques, adaptatives\n \n \n \n \n NANOMETRICS22:14:05\n \n \n ⬡srv-01\n CPU42%\n RAM74%\n DSK31%\n \n \n ⬡srv-02\n CPU78%\n RAM55%\n DSK20%\n \n \n ⬡rpi-03\n CPU12%\n RAM38%\n DSK91%\n \n \n ⬡nas-04\n CPU8%\n RAM60%\n DSK44%\n \n \n \n LIVE\n 4 agents\n 3 ok · 1 warn\n 22:14:05","choice":"a","id":null,"timestamp":1779426189689}
|
||||
{"source":"user-event","type":"click","text":"A\n \n Grille uniforme\n Toutes les tuiles identiques, adaptatives\n \n \n \n \n NANOMETRICS22:14:05\n \n \n ⬡srv-01\n CPU42%\n RAM74%\n DSK31%\n \n \n ⬡srv-02\n CPU78%\n RAM55%\n DSK20%\n \n \n ⬡rpi-03\n CPU12%\n RAM38%\n DSK91%\n \n \n ⬡nas-04\n CPU8%\n RAM60%\n DSK44%\n \n \n \n LIVE\n 4 agents\n 3 ok · 1 warn\n 22:14:05","choice":"a","id":null,"timestamp":1779426247715}
|
||||
{"source":"user-event","type":"click","text":"A\n \n Grille uniforme\n Toutes les tuiles identiques, adaptatives\n \n \n \n \n NANOMETRICS22:14:05\n \n \n ⬡srv-01\n CPU42%\n RAM74%\n DSK31%\n \n \n ⬡srv-02\n CPU78%\n RAM55%\n DSK20%\n \n \n ⬡rpi-03\n CPU12%\n RAM38%\n DSK91%\n \n \n ⬡nas-04\n CPU8%\n RAM60%\n DSK44%\n \n \n \n LIVE\n 4 agents\n 3 ok · 1 warn\n 22:14:05","choice":"a","id":null,"timestamp":1779426255628}
|
||||
{"source":"user-event","type":"click","text":"A\n \n Grille uniforme\n Toutes les tuiles identiques, adaptatives\n \n \n \n \n NANOMETRICS22:14:05\n \n \n ⬡srv-01\n CPU42%\n RAM74%\n DSK31%\n \n \n ⬡srv-02\n CPU78%\n RAM55%\n DSK20%\n \n \n ⬡rpi-03\n CPU12%\n RAM38%\n DSK91%\n \n \n ⬡nas-04\n CPU8%\n RAM60%\n DSK44%\n \n \n \n LIVE\n 4 agents\n 3 ok · 1 warn\n 22:14:05","choice":"a","id":null,"timestamp":1779426320054}
|
||||
{"source":"user-event","type":"click","text":"A\n \n Grille uniforme\n Toutes les tuiles identiques, adaptatives\n \n \n \n \n NANOMETRICS22:14:05\n \n \n ⬡srv-01\n CPU42%\n RAM74%\n DSK31%\n \n \n ⬡srv-02\n CPU78%\n RAM55%\n DSK20%\n \n \n ⬡rpi-03\n CPU12%\n RAM38%\n DSK91%\n \n \n ⬡nas-04\n CPU8%\n RAM60%\n DSK44%\n \n \n \n LIVE\n 4 agents\n 3 ok · 1 warn\n 22:14:05","choice":"a","id":null,"timestamp":1779426600975}
|
||||
{"source":"user-event","type":"click","text":"B\n \n Sidebar + grille\n Liste des agents à gauche, tuiles à droite\n \n \n \n \n NANOMETRICS22:14:05\n \n \n srv-01\n srv-02\n rpi-03\n nas-04\n db-05\n web-06\n \n \n \n ⬡srv-01\n CPU42%\n RAM74%\n DSK31%\n \n \n ⬡srv-02\n CPU78%\n RAM55%\n DSK20%\n \n \n ⬡rpi-03\n CPU12%\n RAM38%\n DSK91%\n \n \n \n \n LIVE\n 6 agents\n 4 ok · 1 warn · 1 err\n 22:14:05","choice":"b","id":null,"timestamp":1779426601678}
|
||||
{"source":"user-event","type":"click","text":"C\n \n 3 colonnes (ops-style)\n Tree nav · cockpit KPI · logs live\n \n \n \n \n NANOMETRICS22:14:05\n \n \n AGENTS\n srv-01\n srv-02\n rpi-03\n nas-04\n db-05\n \n \n \n CPU\n 42%\n \n \n \n MÉMOIRE\n 74%\n \n \n \n DISQUE\n 31%\n \n \n \n UPTIME\n 14j 6h\n depuis dernier boot\n \n \n \n 22:14:05 srv-01 online\n 22:13:58 srv-02 cpu>75%\n 22:13:41 rpi-03 disk>90%\n 22:13:30 nas-04 online\n 22:13:22 db-05 online\n 22:12:55 srv-02 cpu>70%\n 22:12:40 web-06 online\n \n \n \n LIVE\n srv-01 sélectionné\n 5 ok · 1 warn · 1 err\n 22:14:05","choice":"c","id":null,"timestamp":1779426603020}
|
||||
{"source":"user-event","type":"click","text":"C\n \n 3 colonnes (ops-style)\n Tree nav · cockpit KPI · logs live\n \n \n \n \n NANOMETRICS22:14:05\n \n \n AGENTS\n srv-01\n srv-02\n rpi-03\n nas-04\n db-05\n \n \n \n CPU\n 42%\n \n \n \n MÉMOIRE\n 74%\n \n \n \n DISQUE\n 31%\n \n \n \n UPTIME\n 14j 6h\n depuis dernier boot\n \n \n \n 22:14:05 srv-01 online\n 22:13:58 srv-02 cpu>75%\n 22:13:41 rpi-03 disk>90%\n 22:13:30 nas-04 online\n 22:13:22 db-05 online\n 22:12:55 srv-02 cpu>70%\n 22:12:40 web-06 online\n \n \n \n LIVE\n srv-01 sélectionné\n 5 ok · 1 warn · 1 err\n 22:14:05","choice":"c","id":null,"timestamp":1779426603540}
|
||||
{"source":"user-event","type":"click","text":"B\n \n Sidebar + grille\n Liste des agents à gauche, tuiles à droite\n \n \n \n \n NANOMETRICS22:14:05\n \n \n srv-01\n srv-02\n rpi-03\n nas-04\n db-05\n web-06\n \n \n \n ⬡srv-01\n CPU42%\n RAM74%\n DSK31%\n \n \n ⬡srv-02\n CPU78%\n RAM55%\n DSK20%\n \n \n ⬡rpi-03\n CPU12%\n RAM38%\n DSK91%\n \n \n \n \n LIVE\n 6 agents\n 4 ok · 1 warn · 1 err\n 22:14:05","choice":"b","id":null,"timestamp":1779426604201}
|
||||
{"source":"user-event","type":"click","text":"A\n \n Grille uniforme\n Toutes les tuiles identiques, adaptatives\n \n \n \n \n NANOMETRICS22:14:05\n \n \n ⬡srv-01\n CPU42%\n RAM74%\n DSK31%\n \n \n ⬡srv-02\n CPU78%\n RAM55%\n DSK20%\n \n \n ⬡rpi-03\n CPU12%\n RAM38%\n DSK91%\n \n \n ⬡nas-04\n CPU8%\n RAM60%\n DSK44%\n \n \n \n LIVE\n 4 agents\n 3 ok · 1 warn\n 22:14:05","choice":"a","id":null,"timestamp":1779426605083}
|
||||
{"source":"user-event","type":"click","text":"A\n \n Grille uniforme\n Toutes les tuiles identiques, adaptatives\n \n \n \n \n NANOMETRICS22:14:05\n \n \n ⬡srv-01\n CPU42%\n RAM74%\n DSK31%\n \n \n ⬡srv-02\n CPU78%\n RAM55%\n DSK20%\n \n \n ⬡rpi-03\n CPU12%\n RAM38%\n DSK91%\n \n \n ⬡nas-04\n CPU8%\n RAM60%\n DSK44%\n \n \n \n LIVE\n 4 agents\n 3 ok · 1 warn\n 22:14:05","choice":"a","id":null,"timestamp":1779426608505}
|
||||
{"source":"user-event","type":"click","text":"C\n \n 3 colonnes (ops-style)\n Tree nav · cockpit KPI · logs live\n \n \n \n \n NANOMETRICS22:14:05\n \n \n AGENTS\n srv-01\n srv-02\n rpi-03\n nas-04\n db-05\n \n \n \n CPU\n 42%\n \n \n \n MÉMOIRE\n 74%\n \n \n \n DISQUE\n 31%\n \n \n \n UPTIME\n 14j 6h\n depuis dernier boot\n \n \n \n 22:14:05 srv-01 online\n 22:13:58 srv-02 cpu>75%\n 22:13:41 rpi-03 disk>90%\n 22:13:30 nas-04 online\n 22:13:22 db-05 online\n 22:12:55 srv-02 cpu>70%\n 22:12:40 web-06 online\n \n \n \n LIVE\n srv-01 sélectionné\n 5 ok · 1 warn · 1 err\n 22:14:05","choice":"c","id":null,"timestamp":1779426641935}
|
||||
{"type":"screen-added","file":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content/layout-v2.html"}
|
||||
{"type":"screen-added","file":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content/layout-v3.html"}
|
||||
{"type":"screen-added","file":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content/layout-v4.html"}
|
||||
{"type":"screen-added","file":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content/layout-v5.html"}
|
||||
{"type":"screen-added","file":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content/layout-v6.html"}
|
||||
{"type":"screen-added","file":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content/layout-v7.html"}
|
||||
{"type":"screen-added","file":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content/layout-v8.html"}
|
||||
{"type":"screen-added","file":"/home/gilles/projects/nano_metrics/.superpowers/brainstorm/599687-1779425985/content/layout-v9.html"}
|
||||
@@ -0,0 +1 @@
|
||||
599695
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
Projet : Nano-Telemetry - Directives de Développement (CONSIGNE.md)
|
||||
Rôle et Contexte
|
||||
|
||||
tu pourra faire un brainstorming, tu me parlera en francais, l app et les commentaire en francais, tu realisera un plan de devellopement. n hesite pas a utiliser internet pour tes recherches et verifier les deniere version et pour le debuggage
|
||||
|
||||
Tu es un agent de développement IA chargé de créer "Nano-Telemetry", un système client-serveur de surveillance matérielle. L'objectif absolu de ce projet est de concevoir un agent de collecte de métriques (CPU, RAM, Espace Disque) ayant une empreinte système la plus proche possible de zéro sur des machines Debian, et de transmettre ces données sur un réseau local vers un serveur d'agrégation.
|
||||
Ligne de Conduite 1 : L'Agent de Télémétrie (Rust)
|
||||
|
||||
Langage et Empreinte : L'agent doit être écrit en Rust. L'objectif est d'éviter tout ramasse-miettes (Garbage Collector) pour garantir une utilisation mémoire déterministe et une consommation CPU résiduelle.
|
||||
|
||||
Optimisation de Compilation : Configure le fichier Cargo.toml avec un profil de type "release" strict. Utilise les options strip = true pour supprimer les symboles de débogage, opt-level = "z" pour optimiser la taille du binaire, et lto = true pour l'optimisation à l'édition des liens.
|
||||
|
||||
Collecte des données : Utilise la bibliothèque sysinfo, mais tu dois impérativement désactiver ses fonctionnalités par défaut (default-features = false) pour empêcher le lancement de multiples threads en arrière-plan, ce qui augmenterait inutilement l'empreinte mémoire.
|
||||
|
||||
Orchestration Temporelle : N'inclus aucun moteur asynchrone (comme Tokio). Les fréquences d'actualisation différenciées (ex: CPU toutes les 2s, Disque toutes les 60s) doivent être gérées via une boucle mono-thread utilisant des pauses natives std::thread::sleep pour suspendre complètement le processus.
|
||||
|
||||
Configuration : Implémente la lecture d'un fichier config.toml externe via la bibliothèque serde pour paramétrer dynamiquement l'adresse IP du serveur cible (10.0.0.50) et les métriques à activer.
|
||||
|
||||
Transport : Utilise le protocole UDP pour expédier les charges utiles (payloads) en JSON, privilégiant la vitesse sans état (modèle fire-and-forget) sur un réseau local.
|
||||
|
||||
Ligne de Conduite 2 : Le Serveur d'Ingestion (Go)
|
||||
|
||||
Langage : Le serveur central doit être écrit en Go (Golang) pour profiter de son modèle de concurrence ultra-performant sur les opérations réseau.
|
||||
|
||||
Traitement Réseau : Crée un écouteur UDP. Dès la réception d'un datagramme, déclenche une nouvelle goroutine asynchrone pour traiter la désérialisation JSON afin d'éviter tout goulot d'étranglement lors de réceptions simultanées massives.
|
||||
|
||||
Observabilité : Utilise le paquet standard github.com/prometheus/client_golang pour convertir les données reçues en jauges (Gauges) Prometheus en mémoire vive, et expose ces métriques sur un endpoint HTTP standard /metrics.
|
||||
|
||||
Ligne de Conduite 3 : Déploiement et Sécurité (Systemd sous Debian)
|
||||
|
||||
Confinement : Fournis un fichier unitaire de service systemd pour déployer l'agent Rust sur les machines cibles.
|
||||
|
||||
Moindre Privilège : Intègre la directive DynamicUser=yes, qui génère un utilisateur éphémère et applique automatiquement des sécurités matérielles telles que ProtectSystem=strict et ProtectHome=read-only pour verrouiller l'accès au disque.
|
||||
|
||||
Accès Configuration : Utilise la directive ConfigurationDirectory= afin que ce service confiné puisse lire son fichier TOML de manière sécurisée.
|
||||
|
||||
Ligne de Conduite 4 : Intégration du Design Graphique (UI/UX)
|
||||
|
||||
Ressources Graphiques : Le répertoire du projet contient un dossier nommé design_system.
|
||||
|
||||
Application Stricte : Pour toute interface visuelle, tableau de bord, ou composant web (éventuellement servi par l'application Go pour visualiser l'état du serveur), tu dois strictement et obligatoirement te baser sur les fichiers contenus dans ce dossier design_system. Tu dois respecter la charte graphique, les palettes de couleurs, les polices de caractères et la structure des composants (CSS/Assets) qui s'y trouvent.
|
||||
@@ -0,0 +1,304 @@
|
||||
# mon design system — Gruvbox seventies
|
||||
|
||||
> Design system rétro-futuriste pour applications de monitoring, ops, IoT, domotique.
|
||||
> Orange brûlé, fond brun délavé en sombre / gris clair usé en clair.
|
||||
> **Version 1.0** · deux thèmes (dark + light), 14+ composants React, palette GTK pour GNOME.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Démarrage rapide (web)
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="dark">
|
||||
<head>
|
||||
<!-- 1. Polices Google -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- 2. Icônes Font Awesome 6 -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
|
||||
<!-- 3. Tokens (variables CSS) -->
|
||||
<link rel="stylesheet" href="tokens/tokens.css">
|
||||
|
||||
<!-- 4. React + Babel -->
|
||||
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<!-- 5. Composants UI -->
|
||||
<script type="text/babel" src="components/ui-kit.jsx"></script>
|
||||
<script type="text/babel">
|
||||
// Tes composants ici
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Pour voir tout fonctionner, ouvre `examples/exemple-minimal.html`.
|
||||
|
||||
---
|
||||
|
||||
## 📂 Contenu du package
|
||||
|
||||
```
|
||||
export/
|
||||
├── README.md ← Ce fichier
|
||||
├── consigne_design_system.md ← Brief pour agents IA (Claude, ChatGPT…)
|
||||
├── tokens/
|
||||
│ ├── tokens.css ← Variables CSS web (dark + light)
|
||||
│ ├── tokens.gnome.css ← GTK 4 / libadwaita (apps GNOME)
|
||||
│ └── tokens.json ← Format générique (Tailwind, Figma…)
|
||||
├── components/
|
||||
│ └── ui-kit.jsx ← 14 composants React (Button, IconButton, Toggle, Tooltip,
|
||||
│ StatusLed, BatteryGauge, RadialGauge, BigRadialGauge,
|
||||
│ Popup, TreeNav, Sparkline, LineChart, Icon, …)
|
||||
└── examples/
|
||||
└── exemple-minimal.html ← Démo minimale autoportante
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Ce qui est paramétrable
|
||||
|
||||
### 1. Thème global
|
||||
|
||||
```html
|
||||
<html data-theme="dark"> <!-- ou "light" -->
|
||||
```
|
||||
|
||||
Tu peux mettre `data-theme` sur **n'importe quel parent** pour basculer un sous-arbre uniquement (utile pour une preview en mode opposé dans un menu de réglages).
|
||||
|
||||
### 2. Toutes les couleurs (CSS variables)
|
||||
|
||||
Édite `tokens.css` ou surcharge dans ton propre CSS :
|
||||
|
||||
```css
|
||||
:root[data-theme="dark"] {
|
||||
--accent: #fe8019; /* Couleur principale (orange seventies) */
|
||||
--accent-soft: #d65d0e;
|
||||
--bg-1: #2a231d; /* Fond app */
|
||||
--bg-3: #3c332a; /* Cartes */
|
||||
--ink-1: #f2e5c7; /* Texte */
|
||||
--ok: #4dbb26;
|
||||
--warn: #fabd2f;
|
||||
--err: #fb4934;
|
||||
--blue: #3db0d1; /* Datavis additionnel */
|
||||
--purple: #c882c8;
|
||||
}
|
||||
```
|
||||
|
||||
**4 statuts** (ok / warn / err / info) + **2 couleurs datavis** (blue / purple) + **6 niveaux de fond** + **4 niveaux d'encre** + **3 niveaux de bordure**.
|
||||
|
||||
### 3. Polices
|
||||
|
||||
Trois familles, toutes substituables :
|
||||
|
||||
| Variable | Usage | Défaut |
|
||||
|-----------------|-------------------------------------|---------------------|
|
||||
| `--font-ui` | Interface (titres, corps, boutons) | Inter |
|
||||
| `--font-mono` | Données, code, valeurs numériques | JetBrains Mono |
|
||||
| `--font-terminal` | Logs, terminal embarqué, vibe rétro | Share Tech Mono |
|
||||
|
||||
Pour changer, remplace simplement les `@import` Google Fonts et redéfinis les variables.
|
||||
|
||||
### 4. Ombres et relief
|
||||
|
||||
```css
|
||||
--tile-3d /* Relief 3D marqué pour cartes */
|
||||
--shadow-1, -2, -3 /* Niveaux d'élévation */
|
||||
--shadow-press /* Inset pour état pressé */
|
||||
--hover-glow /* Halo accent au survol */
|
||||
```
|
||||
|
||||
### 5. Composants — props paramétrables
|
||||
|
||||
Chaque composant accepte des props pour personnaliser sans toucher au CSS. Exemples :
|
||||
|
||||
```jsx
|
||||
<Button variant="primary|ghost|danger|default" size="sm|md|lg" icon="play">Texte</Button>
|
||||
|
||||
<IconButton icon="cog" label="Tooltip obligatoire" primary danger active />
|
||||
|
||||
<Toggle on={state} onChange={setState} label="Auto" icon="refresh" />
|
||||
|
||||
<BatteryGauge
|
||||
value={64} max={100} unit="%"
|
||||
label="CPU"
|
||||
warnAt={70} errAt={85} // seuils de couleur
|
||||
compact // mode 1 ligne
|
||||
icon="cpu"
|
||||
color="var(--blue)" // couleur fixe (sinon auto selon seuils)
|
||||
/>
|
||||
|
||||
<RadialGauge value={87} label="SCORE" size={120} />
|
||||
<BigRadialGauge value={87} label="santé système" />
|
||||
|
||||
<Popup open={open} onClose={fn} title="…" footer={…}>
|
||||
Contenu
|
||||
</Popup>
|
||||
|
||||
<TreeNav groups={[
|
||||
{ id, icon: 'server', label, count, open, children: [
|
||||
{ id, label, status: 'ok|warn|err', meta }
|
||||
]}
|
||||
]} activeId={id} onSelect={fn} />
|
||||
```
|
||||
|
||||
Voir la doc complète des props : `Component Reference.html` dans le projet original.
|
||||
|
||||
---
|
||||
|
||||
## 🐧 Utilisation dans une app GNOME (GTK 4 / libadwaita)
|
||||
|
||||
Charge `tokens/tokens.gnome.css` comme provider CSS au démarrage de l'app.
|
||||
|
||||
**Python (PyGObject)** :
|
||||
```python
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
css_provider = Gtk.CssProvider()
|
||||
css_provider.load_from_path("tokens.gnome.css")
|
||||
Gtk.StyleContext.add_provider_for_display(
|
||||
Gdk.Display.get_default(),
|
||||
css_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
)
|
||||
```
|
||||
|
||||
**GJS** :
|
||||
```javascript
|
||||
const provider = new Gtk.CssProvider();
|
||||
provider.load_from_path('tokens.gnome.css');
|
||||
Gtk.StyleContext.add_provider_for_display(
|
||||
Gdk.Display.get_default(),
|
||||
provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
);
|
||||
```
|
||||
|
||||
**Rust (gtk4-rs)** :
|
||||
```rust
|
||||
let provider = gtk::CssProvider::new();
|
||||
provider.load_from_path("tokens.gnome.css");
|
||||
gtk::style_context_add_provider_for_display(
|
||||
&gdk::Display::default().unwrap(),
|
||||
&provider,
|
||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
```
|
||||
|
||||
Le fichier override directement les couleurs sémantiques de libadwaita (`@window_bg_color`, `@accent_color`, etc.) ET ajoute des styles spécifiques pour les widgets courants : `button.suggested-action`, `entry`, `switch`, `scale`, `progressbar`, `notebook`, `popover`…
|
||||
|
||||
Classes CSS supplémentaires à appliquer via `add_css_class()` :
|
||||
- `.tile` / `.card` — Tuile en relief 3D
|
||||
- `.mono` — Texte monospace JetBrains Mono
|
||||
- `.terminal` — Texte terminal Share Tech Mono
|
||||
- `.status.ok` / `.status.warn` / `.status.error` / `.status.info` — Badge de statut
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Intégration dans d'autres outils
|
||||
|
||||
### Tailwind CSS
|
||||
|
||||
Convertis `tokens.json` en `tailwind.config.js` :
|
||||
|
||||
```js
|
||||
const tokens = require('./tokens/tokens.json');
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
accent: tokens.themes.dark.accent.primary.value,
|
||||
ok: tokens.themes.dark.status.ok.value,
|
||||
// …
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [tokens.typography.fonts.ui.family, ...tokens.typography.fonts.ui.fallback],
|
||||
mono: [tokens.typography.fonts.mono.family],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Figma / outils de design
|
||||
|
||||
`tokens.json` suit un schéma compatible avec la plupart des plugins de tokens (Figma Tokens, Style Dictionary). Importe-le directement.
|
||||
|
||||
### Variables Sass / SCSS
|
||||
|
||||
```scss
|
||||
@use 'sass:map';
|
||||
$tokens: (
|
||||
accent: #fe8019,
|
||||
bg-1: #2a231d,
|
||||
ok: #4dbb26,
|
||||
);
|
||||
// …
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Personnalisation avancée
|
||||
|
||||
### Créer un thème dérivé
|
||||
|
||||
Duplique `tokens.css`, change le nom du sélecteur (`[data-theme="ocean"]` par exemple) et modifie les variables. Charge les deux fichiers — `data-theme` choisira automatiquement.
|
||||
|
||||
### Ajouter une couleur status custom
|
||||
|
||||
```css
|
||||
:root[data-theme="dark"] {
|
||||
--critical: #ff0080;
|
||||
--critical-glow: rgba(255, 0, 128, 0.45);
|
||||
}
|
||||
```
|
||||
|
||||
Utilisable ensuite partout : `<StatusLed status="critical">` nécessite une PR dans `ui-kit.jsx` (carte `map` dans `StatusLed`), mais en raw CSS tu peux utiliser la variable directement.
|
||||
|
||||
### Désactiver les effets
|
||||
|
||||
Tous les effets de `transition` / `transform` / `box-shadow` sont concentrés dans les classes `.interactive`, `.bg-hover`, `.gauge-hover`. Surcharge-les en CSS si besoin :
|
||||
|
||||
```css
|
||||
.interactive { transition: none !important; }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist d'intégration
|
||||
|
||||
- [ ] Polices Google Fonts chargées (Inter, JetBrains Mono, Share Tech Mono)
|
||||
- [ ] Font Awesome 6 chargé
|
||||
- [ ] `tokens.css` (web) **ou** `tokens.gnome.css` (GTK) chargé
|
||||
- [ ] Attribut `data-theme="dark"` (ou "light") sur `<html>` ou un parent
|
||||
- [ ] React 18 + Babel chargés (uniquement pour `ui-kit.jsx`)
|
||||
- [ ] `ui-kit.jsx` chargé en `type="text/babel"`
|
||||
|
||||
---
|
||||
|
||||
## 📋 Statuts du système
|
||||
|
||||
| Couleur | Token | Hex (dark) | Hex (light) | Usage |
|
||||
|---------|--------|------------|-------------|-----------------------------|
|
||||
| Accent | `--accent` | `#fe8019` | `#af3a03` | Primaire, focus, sélection |
|
||||
| OK | `--ok` | `#4dbb26` | `#3c911c` | Succès, état nominal |
|
||||
| Warn | `--warn` | `#fabd2f` | `#b57614` | Attention, latence élevée |
|
||||
| Err | `--err` | `#fb4934` | `#9d0006` | Erreur, alerte critique |
|
||||
| Info | `--info` | `#83a598` | `#427b58` | Information neutre |
|
||||
| Blue | `--blue` | `#3db0d1` | `#2d82a3` | Datavis catégorie 2 |
|
||||
| Purple | `--purple` | `#c882c8` | `#8c468c` | Datavis catégorie 3 |
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Pour les agents IA
|
||||
|
||||
Si tu utilises ce design system avec une IA (Claude, GPT, Copilot, etc.), partage-lui le fichier **`consigne_design_system.md`**. Il y trouvera toutes les règles d'utilisation, conventions, contre-exemples à éviter.
|
||||
|
||||
---
|
||||
|
||||
**Licence** · Usage libre dans tes projets. Pas de garantie.
|
||||
@@ -0,0 +1,656 @@
|
||||
/* ============================================================
|
||||
ui-kit.jsx
|
||||
Composants haute-fid Gruvbox Seventies.
|
||||
Tout est purement décoratif/interactif côté composant.
|
||||
Effets : transparence (glass), hover glow, click 3D, tooltips.
|
||||
============================================================ */
|
||||
|
||||
const { useState, useRef, useEffect } = React;
|
||||
|
||||
/* ============================================================
|
||||
Icônes — Font Awesome 6 Free.
|
||||
Mapping nom logique → classe FA. Le CSS de FA est chargé en CDN
|
||||
dans le <head>. Le composant garde la MÊME API qu'avant (name,
|
||||
size, style) pour ne rien casser ailleurs.
|
||||
============================================================ */
|
||||
const ICON_MAP = {
|
||||
cpu: 'microchip',
|
||||
memory: 'memory',
|
||||
disk: 'hard-drive',
|
||||
network: 'network-wired',
|
||||
clock: 'clock',
|
||||
grid: 'table-cells',
|
||||
list: 'list',
|
||||
cog: 'gear',
|
||||
alert: 'triangle-exclamation',
|
||||
bell: 'bell',
|
||||
server: 'server',
|
||||
chart: 'chart-line',
|
||||
bars: 'chart-simple',
|
||||
terminal: 'terminal',
|
||||
refresh: 'arrows-rotate',
|
||||
play: 'play',
|
||||
pause: 'pause',
|
||||
power: 'power-off',
|
||||
sun: 'sun',
|
||||
moon: 'moon',
|
||||
search: 'magnifying-glass',
|
||||
close: 'xmark',
|
||||
chevR: 'chevron-right',
|
||||
chevL: 'chevron-left',
|
||||
chevD: 'chevron-down',
|
||||
chevU: 'chevron-up',
|
||||
plus: 'plus',
|
||||
filter: 'filter',
|
||||
download: 'download',
|
||||
folder: 'folder',
|
||||
node: 'circle-nodes',
|
||||
user: 'user',
|
||||
};
|
||||
|
||||
const Icon = ({ name, size = 16, style }) => {
|
||||
const fa = ICON_MAP[name] || 'circle-question';
|
||||
return (
|
||||
<i className={`fa-solid fa-${fa}`} aria-hidden="true" style={{
|
||||
fontSize: size,
|
||||
width: size,
|
||||
height: size,
|
||||
lineHeight: `${size}px`,
|
||||
textAlign: 'center',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flex: '0 0 auto',
|
||||
color: 'currentColor',
|
||||
...style,
|
||||
}} />
|
||||
);
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
Tooltip — apparaît au hover après 300ms, position auto.
|
||||
============================================================ */
|
||||
function Tooltip({ children, label, side = 'top' }) {
|
||||
const [show, setShow] = useState(false);
|
||||
const t = useRef();
|
||||
const onEnter = () => { t.current = setTimeout(() => setShow(true), 280); };
|
||||
const onLeave = () => { clearTimeout(t.current); setShow(false); };
|
||||
const sides = {
|
||||
top: { bottom: 'calc(100% + 8px)', left: '50%', transform: 'translateX(-50%)' },
|
||||
bottom: { top: 'calc(100% + 8px)', left: '50%', transform: 'translateX(-50%)' },
|
||||
left: { right: 'calc(100% + 8px)', top: '50%', transform: 'translateY(-50%)' },
|
||||
right: { left: 'calc(100% + 8px)', top: '50%', transform: 'translateY(-50%)' },
|
||||
};
|
||||
return (
|
||||
<span style={{ position: 'relative', display: 'inline-flex' }}
|
||||
onMouseEnter={onEnter} onMouseLeave={onLeave}>
|
||||
{children}
|
||||
{show && (
|
||||
<span className="glass-strong" style={{
|
||||
position: 'absolute', ...sides[side],
|
||||
padding: '6px 10px',
|
||||
borderRadius: 6,
|
||||
fontSize: 12, lineHeight: 1.3,
|
||||
color: 'var(--ink-1)',
|
||||
whiteSpace: 'nowrap',
|
||||
boxShadow: 'var(--shadow-2)',
|
||||
zIndex: 1000,
|
||||
pointerEvents: 'none',
|
||||
fontFamily: 'JetBrains Mono, monospace',
|
||||
letterSpacing: '0.02em',
|
||||
}}>{label}</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
IconButton — bouton icône seul + tooltip obligatoire.
|
||||
============================================================ */
|
||||
function IconButton({ icon, label, onClick, active, danger, size = 34, primary }) {
|
||||
const bg = active ? 'var(--accent-tint)'
|
||||
: primary ? 'var(--accent)'
|
||||
: 'var(--bg-3)';
|
||||
const fg = active ? 'var(--accent)'
|
||||
: primary ? 'var(--bg-1)'
|
||||
: danger ? 'var(--err)'
|
||||
: 'var(--ink-2)';
|
||||
const bd = active ? 'var(--accent-soft)' : 'var(--border-2)';
|
||||
return (
|
||||
<Tooltip label={label}>
|
||||
<button onClick={onClick} className="interactive" style={{
|
||||
width: size, height: size,
|
||||
background: bg,
|
||||
color: fg,
|
||||
border: `1px solid ${bd}`,
|
||||
borderRadius: 8,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
padding: 0, cursor: 'pointer',
|
||||
boxShadow: primary ? '0 2px 6px var(--accent-glow)' : 'var(--shadow-1)',
|
||||
}}>
|
||||
<Icon name={icon} size={Math.round(size * 0.5)} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Toggle on/off — switch tactile avec glow accent quand ON
|
||||
============================================================ */
|
||||
function Toggle({ on, onChange, label, icon }) {
|
||||
return (
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
|
||||
{icon && <Icon name={icon} size={14} style={{ color: on ? 'var(--accent)' : 'var(--ink-3)' }} />}
|
||||
{label && <span className="label" style={{ color: on ? 'var(--ink-1)' : 'var(--ink-3)' }}>{label}</span>}
|
||||
<button onClick={() => onChange(!on)} className="interactive" style={{
|
||||
width: 42, height: 22, borderRadius: 12,
|
||||
background: on ? 'var(--accent)' : 'var(--bg-4)',
|
||||
border: `1px solid ${on ? 'var(--accent-soft)' : 'var(--border-2)'}`,
|
||||
boxShadow: on ? `0 0 10px var(--accent-glow), var(--shadow-1)` : 'var(--shadow-press)',
|
||||
position: 'relative', cursor: 'pointer', padding: 0,
|
||||
}}>
|
||||
<span style={{
|
||||
position: 'absolute', top: 1, left: on ? 21 : 1,
|
||||
width: 18, height: 18, borderRadius: '50%',
|
||||
background: on ? 'var(--bg-1)' : 'var(--ink-2)',
|
||||
transition: 'left .18s cubic-bezier(.5,.2,.3,1.3), background .15s',
|
||||
boxShadow: 'var(--shadow-1)',
|
||||
}} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Status LED — pastille pulsante (effet halo si critique)
|
||||
============================================================ */
|
||||
function StatusLed({ status = 'ok', size = 10, pulse }) {
|
||||
const map = {
|
||||
ok: { c: 'var(--ok)', g: 'var(--ok-glow)' },
|
||||
warn: { c: 'var(--warn)', g: 'var(--warn-glow)' },
|
||||
err: { c: 'var(--err)', g: 'var(--err-glow)' },
|
||||
off: { c: 'var(--ink-4)', g: 'transparent' },
|
||||
info: { c: 'var(--info)', g: 'var(--info-glow)' },
|
||||
};
|
||||
const { c, g } = map[status];
|
||||
const id = `pulse-${status}-${size}`;
|
||||
return (
|
||||
<>
|
||||
{pulse && (
|
||||
<style>{`@keyframes ${id} { 0%{box-shadow:0 0 0 0 ${g}} 70%{box-shadow:0 0 0 6px transparent} 100%{box-shadow:0 0 0 0 transparent} }`}</style>
|
||||
)}
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
width: size, height: size,
|
||||
borderRadius: '50%',
|
||||
background: c,
|
||||
boxShadow: status === 'off' ? 'none' : `0 0 6px ${g}, inset 0 0 2px rgba(0,0,0,0.3)`,
|
||||
animation: pulse ? `${id} 1.8s ease-out infinite` : 'none',
|
||||
flex: '0 0 auto',
|
||||
}} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
BatteryGauge — jauge horizontale style batterie
|
||||
- Pas de bandes (couleur unie + léger gloss interne)
|
||||
- Pas de graduations verticales
|
||||
- Hover : glow lumineux dans la couleur de la jauge
|
||||
- Mode compact : label [bar] valeur sur une seule ligne
|
||||
============================================================ */
|
||||
function BatteryGauge({ value = 60, label, max = 100, unit = '%', warnAt = 70, errAt = 90, height = 22, compact = false, color: colorOverride, icon }) {
|
||||
const pct = Math.max(0, Math.min(100, (value / max) * 100));
|
||||
const color = colorOverride
|
||||
|| (pct >= errAt ? 'var(--err)' : pct >= warnAt ? 'var(--warn)' : 'var(--ok)');
|
||||
const glowVar = pct >= errAt ? 'var(--err-glow)'
|
||||
: pct >= warnAt ? 'var(--warn-glow)'
|
||||
: 'var(--ok-glow)';
|
||||
|
||||
// Variante compacte : label [bar] valeur sur une seule ligne
|
||||
if (compact) {
|
||||
return (
|
||||
<div className="bg-hover" style={{
|
||||
display: 'flex', alignItems: 'center', gap: 10, minWidth: 0,
|
||||
'--bg-glow': glowVar,
|
||||
}}>
|
||||
{(icon || label) && (
|
||||
<span style={{
|
||||
flex: '0 0 auto', display: 'inline-flex', alignItems: 'center', gap: 6,
|
||||
minWidth: 90,
|
||||
}}>
|
||||
{icon && <Icon name={icon} size={12} style={{ color: 'var(--ink-3)' }} />}
|
||||
{label && <span className="label" style={{ fontSize: 11 }}>{label}</span>}
|
||||
</span>
|
||||
)}
|
||||
<div className="bg-bar" style={{
|
||||
flex: 1, height: 12, borderRadius: 3,
|
||||
background: 'var(--bg-1)',
|
||||
border: '1px solid var(--border-2)',
|
||||
boxShadow: 'inset 0 1px 2px rgba(0,0,0,0.4)',
|
||||
overflow: 'hidden', position: 'relative',
|
||||
transition: 'border-color .2s',
|
||||
}}>
|
||||
<div className="bg-fill" style={{
|
||||
position: 'absolute', top: 1, left: 1, bottom: 1,
|
||||
width: `calc((100% - 2px) * ${pct / 100})`,
|
||||
background: color,
|
||||
borderRadius: 2,
|
||||
transition: 'width .4s cubic-bezier(.3,.6,.3,1), box-shadow .2s',
|
||||
}} />
|
||||
</div>
|
||||
<span className="mono" style={{
|
||||
flex: '0 0 auto', fontSize: 13,
|
||||
color: 'var(--ink-1)', minWidth: 52, textAlign: 'right',
|
||||
}}>
|
||||
{value}<span style={{ color: 'var(--ink-3)', marginLeft: 2 }}>{unit}</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-hover" style={{
|
||||
display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0,
|
||||
'--bg-glow': glowVar,
|
||||
}}>
|
||||
{label && (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
|
||||
<span className="label">{label}</span>
|
||||
<span className="mono" style={{ fontSize: 13, color: 'var(--ink-1)' }}>
|
||||
{value}<span style={{ color: 'var(--ink-3)', marginLeft: 2 }}>{unit}</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="bg-bar" style={{
|
||||
position: 'relative',
|
||||
height, borderRadius: 4,
|
||||
background: 'var(--bg-1)',
|
||||
border: '1px solid var(--border-2)',
|
||||
boxShadow: 'inset 0 1px 2px rgba(0,0,0,0.4)',
|
||||
overflow: 'hidden',
|
||||
transition: 'border-color .2s',
|
||||
}}>
|
||||
<div className="bg-fill" style={{
|
||||
position: 'absolute', top: 1, left: 1, bottom: 1,
|
||||
width: `calc((100% - 2px) * ${pct / 100})`,
|
||||
background: color,
|
||||
borderRadius: 3,
|
||||
transition: 'width .4s cubic-bezier(.3,.6,.3,1), box-shadow .2s',
|
||||
}} />
|
||||
{/* Gloss interne très léger (un seul highlight haut, pas de bande inférieure) */}
|
||||
<div style={{
|
||||
position: 'absolute', top: 1, left: 1, right: 1, height: '40%',
|
||||
background: 'linear-gradient(180deg, rgba(255,255,255,0.18), transparent)',
|
||||
borderRadius: '3px 3px 0 0',
|
||||
pointerEvents: 'none',
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
RadialGauge — jauge ronde, version épurée
|
||||
============================================================ */
|
||||
function RadialGauge({ value = 64, label, size = 120, warnAt = 70, errAt = 90 }) {
|
||||
const pct = Math.max(0, Math.min(100, value));
|
||||
const color = pct >= errAt ? 'var(--err)' : pct >= warnAt ? 'var(--warn)' : 'var(--ok)';
|
||||
const glow = pct >= errAt ? 'var(--err-glow)' : pct >= warnAt ? 'var(--warn-glow)' : 'var(--ok-glow)';
|
||||
const r = size / 2 - 10;
|
||||
const cx = size / 2;
|
||||
const cy = size / 2 + 6;
|
||||
const circ = Math.PI * r;
|
||||
const offset = circ - (pct / 100) * circ;
|
||||
return (
|
||||
<div className="gauge-hover" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
|
||||
<svg width={size} height={size * 0.72} viewBox={`0 0 ${size} ${size * 0.8}`}>
|
||||
<defs>
|
||||
<filter id={`glow-${label}`} x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="2.5" />
|
||||
</filter>
|
||||
</defs>
|
||||
{/* arc background */}
|
||||
<path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
|
||||
fill="none" stroke="var(--bg-4)" strokeWidth="6" strokeLinecap="round" />
|
||||
{/* arc value glow */}
|
||||
<path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
|
||||
fill="none" stroke={color} strokeWidth="8" strokeLinecap="round"
|
||||
strokeDasharray={circ} strokeDashoffset={offset}
|
||||
filter={`url(#glow-${label})`} opacity="0.7" />
|
||||
{/* arc value crisp */}
|
||||
<path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
|
||||
fill="none" stroke={color} strokeWidth="5" strokeLinecap="round"
|
||||
strokeDasharray={circ} strokeDashoffset={offset}
|
||||
style={{ transition: 'stroke-dashoffset .5s cubic-bezier(.3,.6,.3,1)' }} />
|
||||
</svg>
|
||||
<div style={{ marginTop: -10, textAlign: 'center' }}>
|
||||
<div className="mono" style={{ fontSize: size * 0.22, fontWeight: 600, color: 'var(--ink-1)', lineHeight: 1 }}>
|
||||
{value}<span style={{ fontSize: '0.55em', color: 'var(--ink-3)', marginLeft: 2 }}>%</span>
|
||||
</div>
|
||||
{label && <div className="label" style={{ marginTop: 2 }}>{label}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
BigRadialGauge — la grande jauge cockpit "santé système"
|
||||
============================================================ */
|
||||
function BigRadialGauge({ value = 87, label = 'score santé · stable' }) {
|
||||
const size = 320;
|
||||
const r = 130;
|
||||
const cx = size / 2;
|
||||
const cy = size / 2 + 30;
|
||||
const circ = Math.PI * r;
|
||||
const offset = circ - (value / 100) * circ;
|
||||
const color = value >= 80 ? 'var(--ok)' : value >= 50 ? 'var(--warn)' : 'var(--err)';
|
||||
return (
|
||||
<div className="gauge-hover" style={{ position: 'relative', width: size, height: size * 0.78 }}>
|
||||
<svg width={size} height={size * 0.85}>
|
||||
<defs>
|
||||
<filter id="biggauge-glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="4" />
|
||||
</filter>
|
||||
<linearGradient id="biggauge-grad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stopColor={color} stopOpacity="0.7"/>
|
||||
<stop offset="1" stopColor={color}/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
{/* tics */}
|
||||
{Array.from({ length: 21 }).map((_, i) => {
|
||||
const a = Math.PI - (i / 20) * Math.PI;
|
||||
const major = i % 5 === 0;
|
||||
const inner = major ? r + 8 : r + 11;
|
||||
const outer = major ? r + 20 : r + 15;
|
||||
return <line key={i}
|
||||
x1={cx + Math.cos(a) * inner} y1={cy - Math.sin(a) * inner}
|
||||
x2={cx + Math.cos(a) * outer} y2={cy - Math.sin(a) * outer}
|
||||
stroke={major ? 'var(--ink-3)' : 'var(--ink-4)'} strokeWidth={major ? 1.5 : 0.8}
|
||||
/>;
|
||||
})}
|
||||
{[0, 50, 100].map(v => {
|
||||
const a = Math.PI - (v / 100) * Math.PI;
|
||||
const x = cx + Math.cos(a) * (r + 32);
|
||||
const y = cy - Math.sin(a) * (r + 32) + 4;
|
||||
return <text key={v} x={x} y={y} textAnchor="middle"
|
||||
fontFamily="JetBrains Mono" fontSize="11"
|
||||
fill="var(--ink-3)">{v}</text>;
|
||||
})}
|
||||
{/* arc bg */}
|
||||
<path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
|
||||
fill="none" stroke="var(--bg-4)" strokeWidth="10" strokeLinecap="round" />
|
||||
{/* arc value glow */}
|
||||
<path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
|
||||
fill="none" stroke={color} strokeWidth="14" strokeLinecap="round"
|
||||
strokeDasharray={circ} strokeDashoffset={offset}
|
||||
filter="url(#biggauge-glow)" opacity="0.55" />
|
||||
{/* arc value */}
|
||||
<path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
|
||||
fill="none" stroke="url(#biggauge-grad)" strokeWidth="9" strokeLinecap="round"
|
||||
strokeDasharray={circ} strokeDashoffset={offset}
|
||||
style={{ transition: 'stroke-dashoffset .8s cubic-bezier(.3,.6,.3,1)' }} />
|
||||
{/* needle */}
|
||||
<line x1={cx} y1={cy}
|
||||
x2={cx + Math.cos(Math.PI - (value / 100) * Math.PI) * (r - 14)}
|
||||
y2={cy - Math.sin(Math.PI - (value / 100) * Math.PI) * (r - 14)}
|
||||
stroke="var(--accent)" strokeWidth="3" strokeLinecap="round"
|
||||
style={{ filter: 'drop-shadow(0 0 4px var(--accent-glow))' }} />
|
||||
<circle cx={cx} cy={cy} r="9" fill="var(--bg-3)" stroke="var(--border-3)" strokeWidth="1.5"/>
|
||||
<circle cx={cx} cy={cy} r="3" fill="var(--accent)" />
|
||||
</svg>
|
||||
<div style={{ position: 'absolute', bottom: 12, left: 0, right: 0, textAlign: 'center' }}>
|
||||
<div className="mono" style={{
|
||||
fontSize: 64, fontWeight: 700, lineHeight: 1,
|
||||
color: 'var(--ink-1)',
|
||||
textShadow: `0 0 20px ${color}33`,
|
||||
}}>{value}</div>
|
||||
<div className="label" style={{ marginTop: 6 }}>{label}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Popup — modale glassmorphism centrée + bouton fermer
|
||||
============================================================ */
|
||||
function Popup({ open, onClose, title, children, footer, width = 460 }) {
|
||||
if (!open) return null;
|
||||
return (
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0, zIndex: 100,
|
||||
background: 'rgba(0,0,0,0.45)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
animation: 'fadein .2s ease-out',
|
||||
}} onClick={onClose}>
|
||||
<style>{`
|
||||
@keyframes fadein { from { opacity: 0 } to { opacity: 1 } }
|
||||
@keyframes popin { from { opacity: 0; transform: translateY(8px) scale(.98) } to { opacity: 1; transform: translateY(0) scale(1) } }
|
||||
`}</style>
|
||||
<div className="glass-strong" onClick={e => e.stopPropagation()} style={{
|
||||
width, maxWidth: '90%',
|
||||
borderRadius: 12,
|
||||
boxShadow: 'var(--shadow-3)',
|
||||
animation: 'popin .25s cubic-bezier(.3,.7,.3,1.2)',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<div style={{
|
||||
padding: '14px 16px',
|
||||
borderBottom: '1px solid var(--border-1)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
background: 'var(--bg-3)',
|
||||
}}>
|
||||
<div style={{ fontWeight: 600, fontSize: 15, color: 'var(--ink-1)' }}>{title}</div>
|
||||
<IconButton icon="close" label="Fermer" onClick={onClose} size={28} />
|
||||
</div>
|
||||
<div style={{ padding: 18 }}>{children}</div>
|
||||
{footer && (
|
||||
<div style={{
|
||||
padding: '12px 16px',
|
||||
borderTop: '1px solid var(--border-1)',
|
||||
background: 'var(--bg-2)',
|
||||
display: 'flex', justifyContent: 'flex-end', gap: 8,
|
||||
}}>{footer}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Button — bouton classique avec variantes
|
||||
============================================================ */
|
||||
function Button({ children, icon, onClick, variant = 'default', size = 'md' }) {
|
||||
const sizes = {
|
||||
sm: { padding: '5px 10px', fontSize: 12, h: 28 },
|
||||
md: { padding: '7px 14px', fontSize: 13, h: 34 },
|
||||
lg: { padding: '10px 18px', fontSize: 14, h: 40 },
|
||||
}[size];
|
||||
const variants = {
|
||||
default: { bg: 'var(--bg-3)', fg: 'var(--ink-1)', bd: 'var(--border-2)' },
|
||||
primary: { bg: 'var(--accent)', fg: 'var(--bg-1)', bd: 'var(--accent-soft)' },
|
||||
ghost: { bg: 'transparent', fg: 'var(--ink-2)', bd: 'var(--border-2)' },
|
||||
danger: { bg: 'var(--bg-3)', fg: 'var(--err)', bd: 'var(--err)' },
|
||||
}[variant];
|
||||
return (
|
||||
<button onClick={onClick} className="interactive" style={{
|
||||
height: sizes.h,
|
||||
padding: sizes.padding,
|
||||
background: variants.bg,
|
||||
color: variants.fg,
|
||||
border: `1px solid ${variants.bd}`,
|
||||
borderRadius: 8,
|
||||
display: 'inline-flex', alignItems: 'center', gap: 8,
|
||||
fontFamily: 'inherit', fontSize: sizes.fontSize, fontWeight: 500,
|
||||
cursor: 'pointer',
|
||||
boxShadow: variant === 'primary' ? '0 2px 8px var(--accent-glow)' : 'var(--shadow-1)',
|
||||
}}>
|
||||
{icon && <Icon name={icon} size={14} />}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
TreeNav — arbre dépliable avec icône en tête (style B)
|
||||
============================================================ */
|
||||
function TreeNav({ groups, activeId, onSelect }) {
|
||||
const [open, setOpen] = useState(() =>
|
||||
Object.fromEntries(groups.map(g => [g.id, g.open !== false]))
|
||||
);
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{groups.map(g => (
|
||||
<div key={g.id}>
|
||||
<div className="interactive" onClick={() => setOpen({ ...open, [g.id]: !open[g.id] })}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 8,
|
||||
padding: '7px 8px', borderRadius: 6,
|
||||
color: 'var(--ink-2)',
|
||||
background: 'transparent',
|
||||
border: '1px solid transparent',
|
||||
cursor: 'pointer',
|
||||
}}>
|
||||
<Icon name="chevR" size={12} style={{
|
||||
transform: open[g.id] ? 'rotate(90deg)' : 'rotate(0)',
|
||||
transition: 'transform .15s',
|
||||
color: 'var(--ink-3)',
|
||||
}} />
|
||||
<Icon name={g.icon || 'folder'} size={15} style={{ color: 'var(--accent)' }} />
|
||||
<span style={{ flex: 1, fontSize: 13, fontWeight: 500 }}>{g.label}</span>
|
||||
{g.count != null && (
|
||||
<span className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>
|
||||
{g.count}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{open[g.id] && (
|
||||
<div style={{ marginLeft: 18, marginTop: 2, display: 'flex', flexDirection: 'column', gap: 1, paddingLeft: 8, borderLeft: '1px dashed var(--border-1)' }}>
|
||||
{g.children.map(c => {
|
||||
const active = c.id === activeId;
|
||||
return (
|
||||
<div key={c.id} className="interactive" onClick={() => onSelect && onSelect(c.id)}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 8,
|
||||
padding: '6px 10px', borderRadius: 6,
|
||||
background: active ? 'var(--accent-tint)' : 'transparent',
|
||||
color: active ? 'var(--ink-1)' : 'var(--ink-2)',
|
||||
borderLeft: active ? '2px solid var(--accent)' : '2px solid transparent',
|
||||
marginLeft: active ? 0 : 2,
|
||||
fontSize: 12.5,
|
||||
}}>
|
||||
<StatusLed status={c.status} size={8} pulse={c.status === 'err'} />
|
||||
<span className="mono" style={{ fontSize: 11.5, flex: 1 }}>{c.label}</span>
|
||||
{c.meta && <span className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{c.meta}</span>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Sparkline pour les KPI
|
||||
============================================================ */
|
||||
function Sparkline({ points = [], color = 'var(--accent)', h = 28 }) {
|
||||
const w = 100;
|
||||
const max = Math.max(...points);
|
||||
const min = Math.min(...points);
|
||||
const range = max - min || 1;
|
||||
const step = w / (points.length - 1);
|
||||
const path = points.map((p, i) =>
|
||||
`${i === 0 ? 'M' : 'L'} ${(i * step).toFixed(1)} ${(h - 2 - ((p - min) / range) * (h - 4)).toFixed(1)}`
|
||||
).join(' ');
|
||||
const area = path + ` L ${w} ${h} L 0 ${h} Z`;
|
||||
return (
|
||||
<svg viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width: '100%', height: h }}>
|
||||
<path d={area} fill={color} opacity="0.12" />
|
||||
<path d={path} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
LineChart — grand graph multi-séries
|
||||
============================================================ */
|
||||
function LineChart({ series, h = 200, labels }) {
|
||||
const w = 600;
|
||||
const padding = { l: 36, r: 12, t: 12, b: 24 };
|
||||
const innerW = w - padding.l - padding.r;
|
||||
const innerH = h - padding.t - padding.b;
|
||||
const all = series.flatMap(s => s.points);
|
||||
const max = Math.max(...all) * 1.1;
|
||||
const min = 0;
|
||||
const range = max - min;
|
||||
const ptsCount = series[0].points.length;
|
||||
const step = innerW / (ptsCount - 1);
|
||||
return (
|
||||
<svg viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width: '100%', height: h }}>
|
||||
{/* grid horizontal */}
|
||||
{[0, 0.25, 0.5, 0.75, 1].map(p => {
|
||||
const y = padding.t + innerH * p;
|
||||
const v = Math.round(max - range * p);
|
||||
return (
|
||||
<g key={p}>
|
||||
<line x1={padding.l} x2={w - padding.r} y1={y} y2={y}
|
||||
stroke="var(--border-1)" strokeWidth="1" strokeDasharray="3 5" />
|
||||
<text x={padding.l - 6} y={y + 3} textAnchor="end"
|
||||
fontFamily="JetBrains Mono" fontSize="9" fill="var(--ink-3)">{v}</text>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
{/* labels x */}
|
||||
{labels && labels.map((lb, i) => (
|
||||
i % Math.ceil(labels.length / 8) === 0 && (
|
||||
<text key={i} x={padding.l + i * step} y={h - 6} textAnchor="middle"
|
||||
fontFamily="JetBrains Mono" fontSize="9" fill="var(--ink-3)">{lb}</text>
|
||||
)
|
||||
))}
|
||||
{/* séries */}
|
||||
{series.map((s, si) => {
|
||||
const path = s.points.map((p, i) =>
|
||||
`${i === 0 ? 'M' : 'L'} ${(padding.l + i * step).toFixed(1)} ${(padding.t + innerH - ((p - min) / range) * innerH).toFixed(1)}`
|
||||
).join(' ');
|
||||
const area = path + ` L ${padding.l + (ptsCount - 1) * step} ${padding.t + innerH} L ${padding.l} ${padding.t + innerH} Z`;
|
||||
return (
|
||||
<g key={si}>
|
||||
<path d={area} fill={s.color} opacity="0.12" />
|
||||
<path d={path} fill="none" stroke={s.color} strokeWidth="1.8"
|
||||
strokeLinejoin="round" strokeLinecap="round" />
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/* Expose */
|
||||
Object.assign(window, {
|
||||
Icon, Tooltip, IconButton, Toggle, StatusLed,
|
||||
BatteryGauge, RadialGauge, BigRadialGauge,
|
||||
Popup, Button, TreeNav, Sparkline, LineChart,
|
||||
});
|
||||
|
||||
/* Effets hover sur les jauges (sans effet au clic) */
|
||||
(function injectGaugeHoverStyles() {
|
||||
if (document.getElementById('gauge-hover-styles')) return;
|
||||
const s = document.createElement('style');
|
||||
s.id = 'gauge-hover-styles';
|
||||
s.textContent = `
|
||||
.bg-hover:hover .bg-bar {
|
||||
border-color: color-mix(in oklch, var(--accent) 60%, var(--border-3));
|
||||
}
|
||||
.bg-hover:hover .bg-fill {
|
||||
box-shadow: 0 0 14px var(--bg-glow, var(--accent-glow));
|
||||
filter: brightness(1.15);
|
||||
}
|
||||
.gauge-hover { transition: filter .2s; }
|
||||
.gauge-hover:hover { filter: drop-shadow(0 0 8px var(--accent-glow)) brightness(1.08); }
|
||||
`;
|
||||
document.head.appendChild(s);
|
||||
})();
|
||||
@@ -0,0 +1,363 @@
|
||||
# Consignes — mon design system (Gruvbox seventies)
|
||||
|
||||
> **Tu es un agent IA chargé de produire ou modifier du code utilisant ce design system.**
|
||||
> Lis ce fichier en entier avant d'écrire la moindre ligne. Suis les règles à la lettre.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Identité du système
|
||||
|
||||
- **Nom** : mon design system — Gruvbox seventies
|
||||
- **Vibe** : rétro-industriel, console de monitoring, SCADA, terminal années 70
|
||||
- **Palette** : orange brûlé Gruvbox + fond brun délavé (pas noir intense) ou gris clair usé (pas blanc pur)
|
||||
- **Cas d'usage cibles** : tableaux de bord, monitoring, IoT, domotique, ops, scanners réseau
|
||||
- **Public** : utilisateurs techniques (admin sys, devs, makers) — densité d'info élevée acceptée
|
||||
|
||||
---
|
||||
|
||||
## 📁 Fichiers à connaître
|
||||
|
||||
| Fichier | Contient |
|
||||
|---------------------------------|-------------------------------------------------------|
|
||||
| `tokens/tokens.css` | Variables CSS web (`:root[data-theme="dark|light"]`) |
|
||||
| `tokens/tokens.gnome.css` | Tokens GTK 4 / libadwaita (`@define-color`) |
|
||||
| `tokens/tokens.json` | Tokens en JSON pour outils externes |
|
||||
| `components/ui-kit.jsx` | 14 composants React (Button, Icon, Popup…) |
|
||||
| `examples/exemple-minimal.html` | Démo de référence |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Règles absolues — ne JAMAIS enfreindre
|
||||
|
||||
1. **Toujours utiliser les variables CSS**, jamais des hex en dur dans le code utilisateur.
|
||||
✅ `color: var(--accent)`
|
||||
❌ `color: #fe8019`
|
||||
|
||||
2. **Toujours déclarer `data-theme`** sur un parent (`<html>` ou un wrapper).
|
||||
Sans ça, les variables ne sont pas définies et l'UI casse silencieusement.
|
||||
|
||||
3. **Composants existants** — ne jamais en réinventer. Vérifier d'abord la liste ci-dessous.
|
||||
|
||||
4. **Icônes** — utiliser le composant `<Icon name="…">` avec les noms mappés. JAMAIS d'emoji, JAMAIS de SVG inline custom pour un cas où une icône Font Awesome existe.
|
||||
|
||||
5. **Pas d'effet hover** sur les boutons / tuiles / composants généraux (sauf jauges et tuiles Heimdall qui en ont un). Seulement **pression 3D au clic** via `.interactive`.
|
||||
|
||||
6. **Toujours des tooltips** sur les boutons icônes seuls (`<IconButton>` exige `label`).
|
||||
|
||||
7. **Pas de bordure arrondie excessive**. Tuiles : `border-radius: 10-12px`. Boutons : `8px`. Pastilles : `999px`.
|
||||
|
||||
8. **Polices** — respecter strictement les 3 familles :
|
||||
- **Inter** → UI (titres, corps, boutons, labels d'interface généraux)
|
||||
- **JetBrains Mono** → données numériques, valeurs, code, IDs, IPs
|
||||
- **Share Tech Mono** → logs, terminal embarqué, ambiance rétro
|
||||
Toute autre police = bug.
|
||||
|
||||
9. **Tonalité** : labels en `text-transform: uppercase` + `letter-spacing: 0.08em` (classe `.label` déjà fournie).
|
||||
|
||||
10. **Densité** : pas de padding inutile. Ce DS est dense par nature. Tuiles : padding 14-18px. Boutons : 6-10px vertical.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Tokens disponibles
|
||||
|
||||
### Couleurs (toutes définies en `dark` ET `light`)
|
||||
|
||||
#### Fonds (du plus profond au plus haut)
|
||||
```
|
||||
--bg-0 très rare, niveau le plus bas
|
||||
--bg-1 fond application principal
|
||||
--bg-2 panneaux (sidebar, headerbar)
|
||||
--bg-3 cartes, tuiles ← LE PLUS UTILISÉ
|
||||
--bg-4 hover, état actif
|
||||
--bg-5 press, sélection forte
|
||||
```
|
||||
|
||||
#### Texte (du plus contrasté au moins)
|
||||
```
|
||||
--ink-1 texte principal
|
||||
--ink-2 texte secondaire
|
||||
--ink-3 labels, hints
|
||||
--ink-4 désactivé
|
||||
```
|
||||
|
||||
#### Accent
|
||||
```
|
||||
--accent couleur primaire (orange Gruvbox seventies)
|
||||
--accent-soft variante foncée (bordures, hover)
|
||||
--accent-glow halo (rgba)
|
||||
--accent-tint teinte transparente (fonds discrets)
|
||||
```
|
||||
|
||||
#### Statuts
|
||||
```
|
||||
--ok #4dbb26 (vert flashy)
|
||||
--warn #fabd2f (jaune)
|
||||
--err #fb4934 (rouge)
|
||||
--info #83a598 (vert-bleu pastel)
|
||||
```
|
||||
|
||||
#### Datavis additionnel
|
||||
```
|
||||
--blue #3db0d1
|
||||
--purple #c882c8
|
||||
```
|
||||
|
||||
#### Bordures
|
||||
```
|
||||
--border-1, --border-2, --border-3 du plus subtil au plus marqué
|
||||
```
|
||||
|
||||
#### Ombres / relief
|
||||
```
|
||||
--shadow-1, -2, -3 élévations standards
|
||||
--shadow-press état pressé (inset)
|
||||
--tile-3d relief 3D marqué pour cartes ← À utiliser sur les tuiles importantes
|
||||
```
|
||||
|
||||
### Polices
|
||||
```
|
||||
--font-ui 'Inter', system-ui, sans-serif
|
||||
--font-mono 'JetBrains Mono', monospace
|
||||
--font-terminal 'Share Tech Mono', monospace
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Composants — quand utiliser quoi
|
||||
|
||||
| Besoin | Composant | Exemple |
|
||||
|--------|-----------|---------|
|
||||
| Bouton texte avec ou sans icône | `<Button variant="primary|ghost|danger|default">` | Action principale, secondaire |
|
||||
| Bouton icône seul | `<IconButton icon="…" label="…">` | Toolbars, headers (le `label` devient tooltip) |
|
||||
| On/off | `<Toggle on={…} onChange={…} label icon>` | Activer/désactiver une option |
|
||||
| État système | `<StatusLed status="ok|warn|err|info|off" pulse>` | LED pulsante pour critique |
|
||||
| Jauge ronde standard | `<RadialGauge value={…} label size>` | KPI compact, cockpit |
|
||||
| Jauge ronde héro | `<BigRadialGauge value={…} label>` | Métrique principale unique |
|
||||
| Jauge barre standard | `<BatteryGauge value label>` | Stack vertical de ressources |
|
||||
| Jauge barre **inline** | `<BatteryGauge compact value label icon>` | Listes denses, label + barre + valeur sur 1 ligne |
|
||||
| Modale | `<Popup open onClose title footer>` | Confirmation, config détaillée |
|
||||
| Tree dépliable | `<TreeNav groups activeId onSelect>` | Sidebar hiérarchique (clusters/nodes) |
|
||||
| Mini graphe | `<Sparkline points color>` | Dans une tuile KPI |
|
||||
| Graphe ligne | `<LineChart series labels h>` | Évolution temporelle multi-séries |
|
||||
| Tooltip | `<Tooltip label side><…/></Tooltip>` | Toute icône isolée |
|
||||
| Icône | `<Icon name="…" size>` | JAMAIS d'emoji, JAMAIS de SVG custom |
|
||||
|
||||
### Icônes disponibles (noms logiques → Font Awesome)
|
||||
|
||||
`cpu`, `memory`, `disk`, `network`, `clock`, `grid`, `list`, `cog`, `alert`, `bell`, `server`, `chart`, `bars`, `terminal`, `refresh`, `play`, `pause`, `power`, `sun`, `moon`, `search`, `close`, `chevR`, `chevL`, `chevD`, `chevU`, `plus`, `filter`, `download`, `folder`, `node`, `user`.
|
||||
|
||||
Pour un nouveau besoin → utiliser une icône Font Awesome (préfixe `fa-solid fa-…`) en ajoutant l'alias dans `ICON_MAP` au sein de `ui-kit.jsx`.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Patterns d'agencement standards
|
||||
|
||||
### Layout dashboard 3 colonnes
|
||||
```
|
||||
┌─ Header (tabs workspace + search + actions + statut connexion) ─┐
|
||||
├──────┬────────────────────────────────┬──────────────────────────┤
|
||||
│ Tree │ Center cockpit (KPIs + jauges) │ Logs/Terminal repliable │
|
||||
│ nav │ │ │
|
||||
├──────┴────────────────────────────────┴──────────────────────────┤
|
||||
│ Status bar (mode · workspace · stats · horloge) │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Tuile KPI standard
|
||||
```jsx
|
||||
<div className="glass" style={{ padding: 12, borderRadius: 10, ...}}>
|
||||
<Icon name="cpu" /> <span className="label">CPU</span>
|
||||
<span className="mono">{value}<span className="label">%</span></span>
|
||||
<Sparkline points={trend} color="var(--accent)" />
|
||||
</div>
|
||||
```
|
||||
|
||||
### Status bar inférieure
|
||||
- Première cellule = mode courant en fond accent (style tmux)
|
||||
- Cellules séparées par `border-right: 1px solid var(--border-1)`
|
||||
- Police `Share Tech Mono` 11-12px
|
||||
- Horloge à droite
|
||||
|
||||
---
|
||||
|
||||
## 🚫 Anti-patterns à éviter
|
||||
|
||||
### NE PAS faire :
|
||||
|
||||
❌ **Mettre des emoji** pour un état :
|
||||
```jsx
|
||||
<span>✅ Système OK</span> // NON
|
||||
<><StatusLed status="ok" /> Système OK</> // OUI
|
||||
```
|
||||
|
||||
❌ **Inventer de nouvelles couleurs hors palette** :
|
||||
```jsx
|
||||
style={{ color: '#ff00aa' }} // NON — utilise les tokens
|
||||
```
|
||||
|
||||
❌ **Police arbitraire** :
|
||||
```jsx
|
||||
fontFamily: 'Roboto' // NON
|
||||
fontFamily: 'var(--font-ui)' // OUI
|
||||
```
|
||||
|
||||
❌ **Bordures arrondies à 24px+** sur des cartes (vibe trop SaaS pastel).
|
||||
|
||||
❌ **Tooltip absent sur une icône isolée** :
|
||||
```jsx
|
||||
<button><Icon name="cog" /></button> // NON
|
||||
<IconButton icon="cog" label="Configurer" onClick={fn} /> // OUI
|
||||
```
|
||||
|
||||
❌ **`window.alert` / `confirm`** — toujours utiliser `<Popup>`.
|
||||
|
||||
❌ **Texte secondaire en `--ink-1`** — choisir la bonne couche d'encre selon la hiérarchie.
|
||||
|
||||
❌ **Sur-utiliser le glow / shadow** — réservé aux accents importants.
|
||||
|
||||
❌ **Mélanger les casses de label** — labels = uppercase mono, titres = sentence case.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Patterns recommandés
|
||||
|
||||
### Hiérarchie de fond
|
||||
- App / page → `--bg-1`
|
||||
- Sidebar / headerbar → `--bg-2`
|
||||
- Tuiles / cartes principales → `--bg-3` ou `.glass`
|
||||
- Input fields / containers profonds → `--bg-1` avec inset shadow
|
||||
|
||||
### Effet glass standard
|
||||
```jsx
|
||||
className="glass" // backdrop-filter + bg semi-transparent + tile-3d shadow
|
||||
```
|
||||
ou pour plus marqué :
|
||||
```jsx
|
||||
className="glass-strong"
|
||||
```
|
||||
|
||||
### Validation visuelle d'un état critique
|
||||
```jsx
|
||||
<StatusLed status="err" pulse /> // pastille pulsante
|
||||
<Button variant="danger" icon="power">…</Button>
|
||||
// + bordure rouge sur le conteneur :
|
||||
style={{ border: '1px solid var(--err)', boxShadow: 'inset 0 1px 0 rgba(251,73,52,0.2), 0 0 18px rgba(251,73,52,0.15)' }}
|
||||
```
|
||||
|
||||
### Sticky footer d'actions (form)
|
||||
```jsx
|
||||
<div className="glass-strong" style={{
|
||||
padding: '12px 20px',
|
||||
display: 'flex', gap: 12, alignItems: 'center',
|
||||
borderTop: '1px solid var(--border-2)',
|
||||
}}>
|
||||
<StatusLed status={dirty ? 'warn' : 'ok'} pulse={dirty} />
|
||||
<span className="terminal">{dirty ? 'modifications non sauvegardées' : 'à jour'}</span>
|
||||
<span style={{ flex: 1 }}></span>
|
||||
<Button variant="ghost">Annuler</Button>
|
||||
<Button variant="primary" icon="download">Enregistrer</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌗 Gestion des deux thèmes
|
||||
|
||||
**Règle d'or** : tout ce qui s'affiche doit être lisible et cohérent dans les deux thèmes.
|
||||
|
||||
Avant de livrer un écran, **mentalement (ou réellement) bascule `data-theme`** et vérifie :
|
||||
- Les couleurs personnalisées (en dur) cassent forcément → utilise les tokens
|
||||
- Les opacités blanches (`rgba(255,255,255,…)`) en dark passent mal en light → préfère les variables `--border-*`
|
||||
- Les ombres très profondes en dark sont invisibles en light → utilise `--shadow-*` qui s'adapte
|
||||
|
||||
Pour basculer dynamiquement :
|
||||
```jsx
|
||||
document.documentElement.dataset.theme = 'light';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🪟 Cas particulier : applications GNOME
|
||||
|
||||
Pour GTK 4 / libadwaita :
|
||||
1. Charger `tokens/tokens.gnome.css` via `GtkCssProvider`
|
||||
2. Le fichier **override les couleurs sémantiques libadwaita** (`@accent_color`, `@window_bg_color`, etc.) — les widgets standards se ré-habillent automatiquement
|
||||
3. Ajouter `add_css_class("tile")` pour le relief 3D, `("mono")` pour monospace, `("terminal")` pour Share Tech Mono
|
||||
4. Pour les boutons accent : utiliser la classe libadwaita standard `suggested-action` (déjà restylée)
|
||||
5. Pour danger : classe `destructive-action`
|
||||
|
||||
Polices : penser à installer ou bundler Inter / JetBrains Mono / Share Tech Mono dans le `.flatpak` / `.deb` (sinon GTK fallback sur Cantarell / DejaVu).
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Quand l'utilisateur demande quelque chose…
|
||||
|
||||
### "Ajoute un bouton de déconnexion"
|
||||
→ `<IconButton icon="power" label="Se déconnecter" danger />` ou
|
||||
`<Button variant="danger" icon="power">Déconnexion</Button>`
|
||||
|
||||
### "Affiche le statut du serveur"
|
||||
→ Combinaison `<StatusLed status="ok|warn|err" pulse />` + label texte. Le pulse uniquement si c'est critique/nouveau.
|
||||
|
||||
### "Mets une jauge CPU"
|
||||
→ `<BatteryGauge compact value={cpu} label="cpu" icon="cpu" warnAt={70} errAt={85} />` (inline)
|
||||
ou `<RadialGauge value={cpu} label="CPU" />` (visuel)
|
||||
|
||||
### "Crée une modale de confirmation"
|
||||
→ `<Popup>` avec `footer={<><Button variant="ghost">Annuler</Button><Button variant="primary">Confirmer</Button></>}`
|
||||
|
||||
### "Liste hiérarchique des serveurs"
|
||||
→ `<TreeNav>` avec `groups: [{ id, icon: 'server', label, count, open, children: [{ id, label, status, meta }] }]`
|
||||
|
||||
### "Affiche les logs"
|
||||
→ Conteneur avec `font-family: var(--font-terminal)` + lignes colorées par niveau (ERROR → var(--err), WARN → var(--warn), INFO → var(--ink-2)).
|
||||
|
||||
### "Ajoute une option dark/light dans les réglages"
|
||||
→ `<RadioGroup options={[{value:'dark', icon:'moon'}, {value:'light', icon:'sun'}, {value:'auto', icon:'clock'}]}>` + effet de bord :
|
||||
```jsx
|
||||
React.useEffect(() => { document.documentElement.dataset.theme = theme; }, [theme]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📐 Tailles standards à respecter
|
||||
|
||||
| Élément | Taille / Padding |
|
||||
|---------------|------------------------------------------|
|
||||
| Boutons sm | h: 28px · pad: 5px 10px · font: 12px |
|
||||
| Boutons md | h: 34px · pad: 7px 14px · font: 13px |
|
||||
| Boutons lg | h: 40px · pad: 10px 18px · font: 14px |
|
||||
| IconButton | 34px (default) · 26px (compact) |
|
||||
| Inputs | pad: 9px 12px · font: 13px |
|
||||
| Toggle | 42 × 22px |
|
||||
| StatusLed | 8-14px diamètre |
|
||||
| Header app | 48-56px hauteur |
|
||||
| Sidebar | 200-260px largeur |
|
||||
| Volet logs | 320-360px largeur |
|
||||
| Status bar | 24-28px hauteur |
|
||||
| Radius tuile | 10-12px |
|
||||
| Radius button | 8px |
|
||||
| Espacement | 8 / 12 / 14 / 18 / 24px (rythme bas) |
|
||||
|
||||
---
|
||||
|
||||
## 💡 Trucs pour ne pas se tromper
|
||||
|
||||
1. **Avant de créer un composant, cherche d'abord** dans `ui-kit.jsx`. 90% du temps il existe déjà.
|
||||
2. **Avant d'inventer une couleur**, regarde les tokens. Tu as 6 fonds, 4 encres, 4 statuts, 2 datavis = largement assez.
|
||||
3. **Si tu hésites sur une taille de police** : labels = 11px mono uppercase, body = 13-14px, kpi = 18-28px mono bold.
|
||||
4. **Quand tu ajoutes une tuile**, mets `className="glass"` (ou `glass-strong` pour les modales) — tout le styling est inclus.
|
||||
5. **Pour un état critique**, combine plusieurs signaux : couleur + pulse LED + icône + position visuelle. Pas juste une couleur.
|
||||
6. **Quand l'utilisateur demande "un peu d'effet"** : pas de hover (sauf jauges), oui à la pression 3D, oui aux animations d'entrée 200-400ms `cubic-bezier(.3,.7,.3,1.2)`.
|
||||
|
||||
---
|
||||
|
||||
## 🔚 En cas de doute
|
||||
|
||||
- Pas sûr d'une couleur ? → tokens
|
||||
- Pas sûr d'un composant ? → `ui-kit.jsx`
|
||||
- Pas sûr d'un layout ? → `examples/exemple-minimal.html`
|
||||
- Pas sûr d'une convention ? → ce fichier
|
||||
|
||||
Toujours préférer la cohérence avec l'existant à l'innovation.
|
||||
Quand tu doutes, **demande-moi** plutôt que de deviner.
|
||||
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Exemple minimal — mon design system</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- 1. Polices -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- 2. Icônes -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
|
||||
<!-- 3. Tokens du design system -->
|
||||
<link rel="stylesheet" href="../tokens/tokens.css">
|
||||
|
||||
<style>
|
||||
body { padding: 32px; }
|
||||
.row { display: flex; gap: 12px; align-items: center; margin-bottom: 14px; flex-wrap: wrap; }
|
||||
h2 { font-size: 20px; margin: 32px 0 12px; color: var(--ink-1); }
|
||||
h2:first-child { margin-top: 0; }
|
||||
p { color: var(--ink-3); margin: 0 0 8px; }
|
||||
</style>
|
||||
|
||||
<!-- 4. React + composants -->
|
||||
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="text/babel" src="../components/ui-kit.jsx"></script>
|
||||
|
||||
<script type="text/babel">
|
||||
function App() {
|
||||
const [theme, setTheme] = React.useState('dark');
|
||||
const [popupOpen, setPopupOpen] = React.useState(false);
|
||||
const [auto, setAuto] = React.useState(true);
|
||||
|
||||
React.useEffect(() => { document.documentElement.dataset.theme = theme; }, [theme]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 24 }}>
|
||||
<Icon name="grid" size={28} style={{ color: 'var(--accent)' }} />
|
||||
<h1 style={{ margin: 0, fontSize: 28 }}>Exemple minimal</h1>
|
||||
<span style={{ flex: 1 }}></span>
|
||||
<IconButton icon={theme === 'dark' ? 'sun' : 'moon'}
|
||||
label={theme === 'dark' ? 'Mode clair' : 'Mode sombre'}
|
||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} />
|
||||
</div>
|
||||
|
||||
<h2>Boutons</h2>
|
||||
<div className="row">
|
||||
<Button>défaut</Button>
|
||||
<Button variant="primary" icon="play">primaire</Button>
|
||||
<Button variant="ghost" icon="filter">ghost</Button>
|
||||
<Button variant="danger" icon="power">danger</Button>
|
||||
</div>
|
||||
|
||||
<h2>Boutons icônes (avec tooltip)</h2>
|
||||
<div className="row">
|
||||
<IconButton icon="refresh" label="Rafraîchir" />
|
||||
<IconButton icon="cog" label="Configurer" primary />
|
||||
<IconButton icon="bell" label="Notifications" />
|
||||
<IconButton icon="power" label="Arrêter" danger />
|
||||
</div>
|
||||
|
||||
<h2>Statuts</h2>
|
||||
<div className="row">
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><StatusLed status="ok" /> ok</span>
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><StatusLed status="warn" pulse /> warn</span>
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><StatusLed status="err" pulse /> err</span>
|
||||
<Toggle on={auto} onChange={setAuto} label="Auto-refresh" icon="refresh" />
|
||||
</div>
|
||||
|
||||
<h2>Jauges</h2>
|
||||
<div className="row" style={{ alignItems: 'flex-end' }}>
|
||||
<RadialGauge value={28} label="DISQUE" />
|
||||
<RadialGauge value={64} label="CPU" warnAt={70} errAt={85} />
|
||||
<RadialGauge value={92} label="RÉSEAU" warnAt={70} errAt={85} />
|
||||
</div>
|
||||
<div style={{ maxWidth: 520, marginTop: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<BatteryGauge compact value={88} label="mémoire" icon="memory" />
|
||||
<BatteryGauge compact value={64} label="cpu" icon="cpu" warnAt={70} errAt={85} />
|
||||
<BatteryGauge compact value={28} label="disque" icon="disk" />
|
||||
<BatteryGauge compact value={92} label="réseau" icon="network" warnAt={70} errAt={85} />
|
||||
</div>
|
||||
|
||||
<h2>Popup</h2>
|
||||
<Button variant="primary" icon="cog" onClick={() => setPopupOpen(true)}>Ouvrir la popup</Button>
|
||||
<Popup open={popupOpen} onClose={() => setPopupOpen(false)}
|
||||
title="Confirmer l'action"
|
||||
footer={<>
|
||||
<Button variant="ghost" onClick={() => setPopupOpen(false)}>Annuler</Button>
|
||||
<Button variant="primary" onClick={() => setPopupOpen(false)}>OK</Button>
|
||||
</>}>
|
||||
<div style={{ fontSize: 14, color: 'var(--ink-2)', lineHeight: 1.5 }}>
|
||||
Une popup glassmorphism centrée. Clic à l'extérieur ou Échap pour fermer.
|
||||
</div>
|
||||
</Popup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(<App />);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,204 @@
|
||||
/* ============================================================
|
||||
ui-tokens.css
|
||||
Design tokens Gruvbox Seventies — dark (par défaut) + light.
|
||||
Sombre délavé (pas noir intense) / gris clair usé (pas blanc pur).
|
||||
============================================================ */
|
||||
|
||||
:root,
|
||||
[data-theme="dark"] {
|
||||
/* Couches de fond — sombre délavé, brun-gris chaud */
|
||||
--bg-0: #221c17; /* niveau le plus profond (rare) */
|
||||
--bg-1: #2a231d; /* fond app */
|
||||
--bg-2: #322a23; /* panneaux */
|
||||
--bg-3: #3c332a; /* cartes */
|
||||
--bg-4: #4a4035; /* hover */
|
||||
--bg-5: #5a4f43; /* press / actif */
|
||||
|
||||
/* Surfaces translucides */
|
||||
--surf-glass: rgba(50, 42, 35, 0.72);
|
||||
--surf-glass-strong: rgba(50, 42, 35, 0.92);
|
||||
--surf-glass-soft: rgba(50, 42, 35, 0.42);
|
||||
|
||||
/* Bordures */
|
||||
--border-1: rgba(168, 153, 132, 0.18);
|
||||
--border-2: rgba(168, 153, 132, 0.32);
|
||||
--border-3: rgba(168, 153, 132, 0.55);
|
||||
|
||||
/* Texte */
|
||||
--ink-1: #f2e5c7; /* cream principal */
|
||||
--ink-2: #d5c4a1; /* secondaire */
|
||||
--ink-3: #a89984; /* labels / hints */
|
||||
--ink-4: #7c6f64; /* désactivé */
|
||||
|
||||
/* Accent orange seventies */
|
||||
--accent: #fe8019;
|
||||
--accent-soft: #d65d0e;
|
||||
--accent-glow: rgba(254, 128, 25, 0.35);
|
||||
--accent-tint: rgba(254, 128, 25, 0.12);
|
||||
|
||||
/* Statuts */
|
||||
--ok: #4dbb26;
|
||||
--ok-glow: rgba(77, 187, 38, 0.45);
|
||||
--warn: #fabd2f;
|
||||
--warn-glow: rgba(250, 189, 47, 0.45);
|
||||
--err: #fb4934;
|
||||
--err-glow: rgba(251, 73, 52, 0.4);
|
||||
--info: #83a598;
|
||||
--info-glow: rgba(131, 165, 152, 0.4);
|
||||
|
||||
/* Couleurs additionnelles (datavis, badges, catégories) */
|
||||
--blue: #3db0d1;
|
||||
--blue-glow: rgba(61, 176, 209, 0.45);
|
||||
--purple: #c882c8;
|
||||
--purple-glow: rgba(200, 130, 200, 0.45);
|
||||
|
||||
/* Ombres */
|
||||
--shadow-1: 0 1px 2px rgba(0,0,0,0.4);
|
||||
--shadow-2: 0 4px 12px rgba(0,0,0,0.45);
|
||||
--shadow-3: 0 12px 32px rgba(0,0,0,0.55);
|
||||
--shadow-press: inset 0 2px 4px rgba(0,0,0,0.5);
|
||||
|
||||
/* Relief 3D pour tuiles : highlight haut + ombre bas + ombre portée */
|
||||
--tile-3d:
|
||||
inset 0 1px 0 rgba(255, 230, 180, 0.12),
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.45),
|
||||
0 1px 0 rgba(0, 0, 0, 0.35),
|
||||
0 2px 4px rgba(0, 0, 0, 0.4),
|
||||
0 8px 18px rgba(0, 0, 0, 0.5);
|
||||
--tile-3d-strong:
|
||||
inset 0 1px 0 rgba(255, 230, 180, 0.18),
|
||||
inset 0 -2px 0 rgba(0, 0, 0, 0.55),
|
||||
0 1px 0 rgba(0, 0, 0, 0.4),
|
||||
0 4px 8px rgba(0, 0, 0, 0.5),
|
||||
0 14px 28px rgba(0, 0, 0, 0.55);
|
||||
|
||||
/* Polices */
|
||||
--font-ui: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
||||
--font-terminal: 'Share Tech Mono', 'VT323', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
/* Gris clair usé, légèrement chaud (pas blanc pur) */
|
||||
--bg-0: #b8b2a3;
|
||||
--bg-1: #d5d0c5;
|
||||
--bg-2: #dcd7cc;
|
||||
--bg-3: #e3ded3;
|
||||
--bg-4: #ccc6b8;
|
||||
--bg-5: #bdb6a7;
|
||||
|
||||
--surf-glass: rgba(220, 215, 204, 0.72);
|
||||
--surf-glass-strong: rgba(220, 215, 204, 0.94);
|
||||
--surf-glass-soft: rgba(220, 215, 204, 0.42);
|
||||
|
||||
--border-1: rgba(60, 56, 54, 0.15);
|
||||
--border-2: rgba(60, 56, 54, 0.28);
|
||||
--border-3: rgba(60, 56, 54, 0.5);
|
||||
|
||||
--ink-1: #28241f;
|
||||
--ink-2: #3c3836;
|
||||
--ink-3: #5a544c;
|
||||
--ink-4: #8a8278;
|
||||
|
||||
--accent: #af3a03;
|
||||
--accent-soft: #d65d0e;
|
||||
--accent-glow: rgba(175, 58, 3, 0.28);
|
||||
--accent-tint: rgba(175, 58, 3, 0.08);
|
||||
|
||||
--ok: #3c911c;
|
||||
--ok-glow: rgba(60, 145, 28, 0.32);
|
||||
--warn: #b57614;
|
||||
--warn-glow: rgba(181, 118, 20, 0.35);
|
||||
--err: #9d0006;
|
||||
--err-glow: rgba(157, 0, 6, 0.3);
|
||||
--info: #427b58;
|
||||
--info-glow: rgba(66, 123, 88, 0.3);
|
||||
|
||||
/* Couleurs additionnelles (datavis, badges, catégories) */
|
||||
--blue: #2d82a3;
|
||||
--blue-glow: rgba(45, 130, 163, 0.32);
|
||||
--purple: #8c468c;
|
||||
--purple-glow: rgba(140, 70, 140, 0.32);
|
||||
|
||||
--shadow-1: 0 1px 2px rgba(40,30,20,0.12);
|
||||
--shadow-2: 0 4px 12px rgba(40,30,20,0.15);
|
||||
--shadow-3: 0 12px 32px rgba(40,30,20,0.2);
|
||||
--shadow-press: inset 0 2px 4px rgba(40,30,20,0.18);
|
||||
|
||||
/* Relief light : highlight haut blanc cassé + ombre marquée */
|
||||
--tile-3d:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.55),
|
||||
inset 0 -1px 0 rgba(60, 50, 40, 0.18),
|
||||
0 1px 0 rgba(60, 50, 40, 0.1),
|
||||
0 2px 4px rgba(60, 50, 40, 0.12),
|
||||
0 8px 18px rgba(60, 50, 40, 0.18);
|
||||
--tile-3d-strong:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.7),
|
||||
inset 0 -2px 0 rgba(60, 50, 40, 0.22),
|
||||
0 1px 0 rgba(60, 50, 40, 0.15),
|
||||
0 4px 8px rgba(60, 50, 40, 0.18),
|
||||
0 14px 28px rgba(60, 50, 40, 0.22);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Reset minimal + base typo
|
||||
============================================================ */
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 14px;
|
||||
color: var(--ink-1);
|
||||
background: var(--bg-1);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.mono { font-family: var(--font-mono); }
|
||||
.terminal { font-family: var(--font-terminal); letter-spacing: 0.02em; }
|
||||
.label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Surfaces — relief 3D marqué, AUCUN effet hover
|
||||
============================================================ */
|
||||
.glass {
|
||||
background: var(--surf-glass);
|
||||
backdrop-filter: blur(12px) saturate(140%);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(140%);
|
||||
border: 1px solid var(--border-2);
|
||||
box-shadow: var(--tile-3d);
|
||||
}
|
||||
.glass-strong {
|
||||
background: var(--surf-glass-strong);
|
||||
backdrop-filter: blur(16px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(150%);
|
||||
border: 1px solid var(--border-3);
|
||||
box-shadow: var(--tile-3d-strong);
|
||||
}
|
||||
|
||||
/* Élément cliquable : pas de hover, mais réelle pression 3D au clic */
|
||||
.interactive {
|
||||
cursor: pointer;
|
||||
transition: transform .04s ease-out, box-shadow .04s, background .04s;
|
||||
transform: translateY(0);
|
||||
}
|
||||
.interactive:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: var(--shadow-press) !important;
|
||||
filter: brightness(0.92);
|
||||
}
|
||||
|
||||
/* Scrollbar custom */
|
||||
*::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
*::-webkit-scrollbar-track { background: transparent; }
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background: var(--border-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
*::-webkit-scrollbar-thumb:hover { background: var(--accent-soft); }
|
||||
@@ -0,0 +1,378 @@
|
||||
/* ============================================================
|
||||
tokens.gnome.css — Tokens pour applications GNOME (GTK 4 / libadwaita)
|
||||
Gruvbox seventies · v1.0
|
||||
============================================================
|
||||
|
||||
Usage dans une app GTK 4 / libadwaita :
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
GtkCssProvider *provider = gtk_css_provider_new();
|
||||
gtk_css_provider_load_from_path(provider, "tokens.gnome.css");
|
||||
gtk_style_context_add_provider_for_display(
|
||||
gdk_display_get_default(), GTK_STYLE_PROVIDER(provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
Python (PyGObject) :
|
||||
css_provider = Gtk.CssProvider()
|
||||
css_provider.load_from_path("tokens.gnome.css")
|
||||
Gtk.StyleContext.add_provider_for_display(
|
||||
Gdk.Display.get_default(), css_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
|
||||
GJS :
|
||||
const provider = new Gtk.CssProvider();
|
||||
provider.load_from_path('tokens.gnome.css');
|
||||
Gtk.StyleContext.add_provider_for_display(
|
||||
Gdk.Display.get_default(), provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
============================================================ */
|
||||
|
||||
/* ============================================================
|
||||
THÈME SOMBRE (défaut)
|
||||
============================================================ */
|
||||
|
||||
/* Couches de fond (du plus profond au plus haut) */
|
||||
@define-color bg_0 #221c17;
|
||||
@define-color bg_1 #2a231d;
|
||||
@define-color bg_2 #322a23;
|
||||
@define-color bg_3 #3c332a;
|
||||
@define-color bg_4 #4a4035;
|
||||
@define-color bg_5 #5a4f43;
|
||||
|
||||
/* Encres / texte */
|
||||
@define-color ink_1 #f2e5c7;
|
||||
@define-color ink_2 #d5c4a1;
|
||||
@define-color ink_3 #a89984;
|
||||
@define-color ink_4 #7c6f64;
|
||||
|
||||
/* Accent orange seventies */
|
||||
@define-color accent_color #fe8019;
|
||||
@define-color accent_soft #d65d0e;
|
||||
@define-color accent_fg_color #221c17;
|
||||
|
||||
/* Statuts */
|
||||
@define-color success_color #4dbb26;
|
||||
@define-color warning_color #fabd2f;
|
||||
@define-color error_color #fb4934;
|
||||
@define-color info_color #83a598;
|
||||
@define-color blue_color #3db0d1;
|
||||
@define-color purple_color #c882c8;
|
||||
|
||||
/* Bordures */
|
||||
@define-color border_1 alpha(#a89984, 0.18);
|
||||
@define-color border_2 alpha(#a89984, 0.32);
|
||||
@define-color border_3 alpha(#a89984, 0.55);
|
||||
|
||||
/* Couleurs sémantiques GNOME / libadwaita (overrides) */
|
||||
@define-color window_bg_color @bg_1;
|
||||
@define-color window_fg_color @ink_1;
|
||||
@define-color view_bg_color @bg_2;
|
||||
@define-color view_fg_color @ink_1;
|
||||
@define-color headerbar_bg_color @bg_2;
|
||||
@define-color headerbar_fg_color @ink_1;
|
||||
@define-color headerbar_border_color @border_2;
|
||||
@define-color headerbar_backdrop_color @bg_1;
|
||||
@define-color sidebar_bg_color @bg_2;
|
||||
@define-color sidebar_fg_color @ink_1;
|
||||
@define-color sidebar_backdrop_color @bg_1;
|
||||
@define-color popover_bg_color @bg_3;
|
||||
@define-color popover_fg_color @ink_1;
|
||||
@define-color card_bg_color @bg_3;
|
||||
@define-color card_fg_color @ink_1;
|
||||
@define-color shade_color alpha(black, 0.4);
|
||||
@define-color scrollbar_outline_color alpha(@ink_3, 0.3);
|
||||
|
||||
/* ============================================================
|
||||
COMPOSANTS GTK — habillage Gruvbox seventies
|
||||
============================================================ */
|
||||
|
||||
/* Fond global */
|
||||
window {
|
||||
background-color: @window_bg_color;
|
||||
color: @window_fg_color;
|
||||
font-family: 'Inter', 'Cantarell', sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* HeaderBar (barre de titre) */
|
||||
headerbar {
|
||||
background: @bg_2;
|
||||
color: @ink_1;
|
||||
border-bottom: 1px solid @border_2;
|
||||
box-shadow: inset 0 1px 0 alpha(white, 0.04);
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
headerbar .title {
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
}
|
||||
headerbar .subtitle {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 11px;
|
||||
color: @ink_3;
|
||||
}
|
||||
|
||||
/* Boutons — relief 3D et accent */
|
||||
button {
|
||||
background: @bg_3;
|
||||
color: @ink_1;
|
||||
border: 1px solid @border_2;
|
||||
border-radius: 8px;
|
||||
padding: 6px 12px;
|
||||
font-weight: 500;
|
||||
box-shadow:
|
||||
inset 0 1px 0 alpha(white, 0.06),
|
||||
inset 0 -1px 0 alpha(black, 0.3),
|
||||
0 1px 2px alpha(black, 0.4);
|
||||
transition: all 60ms ease;
|
||||
}
|
||||
button:active {
|
||||
background: @bg_4;
|
||||
box-shadow: inset 0 2px 4px alpha(black, 0.5);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
button:disabled {
|
||||
color: @ink_4;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Bouton "suggested-action" = primary (accent orange) */
|
||||
button.suggested-action {
|
||||
background: @accent_color;
|
||||
color: @accent_fg_color;
|
||||
border-color: @accent_soft;
|
||||
box-shadow:
|
||||
inset 0 1px 0 alpha(white, 0.2),
|
||||
0 2px 6px alpha(@accent_color, 0.35);
|
||||
}
|
||||
button.suggested-action:active {
|
||||
background: @accent_soft;
|
||||
}
|
||||
|
||||
/* Bouton "destructive-action" = danger */
|
||||
button.destructive-action {
|
||||
background: @bg_3;
|
||||
color: @error_color;
|
||||
border-color: @error_color;
|
||||
}
|
||||
|
||||
/* Bouton plat (toolbar) */
|
||||
button.flat {
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
button.flat:hover {
|
||||
background: @bg_3;
|
||||
}
|
||||
|
||||
/* Champs de saisie */
|
||||
entry,
|
||||
text {
|
||||
background: @bg_1;
|
||||
color: @ink_1;
|
||||
border: 1px solid @border_2;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
box-shadow: inset 0 1px 2px alpha(black, 0.3);
|
||||
}
|
||||
entry:focus,
|
||||
text:focus {
|
||||
border-color: @accent_color;
|
||||
outline: 2px solid alpha(@accent_color, 0.18);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
/* Listes / treeview */
|
||||
list,
|
||||
treeview {
|
||||
background: @bg_2;
|
||||
color: @ink_1;
|
||||
}
|
||||
list > row {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid @border_1;
|
||||
}
|
||||
list > row:selected,
|
||||
treeview:selected {
|
||||
background: alpha(@accent_color, 0.12);
|
||||
color: @ink_1;
|
||||
border-left: 3px solid @accent_color;
|
||||
}
|
||||
|
||||
/* Switch (toggle) */
|
||||
switch {
|
||||
background: @bg_4;
|
||||
border: 1px solid @border_2;
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 0 1px 2px alpha(black, 0.4);
|
||||
min-height: 22px;
|
||||
min-width: 42px;
|
||||
}
|
||||
switch:checked {
|
||||
background: @accent_color;
|
||||
border-color: @accent_soft;
|
||||
box-shadow: 0 0 10px alpha(@accent_color, 0.35);
|
||||
}
|
||||
switch slider {
|
||||
background: @ink_2;
|
||||
border-radius: 50%;
|
||||
min-width: 18px;
|
||||
min-height: 18px;
|
||||
}
|
||||
switch:checked slider {
|
||||
background: @accent_fg_color;
|
||||
}
|
||||
|
||||
/* Scale (slider) */
|
||||
scale trough {
|
||||
background: @bg_1;
|
||||
border-radius: 4px;
|
||||
min-height: 6px;
|
||||
}
|
||||
scale highlight {
|
||||
background: @accent_color;
|
||||
border-radius: 4px;
|
||||
}
|
||||
scale slider {
|
||||
background: @ink_1;
|
||||
border: 2px solid @accent_color;
|
||||
border-radius: 50%;
|
||||
min-width: 16px;
|
||||
min-height: 16px;
|
||||
box-shadow: 0 1px 4px alpha(black, 0.5);
|
||||
}
|
||||
|
||||
/* Progress bar (jauge horizontale type batterie) */
|
||||
progressbar trough {
|
||||
background: @bg_1;
|
||||
border: 1px solid @border_2;
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 1px 2px alpha(black, 0.4);
|
||||
min-height: 12px;
|
||||
}
|
||||
progressbar progress {
|
||||
background: @success_color;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 8px alpha(@success_color, 0.45);
|
||||
}
|
||||
|
||||
/* Niveaux de progression sémantiques (à appliquer via add_css_class) */
|
||||
progressbar.warning progress { background: @warning_color; }
|
||||
progressbar.error progress { background: @error_color; }
|
||||
progressbar.info progress { background: @info_color; }
|
||||
|
||||
/* Notebook / onglets */
|
||||
notebook header {
|
||||
background: @bg_2;
|
||||
border-bottom: 1px solid @border_2;
|
||||
}
|
||||
notebook tab {
|
||||
padding: 8px 16px;
|
||||
color: @ink_3;
|
||||
border-top: 2px solid transparent;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
notebook tab:checked {
|
||||
color: @ink_1;
|
||||
border-top-color: @accent_color;
|
||||
background: @bg_3;
|
||||
}
|
||||
|
||||
/* Popover */
|
||||
popover contents {
|
||||
background: @bg_3;
|
||||
color: @ink_1;
|
||||
border: 1px solid @border_2;
|
||||
border-radius: 10px;
|
||||
padding: 6px;
|
||||
box-shadow: 0 12px 32px alpha(black, 0.55);
|
||||
}
|
||||
|
||||
/* Menubutton / dropdown */
|
||||
menubutton button {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* Status pill (badge) — à appliquer sur GtkLabel.status */
|
||||
label.status {
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
label.status.ok { background: alpha(@success_color, 0.18); color: @success_color; }
|
||||
label.status.warn { background: alpha(@warning_color, 0.18); color: @warning_color; }
|
||||
label.status.error { background: alpha(@error_color, 0.18); color: @error_color; }
|
||||
label.status.info { background: alpha(@info_color, 0.18); color: @info_color; }
|
||||
|
||||
/* Texte monospace / terminal */
|
||||
label.mono,
|
||||
.mono {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
label.terminal,
|
||||
.terminal {
|
||||
font-family: 'Share Tech Mono', 'VT323', monospace;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Carte tuile (à appliquer via add_css_class("tile")) */
|
||||
.tile,
|
||||
.card {
|
||||
background: @bg_3;
|
||||
color: @ink_1;
|
||||
border: 1px solid @border_2;
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
box-shadow:
|
||||
inset 0 1px 0 alpha(white, 0.06),
|
||||
inset 0 -1px 0 alpha(black, 0.4),
|
||||
0 2px 4px alpha(black, 0.4),
|
||||
0 6px 14px alpha(black, 0.45);
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
scrollbar slider {
|
||||
background: @border_2;
|
||||
border-radius: 4px;
|
||||
min-width: 6px;
|
||||
min-height: 6px;
|
||||
}
|
||||
scrollbar slider:hover {
|
||||
background: @accent_soft;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
THÈME CLAIR — à charger en alternative
|
||||
Pour appliquer le thème clair, charger ce fichier puis
|
||||
`tokens.gnome.light.css` (à dupliquer en remplaçant
|
||||
les @define-color des fonds et encres) OU appliquer
|
||||
un settings GTK light :
|
||||
g_object_set(gtk_settings, "gtk-application-prefer-dark-theme",
|
||||
FALSE, NULL);
|
||||
Et fournir un fichier dérivé avec les valeurs ci-dessous :
|
||||
============================================================ */
|
||||
/*
|
||||
bg_0: #b8b2a3
|
||||
bg_1: #d5d0c5
|
||||
bg_2: #dcd7cc
|
||||
bg_3: #e3ded3
|
||||
bg_4: #ccc6b8
|
||||
bg_5: #bdb6a7
|
||||
ink_1: #28241f
|
||||
ink_2: #3c3836
|
||||
ink_3: #5a544c
|
||||
ink_4: #8a8278
|
||||
accent_color: #af3a03
|
||||
success_color: #3c911c
|
||||
warning_color: #b57614
|
||||
error_color: #9d0006
|
||||
info_color: #427b58
|
||||
blue_color: #2d82a3
|
||||
purple_color: #8c468c
|
||||
*/
|
||||
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"$schema": "design-tokens-v1",
|
||||
"name": "mon design system — gruvbox seventies",
|
||||
"version": "1.0.0",
|
||||
"description": "Design system Gruvbox seventies. Orange brûlé, fond brun délavé en sombre / gris clair usé en clair. Deux thèmes dark/light parfaitement à parité.",
|
||||
"themes": {
|
||||
"dark": {
|
||||
"bg": {
|
||||
"0": { "value": "#221c17", "description": "Niveau le plus profond, rare" },
|
||||
"1": { "value": "#2a231d", "description": "Fond application principal" },
|
||||
"2": { "value": "#322a23", "description": "Panneaux (sidebar, headerbar)" },
|
||||
"3": { "value": "#3c332a", "description": "Cartes, tuiles" },
|
||||
"4": { "value": "#4a4035", "description": "Hover, état actif" },
|
||||
"5": { "value": "#5a4f43", "description": "Press, sélection forte" }
|
||||
},
|
||||
"ink": {
|
||||
"1": { "value": "#f2e5c7", "description": "Texte principal (cream)" },
|
||||
"2": { "value": "#d5c4a1", "description": "Texte secondaire" },
|
||||
"3": { "value": "#a89984", "description": "Labels, hints" },
|
||||
"4": { "value": "#7c6f64", "description": "Désactivé" }
|
||||
},
|
||||
"accent": {
|
||||
"primary": { "value": "#fe8019", "description": "Orange Gruvbox seventies" },
|
||||
"soft": { "value": "#d65d0e", "description": "Orange foncé (hover, bordures)" },
|
||||
"glow": { "value": "rgba(254, 128, 25, 0.35)" },
|
||||
"tint": { "value": "rgba(254, 128, 25, 0.12)" }
|
||||
},
|
||||
"status": {
|
||||
"ok": { "value": "#4dbb26" },
|
||||
"warn": { "value": "#fabd2f" },
|
||||
"err": { "value": "#fb4934" },
|
||||
"info": { "value": "#83a598" }
|
||||
},
|
||||
"extra": {
|
||||
"blue": { "value": "#3db0d1" },
|
||||
"purple": { "value": "#c882c8" }
|
||||
},
|
||||
"border": {
|
||||
"1": { "value": "rgba(168, 153, 132, 0.18)" },
|
||||
"2": { "value": "rgba(168, 153, 132, 0.32)" },
|
||||
"3": { "value": "rgba(168, 153, 132, 0.55)" }
|
||||
}
|
||||
},
|
||||
"light": {
|
||||
"bg": {
|
||||
"0": { "value": "#b8b2a3", "description": "Niveau le plus profond" },
|
||||
"1": { "value": "#d5d0c5", "description": "Fond application principal" },
|
||||
"2": { "value": "#dcd7cc", "description": "Panneaux" },
|
||||
"3": { "value": "#e3ded3", "description": "Cartes, tuiles" },
|
||||
"4": { "value": "#ccc6b8", "description": "Hover" },
|
||||
"5": { "value": "#bdb6a7", "description": "Press" }
|
||||
},
|
||||
"ink": {
|
||||
"1": { "value": "#28241f", "description": "Texte principal" },
|
||||
"2": { "value": "#3c3836", "description": "Texte secondaire" },
|
||||
"3": { "value": "#5a544c", "description": "Labels, hints" },
|
||||
"4": { "value": "#8a8278", "description": "Désactivé" }
|
||||
},
|
||||
"accent": {
|
||||
"primary": { "value": "#af3a03", "description": "Orange brûlé (variante contrastée)" },
|
||||
"soft": { "value": "#d65d0e" },
|
||||
"glow": { "value": "rgba(175, 58, 3, 0.28)" },
|
||||
"tint": { "value": "rgba(175, 58, 3, 0.08)" }
|
||||
},
|
||||
"status": {
|
||||
"ok": { "value": "#3c911c" },
|
||||
"warn": { "value": "#b57614" },
|
||||
"err": { "value": "#9d0006" },
|
||||
"info": { "value": "#427b58" }
|
||||
},
|
||||
"extra": {
|
||||
"blue": { "value": "#2d82a3" },
|
||||
"purple": { "value": "#8c468c" }
|
||||
},
|
||||
"border": {
|
||||
"1": { "value": "rgba(60, 56, 54, 0.15)" },
|
||||
"2": { "value": "rgba(60, 56, 54, 0.28)" },
|
||||
"3": { "value": "rgba(60, 56, 54, 0.5)" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"fonts": {
|
||||
"ui": { "family": "Inter", "weights": [400, 500, 600, 700], "fallback": ["Cantarell", "system-ui", "sans-serif"] },
|
||||
"mono": { "family": "JetBrains Mono", "weights": [400, 500, 600, 700], "fallback": ["ui-monospace", "monospace"] },
|
||||
"terminal": { "family": "Share Tech Mono", "weights": [400], "fallback": ["VT323", "Courier New", "monospace"] }
|
||||
},
|
||||
"scale": {
|
||||
"label": { "size": 11, "weight": 500, "transform": "uppercase", "tracking": "0.08em", "family": "mono" },
|
||||
"caption": { "size": 12, "weight": 400, "family": "ui" },
|
||||
"body": { "size": 14, "weight": 400, "family": "ui" },
|
||||
"body-emph": { "size": 14, "weight": 600, "family": "ui" },
|
||||
"title": { "size": 18, "weight": 700, "family": "ui" },
|
||||
"h2": { "size": 22, "weight": 700, "family": "ui" },
|
||||
"h1": { "size": 28, "weight": 700, "family": "ui" },
|
||||
"display": { "size": 44, "weight": 700, "family": "ui" },
|
||||
"kpi": { "size": 28, "weight": 700, "family": "mono" }
|
||||
}
|
||||
},
|
||||
"radius": {
|
||||
"xs": 3,
|
||||
"sm": 4,
|
||||
"md": 6,
|
||||
"lg": 8,
|
||||
"xl": 10,
|
||||
"2xl": 12,
|
||||
"pill": 999
|
||||
},
|
||||
"spacing": {
|
||||
"1": 4,
|
||||
"2": 6,
|
||||
"3": 8,
|
||||
"4": 10,
|
||||
"5": 12,
|
||||
"6": 14,
|
||||
"7": 16,
|
||||
"8": 18,
|
||||
"9": 20,
|
||||
"10": 24,
|
||||
"12": 32,
|
||||
"14": 40,
|
||||
"16": 56
|
||||
},
|
||||
"shadows": {
|
||||
"1": "0 1px 2px rgba(0,0,0,0.4)",
|
||||
"2": "0 4px 12px rgba(0,0,0,0.45)",
|
||||
"3": "0 12px 32px rgba(0,0,0,0.55)",
|
||||
"press": "inset 0 2px 4px rgba(0,0,0,0.5)",
|
||||
"tile3d": "inset 0 1px 0 rgba(255,230,180,0.12), inset 0 -1px 0 rgba(0,0,0,0.45), 0 1px 0 rgba(0,0,0,0.35), 0 2px 4px rgba(0,0,0,0.4), 0 8px 18px rgba(0,0,0,0.5)"
|
||||
},
|
||||
"motion": {
|
||||
"fast": "60ms ease",
|
||||
"normal": "180ms cubic-bezier(.3,.7,.3,1.2)",
|
||||
"slow": "400ms cubic-bezier(.3,.6,.3,1)"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user