update 2025-05-13 20:45:58

This commit is contained in:
kenzok8 2025-05-13 20:45:58 +08:00
parent 17918091de
commit 59e8d3b64a
49 changed files with 1572 additions and 3240 deletions

View File

@ -7,8 +7,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-ddns-go
PKG_VERSION:=1.5.0
PKG_RELEASE:=20250511
PKG_VERSION:=1.5.1
PKG_RELEASE:=20250513
PKG_MAINTAINER:=sirpdboy <herboy2008@gmail.com>
PKG_CONFIG_DEPENDS:=

View File

@ -9,9 +9,9 @@
'require poll';
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
//handleSaveApply: null,
//handleSave: null,
//handleReset: null,
load: function() {
return uci.load('ddns-go');
},

View File

@ -109,9 +109,9 @@ return view.extend({
]);
},
}
handleSaveApply: null,
handleSave: null,
handleReset: null
//handleSaveApply: null,
//handleSave: null,
//handleReset: null
});

View File

@ -113,6 +113,9 @@ start_service() {
# Client
if [ "$client_enabled" = "1" ]; then
if [ -z "$1" -o "$1" = "mihomo-c" ]; then
# Env variables
export SAFE_PATHS="$RUN_DIR/"
# Generate/Validate client config
ucode -S "$SDL_DIR/generate_client.uc" 2>>"$LOG_PATH" | yq -Poy | yq \
'.sniffer["force-domain"][] style="double"

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 muink
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,20 +1,21 @@
# Copyright (C) 2020-2021 sirpdboy <herboy2008@gmail.com>
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021-2025 sirpdboy <herboy2008@gmail.com>
# https://github.com/sirpdboy/luci-app-ddns-go
# This is free software, licensed under the Apache License, Version 2.0 .
#
# This is free software, licensed under the GNU General Public License v3.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-netspeedtest
PKG_VERSION:=2.3.1
PKG_RELEASE:=20250302
PKG_VERSION:=5.0.1
PKG_RELEASE:=20250512
LUCI_TITLE:=LuCI Support for netspeedtest
LUCI_DEPENDS:=+python3 +iperf3-ssl +homebox
LUCI_DEPENDS:=+speedtest-cli +homebox +iperf3-ssl
LUCI_PKGARCH:=all
PKG_MAINTAINER:=<https://github.com/sirpdboy/netspeedtest>
PKG_MAINTAINER:=sirpdboy <herboy2008@gmail.com>
define Package/$(PKG_NAME)/conffiles

View File

@ -0,0 +1,127 @@
/* Copyright (C) 2021-2025 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-netspeedtest */
'use strict';
'require view';
'require fs';
'require ui';
'require uci';
'require form';
'require poll';
return view.extend({
render: function() {
var state = {
running: false,
port: 3300
};
var container = E('div');
var statusSection = E('div', { 'class': 'cbi-section' });
var statusIcon = E('span', { 'style': 'margin-right: 5px;' });
var statusText = E('span');
var toggleBtn = E('button', { 'class': 'btn cbi-button' });
var statusMessage = E('div', { style: 'text-align: center; padding: 2em;' }, [
E('img', {
src: 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMjQiIGhlaWdodD0iMTAyNCIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCI+PHBhdGggZmlsbD0iI2RmMDAwMCIgZD0iTTk0Mi40MjEgMjM0LjYyNGw4MC44MTEtODAuODExLTE1My4wNDUtMTUzLjA0NS04MC44MTEgODAuODExYy03OS45NTctNTEuNjI3LTE3NS4xNDctODEuNTc5LTI3Ny4zNzYtODEuNTc5LTI4Mi43NTIgMC01MTIgMjI5LjI0OC01MTIgNTEyIDAgMTAyLjIyOSAyOS45NTIgMTk3LjQxOSA4MS41NzkgMjc3LjM3NmwtODAuODExIDgwLjgxMSAxNTMuMDQ1IDE1My4wNDUgODAuODExLTgwLjgxMWM3OS45NTcgNTEuNjI3IDE3NS4xNDcgODEuNTc5IDI3Ny4zNzYgODEuNTc5IDI4Mi43NTIgMCA1MTItMjI5LjI0OCA1MTItNTEyIDAtMTAyLjIyOS0yOS45NTItMTk3LjQxOS04MS41NzktMjc3LjM3NnpNMTk0Ljk0NCA1MTJjMC0xNzUuMTA0IDE0MS45NTItMzE3LjA1NiAzMTcuMDU2LTMxNy4wNTYgNDggMCA5My40ODMgMTAuNjY3IDEzNC4yMjkgMjkuNzgxbC00MjEuNTQ3IDQyMS41NDdjLTE5LjA3Mi00MC43ODktMjkuNzM5LTg2LjI3Mi0yOS43MzktMTM0LjI3MnpNNTEyIDgyOS4wNTZjLTQ4IDAtOTMuNDgzLTEwLjY2Ny0xMzQuMjI5LTI5Ljc4MWw0MjEuNTQ3LTQyMS41NDdjMTkuMDcyIDQwLjc4OSAyOS43ODEgODYuMjcyIDI5Ljc4MSAxMzQuMjI5LTAuMDQzIDE3NS4xNDctMTQxLjk5NSAzMTcuMDk5LTMxNy4wOTkgMzE3LjA5OXoiLz48L3N2Zz4=',
style: 'width: 100px; height: 100px; margin-bottom: 1em;'
}),
E('h2', {}, _('Homebox Service Not Running')),
E('p', {}, _('Please enable the Homebox service'))
]);
var iframe = E('iframe', {
src: window.location.origin + ':' + state.port,
style: 'width: 100%; min-height: 80vh; border: none; border-radius: 3px;'
});
function checkProcess() {
return fs.exec('/bin/pidof', ['homebox']).then(res => ({
running: res.code === 0,
pid: res.code === 0 ? res.stdout.trim() : null
}));
}
function controlService(action) {
var command = action === 'start'
? 'nohup /usr/bin/homebox > /tmp/homebox.log 2>&1 &'
: '/usr/bin/killall homebox';
return fs.exec('/bin/sh', ['-c', command]);
}
function updateStatus() {
statusIcon.textContent = state.running ? '✓' : '✗';
statusIcon.style.color = state.running ? 'green' : 'red';
statusText.textContent = _('Homebox Server') + (state.running ? _('RUNNING') : _('NOT RUNNING'));
statusText.style.color = state.running ? 'green' : 'red';
statusText.style.fontWeight = 'bold';
statusText.style.fontSize = '0.92rem';
// 更新按钮状态
toggleBtn.textContent = state.running ? _('Stop Server') : _('Start Server');
toggleBtn.className = `btn cbi-button cbi-button-${state.running ? 'reset' : 'apply'}`;
// Update container content based on state
container.textContent = '';
if (state.running) {
container.appendChild(iframe);
} else {
container.appendChild(statusMessage);
}
}
toggleBtn.addEventListener('click', ui.createHandlerFn(this, function() {
var action = state.running ? 'stop' : 'start';
return controlService(action)
.then(checkProcess)
.then(res => {
state.running = res.running;
updateStatus();
});
}));
// 开始
statusSection.appendChild(E('div', { 'style': 'margin: 15px' }, [
E('h3', {}, _('Lan Speedtest Homebox')),
E('div', { 'class': 'cbi-map-descr' }, [statusIcon, statusText]),
E('div', {'class': 'cbi-value', 'style': 'margin-top: 20px'}, [
E('div', {'class': 'cbi-value-title'}, _('Homebox service control')),
E('div', {'class': 'cbi-value-field'}, toggleBtn),
E('div', { 'style': 'text-align: right; font-style: italic; margin-top: 20px;' }, [
_('© github '),
E('a', {
'href': 'https://github.com/sirpdboy/luci-app-netspeedtest',
'target': '_blank',
'style': 'text-decoration: none;'
}, 'by sirpdboy')
])
])
]));
// Initial status check
checkProcess().then(res => {
state.running = res.running;
updateStatus();
toggleBtn.disabled = false;
// Start polling
poll.add(() => {
return checkProcess().then(res => {
if (res.running !== state.running) {
state.running = res.running;
updateStatus();
toggleBtn.disabled = false;
}
});
}, 5);
poll.start();
});
return E('div', {}, [
statusSection,
container
]);
}
// handleSaveApply: null,
// handleSave: null,
// handleReset: null
});

View File

@ -0,0 +1,186 @@
/* Copyright (C) 2021-2025 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-netspeedtest */
'use strict';
'require view';
'require fs';
'require ui';
'require uci';
'require form';
'require poll';
var state = {
running: false,
port: null
};
const logPath = '/var/log/iperf3.log';
function checkProcess() {
return fs.exec('/bin/pidof', ['iperf3']).then(res => ({
running: res.code === 0,
pid: res.code === 0 ? res.stdout.trim() : null
}));
}
function pollLog(textarea) {
return fs.read(logPath).then(res => {
const cleanedLog = res ? res.trim()
.replace(/\u001b\[[0-9;]*m/g, '')
.split('\n')
.slice(-50)
.join('\n') : _('Log file is empty');
if (textarea) {
textarea.value = cleanedLog;
textarea.scrollTop = textarea.scrollHeight;
}
return cleanedLog;
}).catch(err => {
console.error('Error reading log:', err);
return _('Failed to read log: ') + err.message;
});
}
function controlService(action) {
const commands = {
start: `/usr/bin/iperf3 -s -D -p 5201 --logfile ${logPath}`,
stop: '/usr/bin/killall iperf3'
};
return (action === 'start'
? fs.exec('/bin/sh', ['-c', `touch ${logPath} && chmod 644 ${logPath}`])
: Promise.resolve()
).then(() => fs.exec('/bin/sh', ['-c', commands[action]]));
}
return view.extend({
// handleSaveApply: null,
// handleSave: null,
// handleReset: null,
load: function() {
return Promise.all([
uci.load('netspeedtest')
]);
},
render: function() {
// 创建状态元素
const statusIcon = E('span', { 'style': 'margin-right: 5px;' });
const btnGroup = E('div', { 'class': 'cbi-value-field', 'style': 'display: flex; gap: 10px;' });
const statusText = E('span');
const toggleBtn = E('button', {
'class': 'btn cbi-button',
'click': ui.createHandlerFn(this, function() {
const action = state.running ? 'stop' : 'start';
return controlService(action)
.then(() => checkProcess())
.then(res => {
state.running = res.running;
updateStatus();
if (logTextarea) {
pollLog(logTextarea);
}
})
.catch(err => ui.addNotification(null, E('p', _('Error: ') + err.message), 'error'));
})
});
function updateStatus() {
statusIcon.textContent = state.running ? '✓' : '✗';
statusIcon.style.color = state.running ? 'green' : 'red';
statusText.textContent = _('Iperf3 Server ') + (state.running ? _('RUNNING') : _('NOT RUNNING'));
statusText.style.color = state.running ? 'green' : 'red';
statusText.style['font-weight'] = 'bold';
statusText.style['font-size'] = '0.92rem';
toggleBtn.textContent = state.running ? _('Stop Server') : _('Start Server');
toggleBtn.className = `btn cbi-button cbi-button-${state.running ? 'reset' : 'apply'}`;
}
// 初始化状态
statusIcon.textContent = '...';
statusText.textContent = _('Checking status...');
toggleBtn.textContent = _('Loading...');
toggleBtn.disabled = true;
// 创建日志区域
let logTextarea;
logTextarea = E('textarea', {
'class': 'cbi-input-textarea',
'wrap': 'off',
'readonly': 'readonly',
'style': 'width: calc(100% - 20px);height: 535px;margin: 10px;overflow-y: scroll;',
});
// 构建UI
const statusSection = E('div', { 'class': 'cbi-section' }, [
E('div', { 'style': 'margin: 15px' }, [
E('h3', {}, _('Lan Speedtest Iperf3')),
E('div', { 'class': 'cbi-map-descr' }, [statusIcon, statusText]),
E('div', {'class': 'cbi-value', 'style': 'margin-top: 20px'}, [
E('div', {'class': 'cbi-value-title'}, _('Iperf3 service control')),
E('div', {'class': 'cbi-value-field'}, toggleBtn),
E('div', {'class': 'cbi-value-title'}, _('Download iperf3 client')),
E('div', {'class': 'cbi-value-field'}, [
E('div', {
'class': 'cbi-value-field',
'style': 'display: flex;'
}, [
E('button', {
'class': 'btn cbi-button cbi-button-save',
'click': ui.createHandlerFn(this, () => window.open('https://iperf.fr/iperf-download.php', '_blank'))
}, _('Official Website')),
E('button', {
'class': 'btn cbi-button cbi-button-save',
'click': ui.createHandlerFn(this, () => window.open('https://github.com/sirpdboy/luci-app-netspeedtest/releases', '_blank'))
}, _('GitHub'))
])
])
]),
E('div', { 'style': 'text-align: right; font-style: italic; margin-top: 20px;' }, [
_('© github '),
E('a', {
'href': 'https://github.com/sirpdboy/luci-app-netspeedtest',
'target': '_blank',
'style': 'text-decoration: none;'
}, 'by sirpdboy')
])
])
]);
// 初始化状态检查
checkProcess().then(res => {
state.running = res.running;
updateStatus();
toggleBtn.disabled = false;
// 启动轮询
poll.add(() => {
return checkProcess().then(res => {
if (res.running !== state.running) {
state.running = res.running;
updateStatus();
}
});
}, 5);
});
// 如果有日志,启动日志轮询
poll.add(() => pollLog(logTextarea), 5);
pollLog(logTextarea);
return E('div', [
statusSection,
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Iperf3 Run Log')),
logTextarea,
E('div', { 'style': 'text-align: right; font-size: small; margin-top: 5px;' },
_('Refresh every 5 seconds.')
)
])
]);
return render();
}
});

