Merge pull request #5270 from vgaetera/iface-lo
[project/luci.git] / applications / luci-app-travelmate / htdocs / luci-static / resources / view / travelmate / stations.js
1 'use strict';
2 'require view';
3 'require poll';
4 'require fs';
5 'require ui';
6 'require uci';
7 'require form';
8 'require network';
9 'require tools.widgets as widgets';
10
11 /*
12 change the status of travelmate stations
13 */
14 function handleToggle(sid) {
15 var w_device, w_ssid, w_bssid, t_sections, row, element, value, enabled;
16
17 w_device = uci.get('wireless', sid, 'device');
18 w_ssid = uci.get('wireless', sid, 'ssid');
19 w_bssid = uci.get('wireless', sid, 'bssid');
20 t_sections = uci.sections('travelmate', 'uplink');
21
22 for (var i = 0; i < t_sections.length; i++) {
23 if (t_sections[i].device === w_device && t_sections[i].ssid === w_ssid && t_sections[i].bssid === w_bssid) {
24 value = t_sections[i]['enabled'];
25 value = (value == 0 ? 1 : 0);
26 enabled = (value == 0 ? 'No' : 'Yes');
27 uci.set('travelmate', t_sections[i]['.name'], 'enabled', value);
28 uci.save().then(function () {
29 row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(sid));
30 element = row.querySelector('.cbi-value-field');
31 element.textContent = enabled;
32 row.setAttribute('style', 'opacity: 0.5; color: #37c !important;');
33 });
34 }
35 }
36 }
37
38 /*
39 remove wireless and stale travelmate sections
40 */
41 function handleRemove(sid) {
42 var w_sections, t_sections, match, row;
43
44 uci.remove('wireless', sid);
45 w_sections = uci.sections('wireless', 'wifi-iface');
46 t_sections = uci.sections('travelmate', 'uplink');
47 for (var i = 0; i < t_sections.length; i++) {
48 match = false;
49 for (var j = 0; j < w_sections.length; j++) {
50 if (t_sections[i].device === w_sections[j].device && t_sections[i].ssid === w_sections[j].ssid && t_sections[i].bssid === w_sections[j].bssid) {
51 match = true;
52 break;
53 }
54 }
55 if (match === false) {
56 uci.remove('travelmate', t_sections[i]['.name']);
57 }
58 }
59 return uci.save().then(function () {
60 row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(sid));
61 row.setAttribute('style', 'opacity: 0.5; color: #a22 !important;');
62 });
63 }
64
65 /*
66 add missing travelmate sections
67 */
68 function handleSectionsAdd(iface) {
69 var w_sections, t_sections, match;
70
71 w_sections = uci.sections('wireless', 'wifi-iface');
72 t_sections = uci.sections('travelmate', 'uplink');
73 for (var i = 0; i < w_sections.length; i++) {
74 if (w_sections[i].mode !== 'sta' || w_sections[i].network !== iface) {
75 continue;
76 }
77 match = false;
78 for (var j = 0; j < t_sections.length; j++) {
79 if (w_sections[i].device === t_sections[j].device && w_sections[i].ssid === t_sections[j].ssid && w_sections[i].bssid === t_sections[j].bssid) {
80 match = true;
81 break;
82 }
83 }
84 if (match === false) {
85 var sid = uci.add('travelmate', 'uplink');
86 uci.set('travelmate', sid, 'enabled', '1');
87 uci.set('travelmate', sid, 'device', w_sections[i].device);
88 uci.set('travelmate', sid, 'ssid', w_sections[i].ssid);
89 uci.set('travelmate', sid, 'bssid', w_sections[i].bssid);
90 uci.set('travelmate', sid, 'con_start_expiry', '0');
91 uci.set('travelmate', sid, 'con_end_expiry', '0');
92 }
93 }
94 }
95
96 /*
97 update travelmate sections
98 */
99 function handleSectionsVal(action, section_id, option, value) {
100 var date, oldValue, w_device, w_ssid, w_bssid, t_sections;
101
102 w_device = uci.get('wireless', section_id, 'device');
103 w_ssid = uci.get('wireless', section_id, 'ssid');
104 w_bssid = uci.get('wireless', section_id, 'bssid');
105 t_sections = uci.sections('travelmate', 'uplink');
106
107 for (var i = 0; i < t_sections.length; i++) {
108 if (t_sections[i].device === w_device && t_sections[i].ssid === w_ssid && t_sections[i].bssid === w_bssid) {
109 if (action === 'get') {
110 return t_sections[i][option];
111 }
112 else if (action === 'set') {
113 if (option === 'enabled') {
114 oldValue = t_sections[i][option];
115 if (oldValue !== value && value === '0') {
116 date = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60 * 1000).toISOString().substr(0, 19).replace(/-/g, '.').replace('T', '-');
117 uci.set('travelmate', t_sections[i]['.name'], 'con_end', date);
118 }
119 else if (oldValue !== value && value === '1') {
120 uci.unset('travelmate', t_sections[i]['.name'], 'con_end');
121 }
122 }
123 return uci.set('travelmate', t_sections[i]['.name'], option, value);
124 }
125 else if (action === 'del') {
126 return uci.unset('travelmate', t_sections[i]['.name'], option);
127 }
128 }
129 }
130 }
131
132 /*
133 update travelmate status
134 */
135 function handleStatus() {
136 poll.add(function () {
137 L.resolveDefault(fs.stat('/var/state/travelmate.refresh'), null).then(function (res) {
138 if (res) {
139 L.resolveDefault(fs.read_direct('/var/state/travelmate.refresh'), null).then(async function (res) {
140 fs.remove('/var/state/travelmate.refresh');
141 if (res && res === 'ui_reload') {
142 location.reload();
143 }
144 else if (res && res === 'cfg_reload') {
145 if (document.readyState === 'complete') {
146 uci.unload('wireless');
147 uci.unload('travelmate');
148 }
149 await Promise.all([
150 uci.load('wireless'),
151 uci.load('travelmate')
152 ]);
153 var rows, item, value;
154 rows = document.querySelectorAll('.cbi-section-table-row[data-sid]');
155 for (var i = 0; i < rows.length; i++) {
156 item = rows[i].querySelector('.cbi-value-field[data-title="Enabled"]');
157 value = handleSectionsVal('get', rows[i].getAttribute('data-sid'), 'enabled');
158 item.textContent = (value == 0 ? 'No' : 'Yes');
159 }
160 }
161 });
162 }
163 });
164 return L.resolveDefault(fs.stat('/tmp/trm_runtime.json'), null).then(function (res) {
165 if (res) {
166 L.resolveDefault(fs.read_direct('/tmp/trm_runtime.json'), null).then(function (res) {
167 if (res) {
168 var info = JSON.parse(res);
169 if (info) {
170 var t_device, t_ssid, t_bssid, oldUplinkView, newUplinkView,
171 uplinkId = info.data.station_id.trim().split('/'),
172 oldUplinkView = document.getElementsByName('uplinkStation'),
173 w_sections = uci.sections('wireless', 'wifi-iface');
174
175 t_device = uplinkId[0];
176 t_bssid = uplinkId[uplinkId.length - 1];
177 for (var i = 1; i < uplinkId.length - 1; i++) {
178 if (!t_ssid) {
179 t_ssid = uplinkId[i];
180 }
181 else {
182 t_ssid = t_ssid + '/' + uplinkId[i];
183 }
184 }
185 if (t_ssid === '-') {
186 if (oldUplinkView.length > 0) {
187 oldUplinkView[0].removeAttribute('style');
188 oldUplinkView[0].removeAttribute('name', 'uplinkStation');
189 }
190 }
191 else {
192 for (var i = 0; i < w_sections.length; i++) {
193 newUplinkView = document.getElementById('cbi-wireless-' + w_sections[i]['.name']);
194 if (t_device === w_sections[i].device && t_ssid === w_sections[i].ssid && t_bssid === (w_sections[i].bssid || '-')) {
195 if (oldUplinkView.length === 0 && newUplinkView) {
196 newUplinkView.setAttribute('name', 'uplinkStation');
197 newUplinkView.setAttribute('style', 'text-align: left !important; color: #37c !important;font-weight: bold !important;');
198 }
199 else if (oldUplinkView.length > 0 && newUplinkView && oldUplinkView[0].getAttribute('id') !== newUplinkView.getAttribute('id')) {
200 oldUplinkView[0].removeAttribute('style');
201 oldUplinkView[0].removeAttribute('name', 'uplinkStation');
202 newUplinkView.setAttribute('name', 'uplinkStation');
203 newUplinkView.setAttribute('style', 'text-align: left !important; color: #37c !important;font-weight: bold !important;');
204 }
205 }
206 }
207 }
208 }
209 }
210 });
211 }
212 });
213 }, 1);
214 }
215
216 return view.extend({
217 load: function () {
218 return Promise.all([
219 L.resolveDefault(fs.exec_direct('/etc/init.d/travelmate', ['assoc']), {}),
220 uci.load('wireless'),
221 uci.load('travelmate')
222 ]);
223 },
224
225 render: function (result) {
226 var m, s, o,
227 iface = uci.get('travelmate', 'global', 'trm_iface') || 'trm_wwan';
228
229 m = new form.Map('wireless');
230 m.chain('travelmate');
231 s = m.section(form.GridSection, 'wifi-iface', null, _('Overview of all configured uplinks for travelmate.<br /> \
232 You can edit, remove or prioritize existing uplinks by drag \&#38; drop and scan for new ones. The currently used uplink is emphasized in blue.'));
233 s.anonymous = true;
234 s.sortable = true;
235 s.filter = function (section_id) {
236 return (uci.get('wireless', section_id, 'network') == iface && uci.get('wireless', section_id, 'mode') == 'sta');
237 };
238 s.tab('wireless', _('Wireless Settings'));
239 s.tab('travelmate', _('Travelmate Settings'));
240 s.tab('vpn', _('VPN Settings'));
241 s.renderRowActions = function (section_id) {
242 var btns;
243 btns = [
244 E('button', {
245 'class': 'btn cbi-button drag-handle center',
246 'title': _('Drag to reorder'),
247 'style': 'cursor:move',
248 'disabled': this.map.readonly || null
249 }, '☰'),
250 E('button', {
251 'class': 'cbi-button cbi-button-action important',
252 'title': _('Edit this network'),
253 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id)
254 }, _('Edit')),
255 E('button', {
256 'class': 'cbi-button cbi-button-apply',
257 'title': _('Enable/Disable this network'),
258 'click': ui.createHandlerFn(this, handleToggle, section_id)
259 }, _('On/Off')),
260 E('button', {
261 'class': 'cbi-button cbi-button-negative remove',
262 'title': _('Remove this network'),
263 'click': ui.createHandlerFn(this, handleRemove, section_id)
264 }, _('Remove'))
265 ];
266 return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns));
267 };
268
269 o = s.taboption('travelmate', form.Flag, '_enabled', _('Enabled'));
270 o.uciconfig = 'travelmate';
271 o.ucisection = 'uplink';
272 o.ucioption = 'enabled';
273 o.rmempty = false;
274 o.cfgvalue = function (section_id) {
275 return handleSectionsVal('get', section_id, 'enabled');
276 }
277 o.write = function (section_id, value) {
278 return handleSectionsVal('set', section_id, 'enabled', value);
279 }
280
281 o = s.taboption('wireless', form.Value, 'device', _('Device'));
282 o.readonly = true;
283
284 o = s.taboption('wireless', form.Value, 'ssid', _('SSID'));
285 o.datatype = 'maxlength(32)';
286 o.readonly = true;
287
288 o = s.taboption('wireless', form.Value, 'bssid', _('BSSID'));
289 o.datatype = 'macaddr';
290 o.readonly = true;
291
292 o = s.taboption('wireless', form.ListValue, 'encryption', _('Encryption'));
293 o.value('sae', _('WPA3 Pers. (SAE)'));
294 o.value('sae-mixed', _('WPA2/WPA3 Pers. (CCMP)'));
295 o.value('psk2', _('WPA2 Pers.'));
296 o.value('psk2+ccmp', _('WPA2 Pers. (CCMP)'));
297 o.value('psk2+tkip', _('WPA2 Pers. (TKIP)'));
298 o.value('psk', _('WPA Pers.'));
299 o.value('psk+ccmp', _('WPA Pers. (CCMP)'));
300 o.value('psk+tkip', _('WPA Pers. (TKIP)'));
301 o.value('psk-mixed+ccmp', _('WPA/WPA2 Pers. (CCMP)'));
302 o.value('psk-mixed+tkip', _('WPA/WPA2 Pers. (TKIP)'));
303 o.value('wpa3', _('WPA3 Ent. (CCMP)'));
304 o.value('wpa3-mixed', _('WPA2/WPA3 Ent. (CCMP)'));
305 o.value('wpa2+ccmp', _('WPA2 Ent. (CCMP)'));
306 o.value('wpa2+tkip', _('WPA2 Ent. (TKIP)'));
307 o.value('wpa+ccmp', _('WPA Ent. (CCMP)'));
308 o.value('wpa+tkip', _('WPA Ent. (TKIP)'));
309 o.value('wpa-mixed+ccmp', _('WPA/WPA2 Ent. (CCMP)'));
310 o.value('wpa-mixed+tkip', _('WPA/WPA2 Ent. (TKIP)'));
311 o.value('owe', _('OWE'));
312 o.value('none', _('none'));
313 o.default = 'none';
314 o.textvalue = function (section_id) {
315 var cfgvalue = this.map.data.get('wireless', section_id, 'encryption');
316 switch (cfgvalue) {
317 case 'sae':
318 cfgvalue = 'WPA3 Pers. (SAE)';
319 break;
320 case 'sae-mixed':
321 cfgvalue = 'WPA2/WPA3 Pers. (CCMP)';
322 break;
323 case 'psk2':
324 cfgvalue = 'WPA2 Pers.';
325 break;
326 case 'psk2+ccmp':
327 cfgvalue = 'WPA2 Pers. (CCMP)';
328 break;
329 case 'psk2+tkip':
330 cfgvalue = 'WPA2 Ent. (TKIP)';
331 break;
332 case 'psk':
333 cfgvalue = 'WPA Pers.';
334 break;
335 case 'psk-mixed+ccmp':
336 cfgvalue = 'WPA/WPA2 Pers. (CCMP)';
337 break;
338 case 'psk-mixed+tkip':
339 cfgvalue = 'WPA/WPA2 Pers. (TKIP)';
340 break;
341 case 'wpa3':
342 cfgvalue = 'WPA3 Ent. (CCMP)';
343 break;
344 case 'wpa3-mixed':
345 cfgvalue = 'WPA2/WPA3 Ent. (CCMP)';
346 break;
347 case 'wpa2+ccmp':
348 cfgvalue = 'WPA2 Ent. (CCMP)';
349 break;
350 case 'wpa2+tkip':
351 cfgvalue = 'WPA2 Ent. (TKIP)';
352 break;
353 case 'wpa+ccmp':
354 cfgvalue = 'WPA Ent. (CCMP)';
355 break;
356 case 'wpa+tkip':
357 cfgvalue = 'WPA Ent. (TKIP)';
358 break;
359 case 'wpa-mixed+ccmp':
360 cfgvalue = 'WPA/WPA2 Ent. (CCMP)';
361 break;
362 case 'wpa-mixed+tkip':
363 cfgvalue = 'WPA/WPA2 Ent. (TKIP)';
364 break;
365 case 'owe':
366 cfgvalue = 'WPA3 OWE (CCMP)';
367 break;
368 case 'none':
369 cfgvalue = 'none';
370 break;
371 }
372 return cfgvalue;
373 };
374 handleStatus();
375
376 /*
377 modal wireless tab
378 */
379 o = s.taboption('wireless', form.Value, 'key', _('Password'));
380 o.datatype = 'wpakey';
381 o.depends({ encryption: 'sae', '!contains': true });
382 o.depends({ encryption: 'psk', '!contains': true });
383 o.depends({ encryption: 'wpa', '!contains': true });
384 o.modalonly = true;
385 o.password = true;
386
387 o = s.taboption('wireless', form.ListValue, 'eap_type', _('EAP-Method'));
388 o.value('tls', _('TLS'));
389 o.value('ttls', _('TTLS'));
390 o.value('peap', _('PEAP'));
391 o.value('fast', _('FAST'));
392 o.default = 'peap';
393 o.depends({ encryption: 'wpa', '!contains': true });
394 o.modalonly = true;
395
396 o = s.taboption('wireless', form.ListValue, 'auth', _('Authentication'));
397 o.value('PAP', _('PAP'));
398 o.value('CHAP', _('CHAP'));
399 o.value('MSCHAP', _('MSCHAP'));
400 o.value('MSCHAPV2', _('MSCHAPV2'));
401 o.value('EAP-GTC', _('EAP-GTC'));
402 o.value('EAP-MD5', _('EAP-MD5'));
403 o.value('EAP-MSCHAPV2', _('EAP-MSCHAPV2'));
404 o.value('EAP-TLS', _('EAP-TLS'));
405 o.value('auth=PAP', _('auth=PAP'));
406 o.value('auth=MSCHAPV2', _('auth=MSCHAPV2'));
407 o.default = 'EAP-MSCHAPV2';
408 o.depends({ encryption: 'wpa', '!contains': true });
409 o.modalonly = true;
410
411 o = s.taboption('wireless', form.Value, 'identify', _('Identify'));
412 o.depends({ encryption: 'wpa', '!contains': true });
413 o.modalonly = true;
414
415 o = s.taboption('wireless', form.Value, 'ca_cert', _('Path to CA-Certificate'));
416 o.depends({ eap_type: 'tls' });
417 o.modalonly = true;
418 o.rmempty = true;
419
420 o = s.taboption('wireless', form.Value, 'client_cert', _('Path to Client-Certificate'));
421 o.depends({ eap_type: 'tls' });
422 o.modalonly = true;
423 o.rmempty = true;
424
425 o = s.taboption('wireless', form.Value, 'priv_key', _('Path to Private Key'));
426 o.depends({ eap_type: 'tls' });
427 o.modalonly = true;
428 o.rmempty = true;
429
430 o = s.taboption('wireless', form.Value, 'priv_key_pwd', _('Password of Private Key'));
431 o.datatype = 'wpakey';
432 o.depends({ eap_type: 'tls' });
433 o.modalonly = true;
434 o.password = true;
435 o.rmempty = true;
436
437 /*
438 modal travelmate tab
439 */
440 var mac, mac_array = [];
441 if (result[0]) {
442 mac_array = result[0].trim().split('\n');
443 }
444
445 o = s.taboption('travelmate', form.Value, '_ssid', _('SSID'));
446 o.modalonly = true;
447 o.uciconfig = 'travelmate';
448 o.ucisection = 'uplink';
449 o.ucioption = 'ssid';
450 o.rmempty = false;
451 o.readonly = true;
452 o.cfgvalue = function (section_id) {
453 return handleSectionsVal('get', section_id, 'ssid');
454 }
455
456 o = s.taboption('travelmate', form.Value, '_bssid', _('BSSID'));
457 o.modalonly = true;
458 o.uciconfig = 'travelmate';
459 o.ucisection = 'uplink';
460 o.ucioption = 'bssid';
461 o.rmempty = true;
462 o.readonly = true;
463 o.cfgvalue = function (section_id) {
464 return handleSectionsVal('get', section_id, 'bssid');
465 }
466
467 o = s.taboption('travelmate', form.Value, '_con_start', _('Connection Start'));
468 o.modalonly = true;
469 o.uciconfig = 'travelmate';
470 o.ucisection = 'uplink';
471 o.ucioption = 'con_start';
472 o.rmempty = true;
473 o.readonly = true;
474 o.cfgvalue = function (section_id) {
475 return handleSectionsVal('get', section_id, 'con_start');
476 }
477
478 o = s.taboption('travelmate', form.Value, '_con_end', _('Connection End'));
479 o.modalonly = true;
480 o.uciconfig = 'travelmate';
481 o.ucisection = 'uplink';
482 o.ucioption = 'con_end';
483 o.rmempty = true;
484 o.readonly = true;
485 o.cfgvalue = function (section_id) {
486 return handleSectionsVal('get', section_id, 'con_end');
487 }
488
489 o = s.taboption('travelmate', form.Flag, '_opensta', _('Auto Added Open Uplink'),
490 _('This option is selected by default if this uplink was added automatically and counts as \'Open Uplink\'.'));
491 o.rmempty = true;
492 o.modalonly = true;
493 o.uciconfig = 'travelmate';
494 o.ucisection = 'uplink';
495 o.ucioption = 'opensta';
496 o.cfgvalue = function (section_id) {
497 return handleSectionsVal('get', section_id, 'opensta');
498 }
499 o.write = function (section_id, value) {
500 return handleSectionsVal('set', section_id, 'opensta', value);
501 }
502 o.remove = function (section_id, value) {
503 return handleSectionsVal('set', section_id, 'opensta', value);
504 }
505
506 o = s.taboption('travelmate', form.Value, '_macaddr', _('MAC Address'),
507 _('Use the specified MAC address for this uplink.'));
508 for (var i = 0; i < mac_array.length; i++) {
509 if (mac_array[i].match(/^\s+([0-9A-Fa-f]{2}[:]?){5}[0-9A-Fa-f]{2}/)) {
510 mac = mac_array[i].slice(4).trim();
511 o.value(mac);
512 }
513 }
514 o.modalonly = true;
515 o.uciconfig = 'travelmate';
516 o.ucisection = 'uplink';
517 o.ucioption = 'macaddr';
518 o.nocreate = false;
519 o.unspecified = true;
520 o.rmempty = true;
521 o.datatype = 'macaddr';
522 o.cfgvalue = function (section_id) {
523 return handleSectionsVal('get', section_id, 'macaddr');
524 }
525 o.write = function (section_id, value) {
526 return handleSectionsVal('set', section_id, 'macaddr', value);
527 }
528 o.remove = function (section_id, value) {
529 return handleSectionsVal('set', section_id, 'macaddr', value);
530 }
531
532 o = s.taboption('travelmate', form.Value, '_con_start_expiry', _('Connection Start Expiry'),
533 _('Automatically disable the uplink after <em>n</em> minutes, e.g. for timed connections.<br /> \
534 The default of \'0\' disables this feature.'));
535 o.modalonly = true;
536 o.uciconfig = 'travelmate';
537 o.ucisection = 'uplink';
538 o.ucioption = 'con_start_expiry';
539 o.rmempty = false;
540 o.placeholder = '0';
541 o.default = '0';
542 o.datatype = 'range(0,720)';
543 o.cfgvalue = function (section_id) {
544 return handleSectionsVal('get', section_id, 'con_start_expiry');
545 }
546 o.write = function (section_id, value) {
547 return handleSectionsVal('set', section_id, 'con_start_expiry', value);
548 }
549
550 o = s.taboption('travelmate', form.Value, '_con_end_expiry', _('Connection End Expiry'),
551 _('Automatically (re-)enable the uplink after <em>n</em> minutes, e.g. after failed login attempts.<br /> \
552 The default of \'0\' disables this feature.'));
553 o.modalonly = true;
554 o.uciconfig = 'travelmate';
555 o.ucisection = 'uplink';
556 o.ucioption = 'con_end_expiry';
557 o.rmempty = false;
558 o.placeholder = '0';
559 o.default = '0';
560 o.datatype = 'range(0,720)';
561 o.cfgvalue = function (section_id) {
562 return handleSectionsVal('get', section_id, 'con_end_expiry');
563 }
564 o.write = function (section_id, value) {
565 return handleSectionsVal('set', section_id, 'con_end_expiry', value);
566 }
567
568 o = s.taboption('travelmate', form.FileUpload, '_script', _('Auto Login Script'),
569 _('External script reference which will be called for automated captive portal logins.'));
570 o.root_directory = '/etc/travelmate';
571 o.enable_remove = false;
572 o.enable_upload = false;
573 o.modalonly = true;
574 o.uciconfig = 'travelmate';
575 o.ucisection = 'uplink';
576 o.ucioption = 'script';
577 o.renderWidget = function (section_id, option_index, cfgvalue) {
578 var browserEl = new ui.FileUpload((cfgvalue != null) ? cfgvalue : this.default, {
579 id: this.cbid(section_id),
580 name: this.cbid(section_id),
581 show_hidden: this.show_hidden,
582 enable_upload: this.enable_upload,
583 enable_remove: this.enable_remove,
584 root_directory: this.root_directory,
585 disabled: (this.readonly != null) ? this.readonly : this.map.readonly
586 });
587 browserEl.renderListing = function (container, path, list) {
588 return ui.FileUpload.prototype.renderListing.apply(this, [
589 container, path,
590 list.filter(function (entry) {
591 return ((entry.type == 'directory') || (entry.type == 'file' && entry.name.match(/\.login$/)));
592 })
593 ]);
594 };
595 return browserEl.render();
596 };
597 o.cfgvalue = function (section_id) {
598 return handleSectionsVal('get', section_id, 'script');
599 }
600 o.write = function (section_id, value) {
601 return handleSectionsVal('set', section_id, 'script', value);
602 }
603 o.remove = function (section_id) {
604 return handleSectionsVal('del', section_id, 'script');
605 }
606
607 o = s.taboption('travelmate', form.Value, '_args', _('Script Arguments'),
608 _('Space separated list of additional arguments passed to the Auto Login Script, i.e. username and password'));
609 o.modalonly = true;
610 o.uciconfig = 'travelmate';
611 o.ucisection = 'uplink';
612 o.ucioption = 'script_args';
613 o.rmempty = true;
614 o.depends({ _script: '/etc/travelmate', '!contains': true });
615 o.cfgvalue = function (section_id) {
616 return handleSectionsVal('get', section_id, 'script_args');
617 }
618 o.write = function (section_id, value) {
619 return handleSectionsVal('set', section_id, 'script_args', value);
620 }
621 o.remove = function (section_id) {
622 return handleSectionsVal('del', section_id, 'script_args');
623 }
624
625 /*
626 modal vpn tab
627 */
628 o = s.taboption('vpn', form.Flag, '_vpn', _('VPN Hook'), _('Automatically handle VPN connections.<br /> \
629 Please note: This feature requires the additional configuration of <em>Wireguard</em> or <em>OpenVPN</em>.'));
630 o.rmempty = true;
631 o.modalonly = true;
632 o.uciconfig = 'travelmate';
633 o.ucisection = 'uplink';
634 o.ucioption = 'vpn';
635 o.cfgvalue = function (section_id) {
636 return handleSectionsVal('get', section_id, 'vpn');
637 }
638 o.write = function (section_id, value) {
639 return handleSectionsVal('set', section_id, 'vpn', value);
640 }
641 o.remove = function (section_id, value) {
642 return handleSectionsVal('set', section_id, 'vpn', value);
643 }
644
645 o = s.taboption('vpn', form.ListValue, '_vpnservice', _('VPN Service'));
646 o.value('wireguard');
647 o.value('openvpn');
648 o.optional = true;
649 o.modalonly = true;
650 o.uciconfig = 'travelmate';
651 o.ucisection = 'uplink';
652 o.ucioption = 'vpnservice';
653 o.cfgvalue = function (section_id) {
654 return handleSectionsVal('get', section_id, 'vpnservice');
655 }
656 o.write = function (section_id, value) {
657 return handleSectionsVal('set', section_id, 'vpnservice', value);
658 }
659
660 o = s.taboption('vpn', widgets.NetworkSelect, '_vpniface', _('VPN Interface'), _('The logical vpn network interface, e.g. \'wg0\' or \'tun0\'.'));
661 o.unspecified = false;
662 o.nocreate = true;
663 o.optional = true;
664 o.modalonly = true;
665 o.uciconfig = 'travelmate';
666 o.ucisection = 'uplink';
667 o.ucioption = 'vpniface';
668 o.cfgvalue = function (section_id) {
669 return handleSectionsVal('get', section_id, 'vpniface');
670 }
671 o.write = function (section_id, value) {
672 return handleSectionsVal('set', section_id, 'vpniface', value);
673 }
674
675 /*
676 scan buttons
677 */
678 s = m.section(form.GridSection, 'wifi-device');
679 s.anonymous = true;
680 s.addremove = false;
681 s.render = function () {
682 return network.getWifiDevices().then(L.bind(function (radios) {
683 var radio, ifname, btns = [];
684 for (var i = 0; i < radios.length; i++) {
685 radio = radios[i].sid;
686 if (radio) {
687 btns.push(E('button', {
688 'class': 'cbi-button cbi-button-positive',
689 'id': radio,
690 'click': ui.createHandlerFn(this, 'handleScan', radio)
691 }, [_('Scan on ' + radio + '...')]),
692 '\xa0')
693 }
694 }
695 return E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, E('div', { 'class': 'left', 'style': 'padding-top:5px; padding-bottom:5px' }, btns));
696 }, this))
697 };
698
699 /*
700 modal 'scan' dialog
701 */
702 s.handleScan = function (radio) {
703 var table = E('table', { 'class': 'table' }, [
704 E('tr', { 'class': 'tr table-titles' }, [
705 E('th', { 'class': 'th col-1 middle left' }, _('Strength')),
706 E('th', { 'class': 'th col-1 middle left hide-xs' }, _('Channel')),
707 E('th', { 'class': 'th col-2 middle left' }, _('SSID')),
708 E('th', { 'class': 'th col-2 middle left' }, _('BSSID')),
709 E('th', { 'class': 'th col-3 middle left' }, _('Encryption')),
710 E('th', { 'class': 'th cbi-section-actions right' }, '\xa0')
711 ])
712 ]);
713 cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan on \'' + radio + '\'...')));
714
715 var md = ui.showModal(_('Wireless Scan'), [
716 table,
717 E('div', { 'class': 'right' }, [
718 E('button', {
719 'class': 'btn',
720 'click': ui.hideModal
721 }, _('Dismiss')),
722 '\xa0',
723 E('button', {
724 'class': 'cbi-button cbi-button-positive important',
725 'click': L.bind(this.handleScan, this, radio)
726 }, _('Repeat Scan'))
727 ])
728 ]);
729
730 md.style.maxWidth = '90%';
731 md.style.maxHeight = 'none';
732
733 return L.resolveDefault(fs.exec_direct('/etc/init.d/travelmate', ['scan', radio]), null)
734 .then(L.bind(function (res) {
735 var lines, strength, channel, encryption, tbl_encryption, bssid, ssid, tbl_ssid, rows = [];
736 if (res) {
737 lines = res.trim().split('\n');
738 for (var i = 0; i < lines.length; i++) {
739 if (lines[i].match(/^\s+[0-9]/)) {
740 encryption = lines[i].slice(80).trim();
741 if (!encryption.includes('WEP')) {
742 strength = lines[i].slice(4, 7).trim();
743 channel = lines[i].slice(15, 18).trim();
744 bssid = lines[i].slice(60, 77).trim();
745 ssid = lines[i].slice(25, 59).trim();
746 if (ssid.startsWith('"')) {
747 ssid = ssid.slice(1, ssid.length - 1);
748 tbl_ssid = ssid;
749 }
750 else {
751 ssid = "hidden";
752 tbl_ssid = "<em>hidden</em>";
753 }
754 switch (encryption) {
755 case 'WPA3 PSK (SAE)':
756 encryption = 'sae';
757 tbl_encryption = 'WPA3 Pers. (SAE)';
758 break;
759 case 'mixed WPA2/WPA3 PSK/SAE (CCMP)':
760 encryption = 'sae-mixed';
761 tbl_encryption = 'WPA2/WPA3 Pers. (CCMP)';
762 break;
763 case 'WPA2 PSK (CCMP)':
764 encryption = 'psk2+ccmp';
765 tbl_encryption = 'WPA2 Pers. (CCMP)';
766 break;
767 case 'WPA2 PSK (TKIP)':
768 encryption = 'psk2+tkip';
769 tbl_encryption = 'WPA2 Pers. (TKIP)';
770 break;
771 case 'mixed WPA/WPA2 PSK (TKIP, CCMP)':
772 encryption = 'psk-mixed+ccmp';
773 tbl_encryption = 'WPA/WPA2 Pers. (CCMP)';
774 break;
775 case 'WPA3 802.1X (CCMP)':
776 encryption = 'wpa3';
777 tbl_encryption = 'WPA3 Ent. (CCMP)';
778 break;
779 case 'mixed WPA2/WPA3 802.1X (CCMP)':
780 encryption = 'wpa3-mixed';
781 tbl_encryption = 'WPA2/WPA3 Ent. (CCMP)';
782 break;
783 case 'WPA PSK (CCMP)':
784 encryption = 'psk2+ccmp';
785 tbl_encryption = 'WPA Pers. (CCMP)';
786 break;
787 case 'WPA PSK (TKIP)':
788 encryption = 'psk2+tkip';
789 tbl_encryption = 'WPA Pers. (TKIP)';
790 break;
791 case 'WPA2 802.1X (CCMP)':
792 encryption = 'wpa2+ccmp';
793 tbl_encryption = 'WPA2 Ent. (CCMP)';
794 break;
795 case 'WPA3 OWE (CCMP)':
796 encryption = 'owe';
797 tbl_encryption = 'WPA3 OWE (CCMP)';
798 break;
799 case 'none':
800 encryption = 'none';
801 tbl_encryption = 'none';
802 break;
803 }
804 rows.push([
805 strength,
806 channel,
807 tbl_ssid,
808 bssid,
809 tbl_encryption,
810 E('div', { 'class': 'right' }, E('button', {
811 'class': 'cbi-button cbi-button-action',
812 'click': ui.createHandlerFn(this, 'handleAdd', radio, iface, ssid, bssid, encryption)
813 }, _('Add Uplink...')))
814 ]);
815 }
816 }
817 else if (lines[i] === '::: Empty resultset') {
818 rows.push([
819 'No scan results (empty resultset)'
820 ]);
821 }
822 }
823 }
824 else {
825 rows.push([
826 'No scan results (timeout)'
827 ]);
828 }
829 cbi_update_table(table, rows);
830 }, this));
831 };
832
833 /*
834 modal 'add' dialog
835 */
836 s.handleAdd = function (radio, iface, ssid, bssid, encryption, ev) {
837 var m2, s2, o2;
838
839 m2 = new form.Map('wireless'),
840 s2 = m2.section(form.NamedSection, '_add_trm');
841
842 s2.render = function () {
843 return Promise.all([
844 {},
845 this.renderUCISection('_add_trm')
846 ]).then(this.renderContents.bind(this));
847 };
848
849 o2 = s2.option(form.Value, 'device', _('Device Name'));
850 o2.default = radio;
851 o2.readonly = true;
852
853 o2 = s2.option(form.Value, 'network', _('Interface Name'));
854 o2.default = iface;
855 o2.readonly = true;
856
857 if (ssid === "hidden") {
858 o2 = s2.option(form.Value, 'ssid', _('SSID (hidden)'));
859 o2.placeholder = 'hidden SSID';
860 }
861 else {
862 o2 = s2.option(form.Value, 'ssid', _('SSID'));
863 o2.default = ssid;
864 }
865 o2.datatype = 'maxlength(32)';
866 o2.rmempty = false;
867
868 o2 = s2.option(form.Flag, 'ignore_bssid', _('Ignore BSSID'));
869 if (ssid === "hidden") {
870 o2.default = '0';
871 }
872 else {
873 o2.default = '1';
874 }
875
876 o2 = s2.option(form.Value, 'bssid', _('BSSID'));
877 o2.depends({ ignore_bssid: '0' });
878 o2.datatype = 'macaddr';
879 o2.rmempty = true;
880 o2.default = bssid;
881
882 o2 = s2.option(form.ListValue, 'encryption', _('Encryption'));
883 o2.value('sae', _('WPA3 Pers. (SAE)'));
884 o2.value('sae-mixed', _('WPA2/WPA3 Pers. (CCMP)'));
885 o2.value('psk2', _('WPA2 Pers.'));
886 o2.value('psk2+ccmp', _('WPA2 Pers. (CCMP)'));
887 o2.value('psk2+tkip', _('WPA2 Pers. (TKIP)'));
888 o2.value('psk', _('WPA Pers.'));
889 o2.value('psk+ccmp', _('WPA Pers. (CCMP)'));
890 o2.value('psk+tkip', _('WPA Pers. (TKIP)'));
891 o2.value('psk-mixed+ccmp', _('WPA/WPA2 Pers. (CCMP)'));
892 o2.value('psk-mixed+tkip', _('WPA/WPA2 Pers. (TKIP)'));
893 o2.value('wpa3', _('WPA3 Ent.'));
894 o2.value('wpa3-mixed', _('WPA2/WPA3 Ent.'));
895 o2.value('wpa2+ccmp', _('WPA2 Ent. (CCMP)'));
896 o2.value('wpa2+tkip', _('WPA2 Ent. (TKIP)'));
897 o2.value('wpa+ccmp', _('WPA Ent. (CCMP)'));
898 o2.value('wpa+tkip', _('WPA Ent. (TKIP)'));
899 o2.value('wpa-mixed+ccmp', _('WPA/WPA2 Ent. (CCMP)'));
900 o2.value('wpa-mixed+tkip', _('WPA/WPA2 Ent. (TKIP)'));
901 o2.value('owe', _('WPA3 OWE (CCMP)'));
902 o2.value('none', _('none'));
903 o2.default = encryption;
904
905 o2 = s2.option(form.Value, 'key', _('Password'));
906 o2.depends({ encryption: 'sae', '!contains': true });
907 o2.depends({ encryption: 'psk', '!contains': true });
908 o2.depends({ encryption: 'wpa', '!contains': true });
909 o2.datatype = 'wpakey';
910 o2.password = true;
911
912 o2 = s2.option(form.ListValue, 'eap_type', _('EAP-Method'));
913 o2.depends({ encryption: 'wpa', '!contains': true });
914 o2.value('tls', _('TLS'));
915 o2.value('ttls', _('TTLS'));
916 o2.value('peap', _('PEAP'));
917 o2.value('fast', _('FAST'));
918 o2.default = 'peap';
919
920 o2 = s2.option(form.ListValue, 'auth', _('Authentication'));
921 o2.depends({ encryption: 'wpa', '!contains': true });
922 o2.value('PAP', _('PAP'));
923 o2.value('CHAP', _('CHAP'));
924 o2.value('MSCHAP', _('MSCHAP'));
925 o2.value('MSCHAPV2', _('MSCHAPV2'));
926 o2.value('EAP-GTC', _('EAP-GTC'));
927 o2.value('EAP-MD5', _('EAP-MD5'));
928 o2.value('EAP-MSCHAPV2', _('EAP-MSCHAPV2'));
929 o2.value('EAP-TLS', _('EAP-TLS'));
930 o2.value('auth=PAP', _('auth=PAP'));
931 o2.value('auth=MSCHAPV2', _('auth=MSCHAPV2'));
932 o2.default = 'EAP-MSCHAPV2';
933
934 o2 = s2.option(form.Value, 'identify', _('Identify'));
935 o2.depends({ encryption: 'wpa', '!contains': true });
936
937 o2 = s2.option(form.Value, 'ca_cert', _('Path to CA-Certificate'));
938 o2.depends({ eap_type: 'tls' });
939 o2.rmempty = true;
940
941 o2 = s2.option(form.Value, 'client_cert', _('Path to Client-Certificate'));
942 o2.depends({ eap_type: 'tls' });
943 o2.rmempty = true;
944
945 o2 = s2.option(form.Value, 'priv_key', _('Path to Private Key'));
946 o2.depends({ eap_type: 'tls' });
947 o2.rmempty = true;
948
949 o2 = s2.option(form.Value, 'priv_key_pwd', _('Password of Private Key'));
950 o2.depends({ eap_type: 'tls' });
951 o2.datatype = 'wpakey';
952 o2.password = true;
953 o2.rmempty = true;
954
955 return m2.render().then(L.bind(function (elements) {
956 ui.showModal(_('Add Uplink %q').replace(/%q/, '"%h"'.format(ssid)), [
957 elements,
958 E('div', { 'class': 'right' }, [
959 E('button', {
960 'class': 'btn',
961 'click': ui.hideModal
962 }, _('Dismiss')),
963 '\xa0',
964 E('button', {
965 'class': 'cbi-button cbi-button-positive important',
966 'click': ui.createHandlerFn(this, 'handleCommit', m2)
967 }, _('Save'))
968 ])
969 ]);
970 }, this));
971 };
972
973 /*
974 save new uplink
975 */
976 s.handleCommit = function (map, ev) {
977 var w_sections = uci.sections('wireless', 'wifi-iface'),
978 device = L.toArray(map.lookupOption('device', '_add_trm'))[0].formvalue('_add_trm'),
979 network = L.toArray(map.lookupOption('network', '_add_trm'))[0].formvalue('_add_trm'),
980 ssid = L.toArray(map.lookupOption('ssid', '_add_trm'))[0].formvalue('_add_trm'),
981 ignore_bssid = L.toArray(map.lookupOption('ignore_bssid', '_add_trm'))[0].formvalue('_add_trm'),
982 bssid = L.toArray(map.lookupOption('bssid', '_add_trm'))[0].formvalue('_add_trm'),
983 encryption = L.toArray(map.lookupOption('encryption', '_add_trm'))[0].formvalue('_add_trm'),
984 password = L.toArray(map.lookupOption('key', '_add_trm'))[0].formvalue('_add_trm');
985 if (!ssid || ((encryption.includes('psk') || encryption.includes('wpa') || encryption.includes('sae')) && !password)) {
986 if (!ssid) {
987 ui.addNotification(null, E('p', 'Empty SSID, the uplink station could not be saved.'), 'error');
988 }
989 else {
990 ui.addNotification(null, E('p', 'Empty Password, the uplink station could not be saved.'), 'error');
991 }
992 return ui.hideModal();
993 }
994 for (var i = 0; i < w_sections.length; i++) {
995 if (w_sections[i].device === device && w_sections[i].ssid === ssid) {
996 if (ignore_bssid === '1' || (ignore_bssid === '0' && w_sections[i].bssid === bssid)) {
997 ui.addNotification(null, E('p', 'Duplicate wireless entry, the uplink station could not be saved.'), 'error');
998 return ui.hideModal();
999 }
1000 }
1001 }
1002
1003 var offset = w_sections.length,
1004 new_sid = 'trm_uplink' + (++offset);
1005 while (uci.get('wireless', new_sid)) {
1006 new_sid = 'trm_uplink' + (++offset);
1007 }
1008 uci.add('wireless', 'wifi-iface', new_sid);
1009 uci.set('wireless', new_sid, 'device', device);
1010 uci.set('wireless', new_sid, 'mode', 'sta');
1011 uci.set('wireless', new_sid, 'network', network);
1012 uci.set('wireless', new_sid, 'ssid', ssid);
1013 if (ignore_bssid === '0') {
1014 uci.set('wireless', new_sid, 'bssid', bssid);
1015 }
1016 uci.set('wireless', new_sid, 'encryption', encryption);
1017 uci.set('wireless', new_sid, 'key', password);
1018 uci.set('wireless', new_sid, 'disabled', '1');
1019 handleSectionsAdd(network);
1020 uci.save()
1021 .then(L.bind(this.map.load, this.map))
1022 .then(L.bind(this.map.reset, this.map))
1023 .then(function () {
1024 var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(new_sid));
1025 row.setAttribute('style', 'opacity: 0.5; color: #4a4 !important;');
1026 })
1027 .then(ui.hideModal)
1028 };
1029 return m.render();
1030 },
1031 handleReset: null
1032 });