const Popups = (() => {
let _currentAgentId = null;
let _agentCfgData = null;
let _resizeObs = 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;
const hint = document.getElementById('icon-hint');
try {
await API.uploadIcon(agentId, file);
const ts = '?t=' + Date.now();
img.src = API.iconUrl(agentId) + ts;
img.style.display = 'block';
document.getElementById('pop-icon-fa').style.display = 'none';
const tileImg = document.querySelector(`#tile-${CSS.escape(agentId)} .t-icon img`);
if (tileImg) tileImg.src = API.iconUrl(agentId) + ts;
} catch (err) {
if (hint) {
hint.style.color = 'var(--err)';
hint.textContent = 'Erreur : ' + (err.message || 'téléversement échoué');
setTimeout(() => {
hint.style.color = '';
hint.textContent = 'Cliquer sur l\'icône pour personnaliser · JPG PNG WEBP · max 128×128 px';
}, 4000);
}
} finally {
e.target.value = '';
}
};
// Uptime
const up = metrics?.uptime;
if (up) {
const d = Math.floor(up / 86400), h = Math.floor((up % 86400) / 3600);
document.getElementById('pop-uptime').innerHTML =
`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);
let ramMin = null, ramMax = null;
for (const h of history) {
if (h.memory_used != null) {
if (ramMin === null || h.memory_used < ramMin) ramMin = h.memory_used;
if (ramMax === null || h.memory_used > ramMax) ramMax = h.memory_used;
}
}
const ramMinMax = ramMin !== null
? `
min ${Grid.fmt(ramMin)}max ${Grid.fmt(ramMax)}
`
: '';
const smartBtn = metrics?.smart
? `
SMART
·
${metrics.smart.passed ? 'PASSED' : 'FAILED'}
${metrics.smart.temperature ? `
${metrics.smart.temperature}°C` : ''}
`
: '';
const protos = [
metrics?.cpu_percent != null ? `UDP` : '',
].filter(Boolean).join('');
document.getElementById('pop-body').innerHTML = `
MÉTRIQUES ACTUELLES
CPU
${(metrics?.cpu_percent ?? 0).toFixed(0)}%
MÉMOIRE
${Grid.fmt(metrics?.memory_used)}
/ ${Grid.fmt(metrics?.memory_total)}
DISQUE
${Grid.fmt(metrics?.hdd_used)}
/ ${Grid.fmt(metrics?.hdd_total)}
UPTIME
${document.getElementById('pop-uptime').textContent.replace(/.*depuis /,'')}
HISTORIQUE — 30 MIN
−30min−15minnow
${ramMinMax}
STOCKAGE
${Grid.fmt(metrics?.hdd_used)} / ${Grid.fmt(metrics?.hdd_total)}
${smartBtn}
`;
requestAnimationFrame(() => {
Charts.renderChart(document.getElementById('det-cpu-chart'), cpuPts, '--accent');
Charts.renderChart(document.getElementById('det-mem-chart'), memPts, '--blue');
});
// Resize → sauvegarder sur serveur
if (_resizeObs) _resizeObs.disconnect();
const pd = document.getElementById('popup-detail');
_resizeObs = new ResizeObserver(() => {
API.putServerConfig({
...App.serverConfig,
popup_detail_w: pd.offsetWidth,
popup_detail_h: pd.offsetHeight,
}).catch(() => {});
});
_resizeObs.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 = `
MÉTRIQUES PAR PROTOCOLE
${metrics.map(m => {
const udpOn = cfg.metrics?.[m]?.udp ? 'udp-on' : '';
const mqttOn = cfg.metrics?.[m]?.mqtt ? 'mqtt-on' : '';
return `
`;
}).join('')}
COMMANDES DISTANTES — BIENTÔT
${[['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]) => `
${label}
bientôt
`).join('')}
`;
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 = `
DONNÉES & RÉTENTION
`;
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),
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();
}
// ══ 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 = `
POINTS DE CONTRÔLE
${m.temperature != null ? `
Température
Normale
${m.temperature}°C
Idéal : 20–50°C. Au-delà de 60°C le disque risque de s'abîmer.
` : ''}
${m.reallocated_sectors != null ? `
Secteurs défectueux
${m.reallocated_sectors} sect.
S'ils apparaissent en grand nombre, une panne est imminente.
` : ''}
${m.power_on_hours != null ? `
Heures de fonctionnement
${m.power_on_hours.toLocaleString('fr-FR')}h
≈${Math.floor(m.power_on_hours/24)} jours. Un disque dure en moyenne 3 à 5 ans.
` : ''}
${m.wear_level != null ? `
Durée de vie SSD
${m.wear_level}%
100% = neuf · 0% = fin de vie recommandée.
` : ''}
`;
document.getElementById('overlay-smart').style.display = 'flex';
}
// ══ SUPPRESSION AGENT ══
let _delAgentId = null;
function confirmDeleteAgent(id, hostname) {
_delAgentId = id;
document.getElementById('del-agent-name').textContent = hostname || id;
document.getElementById('overlay-del').style.display = 'flex';
}
async function doDeleteAgent() {
if (!_delAgentId) return;
const id = _delAgentId;
document.getElementById('overlay-del').style.display = 'none';
_delAgentId = null;
try {
await API.deleteAgent(id);
Grid.removeAgent(id);
} catch (e) {
alert('Erreur lors de la suppression : ' + e.message);
}
}
function _copyInstallCmd(btn) {
const text = document.getElementById('s-install-cmd').value;
const done = () => { btn.textContent = '✓ Copié'; setTimeout(() => btn.textContent = 'Copier', 1500); };
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(done).catch(() => _copyFallback(text, done));
} else {
_copyFallback(text, done);
}
}
function _copyFallback(text, cb) {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.cssText = 'position:fixed;opacity:0;top:0;left:0';
document.body.appendChild(ta);
ta.focus();
ta.select();
try { document.execCommand('copy'); cb(); } catch (_) {}
document.body.removeChild(ta);
}
return {
showDetail, hideDetail,
showAgentCfg, sendAgentConfig, toggleCbox,
showSrvCfg, hideSrvCfg, saveSrvCfg, confirmDeleteAgent, doDeleteAgent,
showSmart, _copyInstallCmd,
};
})();