View File

@ -0,0 +1,86 @@
'use strict';
'require dom';
'require fs';
'require poll';
'require uci';
'require view';
var scrollPosition = 0;
var userScrolled = false;
var logTextarea;
var log_path;
uci.load('netspeedtest').then(function() {
log_path = '/var/log/netspeedtest.log';
});
function pollLog() {
return Promise.all([
fs.read_direct(log_path, 'text').then(function (res) {
return res.trim().split(/\n/).join('\n').replace(/\u001b\[33mWARN\u001b\[0m/g, '').replace(/\u001b\[36mINFO\u001b\[0m/g, '').replace(/\u001b\[31mERRO\u001b\[0m/g, '');
}),
]).then(function (data) {
logTextarea.value = data[0] || _('No log data.');
if (!userScrolled) {
logTextarea.scrollTop = logTextarea.scrollHeight;
} else {
logTextarea.scrollTop = scrollPosition;
}
});
};
return view.extend({
handleCleanLogs: function () {
return fs.write(log_path, '')
.catch(function (e) { ui.addNotification(null, E('p', e.message)) });
},
render: function () {
logTextarea = E('textarea', {
'class': 'cbi-input-textarea',
'wrap': 'off',
'readonly': 'readonly',
'style': 'width: calc(100% - 20px);height: 535px;margin: 10px;overflow-y: scroll;',
});
logTextarea.addEventListener('scroll', function () {
userScrolled = true;
scrollPosition = logTextarea.scrollTop;
});
var log_textarea_wrapper = E('div', { 'id': 'log_textarea' }, logTextarea);
setTimeout(function () {
poll.add(pollLog);
}, 100);
var clear_logs_button = E('input', { 'class': 'btn cbi-button-action', 'type': 'button', 'style': 'margin-left: 10px; margin-top: 10px;', 'value': _('Clear logs') });
clear_logs_button.addEventListener('click', this.handleCleanLogs.bind(this));
return E([
E('div', { 'class': 'cbi-map' }, [
E('div', { 'class': 'cbi-section' }, [
clear_logs_button,
log_textarea_wrapper,
E('div', { 'style': 'text-align:right' },
E('small', {}, _('Refresh every %s seconds.').format(L.env.pollinterval)),
E('div', { 'class': 'cbi-section-actions cbi-section-actions-right' })
]),
E('div', { 'style': 'text-align: right; font-style: italic;' }, [
E('span', {}, [
_('© github '),
E('a', {
'href': 'https://github.com/sirpdboy',
'target': '_blank',
'style': 'text-decoration: none;'
}, 'by sirpdboy')
])
])
]);
}
// handleSaveApply: null,
// handleSave: null,
// handleReset: null
});

View File

@ -0,0 +1,37 @@
'use strict';
'require view';
'require uci';
'require ui';
'require form';
return view.extend({
load() {
return Promise.all([
uci.load('netspeedtest')
]);
},
render(res) {
let m, s, o;
m = new form.Map('netspeedtest', _('OpenSpeedTest'));
s = m.section(form.NamedSection, '_iframe');
s.anonymous = true;
s.render = function (section_id) {
return E('iframe', {
src: '//openspeedtest.com/speedtest',
style: 'border:none;width:100%;height:100%;min-height:360px;border:none;overflow:hidden !important;'
});
};
return m.render();
}
// handleSaveApply: null,
// handleSave: null,
// handleReset: null
});

View File

@ -0,0 +1,132 @@
'use strict';
'require view';
'require poll';
'require dom';
'require fs';
'require rpc';
'require uci';
'require ui';
'require form';
// 全局变量
var TestTimeout = 240 * 1000; // 4 Minutes
var ResultFile = '/tmp/speedtest_result';
var SpeedtestCli = '/usr/bin/speedtest';
var SpeedtestScript = '/usr/lib/netspeedtest/speedtest';
return view.extend({
// handleSaveApply: null,
// handleSave: null,
// handleReset: null,
load: function () {
return Promise.all([
L.resolveDefault(fs.stat(SpeedtestCli), {}),
L.resolveDefault(fs.read(ResultFile), null),
L.resolveDefault(fs.stat(ResultFile), {}),
uci.load('netspeedtest')
]);
},
poll_status: function (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');
// Update status indicators
ookla_stat.style.color = has_ookla ? 'green' : 'red';
dom.content(ookla_stat, [_(has_ookla ? 'Installed' : 'Not Installed')]);
// Update result display
if (result_content.length) {
if (result_content[0] == 'Testing') {
result_stat.innerHTML = "<span style='color:green;font-weight:bold'>" +
"<img src='/luci-static/resources/icons/loading.gif' height='17' style='vertical-align:middle'/> " +
_('Testing in progress...') +
"</span>";
} else if (result_content[0].match(/https?:\S+/)) {
result_stat.innerHTML = "<div style='max-width:500px'><a href='" +
result_content[0] + "' target='_blank'><img src='" +
result_content[0] + '.png' + "' style='max-width:100%'></a></div>";
} else if (result_content[0] == 'Test failed') {
result_stat.innerHTML = "<span style='color:red;font-weight:bold'>" +
_('Test failed.') + "</span>";
}
} else {
result_stat.innerHTML = "<span style='color:gray'>" +
_('No test results yet.') + "</span>";
}
},
render: function (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 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 () {
var content;
if (result_content.length) {
if (result_content[0] == 'Testing') {
content = E('span', { style: 'color:green;font-weight:bold' }, [
E('img', { src: '/luci-static/resources/icons/loading.gif', height: '20' }),
' ', _('Testing in progress...')
]);
} else if (result_content[0].match(/https?:\S+/)) {
content = 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%' })
])
]);
} else {
content = E('span', { style: 'color:red;font-weight:bold' },
_('Test failed.'));
}
} else {
content = E('span', { style: 'color:gray' },
_('No test results yet.'));
}
return E('div', { id: 'speedtest_result' }, content);
};
// Configuration section
s = m.section(form.NamedSection, 'config', 'netspeedtest');
// 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') });
};
o = s.option(form.DummyValue, '_ookla_status', _('Ookla® SpeedTest-CLI'));
o.rawhtml = true;
o.cfgvalue = function () {
return E('span', {
id: 'ookla_status',
style: has_ookla ? 'color:green' : 'color:red'
}, _(has_ookla ? 'Installed' : 'Not Installed'));
};
return m.render()
.then(L.bind(function(m, nodes) {
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));
}, this), 5);
return nodes;
}, this, m));
}
});

View File

@ -1,133 +0,0 @@
-- Copyright (C) 2020-2025 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/netspeedtest
module("luci.controller.netspeedtest", package.seeall)
local http = require "luci.http"
local fs=require"nixio.fs"
local sys=require "luci.sys"
local uci = luci.model.uci.cursor()
name='netspeedtest'
function index()
if not nixio.fs.access("/etc/config/netspeedtest") then return end
local e = entry({"admin","network","netspeedtest"},alias("admin", "network", "netspeedtest", "speedtestlan"),_("Net Speedtest"),90)
e.dependent = false
e.acl_depends = { "luci-app-netspeedtest" }
entry({"admin", "network", "netspeedtest", "speedtestlan"},cbi("netspeedtest/speedtestlan"),_("Lan Speedtest Web"),20).leaf = true
entry({"admin", "network", "netspeedtest", "speedtestiperf3"},cbi("netspeedtest/speedtestiperf3", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}),_("Lan Speedtest Iperf3"),30).leaf = true
entry({"admin", "network", "netspeedtest", "speedtestwan"},cbi("netspeedtest/speedtestwan", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}),_("Broadband speedtest"), 40).leaf = true
entry({"admin", "network", "netspeedtest", "speedtestwanweb"},cbi("netspeedtest/speedtestwanweb", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}),_("Broadband OpenSpeedtest"), 41).leaf = true
entry({"admin", "network", "netspeedtest", "speedtestport"},cbi("netspeedtest/speedtestport", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}),_("Server Port Latency Test"), 50).leaf = true
entry({"admin", "network", "netspeedtest", "log"}, form("netspeedtest/log"), _("Log"), 60).leaf = true
entry({"admin", "network", "netspeedtest", "test_port"}, call("test_port"))
entry({"admin", "network", "iperf3_status"}, call("iperf3_status"))
entry({"admin", "network", "test_iperf0"}, post("test_iperf0"), nil).leaf = true
entry({"admin", "network", "test_iperf1"}, post("test_iperf1"), nil).leaf = true
entry({"admin", "network", "netspeedtest", "speedtestwanrun"}, call("speedtestwanrun"))
entry({"admin", "network", "netspeedtest", "netcheck"}, call("netcheck"))
entry({"admin", "network", "netspeedtest", "dellog"},call("dellog"))
entry({"admin", "network", "netspeedtest", "getlog"},call("getlog"))
end
function netcheck()
http.prepare_content("text/plain; charset=utf-8")
local f=io.open("/tmp/netspeedtest.log", "r+")
local fdp=fs.readfile("/tmp/netspeedtestpos") or 0
f:seek("set",fdp)
local a=f:read(2048000) or ""
fdp=f:seek()
fs.writefile("/tmp/netspeedtestpos",tostring(fdp))
f:close()
http.write(a)
end
function speedtestwanrun()
local cli = luci.http.formvalue('cli')
uci:set(name, 'speedtestwan', 'speedtest_cli', cli)
uci:commit(name)
fs.writefile("/tmp/netspeedtestpos","0")
http.prepare_content("application/json")
http.write('')
sys.exec(string.format("/etc/init.d/netspeedtest wantest " ..cli.. " > /tmp/netspeedtest.log 2>&1 &" ))
end
function test_port()
local e = {}
local domain = luci.http.formvalue('sdomain')
local port = luci.http.formvalue('sport')
local ip=sys.exec("echo "..domain.." | grep -E ^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ || nslookup "..domain.." 2>/dev/null | grep Address | awk -F' ' '{print$NF}' | grep -E ^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ | sed -n 1p")
ip=sys.exec("echo -n "..ip)
e.ping = luci.sys.exec(string.format("echo -n $(tcping -q -c 1 -i 1 -t 2 -p %s %s 2>&1 | grep -o 'time=[0-9]*.[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null", port, ip))
e.type = "tcping"
if e.ping=="" then
e.ping=sys.call("echo -n $(ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*.[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null" % ip)
e.type = "ping"
end
if e.ping=="" then e.ping="0" end
sys.exec(string.format('echo -ne "\n 【$(date)】 服务器:%s -- 端口:%s -- TCP延时%s ms \n">> /var/log/netspeedtest.log',domain,port,e.ping))
uci:set(name, 'speedtestport', 'sdomain', domain)
uci:set(name, 'speedtestport', 'sport', port)
uci:set(name, 'speedtestport', 'tcpspeed', e.ping.." ms")
uci:commit(name)
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function iperf3_status()
local e={}
e.run=sys.call("busybox ps -w | grep iperf3 | grep -v grep >/dev/null") == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function testout(cmd, addr)
luci.http.prepare_content("text/plain")
local util = io.popen(cmd)
if util then
while true do
local ln = util:read("*l")
if not ln then break end
luci.http.write(ln)
luci.http.write("\n")
end
util:close()
end
end
function test_iperf0(addr)
sys.call("pgrep -f unblockneteasemusic | xargs kill -9 >/dev/null 2>&1 ")
sys.call("/etc/init.d/unblockneteasemusic stop ")
sys.call("/etc/init.d/unblockmusic stop ")
testout("iperf3 -s ", addr)
end
function test_iperf1(addr)
sys.call("pgrep -f iperf3 | xargs kill -9 >/dev/null 2>&1 ")
sys.call("/etc/init.d/unblockneteasemusic restart")
sys.call("/etc/init.d/unblockmusic restart")
end
function dellog()
fs.writefile("/var/log/netspeedtest.log","")
http.prepare_content("application/json")
http.write('')
end
function getlog()
logfile="/var/log/netspeedtest.log"
if not fs.access(logfile) then
http.write("")
return
end
local f=io.open(logfile,"r")
local a=f:read("*a") or ""
f:close()
a=string.gsub(a,"\n$","")
http.prepare_content("text/plain; charset=utf-8")
http.write(a)
end

