mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 16:44:23 +08:00
🎁 Sync 2026-05-19 21:08:14
This commit is contained in:
1 parent
487df6e407
commit
9a2ef6ebbc
23 files changed
+813
-677
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2025 gSpot (https://github.com/gSpotx2f/luci-app-cpu-perf)
|
# Copyright (C) 2026 gSpot (https://github.com/gSpotx2f/luci-app-cpu-perf)
|
||||||
#
|
#
|
||||||
# This is free software, licensed under the MIT License.
|
# This is free software, licensed under the MIT License.
|
||||||
#
|
#
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-cpu-perf
|
PKG_NAME:=luci-app-cpu-perf
|
||||||
PKG_VERSION:=0.6.1
|
PKG_VERSION:=0.6.2
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
LUCI_TITLE:=CPU performance information and management for LuCI
|
LUCI_TITLE:=CPU performance information and management for LuCI
|
||||||
LUCI_DEPENDS:=+ucode +ucode-mod-fs
|
LUCI_DEPENDS:=+ucode +ucode-mod-fs
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|||||||
+12
-12
@@ -9,31 +9,31 @@ OpenWrt >= 22.03.
|
|||||||
|
|
||||||
**OpenWrt >= 25.12:**
|
**OpenWrt >= 25.12:**
|
||||||
|
|
||||||
wget --no-check-certificate -O /tmp/luci-app-cpu-perf-0.6.1-r1.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-app-cpu-perf-0.6.1-r1.apk
|
wget --no-check-certificate -O /tmp/luci-app-cpu-perf-0.6.2-r1.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-app-cpu-perf-0.6.2-r1.apk
|
||||||
apk --allow-untrusted add /tmp/luci-app-cpu-perf-0.6.1-r1.apk
|
apk --allow-untrusted add /tmp/luci-app-cpu-perf-0.6.2-r1.apk
|
||||||
rm /tmp/luci-app-cpu-perf-0.6.1-r1.apk
|
rm /tmp/luci-app-cpu-perf-0.6.2-r1.apk
|
||||||
service rpcd restart
|
service rpcd restart
|
||||||
service cpu-perf start
|
service cpu-perf start
|
||||||
|
|
||||||
i18n-ru:
|
i18n-ru:
|
||||||
|
|
||||||
wget --no-check-certificate -O /tmp/luci-i18n-cpu-perf-ru-0.6.1-r1.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-i18n-cpu-perf-ru-0.6.1-r1.apk
|
wget --no-check-certificate -O /tmp/luci-i18n-cpu-perf-ru-0.6.2-r1.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-i18n-cpu-perf-ru-0.6.2-r1.apk
|
||||||
apk --allow-untrusted add /tmp/luci-i18n-cpu-perf-ru-0.6.1-r1.apk
|
apk --allow-untrusted add /tmp/luci-i18n-cpu-perf-ru-0.6.2-r1.apk
|
||||||
rm /tmp/luci-i18n-cpu-perf-ru-0.6.1-r1.apk
|
rm /tmp/luci-i18n-cpu-perf-ru-0.6.2-r1.apk
|
||||||
|
|
||||||
**OpenWrt <= 24.10:**
|
**OpenWrt <= 24.10:**
|
||||||
|
|
||||||
wget --no-check-certificate -O /tmp/luci-app-cpu-perf_0.6.1-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-app-cpu-perf_0.6.1-r1_all.ipk
|
wget --no-check-certificate -O /tmp/luci-app-cpu-perf_0.6.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-app-cpu-perf_0.6.2-r1_all.ipk
|
||||||
opkg install /tmp/luci-app-cpu-perf_0.6.1-r1_all.ipk
|
opkg install /tmp/luci-app-cpu-perf_0.6.2-r1_all.ipk
|
||||||
rm /tmp/luci-app-cpu-perf_0.6.1-r1_all.ipk
|
rm /tmp/luci-app-cpu-perf_0.6.2-r1_all.ipk
|
||||||
service rpcd restart
|
service rpcd restart
|
||||||
service cpu-perf start
|
service cpu-perf start
|
||||||
|
|
||||||
i18n-ru:
|
i18n-ru:
|
||||||
|
|
||||||
wget --no-check-certificate -O /tmp/luci-i18n-cpu-perf-ru_0.6.1-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-i18n-cpu-perf-ru_0.6.1-r1_all.ipk
|
wget --no-check-certificate -O /tmp/luci-i18n-cpu-perf-ru_0.6.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-i18n-cpu-perf-ru_0.6.2-r1_all.ipk
|
||||||
opkg install /tmp/luci-i18n-cpu-perf-ru_0.6.1-r1_all.ipk
|
opkg install /tmp/luci-i18n-cpu-perf-ru_0.6.2-r1_all.ipk
|
||||||
rm /tmp/luci-i18n-cpu-perf-ru_0.6.1-r1_all.ipk
|
rm /tmp/luci-i18n-cpu-perf-ru_0.6.2-r1_all.ipk
|
||||||
|
|
||||||
## Screenshots:
|
## Screenshots:
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ EAS_DEF=1
|
|||||||
config_load $APP_NAME
|
config_load $APP_NAME
|
||||||
|
|
||||||
get_cpu_freq_policies() {
|
get_cpu_freq_policies() {
|
||||||
|
[ ! -d "$SYSFS_CPU_FREQ_DIR" ] && return
|
||||||
echo $(ls "$SYSFS_CPU_FREQ_DIR" | grep "^policy[0-9]*$")
|
echo $(ls "$SYSFS_CPU_FREQ_DIR" | grep "^policy[0-9]*$")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_cpu_freq_policy_config() {
|
reset_cpu_freq_policy_config() {
|
||||||
@@ -67,7 +67,7 @@ set_cpu_freq_policy() {
|
|||||||
local scaling_min_freq scaling_max_freq scaling_governor
|
local scaling_min_freq scaling_max_freq scaling_governor
|
||||||
local policy_dir="${SYSFS_CPU_FREQ_DIR}/${policy}"
|
local policy_dir="${SYSFS_CPU_FREQ_DIR}/${policy}"
|
||||||
|
|
||||||
[ ! -d $policy_dir ] && return
|
[ ! -d "$policy_dir" ] && return
|
||||||
|
|
||||||
local scaling_min_freq_file="${policy_dir}/scaling_min_freq"
|
local scaling_min_freq_file="${policy_dir}/scaling_min_freq"
|
||||||
local scaling_max_freq_file="${policy_dir}/scaling_max_freq"
|
local scaling_max_freq_file="${policy_dir}/scaling_max_freq"
|
||||||
@@ -92,7 +92,7 @@ reset_cpu_freq_policy() {
|
|||||||
local policy="$1"
|
local policy="$1"
|
||||||
local policy_dir="${SYSFS_CPU_FREQ_DIR}/${policy}"
|
local policy_dir="${SYSFS_CPU_FREQ_DIR}/${policy}"
|
||||||
|
|
||||||
[ ! -d $policy_dir ] && return
|
[ ! -d "$policy_dir" ] && return
|
||||||
|
|
||||||
local min_freq_file="${policy_dir}/cpuinfo_min_freq"
|
local min_freq_file="${policy_dir}/cpuinfo_min_freq"
|
||||||
local max_freq_file="${policy_dir}/cpuinfo_max_freq"
|
local max_freq_file="${policy_dir}/cpuinfo_max_freq"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ LUCI_TITLE:=Information dashboard for 3G/LTE dongle
|
|||||||
LUCI_DEPENDS:=+modeminfo
|
LUCI_DEPENDS:=+modeminfo
|
||||||
PKG_LICENSE:=GPLv3
|
PKG_LICENSE:=GPLv3
|
||||||
PKG_VERSION:=0.4.7
|
PKG_VERSION:=0.4.7
|
||||||
PKG_RELEASE:=3
|
PKG_RELEASE:=4
|
||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
|||||||
@@ -9,158 +9,162 @@
|
|||||||
'require dom';
|
'require dom';
|
||||||
'require tools.widgets as widgets';
|
'require tools.widgets as widgets';
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright Konstantine Shevlakov <shevlakov@132lan.ru> 2023
|
Copyright Konstantine Shevlakov <shevlakov@132lan.ru> 2023-2026
|
||||||
|
|
||||||
Licensed to the GNU General Public License v3.0.
|
Licensed to the GNU General Public License v3.0.
|
||||||
|
|
||||||
|
Refactored: bug fixes, XSS prevention, deduplication.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safe getElementById.
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {HTMLElement|null}
|
||||||
|
*/
|
||||||
|
function getEl(id) {
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a value into a cell by id.
|
||||||
|
* Falls back to '--' when value is missing/placeholder.
|
||||||
|
* Uses textContent to prevent XSS.
|
||||||
|
*
|
||||||
|
* @param {string} id - Element id
|
||||||
|
* @param {string} value - Raw value from modem JSON
|
||||||
|
* @param {string} [suffix] - Optional suffix appended to a real value (e.g. ' °C')
|
||||||
|
*/
|
||||||
|
function setCell(id, value, suffix) {
|
||||||
|
const el = getEl(id);
|
||||||
|
if (!el) return;
|
||||||
|
const missing = !value || value === '--';
|
||||||
|
el.textContent = missing ? '--' : value + (suffix || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Field definitions ────────────────────────────────────────────────────────
|
||||||
|
// Each entry: { key: modem JSON field, suffix: optional unit string }
|
||||||
|
const MODEM_FIELDS = [
|
||||||
|
{ key: 'device', suffix: '' },
|
||||||
|
{ key: 'firmware', suffix: '' },
|
||||||
|
{ key: 'imsi', suffix: '' },
|
||||||
|
{ key: 'iccid', suffix: '' },
|
||||||
|
{ key: 'imei', suffix: '' },
|
||||||
|
{ key: 'chiptemp', suffix: ' °C' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Main view ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
|
|
||||||
load: function(data) {
|
// FIX: removed unused `data` parameter
|
||||||
return L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'));
|
load: function() {
|
||||||
|
return L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{"modem": []}');
|
||||||
},
|
},
|
||||||
|
|
||||||
polldata: poll.add(function() {
|
polldata: poll.add(function() {
|
||||||
return L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo')).then(function(res) {
|
return L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{"modem": []}')
|
||||||
var json = JSON.parse(res);
|
.then(function(res) {
|
||||||
if (!json || !json.modem || !Array.isArray(json.modem)) return;
|
// FIX: wrapped in try/catch — was crashing on invalid JSON
|
||||||
for (var i = 0; i < json.modem.length; i++) {
|
let json;
|
||||||
if (document.getElementById('device'+i)) {
|
try {
|
||||||
var view = document.getElementById('device'+i);
|
json = JSON.parse(res);
|
||||||
if (json.modem[i].device == '--') {
|
} catch (e) {
|
||||||
view = document.getElementById('--');
|
console.error('modeminfo hw: JSON parse error', e);
|
||||||
} else {
|
return;
|
||||||
view.innerHTML = String.format(json.modem[i].device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (document.getElementById('firmware'+i)) {
|
|
||||||
var view = document.getElementById('firmware'+i);
|
|
||||||
if (json.modem[i].firmware == '--') {
|
|
||||||
view = document.getElementById('--');
|
|
||||||
} else {
|
|
||||||
view.innerHTML = String.format(json.modem[i].firmware);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (document.getElementById('imsi'+i)) {
|
|
||||||
var view = document.getElementById('imsi'+i);
|
|
||||||
if (json.modem[i].imsi == '--') {
|
|
||||||
view = document.getElementById('--');
|
|
||||||
} else {
|
|
||||||
view.innerHTML = String.format(json.modem[i].imsi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (document.getElementById('iccid'+i)) {
|
|
||||||
var view = document.getElementById('iccid'+i);
|
|
||||||
if (json.modem[i].iccid == '--') {
|
|
||||||
view = document.getElementById('--');
|
|
||||||
} else {
|
|
||||||
view.innerHTML = String.format(json.modem[i].iccid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.getElementById('imei'+i)) {
|
if (!json || !Array.isArray(json.modem)) return;
|
||||||
var view = document.getElementById('imei'+i);
|
|
||||||
if (json.modem[i].imei == '--') {
|
for (let i = 0; i < json.modem.length; i++) {
|
||||||
view = document.getElementById('--');
|
const modem = json.modem[i];
|
||||||
} else {
|
|
||||||
view.innerHTML = String.format(json.modem[i].imei);
|
// FIX: replaced 6 copy-pasted blocks with a single loop.
|
||||||
|
// FIX: `var view` renamed — was shadowing the LuCI `view` module.
|
||||||
|
// FIX: value '--' now explicitly written to cell (was silently ignored).
|
||||||
|
// FIX: innerHTML → textContent to prevent XSS.
|
||||||
|
// FIX: String.format(x) with no extra args replaced by direct assignment.
|
||||||
|
for (const { key, suffix } of MODEM_FIELDS) {
|
||||||
|
setCell(key + i, modem[key], suffix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (document.getElementById('chiptemp'+i)) {
|
|
||||||
var view = document.getElementById('chiptemp'+i);
|
|
||||||
if (json.modem[i].chiptemp == '--') {
|
|
||||||
view = document.getElementById('--');
|
|
||||||
} else {
|
|
||||||
view.innerHTML = String.format(json.modem[i].chiptemp+' °C');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
render: function(data){
|
render: function(data) {
|
||||||
|
// FIX: wrapped in try/catch — was crashing on invalid JSON
|
||||||
var m, s, o;
|
let json;
|
||||||
m = new form.Map('modeminfo', _('Modeminfo: Hardware'), _('Hardware and sim-card info.'));
|
try {
|
||||||
s = m.section(form.TypedSection, 'general', null);
|
json = JSON.parse(data);
|
||||||
var json = JSON.parse(data);
|
} catch (e) {
|
||||||
// for future use
|
json = { modem: [] };
|
||||||
/*
|
}
|
||||||
var vendors = [];
|
|
||||||
var duplicate_modem_index = 1;
|
const m = new form.Map('modeminfo', _('Modeminfo: Hardware'), _('Hardware and sim-card info.'));
|
||||||
json.modem.forEach(obj => {
|
const s = m.section(form.TypedSection, 'general', null);
|
||||||
const device = obj.device;
|
s.anonymous = true;
|
||||||
if (!vendors.includes(device)) {
|
|
||||||
vendors.push(device);
|
for (let i = 0; i < json.modem.length; i++) {
|
||||||
} else {
|
const idx = i + 1;
|
||||||
vendors.push(device+" ("+duplicate_modem_index+")");
|
|
||||||
duplicate_modem_index++;
|
// Pre-build all element ids for this modem slot
|
||||||
}
|
const ids = {};
|
||||||
});
|
for (const { key } of MODEM_FIELDS) {
|
||||||
*/
|
ids[key] = key + i;
|
||||||
for (var i = 0; i < json.modem.length; i++) {
|
}
|
||||||
let device = 'device'+i;
|
|
||||||
let firmware = 'firmware'+i;
|
let o;
|
||||||
let imsi = 'imsi'+i;
|
if (json.modem.length > 1) {
|
||||||
let iccid = 'iccid'+i;
|
s.tab('modem' + i, _('Modem') + ' ' + idx);
|
||||||
let imei = 'imei'+i;
|
o = s.taboption('modem' + i, form.HiddenValue, 'generic');
|
||||||
let chiptemp = 'chiptemp'+i;
|
|
||||||
let m = i+1;
|
|
||||||
if ( json.modem.length > 1 ) {
|
|
||||||
s.tab('modem'+i, _('Modem')+' '+m);
|
|
||||||
o = s.taboption('modem'+i, form.HiddenValue, 'generic');
|
|
||||||
} else {
|
} else {
|
||||||
o = s.option(form.HiddenValue, 'generic');
|
o = s.option(form.HiddenValue, 'generic');
|
||||||
}
|
}
|
||||||
o.render = L.bind(function(data){
|
|
||||||
return (
|
|
||||||
E('div', {}, [
|
|
||||||
E('h3', { 'class': 'data-tab' }),
|
|
||||||
E('div', { 'class': 'cbi-section', 'data-title': 'modem'+i }, [
|
|
||||||
E('table', { 'class': 'table' }, [
|
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
|
||||||
E('td', { 'class': 'td left', 'width': '50%' }, [ _('Device')]),
|
|
||||||
E('td', { 'class': 'td left', 'id': device }, [ '--' ]),
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
|
||||||
E('td', { 'class': 'td left', 'width': '50%' }, [ _('Firmware')]),
|
|
||||||
E('td', { 'class': 'td left', 'id': firmware }, [ '--' ]),
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
|
||||||
E('td', { 'class': 'td left', 'width': '50%' }, [ _('IMSI')]),
|
|
||||||
E('td', { 'class': 'td left', 'id': imsi }, [ '--' ]),
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
|
||||||
E('td', { 'class': 'td left', 'width': '50%' }, [ _('ICCID')]),
|
|
||||||
E('td', { 'class': 'td left', 'id': iccid }, [ '--' ]),
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
|
||||||
E('td', { 'class': 'td left', 'width': '50%' }, [ _('IMEI')]),
|
|
||||||
E('td', { 'class': 'td left', 'id': imei }, [ '--' ]),
|
|
||||||
]),
|
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
|
||||||
E('td', { 'class': 'td left', 'width': '50%' }, [ _('Chiptemp')]),
|
|
||||||
E('td', { 'class': 'td left', 'id': chiptemp }, [ '--' ]),
|
|
||||||
])
|
|
||||||
])
|
|
||||||
]),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)}, o, this.polldata);
|
|
||||||
o.anonymous = true;
|
|
||||||
o.rmempty = true;
|
|
||||||
};
|
|
||||||
s.anonymous = true;
|
|
||||||
return m.render();
|
|
||||||
|
|
||||||
|
o.render = L.bind(function() {
|
||||||
|
return E('div', {}, [
|
||||||
|
E('h3', { 'class': 'data-tab' }),
|
||||||
|
E('div', { 'class': 'cbi-section' }, [
|
||||||
|
E('table', { 'class': 'table' }, [
|
||||||
|
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
||||||
|
E('td', { 'class': 'td left', 'width': '50%' }, [_('Device')]),
|
||||||
|
E('td', { 'class': 'td left', 'id': ids.device }, ['--']),
|
||||||
|
]),
|
||||||
|
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
||||||
|
E('td', { 'class': 'td left', 'width': '50%' }, [_('Firmware')]),
|
||||||
|
E('td', { 'class': 'td left', 'id': ids.firmware }, ['--']),
|
||||||
|
]),
|
||||||
|
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
||||||
|
E('td', { 'class': 'td left', 'width': '50%' }, [_('IMSI')]),
|
||||||
|
E('td', { 'class': 'td left', 'id': ids.imsi }, ['--']),
|
||||||
|
]),
|
||||||
|
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
||||||
|
E('td', { 'class': 'td left', 'width': '50%' }, [_('ICCID')]),
|
||||||
|
E('td', { 'class': 'td left', 'id': ids.iccid }, ['--']),
|
||||||
|
]),
|
||||||
|
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
||||||
|
E('td', { 'class': 'td left', 'width': '50%' }, [_('IMEI')]),
|
||||||
|
E('td', { 'class': 'td left', 'id': ids.imei }, ['--']),
|
||||||
|
]),
|
||||||
|
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
||||||
|
E('td', { 'class': 'td left', 'width': '50%' }, [_('Chiptemp')]),
|
||||||
|
E('td', { 'class': 'td left', 'id': ids.chiptemp }, ['--']),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}, this.polldata);
|
||||||
|
|
||||||
|
o.anonymous = true;
|
||||||
|
o.rmempty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.render();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSaveApply: null,
|
handleSaveApply: null,
|
||||||
handleSave: null,
|
handleSave: null,
|
||||||
handleReset: null
|
handleReset: null,
|
||||||
});
|
});
|
||||||
@@ -9,464 +9,543 @@
|
|||||||
'require dom';
|
'require dom';
|
||||||
'require tools.widgets as widgets';
|
'require tools.widgets as widgets';
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright Konstantine Shevlakov <shevlakov@132lan.ru> 2023
|
Copyright Konstantine Shevlakov <shevlakov@132lan.ru> 2023
|
||||||
|
|
||||||
Licensed to the GNU General Public License v3.0.
|
Licensed to the GNU General Public License v3.0.
|
||||||
|
|
||||||
|
Refactored: bug fixes, performance improvements, code deduplication.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const REGISTERED_STATUSES = [1, 6, 9];
|
const REGISTERED_STATUSES = [1, 6, 9];
|
||||||
const ROAMING_STATUSES = [3, 5, 7, 10];
|
const ROAMING_STATUSES = [3, 5, 7, 10];
|
||||||
|
|
||||||
|
// ─── Signal Icons ────────────────────────────────────────────────────────────
|
||||||
|
const SIGNAL_ICONS = [
|
||||||
|
{ max: 10, icn: 'signal-000-000.svg' },
|
||||||
|
{ max: 25, icn: 'signal-000-025.svg' },
|
||||||
|
{ max: 50, icn: 'signal-025-050.svg' },
|
||||||
|
{ max: 75, icn: 'signal-050-075.svg' },
|
||||||
|
{ max: Infinity, icn: 'signal-075-100.svg' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Registration Status Labels ───────────────────────────────────────────────
|
||||||
|
const REG_STATUSES = new Map([
|
||||||
|
[0, _('No Registration')],
|
||||||
|
[2, _('Searching')], [8, _('Searching')],
|
||||||
|
[3, _('Denied')],
|
||||||
|
[4, _('Unknown')],
|
||||||
|
[5, _('Roaming')], [7, _('Roaming')], [10, _('Roaming')],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ─── Progress bar configuration ───────────────────────────────────────────────
|
||||||
|
// calc(vn, mn): vn = clamped signal value, mn = min boundary
|
||||||
|
// All formulas normalise the value to 0–100% range for the progress bar.
|
||||||
|
const PROGRESS_CONFIG = {
|
||||||
|
rssi: {
|
||||||
|
selector: '#rssi',
|
||||||
|
min: -110, max: -50,
|
||||||
|
// Linear map from [-110..-50] → [0..100]
|
||||||
|
calc: (vn, mn) => Math.floor(100 * (1 - (-50 - vn) / (-50 - mn))),
|
||||||
|
},
|
||||||
|
rsrp: {
|
||||||
|
selector: '#rsrp',
|
||||||
|
min: -140, max: -50,
|
||||||
|
// Slightly expanded scale (×1.2) to better use bar width
|
||||||
|
calc: (vn, mn) => Math.floor(120 * (1 - (-50 - vn) / (-70 - mn))),
|
||||||
|
},
|
||||||
|
sinr: {
|
||||||
|
selector: '#sinr',
|
||||||
|
min: -20, max: 30,
|
||||||
|
// Linear map from [-20..30] → [0..100]
|
||||||
|
calc: (vn, mn) => Math.floor(100 - (100 * (1 - ((mn - vn) / (mn - 30))))),
|
||||||
|
},
|
||||||
|
rsrq: {
|
||||||
|
selector: '#rsrq',
|
||||||
|
min: -20, max: 0,
|
||||||
|
// Proportional map from [-20..0] → [0..100]
|
||||||
|
calc: (vn, mn) => Math.floor(115 - (100 / mn) * vn),
|
||||||
|
},
|
||||||
|
ecio: {
|
||||||
|
selector: '#sinr',
|
||||||
|
min: -24, max: 0,
|
||||||
|
// Same shape as rsrq but for EC/IO range [-24..0]
|
||||||
|
calc: (vn, mn) => Math.floor(100 - (100 / mn) * vn),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── LTE EARFCN → band/frequency table ───────────────────────────────────────
|
||||||
|
const LTE_BANDS = [
|
||||||
|
{ min: 0, max: 599, frdl: 2110, frul: 1920, offset: 0, band: '1' },
|
||||||
|
{ min: 600, max: 1199, frdl: 1930, frul: 1850, offset: 600, band: '2' },
|
||||||
|
{ min: 1200, max: 1949, frdl: 1805, frul: 1710, offset: 1200, band: '3' },
|
||||||
|
{ min: 1950, max: 2399, frdl: 2110, frul: 1710, offset: 1950, band: '4' },
|
||||||
|
{ min: 2400, max: 2469, frdl: 869, frul: 824, offset: 2400, band: '5' },
|
||||||
|
{ min: 2750, max: 3449, frdl: 2620, frul: 2500, offset: 2750, band: '7' },
|
||||||
|
{ min: 3450, max: 3799, frdl: 925, frul: 880, offset: 3450, band: '8' },
|
||||||
|
{ min: 6150, max: 6449, frdl: 791, frul: 832, offset: 6150, band: '20' },
|
||||||
|
{ min: 9210, max: 9659, frdl: 758, frul: 703, offset: 9210, band: '28' },
|
||||||
|
{ min: 9870, max: 9919, frdl: 452.5, frul: 462.5, offset: 9870, band: '31' },
|
||||||
|
{ min: 37750, max: 38249, frdl: 2570, frul: 2570, offset: 37750, band: '38' },
|
||||||
|
{ min: 38650, max: 39649, frdl: 2300, frul: 2300, offset: 38650, band: '40' },
|
||||||
|
{ min: 39650, max: 41589, frdl: 2496, frul: 2496, offset: 39650, band: '41' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Non-LTE (UMTS/GSM) ARFCN → band/frequency table ─────────────────────────
|
||||||
|
const NON_LTE_BANDS = [
|
||||||
|
{
|
||||||
|
condition: rfcn => rfcn >= 10562 && rfcn <= 10838,
|
||||||
|
calc: rfcn => ({ offset: 950, dlfreq: rfcn / 5, ulfreq: (rfcn - 950) / 5, band: 'IMT2100' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
condition: rfcn => rfcn >= 2937 && rfcn <= 3088,
|
||||||
|
calc: rfcn => ({ frul: 925, ulfreq: 340 + (rfcn / 5), dlfreq: (340 + (rfcn / 5)) - 45, band: 'UMTS900' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
condition: rfcn => rfcn >= 955 && rfcn <= 1023,
|
||||||
|
calc: rfcn => ({ frul: 890, ulfreq: 890 + ((rfcn - 1024) / 5), dlfreq: (890 + ((rfcn - 1024) / 5)) + 45, band: 'DSC900' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
condition: rfcn => rfcn >= 512 && rfcn <= 885,
|
||||||
|
calc: rfcn => ({ frul: 1710, ulfreq: 1710 + ((rfcn - 512) / 5), dlfreq: (1710 + ((rfcn - 512) / 5)) + 95, band: 'DCS1800' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
condition: rfcn => rfcn >= 1 && rfcn <= 124,
|
||||||
|
calc: rfcn => ({ frul: 890, ulfreq: 890 + (rfcn / 5), dlfreq: (890 + (rfcn / 5)) + 45, band: 'GSM900' }),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const UMTS_MODES = /(HS|3G|UMTS|WCDMA)/i;
|
||||||
|
|
||||||
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safe getElementById wrapper.
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {HTMLElement|null}
|
||||||
|
*/
|
||||||
|
function getEl(id) {
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show or hide the grandparent row of a signal element.
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @param {boolean} show
|
||||||
|
*/
|
||||||
|
function setRowVisible(el, show) {
|
||||||
|
const row = el && el.parentElement && el.parentElement.parentElement;
|
||||||
|
if (row) row.style.display = show ? '' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a cbi-progressbar element.
|
||||||
|
* @param {string} type - Key in PROGRESS_CONFIG
|
||||||
|
* @param {string} value - Raw signal value string (e.g. "-85 dBm")
|
||||||
|
* @param {number} max - Boundary value (passed as second calc argument)
|
||||||
|
* @param {number} idx - Modem index suffix for the element id
|
||||||
|
*/
|
||||||
|
function updateProgressBar(type, value, max, idx) {
|
||||||
|
const config = PROGRESS_CONFIG[type];
|
||||||
|
if (!config) return;
|
||||||
|
|
||||||
|
const pg = document.querySelector(`${config.selector}${idx}`);
|
||||||
|
if (!pg) return;
|
||||||
|
|
||||||
|
const vn = Math.max(config.min, Math.min(config.max, parseInt(value) || 0));
|
||||||
|
const mn = parseInt(max) || 100;
|
||||||
|
const pc = Math.min(100, Math.max(0, config.calc(vn, mn)));
|
||||||
|
|
||||||
|
pg.firstElementChild.style.width = `${pc}%`;
|
||||||
|
pg.firstElementChild.style.animationDirection = 'reverse';
|
||||||
|
pg.setAttribute('title', String(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format distance string, returns empty string when unavailable.
|
||||||
|
* @param {string|number} dist
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function formatDistance(dist) {
|
||||||
|
if (!dist || dist === '--' || dist === '' || dist === '0.00') return '';
|
||||||
|
return ' ~' + dist + ' km';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the operator status badge HTML safely using LuCI E() helper.
|
||||||
|
* @param {Object} modem - Single modem entry from JSON
|
||||||
|
* @param {string} icon - URL to signal icon
|
||||||
|
* @param {string} reg - Human-readable registration status
|
||||||
|
* @returns {string} - Safe innerHTML string via dom.content
|
||||||
|
*/
|
||||||
|
function formatModemStatus(modem, icon, reg) {
|
||||||
|
const rg = parseInt(modem.reg) || 0;
|
||||||
|
const p = modem.csq_per || 0;
|
||||||
|
const cops = modem.cops || '--';
|
||||||
|
const color = modem.csq_col || '#000000';
|
||||||
|
const distanceText = formatDistance(modem.distance);
|
||||||
|
|
||||||
|
const iconEl = icon ? E('img', { 'class': 'modem-signal-icon', 'src': icon }) : null;
|
||||||
|
const boldEl = E('b', { 'style': `color:${color}` }, [`${p}%`]);
|
||||||
|
|
||||||
|
const children = iconEl
|
||||||
|
? [cops + ' ', iconEl, ' ', boldEl, distanceText]
|
||||||
|
: [cops + ' ', boldEl, distanceText];
|
||||||
|
|
||||||
|
if (REGISTERED_STATUSES.includes(rg)) {
|
||||||
|
return E('span', { 'class': 'ifacebadge' }, children).outerHTML;
|
||||||
|
} else if (ROAMING_STATUSES.includes(rg)) {
|
||||||
|
return E('span', { 'class': 'ifacebadge' }, [`${cops} (${reg}) `, ...(iconEl ? [iconEl, ' '] : []), boldEl, distanceText]).outerHTML;
|
||||||
|
} else {
|
||||||
|
return E('span', { 'class': 'ifacebadge' }, [reg || '--']).outerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive signal icon URL from signal percentage.
|
||||||
|
* @param {number} pct
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function resolveSignalIcon(pct) {
|
||||||
|
const { icn } = SIGNAL_ICONS.find(({ max }) => pct <= max) || SIGNAL_ICONS[SIGNAL_ICONS.length - 1];
|
||||||
|
return L.resource(`view/modem/icons/${icn}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate DL/UL frequencies and band name for LTE mode.
|
||||||
|
* @param {number} rfcn
|
||||||
|
* @returns {{ dlfreq: number, ulfreq: number, band: string, frdl: number, frul: number, offset: number }}
|
||||||
|
*/
|
||||||
|
function calcLteBand(rfcn) {
|
||||||
|
const b = LTE_BANDS.find(b => rfcn >= b.min && rfcn <= b.max);
|
||||||
|
if (!b) return { frdl: 0, frul: 0, offset: 0, band: String(rfcn), dlfreq: 0, ulfreq: 0 };
|
||||||
|
const dlfreq = b.frdl + (rfcn - b.offset) / 10;
|
||||||
|
const ulfreq = b.frul + (rfcn - b.offset) / 10;
|
||||||
|
return { ...b, dlfreq, ulfreq };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate DL/UL frequencies and band name for non-LTE modes.
|
||||||
|
* @param {number} rfcn
|
||||||
|
* @returns {{ dlfreq: number, ulfreq: number, band: string }}
|
||||||
|
*/
|
||||||
|
function calcNonLteBand(rfcn) {
|
||||||
|
const match = NON_LTE_BANDS.find(b => b.condition(rfcn));
|
||||||
|
return match ? match.calc(rfcn) : { ulfreq: 0, dlfreq: 0, band: String(rfcn) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a LAC/CID/eNB/Cell/PCI label and value string.
|
||||||
|
* @param {Object} modem - Single modem entry
|
||||||
|
* @returns {{ namecid: string, lactac: string }}
|
||||||
|
*/
|
||||||
|
function buildCellId(modem) {
|
||||||
|
const { enbid, cell, pci, lac, cid } = modem;
|
||||||
|
const parts = [lac, cid];
|
||||||
|
let namecid = 'LAC/CID';
|
||||||
|
|
||||||
|
if (enbid) {
|
||||||
|
parts.push(enbid);
|
||||||
|
namecid += '/eNB ID';
|
||||||
|
|
||||||
|
if (cell) {
|
||||||
|
parts.push(cell);
|
||||||
|
namecid += '/Cell';
|
||||||
|
|
||||||
|
if (pci) {
|
||||||
|
parts.push(pci);
|
||||||
|
namecid += '/PCI';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lactac = parts.join(' / ');
|
||||||
|
return { namecid, lactac };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive mode-specific labels and carrier aggregation info.
|
||||||
|
* @param {Object} modem - Single modem entry
|
||||||
|
* @param {string} netmode
|
||||||
|
* @param {string} band
|
||||||
|
* @param {number} bw
|
||||||
|
* @returns {{ carrier, bcc, bca, bwDisplay, namech, namesnr, namecid, lactac, namebnd }}
|
||||||
|
*/
|
||||||
|
function buildModeInfo(modem, netmode, band, bw) {
|
||||||
|
let carrier = '';
|
||||||
|
let bcc, bca, bwDisplay, namech, namesnr, namecid, lactac;
|
||||||
|
|
||||||
|
if (netmode === 'LTE') {
|
||||||
|
const calte = modem.lteca;
|
||||||
|
namech = 'EARFCN';
|
||||||
|
namesnr = 'SINR';
|
||||||
|
|
||||||
|
if (calte > 0) {
|
||||||
|
carrier = '+';
|
||||||
|
bwDisplay = modem.bwca;
|
||||||
|
bcc = ` B${band}${modem.scc}`;
|
||||||
|
bca = bwDisplay ? ` / ${bwDisplay} MHz` : '';
|
||||||
|
} else {
|
||||||
|
bwDisplay = bw;
|
||||||
|
bcc = ` B${band}`;
|
||||||
|
bca = bw ? ` / ${bw} MHz` : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const cellInfo = buildCellId(modem);
|
||||||
|
namecid = cellInfo.namecid;
|
||||||
|
lactac = cellInfo.lactac;
|
||||||
|
|
||||||
|
} else if (UMTS_MODES.test(netmode)) {
|
||||||
|
namech = 'UARFCN';
|
||||||
|
namesnr = 'ECIO';
|
||||||
|
namecid = 'LAC/CID';
|
||||||
|
lactac = `${modem.lac} / ${modem.cid}`;
|
||||||
|
bcc = ` ${band}`;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
namech = 'ARFCN';
|
||||||
|
namesnr = 'SINR/ECIO';
|
||||||
|
namecid = 'LAC/CID';
|
||||||
|
lactac = `${modem.lac} / ${modem.cid}`;
|
||||||
|
bcc = ` ${band}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const namebnd = bw
|
||||||
|
? _('Network/Band/Bandwidth')
|
||||||
|
: _('Network/Band');
|
||||||
|
|
||||||
|
return { carrier, bcc, bca: bca || '', bwDisplay, namech, namesnr, namecid, lactac, namebnd };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set textContent of element by id (if it exists).
|
||||||
|
* @param {string} id
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
function setText(id, text) {
|
||||||
|
const el = getEl(id);
|
||||||
|
if (el) el.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set innerHTML of element by id (if it exists).
|
||||||
|
* @param {string} id
|
||||||
|
* @param {string} html
|
||||||
|
*/
|
||||||
|
function setHtml(id, html) {
|
||||||
|
const el = getEl(id);
|
||||||
|
if (el) el.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a signal progress bar, hiding its parent row if value is missing.
|
||||||
|
* @param {string} elId - Element id (without modem index)
|
||||||
|
* @param {number} idx - Modem index
|
||||||
|
* @param {*} rawVal - Raw value from modem JSON
|
||||||
|
* @param {string} unit - Unit suffix, e.g. ' dBm'
|
||||||
|
* @param {string} type - Key in PROGRESS_CONFIG
|
||||||
|
* @param {number} boundary
|
||||||
|
*/
|
||||||
|
function updateSignalBar(elId, idx, rawVal, unit, type, boundary) {
|
||||||
|
const el = getEl(elId + idx);
|
||||||
|
if (!el) return;
|
||||||
|
const missing = !rawVal || rawVal === '--' || rawVal === '';
|
||||||
|
setRowVisible(el, !missing);
|
||||||
|
if (!missing) {
|
||||||
|
updateProgressBar(type, rawVal + unit, boundary, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Main view ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
|
|
||||||
load: function(data) {
|
load: function() {
|
||||||
return L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{"modem": []}');
|
return L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{"modem": []}');
|
||||||
},
|
},
|
||||||
|
|
||||||
polldata: poll.add(function() {
|
polldata: poll.add(function() {
|
||||||
return L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{"modem": []}').then(function(res) {
|
return L.resolveDefault(fs.exec_direct('/usr/bin/modeminfo'), '{"modem": []}')
|
||||||
var json = JSON.parse(res);
|
.then(function(res) {
|
||||||
if (!json || !json.modem || !Array.isArray(json.modem)) return;
|
let json;
|
||||||
for (var i = 0; i < json.modem.length; i++) {
|
try {
|
||||||
// progressbar cellular metric
|
json = JSON.parse(res);
|
||||||
const progressConfig = {
|
} catch (e) {
|
||||||
rssi: {
|
console.error('modeminfo: JSON parse error', e);
|
||||||
selector: '#rssi', min: -110, max: -50,
|
return;
|
||||||
calc: (vn, mn) => Math.floor(100 * (1 - (-50 - vn) / (-50 - mn)))
|
|
||||||
},
|
|
||||||
rsrp: {
|
|
||||||
selector: '#rsrp', min: -140, max: -50,
|
|
||||||
calc: (vn, mn) => Math.floor(120 * (1 - (-50 - vn) / (-70 - mn)))
|
|
||||||
},
|
|
||||||
sinr: {
|
|
||||||
selector: '#sinr', min: -20, max: 30,
|
|
||||||
calc: (vn, mn) => Math.floor(100 - (100 * (1 - ((mn - vn) / (mn - 30)))))
|
|
||||||
},
|
|
||||||
rsrq: {
|
|
||||||
selector: '#rsrq', min: -20, max: 0,
|
|
||||||
calc: (vn, mn) => Math.floor(115 - (100 / mn) * vn)
|
|
||||||
},
|
|
||||||
ecio: {
|
|
||||||
selector: '#sinr', min: -24, max: 0,
|
|
||||||
calc: (vn, mn) => Math.floor(100 - (100 / mn) * vn)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function updateProgressBar(type, value, max, i) {
|
|
||||||
const config = progressConfig[type];
|
|
||||||
if (!config) return;
|
|
||||||
|
|
||||||
const pg = document.querySelector(`${config.selector}${i}`);
|
|
||||||
if (!pg) return;
|
|
||||||
|
|
||||||
const vn = Math.max(config.min, Math.min(config.max, parseInt(value) || 0));
|
|
||||||
const mn = parseInt(max) || 100;
|
|
||||||
const pc = config.calc(vn, mn);
|
|
||||||
|
|
||||||
pg.firstElementChild.style.width = `${pc}%`;
|
|
||||||
pg.style.width = '%d%%';
|
|
||||||
pg.firstElementChild.style.animationDirection = "reverse";
|
|
||||||
pg.setAttribute('title', '%s'.format(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDistance(dist) {
|
if (!json || !Array.isArray(json.modem)) return;
|
||||||
if (!dist || dist === "--" || dist === "" || dist === "0.00") {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return ' ~' + dist + ' km';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatModemStatus(modem, icon, reg) {
|
for (let i = 0; i < json.modem.length; i++) {
|
||||||
var rg = parseInt(modem.reg) || 0;
|
const modem = json.modem[i];
|
||||||
var p = modem.csq_per || 0;
|
const netmode = modem.mode || '';
|
||||||
var cops = modem.cops || '--';
|
const rfcn = modem.arfcn || 0;
|
||||||
var color = modem.csq_col || '#000000';
|
|
||||||
|
// ── Band / Frequency ────────────────────────────────────
|
||||||
var distanceHtml = formatDistance(modem.distance);
|
let dlfreq, ulfreq, band, bw;
|
||||||
var iconHtml = icon ? '<img class="modem-signal-icon" src="' + icon + '"/>' : '';
|
|
||||||
|
if (netmode === 'LTE') {
|
||||||
var percentWithColor = p + '%';
|
({ dlfreq, ulfreq, band } = calcLteBand(rfcn));
|
||||||
if (REGISTERED_STATUSES.includes(rg)) {
|
const bandwidths = [1.4, 3, 5, 10, 15, 20];
|
||||||
return '<span class="ifacebadge">' + cops + ' ' + iconHtml +
|
bw = bandwidths[modem.bwdl] || '';
|
||||||
' <b style="color:' + color + '">' + percentWithColor + '</b>' +
|
|
||||||
distanceHtml + '</span>';
|
|
||||||
} else if (ROAMING_STATUSES.includes(rg)) {
|
|
||||||
return '<span class="ifacebadge">' + cops + ' (' + reg + ') ' + iconHtml +
|
|
||||||
' <b style="color:' + color + '">' + percentWithColor + '</b>' +
|
|
||||||
distanceHtml + '</span>';
|
|
||||||
} else {
|
} else {
|
||||||
return '<span class="ifacebadge">' + (reg || '--') + '</span>';
|
({ dlfreq, ulfreq, band } = calcNonLteBand(rfcn));
|
||||||
}
|
bw = '';
|
||||||
}
|
|
||||||
|
|
||||||
// icon signal strength
|
|
||||||
var icn;
|
|
||||||
|
|
||||||
var signalIcons = [
|
|
||||||
{ max: -1, icn: 'signal-000-000.svg' },
|
|
||||||
{ max: 0, icn: 'signal-000-000.svg' },
|
|
||||||
{ max: 10, icn: 'signal-000-000.svg' },
|
|
||||||
{ max: 25, icn: 'signal-000-025.svg' },
|
|
||||||
{ max: 50, icn: 'signal-025-050.svg' },
|
|
||||||
{ max: 75, icn: 'signal-050-075.svg' },
|
|
||||||
{ max: Infinity, icn: 'signal-075-100.svg' }
|
|
||||||
];
|
|
||||||
|
|
||||||
var p = json.modem[i].csq_per || 0;
|
|
||||||
|
|
||||||
var { icn } = signalIcons.find(({ max }) => p <= max);
|
|
||||||
var icon = L.resource(`view/modem/icons/${icn}`);
|
|
||||||
|
|
||||||
// Registration Status
|
|
||||||
var regStatuses = new Map([
|
|
||||||
[0, _('No Registration')],
|
|
||||||
[2, _('Searching')], [8, _('Searching')],
|
|
||||||
[3, _('Denied')],
|
|
||||||
[4, _('Unknown')],
|
|
||||||
[5, _('Roaming')], [7, _('Roaming')], [10, _('Roaming')]
|
|
||||||
]);
|
|
||||||
|
|
||||||
var rg = Number(json.modem[i].reg);
|
|
||||||
var reg = regStatuses.get(rg) || _('No Data');
|
|
||||||
|
|
||||||
// frequency band calculator
|
|
||||||
var offset, band, bw, frdl, frul;
|
|
||||||
var netmode = (json.modem[i].mode)
|
|
||||||
var rfcn = (json.modem[i].arfcn)
|
|
||||||
if (netmode === "LTE") {
|
|
||||||
// list LTE-bans
|
|
||||||
var lteBands = [
|
|
||||||
{ min: 0, max: 599, frdl: 2110, frul: 1920, offset: 0, band: "1" },
|
|
||||||
{ min: 600, max: 1199, frdl: 1930, frul: 1850, offset: 600, band: "2" },
|
|
||||||
{ min: 1200, max: 1949, frdl: 1805, frul: 1710, offset: 1200, band: "3" },
|
|
||||||
{ min: 1950, max: 2399, frdl: 2110, frul: 1710, offset: 1950, band: "4" },
|
|
||||||
{ min: 2400, max: 2469, frdl: 869, frul: 824, offset: 2400, band: "5" },
|
|
||||||
{ min: 2750, max: 3449, frdl: 2620, frul: 2500, offset: 2750, band: "7" },
|
|
||||||
{ min: 3450, max: 3799, frdl: 925, frul: 880, offset: 3450, band: "8" },
|
|
||||||
{ min: 6150, max: 6449, frdl: 791, frul: 832, offset: 6150, band: "20" },
|
|
||||||
{ min: 9210, max: 9659, frdl: 758, frul: 703, offset: 9210, band: "28" },
|
|
||||||
{ min: 9870, max: 9919, frdl: 452.5, frul: 462.5, offset: 9870, band: "31" },
|
|
||||||
{ min: 37750, max: 38249, frdl: 2570, frul: 2570, offset: 37750, band: "38" },
|
|
||||||
{ min: 38650, max: 39649, frdl: 2300, frul: 2300, offset: 38650, band: "40" },
|
|
||||||
{ min: 39650, max: 41589, frdl: 2496, frul: 2496, offset: 39650, band: "41" }
|
|
||||||
];
|
|
||||||
|
|
||||||
var bandConfig = lteBands.find(b => rfcn >= b.min && rfcn <= b.max) || {
|
|
||||||
frdl: 0, frul: 0, offset: 0, band: rfcn
|
|
||||||
};
|
|
||||||
({ frdl, frul, offset, band } = bandConfig);
|
|
||||||
|
|
||||||
// Bandwidth channel (bw)
|
|
||||||
var bandwidths = [1.4, 3, 5, 10, 15, 20];
|
|
||||||
var bw = bandwidths[json.modem[i].bwdl] || "";
|
|
||||||
|
|
||||||
// Calc frequency
|
|
||||||
var dlfreq = frdl + (rfcn - offset) / 10;
|
|
||||||
var ulfreq = frul + (rfcn - offset) / 10;
|
|
||||||
} else {
|
|
||||||
var nonLteBands = [
|
|
||||||
{
|
|
||||||
condition: (rfcn) => rfcn >= 10562 && rfcn <= 10838,
|
|
||||||
calc: (rfcn) => ({ offset: 950, dlfreq: rfcn / 5, ulfreq: (rfcn - 950) / 5, band: "IMT2100" })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
condition: (rfcn) => rfcn >= 2937 && rfcn <= 3088,
|
|
||||||
calc: (rfcn) => ({ frul: 925, offset: 340, ulfreq: 340 + (rfcn / 5), dlfreq: (340 + (rfcn / 5)) - 45, band: "UMTS900" })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
condition: (rfcn) => rfcn >= 955 && rfcn <= 1023,
|
|
||||||
calc: (rfcn) => ({ frul: 890, ulfreq: 890 + ((rfcn - 1024) / 5), dlfreq: (890 + ((rfcn - 1024) / 5)) + 45, band: "DSC900" })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
condition: (rfcn) => rfcn >= 512 && rfcn <= 885,
|
|
||||||
calc: (rfcn) => ({ frul: 1710, ulfreq: 1710 + ((rfcn - 512) / 5), dlfreq: (1710 + ((rfcn - 512) / 5)) + 95, band: "DCS1800" })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
condition: (rfcn) => rfcn >= 1 && rfcn <= 124,
|
|
||||||
calc: (rfcn) => ({ frul: 890, ulfreq: 890 + (rfcn / 5), dlfreq: (890 + (rfcn / 5)) + 45, band: "GSM900" })
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
var bandConfig = nonLteBands.find(b => b.condition(rfcn))?.calc(rfcn) || { ulfreq: 0, dlfreq: 0, band: String(rfcn) };
|
|
||||||
|
|
||||||
// CALC BANDS
|
|
||||||
({ frul, offset, ulfreq, dlfreq, band } = bandConfig);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var carrier = "";
|
|
||||||
var bcc, freq, distance, calte, namebnd;
|
|
||||||
var dist = (json.modem[i].distance)
|
|
||||||
|
|
||||||
var { enbid, cell, pci, lac, cid } = json.modem[i];
|
|
||||||
|
|
||||||
const parts = [lac, cid];
|
|
||||||
var namecid = "LAC/CID";
|
|
||||||
|
|
||||||
if (enbid) {
|
|
||||||
parts.push(enbid);
|
|
||||||
namecid += "/eNB ID";
|
|
||||||
|
|
||||||
if (cell) {
|
|
||||||
parts.push(`/${cell}`);
|
|
||||||
namecid += "/Cell";
|
|
||||||
|
|
||||||
if (pci) {
|
|
||||||
parts.push(`/${pci}`);
|
|
||||||
namecid += "/PCI";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var lactac = parts.join(' / ').replace(' ', ' ').replace('/ /', '/ ').replace('/ /', '/ ')
|
const arfcnStr = `${rfcn} (${dlfreq} / ${ulfreq} MHz)`;
|
||||||
|
|
||||||
var UMTS_MODES = /(HS|3G|UMTS|WCDMA)/i;
|
// ── Registration ────────────────────────────────────────
|
||||||
|
const rg = Number(modem.reg);
|
||||||
|
const reg = REG_STATUSES.get(rg) || _('No Data');
|
||||||
|
|
||||||
var bcc, scc, cid;
|
// ── Signal icon ─────────────────────────────────────────
|
||||||
var bca = "";
|
const icon = resolveSignalIcon(modem.csq_per || 0);
|
||||||
var arfcn = json.modem[i].arfcn + " (" + dlfreq + " / " + ulfreq + " MHz)";
|
|
||||||
// name channels and signal/noise
|
// ── Mode-specific labels ────────────────────────────────
|
||||||
if (netmode == "LTE") {
|
const { carrier, bcc, bca, namech, namesnr, namecid, lactac, namebnd } =
|
||||||
var calte = (json.modem[i].lteca)
|
buildModeInfo(modem, netmode, band, bw);
|
||||||
var bwca = json.modem[i].bwca;
|
|
||||||
distance = " ~"+ dist +" km";
|
// ── DOM updates ─────────────────────────────────────────
|
||||||
if (calte > 0) {
|
setHtml('status' + i, formatModemStatus(modem, icon, reg));
|
||||||
carrier = "+";
|
|
||||||
scc = json.modem[i].scc;
|
const modeEl = getEl('mode' + i);
|
||||||
bw = bwca;
|
if (modeEl) {
|
||||||
bca = " / " + bw + " MHz";
|
// FIX: was `signal = 0` (assignment), must be `=== 0`
|
||||||
bcc = " B" + band + "" + scc;
|
if (modem.signal === 0 || modem.signal === '' || !netmode) {
|
||||||
|
modeEl.textContent = '--';
|
||||||
} else {
|
} else {
|
||||||
scc = "";
|
modeEl.textContent = `${netmode}${carrier} /${bcc}${bca}`;
|
||||||
bcc = " B" + band;
|
|
||||||
if (bw) {
|
|
||||||
bca = " / " + bw + " MHz";
|
|
||||||
} else{
|
|
||||||
bca = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var namech = "EARFCN";
|
|
||||||
var namesnr = "SINR";
|
|
||||||
} else if (UMTS_MODES.test(netmode)) {
|
|
||||||
var namech = "UARFCN";
|
|
||||||
var namesnr = "ECIO";
|
|
||||||
var namecid = "LAC/CID";
|
|
||||||
var lactac = json.modem[i].lac + " / " + json.modem[i].cid;
|
|
||||||
var bcc = " " + band;
|
|
||||||
} else {
|
|
||||||
var namech = "ARFCN";
|
|
||||||
var namesnr = "SINR/ECIO";
|
|
||||||
var namecid = "LAC/CID";
|
|
||||||
var lactac = json.modem[i].lac + " / " + json.modem[i].cid;
|
|
||||||
var bcc = " " + band;
|
|
||||||
}
|
}
|
||||||
if (bw) {
|
|
||||||
namebnd = _('Network/Band/Bandwidth');
|
|
||||||
} else {
|
|
||||||
namebnd = _('Network/Band');
|
|
||||||
}
|
|
||||||
|
|
||||||
// data by element
|
setText('namebnd' + i, namebnd);
|
||||||
|
setText('chname' + i, namech);
|
||||||
|
setText('namecid' + i, namecid);
|
||||||
|
setText('snrname' + i, namesnr);
|
||||||
|
setHtml('arfcn' + i, arfcnStr);
|
||||||
|
setHtml('lac' + i, lactac);
|
||||||
|
|
||||||
if (document.getElementById('status'+i)) {
|
// ── Progress bars ───────────────────────────────────────
|
||||||
var view = document.getElementById('status'+i);
|
// RSSI always visible if element exists
|
||||||
view.innerHTML = formatModemStatus(json.modem[i], icon, reg);
|
if (getEl('rssi' + i)) {
|
||||||
}
|
if (!modem.rssi || modem.rssi === '') {
|
||||||
|
setText('rssi' + i, '--');
|
||||||
if (document.getElementById('mode'+i)){
|
|
||||||
var view = document.getElementById('mode'+i);
|
|
||||||
if (json.modem[i].signal = 0 || json.modem[i].signal == '') {
|
|
||||||
view.textContent = '--';
|
|
||||||
} else {
|
|
||||||
if (json.modem[i].mode == '') {
|
|
||||||
view.textContent = '--';
|
|
||||||
} else {
|
} else {
|
||||||
view.innerHTML = String.format(json.modem[i].mode + "" + carrier + " /"+ bcc +""+ bca);
|
updateProgressBar('rssi', modem.rssi + ' dBm', -110, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('namebnd'+i)){
|
// SINR / ECIO (shared element, hidden when unavailable)
|
||||||
var view = document.getElementById('namebnd'+i);
|
updateSignalBar(
|
||||||
view.innerHTML = String.format(namebnd);
|
'sinr', i, modem.sinr, ' dB',
|
||||||
}
|
netmode === 'LTE' ? 'sinr' : 'ecio',
|
||||||
|
netmode === 'LTE' ? -20 : -24
|
||||||
|
);
|
||||||
|
|
||||||
if (document.getElementById('chname'+i)){
|
// RSRP (hidden when unavailable)
|
||||||
var view = document.getElementById('chname'+i);
|
updateSignalBar('rsrp', i, modem.rsrp, ' dBm', 'rsrp', -140);
|
||||||
view.innerHTML = String.format(namech);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('namecid'+i)){
|
// RSRQ (hidden when unavailable)
|
||||||
var view = document.getElementById('namecid'+i);
|
updateSignalBar('rsrq', i, modem.rsrq, ' dB', 'rsrq', -20);
|
||||||
view.innerHTML = String.format(namecid);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (document.getElementById('arfcn'+i)){
|
|
||||||
var view = document.getElementById('arfcn'+i);
|
|
||||||
view.innerHTML = String.format(arfcn);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('lac'+i)){
|
|
||||||
var view = document.getElementById('lac'+i);
|
|
||||||
view.innerHTML = String.format(lactac);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('snrname'+i)) {
|
|
||||||
var view = document.getElementById('snrname'+i);
|
|
||||||
view.innerHTML = String.format(namesnr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('rssi'+i)) {
|
|
||||||
var view = document.getElementById('rssi'+i);
|
|
||||||
if (json.modem[i].rssi == '') {
|
|
||||||
view.textContent = '--';
|
|
||||||
} else {
|
|
||||||
updateProgressBar('rssi', json.modem[i].rssi + ' dBm', -110, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (document.getElementById('sinr'+i)) {
|
|
||||||
var sinrElement = document.getElementById('sinr'+i);
|
|
||||||
var sinrParent = sinrElement ? sinrElement.parentElement : null;
|
|
||||||
|
|
||||||
if (!json.modem[i].sinr || json.modem[i].sinr === "--" || json.modem[i].sinr === "") {
|
|
||||||
if (sinrParent && sinrParent.parentElement) {
|
|
||||||
sinrParent.parentElement.style.display = 'none';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (sinrParent && sinrParent.parentElement) {
|
|
||||||
sinrParent.parentElement.style.display = '';
|
|
||||||
}
|
|
||||||
if (netmode == "LTE") {
|
|
||||||
updateProgressBar('sinr', json.modem[i].sinr + ' dB', -20, i);
|
|
||||||
} else {
|
|
||||||
updateProgressBar('ecio', json.modem[i].sinr + ' dB', -24, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('rsrp'+i)) {
|
|
||||||
var rsrpElement = document.getElementById('rsrp'+i);
|
|
||||||
var rsrpParent = rsrpElement ? rsrpElement.parentElement : null;
|
|
||||||
|
|
||||||
if (!json.modem[i].rsrp || json.modem[i].rsrp === "--" || json.modem[i].rsrp === "") {
|
|
||||||
if (rsrpParent && rsrpParent.parentElement) {
|
|
||||||
rsrpParent.parentElement.style.display = 'none';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (rsrpParent && rsrpParent.parentElement) {
|
|
||||||
rsrpParent.parentElement.style.display = '';
|
|
||||||
}
|
|
||||||
updateProgressBar('rsrp', json.modem[i].rsrp + ' dBm', -140, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('rsrq'+i)) {
|
|
||||||
var rsrqElement = document.getElementById('rsrq'+i);
|
|
||||||
var rsrqParent = rsrqElement ? rsrqElement.parentElement : null;
|
|
||||||
|
|
||||||
if (!json.modem[i].rsrq || json.modem[i].rsrq === "--" || json.modem[i].rsrq === "") {
|
|
||||||
if (rsrqParent && rsrqParent.parentElement) {
|
|
||||||
rsrqParent.parentElement.style.display = 'none';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (rsrqParent && rsrqParent.parentElement) {
|
|
||||||
rsrqParent.parentElement.style.display = '';
|
|
||||||
}
|
|
||||||
updateProgressBar('rsrq', json.modem[i].rsrq + ' dB', -20, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
render: function(data){
|
render: function(data) {
|
||||||
var m, s, o;
|
let json;
|
||||||
|
try {
|
||||||
|
json = JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
json = { modem: [] };
|
||||||
|
}
|
||||||
|
|
||||||
m = new form.Map('modeminfo', _('Modeminfo: Network'), _('Cellular network'));
|
const m = new form.Map('modeminfo', _('Modeminfo: Network'), _('Cellular network'));
|
||||||
s = m.section(form.TypedSection, 'general', null);
|
const s = m.section(form.TypedSection, 'general', null);
|
||||||
var json = JSON.parse(data);
|
s.anonymous = true;
|
||||||
|
|
||||||
for (var i = 0; i < json.modem.length; i++) {
|
for (let i = 0; i < json.modem.length; i++) {
|
||||||
let status = 'status'+i;
|
const idx = i + 1;
|
||||||
let mode = 'mode'+i;
|
const statusId = 'status' + i;
|
||||||
let namebnd = 'namebnd'+i;
|
const modeId = 'mode' + i;
|
||||||
let chname = 'chname'+i;
|
const namebndId= 'namebnd'+ i;
|
||||||
let namecid = 'namecid'+i;
|
const chnameId = 'chname' + i;
|
||||||
let arfcn = 'arfcn'+i;
|
const namecidId= 'namecid'+ i;
|
||||||
let lac = 'lac'+i;
|
const arfcnId = 'arfcn' + i;
|
||||||
let rssi = 'rssi'+i;
|
const lacId = 'lac' + i;
|
||||||
let sinr = 'sinr'+i;
|
const rssiId = 'rssi' + i;
|
||||||
let snrname = 'snrname'+i;
|
const sinrId = 'sinr' + i;
|
||||||
let rsrp = 'rsrp'+i;
|
const snrnameId= 'snrname'+ i;
|
||||||
let rsrq = 'rsrq'+i;
|
const rsrpId = 'rsrp' + i;
|
||||||
let m = i+1;
|
const rsrqId = 'rsrq' + i;
|
||||||
|
|
||||||
if ( json.modem.length > 1 ) {
|
let o;
|
||||||
s.tab("modem" + i, _('Modem')+' '+m);
|
if (json.modem.length > 1) {
|
||||||
o = s.taboption('modem'+i, form.HiddenValue, 'generic');
|
s.tab('modem' + i, _('Modem') + ' ' + idx);
|
||||||
|
o = s.taboption('modem' + i, form.HiddenValue, 'generic');
|
||||||
} else {
|
} else {
|
||||||
o = s.option(form.HiddenValue, 'generic');
|
o = s.option(form.HiddenValue, 'generic');
|
||||||
}
|
}
|
||||||
|
|
||||||
o.render = L.bind(function(data){
|
o.render = L.bind(function() {
|
||||||
return (
|
return E('div', {}, [
|
||||||
E('div', {}, [
|
E('h3', { 'class': 'data-tab' }),
|
||||||
E('h3', { 'class': 'data-tab' }),
|
E('div', { 'class': 'cbi-section' }, [
|
||||||
E('div', { 'class': 'cbi-section', 'data-title': 'modem'+i }, [
|
E('table', { 'class': 'table' }, [
|
||||||
E('table', { 'class': 'table' }, [
|
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
E('td', { 'class': 'td left', 'width': '50%' }, [_('Operator')]),
|
||||||
E('td', { 'class': 'td left', 'width': '50%' }, [ _('Operator')]),
|
E('td', { 'class': 'td left', 'id': statusId }, ['--']),
|
||||||
E('td', { 'class': 'td left', 'id': status }, [ '--' ]),
|
]),
|
||||||
]),
|
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
E('td', { 'class': 'td left', 'width': '50%', 'id': namebndId }, [_('Network/Band')]),
|
||||||
E('td', { 'class': 'td left', 'width': '50%', 'id': namebnd }, [ _('Network/band')]),
|
E('td', { 'class': 'td left', 'id': modeId }, ['--']),
|
||||||
E('td', { 'class': 'td left', 'id': mode }, [ '--' ]),
|
]),
|
||||||
]),
|
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
E('td', { 'class': 'td left', 'width': '50%', 'id': chnameId }, [_('E/U/ARFCN')]),
|
||||||
E('td', { 'class': 'td left', 'width': '50%', 'id': chname }, [ _('E/U/ARFCN')]),
|
E('td', { 'class': 'td left', 'id': arfcnId }, ['--']),
|
||||||
E('td', { 'class': 'td left', 'id': arfcn }, [ '--' ]),
|
]),
|
||||||
]),
|
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
E('td', { 'class': 'td left', 'width': '50%', 'id': namecidId }, [_('LAC/CID')]),
|
||||||
E('td', { 'class': 'td left', 'width': '50%', 'id': namecid }, [ _('LAC/CID')]),
|
E('td', { 'class': 'td left', 'id': lacId }, ['--']),
|
||||||
E('td', { 'class': 'td left', 'id': lac }, [ '--' ]),
|
]),
|
||||||
]),
|
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
E('td', { 'class': 'td left', 'width': '50%' }, [_('RSSI')]),
|
||||||
E('td', { 'class': 'td left', 'width': '50%' }, [ _('RSSI')]),
|
E('td', { 'class': 'td left' },
|
||||||
E('td', { 'class': 'td left'}, E('div', {'id': rssi, 'class': 'cbi-progressbar', 'title': '--' }, E('div'))),
|
E('div', { 'id': rssiId, 'class': 'cbi-progressbar', 'title': '--' }, E('div'))
|
||||||
]),
|
),
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
]),
|
||||||
E('td', { 'class': 'td left', 'width': '50%', 'id': snrname }, [ _('SINR/EcIO')]),
|
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
||||||
E('td', { 'class': 'td left'}, E('div', {'id': sinr, 'class': 'cbi-progressbar', 'title': '--'}, E('div'))),
|
E('td', { 'class': 'td left', 'width': '50%', 'id': snrnameId }, [_('SINR/EcIO')]),
|
||||||
]),
|
E('td', { 'class': 'td left' },
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
E('div', { 'id': sinrId, 'class': 'cbi-progressbar', 'title': '--' }, E('div'))
|
||||||
E('td', { 'class': 'td left', 'width': '50%' }, [ _('RSRP')]),
|
),
|
||||||
E('td', { 'class': 'td left'}, E('div', {'id': rsrp, 'class': 'cbi-progressbar', 'title': '--'}, E('div'))),
|
]),
|
||||||
]),
|
E('tr', { 'class': 'tr cbi-rowstyle-2' }, [
|
||||||
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
E('td', { 'class': 'td left', 'width': '50%' }, [_('RSRP')]),
|
||||||
E('td', { 'class': 'td left', 'width': '50%' }, [ _('RSRQ')]),
|
E('td', { 'class': 'td left' },
|
||||||
E('td', { 'class': 'td left'}, E('div', {'id': rsrq, 'class': 'cbi-progressbar', 'title': '--'}, E('div'))),
|
E('div', { 'id': rsrpId, 'class': 'cbi-progressbar', 'title': '--' }, E('div'))
|
||||||
])
|
),
|
||||||
])
|
]),
|
||||||
|
E('tr', { 'class': 'tr cbi-rowstyle-1' }, [
|
||||||
|
E('td', { 'class': 'td left', 'width': '50%' }, [_('RSRQ')]),
|
||||||
|
E('td', { 'class': 'td left' },
|
||||||
|
E('div', { 'id': rsrqId, 'class': 'cbi-progressbar', 'title': '--' }, E('div'))
|
||||||
|
),
|
||||||
|
]),
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
)
|
]);
|
||||||
}, this.polldata);
|
}, this.polldata);
|
||||||
o.anonymous = true;
|
|
||||||
o.rmempty = true;
|
|
||||||
};
|
|
||||||
s.anonymous = true;
|
|
||||||
return m.render();
|
|
||||||
|
|
||||||
|
o.anonymous = true;
|
||||||
|
o.rmempty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.render();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSaveApply: null,
|
handleSaveApply: null,
|
||||||
handleSave: null,
|
handleSave: null,
|
||||||
handleReset: null
|
handleReset: null,
|
||||||
});
|
});
|
||||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-passwall
|
PKG_NAME:=luci-app-passwall
|
||||||
PKG_VERSION:=26.5.11
|
PKG_VERSION:=26.5.11
|
||||||
PKG_RELEASE:=127
|
PKG_RELEASE:=128
|
||||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
|
|||||||
@@ -83,103 +83,10 @@ o.rmempty = false
|
|||||||
|
|
||||||
o = s:option(Value, "interface", translate("Source Interface"))
|
o = s:option(Value, "interface", translate("Source Interface"))
|
||||||
o:value("", translate("All"))
|
o:value("", translate("All"))
|
||||||
-- Populate with actual kernel network devices instead of UCI interface names,
|
local iface = api.get_network_devices()
|
||||||
-- because the backend (nftables iifname / iptables -i) matches kernel device names.
|
for _, d in ipairs(iface) do
|
||||||
do
|
o:value(d.name, d.label)
|
||||||
local nfs = require "nixio.fs"
|
|
||||||
local _cursor = require("luci.model.uci").cursor()
|
|
||||||
local _sysnet = "/sys/class/net/"
|
|
||||||
|
|
||||||
-- Map UCI interface names to their device names and vice versa
|
|
||||||
local _iface_to_dev = {}
|
|
||||||
local _dev_to_ifaces = {}
|
|
||||||
local _iface_proto = {}
|
|
||||||
_cursor:foreach("network", "interface", function(sec)
|
|
||||||
local name = sec[".name"]
|
|
||||||
if name ~= "loopback" then
|
|
||||||
_iface_proto[name] = sec.proto
|
|
||||||
if sec.device then
|
|
||||||
_iface_to_dev[name] = sec.device
|
|
||||||
_dev_to_ifaces[sec.device] = _dev_to_ifaces[sec.device] or {}
|
|
||||||
table.insert(_dev_to_ifaces[sec.device], name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Classify device type using sysfs attributes
|
|
||||||
local function classify_sysfs(dev)
|
|
||||||
if nfs.stat(_sysnet .. dev .. "/bridge", "type") == "dir" then
|
|
||||||
return translate("Bridge")
|
|
||||||
elseif nfs.stat(_sysnet .. dev .. "/wireless", "type") == "dir" then
|
|
||||||
return translate("Wireless Adapter")
|
|
||||||
elseif dev:match("^tun") or dev:match("^tap") or dev:match("^wg") or dev:match("^ppp") then
|
|
||||||
return translate("Tunnel Interface")
|
|
||||||
else
|
|
||||||
return translate("Ethernet Adapter")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Classify offline UCI interfaces by config hints
|
|
||||||
local function classify_uci(dev_name, proto)
|
|
||||||
if dev_name and dev_name:match("^br%-") then
|
|
||||||
return translate("Bridge")
|
|
||||||
elseif proto == "wireguard" or proto == "pppoe" or proto == "pptp" or proto == "l2tp" then
|
|
||||||
return translate("Tunnel Interface")
|
|
||||||
else
|
|
||||||
return translate("Interface")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local _seen = {}
|
|
||||||
local _devices = {}
|
|
||||||
|
|
||||||
-- Active kernel devices from /sys/class/net/.
|
|
||||||
-- Skip bridge member ports (/master) and DSA master devices (/dsa) because
|
|
||||||
-- nftables iifname matches the parent bridge for routed traffic, not
|
|
||||||
-- individual member ports. Also skip internal virtual devices.
|
|
||||||
local _iter = nfs.dir(_sysnet)
|
|
||||||
if _iter then
|
|
||||||
for dev in _iter do
|
|
||||||
if dev ~= "lo"
|
|
||||||
and not dev:match("^veth")
|
|
||||||
and not dev:match("^ifb")
|
|
||||||
and not dev:match("^gre")
|
|
||||||
and not dev:match("^sit")
|
|
||||||
and not dev:match("^ip6tnl")
|
|
||||||
and not dev:match("^erspan")
|
|
||||||
and not nfs.stat(_sysnet .. dev .. "/master", "type")
|
|
||||||
and not nfs.stat(_sysnet .. dev .. "/dsa", "type")
|
|
||||||
then
|
|
||||||
local dtype = classify_sysfs(dev)
|
|
||||||
local label = dtype .. ': "' .. dev .. '"'
|
|
||||||
if _dev_to_ifaces[dev] then
|
|
||||||
label = label .. " (" .. table.concat(_dev_to_ifaces[dev], ", ") .. ")"
|
|
||||||
end
|
|
||||||
_devices[#_devices + 1] = { name = dev, label = label, sort = dtype .. ":" .. dev }
|
|
||||||
_seen[dev] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- UCI interfaces whose device does not currently exist (down tunnels, VPNs, etc.).
|
|
||||||
-- Stored by UCI name since the kernel device is not available yet.
|
|
||||||
-- Dedup by device: if two interfaces share a device, only one is shown.
|
|
||||||
for iface, dev in pairs(_iface_to_dev) do
|
|
||||||
if not _seen[dev] then
|
|
||||||
local dtype = classify_uci(dev, _iface_proto[iface])
|
|
||||||
local label = dtype .. ': "' .. iface .. '"'
|
|
||||||
-- Sort offline entries after active devices
|
|
||||||
_devices[#_devices + 1] = { name = iface, label = label, sort = "zzz:" .. iface }
|
|
||||||
_seen[dev] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(_devices, function(a, b) return a.sort < b.sort end)
|
|
||||||
for _, d in ipairs(_devices) do
|
|
||||||
o:value(d.name, d.label)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
o.validate = function(self, value, section)
|
o.validate = function(self, value, section)
|
||||||
if value == "" or value:match("^[a-zA-Z0-9][a-zA-Z0-9%.%_%-]*$") then
|
if value == "" or value:match("^[a-zA-Z0-9][a-zA-Z0-9%.%_%-]*$") then
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ o = s:option(ListValue, "chain_proxy", translate("Chain Proxy"))
|
|||||||
o:value("", translate("Close(Not use)"))
|
o:value("", translate("Close(Not use)"))
|
||||||
o:value("1", translate("Preproxy Node"))
|
o:value("1", translate("Preproxy Node"))
|
||||||
o:value("2", translate("Landing Node"))
|
o:value("2", translate("Landing Node"))
|
||||||
|
o:value("3", translate("Outbound Interface"))
|
||||||
|
|
||||||
local descrStr = "Chained proxy works only with Xray or Sing-box nodes.<br>"
|
local descrStr = "Chained proxy works only with Xray or Sing-box nodes.<br>"
|
||||||
descrStr = descrStr .. "You can only use manual or imported nodes as chained nodes."
|
descrStr = descrStr .. "You can only use manual or imported nodes as chained nodes."
|
||||||
@@ -305,6 +306,14 @@ o2.description = descrStr
|
|||||||
o2.template = appname .. "/cbi/nodes_listvalue"
|
o2.template = appname .. "/cbi/nodes_listvalue"
|
||||||
o2.group = {}
|
o2.group = {}
|
||||||
|
|
||||||
|
o3 = s:option(Value, "outbound_iface", translate("Outbound Interface"))
|
||||||
|
o3:depends({ ["chain_proxy"] = "3" })
|
||||||
|
o3:value("", translate("All"))
|
||||||
|
local iface = api.get_network_devices()
|
||||||
|
for _, d in ipairs(iface) do
|
||||||
|
o3:value(d.name, d.label)
|
||||||
|
end
|
||||||
|
|
||||||
for k, v in pairs(nodes_table) do
|
for k, v in pairs(nodes_table) do
|
||||||
if (v.type == "Xray" or v.type == "sing-box") and (not v.chain_proxy or v.chain_proxy == "") and v.add_mode ~= "2" then
|
if (v.type == "Xray" or v.type == "sing-box") and (not v.chain_proxy or v.chain_proxy == "") and v.add_mode ~= "2" then
|
||||||
o1:value(v.id, v.remark)
|
o1:value(v.id, v.remark)
|
||||||
|
|||||||
@@ -806,6 +806,7 @@ if not load_shunt_options then
|
|||||||
if not (load_iface_options or load_balancing_options) then
|
if not (load_iface_options or load_balancing_options) then
|
||||||
-- Special node cannot be use pre-proxy.
|
-- Special node cannot be use pre-proxy.
|
||||||
o:value("1", translate("Preproxy Node"))
|
o:value("1", translate("Preproxy Node"))
|
||||||
|
o:value("3", translate("Outbound Interface"))
|
||||||
end
|
end
|
||||||
o:value("2", translate("Landing Node"))
|
o:value("2", translate("Landing Node"))
|
||||||
|
|
||||||
@@ -814,6 +815,14 @@ if not load_shunt_options then
|
|||||||
o1.template = appname .. "/cbi/nodes_listvalue"
|
o1.template = appname .. "/cbi/nodes_listvalue"
|
||||||
o1.group = {}
|
o1.group = {}
|
||||||
|
|
||||||
|
o3 = s:option(Value, _n("outbound_iface"), translate("Outbound Interface"))
|
||||||
|
o3:depends({ [_n("chain_proxy")] = "3" })
|
||||||
|
o3:value("", translate("All"))
|
||||||
|
local iface = api.get_network_devices()
|
||||||
|
for _, d in ipairs(iface) do
|
||||||
|
o3:value(d.name, d.label)
|
||||||
|
end
|
||||||
|
|
||||||
o2 = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Only support a layer of proxy."))
|
o2 = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Only support a layer of proxy."))
|
||||||
o2:depends({ [_n("chain_proxy")] = "2" })
|
o2:depends({ [_n("chain_proxy")] = "2" })
|
||||||
o2.template = appname .. "/cbi/nodes_listvalue"
|
o2.template = appname .. "/cbi/nodes_listvalue"
|
||||||
|
|||||||
@@ -824,17 +824,25 @@ if not load_shunt_options then
|
|||||||
if not (load_iface_options or load_urltest_options) then
|
if not (load_iface_options or load_urltest_options) then
|
||||||
-- Special node cannot be use pre-proxy.
|
-- Special node cannot be use pre-proxy.
|
||||||
o:value("1", translate("Preproxy Node"))
|
o:value("1", translate("Preproxy Node"))
|
||||||
|
o:value("3", translate("Outbound Interface"))
|
||||||
end
|
end
|
||||||
o:value("2", translate("Landing Node"))
|
o:value("2", translate("Landing Node"))
|
||||||
o:depends({ [_n("hysteria2_realms")] = false })
|
|
||||||
|
|
||||||
o1 = s:option(ListValue, _n("preproxy_node"), translate("Preproxy Node"), translate("Only support a layer of proxy."))
|
o1 = s:option(ListValue, _n("preproxy_node"), translate("Preproxy Node"), translate("Only support a layer of proxy."))
|
||||||
o1:depends({ [_n("chain_proxy")] = "1" })
|
o1:depends({ [_n("chain_proxy")] = "1", [_n("hysteria2_realms")] = false })
|
||||||
o1.template = appname .. "/cbi/nodes_listvalue"
|
o1.template = appname .. "/cbi/nodes_listvalue"
|
||||||
o1.group = {}
|
o1.group = {}
|
||||||
|
|
||||||
|
o3 = s:option(Value, _n("outbound_iface"), translate("Outbound Interface"))
|
||||||
|
o3:depends({ [_n("chain_proxy")] = "3" })
|
||||||
|
o3:value("", translate("All"))
|
||||||
|
local iface = api.get_network_devices()
|
||||||
|
for _, d in ipairs(iface) do
|
||||||
|
o3:value(d.name, d.label)
|
||||||
|
end
|
||||||
|
|
||||||
o2 = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Only support a layer of proxy."))
|
o2 = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Only support a layer of proxy."))
|
||||||
o2:depends({ [_n("chain_proxy")] = "2" })
|
o2:depends({ [_n("chain_proxy")] = "2", [_n("hysteria2_realms")] = false })
|
||||||
o2.template = appname .. "/cbi/nodes_listvalue"
|
o2.template = appname .. "/cbi/nodes_listvalue"
|
||||||
o2.group = {}
|
o2.group = {}
|
||||||
|
|
||||||
|
|||||||
@@ -1626,3 +1626,83 @@ function parse_realm_uri(uri)
|
|||||||
end
|
end
|
||||||
return realm
|
return realm
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function get_network_devices()
|
||||||
|
local _sysnet = "/sys/class/net/"
|
||||||
|
-- Map UCI interface names to their device names and vice versa
|
||||||
|
local _iface_to_dev = {}
|
||||||
|
local _dev_to_ifaces = {}
|
||||||
|
local _iface_proto = {}
|
||||||
|
uci:foreach("network", "interface", function(sec)
|
||||||
|
local name = sec[".name"]
|
||||||
|
if name ~= "loopback" then
|
||||||
|
_iface_proto[name] = sec.proto
|
||||||
|
if sec.device then
|
||||||
|
_iface_to_dev[name] = sec.device
|
||||||
|
_dev_to_ifaces[sec.device] = _dev_to_ifaces[sec.device] or {}
|
||||||
|
table.insert(_dev_to_ifaces[sec.device], name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
-- Classify device type using sysfs attributes
|
||||||
|
local function classify_sysfs(dev)
|
||||||
|
if fs.stat(_sysnet .. dev .. "/bridge", "type") == "dir" then
|
||||||
|
return i18n.translate("Bridge")
|
||||||
|
elseif fs.stat(_sysnet .. dev .. "/wireless", "type") == "dir" then
|
||||||
|
return i18n.translate("Wireless Adapter")
|
||||||
|
elseif dev:match("^tun") or dev:match("^tap") or dev:match("^wg") or dev:match("^ppp") then
|
||||||
|
return i18n.translate("Tunnel Interface")
|
||||||
|
else
|
||||||
|
return i18n.translate("Ethernet Adapter")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Classify offline UCI interfaces by config hints
|
||||||
|
local function classify_uci(dev_name, proto)
|
||||||
|
if dev_name and dev_name:match("^br%-") then
|
||||||
|
return i18n.translate("Bridge")
|
||||||
|
elseif proto == "wireguard" or proto == "pppoe" or proto == "pptp" or proto == "l2tp" then
|
||||||
|
return i18n.translate("Tunnel Interface")
|
||||||
|
else
|
||||||
|
return i18n.translate("Interface")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local _seen = {}
|
||||||
|
local _devices = {}
|
||||||
|
-- Active kernel devices from /sys/class/net/
|
||||||
|
-- Skip bridge member ports (/master) and DSA master devices (/dsa)
|
||||||
|
local _iter = fs.dir(_sysnet)
|
||||||
|
if _iter then
|
||||||
|
for dev in _iter do
|
||||||
|
if dev ~= "lo"
|
||||||
|
and not dev:match("^veth")
|
||||||
|
and not dev:match("^ifb")
|
||||||
|
and not dev:match("^gre")
|
||||||
|
and not dev:match("^sit")
|
||||||
|
and not dev:match("^ip6tnl")
|
||||||
|
and not dev:match("^erspan")
|
||||||
|
and not fs.stat(_sysnet .. dev .. "/master", "type")
|
||||||
|
and not fs.stat(_sysnet .. dev .. "/dsa", "type")
|
||||||
|
then
|
||||||
|
local dtype = classify_sysfs(dev)
|
||||||
|
local label = dtype .. ': "' .. dev .. '"'
|
||||||
|
if _dev_to_ifaces[dev] then
|
||||||
|
label = label .. " (" .. table.concat(_dev_to_ifaces[dev], ", ") .. ")"
|
||||||
|
end
|
||||||
|
_devices[#_devices + 1] = { name = dev, label = label, sort = dtype .. ":" .. dev }
|
||||||
|
_seen[dev] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- UCI interfaces whose device does not currently exist
|
||||||
|
for iface, dev in pairs(_iface_to_dev) do
|
||||||
|
if not _seen[dev] then
|
||||||
|
local dtype = classify_uci(dev, _iface_proto[iface])
|
||||||
|
local label = dtype .. ': "' .. iface .. '"'
|
||||||
|
_devices[#_devices + 1] = { name = iface, label = label, sort = "zzz:" .. iface }
|
||||||
|
_seen[dev] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(_devices, function(a, b) return a.sort < b.sort end)
|
||||||
|
return _devices
|
||||||
|
end
|
||||||
@@ -1438,6 +1438,10 @@ function gen_config(var)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if node.chain_proxy == "3" and node.outbound_iface then
|
||||||
|
outbound.detour = nil
|
||||||
|
outbound.bind_interface = node.outbound_iface
|
||||||
|
end
|
||||||
return default_outTag, last_insert_outbound
|
return default_outTag, last_insert_outbound
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2006,6 +2010,10 @@ function gen_config(var)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
route.default_domain_resolver = {
|
||||||
|
server = "direct"
|
||||||
|
}
|
||||||
|
|
||||||
if not dns.rules then dns.rules = {} end
|
if not dns.rules then dns.rules = {} end
|
||||||
|
|
||||||
for i, v in pairs(GLOBAL.DNS_SERVER) do
|
for i, v in pairs(GLOBAL.DNS_SERVER) do
|
||||||
@@ -2102,16 +2110,12 @@ function gen_config(var)
|
|||||||
type = "direct",
|
type = "direct",
|
||||||
tag = "direct",
|
tag = "direct",
|
||||||
routing_mark = 255,
|
routing_mark = 255,
|
||||||
domain_resolver = {
|
|
||||||
server = "direct",
|
|
||||||
strategy = "prefer_ipv6"
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
for index, value in ipairs(config.outbounds) do
|
for index, value in ipairs(config.outbounds) do
|
||||||
if not value["_flag_proxy_tag"] and not value.detour and value["_id"] and value.server and (value.server_port or value.server_ports) and not no_run then
|
if not value["_flag_proxy_tag"] and not value.detour and value["_id"] and value.server and (value.server_port or value.server_ports) and not no_run then
|
||||||
sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list"))
|
sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list"))
|
||||||
end
|
end
|
||||||
if not value.detour and value.server then
|
if not value.detour and not value.bind_interface and value.server then
|
||||||
value.detour = "direct"
|
value.detour = "direct"
|
||||||
end
|
end
|
||||||
if (value.server and not api.datatypes.hostname(value.server)) and not value.realm then
|
if (value.server and not api.datatypes.hostname(value.server)) and not value.realm then
|
||||||
|
|||||||
@@ -1206,6 +1206,11 @@ function gen_config(var)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if node.chain_proxy == "3" and node.outbound_iface then
|
||||||
|
if outbound.streamSettings and outbound.streamSettings.sockopt then
|
||||||
|
outbound.streamSettings.sockopt.interface = node.outbound_iface
|
||||||
|
end
|
||||||
|
end
|
||||||
return default_outTag, last_insert_outbound
|
return default_outTag, last_insert_outbound
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -874,6 +874,8 @@ table td, .table .td {
|
|||||||
p = "URLTest";
|
p = "URLTest";
|
||||||
} else if (protocol == "_shunt") {
|
} else if (protocol == "_shunt") {
|
||||||
p = "<%:Shunt%>";
|
p = "<%:Shunt%>";
|
||||||
|
} else if (protocol == "_iface") {
|
||||||
|
p = "<%:Interface%>";
|
||||||
} else if (protocol == "vmess") {
|
} else if (protocol == "vmess") {
|
||||||
p = "VMess";
|
p = "VMess";
|
||||||
} else if (protocol == "vless") {
|
} else if (protocol == "vless") {
|
||||||
|
|||||||
@@ -1966,6 +1966,9 @@ msgstr "仅支持 Xray 或 Sing-box 类型节点。"
|
|||||||
msgid "Chain Proxy"
|
msgid "Chain Proxy"
|
||||||
msgstr "链式代理"
|
msgstr "链式代理"
|
||||||
|
|
||||||
|
msgid "Outbound Interface"
|
||||||
|
msgstr "出站接口"
|
||||||
|
|
||||||
msgid "Landing Node"
|
msgid "Landing Node"
|
||||||
msgstr "落地节点"
|
msgstr "落地节点"
|
||||||
|
|
||||||
|
|||||||
@@ -1883,7 +1883,7 @@ local function update_node(manual)
|
|||||||
local list = v["list"]
|
local list = v["list"]
|
||||||
local sub_cfg = v["sub_cfg"]
|
local sub_cfg = v["sub_cfg"]
|
||||||
local domain_resolver, domain_resolver_dns, domain_resolver_dns_https, domain_strategy
|
local domain_resolver, domain_resolver_dns, domain_resolver_dns_https, domain_strategy
|
||||||
local preproxy_node_group, to_node_group, chain_node_type = "", "", ""
|
local preproxy_node_group, to_node_group, outbound_iface_group, chain_node_type = "", "", "", ""
|
||||||
-- Subscription Group Chain Agent
|
-- Subscription Group Chain Agent
|
||||||
local function valid_chain_node(node)
|
local function valid_chain_node(node)
|
||||||
if not node then return "" end
|
if not node then return "" end
|
||||||
@@ -1904,6 +1904,8 @@ local function update_node(manual)
|
|||||||
domain_strategy = (sub_cfg.domain_strategy and map[sub_cfg.domain_strategy]) and sub_cfg.domain_strategy or nil
|
domain_strategy = (sub_cfg.domain_strategy and map[sub_cfg.domain_strategy]) and sub_cfg.domain_strategy or nil
|
||||||
preproxy_node_group = (sub_cfg.chain_proxy == "1") and valid_chain_node(sub_cfg.preproxy_node) or ""
|
preproxy_node_group = (sub_cfg.chain_proxy == "1") and valid_chain_node(sub_cfg.preproxy_node) or ""
|
||||||
to_node_group = (sub_cfg.chain_proxy == "2") and valid_chain_node(sub_cfg.to_node) or ""
|
to_node_group = (sub_cfg.chain_proxy == "2") and valid_chain_node(sub_cfg.to_node) or ""
|
||||||
|
outbound_iface_group = (sub_cfg.chain_proxy == "3") and sub_cfg.outbound_iface or ""
|
||||||
|
chain_node_type = (outbound_iface_group ~= "") and "iface" or chain_node_type
|
||||||
end
|
end
|
||||||
for _, vv in ipairs(list) do
|
for _, vv in ipairs(list) do
|
||||||
local cfgid = uci:section(appname, "nodes", api.gen_short_uuid())
|
local cfgid = uci:section(appname, "nodes", api.gen_short_uuid())
|
||||||
@@ -1941,6 +1943,9 @@ local function update_node(manual)
|
|||||||
elseif to_node_group ~= "" then
|
elseif to_node_group ~= "" then
|
||||||
uci:set(appname, cfgid, "chain_proxy", "2")
|
uci:set(appname, cfgid, "chain_proxy", "2")
|
||||||
uci:set(appname, cfgid, "to_node", to_node_group)
|
uci:set(appname, cfgid, "to_node", to_node_group)
|
||||||
|
elseif outbound_iface_group ~= "" then
|
||||||
|
uci:set(appname, cfgid, "chain_proxy", "3")
|
||||||
|
uci:set(appname, cfgid, "outbound_iface", outbound_iface_group)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-xray
|
PKG_NAME:=luci-app-xray
|
||||||
PKG_VERSION:=3.7.1
|
PKG_VERSION:=3.7.1
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
PKG_LICENSE:=MPLv2
|
PKG_LICENSE:=MPLv2
|
||||||
PKG_LICENSE_FILES:=LICENSE
|
PKG_LICENSE_FILES:=LICENSE
|
||||||
|
|||||||
@@ -616,3 +616,21 @@
|
|||||||
chain tp_spec_lan_fw {
|
chain tp_spec_lan_fw {
|
||||||
{{ counter }} mark set 0x000000fb accept
|
{{ counter }} mark set 0x000000fb accept
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{% if (length(fakedns) > 0): %}
|
||||||
|
{% if (general.fakedns_ping_response_prerouting == "1"): %}
|
||||||
|
chain xray_fakedns_ping_prerouting {
|
||||||
|
type nat hook prerouting priority dstnat {{ firewall_priority }}; policy accept;
|
||||||
|
ip daddr {{ general.pool_v4 || "198.18.0.0/15" }} icmp type echo-request {{ counter }} redirect comment "Xray FakeDNS IPv4 Pool ICMP"
|
||||||
|
ip6 daddr {{ general.pool_v6 || "fc00::/18" }} icmpv6 type echo-request {{ counter }} redirect comment "Xray FakeDNS IPv6 Pool ICMPv6"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if (general.fakedns_ping_response_output == "1"): %}
|
||||||
|
chain xray_fakedns_ping_output {
|
||||||
|
type nat hook output priority dstnat {{ firewall_priority }}; policy accept;
|
||||||
|
ip daddr {{ general.pool_v4 || "198.18.0.0/15" }} icmp type echo-request {{ counter }} redirect comment "Xray FakeDNS IPv4 Pool ICMP"
|
||||||
|
ip6 daddr {{ general.pool_v6 || "fc00::/18" }} icmpv6 type echo-request {{ counter }} redirect comment "Xray FakeDNS IPv6 Pool ICMPv6"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
@@ -63,6 +63,9 @@ return view.extend({
|
|||||||
let ttl_override_bypass_ports = s.taboption('firewall', form.DynamicList, 'ttl_override_bypass_ports', _('Ports to bypass TTL override'), _("Do not override TTL for packets with these destination TCP / UDP ports."));
|
let ttl_override_bypass_ports = s.taboption('firewall', form.DynamicList, 'ttl_override_bypass_ports', _('Ports to bypass TTL override'), _("Do not override TTL for packets with these destination TCP / UDP ports."));
|
||||||
ttl_override_bypass_ports.datatype = 'port';
|
ttl_override_bypass_ports.datatype = 'port';
|
||||||
|
|
||||||
|
s.taboption('firewall', form.Flag, 'fakedns_ping_response_prerouting', _('FakeDNS Ping (LAN)'), _('Respond to ICMP echo requests from LAN clients for FakeDNS address pools.'));
|
||||||
|
s.taboption('firewall', form.Flag, 'fakedns_ping_response_output', _('FakeDNS Ping (Local)'), _('Respond to ICMP echo requests from this router for FakeDNS address pools.'));
|
||||||
|
|
||||||
s.tab("sniffing", _("Sniffing"));
|
s.tab("sniffing", _("Sniffing"));
|
||||||
|
|
||||||
s.taboption('sniffing', form.Flag, 'tproxy_sniffing', _('Enable Sniffing'), _('Route requests according to domain settings in "DNS Settings" tab in core settings. Deprecated; use FakeDNS instead.'));
|
s.taboption('sniffing', form.Flag, 'tproxy_sniffing', _('Enable Sniffing'), _('Route requests according to domain settings in "DNS Settings" tab in core settings. Deprecated; use FakeDNS instead.'));
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=mihomo-alpha
|
PKG_NAME:=mihomo-alpha
|
||||||
PKG_VERSION:=2026.05.16
|
PKG_VERSION:=2026.05.19
|
||||||
PKG_RELEASE:=8
|
PKG_RELEASE:=9
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
|
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
|
||||||
PKG_SOURCE_VERSION:=e9e23c4e98c34b158953f1414b317f25f3e3aa1e
|
PKG_SOURCE_VERSION:=a52a27ed563970b79e50ced79f15100abf0bcc85
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
PKG_LICENSE:=GPL3.0+
|
PKG_LICENSE:=GPL3.0+
|
||||||
@@ -18,7 +18,7 @@ PKG_BUILD_DEPENDS:=golang/host
|
|||||||
PKG_BUILD_PARALLEL:=1
|
PKG_BUILD_PARALLEL:=1
|
||||||
PKG_BUILD_FLAGS:=no-mips16
|
PKG_BUILD_FLAGS:=no-mips16
|
||||||
|
|
||||||
PKG_BUILD_VERSION:=alpha-e9e23c4e
|
PKG_BUILD_VERSION:=alpha-a52a27ed
|
||||||
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
|
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
|
||||||
|
|
||||||
GO_PKG:=github.com/metacubex/mihomo
|
GO_PKG:=github.com/metacubex/mihomo
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=modeminfo
|
PKG_NAME:=modeminfo
|
||||||
PKG_VERSION:=0.4.6
|
PKG_VERSION:=0.4.6
|
||||||
PKG_RELEASE:=3
|
PKG_RELEASE:=4
|
||||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ function generic_data(){
|
|||||||
modes["6"] = "HSPA"
|
modes["6"] = "HSPA"
|
||||||
modes["7"] = modes["13"] = "LTE"
|
modes["7"] = modes["13"] = "LTE"
|
||||||
|
|
||||||
prefix = substr($4, 1, 1)
|
prefix = substr($4, 1, 2)
|
||||||
print (prefix in modes ? modes[prefix] : "--")
|
print (prefix in modes ? modes[prefix] : "--")
|
||||||
}')
|
}')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user