diff --git a/luci-app-netspeedtest/Makefile b/luci-app-netspeedtest/Makefile index 27983364f..e1cf5b9c5 100644 --- a/luci-app-netspeedtest/Makefile +++ b/luci-app-netspeedtest/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-3.0-only # # Copyright (C) 2021-2025 sirpdboy -# https://github.com/sirpdboy/luci-app-ddns-go +# https://github.com/sirpdboy/luci-app-netspeedtest # This is free software, licensed under the Apache License, Version 2.0 . # @@ -9,11 +9,11 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-netspeedtest -PKG_VERSION:=5.0.2 -PKG_RELEASE:=20250513 +PKG_VERSION:=5.0.3 +PKG_RELEASE:=20250515 LUCI_TITLE:=LuCI Support for netspeedtest -LUCI_DEPENDS:=+speedtest-cli +homebox +iperf3-ssl +LUCI_DEPENDS:=+speedtest-cli +homebox $(if $(find_package iperf3-ssl),+iperf3-ssl,+iperf3) LUCI_PKGARCH:=all PKG_MAINTAINER:=sirpdboy @@ -23,9 +23,17 @@ define Package/$(PKG_NAME)/conffiles endef define Package/$(PKG_NAME)/postinst +#!/bin/sh +if [ -z "$${IPKG_INSTROOT}" ]; then + if ! uci show netspeedtest.config >/dev/null 2>&1; then + uci set netspeedtest.config=netspeedtest + uci set netspeedtest.config.enabled='1' + uci commit netspeedtest + fi +fi +exit 0 endef - include $(TOPDIR)/feeds/luci/luci.mk diff --git a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/homebox.js b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/homebox.js index 5cf7d5ae9..a942661ef 100644 --- a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/homebox.js +++ b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/homebox.js @@ -6,7 +6,6 @@ 'require uci'; 'require form'; 'require poll'; - return view.extend({ render: function() { var state = { @@ -33,12 +32,28 @@ return view.extend({ style: 'border:none;width: 100%; min-height: 80vh; border: none; border-radius: 3px;overflow:hidden !important;' }); - function checkProcess() { - return fs.exec('/bin/pidof', ['homebox']).then(res => ({ - running: res.code === 0, - pid: res.code === 0 ? res.stdout.trim() : null - })); +async function checkProcess() { + try { + // 尝试使用pgrep + const res = await fs.exec('/usr/bin/pgrep', ['homebox']); + return { + running: res.code === 0, + pid: res.stdout.trim() || null + }; + } catch (err) { + // 回退到ps方法 + try { + const psRes = await fs.exec('/bin/ps', ['-w', '-C', 'homebox', '-o', 'pid=']); + const pid = psRes.stdout.trim(); + return { + running: pid !== '', + pid: pid || null + }; + } catch (err) { + return { running: false, pid: null }; } + } +} function controlService(action) { var command = action === 'start' diff --git a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/iperf3.js b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/iperf3.js index 56c68a761..0c204b097 100644 --- a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/iperf3.js +++ b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/iperf3.js @@ -14,16 +14,34 @@ var state = { const logPath = '/tmp/netspeedtest.log'; -function checkProcess() { - return fs.exec('/bin/pidof', ['iperf3']).then(res => ({ - running: res.code === 0, - pid: res.code === 0 ? res.stdout.trim() : null - })); +async function checkProcess() { + try { + // 尝试使用pgrep + const res = await fs.exec('/usr/bin/pgrep', ['iperf3']); + return { + running: res.code === 0, + pid: res.stdout.trim() || null + }; + } catch (err) { + // 回退到ps方法 + try { + const psRes = await fs.exec('/bin/ps', ['-w', '-C', 'iperf3', '-o', 'pid=']); + const pid = psRes.stdout.trim(); + return { + running: pid !== '', + pid: pid || null + }; + } catch (err) { + return { running: false, pid: null }; + } + } } - function pollLog(textarea) { - return fs.read(logPath).then(res => { - const cleanedLog = res ? res.trim() + return fs.exec('/bin/cat', [logPath]) + .then(res => { + if (res.code !== 0) throw new Error(res.stderr); + + const cleanedLog = res.stdout ? res.stdout.trim() .replace(/\u001b\[[0-9;]*m/g, '') .split('\n') .slice(-50) @@ -40,18 +58,24 @@ function pollLog(textarea) { }); } + function controlService(action) { const commands = { - start: `/usr/bin/iperf3 -s -D -p 5201 --logfile ${logPath}`, - stop: '/usr/bin/killall iperf3' + start: `/usr/bin/iperf3 -s -D -p 5201 --logfile ${logPath} 2>&1`, + stop: '/usr/bin/killall -q iperf3' }; return (action === 'start' - ? fs.exec('/bin/sh', ['-c', `touch ${logPath} && chmod 644 ${logPath}`]) + ? fs.exec('/bin/sh', ['-c', `mkdir -p /tmp/netspeedtest && touch ${logPath} && chmod 644 ${logPath}`]) : Promise.resolve() - ).then(() => fs.exec('/bin/sh', ['-c', commands[action]])); + ).then(() => fs.exec('/bin/sh', ['-c', commands[action]])) + .catch(err => { + console.error('Service control error:', err); + throw err; + }); } + return view.extend({ // handleSaveApply: null, // handleSave: null, diff --git a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/speedtest.js b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/speedtest.js index 2647c0cd4..57b25f7e9 100644 --- a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/speedtest.js +++ b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/speedtest.js @@ -15,118 +15,134 @@ var SpeedtestCli = '/usr/bin/speedtest'; var SpeedtestScript = '/usr/lib/netspeedtest/speedtest'; return view.extend({ -// handleSaveApply: null, -// handleSave: null, -// handleReset: null, - load() { + load() { return Promise.all([ L.resolveDefault(fs.stat(SpeedtestCli), {}), - L.resolveDefault(fs.read(ResultFile), null), - L.resolveDefault(fs.stat(ResultFile), {}), - uci.load('netspeedtest') + L.resolveDefault(fs.read(ResultFile), null), + L.resolveDefault(fs.stat(ResultFile), {}), + uci.load('netspeedtest') ]); }, -poll_status(nodes, res) { - var has_ookla = res[0].path, - result_content = res[1] ? res[1].trim().split("\n") : []; - var ookla_stat = nodes.querySelector('#ookla_status'), - result_stat = nodes.querySelector('#speedtest_result'); + poll_status(nodes, res) { + var has_ookla = res[0].path, + result_content = res[1] ? res[1].trim().split("\n") : []; + var ookla_stat = nodes.querySelector('#ookla_status'), + result_stat = nodes.querySelector('#speedtest_result'), + start_btn = nodes.querySelector('.cbi-button-apply'); - // 获取版本号(新增部分) - var version_info = ''; - if (has_ookla) { - fs.exec_direct('/usr/bin/speedtest', ['--version']) - .then(function(res) { - if (res.stdout) { - var version_match = res.stdout.match(/Speedtest (\d+\.\d+\.\d+)/); - if (version_match) { - version_info = ' ver:' + version_match[1]; - } - } - // 更新状态显示(包含版本号) - ookla_stat.style.color = 'green'; - dom.content(ookla_stat, [_(has_ookla ? 'Installed' + version_info : 'Not Installed')]); - }) - .catch(function() { - // 如果获取版本失败,仍显示基本状态 - ookla_stat.style.color = has_ookla ? 'green' : 'red'; - dom.content(ookla_stat, [_(has_ookla ? 'Installed' : 'Not Installed')]); - }); - } else { - // 未安装时的显示保持不变 - ookla_stat.style.color = 'red'; - dom.content(ookla_stat, [_('Not Installed')]); - } - if (result_content.length) { - if (result_content[0] == 'Testing') { - result_stat.innerHTML = "" + - " " + - _('SpeedTesting in progress...') + - ""; - } else if (result_content[0].match(/https?:\S+/)) { - result_stat.innerHTML = "
"; - } else if (result_content[0] == 'Test failed') { - result_stat.innerHTML = "" + - _('Test failed.') + ""; + // 更新测试按钮状态 + if (start_btn) { + if (result_content.length && result_content[0] == 'Testing' && + (Date.now() - (nodes.result_mtime || 0)) < TestTimeout) { + start_btn.disabled = true; + } else { + start_btn.disabled = false; + } } - } else { - result_stat.innerHTML = "" + - _('No test results yet.') + ""; - } -}, - render(res) { - var has_ookla = res[0].path, - result_content = res[1] ? res[1].trim().split("\n") : [], - result_mtime = res[2] ? res[2].mtime * 1000 : 0, - date = new Date(); + // 获取版本号 + var version_info = ''; + if (has_ookla) { + fs.exec(SpeedtestCli, ['--version']) + .then(function(res) { + if (res.stdout) { + // 匹配 "Speedtest by Ookla 1.2.0.84" 这样的格式 + var version_match = res.stdout.match(/Speedtest by Ookla (\d+\.\d+\.\d+\.\d+)/) || + res.stdout.match(/Speedtest (\d+\.\d+\.\d+\.\d+)/); + if (version_match) { + version_info = ' ver:' + version_match[1]; + } else { + // 如果上面没匹配到,尝试匹配更简单的版本号格式 + version_match = res.stdout.match(/(\d+\.\d+\.\d+)/); + if (version_match) { + version_info = ' ver:' + version_match[1]; + } + } + } + // 更新状态显示(包含版本号) + ookla_stat.style.color = 'green'; + dom.content(ookla_stat, [_(has_ookla ? 'Installed' + version_info : 'Not Installed')]); + }) + .catch(function() { + // 如果获取版本失败,仍显示基本状态 + ookla_stat.style.color = has_ookla ? 'green' : 'red'; + dom.content(ookla_stat, [_(has_ookla ? 'Installed' : 'Not Installed')]); + }); + } else { + ookla_stat.style.color = 'red'; + dom.content(ookla_stat, [_('Not Installed')]); + } - var m, s, o; + if (result_content.length) { + if (result_content[0] == 'Testing') { + result_stat.innerHTML = "" + + " " + + _('SpeedTesting in progress...') + + ""; + } else if (result_content[0].match(/https?:\S+/)) { + result_stat.innerHTML = "
"; + } else if (result_content[0] == 'Test failed') { + result_stat.innerHTML = "" + + _('Test failed.') + ""; + } + } else { + result_stat.innerHTML = "" + + _('No test results yet.') + ""; + } + }, + + render(res) { + var has_ookla = res[0].path, + result_content = res[1] ? res[1].trim().split("\n") : [], + result_mtime = res[2] ? res[2].mtime * 1000 : 0; + + var m, s, o; m = new form.Map('netspeedtest', _('Wan Ookla SpeedTest')); // Result display section s = m.section(form.TypedSection, '_result'); s.anonymous = true; - s.render = function (section_id) { - if (result_content.length) { - if (result_content[0] == 'Testing') { - return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:yellow;font-weight:bold' }, [ - E('img', { 'src': L.resource(['icons/loading.gif']), 'height': '20', 'style': 'vertical-align:middle' }, []), - _('Testing in progress...') - ]) ]) - }; - if (result_content[0].match(/https?:\S+/)) { - return E('div', { 'id': 'speedtest_result' }, [ E('div', { 'style': 'max-width:500px' }, [ - E('a', { 'href': result_content[0], 'target': '_blank' }, [ - E('img', { 'src': result_content[0] + '.png', 'style': 'max-width:100%;max-height:100%;vertical-align:middle' }, []) - ]) ]) ]) - }; - if (result_content[0] == 'Test failed') { - return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:red;font-weight:bold' }, [ _('Test failed.') ]) ]) - } - } else { - return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:red;font-weight:bold;display:none' }, [ _('No result.') ]) ]) - } - }; + s.render = function (section_id) { + if (result_content.length) { + if (result_content[0] == 'Testing') { + return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:yellow;font-weight:bold' }, [ + E('img', { 'src': L.resource(['icons/loading.gif']), 'height': '20', 'style': 'vertical-align:middle' }, []), + _('Testing in progress...') + ]) ]) + }; + if (result_content[0].match(/https?:\S+/)) { + return E('div', { 'id': 'speedtest_result' }, [ E('div', { 'style': 'max-width:500px' }, [ + E('a', { 'href': result_content[0], 'target': '_blank' }, [ + E('img', { 'src': result_content[0] + '.png', 'style': 'max-width:100%;max-height:100%;vertical-align:middle' }, []) + ]) ]) ]) + }; + if (result_content[0] == 'Test failed') { + return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:red;font-weight:bold' }, [ _('Test failed.') ]) ]) + } + } else { + return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:red;font-weight:bold;display:none' }, [ _('No result.') ]) ]) + } + }; // Configuration section s = m.section(form.NamedSection, 'config', 'netspeedtest'); - s.anonymous = true; + s.anonymous = true; // Start test button o = s.option(form.Button, '_start', _('Start Ookla SpeedTest')); o.inputtitle = _('Click to execute'); o.inputstyle = 'apply'; - if (result_content.length && result_content[0] == 'Testing' && (date.getTime() - result_mtime) < TestTimeout) - o.readonly = true; - o.onclick = function() { - return fs.exec_direct(SpeedtestScript) - .then(function(res) { return window.location = window.location.href.split('#')[0] }) - .catch(function(e) { ui.addNotification(null, E('p', e.message), 'error') }); - }; + if (result_content.length && result_content[0] == 'Testing' && (Date.now() - result_mtime) < TestTimeout) { + o.readonly = true; + } + o.onclick = function() { + return fs.exec_direct(SpeedtestScript) + .then(function(res) { return window.location = window.location.href.split('#')[0] }) + .catch(function(e) { ui.addNotification(null, E('p', e.message), 'error') }); + }; o = s.option(form.DummyValue, '_ookla_status', _('Ookla® SpeedTest-CLI')); o.rawhtml = true; @@ -135,15 +151,20 @@ poll_status(nodes, res) { id: 'ookla_status', style: has_ookla ? 'color:green' : 'color:red' }, _(has_ookla ? 'Installed' : 'Not Installed')); - }; + }; - return m.render() - .then(L.bind(function(m, nodes) { + return m.render() + .then(L.bind(function(m, nodes) { + nodes.result_mtime = result_mtime; // 保存结果文件修改时间 poll.add(L.bind(function () { return Promise.all([ - L.resolveDefault(fs.stat(SpeedtestCli), {}), - L.resolveDefault(fs.read(ResultFile), null) - ]).then(L.bind(this.poll_status, this, nodes)); + L.resolveDefault(fs.stat(SpeedtestCli), {}), + L.resolveDefault(fs.read(ResultFile), null), + L.resolveDefault(fs.stat(ResultFile), {}) + ]).then(L.bind(function(res) { + nodes.result_mtime = res[2] ? res[2].mtime * 1000 : 0; + this.poll_status(nodes, res); + }, this)); }, this), 5); return nodes; }, this, m)); diff --git a/luci-app-netspeedtest/root/etc/uci-defaults/99_netspeedtest b/luci-app-netspeedtest/root/etc/uci-defaults/99_netspeedtest index 680dc7961..e37a2d24a 100644 --- a/luci-app-netspeedtest/root/etc/uci-defaults/99_netspeedtest +++ b/luci-app-netspeedtest/root/etc/uci-defaults/99_netspeedtest @@ -1,6 +1,9 @@ #!/bin/sh +[ ! -s "/etc/config/netspeedtest" ] && { touch /etc/config/netspeedtest uci -q batch <<-EOF >/dev/null set netspeedtest.config=netspeedtest commit netspeedtest EOF + +} \ No newline at end of file diff --git a/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/luci-app-passwall/luasrc/passwall/util_sing-box.lua index e204a6238..c0dc67869 100644 --- a/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -459,6 +459,9 @@ function gen_outbound(flag, node, tag, proxy_table) if node.protocol == "anytls" then protocol_table = { password = (node.password and node.password ~= "") and node.password or "", + idle_session_check_interval = "30s", + idle_session_timeout = "30s", + min_idle_session = 5, tls = tls } end