feat(dashboard): popups détail, config agent, SMART, config serveur
This commit is contained in:
@@ -0,0 +1,397 @@
|
||||
const Popups = (() => {
|
||||
let _currentAgentId = null;
|
||||
let _agentCfgData = null;
|
||||
|
||||
// ══ POPUP DÉTAIL ══
|
||||
async function showDetail(agentId) {
|
||||
_currentAgentId = agentId;
|
||||
const entry = Grid.getAgent(agentId);
|
||||
if (!entry) return;
|
||||
const { agent, metrics } = entry;
|
||||
|
||||
document.getElementById('pop-host').textContent = agent.hostname;
|
||||
document.getElementById('pop-ip').textContent = agent.ip || '—';
|
||||
const led = document.getElementById('pop-led');
|
||||
led.className = 'pop-led';
|
||||
led.style.background = agent.status === 'online' ? 'var(--ok)' : 'var(--err)';
|
||||
led.style.boxShadow = `0 0 8px ${agent.status === 'online' ? 'var(--ok)' : 'var(--err)'}`;
|
||||
|
||||
// Icône
|
||||
const img = document.getElementById('pop-icon-img');
|
||||
const fa = document.getElementById('pop-icon-fa');
|
||||
img.src = API.iconUrl(agentId) + '?t=' + Date.now();
|
||||
img.style.display = 'block';
|
||||
img.onerror = () => { img.style.display = 'none'; fa.style.display = 'flex'; };
|
||||
fa.style.display = 'none';
|
||||
|
||||
// Upload icône
|
||||
document.getElementById('pop-icon-wrap').onclick = () => document.getElementById('icon-upload').click();
|
||||
document.getElementById('icon-upload').onchange = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
await API.uploadIcon(agentId, file);
|
||||
img.src = API.iconUrl(agentId) + '?t=' + Date.now();
|
||||
};
|
||||
|
||||
// Uptime
|
||||
const up = metrics?.uptime;
|
||||
if (up) {
|
||||
const d = Math.floor(up / 86400), h = Math.floor((up % 86400) / 3600);
|
||||
document.getElementById('pop-uptime').innerHTML =
|
||||
`<i class="fa-solid fa-clock" style="margin-right:4px"></i>En ligne depuis ${d}j ${h}h`;
|
||||
}
|
||||
|
||||
// Corps du popup
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
let history = [];
|
||||
try { history = await API.getAgentHistory(agentId, now - 1800, now); } catch {}
|
||||
|
||||
const cpuPts = Charts.historyToCpuPts(history);
|
||||
const memPts = Charts.historyToMemPts(history);
|
||||
|
||||
const smartBtn = metrics?.smart
|
||||
? `<div class="smart-btn ok" onclick="Popups.showSmart('${agentId}')" data-tip="Voir la santé complète du disque">
|
||||
<div class="smart-dot"></div>
|
||||
<span style="font-weight:600">SMART</span>
|
||||
<span>·</span>
|
||||
<span>${metrics.smart.passed ? 'PASSED' : 'FAILED'}</span>
|
||||
${metrics.smart.temperature ? `<span style="font-family:var(--font-mono);font-size:10px;color:var(--ink-3)"><i class="fa-solid fa-temperature-half"></i> ${metrics.smart.temperature}°C</span>` : ''}
|
||||
<i class="fa-solid fa-chevron-right" style="font-size:10px;color:var(--ink-4);margin-left:auto"></i>
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
const protos = [
|
||||
metrics?.cpu_percent != null ? `<span class="proto-badge udp"><i class="fa-solid fa-arrow-up"></i>UDP</span>` : '',
|
||||
].filter(Boolean).join('');
|
||||
|
||||
document.getElementById('pop-body').innerHTML = `
|
||||
<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">${(metrics?.cpu_percent ?? 0).toFixed(0)}<span class="u">%</span></div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">MÉMOIRE</div>
|
||||
<div class="kpi-val">${Grid.fmt(metrics?.memory_used)}</div>
|
||||
<div class="kpi-sub">/ ${Grid.fmt(metrics?.memory_total)}</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">DISQUE</div>
|
||||
<div class="kpi-val">${Grid.fmt(metrics?.hdd_used)}</div>
|
||||
<div class="kpi-sub">/ ${Grid.fmt(metrics?.hdd_total)}</div></div>
|
||||
<div class="kpi"><div class="kpi-lbl">UPTIME</div>
|
||||
<div class="kpi-val" style="font-size:15px">${document.getElementById('pop-uptime').textContent.replace(/.*depuis /,'')}</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">${(metrics?.cpu_percent ?? 0).toFixed(0)}%</span>
|
||||
</div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 52" preserveAspectRatio="none" id="det-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)">${Grid.fmtPct(metrics?.memory_used && metrics?.memory_total ? metrics.memory_used / metrics.memory_total * 100 : null)}</span>
|
||||
</div>
|
||||
<svg class="chart-svg" viewBox="0 0 200 52" preserveAspectRatio="none" id="det-mem-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 style="display:flex;flex-direction:column;gap:8px">
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
<div style="width:22px;text-align:center;font-size:13px;cursor:help" data-tip="Utilisé"><i class="fa-solid fa-hard-drive"></i></div>
|
||||
<div style="flex:1;height:7px;border-radius:4px;background:var(--bg-1);overflow:hidden">
|
||||
<div style="height:100%;border-radius:4px;background:var(--ok);width:${metrics?.hdd_total ? (metrics.hdd_used/metrics.hdd_total*100).toFixed(0) : 0}%"></div></div>
|
||||
<span style="font-family:var(--font-mono);font-size:12px;color:var(--ink-2);width:90px;text-align:right">${Grid.fmt(metrics?.hdd_used)} / ${Grid.fmt(metrics?.hdd_total)}</span>
|
||||
</div>
|
||||
${smartBtn}
|
||||
</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">${agent.hostname}</div></div>
|
||||
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">${agent.ip || '—'}</div></div>
|
||||
<div class="meta"><div class="meta-lbl">PROTOCOLES ACTIFS</div><div style="display:flex;gap:5px;margin-top:4px">${protos || '—'}</div></div>
|
||||
<div class="meta"><div class="meta-lbl">DERNIER CONTACT</div><div class="meta-val">${new Date(agent.last_seen * 1000).toLocaleTimeString('fr-FR')}</div></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
Charts.renderChart(document.getElementById('det-cpu-chart'), cpuPts, '--accent');
|
||||
Charts.renderChart(document.getElementById('det-mem-chart'), memPts, '--blue');
|
||||
});
|
||||
|
||||
// Resize → sauvegarder sur serveur
|
||||
const pd = document.getElementById('popup-detail');
|
||||
new ResizeObserver(() => {
|
||||
API.putServerConfig({
|
||||
...App.serverConfig,
|
||||
popup_detail_w: pd.offsetWidth,
|
||||
popup_detail_h: pd.offsetHeight,
|
||||
}).catch(() => {});
|
||||
}).observe(pd);
|
||||
|
||||
document.getElementById('overlay-detail').style.display = 'flex';
|
||||
}
|
||||
|
||||
function hideDetail() {
|
||||
document.getElementById('overlay-detail').style.display = 'none';
|
||||
}
|
||||
|
||||
// ══ CONFIG AGENT ══
|
||||
async function showAgentCfg() {
|
||||
if (!_currentAgentId) return;
|
||||
let cfg = {};
|
||||
try { cfg = await API.getAgentConfig(_currentAgentId); } catch {}
|
||||
_agentCfgData = cfg;
|
||||
|
||||
document.getElementById('agentcfg-sub').textContent =
|
||||
`${_currentAgentId} · config récupérée`;
|
||||
|
||||
const metrics = ['cpu','memory','disk','smart','uptime','network','temperature'];
|
||||
const icons = {
|
||||
cpu:'fa-microchip',memory:'fa-memory',disk:'fa-hard-drive',
|
||||
smart:'fa-shield-heart',uptime:'fa-clock',network:'fa-network-wired',
|
||||
temperature:'fa-thermometer-half'
|
||||
};
|
||||
const mqttCfg = cfg.protocols?.mqtt ?? {};
|
||||
|
||||
document.getElementById('agentcfg-body').innerHTML = `
|
||||
<div style="display:flex;flex-direction:column;gap:8px">
|
||||
<div style="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)">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>
|
||||
<span class="mh-proto mqtt"><i class="fa-solid fa-tower-broadcast" style="font-size:8px"></i> MQTT</span>
|
||||
</div>
|
||||
${metrics.map(m => {
|
||||
const udpOn = cfg.metrics?.[m]?.udp ? 'udp-on' : '';
|
||||
const mqttOn = cfg.metrics?.[m]?.mqtt ? 'mqtt-on' : '';
|
||||
return `<div class="metric-row">
|
||||
<div class="metric-cell">
|
||||
<div class="metric-ico"><i class="fa-solid ${icons[m]}"></i></div>
|
||||
<span class="metric-name">${m}</span>
|
||||
</div>
|
||||
<div class="metric-chk"><div class="cbox ${udpOn}" id="cbox-${m}-udp" onclick="Popups.toggleCbox(this,'${m}','udp')" data-tip="${m} via UDP"><i class="fa-solid fa-check"></i></div></div>
|
||||
<div class="metric-chk"><div class="cbox ${mqttOn}" id="cbox-${m}-mqtt" onclick="Popups.toggleCbox(this,'${m}','mqtt')" data-tip="${m} via MQTT"><i class="fa-solid fa-check"></i></div></div>
|
||||
</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:8px">
|
||||
<div style="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</div>
|
||||
<div style="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">
|
||||
<div style="display:flex;align-items:center;gap:10px"><label style="font-size:11px;color:var(--ink-3);font-family:var(--font-terminal);width:90px">Broker</label>
|
||||
<input id="mqtt-host" style="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)" value="${mqttCfg.host ?? '10.0.0.3'}"></div>
|
||||
<div style="display:flex;align-items:center;gap:10px"><label style="font-size:11px;color:var(--ink-3);font-family:var(--font-terminal);width:90px">Port</label>
|
||||
<input id="mqtt-port" type="number" style="width:90px;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)" value="${mqttCfg.port ?? 1883}"></div>
|
||||
<div style="display:flex;align-items:center;gap:10px"><label style="font-size:11px;color:var(--ink-3);font-family:var(--font-terminal);width:90px">Topic base</label>
|
||||
<input id="mqtt-topic" style="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)" value="${mqttCfg.topic_base ?? 'nanometrics/agents'}"></div>
|
||||
<div style="border-top:1px solid var(--border-1);padding-top:8px;display:flex;flex-direction:column;gap:5px">
|
||||
${['auto_discovery:Auto-discovery (Home Assistant):fa-satellite-dish',
|
||||
'birth_message:Birth message:fa-arrow-right-to-bracket',
|
||||
'last_will:Last Will message:fa-skull'].map(s => {
|
||||
const [key, label, icon] = s.split(':');
|
||||
return `<div style="display:flex;align-items:center;justify-content:space-between;padding:3px 0">
|
||||
<label style="font-size:12px;color:var(--ink-2);display:flex;align-items:center;gap:7px;cursor:pointer">
|
||||
<i class="fa-solid ${icon}" style="color:var(--purple);font-size:11px"></i>${label}
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="mqtt-${key}" ${mqttCfg[key] !== false ? 'checked' : ''}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:8px">
|
||||
<div style="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)">
|
||||
COMMANDES DISTANTES <span style="color:var(--ink-4);font-size:8px;margin-left:6px">— BIENTÔT</span>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:6px">
|
||||
${[['fa-rotate-right','reboot'],['fa-power-off','shutdown'],['fa-display','screen off'],
|
||||
['fa-arrow-up-from-bracket','update'],['fa-arrow-up-right-dots','upgrade'],['fa-terminal','shell cmd']].map(
|
||||
([icon, label]) => `<div style="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">
|
||||
<i class="fa-solid ${icon}" style="font-size:16px;color:var(--ink-3)"></i>
|
||||
<span style="font-size:10px;color:var(--ink-4);font-family:var(--font-terminal)">${label}</span>
|
||||
<span style="font-size:8px;color:var(--ink-4);font-family:var(--font-terminal)">bientôt</span>
|
||||
</div>`).join('')}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
document.getElementById('overlay-agentcfg').style.display = 'flex';
|
||||
}
|
||||
|
||||
function toggleCbox(el, metric, proto) {
|
||||
const isOn = el.classList.contains(proto + '-on');
|
||||
el.classList.toggle(proto + '-on', !isOn);
|
||||
el.style.color = isOn ? 'transparent' : '';
|
||||
if (!_agentCfgData.metrics) _agentCfgData.metrics = {};
|
||||
if (!_agentCfgData.metrics[metric]) _agentCfgData.metrics[metric] = {};
|
||||
_agentCfgData.metrics[metric][proto] = !isOn;
|
||||
}
|
||||
|
||||
async function sendAgentConfig() {
|
||||
if (!_currentAgentId || !_agentCfgData) return;
|
||||
if (!_agentCfgData.protocols) _agentCfgData.protocols = {};
|
||||
_agentCfgData.protocols.mqtt = {
|
||||
..._agentCfgData.protocols.mqtt,
|
||||
host: document.getElementById('mqtt-host')?.value ?? '10.0.0.3',
|
||||
port: parseInt(document.getElementById('mqtt-port')?.value ?? '1883'),
|
||||
topic_base: document.getElementById('mqtt-topic')?.value ?? 'nanometrics/agents',
|
||||
auto_discovery: document.getElementById('mqtt-auto_discovery')?.checked ?? true,
|
||||
birth_message: document.getElementById('mqtt-birth_message')?.checked ?? true,
|
||||
last_will: document.getElementById('mqtt-last_will')?.checked ?? true,
|
||||
};
|
||||
try {
|
||||
await API.putAgentConfig(_currentAgentId, _agentCfgData);
|
||||
document.getElementById('overlay-agentcfg').style.display = 'none';
|
||||
} catch (e) {
|
||||
alert('Erreur lors de l\'envoi : ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ══ CONFIG SERVEUR ══
|
||||
async function showSrvCfg() {
|
||||
const cfg = App.serverConfig ?? {};
|
||||
document.getElementById('btn-srvcfg').classList.add('active-btn');
|
||||
document.getElementById('srvcfg-body').innerHTML = `
|
||||
<div style="display:flex;flex-direction:column;gap:8px">
|
||||
<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="${cfg.tile_min_width ?? 220}"
|
||||
oninput="this.nextElementSibling.textContent=this.value+'px'" id="s-tile-w">
|
||||
<span class="scfg-val">${cfg.tile_min_width ?? 220}px</span></div>
|
||||
<div class="scfg-row"><label>Taille du texte</label>
|
||||
<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>
|
||||
<div style="display:flex;flex-direction:column;gap:8px">
|
||||
<div class="scfg-sec-title">SEUILS D'ALERTE</div>
|
||||
<div class="scfg-row"><label>Warning CPU/RAM</label>
|
||||
<input type="range" class="scfg-slider" min="50" max="95" value="${cfg.warn_cpu ?? 70}"
|
||||
oninput="this.nextElementSibling.textContent=this.value+'%'" id="s-warn-cpu">
|
||||
<span class="scfg-val">${cfg.warn_cpu ?? 70}%</span></div>
|
||||
<div class="scfg-row"><label>Erreur CPU/RAM</label>
|
||||
<input type="range" class="scfg-slider" min="60" max="100" value="${cfg.err_cpu ?? 85}"
|
||||
oninput="this.nextElementSibling.textContent=this.value+'%'" id="s-err-cpu">
|
||||
<span class="scfg-val">${cfg.err_cpu ?? 85}%</span></div>
|
||||
<div class="scfg-row"><label>Warning Disque</label>
|
||||
<input type="range" class="scfg-slider" min="50" max="95" value="${cfg.warn_disk ?? 75}"
|
||||
oninput="this.nextElementSibling.textContent=this.value+'%'" id="s-warn-disk">
|
||||
<span class="scfg-val">${cfg.warn_disk ?? 75}%</span></div>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:8px">
|
||||
<div class="scfg-sec-title">DONNÉES & RÉTENTION</div>
|
||||
<div class="scfg-row"><label>Historique</label>
|
||||
<select class="scfg-select" id="s-retention">
|
||||
${[7,30,90,365].map(d => `<option value="${d}" ${(cfg.retention_days??30)==d?'selected':''}>${d} jours</option>`).join('')}
|
||||
</select></div>
|
||||
<div class="scfg-row"><label>Courbes (durée)</label>
|
||||
<select class="scfg-select" id="s-chart-dur">
|
||||
${[[15,'15 min'],[30,'30 min'],[60,'1 heure'],[360,'6 heures']].map(([v,l]) =>
|
||||
`<option value="${v}" ${(cfg.chart_duration_min??30)==v?'selected':''}>${l}</option>`).join('')}
|
||||
</select></div>
|
||||
</div>`;
|
||||
document.getElementById('overlay-srvcfg').style.display = 'flex';
|
||||
}
|
||||
|
||||
function hideSrvCfg() {
|
||||
document.getElementById('overlay-srvcfg').style.display = 'none';
|
||||
document.getElementById('btn-srvcfg').classList.remove('active-btn');
|
||||
}
|
||||
|
||||
async function saveSrvCfg() {
|
||||
const cfg = {
|
||||
...App.serverConfig,
|
||||
tile_min_width: parseInt(document.getElementById('s-tile-w')?.value ?? 220),
|
||||
font_size: parseInt(document.getElementById('s-font')?.value ?? 13),
|
||||
warn_cpu: parseInt(document.getElementById('s-warn-cpu')?.value ?? 70),
|
||||
err_cpu: parseInt(document.getElementById('s-err-cpu')?.value ?? 85),
|
||||
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),
|
||||
};
|
||||
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';
|
||||
hideSrvCfg();
|
||||
}
|
||||
|
||||
// ══ POPUP SMART ══
|
||||
function showSmart(agentId) {
|
||||
const m = Grid.getAgent(agentId)?.metrics?.smart;
|
||||
if (!m) return;
|
||||
document.getElementById('smart-sub').textContent = agentId;
|
||||
const passColor = m.passed ? 'var(--ok)' : 'var(--err)';
|
||||
const passText = m.passed ? 'Disque en bonne santé' : 'Disque en mauvais état';
|
||||
const passSub = m.passed
|
||||
? 'Aucun problème détecté. Le disque fonctionne normalement.'
|
||||
: 'Des problèmes ont été détectés. Envisagez un remplacement.';
|
||||
|
||||
document.getElementById('smart-body').innerHTML = `
|
||||
<div class="smart-verdict" style="${m.passed ? '' : 'background:rgba(251,73,52,.1);border-color:rgba(251,73,52,.3)'}">
|
||||
<div style="font-size:28px;color:${passColor}"><i class="fa-solid ${m.passed ? 'fa-circle-check' : 'fa-circle-xmark'}"></i></div>
|
||||
<div><div style="font-size:16px;font-weight:700;color:${passColor}">${passText}</div>
|
||||
<div style="font-size:12px;color:var(--ink-3);margin-top:3px">${passSub}</div></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sec-title">POINTS DE CONTRÔLE</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
|
||||
${m.temperature != null ? `<div style="background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1)">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
|
||||
<span style="color:var(--warn);font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-temperature-half"></i></span>
|
||||
<span style="font-weight:600;font-size:12px;flex:1">Température</span>
|
||||
<span style="font-size:10px;font-family:var(--font-terminal);font-weight:700;padding:1px 7px;border-radius:999px;background:rgba(77,187,38,.15);color:var(--ok)">Normale</span>
|
||||
</div>
|
||||
<div class="si-val">${m.temperature}<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.</div>
|
||||
</div>` : ''}
|
||||
${m.reallocated_sectors != null ? `<div style="background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1)">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
|
||||
<span style="color:${m.reallocated_sectors > 0 ? 'var(--err)' : 'var(--ok)'};font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-circle-check"></i></span>
|
||||
<span style="font-weight:600;font-size:12px;flex:1">Secteurs défectueux</span>
|
||||
</div>
|
||||
<div class="si-val">${m.reallocated_sectors}<span class="u"> sect.</span></div>
|
||||
<div class="si-desc">S'ils apparaissent en grand nombre, une panne est imminente.</div>
|
||||
</div>` : ''}
|
||||
${m.power_on_hours != null ? `<div style="background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1)">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
|
||||
<span style="color:var(--blue);font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-clock-rotate-left"></i></span>
|
||||
<span style="font-weight:600;font-size:12px;flex:1">Heures de fonctionnement</span>
|
||||
</div>
|
||||
<div class="si-val">${m.power_on_hours.toLocaleString('fr-FR')}<span class="u">h</span></div>
|
||||
<div class="si-desc">≈${Math.floor(m.power_on_hours/24)} jours. Un disque dure en moyenne 3 à 5 ans.</div>
|
||||
</div>` : ''}
|
||||
${m.wear_level != null ? `<div style="background:var(--bg-3);border-radius:8px;padding:12px 14px;border:1px solid var(--border-1)">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
|
||||
<span style="color:var(--ok);font-size:14px;width:22px;text-align:center"><i class="fa-solid fa-battery-full"></i></span>
|
||||
<span style="font-weight:600;font-size:12px;flex:1">Durée de vie SSD</span>
|
||||
</div>
|
||||
<div class="si-val">${m.wear_level}<span class="u">%</span></div>
|
||||
<div class="si-desc">100% = neuf · 0% = fin de vie recommandée.</div>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
document.getElementById('overlay-smart').style.display = 'flex';
|
||||
}
|
||||
|
||||
return {
|
||||
showDetail, hideDetail,
|
||||
showAgentCfg, sendAgentConfig, toggleCbox,
|
||||
showSrvCfg, hideSrvCfg, saveSrvCfg,
|
||||
showSmart,
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user