luci-proto-wireguard: merge status page functionality

Merge status page functionality from the separate `luci-app-wireguard`
package into the `luci-proto-wirguard` protocol backend.

Also rewrite the status page markup to be more compact while we're at it.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich
2022-11-22 10:27:15 +01:00
parent 90a2b1eaeb
commit 6e6fce3eb4
42 changed files with 220 additions and 4401 deletions

View File

@@ -10,6 +10,8 @@ LUCI_TITLE:=Support for WireGuard VPN
LUCI_DEPENDS:=+wireguard-tools +ucode
LUCI_PKGARCH:=all
PKG_PROVIDES:=luci-app-wireguard
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@@ -0,0 +1,212 @@
'use strict';
'require view';
'require rpc';
'require poll';
'require dom';
'require ui';
var callGetWgInstances = rpc.declare({
object: 'luci.wireguard',
method: 'getWgInstances'
});
function timestampToStr(timestamp) {
if (timestamp < 1)
return _('Never', 'No WireGuard peer handshake yet');
var seconds = (Date.now() / 1000) - timestamp;
var ago;
if (seconds < 60)
ago = _('%ds ago').format(seconds);
else if (seconds < 3600)
ago = _('%dm ago').format(seconds / 60);
else if (seconds < 86401)
ago = _('%dh ago').format(seconds / 3600);
else
ago = _('over a day ago');
return (new Date(timestamp * 1000)).toUTCString() + ' (' + ago + ')';
}
/*
{
"jow": {
"public_key": "o4iLoC1pl8+vEUshV7eUyKrryo7cr0WJkjS/Dlixxy8=",
"name": "jow",
"fwmark": "off",
"listen_port": "51821",
"peers": [
{
"endpoint": "45.13.105.118:51821",
"public_key": "672KLy/R4miKXdvw2unuf9jQzVEmNnqen5kF+zVjMX0=",
"name": "m300",
"latest_handshake": "1668680574",
"persistent_keepalive": "off",
"allowed_ips": [
"192.168.220.10/32"
],
"transfer_tx": "308",
"transfer_rx": "220"
},
{
"endpoint": "171.22.3.161:51821",
"public_key": "yblNj1s41F8m1MdXhxD2U+Aew6ZR6Miy8OcNK/fkAks=",
"name": "ac2",
"latest_handshake": "0",
"persistent_keepalive": "off",
"allowed_ips": [
"192.168.220.11/32"
],
"transfer_tx": "2960",
"transfer_rx": "0"
}
]
}
}
*/
function handleInterfaceDetails(iface) {
ui.showModal(_('Instance Details'), [
ui.itemlist(E([]), [
_('Name'), iface.name,
_('Public Key'), E('code', [ iface.public_key ]),
_('Listen Port'), iface.listen_port,
_('Firewall Mark'), iface.fwmark != 'off' ? iface.fwmark : E('em', _('none'))
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn cbi-button',
'click': ui.hideModal
}, [ _('Dismiss') ])
])
]);
}
function handlePeerDetails(peer) {
ui.showModal(_('Peer Details'), [
ui.itemlist(E([]), [
_('Description'), peer.name,
_('Public Key'), E('code', [ peer.public_key ]),
_('Endpoint'), peer.endpoint,
_('Allowed IPs'), (Array.isArray(peer.allowed_ips) && peer.allowed_ips.length) ? peer.allowed_ips.join(', ') : E('em', _('none')),
_('Received Data'), '%1024mB'.format(peer.transfer_rx),
_('Transmitted Data'), '%1024mB'.format(peer.transfer_tx),
_('Latest Handshake'), timestampToStr(+peer.latest_handshake),
_('Keep-Alive'), (peer.persistent_keepalive != 'off') ? _('every %ds', 'WireGuard keep alive interval').format(+peer.persistent_keepalive) : E('em', _('none')),
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn cbi-button',
'click': ui.hideModal
}, [ _('Dismiss') ])
])
]);
}
function renderPeerTable(instanceName, peers) {
var t = new L.ui.Table(
[
_('Peer'),
_('Endpoint'),
_('Data Received'),
_('Data Transmitted'),
_('Latest Handshake')
],
{
id: 'peers-' + instanceName
},
E('em', [
_('No peers connected')
])
);
t.update(peers.map(function(peer) {
return [
[
peer.name || '',
E('div', {
'style': 'cursor:pointer',
'click': ui.createHandlerFn(this, handlePeerDetails, peer)
}, [
E('p', [
peer.name ? E('span', [ peer.name ]) : E('em', [ _('Untitled peer') ])
]),
E('span', {
'class': 'ifacebadge hide-sm',
'data-tooltip': _('Public key: %h', 'Tooltip displaying full WireGuard peer public key').format(peer.public_key)
}, [
E('code', [ peer.public_key.replace(/^(.{5}).+(.{6})$/, '$1…$2') ])
])
])
],
peer.endpoint,
[ +peer.transfer_rx, '%1024mB'.format(+peer.transfer_rx) ],
[ +peer.transfer_tx, '%1024mB'.format(+peer.transfer_tx) ],
[ +peer.latest_handshake, timestampToStr(+peer.latest_handshake) ]
];
}));
return t.render();
}
return view.extend({
renderIfaces: function(ifaces) {
var res = [
E('h2', [ _('WireGuard Status') ])
];
for (var instanceName in ifaces) {
res.push(
E('h3', [ _('Instance "%h"', 'WireGuard instance heading').format(instanceName) ]),
E('p', {
'style': 'cursor:pointer',
'click': ui.createHandlerFn(this, handleInterfaceDetails, ifaces[instanceName])
}, [
E('span', { 'class': 'ifacebadge' }, [
E('img', { 'src': L.resource('icons', 'tunnel.png') }),
'\xa0',
instanceName
]),
E('span', { 'style': 'opacity:.8' }, [
' · ',
_('Port %d', 'WireGuard listen port').format(ifaces[instanceName].listen_port),
' · ',
E('code', { 'click': '' }, [ ifaces[instanceName].public_key ])
])
]),
renderPeerTable(instanceName, ifaces[instanceName].peers)
);
}
if (res.length == 1)
res.push(E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [
E('em', [ _('No WireGuard interfaces configured.') ])
]));
return E([], res);
},
render: function() {
poll.add(L.bind(function () {
return callGetWgInstances().then(L.bind(function(ifaces) {
dom.content(
document.querySelector('#view'),
this.renderIfaces(ifaces)
);
}, this));
}, this), 5);
return E([], [
E('h2', [ _('WireGuard Status') ]),
E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [
E('em', [ _('Loading data…') ])
])
]);
},
handleReset: null,
handleSaveApply: null,
handleSave: null
});

View File

@@ -0,0 +1,14 @@
{
"admin/status/wireguard": {
"title": "WireGuard",
"order": 92,
"action": {
"type": "view",
"path": "wireguard/status"
},
"depends": {
"acl": [ "luci-proto-wireguard" ],
"uci": { "network": true }
}
}
}

View File

@@ -5,6 +5,11 @@
"file": {
"/usr/bin/qrencode --inline --8bit --type=SVG --output=- -- *": [ "exec" ]
},
"ubus": {
"luci.wireguard": [
"getWgInstances"
]
},
"uci": [ "ddns", "system" ]
},
"write": {