diff --git a/dashboard/js/app.js b/dashboard/js/app.js new file mode 100644 index 0000000..1489009 --- /dev/null +++ b/dashboard/js/app.js @@ -0,0 +1,103 @@ +const App = (() => { + let _ws = null; + let _reconnectDelay = 1000; + let _serverConfig = null; + + // Tooltip global position:fixed + 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'); + }, 120); + }); + document.addEventListener('mousemove', e => { + if (!tip.classList.contains('show')) return; + 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'; + }); + document.addEventListener('mouseout', e => { + if (!e.target.closest('[data-tip]')) return; + clearTimeout(_tt); + tip.classList.remove('show'); + }); + + 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'; + } + + function updateClock() { + document.getElementById('f-time').textContent = + new Date().toLocaleTimeString('fr-FR'); + } + + function connectWS() { + const proto = location.protocol === 'https:' ? 'wss' : 'ws'; + _ws = new WebSocket(`${proto}://${location.host}/ws`); + + _ws.onopen = () => { + _reconnectDelay = 1000; + document.querySelector('.logo-led').style.animation = 'blink 2s infinite'; + }; + + _ws.onmessage = (event) => { + try { + const msg = JSON.parse(event.data); + if (msg.type === 'metrics_update') { + Grid.update(msg.agent_id, msg.data); + updateClock(); + } + } catch {} + }; + + _ws.onclose = () => { + setTimeout(connectWS, _reconnectDelay); + _reconnectDelay = Math.min(_reconnectDelay * 2, 30000); + }; + } + + async function init() { + try { + _serverConfig = await API.getServerConfig(); + if (_serverConfig.tile_min_width) { + document.documentElement.style.setProperty('--tile-min', _serverConfig.tile_min_width + 'px'); + } + if (_serverConfig.font_size) { + document.body.style.fontSize = _serverConfig.font_size + 'px'; + } + if (_serverConfig.popup_detail_w && _serverConfig.popup_detail_h) { + const pd = document.getElementById('popup-detail'); + pd.style.width = _serverConfig.popup_detail_w + 'px'; + pd.style.height = _serverConfig.popup_detail_h + 'px'; + } + } catch {} + + try { + const agents = await API.getAgents(); + Grid.refresh(agents); + } catch {} + + connectWS(); + updateClock(); + setInterval(updateClock, 1000); + } + + document.addEventListener('DOMContentLoaded', init); + + return { + toggleTheme, + get serverConfig() { return _serverConfig; }, + set serverConfig(v) { _serverConfig = v; }, + }; +})();