'use strict';
'require view';
'require poll';
'require fs';
'require dom';
'require ui';
'require uci';
'require form';
'require network';
'require tools.widgets as widgets';
/*
Cipher helper function
*/
function resolveCipher(cipherRaw) {
const cipherMap = {
'2': { name: 'TKIP', aes: false },
'4': { name: 'CCMP', aes: true },
'6': { name: 'BIP', aes: false },
'8': { name: 'GCMP', aes: true },
'9': { name: 'GCMP-256', aes: true },
'10': { name: 'CCMP-256', aes: true },
'11': { name: 'BIP-GMAC-128', aes: false },
'12': { name: 'BIP-GMAC-256', aes: false },
'13': { name: 'BIP-CMAC-256', aes: false }
};
if (!cipherRaw) return { name: 'unknown', aes: false };
// lookup by OUI suite id (e.g. "00-0f-ac:4")
if (cipherRaw.includes(':')) {
const id = cipherRaw.split(':').pop();
if (cipherMap[id]) return cipherMap[id];
return { name: `unknown (${cipherRaw})`, aes: false };
}
// fallback: lookup by name (e.g. raw "CCMP" from iwinfo)
for (const k in cipherMap) {
if (cipherMap[k].name === cipherRaw) return cipherMap[k];
}
return { name: cipherRaw, aes: false };
}
/*
normalize a bssid to upper case for case-insensitive comparison
*/
function normBssid(bssid) {
return (bssid || '').toUpperCase();
}
/*
identity of all networks already configured as travelmate uplink
*/
function configuredUplinks(iface) {
const sections = uci.sections('wireless', 'wifi-iface');
const list = [];
for (let i = 0; i < sections.length; i++) {
if (sections[i].network === iface && sections[i].mode === 'sta') {
list.push({
device: sections[i].device || '',
ssid: sections[i].ssid || '',
bssid: normBssid(sections[i].bssid)
});
}
}
return list;
}
/*
check whether a scanned network is already configured, an uplink
without bssid matches every bssid of the same ssid
*/
function isConfigured(list, radio, ssid, bssid) {
for (let i = 0; i < list.length; i++) {
if (list[i].device !== radio || list[i].ssid !== ssid) {
continue;
}
if (!list[i].bssid || list[i].bssid === normBssid(bssid)) {
return true;
}
}
return false;
}
/*
scan dialog styles, aligned with the overview page
*/
function scanStyle() {
return E('style', { 'type': 'text/css' }, [
'#trm-scan {' +
'--trm-card-bg: rgba(128,128,128,.07);' +
'--trm-card-border: rgba(128,128,128,.28);' +
'--trm-muted: GrayText;' +
'--trm-ok: #1f8a5f;' +
'--trm-err: #c0392b;' +
'--trm-warn: #b7791f;' +
'}' +
'@media (prefers-color-scheme: dark) {' +
'#trm-scan {' +
'--trm-ok: #63c79b;' +
'--trm-err: #e8897e;' +
'--trm-warn: #e0b35c;' +
'}}' +
'#trm-scan .trm-list { display: grid; gap: .5em; margin-bottom: .75em; }' +
'#trm-scan .trm-card { display: grid; gap: .4em; background: var(--trm-card-bg); border: 1px solid var(--trm-card-border); border-radius: 8px; padding: .6em .8em; min-width: 0; }' +
'#trm-scan .trm-head { display: flex; align-items: baseline; gap: .6em; }' +
'#trm-scan .trm-ssid { flex: 1 1 auto; min-width: 0; font-size: 1.1em; font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }' +
'#trm-scan .trm-qual { flex: 0 0 auto; font-variant-numeric: tabular-nums; }' +
'#trm-scan .trm-bar { height: .35em; border-radius: 3px; background: var(--trm-card-border); overflow: hidden; }' +
'#trm-scan .trm-bar-fill { height: 100%; background: var(--trm-muted); }' +
'#trm-scan .trm-card[data-signal="good"] .trm-qual { color: var(--trm-ok); }' +
'#trm-scan .trm-card[data-signal="good"] .trm-bar-fill { background: var(--trm-ok); }' +
'#trm-scan .trm-card[data-signal="weak"] .trm-qual { color: var(--trm-warn); }' +
'#trm-scan .trm-card[data-signal="weak"] .trm-bar-fill { background: var(--trm-warn); }' +
'#trm-scan .trm-card[data-signal="low"] .trm-qual { color: var(--trm-err); }' +
'#trm-scan .trm-card[data-signal="low"] .trm-bar-fill { background: var(--trm-err); }' +
'#trm-scan .trm-foot { display: flex; flex-wrap: wrap; align-items: center; gap: .35em .6em; font-size: .85em; color: var(--trm-muted); }' +
'#trm-scan .trm-mono { font-family: monospace; }' +
'#trm-scan .trm-chip { padding: .1em .5em; border-radius: 6px; border: 1px solid var(--trm-card-border); }' +
'#trm-scan .trm-chip-open { color: var(--trm-warn); border-color: var(--trm-warn); }' +
'#trm-scan .trm-act { margin-left: auto; }' +
'#trm-scan .trm-known { color: var(--trm-ok); }' +
'#trm-scan .trm-empty { color: var(--trm-muted); padding: .5em 0; }'
]);
}
/*
render a single scan result card
*/
function scanCard(quality, minQuality, ssidNode, channel, bssid, encLabel, encOpen, action) {
const value = isNaN(quality) ? 0 : Math.max(0, Math.min(100, quality));
let signal = 'low';
if (value >= Math.min(100, minQuality + 25)) {
signal = 'good';
} else if (value >= minQuality) {
signal = 'weak';
}
return E('div', { 'class': 'trm-card', 'data-signal': signal }, [
E('div', { 'class': 'trm-head' }, [
E('div', { 'class': 'trm-ssid' }, [ssidNode]),
E('span', { 'class': 'trm-qual' }, [value + ' %'])
]),
E('div', { 'class': 'trm-bar' }, [
E('div', { 'class': 'trm-bar-fill', 'style': 'width:' + value + '%' })
]),
E('div', { 'class': 'trm-foot' }, [
E('span', { 'class': 'trm-chip' + (encOpen ? ' trm-chip-open' : '') }, [encLabel]),
E('span', {}, [_('Channel') + ' ' + channel]),
E('span', { 'class': 'trm-mono' }, [bssid]),
E('div', { 'class': 'trm-act' }, [action])
])
]);
}
/*
change the status of travelmate stations
*/
function handleToggle(sid) {
let w_device, w_ssid, w_bssid, t_sections, row, element, value, enabled;
w_device = uci.get('wireless', sid, 'device');
w_ssid = uci.get('wireless', sid, 'ssid');
w_bssid = uci.get('wireless', sid, 'bssid');
t_sections = uci.sections('travelmate', 'uplink');
for (let i = 0; i < t_sections.length; i++) {
if (t_sections[i].device === w_device && t_sections[i].ssid === w_ssid && normBssid(t_sections[i].bssid) === normBssid(w_bssid)) {
value = t_sections[i]['enabled'];
value = (value == 0 ? 1 : 0);
enabled = (value == 0 ? _('No') : _('Yes'));
uci.set('travelmate', t_sections[i]['.name'], 'enabled', value);
return uci.save().then(function () {
row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(sid));
if (row) {
element = row.querySelector('.cbi-value-field');
if (element) {
element.textContent = enabled;
}
row.setAttribute('style', 'opacity: 0.5; color: #37c !important;');
}
});
}
}
}
/*
remove wireless and stale travelmate sections
*/
function handleRemove(sid) {
let w_sections, t_sections, match, row, open, count;
uci.remove('wireless', sid);
w_sections = uci.sections('wireless', 'wifi-iface');
t_sections = uci.sections('travelmate', 'uplink');
for (let i = 0; i < t_sections.length; i++) {
match = false;
for (let j = 0; j < w_sections.length; j++) {
if (t_sections[i].device === w_sections[j].device && t_sections[i].ssid === w_sections[j].ssid && normBssid(t_sections[i].bssid) === normBssid(w_sections[j].bssid)) {
match = true;
break;
}
}
if (match === false) {
open = +t_sections[i].opensta || 0;
if (open === 1) {
count = +uci.get('travelmate', 'global', 'trm_autoaddcnt') || 0;
if (count > 0) {
count--;
uci.set('travelmate', 'global', 'trm_autoaddcnt', count);
}
}
uci.remove('travelmate', t_sections[i]['.name']);
}
}
return uci.save().then(function () {
row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(sid));
if (row) {
row.setAttribute('style', 'opacity: 0.5; color: #a22 !important;');
}
});
}
/*
add missing travelmate sections
*/
function handleSectionsAdd(iface) {
let w_sections, t_sections, match;
w_sections = uci.sections('wireless', 'wifi-iface');
t_sections = uci.sections('travelmate', 'uplink');
for (let i = 0; i < w_sections.length; i++) {
if (w_sections[i].mode !== 'sta' || w_sections[i].network !== iface) {
continue;
}
match = false;
for (let j = 0; j < t_sections.length; j++) {
if (w_sections[i].device === t_sections[j].device && w_sections[i].ssid === t_sections[j].ssid && normBssid(w_sections[i].bssid) === normBssid(t_sections[j].bssid)) {
match = true;
break;
}
}
if (match === false) {
let vpn_stdservice = uci.get('travelmate', 'global', 'trm_stdvpnservice');
let vpn_stdiface = uci.get('travelmate', 'global', 'trm_stdvpniface');
let sid = uci.add('travelmate', 'uplink');
uci.set('travelmate', sid, 'enabled', '1');
uci.set('travelmate', sid, 'device', w_sections[i].device);
uci.set('travelmate', sid, 'ssid', w_sections[i].ssid);
uci.set('travelmate', sid, 'bssid', w_sections[i].bssid);
if (vpn_stdservice && vpn_stdiface) {
uci.set('travelmate', sid, 'vpn', '1');
uci.set('travelmate', sid, 'vpnservice', vpn_stdservice);
uci.set('travelmate', sid, 'vpniface', vpn_stdiface);
}
}
}
}
/*
update travelmate sections
*/
function handleSectionsVal(action, section_id, option, value) {
let w_device, w_ssid, w_bssid, t_sections;
w_device = uci.get('wireless', section_id, 'device');
w_ssid = uci.get('wireless', section_id, 'ssid');
w_bssid = uci.get('wireless', section_id, 'bssid');
t_sections = uci.sections('travelmate', 'uplink');
for (let i = 0; i < t_sections.length; i++) {
if (t_sections[i].device === w_device && t_sections[i].ssid === w_ssid && normBssid(t_sections[i].bssid) === normBssid(w_bssid)) {
if (action === 'get') {
return t_sections[i][option];
} else if (action === 'set') {
return uci.set('travelmate', t_sections[i]['.name'], option, value);
} else if (action === 'del') {
return uci.unset('travelmate', t_sections[i]['.name'], option);
}
}
}
}
/*
update travelmate status
*/
function handleStatus() {
let parseErrCount = 0;
poll.add(function () {
return Promise.all([
L.resolveDefault(fs.stat('/var/run/travelmate/travelmate.refresh'), null).then(function (stat) {
if (!stat) return;
return L.resolveDefault(fs.read_direct('/var/run/travelmate/travelmate.refresh'), null).then(async function (content) {
await fs.remove('/var/run/travelmate/travelmate.refresh');
if (content === 'ui_reload') {
location.reload();
} else if (content === 'cfg_reload') {
if (document.readyState === 'complete') {
uci.unload('wireless');
uci.unload('travelmate');
}
await Promise.all([
uci.load('wireless'),
uci.load('travelmate')
]);
const rows = document.querySelectorAll('.cbi-section-table-row[data-sid]');
for (let i = 0; i < rows.length; i++) {
const item = rows[i].querySelector('.cbi-value-field[data-title="Enabled"]');
const value = handleSectionsVal('get', rows[i].getAttribute('data-sid'), 'enabled');
if (item) {
item.textContent = (value === '1' ? _('Yes') : _('No'));
}
}
}
});
}),
L.resolveDefault(fs.stat('/var/run/travelmate/travelmate.runtime.json'), null).then(function (stat) {
if (!stat) return;
return L.resolveDefault(fs.read_direct('/var/run/travelmate/travelmate.runtime.json'), null).then(function (content) {
if (!content) return;
let info = null;
try {
info = JSON.parse(content);
parseErrCount = 0;
} catch (e) {
parseErrCount++;
if (parseErrCount >= 5) {
ui.addNotification(null, E('p', _('Unable to parse the travelmate runtime information!')), 'error');
poll.stop();
}
return;
}
if (!info || !info.data) return;
const vpnMatch = (info.data.run_flags || '').match(/vpn:\s*(.)/);
const uplinkId = (info.data.station_id || '').trim().split('/');
const oldUplinkView = document.getElementsByName('uplinkStation');
const w_sections = uci.sections('wireless', 'wifi-iface');
const vpnStatus = vpnMatch ? vpnMatch[1] : '✘';
let t_device, t_ssid, t_bssid, newUplinkView, uplinkColor;
t_device = uplinkId[0];
t_bssid = uplinkId[uplinkId.length - 1];
for (let i = 1; i < uplinkId.length - 1; i++) {
if (!t_ssid) {
t_ssid = uplinkId[i];
} else {
t_ssid = t_ssid + '/' + uplinkId[i];
}
}
if (t_ssid === '-') {
if (oldUplinkView.length > 0) {
oldUplinkView[0].removeAttribute('style');
oldUplinkView[0].removeAttribute('name');
}
} else {
uplinkColor = (vpnStatus === '✔' ? 'rgb(68, 170, 68)' : 'rgb(51, 119, 204)');
for (let i = 0; i < w_sections.length; i++) {
newUplinkView = document.getElementById('cbi-wireless-' + w_sections[i]['.name']);
if (t_device === w_sections[i].device && t_ssid === w_sections[i].ssid && normBssid(t_bssid) === normBssid(w_sections[i].bssid || '-')) {
if (oldUplinkView.length === 0 && newUplinkView) {
newUplinkView.setAttribute('name', 'uplinkStation');
newUplinkView.setAttribute('style', 'text-align: left !important; color: ' + uplinkColor + ' !important;font-weight: bold !important;');
} else if (oldUplinkView.length > 0 && newUplinkView && oldUplinkView[0].getAttribute('id') !== newUplinkView.getAttribute('id')) {
oldUplinkView[0].removeAttribute('style');
oldUplinkView[0].removeAttribute('name');
newUplinkView.setAttribute('name', 'uplinkStation');
newUplinkView.setAttribute('style', 'text-align: left !important; color: ' + uplinkColor + ' !important;font-weight: bold !important;');
} else if (newUplinkView && newUplinkView.style.color !== uplinkColor) {
newUplinkView.setAttribute('style', 'text-align: left !important; color: ' + uplinkColor + ' !important;font-weight: bold !important;');
}
}
}
}
});
})
]);
}, 2);
}
return view.extend({
load: function () {
return Promise.all([
uci.load('wireless').catch(() => 0),
uci.load('travelmate').catch(() => 0)
]);
},
render: function () {
/*
basic config check
*/
if (!uci.sections('wireless', 'wifi-device').length) {
ui.addNotification(null, E('p', _('No wireless config / radio found!')), 'error');
return;
} else if (!uci.get('travelmate', 'global')) {
ui.addNotification(null, E('p', _('No travelmate config found!')), 'error');
return;
}
/*
main map
*/
let m, s, o, count;
let iface = uci.get('travelmate', 'global', 'trm_iface') || 'trm_wwan';
m = new form.Map('wireless');
m.chain('travelmate');
s = m.section(form.GridSection, 'wifi-iface', null, _('Overview of all configured uplinks for travelmate. \
You can edit, remove or prioritize existing uplinks by drag & drop and scan for new ones.
\
The currently used uplink connection is emphasized in blue, \
an encrypted VPN uplink connection is emphasized in green.'));
s.filter = function (section_id) {
return (uci.get('wireless', section_id, 'network') == iface && uci.get('wireless', section_id, 'mode') == 'sta');
};
s.anonymous = true;
s.sortable = true;
s.tab('wireless', _('Wireless Settings'));
s.tab('travelmate', _('Travelmate Settings'));
s.tab('vpn', _('VPN Settings'));
s.renderRowActions = function (section_id) {
const btns = [
E('button', {
'class': 'cbi-button drag-handle center',
'style': 'float:none;margin-right:.4em;cursor:move;',
'draggable': true,
'dragstart': L.bind(function (ev) {
this.handleDragStart(ev, this.handleDrag);
}, this),
'dragend': L.bind(function (ev) {
this.handleDragEnd(ev, this.handleDrag);
}, this),
'touchmove': L.bind(function (ev) {
this.handleTouchMove(ev);
}, this),
'touchend': L.bind(function (ev) {
this.handleTouchEnd(ev);
}, this),
'title': _('Drag to reorder'),
'disabled': this.map.readonly || null
}, '☰'),
E('button', {
'class': 'cbi-button cbi-button-action important',
'style': 'float:none;margin-right:.4em;',
'title': _('Edit this network'),
'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
}, _('Edit')),
E('button', {
'class': 'cbi-button cbi-button-apply',
'style': 'float:none;margin-right:.4em;',
'title': _('Enable/Disable this network'),
'click': ui.createHandlerFn(this, handleToggle, section_id)
}, _('On/Off')),
E('button', {
'class': 'cbi-button cbi-button-negative remove',
'title': _('Remove this network'),
'click': ui.createHandlerFn(this, handleRemove, section_id)
}, _('Remove'))
];
return E('td', { 'class': 'td cbi-section-table-cell nowrap cbi-section-actions' }, E('div', btns));
};
o = s.taboption('travelmate', form.Flag, '_enabled', _('Enabled'));
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'enabled';
o.rmempty = false;
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'enabled');
}
o.write = function (section_id, value) {
return handleSectionsVal('set', section_id, 'enabled', value);
}
o = s.taboption('wireless', form.Value, 'device', _('Device'));
o.readonly = true;
o = s.taboption('wireless', form.Value, 'ssid', _('SSID'));
o.datatype = 'maxlength(32)';
o.readonly = true;
o = s.taboption('wireless', form.Value, 'bssid', _('BSSID'));
o.datatype = 'macaddr';
o.readonly = true;
o.textvalue = function (section_id) {
const bssid = uci.get('wireless', section_id, 'bssid');
return bssid ? normBssid(bssid) : null;
};
o = s.taboption('wireless', form.ListValue, 'encryption', _('Encryption'));
o.value('sae', _('WPA3 PSK (SAE)'));
o.value('sae-mixed', _('Mixed WPA2/WPA3 PSK (CCMP)'));
o.value('psk2+ccmp', _('WPA2 PSK (CCMP)'));
o.value('psk2+tkip', _('WPA2 PSK (TKIP)'));
o.value('psk+ccmp', _('WPA PSK (CCMP)'));
o.value('psk+tkip', _('WPA PSK (TKIP)'));
o.value('psk-mixed+ccmp', _('Mixed WPA/WPA2 PSK (CCMP)'));
o.value('psk-mixed+tkip', _('Mixed WPA/WPA2 PSK (TKIP)'));
o.value('wpa3', _('WPA3 802.1X'));
o.value('wpa3-mixed', _('Mixed WPA2/WPA3 802.1X'));
o.value('wpa2+ccmp', _('WPA2 802.1X (CCMP)'));
o.value('wpa2+tkip', _('WPA2 802.1X (TKIP)'));
o.value('wpa+ccmp', _('WPA 802.1X (CCMP)'));
o.value('wpa+tkip', _('WPA 802.1X (TKIP)'));
o.value('wpa-mixed+ccmp', _('Mixed WPA/WPA2 802.1X (CCMP)'));
o.value('wpa-mixed+tkip', _('Mixed WPA/WPA2 802.1X (TKIP)'));
o.value('owe', _('WPA3 OWE'));
o.value('none', _('Open'));
o.default = 'none';
o.textvalue = function (section_id) {
let cfgvalue = this.map.data.get('wireless', section_id, 'encryption');
switch (cfgvalue) {
case 'sae':
cfgvalue = _('WPA3 PSK (SAE)');
break;
case 'sae-mixed':
cfgvalue = _('Mixed WPA2/WPA3 PSK (CCMP)');
break;
case 'psk2+ccmp':
cfgvalue = _('WPA2 PSK (CCMP)');
break;
case 'psk2+tkip':
cfgvalue = _('WPA2 PSK (TKIP)');
break;
case 'psk+ccmp':
cfgvalue = _('WPA PSK (CCMP)');
break;
case 'psk+tkip':
cfgvalue = _('WPA PSK (TKIP)');
break;
case 'psk-mixed+ccmp':
cfgvalue = _('Mixed WPA/WPA2 PSK (CCMP)');
break;
case 'psk-mixed+tkip':
cfgvalue = _('Mixed WPA/WPA2 PSK (TKIP)');
break;
case 'wpa3':
cfgvalue = _('WPA3 802.1X');
break;
case 'wpa3-mixed':
cfgvalue = _('Mixed WPA2/WPA3 802.1X');
break;
case 'wpa2+ccmp':
cfgvalue = _('WPA2 802.1X (CCMP)');
break;
case 'wpa2+tkip':
cfgvalue = _('WPA2 802.1X (TKIP)');
break;
case 'wpa+ccmp':
cfgvalue = _('WPA 802.1X (CCMP)');
break;
case 'wpa+tkip':
cfgvalue = _('WPA 802.1X (TKIP)');
break;
case 'wpa-mixed+ccmp':
cfgvalue = _('Mixed WPA/WPA2 802.1X (CCMP)');
break;
case 'wpa-mixed+tkip':
cfgvalue = _('Mixed WPA/WPA2 802.1X (TKIP)');
break;
case 'owe':
cfgvalue = _('WPA3 OWE');
break;
case 'none':
cfgvalue = _('Open');
break;
}
return cfgvalue;
};
handleStatus();
/*
modal wireless tab
*/
o = s.taboption('wireless', form.Value, 'key', _('Password'));
o.datatype = 'wpakey';
o.depends({ encryption: 'sae', '!contains': true });
o.depends({ encryption: 'psk', '!contains': true });
o.modalonly = true;
o.password = true;
o = s.taboption('wireless', form.Value, 'password', _('Password'));
o.datatype = 'wpakey';
o.depends({ encryption: 'wpa', '!contains': true });
o.modalonly = true;
o.password = true;
o = s.taboption('wireless', form.ListValue, 'eap_type', _('EAP-Method'));
o.depends({ encryption: 'wpa', '!contains': true });
o.value('tls', _('TLS'));
o.value('ttls', _('TTLS'));
o.value('peap', _('PEAP'));
o.value('fast', _('FAST'));
o.default = 'peap';
o.modalonly = true;
o = s.taboption('wireless', form.ListValue, 'auth', _('Authentication'));
o.value('PAP', _('PAP'));
o.value('CHAP', _('CHAP'));
o.value('MSCHAP', _('MSCHAP'));
o.value('MSCHAPV2', _('MSCHAPV2'));
o.value('EAP-GTC', _('EAP-GTC'));
o.value('EAP-MD5', _('EAP-MD5'));
o.value('EAP-MSCHAPV2', _('EAP-MSCHAPV2'));
o.value('EAP-TLS', _('EAP-TLS'));
o.value('auth=PAP', _('auth=PAP'));
o.value('auth=MSCHAPV2', _('auth=MSCHAPV2'));
o.default = 'EAP-MSCHAPV2';
o.depends({ encryption: 'wpa', '!contains': true });
o.modalonly = true;
o = s.taboption('wireless', form.Value, 'identity', _('Identity'));
o.depends({ encryption: 'wpa', '!contains': true });
o.modalonly = true;
o = s.taboption('wireless', form.Value, 'anonymous_identity', _('Anonymous Identity'));
o.depends({ encryption: 'wpa', '!contains': true });
o.modalonly = true;
o = s.taboption('wireless', form.ListValue, 'ieee80211w', _('Mgmt. Frame Protection'));
o.depends({ encryption: 'sae', '!contains': true });
o.depends({ encryption: 'owe', '!contains': true });
o.depends({ encryption: 'wpa', '!contains': true });
o.depends({ encryption: 'psk', '!contains': true });
o.value('', _('Disabled'));
o.value('1', _('Optional'));
o.value('2', _('Required'));
o.modalonly = true;
o.defaults = {
'2': [{ encryption: 'sae' }, { encryption: 'owe' }, { encryption: 'wpa3' }, { encryption: 'wpa3-mixed' }],
'1': [{ encryption: 'sae-mixed' }],
'': []
};
o = s.taboption('wireless', form.Flag, 'ca_cert_usesystem', _('Use system certificates'), _("Validate server certificate using built-in system CA bundle"));
o.depends({ encryption: 'wpa', '!contains': true });
o.enabled = '1';
o.disabled = '0';
o.modalonly = true;
o.default = o.disabled;
o = s.taboption('wireless', form.Value, 'ca_cert', _('Path to CA-Certificate'));
o.depends({ encryption: 'wpa', '!contains': true });
o.depends({ ca_cert_usesystem: '0' });
o.modalonly = true;
o.rmempty = true;
o = s.taboption('wireless', form.Value, 'client_cert', _('Path to Client-Certificate'));
o.depends({ eap_type: 'tls' });
o.modalonly = true;
o.rmempty = true;
o = s.taboption('wireless', form.Value, 'priv_key', _('Path to Private Key'));
o.depends({ eap_type: 'tls' });
o.modalonly = true;
o.rmempty = true;
o = s.taboption('wireless', form.Value, 'priv_key_pwd', _('Password of Private Key'));
o.depends({ eap_type: 'tls' });
o.modalonly = true;
o.password = true;
o.rmempty = true;
/*
modal travelmate tab
*/
o = s.taboption('travelmate', form.Value, '_ssid', _('SSID'));
o.modalonly = true;
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'ssid';
o.rmempty = false;
o.readonly = true;
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'ssid');
}
o = s.taboption('travelmate', form.Value, '_bssid', _('BSSID'));
o.modalonly = true;
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'bssid';
o.rmempty = true;
o.readonly = true;
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'bssid');
}
o = s.taboption('travelmate', form.Flag, '_opensta', _('Auto Added Open Uplink'),
_('This option is selected by default if this uplink was added automatically and counts as \'Open Uplink\'.'));
o.rmempty = true;
o.modalonly = true;
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'opensta';
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'opensta');
}
o.write = function (section_id, value) {
count = +uci.get('travelmate', 'global', 'trm_autoaddcnt') || 0;
count++;
uci.set('travelmate', 'global', 'trm_autoaddcnt', count);
return handleSectionsVal('set', section_id, 'opensta', value);
}
o.remove = function (section_id, value) {
count = +uci.get('travelmate', 'global', 'trm_autoaddcnt') || 0;
if (count > 0) {
count--;
uci.set('travelmate', 'global', 'trm_autoaddcnt', count);
}
return handleSectionsVal('set', section_id, 'opensta', value);
}
o = s.taboption('travelmate', form.Value, '_macaddr', _('MAC Address'),
_('Use the specified MAC address for this uplink.'));
o.modalonly = true;
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'macaddr';
o.nocreate = false;
o.rmempty = true;
o.datatype = 'macaddr';
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'macaddr');
}
o.write = function (section_id, value) {
return handleSectionsVal('set', section_id, 'macaddr', value);
}
o.remove = function (section_id, value) {
return handleSectionsVal('set', section_id, 'macaddr', value);
}
o = s.taboption('travelmate', form.ListValue, '_revive', _('Revive Uplink'),
_('Re-enable this uplink after the selected number of run cycles if it has been disabled by the retry limit. The number of revivals is limited by the global retry limit.'));
o.modalonly = true;
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'revive';
o.nocreate = false;
o.rmempty = true;
o.default = '0';
o.value('0', _('disabled'));
o.value('10', _('10 run cycles'));
o.value('30', _('30 run cycles'));
o.value('60', _('60 run cycles'));
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'revive');
}
o.write = function (section_id, value) {
return handleSectionsVal('set', section_id, 'revive', value);
}
o.remove = function (section_id, value) {
return handleSectionsVal('set', section_id, 'revive', value);
}
o = s.taboption('travelmate', form.FileUpload, '_script', _('Auto Login Script'),
_('External script reference which will be called for automated captive portal logins.'));
o.root_directory = '/etc/travelmate';
o.enable_remove = false;
o.enable_upload = false;
o.modalonly = true;
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'script';
o.renderWidget = function (section_id, option_index, cfgvalue) {
let browserEl = new ui.FileUpload((cfgvalue != null) ? cfgvalue : this.default, {
id: this.cbid(section_id),
name: this.cbid(section_id),
show_hidden: this.show_hidden,
enable_upload: this.enable_upload,
enable_remove: this.enable_remove,
root_directory: this.root_directory,
disabled: (this.readonly != null) ? this.readonly : this.map.readonly
});
browserEl.renderListing = function (container, path, list) {
return ui.FileUpload.prototype.renderListing.apply(this, [
container, path,
list.filter(function (entry) {
return ((entry.type == 'directory') || (entry.type == 'file' && entry.name.match(/\.login$/)));
})
]);
};
return browserEl.render();
};
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'script');
}
o.write = function (section_id, value) {
return handleSectionsVal('set', section_id, 'script', value);
}
o.remove = function (section_id) {
return handleSectionsVal('del', section_id, 'script');
}
o = s.taboption('travelmate', form.Value, '_args', _('Script Arguments'),
_('Space separated list of additional arguments passed to the Auto Login Script, i.e. username and password'));
o.modalonly = true;
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'script_args';
o.rmempty = true;
o.depends({ _script: '/etc/travelmate', '!contains': true });
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'script_args');
}
o.write = function (section_id, value) {
return handleSectionsVal('set', section_id, 'script_args', value);
}
o.remove = function (section_id) {
return handleSectionsVal('del', section_id, 'script_args');
}
/*
modal vpn tab
*/
o = s.taboption('vpn', form.Flag, '_vpn', _('VPN Hook'), _('Automatically handle VPN connections.
\
Please note: This feature requires the additional configuration of Wireguard or OpenVPN.'));
o.rmempty = true;
o.modalonly = true;
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'vpn';
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'vpn');
}
o.write = function (section_id, value) {
return handleSectionsVal('set', section_id, 'vpn', value);
}
o.remove = function (section_id, value) {
return handleSectionsVal('set', section_id, 'vpn', value);
}
o = s.taboption('vpn', form.ListValue, '_vpnservice', _('VPN Service'));
o.value('wireguard');
o.value('openvpn');
o.optional = true;
o.modalonly = true;
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'vpnservice';
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'vpnservice');
}
o.write = function (section_id, value) {
return handleSectionsVal('set', section_id, 'vpnservice', value);
}
o = s.taboption('vpn', widgets.NetworkSelect, '_vpniface', _('VPN Interface'), _('The logical vpn network interface like \'wg0\'.'));
o.nocreate = true;
o.optional = true;
o.modalonly = true;
o.uciconfig = 'travelmate';
o.ucisection = 'uplink';
o.ucioption = 'vpniface';
o.cfgvalue = function (section_id) {
return handleSectionsVal('get', section_id, 'vpniface');
}
o.write = function (section_id, value) {
return handleSectionsVal('set', section_id, 'vpniface', value);
}
/*
scan buttons
*/
s = m.section(form.GridSection, 'wifi-device');
s.anonymous = true;
s.addremove = false;
s.render = function () {
return network.getWifiDevices().then(L.bind(function (radios) {
let radio, ifname, btns = [];
for (let i = 0; i < radios.length; i++) {
radio = radios[i].sid;
if (radio) {
btns.push(E('button', {
'class': 'cbi-button cbi-button-apply',
'style': 'float:none;margin-right:.4em;',
'id': radio,
'click': ui.createHandlerFn(this, 'handleScan', radio)
}, [_('Scan on %s...').format(radio)]))
}
}
return E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, E('div', { 'class': 'left', 'style': 'padding-top:5px; padding-bottom:5px' }, btns));
}, this))
};
/*
modal 'scan' dialog
*/
s.handleScan = function (radio) {
poll.stop();
const minQuality = parseInt(uci.get('travelmate', 'global', 'trm_minquality') || '35', 10);
const uplinks = configuredUplinks(iface);
let list = E('div', { 'class': 'trm-list' }, [
E('em', { 'class': 'spinning' }, _('Starting wireless scan on \'%s\'...').format(radio))
]);
let md = ui.showModal(_('Wireless Scan'), [
E('div', { 'id': 'trm-scan' }, [scanStyle(), list]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'style': 'float:none;margin-right:.4em;',
'click': ui.hideModal
}, _('Dismiss')),
E('button', {
'class': 'cbi-button cbi-button-positive important',
'id': 'scan-btn',
'disabled': 'disabled',
'click': L.bind(this.handleScan, this, radio)
}, _('Repeat Scan'))
])
]);
md.style.maxWidth = '90%';
md.style.maxHeight = 'none';
return L.resolveDefault(fs.exec_direct('/etc/init.d/travelmate', ['scan', radio]))
.then(L.bind(function () {
return L.resolveDefault(fs.read_direct('/var/run/travelmate/travelmate.scan'), '')
.then(L.bind(function (res) {
let lines, strength, channel, bssid, wpa, cipher, auth, tbl_ssid, ssid, rows = [];
if (res) {
lines = res.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].match(/^\s*\d+/)) {
/*
result columns
*/
strength = lines[i].slice(0, 3).trim();
channel = lines[i].slice(3, 7).trim();
bssid = lines[i].slice(7, 25).trim();
wpa = lines[i].slice(25, 37).trim();
cipher = lines[i].slice(37, 48).trim();
auth = lines[i].slice(48, 59).trim().split(',');
ssid = lines[i].slice(59).trim();
/*
SSID preparation
*/
if (ssid === 'hidden') {
tbl_ssid = E('em', {}, [_('hidden')]);
} else {
ssid = ssid.replace(/^"(.*)"$/, '$1');
tbl_ssid = ssid;
}
/*
WPA detection
*/
let hasWPA1 = wpa.includes("WPA1");
let hasWPA2 = wpa.includes("WPA2");
let hasWPA3 = wpa.includes("WPA3");
/*
Auth detection
*/
let hasPSK = auth.some(a => a.includes("PSK"));
let hasSAE = auth.some(a => a.includes("SAE"));
let has8021x = auth.some(a => a.includes("802.1X"));
let hasOWE = auth.includes("OWE");
let hasSuiteB = auth.some(a => a.includes("SUITE-B"));
let resCipher = resolveCipher(cipher);
/*
encryption classification
*/
let tbl_encryption = '';
let encryption = 'none';
if (cipher === '-' && wpa === '-') {
tbl_encryption = _('Open');
encryption = 'none';
} else if (hasOWE) {
tbl_encryption = `WPA3 OWE (${resCipher.name})`;
encryption = 'owe';
} else if (hasSuiteB) {
tbl_encryption = `WPA3 Enterprise (${resCipher.name})`;
encryption = 'wpa3';
} else if (hasWPA2 && hasWPA3 && hasPSK && !has8021x) {
tbl_encryption = `Mixed WPA2/WPA3 PSK (${resCipher.name})`;
encryption = 'sae-mixed';
} else if (hasWPA2 && hasWPA3 && has8021x) {
tbl_encryption = `Mixed WPA2/WPA3 802.1X (${resCipher.name})`;
encryption = 'wpa3-mixed';
} else if (hasWPA3 && hasSAE && !has8021x) {
tbl_encryption = `WPA3 PSK (SAE)`;
encryption = 'sae';
} else if (hasWPA3 && has8021x) {
tbl_encryption = `WPA3 802.1X (${resCipher.name})`;
encryption = 'wpa3';
} else if (hasWPA1 && hasWPA2 && has8021x) {
tbl_encryption = `Mixed WPA/WPA2 802.1X (${resCipher.name})`;
encryption = resCipher.aes ? 'wpa-mixed+ccmp' : 'wpa-mixed+tkip';
} else if (hasWPA2 && has8021x) {
tbl_encryption = `WPA2 802.1X (${resCipher.name})`;
encryption = resCipher.aes ? 'wpa2+ccmp' : 'wpa2+tkip';
} else if (hasWPA1 && has8021x) {
tbl_encryption = `WPA 802.1X (${resCipher.name})`;
encryption = resCipher.aes ? 'wpa+ccmp' : 'wpa+tkip';
} else if (hasWPA1 && hasWPA2 && hasPSK) {
tbl_encryption = `Mixed WPA/WPA2 PSK (${resCipher.name})`;
encryption = resCipher.aes ? 'psk-mixed+ccmp' : 'psk-mixed+tkip';
} else if (hasWPA2 && hasPSK) {
tbl_encryption = `WPA2 PSK (${resCipher.name})`;
encryption = resCipher.aes ? 'psk2+ccmp' : 'psk2+tkip';
} else if (hasWPA1 && hasPSK) {
tbl_encryption = `WPA PSK (${resCipher.name})`;
encryption = resCipher.aes ? 'psk+ccmp' : 'psk+tkip';
} else {
tbl_encryption = _('unknown');
encryption = 'none';
}
/*
push result card into the list
*/
const known = isConfigured(uplinks, radio, ssid, bssid);
const action = known
? E('span', { 'class': 'trm-known' }, [_('Configured')])
: E('button', {
'class': 'cbi-button cbi-button-action',
'click': ui.createHandlerFn(this, 'handleAdd', radio, iface, ssid, bssid, encryption)
}, _('Add Uplink...'));
rows.push(scanCard(parseInt(strength, 10), minQuality, tbl_ssid, channel, bssid, tbl_encryption, encryption === 'none', action));
}
}
} else {
rows.push(E('div', { 'class': 'trm-empty' }, [_('Empty resultset')]));
}
dom.content(list, rows);
document.getElementById('scan-btn').disabled = false;
poll.start();
}, this));
}, this));
};
/*
modal 'add' dialog
*/
s.handleAdd = function (radio, iface, ssid, bssid, encryption, ev) {
var m2, s2, o2;
m2 = new form.Map('wireless'),
s2 = m2.section(form.NamedSection, '_add_trm');
s2.render = function () {
return Promise.all([
{},
this.renderUCISection('_add_trm')
]).then(this.renderContents.bind(this));
};
o2 = s2.option(form.Value, 'device', _('Device Name'));
o2.default = radio;
o2.readonly = true;
o2 = s2.option(form.Value, 'network', _('Interface Name'));
o2.default = iface;
o2.readonly = true;
if (ssid === "hidden") {
o2 = s2.option(form.Value, 'ssid', _('SSID (hidden)'));
o2.placeholder = 'hidden SSID';
} else {
o2 = s2.option(form.Value, 'ssid', _('SSID'));
o2.default = ssid;
}
o2.datatype = 'maxlength(32)';
o2.rmempty = false;
o2 = s2.option(form.Flag, 'ignore_bssid', _('Ignore BSSID'));
if (ssid === 'hidden') {
o2.default = '0';
} else {
o2.default = '1';
}
o2 = s2.option(form.Value, 'bssid', _('BSSID'));
o2.depends({ ignore_bssid: '0' });
o2.datatype = 'macaddr';
o2.rmempty = true;
o2.default = bssid;
o2 = s2.option(form.ListValue, 'encryption', _('Encryption'));
o2.value('sae', _('WPA3 PSK (SAE)'));
o2.value('sae-mixed', _('Mixed WPA2/WPA3 PSK (CCMP)'));
o2.value('psk2+ccmp', _('WPA2 PSK (CCMP)'));
o2.value('psk2+tkip', _('WPA2 PSK (TKIP)'));
o2.value('psk+ccmp', _('WPA PSK (CCMP)'));
o2.value('psk+tkip', _('WPA PSK (TKIP)'));
o2.value('psk-mixed+ccmp', _('Mixed WPA/WPA2 PSK (CCMP)'));
o2.value('psk-mixed+tkip', _('Mixed WPA/WPA2 PSK (TKIP)'));
o2.value('wpa3', _('WPA3 802.1X'));
o2.value('wpa3-mixed', _('Mixed WPA2/WPA3 802.1X'));
o2.value('wpa2+ccmp', _('WPA2 802.1X (CCMP)'));
o2.value('wpa2+tkip', _('WPA2 802.1X (TKIP)'));
o2.value('wpa+ccmp', _('WPA 802.1X (CCMP)'));
o2.value('wpa+tkip', _('WPA 802.1X (TKIP)'));
o2.value('wpa-mixed+ccmp', _('Mixed WPA/WPA2 802.1X (CCMP)'));
o2.value('wpa-mixed+tkip', _('Mixed WPA/WPA2 802.1X (TKIP)'));
o2.value('owe', _('WPA3 OWE'));
o2.value('none', _('Open'));
o2.default = encryption;
o2 = s2.option(form.Value, 'key', _('Password'));
o2.depends({ encryption: 'sae', '!contains': true });
o2.depends({ encryption: 'psk', '!contains': true });
o2.datatype = 'wpakey';
o2.password = true;
o2 = s2.option(form.Value, 'password', _('Password'));
o2.depends({ encryption: 'wpa', '!contains': true });
o2.datatype = 'wpakey';
o2.password = true;
o2 = s2.option(form.ListValue, 'eap_type', _('EAP-Method'));
o2.depends({ encryption: 'wpa', '!contains': true });
o2.value('tls', _('TLS'));
o2.value('ttls', _('TTLS'));
o2.value('peap', _('PEAP'));
o2.value('fast', _('FAST'));
o2.default = 'peap';
o2 = s2.option(form.ListValue, 'auth', _('Authentication'));
o2.depends({ encryption: 'wpa', '!contains': true });
o2.value('PAP', _('PAP'));
o2.value('CHAP', _('CHAP'));
o2.value('MSCHAP', _('MSCHAP'));
o2.value('MSCHAPV2', _('MSCHAPV2'));
o2.value('EAP-GTC', _('EAP-GTC'));
o2.value('EAP-MD5', _('EAP-MD5'));
o2.value('EAP-MSCHAPV2', _('EAP-MSCHAPV2'));
o2.value('EAP-TLS', _('EAP-TLS'));
o2.value('auth=PAP', _('auth=PAP'));
o2.value('auth=MSCHAPV2', _('auth=MSCHAPV2'));
o2.default = 'EAP-MSCHAPV2';
o2 = s2.option(form.Value, 'identity', _('Identity'));
o2.depends({ encryption: 'wpa', '!contains': true });
o2 = s2.option(form.Value, 'anonymous_identity', _('Anonymous Identity'));
o2.depends({ encryption: 'wpa', '!contains': true });
o2 = s2.option(form.ListValue, 'ieee80211w', _('Mgmt. Frame Protection'));
o2.depends({ encryption: 'sae', '!contains': true });
o2.depends({ encryption: 'owe', '!contains': true });
o2.depends({ encryption: 'wpa', '!contains': true });
o2.depends({ encryption: 'psk', '!contains': true });
o2.value('', _('Disabled'));
o2.value('1', _('Optional'));
o2.value('2', _('Required'));
o2.defaults = {
'2': [{ encryption: 'sae' }, { encryption: 'owe' }, { encryption: 'wpa3' }, { encryption: 'wpa3-mixed' }],
'1': [{ encryption: 'sae-mixed' }],
'': []
};
o2 = s2.option(form.Flag, 'ca_cert_usesystem', _('Use system certificates'), _("Validate server certificate using built-in system CA bundle"));
o2.depends({ encryption: 'wpa', '!contains': true });
o2.enabled = '1';
o2.disabled = '0';
o2.default = o2.disabled;
o2 = s2.option(form.Value, 'ca_cert', _('Path to CA-Certificate'));
o2.depends({ encryption: 'wpa', '!contains': true });
o2.depends({ ca_cert_usesystem: '0' });
o2.rmempty = true;
o2 = s2.option(form.Value, 'client_cert', _('Path to Client-Certificate'));
o2.depends({ eap_type: 'tls' });
o2.rmempty = true;
o2 = s2.option(form.Value, 'priv_key', _('Path to Private Key'));
o2.depends({ eap_type: 'tls' });
o2.rmempty = true;
o2 = s2.option(form.Value, 'priv_key_pwd', _('Password of Private Key'));
o2.depends({ eap_type: 'tls' });
o2.password = true;
o2.rmempty = true;
return m2.render().then(L.bind(function (elements) {
ui.showModal(_('Add Uplink "%h"').format(ssid), [
elements,
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'style': 'float:none;margin-right:.4em;',
'click': ui.hideModal
}, _('Dismiss')),
E('button', {
'class': 'cbi-button cbi-button-positive important',
'click': ui.createHandlerFn(this, 'handleCommit', m2)
}, _('Save'))
])
]);
}, this));
};
/*
save new uplink
*/
s.handleCommit = function (map, ev) {
const w_sections = uci.sections('wireless', 'wifi-iface');
const device = L.toArray(map.lookupOption('device', '_add_trm'))[0].formvalue('_add_trm');
const network = L.toArray(map.lookupOption('network', '_add_trm'))[0].formvalue('_add_trm');
const ssid = L.toArray(map.lookupOption('ssid', '_add_trm'))[0].formvalue('_add_trm');
const ignore_bssid = L.toArray(map.lookupOption('ignore_bssid', '_add_trm'))[0].formvalue('_add_trm');
const bssid = L.toArray(map.lookupOption('bssid', '_add_trm'))[0].formvalue('_add_trm');
const encryption = L.toArray(map.lookupOption('encryption', '_add_trm'))[0].formvalue('_add_trm');
let password = null;
let eap_type, auth, identity, anonymous_identity, ca_cert_usesystem, ca_cert, ieee80211w;
let client_cert, priv_key, priv_key_pwd;
if (encryption.includes('wpa')) {
eap_type = L.toArray(map.lookupOption('eap_type', '_add_trm'))[0].formvalue('_add_trm');
auth = L.toArray(map.lookupOption('auth', '_add_trm'))[0].formvalue('_add_trm');
identity = L.toArray(map.lookupOption('identity', '_add_trm'))[0].formvalue('_add_trm');
anonymous_identity = L.toArray(map.lookupOption('anonymous_identity', '_add_trm'))[0].formvalue('_add_trm');
password = L.toArray(map.lookupOption('password', '_add_trm'))[0].formvalue('_add_trm');
ca_cert_usesystem = L.toArray(map.lookupOption('ca_cert_usesystem', '_add_trm'))[0].formvalue('_add_trm');
ca_cert = L.toArray(map.lookupOption('ca_cert', '_add_trm'))[0].formvalue('_add_trm');
ieee80211w = L.toArray(map.lookupOption('ieee80211w', '_add_trm'))[0].formvalue('_add_trm');
if (eap_type.includes('tls')) {
client_cert = L.toArray(map.lookupOption('client_cert', '_add_trm'))[0].formvalue('_add_trm');
priv_key = L.toArray(map.lookupOption('priv_key', '_add_trm'))[0].formvalue('_add_trm');
priv_key_pwd = L.toArray(map.lookupOption('priv_key_pwd', '_add_trm'))[0].formvalue('_add_trm');
}
}
else {
password = L.toArray(map.lookupOption('key', '_add_trm'))[0].formvalue('_add_trm');
}
if (!ssid || ((encryption.includes('psk') || encryption.includes('wpa') || encryption.includes('sae')) && !password)) {
if (!ssid) {
ui.addNotification(null, E('p', _('Empty SSID, the uplink station could not be saved.')), 'error');
} else {
ui.addNotification(null, E('p', _('Empty Password, the uplink station could not be saved.')), 'error');
}
return ui.hideModal();
}
for (let i = 0; i < w_sections.length; i++) {
if (w_sections[i].device === device && w_sections[i].ssid === ssid) {
if (ignore_bssid === '1' || (ignore_bssid === '0' && normBssid(w_sections[i].bssid) === normBssid(bssid))) {
ui.addNotification(null, E('p', _('Duplicate wireless entry, the uplink station could not be saved.')), 'error');
return ui.hideModal();
}
}
}
var offset = w_sections.length,
new_sid = 'trm_uplink' + (++offset);
while (uci.get('wireless', new_sid)) {
new_sid = 'trm_uplink' + (++offset);
}
uci.add('wireless', 'wifi-iface', new_sid);
uci.set('wireless', new_sid, 'device', device);
uci.set('wireless', new_sid, 'mode', 'sta');
uci.set('wireless', new_sid, 'network', network);
uci.set('wireless', new_sid, 'ssid', ssid);
if (ignore_bssid === '0') {
uci.set('wireless', new_sid, 'bssid', bssid);
}
uci.set('wireless', new_sid, 'encryption', encryption);
if (encryption.includes('wpa')) {
uci.set('wireless', new_sid, 'eap_type', eap_type);
uci.set('wireless', new_sid, 'auth', auth);
uci.set('wireless', new_sid, 'identity', identity);
uci.set('wireless', new_sid, 'anonymous_identity', anonymous_identity);
uci.set('wireless', new_sid, 'password', password);
uci.set('wireless', new_sid, 'ca_cert_usesystem', ca_cert_usesystem);
uci.set('wireless', new_sid, 'ca_cert', ca_cert);
uci.set('wireless', new_sid, 'ieee80211w', ieee80211w);
if (eap_type.includes('tls')) {
uci.set('wireless', new_sid, 'client_cert', client_cert);
uci.set('wireless', new_sid, 'priv_key', priv_key);
uci.set('wireless', new_sid, 'priv_key_pwd', priv_key_pwd);
}
} else {
uci.set('wireless', new_sid, 'key', password);
}
uci.set('wireless', new_sid, 'disabled', '1');
handleSectionsAdd(network);
uci.save()
.then(L.bind(this.map.load, this.map))
.then(L.bind(this.map.reset, this.map))
.then(function () {
let row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(new_sid));
if (row) {
row.setAttribute('style', 'opacity: 0.5; color: #4a4 !important;');
}
})
.then(ui.hideModal)
.catch(function (err) {
ui.hideModal();
ui.addNotification(null, E('p', _('Saving the new uplink failed: %s').format(err)), 'error');
});
};
return m.render();
},
handleReset: null
});