feat: band-aware channel pickers for Open AP and Evil WPA
This commit is contained in:
@@ -410,10 +410,53 @@ views.pineap = (root) => {
|
|||||||
return { destroy: () => clearInterval(iv) };
|
return { destroy: () => clearInterval(iv) };
|
||||||
};
|
};
|
||||||
|
|
||||||
const OPEN_CHANNELS = Array.from({ length: 11 }, (_, i) => {
|
const BAND_GROUPS = [
|
||||||
const c = i + 1;
|
{ band: '2.4', label: '2.4 GHz', dfs: false,
|
||||||
return [c, 'Channel ' + c + ' (' + (2412 + (c - 1) * 5) + ' MHz)'];
|
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 = [
|
const OPEN_COUNTRIES = [
|
||||||
['US', 'United States'], ['DZ', 'Algeria'], ['AR', 'Argentina'], ['AU', 'Australia'],
|
['US', 'United States'], ['DZ', 'Algeria'], ['AR', 'Argentina'], ['AU', 'Australia'],
|
||||||
['AT', 'Austria'], ['BH', 'Bahrain'], ['BM', 'Bermuda'], ['BO', 'Bolivia'], ['BR', 'Brazil'],
|
['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 ssidIn = h('input', { id: 'oa-ssid' });
|
||||||
const bssidIn = h('input', { id: 'oa-bssid' });
|
const bssidIn = h('input', { id: 'oa-bssid' });
|
||||||
const chSel = h('select', { id: 'oa-channel' });
|
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' });
|
const coSel = h('select', { id: 'oa-country' });
|
||||||
OPEN_COUNTRIES.forEach(([v, l]) => coSel.appendChild(h('option', { value: v, text: l })));
|
OPEN_COUNTRIES.forEach(([v, l]) => coSel.appendChild(h('option', { value: v, text: l })));
|
||||||
const hiddenCb = h('input', { type: 'checkbox', id: 'oa-hidden' });
|
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', {}, 'Open SSID', ssidIn)),
|
||||||
h('div', {}, h('label', {}, 'BSSID', bssidIn))));
|
h('div', {}, h('label', {}, 'BSSID', bssidIn))));
|
||||||
card.appendChild(h('div', { class: 'row' },
|
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))));
|
h('div', {}, h('label', {}, 'Current Country', coSel))));
|
||||||
card.appendChild(h('div', { class: 'row' },
|
card.appendChild(h('div', { class: 'row' },
|
||||||
h('div', {}, h('label', { class: 'switch' }, hiddenCb, h('span', { class: 'track' }), ' Hidden')),
|
h('div', {}, h('label', { class: 'switch' }, hiddenCb, h('span', { class: 'track' }), ' Hidden')),
|
||||||
@@ -579,7 +627,12 @@ views.pineap_open = (root) => {
|
|||||||
const open = a.open || {};
|
const open = a.open || {};
|
||||||
ssidIn.value = open.ssid || '';
|
ssidIn.value = open.ssid || '';
|
||||||
bssidIn.value = open.bssid || '';
|
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;
|
if (open.country) coSel.value = open.country;
|
||||||
hiddenCb.checked = !!open.hidden;
|
hiddenCb.checked = !!open.hidden;
|
||||||
state.enabledLoaded = !!(a.open);
|
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 })));
|
EVIL_ENC.forEach(([v, l]) => encSel.appendChild(h('option', { value: v, text: l })));
|
||||||
const hiddenCb = h('input', { type: 'checkbox', id: 'ew-hidden' });
|
const hiddenCb = h('input', { type: 'checkbox', id: 'ew-hidden' });
|
||||||
const enabledCb = h('input', { type: 'checkbox', id: 'ew-enabled' });
|
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', {}, 'SSID', ssidIn));
|
||||||
cfg.appendChild(h('label', {}, 'Passphrase', pskIn));
|
cfg.appendChild(h('label', {}, 'Passphrase', pskIn));
|
||||||
cfg.appendChild(h('label', {}, 'Encryption', encSel));
|
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' }, hiddenCb, h('span', { class: 'track' }), 'Hidden'));
|
||||||
cfg.appendChild(h('label', { class: 'switch' }, enabledCb, h('span', { class: 'track' }), 'Enabled'));
|
cfg.appendChild(h('label', { class: 'switch' }, enabledCb, h('span', { class: 'track' }), 'Enabled'));
|
||||||
cfg.appendChild(h('div', { class: 'row' },
|
cfg.appendChild(h('div', { class: 'row' },
|
||||||
h('div', {}, btn('Save', () => {
|
h('div', {}, btn('Save', () => {
|
||||||
PagerAPI.post('/api/pineap/wifi/set_ap', {
|
PagerAPI.post('/api/pineap/wifi/set_ap', {
|
||||||
wpa: { ssid: ssidIn.value, passphrase: pskIn.value, enctype: encSel.value,
|
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'));
|
}).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.')));
|
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;
|
hiddenCb.checked = !!w.hidden;
|
||||||
enabledCb.checked = !!w.enabled;
|
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(() => {});
|
}).catch(() => {});
|
||||||
PagerAPI.get('/api/pineap/get_config').then((r) => {
|
PagerAPI.get('/api/pineap/get_config').then((r) => {
|
||||||
const p = r.data || {};
|
const p = r.data || {};
|
||||||
|
|||||||
Reference in New Issue
Block a user