diff --git a/modules/luci-base/htdocs/luci-static/resources/network.js b/modules/luci-base/htdocs/luci-static/resources/network.js index b6b996818a54..c504279eeb88 100644 --- a/modules/luci-base/htdocs/luci-static/resources/network.js +++ b/modules/luci-base/htdocs/luci-static/resources/network.js @@ -3371,6 +3371,7 @@ WifiDevice = baseclass.extend(/** @lends LuCI.network.WifiDevice.prototype */ { * - `n` - IEEE 802.11n mode, 2.4 or 5 GHz, up to 600 Mbit/s * - `ac` - IEEE 802.11ac mode, 5 GHz, up to 6770 Mbit/s * - `ax` - IEEE 802.11ax mode, 2.4 or 5 GHz + * - 'be' - IEEE 802.11be mode, 2.4, 5 or 6 GHz */ getHWModes: function() { var hwmodes = this.ubus('dev', 'iwinfo', 'hwmodes'); @@ -3396,6 +3397,11 @@ WifiDevice = baseclass.extend(/** @lends LuCI.network.WifiDevice.prototype */ { * - `HE40` - applicable to IEEE 802.11ax, 40 MHz wide channels * - `HE80` - applicable to IEEE 802.11ax, 80 MHz wide channels * - `HE160` - applicable to IEEE 802.11ax, 160 MHz wide channels + * - `EHT20` - applicable to IEEE 802.11be, 20 MHz wide channels + * - `EHT40` - applicable to IEEE 802.11be, 40 MHz wide channels + * - `EHT80` - applicable to IEEE 802.11be, 80 MHz wide channels + * - `EHT160` - applicable to IEEE 802.11be, 160 MHz wide channels + * - `EHT320` - applicable to IEEE 802.11be, 320 MHz wide channels */ getHTModes: function() { var htmodes = this.ubus('dev', 'iwinfo', 'htmodes'); @@ -4101,6 +4107,17 @@ WifiNetwork = baseclass.extend(/** @lends LuCI.network.WifiNetwork.prototype */ * @property {number} [he_dcm] * Specifies whether dual concurrent modulation is used for the transmission. * Only applicable to HE rates. + * + * @property {boolean} [eht] + * Specifies whether this rate is an EHT (IEEE 802.11be) rate. + * + * @property {number} [eht_gi] + * Specifies whether the guard interval used for the transmission. + * Only applicable to EHT rates. + * + * @property {number} [eht_dcm] + * Specifies whether dual concurrent modulation is used for the transmission. + * Only applicable to EHT rates. */ /** diff --git a/modules/luci-base/root/usr/share/rpcd/ucode/luci b/modules/luci-base/root/usr/share/rpcd/ucode/luci index 93ce1dd3b92c..9bb8bb88c7d8 100644 --- a/modules/luci-base/root/usr/share/rpcd/ucode/luci +++ b/modules/luci-base/root/usr/share/rpcd/ucode/luci @@ -208,7 +208,7 @@ const methods = { relayd: access('/usr/sbin/relayd') == true, }; - const wifi_features = [ 'eap', '11ac', '11ax', '11r', 'acs', 'sae', 'owe', 'suiteb192', 'wep', 'wps', 'ocv' ]; + const wifi_features = [ 'eap', '11ac', '11ax', '11be', '11r', 'acs', 'sae', 'owe', 'suiteb192', 'wep', 'wps', 'ocv' ]; if (access('/usr/sbin/hostapd')) { result.hostapd = { cli: access('/usr/sbin/hostapd_cli') == true }; diff --git a/modules/luci-compat/luasrc/view/cbi/wireless_modefreq.htm b/modules/luci-compat/luasrc/view/cbi/wireless_modefreq.htm index eeb1d5c5cbc7..8e2975e14bb2 100644 --- a/modules/luci-compat/luasrc/view/cbi/wireless_modefreq.htm +++ b/modules/luci-compat/luasrc/view/cbi/wireless_modefreq.htm @@ -29,7 +29,9 @@ var modes = [ '', 'Legacy', true, 'n', 'N', hwmodes.n, - 'ac', 'AC', hwmodes.ac + 'ac', 'AC', hwmodes.ac, + 'ax', 'AX', hwmodes.ax, + 'be', 'BE', hwmodes.be ]; var htmodes = { @@ -45,6 +47,19 @@ 'VHT40', '40 MHz', htmodes.VHT40, 'VHT80', '80 MHz', htmodes.VHT80, 'VHT160', '160 MHz', htmodes.VHT160 + ], + 'ax': [ + 'HE20', '20 MHz', htmodes.HE20, + 'HE40', '40 MHz', htmodes.HE40, + 'HE80', '80 MHz', htmodes.HE80, + 'HE160', '160 MHz', htmodes.HE160 + ], + 'be': [ + 'EHT20', '20 MHz', htmodes.EHT20, + 'EHT40', '40 MHz', htmodes.EHT40, + 'EHT80', '80 MHz', htmodes.EHT80, + 'EHT160', '160 MHz', htmodes.EHT160, + 'EHT320', '320 MHz', htmodes.EHT320 ] }; @@ -59,6 +74,12 @@ ], 'ac': [ '11a', '5 GHz', true + ], + 'ax': [ + '11a', '5 GHz', true + ], + 'be': [ + '11a', '6 GHz', true ] }; @@ -128,8 +149,12 @@ var bwdt = document.getElementById(id + '.htmode'); cbi_set_values(mode, modes); - - if (/VHT20|VHT40|VHT80|VHT160/.test(<%= luci.http.write_json(self.map:get(section, "htmode")) %>)) + + if (/EHT20|EHT40|HE80|EHT160|EHT320/.test(<%= luci.http.write_json(self.map:get(section, "htmode")) %>)) + mode.value = 'be'; + else if (/HE20|HE40|HE80|HE160/.test(<%= luci.http.write_json(self.map:get(section, "htmode")) %>)) + mode.value = 'ax'; + else if (/VHT20|VHT40|VHT80|VHT160/.test(<%= luci.http.write_json(self.map:get(section, "htmode")) %>)) mode.value = 'ac'; else if (/HT20|HT40/.test(<%= luci.http.write_json(self.map:get(section, "htmode")) %>)) mode.value = 'n'; 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 cedf1675ffa3..cbc77338d96a 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 @@ -197,11 +197,13 @@ function render_modal_status(node, radioNet) { function format_wifirate(rate) { var s = '%.1f\xa0%s, %d\xa0%s'.format(rate.rate / 1000, _('Mbit/s'), rate.mhz, _('MHz')), - ht = rate.ht, vht = rate.vht, - mhz = rate.mhz, nss = rate.nss, - mcs = rate.mcs, sgi = rate.short_gi, - he = rate.he, he_gi = rate.he_gi, - he_dcm = rate.he_dcm; + ht = rate.ht, vht = rate.vht, + mhz = rate.mhz, nss = rate.nss, + mcs = rate.mcs, sgi = rate.short_gi, + he = rate.he, he_gi = rate.he_gi, + he_dcm = rate.he_dcm, + eht = rate.eht, eht_gi = rate.eht_gi, + eht_dcm = rate.eht_dcm if (ht || vht) { if (vht) s += ', VHT-MCS\xa0%d'.format(mcs); @@ -217,6 +219,13 @@ function format_wifirate(rate) { if (he_dcm) s += ', HE-DCM\xa0%d'.format(he_dcm); } + if (eht) { + s += ', EHT-MCS\xa0%d'.format(mcs); + if (nss) s += ', EHT-NSS\xa0%d'.format(nss); + if (eht_gi) s += ', EHT-GI\xa0%d'.format(eht_gi); + if (eht_dcm) s += ', EHT-DCM\xa0%d'.format(eht_dcm); + } + return s; } @@ -298,117 +307,144 @@ function add_dependency_permutations(o, deps) { o.depends(res[i]); } +// Define a class CBIWifiFrequencyValue that extends form.Value var CBIWifiFrequencyValue = form.Value.extend({ + // Declare an RPC method to get the frequency list for a given device callFrequencyList: rpc.declare({ object: 'iwinfo', method: 'freqlist', - params: [ 'device' ], + params: ['device'], expect: { results: [] } }), + // Load method to fetch WiFi device details and frequency list load: function(section_id) { return Promise.all([ - network.getWifiDevice(section_id), - this.callFrequencyList(section_id) + network.getWifiDevice(section_id), // Get WiFi device info + this.callFrequencyList(section_id) // Call frequency list ]).then(L.bind(function(data) { - this.channels = { - '2g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [], - '5g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [], - '6g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [], - '60g': [] - }; - - for (var i = 0; i < data[1].length; i++) { - if (!data[1][i].band) - continue; + // Initialize channels for different bands + this.channels = { + '2g': L.hasSystemFeature('hostapd', 'acs') ? ['auto', 'auto', true] : [], + '5g': L.hasSystemFeature('hostapd', 'acs') ? ['auto', 'auto', true] : [], + '6g': L.hasSystemFeature('hostapd', 'acs') ? ['auto', 'auto', true] : [], + '60g': [] + }; - var band = '%dg'.format(data[1][i].band); + // Populate channels based on the frequency data received + for (var i = 0; i < data[1].length; i++) { + if (!data[1][i].band) continue; // Skip if no band information - this.channels[band].push( - data[1][i].channel, - '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz), - !data[1][i].restricted - ); - } + var band = '%dg'.format(data[1][i].band); // Format band string - var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null) - .reduce(function(o, v) { o[v] = true; return o }, {}); + this.channels[band].push( + data[1][i].channel, + '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz), // Channel details + !data[1][i].restricted // Restricted status + ); + } - this.modes = [ - '', 'Legacy', hwmodelist.a || hwmodelist.b || hwmodelist.g, - 'n', 'N', hwmodelist.n, - 'ac', 'AC', L.hasSystemFeature('hostapd', '11ac') && hwmodelist.ac, - 'ax', 'AX', L.hasSystemFeature('hostapd', '11ax') && hwmodelist.ax - ]; + // Create a list of hardware modes based on the device capabilities + var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null) + .reduce(function(o, v) { o[v] = true; return o }, {}); + + // Define supported modes + this.modes = [ + '', 'Legacy', hwmodelist.a || hwmodelist.b || hwmodelist.g, + 'n', 'N', hwmodelist.n, + 'ac', 'AC', L.hasSystemFeature('hostapd', '11ac') && hwmodelist.ac, + 'ax', 'AX', L.hasSystemFeature('hostapd', '11ax') && hwmodelist.ax, + 'be', 'BE', L.hasSystemFeature('hostapd', '11be') && hwmodelist.be + ]; + // Create a list of HT modes based on device capabilities 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 - ], - 'ax': [ - 'HE20', '20 MHz', htmodelist.HE20, - 'HE40', '40 MHz', htmodelist.HE40, - 'HE80', '80 MHz', htmodelist.HE80, - 'HE160', '160 MHz', htmodelist.HE160 - ] - }; + // Define supported HT modes + 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 + ], + 'ax': [ + 'HE20', '20 MHz', htmodelist.HE20, + 'HE40', '40 MHz', htmodelist.HE40, + 'HE80', '80 MHz', htmodelist.HE80, + 'HE160', '160 MHz', htmodelist.HE160 + ], + 'be': [ + 'EHT20', '20 MHz', htmodelist.EHT20, + 'EHT40', '40 MHz', htmodelist.EHT40, + 'EHT80', '80 MHz', htmodelist.EHT80, + 'EHT160', '160 MHz', htmodelist.EHT160, + 'EHT320', '320 MHz', htmodelist.EHT320 + ] + }; - this.bands = { - '': [ - '2g', '2.4 GHz', this.channels['2g'].length > 3, - '5g', '5 GHz', this.channels['5g'].length > 3, - '60g', '60 GHz', this.channels['60g'].length > 0 - ], - 'n': [ - '2g', '2.4 GHz', this.channels['2g'].length > 3, - '5g', '5 GHz', this.channels['5g'].length > 3 - ], - 'ac': [ - '5g', '5 GHz', true - ], - 'ax': [ - '2g', '2.4 GHz', this.channels['2g'].length > 3, - '5g', '5 GHz', this.channels['5g'].length > 3, - '6g', '6 GHz', this.channels['6g'].length > 3 - ] + // Define available bands for widget selection based on channel availability + // AX and BE are available on 2/5/6G bands + this.bands = { + '': [ + '2g', '2.4 GHz', this.channels['2g'].length > 3, + '5g', '5 GHz', this.channels['5g'].length > 3, + '6g', '6 GHz', this.channels['6g'].length > 3, + '60g', '60 GHz', this.channels['60g'].length > 0 + ], + 'n': [ + '2g', '2.4 GHz', this.channels['2g'].length > 3, + '5g', '5 GHz', this.channels['5g'].length > 3 + ], + 'ac': [ + '5g', '5 GHz', true + ], + 'ax': [ + '2g', '2.4 GHz', this.channels['2g'].length > 3, + '5g', '5 GHz', this.channels['5g'].length > 3, + '6g', '6 GHz', this.channels['6g'].length > 3 + ], + 'be': [ + '2g', '2.4 GHz', this.channels['2g'].length > 3, + '5g', '5 GHz', this.channels['5g'].length > 3, + '6g', '6 GHz', this.channels['6g'].length > 3 + ], }; }, this)); }, + // Set values in the select element setValues: function(sel, vals) { - if (sel.vals) - sel.vals.selected = sel.selectedIndex; + if (sel.vals) sel.vals.selected = sel.selectedIndex; // Save current selection - while (sel.options[0]) - sel.remove(0); + while (sel.options[0]) sel.remove(0); // Clear existing options + // Add new options based on provided values 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 (vals[i + 2]) // Check if the option is not restricted + sel.add(E('option', { value: vals[i + 0] }, [vals[i + 1]])); + // Restore the previous selection if (vals && !isNaN(vals.selected)) sel.selectedIndex = vals.selected; - sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : ''; - sel.vals = vals; + sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : ''; // Hide if only one option + sel.vals = vals; // Store values for later reference }, + // Toggle WiFi mode selection toggleWifiMode: function(elem) { this.toggleWifiHTMode(elem); this.toggleWifiBand(elem); }, + // Update the HT mode based on the selected mode toggleWifiHTMode: function(elem) { var mode = elem.querySelector('.mode'); var bwdt = elem.querySelector('.htmode'); @@ -416,16 +452,17 @@ var CBIWifiFrequencyValue = form.Value.extend({ this.setValues(bwdt, this.htmodes[mode.value]); }, + // Update the band selection based on the selected mode toggleWifiBand: function(elem) { var mode = elem.querySelector('.mode'); var band = elem.querySelector('.band'); this.setValues(band, this.bands[mode.value]); - this.toggleWifiChannel(elem); - - this.map.checkDepends(); + this.toggleWifiChannel(elem); // Update available channels + this.map.checkDepends(); // Check dependencies for the UI }, + // Update the channel selection based on the selected band toggleWifiChannel: function(elem) { var band = elem.querySelector('.band'); var chan = elem.querySelector('.channel'); @@ -433,19 +470,23 @@ var CBIWifiFrequencyValue = form.Value.extend({ this.setValues(chan, this.channels[band.value]); }, + // Set initial values for the configuration elements 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'), - bandval = uci.get('wireless', section_id, 'band'); - - this.setValues(mode, this.modes); - - if (/HE20|HE40|HE80|HE160/.test(htval)) + 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'), + bandval = uci.get('wireless', section_id, 'band'); + + this.setValues(mode, this.modes); // Set modes + + // Determine mode based on htmode value + if (/EHT20|EHT40|EHT80|EHT160|EHT320/.test(htval)) + mode.value = 'be'; + else if (/HE20|HE40|HE80|HE160/.test(htval)) mode.value = 'ax'; else if (/VHT20|VHT40|VHT80|VHT160/.test(htval)) mode.value = 'ac'; @@ -454,33 +495,36 @@ var CBIWifiFrequencyValue = form.Value.extend({ else mode.value = ''; - this.toggleWifiMode(elem); + this.toggleWifiMode(elem); // Update dependent selections + // Determine initial band selection based on hwmode if (hwval != null) { this.useBandOption = false; - if (/a/.test(hwval)) + if (mode.value === 'be') + band.value = '6g'; + else if (/a/.test(hwval)) band.value = '5g'; else band.value = '2g'; - } - else { + } else { this.useBandOption = true; - - band.value = bandval; + band.value = bandval; // Use provided band value } - this.toggleWifiBand(elem); + this.toggleWifiBand(elem); // Update dependent selections - bwdt.value = htval; - chan.value = chval || (chan.options[0] ? chan.options[0].value : 'auto'); + bwdt.value = htval; // Set HT mode + chan.value = chval || (chan.options[0] ? chan.options[0].value : 'auto'); // Set channel - return elem; + return elem; // Return the element for further processing }, + // Render the widget for WiFi configuration renderWidget: function(section_id, option_index, cfgvalue) { var elem = E('div'); + // Create labels and select elements for mode, band, channel, and width dom.content(elem, [ E('label', { 'style': 'float:left; margin-right:3px' }, [ _('Mode'), E('br'), @@ -518,39 +562,43 @@ var CBIWifiFrequencyValue = form.Value.extend({ 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly }) ]), - E('br', { 'style': 'clear:left' }) + E('br', { 'style': 'clear:left' }) // Clear float ]); - return this.setInitialValues(section_id, elem); + return this.setInitialValues(section_id, elem); // Initialize values for the widget }, + // Retrieve configuration values from UCI for a given section cfgvalue: function(section_id) { return [ - uci.get('wireless', section_id, 'htmode'), - uci.get('wireless', section_id, 'hwmode') || uci.get('wireless', section_id, 'band'), - uci.get('wireless', section_id, 'channel') + uci.get('wireless', section_id, 'htmode'), + uci.get('wireless', section_id, 'hwmode') || uci.get('wireless', section_id, 'band'), + uci.get('wireless', section_id, 'channel') ]; }, + // Retrieve form values for a given section 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 + node.querySelector('.htmode').value, + node.querySelector('.band').value, + node.querySelector('.channel').value ]; }, + // Write configuration values back to UCI write: function(section_id, value) { - uci.set('wireless', section_id, 'htmode', value[0] || null); + uci.set('wireless', section_id, 'htmode', value[0] || null); // Set HT mode + // Set band or hardware mode based on selection if (this.useBandOption) uci.set('wireless', section_id, 'band', value[1]); else - uci.set('wireless', section_id, 'hwmode', (value[1] == '2g') ? '11g' : '11a'); + uci.set('wireless', section_id, 'hwmode', (value[1] === '2g') ? '11g' : '11a'); - uci.set('wireless', section_id, 'channel', value[2]); + uci.set('wireless', section_id, 'channel', value[2]); // Set channel } });