luci-app-travelmate: release 2.4.0-1
authorDirk Brenken <redacted>
Wed, 18 Feb 2026 20:25:54 +0000 (21:25 +0100)
committerDirk Brenken <redacted>
Wed, 18 Feb 2026 20:26:14 +0000 (21:26 +0100)
* sync with travelmate 2.4.0-1
* fix #8326

Signed-off-by: Dirk Brenken <redacted>
applications/luci-app-travelmate/Makefile
applications/luci-app-travelmate/htdocs/luci-static/resources/view/travelmate/overview.js
applications/luci-app-travelmate/htdocs/luci-static/resources/view/travelmate/stations.js

index 648960f3977f3fe6e8f804d383fb3bf507d56f5a..7826fc0d35c6a3fd6844d9add2b8c83e55cf2c4b 100644 (file)
@@ -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>
 
index 620d7b8a024f5148d0b74b49c18726b68ec43a9e..14643a38f8474b091817db562a9ff9f5eb2b4894 100644 (file)
@@ -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;
 
index 3bfbe467ffd7966214cdfe51414dd6f1fe4a3351..606cacdcb1033c67f8f8a8e76425dbebcb3782b3 100644 (file)
@@ -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 &#38; 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';
git clone https://git.99rst.org/PROJECT