diff --git a/dbcleanup.log b/dbcleanup.log new file mode 100644 index 0000000..dea5664 --- /dev/null +++ b/dbcleanup.log @@ -0,0 +1,14 @@ +2025-10-14 20:13:45,221 [INFO] Daily cleanup is disabled by configuration. +2025-10-14 20:25:47,645 [INFO] Daily cleanup is disabled by configuration. +2025-10-14 20:34:48,026 [INFO] Daily cleanup is disabled by configuration. +2025-10-14 21:11:16,069 [INFO] Daily cleanup is disabled by configuration. +2025-10-14 21:19:58,777 [INFO] Daily cleanup is disabled by configuration. +2025-10-14 21:20:29,595 [INFO] Daily cleanup is disabled by configuration. +2025-10-15 10:28:37,193 [INFO] Daily cleanup is disabled by configuration. +2025-10-15 15:54:56,829 [INFO] Daily cleanup is disabled by configuration. +2025-10-15 15:59:16,304 [INFO] Daily cleanup is disabled by configuration. +2025-10-15 16:27:05,307 [INFO] Daily cleanup is disabled by configuration. +2025-10-15 16:29:14,882 [INFO] Daily cleanup is disabled by configuration. +2025-10-15 17:04:31,298 [INFO] Daily cleanup is disabled by configuration. +2025-10-15 17:11:28,215 [INFO] Daily cleanup is disabled by configuration. +2025-10-15 18:00:31,833 [INFO] Daily cleanup is disabled by configuration. diff --git a/meshview/templates/nodegraph.html b/meshview/templates/nodegraph.html index f71cf58..b75b466 100644 --- a/meshview/templates/nodegraph.html +++ b/meshview/templates/nodegraph.html @@ -216,6 +216,7 @@ const edges = [ { source: "{{ edge.from }}", // edge source as string target: "{{ edge.to }}", // edge target as string + type: {{ edge.type | tojson }}, originalColor: colors.edge[{{edge.type | tojson}}], lineStyle: { color: colors.edge[{{edge.type | tojson}}], @@ -228,32 +229,55 @@ const edges = [ let filteredNodes = []; let filteredEdges = []; let lastSelectedNode = null; -const nodeChannelSet = [...new Set(nodes.map(n => n.channel).filter(Boolean))]; -let channelOptions = [...nodeChannelSet].sort(); -async function fetchChannelOptionsFromAPI() { - try { - const res = await fetch('/api/channels?period_type=day&length=30'); - if (!res.ok) return []; - const data = await res.json(); - if (!data || !Array.isArray(data.channels)) return []; - return data.channels.filter(ch => typeof ch === 'string' && ch.trim().length > 0); - } catch (err) { - console.error('Channel fetch failed:', err); - return []; +const normalizeChannel = (value) => { + const trimmed = (value || '').toString().trim(); + return trimmed || 'Unknown'; +}; + +const channelByNodeId = new Map(); +nodes.forEach(n=>{ + const key = String(n.name ?? n.node_id ?? ''); + if(key){ + channelByNodeId.set(key, normalizeChannel(n.channel)); } +}); + +const tracerouteChannelCounts = new Map(); +edges.forEach(edge=>{ + if(edge.type === 'traceroute'){ + const weight = typeof edge.weight === 'number' ? edge.weight : 1; + const fromChannel = channelByNodeId.get(String(edge.source)); + const toChannel = channelByNodeId.get(String(edge.target)); + if(fromChannel){ + tracerouteChannelCounts.set(fromChannel, (tracerouteChannelCounts.get(fromChannel) || 0) + weight); + } + if(toChannel){ + tracerouteChannelCounts.set(toChannel, (tracerouteChannelCounts.get(toChannel) || 0) + weight); + } + } +}); + +let channelOptions = []; +if (tracerouteChannelCounts.size) { + channelOptions = Array.from( + [...tracerouteChannelCounts.entries()] + .filter(([_, count]) => count > 0) + .map(([channel]) => channel) + ).sort(); } -async function initializeChannelOptions() { - const fetched = await fetchChannelOptionsFromAPI(); - const merged = new Set([...nodeChannelSet, ...fetched]); - if (hasChannelSelection(selectedChannel)) { - merged.add(selectedChannel); - } - channelOptions = Array.from(merged).sort(); - if (!hasChannelSelection(selectedChannel) && channelOptions.length) { - selectedChannel = channelOptions[0]; - } +if (!channelOptions.length) { + channelOptions = Array.from(new Set(nodes.map(n=>normalizeChannel(n.channel)))).sort(); +} + +if (hasChannelSelection(selectedChannel) && channelOptions.length && !channelOptions.includes(selectedChannel)) { + channelOptions.push(selectedChannel); + channelOptions.sort(); +} + +if (!hasChannelSelection(selectedChannel) && channelOptions.length) { + selectedChannel = channelOptions[0]; } function populateChannelDropdown() { diff --git a/meshview/templates/top.html b/meshview/templates/top.html index cb72ea1..2be17a2 100644 --- a/meshview/templates/top.html +++ b/meshview/templates/top.html @@ -223,20 +223,45 @@ function updateStatsAndChart() { } // Sort table -function sortTable(n) { +function sortTable(columnIndex) { const table = document.getElementById("trafficTable"); - const rows = Array.from(table.rows).slice(1); - const header = table.rows[0].cells[n]; - const isNumeric = !isNaN(rows[0].cells[n].innerText.replace('%','')); - let sortedRows = rows.sort((a,b)=>{ - const valA = isNumeric ? parseFloat(a.cells[n].innerText.replace('%','')) : a.cells[n].innerText.toLowerCase(); - const valB = isNumeric ? parseFloat(b.cells[n].cells[n].innerText.replace('%','')) : b.cells[n].innerText.toLowerCase(); - return valA > valB ? 1 : -1; - }); - if(header.getAttribute('data-sort-direction')==='asc'){ sortedRows.reverse(); header.setAttribute('data-sort-direction','desc'); } - else header.setAttribute('data-sort-direction','asc'); const tbody = table.tBodies[0]; - sortedRows.forEach(row=>tbody.appendChild(row)); + const headerCell = table.tHead.rows[0].cells[columnIndex]; + const rows = Array.from(tbody.rows); + + if (!rows.length) return; + + const sampleText = rows[0].cells[columnIndex].innerText.trim().replace('%',''); + const isNumeric = sampleText !== '' && !isNaN(sampleText); + + const currentDirection = headerCell.getAttribute('data-sort-direction') || 'none'; + const sortAscending = currentDirection === 'desc' || currentDirection === 'none'; + + rows.sort((rowA, rowB) => { + const textA = rowA.cells[columnIndex].innerText.trim(); + const textB = rowB.cells[columnIndex].innerText.trim(); + + let valueA = isNumeric ? parseFloat(textA.replace('%','')) : textA.toLowerCase(); + let valueB = isNumeric ? parseFloat(textB.replace('%','')) : textB.toLowerCase(); + + if (isNumeric) { + valueA = isNaN(valueA) ? Number.NEGATIVE_INFINITY : valueA; + valueB = isNaN(valueB) ? Number.NEGATIVE_INFINITY : valueB; + } + + if (valueA > valueB) return 1; + if (valueA < valueB) return -1; + return 0; + }); + + if (!sortAscending) { + rows.reverse(); + } + + Array.from(table.tHead.rows[0].cells).forEach(cell => cell.removeAttribute('data-sort-direction')); + headerCell.setAttribute('data-sort-direction', sortAscending ? 'asc' : 'desc'); + + rows.forEach(row => tbody.appendChild(row)); } // Initialize