mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-26 23:12:52 +08:00
🚀 Sync 2026-03-08 00:54:34
This commit is contained in:
1 parent
97d524986e
commit
5997aee442
22 files changed
+3954
-28
No files matched your search
@@ -0,0 +1,20 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 sirpdboy
|
||||
|
||||
Hereby grant a license free of charge to anyone who obtains copies
|
||||
This software and related documentation files ("Software") are used to process
|
||||
Unrestricted in software, including but not limited to rights
|
||||
Use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
Copy of the software and allow the recipient of the software
|
||||
Provide based on the following conditions:
|
||||
|
||||
The above copyright statement and this license statement should be included in all
|
||||
Copy or substantial part of the software.
|
||||
|
||||
The software is provided "as is" without any form of express or warranty
|
||||
Implied, including but not limited to warranties of merchantability,
|
||||
Suitable for specific purposes without causing harm. In any case
|
||||
The author or copyright holder shall be liable for any claims, damages, or other damages
|
||||
Liability arising from the following reasons, whether in contract, tort or other litigation,
|
||||
For or related to software, usage, or other transactions.
|
||||
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com>
|
||||
# https://github.com/sirpdboy/luci-app-netspeedtest
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-netspeedtest
|
||||
|
||||
PKG_VERSION:=5.1.2
|
||||
PKG_RELEASE:=1
|
||||
|
||||
LUCI_TITLE:=LuCI Support for netspeedtest
|
||||
LUCI_DEPENDS:=+ookla-speedtest +homebox +python3-light +python3-pkg-resources +python3-xml +python3-email \
|
||||
+python3-urllib +python3-codecs +python3-openssl +jq $(if $(find_package iperf3-ssl),+iperf3-ssl,+iperf3)
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_MAINTAINER:=sirpdboy <herboy2008@gmail.com>
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -0,0 +1,165 @@
|
||||
/* Copyright (C) 2021-2026 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 isHttps = window.location.protocol === 'https:';
|
||||
var iframe;
|
||||
|
||||
if (!isHttps) {
|
||||
iframe = E('iframe', {
|
||||
src: window.location.origin + ':' + state.port,
|
||||
style: 'border:none;width: 100%; min-height: 80vh; border: none; border-radius: 3px;overflow:hidden !important;'
|
||||
});
|
||||
}
|
||||
|
||||
function createHttpsButton() {
|
||||
return E('div', {
|
||||
style: 'text-align: center; padding: 2em;'
|
||||
}, [
|
||||
E('h2', {}, _('Homebox Control panel')),
|
||||
E('p', {}, _('Due to browser security policies, the Homebox interface https cannot be embedded directly.')),
|
||||
E('a', {
|
||||
href: 'http://' + window.location.hostname + ':' + state.port,
|
||||
target: '_blank',
|
||||
class: 'cbi-button cbi-button-apply',
|
||||
style: 'display: inline-block; margin-top: 1em; padding: 10px 20px; font-size: 16px; text-decoration: none; color: white;'
|
||||
}, _('Open Web Interface'))
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
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'
|
||||
? 'nohup /usr/bin/homebox >> /tmp/netspeedtest.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 and protocol
|
||||
container.textContent = '';
|
||||
if (state.running) {
|
||||
if (isHttps) {
|
||||
container.appendChild(createHttpsButton());
|
||||
} else {
|
||||
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', {}, _('Throughput 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',
|
||||
'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
|
||||
});
|
||||
@@ -0,0 +1,168 @@
|
||||
/* Copyright (C) 2021-2026 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 = '/tmp/netspeedtest.log';
|
||||
|
||||
async function checkProcess() {
|
||||
try {
|
||||
const res = await fs.exec('/usr/bin/pgrep', ['iperf3']);
|
||||
if (res.code === 0 && res.stdout.trim()) {
|
||||
return { running: true, pid: res.stdout.trim() };
|
||||
}
|
||||
|
||||
// 回退检查
|
||||
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) {
|
||||
console.error('Process check error:', err);
|
||||
return { running: false, pid: null };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function controlService(action) {
|
||||
const commands = {
|
||||
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', `mkdir -p /tmp/netspeedtest && touch ${logPath} && chmod 644 ${logPath}`])
|
||||
: Promise.resolve()
|
||||
).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,
|
||||
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';
|
||||
toggleBtn.disabled = true; // 禁用按钮
|
||||
|
||||
return controlService(action)
|
||||
.then(() => checkProcess())
|
||||
.then(res => {
|
||||
state.running = res.running;
|
||||
updateStatus();
|
||||
toggleBtn.disabled = false; // 恢复按钮
|
||||
})
|
||||
.catch(err => {
|
||||
ui.addNotification(null, E('p', _('Error: ') + err.message), 'error');
|
||||
toggleBtn.disabled = false; // 出错时也要恢复按钮
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
|
||||
// 构建UI
|
||||
const statusSection = E('div', { 'class': 'cbi-section' }, [
|
||||
E('div', { 'style': 'margin: 15px' }, [
|
||||
E('h3', {}, _('Throughput 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);
|
||||
});
|
||||
|
||||
|
||||
return statusSection;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
/* Copyright (C) 2021-2026 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-netspeedtest */
|
||||
'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 = '/tmp/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: 20px; margin-top: 10px;',
|
||||
'value': _('Clear logs')
|
||||
});
|
||||
clear_logs_button.addEventListener('click', this.handleCleanLogs.bind(this));
|
||||
|
||||
return 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; margin-top: 10px;' }, [
|
||||
E('span', {}, [
|
||||
_('© github '),
|
||||
E('a', {
|
||||
'href': 'https://github.com/sirpdboy',
|
||||
'target': '_blank',
|
||||
'style': 'text-decoration: none;'
|
||||
}, 'by sirpdboy')
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
// handleSaveApply: null,
|
||||
// handleSave: null,
|
||||
// handleReset: null
|
||||
});
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
/* Copyright (C) 2021-2026 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-netspeedtest */
|
||||
'use strict';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require form';
|
||||
|
||||
return view.extend({
|
||||
|
||||
load() {
|
||||
return Promise.all([
|
||||
uci.load('netspeedtest')
|
||||
]);
|
||||
},
|
||||
|
||||
render(res) {
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('netspeedtest', _('Online SpeedTest'));
|
||||
|
||||
let currentSpeedTestUrl = uci.get('netspeedtest', 'config', 'speedtest_site') || 'https://plugin.speedtest.cn/taste#/?t=1767018285493';
|
||||
if (!currentSpeedTestUrl) {
|
||||
currentSpeedTestUrl = 'https://plugin.speedtest.cn/taste#/?t=1767018285493';
|
||||
uci.set('netspeedtest', 'config', 'speedtest_site', currentSpeedTestUrl);
|
||||
uci.save();
|
||||
}
|
||||
|
||||
s = m.section(form.NamedSection, 'config', 'netspeedtest');
|
||||
s.anonymous = true;
|
||||
|
||||
// 下拉选择
|
||||
o = s.option(form.DummyValue, '_speedtest_select');
|
||||
o.textvalue = function() {
|
||||
return '';
|
||||
};
|
||||
o.render = function(section_id) {
|
||||
const sites = [
|
||||
{url: 'https://plugin.speedtest.cn/taste#/?t=1767018285493', name: 'speedtest.cn'},
|
||||
{url: 'https://static.hdslb.com/', name: 'hdslb.com'},
|
||||
{url: 'https://test.ustc.edu.cn/', name: 'ustc.edu.cn'},
|
||||
{url: 'https://www1.szu.edu.cn/nc/speedtest/', name: 'szu.edu.cn'},
|
||||
{url: 'https://10000.gd.cn/#/speed', name: 'gd.cn'},
|
||||
{url: 'https://speed.cloudflare.com/', name: 'cloudflare.com'},
|
||||
{url: 'https://fast.com/', name: 'Netflix fast.com'},
|
||||
{url: '//openspeedtest.com/speedtest', name: 'openspeedtest.com'}
|
||||
];
|
||||
|
||||
const container = E('div', {
|
||||
class: 'cbi-value'
|
||||
});
|
||||
|
||||
const label = E('label', {
|
||||
class: 'cbi-value-title',
|
||||
for: 'speedtest-site-select'
|
||||
}, _('Select speed measurement station'));
|
||||
|
||||
const field = E('div', {
|
||||
class: 'cbi-value-field',
|
||||
});
|
||||
|
||||
const select = E('select', {
|
||||
id: 'speedtest-site-select',
|
||||
class: 'cbi-input-select'
|
||||
});
|
||||
|
||||
sites.forEach(site => {
|
||||
const option = E('option', {
|
||||
value: site.url,
|
||||
selected: currentSpeedTestUrl === site.url ? 'selected' : null
|
||||
}, site.name);
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
function saveConfig(url) {
|
||||
try {
|
||||
uci.set('netspeedtest', 'config', 'speedtest_site', url);
|
||||
uci.save();
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
select.addEventListener('change', function() {
|
||||
const iframe = document.getElementById('speedtest-iframe');
|
||||
if (iframe && this.value) {
|
||||
currentSpeedTestUrl = this.value;
|
||||
iframe.src = this.value;
|
||||
saveConfig(this.value);
|
||||
}
|
||||
});
|
||||
|
||||
field.appendChild(select);
|
||||
container.appendChild(label);
|
||||
container.appendChild(field);
|
||||
|
||||
const saveStatus = E('small', {
|
||||
id: 'save-status',
|
||||
style: 'display: block; margin-top: 5px; color: #28a745; opacity: 0; transition: opacity 0.3s;'
|
||||
});
|
||||
|
||||
field.appendChild(saveStatus);
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
s = m.section(form.NamedSection, '_iframe');
|
||||
s.anonymous = true;
|
||||
|
||||
s.render = function(section_id) {
|
||||
const container = E('div', {
|
||||
class: 'speedtest-wrapper mobiliframe',
|
||||
style: 'width:100%;height:550px;position:relative;overflow:hidden;margin-top:20px;'
|
||||
});
|
||||
const header = E('div', {
|
||||
style: 'display:flex;justify-content:space-between;align-items:center;padding: 0.5rem 1rem;'
|
||||
}, [
|
||||
E('span', {
|
||||
style: 'font-weight:bold;'
|
||||
}, _('NetSpeedtest')),
|
||||
]);
|
||||
|
||||
const iframeContainer = E('div', {
|
||||
style: 'height: calc(100% - 33px);'
|
||||
});
|
||||
|
||||
const iframe = E('iframe', {
|
||||
id: 'speedtest-iframe',
|
||||
src: currentSpeedTestUrl,
|
||||
style: 'width:100%;height:100%;border:none;background: #fff'
|
||||
});
|
||||
|
||||
iframeContainer.appendChild(iframe);
|
||||
|
||||
const style = E('style', {}, `
|
||||
.speedtest-wrapper {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.speedtest-wrapper:hover {
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
#save-status {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.speedtest-wrapper {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
|
||||
.speedtest-wrapper > div:first-child {
|
||||
background: #3d3d3d;
|
||||
border-color: #555;
|
||||
}
|
||||
|
||||
.speedtest-wrapper > div:first-child span {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.speedtest-wrapper {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.cbi-value-title {
|
||||
width: 100% !important;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.cbi-value-field {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#speedtest-site-select {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
container.appendChild(header);
|
||||
container.appendChild(iframeContainer);
|
||||
container.appendChild(style);
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
return m.render();
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@@ -0,0 +1,481 @@
|
||||
// Copyright (C) 2019-2026 sirpdboy
|
||||
// Fixed version - better Ookla result parsing
|
||||
|
||||
'use strict';
|
||||
'require view';
|
||||
'require poll';
|
||||
'require dom';
|
||||
'require fs';
|
||||
'require uci';
|
||||
'require ui';
|
||||
'require form';
|
||||
|
||||
var Timeout = 300 * 1000;
|
||||
var ResultFile = '/tmp/netspeedtest_result';
|
||||
var SpeedtestScript = '/usr/bin/netspeedtest.sh';
|
||||
|
||||
return view.extend({
|
||||
load() {
|
||||
return Promise.all([
|
||||
L.resolveDefault(fs.stat('/usr/bin/ookla-speedtest'), {}),
|
||||
L.resolveDefault(fs.stat('/usr/bin/speedtest'), {}),
|
||||
L.resolveDefault(fs.read(ResultFile), null),
|
||||
L.resolveDefault(fs.stat(ResultFile), {}),
|
||||
uci.load('netspeedtest')
|
||||
]);
|
||||
},
|
||||
|
||||
// 检测可用版本
|
||||
detectVersions: function(res) {
|
||||
var hasOokla = !!(res[0] && res[0].path);
|
||||
var hasPython = !!(res[1] && res[1].path);
|
||||
|
||||
return {
|
||||
ookla: hasOokla,
|
||||
python: hasPython,
|
||||
available: hasOokla || hasPython
|
||||
};
|
||||
},
|
||||
|
||||
// 检查是否真正在测试中(文件新鲜且内容为Testing)
|
||||
isTesting: function(resultContent, resultMtime) {
|
||||
if (!resultContent || resultContent.length === 0) return false;
|
||||
if (resultContent[0].trim() !== 'Testing') return false;
|
||||
|
||||
// 检查文件是否在超时时间内
|
||||
var fileAge = Date.now() - resultMtime;
|
||||
return fileAge < Timeout;
|
||||
},
|
||||
|
||||
// 解析结果内容
|
||||
parseResult: function(content) {
|
||||
if (!content || content.length === 0) {
|
||||
return { type: 'none', data: null };
|
||||
}
|
||||
|
||||
var firstLine = content[0].trim();
|
||||
|
||||
// 检查是否是Testing状态
|
||||
if (firstLine === 'Testing') {
|
||||
return { type: 'testing', data: null };
|
||||
}
|
||||
|
||||
// 检查是否是失败状态
|
||||
if (firstLine === 'Test failed') {
|
||||
return { type: 'failed', data: null };
|
||||
}
|
||||
|
||||
// 检查是否包含Result URL (Ookla格式)
|
||||
var resultUrl = null;
|
||||
for (var i = 0; i < content.length; i++) {
|
||||
var line = content[i];
|
||||
if (line.match(/Result URL:/i)) {
|
||||
var urlMatch = line.match(/https?:\/\/[^\s]+/);
|
||||
if (urlMatch) {
|
||||
resultUrl = urlMatch[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resultUrl) {
|
||||
return { type: 'url', data: resultUrl };
|
||||
}
|
||||
|
||||
// 检查是否是直接URL (Python格式)
|
||||
if (firstLine.match(/^https?:\/\//) || firstLine.match(/\.png$/)) {
|
||||
return { type: 'url', data: firstLine };
|
||||
}
|
||||
|
||||
// 检查是否是速度数据
|
||||
if (firstLine.match(/Download:/i) || firstLine.match(/Upload:/i)) {
|
||||
return { type: 'speed', data: content.join('\n') };
|
||||
}
|
||||
|
||||
// 其他情况,可能是完整的速度测试结果
|
||||
// 检查是否包含Download和Upload信息
|
||||
var hasDownload = false;
|
||||
var hasUpload = false;
|
||||
for (var j = 0; j < content.length; j++) {
|
||||
if (content[j].match(/Download:/i)) hasDownload = true;
|
||||
if (content[j].match(/Upload:/i)) hasUpload = true;
|
||||
}
|
||||
|
||||
if (hasDownload && hasUpload) {
|
||||
return { type: 'speed', data: content.join('\n') };
|
||||
}
|
||||
|
||||
return { type: 'unknown', data: content.join('\n') };
|
||||
},
|
||||
|
||||
poll_status(nodes, res) {
|
||||
var result_content = res[2] ? res[2].trim().split("\n") : [];
|
||||
var result_mtime = res[3] ? res[3].mtime * 1000 : 0;
|
||||
|
||||
var result_stat = nodes.querySelector('#speedtest_result');
|
||||
var start_btn = nodes.querySelector('.cbi-button-apply');
|
||||
|
||||
// 解析结果
|
||||
var result = this.parseResult(result_content);
|
||||
var is_testing = this.isTesting(result_content, result_mtime);
|
||||
|
||||
|
||||
// 更新测试按钮状态
|
||||
if (start_btn) {
|
||||
start_btn.disabled = is_testing;
|
||||
}
|
||||
|
||||
// 更新结果状态
|
||||
if (result_stat) {
|
||||
if (is_testing) {
|
||||
result_stat.innerHTML = "<span style='color:green;font-weight:bold;margin-left:20px'>" +
|
||||
"<img src='/luci-static/resources/icons/loading.svg' height='17' style='vertical-align:middle; margin-right:5px'/> " +
|
||||
_('Speed testing in progress...') +
|
||||
"</span>";
|
||||
return;
|
||||
}
|
||||
|
||||
switch(result.type) {
|
||||
case 'failed':
|
||||
result_stat.innerHTML = "<span style='color:red;font-weight:bold;margin-left:20px'>" +
|
||||
"⚠️ " + _('Test failed. Please check log for details.') +
|
||||
"</span>";
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
var imageUrl = result.data;
|
||||
if (!imageUrl.match(/\.png$/)) {
|
||||
imageUrl = imageUrl + '.png';
|
||||
}
|
||||
// 处理speedtest.net的特殊URL
|
||||
if (imageUrl.includes('speedtest.net/result/')) {
|
||||
imageUrl = imageUrl.replace('/result/c/', '/result/');
|
||||
}
|
||||
|
||||
result_stat.innerHTML = "<div style='max-width:500px; margin-left:20px'>" +
|
||||
"<a href='" + result.data + "' target='_blank'>" +
|
||||
"<img src='" + result.data + "' style='max-width:100%; border-radius:4px; box-shadow:0 2px 4px rgba(0,0,0,0.1)' " +
|
||||
"onerror='this.onerror=null; this.src=\"/luci-static/resources/icons/error.png\"'/>" +
|
||||
"</a><br>" +
|
||||
"</div>";
|
||||
break;
|
||||
|
||||
case 'speed':
|
||||
var lines = result.data.split('\n');
|
||||
var download = '', upload = '', latency = '', packetLoss = '', server = '', isp = '';
|
||||
|
||||
lines.forEach(function(line) {
|
||||
if (line.match(/Download:/i)) download = line;
|
||||
else if (line.match(/Upload:/i)) upload = line;
|
||||
else if (line.match(/Latency:/i) || line.match(/Idle Latency:/i)) latency = line;
|
||||
else if (line.match(/Packet Loss:/i)) packetLoss = line;
|
||||
else if (line.match(/Server:/i)) server = line;
|
||||
else if (line.match(/ISP:/i)) isp = line;
|
||||
});
|
||||
|
||||
var html = "<div style='margin-left:20px; padding:10px; background:#f5f5f5; border-radius:4px'>";
|
||||
html += "<strong>" + _('Test Results:') + "</strong>";
|
||||
|
||||
if (server) {
|
||||
html += "<div style='margin-top:5px; padding:5px; color:#666'>" +
|
||||
server.trim() + "</div>";
|
||||
}
|
||||
|
||||
if (download) {
|
||||
html += "<div style='margin-top:10px; padding:8px; background:#e8f5e8; border-radius:4px'>" +
|
||||
"<span style='font-weight:bold'>⬇️ " + _('Download:') + "</span> " +
|
||||
"<span style='color:#2e7d32; font-weight:500'>" + download.replace(/Download:/i, '').trim() + "</span>" +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
if (upload) {
|
||||
html += "<div style='margin-top:5px; padding:8px; background:#e3f2fd; border-radius:4px'>" +
|
||||
"<span style='font-weight:bold'>⬆️ " + _('Upload:') + "</span> " +
|
||||
"<span style='color:#1565c0; font-weight:500'>" + upload.replace(/Upload:/i, '').trim() + "</span>" +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
if (latency) {
|
||||
html += "<div style='margin-top:5px; padding:5px; background:#f5f5f5; border-radius:4px'>" +
|
||||
"<span style='font-weight:bold'>⏱️ " + _('Latency:') + "</span> " +
|
||||
"<span style='color:#666'>" + latency.replace(/Latency:/i, '').replace(/Idle Latency:/i, '').trim() + "</span>" +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
if (packetLoss) {
|
||||
html += "<div style='margin-top:5px; padding:5px; color:#666'>" +
|
||||
packetLoss.trim() + "</div>";
|
||||
}
|
||||
|
||||
html += "</div>";
|
||||
result_stat.innerHTML = html;
|
||||
break;
|
||||
|
||||
case 'unknown':
|
||||
// 未知格式,直接显示
|
||||
result_stat.innerHTML = "<div style='margin-left:20px; padding:10px; background:#f5f5f5; border-radius:4px'>" +
|
||||
"<strong>" + _('Test Results:') + "</strong><br>" +
|
||||
"<pre style='margin:5px 0 0 0; font-family:inherit; white-space:pre-wrap; color:#2e7d32'>" +
|
||||
escapeHTML(result.data) + "</pre>" +
|
||||
"</div>";
|
||||
break;
|
||||
|
||||
default:
|
||||
// 无结果
|
||||
result_stat.innerHTML = "<span style='color:gray;margin-left:20px'>" +
|
||||
"<em>" + _('No test results yet. Click "Start Speed Test" to begin.') + "</em>" +
|
||||
"</span>";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render(res) {
|
||||
var self = this;
|
||||
var has_ookla = !!(res[0] && res[0].path);
|
||||
var has_python = !!(res[1] && res[1].path);
|
||||
var result_content = res[2] ? res[2].trim().split("\n") : [];
|
||||
var result_mtime = res[3] ? res[3].mtime * 1000 : 0;
|
||||
|
||||
var versions = this.detectVersions(res);
|
||||
var result = this.parseResult(result_content);
|
||||
var is_testing = this.isTesting(result_content, result_mtime);
|
||||
|
||||
var m, s, o;
|
||||
m = new form.Map('netspeedtest', _('WAN SpeedTest'));
|
||||
|
||||
// 结果显示区域
|
||||
s = m.section(form.TypedSection, '_result');
|
||||
s.anonymous = true;
|
||||
s.render = function(section_id) {
|
||||
var result_id = 'speedtest_result';
|
||||
|
||||
if (is_testing) {
|
||||
return E('div', { id: result_id, class: 'cbi-section' }, [
|
||||
E('span', { style: 'color:green;font-weight:bold;margin-left:20px' }, [
|
||||
E('img', {
|
||||
src: '/luci-static/resources/icons/loading.svg',
|
||||
height: '17',
|
||||
style: 'vertical-align:middle; margin-right:5px'
|
||||
}),
|
||||
_('Speed testing in progress...')
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
switch(result.type) {
|
||||
case 'failed':
|
||||
return E('div', { id: result_id, class: 'cbi-section' }, [
|
||||
E('span', { style: 'color:red;font-weight:bold;margin-left:20px' }, [
|
||||
'⚠️ ' + _('Test failed. Please check log for details.')
|
||||
])
|
||||
]);
|
||||
|
||||
case 'url':
|
||||
var imageUrl = result.data;
|
||||
if (!imageUrl.match(/\.png$/)) {
|
||||
imageUrl = imageUrl + '.png';
|
||||
}
|
||||
if (imageUrl.includes('speedtest.net/result/')) {
|
||||
imageUrl = imageUrl.replace('/result/c/', '/result/');
|
||||
}
|
||||
|
||||
return E('div', { id: result_id, class: 'cbi-section' }, [
|
||||
E('div', { style: 'max-width:500px; margin-left:20px' }, [
|
||||
E('a', { href: result.data, target: '_blank' }, [
|
||||
E('img', {
|
||||
src: imageUrl,
|
||||
style: 'max-width:100%; border-radius:4px; box-shadow:0 2px 4px rgba(0,0,0,0.1)'
|
||||
})
|
||||
]),
|
||||
E('br'),
|
||||
E('small', {}, [
|
||||
E('a', {
|
||||
href: result.data,
|
||||
target: '_blank',
|
||||
style: 'display:inline-block; margin-top:5px; color:#0066cc'
|
||||
}, _('View detailed results'))
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
case 'speed':
|
||||
var lines = result.data.split('\n');
|
||||
var download = '', upload = '', latency = '';
|
||||
|
||||
lines.forEach(function(line) {
|
||||
if (line.match(/Download:/i)) download = line;
|
||||
else if (line.match(/Upload:/i)) upload = line;
|
||||
else if (line.match(/Latency:/i) || line.match(/Idle Latency:/i)) latency = line;
|
||||
});
|
||||
|
||||
var children = [
|
||||
E('strong', {}, _('Test Results:'))
|
||||
];
|
||||
|
||||
if (download) {
|
||||
children.push(
|
||||
E('div', {
|
||||
style: 'margin-top:10px; padding:8px; background:#e8f5e8; border-radius:4px'
|
||||
}, [
|
||||
E('span', { style: 'font-weight:bold' }, '⬇️ ' + _('Download:') + ' '),
|
||||
E('span', { style: 'color:#2e7d32; font-weight:500' }, download.replace(/Download:/i, '').trim())
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (upload) {
|
||||
children.push(
|
||||
E('div', {
|
||||
style: 'margin-top:5px; padding:8px; background:#e3f2fd; border-radius:4px'
|
||||
}, [
|
||||
E('span', { style: 'font-weight:bold' }, '⬆️ ' + _('Upload:') + ' '),
|
||||
E('span', { style: 'color:#1565c0; font-weight:500' }, upload.replace(/Upload:/i, '').trim())
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (latency) {
|
||||
children.push(
|
||||
E('div', {
|
||||
style: 'margin-top:5px; padding:5px; background:#f5f5f5; border-radius:4px'
|
||||
}, [
|
||||
E('span', { style: 'font-weight:bold' }, '⏱️ ' + _('Latency:') + ' '),
|
||||
E('span', { style: 'color:#666' }, latency.replace(/Latency:/i, '').replace(/Idle Latency:/i, '').trim())
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return E('div', {
|
||||
id: result_id,
|
||||
class: 'cbi-section'
|
||||
}, [
|
||||
E('div', { style: 'margin-left:20px; padding:10px; background:#f5f5f5; border-radius:4px' }, children)
|
||||
]);
|
||||
|
||||
case 'unknown':
|
||||
// 未知格式,直接显示
|
||||
return E('div', { id: result_id, class: 'cbi-section' }, [
|
||||
E('div', { style: 'margin-left:20px; padding:10px; background:#f5f5f5; border-radius:4px' }, [
|
||||
E('strong', {}, _('Test Results:')),
|
||||
E('br'),
|
||||
E('pre', {
|
||||
style: 'margin:5px 0 0 0; font-family:inherit; white-space:pre-wrap; color:#2e7d32'
|
||||
}, escapeHTML(result.data))
|
||||
])
|
||||
]);
|
||||
|
||||
default:
|
||||
// 无结果
|
||||
return E('div', { id: result_id, class: 'cbi-section' }, [
|
||||
E('span', { style: 'color:gray;margin-left:20px' }, [
|
||||
E('em', {}, _('No test results yet. Click "Start Speed Test" to begin.'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
// 配置部分
|
||||
s = m.section(form.NamedSection, 'config', 'netspeedtest');
|
||||
s.anonymous = true;
|
||||
|
||||
// 版本选择
|
||||
o = s.option(form.ListValue, 'test_version', _('Select Test Version'));
|
||||
|
||||
if (has_ookla) {
|
||||
o.value('ookla', 'Ookla SpeedTest');
|
||||
}
|
||||
if (has_python) {
|
||||
o.value('python', 'Python speedtest-cli');
|
||||
}
|
||||
|
||||
if (has_ookla) {
|
||||
o.default = 'ookla';
|
||||
} else if (has_python) {
|
||||
o.default = 'python';
|
||||
}
|
||||
|
||||
o.write = function(section_id, formvalue) {
|
||||
uci.set('netspeedtest', section_id, 'test_version', formvalue);
|
||||
return uci.save('netspeedtest');
|
||||
};
|
||||
|
||||
// 开始测试按钮
|
||||
o = s.option(form.Button, 'start', _('Start Speed Test'));
|
||||
o.inputtitle = _('Click to start speed test');
|
||||
o.inputstyle = 'apply';
|
||||
|
||||
// 只有真正在测试中才禁用按钮
|
||||
if (is_testing) {
|
||||
o.readonly = true;
|
||||
}
|
||||
|
||||
o.onclick = function() {
|
||||
var btn = this;
|
||||
btn.disabled = true;
|
||||
|
||||
// 获取选中的版本
|
||||
var versionSelect = document.getElementById('widget.cbid.netspeedtest.config.test_version');
|
||||
if (!versionSelect) {
|
||||
versionSelect = document.querySelector('select[name="test_version"]');
|
||||
}
|
||||
var version = versionSelect ? versionSelect.value : (has_ookla ? 'ookla' : 'python');
|
||||
|
||||
// 先写入Testing状态
|
||||
return fs.write(ResultFile, 'Testing\n')
|
||||
.then(function() {
|
||||
// 在后台执行测试脚本
|
||||
var cmd = 'nohup ' + SpeedtestScript;
|
||||
if (version) {
|
||||
cmd += ' --version ' + version;
|
||||
}
|
||||
cmd += ' > /dev/null 2>&1 &';
|
||||
|
||||
return fs.exec('/bin/sh', ['-c', cmd]);
|
||||
})
|
||||
.then(function() {
|
||||
ui.addNotification(null, E('p', _('Speed test started. Please wait...')), 'info');
|
||||
})
|
||||
.catch(function(e) {
|
||||
console.error('Failed to start test:', e);
|
||||
ui.addNotification(null, E('p', _('Failed to start speed test: ') + e.message), 'error');
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
return m.render()
|
||||
.then(L.bind(function(m, nodes) {
|
||||
nodes.result_mtime = result_mtime; // 保存结果文件修改时间
|
||||
|
||||
// 添加轮询 - 每2秒检查一次结果
|
||||
poll.add(L.bind(function() {
|
||||
return Promise.all([
|
||||
L.resolveDefault(fs.stat('/usr/bin/ookla-speedtest'), {}),
|
||||
L.resolveDefault(fs.stat('/usr/bin/speedtest'), {}),
|
||||
L.resolveDefault(fs.read(ResultFile), null),
|
||||
L.resolveDefault(fs.stat(ResultFile), {})
|
||||
]).then(L.bind(function(res) {
|
||||
nodes.result_mtime = res[3] ? res[3].mtime * 1000 : 0;
|
||||
this.poll_status(nodes, res);
|
||||
}, this));
|
||||
}, this), 2); // 2秒轮询
|
||||
|
||||
return nodes;
|
||||
}, this, m));
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
|
||||
// 辅助函数
|
||||
function escapeHTML(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
#
|
||||
# Copyright (C) 2021-2025 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 "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 SpeedTest"
|
||||
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 "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 "SpeedTesting in progress..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Test failed."
|
||||
msgstr ""
|
||||
|
||||
msgid "No test results yet."
|
||||
msgstr ""
|
||||
|
||||
msgid "Test Script"
|
||||
msgstr ""
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
zh_Hans
|
||||
@@ -0,0 +1,150 @@
|
||||
#
|
||||
# Copyright (C) 2020-2026 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 "NetSpeedtest"
|
||||
msgstr "网速测试"
|
||||
|
||||
msgid "A tool for testing network speed in multiple aspects"
|
||||
msgstr "一个用于从多方面测试本地和宽带网络速度的测速工具"
|
||||
|
||||
msgid "Throughput speedtest Iperf3"
|
||||
msgstr "吞吐iperf3测速"
|
||||
|
||||
msgid "Throughput speedtest Homebox"
|
||||
msgstr "吞吐homebox测速"
|
||||
|
||||
msgid "Wan SpeedTest"
|
||||
msgstr "宽带测速"
|
||||
|
||||
msgid "Select Test Version"
|
||||
msgstr "选择测速版本"
|
||||
|
||||
msgid "Select Server"
|
||||
msgstr "选择测速服务节点"
|
||||
|
||||
msgid "Auto (Let speedtest decide)"
|
||||
msgstr "自动(由速度测试决定)"
|
||||
|
||||
msgid "Server not obtained"
|
||||
msgstr "未获取到测速服务节点"
|
||||
|
||||
msgid "View detailed results"
|
||||
msgstr "查看详细结果"
|
||||
|
||||
msgid "Online SpeedTest"
|
||||
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 "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 "Run Log"
|
||||
msgstr "运行日志"
|
||||
|
||||
msgid "Refresh Log"
|
||||
msgstr "刷新日志"
|
||||
|
||||
msgid "Official Website"
|
||||
msgstr "官方网站"
|
||||
|
||||
msgid "Please enable the Homebox service"
|
||||
msgstr "请将homebox服务启用"
|
||||
|
||||
msgid "Homebox service control"
|
||||
msgstr "Homebox服务控制"
|
||||
|
||||
msgid "Homebox Server"
|
||||
msgstr "Homebox服务端"
|
||||
|
||||
msgid "Homebox Service Not Running"
|
||||
msgstr "Homebox服务端未运行"
|
||||
|
||||
msgid "Open Web Interface"
|
||||
msgstr "打开WEB页面"
|
||||
|
||||
msgid "Start Speed Test"
|
||||
msgstr "开始测速"
|
||||
|
||||
msgid "Click to start speed test"
|
||||
msgstr "点击开始测速"
|
||||
|
||||
msgid "Speed testing in progress..."
|
||||
msgstr "测速中,请稍候..."
|
||||
|
||||
msgid "Test Failed"
|
||||
msgstr "测速失败"
|
||||
|
||||
msgid "No test results yet."
|
||||
msgstr "还没有测试结果"
|
||||
|
||||
msgid "Test Script"
|
||||
msgstr "测速脚本"
|
||||
|
||||
msgid "Homebox Control panel"
|
||||
msgstr "Homebox控制台"
|
||||
|
||||
msgid "Due to browser security policies, the Homebox interface https cannot be embedded directly."
|
||||
msgstr "由于浏览器安全策略,Homebox接口https不能直接嵌入。"
|
||||
|
||||
msgid "Select speed measurement station"
|
||||
msgstr "选择测速站点"
|
||||
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
config netspeedtest 'config'
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
chmod +x /usr/bin/netspeedtest.sh /usr/bin/speedtest /usr/libexec/rpcd/luci.netspeedtest
|
||||
|
||||
rm -f /tmp/luci-indexcache
|
||||
exit 0
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2019-2026 sirpdboy
|
||||
# Fixed version - correct image URL handling
|
||||
|
||||
RESULT_FILE='/tmp/netspeedtest_result'
|
||||
LOG_FILE='/tmp/netspeedtest.log'
|
||||
LOCK_FILE='/var/run/netspeedtest.lock'
|
||||
PID_FILE='/var/run/netspeedtest.pid'
|
||||
TIMEOUT=120
|
||||
|
||||
# 默认设置
|
||||
PREFERRED_VERSION="auto"
|
||||
SELECTED_SERVER="auto"
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 检查进程锁
|
||||
check_running() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
local old_pid=$(cat "$PID_FILE" 2>/dev/null)
|
||||
if kill -0 "$old_pid" 2>/dev/null; then
|
||||
log "Another speedtest is already running (PID: $old_pid)"
|
||||
echo "Testing" > "$RESULT_FILE"
|
||||
exit 1
|
||||
else
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
exec 200>"$LOCK_FILE"
|
||||
if ! flock -n 200; then
|
||||
log "Another speedtest is already running (flock)"
|
||||
echo "Testing" > "$RESULT_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo $$ > "$PID_FILE"
|
||||
}
|
||||
|
||||
# 清理函数
|
||||
cleanup() {
|
||||
rm -f "$PID_FILE"
|
||||
flock -u 200 2>/dev/null
|
||||
exec 200>&-
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# 检测可用的speedtest版本
|
||||
detect_speedtest() {
|
||||
local preferred="$1"
|
||||
local available_versions=""
|
||||
|
||||
# 检查Ookla官方版
|
||||
if [ -f '/usr/bin/ookla-speedtest' ] && [ -x '/usr/bin/ookla-speedtest' ]; then
|
||||
available_versions="ookla:/usr/bin/ookla-speedtest"
|
||||
fi
|
||||
|
||||
# 检查Python版
|
||||
if [ -f '/usr/bin/speedtest' ] && [ -x '/usr/bin/speedtest' ]; then
|
||||
if [ -n "$available_versions" ]; then
|
||||
available_versions="$available_versions|python:/usr/bin/speedtest"
|
||||
else
|
||||
available_versions="python:/usr/bin/speedtest"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 如果没有找到任何版本
|
||||
if [ -z "$available_versions" ]; then
|
||||
log "No speedtest versions found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 如果指定了偏好版本
|
||||
if [ "$preferred" != "auto" ]; then
|
||||
IFS='|' read -ra versions <<< "$available_versions"
|
||||
for ver in "${versions[@]}"; do
|
||||
if [[ "$ver" == "$preferred:"* ]]; then
|
||||
echo "$ver"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "$available_versions" | cut -d'|' -f1
|
||||
return 0
|
||||
}
|
||||
|
||||
# 运行Ookla版测试
|
||||
run_ookla_test() {
|
||||
local speedtest_bin=$1
|
||||
local server_id=$2
|
||||
|
||||
local cmd="$speedtest_bin --accept-gdpr --accept-license --progress=no"
|
||||
[ -n "$server_id" ] && [ "$server_id" != "auto" ] && cmd="$cmd --server-id=$server_id"
|
||||
# 使用临时文件存储输出
|
||||
local tmp_output=$(mktemp)
|
||||
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout $TIMEOUT $cmd > "$tmp_output" 2>&1
|
||||
local exit_code=$?
|
||||
if [ $exit_code -eq 124 ]; then
|
||||
log "Error: Test timed out"
|
||||
rm -f "$tmp_output"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
$cmd > "$tmp_output" 2>&1
|
||||
fi
|
||||
|
||||
cat "$tmp_output"
|
||||
rm -f "$tmp_output"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 运行Python版测试
|
||||
run_python_test() {
|
||||
local speedtest_bin=$1
|
||||
local server_id=$2
|
||||
|
||||
local cmd="$speedtest_bin --share --simple"
|
||||
if [ -n "$server_id" ] && [ "$server_id" != "auto" ]; then
|
||||
cmd="$speedtest_bin --server $server_id --share --simple"
|
||||
fi
|
||||
|
||||
|
||||
local tmp_output=$(mktemp)
|
||||
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout 90 $cmd > "$tmp_output" 2>&1
|
||||
local exit_code=$?
|
||||
if [ $exit_code -eq 124 ]; then
|
||||
log "Error: Python test timed out"
|
||||
|
||||
# 尝试使用常用服务器
|
||||
timeout 90 $speedtest_bin --server 59016 --share --simple > "$tmp_output" 2>&1
|
||||
if [ $? -eq 124 ]; then
|
||||
log "Error: Second attempt also timed out"
|
||||
rm -f "$tmp_output"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
$cmd > "$tmp_output" 2>&1
|
||||
fi
|
||||
|
||||
cat "$tmp_output"
|
||||
rm -f "$tmp_output"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 解析Python版结果
|
||||
parse_python_result() {
|
||||
local result="$1"
|
||||
|
||||
# 检查是否包含错误信息
|
||||
if echo "$result" | grep -q -i "error\|failed\|cannot\|timeout"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 提取分享链接
|
||||
local share_url=$(echo "$result" | grep -o 'http://www.speedtest.net/result/[0-9]*\.png' | head -1)
|
||||
if [ -n "$share_url" ]; then
|
||||
echo "$share_url"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 提取速度数据
|
||||
local download=$(echo "$result" | grep -i "download" | head -1)
|
||||
local upload=$(echo "$result" | grep -i "upload" | head -1)
|
||||
local latency=$(echo "$result" | grep -i "latency" | head -1)
|
||||
|
||||
if [ -n "$download" ] && [ -n "$upload" ]; then
|
||||
{
|
||||
echo "$download"
|
||||
echo "$upload"
|
||||
[ -n "$latency" ] && echo "$latency"
|
||||
}
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
parse_ookla_result() {
|
||||
local result="$1"
|
||||
|
||||
# 提取结果URL
|
||||
local result_url=$(echo "$result" | grep -i "result url" | grep -o 'https\?://[^ ]*' | head -1)
|
||||
|
||||
if [ -n "$result_url" ]; then
|
||||
echo "$result_url.png"
|
||||
return 0
|
||||
fi
|
||||
local download=$(echo "$result" | grep -i "download" | grep -o "[0-9.]\+ [Mk]bits" | head -1)
|
||||
local upload=$(echo "$result" | grep -i "upload" | grep -o "[0-9.]\+ [Mk]bits" | head -1)
|
||||
local latency=$(echo "$result" | grep -i "latency" | grep -o "[0-9.]\+ ms" | head -1)
|
||||
|
||||
if [ -n "$download" ] && [ -n "$upload" ]; then
|
||||
{
|
||||
echo "Download: $download"
|
||||
echo "Upload: $upload"
|
||||
[ -n "$latency" ] && echo "Latency: $latency"
|
||||
}
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# 主测试函数
|
||||
main_test() {
|
||||
log "=== Speedtest started (PID: $$) ==="
|
||||
|
||||
# 检测可用的speedtest版本
|
||||
local version_info=$(detect_speedtest "$PREFERRED_VERSION")
|
||||
if [ -z "$version_info" ]; then
|
||||
log "Error: No speedtest version found"
|
||||
echo "Test failed" > "$RESULT_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local version_type=${version_info%:*}
|
||||
local speedtest_bin=${version_info#*:}
|
||||
log "Using $version_type version: $speedtest_bin"
|
||||
TEST_SUCCESS=0
|
||||
|
||||
# 运行测试
|
||||
case $version_type in
|
||||
ookla)
|
||||
RUNTEST=$(run_ookla_test "$speedtest_bin" "$SELECTED_SERVER")
|
||||
;;
|
||||
python)
|
||||
RUNTEST=$(run_python_test "$speedtest_bin" "$SELECTED_SERVER")
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$RUNTEST" >> "$LOG_FILE"
|
||||
|
||||
# 解析结果
|
||||
case $version_type in
|
||||
ookla) RESULT=$(parse_ookla_result "$RUNTEST") ;;
|
||||
python) RESULT=$(parse_python_result "$RUNTEST") ;;
|
||||
esac
|
||||
|
||||
if [ -n "$RESULT" ]; then
|
||||
echo "$RESULT" > "$RESULT_FILE"
|
||||
TEST_SUCCESS=1
|
||||
log "Test successful"
|
||||
fi
|
||||
|
||||
# 如果失败,尝试自动选择
|
||||
if [ $TEST_SUCCESS -eq 0 ] && [ "$SELECTED_SERVER" != "auto" ]; then
|
||||
log "Test failed with selected server, trying automatic server selection"
|
||||
SELECTED_SERVER="auto"
|
||||
|
||||
case $version_type in
|
||||
ookla)
|
||||
RUNTEST=$(run_ookla_test "$speedtest_bin" "auto")
|
||||
;;
|
||||
python)
|
||||
RUNTEST=$(run_python_test "$speedtest_bin" "auto")
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$RUNTEST" >> "$LOG_FILE"
|
||||
|
||||
case $version_type in
|
||||
ookla) RESULT=$(parse_ookla_result "$RUNTEST") ;;
|
||||
python) RESULT=$(parse_python_result "$RUNTEST") ;;
|
||||
esac
|
||||
|
||||
if [ -n "$RESULT" ]; then
|
||||
echo "$RESULT" > "$RESULT_FILE"
|
||||
log "Test successful with automatic server selection"
|
||||
TEST_SUCCESS=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 最终检查
|
||||
if [ $TEST_SUCCESS -eq 0 ]; then
|
||||
echo "Test failed" > "$RESULT_FILE"
|
||||
log "All tests failed"
|
||||
fi
|
||||
|
||||
log "=== Speedtest completed (PID: $$) ==="
|
||||
echo "" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo "Options:"
|
||||
echo " --version VERSION Preferred version (ookla/python/auto)"
|
||||
echo " --server SERVER_ID Server ID to test with"
|
||||
echo " --help Show this help message"
|
||||
}
|
||||
|
||||
# 命令行参数处理
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--version)
|
||||
if [ -n "$2" ]; then
|
||||
PREFERRED_VERSION="$2"
|
||||
shift 2
|
||||
else
|
||||
echo "Error: --version requires an argument"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--server)
|
||||
if [ -n "$2" ]; then
|
||||
SELECTED_SERVER="$2"
|
||||
shift 2
|
||||
else
|
||||
echo "Error: --server requires an argument"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 运行测试
|
||||
check_running
|
||||
main_test
|
||||
exit 0
|
||||
Executable
+1970
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,88 @@
|
||||
#!/bin/sh
|
||||
# Copyright (C) 2019-2026 sirpdboy
|
||||
# RPC plugin for netspeedtest
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
get_ookla_servers() {
|
||||
local servers="[]"
|
||||
|
||||
if [ -f '/usr/bin/ookla-speedtest' ] && [ -x '/usr/bin/ookla-speedtest' ]; then
|
||||
local json_output=$(/usr/bin/ookla-speedtest --servers --format=json 2>/dev/null)
|
||||
|
||||
if [ -n "$json_output" ]; then
|
||||
servers=$(echo "$json_output" | sed -n 's/.*"servers":\(\[.*\]\).*/\1/p')
|
||||
[ -z "$servers" ] && servers="[]"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$servers"
|
||||
}
|
||||
|
||||
get_python_servers() {
|
||||
local servers="[]"
|
||||
local temp_file="/tmp/python_servers.tmp"
|
||||
|
||||
if [ -f '/usr/bin/speedtest' ] && [ -x '/usr/bin/speedtest' ]; then
|
||||
/usr/bin/speedtest --list > "$temp_file" 2>/dev/null
|
||||
|
||||
if [ -s "$temp_file" ]; then
|
||||
local json=""
|
||||
local count=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
if echo "$line" | grep -qE '^[0-9]+\)'; then
|
||||
local id=$(echo "$line" | sed -E 's/^([0-9]+)\).*/\1/')
|
||||
local sponsor=$(echo "$line" | sed -E 's/^[0-9]+\)[[:space:]]+([^(]+).*/\1/' | sed -e 's/[[:space:]]*$//' -e 's/"/\\"/g')
|
||||
local city_country=$(echo "$line" | grep -o '([^)]*)' | head -1 | sed 's/[()]//g')
|
||||
local city=$(echo "$city_country" | cut -d',' -f1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/"/\\"/g')
|
||||
local country=$(echo "$city_country" | cut -d',' -f2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/"/\\"/g')
|
||||
local distance=$(echo "$line" | grep -o '\[[0-9.]* km\]' | sed 's/\[//g' | sed 's/ km\]//g')
|
||||
|
||||
[ -z "$distance" ] && distance="0"
|
||||
|
||||
if [ $count -gt 0 ]; then
|
||||
json="$json,"
|
||||
fi
|
||||
|
||||
json="$json{\"id\":$id,\"sponsor\":\"$sponsor\",\"city\":\"$city\",\"country\":\"$country\",\"distance\":$distance}"
|
||||
count=$((count + 1))
|
||||
|
||||
[ $count -ge 10 ] && break
|
||||
fi
|
||||
done < "$temp_file"
|
||||
|
||||
[ -n "$json" ] && servers="[$json]"
|
||||
fi
|
||||
rm -f "$temp_file"
|
||||
fi
|
||||
|
||||
echo "$servers"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
echo '{
|
||||
"get_servers": {
|
||||
"signature": [],
|
||||
"help": "Get speedtest servers"
|
||||
}
|
||||
}'
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
get_servers)
|
||||
json_init
|
||||
json_add_string "ookla" "$(get_ookla_servers)"
|
||||
json_add_string "python" "$(get_python_servers)"
|
||||
json_dump
|
||||
;;
|
||||
*)
|
||||
echo '{"error": "Method not found"}'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo '{"error": "Invalid command"}'
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"admin/network/netspeedtest": {
|
||||
"title": "NetSpeedtest",
|
||||
"order": 90,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-netspeedtest" ],
|
||||
"uci": { "netspeedtest": true }
|
||||
}
|
||||
},
|
||||
"admin/network/netspeedtest/iperf3": {
|
||||
"title": "Throughput speedtest Iperf3",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "netspeedtest/iperf3"
|
||||
}
|
||||
},
|
||||
"admin/network/netspeedtest/homebox": {
|
||||
"title": "Throughput speedtest Homebox",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "netspeedtest/homebox"
|
||||
}
|
||||
},
|
||||
"admin/network/netspeedtest/wanspeedtest": {
|
||||
"title": "Wan SpeedTest",
|
||||
"order": 40,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "netspeedtest/wanspeedtest"
|
||||
}
|
||||
},
|
||||
"admin/network/netspeedtest/onlinespeedtest": {
|
||||
"title": "Online SpeedTest",
|
||||
"order": 50,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "netspeedtest/onlinespeedtest"
|
||||
}
|
||||
},
|
||||
"admin/network/netspeedtest/logs": {
|
||||
"title": "Log",
|
||||
"order": 60,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "netspeedtest/logs"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"luci-app-netspeedtest": {
|
||||
"description": "Grant access to netspeedtest procedures",
|
||||
"read": {
|
||||
"file": {
|
||||
"/usr/bin/netspeedtest.sh": [ "exec" ],
|
||||
"/usr/bin/pgrep": [ "exec" ],
|
||||
"/bin/ps": [ "exec" ],
|
||||
"/usr/bin/homebox": [ "exec" ],
|
||||
"/usr/bin/killall": [ "exec" ],
|
||||
"/bin/sh": [ "exec" ],
|
||||
"/bin/cat": [ "exec" ],
|
||||
"/usr/bin/iperf3": [ "exec" ],
|
||||
"/usr/bin/nohup": [ "exec" ],
|
||||
"/usr/bin/speedtest": [ "exec" ],
|
||||
"/usr/bin/ookla-speedtest": [ "exec" ],
|
||||
"/tmp/netspeedtest_result": [ "read" ],
|
||||
"/tmp/netspeedtest_progress": [ "read" ],
|
||||
"/var/run/netspeedtest.pid": [ "read" ],
|
||||
"/tmp/netspeedtest.log": [ "read" ]
|
||||
},
|
||||
"ubus": {
|
||||
"luci.netspeedtest": [ "get_servers" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/tmp/netspeedtest_result": [ "write" ],
|
||||
"/tmp/netspeedtest_progress": [ "write" ],
|
||||
"/var/run/netspeedtest.pid": [ "write" ],
|
||||
"/tmp/netspeedtest.log": [ "write" ]
|
||||
},
|
||||
"ubus": {
|
||||
"luci.netspeedtest": [ "get_servers" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
-28
@@ -2,40 +2,52 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ookla-speedtest
|
||||
PKG_VERSION:=1.2.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||
PKG_RELEASE:=2
|
||||
|
||||
ifeq ($(ARCH),aarch64)
|
||||
PKG_HASH:=skip
|
||||
else ifeq ($(ARCH),arm)
|
||||
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
|
||||
ifeq ($(ARM_CPU_FEATURES),)
|
||||
ARCH:=armel
|
||||
PKG_HASH:=skip
|
||||
else
|
||||
ARCH:=armhf
|
||||
PKG_HASH:=skip
|
||||
endif
|
||||
else ifeq ($(ARCH),i386)
|
||||
PKG_HASH:=skip
|
||||
else ifeq ($(ARCH),x86_64)
|
||||
PKG_HASH:=skip
|
||||
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)
|
||||
TITLE:=Speedtest from Ookla
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=Speedtest CLI by Ookla
|
||||
DEPENDS:=@(aarch64||arm||i386||x86_64) +ca-certificates
|
||||
URL:=https://www.speedtest.net/
|
||||
endef
|
||||
|
||||
ifeq ($(ARCH),x86_64)
|
||||
BIN_PATH:=./files/x86_64
|
||||
endif
|
||||
ifeq ($(ARCH),i386)
|
||||
BIN_PATH:=./files/i386
|
||||
endif
|
||||
ifeq ($(ARCH),arm)
|
||||
BIN_PATH:=./files/armhf
|
||||
endif
|
||||
ifeq ($(ARCH),aarch64)
|
||||
BIN_PATH:=./files/aarch64
|
||||
endif
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Speedtest CLI brings the trusted technology and global server network behind Speedtest to the command line.
|
||||
Built for software developers, system administrators and computer enthusiasts alike,
|
||||
Speedtest CLI is the first official Linux-native Speedtest application backed by Ookla®.
|
||||
The Global Broadband Speed Test
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
( \
|
||||
pushd $(PKG_BUILD_DIR) ; \
|
||||
$(TAR) -zxf $(DL_DIR)/ookla-speedtest-$(PKG_VERSION)-linux-$(ARCH).tgz -C . ; \
|
||||
popd ; \
|
||||
)
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
@@ -43,9 +55,7 @@ endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(CP) $(BIN_PATH)/speedtest $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/speedtest $(1)/usr/bin/ookla-speedtest
|
||||
endef
|
||||
|
||||
ifeq ($(ARCH),$(filter $(ARCH), x86_64 i386 arm aarch64))
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
endif
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user