View File

@ -1,13 +0,0 @@
local fs = require "nixio.fs"
local uci = require"luci.model.uci".cursor()
local f, t
f = SimpleForm("logview")
f.reset = false
f.submit = false
t=f:field(TextValue,"conf")
t.rmempty=true
t.rows=20
t.template="netspeedtest/log"
t.readonly="readonly"
return f

View File

@ -1,18 +0,0 @@
-- Copyright (C) 2020-2025 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/netspeedtest
require("luci.util")
local o,s,e
o = Map("netspeedtest", "<font color='green'>" .. translate("Net Speedtest") .."</font>",translate( "Network speed diagnosis test (including intranet and extranet)<br/>For specific usage, see:") ..translate("<a href=\'https://github.com/sirpdboy/netspeedtest.git' target=\'_blank\'>GitHub @sirpdboy/netspeedtest</a>") )
o:section(SimpleSection).template = "netspeedtest/speedtestiperf3_status"
s = o:section(TypedSection, "speedtestiperf3", translate('Lan Speedtest Iperf3'))
s.addremove=false
s.anonymous=true
e = s:option(DummyValue, '', '')
e.rawhtml = true
e.template ='netspeedtest/speedtestiperf3'
return o

View File

@ -1,21 +0,0 @@
-- Copyright (C) 2020-2025 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/netspeedtest
local m, s ,o
m = Map("netspeedtest", "<font color='green'>" .. translate("Net Speedtest") .."</font>",translate( "Network speed diagnosis test (including intranet and extranet)<br/>For specific usage, see:") ..translate("<a href=\'https://github.com/sirpdboy/netspeedtest.git' target=\'_blank\'>GitHub @sirpdboy/netspeedtest</a>") )
s = m:section(TypedSection, "netspeedtest", translate('Lan Speedtest Web'))
s.anonymous = true
o=s:option(Flag,"enabled",translate("Enable Homebox service"))
o.default=0
o = s:option(DummyValue, '', '')
o.rawhtml = true
o.template ='netspeedtest/speedtestlan'
m.apply_on_parse = true
m.on_after_apply = function(self,map)
io.popen("/etc/init.d/netspeedtest restart")
luci.http.redirect(luci.dispatcher.build_url("admin","network","netspeedtest","speedtestlan"))
end
return m

View File

@ -1,25 +0,0 @@
-- Copyright (C) 2020-2025 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/netspeedtest
require("luci.util")
local o,t,e
o = Map("netspeedtest", "<font color='green'>" .. translate("Net Speedtest") .."</font>",translate( "Network speed diagnosis test (including intranet and extranet)<br/>For specific usage, see:") ..translate("<a href=\'https://github.com/sirpdboy/netspeedtest.git' target=\'_blank\'>GitHub @sirpdboy/netspeedtest</a>") )
t = o:section(TypedSection, "speedtestport", translate('Server Port Latency Test'))
t.addremove=false
t.anonymous=true
e = t:option(Value, 'sdomain', translate('Test server address'))
e.default = "www.baidu.com"
e.description = translate('Enter the domain name or IP address of the server that needs to be tested')
e = t:option(Value, 'sport', translate('Test server port'))
e.default = "443"
e = t:option(Value, 'tcpspeed', translate('Server Port Delay Value'))
e = t:option(DummyValue, '', '')
e.rawhtml = true
e.template ='netspeedtest/speedtestport'
return o

View File

@ -1,21 +0,0 @@
-- Copyright (C) 2020-2025 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/netspeedtest
require("luci.util")
local o,t,e
luci.sys.exec("echo '-' >/tmp/netspeedtest.log&&echo 1 > /tmp/netspeedtestpos" )
o = Map("netspeedtest", "<font color='green'>" .. translate("Net Speedtest") .."</font>",translate( "Network speed diagnosis test (including intranet and extranet)<br/>For specific usage, see:") ..translate("<a href=\'https://github.com/sirpdboy/netspeedtest.git' target=\'_blank\'>GitHub @sirpdboy/netspeedtest</a>") )
t=o:section(TypedSection,"speedtestwan",translate("Broadband speedtest"))
t.anonymous=true
e = t:option(ListValue, 'speedtest_cli', translate('client version selection'))
e:value("0",translate("ookla-speedtest-cli"))
e:value("1",translate("python3-speedtest-cli"))
e.default = "1"
e=t:option(Button, "restart", translate("speedtest.net Broadband speed test"))
e.inputtitle=translate("Click to execute")
e.template ='netspeedtest/speedtestwan'
return o

View File

@ -1,15 +0,0 @@
-- Copyright (C) 2020-2025 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/netspeedtest
require("luci.util")
local o,t,e
luci.sys.exec("echo '-' >/tmp/netspeedtest.log&&echo 1 > /tmp/netspeedtestpos" )
o = Map("netspeedtest", "<font color='green'>" .. translate("Net Speedtest") .."</font>",translate( "Network speed diagnosis test (including intranet and extranet)<br/>For specific usage, see:") ..translate("<a href=\'https://github.com/sirpdboy/netspeedtest.git' target=\'_blank\'>GitHub @sirpdboy/netspeedtest</a>") )
t=o:section(TypedSection,"speedtestwan",translate("Broadband OpenSpeedtest"))
t.anonymous=true
e = t:option(DummyValue, '', '')
e.rawhtml = true
e.template ='netspeedtest/speedtestwanweb'
return o

View File

@ -1,53 +0,0 @@
<%+cbi/valueheader%>
<input type="button" class="btn cbi-button cbi-button-apply" id="apply_update_button" value="<%:Clear Log%>" onclick="apply_del_log()"/>
<input type="checkbox" name="NAME" value="reverse" onclick="reverselog()" style="vertical-align:middle;height:auto;"><%:Reverse%></input>
<textarea id="cbid.logview.1.conf" class="cbi-input-textarea" style="width: 100%;display:inline" data-update="change" rows="32" cols="60" readonly="readonly" > </textarea>
<script type="text/javascript">//<![CDATA[
var islogreverse=false;
function createAndDownloadFile(fileName,content){
var aTag=document.createElement('a');
var blob=new Blob([content]);
aTag.download=fileName;
aTag.href=URL.createObjectURL(blob);
aTag.click();
URL.revokeObjectURL(blob);
}
function apply_del_log(){
XHR.get('<%=url([[admin]],[[network]],[[netspeedtest]],[[dellog]])%>',null,function(x,data){
var lv=document.getElementById('cbid.logview.1.conf');
lv.innerHTML="";
}
);
return
}
function reverselog(){
var lv=document.getElementById('cbid.logview.1.conf');
lv.innerHTML=lv.innerHTML.split('\n').reverse().join('\n')
if (islogreverse){
islogreverse=false;
}else{
islogreverse=true;
}
return
}
XHR.poll(3,'<%=url([[admin]],[[network]],[[netspeedtest]],[[getlog]])%>',null,
function(x,data){
var lv=document.getElementById('cbid.logview.1.conf');
lv.innerHTML=""
if (x.responseText && lv){
if (islogreverse){
lv.innerHTML=x.responseText.split('\n').reverse().join('\n')+lv.innerHTML;
}else{
lv.innerHTML+=x.responseText;
}
}
}
)
//]]>
</script>
<%+cbi/valuefooter%>

View File

@ -1,93 +0,0 @@
<%#
Copyright 2020-2025 sirpdboy Wich <sirpdboy@qq.com>
https://github.com/sirpdboy/netspeedtest
Licensed to the public under the Apache License 2.0.
-%>
<script type="text/javascript" src="<%=resource%>/cbi.js?v=1.1"></script>
<%+cbi/valueheader%>
<script type="text/javascript">
var stxhr = new XHR();
function update_status(btn,field, proto)
{
var tool = field.name;
var addr = field.value;
var protocol = proto ;
var legend = document.getElementById('test-iperf-legend');
var output = document.getElementById('test-iperf-output');
if (legend && output)
{
output.innerHTML =
'<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> ' +
'<%:Waiting for command to complete...%>';
legend.parentNode.style.display = 'block';
legend.style.display = 'inline';
btn.value='<%:Waiting (executing)...%>';
btn.disabled=true;
stxhr.post('<%=url('admin/network')%>/test_' + tool + protocol + '/' + addr, { token: '<%=token%>' },
function(x)
{
if (x.responseText)
{
legend.style.display = 'none';
output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
}
else
{
legend.style.display = 'none';
output.innerHTML ='</p> <%:Operation execution complete%></p>';
}
btn.disabled=false;
btn.value='<%:Click to execute%>';
}
);
}
}
</script>
<form method="post" class="cbi-map" action="<%=url('admin/network/netspeedtest')%>">
<div class="cbi-value" >
<label class="cbi-value-title" ><%:Select function%></label>
<div class="cbi-value-field">
<input style="margin: 5px 0" type="hidden" value="" name="iperf" />
<select name="iperf_to" style="width:auto">
<option value="0" selected="selected"><%:iperfstart%></option>
<option value="1"><%:iperfstop%></option>
</select>
<div class="cbi-value-description">
<%:The speed measurement terminal must be in the same LAN as the router that starts the speed measurement%><br />
<%:Operation steps: start router speed measurement service download test client run speed measurement client input IP address of router speed measurement service%>
</div></div></div>
<div class="cbi-value" ><label class="cbi-value-title" ><%:Execute selected functions%></label>
<div class="cbi-value-field"><input type="button" value="<%:Click to execute%>" class="cbi-button cbi-button-apply" onclick="return update_status(this,this.form.iperf,this.form.iperf_to.selectedIndex)" />
</div></div>
<div class="cbi-value" >
<label class="cbi-value-title"><%:Iperf3 speed measurement software download%></label>
<div class="cbi-value-field">
<input type="button" class="cbi-button cbi-input-reload" value="<%:Github download iperf3%>" onclick="javascript:window.open('https://github.com/sirpdboy/netspeedtest/releases','target');" />
<input type="button" class="cbi-button cbi-input-reload" value="<%:Download from foreign official websites%>" onclick="javascript:window.open('https://iperf.fr/iperf-download.php','target');" />
</div>
</div>
<div class="cbi-value" >
<label class="cbi-value-title"><%:iperf3 commands reference%></label>
<div class="cbi-value-field">
<%:-c, --client host ................run in client mode, connecting to host%><br />
<%:-s, --server .....................run in server mode%><br />
<%:-u, --udp ........................use UDP rather than TCP%><br />
<%:-b, --bandwidth ..<number>[KMG]...target bandwidth in bits/sec (0 for unlimited)%><br />
<%:-t, --time .......<number>........time in seconds to transmit for (default 10 secs)%><br />
<%:-i, --interval ...<number>........seconds between periodic bandwidth reports%><br />
<%:-P, --parallel ...<number>........number of parallel client streams to run%><br />
<%:-R, --reverse ....................run in reverse mode (server sends, client receives)%><br />
</div>
</div>
<fieldset class="cbi-section" style="display:none">
<legend id="test-iperf-legend">
<%:Collecting data...%>
</legend>
<span id="test-iperf-output"></span>
</fieldset>
</form>
<%+cbi/valuefooter%>

View File

@ -1,27 +0,0 @@
<script type="text/javascript">//<![CDATA[
XHR.poll(3, '<%=url([[admin]], [[network]], [[iperf3_status]])%>', null,
function(x, data) {
var tb = document.getElementById('iperf3_status');
if (data && tb)
{
if (data.run)
{
tb.innerHTML = '<br/><em style=\"color:green\"><%:The Iperf3 service is running.%></em>';
}
else
{
tb.innerHTML = '<br/><em style=\"color:red\"><%:The Iperf3 service is not running.%></em>';
}
}
}
);
//]]></script>
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
<fieldset class="cbi-section">
<legend><%:Iperf3 Status%></legend>
<p id="iperf3_status">
<em><%:Collecting data...%></em>
</p>
</fieldset>

View File

