mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 09:23:56 +08:00
🎁 Sync 2026-03-15 08:51:25
This commit is contained in:
1 parent
2d75925beb
commit
a485533d81
10 files changed
+597
-354
No files matched your search
+34
-29
@@ -1,41 +1,44 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-only
|
# SPDX-License-Identifier: GPL-3.0-only
|
||||||
# Copyright (c) 2020-2024 sirpdboy herboy2008@gmail.com
|
# Copyright (c) 2022-2026 sirpdboy herboy2008@gmail.com
|
||||||
#
|
#
|
||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=homebox
|
PKG_NAME:=homebox
|
||||||
PKG_VERSION:=0.0.0.2024101306
|
PKG_VERSION:=1.0.1
|
||||||
PKG_REAL_VER:=0.0.0-dev.2024101306
|
PKG_RELEASE:=4
|
||||||
PKG_RELEASE:=3
|
|
||||||
|
|
||||||
ifeq ($(ARCH),aarch64)
|
|
||||||
H_ARCH:=arm64
|
|
||||||
|
|
||||||
else ifeq ($(ARCH),arm)
|
|
||||||
H_ARCH:=arm
|
|
||||||
|
|
||||||
|
ifneq ($(filter aarch64%,$(ARCH)),)
|
||||||
|
PKG_ARCH:=arm64
|
||||||
|
PKG_HASH:=skip
|
||||||
|
else ifneq ($(filter arm%,$(ARCH)),)
|
||||||
|
PKG_ARCH:=armv7
|
||||||
|
PKG_HASH:=skip
|
||||||
else ifeq ($(ARCH),i386)
|
else ifeq ($(ARCH),i386)
|
||||||
H_ARCH:=386
|
PKG_ARCH:=386
|
||||||
|
PKG_HASH:=skip
|
||||||
else ifeq ($(ARCH),mips)
|
|
||||||
H_ARCH:=mips
|
|
||||||
|
|
||||||
else ifeq ($(ARCH),mipsel)
|
|
||||||
H_ARCH:=mips
|
|
||||||
|
|
||||||
else ifeq ($(ARCH),x86_64)
|
else ifeq ($(ARCH),x86_64)
|
||||||
H_ARCH:=amd64
|
PKG_ARCH:=amd64
|
||||||
|
PKG_HASH:=skip
|
||||||
|
else
|
||||||
|
$(error Unsupported architecture: $(ARCH))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
PKG_SOURCE:=homebox-linux-$(PKG_ARCH)-musl-v$(PKG_VERSION).tar.gz
|
||||||
|
PKG_SOURCE_URL:=https://github.com/sirpdboy/homebox/releases/download/v$(PKG_VERSION)/
|
||||||
|
|
||||||
|
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
define Package/$(PKG_NAME)
|
define Package/$(PKG_NAME)
|
||||||
SECTION:=net
|
SECTION:=net
|
||||||
CATEGORY:=Network
|
CATEGORY:=Network
|
||||||
TITLE:=A Toolbox for Home Local Networks Speed Test
|
SUBMENU:=Speed Test
|
||||||
URL:=https://github.com/XGHeaven/homebox
|
TITLE:=Home Local Network Speed Test Tool
|
||||||
DEPENDS:=@(i386||x86_64||arm||aarch64||mipsel||mips) +libstdcpp
|
URL:=https://github.com/XGHeaven/homebox
|
||||||
|
DEPENDS:=@(i386||x86_64||arm||aarch64)
|
||||||
|
MAINTAINER:=sirpdboy <herboy2008@gmail.com>
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/$(PKG_NAME)/description
|
define Package/$(PKG_NAME)/description
|
||||||
@@ -43,9 +46,11 @@ define Package/$(PKG_NAME)/description
|
|||||||
endef
|
endef
|
||||||
|
|
||||||
define Build/Prepare
|
define Build/Prepare
|
||||||
mkdir -p $(PKG_BUILD_DIR)
|
( \
|
||||||
[ ! -f $(PKG_BUILD_DIR)/server-linux-$(H_ARCH).tar.gz ] && wget https://github.com/XGHeaven/homebox/releases/download/v$(PKG_REAL_VER)/server-linux-$(H_ARCH).tar.gz -O $(PKG_BUILD_DIR)/server-linux-$(H_ARCH).tar.gz
|
pushd $(PKG_BUILD_DIR) ; \
|
||||||
tar -xzvf $(PKG_BUILD_DIR)/server-linux-$(H_ARCH).tar.gz -C $(PKG_BUILD_DIR)
|
$(TAR) -zxf $(DL_DIR)/$(PKG_SOURCE) -C . ; \
|
||||||
|
popd ; \
|
||||||
|
)
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Build/Compile
|
define Build/Compile
|
||||||
@@ -53,7 +58,7 @@ endef
|
|||||||
|
|
||||||
define Package/$(PKG_NAME)/install
|
define Package/$(PKG_NAME)/install
|
||||||
$(INSTALL_DIR) $(1)/usr/bin
|
$(INSTALL_DIR) $(1)/usr/bin
|
||||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/server-linux-$(H_ARCH) $(1)/usr/bin/homebox
|
$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)/usr/bin/homebox
|
||||||
endef
|
endef
|
||||||
|
|
||||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||||
@@ -9,8 +9,8 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-netspeedtest
|
PKG_NAME:=luci-app-netspeedtest
|
||||||
|
|
||||||
PKG_VERSION:=5.1.3
|
PKG_VERSION:=5.2.0
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI Support for netspeedtest
|
LUCI_TITLE:=LuCI Support for netspeedtest
|
||||||
LUCI_DEPENDS:=+ookla-speedtest +homebox +python3-light +python3-pkg-resources +python3-xml +python3-email \
|
LUCI_DEPENDS:=+ookla-speedtest +homebox +python3-light +python3-pkg-resources +python3-xml +python3-email \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2021-2026 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-netspeedtest */
|
/* Copyright (C) 2021-2026 sirpdboy herboy2008@gmail.com */
|
||||||
'use strict';
|
'use strict';
|
||||||
'require view';
|
'require view';
|
||||||
'require fs';
|
'require fs';
|
||||||
@@ -6,36 +6,142 @@
|
|||||||
'require uci';
|
'require uci';
|
||||||
'require form';
|
'require form';
|
||||||
'require poll';
|
'require poll';
|
||||||
|
|
||||||
|
var state = {
|
||||||
|
running: false,
|
||||||
|
port: 3300,
|
||||||
|
enabled: false,
|
||||||
|
operationInProgress: false,
|
||||||
|
operationType: null // 'start' 或 'stop'
|
||||||
|
};
|
||||||
|
|
||||||
|
const logPath = '/tmp/netspeedtest.log';
|
||||||
|
|
||||||
|
function checkProcess(quick = false) {
|
||||||
|
if (quick) {
|
||||||
|
return fs.exec('/usr/bin/pgrep', ['homebox'])
|
||||||
|
.then(function(res) {
|
||||||
|
return res.code === 0 && res.stdout.trim() !== '';
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return fs.exec('/usr/bin/pgrep', ['homebox'])
|
||||||
|
.then(function(res) {
|
||||||
|
if (res.code === 0 && res.stdout.trim()) {
|
||||||
|
return {
|
||||||
|
running: true,
|
||||||
|
pid: res.stdout.trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return fs.exec('/bin/ps', ['-w', '-C', 'homebox', '-o', 'pid='])
|
||||||
|
.then(function(psRes) {
|
||||||
|
var pid = psRes.stdout.trim();
|
||||||
|
return {
|
||||||
|
running: pid !== '',
|
||||||
|
pid: pid || null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
return { running: false, pid: null };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function controlService(action, port) {
|
||||||
|
if (action === 'start') {
|
||||||
|
return fs.exec('/usr/bin/killall', ['homebox'])
|
||||||
|
.catch(function() { return Promise.resolve(); })
|
||||||
|
.then(function() {
|
||||||
|
var command = 'nohup /usr/bin/homebox serve --port ' + port + ' > ' + logPath + ' 2>&1 &';
|
||||||
|
return fs.exec('/bin/sh', ['-c', command]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return fs.exec('/etc/init.d/netspeedtest', ['stop']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConfiguration(newPort, enabled) {
|
||||||
|
const uciContent = `config netspeedtest 'config'
|
||||||
|
\toption homebox_port '${newPort}'
|
||||||
|
\toption homebox_enabled '${enabled ? '1' : '0'}'
|
||||||
|
`;
|
||||||
|
|
||||||
|
return fs.write('/etc/config/netspeedtest', uciContent)
|
||||||
|
.then(() => {
|
||||||
|
if (enabled) {
|
||||||
|
return fs.exec('/etc/init.d/netspeedtest', ['enable']);
|
||||||
|
} else {
|
||||||
|
return fs.exec('/etc/init.d/netspeedtest', ['disable']);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return uci.load('netspeedtest');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
|
handleSaveApply: null,
|
||||||
|
handleSave: null,
|
||||||
|
handleReset: null,
|
||||||
|
|
||||||
|
load: function() {
|
||||||
|
return Promise.all([
|
||||||
|
uci.load('netspeedtest')
|
||||||
|
]).then(() => {
|
||||||
|
var port = uci.get('netspeedtest', 'config', 'homebox_port');
|
||||||
|
if (port) {
|
||||||
|
state.port = parseInt(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
var enabled = uci.get('netspeedtest', 'config', 'homebox_enabled');
|
||||||
|
state.enabled = enabled === '1';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var state = {
|
|
||||||
running: false,
|
|
||||||
port: 3300
|
|
||||||
};
|
|
||||||
var container = E('div');
|
var container = E('div');
|
||||||
var statusSection = E('div', { 'class': 'cbi-section' });
|
var statusSection = E('div', { 'class': 'cbi-section' });
|
||||||
var statusIcon = E('span', { 'style': 'margin-right: 5px;' });
|
var statusIcon = E('span', { 'style': 'margin-right: 5px;' });
|
||||||
var statusText = E('span');
|
var statusText = E('span');
|
||||||
var toggleBtn = E('button', { 'class': 'btn cbi-button' });
|
var toggleBtn = E('button', { 'class': 'btn cbi-button' });
|
||||||
|
|
||||||
var statusMessage = E('div', { style: 'text-align: center; padding: 2em;' }, [
|
var saveBtn = E('button', {
|
||||||
E('img', {
|
'class': 'btn cbi-button cbi-button-apply',
|
||||||
src: 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMjQiIGhlaWdodD0iMTAyNCIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCI+PHBhdGggZmlsbD0iI2RmMDAwMCIgZD0iTTk0Mi40MjEgMjM0LjYyNGw4MC44MTEtODAuODExLTE1My4wNDUtMTUzLjA0NS04MC44MTEgODAuODExYy03OS45NTctNTEuNjI3LTE3NS4xNDctODEuNTc5LTI3Ny4zNzYtODEuNTc5LTI4Mi43NTIgMC01MTIgMjI5LjI0OC01MTIgNTEyIDAgMTAyLjIyOSAyOS45NTIgMTk3LjQxOSA4MS41NzkgMjc3LjM3NmwtODAuODExIDgwLjgxMSAxNTMuMDQ1IDE1My4wNDUgODAuODExLTgwLjgxMWM3OS45NTcgNTEuNjI3IDE3NS4xNDcgODEuNTc5IDI3Ny4zNzYgODEuNTc5IDI4Mi43NTIgMCA1MTItMjI5LjI0OCA1MTItNTEyIDAtMTAyLjIyOS0yOS45NTItMTk3LjQxOS04MS41NzktMjc3LjM3NnpNMTk0Ljk0NCA1MTJjMC0xNzUuMTA0IDE0MS45NTItMzE3LjA1NiAzMTcuMDU2LTMxNy4wNTYgNDggMCA5My40ODMgMTAuNjY3IDEzNC4yMjkgMjkuNzgxbC00MjEuNTQ3IDQyMS41NDdjLTE5LjA3Mi00MC43ODktMjkuNzM5LTg2LjI3Mi0yOS43MzktMTM0LjI3MnpNNTEyIDgyOS4wNTZjLTQ4IDAtOTMuNDgzLTEwLjY2Ny0xMzQuMjI5LTI5Ljc4MWw0MjEuNTQ3LTQyMS41NDdjMTkuMDcyIDQwLjc4OSAyOS43ODEgODYuMjcyIDI5Ljc4MSAxMzQuMjI5LTAuMDQzIDE3NS4xNDctMTQxLjk5NSAzMTcuMDk5LTMxNy4wOTkgMzE3LjA5OXoiLz48L3N2Zz4=',
|
'style': 'margin-left: 10px;'
|
||||||
style: 'width: 100px; height: 100px; margin-bottom: 1em;'
|
}, _('Save'));
|
||||||
}),
|
|
||||||
E('h2', {}, _('Homebox Service Not Running')),
|
|
||||||
E('p', {}, _('Please enable the Homebox service'))
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
var enableCheckbox = E('input', {
|
||||||
|
'type': 'checkbox',
|
||||||
|
'id': 'homebox_enable',
|
||||||
|
'class': 'cbi-input-checkbox'
|
||||||
|
});
|
||||||
|
var statusMessage = E('div', { style: 'text-align: center; padding: 2em;' }, [
|
||||||
|
E('div', { style: 'font-size: 5em; color: #f39c12; margin-bottom: 0.2em;' }, '⚠️'),
|
||||||
|
E('h2', {}, _('Homebox Service Not Running')),
|
||||||
|
E('p', { style: 'color: #666; margin-top: 1em;' }, _('Please start the Homebox service'))
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
var isHttps = window.location.protocol === 'https:';
|
var isHttps = window.location.protocol === 'https:';
|
||||||
var iframe;
|
var iframe;
|
||||||
|
|
||||||
if (!isHttps) {
|
var portInput = E('input', {
|
||||||
iframe = E('iframe', {
|
'type': 'number',
|
||||||
src: window.location.origin + ':' + state.port,
|
'class': 'cbi-input-text',
|
||||||
style: 'border:none;width: 100%; min-height: 80vh; border: none; border-radius: 3px;overflow:hidden !important;'
|
'style': 'width: 100px;',
|
||||||
});
|
'value': state.port,
|
||||||
}
|
'min': 1024,
|
||||||
|
'max': 65535,
|
||||||
|
'placeholder': '3300'
|
||||||
|
});
|
||||||
|
|
||||||
|
var portError = E('span', {
|
||||||
|
'class': 'error',
|
||||||
|
'style': 'color: red; margin-left: 10px; display: none;'
|
||||||
|
}, _('Port range must be 1024-65535'));
|
||||||
|
|
||||||
function createHttpsButton() {
|
function createHttpsButton() {
|
||||||
return E('div', {
|
return E('div', {
|
||||||
@@ -49,57 +155,57 @@ return view.extend({
|
|||||||
class: 'cbi-button cbi-button-apply',
|
class: 'cbi-button cbi-button-apply',
|
||||||
style: 'display: inline-block; margin-top: 1em; padding: 10px 20px; font-size: 16px; text-decoration: none; color: white;'
|
style: 'display: inline-block; margin-top: 1em; padding: 10px 20px; font-size: 16px; text-decoration: none; color: white;'
|
||||||
}, _('Open Web Interface'))
|
}, _('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() {
|
function updateStatus() {
|
||||||
statusIcon.textContent = state.running ? '✓' : '✗';
|
statusIcon.textContent = state.running ? '✓' : '✗';
|
||||||
statusIcon.style.color = state.running ? 'green' : 'red';
|
statusIcon.style.color = state.running ? 'green' : 'red';
|
||||||
statusText.textContent = _('Homebox Server') + (state.running ? _('RUNNING') : _('NOT RUNNING'));
|
statusText.textContent = _('Homebox Server') + ' ' + (state.running ? _('RUNNING') : _('NOT RUNNING'));
|
||||||
statusText.style.color = state.running ? 'green' : 'red';
|
statusText.style.color = state.running ? 'green' : 'red';
|
||||||
statusText.style.fontWeight = 'bold';
|
statusText.style.fontWeight = 'bold';
|
||||||
statusText.style.fontSize = '0.92rem';
|
statusText.style.fontSize = '0.92rem';
|
||||||
|
|
||||||
toggleBtn.textContent = state.running ? _('Stop Server') : _('Start Server');
|
if (state.operationInProgress) {
|
||||||
toggleBtn.className = `btn cbi-button cbi-button-${state.running ? 'reset' : 'apply'}`;
|
if (state.operationType === 'start') {
|
||||||
|
toggleBtn.textContent = _('Starting...');
|
||||||
|
} else if (state.operationType === 'stop') {
|
||||||
|
toggleBtn.textContent = _('Stopping...');
|
||||||
|
}
|
||||||
|
toggleBtn.disabled = true;
|
||||||
|
if (state.operationType === 'stop' && !state.running) {
|
||||||
|
console.log('Stop confirmed - operation complete');
|
||||||
|
state.operationInProgress = false;
|
||||||
|
state.operationType = null;
|
||||||
|
toggleBtn.textContent = state.running ? _('Stop Server') : _('Start Server');
|
||||||
|
toggleBtn.className = 'btn cbi-button cbi-button-' + (state.running ? 'reset' : 'apply');
|
||||||
|
toggleBtn.disabled = false;
|
||||||
|
} else if (state.operationType === 'start' && state.running) {
|
||||||
|
console.log('Start confirmed - operation complete');
|
||||||
|
state.operationInProgress = false;
|
||||||
|
state.operationType = null;
|
||||||
|
toggleBtn.textContent = state.running ? _('Stop Server') : _('Start Server');
|
||||||
|
toggleBtn.className = 'btn cbi-button cbi-button-' + (state.running ? 'reset' : 'apply');
|
||||||
|
toggleBtn.disabled = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toggleBtn.textContent = state.running ? _('Stop Server') : _('Start Server');
|
||||||
|
toggleBtn.className = 'btn cbi-button cbi-button-' + (state.running ? 'reset' : 'apply');
|
||||||
|
toggleBtn.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
enableCheckbox.checked = state.enabled;
|
||||||
|
portInput.value = state.port;
|
||||||
|
|
||||||
// Update container content based on state and protocol
|
container.innerHTML = '';
|
||||||
container.textContent = '';
|
|
||||||
if (state.running) {
|
if (state.running) {
|
||||||
if (isHttps) {
|
if (isHttps) {
|
||||||
container.appendChild(createHttpsButton());
|
container.appendChild(createHttpsButton());
|
||||||
} else {
|
} else {
|
||||||
|
iframe = E('iframe', {
|
||||||
|
src: 'http://' + window.location.hostname + ':' + state.port,
|
||||||
|
style: 'border:none;width: 100%; min-height: 80vh; border: none; border-radius: 3px;overflow:hidden !important;'
|
||||||
|
});
|
||||||
container.appendChild(iframe);
|
container.appendChild(iframe);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -107,59 +213,185 @@ return view.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleBtn.addEventListener('click', ui.createHandlerFn(this, function() {
|
toggleBtn.addEventListener('click', function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (toggleBtn.disabled || state.operationInProgress) return;
|
||||||
|
|
||||||
var action = state.running ? 'stop' : 'start';
|
var action = state.running ? 'stop' : 'start';
|
||||||
return controlService(action)
|
var startTime = Date.now();
|
||||||
.then(checkProcess)
|
|
||||||
.then(res => {
|
state.operationInProgress = true;
|
||||||
state.running = res.running;
|
state.operationType = action;
|
||||||
|
updateStatus();
|
||||||
|
|
||||||
|
controlService(action, state.port)
|
||||||
|
.then(function() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var checkCount = 0;
|
||||||
|
var maxChecks = 30;
|
||||||
|
|
||||||
|
function doCheck() {
|
||||||
|
checkProcess(true).then(function(isRunning) {
|
||||||
|
if (action === 'stop' && !isRunning) {
|
||||||
|
console.log('Stop success after', Date.now() - startTime, 'ms');
|
||||||
|
resolve({ running: false });
|
||||||
|
} else if (action === 'start' && isRunning) {
|
||||||
|
console.log('Start success after', Date.now() - startTime, 'ms');
|
||||||
|
checkProcess().then(resolve).catch(resolve);
|
||||||
|
} else if (checkCount < maxChecks) {
|
||||||
|
checkCount++;
|
||||||
|
setTimeout(doCheck, 200);
|
||||||
|
} else {
|
||||||
|
console.log('Check timeout, using full check');
|
||||||
|
|
||||||
|
checkProcess().then(resolve).catch(resolve);
|
||||||
|
}
|
||||||
|
}).catch(function() {
|
||||||
|
if (checkCount < maxChecks) {
|
||||||
|
checkCount++;
|
||||||
|
setTimeout(doCheck, 100);
|
||||||
|
} else {
|
||||||
|
checkProcess().then(resolve).catch(resolve);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doCheck();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function(res) {
|
||||||
|
if (res) {
|
||||||
|
if (typeof res === 'boolean') {
|
||||||
|
state.running = res;
|
||||||
|
} else {
|
||||||
|
state.running = res.running || false;
|
||||||
|
if (res.port) {
|
||||||
|
state.port = res.port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.operationInProgress = false;
|
||||||
|
state.operationType = null;
|
||||||
updateStatus();
|
updateStatus();
|
||||||
|
|
||||||
|
var message = action === 'start' ?
|
||||||
|
_('Homebox server started on port %s').replace('%s', state.port) :
|
||||||
|
_('Homebox server stopped');
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.error('Service control error:', err);
|
||||||
|
|
||||||
|
// 发生错误时重新检查
|
||||||
|
checkProcess().then(function(res) {
|
||||||
|
state.running = res.running || false;
|
||||||
|
if (res.port) state.port = res.port;
|
||||||
|
|
||||||
|
state.operationInProgress = false;
|
||||||
|
state.operationType = null;
|
||||||
|
updateStatus();
|
||||||
|
|
||||||
|
}).catch(function() {
|
||||||
|
state.operationInProgress = false;
|
||||||
|
state.operationType = null;
|
||||||
|
updateStatus();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}));
|
});
|
||||||
|
|
||||||
|
saveBtn.addEventListener('click', function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
var newPort = parseInt(portInput.value, 10);
|
||||||
|
if (isNaN(newPort) || newPort < 1024 || newPort > 65535) {
|
||||||
|
portError.style.display = 'inline';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
portError.style.display = 'none';
|
||||||
|
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
saveBtn.textContent = _('Saving...');
|
||||||
|
|
||||||
|
saveConfiguration(newPort, enableCheckbox.checked)
|
||||||
|
.then(function() {
|
||||||
|
state.port = newPort;
|
||||||
|
state.enabled = enableCheckbox.checked;
|
||||||
|
updateStatus();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
})
|
||||||
|
.finally(function() {
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.textContent = _('Save');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
enableCheckbox.addEventListener('change', function(ev) {
|
||||||
|
saveConfiguration(state.port, enableCheckbox.checked)
|
||||||
|
.then(function() {
|
||||||
|
state.enabled = enableCheckbox.checked;
|
||||||
|
updateStatus();
|
||||||
|
|
||||||
|
var message = enableCheckbox.checked ?
|
||||||
|
_('Auto start enabled') :
|
||||||
|
_('Auto start disabled');
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
enableCheckbox.checked = state.enabled;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
statusSection.appendChild(E('div', { 'style': 'margin: 15px' }, [
|
statusSection.appendChild(E('div', { 'style': 'margin: 15px' }, [
|
||||||
E('h3', {}, _('Throughput speedtest Homebox')),
|
E('h3', {}, _('Throughput speedtest Homebox')),
|
||||||
E('div', { 'class': 'cbi-map-descr' }, [statusIcon, statusText]),
|
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'}, [
|
||||||
E('div', {'class': 'cbi-value-field'}, toggleBtn),
|
E('div', {'class': 'cbi-value-title'}, _('Auto Start')),
|
||||||
E('div', { 'style': 'text-align: right; font-style: italic; margin-top: 20px;' }, [
|
E('div', {'class': 'cbi-value-field'}, [
|
||||||
_('© github '),
|
enableCheckbox,
|
||||||
E('a', {
|
E('label', {'for': 'homebox_enable', 'style': 'margin-left: 5px;'})
|
||||||
'href': 'https://github.com/sirpdboy',
|
|
||||||
'target': '_blank',
|
|
||||||
'style': 'text-decoration: none;'
|
|
||||||
}, 'by sirpdboy')
|
|
||||||
])
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('div', {'class': 'cbi-value' }, [
|
||||||
|
E('div', {'class': 'cbi-value-title'}, _('Port Setting')),
|
||||||
|
E('div', {'class': 'cbi-value-field', 'style': 'align-items: center;'}, [
|
||||||
|
portInput,
|
||||||
|
saveBtn,
|
||||||
|
portError
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('div', {'class': 'cbi-value'}, [
|
||||||
|
E('div', {'class': 'cbi-value-title'}, _('Service Control')),
|
||||||
|
E('div', {'class': 'cbi-value-field'}, toggleBtn)
|
||||||
])
|
])
|
||||||
]));
|
]));
|
||||||
|
|
||||||
// Initial status check
|
checkProcess().then(function(res) {
|
||||||
checkProcess().then(res => {
|
|
||||||
state.running = res.running;
|
state.running = res.running;
|
||||||
|
if (res.port) {
|
||||||
|
state.port = res.port;
|
||||||
|
}
|
||||||
updateStatus();
|
updateStatus();
|
||||||
toggleBtn.disabled = false;
|
|
||||||
// Start polling
|
poll.add(function() {
|
||||||
poll.add(() => {
|
return checkProcess().then(function(res) {
|
||||||
return checkProcess().then(res => {
|
if (res.running !== state.running || (res.port && res.port !== state.port)) {
|
||||||
if (res.running !== state.running) {
|
|
||||||
state.running = res.running;
|
state.running = res.running;
|
||||||
|
if (res.port) {
|
||||||
|
state.port = res.port;
|
||||||
|
}
|
||||||
updateStatus();
|
updateStatus();
|
||||||
toggleBtn.disabled = false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 5);
|
}, 5);
|
||||||
|
|
||||||
poll.start();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return E('div', {}, [
|
return E('div', {}, [
|
||||||
statusSection,
|
statusSection,
|
||||||
container
|
container
|
||||||
]);
|
]);
|
||||||
},
|
}
|
||||||
|
|
||||||
handleSaveApply: null,
|
|
||||||
handleSave: null,
|
|
||||||
handleReset: null
|
|
||||||
});
|
});
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
var state = {
|
var state = {
|
||||||
running: false,
|
running: false,
|
||||||
port: null
|
port: 5201
|
||||||
};
|
};
|
||||||
|
|
||||||
const logPath = '/tmp/netspeedtest.log';
|
const logPath = '/tmp/netspeedtest.log';
|
||||||
@@ -34,10 +34,9 @@ async function checkProcess() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function controlService(action, port) {
|
||||||
function controlService(action) {
|
|
||||||
const commands = {
|
const commands = {
|
||||||
start: `/usr/bin/iperf3 -s -D -p 5201 --logfile ${logPath} 2>&1`,
|
start: `/usr/bin/iperf3 -s -D -p ${port} --logfile ${logPath} 2>&1`,
|
||||||
stop: '/usr/bin/killall -q iperf3'
|
stop: '/usr/bin/killall -q iperf3'
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,45 +49,105 @@ function controlService(action) {
|
|||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function saveConfiguration(newPort, enabled) {
|
||||||
|
const uciContent = `config netspeedtest 'config'
|
||||||
|
\toption iperf3port '${newPort}'
|
||||||
|
\toption iperf3_enabled '${enabled ? '1' : '0'}'
|
||||||
|
`;
|
||||||
|
|
||||||
|
return fs.write('/etc/config/netspeedtest', uciContent)
|
||||||
|
.then(() => {
|
||||||
|
// 更新开机自启
|
||||||
|
if (enabled) {
|
||||||
|
return fs.exec('/etc/init.d/netspeedtest', ['enable']);
|
||||||
|
} else {
|
||||||
|
return fs.exec('/etc/init.d/netspeedtest', ['disable']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return view.extend({
|
return view.extend({
|
||||||
handleSaveApply: null,
|
handleSaveApply: null,
|
||||||
handleSave: null,
|
handleSave: null,
|
||||||
handleReset: null,
|
handleReset: null,
|
||||||
|
|
||||||
load: function() {
|
load: function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
uci.load('netspeedtest')
|
uci.load('netspeedtest')
|
||||||
]);
|
]).then(() => {
|
||||||
|
const port = uci.get('netspeedtest', 'config', 'iperf3port');
|
||||||
|
if (port) {
|
||||||
|
state.port = parseInt(port);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
||||||
// 创建状态元素
|
|
||||||
const statusIcon = E('span', { 'style': 'margin-right: 5px;' });
|
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 statusText = E('span');
|
||||||
|
|
||||||
|
const portInput = E('input', {
|
||||||
|
'type': 'number',
|
||||||
|
'class': 'cbi-input-text',
|
||||||
|
'style': 'width: 100px;',
|
||||||
|
'value': state.port,
|
||||||
|
'min': 1024,
|
||||||
|
'max': 65535,
|
||||||
|
'placeholder': '5201'
|
||||||
|
});
|
||||||
|
const enableCheckbox = E('input', {
|
||||||
|
'type': 'checkbox',
|
||||||
|
'id': 'iperf3_enable',
|
||||||
|
'class': 'cbi-input-checkbox',
|
||||||
|
'checked': state.enabled
|
||||||
|
});
|
||||||
|
const portError = E('span', {
|
||||||
|
'class': 'error',
|
||||||
|
'style': 'color: red; margin-left: 10px; display: none;'
|
||||||
|
}, _('Port range must be 1024-65535'));
|
||||||
|
|
||||||
|
const statusContainer = E('div', { 'class': 'cbi-map-descr' }, [
|
||||||
|
statusIcon,
|
||||||
|
statusText
|
||||||
|
]);
|
||||||
|
|
||||||
const toggleBtn = E('button', {
|
const toggleBtn = E('button', {
|
||||||
'class': 'btn cbi-button',
|
'class': 'btn cbi-button',
|
||||||
'click': ui.createHandlerFn(this, function() {
|
'click': ui.createHandlerFn(this, function() {
|
||||||
const action = state.running ? 'stop' : 'start';
|
const action = state.running ? 'stop' : 'start';
|
||||||
toggleBtn.disabled = true; // 禁用按钮
|
toggleBtn.disabled = true;
|
||||||
|
|
||||||
return controlService(action)
|
controlService(action, state.port)
|
||||||
.then(() => checkProcess())
|
.then(() => {
|
||||||
.then(res => {
|
return new Promise(resolve => setTimeout(resolve, 500));
|
||||||
state.running = res.running;
|
})
|
||||||
updateStatus();
|
.then(() => checkProcess())
|
||||||
toggleBtn.disabled = false; // 恢复按钮
|
.then(res => {
|
||||||
})
|
state.running = res.running;
|
||||||
.catch(err => {
|
updateStatus();
|
||||||
ui.addNotification(null, E('p', _('Error: ') + err.message), 'error');
|
toggleBtn.disabled = false;
|
||||||
toggleBtn.disabled = false; // 出错时也要恢复按钮
|
})
|
||||||
});
|
.catch(err => {
|
||||||
|
ui.addNotification(null, E('p', _('Error: ') + err.message), 'error');
|
||||||
|
toggleBtn.disabled = false;
|
||||||
|
});
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const savePortBtn = E('button', {
|
||||||
|
'class': 'btn cbi-button cbi-button-apply',
|
||||||
|
'style': 'margin-left: 10px;',
|
||||||
|
'click': ui.createHandlerFn(this, function() {
|
||||||
|
const newPort = parseInt(portInput.value);
|
||||||
|
if (isNaN(newPort) || newPort < 1024 || newPort > 65535) {
|
||||||
|
portError.style.display = 'inline';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
portError.style.display = 'none';
|
||||||
|
saveConfiguration(newPort,state.enabled)
|
||||||
|
|
||||||
|
})
|
||||||
|
}, _('Save'));
|
||||||
|
|
||||||
function updateStatus() {
|
function updateStatus() {
|
||||||
statusIcon.textContent = state.running ? '✓' : '✗';
|
statusIcon.textContent = state.running ? '✓' : '✗';
|
||||||
statusIcon.style.color = state.running ? 'green' : 'red';
|
statusIcon.style.color = state.running ? 'green' : 'red';
|
||||||
@@ -98,51 +157,54 @@ return view.extend({
|
|||||||
statusText.style['font-size'] = '0.92rem';
|
statusText.style['font-size'] = '0.92rem';
|
||||||
toggleBtn.textContent = state.running ? _('Stop Server') : _('Start Server');
|
toggleBtn.textContent = state.running ? _('Stop Server') : _('Start Server');
|
||||||
toggleBtn.className = `btn cbi-button cbi-button-${state.running ? 'reset' : 'apply'}`;
|
toggleBtn.className = `btn cbi-button cbi-button-${state.running ? 'reset' : 'apply'}`;
|
||||||
|
|
||||||
|
portInput.value = state.port;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化状态
|
|
||||||
statusIcon.textContent = '...';
|
statusIcon.textContent = '...';
|
||||||
statusText.textContent = _('Checking status...');
|
statusText.textContent = _('Checking status...');
|
||||||
toggleBtn.textContent = _('Loading...');
|
toggleBtn.textContent = _('Loading...');
|
||||||
toggleBtn.disabled = true;
|
toggleBtn.disabled = true;
|
||||||
|
|
||||||
|
const statusSection = E('div', { 'class': 'cbi-section' }, [
|
||||||
// 构建UI
|
E('div', { 'style': 'margin: 15px' }, [
|
||||||
const statusSection = E('div', { 'class': 'cbi-section' }, [
|
E('h3', {}, _('Throughput speedtest Iperf3')),
|
||||||
E('div', { 'style': 'margin: 15px' }, [
|
statusContainer,
|
||||||
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', 'style': 'margin-top: 20px'}, [
|
E('div', {'class': 'cbi-value-title'}, _('Port Setting')),
|
||||||
E('div', {'class': 'cbi-value-title'}, _('Iperf3 service control')),
|
E('div', {'class': 'cbi-value-field'}, [
|
||||||
E('div', {'class': 'cbi-value-field'}, toggleBtn),
|
portInput,
|
||||||
|
savePortBtn,
|
||||||
E('div', {'class': 'cbi-value-title'}, _('Download iperf3 client')),
|
portError
|
||||||
E('div', {'class': 'cbi-value-field'}, [
|
])
|
||||||
E('div', {
|
]),
|
||||||
'class': 'cbi-value-field',
|
|
||||||
'style': 'display: flex;'
|
E('div', {'class': 'cbi-value'}, [
|
||||||
}, [
|
E('div', {'class': 'cbi-value-title'}, _('Service Control')),
|
||||||
E('button', {
|
E('div', {'class': 'cbi-value-field'}, toggleBtn),
|
||||||
'class': 'btn cbi-button cbi-button-save',
|
]),
|
||||||
'click': ui.createHandlerFn(this, () => window.open('https://iperf.fr/iperf-download.php', '_blank'))
|
|
||||||
}, _('Official Website')),
|
E('div', {'class': 'cbi-value'}, [
|
||||||
E('button', {
|
E('div', {'class': 'cbi-value-title'}, _('Download iperf3 client')),
|
||||||
'class': 'btn cbi-button cbi-button-save',
|
E('div', {'class': 'cbi-value-field'}, [
|
||||||
'click': ui.createHandlerFn(this, () => window.open('https://github.com/sirpdboy/luci-app-netspeedtest/releases', '_blank'))
|
E('div', {
|
||||||
}, _('GitHub'))
|
'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/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 => {
|
checkProcess().then(res => {
|
||||||
@@ -161,8 +223,6 @@ const statusSection = E('div', { 'class': 'cbi-section' }, [
|
|||||||
}, 5);
|
}, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return statusSection;
|
return statusSection;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
|
||||||
@@ -78,21 +78,11 @@ return view.extend({
|
|||||||
E('small', {}, _('Refresh every %s seconds.').format(L.env.pollinterval))
|
E('small', {}, _('Refresh every %s seconds.').format(L.env.pollinterval))
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'cbi-section-actions cbi-section-actions-right' })
|
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,
|
handleSaveApply: null,
|
||||||
// handleSave: null,
|
handleSave: null,
|
||||||
// handleReset: null
|
handleReset: null
|
||||||
});
|
});
|
||||||
+5
-37
@@ -1,5 +1,5 @@
|
|||||||
|
// Copyright (C) 2023-2025 muink
|
||||||
// Copyright (C) 2019-2026 sirpdboy
|
// Copyright (C) 2019-2026 sirpdboy
|
||||||
// Fixed version - better Ookla result parsing
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
'require view';
|
'require view';
|
||||||
@@ -25,7 +25,6 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
// 检测可用版本
|
|
||||||
detectVersions: function(res) {
|
detectVersions: function(res) {
|
||||||
var hasOokla = !!(res[0] && res[0].path);
|
var hasOokla = !!(res[0] && res[0].path);
|
||||||
var hasPython = !!(res[1] && res[1].path);
|
var hasPython = !!(res[1] && res[1].path);
|
||||||
@@ -37,17 +36,14 @@ return view.extend({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
// 检查是否真正在测试中(文件新鲜且内容为Testing)
|
|
||||||
isTesting: function(resultContent, resultMtime) {
|
isTesting: function(resultContent, resultMtime) {
|
||||||
if (!resultContent || resultContent.length === 0) return false;
|
if (!resultContent || resultContent.length === 0) return false;
|
||||||
if (resultContent[0].trim() !== 'Testing') return false;
|
if (resultContent[0].trim() !== 'Testing') return false;
|
||||||
|
|
||||||
// 检查文件是否在超时时间内
|
|
||||||
var fileAge = Date.now() - resultMtime;
|
var fileAge = Date.now() - resultMtime;
|
||||||
return fileAge < Timeout;
|
return fileAge < Timeout;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 解析结果内容
|
|
||||||
parseResult: function(content) {
|
parseResult: function(content) {
|
||||||
if (!content || content.length === 0) {
|
if (!content || content.length === 0) {
|
||||||
return { type: 'none', data: null };
|
return { type: 'none', data: null };
|
||||||
@@ -55,17 +51,13 @@ return view.extend({
|
|||||||
|
|
||||||
var firstLine = content[0].trim();
|
var firstLine = content[0].trim();
|
||||||
|
|
||||||
// 检查是否是Testing状态
|
|
||||||
if (firstLine === 'Testing') {
|
if (firstLine === 'Testing') {
|
||||||
return { type: 'testing', data: null };
|
return { type: 'testing', data: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是失败状态
|
|
||||||
if (firstLine === 'Test failed') {
|
if (firstLine === 'Test failed') {
|
||||||
return { type: 'failed', data: null };
|
return { type: 'failed', data: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否包含Result URL (Ookla格式)
|
|
||||||
var resultUrl = null;
|
var resultUrl = null;
|
||||||
for (var i = 0; i < content.length; i++) {
|
for (var i = 0; i < content.length; i++) {
|
||||||
var line = content[i];
|
var line = content[i];
|
||||||
@@ -82,18 +74,13 @@ return view.extend({
|
|||||||
return { type: 'url', data: resultUrl };
|
return { type: 'url', data: resultUrl };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是直接URL (Python格式)
|
|
||||||
if (firstLine.match(/^https?:\/\//) || firstLine.match(/\.png$/)) {
|
if (firstLine.match(/^https?:\/\//) || firstLine.match(/\.png$/)) {
|
||||||
return { type: 'url', data: firstLine };
|
return { type: 'url', data: firstLine };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是速度数据
|
|
||||||
if (firstLine.match(/Download:/i) || firstLine.match(/Upload:/i)) {
|
if (firstLine.match(/Download:/i) || firstLine.match(/Upload:/i)) {
|
||||||
return { type: 'speed', data: content.join('\n') };
|
return { type: 'speed', data: content.join('\n') };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他情况,可能是完整的速度测试结果
|
|
||||||
// 检查是否包含Download和Upload信息
|
|
||||||
var hasDownload = false;
|
var hasDownload = false;
|
||||||
var hasUpload = false;
|
var hasUpload = false;
|
||||||
for (var j = 0; j < content.length; j++) {
|
for (var j = 0; j < content.length; j++) {
|
||||||
@@ -114,18 +101,13 @@ return view.extend({
|
|||||||
|
|
||||||
var result_stat = nodes.querySelector('#speedtest_result');
|
var result_stat = nodes.querySelector('#speedtest_result');
|
||||||
var start_btn = nodes.querySelector('.cbi-button-apply');
|
var start_btn = nodes.querySelector('.cbi-button-apply');
|
||||||
|
|
||||||
// 解析结果
|
|
||||||
var result = this.parseResult(result_content);
|
var result = this.parseResult(result_content);
|
||||||
var is_testing = this.isTesting(result_content, result_mtime);
|
var is_testing = this.isTesting(result_content, result_mtime);
|
||||||
|
|
||||||
|
|
||||||
// 更新测试按钮状态
|
|
||||||
if (start_btn) {
|
if (start_btn) {
|
||||||
start_btn.disabled = is_testing;
|
start_btn.disabled = is_testing;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新结果状态
|
|
||||||
if (result_stat) {
|
if (result_stat) {
|
||||||
if (is_testing) {
|
if (is_testing) {
|
||||||
result_stat.innerHTML = "<span style='color:green;font-weight:bold;margin-left:20px'>" +
|
result_stat.innerHTML = "<span style='color:green;font-weight:bold;margin-left:20px'>" +
|
||||||
@@ -212,7 +194,6 @@ return view.extend({
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'unknown':
|
case 'unknown':
|
||||||
// 未知格式,直接显示
|
|
||||||
result_stat.innerHTML = "<div style='margin-left:20px; padding:10px; background:#f5f5f5; border-radius:4px'>" +
|
result_stat.innerHTML = "<div style='margin-left:20px; padding:10px; background:#f5f5f5; border-radius:4px'>" +
|
||||||
"<strong>" + _('Test Results:') + "</strong><br>" +
|
"<strong>" + _('Test Results:') + "</strong><br>" +
|
||||||
"<pre style='margin:5px 0 0 0; font-family:inherit; white-space:pre-wrap; color:#2e7d32'>" +
|
"<pre style='margin:5px 0 0 0; font-family:inherit; white-space:pre-wrap; color:#2e7d32'>" +
|
||||||
@@ -221,9 +202,8 @@ return view.extend({
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// 无结果
|
|
||||||
result_stat.innerHTML = "<span style='color:gray;margin-left:20px'>" +
|
result_stat.innerHTML = "<span style='color:gray;margin-left:20px'>" +
|
||||||
"<em>" + _('No test results yet. Click "Start Speed Test" to begin.') + "</em>" +
|
"<em>" + _('No test results yet.') + "</em>" +
|
||||||
"</span>";
|
"</span>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,9 +221,8 @@ return view.extend({
|
|||||||
var is_testing = this.isTesting(result_content, result_mtime);
|
var is_testing = this.isTesting(result_content, result_mtime);
|
||||||
|
|
||||||
var m, s, o;
|
var m, s, o;
|
||||||
m = new form.Map('netspeedtest', _('WAN SpeedTest'));
|
m = new form.Map('netspeedtest', _('Wan SpeedTest'));
|
||||||
|
|
||||||
// 结果显示区域
|
|
||||||
s = m.section(form.TypedSection, '_result');
|
s = m.section(form.TypedSection, '_result');
|
||||||
s.anonymous = true;
|
s.anonymous = true;
|
||||||
s.render = function(section_id) {
|
s.render = function(section_id) {
|
||||||
@@ -353,7 +332,6 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
case 'unknown':
|
case 'unknown':
|
||||||
// 未知格式,直接显示
|
|
||||||
return E('div', { id: result_id, class: 'cbi-section' }, [
|
return E('div', { id: result_id, class: 'cbi-section' }, [
|
||||||
E('div', { style: 'margin-left:20px; padding:10px; background:#f5f5f5; border-radius:4px' }, [
|
E('div', { style: 'margin-left:20px; padding:10px; background:#f5f5f5; border-radius:4px' }, [
|
||||||
E('strong', {}, _('Test Results:')),
|
E('strong', {}, _('Test Results:')),
|
||||||
@@ -365,20 +343,17 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// 无结果
|
|
||||||
return E('div', { id: result_id, class: 'cbi-section' }, [
|
return E('div', { id: result_id, class: 'cbi-section' }, [
|
||||||
E('span', { style: 'color:gray;margin-left:20px' }, [
|
E('span', { style: 'color:gray;margin-left:20px' }, [
|
||||||
E('em', {}, _('No test results yet. Click "Start Speed Test" to begin.'))
|
E('em', {}, _('No test results yet.'))
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 配置部分
|
|
||||||
s = m.section(form.NamedSection, 'config', 'netspeedtest');
|
s = m.section(form.NamedSection, 'config', 'netspeedtest');
|
||||||
s.anonymous = true;
|
s.anonymous = true;
|
||||||
|
|
||||||
// 版本选择
|
|
||||||
o = s.option(form.ListValue, 'test_version', _('Select Test Version'));
|
o = s.option(form.ListValue, 'test_version', _('Select Test Version'));
|
||||||
|
|
||||||
if (has_ookla) {
|
if (has_ookla) {
|
||||||
@@ -404,7 +379,6 @@ return view.extend({
|
|||||||
o.inputtitle = _('Click to start speed test');
|
o.inputtitle = _('Click to start speed test');
|
||||||
o.inputstyle = 'apply';
|
o.inputstyle = 'apply';
|
||||||
|
|
||||||
// 只有真正在测试中才禁用按钮
|
|
||||||
if (is_testing) {
|
if (is_testing) {
|
||||||
o.readonly = true;
|
o.readonly = true;
|
||||||
}
|
}
|
||||||
@@ -413,17 +387,14 @@ return view.extend({
|
|||||||
var btn = this;
|
var btn = this;
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
|
||||||
// 获取选中的版本
|
|
||||||
var versionSelect = document.getElementById('widget.cbid.netspeedtest.config.test_version');
|
var versionSelect = document.getElementById('widget.cbid.netspeedtest.config.test_version');
|
||||||
if (!versionSelect) {
|
if (!versionSelect) {
|
||||||
versionSelect = document.querySelector('select[name="test_version"]');
|
versionSelect = document.querySelector('select[name="test_version"]');
|
||||||
}
|
}
|
||||||
var version = versionSelect ? versionSelect.value : (has_ookla ? 'ookla' : 'python');
|
var version = versionSelect ? versionSelect.value : (has_ookla ? 'ookla' : 'python');
|
||||||
|
|
||||||
// 先写入Testing状态
|
|
||||||
return fs.write(ResultFile, 'Testing\n')
|
return fs.write(ResultFile, 'Testing\n')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
// 在后台执行测试脚本
|
|
||||||
var cmd = 'nohup ' + SpeedtestScript;
|
var cmd = 'nohup ' + SpeedtestScript;
|
||||||
if (version) {
|
if (version) {
|
||||||
cmd += ' --version ' + version;
|
cmd += ' --version ' + version;
|
||||||
@@ -445,9 +416,7 @@ return view.extend({
|
|||||||
|
|
||||||
return m.render()
|
return m.render()
|
||||||
.then(L.bind(function(m, nodes) {
|
.then(L.bind(function(m, nodes) {
|
||||||
nodes.result_mtime = result_mtime; // 保存结果文件修改时间
|
nodes.result_mtime = result_mtime;
|
||||||
|
|
||||||
// 添加轮询 - 每2秒检查一次结果
|
|
||||||
poll.add(L.bind(function() {
|
poll.add(L.bind(function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
L.resolveDefault(fs.stat('/usr/bin/ookla-speedtest'), {}),
|
L.resolveDefault(fs.stat('/usr/bin/ookla-speedtest'), {}),
|
||||||
@@ -469,7 +438,6 @@ return view.extend({
|
|||||||
handleReset: null
|
handleReset: null
|
||||||
});
|
});
|
||||||
|
|
||||||
// 辅助函数
|
|
||||||
function escapeHTML(str) {
|
function escapeHTML(str) {
|
||||||
if (!str) return '';
|
if (!str) return '';
|
||||||
return String(str)
|
return String(str)
|
||||||
|
|||||||
@@ -70,41 +70,29 @@ msgstr "Iperf3服务端"
|
|||||||
msgid "Command failed"
|
msgid "Command failed"
|
||||||
msgstr "命令无效"
|
msgstr "命令无效"
|
||||||
|
|
||||||
msgid "Failed to read log file"
|
|
||||||
msgstr "读取日志失败"
|
|
||||||
|
|
||||||
msgid "No log content available"
|
|
||||||
msgstr "没有日志记录"
|
|
||||||
|
|
||||||
msgid "Error: "
|
msgid "Error: "
|
||||||
msgstr "错误: "
|
msgstr "错误: "
|
||||||
|
|
||||||
msgid "Iperf3 service control"
|
msgid "Service Control"
|
||||||
msgstr "Iperf3 服务控制"
|
msgstr "服务控制"
|
||||||
|
|
||||||
msgid "Listen Port"
|
|
||||||
msgstr "监听端口"
|
|
||||||
|
|
||||||
msgid "Invalid format. Use [::]:port or ip:port"
|
|
||||||
msgstr "格式错误.如 [::]:端口 或 ip:端口"
|
|
||||||
|
|
||||||
msgid "Enable Log View"
|
|
||||||
msgstr "开启日志显示"
|
|
||||||
|
|
||||||
msgid "Download iperf3 client"
|
msgid "Download iperf3 client"
|
||||||
msgstr "下载iperf3客户端"
|
msgstr "下载iperf3客户端"
|
||||||
|
|
||||||
msgid "Run Log"
|
msgid "Refresh every %s seconds."
|
||||||
msgstr "运行日志"
|
msgstr "每 %s 秒刷新"
|
||||||
|
|
||||||
msgid "Refresh Log"
|
msgid "Clear logs"
|
||||||
msgstr "刷新日志"
|
msgstr "清除日志"
|
||||||
|
|
||||||
msgid "Official Website"
|
msgid "Official Website"
|
||||||
msgstr "官方网站"
|
msgstr "官方网站"
|
||||||
|
|
||||||
msgid "Please enable the Homebox service"
|
msgid "Please start the Homebox service"
|
||||||
msgstr "请将homebox服务启用"
|
msgstr "请启动homebox服务"
|
||||||
|
|
||||||
msgid "Homebox service control"
|
msgid "Homebox service control"
|
||||||
msgstr "Homebox服务控制"
|
msgstr "Homebox服务控制"
|
||||||
@@ -145,6 +133,18 @@ msgstr "由于浏览器安全策略,Homebox接口https不能直接嵌入。"
|
|||||||
msgid "Select speed measurement station"
|
msgid "Select speed measurement station"
|
||||||
msgstr "选择测速站点"
|
msgstr "选择测速站点"
|
||||||
|
|
||||||
|
msgid "Iperf3 Port"
|
||||||
|
msgstr "Iperf3监听端口"
|
||||||
|
|
||||||
|
msgid "Port range must be 1024-65535"
|
||||||
|
msgstr "端口范围1024-65535"
|
||||||
|
|
||||||
|
msgid "Auto Start"
|
||||||
|
msgstr "自动启动"
|
||||||
|
|
||||||
|
msgid "Port Setting"
|
||||||
|
msgstr "监听端口"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# netspeedtest init script
|
||||||
|
|
||||||
|
START=95
|
||||||
|
STOP=15
|
||||||
|
USE_PROCD=1
|
||||||
|
|
||||||
|
PATH=/usr/bin:/bin:/usr/sbin:/sbin
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
|
||||||
|
START_IPERF3=$(uci get netspeedtest.config.iperf3_enabled 2>/dev/null || echo "0")
|
||||||
|
START_HOMEBOX=$(uci get netspeedtest.config.homebox_enabled 2>/dev/null || echo "0")
|
||||||
|
|
||||||
|
[ "$START_HOMEBOX" = "1" ] || return 0
|
||||||
|
port=$(uci get netspeedtest.config.homebox_port 2>/dev/null || echo "3300")
|
||||||
|
|
||||||
|
|
||||||
|
procd_open_instance
|
||||||
|
procd_set_param command /usr/bin/homebox serve --port "$port"
|
||||||
|
procd_set_param stdout 1
|
||||||
|
procd_set_param stderr 1
|
||||||
|
procd_set_param respawn
|
||||||
|
procd_close_instance
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
logger -t netspeedtest "Stopping homebox"
|
||||||
|
|
||||||
|
ubus call service delete '{ "name": "netspeedtest" }' 2>/dev/null
|
||||||
|
|
||||||
|
killall homebox 2>/dev/null || true
|
||||||
|
|
||||||
|
local retries=5
|
||||||
|
while pidof homebox >/dev/null 2>&1 && [ $retries -gt 0 ]; do
|
||||||
|
sleep 0.2
|
||||||
|
retries=$((retries-1))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
service_triggers() {
|
||||||
|
procd_add_reload_trigger "netspeedtest"
|
||||||
|
}
|
||||||
|
|
||||||
|
reload_service() {
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
|
||||||
|
START_HOMEBOX=$(uci get netspeedtest.config.homebox_enabled 2>/dev/null || echo "0")
|
||||||
|
|
||||||
|
if [ "$START_HOMEBOX" != "1" ]; then
|
||||||
|
return 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
_procd_status
|
||||||
|
}
|
||||||
|
|
||||||
|
boot() {
|
||||||
|
sleep 2
|
||||||
|
start
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#!/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
|
|
||||||
+23
-11
@@ -1,51 +1,63 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2024 sbwml <admin@cooluc.com>
|
||||||
|
# Copyright (c) 2025-2026 sirpdboy <herboy2008@gmail.com>
|
||||||
|
#
|
||||||
|
# This is free software, licensed under the GPL-3.0 License.
|
||||||
|
#
|
||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ookla-speedtest
|
PKG_NAME:=ookla-speedtest
|
||||||
PKG_VERSION:=1.2.0
|
PKG_VERSION:=1.2.0
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
ifeq ($(ARCH),aarch64)
|
ifeq ($(ARCH),aarch64)
|
||||||
|
PKG_ARCH_SUFFIX:=aarch64
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
else ifeq ($(ARCH),arm)
|
else ifeq ($(ARCH),arm)
|
||||||
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
|
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
|
||||||
ifeq ($(ARM_CPU_FEATURES),)
|
ifeq ($(ARM_CPU_FEATURES),)
|
||||||
ARCH:=armel
|
PKG_ARCH_SUFFIX:=armel
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
else
|
else
|
||||||
ARCH:=armhf
|
PKG_ARCH_SUFFIX:=armhf
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
endif
|
endif
|
||||||
else ifeq ($(ARCH),i386)
|
else ifeq ($(ARCH),i386)
|
||||||
|
PKG_ARCH_SUFFIX:=i386
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
else ifeq ($(ARCH),x86_64)
|
else ifeq ($(ARCH),x86_64)
|
||||||
|
PKG_ARCH_SUFFIX:=x86_64
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
endif
|
endif
|
||||||
|
|
||||||
PKG_SOURCE:=ookla-speedtest-$(PKG_VERSION)-linux-$(ARCH).tgz
|
PKG_SOURCE:=ookla-speedtest-$(PKG_VERSION)-linux-$(PKG_ARCH_SUFFIX).tgz
|
||||||
PKG_SOURCE_URL:=https://install.speedtest.net/app/cli
|
PKG_SOURCE_URL:=https://install.speedtest.net/app/cli
|
||||||
|
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)-$(PKG_ARCH_SUFFIX)
|
||||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
|
||||||
|
|
||||||
PKG_MAINTAINER:=sbwml <admin@cooluc.com>
|
PKG_MAINTAINER:=sbwml <admin@cooluc.com>
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
define Package/$(PKG_NAME)
|
define Package/$(PKG_NAME)
|
||||||
SECTION:=net
|
SECTION:=net
|
||||||
CATEGORY:=Network
|
CATEGORY:=Network
|
||||||
|
SUBMENU:=Speed Test
|
||||||
TITLE:=Speedtest CLI by Ookla
|
TITLE:=Speedtest CLI by Ookla
|
||||||
|
URL:=https://www.speedtest.net/
|
||||||
DEPENDS:=@(aarch64||arm||i386||x86_64) +ca-certificates
|
DEPENDS:=@(aarch64||arm||i386||x86_64) +ca-certificates
|
||||||
URL:=https://www.speedtest.net/
|
URL:=https://www.speedtest.net/
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Package/$(PKG_NAME)/description
|
define Package/$(PKG_NAME)/description
|
||||||
The Global Broadband Speed Test
|
Speedtest CLI by Ookla is the official command line client
|
||||||
|
for testing internet bandwidth using speedtest.net servers.
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
|
||||||
define Build/Prepare
|
define Build/Prepare
|
||||||
( \
|
( \
|
||||||
pushd $(PKG_BUILD_DIR) ; \
|
pushd $(PKG_BUILD_DIR) ; \
|
||||||
$(TAR) -zxf $(DL_DIR)/ookla-speedtest-$(PKG_VERSION)-linux-$(ARCH).tgz -C . ; \
|
$(TAR) -zxf $(DL_DIR)/ookla-speedtest-$(PKG_VERSION)-linux-$(PKG_ARCH_SUFFIX).tgz -C . ; \
|
||||||
popd ; \
|
popd ; \
|
||||||
)
|
)
|
||||||
endef
|
endef
|
||||||
@@ -58,4 +70,4 @@ define Package/$(PKG_NAME)/install
|
|||||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/speedtest $(1)/usr/bin/ookla-speedtest
|
$(INSTALL_BIN) $(PKG_BUILD_DIR)/speedtest $(1)/usr/bin/ookla-speedtest
|
||||||
endef
|
endef
|
||||||
|
|
||||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||||
Reference in new issue
Block a user