'use strict'; 'require view'; 'require rpc'; 'require form'; 'require poll'; var callGetWgInstances = rpc.declare({ object: 'luci.wireguard_status', method: 'getWgInstances' }); function timestampToStr(timestamp) { if (timestamp < 1) { return _('Never'); } var now = new Date(); var seconds = (now.getTime() / 1000) - timestamp; var ago = ''; if (seconds < 60) { ago = parseInt(seconds) + _('s ago'); } else if (seconds < 3600) { ago = parseInt(seconds / 60) + _('m ago'); } else if (seconds < 86401) { ago = parseInt(seconds / 3600) + _('h ago'); } else { ago = _('over a day ago'); } var t = new Date(timestamp * 1000); return t.toUTCString() + ' (' + ago + ')'; } function generatePeerOption(key, title, value) { return E('div', { 'class': 'cbi-value', 'style': 'padding: 0;' }, [ E('label', { 'class': 'cbi-value-title', 'style': 'font-weight: bold;' }, title), E('input', { 'class': 'cbi-input-text', 'data-name': key, 'style': 'border: none; float: left; width: 50%;', 'disabled': '', 'value': value }) ]); } function generatePeerTable(options, iconSrc) { return E('div', { 'class': 'table cbi-section-table' }, [ E('div', { 'class': 'td' }, E('img', { 'src': iconSrc, 'class': 'tunnel-icon' }) ), E('div', { 'class': 'td peer-options' }, options.filter(function (option) { return option[2] != null; }).map(function (option) { return generatePeerOption.apply(null, option); }) ) ]); } function getTunnelIcon(latestHandshake) { var img = (new Date().getTime() / 1000 - latestHandshake) < 140 ? 'tunnel' : 'tunnel_disabled'; return L.resource('icons', img + '.png'); } function generatePeerRows(peers) { var peerRows = []; peers.forEach(function (peer) { var peerData = parsePeerData(peer); var iconSrc = getTunnelIcon(peer.latest_handshake); peerRows.push(E('div', { 'class': 'tr cbi-section-table-row' }, [ E('div', { 'class': 'td peer-name', 'style': 'width: 25%; font-size: 0.9rem;' }, peer.name), E('div', { 'class': 'td', 'data-section-id': peer.name }, generatePeerTable(peerData, iconSrc) ) ])); }); return peerRows; } function parseIfaceData(iface) { return [ ['public_key', _('Public Key'), iface.public_key != '(none)' ? iface.public_key : null], ['listen_port', _('Listen Port'), iface.listen_port > 0 ? iface.listen_port : null], ['fwmark', _('Firewall Mark'), iface.fwmark != 'off' ? iface.fwmark : null] ]; } function parsePeerData(peer) { return [ ['public_key', _('Public Key'), peer.public_key], ['endpoint', _('Endpoint'), peer.endpoint == '(none)' ? null : peer.endpoint], ['allowed_ips', _('Allowed IPs'), peer.allowed_ips.length == 0 ? null : peer.allowed_ips.join('\n')], ['persistent_keepalive', _('Persistent Keepalive'), peer.persistent_keepalive == 'off' ? null : peer.persistent_keepalive + 's'], ['latest_handshake', _('Latest Handshake'), timestampToStr(peer.latest_handshake)], ['transfer_rx', _('Data Received'), '%1024mB'.format(peer.transfer_rx)], ['transfer_tx', _('Data Transmitted'), '%1024mB'.format(peer.transfer_tx)] ]; } return view.extend({ load: function () { return callGetWgInstances(); }, poll_status: function (nodes, ifaces) { Object.keys(ifaces).forEach(function (ifaceName) { var iface = ifaces[ifaceName]; var section = nodes.querySelector( '[data-section-id="%q"]'.format(ifaceName) ); parseIfaceData(iface).forEach(function (option) { if (option[2] != null) { var optionEl = section.querySelector( '[data-name="%q"]'.format(option[0]) ); var inputEl = optionEl.querySelector('input'); inputEl.value = option[2]; } }); iface.peers.forEach(function (peer) { var peerData = parsePeerData(peer); var iconSrc = getTunnelIcon(peer.latest_handshake); var peerSection = section.querySelector( '[data-section-id="%q"]'.format(peer.name) ); var iconEl = peerSection.querySelector('.tunnel-icon'); iconEl.src = iconSrc; peerData.forEach(function (option) { if (option[2]) { var inputEl = peerSection.querySelector( '[data-name="%q"]'.format(option[0]) ); inputEl.value = option[2]; } }) }); }); }, render: function (ifaces) { var m, s, o, ss; m = new form.JSONMap(ifaces, _('WireGuard Status')); m.tabbed = true; var ifaceNames = Object.keys(ifaces); for (var i = ifaceNames.length - 1; i >= 0; i--) { var ifaceName = ifaceNames[i]; var iface = ifaces[ifaceName]; s = m.section(form.TypedSection, ifaceName); s.tabbed = true; s.anonymous = true; var ifaceData = parseIfaceData(iface); ifaceData.forEach(function (option) { if (option[2] != null) { o = s.option(form.Value, option[0], option[1]); o.readonly = true; } }); o = s.option(form.SectionValue, 'peers', form.TypedSection, 'peers'); ss = o.subsection; ss.render = L.bind(function (view, section_id) { return E('div', { 'class': 'cbi-section' }, [ E('h3', _('Peers')), E('div', { 'class': 'table cbi-section-table' }, generatePeerRows(this.peers)) ]); }, iface, this); } return m.render().then(L.bind(function (m, nodes) { poll.add(L.bind(function () { return callGetWgInstances().then( L.bind(this.poll_status, this, nodes) ); }, this), 5); return nodes; }, this, m)); }, handleReset: null, handleSaveApply: null, handleSave: null });