@ -1,32 +0,0 @@
<%#
Copyright 2020-2025 sirpdboy Wich <sirpdboy@qq.com>
https://github.com/sirpdboy/netspeedtest
Licensed to the public under the Apache License 2.0.
-%>
<%
local running = luci.sys.exec("busybox ps -w | grep homebox | grep -v grep >/dev/null && echo -ne '1' ")
%>
<%+cbi/valueheader%>
<div class="cbi-map">
<% if running == "1" then %>
<iframe id="homebox" style="width: 100%; min-height: 400px; height: 650px;border: none; border-radius: 3px;"></iframe>
<script type="text/javascript">
document.getElementById("homebox").src = window.location.protocol + "//" + window.location.hostname + ":3300";
document.getElementById("homebox").height = document.documentElement.clientHeight;
window.onresize = function(){
document.getElementById("homebox").height = document.documentElement.clientHeight;
}
</script>
<% else %>
<style>.running{text-align: center;} .running>h1{font-size: 25px; color: #333; margin: 1rem;} .running>p{ font-size: 1.2rem; color: #8391a2; margin: 1rem;}</style>
<div class="running">
<img src="data:image/svg+xml;base64,PCEtLSBHZW5lcmF0ZWQgYnkgSWNvTW9vbi5pbyAtLT4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMjQiIGhlaWdodD0iMTAyNCIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCI+Cjx0aXRsZT48L3RpdGxlPgo8ZyBpZD0iaWNvbW9vbi1pZ25vcmUiPgo8L2c+CjxwYXRoIGZpbGw9IiNkZjAwMDAiIGQ9Ik05NDIuNDIxIDIzNC42MjRsODAuODExLTgwLjgxMS0xNTMuMDQ1LTE1My4wNDUtODAuODExIDgwLjgxMWMtNzkuOTU3LTUxLjYyNy0xNzUuMTQ3LTgxLjU3OS0yNzcuMzc2LTgxLjU3OS0yODIuNzUyIDAtNTEyIDIyOS4yNDgtNTEyIDUxMiAwIDEwMi4yMjkgMjkuOTUyIDE5Ny40MTkgODEuNTc5IDI3Ny4zNzZsLTgwLjgxMSA4MC44MTEgMTUzLjA0NSAxNTMuMDQ1IDgwLjgxMS04MC44MTFjNzkuOTU3IDUxLjYyNyAxNzUuMTQ3IDgxLjU3OSAyNzcuMzc2IDgxLjU3OSAyODIuNzUyIDAgNTEyLTIyOS4yNDggNTEyLTUxMiAwLTEwMi4yMjktMjkuOTUyLTE5Ny40MTktODEuNTc5LTI3Ny4zNzZ6TTE5NC45NDQgNTEyYzAtMTc1LjEwNCAxNDEuOTUyLTMxNy4wNTYgMzE3LjA1Ni0zMTcuMDU2IDQ4IDAgOTMuNDgzIDEwLjY2NyAxMzQuMjI5IDI5Ljc4MWwtNDIxLjU0NyA0MjEuNTQ3Yy0xOS4wNzItNDAuNzg5LTI5LjczOS04Ni4yNzItMjkuNzM5LTEzNC4yNzJ6TTUxMiA4MjkuMDU2Yy00OCAwLTkzLjQ4My0xMC42NjctMTM0LjIyOS0yOS43ODFsNDIxLjU0Ny00MjEuNTQ3YzE5LjA3MiA0MC43ODkgMjkuNzgxIDg2LjI3MiAyOS43ODEgMTM0LjIyOS0wLjA0MyAxNzUuMTQ3LTE0MS45OTUgMzE3LjA5OS0zMTcuMDk5IDMxNy4wOTl6Ij48L3BhdGg+Cjwvc3ZnPgo=" height="100">
<h1><font color="color:red"><%:The homebox service is not running.%></font></h1>
<p><%:Please enable the Homebox service%></p>
</div>
<% end %>
</div>
<%+cbi/valuefooter%>

View File

@ -1,81 +0,0 @@
<%#
Copyright 2020-2025 sirpdboy Wich <sirpdboy@qq.com>
https://github.com/sirpdboy/netspeedtest
Licensed to the public under the Apache License 2.0.
-%>
<%+cbi/valueheader%>
<script type="text/javascript">//<![CDATA[
function speedtestportrun(btn) {
var sid='speedtestport'
var opt={
base:"cbid.netspeedtest."+sid,
get:function(opt){
var id=this.base+'.'+opt;
var obj=document.getElementsByName(id)[0] || document.getElementsByClassName(id)[0] || document.getElementById(id)
if (obj){
return obj;
}else{
return null;
}
},
getlist:function(opt){
var id=this.base+'.'+opt;
var objs=document.getElementsByName(id) || document.getElementsByClassName(id);
var ret=[];
if (objs){
for (var i=0;i < objs.length;i++){
ret[i]=objs[i].value;
}
}else{
alert("<%:Fatal on get option,please help in debug%>:"+opt);
}
return ret;
}
}
const RUN_URL = '<%=luci.dispatcher.build_url("admin", "network", "netspeedtest","test_port")%>';
const S_URL = '<%=luci.dispatcher.build_url("admin", "network", "netspeedtest","speedtestport")%>';
var output=document.getElementById("speedtestport-status");
btn.value='<%:Waiting (executing)...%>';
btn.disabled=true;
var sdomain=opt.get("sdomain").value;
var sport=opt.get("sport").value;
XHR.get('<%=luci.dispatcher.build_url("admin/network/netspeedtest/test_port")%>',{
sdomain: sdomain,
sport: sport
},
(x) =>{
if(x && x.status == 200) {
output.innerHTML="<font style=\'color:green\'>"+"<%:Perform OK%>"+"</font>";
}
else{
output.innerHTML="<font style=\'color:green\'>"+"<%:Test failed%>"+"</font>";
}
setTimeout(function(){
window.location = S_URL
},1000);
btn.disabled=false;
btn.value='<%:Click to execute%>';
}
);
return false;
}
//]]></script>
<label class="cbi-value-title"><%= translate("Test server port delay") %></label>
<div class="cbi-value-field">
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:Click to execute%>' onclick="return speedtestportrun(this)" />
<span id="speedtestport-status"></span>
</div>
<%+cbi/valuefooter%>

View File

@ -1,99 +0,0 @@
<%#
Copyright 2020-2025 sirpdboy Wich <sirpdboy@qq.com>
https://github.com/sirpdboy/netspeedtest
Licensed to the public under the Apache License 2.0.
-%>
<%+cbi/valueheader%>
<%local fs=require"nixio.fs"%>
<input type="button" class="btn cbi-button cbi-button-apply" id="apply_run_button" value="<%:Click to execute%>" onclick=" return apply_run(this) "/>
<div id="logview" style="display:none">
<input type="checkbox" id="reversetag" value="reverse" onclick=" return reverselog()" style="vertical-align:middle;height: auto;"><%:reverse%></input>
<textarea id="cbid.logview.test.conf" class="cbi-input-textarea" style="width: 100%;display:block;" data-update="change" rows="20" cols="80" readonly="readonly" > </textarea>
</div>
<script type="text/javascript">//<![CDATA[
const RUN_URL = '<%=luci.dispatcher.build_url("admin", "network", "netspeedtest","speedtestwanrun")%>';
const S_URL = '<%=luci.dispatcher.build_url("admin", "network", "netspeedtest","netcheck")%>';
var checkbtn = document.getElementById('apply_run_button');
var islogreverse = false;
function reverselog(){
var lv = document.getElementById('cbid.logview.test.conf');
lv.innerHTML=lv.innerHTML.split('\n').reverse().join('\n')
if (islogreverse){
islogreverse=false;
}else{
islogreverse=true;
}
return
}
function apply_run(btn) {
var sid='speedtestwan'
var opt={
base:"cbid.netspeedtest."+sid,
get:function(opt){
var id=this.base+'.'+opt;
var obj=document.getElementsByName(id)[0] || document.getElementsByClassName(id)[0] || document.getElementById(id)
if (obj){
return obj;
}else{
return null;
}
},
getlist:function(opt){
var id=this.base+'.'+opt;
var objs=document.getElementsByName(id) || document.getElementsByClassName(id);
var ret=[];
if (objs){
for (var i=0;i < objs.length;i++){
ret[i]=objs[i].value;
}
}else{
alert("<%:Fatal on get option,please help in debug%>:"+opt);
}
return ret;
}
}
btn.value='<%:Waiting (executing)...%>';
btn.disabled=true;
var cli=opt.get("speedtest_cli").value;
console.log(cli);
XHR.get(RUN_URL, { cli: cli },
(x) =>{}
);
poll_check();
return;
}
function poll_check(){
var tag = document.getElementById('logview');
tag.style.display="block"
XHR.poll(3, '<%=url([[admin]], [[network]], [[netspeedtest]], [[netcheck]])%>', null,
function(x, data) {
var lv = document.getElementById('cbid.logview.test.conf');
if (x.responseText && lv) {
if (x.responseText=="\u0000"){
for(j = 0,len=this.XHR._q.length; j < len; j++) {
if (this.XHR._q[j].url == '<%=url([[admin]], [[network]], [[netspeedtest]], [[netcheck]])%>'){
this.XHR._q.splice(j,1);
checkbtn.disabled = false;
checkbtn.value = '<%:Click to execute%>';
break;
}
}
return
}
if (islogreverse){
lv.innerHTML = x.responseText.split('\n').reverse().join('\n')+lv.innerHTML;
}else{
lv.innerHTML += x.responseText;
}
}
}
);}
//]]>
</script>
<%+cbi/valuefooter%>

View File

@ -1,10 +0,0 @@
<%#
Copyright 2020-2025 sirpdboy Wich <sirpdboy@qq.com>
https://github.com/sirpdboy/netspeedtest
Licensed to the public under the Apache License 2.0.
-%>
<%+cbi/valueheader%>
<!--OST Widget code start--><div style="text-align:right;"><div style="min-height:360px;"><div style="width:100%;height:0;padding-bottom:50%;position:relative;"><iframe style="border:none;position:absolute;top:0;left:0;width:100%;height:100%;min-height:360px;border:none;overflow:hidden !important;" src="//openspeedtest.com/speedtest"></iframe></div></div>Provided by OpenSpeedtest.com </div><!-- OST Widget code end -->
<%+cbi/valuefooter%>

View File

@ -0,0 +1,122 @@
#
# Copyright (C) 2021-2025 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-netspeedtest
# This is free software, licensed under the GNU General Public License v3.
#
msgid ""
msgstr ""
"Project-Id-Version: LuCi Chinese Translation\n"
"Report-Msgid-Bugs-To: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Pootle 2.0.6\n"
msgid "NetSpeedtest"
msgstr ""
msgid "A tool for testing network speed in multiple aspects"
msgstr ""
msgid "Lan Speedtest Iperf3"
msgstr ""
msgid "Lan Speedtest Homebox"
msgstr ""
msgid "Wan Ookla SpeedTest"
msgstr ""
msgid "Wan OpenSpeedTest"
msgstr ""
msgid "Log"
msgstr ""
msgid "Start Server"
msgstr ""
msgid "Stop Server"
msgstr ""
msgid "Loading..."
msgstr ""
msgid "RUNNING"
msgstr ""
msgid "NOT RUNNING"
msgstr ""
msgid "Iperf3 Server"
msgstr ""
msgid "Command failed"
msgstr ""
msgid "Failed to read log file"
msgstr ""
msgid "No log content available"
msgstr ""
msgid "Error: "
msgstr ""
msgid "Iperf3 service control"
msgstr ""
msgid "Listen Port"
msgstr ""
msgid "Invalid format. Use [::]:port or ip:port"
msgstr ""
msgid "Enable Log View"
msgstr ""
msgid "Download iperf3 client"
msgstr ""
msgid "Iperf3 Run Log"
msgstr ""
msgid "Refresh Log"
msgstr ""
msgid "Official Website"
msgstr ""
msgid "Please enable the Homebox service"
msgstr ""
msgid "Homebox service control"
msgstr ""
msgid "Homebox Server"
msgstr ""
msgid "Homebox Service Not Running"
msgstr ""
msgid "Ookla® SpeedTest-CLI"
msgstr ""
msgid "Start Ookla SpeedTest"
msgstr ""
msgid "Click to execute"
msgstr ""
msgid "Testing in progress..."
msgstr ""
msgid "Test failed."
msgstr ""
msgid "No test results yet."
msgstr ""
msgid "Test Script"
msgstr ""

View File

@ -0,0 +1 @@
zh_Hans

View File

@ -1,172 +0,0 @@
#
# Copyright (C) 2020-2022 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/netspeedtest
# This is free software, licensed under the GNU General Public License v3.
#
msgid ""
msgstr ""
"Project-Id-Version: LuCi Chinese Translation\n"
"Report-Msgid-Bugs-To: \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Pootle 2.0.6\n"
msgid "Net Speedtest"
msgstr "网速测试"
msgid "Network speed diagnosis test (including intranet and extranet)<br/>For specific usage, see:"
msgstr "网络速度诊断测试(包括内网、外网、特定端口服务器测速)<br/>使用说明见:"
msgid "Lan Speedtest Iperf3"
msgstr "本地iperf3吞吐测速"
msgid "Lan Speedtest Web"
msgstr "本地测速网页版"
msgid "Broadband speedtest"
msgstr "宽带测速speedtest"
msgid "Broadband OpenSpeedtest"
msgstr "宽带测速OpenSpeedtest"
msgid "Running state"
msgstr "运行状态"
msgid "Enable Homebox service"
msgstr "启用homebox服务"
msgid "The Iperf3 service is running."
msgstr "iperf3服务已启动"
msgid "The Iperf3 service is not running."
msgstr "iperf3服务未启动"
msgid "Iperf3 Status"
msgstr "iperf3服务状态"
msgid "The homebox service is running."
msgstr "homebox网页测速服务已启动"
msgid "The homebox service is not running."
msgstr "homebox网页测速服务未启动"
msgid "Please enable the Homebox service"
msgstr "请将homebox服务启用"
msgid "homebox Status"
msgstr "homebox网页测速服务状态"
msgid "</br>For specific usage, see:"
msgstr "</br>具体使用方法参见:"
msgid "iperfstart"
msgstr "iperf服务启动"
msgid "iperfstop"
msgstr "iperf服务停止"
msgid "Select function"
msgstr "选择功能"
msgid "Execute selected functions"
msgstr "执行选择的功能"
msgid "client version selection"
msgstr "客户端版本"
msgid "python3-speedtest-cli"
msgstr "python3网络测试客户端"
msgid "ookla-speedtest-cli"
msgstr "ookla网络测试客户端"
msgid "iperf3 instructions"
msgstr "iperf3使用说明"
msgid "Test speed service started"
msgstr "测试速度服务已经启动"
msgid "The speed measurement terminal must be in the same LAN as the router that starts the speed measurement"
msgstr "测速终端机必须与启动测速的路由器在同一局域网内"
msgid "Operation steps: start router speed measurement service download test client run speed measurement client input IP address of router speed measurement service"
msgstr "使用步骤A.启动路由器测速服务 B.下载测试客户端 C.运行测速客户端 D.输入路由器测速服务IP地址。 "
msgid "Github download iperf3"
msgstr "Github下载iperf3"
msgid "Iperf3 speed measurement software download"
msgstr "iperf3测速软件下载"
msgid "speedtest.net Broadband speed test"
msgstr "speedtest.net宽带网速测试"
msgid "Operation execution complete"
msgstr "操作执行完毕"
msgid "Network speed test, please wait..."
msgstr "网速测试中,请稍侯..."
msgid "Download from foreign official websites"
msgstr "国外官网"
msgid "Server Port Latency Test"
msgstr "服务器端口延迟测试"
msgid "Test server address"
msgstr "测试服务器地址"
msgid "Test server port"
msgstr "测试服务器端口"
msgid "Enter the domain name or IP address of the server that needs to be tested"
msgstr "输入需要测试的服务器域名或者IP地址"
msgid "Test server port delay"
msgstr "测试端口延时"
msgid "Click to execute"
msgstr "点击执行"
msgid "Test failed"
msgstr "测试失败"
msgid "Waiting (executing)..."
msgstr "努力执行中"
msgid "Server Port Delay Value"
msgstr "服务器端口延时值"
msgid "iperf3 commands reference"
msgstr "iperf3命令参考"
msgid "-b, --bandwidth ..<number>[KMG]...target bandwidth in bits/sec (0 for unlimited)"
msgstr "-b, --bandwidth ..<number>[KMG]...目标带宽(以位/秒为单位0表示无限制"
msgid "-t, --time .......<number>........time in seconds to transmit for (default 10 secs)"
msgstr "-t, --time .......<number>........传输时间以秒为单位默认为10秒"
msgid "-i, --interval ...<number>........seconds between periodic bandwidth reports"
msgstr "-i, --interval ...<number>........测试报告之间的秒数"
msgid "-P, --parallel ...<number>........number of parallel client streams to run"
msgstr "-P, --parallel ...<number>........多线程运行的数量"
msgid "-R, --reverse ....................run in reverse mode (server sends, client receives)"
msgstr "-R, --reverse ....................反向模式运行(服务器发送,客户端接收)"
msgid "-c, --client host ................run in client mode, connecting to host"
msgstr "-c, --client host ................客户端模式下运行连接到host"
msgid "-s, --server .....................run in server mode"
msgstr "-s, --server .....................服务器模式下运行"
msgid "-u, --udp ........................use UDP rather than TCP"
msgstr "-u, --udp ........................测试传输使用UDP而不是TCP"
msgid "Perform OK"
msgstr "执行完成"

View File

@ -1,5 +1,5 @@
#
# Copyright (C) 2020-2022 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/netspeedtest
# Copyright (C) 2020-2025 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/netspeedtest
# This is free software, licensed under the GNU General Public License v3.
#
msgid ""
@ -13,160 +13,110 @@ msgstr ""
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Pootle 2.0.6\n"
msgid "Net Speedtest"
msgid "NetSpeedtest"
msgstr "网速测试"
msgid "Network speed diagnosis test (including intranet and extranet)<br/>For specific usage, see:"
msgstr "网络速度诊断测试(包括内网、外网、特定端口服务器测速)<br/>使用说明见:"
msgid "A tool for testing network speed in multiple aspects"
msgstr "一个用于从多方面测试本地和宽带网络速度的工具"
msgid "Lan Speedtest Iperf3"
msgstr "本地iperf3吞吐测速"
msgid "Lan Speedtest Web"
msgstr "本地测速网页版"
msgid "Lan Speedtest Homebox"
msgstr "本地homebox网页测速"
msgid "Broadband speedtest"
msgstr "宽带测速speedtest"
msgid "Wan Ookla SpeedTest"
msgstr "宽带Ookla网速测速"
msgid "Broadband OpenSpeedtest"
msgstr "宽带测速OpenSpeedtest"
msgid "Wan OpenSpeedTest"
msgstr "宽带OpenSpeedTest测速"
msgid "Running state"
msgstr "运行状态"
msgid "Log"
msgstr "日志"
msgid "Enable Homebox service"
msgstr "启用homebox服务"
msgid "Start Server"
msgstr "启服务"
msgid "The Iperf3 service is running."
msgstr "iperf3服务已启动"
msgid "Stop Server"
msgstr "停止服务"
msgid "The Iperf3 service is not running."
msgstr "iperf3服务未启动"
msgid "Loading..."
msgstr "装载中..."
msgid "Iperf3 Status"
msgstr "iperf3服务状态"
msgid "RUNNING"
msgstr "运行中"
msgid "The homebox service is running."
msgstr "homebox网页测速服务已启动"
msgid "NOT RUNNING"
msgstr "未运行"
msgid "The homebox service is not running."
msgstr "homebox网页测速服务未启动"
msgid "Iperf3 Server"
msgstr "Iperf3服务端"
msgid "Command failed"
msgstr "命令无效"
msgid "Failed to read log file"
msgstr "读取日志失败"
msgid "No log content available"
msgstr "没有日志记录"
msgid "Error: "
msgstr "错误: "
msgid "Iperf3 service control"
msgstr "Iperf3 服务控制"
msgid "Listen Port"
msgstr "监听端口"
msgid "Invalid format. Use [::]:port or ip:port"
msgstr "格式错误.如 [::]:端口 或 ip:端口"
msgid "Enable Log View"
msgstr "开启日志显示"
msgid "Download iperf3 client"
msgstr "下载iperf3客户端"
msgid "Iperf3 Run Log"
msgstr "Iperf3运行日志"
msgid "Refresh Log"
msgstr "刷新日志"
msgid "Official Website"
msgstr "官方网站"
msgid "Please enable the Homebox service"
msgstr "请将homebox服务启用"
msgid "homebox Status"
msgstr "homebox网页测速服务状态"
msgid "Homebox service control"
msgstr "Homebox服务控制"
msgid "</br>For specific usage, see:"
msgstr "</br>具体使用方法参见:"
msgid "Homebox Server"
msgstr "Homebox服务端"
msgid "iperfstart"
msgstr "iperf服务启动"
msgid "Homebox Service Not Running"
msgstr "Homebox服务端未运行"
msgid "iperfstop"
msgstr "iperf服务停止"
msgid "Ookla® SpeedTest-CLI"
msgstr ""
msgid "Select function"
msgstr "选择功能"
msgid "Execute selected functions"
msgstr "执行选择的功能"
msgid "client version selection"
msgstr "客户端版本"
msgid "python3-speedtest-cli"
msgstr "python3网络测试客户端"
msgid "ookla-speedtest-cli"
msgstr "ookla网络测试客户端"
msgid "iperf3 instructions"
msgstr "iperf3使用说明"
msgid "Test speed service started"
msgstr "测试速度服务已经启动"
msgid "The speed measurement terminal must be in the same LAN as the router that starts the speed measurement"
msgstr "测速终端机必须与启动测速的路由器在同一局域网内"
msgid "Operation steps: start router speed measurement service download test client run speed measurement client input IP address of router speed measurement service"
msgstr "使用步骤A.启动路由器测速服务 B.下载测试客户端 C.运行测速客户端 D.输入路由器测速服务IP地址。 "
msgid "Github download iperf3"
msgstr "Github下载iperf3"
msgid "Iperf3 speed measurement software download"
msgstr "iperf3测速软件下载"
msgid "speedtest.net Broadband speed test"
msgstr "speedtest.net宽带网速测试"
msgid "Operation execution complete"
msgstr "操作执行完毕"
msgid "Network speed test, please wait..."
msgstr "网速测试中,请稍侯..."
msgid "Download from foreign official websites"
msgstr "国外官网"
msgid "Server Port Latency Test"
msgstr "服务器端口延迟测试"
msgid "Test server address"
msgstr "测试服务器地址"
msgid "Test server port"
msgstr "测试服务器端口"
msgid "Enter the domain name or IP address of the server that needs to be tested"
msgstr "输入需要测试的服务器域名或者IP地址"
msgid "Test server port delay"
msgstr "测试端口延时"
msgid "Start Ookla SpeedTest"
msgstr "开始宽带测速"
msgid "Click to execute"
msgstr "点击执行"
msgid "Test failed"
msgstr "测试失败"
msgid "Testing in progress..."
msgstr "测试正在进行中..."
msgid "Waiting (executing)..."
msgstr "努力执行中"
msgid "Test failed."
msgstr "测速失败"
msgid "Server Port Delay Value"
msgstr "服务器端口延时值"
msgid "iperf3 commands reference"
msgstr "iperf3命令参考"
msgid "-b, --bandwidth ..<number>[KMG]...target bandwidth in bits/sec (0 for unlimited)"
msgstr "-b, --bandwidth ..<number>[KMG]...目标带宽(以位/秒为单位0表示无限制"
msgid "-t, --time .......<number>........time in seconds to transmit for (default 10 secs)"
msgstr "-t, --time .......<number>........传输时间以秒为单位默认为10秒"
msgid "-i, --interval ...<number>........seconds between periodic bandwidth reports"
msgstr "-i, --interval ...<number>........测试报告之间的秒数"
msgid "-P, --parallel ...<number>........number of parallel client streams to run"
msgstr "-P, --parallel ...<number>........多线程运行的数量"
msgid "-R, --reverse ....................run in reverse mode (server sends, client receives)"
msgstr "-R, --reverse ....................反向模式运行(服务器发送,客户端接收)"
msgid "-c, --client host ................run in client mode, connecting to host"
msgstr "-c, --client host ................客户端模式下运行连接到host"
msgid "-s, --server .....................run in server mode"
msgstr "-s, --server .....................服务器模式下运行"
msgid "-u, --udp ........................use UDP rather than TCP"
msgstr "-u, --udp ........................测试传输使用UDP而不是TCP"
msgid "Perform OK"
msgstr "执行完成"
msgid "No test results yet."
msgstr "还没有测试结果"
msgid "Test Script"
msgstr "测速脚本"

View File

@ -1,15 +0,0 @@
config netspeedtest 'netspeedtest'
option enabled '0'
option port '3300'
config homebox 'homebox'
option enabled '0'
option port '3300'
config speedtestiperf3 'speedtestiperf3'
config speedtestwan 'speedtestwan'
config speedtestport 'speedtestport'
option sport '443'
option sdomain 'www.taobao.com'

199
luci-app-netspeedtest/root/etc/init.d/netspeedtest Executable file → Normal file
View File

@ -1,161 +1,72 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2021-2025 sirpdboy
#
# Copyright (C) 2020-2025 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/netspeedtest
# This is free software, licensed under the Apache License, Version 2.0 .
#
. "${IPKG_INSTROOT}/lib/functions.sh"
START=99
USE_PROCD=1
PROG=/usr/bin/homebox
EXTRA_COMMANDS="wantest"
EXTRA_COMMANDS="download_ookla ookla_verify"
EXTRA_HELP=\
" download_ookla Download Ookla Speedtest-CLI
ookla_verify Verify Ookla Speedtest-CLI integrity"
TMP_T=/var/netspeedtest_nstest.tmp
BINSPEEDTEST='/usr/bin/speedtest'
LOCK=/var/lock/netspeedtest_nstest.lock
LOGD=/etc/netspeedtest
LOG=/var/log/netspeedtest.log
LOGE=$LOGD/netspeedtest.log
LOGT=$LOGD/netspeedtestpos
[ -d "$LOGD" ] || mkdir -p $LOGD
[ -f "$LOGE" ] || echo "start" > $LOGE
[ -f "$LOGT" ] || echo "start" > $LOGT
[ -f "$LOG" ] || echo "start" > $LOG
MAX_LOG=500
limit_log() {
local logf=$1
local max=$2
[ ! -f "$logf" ] && return
sc=${max:-$MAX_LOG}
local count=$(grep -c "" $logf)
if [ $count -gt $sc ];then
let count=count-$sc
sed -i "1,$count d" $logf
fi
}
echone() {
echo -ne $* >> $LOG
}
wantest() {
[ -f $LOCK ] && exit
limit_log $LOG 500
touch $LOCK
TESTMODE=$1
BINTEMP=$(git_bin_handle $TESTMODE)
case $TESTMODE in
0)
echo -ne "\n —————ookla-speedtest测速—————\n" | tee -a $LOG
$BINTEMP | tee $TMP_T
RESULT=$(cat $TMP_T | grep 'URL' | cut -c15-)
if [ -n "$RESULT" ] ;then
echo -ne "\n 测服信息:`cat $TMP_T | grep 'Server'| cut -c10- | awk -F: '{printf $2$3}'` 线路:`cat $TMP_T | grep 'ISP' | awk -F: '{printf $2}' ` 延时:`cat $TMP_T | grep 'Latency' | awk -F: '{printf $2}' | awk -F '(' '{printf $1}'`" >> $LOG
echo -ne "\n 下行速率:`cat $TMP_T | grep 'Download' |awk -F: '{printf $2}' | awk -F '(' '{printf $1}'` --" >> $LOG
echo -ne "-- 上行速率:`cat $TMP_T | grep 'Upload' |awk -F: '{printf $2}' | awk -F '(' '{printf $1}'`" >> $LOG
echo -ne "\n 测速结果图片链接:`cat $TMP_T | grep 'URL' | cut -c15-`" >> $LOG
else
echo -ne "\n 因客户端在LUCI下运行错误测试失败" | tee -a $LOG
echo -ne "\n 请SSH运行/etc/init.d/netspeedtest wantest 0 测试,完成后,在日志中有测试结果!" | tee -a $LOG
fi
echo -ne "\n 测试时间: `date +%Y-%m-%d' '%H:%M:%S`" | tee -a $LOG
echo -ne "\n ————————————————————\n" | tee -a $LOG
;;
*)
echo -ne "\n —————python3-speedtest测速—————\n" | tee -a $LOG
$BINTEMP | tee $TMP_T
echo -ne "\n 测服信息:$(cat $TMP_T | grep 'Hosted by'| cut -c10- | awk -F: '{printf $1}') 延时:$(cat $TMP_T | grep 'Hosted by' | awk -F: '{printf $2}')" >> $LOG
echo -ne "\n 下行速率:$(cat $TMP_T | grep 'Download:' |awk -F: '{printf $2}' ) --" >> $LOG
echo -ne "-- 上行速率:$(cat $TMP_T | grep 'Upload:' |awk -F: '{printf $2}' )" >> $LOG
echo -ne "\n 测试结果图片链接:$(cat $TMP_T | grep 'results:' | cut -c16- )" >> $LOG
echo -ne "\n 测试时间:`date +%Y-%m-%d" "%H:%M:%S`" | tee -a $LOG
echo -ne "\n ————————————————————\n" | tee -a $LOG
;;
esac
rm -f $LOCK
}
getcpucore(){
#i386, x86_64, arm32, arm32hf, and arm64.
cputype=$(uname -ms | tr ' ' '_' | tr '[A-Z]' '[a-z]')
[ -n "$(echo $cputype | grep -E "linux.*armv.*")" ] && cpucore="armel"
[ -n "$(echo $cputype | grep -E "linux.*armv7.*")" ] && [ -n "$(cat /proc/cpuinfo | grep vfp)" ] && [ ! -d /jffs/clash ] && cpucore="armhf"
[ -n "$(echo $cputype | grep -E "linux.*aarch64.*|linux.*armv8.*")" ] && cpucore="aarch64"
[ -n "$(echo $cputype | grep -E "linux.*86.*")" ] && cpucore="i386"
[ -n "$(echo $cputype | grep -E "linux.*86_64.*")" ] && cpucore="x86_64"
echo $cpucore
}
git_bin_handle()
{
case $1 in
0)
TMPDIR=/var/netspeedtest
BINSPEEDTEST=$TMPDIR/speedtest
[ ! -d $TMPDIR ] && mkdir -p $TMPDIR 2>/dev/null
if [ -x "$BINSPEEDTEST" ]; then
chmod 755 $BINSPEEDTEST >/dev/null 2>&1
echo "$BINSPEEDTEST --accept-gdpr --accept-license --progress=no"
else
ooklaurl=`curl --connect-timeout 5 -m 60 -sSL 'https://www.speedtest.net/apps/cli' | grep 'Download for Linux' | sed 's|<|\n<|g' | sed -n '/Download for Linux/,/<\/div>/p' | sed -En "s|.*<a href=\"([^\"]+)\">$(getcpucore)|\1|p" `
[ -n "$ooklaurl" ] && curl -sSL $ooklaurl | tar -xvz -C /tmp >/dev/null 2>&1 || return
mv /tmp/speedtest $BINSPEEDTEST >/dev/null 2>&1
chmod 755 $BINSPEEDTEST >/dev/null 2>&1
[ -x "$BINSPEEDTEST" ] && echo "$BINSPEEDTEST --accept-gdpr --accept-license --progress=no"
fi
;;
*)
BINSPEEDTEST='/usr/bin/speedtest'
[ -x "$BINSPEEDTEST" ] && echo "$BINSPEEDTEST --share"
;;
esac
}
speedtest_start(){
case $1 in
0)
speedtest_start_ookla $2 ;;
*)
speedtest_start_cli $2 ;;
esac
}
tcptest(){
NDATA=`date +%Y-%m-%d' '%H:%M:%S`
echo -ne "\n $NDATA 服务器:$1 ---- 端口:$2 ---- TCP延时$3 ms \n" >> $LOG
}
#
OOKLA_SPEEDTEST='/usr/bin/speedtest'
# uci
CONFIG='netspeedtest'
NAMEDDSECTION='config'
get_config() {
config_get_bool enabled $1 enabled 1
config_get_bool logger $1 logger 1
config_load netspeedtest
config_get "proxy_enabled" "config" "proxy_enabled" "0"
}
homebox_prepare() {
pgrep -f homebox | xargs kill -9 >/dev/null 2>&1
killall homebox
killall homebox
download_ookla() {
local url arch=$1
[ -z "$arch" ] && return 1
[ "$(uci -q get $CONFIG.$NAMEDDSECTION.proxy_enabled)" == "1" ] && \
export ALL_PROXY=$(uci -q get $CONFIG.$NAMEDDSECTION.proxy_protocol)://$(uci -q get $CONFIG.$NAMEDDSECTION.proxy_server)
UA='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36'
url=$( \
curl --connect-timeout 10 --retry 3 -sSL 'https://www.speedtest.net/apps/cli' \
--user-agent "$UA" \
| grep "Download for Linux" \
| sed 's|<|\n<|g' \
| sed -n '/Download for Linux/,/<\/div>/p' \
| sed -En "s|.*<a href=\"([^\"]+)\">${arch}|\1|p" \
)
[ -z "$url" ] && return 1
[ -n "$url" ] && curl -sSL $url --user-agent "$UA" | tar -xvz -C /tmp
mkdir -p ${OOKLA_SPEEDTEST%/*} 2>/dev/null
cp -f /tmp/speedtest $OOKLA_SPEEDTEST
chmod 755 $OOKLA_SPEEDTEST
ookla_verify || rm -f $OOKLA_SPEEDTEST
unset ALL_PROXY
}
ookla_verify() {
if [ -x "$OOKLA_SPEEDTEST" ]; then
return 0
else
return 1
fi
}
start_service() {
config_load netspeedtest
config_foreach get_config netspeedtest
[ "x$enabled" != "x1" ] && {
homebox_prepare
exit
}
procd_open_instance
procd_set_param command $PROG
[ "x$logger" == x1 ] && procd_set_param stderr 1
# procd_set_param respawn
procd_close_instance
ookla_verify
}
service_triggers() {
procd_add_reload_trigger "netspeedtest"
}
service_triggers() {
procd_add_reload_trigger "netspeedtest"
reload_service() {
stop
sleep 1
start
}

View File

@ -1,37 +0,0 @@
#!/bin/sh
chmod +x /etc/init.d/netspeedtest >/dev/null 2>&1
[ -f "/etc/config/netspeedtest" ] || {
cat >/etc/config/netspeedtest <<-EOF
config netspeedtest 'netspeedtest'
option enabled '0'
option port '3300'
config homebox 'homebox'
option enabled '0'
option port '3300'
config speedtestiperf3 'speedtestiperf3'
config speedtestwan 'speedtestwan'
config speedtestport 'speedtestport'
option sport '443'
option sdomain 'www.taobao.com'
EOF
}
[ `uci -q get netspeedtest.global` ] && uci set netspeedtest.global=global
[ `uci -q get netspeedtest.netspeedtest` ] || uci set netspeedtest.netspeedtest=netspeedtest
[ `uci -q get netspeedtest.speedtestiperf3` ] || uci set netspeedtest.speedtestiperf3=speedtestiperf3
[ `uci -q get netspeedtest.speedtestport` ] || uci set netspeedtest.speedtestport=speedtestport
[ `uci -q get netspeedtest.speedtestwan` ] || uci set netspeedtest.speedtestport=speedtestwan
LOGD=/etc/netspeedtest
LOG=/var/log/netspeedtest.log
LOGE=$LOGD/netspeedtest.log
LOGT=$LOGD/netspeedtestpos
[ -d "$LOGD" ] || mkdir -p $LOGD
[ -f "$LOGE" ] || echo "start" > $LOGE
[ -f "$LOGT" ] || echo "1" > $LOGT
[ -f "$LOG" ] || echo "start" > $LOG
rm -rf /tmp/luci-modulecache /tmp/luci-indexcache*
exit 0

View File

@ -0,0 +1,6 @@
#!/bin/sh
touch /etc/config/netspeedtest
uci -q batch <<-EOF >/dev/null
set netspeedtest.config=netspeedtest
commit netspeedtest
EOF

View File

@ -1,98 +0,0 @@
################################################################################
# (netspeedtest) functions.sh
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# Copyright (C) 2019-2025 The Sirpdboy Team <herboy2008@gmail.com>
# . /lib/netspeedtest/functions.sh
################################################################################
# tmp speedtest
TMP_TEST=/var/netspeedtest.tmp
MAX_LOG=200
LOG=/var/log/netspeedtest.log
limit_log() {
local logf=$1
local max=$2
[ ! -f "$logf" ] && return
sc=${max:-$MAX_LOG}
local count=$(grep -c "" $logf)
if [ $count -gt $sc ];then
let count=count-$sc
sed -i "1,$count d" $logf
fi
}
init_log() {
local logf=$1
[ ! -f "$logf" ] && echo "" > $logf
}
echone() {
echo -ne $* >> $LOG
}
git_bin_handle()
{
case $1 in
0)
TMPDIR=/var/netspeedtest
BINSPEEDTEST=$TMPDIR/speedtest
[ ! -d $TMPDIR ] && mkdir -p $TMPDIR 2>/dev/null
if [ -x "$BINSPEEDTEST" ]; then
chmod 755 $BINSPEEDTEST >/dev/null 2>&1
echo "$BINSPEEDTEST --accept-gdpr --accept-license --progress=no"
else
ooklaurl=`curl --connect-timeout 5 -m 60 -sSL 'https://www.speedtest.net/apps/cli' | grep 'Download for Linux' | sed 's|<|\n<|g' | sed -n '/Download for Linux/,/<\/div>/p' | sed -En "s|.*<a href=\"([^\"]+)\">x86_64|\1|p" `
[ -n "$ooklaurl" ] && curl -sSL $ooklaurl | tar -xvz -C /tmp >/dev/null 2>&1 || return
mv /tmp/speedtest $BINSPEEDTEST >/dev/null 2>&1
chmod 755 $BINSPEEDTEST >/dev/null 2>&1
[ -x "$BINSPEEDTEST" ] && echo "$BINSPEEDTEST --accept-gdpr --accept-license --progress=no"
fi
;;
*)
BINSPEEDTEST='/usr/bin/speedtest'
[ -x "$BINSPEEDTEST" ] && echo "$BINSPEEDTEST --share"
;;
esac
}
speedtest_start(){
case $1 in
0)
speedtest_start_ookla $2 ;;
*)
speedtest_start_cli $2 ;;
esac
}
speedtest_start_ookla(){
echone "\n ookla-speedtest测速"
info=$($1 > $TMP_TEST ) >/dev/null 2>&1
echone "\n info:$info ----------------- "
echone "\n 测服信息:`cat $TMP_TEST | grep 'Server'| cut -c10- | awk -F: '{printf $2$3}'` 线路:`cat $TMP_TEST | grep 'ISP' | awk -F: '{printf $2}' ` 延时:`cat $TMP_TEST | grep 'Latency' | awk -F: '{printf $2}' | awk -F '(' '{printf $1}'`"
echone "\n 下行速率:`cat $TMP_TEST | grep 'Download' |awk -F: '{printf $2}' | awk -F '(' '{printf $1}'` --"
echone "-- 上行速率:`cat $TMP_TEST | grep 'Upload' |awk -F: '{printf $2}' | awk -F '(' '{printf $1}'`"
echo -ne "\n 测速结果图片链接:`cat $TMP_TEST | grep 'URL' | cut -c15-`" >> $LOG
echone "\n 测试时间: `date +%Y-%m-%d' '%H:%M:%S`"
echone "\n ————————————————————————————\n"
cat $TMP_TEST | grep 'URL' | cut -c15- > /var/speedtesturl.tmp
echo -ne "`cat $TMP_TEST | grep 'URL' | cut -c15- `"
}
speedtest_start_cli(){
echone "\n python3-speedtest测速"
info=$($1 > $TMP_TEST ) >/dev/null 2>&1
echone "\n 测服信息:`cat $TMP_TEST | grep 'Hosted by'| cut -c10- | awk -F: '{printf $1}'` 延时:`cat $TMP_TEST | grep 'Hosted by' | awk -F: '{printf $2}'`"
echone "\n 下行速率:`cat $TMP_TEST | grep 'Download:' |awk -F: '{printf $2}' ` --"
echone "-- 上行速率:`cat $TMP_TEST | grep 'Upload:' |awk -F: '{printf $2}' `"
echo -ne "\n 测速结果图片链接:`cat $TMP_TEST | grep 'results' | cut -c16-`" >> $LOG
echone "\n 测试时间: `date +%Y-%m-%d' '%H:%M:%S`"
echone "\n ————————————————————————————\n"
cat $TMP_TEST | grep 'results:' | cut -c16- > /var/speedtesturl.tmp
echo -ne "`cat $TMP_TEST | grep 'results:' | cut -c16-`"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
#!/bin/bash
mkdir -p /etc/speedtest
export HOME='/etc/speedtest'
SPEEDTEST_CLI='/usr/bin/speedtest'
SPEEDTEST_RESULT='/tmp/speedtest_result'
[ -n "$(pgrep -f "$SPEEDTEST_CLI")" ] && exit 1
echo "Start Testing" > "$SPEEDTEST_RESULT"
LOCAL_IP=$(curl -s -4 --connect-timeout 3 http://ip.3322.net)
BAIDU_SK="LHHGlmhcb4ENvIXpR9QQ2tBYa6ooUowX hYCENCEx1nXO0Nt46ldexfG9oI49xBGh 0kKZnWWhXEPfzIkklmzAa3dZ"
if [ -n "$LOCAL_IP" ]; then
for SK in $BAIDU_SK
do
INFO=$(curl -sk --connect-timeout 3 "https://api.map.baidu.com/location/ip?ip="$LOCAL_IP"&coor=bd09ll&ak=$SK")
if [ "$(echo $INFO | jsonfilter -e "@['status']")" = 0 ]; then
status=0
break
fi
done
if [ "$status" = 0 ]; then
lon=$(echo $INFO | jsonfilter -e "@['content']['point']['x']")
lat=$(echo $INFO | jsonfilter -e "@['content']['point']['y']")
server_id=$(curl -sk --connect-timeout 3 "https://www.speedtest.net/api/ios-config.php?lon=$lon&lat=$lat" | grep "server url" | head -n1 | sed 's/.*id="//;s/".*//')
[ -n "$server_id" ] && ARG="-s $server_id"
fi
fi
RUNTEST=$($SPEEDTEST_CLI --accept-gdpr --accept-license --progress=no $ARG 2>&1)
if [ $(echo $RUNTEST | grep -c "No servers defined") -ge 1 ] || [ $(echo $RUNTEST | grep -c "error") -ge 1 ]; then
RUNTEST=$($SPEEDTEST_CLI --accept-gdpr --accept-license --progress=no 2>&1)
fi
RESULT=$(echo "$RUNTEST" | grep "Result URL" | awk '{print $NF}')
[ -n "$RESULT" ] && echo "$RESULT" > "$SPEEDTEST_RESULT" || echo "Test failed" > "$SPEEDTEST_RESULT"

View File

@ -0,0 +1,45 @@
{
"admin/network/netspeedtest": {
"title": "NetSpeedtest",
"order": 90,
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-netspeedtest" ],
"uci": { "netspeedtest": true }
}
},
"admin/network/netspeedtest/iperf3": {
"title": "Lan Speedtest Iperf3",
"order": 1,
"action": {
"type": "view",
"path": "netspeedtest/iperf3"
}
},
"admin/network/netspeedtest/homebox": {
"title": "Lan Speedtest Homebox",
"order": 2,
"action": {
"type": "view",
"path": "netspeedtest/homebox"
}
},
"admin/network/netspeedtest/speedtest": {
"title": "Wan Ookla SpeedTest",
"order": 4,
"action": {
"type": "view",
"path": "netspeedtest/speedtest"
}
},
"admin/network/netspeedtest/openspeedtest": {
"title": "Wan OpenSpeedTest",
"order": 5,
"action": {
"type": "view",
"path": "netspeedtest/openspeedtest"
}
}
}

View File

@ -1,11 +1,19 @@
{
"luci-app-netspeedtest": {
"description": "Grant UCI access for luci-app-netspeedtest",
"read": {
"uci": [ "netspeedtest" ]
},
"write": {
"uci": [ "netspeedtest" ]
}
}
"luci-app-netspeedtest": {
"description": "Grant access to netspeedtest procedures",
"read": {
"file": {
"/etc/init.d/netspeedtest": [ "exec" ],
"/usr/lib/netspeedtest/speedtest": [ "exec" ],
"/tmp/speedtest_result": [ "read" ]
},
"ubus": {
"service": [ "list" ]
},
"uci": [ "netspeedtest" ]
},
"write": {
"uci": [ "netspeedtest" ]
}
}
}

View File

@ -544,6 +544,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
params += opt.query("sni", dom_prefix + "tls_serverName");
params += opt.query("alpn", dom_prefix + "tuic_alpn");
params += opt.query("congestion_control", dom_prefix + "tuic_congestion_control");
params += opt.query("udp_relay_mode", dom_prefix + "tuic_udp_relay_mode");
params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure");
params += "#" + encodeURI(v_alias.value);
@ -1437,6 +1438,7 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin
}
}
opt.set(dom_prefix + 'tuic_congestion_control', queryParam.congestion_control || 'cubic');
opt.set(dom_prefix + 'tuic_udp_relay_mode', queryParam.udp_relay_mode || 'native');
opt.set(dom_prefix + 'tuic_alpn', queryParam.alpn || 'default');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
opt.set(dom_prefix + 'tls_allowInsecure', true);

