mirror of
https://github.com/lucaspalomodevelop/opnsense-core.git
synced 2026-03-13 08:09:42 +00:00
vpn/wireguard: Change tracking of wg peer status, improve widget and diagnostics (#8337)
* vpn/wireguard: Introduce latest-handshake-age to calculate if tunnel is online in backend. Implement it in wireguard.js widget and diagnostics.volt * vpn/wireguard: expose peer-connected via API to approximate state of wireguard peers online/offline status, change status formatter to show statos of interfaces and peers, improve diagnostic grid * vpn/wireguard: Move epoch calculation from frontend to controller * vpn/wireguard: Track 3 different status instead of a boolean offline/online. Online means a handshake happened recently, Stale means a handshake happened in the past above a threshold of 300s, Offline means there was never a handshake yet. The same icons are implemented in the widget and the wireguard diagnostics page. * vpn/wireguard: Remote peer disconnected translation since this is tracked by the icon now. Add stale translation. * vpn/wireguard: Compact widget information for better readability
This commit is contained in:
parent
82b36deee3
commit
2cc36105da
@ -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;
|
||||
|
||||
@ -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 '<span class="fa fa-question-circle fa-fw" data-toggle="tooltip" title="{{ lang._('Stale') }}"></span>';
|
||||
}
|
||||
|
||||
if (
|
||||
(row.type === 'interface' && row.status === 'up') ||
|
||||
(row.type === 'peer' && row['peer-status'] === 'online')
|
||||
) {
|
||||
return '<span class="fa fa-check-circle fa-fw text-success" data-toggle="tooltip" title="{{ lang._('Online') }}"></span>';
|
||||
}
|
||||
|
||||
return '<span class="fa fa-times-circle fa-fw text-danger" data-toggle="tooltip" title="{{ lang._('Offline') }}"></span>';
|
||||
},
|
||||
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
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 @@
|
||||
<table id="grid-sessions" class="table table-condensed table-hover table-striped table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="if" data-type="string" data-width="8em">{{ lang._('Device') }}</th>
|
||||
<th data-column-id="type" data-type="string" data-width="8em" data-visible="false">{{ lang._('Type') }}</th>
|
||||
<th data-column-id="status" data-type="string" data-width="8em" >{{ lang._('Status') }}</th>
|
||||
<th data-column-id="public-key" data-type="string" data-identifier="true">{{ lang._('Public key') }}</th>
|
||||
<th data-column-id="status" data-formatter="status" data-type="string" data-width="6em" >{{ lang._('Status') }}</th>
|
||||
<th data-column-id="if" data-type="string" data-width="6em">{{ lang._('Device') }}</th>
|
||||
<th data-column-id="type" data-type="string" data-width="6em">{{ lang._('Type') }}</th>
|
||||
<th data-column-id="public-key" data-type="string" data-width="26em" data-identifier="true" data-visible="false">{{ lang._('Public key') }}</th>
|
||||
<th data-column-id="name" data-type="string">{{ lang._('Name') }}</th>
|
||||
<th data-column-id="endpoint" data-type="string">{{ lang._('Port / Endpoint') }}</th>
|
||||
<th data-column-id="latest-handshake" data-formatter="epoch" data-type="numeric">{{ lang._('Handshake') }}</th>
|
||||
<th data-column-id="latest-handshake-epoch" data-formatter="epoch" data-type="numeric" data-visible="false">{{ lang._('Handshake') }}</th>
|
||||
<th data-column-id="latest-handshake-age" data-formatter="seconds" data-type="numeric">{{ lang._('Handshake Age') }}</th>
|
||||
<th data-column-id="transfer-tx" data-formatter="bytes" data-type="numeric">{{ lang._('Sent') }}</th>
|
||||
<th data-column-id="transfer-rx" data-formatter="bytes" data-type="numeric">{{ lang._('Received') }}</th>
|
||||
</tr>
|
||||
|
||||
@ -288,9 +288,8 @@
|
||||
<notunnels>No tunnels connected</notunnels>
|
||||
<online>Online</online>
|
||||
<offline>Offline</offline>
|
||||
<total>Tunnels</total>
|
||||
<stale>Stale</stale>
|
||||
<notavailable>N/A</notavailable>
|
||||
<disconnected>Peer disconnected</disconnected>
|
||||
</translations>
|
||||
</Wireguard>
|
||||
<services>
|
||||
|
||||
@ -69,7 +69,7 @@ export default class Wireguard extends BaseTableWidget {
|
||||
}
|
||||
|
||||
displayError(message) {
|
||||
$('#wgTunnelTable'). empty().append(
|
||||
$('#wgTunnelTable').empty().append(
|
||||
$(`<div class="error-message"><a href="/ui/wireguard/general">${message}</a></div>`)
|
||||
);
|
||||
}
|
||||
@ -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 = `
|
||||
<div>
|
||||
<span>${this.translations.total}: ${tunnels.length} | ${this.translations.online}: ${onlineCount} | ${this.translations.offline}: ${offlineCount}</span>
|
||||
<span>
|
||||
${this.translations.online}: ${onlineCount} |
|
||||
${this.translations.stale}: ${staleCount} |
|
||||
${this.translations.offline}: ${offlineCount}
|
||||
</span>
|
||||
</div>`;
|
||||
|
||||
super.updateTable('wgTunnelTable', [[summaryRow, '']], 'wg-summary');
|
||||
@ -110,23 +142,23 @@ export default class Wireguard extends BaseTableWidget {
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<i class="fa ${tunnel.statusIcon} wireguard-interface" style="cursor: pointer;"
|
||||
data-toggle="tooltip" title="${tunnel.connected ? this.translations.online : this.translations.offline}">
|
||||
data-toggle="tooltip" title="${tunnel.statusTooltip}">
|
||||
</i>
|
||||
|
||||
<span><b>${tunnel.ifname}</b></span>
|
||||
<a href="/ui/wireguard/general#peers&search=${encodeURIComponent(tunnel.name)}" target="_blank" rel="noopener noreferrer">
|
||||
${tunnel.if} | ${tunnel.name}
|
||||
</a>
|
||||
</div>
|
||||
</div>`;
|
||||
let row = `
|
||||
<div>
|
||||
<span>
|
||||
<a href="/ui/wireguard/general#peers&search=${encodeURIComponent(tunnel.name)}" target="_blank" rel="noopener noreferrer">
|
||||
${tunnel.name}
|
||||
</a> | ${tunnel.allowed_ips}
|
||||
${tunnel.allowed_ips}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
${tunnel.latest_handshake_fmt
|
||||
? `<span>${tunnel.latest_handshake_fmt}</span>
|
||||
${tunnel.latest_handshake_epoch
|
||||
? `<span>${tunnel.latest_handshake_epoch}</span>
|
||||
<div style="padding-bottom: 10px;">
|
||||
<i class="fa fa-arrow-down" style="font-size: 13px;"></i>
|
||||
${tunnel.rx}
|
||||
@ -134,7 +166,7 @@ export default class Wireguard extends BaseTableWidget {
|
||||
<i class="fa fa-arrow-up" style="font-size: 13px;"></i>
|
||||
${tunnel.tx}
|
||||
</div>`
|
||||
: `<span>${this.translations.disconnected}</span>`}
|
||||
: ''}
|
||||
</div>`;
|
||||
|
||||
// Update the HTML table with the sorted rows
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user