diff options
Diffstat (limited to 'applications/luci-app-dawn/htdocs')
3 files changed, 292 insertions, 0 deletions
diff --git a/applications/luci-app-dawn/htdocs/luci-static/resources/dawn/dawn-common.js b/applications/luci-app-dawn/htdocs/luci-static/resources/dawn/dawn-common.js new file mode 100644 index 0000000000..9dec2c6ec1 --- /dev/null +++ b/applications/luci-app-dawn/htdocs/luci-static/resources/dawn/dawn-common.js @@ -0,0 +1,90 @@ +'use strict'; +'require baseclass'; +'require rpc'; + +let callDawnGetNetwork, callDawnGetHearingMap, callHostHints; + +callDawnGetNetwork = rpc.declare({ + object: 'dawn', + method: 'get_network', + expect: { } +}); + +callDawnGetHearingMap = rpc.declare({ + object: 'dawn', + method: 'get_hearing_map', + expect: { } +}); + +callHostHints = rpc.declare({ + object: 'luci-rpc', + method: 'getHostHints', + expect: { } +}); + +function isDawnRPCAvailable() { + return rpc.list("dawn").then(function(signatures) { + return 'dawn' in signatures && 'get_network' in signatures.dawn && 'get_hearing_map' in signatures.dawn; + }); +} + +function getAvailableText(available) { + return ( available ? _('Available') : _('Not available') ); +} + +function getYesText(yes) { + return ( yes ? _('Yes') : _('No') ); +} + +function getChannelFromFrequency(freq) { + if (freq <= 2400) { + return 0; + } + else if (freq == 2484) { + return 14; + } + else if (freq < 2484) { + return (freq - 2407) / 5; + } + else if (freq >= 4910 && freq <= 4980) { + return (freq - 4000) / 5; + } + else if (freq <= 45000) { + return (freq - 5000) / 5; + } + else if (freq >= 58320 && freq <= 64800) { + return (freq - 56160) / 2160; + } + else { + return 0; + } +} + +function getFormattedNumber(num, decimals, divider = 1) { + return (num/divider).toFixed(decimals); +} + +function getHostnameFromMAC(hosthints, mac) { + return ( hosthints[mac] && hosthints[mac].name ? hosthints[mac].name + ' (' + mac + ')' : mac ); +} + +function getDawnServiceNotRunningErrorMessage() { + return E('div', { 'class': 'alert-message fade-in warning' }, [ + E('h4', _('DAWN service unavailable')), + E('p', _('Unable to query the DAWN service via ubus, the service appears to be stopped.')), + E('a', { 'href': L.url('admin/system/startup') }, _('Check Startup services')) + ]); +} + +return L.Class.extend({ + callDawnGetNetwork: callDawnGetNetwork, + callDawnGetHearingMap: callDawnGetHearingMap, + callHostHints: callHostHints, + isDawnRPCAvailable: isDawnRPCAvailable, + getAvailableText: getAvailableText, + getYesText: getYesText, + getChannelFromFrequency: getChannelFromFrequency, + getFormattedNumber: getFormattedNumber, + getHostnameFromMAC: getHostnameFromMAC, + getDawnServiceNotRunningErrorMessage: getDawnServiceNotRunningErrorMessage +}); diff --git a/applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/hearing_map.js b/applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/hearing_map.js new file mode 100644 index 0000000000..8b93fbfd6d --- /dev/null +++ b/applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/hearing_map.js @@ -0,0 +1,103 @@ +'use strict'; +'require uci'; +'require view'; +'require dawn.dawn-common as dawn'; + +return view.extend({ + handleSaveApply: null, + handleSave: null, + handleReset: null, + + load: function() { + return Promise.all([ + dawn.isDawnRPCAvailable().then(function(isAvailable) { + return ( isAvailable ? dawn.callDawnGetHearingMap() : null ) + }), + dawn.isDawnRPCAvailable().then(function(isAvailable) { + return ( isAvailable ? dawn.callDawnGetNetwork() : null ) + }), + dawn.callHostHints() + ]); + }, + + render: function(data) { + + const dawnHearingMapData = data[0]; + const dawnNetworkData = data[1]; + const hostHintsData = data[2]; + + let accessPointsHintsData = {}; + let connectedClients = {}; + for (let network in dawnNetworkData) { + connectedClients[network] = []; + let aps = Object.entries(dawnNetworkData[network]).map(function(ap) { + accessPointsHintsData[ap[0]] = {name: ap[1].hostname}; + let clientData = Object.entries(ap[1]); + for (let i = 0; i < clientData.length; i++) { + if (typeof clientData[i][1] === 'object') { + connectedClients[network].push(clientData[i][0]); + } + } + }); + } + + if (!dawnHearingMapData || !dawnNetworkData) { + return dawn.getDawnServiceNotRunningErrorMessage(); + } + + const body = E([ + E('h2', _('Hearing Map')) + ]); + + for (let network in dawnHearingMapData) { + + body.appendChild( + E('h3', 'SSID: ' + network) + ); + + let hearing_map_table = E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, _('Client')), + E('th', { 'class': 'th' }, _('Access Point')), + E('th', { 'class': 'th' }, _('Frequency')), + E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('High Throughput') }, [ _('HT') ])), + E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Very High Throughput') }, [ _('VHT') ])), + E('th', { 'class': 'th' }, _('Signal')), + E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Received Channel Power Indication') }, [ _('RCPI') ])), + E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Received Signal to Noise Indicator') }, [ _('RSNI') ])), + E('th', { 'class': 'th' }, _('Channel Utilization')), + E('th', { 'class': 'th' }, _('Connected to Network')), + E('th', { 'class': 'th' }, _('Score')) + ]) + ]); + + let clients = Object.entries(dawnHearingMapData[network]).map(function(client) { + + return Object.entries(client[1]).map(function(ap) { + + if (ap[1].freq != 0) { + return [ + dawn.getHostnameFromMAC(hostHintsData, client[0]), + dawn.getHostnameFromMAC(accessPointsHintsData, ap[0]), + dawn.getFormattedNumber(ap[1].freq, 3, 1000) + ' GHz (' + _('Channel') + ': ' + dawn.getChannelFromFrequency(ap[1].freq) + ')', + dawn.getAvailableText(ap[1].ht_capabilities && ap[1].ht_support), + dawn.getAvailableText(ap[1].vht_capabilities && ap[1].vht_support), + ap[1].signal, + ap[1].rcpi, + ap[1].rsni, + dawn.getFormattedNumber(ap[1].channel_utilization, 2, 2.55) + '%', + dawn.getYesText(connectedClients[network].includes(client[0])), + ap[1].score + ] + } + }) + + }).flat(); + + cbi_update_table(hearing_map_table, clients, E('em', _('No clients connected.'))); + + body.appendChild(hearing_map_table); + } + return body; + } +}); diff --git a/applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/network_overview.js b/applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/network_overview.js new file mode 100644 index 0000000000..a605858eca --- /dev/null +++ b/applications/luci-app-dawn/htdocs/luci-static/resources/view/dawn/network_overview.js @@ -0,0 +1,99 @@ +'use strict'; +'require uci'; +'require view'; +'require dawn.dawn-common as dawn'; + +return view.extend({ + handleSaveApply: null, + handleSave: null, + handleReset: null, + + load: function() { + return Promise.all([ + dawn.isDawnRPCAvailable().then(function(isAvailable) { + return ( isAvailable ? dawn.callDawnGetNetwork() : null ); + }), + dawn.callHostHints() + ]); + }, + + render: function(data) { + + const dawnNetworkData = data[0]; + const hostHintsData = data[1]; + + if (!dawnNetworkData) { + return dawn.getDawnServiceNotRunningErrorMessage(); + } + + const body = E([ + E('h2', _('Network Overview')) + ]); + + let client_table = {}; + + for (let network in dawnNetworkData) { + + body.appendChild( + E('h3', 'SSID: ' + network) + ); + + let ap_table = E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th left cbi-section-actions' }, _('Access Point')), + E('th', { 'class': 'th left cbi-section-actions' }, _('Interface')), + E('th', { 'class': 'th left cbi-section-actions' }, _('MAC')), + E('th', { 'class': 'th left cbi-section-actions' }, _('Utilization')), + E('th', { 'class': 'th left cbi-section-actions' }, _('Frequency')), + E('th', { 'class': 'th left cbi-section-actions' }, _('Stations Connected')), + E('th', { 'class': 'th left cbi-section-actions' }, E('span', { 'data-tooltip': _('High Throughput') }, [ _('HT') ])), + E('th', { 'class': 'th left cbi-section-actions' }, E('span', { 'data-tooltip': _('Very High Throughput') }, [ _('VHT') ])), + E('th', { 'class': 'th center cbi-section-actions' }, _('Clients')), + ]) + ]); + + let aps = Object.entries(dawnNetworkData[network]).map(function(ap) { + client_table[ap[0]] = E('table', { 'class': 'table cbi-section-table', 'style': 'display: table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, _('Client')), + E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('High Throughput') }, [ _('HT') ])), + E('th', { 'class': 'th' }, E('span', { 'data-tooltip': _('Very High Throughput') }, [ _('VHT') ])), + E('th', { 'class': 'th' }, _('Signal')) + ]) + ]); + + let clients = []; + let clientData = Object.entries(ap[1]); + for (let i = 0; i < clientData.length; i++) { + if (typeof clientData[i][1] === 'object') { + clients.push([ + dawn.getHostnameFromMAC(hostHintsData ,clientData[i][0]), + dawn.getAvailableText(clientData[i][1].ht), + dawn.getAvailableText(clientData[i][1].vht), + clientData[i][1].signal + ]); + } + } + + cbi_update_table(client_table[ap[0]], clients, E('em', _('No clients connected.'))); + + return [ + ap[1].hostname, + ap[1].iface, + ap[0], + dawn.getFormattedNumber(ap[1].channel_utilization, 2, 2.55) + '%', + dawn.getFormattedNumber(ap[1].freq, 3, 1000) + ' GHz (' + _('Channel') + ': ' + dawn.getChannelFromFrequency(ap[1].freq) + ')', + ap[1].num_sta, + dawn.getAvailableText(ap[1].ht_support), + dawn.getAvailableText(ap[1].vht_support), + ap[1].num_sta > 0 ? client_table[ap[0]] : E('em', { 'style': 'display: inline' }, _('No clients connected.')) + ] + }); + + cbi_update_table(ap_table, aps, E('em', _('No access points available.'))); + + body.appendChild(ap_table); + } + return body; + } +}); |