-
Notifications
You must be signed in to change notification settings - Fork 2.9k
luci-base, luci-mod-network: support wifi-iface sections spanning multiple radios #8893
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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') | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The user-visible consequence is a state mismatch with the row button: Since the row is already per-radio, the cleanest fix is to hand the radio name in from Generated by Claude Code |
||
| 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,25 +920,45 @@ 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()); | ||
| }); | ||
| }); | ||
|
|
||
| return rv; | ||
| }; | ||
|
|
||
| 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,14 +989,15 @@ return view.extend({ | |
| ]; | ||
| } | ||
| else { | ||
| const sid = inst.getName(); | ||
| const isDisabled = (inst.get('disabled') == '1' || | ||
| uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1'); | ||
|
|
||
| btns = [ | ||
| 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; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This correctly binds the modal to the clicked radio, but the modal's Status field does not stay bound to it.
Keying the badge on the netid and matching it with the same Generated by Claude Code |
||
|
|
||
| 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) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: widening this to a membership test changes what "Replace wireless configuration" does to multi-radio sections. Before this PR they were skipped here ( Is that the intent, or should the join flow drop just Generated by Claude Code |
||
| 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'); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One
devicecomparison in this same class was missed:WifiDevice.deleteWifiNetwork()at network.js:3703 still doesuci.get('wireless', sid, 'device') != this.sid.For a section spanning several radios that value is an array, so the comparison is always true and the method returns
Promise.resolve(false)without ever removing the section — i.e. deleting a multi-radio network through the public API silently fails. It needs the sameL.toArray(...).indexOf(this.sid) == -1treatment applied here and ingetWifiNetwork().Generated by Claude Code