diff --git a/modules/luci-base/htdocs/luci-static/resources/network.js b/modules/luci-base/htdocs/luci-static/resources/network.js index b5e4c2b13da7..e795bfcc7abd 100644 --- a/modules/luci-base/htdocs/luci-static/resources/network.js +++ b/modules/luci-base/htdocs/luci-static/resources/network.js @@ -129,12 +129,15 @@ function getProtocolHandlers() { }); } -function getWifiStateBySid(sid) { +function getWifiStateBySid(sid, radioname) { const s = uci.get('wireless', sid); if (s != null && s['.type'] == 'wifi-iface') { - for (let radioname in _state.radios) { - for (let netstate of _state.radios[radioname].interfaces) { + for (let rname in _state.radios) { + if (radioname != null && rname != radioname) + continue; + + for (let netstate of _state.radios[rname].interfaces) { if (typeof(netstate.section) != 'string') continue; @@ -145,7 +148,7 @@ function getWifiStateBySid(sid) { if (s2['.anonymous'] == false && netstate.section.charAt(0) == '@') return null; - return [ radioname, _state.radios[radioname], netstate ]; + return [ rname, _state.radios[rname], netstate ]; } } } @@ -183,7 +186,7 @@ function getWifiSidByNetid(netid) { const sections = uci.sections('wireless', 'wifi-iface'); let n = 0; for (let s of sections) { - if (s.device != m[1]) + if (L.toArray(s.device).indexOf(m[1]) == -1) continue; if (++n == +m[2]) @@ -208,15 +211,19 @@ function getWifiSidByIfname(ifname) { return null; } -function getWifiNetidBySid(sid) { +function getWifiNetidBySid(sid, radioname) { const s = uci.get('wireless', sid); if (s != null && s['.type'] == 'wifi-iface') { - const radioname = s.device; - if (typeof(radioname) == 'string') { + const radionames = L.toArray(s.device); + + if (radioname == null) + radioname = radionames[0]; + + if (typeof(radioname) == 'string' && radionames.indexOf(radioname) != -1) { const sections = uci.sections('wireless', 'wifi-iface'); let n = 0; for (let sec of sections) { - if (sec.device != radioname) + if (L.toArray(sec.device).indexOf(radioname) == -1) continue; n++; @@ -224,7 +231,7 @@ function getWifiNetidBySid(sid) { if (sec['.name'] != s['.name']) continue; - return [ '%s.network%d'.format(s.device, n), s.device ]; + return [ '%s.network%d'.format(radioname, n), radioname ]; } } @@ -1292,14 +1299,13 @@ Network = baseclass.extend(/** @lends LuCI.network.prototype */ { const networkCount = {}; for (let wf_if of uciWifiIfaces) { - if (typeof(wf_if.device) != 'string') - continue; - - networkCount[wf_if.device] = (networkCount[wf_if.device] || 0) + 1; + for (let radioname of L.toArray(wf_if.device)) { + networkCount[radioname] = (networkCount[radioname] || 0) + 1; - const netid = '%s.network%d'.format(wf_if.device, networkCount[wf_if.device]); + const netid = '%s.network%d'.format(radioname, networkCount[radioname]); - devices[netid] = this.instantiateDevice(netid); + devices[netid] = this.instantiateDevice(netid); + } } /* find uci declared devices */ @@ -1430,8 +1436,17 @@ Network = baseclass.extend(/** @lends LuCI.network.prototype */ { const wifiIfaces = uci.sections('wireless', 'wifi-iface'); const rv = []; - for (let wf_if of wifiIfaces) - rv.push(this.lookupWifiNetwork(wf_if['.name'])); + for (let wf_if of wifiIfaces) { + const radionames = L.toArray(wf_if.device); + + if (radionames.length < 2) { + rv.push(this.lookupWifiNetwork(wf_if['.name'])); + continue; + } + + for (let radioname of radionames) + rv.push(this.lookupWifiNetwork(wf_if['.name'], radioname)); + } rv.sort(function(a, b) { return L.naturalCompare(a.getID(), b.getID()); @@ -1687,15 +1702,21 @@ Network = baseclass.extend(/** @lends LuCI.network.prototype */ { }, /* private */ - lookupWifiNetwork(netname) { - let sid, res, netid, radioname, radiostate, netstate; + lookupWifiNetwork(netname, radioname) { + let sid, res, netid, radiostate, netstate; + + if (typeof(radioname) != 'string') + radioname = null; sid = getWifiSidByNetid(netname); if (sid != null) { - res = getWifiStateBySid(sid); + const m = /^(\w+)\.network\d+$/.exec(netname); + const multiradio = (L.toArray(uci.get('wireless', sid, 'device')).length > 1); + + res = getWifiStateBySid(sid, (multiradio && m) ? m[1] : null); netid = netname; - radioname = res ? res[0] : null; + radioname = res ? res[0] : (m ? m[1] : null); radiostate = res ? res[1] : null; netstate = res ? res[2] : null; } @@ -1707,20 +1728,20 @@ Network = baseclass.extend(/** @lends LuCI.network.prototype */ { radiostate = res[1]; netstate = res[2]; sid = netstate.section; - netid = L.toArray(getWifiNetidBySid(sid))[0]; + netid = L.toArray(getWifiNetidBySid(sid, radioname))[0]; } else { - res = getWifiStateBySid(netname); + res = getWifiStateBySid(netname, radioname); if (res != null) { radioname = res[0]; radiostate = res[1]; netstate = res[2]; sid = netname; - netid = L.toArray(getWifiNetidBySid(sid))[0]; + netid = L.toArray(getWifiNetidBySid(sid, radioname))[0]; } else { - res = getWifiNetidBySid(netname); + res = getWifiNetidBySid(netname, radioname); if (res != null) { netid = res[0]; @@ -2779,19 +2800,19 @@ Protocol = baseclass.extend(/** @lends LuCI.network.Protocol.prototype */ { const uciWifiIfaces = uci.sections('wireless', 'wifi-iface'); for (let wf_if of uciWifiIfaces) { - if (typeof(wf_if.device) != 'string') - continue; - + const radionames = L.toArray(wf_if.device); const networks = L.toArray(wf_if.network); for (let n of networks) { if (n != this.sid) continue; - const netid = getWifiNetidBySid(wf_if['.name']); + for (let radioname of radionames) { + const netid = getWifiNetidBySid(wf_if['.name'], radioname); - if (netid != null) - rv.push(Network.prototype.instantiateDevice(netid[0], this)); + if (netid != null) + rv.push(Network.prototype.instantiateDevice(netid[0], this)); + } } } @@ -3602,7 +3623,7 @@ WifiDevice = baseclass.extend(/** @lends LuCI.network.WifiDevice.prototype */ { return Network.prototype.getWifiNetwork(network).then(L.bind(function(networkInstance) { const uciWifiIface = (networkInstance.sid ? uci.get('wireless', networkInstance.sid) : null); - if (uciWifiIface == null || uciWifiIface['.type'] != 'wifi-iface' || uciWifiIface.device != this.sid) + if (uciWifiIface == null || uciWifiIface['.type'] != 'wifi-iface' || L.toArray(uciWifiIface.device).indexOf(this.sid) == -1) return Promise.reject(); return networkInstance; @@ -3679,7 +3700,7 @@ WifiDevice = baseclass.extend(/** @lends LuCI.network.WifiDevice.prototype */ { sid = getWifiSidByIfname(network); } - if (sid == null || uci.get('wireless', sid, 'device') != this.sid) + if (sid == null || L.toArray(uci.get('wireless', sid, 'device')).indexOf(this.sid) == -1) return Promise.resolve(false); uci.remove('wireless', sid); @@ -3895,7 +3916,7 @@ WifiNetwork = baseclass.extend(/** @lends LuCI.network.WifiNetwork.prototype */ * or `null` if it cannot be determined. */ getWifiDeviceName() { - return this.ubus('radio') || this.get('device'); + return this.ubus('radio') || L.toArray(this.get('device'))[0]; }, /** diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js index f0ac294d1198..d87ad01c2330 100644 --- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js +++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js @@ -180,7 +180,7 @@ function render_modal_status(node, radioNet) { const 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') ]); + node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getID() ?? radioNet.getName() }, [ E('small'), E('span') ]); dom.content(node.firstElementChild, render_signal_badge( disabled ? -1 : radioNet.getSignalPercent(), @@ -261,7 +261,7 @@ function network_updown(id, map, ev) { const wifi_ifaces = uci.sections('wireless', 'wifi-iface'); wifi_ifaces.forEach(wifi_iface => { - if (wifi_iface.device == radio && wifi_iface.disabled != '1') + if (L.toArray(wifi_iface.device).indexOf(radio) != -1 && wifi_iface.disabled != '1') all_networks_disabled = false; }); @@ -699,7 +699,7 @@ return view.extend({ rows.forEach(row => { const section_id = row.getAttribute('data-sid'); const radioDev = data[1].filter(function(d) { return d.getName() == section_id; })[0]; - const radioNet = data[2].filter(function(n) { return n.getName() == section_id; })[0]; + const radioNet = data[2].filter(function(n) { return n.getID() == section_id || n.getName() == section_id; })[0]; const badge = row.querySelector('[data-name="_badge"] > div'); const stat = row.querySelector('[data-name="_stat"]'); const btns = row.querySelectorAll('.cbi-section-actions button'); @@ -816,7 +816,10 @@ return view.extend({ const status = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large'); if (status) - render_modal_status(status, data[2].filter(function(n) { return n.getName() == status.getAttribute('data-network'); })[0]); + render_modal_status(status, data[2].filter(function(n) { + const key = status.getAttribute('data-network'); + return n.getID() == key || n.getName() == key; + })[0]); return network.flushCache(); }, @@ -917,8 +920,15 @@ return view.extend({ rv.push(radio.getName()); this.wifis.forEach(wifi => { - if (wifi.getWifiDeviceName() == radio.getName()) - rv.push(wifi.getName()); + if (wifi.getWifiDeviceName() != radio.getName()) + return; + + /* Sections spanning several radios render one row per radio + * and need the netid to stay unique. Single-radio sections + * keep their UCI section name as before. */ + const multiradio = (L.toArray(uci.get('wireless', wifi.getName(), 'device')).length > 1); + + rv.push(multiradio ? (wifi.getID() || wifi.getName()) : wifi.getName()); }); }); @@ -926,16 +936,29 @@ return view.extend({ }; s.modaltitle = function(section_id) { - const radioNet = this.wifis.filter(function(w) { return w.getName() == section_id; })[0]; + const radioNet = this.editWifiNetwork || this.wifis.filter(function(w) { return w.getID() == section_id || w.getName() == section_id; })[0]; return radioNet ? radioNet.getI18n() : _('Edit wireless network'); }; + s.renderMoreOptionsModal = function(section_id, ev) { + const inst = this.lookupRadioOrNetwork(section_id); + + this.editWifiNetwork = null; + + if (inst && !inst.getWifiNetworks) { + this.editWifiNetwork = inst; + section_id = inst.getName(); + } + + return form.GridSection.prototype.renderMoreOptionsModal.apply(this, [section_id, ev]); + }; + s.lookupRadioOrNetwork = function(section_id) { const radioDev = this.radios.filter(function(r) { return r.getName() == section_id; })[0]; if (radioDev) return radioDev; - const radioNet = this.wifis.filter(function(w) { return w.getName() == section_id; })[0]; + const radioNet = this.wifis.filter(function(w) { return w.getID() == section_id || w.getName() == section_id; })[0]; if (radioNet) return radioNet; @@ -966,6 +989,7 @@ return view.extend({ ]; } else { + const sid = inst.getName(); const isDisabled = (inst.get('disabled') == '1' || uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1'); @@ -973,7 +997,7 @@ return view.extend({ E('button', { 'class': 'cbi-button cbi-button-neutral enable-disable', 'title': isDisabled ? _('Enable this network') : _('Disable this network'), - 'click': ui.createHandlerFn(this, network_updown, section_id, this.map) + 'click': ui.createHandlerFn(this, network_updown, sid, this.map) }, isDisabled ? _('Enable') : _('Disable')), E('button', { 'class': 'cbi-button cbi-button-action important', @@ -992,7 +1016,11 @@ return view.extend({ }; s.addModalOptions = function(s) { + const editWifiNetwork = this.editWifiNetwork; + return network.getWifiNetwork(s.section).then(function(radioNet) { + radioNet = editWifiNetwork || radioNet; + const hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type'); const have_mesh = L.hasSystemFeature('hostapd', 'mesh') || L.hasSystemFeature('wpasupplicant', 'mesh'); let o, ss; @@ -2215,8 +2243,38 @@ return view.extend({ }; 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]); + const inst = this.lookupRadioOrNetwork(section_id); + let sid = section_id; + + if (inst && !inst.getWifiNetworks) { + sid = inst.getName(); + + const radioname = inst.getWifiDeviceName(); + const devices = L.toArray(uci.get('wireless', sid, 'device')); + + if (devices.length > 1 && devices.indexOf(radioname) != -1) { + const row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)); + + if (row) + row.style.opacity = 0.5; + + uci.set('wireless', sid, 'device', devices.filter(d => d != radioname)); + + return this.map.save(null, true); + } + } + + this.wifis.forEach(wifi => { + if (wifi.getName() != sid) + return; + + const row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(wifi.getID() || wifi.getName())); + + if (row) + row.style.opacity = 0.5; + }); + + return form.TypedSection.prototype.handleRemove.apply(this, [sid, ev]); }; s.handleScan = function(radioDev, ev) { @@ -2380,13 +2438,13 @@ return view.extend({ if (replopt.formvalue('_new_') == '1') { for (let ws of wifi_sections) - if (ws.device == radioDev.getName()) + if (L.toArray(ws.device).indexOf(radioDev.getName()) != -1) uci.remove('wireless', ws['.name']); } if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') { for (let ws of wifi_sections) - if (ws.device == radioDev.getName()) + if (L.toArray(ws.device).indexOf(radioDev.getName()) != -1) uci.set('wireless', ws['.name'], 'disabled', '1'); uci.unset('wireless', radioDev.getName(), 'disabled');