diff --git a/static/js/pages/visual-route.js b/static/js/pages/visual-route.js index 8e591c0..ff4c151 100644 --- a/static/js/pages/visual-route.js +++ b/static/js/pages/visual-route.js @@ -1,107 +1,143 @@ document.addEventListener('DOMContentLoaded', function() { + let network = null; + let allNodes = new vis.DataSet(); + let allEdges = new vis.DataSet(); + const form = document.getElementById('bgprtv-form'); - if (!form) { - return; - } + if (!form) return; const ipAddressInput = document.getElementById('bgprtv-ip-address'); const loader = document.getElementById('bgprtv-loader'); const errorMessageContainer = document.getElementById('bgprtv-error-message'); const networkContainer = document.getElementById('bgprtv-mynetwork'); - const graphUrl = networkContainer.dataset.graphUrl; + + const filterControls = document.querySelectorAll('input[name="bgprtv-filter"]'); + const allCommunitiesCheckbox = document.querySelector('input[value="all"]'); + const activeCheckbox = document.querySelector('input[value="active"]'); if (!graphUrl) { - errorMessageContainer.textContent = 'Configuration Error: The graph URL is missing. Ensure the network container div in your HTML has a "data-graph-url" attribute pointing to the correct API endpoint.'; + errorMessageContainer.textContent = 'Configuration Error: The graph URL is missing.'; errorMessageContainer.style.display = 'block'; - const submitButton = form.querySelector('input[type="submit"]'); - if (submitButton) { - submitButton.disabled = true; - } + form.querySelector('input[type="submit"]').disabled = true; return; } form.addEventListener('submit', function(event) { event.preventDefault(); - const ipAddress = ipAddressInput.value; - loader.style.display = 'block'; errorMessageContainer.style.display = 'none'; errorMessageContainer.textContent = ''; - - const visContent = networkContainer.querySelector('.vis-network'); - if (visContent) { - visContent.remove(); + if (network) { + network.destroy(); + network = null; } - fetch(graphUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ip_address: ipAddress }) }) .then(response => { - return response.json().then(data => { - if (!response.ok && data.error) { - throw new Error(data.error); - } - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return data; - }); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + return response.json(); }) .then(data => { loader.style.display = 'none'; - if (data.nodes && data.nodes.length > 0) { + if (data.error) { + errorMessageContainer.textContent = data.error; + errorMessageContainer.style.display = 'block'; + } else if (data.nodes && data.nodes.length > 0) { drawGraph(data); } else { - errorMessageContainer.textContent = data.error || 'Could not parse any valid AS paths from the API response.'; + errorMessageContainer.textContent = 'Could not parse any valid AS paths from the API response.'; errorMessageContainer.style.display = 'block'; } }) .catch(error => { loader.style.display = 'none'; - errorMessageContainer.textContent = error.message; + errorMessageContainer.textContent = `An unexpected error occurred: ${error.message}`; errorMessageContainer.style.display = 'block'; console.error('Error:', error); }); }); - function drawGraph(data) { - const nodes = new vis.DataSet(data.nodes); - const edges = new vis.DataSet(data.edges); - const graphData = { nodes: nodes, edges: edges }; - - const options = { - layout: { hierarchical: false }, - edges: { - arrows: { to: { enabled: true, scaleFactor: 0.7 } }, - smooth: { - enabled: true, - type: "cubicBezier", - forceDirection: "horizontal", - roundness: 0.85 - } - }, - nodes: { - shape: 'box', - margin: 15, - font: { - size: 14, - color: '#343a40', - multi: 'html', - align: 'left' - }, - borderWidth: 2 - }, - physics: { enabled: false }, - interaction: { - dragNodes: true, - dragView: true, - zoomView: true - } +function drawGraph(data) { + allNodes = new vis.DataSet(data.nodes); + allEdges = new vis.DataSet(data.edges); + + const options = { + layout: { hierarchical: false }, + edges: { + arrows: { to: { enabled: true, scaleFactor: 0.7 } } + }, + nodes: { + shape: 'box', + margin: 15, + font: { size: 14, color: '#343a40', multi: 'html', align: 'left' }, + borderWidth: 2 + }, + physics: { enabled: false }, + interaction: { dragNodes: true, dragView: true, zoomView: true } + }; + + network = new vis.Network(networkContainer, {}, options); + + activeCheckbox.checked = false; + allCommunitiesCheckbox.checked = true; + filterControls.forEach(cb => { if (cb.value !== 'all' && cb.value !== 'active') cb.checked = false; }); + filterGraph(); +} + + function filterGraph() { + if (!network) return; + + const showOnlyActive = activeCheckbox.checked; + const selectedCategories = Array.from(filterControls) + .filter(cb => cb.checked && cb.value !== 'active' && cb.value !== 'all') + .map(cb => cb.value); + const showAllCommunities = allCommunitiesCheckbox.checked; + + const itemFilter = (item) => { + const activeFilterPassed = !showOnlyActive || item.is_active; + + const categoryFilterPassed = showAllCommunities || selectedCategories.includes(item.path_category); + + return item.path_category === 'global' || (activeFilterPassed && categoryFilterPassed); }; - new vis.Network(networkContainer, graphData, options); + + const edgeFilter = (item) => { + const activeFilterPassed = !showOnlyActive || item.is_active; + const categoryFilterPassed = showAllCommunities || selectedCategories.includes(item.path_category); + return activeFilterPassed && categoryFilterPassed; + }; + + const filteredNodes = allNodes.get({ filter: itemFilter }); + const filteredEdges = allEdges.get({ filter: edgeFilter }); + + network.setData({ nodes: new vis.DataSet(filteredNodes), edges: new vis.DataSet(filteredEdges) }); } + + filterControls.forEach(checkbox => { + checkbox.addEventListener('change', function(e) { + const changedValue = e.target.value; + + if (changedValue === 'all' && e.target.checked) { + filterControls.forEach(cb => { + if (cb.value !== 'all' && cb.value !== 'active') { + cb.checked = false; + } + }); + } else if (changedValue !== 'all' && changedValue !== 'active' && e.target.checked) { + allCommunitiesCheckbox.checked = false; + } + + const specificCommunityChecked = Array.from(filterControls).some(cb => cb.checked && cb.value !== 'all' && cb.value !== 'active'); + if (!specificCommunityChecked) { + allCommunitiesCheckbox.checked = true; + } + + filterGraph(); + }); + }); }); \ No newline at end of file