diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Wireguard/Api/ServiceController.php b/src/opnsense/mvc/app/controllers/OPNsense/Wireguard/Api/ServiceController.php index 0d11d5722..7a97615be 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Wireguard/Api/ServiceController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Wireguard/Api/ServiceController.php @@ -95,6 +95,26 @@ class ServiceController extends ApiMutableServiceControllerBase $record['name'] = $key_descriptions[$key]; } } + + if (!empty($record['latest-handshake'])) { + $record['latest-handshake-age'] = time() - (int)$record['latest-handshake']; + $record['latest-handshake-epoch'] = date('Y-m-d H:i:s', (int)$record['latest-handshake']); + } else { + $record['latest-handshake-age'] = null; + $record['latest-handshake-epoch'] = null; + } + + // Peer is considered online if handshake was within 300s, wg handshakes approx every 120s. + if ($record['type'] === 'peer' && !is_null($record['latest-handshake-age'])) { + if ($record['latest-handshake-age'] <= 300) { + $record['peer-status'] = 'online'; + } elseif ($record['latest-handshake-age'] > 300) { + $record['peer-status'] = 'stale'; + } + } else { + $record['peer-status'] = 'offline'; + } + $record['ifname'] = $ifnames[$record['if']]; } $filter_funct = null; diff --git a/src/opnsense/mvc/app/views/OPNsense/Wireguard/diagnostics.volt b/src/opnsense/mvc/app/views/OPNsense/Wireguard/diagnostics.volt index af3cf7631..35e885e93 100644 --- a/src/opnsense/mvc/app/views/OPNsense/Wireguard/diagnostics.volt +++ b/src/opnsense/mvc/app/views/OPNsense/Wireguard/diagnostics.volt @@ -41,13 +41,36 @@ return row[column.id]; }, epoch: function(column, row) { - if (row[column.id]) { - return moment.unix(row[column.id]).local().format('YYYY-MM-DD HH:mm:ss'); + if (row[column.id] !== null) { + return row[column.id] } else { return ''; } + }, + seconds: function(column, row) { + if (row[column.id] !== null) { + return row[column.id] + "s"; + } else { + return ''; + } + }, + status: function(column, row) { + if (row.type === 'peer' && row['peer-status'] === 'stale') { + return ''; + } + + if ( + (row.type === 'interface' && row.status === 'up') || + (row.type === 'peer' && row['peer-status'] === 'online') + ) { + return ''; + } + + return ''; + }, + + - } }, requestHandler: function(request){ if ( $('#type_filter').val().length > 0) { @@ -62,6 +85,10 @@ $('#grid-sessions').bootgrid('reload'); }); + $("#grid-sessions").on('loaded.rs.jquery.bootgrid', function() { + $('[data-toggle="tooltip"]').tooltip(); + }); + $("#type_filter_container").detach().prependTo('#grid-sessions-header > .row > .actionBar > .actions'); }); @@ -80,13 +107,14 @@ - - - - + + + + - + + diff --git a/src/opnsense/www/js/widgets/Metadata/Core.xml b/src/opnsense/www/js/widgets/Metadata/Core.xml index 87ca4695d..b5a201be5 100644 --- a/src/opnsense/www/js/widgets/Metadata/Core.xml +++ b/src/opnsense/www/js/widgets/Metadata/Core.xml @@ -288,9 +288,8 @@ No tunnels connectedOnlineOffline - Tunnels + StaleN/A - Peer disconnected diff --git a/src/opnsense/www/js/widgets/Wireguard.js b/src/opnsense/www/js/widgets/Wireguard.js index 57f251aeb..71af82fa3 100644 --- a/src/opnsense/www/js/widgets/Wireguard.js +++ b/src/opnsense/www/js/widgets/Wireguard.js @@ -69,7 +69,7 @@ export default class Wireguard extends BaseTableWidget { } displayError(message) { - $('#wgTunnelTable'). empty().append( + $('#wgTunnelTable').empty().append( $(`
${message}
`) ); } @@ -77,29 +77,61 @@ export default class Wireguard extends BaseTableWidget { processTunnels(newTunnels) { $('.wireguard-interface').tooltip('hide'); - let now = moment().unix(); // Current time in seconds - let tunnels = newTunnels.filter(row => row.type == 'peer').map(row => ({ - ifname: row.ifname ? row.if + ' (' + row.ifname + ') ' : row.if, - name: row.name, - allowed_ips: row['allowed-ips'] || this.translations.notavailable, - rx: row['transfer-rx'] ? this._formatBytes(row['transfer-rx']) : this.translations.notavailable, - tx: row['transfer-tx'] ? this._formatBytes(row['transfer-tx']) : this.translations.notavailable, - latest_handshake: row['latest-handshake'], // No fallback since we handle if 0 - latest_handshake_fmt: row['latest-handshake'] ? moment.unix(row['latest-handshake']).local().format('YYYY-MM-DD HH:mm:ss') : null, - connected: row['latest-handshake'] && (now - row['latest-handshake']) <= 180, // Considered online if last handshake was within 3 minutes - statusIcon: row['latest-handshake'] && (now - row['latest-handshake']) <= 180 ? 'fa-exchange text-success' : 'fa-exchange text-danger', - publicKey: row['public-key'], - uniqueId: row.if + row['public-key'] - })); + let tunnels = newTunnels + .filter(row => row.type == 'peer') + .map(row => ({ + if: row.if, - tunnels.sort((a, b) => a.connected === b.connected ? 0 : a.connected ? -1 : 1); + name: row.name, + allowed_ips: row['allowed-ips'] || this.translations.notavailable, - let onlineCount = tunnels.filter(tunnel => tunnel.connected).length; - let offlineCount = tunnels.length - onlineCount; + rx: row['transfer-rx'] + ? this._formatBytes(row['transfer-rx']) + : this.translations.notavailable, + + tx: row['transfer-tx'] + ? this._formatBytes(row['transfer-tx']) + : this.translations.notavailable, + + // No fallback since we handle if null + latest_handshake_epoch: row['latest-handshake-epoch'], + + peerStatus: row['peer-status'], + + statusIcon: row['peer-status'] === 'online' + ? 'fa-check-circle fa-fw text-success' + : row['peer-status'] === 'stale' + ? 'fa-question-circle fa-fw' + : 'fa-times-circle fa-fw text-danger', + + statusTooltip: row['peer-status'] === 'online' + ? this.translations.online + : row['peer-status'] === 'stale' + ? this.translations.stale + : this.translations.offline, + + publicKey: row['public-key'], + uniqueId: row.if + row['public-key'] + })); + + tunnels.sort((a, b) => { + if (a.peerStatus === b.peerStatus) return 0; + if (a.peerStatus === 'online') return -1; + if (a.peerStatus === 'stale' && b.peerStatus !== 'online') return -1; + return 1; + }); + + let onlineCount = tunnels.filter(tunnel => tunnel.peerStatus === 'online').length; + let staleCount = tunnels.filter(tunnel => tunnel.peerStatus === 'stale').length; + let offlineCount = tunnels.length - onlineCount - staleCount; let summaryRow = `
- ${this.translations.total}: ${tunnels.length} | ${this.translations.online}: ${onlineCount} | ${this.translations.offline}: ${offlineCount} + + ${this.translations.online}: ${onlineCount} | + ${this.translations.stale}: ${staleCount} | + ${this.translations.offline}: ${offlineCount} +
`; super.updateTable('wgTunnelTable', [[summaryRow, '']], 'wg-summary'); @@ -110,23 +142,23 @@ export default class Wireguard extends BaseTableWidget {
+ data-toggle="tooltip" title="${tunnel.statusTooltip}">   - ${tunnel.ifname} + + ${tunnel.if} | ${tunnel.name} +
`; let row = `
- - ${tunnel.name} - | ${tunnel.allowed_ips} + ${tunnel.allowed_ips}
- ${tunnel.latest_handshake_fmt - ? `${tunnel.latest_handshake_fmt} + ${tunnel.latest_handshake_epoch + ? `${tunnel.latest_handshake_epoch}
${tunnel.rx} @@ -134,7 +166,7 @@ export default class Wireguard extends BaseTableWidget { ${tunnel.tx}
` - : `${this.translations.disconnected}`} + : ''}
`; // Update the HTML table with the sorted rows
{{ lang._('Device') }}{{ lang._('Type') }}{{ lang._('Status') }}{{ lang._('Public key') }}{{ lang._('Status') }}{{ lang._('Device') }}{{ lang._('Type') }}{{ lang._('Public key') }} {{ lang._('Name') }} {{ lang._('Port / Endpoint') }}{{ lang._('Handshake') }}{{ lang._('Handshake') }}{{ lang._('Handshake Age') }} {{ lang._('Sent') }} {{ lang._('Received') }}