View File

@ -1323,6 +1323,7 @@ local function processData(szType, content, add_mode, add_from)
result.tls_serverName = params.sni
result.tuic_alpn = params.alpn or "default"
result.tuic_congestion_control = params.congestion_control or "cubic"
result.tuic_udp_relay_mode = params.udp_relay_mode or "native"
params.allowinsecure = params.allowinsecure or params.insecure
if params.allowinsecure then
if params.allowinsecure == "1" or params.allowinsecure == "0" then

View File

@ -49,6 +49,10 @@ Some features are deprecated / unstable so they are placed in preview app. To en
* Reboot your router
* There will be a new menu option `Xray (preview)` in `Services`
## Changelog since 3.6.0
* 2025-05-13 feat: geodata reader
## Changelog since 3.5.0
* 2024-11-26 chore: bump status version

View File

@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-xray
PKG_VERSION:=3.5.0
PKG_VERSION:=3.6.0
PKG_RELEASE:=1
PKG_LICENSE:=MPLv2

View File

@ -0,0 +1,42 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-xray-geodata
PKG_VERSION:=3.6.0
PKG_RELEASE:=1
PKG_LICENSE:=MPLv2
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=yichya <mail@yichya.dev>
PKG_BUILD_PARALLEL:=1
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=Custom
CATEGORY:=Extra packages
TITLE:=LuCI Support for Xray (geodata page)
DEPENDS:=luci-app-xray +xray-geodata
PKGARCH:=all
endef
define Package/$(PKG_NAME)/description
LuCI Support for Xray (Client-side Rendered) (geodata page).
endef
define Build/Compile
echo "luci-app-xray $(PKG_VERSION)-$(PKG_RELEASE) `git rev-parse HEAD` `date +%s`" > $(PKG_BUILD_DIR)/version.txt
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/xray
$(INSTALL_DATA) ./root/www/luci-static/resources/view/xray/geodata.js $(1)/www/luci-static/resources/view/xray/geodata.js
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-xray-geodata.json $(1)/usr/share/luci/menu.d/luci-app-xray-geodata.json
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-xray-geodata.json $(1)/usr/share/rpcd/acl.d/luci-app-xray-geodata.json
$(INSTALL_DIR) $(1)/www/xray
$(LN) /usr/share/xray/geoip.dat $(1)/www/xray/geoip.dat
$(LN) /usr/share/xray/geosite.dat $(1)/www/xray/geosite.dat
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

