From 83a71eba0d9112cbe4d20073d58ee136117a4c17 Mon Sep 17 00:00:00 2001 From: remotetohome Date: Wed, 4 Feb 2026 23:44:15 -0600 Subject: [PATCH] Status page: add sortable columns with reset functionality All columns are now sortable by clicking headers: - Text columns sort alphabetically - Numeric columns (Received/Transmitted) sort by byte value - Connected column sorts by boolean state - Last Handshake sorts by date/time Click a column to sort descending, click again for ascending, click a third time to reset. Reset Sort button also available. --- templates/status.html | 190 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 157 insertions(+), 33 deletions(-) diff --git a/templates/status.html b/templates/status.html index a9b770b..aff7643 100644 --- a/templates/status.html +++ b/templates/status.html @@ -27,6 +27,124 @@ Connected Peers return parseFloat(temporal.toFixed(2)) + units[pow]+"B" } + + // Table sorting functionality + let currentSortColumn = null; + let currentSortDirection = null; + const originalRowOrders = new Map(); // Store original order per table + + function getSortValue(cell, sortType) { + switch (sortType) { + case 'number': + return parseInt(cell.getAttribute('data-value') || cell.textContent || '0', 10); + case 'bytes': + return parseInt(cell.getAttribute('data-value') || '0', 10); + case 'boolean': + return cell.getAttribute('data-value') === '1' ? 1 : 0; + case 'date': + return new Date(cell.getAttribute('data-value') || 0).getTime(); + case 'text': + default: + return (cell.textContent || '').toLowerCase().trim(); + } + } + + function sortTableByColumn(table, columnIndex, direction, sortType) { + const tbody = table.querySelector('tbody'); + const rows = Array.from(tbody.querySelectorAll('tr')); + + rows.sort((a, b) => { + const aValue = getSortValue(a.cells[columnIndex], sortType); + const bValue = getSortValue(b.cells[columnIndex], sortType); + + let comparison = 0; + if (sortType === 'text') { + comparison = aValue.localeCompare(bValue); + } else { + comparison = aValue - bValue; + } + + return direction === 'asc' ? comparison : -comparison; + }); + + rows.forEach(row => tbody.appendChild(row)); + } + + function resetTableSort(table) { + const tbody = table.querySelector('tbody'); + const originalOrder = originalRowOrders.get(table); + if (originalOrder) { + originalOrder.forEach(row => tbody.appendChild(row)); + } + currentSortColumn = null; + currentSortDirection = null; + + // Clear all sort indicators + table.querySelectorAll('th[data-sort-column]').forEach(header => { + header.innerHTML = header.getAttribute('data-original-text'); + }); + } + + function handleSortClick(event) { + const th = event.currentTarget; + const table = th.closest('table'); + const columnIndex = parseInt(th.getAttribute('data-sort-column'), 10); + const sortType = th.getAttribute('data-sort-type') || 'text'; + + // Determine new sort direction + if (currentSortColumn === columnIndex) { + // Cycle: desc -> asc -> reset + if (currentSortDirection === 'desc') { + currentSortDirection = 'asc'; + } else { + // Reset to original order + resetTableSort(table); + return; + } + } else { + currentSortColumn = columnIndex; + currentSortDirection = 'desc'; + } + + // Update all sortable headers in this table + table.querySelectorAll('th[data-sort-column]').forEach(header => { + const idx = parseInt(header.getAttribute('data-sort-column'), 10); + const text = header.getAttribute('data-original-text'); + if (idx === columnIndex) { + header.innerHTML = text + ' ' + (currentSortDirection === 'asc' ? '↑' : '↓'); + } else { + header.innerHTML = text; + } + }); + + sortTableByColumn(table, columnIndex, currentSortDirection, sortType); + } + + function handleResetClick(event) { + const button = event.currentTarget; + const table = button.closest('.table-container').querySelector('table'); + resetTableSort(table); + } + + document.addEventListener('DOMContentLoaded', function() { + // Store original row order for each table + document.querySelectorAll('table.table').forEach(table => { + const tbody = table.querySelector('tbody'); + originalRowOrders.set(table, Array.from(tbody.querySelectorAll('tr'))); + }); + + // Setup sortable headers + document.querySelectorAll('th[data-sort-column]').forEach(th => { + th.style.cursor = 'pointer'; + th.setAttribute('data-original-text', th.textContent); + th.addEventListener('click', handleSortClick); + }); + + // Setup reset buttons + document.querySelectorAll('.reset-sort-btn').forEach(btn => { + btn.addEventListener('click', handleResetClick); + }); + });
@@ -34,39 +152,45 @@ Connected Peers {{ end}} {{ range $dev := .devices }} - - - - - - - - - - - - - - - - - - {{ range $idx, $peer := $dev.Peers }} - - - - - - - - - - - - - {{ end }} - -
List of connected peers for device with name {{ $dev.Name }}
#NameEmailAllocated IPsEndpointPublic KeyReceivedTransmittedConnected (Approximation)Last Handshake
{{ $idx }}{{ $peer.Name }}{{ $peer.Email }}{{ $peer.AllocatedIP }}{{ $peer.Endpoint }}{{ $peer.PublicKey }}{{ if $peer.Connected }}✓{{end}}{{ $peer.LastHandshakeTime.Format "2006-01-02 15:04:05 MST" }}
+
+
+ + Click column headers to sort. Click sorted column again to reverse, then again to reset. +
+ + + + + + + + + + + + + + + + + + {{ range $idx, $peer := $dev.Peers }} + + + + + + + + + + + + + {{ end }} + +
List of connected peers for device with name {{ $dev.Name }}
#NameEmailAllocated IPsEndpointPublic KeyReceivedTransmittedConnectedLast Handshake
{{ $idx }}{{ $peer.Name }}{{ $peer.Email }}{{ $peer.AllocatedIP }}{{ $peer.Endpoint }}{{ $peer.PublicKey }}{{ if $peer.Connected }}✓{{end}}{{ $peer.LastHandshakeTime.Format "2006-01-02 15:04:05 MST" }}
+
{{ end }}