8 'require tools.widgets as widgets';
13 function handleAction(ev
) {
15 if (ev
=== 'restart') {
16 ifaceValue
= String(uci
.get('travelmate', 'global', 'trm_iface') || 'trm_wwan');
17 return fs
.exec('/etc/init.d/travelmate', ['stop'])
18 .then(fs
.exec('/sbin/ifup', [ifaceValue
]))
19 .then(fs
.exec('/etc/init.d/travelmate', ['start']))
22 ifaceValue
= String(uci
.get('travelmate', 'global', 'trm_iface') || '');
23 L
.ui
.showModal(_('Interface Wizard'), [
24 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.')),
25 E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
26 E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
27 E('input', { 'class': 'cbi-input-text', 'id': 'iface', 'placeholder': 'trm_wwan', 'value': ifaceValue
, 'maxlength': '15', 'spellcheck': 'false' }),
29 _('The uplink interface name')
31 E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
32 E('input', { 'class': 'cbi-input-text', 'id': 'zone', 'placeholder': 'wan', 'maxlength': '15', 'spellcheck': 'false' }),
34 _('The firewall zone name')
36 E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
37 E('input', { 'class': 'cbi-input-text', 'id': 'metric', 'placeholder': '100', 'maxlength': '3', 'spellcheck': 'false' }),
39 _('The interface metric')
42 E('div', { 'class': 'right' }, [
49 'class': 'cbi-button cbi-button-positive important',
50 'click': ui
.createHandlerFn(this, function (ev
) {
51 var iface
= document
.getElementById('iface').value
|| 'trm_wwan',
52 zone
= document
.getElementById('zone').value
|| 'wan',
53 metric
= document
.getElementById('metric').value
|| '100';
54 L
.resolveDefault(fs
.exec('/etc/init.d/travelmate', ['setup', iface
, zone
, metric
]))
55 .then(function (res
) {
57 ui
.addNotification(null, E('p', res
.trim() + '.'), 'error');
59 ui
.addNotification(null, E('p', _('The uplink interface has been updated.')), 'info');
67 return document
.getElementById('iface').focus();
70 if (ev
=== 'qrcode') {
74 var w_sid
, w_device
, w_ssid
, w_enc
, w_key
, w_hidden
, result
,
75 w_sections
= uci
.sections('wireless', 'wifi-iface'),
76 optionsAP
= [E('option', { value
: '' }, [_('-- AP Selection --')])];
77 for (var i
= 0; i
< w_sections
.length
; i
++) {
78 if (w_sections
[i
].mode
=== 'ap' && w_sections
[i
].disabled
!== '1') {
80 w_device
= w_sections
[i
].device
;
81 w_ssid
= w_sections
[i
].ssid
;
82 optionsAP
.push(E('option', { value
: w_sid
}, w_device
+ ', ' + w_ssid
));
85 var selectAP
= E('select', {
87 class: 'cbi-input-select',
88 change: function (ev
) {
89 result
= document
.getElementById('qrcode');
90 if (document
.getElementById("selectID").value
) {
91 w_sid
= document
.getElementById("selectID").value
;
92 w_ssid
= w_sections
[w_sid
].ssid
;
93 w_enc
= w_sections
[w_sid
].encryption
;
94 w_key
= w_sections
[w_sid
].key
;
95 w_hidden
= (w_sections
[w_sid
].hidden
== 1 ? 'true' : 'false');
96 if (w_enc
=== 'none') {
102 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
) {
104 result
.innerHTML
= res
.trim();
107 result
.textContent
= _('The QR-Code could not be generated!');
112 result
.textContent
= '';
116 L
.ui
.showModal(_('QR-Code Overview'), [
117 E('p', _('Render the QR-Code of the selected Access Point to comfortably transfer the WLAN credentials to your mobile devices.')),
118 E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
119 E('label', { 'class': 'cbi-input-select', 'style': 'padding-top:.5em' }, [
127 E('div', { 'class': 'right' }, [
141 uci
.load('travelmate')
145 render: function (result
) {
148 m
= new form
.Map('travelmate', 'Travelmate', _('Configuration of the travelmate package to enable travel router functionality. \
149 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 /> \
150 <em>Please note:</em> On first start please call the \'Interface Wizard\' once, to make the necessary network- and firewall settings.'));
153 poll runtime information
155 pollData
: poll
.add(function () {
156 return L
.resolveDefault(fs
.stat('/tmp/trm_runtime.json'), null).then(function (res
) {
157 var status
= document
.getElementById('status');
158 if (res
&& res
.size
> 0) {
159 L
.resolveDefault(fs
.read_direct('/tmp/trm_runtime.json'), null).then(function (res
) {
161 var info
= JSON
.parse(res
);
162 if (status
&& info
) {
163 status
.textContent
= (info
.data
.travelmate_status
|| '-') + ' / ' + (info
.data
.travelmate_version
|| '-');
164 if (info
.data
.travelmate_status
.startsWith('running')) {
165 if (!status
.classList
.contains("spinning")) {
166 status
.classList
.add("spinning");
169 if (status
.classList
.contains("spinning")) {
170 status
.classList
.remove("spinning");
174 status
.textContent
= '-';
175 if (status
.classList
.contains("spinning")) {
176 status
.classList
.remove("spinning");
179 var station_id
= document
.getElementById('station_id');
180 if (station_id
&& info
) {
181 station_id
.textContent
= info
.data
.station_id
|| '-';
183 var station_mac
= document
.getElementById('station_mac');
184 if (station_mac
&& info
) {
185 station_mac
.textContent
= info
.data
.station_mac
|| '-';
187 var station_interfaces
= document
.getElementById('station_interfaces');
188 if (station_interfaces
&& info
) {
189 station_interfaces
.textContent
= info
.data
.station_interfaces
|| '-';
191 var wpa_flags
= document
.getElementById('wpa_flags');
192 if (wpa_flags
&& info
) {
193 wpa_flags
.textContent
= info
.data
.wpa_flags
|| '-';
195 var run_flags
= document
.getElementById('run_flags');
196 if (run_flags
&& info
) {
197 run_flags
.textContent
= info
.data
.run_flags
|| '-';
199 var ext_hooks
= document
.getElementById('ext_hooks');
200 if (ext_hooks
&& info
) {
201 ext_hooks
.textContent
= info
.data
.ext_hooks
|| '-';
203 var run
= document
.getElementById('run');
205 run
.textContent
= info
.data
.last_run
|| '-';
210 status
.textContent
= '-';
211 if (status
.classList
.contains("spinning")) {
212 status
.classList
.remove("spinning");
219 runtime information and buttons
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' }, [
226 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Status / Version')),
227 E('div', { 'class': 'cbi-value-field spinning', 'id': 'status', 'style': 'color:#37c' }, '\xa0')
229 E('div', { 'class': 'cbi-value' }, [
230 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Station ID')),
231 E('div', { 'class': 'cbi-value-field', 'id': 'station_id', 'style': 'color:#37c' }, '-')
233 E('div', { 'class': 'cbi-value' }, [
234 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Station MAC')),
235 E('div', { 'class': 'cbi-value-field', 'id': 'station_mac', 'style': 'color:#37c' }, '-')
237 E('div', { 'class': 'cbi-value' }, [
238 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Station Interfaces')),
239 E('div', { 'class': 'cbi-value-field', 'id': 'station_interfaces', 'style': 'color:#37c' }, '-')
241 E('div', { 'class': 'cbi-value' }, [
242 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('WPA Flags')),
243 E('div', { 'class': 'cbi-value-field', 'id': 'wpa_flags', 'style': 'color:#37c' }, '-')
245 E('div', { 'class': 'cbi-value' }, [
246 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Run Flags')),
247 E('div', { 'class': 'cbi-value-field', 'id': 'run_flags', 'style': 'color:#37c' }, '-')
249 E('div', { 'class': 'cbi-value' }, [
250 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Ext. Hooks')),
251 E('div', { 'class': 'cbi-value-field', 'id': 'ext_hooks', 'style': 'color:#37c' }, '-')
253 E('div', { 'class': 'cbi-value' }, [
254 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Last Run')),
255 E('div', { 'class': 'cbi-value-field', 'id': 'run', 'style': 'color:#37c' }, '-')
257 E('div', { class: 'right' }, [
259 'class': 'cbi-button cbi-button-apply',
261 'click': ui
.createHandlerFn(this, function () {
262 L
.resolveDefault(fs
.stat('/usr/bin/qrencode'), null).then(function (res
) {
264 return handleAction('qrcode');
266 return ui
.addNotification(null, E('p', _('Please install the separate \'qrencode\' package.')), 'info');
269 }, [_('AP QR-Codes...')]),
272 'class': 'cbi-button cbi-button-negative',
273 'click': ui
.createHandlerFn(this, function () {
274 return handleAction('restart');
276 }, [_('Restart Interface')]),
279 'class': 'cbi-button cbi-button-negative',
280 'click': ui
.createHandlerFn(this, function () {
281 return handleAction('setup');
283 }, [_('Interface Wizard...')])
290 tabbed config section
292 s
= m
.section(form
.NamedSection
, 'global', 'travelmate', _('Settings'));
294 s
.tab('general', _('General Settings'));
295 s
.tab('additional', _('Additional Settings'));
296 s
.tab('adv_email', _('E-Mail Settings'), _('Please note: E-Mail notifications require the separate setup of the <em>mstmp</em> package.<br /><p> </p>'));
301 o
= s
.taboption('general', form
.Flag
, 'trm_enabled', _('Enabled'), _('Enable the travelmate service.'));
304 o
= s
.taboption('general', form
.Flag
, 'trm_debug', _('Verbose Debug Logging'), _('Enable verbose debug logging in case of any processing errors.'));
307 o
= s
.taboption('general', form
.Value
, 'trm_radio', _('Radio Selection'), _('Restrict travelmate to a single radio or change the overall scanning order.'));
308 o
.value('radio0', _('use the first radio only (radio0)'));
309 o
.value('radio1', _('use the second radio only (radio1)'));
310 o
.value('radio0 radio1', _('use both radios, normal sort order (radio0 radio1)'));
311 o
.value('radio1 radio0', _('use both radios, reverse sort order (radio1 radio0)'));
314 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\'.'));
318 o
= s
.taboption('general', form
.Flag
, 'trm_vpn', _('VPN processing'), _('VPN connections will be managed by travelmate.'));
322 o
= s
.taboption('general', widgets
.NetworkSelect
, 'trm_vpnifacelist', _('Limit VPN processing'), _('Limit VPN processing to certain interfaces.'));
323 o
.depends('trm_vpn', '1');
328 o
= s
.taboption('general', form
.Value
, 'trm_stdvpnservice', _('Standard VPN Service'), _('Standard VPN service which will be automatically added to new STA profiles.'));
329 o
.depends('trm_vpn', '1');
330 o
.value('wireguard');
334 o
= s
.taboption('general', widgets
.NetworkSelect
, 'trm_stdvpniface', _('Standard VPN interface'), _('Standard VPN interface which will be automatically added to new STA profiles.'));
335 o
.depends('trm_vpn', '1');
339 o
= s
.taboption('general', form
.Flag
, 'trm_netcheck', _('Net Error Check'), _('Treat missing internet availability as an error.'));
340 o
.depends('trm_captive', '1');
344 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.'));
348 o
= s
.taboption('general', form
.Flag
, 'trm_randomize', _('Randomize MAC Addresses'), _('Generate a random unicast MAC address for each uplink connection.'));
352 o
= s
.taboption('general', form
.Flag
, 'trm_autoadd', _('AutoAdd Open Uplinks'), _('Automatically add open uplinks like hotel captive portals to your wireless config.'));
356 o
= s
.taboption('general', form
.Value
, 'trm_maxautoadd', _('Limit AutoAdd'), _('Limit the maximum number of automatically added open uplinks. To disable this limitation set it to \'0\'.'));
357 o
.depends('trm_autoadd', '1');
359 o
.datatype
= 'range(0,30)';
363 additional settings tab
365 o
= s
.taboption('additional', form
.Value
, 'trm_triggerdelay', _('Trigger Delay'), _('Additional trigger delay in seconds before travelmate processing begins.'));
367 o
.datatype
= 'range(1,60)';
370 o
= s
.taboption('additional', form
.Value
, 'trm_maxretry', _('Connection Limit'), _('Retry limit to connect to an uplink.'));
372 o
.datatype
= 'range(1,10)';
375 o
= s
.taboption('additional', form
.Value
, 'trm_minquality', _('Signal Quality Threshold'), _('Minimum signal quality threshold as percent for conditional uplink (dis-) connections.'));
376 o
.placeholder
= '35';
377 o
.datatype
= 'range(20,80)';
380 o
= s
.taboption('additional', form
.Value
, 'trm_maxwait', _('Interface Timeout'), _('How long should travelmate wait for a successful wlan uplink connection.'));
381 o
.placeholder
= '30';
382 o
.datatype
= 'range(20,40)';
385 o
= s
.taboption('additional', form
.Value
, 'trm_timeout', _('Overall Timeout'), _('Overall retry timeout in seconds.'));
386 o
.placeholder
= '60';
387 o
.datatype
= 'range(30,300)';
390 o
= s
.taboption('additional', form
.ListValue
, 'trm_captiveurl', _('Captive Portal URL'), _('The selected URL will be used for connectivity- and captive portal checks.'));
391 o
.value('http://detectportal.firefox.com', 'Firefox (default)');
392 o
.value('http://connectivity-check.ubuntu.com', 'Ubuntu');
393 o
.value('http://captive.apple.com', 'Apple');
394 o
.value('http://connectivitycheck.android.com/generate_204', 'Google');
395 o
.value('http://www.msftncsi.com/ncsi.txt', 'Microsoft');
399 o
= s
.taboption('additional', form
.ListValue
, 'trm_useragent', _('User Agent'), _('The selected user agent will be used for connectivity- and captive portal checks.'));
400 o
.value('Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0', 'Firefox (default)');
401 o
.value('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36', 'Chromium');
402 o
.value('Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36', 'Safari');
403 o
.value('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.61', 'Edge');
404 o
.value('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 OPR/103.0.0.0', 'Opera');
408 o
= s
.taboption('additional', form
.ListValue
, 'trm_nice', _('Service Priority'), _('The selected priority will be used for travelmate processes.'));
409 o
.value('-20', 'Highest Priority');
410 o
.value('-10', 'High Priority');
411 o
.value('0', 'Normal Priority (default)');
412 o
.value('10', 'Less Priority');
413 o
.value('19', 'Least Priority');
418 advanced email settings tab
420 o
= s
.taboption('adv_email', form
.Flag
, 'trm_mail', _('E-Mail Hook'), _('Sends notification E-Mails after every succesful uplink connect.'));
423 o
= s
.taboption('adv_email', form
.Value
, 'trm_mailreceiver', _('E-Mail Receiver Address'), _('Receiver address for travelmate notification E-Mails.'));
424 o
.depends('trm_mail', '1');
425 o
.placeholder
= 'name@example.com';
428 o
= s
.taboption('adv_email', form
.Value
, 'trm_mailsender', _('E-Mail Sender Address'), _('Sender address for travelmate notification E-Mails.'));
429 o
.depends({ 'trm_mailreceiver': '@', '!contains': true });
430 o
.placeholder
= 'no-reply@travelmate';
433 o
= s
.taboption('adv_email', form
.Value
, 'trm_mailtopic', _('E-Mail Topic'), _('Topic for travelmate notification E-Mails.'));
434 o
.depends({ 'trm_mailreceiver': '@', '!contains': true });
435 o
.placeholder
= 'travelmate connection to \'<station>\'';
438 o
= s
.taboption('adv_email', form
.Value
, 'trm_mailprofile', _('E-Mail Profile'), _('Profile used by \'msmtp\' for travelmate notification E-Mails.'));
439 o
.depends({ 'trm_mailreceiver': '@', '!contains': true });
440 o
.placeholder
= 'trm_notify';