feat: type de jauge configurable (compact / standard)

- ServerConfig: champ gauge_type (défaut "compact")
- CSS: classes .gs-* pour la BatteryGauge standard (label + bar 9px + gloss interne)
- Grid: helper renderGaugeRow() — sélectionne compact ou standard selon la config
- Grid: rerenderAll() pour appliquer le changement sans recharger la page
- Popup config serveur: select "Type de jauge" dans la section Affichage des tuiles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gilles Soulier
2026-05-23 05:10:28 +02:00
parent 22b429f247
commit fdf76477e5
4 changed files with 74 additions and 30 deletions
+11
View File
@@ -117,6 +117,17 @@ body{background:var(--bg-1);color:var(--ink-1);font-family:var(--font-ui);font-s
.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);
min-width:34px;white-space:nowrap;flex-shrink:0;text-align:right}
/* Jauge standard (BatteryGauge) */
.gs-row{display:flex;flex-direction:column;gap:3px}
.gs-header{display:flex;align-items:center;gap:6px}
.gs-ico{width:14px;text-align:center;font-size:10px;color:var(--ink-3);flex-shrink:0;cursor:help}
.gs-lbl{flex:1;font-family:var(--font-terminal);font-size:10px;color:var(--ink-3);letter-spacing:.04em}
.gs-val{font-family:var(--font-mono);font-size:11px;color:var(--ink-2);white-space:nowrap;flex-shrink:0}
.gs-bar{position:relative;height:9px;border-radius:3px;background:var(--bg-1);
border:1px solid var(--border-1);overflow:hidden;box-shadow:inset 0 1px 2px rgba(0,0,0,.3)}
.gs-fill{height:100%;border-radius:2px;background:var(--ok);transition:width .3s}
.gs-fill.w{background:var(--warn)}.gs-fill.e{background:var(--err)}
.gs-gloss{position:absolute;inset:0;background:linear-gradient(180deg,rgba(255,255,255,.12),transparent);pointer-events:none}
.tile-foot{font-family:var(--font-terminal);font-size:10px;color:var(--ink-4);
display:flex;align-items:center;justify-content:space-between;user-select:none}
.tile-foot-info{display:flex;align-items:center;gap:5px;min-width:0;overflow:hidden}
+42 -19
View File
@@ -36,6 +36,28 @@ const Grid = (() => {
return '';
}
function renderGaugeRow(faIcon, tip, label, pct, fillClass, valStr, extra) {
const standard = (App.serverConfig?.gauge_type ?? 'compact') === 'standard';
if (standard) {
return `<div class="gs-row">
<div class="gs-header">
<span class="gs-ico" data-tip="${tip}"><i class="fa-solid fa-${faIcon}"></i></span>
<span class="gs-lbl">${label}</span>
<span class="gs-val">${valStr}</span>${extra || ''}
</div>
<div class="gs-bar">
<div class="gs-fill ${fillClass}" style="width:${(pct ?? 0).toFixed(1)}%"></div>
<div class="gs-gloss"></div>
</div>
</div>`;
}
return `<div class="g-row">
<div class="g-ico" data-tip="${tip}"><i class="fa-solid fa-${faIcon}"></i></div>
<div class="g-bar"><div class="g-fill ${fillClass}" style="width:${(pct ?? 0).toFixed(0)}%"></div></div>
<span class="g-val">${valStr}</span>${extra || ''}
</div>`;
}
function renderTile(agent, metrics) {
const id = agent.id;
const sc = statusClass(agent);
@@ -77,24 +99,19 @@ const Grid = (() => {
<div class="t-led ${ledClass(agent.status)}"></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 ${offline ? '' : gFill(cpu ?? 0)}"
style="width:${offline ? 0 : (cpu ?? 0).toFixed(0)}%"></div></div>
<span class="g-val">${offline ? '—' : fmtPct(cpu)}</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 ${offline ? '' : gFill(memPct ?? 0)}"
style="width:${offline ? 0 : (memPct ?? 0).toFixed(0)}%"></div></div>
<span class="g-val">${offline ? '' : (metrics?.memory_used && metrics?.memory_total ? fmt(metrics.memory_used) + '/' + fmt(metrics.memory_total) : '')}</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 ${offline ? '' : (diskPct >= (App.serverConfig?.warn_disk ?? 75) ? 'w' : '')}"
style="width:${offline ? 0 : (diskPct ?? 0).toFixed(0)}%"></div></div>
<span class="g-val">${offline ? '—' : (metrics?.hdd_used && metrics?.hdd_total ? fmt(metrics.hdd_used) + '/' + fmt(metrics.hdd_total) : '—')}</span>${smartIco}
</div>
${renderGaugeRow('microchip', 'CPU', 'CPU',
offline ? 0 : (cpu ?? 0),
offline ? '' : gFill(cpu ?? 0),
offline ? '—' : fmtPct(cpu))}
${renderGaugeRow('memory', 'RAM', 'MÉMOIRE',
offline ? 0 : (memPct ?? 0),
offline ? '' : gFill(memPct ?? 0),
offline ? '—' : (metrics?.memory_used && metrics?.memory_total ? fmt(metrics.memory_used) + '/' + fmt(metrics.memory_total) : '—'))}
${renderGaugeRow('hard-drive', 'Disque', 'DISQUE',
offline ? 0 : (diskPct ?? 0),
offline ? '' : (diskPct >= (App.serverConfig?.warn_disk ?? 75) ? 'w' : ''),
offline ? '—' : (metrics?.hdd_used && metrics?.hdd_total ? fmt(metrics.hdd_used) + '/' + fmt(metrics.hdd_total) : '—'),
smartIco)}
</div>
<div class="tile-foot">
<span class="tile-foot-info">
@@ -173,6 +190,12 @@ const Grid = (() => {
document.getElementById('stat-err').textContent = err;
}
function rerenderAll() {
const grid = document.getElementById('agents-grid');
if (!grid) return;
grid.innerHTML = [..._agents.values()].map(({ agent, metrics }) => renderTile(agent, metrics)).join('');
}
function removeAgent(id) {
_agents.delete(id);
const el = document.getElementById('tile-' + id);
@@ -191,5 +214,5 @@ const Grid = (() => {
updateStats();
}
return { refresh, update, updateStatus, removeAgent, getAgent, fmt, fmtPct };
return { refresh, update, updateStatus, removeAgent, rerenderAll, getAgent, fmt, fmtPct };
})();
+8
View File
@@ -312,6 +312,11 @@ const Popups = (() => {
<input type="range" class="scfg-slider" min="10" max="18" value="${cfg.font_size ?? 13}"
oninput="this.nextElementSibling.textContent=this.value+'px'" id="s-font">
<span class="scfg-val">${cfg.font_size ?? 13}px</span></div>
<div class="scfg-row"><label>Type de jauge</label>
<select class="scfg-select" id="s-gauge-type">
<option value="compact" ${(cfg.gauge_type ?? 'compact') === 'compact' ? 'selected' : ''}>Compact</option>
<option value="standard" ${(cfg.gauge_type ?? 'compact') === 'standard' ? 'selected' : ''}>Standard</option>
</select></div>
</div>
<div style="display:flex;flex-direction:column;gap:8px">
<div class="scfg-sec-title">SEUILS D'ALERTE</div>
@@ -372,11 +377,14 @@ const Popups = (() => {
warn_disk: parseInt(document.getElementById('s-warn-disk')?.value ?? 75),
retention_days: parseInt(document.getElementById('s-retention')?.value ?? 30),
chart_duration_min: parseInt(document.getElementById('s-chart-dur')?.value ?? 30),
gauge_type: document.getElementById('s-gauge-type')?.value ?? 'compact',
};
const prevGaugeType = App.serverConfig?.gauge_type ?? 'compact';
await API.putServerConfig(cfg);
App.serverConfig = cfg;
document.documentElement.style.setProperty('--tile-min', cfg.tile_min_width + 'px');
document.body.style.fontSize = cfg.font_size + 'px';
if (cfg.gauge_type !== prevGaugeType) Grid.rerenderAll();
hideSrvCfg();
}
+2
View File
@@ -88,6 +88,7 @@ type ServerConfig struct {
Notifications bool `json:"notifications"`
PopupDetailW int `json:"popup_detail_w"`
PopupDetailH int `json:"popup_detail_h"`
GaugeType string `json:"gauge_type"`
}
func DefaultAgentConfig() *AgentConfig {
@@ -111,6 +112,7 @@ func DefaultServerConfig() ServerConfig {
RetentionDays: 30, ChartDurationMin: 30,
HideOffline: false, Notifications: true,
PopupDetailW: 560, PopupDetailH: 600,
GaugeType: "compact",
}
}