luci-mod-network: switch to client side wifi configuration pages
authorJo-Philipp Wich <jo@mein.io>
Wed, 4 Sep 2019 15:26:38 +0000 (17:26 +0200)
committerJo-Philipp Wich <jo@mein.io>
Tue, 10 Sep 2019 13:28:16 +0000 (15:28 +0200)
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>
modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_join.js [deleted file]
modules/luci-mod-network/htdocs/luci-static/resources/view/network/wifi_status.js [deleted file]
modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js
modules/luci-mod-network/luasrc/controller/admin/network.lua
modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi.lua [deleted file]
modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi_add.lua [deleted file]
modules/luci-mod-network/luasrc/model/cbi/admin_network/wifi_overview.lua [deleted file]
modules/luci-mod-network/luasrc/view/admin_network/wifi_join.htm [deleted file]
modules/luci-mod-network/luasrc/view/admin_network/wifi_overview.htm [deleted file]
modules/luci-mod-network/luasrc/view/admin_network/wifi_status.htm [deleted file]

index 57e0ae384b5a13e102c3b746551a7c683089a28e..d05ba841e48ec7ab8e2c9d85cc225fc0cf12c7fc 100644 (file)
                        },
                        "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 (file)
index f30e47e..0000000
+++ /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 (file)
index 108a141..0000000
+++ /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();
-       });
-});
index 57e6bbb04cd67b1f8bf59ecbe3fd0de076d1a753..a058b3fe52bd640223d03c82f2408dfaa6eb0131 100644 (file)
-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));
        }
-);
+});
index 4578d12578ab973ccefa4929c3954ebe1993a166..a381bbc614213c057560bbd6d876a78bd27dcc3a 100644 (file)
@@ -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 (file)
index 2e6c026..0000000
+++ /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 (file)
index e8a3058..0000000
+++ /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 (file)
index 54720d6..0000000
+++ /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 (file)
index 5a61ba0..0000000
+++ /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">&#160;</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 (file)
index 89bb404..0000000
+++ /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 (file)
index 93ae2f5..0000000
+++ /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%>" />&#160;
-       </small>
-       <span>
-               <em class="spinning"><%:Collecting data...%></em>
-       </span>
-</span>
-
-<script type="text/javascript" src="<%=resource%>/view/network/wifi_status.js"></script>
-
-<%+cbi/valuefooter%>