From ff3bd1685567a5c628f1ea58be53b901f1ff94da Mon Sep 17 00:00:00 2001 From: c4ch3c4d3 Date: Tue, 18 Aug 2026 07:26:55 -0500 Subject: [PATCH] feat: band-aware channel pickers for Open AP and Evil WPA --- .../remote_access/pager-webui/www/js/views.js | 88 +++++++++++++++++-- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/payload/user/remote_access/pager-webui/www/js/views.js b/payload/user/remote_access/pager-webui/www/js/views.js index 2c4c5be..23d9a4a 100644 --- a/payload/user/remote_access/pager-webui/www/js/views.js +++ b/payload/user/remote_access/pager-webui/www/js/views.js @@ -410,10 +410,53 @@ views.pineap = (root) => { return { destroy: () => clearInterval(iv) }; }; -const OPEN_CHANNELS = Array.from({ length: 11 }, (_, i) => { - const c = i + 1; - return [c, 'Channel ' + c + ' (' + (2412 + (c - 1) * 5) + ' MHz)']; -}); +const BAND_GROUPS = [ + { band: '2.4', label: '2.4 GHz', dfs: false, + channels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] }, + { band: '5', label: '5 GHz', dfs: true, + channels: [36, 40, 44, 48, 52, 56, 60, 64, + 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144, + 149, 153, 157, 161, 165] }, + { band: '6', label: '6 GHz (WPA3/OWE only)', dfs: false, + channels: Array.from({ length: 14 }, (_, i) => 181 + i * 4) } +]; +const DFS_SET = new Set([52, 56, 60, 64, 100, 104, 108, 112, 116, 120, + 124, 128, 132, 136, 140, 144]); + +function chanFreq(band, ch) { + if (band === '2.4') return 2412 + (ch - 1) * 5; + if (band === '5') return 5180 + (ch - 36) * 5; + return 5955 + (ch - 1) * 5; // 6 GHz +} + +function chanLabel(band, ch) { + return 'Channel ' + ch + ' (' + chanFreq(band, ch) + ' MHz)' + + (band === '5' && DFS_SET.has(ch) ? ' (DFS)' : ''); +} + +function chanSelect(sel, value) { + BAND_GROUPS.forEach((g) => { + const og = h('optgroup', { label: g.label }); + g.channels.forEach((ch) => { + og.appendChild(h('option', { value: ch, text: chanLabel(g.band, ch) })); + }); + sel.appendChild(og); + }); + if (value != null) { + const opts = Array.prototype.slice.call(sel.options); + const hit = opts.find((o) => Number(o.value) === Number(value)); + if (hit) sel.value = hit.value; + } + return sel; +} + +function bandOfChannel(ch) { + if (ch == null) return '2.4'; + ch = Number(ch); + if (ch >= 1 && ch <= 14) return '2.4'; + if (ch >= 36 && ch <= 177) return '5'; + return '6'; +} const OPEN_COUNTRIES = [ ['US', 'United States'], ['DZ', 'Algeria'], ['AR', 'Argentina'], ['AU', 'Australia'], ['AT', 'Austria'], ['BH', 'Bahrain'], ['BM', 'Bermuda'], ['BO', 'Bolivia'], ['BR', 'Brazil'], @@ -448,7 +491,12 @@ views.pineap_open = (root) => { const ssidIn = h('input', { id: 'oa-ssid' }); const bssidIn = h('input', { id: 'oa-bssid' }); const chSel = h('select', { id: 'oa-channel' }); - OPEN_CHANNELS.forEach(([v, l]) => chSel.appendChild(h('option', { value: v, text: l }))); + chanSelect(chSel, null); + const bandHint = h('div', { class: 'muted', style: 'font-size:12px;margin-top:4px' }); + chSel.addEventListener('change', () => { + const b = bandOfChannel(chSel.value); + bandHint.textContent = b === '6' ? '6 GHz open APs require WPA3/OWE on real clients — most devices will not associate to an open 6 GHz network.' : ''; + }); const coSel = h('select', { id: 'oa-country' }); OPEN_COUNTRIES.forEach(([v, l]) => coSel.appendChild(h('option', { value: v, text: l }))); const hiddenCb = h('input', { type: 'checkbox', id: 'oa-hidden' }); @@ -464,7 +512,7 @@ views.pineap_open = (root) => { h('div', {}, h('label', {}, 'Open SSID', ssidIn)), h('div', {}, h('label', {}, 'BSSID', bssidIn)))); card.appendChild(h('div', { class: 'row' }, - h('div', {}, h('label', {}, 'Channel', chSel)), + h('div', {}, h('label', {}, 'Channel', chSel), bandHint), h('div', {}, h('label', {}, 'Current Country', coSel)))); card.appendChild(h('div', { class: 'row' }, h('div', {}, h('label', { class: 'switch' }, hiddenCb, h('span', { class: 'track' }), ' Hidden')), @@ -579,7 +627,12 @@ views.pineap_open = (root) => { const open = a.open || {}; ssidIn.value = open.ssid || ''; bssidIn.value = open.bssid || ''; - if (open.channel != null) chSel.value = String(open.channel); + if (open.channel != null) { + const opts = Array.prototype.slice.call(chSel.options); + if (opts.some((o) => Number(o.value) === Number(open.channel))) { + chSel.value = String(open.channel); + } + } if (open.country) coSel.value = open.country; hiddenCb.checked = !!open.hidden; state.enabledLoaded = !!(a.open); @@ -614,16 +667,29 @@ views.pineap_evilwpa = (root) => { EVIL_ENC.forEach(([v, l]) => encSel.appendChild(h('option', { value: v, text: l }))); const hiddenCb = h('input', { type: 'checkbox', id: 'ew-hidden' }); const enabledCb = h('input', { type: 'checkbox', id: 'ew-enabled' }); + const wpaChan = h('select', { id: 'ew-channel' }); + chanSelect(wpaChan, null); + const wpaHint = h('div', { class: 'muted', style: 'font-size:12px;margin-top:4px' }); + wpaChan.addEventListener('change', () => { + const b = bandOfChannel(wpaChan.value); + const six = b === '6'; + Array.prototype.forEach.call(encSel.options, (o) => { o.disabled = six && o.value === 'psk2'; }); + if (six && encSel.value === 'psk2') encSel.value = 'sae'; + wpaHint.textContent = six ? '6 GHz requires WPA3 (SAE or OWE).' : ''; + }); cfg.appendChild(h('label', {}, 'SSID', ssidIn)); cfg.appendChild(h('label', {}, 'Passphrase', pskIn)); cfg.appendChild(h('label', {}, 'Encryption', encSel)); + cfg.appendChild(h('label', {}, 'Channel', wpaChan)); + cfg.appendChild(wpaHint); cfg.appendChild(h('label', { class: 'switch' }, hiddenCb, h('span', { class: 'track' }), 'Hidden')); cfg.appendChild(h('label', { class: 'switch' }, enabledCb, h('span', { class: 'track' }), 'Enabled')); cfg.appendChild(h('div', { class: 'row' }, h('div', {}, btn('Save', () => { PagerAPI.post('/api/pineap/wifi/set_ap', { wpa: { ssid: ssidIn.value, passphrase: pskIn.value, enctype: encSel.value, - hidden: hiddenCb.checked, enabled: enabledCb.checked } + hidden: hiddenCb.checked, enabled: enabledCb.checked, + channel: wpaChan.value ? parseInt(wpaChan.value, 10) : 1 } }).then(() => { App.toast('Evil WPA saved'); load(); }).catch(() => App.toast('Failed', 'error')); })), h('div', { class: 'muted', style: 'align-self:center;font-size:12px' }, 'Applying reconfigures the radio — you may be disconnected briefly.'))); @@ -675,6 +741,12 @@ views.pineap_evilwpa = (root) => { } hiddenCb.checked = !!w.hidden; enabledCb.checked = !!w.enabled; + if (w.channel != null) { + const opts = Array.prototype.slice.call(wpaChan.options); + if (opts.some((o) => Number(o.value) === Number(w.channel))) { + wpaChan.value = String(w.channel); + } + } }).catch(() => {}); PagerAPI.get('/api/pineap/get_config').then((r) => { const p = r.data || {};