diff options
| author | Dirk Brenken | 2026-02-18 20:25:54 +0000 |
|---|---|---|
| committer | Dirk Brenken | 2026-02-18 20:26:14 +0000 |
| commit | ac8c3ddffb25f21b3d7f7e43544630497cc6e595 (patch) | |
| tree | 1a434c8812d22670101a9a9006eef4549862dbad | |
| parent | 755e299c340cc9726129c333a0dd04a771908b46 (diff) | |
| download | luci-ac8c3ddffb25f21b3d7f7e43544630497cc6e595.tar.gz | |
luci-app-travelmate: release 2.4.0-1
* sync with travelmate 2.4.0-1
* fix #8326
Signed-off-by: Dirk Brenken <dev@brenken.org>
3 files changed, 244 insertions, 170 deletions
diff --git a/applications/luci-app-travelmate/Makefile b/applications/luci-app-travelmate/Makefile index 648960f397..7826fc0d35 100644 --- a/applications/luci-app-travelmate/Makefile +++ b/applications/luci-app-travelmate/Makefile @@ -1,4 +1,4 @@ -# Copyright 2017-2025 Dirk Brenken (dev@brenken.org) +# Copyright 2017-2026 Dirk Brenken (dev@brenken.org) # This is free software, licensed under the Apache License, Version 2.0 include $(TOPDIR)/rules.mk @@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=LuCI support for Travelmate LUCI_DEPENDS:=+luci-base +luci-lib-uqr +travelmate -PKG_VERSION:=2.3.0 -PKG_RELEASE:=2 +PKG_VERSION:=2.4.0 +PKG_RELEASE:=1 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=Dirk Brenken <dev@brenken.org> diff --git a/applications/luci-app-travelmate/htdocs/luci-static/resources/view/travelmate/overview.js b/applications/luci-app-travelmate/htdocs/luci-static/resources/view/travelmate/overview.js index 620d7b8a02..14643a38f8 100644 --- a/applications/luci-app-travelmate/htdocs/luci-static/resources/view/travelmate/overview.js +++ b/applications/luci-app-travelmate/htdocs/luci-static/resources/view/travelmate/overview.js @@ -95,60 +95,60 @@ function handleAction(ev) { } } let selectAP = E('select', { - id: 'selectID', - class: 'cbi-input-select', - change: function (ev) { - result = document.getElementById('qrcode'); - if (document.getElementById("selectID").value) { - w_sid = document.getElementById("selectID").value; - w_ssid = w_sections[w_sid].ssid; - w_enc = w_sections[w_sid].encryption; - w_key = w_sections[w_sid].key; - w_hidden = (w_sections[w_sid].hidden == 1 ? 'true' : 'false'); - if (w_enc === 'none') { - w_enc = 'nopass'; - w_key = 'nokey'; + id: 'selectID', + class: 'cbi-input-select', + change: function (ev) { + result = document.getElementById('qrcode'); + if (document.getElementById("selectID").value) { + w_sid = document.getElementById("selectID").value; + w_ssid = w_sections[w_sid].ssid; + w_enc = w_sections[w_sid].encryption; + w_key = w_sections[w_sid].key; + w_hidden = (w_sections[w_sid].hidden == 1 ? 'true' : 'false'); + if (w_enc === 'none') { + w_enc = 'nopass'; + w_key = 'nokey'; + } else { + w_enc = 'WPA'; + } + const data = `WIFI:S:${w_ssid};T:${w_enc};P:${w_key};H:${w_hidden};;`; + const options = { + pixelSize: 12, + margin: 1, + ecLevel: 'M', + whiteColor: 'white', + blackColor: 'black' + }; + const svg = uqr.renderSVG(data, options); + result.innerHTML = svg.trim(); } else { - w_enc = 'WPA'; + result.textContent = ''; } - const data = `WIFI:S:${w_ssid};T:${w_enc};P:${w_key};H:${w_hidden};;`; - const options = { - pixelSize: 12, - margin: 1, - ecLevel: 'M', - whiteColor: 'white', - blackColor: 'black' - }; - const svg = uqr.renderSVG(data, options); - result.innerHTML = svg.trim(); - } else { - result.textContent = ''; } - } - }, optionsAP); - L.ui.showModal(_('QR-Code Overview'), [ - E('p', _('Render the QR-Code of the selected Access Point to transfer the WLAN credentials to your mobile devices comfortably.')), - E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [ - E('label', { 'class': 'cbi-input-select', 'style': 'padding-top:.5em' }, [selectAP,]) - ]), - E('div', { - 'id': 'qrcode' - }), - E('div', { 'class': 'right' }, [ - E('button', { - 'class': 'cbi-button', - 'click': L.hideModal - }, _('Dismiss')) - ]) - ]); - }); + }, optionsAP); + L.ui.showModal(_('QR-Code Overview'), [ + E('p', _('Render the QR-Code of the selected Access Point to transfer the WLAN credentials to your mobile devices comfortably.')), + E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [ + E('label', { 'class': 'cbi-input-select', 'style': 'padding-top:.5em' }, [selectAP,]) + ]), + E('div', { + 'id': 'qrcode' + }), + E('div', { 'class': 'right' }, [ + E('button', { + 'class': 'cbi-button', + 'click': L.hideModal + }, _('Dismiss')) + ]) + ]); + }); } } return view.extend({ load: function () { return Promise.all([ - uci.load('travelmate'), + uci.load('travelmate').catch(() => 0), network.getWifiDevices().then(function (res) { const radios = []; for (let i = 0; i < res.length; i++) { @@ -160,13 +160,23 @@ return view.extend({ }, render: function (result) { - let m, s, o; + /* + basic result check + */ + if (!result[0] || result[0].length === 0) { + ui.addNotification(null, E('p', _('No travelmate config found!')), 'error'); + return; + } else if (!result[1] || result[1].length === 0) { + ui.addNotification(null, E('p', _('No wireless config / radio found!')), 'error'); + return; + } /* main map */ + let m, s, o; m = new form.Map('travelmate', 'Travelmate', _('Configuration of the travelmate package to enable travel router functionality. \ - For further information %s.'.format(`<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 />' + \ + For further information %s.'.format(`<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 />' + _('<b><em>Please note:</em></b> On first start please call the \'Interface Wizard\' once, to make the necessary network- and firewall settings.')); /* @@ -310,13 +320,6 @@ return view.extend({ o.default = 0; o.rmempty = false; - o = s.taboption('general', form.ListValue, 'trm_scanmode', _('WLAN Scan Mode'), _('Send active probe requests or passively listen for beacon frames that are regularly sent by access points.')); - o.value('active', _('active')); - o.value('passive', _('passive')); - o.placeholder = _('-- default --'); - o.optional = true; - o.rmempty = true; - 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\'.')); o.default = 1; o.rmempty = false; @@ -394,7 +397,7 @@ return view.extend({ o.rmempty = true; o = s.taboption('additional', form.Value, 'trm_triggerdelay', _('Trigger Delay'), _('Additional trigger delay in seconds before travelmate processing begins.')); - o.placeholder = '2'; + o.placeholder = '5'; o.datatype = 'range(1,60)'; o.rmempty = true; diff --git a/applications/luci-app-travelmate/htdocs/luci-static/resources/view/travelmate/stations.js b/applications/luci-app-travelmate/htdocs/luci-static/resources/view/travelmate/stations.js index 3bfbe467ff..606cacdcb1 100644 --- a/applications/luci-app-travelmate/htdocs/luci-static/resources/view/travelmate/stations.js +++ b/applications/luci-app-travelmate/htdocs/luci-static/resources/view/travelmate/stations.js @@ -26,7 +26,7 @@ function resolveCipher(cipherRaw) { if (!cipherRaw) return "unknown"; if (!cipherRaw.includes(':')) return cipherRaw; let id = cipherRaw.split(':').pop(); - return cipherMap[id] || `Unknown (${cipherRaw})`; + return cipherMap[id] || `unknown (${cipherRaw})`; } /* @@ -60,11 +60,12 @@ function handleToggle(sid) { remove wireless and stale travelmate sections */ function handleRemove(sid) { - let w_sections, t_sections, match, row; + 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++) { @@ -74,6 +75,14 @@ function handleRemove(sid) { } } 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']); } } @@ -142,7 +151,7 @@ function handleSectionsVal(action, section_id, option, value) { if (option === 'enabled') { oldValue = t_sections[i][option]; if (oldValue !== value && value === '0') { - date = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60 * 1000).toISOString().substr(0, 19).replace(/-/g, '.').replace('T', '-'); + date = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60 * 1000).toISOString().substring(0, 19).replace(/-/g, '.').replace('T', '-'); uci.set('travelmate', t_sections[i]['.name'], 'con_end', date); } else if (oldValue !== value && value === '1') { uci.unset('travelmate', t_sections[i]['.name'], 'con_end'); @@ -236,42 +245,68 @@ function handleStatus() { }); } }); - }, 1); + }, 2); } return view.extend({ load: function () { return Promise.all([ - uci.load('wireless'), - uci.load('travelmate') + uci.load('wireless').catch(() => 0), + uci.load('travelmate').catch(() => 0) ]); }, render: function (result) { - var m, s, o - let iface = uci.get('travelmate', 'global', 'trm_iface') || 'trm_wwan'; + /* + basic result check + */ + if (!result[0] || result[0].length === 0) { + ui.addNotification(null, E('p', _('No wireless config / radio found!')), 'error'); + return; + } else if (!result[1] || result[1].length === 0) { + 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.<br /> \ The currently used uplink connection is emphasized in <span style="color:rgb(51, 119, 204);font-weight:bold">blue</span>, \ an encrypted VPN uplink connection is emphasized in <span style="color:rgb(68, 170, 68);font-weight:bold">green</span>.')); - s.anonymous = true; - s.sortable = true; 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) { - let btns = [ + const btns = [ E('button', { - 'class': 'btn cbi-button drag-handle right', - 'style': 'float:none;margin-right:.4em;', + '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'), - 'style': 'cursor:move', 'disabled': this.map.readonly || null }, '☰'), E('button', { @@ -292,7 +327,7 @@ return view.extend({ 'click': ui.createHandlerFn(this, handleRemove, section_id) }, _('Remove')) ]; - return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns)); + return E('td', { 'class': 'td cbi-section-table-cell nowrap cbi-section-actions' }, E('div', btns)); }; o = s.taboption('travelmate', form.Flag, '_enabled', _('Enabled')); @@ -550,9 +585,17 @@ return view.extend({ 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); } @@ -779,105 +822,133 @@ return view.extend({ 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.scan'), '') - .then(L.bind(function (res) { - let lines, strength, channel, bssid, wpa, rsn, cipher, auth = [], ssid, rows = []; - if (res) { - lines = res.split('\n'); - for (let i = 0; i < lines.length; i++) { - if (lines[i].match(/^\s*\d+/)) { - strength = lines[i].slice(0, 3).trim(); - channel = lines[i].slice(3, 7).trim(); - bssid = lines[i].slice(7, 25).trim(); - rsn = lines[i].slice(26, 27).trim(); - wpa = lines[i].slice(28, 29).trim(); - cipher = lines[i].slice(29, 40).trim(); - auth = lines[i].slice(40, 71).trim().split(','); - ssid = lines[i].slice(71).trim(); - let tbl_ssid = ssid; - if (ssid === "") { - tbl_ssid = "<em>hidden</em>"; - ssid = "hidden"; - } - let encryption = 'Open'; - let tbl_encryption = ''; - let hasWPA = wpa === '+'; - let hasRSN = rsn === '+'; - let hasPSK = auth.some(a => a.includes("PSK")); - let hasSAE = auth.includes("SAE") || auth.some(a => a.includes("SHA-256")); - let has8021x = auth.some(a => a.includes("802.1X")); - let hasSuiteB = auth.some(a => a.includes("SUITE-B")); - let hasOWE = auth.includes("OWE"); - let resCipher = resolveCipher(cipher); - if (cipher === '-' && !hasWPA && !hasRSN) { - tbl_encryption = 'Open'; - encryption = 'none'; - } else if (hasOWE) { - tbl_encryption = `WPA3 OWE (${resCipher})`; - encryption = 'owe'; - } else if (hasSuiteB) { - tbl_encryption = `WPA3 Enterprise (${resCipher})`; - encryption = 'wpa3'; - } else if (hasSAE && hasPSK && !has8021x) { - tbl_encryption = `Mixed WPA2/WPA3 PSK (${resCipher})`; - encryption = 'sae-mixed'; - } else if (hasSAE && has8021x) { - tbl_encryption = `Mixed WPA2/WPA3 802.1X (${resCipher})`; - encryption = 'wpa3-mixed'; - } else if (hasSAE && !hasPSK && !has8021x) { - tbl_encryption = `WPA3 PSK (${resCipher})`; - encryption = 'sae'; - } else if (hasSAE && !hasPSK && has8021x) { - tbl_encryption = `WPA3 802.1X (${resCipher})`; - encryption = 'wpa3'; - } else if (has8021x && hasRSN && hasWPA) { - tbl_encryption = `Mixed WPA/WPA2 802.1X (${resCipher})`; - encryption = (resCipher === 'CCMP') ? 'wpa-mixed+ccmp' : 'wpa-mixed+tkip'; - } else if (has8021x && hasRSN) { - tbl_encryption = `WPA2 802.1X (${resCipher})`; - encryption = (resCipher === 'CCMP' || resCipher === 'GCMP-256') ? 'wpa2+ccmp' : 'wpa2+tkip'; - } else if (has8021x) { - tbl_encryption = `WPA 802.1X (${resCipher})`; - encryption = (resCipher === 'CCMP') ? 'wpa+ccmp' : 'wpa+tkip'; - } else if (hasPSK && hasRSN && hasWPA) { - tbl_encryption = `Mixed WPA/WPA2 PSK (${resCipher})`; - encryption = (resCipher === 'CCMP') ? 'psk-mixed+ccmp' : 'psk-mixed+tkip'; - } else if (hasPSK && hasRSN) { - tbl_encryption = `WPA2 PSK (${resCipher})`; - encryption = (resCipher === 'CCMP' || resCipher === 'GCMP-256') ? 'psk2+ccmp' : 'psk2+tkip'; - } else if (hasPSK && hasWPA) { - tbl_encryption = `WPA PSK (${resCipher})`; - encryption = (resCipher === 'CCMP') ? 'psk+ccmp' : 'psk+tkip'; - } else { - tbl_encryption = 'Unknown'; - encryption = 'none'; + .then(L.bind(function () { + return L.resolveDefault(fs.read_direct('/var/run/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 = "<em>hidden</em>"; + } 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})`; + encryption = 'owe'; + } else if (hasSuiteB) { + tbl_encryption = `WPA3 Enterprise (${resCipher})`; + encryption = 'wpa3'; + } else if (hasWPA2 && hasWPA3 && hasPSK && !has8021x) { + tbl_encryption = `Mixed WPA2/WPA3 PSK (${resCipher})`; + encryption = 'sae-mixed'; + } else if (hasWPA2 && hasWPA3 && has8021x) { + tbl_encryption = `Mixed WPA2/WPA3 802.1X (${resCipher})`; + 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})`; + encryption = 'wpa3'; + } else if (hasWPA1 && hasWPA2 && has8021x) { + tbl_encryption = `Mixed WPA/WPA2 802.1X (${resCipher})`; + encryption = (resCipher === 'CCMP') ? 'wpa-mixed+ccmp' : 'wpa-mixed+tkip'; + } else if (hasWPA2 && has8021x) { + tbl_encryption = `WPA2 802.1X (${resCipher})`; + encryption = (resCipher === 'CCMP' || resCipher === 'GCMP-256') ? 'wpa2+ccmp' : 'wpa2+tkip'; + } else if (hasWPA1 && has8021x) { + tbl_encryption = `WPA 802.1X (${resCipher})`; + encryption = (resCipher === 'CCMP') ? 'wpa+ccmp' : 'wpa+tkip'; + } else if (hasWPA1 && hasWPA2 && hasPSK) { + tbl_encryption = `Mixed WPA/WPA2 PSK (${resCipher})`; + encryption = (resCipher === 'CCMP') ? 'psk-mixed+ccmp' : 'psk-mixed+tkip'; + } else if (hasWPA2 && hasPSK) { + tbl_encryption = `WPA2 PSK (${resCipher})`; + encryption = (resCipher === 'CCMP' || resCipher === 'GCMP-256') ? 'psk2+ccmp' : 'psk2+tkip'; + } else if (hasWPA1 && hasPSK) { + tbl_encryption = `WPA PSK (${resCipher})`; + encryption = (resCipher === 'CCMP') ? 'psk+ccmp' : 'psk+tkip'; + } else { + tbl_encryption = 'unknown'; + encryption = 'none'; + } + + /* + push result row into table + */ + rows.push([ + strength, + channel, + tbl_ssid, + bssid, + tbl_encryption, + E('div', { 'class': 'right' }, + E('button', { + 'class': 'cbi-button cbi-button-action', + 'click': ui.createHandlerFn(this, 'handleAdd', radio, iface, ssid, bssid, encryption) + }, _('Add Uplink...')) + ) + ]); + } } - rows.push([ - strength, - channel, - tbl_ssid, - bssid, - tbl_encryption, - E('div', { 'class': 'right' }, - E('button', { - 'class': 'cbi-button cbi-button-action', - 'click': ui.createHandlerFn(this, 'handleAdd', radio, iface, ssid, bssid, encryption) - }, _('Add Uplink...')) - ) - ]); + } else { + rows.push(['Empty resultset']); } - } - } else { - rows.push([ - 'Empty resultset' - ]); - } - cbi_update_table(table, rows); - document.getElementById('scan-btn').disabled = false; - poll.start(); + + cbi_update_table(table, rows); + document.getElementById('scan-btn').disabled = false; + poll.start(); + }, this)); }, this)); - }, this)); + }; /* @@ -915,7 +986,7 @@ return view.extend({ o2.rmempty = false; o2 = s2.option(form.Flag, 'ignore_bssid', _('Ignore BSSID')); - if (ssid === "hidden") { + if (ssid === 'hidden') { o2.default = '0'; } else { o2.default = '1'; |