From d7d168cb57c9b0a62857f7622c842cd95e3df941 Mon Sep 17 00:00:00 2001 From: remotetohome Date: Wed, 4 Feb 2026 23:44:15 -0600 Subject: [PATCH 1/2] 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 }}
From 768ca4b5abe23e46841b74117546609029ba98d5 Mon Sep 17 00:00:00 2001 From: remotetohome <178868468+remotetohome@users.noreply.github.com> Date: Thu, 5 Feb 2026 01:05:33 -0600 Subject: [PATCH 2/2] Add fork notice to README with feature summary and Docker image --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 74c446e..4be1d4c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,16 @@ # wireguard-ui +> **Fork Notice:** This is a fork of [ngoduykhanh/wireguard-ui](https://github.com/ngoduykhanh/wireguard-ui) with the following enhancements: +> +> - **Sortable columns on the Status page** - Click any column header to sort (descending → ascending → reset) +> - Supports sorting by: Name, Email, Received/Transmitted bytes, Connected status, Last Handshake, and more +> - Reset Sort button to restore original order +> +> A [pull request](https://github.com/ngoduykhanh/wireguard-ui/pull/685) has been submitted to merge these changes upstream. +> +> **Docker image:** `ghcr.io/remotetohome-io/wireguard-ui:sortable-columns` + A web user interface to manage your WireGuard setup. ## Features