luci-app-travelmate: release 2.0
[project/luci.git] / applications / luci-app-travelmate / htdocs / luci-static / resources / view / travelmate / overview.js
1 'use strict';
2 'require view';
3 'require poll';
4 'require fs';
5 'require ui';
6 'require uci';
7 'require form';
8 'require tools.widgets as widgets';
9
10 /*
11 button handling
12 */
13 async function handleAction(ev) {
14 if (ev === 'restart') {
15 fs.exec_direct('/etc/init.d/travelmate', [ev])
16 }
17 if (ev === 'setup') {
18 var ifaceValue = String(uci.get('travelmate', 'global', 'trm_iface') || '');
19 L.ui.showModal(_('Interface Wizard'), [
20 E('p', _('To use Travelmate, you have to set up an uplink interface once. This wizard creates an IPv4- and an IPv6 alias network interface with all required network- and firewall settings.')),
21 E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
22 E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
23 E('input', { 'class': 'cbi-input-text', 'id': 'iface', 'placeholder': 'trm_wwan', 'value': ifaceValue, 'maxlength': '15', 'spellcheck': 'false' }, [
24 ]),
25 '\xa0\xa0\xa0',
26 _('The uplink interface name')
27 ]),
28 E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
29 E('input', { 'class': 'cbi-input-text', 'id': 'zone', 'placeholder': 'wan', 'maxlength': '15', 'spellcheck': 'false' }),
30 '\xa0\xa0\xa0',
31 _('The firewall zone name')
32 ]),
33 E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
34 E('input', { 'class': 'cbi-input-text', 'id': 'metric', 'placeholder': '100', 'maxlength': '3', 'spellcheck': 'false' }),
35 '\xa0\xa0\xa0',
36 _('The interface metric')
37 ])
38 ]),
39 E('div', { 'class': 'right' }, [
40 E('button', {
41 'class': 'btn',
42 'click': L.hideModal
43 }, _('Dismiss')),
44 ' ',
45 E('button', {
46 'class': 'cbi-button cbi-button-positive important',
47 'click': ui.createHandlerFn(this, function(ev) {
48 var iface = document.getElementById('iface').value || 'trm_wwan',
49 zone = document.getElementById('zone').value || 'wan',
50 metric = document.getElementById('metric').value || '100';
51 L.resolveDefault(fs.exec_direct('/etc/init.d/travelmate', ['setup', iface, zone, metric]))
52 .then(function(res) {
53 if (res) {
54 ui.addNotification(null, E('p', res.trim() + '.'), 'error');
55 } else {
56 ui.addNotification(null, E('p', _('The uplink interface has been updated.')), 'info');
57 }
58 });
59 L.hideModal();
60 })
61 }, _('Save'))
62 ])
63 ]);
64 return document.getElementById('iface').focus();
65 }
66
67 if (ev === 'qrcode') {
68 return Promise.all([
69 uci.load('wireless')
70 ]).then(function() {
71 var w_sid, w_device, w_ssid, w_enc, w_key, w_hidden, result,
72 w_sections = uci.sections('wireless', 'wifi-iface'),
73 optionsAP = [E('option', { value: '' }, [_('-- AP Selection --')])];
74 for (var i = 0; i < w_sections.length; i++) {
75 if (w_sections[i].mode === 'ap' && w_sections[i].disabled !== '1') {
76 w_sid = i;
77 w_device = w_sections[i].device;
78 w_ssid = w_sections[i].ssid;
79 optionsAP.push(E('option', { value: w_sid }, w_device + ', ' + w_ssid));
80 }
81 }
82 var selectAP = E('select', {
83 id: 'selectID',
84 class: 'cbi-input-select',
85 change: function(ev) {
86 result = document.getElementById('qrcode');
87 if (document.getElementById("selectID").value) {
88 w_sid = document.getElementById("selectID").value;
89 w_ssid = w_sections[w_sid].ssid;
90 w_enc = w_sections[w_sid].encryption;
91 w_key = w_sections[w_sid].key;
92 w_hidden = (w_sections[w_sid].hidden == 1 ? 'true' : 'false');
93 if (w_enc.startsWith('psk')) {
94 w_enc = 'WPA';
95 }
96 else if (w_enc === 'none') {
97 w_enc = 'nopass';
98 w_key = 'nokey';
99 }
100 L.resolveDefault(fs.exec_direct('/usr/bin/qrencode', ['--inline', '--8bit', '--type=SVG', '--output=-', 'WIFI:S:' + w_ssid + ';T:' + w_enc + ';P:' + w_key + ';H:' + w_hidden + ';']), null).then(function(res) {
101 if (res) {
102 result.innerHTML = res.trim();
103 }
104 else {
105 result.innerHTML = _('The QR-Code could not be generated!');
106 }
107 });
108 }
109 else {
110 result.innerHTML = '';
111 }
112 }
113 }, optionsAP);
114 L.ui.showModal(_('QR-Code Overview'), [
115 E('p', _('Render the QR-Code of the selected Access Point to comfortably transfer the WLAN credentials to your mobile devices.')),
116 E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
117 E('label', { 'class': 'cbi-input-select', 'style': 'padding-top:.5em' }, [
118 selectAP,
119 ])
120 ]),
121 '\xa0',
122 E('div', {
123 'id': 'qrcode'
124 }),
125 E('div', { 'class': 'right' }, [
126 E('button', {
127 'class': 'btn',
128 'click': L.hideModal
129 }, _('Dismiss'))
130 ])
131 ]);
132 });
133 return;
134 }
135 }
136
137 return view.extend({
138 load: function() {
139 return Promise.all([
140 uci.load('travelmate')
141 ]);
142 },
143
144 render: function(result) {
145 var m, s, o;
146
147 m = new form.Map('travelmate', 'Travelmate', _('Configuration of the travelmate package to to enable travel router functionality. \
148 For further information <a href="https://github.com/openwrt/packages/blob/master/net/travelmate/files/README.md" target="_blank" rel="noreferrer noopener" >check the online documentation</a>. <br /> \
149 <em>Please note:</em> On first start please call the \'Interface Wizard\' once, to make the necessary network- and firewall settings.'));
150
151 /*
152 poll runtime information
153 */
154 pollData: poll.add(function() {
155 return L.resolveDefault(fs.stat('/tmp/trm_runtime.json'), null).then(function(res) {
156 var status = document.getElementById('status');
157 if (res) {
158 L.resolveDefault(fs.read_direct('/tmp/trm_runtime.json'), null).then(function(res) {
159 if (res) {
160 var info = JSON.parse(res);
161 if (status && info) {
162 status.textContent = (info.data.travelmate_status || '-') + ' / ' + (info.data.travelmate_version || '-');
163 if (info.data.travelmate_status.startsWith('running')) {
164 if (!status.classList.contains("spinning")) {
165 status.classList.add("spinning");
166 }
167 } else {
168 if (status.classList.contains("spinning")) {
169 status.classList.remove("spinning");
170 }
171 }
172 } else if (status) {
173 status.textContent = '-';
174 if (status.classList.contains("spinning")) {
175 status.classList.remove("spinning");
176 }
177 }
178 var station_id = document.getElementById('station_id');
179 if (station_id && info) {
180 station_id.textContent = info.data.station_id || '-';
181 }
182 var station_mac = document.getElementById('station_mac');
183 if (station_mac && info) {
184 station_mac.textContent = info.data.station_mac || '-';
185 }
186 var station_interface = document.getElementById('station_interface');
187 if (station_interface && info) {
188 station_interface.textContent = info.data.station_interface || '-';
189 }
190 var wpa_flags = document.getElementById('wpa_flags');
191 if (wpa_flags && info) {
192 wpa_flags.textContent = info.data.wpa_flags || '-';
193 }
194 var run_flags = document.getElementById('run_flags');
195 if (run_flags && info) {
196 run_flags.textContent = info.data.run_flags || '-';
197 }
198 var ext_hooks = document.getElementById('ext_hooks');
199 if (ext_hooks && info) {
200 ext_hooks.textContent = info.data.ext_hooks || '-';
201 }
202 var run = document.getElementById('run');
203 if (run && info) {
204 run.textContent = info.data.last_run || '-';
205 }
206 }
207 });
208 }
209 else {
210 if (status && status.classList.contains("spinning")) {
211 status.textContent = '-';
212 status.classList.remove("spinning");
213 }
214 }
215 });
216 }, 1);
217
218 /*
219 runtime information and buttons
220 */
221 s = m.section(form.NamedSection, 'global');
222 s.render = L.bind(function(view, section_id) {
223 return E('div', { 'class': 'cbi-section' }, [
224 E('h3', _('Information')),
225 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
226 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Status / Version')),
227 E('div', { 'class': 'cbi-value-field', 'id': 'status', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
228 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
229 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Station ID')),
230 E('div', { 'class': 'cbi-value-field', 'id': 'station_id', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
231 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
232 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Station MAC')),
233 E('div', { 'class': 'cbi-value-field', 'id': 'station_mac', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
234 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
235 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Station Interface')),
236 E('div', { 'class': 'cbi-value-field', 'id': 'station_interface', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
237 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
238 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('WPA Flags')),
239 E('div', { 'class': 'cbi-value-field', 'id': 'wpa_flags', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
240 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
241 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Run Flags')),
242 E('div', { 'class': 'cbi-value-field', 'id': 'run_flags', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
243 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
244 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Ext. Hooks')),
245 E('div', { 'class': 'cbi-value-field', 'id': 'ext_hooks', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
246 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
247 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Last Run')),
248 E('div', { 'class': 'cbi-value-field', 'id': 'run', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
249 E('div', { class: 'right' }, [
250 E('button', {
251 'class': 'cbi-button cbi-button-apply',
252 'id': 'btn_suspend',
253 'click': ui.createHandlerFn(this, function() {
254 L.resolveDefault(fs.stat('/usr/bin/qrencode'), null).then(function(res) {
255 if (res) {
256 return handleAction('qrcode');
257 }
258 return ui.addNotification(null, E('p', _('Please install the separate \'qrencode\' package.')), 'info');
259 })
260 })
261 }, [ _('AP QR-Codes...') ]),
262 '\xa0',
263 E('button', {
264 'class': 'cbi-button cbi-button-reset',
265 'click': ui.createHandlerFn(this, function() {
266 return handleAction('setup');
267 })
268 }, [ _('Interface Wizard...') ])
269 ])
270 ]);
271 }, o, this);
272 this.pollData;
273
274 /*
275 tabbed config section
276 */
277 s = m.section(form.NamedSection, 'global', 'travelmate', _('Settings'));
278 s.addremove = false;
279 s.tab('general', _('General Settings'));
280 s.tab('additional', _('Additional Settings'));
281 s.tab('adv_vpn', _('VPN Settings'), _('Please note: VPN connections require the separate setup of the <em>Wireguard</em> or <em>OpenVPN</em> package.<br /><p>&#xa0;</p>'));
282 s.tab('adv_email', _('E-Mail Settings'), _('Please note: E-Mail notifications require the separate setup of the <em>mstmp</em> package.<br /><p>&#xa0;</p>'));
283
284 /*
285 general settings tab
286 */
287 o = s.taboption('general', form.Flag, 'trm_enabled', _('Enabled'), _('Enable the travelmate service.'));
288 o.rmempty = false;
289
290 o = s.taboption('general', form.Flag, 'trm_debug', _('Verbose Debug Logging'), _('Enable verbose debug logging in case of any processing errors.'));
291 o.rmempty = false;
292
293 o = s.taboption('general', form.Value, 'trm_radio', _('Radio Selection'), _('Restrict travelmate to a single radio or change the overall scanning order (e.g. \'radio1 radio0\').'));
294 o.placeholder = 'radio0';
295 o.rmempty = true;
296
297 o = s.taboption('general', form.Flag, 'trm_captive', _('Captive Portal Detection'), _('Check the internet availability, handle captive portal redirections and keep the uplink connection \'alive\'.'));
298 o.default = 1;
299 o.rmempty = false;
300
301 o = s.taboption('general', form.Flag, 'trm_netcheck', _('Net Error Check'), _('Treat missing internet availability as an error.'));
302 o.depends('trm_captive', '1');
303 o.default = 0;
304 o.rmempty = false;
305
306 o = s.taboption('general', form.Flag, 'trm_proactive', _('ProActive Uplink Switch'), _('Proactively scan and switch to a higher prioritized uplink, despite of an already existing connection.'));
307 o.default = 1;
308 o.rmempty = false;
309
310 o = s.taboption('general', form.Flag, 'trm_autoadd', _('AutoAdd Open Uplinks'), _('Automatically add open uplinks like hotel captive portals to your wireless config.'));
311 o.default = 0;
312 o.rmempty = false;
313
314 o = s.taboption('general', form.Flag, 'trm_randomize', _('Randomize MAC Addresses'), _('Generate a random unicast MAC address for each uplink connection.'));
315 o.default = 0;
316 o.rmempty = false;
317
318 /*
319 additional settings tab
320 */
321 o = s.taboption('additional', form.Value, 'trm_triggerdelay', _('Trigger Delay'), _('Additional trigger delay in seconds before travelmate processing begins.'));
322 o.placeholder = '2';
323 o.datatype = 'range(1,60)';
324 o.rmempty = true;
325
326 o = s.taboption('additional', form.Value, 'trm_maxretry', _('Connection Limit'), _('Retry limit to connect to an uplink.'));
327 o.placeholder = '3';
328 o.datatype = 'range(1,10)';
329 o.rmempty = true;
330
331 o = s.taboption('additional', form.Value, 'trm_minquality', _('Signal Quality Threshold'), _('Minimum signal quality threshold as percent for conditional uplink (dis-) connections.'));
332 o.placeholder = '35';
333 o.datatype = 'range(20,80)';
334 o.rmempty = true;
335
336 o = s.taboption('additional', form.Value, 'trm_maxwait', _('Interface Timeout'), _('How long should travelmate wait for a successful wlan uplink connection.'));
337 o.placeholder = '30';
338 o.datatype = 'range(20,40)';
339 o.rmempty = true;
340
341 o = s.taboption('additional', form.Value, 'trm_timeout', _('Overall Timeout'), _('Overall retry timeout in seconds.'));
342 o.placeholder = '60';
343 o.datatype = 'range(30,300)';
344 o.rmempty = true;
345
346 o = s.taboption('additional', form.Value, 'trm_scanbuffer', _('Scan Buffer Size'), _('Buffer size in bytes to prepare nearby scan results.'));
347 o.placeholder = '1024';
348 o.datatype = 'range(256,4096)';
349 o.rmempty = true;
350
351 o = s.taboption('additional', form.ListValue, 'trm_captiveurl', _('Captive Portal URL'), _('The selected URL will be used for connectivity- and captive portal checks.'));
352 o.value('http://captive.apple.com', 'Apple (default)');
353 o.value('http://connectivity-check.ubuntu.com', 'Ubuntu');
354 o.value('http://connectivitycheck.android.com/generate_204', 'Google');
355 o.value('http://www.msftncsi.com/ncsi.txt', 'Microsoft');
356 o.optional = true;
357 o.rmempty = true;
358
359 o = s.taboption('additional', form.ListValue, 'trm_useragent', _('User Agent'), _('The selected user agent will be used for connectivity- and captive portal checks.'));
360 o.value('Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0', 'Firefox (default)');
361 o.value('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36', 'Chromium');
362 o.value('Mozilla/5.0 (iPhone; CPU iPhone OS 13_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/85.0.4183.92 Mobile/15E148 Safari/604.1', 'Safari');
363 o.value('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.44', 'Edge');
364 o.value('Mozilla/5.0 (Linux; Android 10; SM-G970F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.81 Mobile Safari/537.36 OPR/59.1.2926.54067', 'Opera');
365 o.optional = true;
366 o.rmempty = true;
367
368 o = s.taboption('additional', form.ListValue, 'trm_nice', _('Service Priority'), _('The selected priority will be used for travelmate processes.'));
369 o.value('-20', 'Highest Priority');
370 o.value('-10', 'High Priority');
371 o.value('0', 'Normal Priority (default)');
372 o.value('10', 'Less Priority');
373 o.value('19', 'Least Priority');
374 o.optional = true;
375 o.rmempty = true;
376
377 /*
378 advanced vpn settings tab
379 */
380 o = s.taboption('adv_vpn', form.Flag, 'trm_vpn', _('VPN Hook'), _('Automatically handle VPN (re-) connections.'));
381 o.rmempty = false;
382
383 o = s.taboption('adv_vpn', form.ListValue, 'trm_vpnservice', _('VPN Service'));
384 o.depends('trm_vpn', '1');
385 o.value('wireguard');
386 o.value('openvpn');
387 o.rmempty = true;
388
389 o = s.taboption('adv_vpn', widgets.NetworkSelect, 'trm_vpniface', _('VPN Interface'), _('The logical vpn network interface, e.g. \'wg0\' or \'tun0\'.'));
390 o.depends('trm_vpn', '1');
391 o.unspecified = false;
392 o.nocreate = true;
393 o.rmempty = true;
394
395 o = s.taboption('adv_vpn', widgets.DeviceSelect, 'trm_landevice', _('LAN Device'), _('The lan network device, e.g. \'br-lan\'.'));
396 o.depends('trm_vpn', '1');
397 o.unspecified = false;
398 o.nocreate = true;
399 o.rmempty = true;
400
401 /*
402 advanced email settings tab
403 */
404 o = s.taboption('adv_email', form.Flag, 'trm_mail', _('E-Mail Hook'), _('Sends notification E-Mails after every succesful uplink connect.'));
405 o.rmempty = false;
406
407 o = s.taboption('adv_email', form.Value, 'trm_mailreceiver', _('E-Mail Receiver Address'), _('Receiver address for travelmate notification E-Mails.'));
408 o.depends('trm_mail', '1');
409 o.placeholder = 'name@example.com';
410 o.rmempty = true;
411
412 o = s.taboption('adv_email', form.Value, 'trm_mailsender', _('E-Mail Sender Address'), _('Sender address for travelmate notification E-Mails.'));
413 o.depends({ 'trm_mailreceiver': '@', '!contains': true });
414 o.placeholder = 'no-reply@travelmate';
415 o.rmempty = true;
416
417 o = s.taboption('adv_email', form.Value, 'trm_mailtopic', _('E-Mail Topic'), _('Topic for travelmate notification E-Mails.'));
418 o.depends({ 'trm_mailreceiver': '@', '!contains': true });
419 o.placeholder = 'travelmate connection to \'<station>\'';
420 o.rmempty = true;
421
422 o = s.taboption('adv_email', form.Value, 'trm_mailprofile', _('E-Mail Profile'), _('Profile used by \'msmtp\' for travelmate notification E-Mails.'));
423 o.depends({ 'trm_mailreceiver': '@', '!contains': true });
424 o.placeholder = 'trm_notify';
425 o.rmempty = true;
426
427 return m.render();
428 },
429 handleReset: null
430 });