diff options
author | Jo-Philipp Wich <jo@mein.io> | 2019-09-04 17:26:38 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2019-09-10 15:28:16 +0200 |
commit | 963b7636b46fd1b9ae47e2317ef491ce1b0ecfd5 (patch) | |
tree | 63c6f7ba20e2b88a50b2cf5656906f344fc036a5 /modules | |
parent | dab0a11b7357e5a609906e43fc5699b73ee11dfb (diff) |
luci-mod-network: switch to client side wifi configuration pages
Rewrite the wireless network management views in client side JS using ubus
rpc calls for the router communication.
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'modules')
11 files changed, 1924 insertions, 2116 deletions
diff --git a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json index 57e0ae384..d05ba841e 100644 --- a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json +++ b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json @@ -26,11 +26,10 @@ }, "ubus": { "file": [ "list", "stat" ], - "iwinfo": [ "assoclist" ], + "iwinfo": [ "assoclist", "freqlist", "txpowerlist", "countrylist" ], "luci": [ "getBoardJSON", "getDUIDHints", "getHostHints", "getIfaddrs", "getInitList", "getLocaltime", "getTimezones", "getDHCPLeases", "getLEDs", "getNetworkDevices", "getUSBDevices", "getHostname", "getTTYDevices", "getWirelessDevices" ], "network.device": [ "status" ], "network.interface": [ "dump" ], - "network.wireless": [ "status" ], "network": [ "get_proto_handlers" ], "uci": [ "changes", "get" ] }, @@ -45,7 +44,7 @@ "file": [ "remove" ], "iwinfo": [ "scan" ], "luci": [ "setInitAction", "setLocaltime" ], - "uci": [ "add", "apply", "confirm", "delete", "order", "set" ] + "uci": [ "add", "apply", "confirm", "delete", "order", "set", "rename" ] }, "uci": [ "*" ] } diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js deleted file mode 100644 index f30e47ec7..000000000 --- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js +++ /dev/null @@ -1,164 +0,0 @@ -var poll = null; - -function format_signal(bss) { - var qval = bss.quality || 0, - qmax = bss.quality_max || 100, - scale = 100 / qmax * qval, - range = 'none'; - - if (!bss.bssid || bss.bssid == '00:00:00:00:00:00') - range = 'none'; - else if (scale < 15) - range = '0'; - else if (scale < 35) - range = '0-25'; - else if (scale < 55) - range = '25-50'; - else if (scale < 75) - range = '50-75'; - else - range = '75-100'; - - return E('span', { - class: 'ifacebadge', - title: '%s: %d%s / %s: %d/%d'.format(_('Signal'), bss.signal, _('dB'), _('Quality'), qval, qmax) - }, [ - E('img', { src: L.resource('icons/signal-%s.png').format(range) }), - ' %d%%'.format(scale) - ]); -} - -function format_encryption(bss) { - var enc = bss.encryption || { } - - if (enc.wep === true) - return 'WEP'; - else if (enc.wpa > 0) - return E('abbr', { - title: 'Pairwise: %h / Group: %h'.format( - enc.pair_ciphers.join(', '), - enc.group_ciphers.join(', ')) - }, - '%h - %h'.format( - (enc.wpa === 3) ? _('mixed WPA/WPA2') : (enc.wpa === 2 ? 'WPA2' : 'WPA'), - enc.auth_suites.join(', '))); - else - return E('em', enc.enabled ? _('unknown') : _('open')); -} - -function format_actions(dev, type, bss) { - var enc = bss.encryption || { }, - input = [ - E('input', { type: 'submit', class: 'cbi-button cbi-button-action important', value: _('Join Network') }), - E('input', { type: 'hidden', name: 'token', value: L.env.token }), - E('input', { type: 'hidden', name: 'device', value: dev }), - E('input', { type: 'hidden', name: 'join', value: bss.ssid }), - E('input', { type: 'hidden', name: 'mode', value: bss.mode }), - E('input', { type: 'hidden', name: 'bssid', value: bss.bssid }), - E('input', { type: 'hidden', name: 'channel', value: bss.channel }), - E('input', { type: 'hidden', name: 'clbridge', value: type === 'wl' ? 1 : 0 }), - E('input', { type: 'hidden', name: 'wep', value: enc.wep ? 1 : 0 }) - ]; - - if (enc.wpa) { - input.push(E('input', { type: 'hidden', name: 'wpa_version', value: enc.wpa })); - - enc.auth_suites.forEach(function(s) { - input.push(E('input', { type: 'hidden', name: 'wpa_suites', value: s })); - }); - - enc.group_ciphers.forEach(function(s) { - input.push(E('input', { type: 'hidden', name: 'wpa_group', value: s })); - }); - - enc.pair_ciphers.forEach(function(s) { - input.push(E('input', { type: 'hidden', name: 'wpa_pairwise', value: s })); - }); - } - - return E('form', { - class: 'inline', - method: 'post', - action: L.url('admin/network/wireless_join') - }, input); -} - -function fade(bss, content) { - if (bss.stale) - return E('span', { style: 'opacity:0.5' }, content); - else - return content; -} - -function flush() { - L.stop(poll); - L.halt(); - - scan(); -} - -function scan() { - var tbl = document.querySelector('[data-wifi-scan]'), - dev = tbl.getAttribute('data-wifi-scan'), - type = tbl.getAttribute('data-wifi-type'); - - cbi_update_table(tbl, [], E('em', { class: 'spinning' }, _('Starting wireless scan...'))); - - L.post(L.url('admin/network/wireless_scan_trigger', dev), null, function(s) { - if (s.status !== 204) { - cbi_update_table(tbl, [], E('em', _('Scan request failed'))); - return; - } - - var count = 0; - - poll = L.poll(3, L.url('admin/network/wireless_scan_results', dev), null, function(s, results) { - - if (Array.isArray(results)) { - var bss = []; - - results.sort(function(a, b) { - var diff = (b.quality - a.quality) || (a.channel - b.channel); - - if (diff) - return diff; - - if (a.ssid < b.ssid) - return -1; - else if (a.ssid > b.ssid) - return 1; - - if (a.bssid < b.bssid) - return -1; - else if (a.bssid > b.bssid) - return 1; - }).forEach(function(res) { - bss.push([ - fade(res, format_signal(res)), - fade(res, res.ssid ? '%h'.format(res.ssid) : E('em', {}, _('hidden'))), - fade(res, res.channel), - fade(res, res.mode), - fade(res, res.bssid), - fade(res, format_encryption(res)), - format_actions(dev, type, res) - ]); - }); - - cbi_update_table(tbl, bss, E('em', {}, _('No networks in range'))); - } - else { - cbi_update_table(tbl, [], E('em', { class: 'spinning' }, _('No scan results available yet...'))); - } - - - if (count++ >= 3) { - count = 0; - L.post(L.url('admin/network/wireless_scan_trigger', dev, 1), null, function() {}); - } - }); - - L.run(); - }); -} - -document.addEventListener('DOMContentLoaded', scan); diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_status.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_status.js deleted file mode 100644 index 108a141f8..000000000 --- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_status.js +++ /dev/null @@ -1,59 +0,0 @@ -requestAnimationFrame(function() { - document.querySelectorAll('[data-wifi-status]').forEach(function(container) { - var ifname = container.getAttribute('data-wifi-status'), - small = container.querySelector('small'), - info = container.querySelector('span'); - - L.poll(5, L.url('admin/network/wireless_status', ifname), null, function(xhr, iws) { - var iw = Array.isArray(iws) ? iws[0] : null; - if (!iw) - return; - - var is_assoc = (iw.bssid && iw.bssid != '00:00:00:00:00:00' && iw.channel && !iw.disabled); - var p = iw.quality; - var q = iw.disabled ? -1 : p; - - var icon; - if (q < 0) - icon = L.resource('icons/signal-none.png'); - else if (q == 0) - icon = L.resource('icons/signal-0.png'); - else if (q < 25) - icon = L.resource('icons/signal-0-25.png'); - else if (q < 50) - icon = L.resource('icons/signal-25-50.png'); - else if (q < 75) - icon = L.resource('icons/signal-50-75.png'); - else - icon = L.resource('icons/signal-75-100.png'); - - L.dom.content(small, [ - E('img', { - src: icon, - title: '%s: %d %s / %s: %d %s'.format( - _('Signal'), iw.signal, _('dBm'), - _('Noise'), iw.noise, _('dBm')) - }), - '\u00a0', E('br'), '%d%%\u00a0'.format(p) - ]); - - L.itemlist(info, [ - _('Mode'), iw.mode, - _('SSID'), iw.ssid || '?', - _('BSSID'), is_assoc ? iw.bssid : null, - _('Encryption'), is_assoc ? iw.encryption || _('None') : null, - _('Channel'), is_assoc ? '%d (%.3f %s)'.format(iw.channel, iw.frequency || 0, _('GHz')) : null, - _('Tx-Power'), is_assoc ? '%d %s'.format(iw.txpower, _('dBm')) : null, - _('Signal'), is_assoc ? '%d %s'.format(iw.signal, _('dBm')) : null, - _('Noise'), is_assoc ? '%d %s'.format(iw.noise, _('dBm')) : null, - _('Bitrate'), is_assoc ? '%.1f %s'.format(iw.bitrate || 0, _('Mbit/s')) : null, - _('Country'), is_assoc ? iw.country : null - ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]); - - if (!is_assoc) - L.dom.append(info, E('em', iw.disabled ? _('Wireless is disabled') : _('Wireless is not associated'))); - }); - - L.run(); - }); -}); diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js index 57e6bbb04..a058b3fe5 100644 --- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js +++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js @@ -1,93 +1,1942 @@ -function wifi_delete(ev) { - if (!confirm(_('Really delete this wireless network? The deletion cannot be undone! You might lose access to this device if you are connected via this network.'))) { - ev.preventDefault(); - return false; +'use strict'; +'require rpc'; +'require uci'; +'require form'; +'require network'; +'require firewall'; +'require tools.widgets as widgets'; + +function count_changes(section_id) { + var changes = L.ui.changes.changes, n = 0; + + if (!L.isObject(changes)) + return n; + + if (Array.isArray(changes.wireless)) + for (var i = 0; i < changes.wireless.length; i++) + n += (changes.wireless[i][1] == section_id); + + return n; +} + +function render_radio_badge(radioDev) { + return E('span', { 'class': 'ifacebadge' }, [ + E('img', { 'src': L.resource('icons/wifi%s.png').format(radioDev.isUp() ? '' : '_disabled') }), + ' ', + radioDev.getName() + ]); +} + +function render_signal_badge(signalPercent, signalValue, noiseValue, wrap) { + var icon, title; + + if (signalPercent < 0) + icon = L.resource('icons/signal-none.png'); + else if (signalPercent == 0) + icon = L.resource('icons/signal-0.png'); + else if (signalPercent < 25) + icon = L.resource('icons/signal-0-25.png'); + else if (signalPercent < 50) + icon = L.resource('icons/signal-25-50.png'); + else if (signalPercent < 75) + icon = L.resource('icons/signal-50-75.png'); + else + icon = L.resource('icons/signal-75-100.png'); + + if (signalValue != null && signalValue != 0) { + title = '%s %d %s'.format(_('Signal'), signalValue, _('dBm')); + + if (noiseValue != null && noiseValue != 0) + title += ' / %s: %d %s'.format(_('Noise'), noiseValue, _('dBm')); } + else { + title = _('No signal'); + } + + return E('div', { 'class': wrap ? 'center' : 'ifacebadge', 'title': title }, + [ E('img', { 'src': icon }), wrap ? E('br') : ' ', '%d%%'.format(Math.max(signalPercent, 0)) ]); +} - ev.target.previousElementSibling.value = '1'; - return true; +function render_network_badge(radioNet) { + return render_signal_badge(radioNet.isUp() ? radioNet.getSignalPercent() : -1, radioNet.getSignal(), radioNet.getNoise()); } -function wifi_restart(ev) { - L.halt(); +function render_radio_status(radioDev, wifiNets) { + var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''), + node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]), + channel, frequency, bitrate; + + for (var i = 0; i < wifiNets.length; i++) { + channel = channel || wifiNets[i].getChannel(); + frequency = frequency || wifiNets[i].getFrequency(); + bitrate = bitrate || wifiNets[i].getBitRate(); + } + + if (radioDev.isUp()) + L.itemlist(node.lastElementChild, [ + _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')), + _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s')) + ], ' | '); + else + node.lastElementChild.appendChild(E('em', _('Device is not active'))); + + return node; +} + +function render_network_status(radioNet) { + var mode = radioNet.getActiveMode(), + bssid = radioNet.getActiveBSSID(), + channel = radioNet.getChannel(), + disabled = (radioNet.get('disabled') == '1'), + is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled), + changecount = count_changes(radioNet.getName()), + status_text = null; + + if (changecount) + status_text = E('a', { + href: '#', + click: L.bind(L.ui.changes.displayChanges, L.ui.changes) + }, _('Interface has %d pending changes').format(changecount)); + else if (!is_assoc) + status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')); + + return L.itemlist(E('div'), [ + _('SSID'), radioNet.getSSID() || '?', + _('Mode'), mode, + _('BSSID'), (!changecount && is_assoc) ? bssid : null, + _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null, + null, status_text + ], [ ' | ', E('br') ]); +} + +function render_modal_status(node, radioNet) { + var mode = radioNet.getActiveMode(), + noise = radioNet.getNoise(), + bssid = radioNet.getActiveBSSID(), + channel = radioNet.getChannel(), + disabled = (radioNet.get('disabled') == '1'), + is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled); + + if (node == null) + node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]); + + L.dom.content(node.firstElementChild, render_signal_badge(disabled ? -1 : radioNet.getSignalPercent(), radioNet.getSignal(), noise, true)); + + L.itemlist(node.lastElementChild, [ + _('Mode'), mode, + _('SSID'), radioNet.getSSID() || '?', + _('BSSID'), is_assoc ? bssid : null, + _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null, + _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null, + _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null, + _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null, + _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null, + _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null, + _('Country'), is_assoc ? radioNet.getCountryCode() : null + ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]); + + if (!is_assoc) + L.dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'))); + + return node; +} + +function format_wifirate(rate) { + var s = '%.1f Mbit/s, %dMHz'.format(rate.rate / 1000, rate.mhz); + + if (rate.ht || rate.vht) { + if (rate.vht) s += ', VHT-MCS %d'.format(rate.mcs); + if (rate.nss) s += ', VHT-NSS %d'.format(rate.nss); + if (rate.ht) s += ', MCS %s'.format(rate.mcs); + if (rate.short_gi) s += ', Short GI'; + } + + return s; +} + +function radio_restart(id, ev) { + var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)), + dsc = row.querySelector('[data-name="_stat"] > div'), + btn = row.querySelector('.cbi-section-actions button'); + + btn.blur(); + btn.classList.add('spinning'); + btn.disabled = true; + + dsc.setAttribute('restart', ''); + L.dom.content(dsc, E('em', _('Device is restarting…'))); +} + +function network_updown(id, map, ev) { + var radio = uci.get('wireless', id, 'device'), + disabled = (uci.get('wireless', id, 'disabled') == '1') || + (uci.get('wireless', radio, 'disabled') == '1'); + + if (disabled) { + uci.unset('wireless', id, 'disabled'); + uci.unset('wireless', radio, 'disabled'); + } + else { + uci.set('wireless', id, 'disabled', '1'); + + var all_networks_disabled = true, + wifi_ifaces = uci.sections('wireless', 'wifi-iface'); + + for (var i = 0; i < wifi_ifaces.length; i++) { + if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') { + all_networks_disabled = false; + break; + } + } + + if (all_networks_disabled) + uci.set('wireless', radio, 'disabled', '1'); + } - findParent(ev.target, '.table').querySelectorAll('[data-disabled="false"]').forEach(function(s) { - L.dom.content(s, E('em', _('Wireless is restarting...'))); + return map.save().then(function() { + L.ui.changes.apply() }); +} + +function next_free_sid(offset) { + var sid = 'wifinet' + offset; - L.post(L.url('admin/network/wireless_reconnect', ev.target.getAttribute('data-radio')), L.run); + while (uci.get('wireless', sid)) + sid = 'wifinet' + (++offset); + + return sid; } -var networks = [ ]; +var CBIWifiFrequencyValue = form.Value.extend({ + callFrequencyList: rpc.declare({ + object: 'iwinfo', + method: 'freqlist', + params: [ 'device' ], + expect: { results: [] } + }), + + load: function(section_id) { + return Promise.all([ + network.getWifiDevice(section_id), + this.callFrequencyList(section_id) + ]).then(L.bind(function(data) { + this.channels = { + '11g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [], + '11a': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [] + }; + + for (var i = 0; Array.isArray(data[1]) && i < data[1].length; i++) + this.channels[(data[1][i].mhz > 2484) ? '11a' : '11g'].push( + data[1][i].channel, + '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz), + !data[1][i].restricted + ); + + var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null) + .reduce(function(o, v) { o[v] = true; return o }, {}); + + this.modes = [ + '', 'Legacy', true, + 'n', 'N', hwmodelist.n, + 'ac', 'AC', hwmodelist.ac + ]; + + var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null) + .reduce(function(o, v) { o[v] = true; return o }, {}); + + this.htmodes = { + '': [ '', '-', true ], + 'n': [ + 'HT20', '20 MHz', htmodelist.HT20, + 'HT40', '40 MHz', htmodelist.HT40 + ], + 'ac': [ + 'VHT20', '20 MHz', htmodelist.VHT20, + 'VHT40', '40 MHz', htmodelist.VHT40, + 'VHT80', '80 MHz', htmodelist.VHT80, + 'VHT160', '160 MHz', htmodelist.VHT160 + ] + }; + + this.bands = { + '': [ + '11g', '2.4 GHz', this.channels['11g'].length > 3, + '11a', '5 GHz', this.channels['11a'].length > 3 + ], + 'n': [ + '11g', '2.4 GHz', this.channels['11g'].length > 3, + '11a', '5 GHz', this.channels['11a'].length > 3 + ], + 'ac': [ + '11a', '5 GHz', true + ] + }; + }, this)); + }, + + setValues: function(sel, vals) { + if (sel.vals) + sel.vals.selected = sel.selectedIndex; + + while (sel.options[0]) + sel.remove(0); + + for (var i = 0; vals && i < vals.length; i += 3) + if (vals[i+2]) + sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ])); + + if (!isNaN(vals.selected)) + sel.selectedIndex = vals.selected; + + sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : ''; + sel.vals = vals; + }, + + toggleWifiMode: function(elem) { + this.toggleWifiHTMode(elem); + this.toggleWifiBand(elem); + }, + + toggleWifiHTMode: function(elem) { + var mode = elem.querySelector('.mode'); + var bwdt = elem.querySelector('.htmode'); + + this.setValues(bwdt, this.htmodes[mode.value]); + }, + + toggleWifiBand: function(elem) { + var mode = elem.querySelector('.mode'); + var band = elem.querySelector('.band'); -document.querySelectorAll('[data-network]').forEach(function(n) { - networks.push(n.getAttribute('data-network')); + this.setValues(band, this.bands[mode.value]); + this.toggleWifiChannel(elem); + }, + + toggleWifiChannel: function(elem) { + var band = elem.querySelector('.band'); + var chan = elem.querySelector('.channel'); + + this.setValues(chan, this.channels[band.value]); + }, + + setInitialValues: function(section_id, elem) { + var mode = elem.querySelector('.mode'), + band = elem.querySelector('.band'), + chan = elem.querySelector('.channel'), + bwdt = elem.querySelector('.htmode'), + htval = uci.get('wireless', section_id, 'htmode'), + hwval = uci.get('wireless', section_id, 'hwmode'), + chval = uci.get('wireless', section_id, 'channel'); + + this.setValues(mode, this.modes); + + if (/VHT20|VHT40|VHT80|VHT160/.test(htval)) + mode.value = 'ac'; + else if (/HT20|HT40/.test(htval)) + mode.value = 'n'; + else + mode.value = ''; + + this.toggleWifiMode(elem); + + if (/a/.test(hwval)) + band.value = '11a'; + else + band.value = '11g'; + + this.toggleWifiBand(elem); + + bwdt.value = htval; + chan.value = chval; + + return elem; + }, + + renderWidget: function(section_id, option_index, cfgvalue) { + var elem = E('div'); + + L.dom.content(elem, [ + E('label', { 'style': 'float:left; margin-right:3px' }, [ + _('Mode'), E('br'), + E('select', { + 'class': 'mode', + 'style': 'width:auto', + 'change': L.bind(this.toggleWifiMode, this, elem) + }) + ]), + E('label', { 'style': 'float:left; margin-right:3px' }, [ + _('Band'), E('br'), + E('select', { + 'class': 'band', + 'style': 'width:auto', + 'change': L.bind(this.toggleWifiBand, this, elem) + }) + ]), + E('label', { 'style': 'float:left; margin-right:3px' }, [ + _('Channel'), E('br'), + E('select', { + 'class': 'channel', + 'style': 'width:auto' + }) + ]), + E('label', { 'style': 'float:left; margin-right:3px' }, [ + _('Width'), E('br'), + E('select', { + 'class': 'htmode', + 'style': 'width:auto' + }) + ]), + E('br', { 'style': 'clear:left' }) + ]); + + return this.setInitialValues(section_id, elem); + }, + + cfgvalue: function(section_id) { + return [ + uci.get('wireless', section_id, 'htmode'), + uci.get('wireless', section_id, 'hwmode'), + uci.get('wireless', section_id, 'channel') + ]; + }, + + formvalue: function(section_id) { + var node = this.map.findElement('data-field', this.cbid(section_id)); + + return [ + node.querySelector('.htmode').value, + node.querySelector('.band').value, + node.querySelector('.channel').value + ]; + }, + + write: function(section_id, value) { + uci.set('wireless', section_id, 'htmode', value[0] || null); + uci.set('wireless', section_id, 'hwmode', value[1]); + uci.set('wireless', section_id, 'channel', value[2]); + } }); -L.poll(5, L.url('admin/network/wireless_status', networks.join(',')), null, - function(x, st) { - if (st) { - var rowstyle = 1; - var radiostate = { }; +var CBIWifiTxPowerValue = form.ListValue.extend({ + callTxPowerList: rpc.declare({ + object: 'iwinfo', + method: 'txpowerlist', + params: [ 'device' ], + expect: { results: [] } + }), - st.forEach(function(s) { - var r = radiostate[s.device.device] || (radiostate[s.device.device] = {}); + load: function(section_id) { + return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) { + this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null; + this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null; - s.is_assoc = (s.bssid && s.bssid != '00:00:00:00:00:00' && s.channel && s.mode != 'Unknown' && !s.disabled); + this.value('', _('driver default')); - r.up = r.up || s.is_assoc; - r.channel = r.channel || s.channel; - r.bitrate = r.bitrate || s.bitrate; - r.frequency = r.frequency || s.frequency; - }); + for (var i = 0; i < pwrlist.length; i++) + this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw)); + + return form.ListValue.prototype.load.apply(this, [section_id]); + }, this)); + }, + + renderWidget: function(section_id, option_index, cfgvalue) { + var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]); + widget.firstElementChild.style.width = 'auto'; + + L.dom.append(widget, E('span', [ + ' - ', _('Current power'), ': ', + E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]), + this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : '' + ])); - for (var i = 0; i < st.length; i++) { - var iw = st[i], - sig = document.getElementById(iw.id + '-iw-signal'), - info = document.getElementById(iw.id + '-iw-status'), - disabled = (info && info.getAttribute('data-disabled') === 'true'); - - var p = iw.quality; - var q = disabled ? -1 : p; - - var icon; - if (q < 0) - icon = L.resource('icons/signal-none.png'); - else if (q == 0) - icon = L.resource('icons/signal-0.png'); - else if (q < 25) - icon = L.resource('icons/signal-0-25.png'); - else if (q < 50) - icon = L.resource('icons/signal-25-50.png'); - else if (q < 75) - icon = L.resource('icons/signal-50-75.png'); - else - icon = L.resource('icons/signal-75-100.png'); - - L.dom.content(sig, E('span', { - class: 'ifacebadge', - title: '%s %d %s / %s: %d %s'.format(_('Signal'), iw.signal, _('dBm'), _('Noise'), iw.noise, _('dBm')) - }, [ E('img', { src: icon }), ' %d%%'.format(p) ])); - - L.itemlist(info, [ - _('SSID'), iw.ssid || '?', - _('Mode'), iw.mode, - _('BSSID'), iw.is_assoc ? iw.bssid : null, - _('Encryption'), iw.is_assoc ? iw.encryption || _('None') : null, - null, iw.is_assoc ? null : E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')) - ], [ ' | ', E('br') ]); + return widget; + } +}); + +var CBIWifiCountryValue = form.Value.extend({ + callCountryList: rpc.declare({ + object: 'iwinfo', + method: 'countrylist', + params: [ 'device' ], + expect: { results: [] } + }), + + load: function(section_id) { + return this.callCountryList(section_id).then(L.bind(function(countrylist) { + if (Array.isArray(countrylist) && countrylist.length > 0) { + this.value('', _('driver default')); + + for (var i = 0; i < countrylist.length; i++) + this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country)); } - for (var dev in radiostate) { - var img = document.getElementById(dev + '-iw-upstate'); - if (img) img.src = L.resource('icons/wifi' + (radiostate[dev].up ? '' : '_disabled') + '.png'); + return form.Value.prototype.load.apply(this, [section_id]); + }, this)); + }, + + validate: function(section_id, formvalue) { + if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue)) + return _('Use ISO/IEC 3166 alpha2 country codes.'); + + return true; + }, + + renderWidget: function(section_id, option_index, cfgvalue) { + var typeClass = this.keylist.length ? form.ListValue : form.Value; + return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]); + } +}); + +return L.view.extend({ + poll_status: function(map, data) { + var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]'); + + for (var i = 0; i < rows.length; i++) { + var section_id = rows[i].getAttribute('data-sid'), + radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0], + radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0], + badge = rows[i].querySelector('[data-name="_badge"] > div'), + stat = rows[i].querySelector('[data-name="_stat"]'), + btns = rows[i].querySelectorAll('.cbi-section-actions button'), + busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning'); - var stat = document.getElementById(dev + '-iw-devinfo'); - L.itemlist(stat, [ - _('Channel'), '%s (%s %s)'.format(radiostate[dev].channel || '?', radiostate[dev].frequency || '?', _('GHz')), - _('Bitrate'), '%s %s'.format(radiostate[dev].bitrate || '?', _('Mbit/s')) - ], ' | '); + if (radioDev) { + L.dom.content(badge, render_radio_badge(radioDev)); + L.dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() }))); } + else { + L.dom.content(badge, render_network_badge(radioNet)); + L.dom.content(stat, render_network_status(radioNet)); + } + + if (stat.hasAttribute('restart')) + L.dom.content(stat, E('em', _('Device is restarting…'))); + + btns[0].disabled = busy; + btns[1].disabled = busy; + btns[2].disabled = busy; } + + var table = document.querySelector('wifi_assoclist_table'), + hosts = data[0], + trows = []; + + for (var i = 0; i < data[3].length; i++) { + var bss = data[3][i], + name = hosts.getHostnameByMACAddr(bss.mac), + ipv4 = hosts.getIPAddrByMACAddr(bss.mac), + ipv6 = hosts.getIP6AddrByMACAddr(bss.mac); + + trows.push([ + E('span', { 'class': 'ifacebadge' }, [ + E('img', { + 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'), + 'title': bss.radio.getI18n() + }), + ' %s '.format(bss.network.getShortName()), + E('small', '(%s)'.format(bss.network.getIfname())) + ]), + bss.mac, + name ? '%s (%s)'.format(name, ipv4 || ipv6 || '?') : ipv4 || ipv6 || '?', + render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise), + E('span', {}, [ + E('span', format_wifirate(bss.rx)), + E('br'), + E('span', format_wifirate(bss.tx)) + ]) + ]); + } + + cbi_update_table('#wifi_assoclist_table', trows, E('em', _('No information available'))); + + var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large'); + + if (stat) + render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]); + + return network.flushCache(); + }, + + load: function() { + return Promise.all([ + uci.changes(), + uci.load('wireless') + ]); + }, + + checkAnonymousSections: function() { + var wifiIfaces = uci.sections('wireless', 'wifi-iface'); + + for (var i = 0; i < wifiIfaces.length; i++) + if (wifiIfaces[i]['.anonymous']) + return true; + + return false; + }, + + callUciRename: rpc.declare({ + object: 'uci', + method: 'rename', + params: [ 'config', 'section', 'name' ] + }), + + render: function() { + if (this.checkAnonymousSections()) + return this.renderMigration(); + else + return this.renderOverview(); + }, + + handleMigration: function(ev) { + var wifiIfaces = uci.sections('wireless', 'wifi-iface'), + id_offset = 0, + tasks = []; + + for (var i = 0; i < wifiIfaces.length; i++) { + if (!wifiIfaces[i]['.anonymous']) + continue; + + var new_name = next_free_sid(id_offset); + + tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name)); + id_offset = +new_name.substring(7) + 1; + } + + return Promise.all(tasks) + .then(L.bind(L.ui.changes.init, L.ui.changes)) + .then(L.bind(L.ui.changes.apply, L.ui.changes)); + }, + + renderMigration: function() { + L.ui.showModal(_('Wireless configuration migration'), [ + E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')), + E('p', _('Upon pressing "Continue", anonymous "wifi-iface" sections will be assigned with a name in the form <em>wifinet#</em> and the network will be restarted to apply the updated configuration.')), + E('div', { 'class': 'right' }, + E('button', { + 'class': 'btn cbi-button-action important', + 'click': L.ui.createHandlerFn(this, 'handleMigration') + }, _('Continue'))) + ]); + }, + + renderOverview: function() { + var m, s, o; + + m = new form.Map('wireless'); + m.chain('network'); + m.chain('firewall'); + + s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview')); + s.anonymous = true; + s.addremove = false; + + s.load = function() { + return network.getWifiDevices().then(L.bind(function(radios) { + this.radios = radios.sort(function(a, b) { + return a.getName() > b.getName(); + }); + + var tasks = []; + + for (var i = 0; i < radios.length; i++) + tasks.push(radios[i].getWifiNetworks()); + + return Promise.all(tasks); + }, this)).then(L.bind(function(data) { + this.wifis = []; + + for (var i = 0; i < data.length; i++) + this.wifis.push.apply(this.wifis, data[i]); + }, this)); + }; + + s.cfgsections = function() { + var rv = []; + + for (var i = 0; i < this.radios.length; i++) { + rv.push(this.radios[i].getName()); + + for (var j = 0; j < this.wifis.length; j++) + if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName()) + rv.push(this.wifis[j].getName()); + } + + return rv; + }; + + s.modaltitle = function(section_id) { + var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0]; + return radioNet ? radioNet.getI18n() : _('Edit wireless network'); + }; + + s.lookupRadioOrNetwork = function(section_id) { + var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0]; + if (radioDev) + return radioDev; + + var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0]; + if (radioNet) + return radioNet; + + return null; + }; + + s.renderRowActions = function(section_id) { + var inst = this.lookupRadioOrNetwork(section_id), btns; + + if (inst.getWifiNetworks) { + btns = [ + E('button', { + 'class': 'cbi-button cbi-button-neutral', + 'title': _('Restart radio interface'), + 'click': L.ui.createHandlerFn(this, radio_restart, section_id) + }, _('Restart')), + E('button', { + 'class': 'cbi-button cbi-button-action important', + 'title': _('Find and join network'), + 'click': L.ui.createHandlerFn(this, 'handleScan', inst) + }, _('Scan')), + E('button', { + 'class': 'cbi-button cbi-button-add', + 'title': _('Provide new network'), + 'click': L.ui.createHandlerFn(this, 'handleAdd', inst) + }, _('Add')) + ]; + } + else { + var isDisabled = (inst.get('disabled') == '1'); + + btns = [ + E('button', { + 'class': 'cbi-button cbi-button-neutral enable-disable', + 'title': isDisabled ? _('Enable this network') : _('Disable this network'), + 'click': L.ui.createHandlerFn(this, network_updown, section_id, this.map) + }, isDisabled ? _('Enable') : _('Disable')), + E('button', { + 'class': 'cbi-button cbi-button-action important', + 'title': _('Edit this network'), + 'click': L.ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id) + }, _('Edit')), + E('button', { + 'class': 'cbi-button cbi-button-negative remove', + 'title': _('Delete this network'), + 'click': L.ui.createHandlerFn(this, 'handleRemove', section_id) + }, _('Remove')) + ]; + } + + return E('div', { 'class': 'td middle cbi-section-actions' }, E('div', btns)); + }; + + s.addModalOptions = function(s) { + return network.getWifiNetwork(s.section).then(function(radioNet) { + var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type'); + var o, ss; + + o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration')); + o.modalonly = true; + + ss = o.subsection; + ss.tab('general', _('General Setup')); + ss.tab('advanced', _('Advanced Settings')); + + var isDisabled = (radioNet.get('disabled') == '1'); + + o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status')); + o.cfgvalue = L.bind(function(radioNet) { + return render_modal_status(null, radioNet); + }, this, radioNet); + o.write = function() {}; + + o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled')); + o.inputstyle = isDisabled ? 'apply' : 'reset'; + o.inputtitle = isDisabled ? _('Enable') : _('Disable'); + o.onclick = L.ui.createHandlerFn(s, network_updown, s.section, s.map); + + o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '<br />' + _('Operating frequency')); + o.ucisection = s.section; + + if (hwtype == 'mac80211') { + o = ss.taboption('general', CBIWifiTxPowerValue, 'txpower', _('Maximum transmit power'), _('Specifies the maximum transmit power the wireless radio may use. Depending on regulatory requirements and wireless usage, the actual transmit power may be reduced by the driver.')); + o.wifiNetwork = radioNet; + + o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code')); + o.wifiNetwork = radioNet; + + o = ss.taboption('advanced', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates')); + o.default = o.enabled; + + o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.')); + o.datatype = 'range(0,114750)'; + o.placeholder = 'auto'; + + o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold')); + o.datatype = 'min(256)'; + o.placeholder = _('off'); + + o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold')); + o.datatype = 'uinteger'; + o.placeholder = _('off'); + + o = ss.taboption('advanced', form.Flag, 'noscan', _('Force 40MHz mode'), _('Always use 40MHz channels even if the secondary channel overlaps. Using this option does not comply with IEEE 802.11n-2009!')); + o.rmempty = true; + + o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval')); + o.datatype = 'range(15,65535)'; + o.placeholder = 100; + o.rmempty = true; + } + + + o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration')); + o.modalonly = true; + + ss = o.subsection; + ss.tab('general', _('General Setup')); + ss.tab('encryption', _('Wireless Security')); + ss.tab('macfilter', _('MAC-Filter')); + ss.tab('advanced', _('Advanced Settings')); + + o = ss.taboption('general', form.ListValue, 'mode', _('Mode')); + o.value('ap', _('Access Point')); + o.value('sta', _('Client')); + o.value('adhoc', _('Ad-Hoc')); + + o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id')); + o.depends('mode', 'mesh'); + + o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic')); + o.rmempty = false; + o.default = '1'; + o.depends('mode', 'mesh'); + + o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default')); + o.rmempty = false; + o.default = '0'; + o.datatype = 'range(-255,1)'; + o.depends('mode', 'mesh'); + + o = ss.taboption('general', form.Value, 'ssid', _('<abbr title="Extended Service Set Identifier">ESSID</abbr>')); + o.datatype = 'maxlength(32)'; + o.depends('mode', 'ap'); + o.depends('mode', 'sta'); + o.depends('mode', 'adhoc'); + o.depends('mode', 'ahdemo'); + o.depends('mode', 'monitor'); + o.depends('mode', 'ap-wds'); + o.depends('mode', 'sta-wds'); + o.depends('mode', 'wds'); + + o = ss.taboption('general', form.Value, 'bssid', _('<abbr title="Basic Service Set Identifier">BSSID</abbr>')); + o.datatype = 'macaddr'; + + o = ss.taboption('general', widgets.NetworkSelect, 'network', _('Network'), _('Choose the network(s) you want to attach to this wireless interface or fill out the <em>create</em> field to define a new network.')); + o.rmempty = true; + o.multiple = true; + o.novirtual = true; + o.write = function(section_id, value) { + return network.getDevice(section_id).then(L.bind(function(dev) { + var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}), + new_networks = {}, + values = L.toArray(value), + tasks = []; + + for (var i = 0; i < values.length; i++) { + new_networks[values[i]] = true; + + if (old_networks[values[i]]) + continue; + + tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) { + return net || network.addNetwork(name, { proto: 'none' }); + }, this, values[i])).then(L.bind(function(dev, net) { + if (net) { + if (!net.isEmpty()) + net.set('type', 'bridge'); + net.addDevice(dev); + } + }, this, dev))); + } + + for (var name in old_networks) + if (!new_networks[name]) + tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) { + if (net) + net.deleteDevice(dev); + }, this, dev))); + + return Promise.all(tasks); + }, this)); + }; + + if (hwtype == 'mac80211') { + var mode = ss.children[0], + bssid = ss.children[5], + encr; + + mode.value('mesh', '802.11s'); + mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)')); + mode.value('monitor', _('Monitor')); + + bssid.depends('mode', 'adhoc'); + bssid.depends('mode', 'sta'); + bssid.depends('mode', 'sta-wds'); + + o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC-Address Filter')); + o.depends('mode', 'ap'); + o.depends('mode', 'ap-wds'); + o.value('', _('disable')); + o.value('allow', _('Allow listed only')); + o.value('deny', _('Allow all except listed')); + + o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List')); + o.datatype = 'macaddr'; + o.depends('macfilter', 'allow'); + o.depends('macfilter', 'deny'); + //nt.mac_hints(function(mac, name) ml:value(mac, '%s (%s)' %{ mac, name }) end); + + mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS'))); + mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS'))); + + mode.write = function(section_id, value) { + switch (value) { + case 'ap-wds': + uci.set('wireless', section_id, 'mode', 'ap'); + uci.set('wireless', section_id, 'wds', '1'); + break; + + case 'sta-wds': + uci.set('wireless', section_id, 'mode', 'sta'); + uci.set('wireless', section_id, 'wds', '1'); + break; + + default: + uci.set('wireless', section_id, 'mode', value); + uci.unset('wireless', section_id, 'wds'); + break; + } + }; + + mode.cfgvalue = function(section_id) { + var mode = uci.get('wireless', section_id, 'mode'), + wds = uci.get('wireless', section_id, 'wds'); + + if (mode == 'ap' && wds) + return 'ap-wds'; + else if (mode == 'sta' && wds) + return 'sta-wds'; + + return mode; + }; + + o = ss.taboption('general', form.Flag, 'hidden', _('Hide <abbr title="Extended Service Set Identifier">ESSID</abbr>')); + o.depends('mode', 'ap'); + o.depends('mode', 'ap-wds'); + + o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode')); + o.depends('mode', 'ap'); + o.depends('mode', 'ap-wds'); + o.default = o.enabled; + + o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication')); + o.depends('mode', 'ap'); + o.depends('mode', 'ap-wds'); + + o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name')); + o.optional = true; + o.placeholder = radioNet.getIfname(); + if (/^radio\d+\.network/.test(o.placeholder)) + o.placeholder = ''; + + o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble')); + o.default = o.enabled; + + o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval')); + o.optional = true; + o.placeholder = 2; + o.datatype = 'range(1,255)'; + + o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec')); + o.optional = true; + o.placeholder = 600; + o.datatype = 'uinteger'; + + o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling')); + o.optional = true; + o.datatype = 'uinteger'; + + o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('sec')); + o.optional = true; + o.placeholder = 300; + o.datatype = 'uinteger'; + + o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval')); + o.optional = true; + o.placeholder = 65535; + o.datatype = 'uinteger'; + + o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition')); + o.default = o.enabled; + } + + + encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption')); + o.depends('mode', 'ap'); + o.depends('mode', 'sta'); + o.depends('mode', 'adhoc'); + o.depends('mode', 'ahdemo'); + o.depends('mode', 'ap-wds'); + o.depends('mode', 'sta-wds'); + o.depends('mode', 'mesh'); + + o.cfgvalue = function(section_id) { + var v = String(uci.get('wireless', section_id, 'encryption')); + if (v == 'wep') + return 'wep-open'; + else if (v.match(/\+/)) + return v.replace(/\+.+$/, ''); + return v; + }; + + o.write = function(section_id, value) { + var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id), + co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id); + + if (value == 'wpa' || value == 'wpa2') + uci.unset('wireless', section_id, 'key'); + + if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp')) + e += '+' + c; + + uci.set('wireless', section_id, 'encryption', e); + }; + + o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher')); + o.depends('encryption', 'wpa'); + o.depends('encryption', 'wpa2'); + o.depends('encryption', 'psk'); + o.depends('encryption', 'psk2'); + o.depends('encryption', 'wpa-mixed'); + o.depends('encryption', 'psk-mixed'); + o.value('auto', _('auto')); + o.value('ccmp', _('Force CCMP (AES)')); + o.value('tkip', _('Force TKIP')); + o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)')); + o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write; + + o.cfgvalue = function(section_id) { + var v = String(uci.get('wireless', section_id, 'encryption')); + if (v.match(/\+/)) { + v = v.replace(/^[^+]+\+/, ''); + if (v == 'aes') + v = 'ccmp'; + else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip') + v = 'tkip+ccmp'; + } + return v; + }; + + + encr.value('none', _('No Encryption')); + encr.value('wep-open', _('WEP Open System')); + encr.value('wep-shared', _('WEP Shared Key')); + + if (hwtype == 'mac80211') { + var has_supplicant = L.hasSystemFeature('wpasupplicant'), + has_hostapd = L.hasSystemFeature('hostapd'); + + // Probe EAP support + var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'), + has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap'); + + // Probe SAE support + var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'), + has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae'); + + // Probe OWE support + var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'), + has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe'); + + + if (has_hostapd || has_supplicant) { + encr.value('psk', 'WPA-PSK'); + encr.value('psk2', 'WPA2-PSK'); + encr.value('psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode'); + } + else { + encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.'); + } + + if (has_ap_sae || has_sta_sae) { + encr.value('sae', 'WPA3-SAE'); + encr.value('sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode'); + } + + if (has_ap_eap || has_sta_eap) { + encr.value('wpa', 'WPA-EAP'); + encr.value('wpa2', 'WPA2-EAP'); + } + + if (has_ap_owe || has_sta_owe) { + encr.value('owe', 'OWE'); + } + + encr.crypto_support = { + 'ap': { + 'wep-open': true, + 'wep-shared': true, + 'psk': has_hostapd || _('Requires hostapd'), + 'psk2': has_hostapd || _('Requires hostapd'), + 'psk-mixed': has_hostapd || _('Requires hostapd'), + 'sae': has_ap_sae || _('Requires hostapd with SAE support'), + 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'), + 'wpa': has_ap_eap || _('Requires hostapd with EAP support'), + 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'), + 'owe': has_ap_owe || _('Requires hostapd with OWE support') + }, + 'sta': { + 'wep-open': true, + 'wep-shared': true, + 'psk': has_supplicant || _('Requires wpa-supplicant'), + 'psk2': has_supplicant || _('Requires wpa-supplicant'), + 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'), + 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'), + 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'), + 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'), + 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'), + 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support') + }, + 'adhoc': { + 'wep-open': true, + 'wep-shared': true, + 'psk': has_supplicant || _('Requires wpa-supplicant'), + 'psk2': has_supplicant || _('Requires wpa-supplicant'), + 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'), + }, + 'mesh': { + 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support') + }, + 'ahdemo': { + 'wep-open': true, + 'wep-shared': true + }, + 'wds': { + 'wep-open': true, + 'wep-shared': true + } + }; + + encr.crypto_support['ap-wds'] = encr.crypto_support['ap']; + encr.crypto_support['sta-wds'] = encr.crypto_support['sta']; + + encr.validate = function(section_id, value) { + var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0], + modeval = modeopt.formvalue(section_id), + modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)], + enctitle = this.vallist[this.keylist.indexOf(value)]; + + if (value == 'none') + return true; + + if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value)) + return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle); + + return this.crypto_support[modeval][value]; + }; + } + else if (hwtype == 'broadcom') { + encr.value('psk', 'WPA-PSK'); + encr.value('psk2', 'WPA2-PSK'); + encr.value('psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode'); + } + + + o = ss.taboption('encryption', form.Value, 'auth_server', _('Radius-Authentication-Server')); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.rmempty = true; + o.datatype = 'host(0)'; + + o = ss.taboption('encryption', form.Value, 'auth_port', _('Radius-Authentication-Port'), _('Default %d').format(1812)); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.rmempty = true; + o.datatype = 'port'; + + o = ss.taboption('encryption', form.Value, 'auth_secret', _('Radius-Authentication-Secret')); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.rmempty = true; + o.password = true; + + o = ss.taboption('encryption', form.Value, 'acct_server', _('Radius-Accounting-Server')); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.rmempty = true; + o.datatype = 'host(0)'; + + o = ss.taboption('encryption', form.Value, 'acct_port', _('Radius-Accounting-Port'), _('Default %d').format(1813)); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.rmempty = true; + o.datatype = 'port'; + + o = ss.taboption('encryption', form.Value, 'acct_secret', _('Radius-Accounting-Secret')); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.rmempty = true; + o.password = true; + + o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client')); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.rmempty = true; + o.datatype = 'host(0)'; + + o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Default %d').format(3799)); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.rmempty = true; + o.datatype = 'port'; + + o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret')); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.rmempty = true; + o.password = true; + + + o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key')); + o.depends('encryption', 'psk'); + o.depends('encryption', 'psk2'); + o.depends('encryption', 'psk+psk2'); + o.depends('encryption', 'psk-mixed'); + o.depends('encryption', 'sae'); + o.depends('encryption', 'sae-mixed'); + o.datatype = 'wpakey'; + o.rmempty = true; + o.password = true; + + o.cfgvalue = function(section_id) { + var key = uci.get('wireless', section_id, 'key'); + return /^[1234]$/.test(key) ? null : key; + }; + + o.write = function(section_id, value) { + uci.set('wireless', section_id, 'key', value); + uci.unset('wireless', section_id, 'key1'); + }; + + + o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot')); + o.depends('encryption', 'wep-open'); + o.depends('encryption', 'wep-shared'); + o.value('1', _('Key #%d').format(1)); + o.value('2', _('Key #%d').format(2)); + o.value('3', _('Key #%d').format(3)); + o.value('4', _('Key #%d').format(4)); + + o.cfgvalue = function(section_id) { + var slot = +uci.get('wireless', section_id, 'key'); + return (slot >= 1 && slot <= 4) ? slot : 1; + }; + + o.write = function(section_id, value) { + uci.set('wireless', section_id, 'key', value); + }; + + for (var slot = 1; slot <= 4; slot++) { + o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot)); + o.depends('encryption', 'wep-open'); + o.depends('encryption', 'wep-shared'); + o.datatype = 'wepkey'; + o.rmempty = true; + o.password = true; + + o.write = function(section_id, value) { + if (value != null && (value.length == 5 || value.length == 13)) + value = 's:%s'.format(value); + uci.set('wireless', section_id, this.option, value); + }; + } + + + if (hwtype == 'mac80211') { + // Probe 802.11r support (and EAP support as a proxy for Openwrt) + var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap'); + + o = ss.taboption('encryption', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain')); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + if (has_80211r) { + o.depends({ mode: 'ap', encryption: 'psk' }); + o.depends({ mode: 'ap', encryption: 'psk2' }); + o.depends({ mode: 'ap', encryption: 'psk-mixed' }); + o.depends({ mode: 'ap', encryption: 'sae' }); + o.depends({ mode: 'ap', encryption: 'sae-mixed' }); + o.depends({ mode: 'ap-wds', encryption: 'psk' }); + o.depends({ mode: 'ap-wds', encryption: 'psk2' }); + o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' }); + o.depends({ mode: 'ap-wds', encryption: 'sae' }); + o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' }); + } + o.rmempty = true; + + o = ss.taboption('encryption', form.Value, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.')); + o.depends({ mode: 'ap', encryption: 'wpa' }); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.depends({ ieee80211r: '1' }); + o.rmempty = true; + + o = ss.taboption('encryption', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID')); + o.depends({ ieee80211r: '1' }); + o.placeholder = '4f57'; + o.datatype = 'and(hexstring,length(4))'; + o.rmempty = true; + + o = ss.taboption('encryption', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]')); + o.depends({ ieee80211r: '1' }); + o.placeholder = '1000'; + o.datatype = 'range(1000,65535)'; + o.rmempty = true; + + o = ss.taboption('encryption', form.ListValue, 'ft_over_ds', _('FT protocol')); + o.depends({ ieee80211r: '1' }); + o.value('1', _('FT over DS')); + o.value('0', _('FT over the Air')); + o.rmempty = true; + + o = ss.taboption('encryption', form.Flag, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.')); + o.depends({ ieee80211r: '1' }); + o.default = o.enabled; + o.rmempty = false; + + o = ss.taboption('encryption', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes')); + o.depends({ ieee80211r: '1' }); + o.placeholder = '10000'; + o.datatype = 'uinteger'; + o.rmempty = true; + + o = ss.taboption('encryption', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons')); + o.depends({ ieee80211r: '1' }); + o.placeholder = '00004f577274'; + o.datatype = 'and(hexstring,length(12))'; + o.rmempty = true; + + o = ss.taboption('encryption', form.Flag, 'pmk_r1_push', _('PMK R1 Push')); + o.depends({ ieee80211r: '1' }); + o.placeholder = '0'; + o.rmempty = true; + + o = ss.taboption('encryption', form.DynamicList, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain. <br />Format: MAC-address,NAS-Identifier,128-bit key as hex string. <br />This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.')); + o.depends({ ieee80211r: '1' }); + o.rmempty = true; + + o = ss.taboption('encryption', form.DynamicList, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain. <br />Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string. <br />This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.')); + o.depends({ ieee80211r: '1' }); + o.rmempty = true; + // End of 802.11r options + + o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method')); + o.value('tls', 'TLS'); + o.value('ttls', 'TTLS'); + o.value('peap', 'PEAP'); + o.value('fast', 'FAST'); + o.depends({ mode: 'sta', encryption: 'wpa' }); + o.depends({ mode: 'sta', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', encryption: 'wpa2' }); + + o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate')); + o.depends({ mode: 'sta', encryption: 'wpa' }); + o.depends({ mode: 'sta', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', encryption: 'wpa2' }); + + o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate')); + o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' }); + + o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key')); + o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' }); + + o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key')); + o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' }); + o.password = true; + + o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication')); + o.value('PAP', 'PAP'); + o.value('CHAP', 'CHAP'); + o.value('MSCHAP', 'MSCHAP'); + o.value('MSCHAPV2', 'MSCHAPv2'); + o.value('EAP-GTC'); + o.value('EAP-MD5'); + o.value('EAP-MSCHAPV2'); + o.value('EAP-TLS'); + o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' }); + + o.validate = function(section_id, value) { + var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0], + ev = eo.formvalue(section_id); + + if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2')) + return _('This authentication type is not applicable to the selected EAP method.'); + + return true; + }; + + o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate')); + o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' }); + o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' }); + + o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate')); + o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' }); + o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' }); + + o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key')); + o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' }); + o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' }); + + o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key')); + o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa' }); + o.depends({ mode: 'sta', auth: 'EAP-TLS', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', auth: 'EAP-TLS', encryption: 'wpa2' }); + o.password = true; + + o = ss.taboption('encryption', form.Value, 'identity', _('Identity')); + o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' }); + + o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity')); + o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'tls', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'tls', encryption: 'wpa' }); + + o = ss.taboption('encryption', form.Value, 'password', _('Password')); + o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'fast', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'peap', encryption: 'wpa' }); + o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa2' }); + o.depends({ mode: 'sta', eap_type: 'ttls', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'fast', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'peap', encryption: 'wpa' }); + o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', eap_type: 'ttls', encryption: 'wpa' }); + o.password = true; + + + if (hwtype == 'mac80211') { + // ieee802.11w options + if (L.hasSystemFeature('hostapd', '11w')) { + o = ss.taboption('encryption', form.ListValue, 'ieee80211w', _('802.11w Management Frame Protection'), _("Requires the 'full' version of wpad/hostapd and support from the wifi driver <br />(as of Jan 2019: ath9k, ath10k, mwlwifi and mt76)")); + o.default = ''; + o.value('', _('Disabled (default)')); + o.value('1', _('Optional')); + o.value('2', _('Required')); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.depends({ mode: 'ap', encryption: 'psk2' }); + o.depends({ mode: 'ap', encryption: 'psk-mixed' }); + o.depends({ mode: 'ap', encryption: 'sae' }); + o.depends({ mode: 'ap', encryption: 'sae-mixed' }); + o.depends({ mode: 'ap', encryption: 'owe' }); + o.depends({ mode: 'ap-wds', encryption: 'psk2' }); + o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' }); + o.depends({ mode: 'ap-wds', encryption: 'sae' }); + o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' }); + o.depends({ mode: 'ap-wds', encryption: 'owe' }); + o.depends({ mode: 'sta', encryption: 'wpa2' }); + o.depends({ mode: 'sta-wds', encryption: 'wpa2' }); + o.depends({ mode: 'sta', encryption: 'psk2' }); + o.depends({ mode: 'sta', encryption: 'psk-mixed' }); + o.depends({ mode: 'sta', encryption: 'sae' }); + o.depends({ mode: 'sta', encryption: 'sae-mixed' }); + o.depends({ mode: 'sta', encryption: 'owe' }); + o.depends({ mode: 'sta-wds', encryption: 'psk2' }); + o.depends({ mode: 'sta-wds', encryption: 'psk-mixed' }); + o.depends({ mode: 'sta-wds', encryption: 'sae' }); + o.depends({ mode: 'sta-wds', encryption: 'sae-mixed' }); + o.depends({ mode: 'sta-wds', encryption: 'owe' }); + + o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout')); + o.depends('ieee80211w', '1'); + o.depends('ieee80211w', '2'); + o.datatype = 'uinteger'; + o.placeholder = '1000'; + o.rmempty = true; + + o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout')); + o.depends('ieee80211w', '1'); + o.depends('ieee80211w', '2'); + o.datatype = 'uinteger'; + o.placeholder = '201'; + o.rmempty = true; + }; + + o = ss.taboption('encryption', form.Flag, 'wpa_disable_eapol_key_retries', _('Enable key reinstallation (KRACK) countermeasures'), _('Complicates key reinstallation attacks on the client side by disabling retransmission of EAPOL-Key frames that are used to install keys. This workaround might cause interoperability issues and reduced robustness of key negotiation especially in environments with heavy traffic load.')); + o.depends({ mode: 'ap', encryption: 'wpa2' }); + o.depends({ mode: 'ap', encryption: 'psk2' }); + o.depends({ mode: 'ap', encryption: 'psk-mixed' }); + o.depends({ mode: 'ap', encryption: 'sae' }); + o.depends({ mode: 'ap', encryption: 'sae-mixed' }); + o.depends({ mode: 'ap-wds', encryption: 'wpa2' }); + o.depends({ mode: 'ap-wds', encryption: 'psk2' }); + o.depends({ mode: 'ap-wds', encryption: 'psk-mixed' }); + o.depends({ mode: 'ap-wds', encryption: 'sae' }); + o.depends({ mode: 'ap-wds', encryption: 'sae-mixed' }); + + if (L.hasSystemFeature('hostapd', 'cli') && L.hasSystemFeature('wpasupplicant')) { + o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK')) + o.enabled = '1'; + o.disabled = '0'; + o.default = o.disabled; + o.depends('encryption', 'psk'); + o.depends('encryption', 'psk2'); + o.depends('encryption', 'psk-mixed'); + } + } + } + }); + }; + + s.handleRemove = function(section_id, ev) { + document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5; + return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]); + }; + + s.handleScan = function(radioDev, ev) { + var table = E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th col-2 middle center' }, _('Signal')), + E('div', { 'class': 'th col-4 middle left' }, _('SSID')), + E('div', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')), + E('div', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')), + E('div', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')), + E('div', { 'class': 'th col-3 middle left' }, _('Encryption')), + E('div', { 'class': 'th cbi-section-actions right' }, ' '), + ]) + ]); + + cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...'))); + + var md = L.ui.showModal(_('Join Network: Wireless Scan'), [ + table, + E('div', { 'class': 'right' }, + E('button', { + 'class': 'btn', + 'click': L.bind(this.handleScanAbort, this) + }, _('Dismiss'))) + ]); + + md.style.maxWidth = '90%'; + md.style.maxHeight = 'none'; + + this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table); + + L.Poll.add(this.pollFn); + L.Poll.start(); + }; + + s.handleScanRefresh = function(radioDev, scanCache, table) { + return radioDev.getScanList().then(L.bind(function(results) { + var rows = []; + + for (var i = 0; i < results.length; i++) + scanCache[results[i].bssid] = results[i]; + + for (var k in scanCache) + if (scanCache[k].stale) + results.push(scanCache[k]); + + results.sort(function(a, b) { + var diff = (b.quality - a.quality) || (a.channel - b.channel); + + if (diff) + return diff; + + if (a.ssid < b.ssid) + return -1; + else if (a.ssid > b.ssid) + return 1; + + if (a.bssid < b.bssid) + return -1; + else if (a.bssid > b.bssid) + return 1; + }); + + for (var i = 0; i < results.length; i++) { + var res = results[i], + qv = res.quality || 0, + qm = res.quality_max || 0, + q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0, + s = res.stale ? 'opacity:0.5' : ''; + + rows.push([ + E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)), + E('span', { 'style': s }, '%h'.format(res.ssid)), + E('span', { 'style': s }, '%d'.format(res.channel)), + E('span', { 'style': s }, '%h'.format(res.mode)), + E('span', { 'style': s }, '%h'.format(res.bssid)), + E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))), + E('div', { 'class': 'right' }, E('button', { + 'class': 'cbi-button cbi-button-action important', + 'click': L.bind(this.handleJoin, this, radioDev, res) + }, _('Join Network'))) + ]); + + res.stale = true; + } + + cbi_update_table(table, rows); + }, this)); + }; + + s.handleScanAbort = function(ev) { + var md = L.dom.parent(ev.target, 'div[aria-modal="true"]'); + if (md) { + md.style.maxWidth = ''; + md.style.maxHeight = ''; + } + + L.ui.hideModal(); + L.Poll.remove(this.pollFn); + + this.pollFn = null; + }; + + s.handleJoinConfirm = function(radioDev, bss, form, ev) { + var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0], + passopt = L.toArray(form.lookupOption('password', '_new_'))[0], + zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0], + replopt = L.toArray(form.lookupOption('replace', '_new_'))[0], + nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null, + passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null, + zoneval = zoneopt ? zoneopt.formvalue('_new_') : null, + enc = L.isObject(bss.encryption) ? bss.encryption : null, + is_wep = (enc && Array.isArray(enc.wep)), + is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk'); + + if (nameval == null || (passopt && passval == null)) + return; + + var section_id = null; + + return this.map.save(function() { + if (replopt.formvalue('_new_') == '1') { + var sections = uci.sections('wireless', 'wifi-iface'); + + for (var i = 0; i < sections.length; i++) + if (sections[i].device == radioDev.getName()) + uci.remove('wireless', sections[i]['.name']); + } + + section_id = next_free_sid(uci.sections('wifi-iface').length); + + uci.add('wireless', 'wifi-iface', section_id); + uci.set('wireless', section_id, 'device', radioDev.getName()); + uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta'); + uci.set('wireless', section_id, 'network', nameval); + + if (bss.ssid != null) + uci.set('wireless', section_id, 'ssid', bss.ssid); + else if (bss.bssid != null) + uci.set('wireless', section_id, 'bssid', bss.bssid); + + if (is_psk) { + for (var i = enc.wpa.length - 1; i >= 0; i--) { + if (enc.wpa[i] == 2) { + uci.set('wireless', section_id, 'encryption', 'psk2'); + break; + } + else if (enc.wpa[i] == 1) { + uci.set('wireless', section_id, 'encryption', 'psk'); + break; + } + } + + uci.set('wireless', section_id, 'key', passval); + } + else if (is_wep) { + uci.set('wireless', section_id, 'encryption', 'wep-open'); + uci.set('wireless', section_id, 'key', '1'); + uci.set('wireless', section_id, 'key1', passval); + } + + var zonePromise = zoneval + ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) }) + : Promise.resolve(); + + return zonePromise.then(function(zone) { + return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) { + firewall.deleteNetwork(net.getName()); + + if (zone) + zone.addNetwork(net.getName()); + }); + }); + }).then(L.bind(function() { + return this.renderMoreOptionsModal(section_id); + }, this)); + }; + + s.handleJoin = function(radioDev, bss, ev) { + this.handleScanAbort(ev); + + var m2 = new form.Map('wireless'), + s2 = m2.section(form.NamedSection, '_new_'), + enc = L.isObject(bss.encryption) ? bss.encryption : null, + is_wep = (enc && Array.isArray(enc.wep)), + is_psk = (enc && Array.isArray(enc.wpa) && Array.isArray(enc.authentication) && enc.authentication[0] == 'psk'), + replace, passphrase, name, zone; + + s2.render = function() { + return Promise.all([ + {}, + this.renderUCISection('_new_') + ]).then(this.renderContents.bind(this)); + }; + + replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.')); + + name = s2.option(form.Value, 'name', _('Name of the new network'), _('The allowed characters are: <code>A-Z</code>, <code>a-z</code>, <code>0-9</code> and <code>_</code>')); + name.datatype = 'uciname'; + name.default = 'wwan'; + name.rmempty = false; + name.validate = function(section_id, value) { + if (uci.get('network', value)) + return _('The network name is already used'); + + return true; + }; + + for (var i = 2; uci.get('network', name.default); i++) + name.default = 'wwan%d'.format(i); + + if (is_wep || is_psk) { + passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.')); + passphrase.datatype = is_wep ? 'wepkey' : 'wpakey'; + passphrase.password = true; + passphrase.rmempty = false; + } + + zone = s2.option(widgets.ZoneSelect, 'zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the create field to define a new zone and attach the interface to it.')); + zone.default = 'wan'; + + return m2.render().then(L.bind(function(nodes) { + L.ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [ + nodes, + E('div', { 'class': 'right' }, [ + E('button', { + 'class': 'btn', + 'click': L.ui.hideModal + }, _('Cancel')), ' ', + E('button', { + 'class': 'cbi-button cbi-button-positive important', + 'click': L.ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2) + }, _('Submit')) + ]) + ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus(); + }, this)); + }; + + s.handleAdd = function(radioDev, ev) { + var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length); + + uci.unset('wireless', radioDev.getName(), 'disabled'); + + uci.add('wireless', 'wifi-iface', section_id); + uci.set('wireless', section_id, 'device', radioDev.getName()); + uci.set('wireless', section_id, 'mode', 'ap'); + uci.set('wireless', section_id, 'ssid', 'OpenWrt'); + uci.set('wireless', section_id, 'encryption', 'none'); + + this.addedSection = section_id; + return this.renderMoreOptionsModal(section_id); + }; + + o = s.option(form.DummyValue, '_badge'); + o.modalonly = false; + o.textvalue = function(section_id) { + var inst = this.section.lookupRadioOrNetwork(section_id), + node = E('div', { 'class': 'center' }); + + if (inst.getWifiNetworks) + node.appendChild(render_radio_badge(inst)); + else + node.appendChild(render_network_badge(inst)); + + return node; + }; + + o = s.option(form.DummyValue, '_stat'); + o.modalonly = false; + o.textvalue = function(section_id) { + var inst = this.section.lookupRadioOrNetwork(section_id); + + if (inst.getWifiNetworks) + return render_radio_status(inst, this.section.wifis.filter(function(e) { + return (e.getWifiDeviceName() == inst.getName()); + })); + else + return render_network_status(inst); + }; + + return m.render().then(L.bind(function(m, nodes) { + L.Poll.add(L.bind(function() { + var section_ids = m.children[0].cfgsections(), + tasks = [ network.getHostHints(), network.getWifiDevices() ]; + + for (var i = 0; i < section_ids.length; i++) { + var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])), + dsc = row.querySelector('[data-name="_stat"] > div'), + btns = row.querySelectorAll('.cbi-section-actions button'); + + if (dsc.getAttribute('restart') == '') { + dsc.setAttribute('restart', '1'); + tasks.push(L.Request.post( + L.url('admin/network/wireless_reconnect', section_ids[i]), + 'token=' + L.env.token, + { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } + ).catch(function() {})); + } + else if (dsc.getAttribute('restart') == '1') { + dsc.removeAttribute('restart'); + btns[0].classList.remove('spinning'); + btns[0].disabled = false; + } + } + + return Promise.all(tasks) + .then(L.bind(function(hosts_radios) { + var tasks = []; + + for (var i = 0; i < hosts_radios[1].length; i++) + tasks.push(hosts_radios[1][i].getWifiNetworks()); + + return Promise.all(tasks).then(function(data) { + hosts_radios[2] = []; + + for (var i = 0; i < data.length; i++) + hosts_radios[2].push.apply(hosts_radios[2], data[i]); + + return hosts_radios; + }); + }, network)) + .then(L.bind(function(hosts_radios_wifis) { + var tasks = []; + + for (var i = 0; i < hosts_radios_wifis[2].length; i++) + tasks.push(hosts_radios_wifis[2][i].getAssocList()); + + return Promise.all(tasks).then(function(data) { + hosts_radios_wifis[3] = []; + + for (var i = 0; i < data.length; i++) { + var wifiNetwork = hosts_radios_wifis[2][i], + radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0]; + + for (var j = 0; j < data[i].length; j++) + hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j])); + } + + return hosts_radios_wifis; + }); + }, network)) + .then(L.bind(this.poll_status, this, nodes)); + }, this), 5); + + var table = E('div', { 'class': 'table', 'id': 'wifi_assoclist_table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th nowrap' }, _('Network')), + E('div', { 'class': 'th hide-xs' }, _('MAC-Address')), + E('div', { 'class': 'th nowrap' }, _('Host')), + E('div', { 'class': 'th nowrap' }, _('Signal / Noise')), + E('div', { 'class': 'th nowrap' }, _('RX Rate / TX Rate')) + ]) + ]); + + cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...'))) + + return E([ nodes, E('h3', _('Associated Stations')), table ]); + }, this, m)); } -); +}); diff --git a/modules/luci-mod-network/luasrc/controller/admin/network.lua b/modules/luci-mod-network/luasrc/controller/admin/network.lua index 4578d1257..a381bbc61 100644 --- a/modules/luci-mod-network/luasrc/controller/admin/network.lua +++ b/modules/luci-mod-network/luasrc/controller/admin/network.lua @@ -37,42 +37,14 @@ function index() end) if has_wifi then - page = entry({"admin", "network", "wireless_join"}, post("wifi_join"), nil) - page.leaf = true - - page = entry({"admin", "network", "wireless_add"}, post("wifi_add"), nil) - page.leaf = true - page = entry({"admin", "network", "wireless_status"}, call("wifi_status"), nil) page.leaf = true page = entry({"admin", "network", "wireless_reconnect"}, post("wifi_reconnect"), nil) page.leaf = true - page = entry({"admin", "network", "wireless_scan_trigger"}, post("wifi_scan_trigger"), nil) + page = entry({"admin", "network", "wireless"}, view("network/wireless"), _('Wireless'), 15) page.leaf = true - - page = entry({"admin", "network", "wireless_scan_results"}, call("wifi_scan_results"), nil) - page.leaf = true - - page = entry({"admin", "network", "wireless"}, arcombine(cbi("admin_network/wifi_overview"), cbi("admin_network/wifi")), _("Wireless"), 15) - page.leaf = true - page.subindex = true - - if page.inreq then - local wdev - local net = require "luci.model.network".init(uci) - for _, wdev in ipairs(net:get_wifidevs()) do - local wnet - for _, wnet in ipairs(wdev:get_wifinets()) do - entry( - {"admin", "network", "wireless", wnet:id()}, - alias("admin", "network", "wireless"), - wdev:name() .. ": " .. wnet:shortname() - ) - end - end - end end @@ -129,50 +101,6 @@ function index() -- end end -function wifi_join() - local tpl = require "luci.template" - local http = require "luci.http" - local dev = http.formvalue("device") - local ssid = http.formvalue("join") - - if dev and ssid then - local cancel = (http.formvalue("cancel") or http.formvalue("cbi.cancel")) - if not cancel then - local cbi = require "luci.cbi" - local map = luci.cbi.load("admin_network/wifi_add")[1] - - if map:parse() ~= cbi.FORM_DONE then - tpl.render("header") - map:render() - tpl.render("footer") - end - - return - end - end - - tpl.render("admin_network/wifi_join") -end - -function wifi_add() - local dev = luci.http.formvalue("device") - local ntm = require "luci.model.network".init() - - dev = dev and ntm:get_wifidev(dev) - - if dev then - local net = dev:add_wifinet({ - mode = "ap", - ssid = "OpenWrt", - encryption = "none", - disabled = 1 - }) - - ntm:save("wireless") - luci.http.redirect(net:adminlink()) - end -end - function iface_status(ifaces) local netm = require "luci.model.network".init() local rv = { } @@ -334,7 +262,7 @@ function wifi_status(devs) end function wifi_reconnect(radio) - local rc = luci.sys.call("env -i /sbin/wifi up %s" % luci.util.shellquote(radio)) + local rc = luci.sys.call("env -i /sbin/wifi up %s >/dev/null" % luci.util.shellquote(radio)) if rc == 0 then luci.http.status(200, "Reconnected") @@ -343,80 +271,6 @@ function wifi_reconnect(radio) end end -local function _wifi_get_scan_results(cache_key) - local results = luci.util.ubus("session", "get", { - ubus_rpc_session = luci.model.uci:get_session_id(), - keys = { cache_key } - }) - - if type(results) == "table" and - type(results.values) == "table" and - type(results.values[cache_key]) == "table" - then - return results.values[cache_key] - end - - return nil -end - -function wifi_scan_trigger(radio, update) - local iw = radio and luci.sys.wifi.getiwinfo(radio) - - if not iw then - luci.http.status(404, "No such radio device") - return - end - - luci.http.status(204, "Scan scheduled") - - if nixio.fork() == 0 then - io.stderr:close() - io.stdout:close() - - local _, bss - local data, bssids = { }, { } - local cache_key = "scan_%s" % radio - - luci.util.ubus("session", "set", { - ubus_rpc_session = luci.model.uci:get_session_id(), - values = { [cache_key] = nil } - }) - - for _, bss in ipairs(iw.scanlist or { }) do - data[_] = bss - bssids[bss.bssid] = bss - end - - if update then - local cached = _wifi_get_scan_results(cache_key) - if cached then - for _, bss in ipairs(cached) do - if not bssids[bss.bssid] then - bss.stale = true - data[#data + 1] = bss - end - end - end - end - - luci.util.ubus("session", "set", { - ubus_rpc_session = luci.model.uci:get_session_id(), - values = { [cache_key] = data } - }) - end -end - -function wifi_scan_results(radio) - local results = radio and _wifi_get_scan_results("scan_%s" % radio) - - if results then - luci.http.prepare_content("application/json") - luci.http.write_json(results) - else - luci.http.status(404, "No wireless scan results") - end -end - function switch_status(switches) local s = require "luci.tools.status" diff --git a/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi.lua b/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi.lua deleted file mode 100644 index 2e6c026bb..000000000 --- a/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi.lua +++ /dev/null @@ -1,1216 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Licensed to the public under the Apache License 2.0. - -local wa = require "luci.tools.webadmin" -local nw = require "luci.model.network" -local ut = require "luci.util" -local nt = require "luci.sys".net -local fs = require "nixio.fs" - -local acct_port, acct_secret, acct_server, anonymous_identity, ant1, ant2, - auth, auth_port, auth_secret, auth_server, bssid, cacert, cacert2, - cc, ch, cipher, clientcert, clientcert2, ea, eaptype, en, encr, - ft_protocol, ft_psk_generate_local, hidden, htmode, identity, - ieee80211r, ieee80211w, ifname, isolate, key_retries, - legacyrates, max_timeout, meshfwd, meshid, ml, mobility_domain, mode, - mp, nasid, network, password, pmk_r1_push, privkey, privkey2, privkeypwd, - privkeypwd2, r0_key_lifetime, r0kh, r1_key_holder, r1kh, - reassociation_deadline, retry_timeout, ssid, st, tp, wepkey, wepslot, - wmm, wpakey, wps, disassoc_low_ack, short_preamble, beacon_int, dtim_period, - wparekey, inactivitypool, maxinactivity, listeninterval, - dae_client, dae_port, dae_port - - -arg[1] = arg[1] or "" - -m = Map("wireless", "", - translate("The <em>Device Configuration</em> section covers physical settings of the radio " .. - "hardware such as channel, transmit power or antenna selection which are shared among all " .. - "defined wireless networks (if the radio hardware is multi-SSID capable). Per network settings " .. - "like encryption or operation mode are grouped in the <em>Interface Configuration</em>.")) - -m:chain("network") -m:chain("firewall") -m.redirect = luci.dispatcher.build_url("admin/network/wireless") - -nw.init(m.uci) - -local wnet = nw:get_wifinet(arg[1]) -local wdev = wnet and wnet:get_device() - --- redirect to overview page if network does not exist anymore (e.g. after a revert) -if not wnet or not wdev then - luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless")) - return -end - -local function txpower_list(iw) - local list = iw.txpwrlist or { } - local off = tonumber(iw.txpower_offset) or 0 - local new = { } - local prev = -1 - local _, val - for _, val in ipairs(list) do - local dbm = val.dbm + off - local mw = math.floor(10 ^ (dbm / 10)) - if mw ~= prev then - prev = mw - new[#new+1] = { - display_dbm = dbm, - display_mw = mw, - driver_dbm = val.dbm, - driver_mw = val.mw - } - end - end - return new -end - -local function txpower_current(pwr, list) - pwr = tonumber(pwr) - if pwr ~= nil then - local _, item - for _, item in ipairs(list) do - if item.driver_dbm >= pwr then - return item.driver_dbm - end - end - end - return pwr or "" -end - -local iw = luci.sys.wifi.getiwinfo(arg[1]) -local hw_modes = iw.hwmodelist or { } -local tx_power_list = txpower_list(iw) -local tx_power_cur = txpower_current(wdev:get("txpower"), tx_power_list) - --- wireless toggle was requested, commit and reload page -function m.parse(map) - local new_cc = m:formvalue("cbid.wireless.%s.country" % wdev:name()) - local old_cc = m:get(wdev:name(), "country") - - if m:formvalue("cbid.wireless.%s.__toggle" % wdev:name()) then - if wdev:get("disabled") == "1" or wnet:get("disabled") == "1" then - wnet:set("disabled", nil) - else - wnet:set("disabled", "1") - end - wdev:set("disabled", nil) - m.apply_needed = true - m.redirect = nil - end - - Map.parse(map) - - if m:get(wdev:name(), "type") == "mac80211" and new_cc and new_cc ~= old_cc then - luci.sys.call("iw reg set %s" % ut.shellquote(new_cc)) - - local old_ch = tonumber(m:formvalue("cbid.wireless.%s._mode_freq.channel" % wdev:name()) or "") - if old_ch then - local _, c, new_ch - for _, c in ipairs(iw.freqlist) do - if c.channel > old_ch or (old_ch <= 14 and c.channel > 14) then - break - end - new_ch = c.channel - end - if new_ch ~= old_ch then - wdev:set("channel", new_ch) - m.message = translatef("Channel %d is not available in the %s regulatory domain and has been auto-adjusted to %d.", - old_ch, new_cc, new_ch) - end - end - end - - if wdev:get("disabled") == "1" or wnet:get("disabled") == "1" then - en.title = translate("Wireless network is disabled") - en.inputtitle = translate("Enable") - en.inputstyle = "apply" - else - en.title = translate("Wireless network is enabled") - en.inputtitle = translate("Disable") - en.inputstyle = "reset" - end -end - -m.title = luci.util.pcdata(wnet:get_i18n()) - -s = m:section(NamedSection, wdev:name(), "wifi-device", translate("Device Configuration")) -s.addremove = false - -s:tab("general", translate("General Setup")) -s:tab("macfilter", translate("MAC-Filter")) -s:tab("advanced", translate("Advanced Settings")) - -st = s:taboption("general", DummyValue, "__status", translate("Status")) -st.template = "admin_network/wifi_status" -st.ifname = arg[1] - -en = s:taboption("general", Button, "__toggle") - -local hwtype = wdev:get("type") - --- NanoFoo -local nsantenna = wdev:get("antenna") - --- Check whether there are client interfaces on the same radio, --- if yes, lock the channel choice as these stations will dicatate the freq -local found_sta = nil -local _, net -if wnet:mode() ~= "sta" then - for _, net in ipairs(wdev:get_wifinets()) do - if net:mode() == "sta" and net:get("disabled") ~= "1" then - if not found_sta then - found_sta = {} - found_sta.channel = net:channel() - found_sta.names = {} - end - found_sta.names[#found_sta.names+1] = net:shortname() - end - end -end - -if found_sta then - ch = s:taboption("general", DummyValue, "choice", translate("Channel")) - ch.value = translatef("Locked to channel %s used by: %s", - found_sta.channel or "(auto)", table.concat(found_sta.names, ", ")) -else - ch = s:taboption("general", Value, "_mode_freq", '<br />'..translate("Operating frequency")) - ch.iwinfo = iw - ch.hostapd_acs = (os.execute("hostapd -vacs >/dev/null 2>/dev/null") == 0) - ch.template = "cbi/wireless_modefreq" - - function ch.cfgvalue(self, section) - return { - m:get(section, "hwmode") or "", - m:get(section, "channel") or "auto", - m:get(section, "htmode") or "" - } - end - - function ch.formvalue(self, section) - return { - m:formvalue(self:cbid(section) .. ".band") or (hw_modes.g and "11g" or "11a"), - m:formvalue(self:cbid(section) .. ".channel") or "auto", - m:formvalue(self:cbid(section) .. ".htmode") or "" - } - end - - function ch.write(self, section, value) - m:set(section, "hwmode", value[1]) - m:set(section, "channel", value[2]) - m:set(section, "htmode", value[3]) - end -end - -------------------- MAC80211 Device ------------------ - -if hwtype == "mac80211" then - if #tx_power_list > 0 then - tp = s:taboption("general", ListValue, - "txpower", translate("Transmit Power"), "dBm") - tp.rmempty = true - tp.default = tx_power_cur - function tp.cfgvalue(...) - return txpower_current(Value.cfgvalue(...), tx_power_list) - end - - tp:value("", translate("auto")) - for _, p in ipairs(tx_power_list) do - tp:value(p.driver_dbm, "%i dBm (%i mW)" - %{ p.display_dbm, p.display_mw }) - end - end - - local cl = iw and iw.countrylist - if cl and #cl > 0 then - cc = s:taboption("advanced", ListValue, "country", translate("Country Code"), translate("Use ISO/IEC 3166 alpha2 country codes.")) - cc.default = tostring(iw and iw.country or "00") - for _, c in ipairs(cl) do - cc:value(c.alpha2, "%s - %s" %{ c.alpha2, c.name }) - end - else - s:taboption("advanced", Value, "country", translate("Country Code"), translate("Use ISO/IEC 3166 alpha2 country codes.")) - end - - legacyrates = s:taboption("advanced", Flag, "legacy_rates", translate("Allow legacy 802.11b rates")) - legacyrates.rmempty = false - legacyrates.default = "1" - - s:taboption("advanced", Value, "distance", translate("Distance Optimization"), - translate("Distance to farthest network member in meters.")) - - -- external antenna profiles - local eal = iw and iw.extant - if eal and #eal > 0 then - ea = s:taboption("advanced", ListValue, "extant", translate("Antenna Configuration")) - for _, eap in ipairs(eal) do - ea:value(eap.id, "%s (%s)" %{ eap.name, eap.description }) - if eap.selected then - ea.default = eap.id - end - end - end - - s:taboption("advanced", Value, "frag", translate("Fragmentation Threshold")) - s:taboption("advanced", Value, "rts", translate("RTS/CTS Threshold")) - - s:taboption("advanced", Flag, "noscan", translate("Force 40MHz mode"), - translate("Always use 40MHz channels even if the secondary channel overlaps. Using this option does not comply with IEEE 802.11n-2009!")).optional = true - - beacon_int = s:taboption("advanced", Value, "beacon_int", translate("Beacon Interval")) - beacon_int.optional = true - beacon_int.placeholder = 100 - beacon_int.datatype = "range(15,65535)" -end - - -------------------- Broadcom Device ------------------ - -if hwtype == "broadcom" then - tp = s:taboption("general", - (#tx_power_list > 0) and ListValue or Value, - "txpower", translate("Transmit Power"), "dBm") - - tp.rmempty = true - tp.default = tx_power_cur - - function tp.cfgvalue(...) - return txpower_current(Value.cfgvalue(...), tx_power_list) - end - - tp:value("", translate("auto")) - for _, p in ipairs(tx_power_list) do - tp:value(p.driver_dbm, "%i dBm (%i mW)" - %{ p.display_dbm, p.display_mw }) - end - - mode = s:taboption("advanced", ListValue, "hwmode", translate("Band")) - if hw_modes.b then - mode:value("11b", "2.4GHz (802.11b)") - if hw_modes.g then - mode:value("11bg", "2.4GHz (802.11b+g)") - end - end - if hw_modes.g then - mode:value("11g", "2.4GHz (802.11g)") - mode:value("11gst", "2.4GHz (802.11g + Turbo)") - mode:value("11lrs", "2.4GHz (802.11g Limited Rate Support)") - end - if hw_modes.a then mode:value("11a", "5GHz (802.11a)") end - if hw_modes.n then - if hw_modes.g then - mode:value("11ng", "2.4GHz (802.11g+n)") - mode:value("11n", "2.4GHz (802.11n)") - end - if hw_modes.a then - mode:value("11na", "5GHz (802.11a+n)") - mode:value("11n", "5GHz (802.11n)") - end - htmode = s:taboption("advanced", ListValue, "htmode", translate("HT mode (802.11n)")) - htmode:depends("hwmode", "11ng") - htmode:depends("hwmode", "11na") - htmode:depends("hwmode", "11n") - htmode:value("HT20", "20MHz") - htmode:value("HT40", "40MHz") - end - - ant1 = s:taboption("advanced", ListValue, "txantenna", translate("Transmitter Antenna")) - ant1.widget = "radio" - ant1:depends("diversity", "") - ant1:value("3", translate("auto")) - ant1:value("0", translate("Antenna 1")) - ant1:value("1", translate("Antenna 2")) - - ant2 = s:taboption("advanced", ListValue, "rxantenna", translate("Receiver Antenna")) - ant2.widget = "radio" - ant2:depends("diversity", "") - ant2:value("3", translate("auto")) - ant2:value("0", translate("Antenna 1")) - ant2:value("1", translate("Antenna 2")) - - s:taboption("advanced", Flag, "frameburst", translate("Frame Bursting")) - - s:taboption("advanced", Value, "distance", translate("Distance Optimization")) - --s:option(Value, "slottime", translate("Slot time")) - - s:taboption("advanced", Value, "country", translate("Country Code")) - s:taboption("advanced", Value, "maxassoc", translate("Connection Limit")) -end - - ---------------------- HostAP Device --------------------- - -if hwtype == "prism2" then - s:taboption("advanced", Value, "txpower", translate("Transmit Power"), "att units").rmempty = true - - s:taboption("advanced", Flag, "diversity", translate("Diversity")).rmempty = false - - s:taboption("advanced", Value, "txantenna", translate("Transmitter Antenna")) - s:taboption("advanced", Value, "rxantenna", translate("Receiver Antenna")) -end - - ------------------------ Interface ----------------------- - -s = m:section(NamedSection, wnet.sid, "wifi-iface", translate("Interface Configuration")) -s.addremove = false -s.anonymous = true -s.defaults.device = wdev:name() - -s:tab("general", translate("General Setup")) -s:tab("encryption", translate("Wireless Security")) -s:tab("macfilter", translate("MAC-Filter")) -s:tab("advanced", translate("Advanced Settings")) - -mode = s:taboption("general", ListValue, "mode", translate("Mode")) -mode.override_values = true -mode:value("ap", translate("Access Point")) -mode:value("sta", translate("Client")) -mode:value("adhoc", translate("Ad-Hoc")) - -meshid = s:taboption("general", Value, "mesh_id", translate("Mesh Id")) -meshid:depends({mode="mesh"}) - -meshfwd = s:taboption("advanced", Flag, "mesh_fwding", translate("Forward mesh peer traffic")) -meshfwd.rmempty = false -meshfwd.default = "1" -meshfwd:depends({mode="mesh"}) - -mesh_rssi_th = s:taboption("advanced", Value, "mesh_rssi_threshold", - translate("RSSI threshold for joining"), - translate("0 = not using RSSI threshold, 1 = do not change driver default")) -mesh_rssi_th.rmempty = false -mesh_rssi_th.default = "0" -mesh_rssi_th.datatype = "range(-255,1)" -mesh_rssi_th:depends({mode="mesh"}) - -ssid = s:taboption("general", Value, "ssid", translate("<abbr title=\"Extended Service Set Identifier\">ESSID</abbr>")) -ssid.datatype = "maxlength(32)" -ssid:depends({mode="ap"}) -ssid:depends({mode="sta"}) -ssid:depends({mode="adhoc"}) -ssid:depends({mode="ahdemo"}) -ssid:depends({mode="monitor"}) -ssid:depends({mode="ap-wds"}) -ssid:depends({mode="sta-wds"}) -ssid:depends({mode="wds"}) - -bssid = s:taboption("general", Value, "bssid", translate("<abbr title=\"Basic Service Set Identifier\">BSSID</abbr>")) -bssid.datatype = "macaddr" - -network = s:taboption("general", Value, "network", translate("Network"), - translate("Choose the network(s) you want to attach to this wireless interface or " .. - "fill out the <em>create</em> field to define a new network.")) - -network.rmempty = true -network.template = "cbi/network_netlist" -network.widget = "checkbox" -network.novirtual = true - -function network.write(self, section, value) - local i = nw:get_interface(section) - if i then - local _, net, old, new = nil, nil, {}, {} - - for _, net in ipairs(i:get_networks()) do - old[net:name()] = true - end - - for net in ut.imatch(value) do - new[net] = true - if not old[net] then - local n = nw:get_network(net) or nw:add_network(net, { proto = "none" }) - if n then - if not n:is_empty() then - n:set("type", "bridge") - end - n:add_interface(i) - end - end - end - - for net, _ in pairs(old) do - if not new[net] then - local n = nw:get_network(net) - if n then - n:del_interface(i) - end - end - end - end -end - --------------------- MAC80211 Interface ---------------------- - -if hwtype == "mac80211" then - if fs.access("/usr/sbin/iw") then - mode:value("mesh", "802.11s") - end - - mode:value("ahdemo", translate("Pseudo Ad-Hoc (ahdemo)")) - mode:value("monitor", translate("Monitor")) - bssid:depends({mode="adhoc"}) - bssid:depends({mode="sta"}) - bssid:depends({mode="sta-wds"}) - - mp = s:taboption("macfilter", ListValue, "macfilter", translate("MAC-Address Filter")) - mp:depends({mode="ap"}) - mp:depends({mode="ap-wds"}) - mp:value("", translate("disable")) - mp:value("allow", translate("Allow listed only")) - mp:value("deny", translate("Allow all except listed")) - - ml = s:taboption("macfilter", DynamicList, "maclist", translate("MAC-List")) - ml.datatype = "macaddr" - ml:depends({macfilter="allow"}) - ml:depends({macfilter="deny"}) - nt.mac_hints(function(mac, name) ml:value(mac, "%s (%s)" %{ mac, name }) end) - - mode:value("ap-wds", "%s (%s)" % {translate("Access Point"), translate("WDS")}) - mode:value("sta-wds", "%s (%s)" % {translate("Client"), translate("WDS")}) - - function mode.write(self, section, value) - if value == "ap-wds" then - ListValue.write(self, section, "ap") - m.uci:set("wireless", section, "wds", 1) - elseif value == "sta-wds" then - ListValue.write(self, section, "sta") - m.uci:set("wireless", section, "wds", 1) - else - ListValue.write(self, section, value) - m.uci:delete("wireless", section, "wds") - end - end - - function mode.cfgvalue(self, section) - local mode = ListValue.cfgvalue(self, section) - local wds = m.uci:get("wireless", section, "wds") == "1" - - if mode == "ap" and wds then - return "ap-wds" - elseif mode == "sta" and wds then - return "sta-wds" - else - return mode - end - end - - hidden = s:taboption("general", Flag, "hidden", translate("Hide <abbr title=\"Extended Service Set Identifier\">ESSID</abbr>")) - hidden:depends({mode="ap"}) - hidden:depends({mode="ap-wds"}) - - wmm = s:taboption("general", Flag, "wmm", translate("WMM Mode")) - wmm:depends({mode="ap"}) - wmm:depends({mode="ap-wds"}) - wmm.default = wmm.enabled - - isolate = s:taboption("advanced", Flag, "isolate", translate("Isolate Clients"), - translate("Prevents client-to-client communication")) - isolate:depends({mode="ap"}) - isolate:depends({mode="ap-wds"}) - - ifname = s:taboption("advanced", Value, "ifname", translate("Interface name"), translate("Override default interface name")) - ifname.optional = true - - short_preamble = s:taboption("advanced", Flag, "short_preamble", translate("Short Preamble")) - short_preamble.default = short_preamble.enabled - - dtim_period = s:taboption("advanced", Value, "dtim_period", translate("DTIM Interval"), translate("Delivery Traffic Indication Message Interval")) - dtim_period.optional = true - dtim_period.placeholder = 2 - dtim_period.datatype = "range(1,255)" - - - wparekey = s:taboption("advanced", Value, "wpa_group_rekey", translate("Time interval for rekeying GTK"), translate("sec")) - wparekey.optional = true - wparekey.placeholder = 600 - wparekey.datatype = "uinteger" - - inactivitypool = s:taboption("advanced", Flag , "skip_inactivity_poll", translate("Disable Inactivity Polling")) - inactivitypool.optional = true - inactivitypool.datatype = "uinteger" - - maxinactivity = s:taboption("advanced", Value, "max_inactivity", translate("Station inactivity limit"), translate("sec")) - maxinactivity.optional = true - maxinactivity.placeholder = 300 - maxinactivity.datatype = "uinteger" - - listeninterval = s:taboption("advanced", Value, "max_listen_interval", translate("Maximum allowed Listen Interval")) - listeninterval.optional = true - listeninterval.placeholder = 65535 - listeninterval.datatype = "uinteger" - - disassoc_low_ack = s:taboption("advanced", Flag, "disassoc_low_ack", translate("Disassociate On Low Acknowledgement"), - translate("Allow AP mode to disconnect STAs based on low ACK condition")) - disassoc_low_ack.default = disassoc_low_ack.enabled -end - - --------------------- Broadcom Interface ---------------------- - -if hwtype == "broadcom" then - mode:value("wds", translate("WDS")) - mode:value("monitor", translate("Monitor")) - - hidden = s:taboption("general", Flag, "hidden", translate("Hide <abbr title=\"Extended Service Set Identifier\">ESSID</abbr>")) - hidden:depends({mode="ap"}) - hidden:depends({mode="adhoc"}) - hidden:depends({mode="wds"}) - - isolate = s:taboption("advanced", Flag, "isolate", translate("Separate Clients"), - translate("Prevents client-to-client communication")) - isolate:depends({mode="ap"}) - - s:taboption("advanced", Flag, "doth", "802.11h") - s:taboption("advanced", Flag, "wmm", translate("WMM Mode")) - - bssid:depends({mode="wds"}) - bssid:depends({mode="adhoc"}) -end - - ------------------------ HostAP Interface --------------------- - -if hwtype == "prism2" then - mode:value("wds", translate("WDS")) - mode:value("monitor", translate("Monitor")) - - hidden = s:taboption("general", Flag, "hidden", translate("Hide <abbr title=\"Extended Service Set Identifier\">ESSID</abbr>")) - hidden:depends({mode="ap"}) - hidden:depends({mode="adhoc"}) - hidden:depends({mode="wds"}) - - bssid:depends({mode="sta"}) - - mp = s:taboption("macfilter", ListValue, "macpolicy", translate("MAC-Address Filter")) - mp:value("", translate("disable")) - mp:value("allow", translate("Allow listed only")) - mp:value("deny", translate("Allow all except listed")) - ml = s:taboption("macfilter", DynamicList, "maclist", translate("MAC-List")) - ml:depends({macpolicy="allow"}) - ml:depends({macpolicy="deny"}) - nt.mac_hints(function(mac, name) ml:value(mac, "%s (%s)" %{ mac, name }) end) - - s:taboption("advanced", Value, "rate", translate("Transmission Rate")) - s:taboption("advanced", Value, "frag", translate("Fragmentation Threshold")) - s:taboption("advanced", Value, "rts", translate("RTS/CTS Threshold")) -end - - -------------------- WiFI-Encryption ------------------- - -encr = s:taboption("encryption", ListValue, "encryption", translate("Encryption")) -encr.override_values = true -encr.override_depends = true -encr:depends({mode="ap"}) -encr:depends({mode="sta"}) -encr:depends({mode="adhoc"}) -encr:depends({mode="ahdemo"}) -encr:depends({mode="ap-wds"}) -encr:depends({mode="sta-wds"}) -encr:depends({mode="mesh"}) - -cipher = s:taboption("encryption", ListValue, "cipher", translate("Cipher")) -cipher:depends({encryption="wpa"}) -cipher:depends({encryption="wpa2"}) -cipher:depends({encryption="psk"}) -cipher:depends({encryption="psk2"}) -cipher:depends({encryption="wpa-mixed"}) -cipher:depends({encryption="psk-mixed"}) -cipher:value("auto", translate("auto")) -cipher:value("ccmp", translate("Force CCMP (AES)")) -cipher:value("tkip", translate("Force TKIP")) -cipher:value("tkip+ccmp", translate("Force TKIP and CCMP (AES)")) - -function encr.cfgvalue(self, section) - local v = tostring(ListValue.cfgvalue(self, section)) - if v == "wep" then - return "wep-open" - elseif v and v:match("%+") then - return (v:gsub("%+.+$", "")) - end - return v -end - -function encr.write(self, section, value) - local e = tostring(encr:formvalue(section)) - local c = tostring(cipher:formvalue(section)) - if value == "wpa" or value == "wpa2" then - self.map.uci:delete("wireless", section, "key") - end - if e and (c == "tkip" or c == "ccmp" or c == "tkip+ccmp") then - e = e .. "+" .. c - end - self.map:set(section, "encryption", e) -end - -function cipher.cfgvalue(self, section) - local v = tostring(ListValue.cfgvalue(encr, section)) - if v and v:match("%+") then - v = v:gsub("^[^%+]+%+", "") - if v == "aes" then v = "ccmp" - elseif v == "tkip+aes" then v = "tkip+ccmp" - elseif v == "aes+tkip" then v = "tkip+ccmp" - elseif v == "ccmp+tkip" then v = "tkip+ccmp" - end - end - return v -end - -function cipher.write(self, section) - return encr:write(section) -end - - -encr:value("none", "No Encryption") -encr:value("wep-open", translate("WEP Open System"), {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}, {mode="ahdemo"}, {mode="wds"}) -encr:value("wep-shared", translate("WEP Shared Key"), {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}, {mode="ahdemo"}, {mode="wds"}) - -if hwtype == "mac80211" or hwtype == "prism2" then - local supplicant = fs.access("/usr/sbin/wpa_supplicant") - local hostapd = fs.access("/usr/sbin/hostapd") - - -- Probe EAP support - local has_ap_eap = (os.execute("hostapd -veap >/dev/null 2>/dev/null") == 0) - local has_sta_eap = (os.execute("wpa_supplicant -veap >/dev/null 2>/dev/null") == 0) - - -- Probe SAE support - local has_ap_sae = (os.execute("hostapd -vsae >/dev/null 2>/dev/null") == 0) - local has_sta_sae = (os.execute("wpa_supplicant -vsae >/dev/null 2>/dev/null") == 0) - - -- Probe OWE support - local has_ap_owe = (os.execute("hostapd -vowe >/dev/null 2>/dev/null") == 0) - local has_sta_owe = (os.execute("wpa_supplicant -vowe >/dev/null 2>/dev/null") == 0) - - if hostapd and supplicant then - encr:value("psk", "WPA-PSK", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}) - encr:value("psk2", "WPA2-PSK", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}) - encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}) - if has_ap_sae and has_sta_sae then - encr:value("sae", "WPA3-SAE", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}, {mode="mesh"}) - encr:value("sae-mixed", "WPA2-PSK/WPA3-SAE Mixed Mode", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}) - end - if has_ap_eap and has_sta_eap then - encr:value("wpa", "WPA-EAP", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}) - encr:value("wpa2", "WPA2-EAP", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}) - end - if has_ap_owe and has_sta_owe then - encr:value("owe", "OWE", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}) - end - elseif hostapd and not supplicant then - encr:value("psk", "WPA-PSK", {mode="ap"}, {mode="ap-wds"}) - encr:value("psk2", "WPA2-PSK", {mode="ap"}, {mode="ap-wds"}) - encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode", {mode="ap"}, {mode="ap-wds"}) - if has_ap_sae then - encr:value("sae", "WPA3-SAE", {mode="ap"}, {mode="ap-wds"}) - encr:value("sae-mixed", "WPA2-PSK/WPA3-SAE Mixed Mode", {mode="ap"}, {mode="ap-wds"}) - end - if has_ap_eap then - encr:value("wpa", "WPA-EAP", {mode="ap"}, {mode="ap-wds"}) - encr:value("wpa2", "WPA2-EAP", {mode="ap"}, {mode="ap-wds"}) - end - if has_ap_owe then - encr:value("owe", "OWE", {mode="ap"}, {mode="ap-wds"}) - end - encr.description = translate( - "WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP " .. - "and ad-hoc mode) to be installed." - ) - elseif not hostapd and supplicant then - encr:value("psk", "WPA-PSK", {mode="sta"}, {mode="sta-wds"}, {mode="adhoc"}) - encr:value("psk2", "WPA2-PSK", {mode="sta"}, {mode="sta-wds"}, {mode="adhoc"}) - encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode", {mode="sta"}, {mode="sta-wds"}, {mode="adhoc"}) - if has_sta_sae then - encr:value("sae", "WPA3-SAE", {mode="sta"}, {mode="sta-wds"}, {mode="mesh"}) - encr:value("sae-mixed", "WPA2-PSK/WPA3-SAE Mixed Mode", {mode="sta"}, {mode="sta-wds"}) - end - if has_sta_eap then - encr:value("wpa", "WPA-EAP", {mode="sta"}, {mode="sta-wds"}) - encr:value("wpa2", "WPA2-EAP", {mode="sta"}, {mode="sta-wds"}) - end - if has_sta_owe then - encr:value("owe", "OWE", {mode="sta"}, {mode="sta-wds"}) - end - encr.description = translate( - "WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP " .. - "and ad-hoc mode) to be installed." - ) - else - encr.description = translate( - "WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP " .. - "and ad-hoc mode) to be installed." - ) - end -elseif hwtype == "broadcom" then - encr:value("psk", "WPA-PSK") - encr:value("psk2", "WPA2-PSK") - encr:value("psk+psk2", "WPA-PSK/WPA2-PSK Mixed Mode") -end - -auth_server = s:taboption("encryption", Value, "auth_server", translate("Radius-Authentication-Server")) -auth_server:depends({mode="ap", encryption="wpa"}) -auth_server:depends({mode="ap", encryption="wpa2"}) -auth_server:depends({mode="ap-wds", encryption="wpa"}) -auth_server:depends({mode="ap-wds", encryption="wpa2"}) -auth_server.rmempty = true -auth_server.datatype = "host(0)" - -auth_port = s:taboption("encryption", Value, "auth_port", translate("Radius-Authentication-Port"), translatef("Default %d", 1812)) -auth_port:depends({mode="ap", encryption="wpa"}) -auth_port:depends({mode="ap", encryption="wpa2"}) -auth_port:depends({mode="ap-wds", encryption="wpa"}) -auth_port:depends({mode="ap-wds", encryption="wpa2"}) -auth_port.rmempty = true -auth_port.datatype = "port" - -auth_secret = s:taboption("encryption", Value, "auth_secret", translate("Radius-Authentication-Secret")) -auth_secret:depends({mode="ap", encryption="wpa"}) -auth_secret:depends({mode="ap", encryption="wpa2"}) -auth_secret:depends({mode="ap-wds", encryption="wpa"}) -auth_secret:depends({mode="ap-wds", encryption="wpa2"}) -auth_secret.rmempty = true -auth_secret.password = true - -acct_server = s:taboption("encryption", Value, "acct_server", translate("Radius-Accounting-Server")) -acct_server:depends({mode="ap", encryption="wpa"}) -acct_server:depends({mode="ap", encryption="wpa2"}) -acct_server:depends({mode="ap-wds", encryption="wpa"}) -acct_server:depends({mode="ap-wds", encryption="wpa2"}) -acct_server.rmempty = true -acct_server.datatype = "host(0)" - -acct_port = s:taboption("encryption", Value, "acct_port", translate("Radius-Accounting-Port"), translatef("Default %d", 1813)) -acct_port:depends({mode="ap", encryption="wpa"}) -acct_port:depends({mode="ap", encryption="wpa2"}) -acct_port:depends({mode="ap-wds", encryption="wpa"}) -acct_port:depends({mode="ap-wds", encryption="wpa2"}) -acct_port.rmempty = true -acct_port.datatype = "port" - -acct_secret = s:taboption("encryption", Value, "acct_secret", translate("Radius-Accounting-Secret")) -acct_secret:depends({mode="ap", encryption="wpa"}) -acct_secret:depends({mode="ap", encryption="wpa2"}) -acct_secret:depends({mode="ap-wds", encryption="wpa"}) -acct_secret:depends({mode="ap-wds", encryption="wpa2"}) -acct_secret.rmempty = true -acct_secret.password = true - -dae_client = s:taboption("encryption", Value, "dae_client", translate("DAE-Client")) -dae_client:depends({mode="ap", encryption="wpa"}) -dae_client:depends({mode="ap", encryption="wpa2"}) -dae_client:depends({mode="ap-wds", encryption="wpa"}) -dae_client:depends({mode="ap-wds", encryption="wpa2"}) -dae_client.rmempty = true -dae_client.datatype = "host(0)" - -dae_port = s:taboption("encryption", Value, "dae_port", translate("DAE-Port"), translatef("Default %d", 3799)) -dae_port:depends({mode="ap", encryption="wpa"}) -dae_port:depends({mode="ap", encryption="wpa2"}) -dae_port:depends({mode="ap-wds", encryption="wpa"}) -dae_port:depends({mode="ap-wds", encryption="wpa2"}) -dae_port.rmempty = true -dae_port.datatype = "port" - -dae_secret = s:taboption("encryption", Value, "dae_secret", translate("DAE-Secret")) -dae_secret:depends({mode="ap", encryption="wpa"}) -dae_secret:depends({mode="ap", encryption="wpa2"}) -dae_secret:depends({mode="ap-wds", encryption="wpa"}) -dae_secret:depends({mode="ap-wds", encryption="wpa2"}) -dae_secret.rmempty = true -dae_secret.password = true - -wpakey = s:taboption("encryption", Value, "_wpa_key", translate("Key")) -wpakey:depends("encryption", "psk") -wpakey:depends("encryption", "psk2") -wpakey:depends("encryption", "psk+psk2") -wpakey:depends("encryption", "psk-mixed") -wpakey:depends("encryption", "sae") -wpakey:depends("encryption", "sae-mixed") -wpakey.datatype = "wpakey" -wpakey.rmempty = true -wpakey.password = true - -wpakey.cfgvalue = function(self, section, value) - local key = m.uci:get("wireless", section, "key") - if key == "1" or key == "2" or key == "3" or key == "4" then - return nil - end - return key -end - -wpakey.write = function(self, section, value) - self.map.uci:set("wireless", section, "key", value) - self.map.uci:delete("wireless", section, "key1") -end - - -wepslot = s:taboption("encryption", ListValue, "_wep_key", translate("Used Key Slot")) -wepslot:depends("encryption", "wep-open") -wepslot:depends("encryption", "wep-shared") -wepslot:value("1", translatef("Key #%d", 1)) -wepslot:value("2", translatef("Key #%d", 2)) -wepslot:value("3", translatef("Key #%d", 3)) -wepslot:value("4", translatef("Key #%d", 4)) - -wepslot.cfgvalue = function(self, section) - local slot = tonumber(m.uci:get("wireless", section, "key")) - if not slot or slot < 1 or slot > 4 then - return 1 - end - return slot -end - -wepslot.write = function(self, section, value) - self.map.uci:set("wireless", section, "key", value) -end - -local slot -for slot=1,4 do - wepkey = s:taboption("encryption", Value, "key" .. slot, translatef("Key #%d", slot)) - wepkey:depends("encryption", "wep-open") - wepkey:depends("encryption", "wep-shared") - wepkey.datatype = "wepkey" - wepkey.rmempty = true - wepkey.password = true - - function wepkey.write(self, section, value) - if value and (#value == 5 or #value == 13) then - value = "s:" .. value - end - return Value.write(self, section, value) - end -end - -if hwtype == "mac80211" or hwtype == "prism2" then - - -- Probe 802.11r support (and EAP support as a proxy for Openwrt) - local has_80211r = (os.execute("hostapd -v11r 2>/dev/null || hostapd -veap 2>/dev/null") == 0) - - ieee80211r = s:taboption("encryption", Flag, "ieee80211r", - translate("802.11r Fast Transition"), - translate("Enables fast roaming among access points that belong " .. - "to the same Mobility Domain")) - ieee80211r:depends({mode="ap", encryption="wpa"}) - ieee80211r:depends({mode="ap", encryption="wpa2"}) - ieee80211r:depends({mode="ap-wds", encryption="wpa"}) - ieee80211r:depends({mode="ap-wds", encryption="wpa2"}) - if has_80211r then - ieee80211r:depends({mode="ap", encryption="psk"}) - ieee80211r:depends({mode="ap", encryption="psk2"}) - ieee80211r:depends({mode="ap", encryption="psk-mixed"}) - ieee80211r:depends({mode="ap", encryption="sae"}) - ieee80211r:depends({mode="ap", encryption="sae-mixed"}) - ieee80211r:depends({mode="ap-wds", encryption="psk"}) - ieee80211r:depends({mode="ap-wds", encryption="psk2"}) - ieee80211r:depends({mode="ap-wds", encryption="psk-mixed"}) - ieee80211r:depends({mode="ap-wds", encryption="sae"}) - ieee80211r:depends({mode="ap-wds", encryption="sae-mixed"}) - end - ieee80211r.rmempty = true - - nasid = s:taboption("encryption", Value, "nasid", translate("NAS ID"), - translate("Used for two different purposes: RADIUS NAS ID and " .. - "802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.")) - nasid:depends({mode="ap", encryption="wpa"}) - nasid:depends({mode="ap", encryption="wpa2"}) - nasid:depends({mode="ap-wds", encryption="wpa"}) - nasid:depends({mode="ap-wds", encryption="wpa2"}) - nasid:depends({ieee80211r="1"}) - nasid.rmempty = true - - mobility_domain = s:taboption("encryption", Value, "mobility_domain", - translate("Mobility Domain"), - translate("4-character hexadecimal ID")) - mobility_domain:depends({ieee80211r="1"}) - mobility_domain.placeholder = "4f57" - mobility_domain.datatype = "and(hexstring,rangelength(4,4))" - mobility_domain.rmempty = true - - reassociation_deadline = s:taboption("encryption", Value, "reassociation_deadline", - translate("Reassociation Deadline"), - translate("time units (TUs / 1.024 ms) [1000-65535]")) - reassociation_deadline:depends({ieee80211r="1"}) - reassociation_deadline.placeholder = "1000" - reassociation_deadline.datatype = "range(1000,65535)" - reassociation_deadline.rmempty = true - - ft_protocol = s:taboption("encryption", ListValue, "ft_over_ds", translate("FT protocol")) - ft_protocol:depends({ieee80211r="1"}) - ft_protocol:value("1", translatef("FT over DS")) - ft_protocol:value("0", translatef("FT over the Air")) - ft_protocol.rmempty = true - - ft_psk_generate_local = s:taboption("encryption", Flag, "ft_psk_generate_local", - translate("Generate PMK locally"), - translate("When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.")) - ft_psk_generate_local:depends({ieee80211r="1"}) - ft_psk_generate_local.default = ft_psk_generate_local.enabled - ft_psk_generate_local.rmempty = false - - r0_key_lifetime = s:taboption("encryption", Value, "r0_key_lifetime", - translate("R0 Key Lifetime"), translate("minutes")) - r0_key_lifetime:depends({ieee80211r="1"}) - r0_key_lifetime.placeholder = "10000" - r0_key_lifetime.datatype = "uinteger" - r0_key_lifetime.rmempty = true - - r1_key_holder = s:taboption("encryption", Value, "r1_key_holder", - translate("R1 Key Holder"), - translate("6-octet identifier as a hex string - no colons")) - r1_key_holder:depends({ieee80211r="1"}) - r1_key_holder.placeholder = "00004f577274" - r1_key_holder.datatype = "and(hexstring,rangelength(12,12))" - r1_key_holder.rmempty = true - - pmk_r1_push = s:taboption("encryption", Flag, "pmk_r1_push", translate("PMK R1 Push")) - pmk_r1_push:depends({ieee80211r="1"}) - pmk_r1_push.placeholder = "0" - pmk_r1_push.rmempty = true - - r0kh = s:taboption("encryption", DynamicList, "r0kh", translate("External R0 Key Holder List"), - translate("List of R0KHs in the same Mobility Domain. " .. - "<br />Format: MAC-address,NAS-Identifier,128-bit key as hex string. " .. - "<br />This list is used to map R0KH-ID (NAS Identifier) to a destination " .. - "MAC address when requesting PMK-R1 key from the R0KH that the STA " .. - "used during the Initial Mobility Domain Association.")) - r0kh:depends({ieee80211r="1"}) - r0kh.rmempty = true - - r1kh = s:taboption("encryption", DynamicList, "r1kh", translate("External R1 Key Holder List"), - translate ("List of R1KHs in the same Mobility Domain. ".. - "<br />Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string. ".. - "<br />This list is used to map R1KH-ID to a destination MAC address " .. - "when sending PMK-R1 key from the R0KH. This is also the " .. - "list of authorized R1KHs in the MD that can request PMK-R1 keys.")) - r1kh:depends({ieee80211r="1"}) - r1kh.rmempty = true - -- End of 802.11r options - - eaptype = s:taboption("encryption", ListValue, "eap_type", translate("EAP-Method")) - eaptype:value("tls", "TLS") - eaptype:value("ttls", "TTLS") - eaptype:value("peap", "PEAP") - eaptype:value("fast", "FAST") - eaptype:depends({mode="sta", encryption="wpa"}) - eaptype:depends({mode="sta", encryption="wpa2"}) - eaptype:depends({mode="sta-wds", encryption="wpa"}) - eaptype:depends({mode="sta-wds", encryption="wpa2"}) - - cacert = s:taboption("encryption", FileUpload, "ca_cert", translate("Path to CA-Certificate")) - cacert:depends({mode="sta", encryption="wpa"}) - cacert:depends({mode="sta", encryption="wpa2"}) - cacert:depends({mode="sta-wds", encryption="wpa"}) - cacert:depends({mode="sta-wds", encryption="wpa2"}) - cacert.rmempty = true - - clientcert = s:taboption("encryption", FileUpload, "client_cert", translate("Path to Client-Certificate")) - clientcert:depends({mode="sta", eap_type="tls", encryption="wpa"}) - clientcert:depends({mode="sta", eap_type="tls", encryption="wpa2"}) - clientcert:depends({mode="sta-wds", eap_type="tls", encryption="wpa"}) - clientcert:depends({mode="sta-wds", eap_type="tls", encryption="wpa2"}) - - privkey = s:taboption("encryption", FileUpload, "priv_key", translate("Path to Private Key")) - privkey:depends({mode="sta", eap_type="tls", encryption="wpa2"}) - privkey:depends({mode="sta", eap_type="tls", encryption="wpa"}) - privkey:depends({mode="sta-wds", eap_type="tls", encryption="wpa2"}) - privkey:depends({mode="sta-wds", eap_type="tls", encryption="wpa"}) - - privkeypwd = s:taboption("encryption", Value, "priv_key_pwd", translate("Password of Private Key")) - privkeypwd:depends({mode="sta", eap_type="tls", encryption="wpa2"}) - privkeypwd:depends({mode="sta", eap_type="tls", encryption="wpa"}) - privkeypwd:depends({mode="sta-wds", eap_type="tls", encryption="wpa2"}) - privkeypwd:depends({mode="sta-wds", eap_type="tls", encryption="wpa"}) - privkeypwd.rmempty = true - privkeypwd.password = true - - auth = s:taboption("encryption", ListValue, "auth", translate("Authentication")) - auth:value("PAP", "PAP", {eap_type="ttls"}) - auth:value("CHAP", "CHAP", {eap_type="ttls"}) - auth:value("MSCHAP", "MSCHAP", {eap_type="ttls"}) - auth:value("MSCHAPV2", "MSCHAPv2", {eap_type="ttls"}) - auth:value("EAP-GTC") - auth:value("EAP-MD5") - auth:value("EAP-MSCHAPV2") - auth:value("EAP-TLS") - auth:depends({mode="sta", eap_type="fast", encryption="wpa2"}) - auth:depends({mode="sta", eap_type="fast", encryption="wpa"}) - auth:depends({mode="sta", eap_type="peap", encryption="wpa2"}) - auth:depends({mode="sta", eap_type="peap", encryption="wpa"}) - auth:depends({mode="sta", eap_type="ttls", encryption="wpa2"}) - auth:depends({mode="sta", eap_type="ttls", encryption="wpa"}) - auth:depends({mode="sta-wds", eap_type="fast", encryption="wpa2"}) - auth:depends({mode="sta-wds", eap_type="fast", encryption="wpa"}) - auth:depends({mode="sta-wds", eap_type="peap", encryption="wpa2"}) - auth:depends({mode="sta-wds", eap_type="peap", encryption="wpa"}) - auth:depends({mode="sta-wds", eap_type="ttls", encryption="wpa2"}) - auth:depends({mode="sta-wds", eap_type="ttls", encryption="wpa"}) - - cacert2 = s:taboption("encryption", FileUpload, "ca_cert2", translate("Path to inner CA-Certificate")) - cacert2:depends({mode="sta", auth="EAP-TLS", encryption="wpa"}) - cacert2:depends({mode="sta", auth="EAP-TLS", encryption="wpa2"}) - cacert2:depends({mode="sta-wds", auth="EAP-TLS", encryption="wpa"}) - cacert2:depends({mode="sta-wds", auth="EAP-TLS", encryption="wpa2"}) - - clientcert2 = s:taboption("encryption", FileUpload, "client_cert2", translate("Path to inner Client-Certificate")) - clientcert2:depends({mode="sta", auth="EAP-TLS", encryption="wpa"}) - clientcert2:depends({mode="sta", auth="EAP-TLS", encryption="wpa2"}) - clientcert2:depends({mode="sta-wds", auth="EAP-TLS", encryption="wpa"}) - clientcert2:depends({mode="sta-wds", auth="EAP-TLS", encryption="wpa2"}) - - privkey2 = s:taboption("encryption", FileUpload, "priv_key2", translate("Path to inner Private Key")) - privkey2:depends({mode="sta", auth="EAP-TLS", encryption="wpa"}) - privkey2:depends({mode="sta", auth="EAP-TLS", encryption="wpa2"}) - privkey2:depends({mode="sta-wds", auth="EAP-TLS", encryption="wpa"}) - privkey2:depends({mode="sta-wds", auth="EAP-TLS", encryption="wpa2"}) - - privkeypwd2 = s:taboption("encryption", Value, "priv_key2_pwd", translate("Password of inner Private Key")) - privkeypwd2:depends({mode="sta", auth="EAP-TLS", encryption="wpa"}) - privkeypwd2:depends({mode="sta", auth="EAP-TLS", encryption="wpa2"}) - privkeypwd2:depends({mode="sta-wds", auth="EAP-TLS", encryption="wpa"}) - privkeypwd2:depends({mode="sta-wds", auth="EAP-TLS", encryption="wpa2"}) - privkeypwd2.rmempty = true - privkeypwd2.password = true - - identity = s:taboption("encryption", Value, "identity", translate("Identity")) - identity:depends({mode="sta", eap_type="fast", encryption="wpa2"}) - identity:depends({mode="sta", eap_type="fast", encryption="wpa"}) - identity:depends({mode="sta", eap_type="peap", encryption="wpa2"}) - identity:depends({mode="sta", eap_type="peap", encryption="wpa"}) - identity:depends({mode="sta", eap_type="ttls", encryption="wpa2"}) - identity:depends({mode="sta", eap_type="ttls", encryption="wpa"}) - identity:depends({mode="sta-wds", eap_type="fast", encryption="wpa2"}) - identity:depends({mode="sta-wds", eap_type="fast", encryption="wpa"}) - identity:depends({mode="sta-wds", eap_type="peap", encryption="wpa2"}) - identity:depends({mode="sta-wds", eap_type="peap", encryption="wpa"}) - identity:depends({mode="sta-wds", eap_type="ttls", encryption="wpa2"}) - identity:depends({mode="sta-wds", eap_type="ttls", encryption="wpa"}) - identity:depends({mode="sta", eap_type="tls", encryption="wpa2"}) - identity:depends({mode="sta", eap_type="tls", encryption="wpa"}) - identity:depends({mode="sta-wds", eap_type="tls", encryption="wpa2"}) - identity:depends({mode="sta-wds", eap_type="tls", encryption="wpa"}) - - anonymous_identity = s:taboption("encryption", Value, "anonymous_identity", translate("Anonymous Identity")) - anonymous_identity:depends({mode="sta", eap_type="fast", encryption="wpa2"}) - anonymous_identity:depends({mode="sta", eap_type="fast", encryption="wpa"}) - anonymous_identity:depends({mode="sta", eap_type="peap", encryption="wpa2"}) - anonymous_identity:depends({mode="sta", eap_type="peap", encryption="wpa"}) - anonymous_identity:depends({mode="sta", eap_type="ttls", encryption="wpa2"}) - anonymous_identity:depends({mode="sta", eap_type="ttls", encryption="wpa"}) - anonymous_identity:depends({mode="sta-wds", eap_type="fast", encryption="wpa2"}) - anonymous_identity:depends({mode="sta-wds", eap_type="fast", encryption="wpa"}) - anonymous_identity:depends({mode="sta-wds", eap_type="peap", encryption="wpa2"}) - anonymous_identity:depends({mode="sta-wds", eap_type="peap", encryption="wpa"}) - anonymous_identity:depends({mode="sta-wds", eap_type="ttls", encryption="wpa2"}) - anonymous_identity:depends({mode="sta-wds", eap_type="ttls", encryption="wpa"}) - anonymous_identity:depends({mode="sta", eap_type="tls", encryption="wpa2"}) - anonymous_identity:depends({mode="sta", eap_type="tls", encryption="wpa"}) - anonymous_identity:depends({mode="sta-wds", eap_type="tls", encryption="wpa2"}) - anonymous_identity:depends({mode="sta-wds", eap_type="tls", encryption="wpa"}) - - password = s:taboption("encryption", Value, "password", translate("Password")) - password:depends({mode="sta", eap_type="fast", encryption="wpa2"}) - password:depends({mode="sta", eap_type="fast", encryption="wpa"}) - password:depends({mode="sta", eap_type="peap", encryption="wpa2"}) - password:depends({mode="sta", eap_type="peap", encryption="wpa"}) - password:depends({mode="sta", eap_type="ttls", encryption="wpa2"}) - password:depends({mode="sta", eap_type="ttls", encryption="wpa"}) - password:depends({mode="sta-wds", eap_type="fast", encryption="wpa2"}) - password:depends({mode="sta-wds", eap_type="fast", encryption="wpa"}) - password:depends({mode="sta-wds", eap_type="peap", encryption="wpa2"}) - password:depends({mode="sta-wds", eap_type="peap", encryption="wpa"}) - password:depends({mode="sta-wds", eap_type="ttls", encryption="wpa2"}) - password:depends({mode="sta-wds", eap_type="ttls", encryption="wpa"}) - password.rmempty = true - password.password = true -end - --- ieee802.11w options -if hwtype == "mac80211" then - local has_80211w = (os.execute("hostapd -v11w 2>/dev/null || hostapd -veap 2>/dev/null") == 0) - if has_80211w then - ieee80211w = s:taboption("encryption", ListValue, "ieee80211w", - translate("802.11w Management Frame Protection"), - translate("Requires the 'full' version of wpad/hostapd " .. - "and support from the wifi driver <br />(as of Jan 2019: " .. - "ath9k, ath10k, mwlwifi and mt76)")) - ieee80211w.default = "" - ieee80211w.rmempty = true - ieee80211w:value("", translate("Disabled (default)")) - ieee80211w:value("1", translate("Optional")) - ieee80211w:value("2", translate("Required")) - ieee80211w:depends({mode="ap", encryption="wpa2"}) - ieee80211w:depends({mode="ap-wds", encryption="wpa2"}) - ieee80211w:depends({mode="ap", encryption="psk2"}) - ieee80211w:depends({mode="ap", encryption="psk-mixed"}) - ieee80211w:depends({mode="ap", encryption="sae"}) - ieee80211w:depends({mode="ap", encryption="sae-mixed"}) - ieee80211w:depends({mode="ap", encryption="owe"}) - ieee80211w:depends({mode="ap-wds", encryption="psk2"}) - ieee80211w:depends({mode="ap-wds", encryption="psk-mixed"}) - ieee80211w:depends({mode="ap-wds", encryption="sae"}) - ieee80211w:depends({mode="ap-wds", encryption="sae-mixed"}) - ieee80211w:depends({mode="ap-wds", encryption="owe"}) - ieee80211w:depends({mode="sta", encryption="wpa2"}) - ieee80211w:depends({mode="sta-wds", encryption="wpa2"}) - ieee80211w:depends({mode="sta", encryption="psk2"}) - ieee80211w:depends({mode="sta", encryption="psk-mixed"}) - ieee80211w:depends({mode="sta", encryption="sae"}) - ieee80211w:depends({mode="sta", encryption="sae-mixed"}) - ieee80211w:depends({mode="sta", encryption="owe"}) - ieee80211w:depends({mode="sta-wds", encryption="psk2"}) - ieee80211w:depends({mode="sta-wds", encryption="psk-mixed"}) - ieee80211w:depends({mode="sta-wds", encryption="sae"}) - ieee80211w:depends({mode="sta-wds", encryption="sae-mixed"}) - ieee80211w:depends({mode="sta-wds", encryption="owe"}) - - max_timeout = s:taboption("encryption", Value, "ieee80211w_max_timeout", - translate("802.11w maximum timeout"), - translate("802.11w Association SA Query maximum timeout")) - max_timeout:depends({ieee80211w="1"}) - max_timeout:depends({ieee80211w="2"}) - max_timeout.datatype = "uinteger" - max_timeout.placeholder = "1000" - max_timeout.rmempty = true - - retry_timeout = s:taboption("encryption", Value, "ieee80211w_retry_timeout", - translate("802.11w retry timeout"), - translate("802.11w Association SA Query retry timeout")) - retry_timeout:depends({ieee80211w="1"}) - retry_timeout:depends({ieee80211w="2"}) - retry_timeout.datatype = "uinteger" - retry_timeout.placeholder = "201" - retry_timeout.rmempty = true - end - - key_retries = s:taboption("encryption", Flag, "wpa_disable_eapol_key_retries", - translate("Enable key reinstallation (KRACK) countermeasures"), - translate("Complicates key reinstallation attacks on the client side by disabling retransmission of EAPOL-Key frames that are used to install keys. This workaround might cause interoperability issues and reduced robustness of key negotiation especially in environments with heavy traffic load.")) - - key_retries:depends({mode="ap", encryption="wpa2"}) - key_retries:depends({mode="ap", encryption="psk2"}) - key_retries:depends({mode="ap", encryption="psk-mixed"}) - key_retries:depends({mode="ap", encryption="sae"}) - key_retries:depends({mode="ap", encryption="sae-mixed"}) - key_retries:depends({mode="ap-wds", encryption="wpa2"}) - key_retries:depends({mode="ap-wds", encryption="psk2"}) - key_retries:depends({mode="ap-wds", encryption="psk-mixed"}) - key_retries:depends({mode="ap-wds", encryption="sae"}) - key_retries:depends({mode="ap-wds", encryption="sae-mixed"}) -end - -if hwtype == "mac80211" or hwtype == "prism2" then - local wpasupplicant = fs.access("/usr/sbin/wpa_supplicant") - local hostcli = fs.access("/usr/sbin/hostapd_cli") - if hostcli and wpasupplicant then - wps = s:taboption("encryption", Flag, "wps_pushbutton", translate("Enable WPS pushbutton, requires WPA(2)-PSK")) - wps.enabled = "1" - wps.disabled = "0" - wps.rmempty = false - wps:depends("encryption", "psk") - wps:depends("encryption", "psk2") - wps:depends("encryption", "psk-mixed") - end -end - -return m diff --git a/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi_add.lua b/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi_add.lua deleted file mode 100644 index e8a305882..000000000 --- a/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi_add.lua +++ /dev/null @@ -1,168 +0,0 @@ --- Copyright 2009 Jo-Philipp Wich <jow@openwrt.org> --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local nw = require "luci.model.network" -local fw = require "luci.model.firewall" -local uci = require "luci.model.uci".cursor() -local http = require "luci.http" - -local iw = luci.sys.wifi.getiwinfo(http.formvalue("device")) - -local has_firewall = fs.access("/etc/config/firewall") - -if not iw then - luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless")) - return -end - -m = SimpleForm("network", translatef("Joining Network: %q", http.formvalue("join"))) -m.cancel = translate("Back to scan results") -m.reset = false - -function m.on_cancel() - local dev = http.formvalue("device") - http.redirect(luci.dispatcher.build_url( - dev and "admin/network/wireless_join?device=" .. dev - or "admin/network/wireless" - )) -end - -nw.init(uci) -fw.init(uci) - -m.hidden = { - device = http.formvalue("device"), - join = http.formvalue("join"), - channel = http.formvalue("channel"), - mode = http.formvalue("mode"), - bssid = http.formvalue("bssid"), - wep = http.formvalue("wep"), - wpa_suites = http.formvalue("wpa_suites"), - wpa_version = http.formvalue("wpa_version") -} - -if iw and iw.mbssid_support then - replace = m:field(Flag, "replace", translate("Replace wireless configuration"), - translate("Check this option to delete the existing networks from this radio.")) - - function replace.cfgvalue() return "0" end -else - replace = m:field(DummyValue, "replace", translate("Replace wireless configuration")) - replace.default = translate("The hardware is not multi-SSID capable and the existing " .. - "configuration will be replaced if you proceed.") - - function replace.formvalue() return "1" end -end - -if http.formvalue("wep") == "1" then - key = m:field(Value, "key", translate("WEP passphrase"), - translate("Specify the secret encryption key here.")) - - key.password = true - key.datatype = "wepkey" - -elseif (tonumber(m.hidden.wpa_version) or 0) > 0 and - (m.hidden.wpa_suites == "PSK" or m.hidden.wpa_suites == "PSK2") -then - key = m:field(Value, "key", translate("WPA passphrase"), - translate("Specify the secret encryption key here.")) - - key.password = true - key.datatype = "wpakey" - --m.hidden.wpa_suite = (tonumber(http.formvalue("wpa_version")) or 0) >= 2 and "psk2" or "psk" -end - -newnet = m:field(Value, "_netname_new", translate("Name of the new network"), - translate("The allowed characters are: <code>A-Z</code>, <code>a-z</code>, " .. - "<code>0-9</code> and <code>_</code>" - )) - -newnet.default = m.hidden.mode == "Ad-Hoc" and "mesh" or "wwan" -newnet.datatype = "uciname" - -if has_firewall then - fwzone = m:field(Value, "_fwzone", - translate("Create / Assign firewall-zone"), - translate("Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> field to define a new zone and attach the interface to it.")) - - fwzone.template = "cbi/firewall_zonelist" - fwzone.default = m.hidden.mode == "Ad-Hoc" and "mesh" or "wan" -end - -function newnet.parse(self, section) - local net, zone - - if has_firewall then - local value = fwzone:formvalue(section) - if value and #value > 0 then - zone = fw:get_zone(value) or fw:add_zone(value) - end - end - - local wdev = nw:get_wifidev(m.hidden.device) - - wdev:set("disabled", false) - wdev:set("channel", m.hidden.channel) - - if replace:formvalue(section) then - local n - for _, n in ipairs(wdev:get_wifinets()) do - wdev:del_wifinet(n) - end - end - - local wconf = { - device = m.hidden.device, - ssid = m.hidden.join, - mode = (m.hidden.mode == "Ad-Hoc" and "adhoc" or "sta") - } - - if m.hidden.wep == "1" then - wconf.encryption = "wep-open" - wconf.key = "1" - wconf.key1 = key and key:formvalue(section) or "" - elseif (tonumber(m.hidden.wpa_version) or 0) > 0 then - wconf.encryption = (tonumber(m.hidden.wpa_version) or 0) >= 2 and "psk2" or "psk" - wconf.key = key and key:formvalue(section) or "" - else - wconf.encryption = "none" - end - - if wconf.mode == "adhoc" or wconf.mode == "sta" then - wconf.bssid = m.hidden.bssid - end - - local value = self:formvalue(section) - net = nw:add_network(value, { proto = "dhcp" }) - - if not net then - self.error = { [section] = "missing" } - else - wconf.network = net:name() - - local wnet = wdev:add_wifinet(wconf) - if wnet then - if zone then - fw:del_network(net:name()) - zone:add_network(net:name()) - end - - uci:save("wireless") - uci:save("network") - uci:save("firewall") - - luci.http.redirect(wnet:adminlink()) - end - end -end - -if has_firewall then - function fwzone.cfgvalue(self, section) - self.iface = section - local z = fw:get_zone_by_network(section) - return z and z:name() - end -end - -return m diff --git a/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi_overview.lua b/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi_overview.lua deleted file mode 100644 index 54720d688..000000000 --- a/modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi_overview.lua +++ /dev/null @@ -1,153 +0,0 @@ --- Copyright 2018 Jo-Philipp Wich <jo@mein.io> --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local utl = require "luci.util" -local tpl = require "luci.template" -local ntm = require "luci.model.network" - -local has_iwinfo = pcall(require, "iwinfo") - -function guess_wifi_hw(dev) - local bands = "" - local ifname = dev:name() - local name, idx = ifname:match("^([a-z]+)(%d+)") - idx = tonumber(idx) - - if has_iwinfo then - local bl = dev.iwinfo.hwmodelist - if bl and next(bl) then - if bl.a then bands = bands .. "a" end - if bl.b then bands = bands .. "b" end - if bl.g then bands = bands .. "g" end - if bl.n then bands = bands .. "n" end - if bl.ac then bands = bands .. "ac" end - end - - local hw = dev.iwinfo.hardware_name - if hw then - return "%s 802.11%s" %{ hw, bands } - end - end - - -- wl.o - if name == "wl" then - local name = translatef("Broadcom 802.11%s Wireless Controller", bands) - local nm = 0 - - local fd = nixio.open("/proc/bus/pci/devices", "r") - if fd then - local ln - for ln in fd:linesource() do - if ln:match("wl$") then - if nm == idx then - local version = ln:match("^%S+%s+%S%S%S%S([0-9a-f]+)") - name = translatef( - "Broadcom BCM%04x 802.11 Wireless Controller", - tonumber(version, 16) - ) - - break - else - nm = nm + 1 - end - end - end - fd:close() - end - - return name - - -- dunno yet - else - return translatef("Generic 802.11%s Wireless Controller", bands) - end -end - - -m = Map("wireless", translate("Wireless Overview")) -m:chain("network") -m.pageaction = false - -if not has_iwinfo then - s = m:section(NamedSection, "__warning__") - - function s.render(self) - tpl.render_string([[ - <div class="alert-message warning"> - <h4><%:Package libiwinfo required!%></h4> - <p><%_The <em>libiwinfo-lua</em> package is not installed. You must install this component for working wireless configuration!%></p> - </div> - ]]) - end -end - -local _, dev, net -for _, dev in ipairs(ntm:get_wifidevs()) do - s = m:section(TypedSection) - s.template = "admin_network/wifi_overview" - s.wnets = dev:get_wifinets() - s.dev = dev - s.hw = guess_wifi_hw(dev) - - function s.cfgsections(self) - local _, net, sl = nil, nil, { } - for _, net in ipairs(self.wnets) do - sl[#sl+1] = net:name() - self.wnets[net:name()] = net - end - return sl - end - - o = s:option(Value, "__disable__") - - function o.cfgvalue(self, sid) - local wnet = self.section.wnets[sid] - local wdev = wnet:get_device() - - return ((wnet and wnet:get("disabled") == "1") or - (wdev and wdev:get("disabled") == "1")) and "1" or "0" - end - - function o.write(self, sid, value) - local wnet = self.section.wnets[sid] - local wdev = wnet:get_device() - - if value ~= "1" then - wnet:set("disabled", nil) - wdev:set("disabled", nil) - else - wnet:set("disabled", "1") - end - end - - o.remove = o.write - - - o = s:option(Value, "__delete__") - - function o.write(self, sid, value) - local wnet = self.section.wnets[sid] - local nets = wnet:get_networks() - - ntm:del_wifinet(wnet:id()) - - local _, net - for _, net in ipairs(nets) do - if net:is_empty() then - ntm:del_network(net:name()) - end - end - end -end - -s = m:section(NamedSection, "__assoclist__") - -function s.render(self, sid) - tpl.render_string([[ - <h2><%:Associated Stations%></h2> - <%+wifi_assoclist%> - ]]) -end - -return m diff --git a/modules/luci-mod-network/luasrc/view/admin_network/wifi_join.htm b/modules/luci-mod-network/luasrc/view/admin_network/wifi_join.htm deleted file mode 100644 index 5a61ba099..000000000 --- a/modules/luci-mod-network/luasrc/view/admin_network/wifi_join.htm +++ /dev/null @@ -1,59 +0,0 @@ -<%# - Copyright 2009-2015 Jo-Philipp Wich <jow@openwrt.org> - Licensed to the public under the Apache License 2.0. --%> - -<%- - - local sys = require "luci.sys" - local utl = require "luci.util" - - local dev = luci.http.formvalue("device") - local iw = luci.sys.wifi.getiwinfo(dev) - - if not iw then - luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless")) - return - end --%> - -<%+header%> - -<h2 name="content"><%:Join Network: Wireless Scan%></h2> - -<div class="cbi-map"> - <div class="cbi-section"> - <div class="table"<%=attr("data-wifi-scan", dev) .. attr("data-wifi-type", iw.type)%>> - <div class="tr table-titles"> - <div class="th col-2 middle center"><%:Signal%></div> - <div class="th col-4 middle left"><%:SSID%></div> - <div class="th col-2 middle center hide-xs"><%:Channel%></div> - <div class="th col-2 middle left hide-xs"><%:Mode%></div> - <div class="th col-3 middle left hide-xs"><%:BSSID%></div> - <div class="th col-3 middle left"><%:Encryption%></div> - <div class="th cbi-section-actions"> </div> - </div> - - <div class="tr placeholder"> - <div class="td"> - <img src="<%=resource%>/icons/loading.gif" class="middle" /> - <em><%:Collecting data...%></em> - </div> - </div> - </div> - </div> -</div> -<div class="cbi-page-actions right"> - <form class="inline" action="<%=url("admin/network/wireless")%>" method="get"> - <input class="cbi-button cbi-button-neutral" type="submit" value="<%:Back to overview%>" /> - </form> - <form class="inline" action="<%=url('admin/network/wireless_join')%>" method="post"> - <input type="hidden" name="token" value="<%=token%>" /> - <input type="hidden" name="device" value="<%=utl.pcdata(dev)%>" /> - <input type="button" class="cbi-button cbi-button-action" value="<%:Repeat scan%>" onclick="flush()" /> - </form> -</div> - -<script type="text/javascript" src="<%=resource%>/view/network/wifi_join.js"></script> - -<%+footer%> diff --git a/modules/luci-mod-network/luasrc/view/admin_network/wifi_overview.htm b/modules/luci-mod-network/luasrc/view/admin_network/wifi_overview.htm deleted file mode 100644 index 89bb404fd..000000000 --- a/modules/luci-mod-network/luasrc/view/admin_network/wifi_overview.htm +++ /dev/null @@ -1,61 +0,0 @@ -<div class="cbi-section-node"> - <div class="table"> - <!-- physical device --> - <div class="tr cbi-rowstyle-2"> - <div class="td col-2 center middle"> - <span class="ifacebadge"><img src="<%=resource%>/icons/wifi_disabled.png" id="<%=self.dev:name()%>-iw-upstate" /> <%=self.dev:name()%></span> - </div> - <div class="td col-7 left middle"> - <big><strong><%=self.hw%></strong></big><br /> - <span id="<%=self.dev:name()%>-iw-devinfo"></span> - </div> - <div class="td middle cbi-section-actions"> - <div> - <input type="button" class="cbi-button cbi-button-neutral" title="<%:Restart radio interface%>" value="<%:Restart%>" data-radio="<%=self.dev:name()%>" onclick="wifi_restart(event)" /> - <input type="button" class="cbi-button cbi-button-action important" title="<%:Find and join network%>" value="<%:Scan%>" onclick="cbi_submit(this, 'device', '<%=self.dev:name()%>', '<%=url('admin/network/wireless_join')%>')" /> - <input type="button" class="cbi-button cbi-button-add" title="<%:Provide new network%>" value="<%:Add%>" onclick="cbi_submit(this, 'device', '<%=self.dev:name()%>', '<%=url('admin/network/wireless_add')%>')" /> - </div> - </div> - </div> - <!-- /physical device --> - - <!-- network list --> - <% if #self.wnets > 0 then %> - <% for i, net in ipairs(self.wnets) do local disabled = (self.dev:get("disabled") == "1" or net:get("disabled") == "1") %> - <div class="tr cbi-rowstyle-<%=1 + ((i-1) % 2)%>"> - <div class="td col-2 center middle" id="<%=net:id()%>-iw-signal"> - <span class="ifacebadge" title="<%:Not associated%>"><img src="<%=resource%>/icons/signal-<%= disabled and "none" or "0" %>.png" /> 0%</span> - </div> - <div class="td col-7 left middle" id="<%=net:id()%>-iw-status" data-network="<%=net:id()%>" data-disabled="<%= disabled and "true" or "false" %>"> - <em><%= disabled and translate("Wireless is disabled") or translate("Collecting data...") %></em> - </div> - <div class="td middle cbi-section-actions"> - <div> - <% if disabled then %> - <input name="cbid.wireless.<%=net:name()%>.__disable__" type="hidden" value="1" /> - <input name="cbi.apply" type="submit" class="cbi-button cbi-button-neutral" title="<%:Enable this network%>" value="<%:Enable%>" onclick="this.previousElementSibling.value='0'" /> - <% else %> - <input name="cbid.wireless.<%=net:name()%>.__disable__" type="hidden" value="0" /> - <input name="cbi.apply" type="submit" class="cbi-button cbi-button-neutral" title="<%:Disable this network%>" value="<%:Disable%>" onclick="this.previousElementSibling.value='1'" /> - <% end %> - - <input type="button" class="cbi-button cbi-button-action important" onclick="location.href='<%=net:adminlink()%>'" title="<%:Edit this network%>" value="<%:Edit%>" /> - - <input name="cbid.wireless.<%=net:name()%>.__delete__" type="hidden" value="" /> - <input name="cbi.apply" type="submit" class="cbi-button cbi-button-negative" title="<%:Delete this network%>" value="<%:Remove%>" onclick="wifi_delete(event)" /> - </div> - </div> - </div> - <% end %> - <% else %> - <div class="tr placeholder"> - <div class="td"> - <em><%:No network configured on this device%></em> - </div> - </div> - <% end %> - <!-- /network list --> - </div> -</div> - -<script type="text/javascript" src="<%=resource%>/view/network/wireless.js"></script> diff --git a/modules/luci-mod-network/luasrc/view/admin_network/wifi_status.htm b/modules/luci-mod-network/luasrc/view/admin_network/wifi_status.htm deleted file mode 100644 index 93ae2f51f..000000000 --- a/modules/luci-mod-network/luasrc/view/admin_network/wifi_status.htm +++ /dev/null @@ -1,14 +0,0 @@ -<%+cbi/valueheader%> - -<span class="ifacebadge large"<%=attr("data-wifi-status", self.ifname)%>> - <small> - <img src="<%=resource%>/icons/signal-none.png" title="<%:Not associated%>" />  - </small> - <span> - <em class="spinning"><%:Collecting data...%></em> - </span> -</span> - -<script type="text/javascript" src="<%=resource%>/view/network/wifi_status.js"></script> - -<%+cbi/valuefooter%> |