fix(dashboard): XSS escaping, ResizeObserver leak, WS reconnect timer
- Ajout de esc() dans api.js pour échapper les valeurs serveur avant injection innerHTML - Application de esc() sur hostname, ip et agentId dans grid.js et popups.js - Fix fuite mémoire ResizeObserver dans showDetail : déconnexion avant recréation (_resizeObs) - Fix WebSocket reconnect : clearTimeout avant setTimeout pour éviter les timers concurrents (_reconnectTimer) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,14 @@
|
||||
// Échappe les valeurs serveur avant injection dans innerHTML
|
||||
function esc(s) {
|
||||
if (s == null) return '—';
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
const API = (() => {
|
||||
const BASE = ''; // même origine, proxy Nginx vers le serveur Go
|
||||
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
const App = (() => {
|
||||
let _ws = null;
|
||||
let _reconnectDelay = 1000;
|
||||
let _reconnectTimer = null;
|
||||
let _serverConfig = null;
|
||||
|
||||
// Tooltip global position:fixed
|
||||
@@ -62,7 +63,8 @@ const App = (() => {
|
||||
};
|
||||
|
||||
_ws.onclose = () => {
|
||||
setTimeout(connectWS, _reconnectDelay);
|
||||
clearTimeout(_reconnectTimer);
|
||||
_reconnectTimer = setTimeout(connectWS, _reconnectDelay);
|
||||
_reconnectDelay = Math.min(_reconnectDelay * 2, 30000);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,12 +61,12 @@ const Grid = (() => {
|
||||
<span style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--accent)">
|
||||
<i class="fa-solid fa-server"></i></span>`;
|
||||
|
||||
return `<div class="tile ${sc}" id="tile-${id}" onclick="Popups.showDetail('${id}')">
|
||||
return `<div class="tile ${sc}" id="tile-${id}" onclick="Popups.showDetail('${esc(id)}')">
|
||||
<div class="tile-head">
|
||||
<div class="t-icon">${iconContent}</div>
|
||||
<div class="t-names">
|
||||
<div class="t-host">${agent.hostname}</div>
|
||||
<div class="t-ip">${agent.ip || '—'}</div>
|
||||
<div class="t-host">${esc(agent.hostname)}</div>
|
||||
<div class="t-ip">${esc(agent.ip) || '—'}</div>
|
||||
</div>
|
||||
<div class="t-led ${ledClass(agent.status)}"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const Popups = (() => {
|
||||
let _currentAgentId = null;
|
||||
let _agentCfgData = null;
|
||||
let _resizeObs = null;
|
||||
|
||||
// ══ POPUP DÉTAIL ══
|
||||
async function showDetail(agentId) {
|
||||
@@ -50,7 +51,7 @@ const Popups = (() => {
|
||||
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-btn ok" onclick="Popups.showSmart('${esc(agentId)}')" data-tip="Voir la santé complète du disque">
|
||||
<div class="smart-dot"></div>
|
||||
<span style="font-weight:600">SMART</span>
|
||||
<span>·</span>
|
||||
@@ -116,8 +117,8 @@ const Popups = (() => {
|
||||
<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">HOSTNAME</div><div class="meta-val">${esc(agent.hostname)}</div></div>
|
||||
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">${esc(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>
|
||||
@@ -129,14 +130,16 @@ const Popups = (() => {
|
||||
});
|
||||
|
||||
// Resize → sauvegarder sur serveur
|
||||
if (_resizeObs) _resizeObs.disconnect();
|
||||
const pd = document.getElementById('popup-detail');
|
||||
new ResizeObserver(() => {
|
||||
_resizeObs = new ResizeObserver(() => {
|
||||
API.putServerConfig({
|
||||
...App.serverConfig,
|
||||
popup_detail_w: pd.offsetWidth,
|
||||
popup_detail_h: pd.offsetHeight,
|
||||
}).catch(() => {});
|
||||
}).observe(pd);
|
||||
});
|
||||
_resizeObs.observe(pd);
|
||||
|
||||
document.getElementById('overlay-detail').style.display = 'flex';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user