diff --git a/frontend/assets/js/app.js b/frontend/assets/js/app.js index d28e22c..caf7347 100644 --- a/frontend/assets/js/app.js +++ b/frontend/assets/js/app.js @@ -280,6 +280,7 @@ async function loadTeams() { async function loadEvents() { events = await apiFetch('events'); + syncHashes.events = JSON.stringify(events); loadAllTags(); renderTimeline(); } @@ -1209,7 +1210,7 @@ function setupCanvasDrop() { } else { await apiFetch('nodes', { method: 'POST', body: JSON.stringify({ label: data.charAt(0).toUpperCase() + data.slice(1), - ip_address: '', node_type: data, status: 'unknown', + ip_address: '', node_type: data || 'host', status: 'unknown', group_name: 'default', notes: '', pos_x: mx, pos_y: my })}); @@ -1218,25 +1219,46 @@ function setupCanvasDrop() { }); } -// Real-time sync: poll backend every 5 seconds only when network tab is visible +// Real-time sync: poll backend every 5 seconds for all data let syncInterval = null; -let lastSyncData = null; +let syncHashes = { events: null, documents: null, network: null }; function startSync() { if (syncInterval) return; syncInterval = setInterval(async () => { - const netTab = document.getElementById('network-tab'); - if (!netTab || !netTab.classList.contains('active')) return; + // Skip sync if a modal is open (user is editing) + const openModals = document.querySelectorAll('.modal.show'); + if (openModals.length > 0) return; try { - const [n, l, s] = await Promise.all([ - apiFetch('nodes'), apiFetch('links'), apiFetch('shapes') + const [eventsData, docsData, nodesData, linksData, shapesData] = await Promise.all([ + apiFetch('events'), + apiFetch('documents'), + apiFetch('nodes'), + apiFetch('links'), + apiFetch('shapes'), ]); - const hash = JSON.stringify({ n, l, s }); - if (hash !== lastSyncData) { - lastSyncData = hash; - nodes = Array.isArray(n) ? n : []; - links = Array.isArray(l) ? l : []; - shapes = Array.isArray(s) ? s : []; + + const eventsHash = JSON.stringify(eventsData); + if (eventsHash !== syncHashes.events) { + syncHashes.events = eventsHash; + events = Array.isArray(eventsData) ? eventsData : []; + loadAllTags(); + renderTimeline(); + } + + const docsHash = JSON.stringify(docsData); + if (docsHash !== syncHashes.documents) { + syncHashes.documents = docsHash; + documents = Array.isArray(docsData) ? docsData : []; + renderDocuments(); + } + + const netHash = JSON.stringify({ n: nodesData, l: linksData, s: shapesData }); + if (netHash !== syncHashes.network) { + syncHashes.network = netHash; + nodes = Array.isArray(nodesData) ? nodesData : []; + links = Array.isArray(linksData) ? linksData : []; + shapes = Array.isArray(shapesData) ? shapesData : []; populateNodeSelects(); renderNodeList(); renderShapeList(); @@ -1248,10 +1270,6 @@ function startSync() { }, 5000); } -function stopSync() { - if (syncInterval) { clearInterval(syncInterval); syncInterval = null; } -} - function getCanvasNodeAt(mx, my) { return canvasNodes.find(n => Math.hypot(mx - n.x, my - n.y) < 28); } @@ -1491,6 +1509,7 @@ document.getElementById('registrationToggle').addEventListener('change', saveReg async function loadDocuments() { try { documents = await apiFetch('documents'); + syncHashes.documents = JSON.stringify(documents); } catch (e) { documents = []; }