View File

@ -0,0 +1,17 @@
{
"admin/services/xray_geodata": {
"title": "Xray (geodata)",
"action": {
"type": "view",
"path": "xray/geodata"
},
"depends": {
"acl": [
"luci-app-xray-geodata"
],
"uci": {
"xray_core": true
}
}
}
}

View File

@ -0,0 +1,10 @@
{
"luci-app-xray-geodata": {
"description": "Grant access to xray configurations",
"read": {
"uci": [
"xray_core"
]
}
}
}

View File

@ -0,0 +1,460 @@
'use strict';
'require dom';
'require uci';
'require ui';
'require view';
'require view.xray.shared as shared';
const maxResults = 2048;
const WireType = {
VARINT: 0,
FIXED64: 1,
LENGTH_DELIMITED: 2,
FIXED32: 5
};
class ProtoReader {
constructor(buffer) {
this.buffer = new Uint8Array(buffer);
this.pos = 0;
}
readVarint() {
let result = 0;
let shift = 0;
while (this.pos < this.buffer.length) {
const byte = this.buffer[this.pos++];
result |= (byte & 0x7F) << shift;
if ((byte & 0x80) === 0) {
return result;
}
shift += 7;
}
throw new Error('Malformed varint');
}
readString() {
const length = this.readVarint();
const value = new TextDecoder().decode(
this.buffer.slice(this.pos, this.pos + length)
);
this.pos += length;
return value;
}
readBytes() {
const length = this.readVarint();
const bytes = this.buffer.slice(this.pos, this.pos + length);
this.pos += length;
return bytes;
}
readTag() {
const tag = this.readVarint();
return {
fieldNumber: tag >>> 3,
wireType: tag & 0x7
};
}
}
// Domain Type enum
const DomainType = {
Plain: 0,
Regex: 1,
Domain: 2,
Full: 3
};
function decodeDomainAttribute(reader) {
const attribute = {
key: '',
typedValue: null
};
while (reader.pos < reader.buffer.length) {
const tag = reader.readTag();
switch (tag.fieldNumber) {
case 1: // key
attribute.key = reader.readString();
break;
case 2: // bool_value
attribute.typedValue = reader.readVarint() !== 0;
break;
case 3: // int_value
attribute.typedValue = reader.readVarint();
break;
default:
throw new Error(`Unknown field number: ${tag.fieldNumber}`);
}
}
return attribute;
}
function decodeDomain(reader) {
const domain = {
type: DomainType.Plain,
value: '',
attribute: []
};
while (reader.pos < reader.buffer.length) {
const tag = reader.readTag();
switch (tag.fieldNumber) {
case 1: // type
domain.type = reader.readVarint();
break;
case 2: // value
domain.value = reader.readString();
break;
case 3: // attribute
const attrBytes = reader.readBytes();
domain.attribute.push(
decodeDomainAttribute(new ProtoReader(attrBytes))
);
break;
default:
throw new Error(`Unknown field number: ${tag.fieldNumber}`);
}
}
return domain;
}
function decodeCIDR(reader) {
const cidr = {
ip: new Uint8Array(),
prefix: 0
};
while (reader.pos < reader.buffer.length) {
const tag = reader.readTag();
switch (tag.fieldNumber) {
case 1: // ip
cidr.ip = reader.readBytes();
break;
case 2: // prefix
cidr.prefix = reader.readVarint();
break;
default:
throw new Error(`Unknown field number: ${tag.fieldNumber}`);
}
}
return cidr;
}
function decodeGeoIP(reader) {
const geoIP = {
countryCode: '',
cidr: [],
reverseMatch: false
};
while (reader.pos < reader.buffer.length) {
const tag = reader.readTag();
switch (tag.fieldNumber) {
case 1: // country_code
geoIP.countryCode = reader.readString();
break;
case 2: // cidr
const cidrBytes = reader.readBytes();
geoIP.cidr.push(
decodeCIDR(new ProtoReader(cidrBytes))
);
break;
case 3: // reverse_match
geoIP.reverseMatch = reader.readVarint() !== 0;
break;
default:
throw new Error(`Unknown field number: ${tag.fieldNumber}`);
}
}
return geoIP;
}
function decodeGeoSite(reader) {
const geoSite = {
countryCode: '',
domain: []
};
while (reader.pos < reader.buffer.length) {
const tag = reader.readTag();
switch (tag.fieldNumber) {
case 1: // country_code
geoSite.countryCode = reader.readString();
break;
case 2: // domain
const domainBytes = reader.readBytes();
geoSite.domain.push(
decodeDomain(new ProtoReader(domainBytes))
);
break;
default:
throw new Error(`Unknown field number: ${tag.fieldNumber}`);
}
}
return geoSite;
}
function decodeGeoSiteList(buffer) {
const reader = new ProtoReader(buffer);
const geoSiteList = {
entry: []
};
while (reader.pos < reader.buffer.length) {
const tag = reader.readTag();
if (tag.fieldNumber === 1) { // entry
const entryBytes = reader.readBytes();
geoSiteList.entry.push(
decodeGeoSite(new ProtoReader(entryBytes))
);
} else {
throw new Error(`Unknown field number: ${tag.fieldNumber}`);
}
}
return geoSiteList;
}
function decodeGeoIPList(buffer) {
const reader = new ProtoReader(buffer);
const geoIPList = {
entry: []
};
while (reader.pos < reader.buffer.length) {
const tag = reader.readTag();
if (tag.fieldNumber === 1) { // entry
const entryBytes = reader.readBytes();
geoIPList.entry.push(
decodeGeoIP(new ProtoReader(entryBytes))
);
} else {
throw new Error(`Unknown field number: ${tag.fieldNumber}`);
}
}
return geoIPList;
}
function matchesCIDR(cidrIp, prefix, queryIp) {
// Check if input IP matches CIDR IP version
if ((cidrIp.length === 4 && queryIp.length !== 4) || (cidrIp.length === 16 && queryIp.length !== 16)) {
return false;
}
// Compare full bytes first
const prefixBytes = Math.floor(prefix / 8);
for (let i = 0; i < prefixBytes; i++) {
if (cidrIp[i] !== queryIp[i]) return false;
}
// Compare remaining bits if any
const remainingBits = prefix % 8;
if (remainingBits > 0) {
const mask = 0xFF << (8 - remainingBits);
return (cidrIp[prefixBytes] & mask) === (queryIp[prefixBytes] & mask);
}
return true;
}
function renderGeoIPResults(results) {
const container = document.getElementById('geoip-results');
container.innerHTML = '<br/>';
const flatResults = results.flatMap(entry => entry.cidr.map(cidr => ({ entry, cidr })));
const table = E('table', { 'class': 'table' }, [
E('thead', {}, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Country Code')),
E('th', { 'class': 'th' }, _(`CIDR (${flatResults.length} total)`))
])
]),
E('tbody', {}, flatResults.slice(0, maxResults).map(({ entry, cidr }, index) => {
const ip = cidr.ip.length === 4 ? Array.from(cidr.ip).join('.') : Array.from(cidr.ip).reduce((arr, byte, i) => {
if (i % 2 === 0) {
arr.push((byte << 8) | cidr.ip[i + 1]);
}
return arr;
}, []).map(part => part.toString(16).padStart(4, '0')).join(':').replace(/\b(?:0+:){2,}/, ':').split(':').map(octet => octet.replace(/\b0+/g, '')).join(':');
return E('tr', { 'class': `tr cbi-rowstyle-${index % 2 + 1}` }, [
E('td', { 'class': 'td' }, entry.countryCode),
E('td', { 'class': 'td' }, `${ip}/${cidr.prefix}`)
]);
}))
]);
container.appendChild(table);
}
function renderGeoSiteResults(results) {
const container = document.getElementById('geosite-results');
container.innerHTML = '<br/>';
const flatResults = results.flatMap(entry => entry.domain.map(domain => ({ entry, domain })));
const table = E('table', { 'class': 'table' }, [
E('thead', {}, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Country Code')),
E('th', { 'class': 'th' }, _(`Domain (${flatResults.length} total)`))
])
]),
E('tbody', {}, flatResults.slice(0, maxResults).map(({ entry, domain }, index) =>
E('tr', { 'class': `tr cbi-rowstyle-${index % 2 + 1}` }, [
E('td', { 'class': 'td' }, entry.countryCode),
E('td', { 'class': 'td' }, domain.value)
])
))
]);
container.appendChild(table);
}
return view.extend({
load: function () {
return Promise.all([
new Date(),
uci.load(shared.variant),
fetch("/xray/geoip.dat").then(v => v.arrayBuffer()),
fetch("/xray/geosite.dat").then(v => v.arrayBuffer()),
]);
},
render: function (load_result) {
const geoip_result = decodeGeoIPList(load_result[2]);
const geosite_result = decodeGeoSiteList(load_result[3]);
const result = E([], {}, [
E('div', {}, [
E('div', { 'class': 'cbi-section', 'data-tab': 'geoip', 'data-tab-title': _('GeoIP') }, [
E('select', {
'id': 'geoip-select',
'change': function () {
const selectedCode = document.getElementById('geoip-select').value;
const results = selectedCode ? geoip_result.entry.filter(entry => entry.countryCode === selectedCode) : [];
renderGeoIPResults(results);
}
}, [
E('option', { 'value': '' }, _('Filter GeoIP by Country Code')),
...Array.from(new Set(geoip_result.entry.map(entry => entry.countryCode)))
.sort()
.map(code => {
const count = geoip_result.entry
.find(entry => entry.countryCode === code)
.cidr.length;
return E('option', { 'value': code }, `${code} (${count} items)`);
})
]),
E('div', { 'class': 'cbi-section-create' }, [
E('input', {
'type': 'text',
'id': 'geoip-search',
'class': 'cbi-input-text',
'placeholder': _('Search GeoIP...')
}),
E('button', {
'class': 'cbi-button',
'click': function () {
const query = document.getElementById('geoip-search').value.trim();
let queryIp = null;
if (query.includes('.')) {
queryIp = query.split('.').map(Number);
} else if (query.includes(':')) {
queryIp = query.split(':').reduce((acc, part, i, arr) => {
if (part === '') {
const padding = new Array((8 - arr.filter(x => x !== '').length) * 2).fill(0);
return acc.concat(padding);
}
const hex = part.padStart(4, '0');
return acc.concat([parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16)]);
}, []);
console.log(queryIp);
}
const selectedCode = document.getElementById('geoip-select').value;
const results = geoip_result.entry.map(entry => ({
...entry,
cidr: entry.cidr.filter(cidr => {
return queryIp && (selectedCode === '' || entry.countryCode === selectedCode) && matchesCIDR(cidr.ip, cidr.prefix, queryIp);
})
})).filter(entry => entry.cidr.length > 0);
renderGeoIPResults(results);
}
}, _('Search GeoIP')),
]),
E('div', { 'id': 'geoip-results', 'class': 'results-container' }),
]),
E('div', { 'class': 'cbi-section', 'data-tab': 'geosite', 'data-tab-title': _('GeoSite') }, [
E('select', {
'id': 'geosite-select',
'change': function () {
const selectedCode = document.getElementById('geosite-select').value;
const results = selectedCode ? geosite_result.entry.filter(entry => entry.countryCode === selectedCode) : [];
renderGeoSiteResults(results);
}
}, [
E('option', { 'value': '' }, _('Filter GeoSite by Country Code')),
...Array.from(new Set(geosite_result.entry.map(entry => entry.countryCode)))
.sort()
.map(code => {
const count = geosite_result.entry
.find(entry => entry.countryCode === code)
.domain.length;
return E('option', { 'value': code }, `${code} (${count} items)`);
})
]),
E('div', { 'class': 'cbi-section-create' }, [
E('input', {
'type': 'text',
'id': 'geosite-search',
'class': 'cbi-input-text',
'placeholder': _('Search GeoSite...')
}),
E('button', {
'class': 'cbi-button',
'click': function () {
const query = document.getElementById('geosite-search').value.toLowerCase();
const selectedCode = document.getElementById('geosite-select').value;
const results = geosite_result.entry.map(entry => ({
...entry,
domain: entry.domain.filter(domain => {
return query && (selectedCode === '' || entry.countryCode === selectedCode) && domain.value.toLowerCase().includes(query);
})
})).filter(entry => entry.domain.length > 0);
renderGeoSiteResults(results);
}
}, _('Search GeoSite')),
]),
E('div', { 'id': 'geosite-results', 'class': 'results-container' })
]),
])
]);
ui.tabs.initTabGroup(result.lastElementChild.childNodes);
return E([], [
E('h2', _('Xray (geodata)')),
E('p', { 'class': 'cbi-map-descr' }, `${_("Only first")} ${maxResults} ${_("results will be shown. Load GeoData files cost")} ${new Date().getTime() - load_result[0].getTime()} ${_("ms")}; ${geoip_result.entry.length} ${_("GeoIP entries")}, ${geosite_result.entry.length} ${_("GeoSite entries")}.`),
result
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-xray-status
PKG_VERSION:=3.5.0
PKG_VERSION:=3.6.0
PKG_RELEASE:=1
PKG_LICENSE:=MPLv2

View File

@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=mihomo
PKG_VERSION:=1.19.7
PKG_VERSION:=1.19.8
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/metacubex/mihomo/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=b857289776f6ecfedaaca2cdfd9ed39746fda697bdcbbbb0b1bb172f1fc9462d
PKG_HASH:=47e38ea4220f5b84485b06d980ff06cc9a48cd7bd9bdf1236168adf728a99835
PKG_MAINTAINER:=Anya Lin <hukk1996@gmail.com>
PKG_LICENSE:=GPL-2.0

61
speedtest-cli/Makefile Normal file
View File

@ -0,0 +1,61 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=speedtest-cli
PKG_VERSION:=1.2.0
PKG_RELEASE:=1
ifeq ($(ARCH),aarch64)
PKG_HASH:=3953d231da3783e2bf8904b6dd72767c5c6e533e163d3742fd0437affa431bd3
else ifeq ($(ARCH),arm)
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
ifeq ($(ARM_CPU_FEATURES),)
ARCH:=armel
PKG_HASH:=629a455a2879224bd0dbd4b36d8c721dda540717937e4660b4d2c966029466bf
else
ARCH:=armhf
PKG_HASH:=e45fcdebbd8a185553535533dd032d6b10bc8c64eee4139b1147b9c09835d08d
endif
else ifeq ($(ARCH),i386)
PKG_HASH:=9ff7e18dbae7ee0e03c66108445a2fb6ceea6c86f66482e1392f55881b772fe8
else ifeq ($(ARCH),x86_64)
PKG_HASH:=5690596c54ff9bed63fa3732f818a05dbc2db19ad36ed68f21ca5f64d5cfeeb7
endif
PKG_SOURCE:=ookla-speedtest-$(PKG_VERSION)-linux-$(ARCH).tgz
PKG_SOURCE_URL:=https://install.speedtest.net/app/cli
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
PKG_MAINTAINER:=sbwml <admin@cooluc.com>
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
TITLE:=Speedtest CLI by Ookla
DEPENDS:=@(aarch64||arm||i386||x86_64) +ca-bundle
URL:=https://www.speedtest.net/
endef
define Package/$(PKG_NAME)/description
The Global Broadband Speed Test
endef
define Build/Prepare
( \
pushd $(PKG_BUILD_DIR) ; \
$(TAR) -zxf $(DL_DIR)/ookla-speedtest-$(PKG_VERSION)-linux-$(ARCH).tgz -C . ; \
popd ; \
)
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/speedtest $(1)/usr/bin
endef
$(eval $(call BuildPackage,$(PKG_NAME)))