diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..b1b64f9 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,77 @@ +name: Build + +on: + push: + tags: + - v* + +jobs: + build: + name: Build ${{ matrix.arch }}-${{ matrix.sdk }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + arch: + - aarch64_cortex-a53 + - aarch64_cortex-a72 + - aarch64_generic + - arm_arm1176jzf-s_vfp + - arm_arm926ej-s + - arm_cortex-a15_neon-vfpv4 + - arm_cortex-a5_vfpv4 + - arm_cortex-a7 + - arm_cortex-a7_neon-vfpv4 + - arm_cortex-a8_vfpv3 + - arm_cortex-a9 + - arm_cortex-a9_neon + - arm_cortex-a9_vfpv3-d16 + - arm_fa526 + - arm_mpcore + - arm_xscale + - i386_pentium-mmx + - i386_pentium4 + - mips64_octeonplus + - mips_24kc + - mips_4kec + - mips_mips32 + - mipsel_24kc + - mipsel_24kc_24kf + - mipsel_74kc + - mipsel_mips32 + - x86_64 + + steps: + - uses: actions/checkout@main + with: + fetch-depth: 0 + - name: Building packages + uses: sbwml/openwrt-gh-action-sdk@go1.25 + env: + ARCH: ${{ matrix.arch }}-openwrt-24.10 + FEEDNAME: packages_ci + PACKAGES: luci-app-netspeedtest + NO_REFRESH_CHECK: true + + - name: add iperf + run: | + cp iperf-3.1.3-win64.zip bin/packages/${{ matrix.arch }}/packages_ci + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.arch }} + path: bin/packages/${{ matrix.arch }}/packages_ci/*.ipk + - name: Create compress files + run: | + tar -zcvf openwrt-24.10-${{ matrix.arch }}.tar.gz -C bin/packages/${{ matrix.arch }}/ packages_ci + + + - name: Upload packages + uses: ncipollo/release-action@v1 + with: + name: ${{ github.ref_name }} + token: ${{ secrets.GITHUB_TOKEN }} + allowUpdates: true + replacesArtifacts: true + artifacts: "${{ matrix.sdk }}-${{ matrix.arch }}.tar.gz" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..574c7ac --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 sirpdboy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 087b077..1c7c252 100644 --- a/README.md +++ b/README.md @@ -1,167 +1,115 @@ -## luci-app-netspeedtest +![hello](https://views.whatilearened.today/views/github/sirpdboy/deplives.svg) [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ) -### 访问数:[![](https://visitor-badge.glitch.me/badge?page_id=sirpdboy-visitor-badge)] [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ) +

+
Net Speed Test
+

-![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明1.jpg) - -luci-app-netspeedtest 网络速度诊断测试(包括:内网网页版测速、内网iperf3吞吐测速、外网speedtest.net网速测试、特定服务器的端口延迟测速) - -[luci-app-netspeedtest 网络速度诊断测试](https://github.com/sirpdboy/netspeedtest) - -请 **认真阅读完毕** 本页面,本页面包含注意事项和如何使用。 - -## 写在前面 - - - 一直在找OPENWRT上测试速度的插件,苦寻不到,于是有了它! 此插件可进行内外和外网网络速度测试。 - - TG群友说插件2年没更新了,花了几天时间结合时下需要,将网络测试功能升级到2.0版本。 - - - -## [菜单向导](#luci-app-netspeedtest) - - [功能说明](#功能说明) - - [注意事项](#iperf3吞吐测试注意事项) - - [版本说明](#版本说明) - - [使用方法](#使用方法) - - [源码说明](#源码说明) - - [界面](#界面) - - [其它](#其它) - - [感谢](#感谢) - - [捐助](#捐助) - - - -## 功能说明 -- 内网网页版测速插件 :基于speedtest-web网页版,启用后再点start进行测速。网页版启动后程序会驻留内存不测速建议不启用服务。 -- 内网iperf3吞吐测试 ,服务端路由器如果没有安装请先安装此iperf3插件。 -- 外网测速使用speedtest.net测速内核,基于speedtest-cli,需要有python3才能执行。 -- 特定服务器的端口延迟测速,是测试指定服务器的端口的延迟情况。 - -## iperf3吞吐测试注意事项 -- 测速的终端使用机器必须和测速服务器在同一个局域网络中! -- 客户端使用步骤:启动测速服务器端-->下载测试客户端-->运行测速客户端-->输入服务端IP地址-->查看结果。 -- 客户端运行,国内端下载中有“iperf3测速客户端”,运行它输入服务器IP即可。 - 国外原版,需要手动进入 CMD命令模式,再输入命令:iperf3.exe -c 服务器IP -- 网络测速iperf3客户端下载地址:https://sipdboy.lanzoui.com/b01c3esih 密码:cpd6 -- 需要依赖: python3 iperf3 speedtest-web - -## 版本说明 - - -### 2023.3.2 网速测试V2.1.3: - - 修复测速Speedtest看不到测试报名问题。 - - 重新调试IPERF3测试页面代码,解决某些主题显示不优雅的问题。 - - 修复取消服务自动启用的问题 - -### 2023.1.15 网速测试V2.1: - - 内网测试速度WEB页采用homebox。 - - 修复WEB页内网测试自动启用问题。 - - 外网测速加入Netperf测试。 - - 外网测速Speedtest某些节点会禁止测速。要测速建议关了留国的插件。 - -### 2022.10.18 网速测试V2.0.3: - - 代码基本重写和优化。 - - Iperf3可实时体现服务状态。 - - 增加内网测试网页版。 - - 外网测速,加入更详细测试报告。 - -### 2021.3.2 网速测试V1.6: - - 升级宽带测试带2.13内核。 - - 解决1.806以上版本不能编译问题。 - -## 使用方法 - -将NetSpeedTest 主题添加至 LEDE/OpenWRT 源码的方法。 - -### 下载源码: - - # 下载源码 - - git clone https://github.com/sirpdboy/netspeedtest.git package/netspeedtest - make menuconfig - - ``` -### 配置菜单 - - ```Brach - make menuconfig - # 找到 LuCI -> Applications, 选择 luci-app-netspeedtest, 保存后退出。 - ``` - -### 编译 - - ```Brach - # 编译固件 - make package/netspeedtest/luci-app-netspeedtest/compile V=s - ``` - - -## 源码说明 - -- 源码来源和依赖: -- luci-app-netspeedtest:https://github.com/sirpdboy/netspeedtest -- speedtest-web:https://github.com/ZeaKyX/speedtest-web -- speedtest-cl:https://github.com/sivel/speedtest-cli +

-- 你可以随意使用其中的源码,但请注明出处。 + + + +

-![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明2.jpg) +[中文](README_CN.md) | English -## 界面 +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明1.jpg) -![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest1.jpg) +Luci app netspeedtest network speed diagnostic test (including: intranet web version speed test, intranet iperf3 throughput speed test, intranet speedtest.net network speed test, specific server port latency speed test) -![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest2.jpg) +Please read this page carefully, which includes precautions and instructions on how to use it. -![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest3.jpg) +##Write it in front -![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest4.jpg) +- I have been looking for a plugin to test speed on OPENWRT, but I couldn't find it, so I came up with it! This plugin can perform internal and external network speed testing. +- TG group members said that the plugin hasn't been updated for 2 years, and it took a few days to upgrade the network testing function to version 2.0 based on current needs. +## Function Description +- Internal network web version speed measurement plugin: Based on the HomeBox web version, enable it and then click start to perform speed measurement. After the web version is launched, the program will reside in memory. It is not recommended to enable the service due to slow speed. +- Internal iperf3 throughput test, if the server router is not installed, please install this iperf3 plugin first. +- The external network speed measurement uses the speedtest.net speed measurement kernel, based on speedtest cli, and cancels the old Python 3. +- The port delay speed measurement of a specific server is to test the delay situation of the specified server's port. -## 使用与授权相关说明 - -- 本人开源的所有源码,任何引用需注明本处出处,如需修改二次发布必告之本人,未经许可不得做于任何商用用途。 +## Precautions for iperf3 throughput testing +- The terminal for speed measurement must be on the same local area network as the speed measurement server! +- Client usage steps: Start the speed measurement server -->Download the test client -->Run the speed measurement client -->Enter the server IP address -->View the results. +- The client is running, and there is a "iperf3 speed measurement client" available for download on the domestic end. Simply enter the server IP to run it. +The original version from abroad requires manually entering CMD command mode and then entering the command: iperf3.exe - c server IP +- Download link for iperf3 client for network speed measurement: https://sipdboy.lanzoui.com/b01c3esih Password: cpd6 + -Need to rely on: speedtest cli -# My other project -- 网络速度测试 :https://github.com/sirpdboy/NetSpeedTest +### downloading source: -- 定时设置插件 : https://github.com/sirpdboy/luci-app-autotimeset + ```Brach + # downloading + git clone https://github.com/sirpdboy/luci-app-netspeedtest package/netspeedtest + make menuconfig + + ``` +### Configuration Menu -- 关机功能插件 : https://github.com/sirpdboy/luci-app-poweroffdevice + ```Brach + make menuconfig + # find LuCI -> Applications, select luci-app-netspeedtest, save and exit + ``` +### compile + ```Brach + # compile + make package/netspeedtest/luci-app-netspeedtest/compile V=s + ``` -- opentopd主题 : https://github.com/sirpdboy/luci-theme-opentopd -- kucat 主题: https://github.com/sirpdboy/luci-theme-kucat +## describe +- luci-app-netspeedtest:https://github.com/sirpdboy/luci-app-netspeedtest +- homebox:https://github.com/hay-kot/homebox +- speedtest-cli: https://github.com/sbwml/openwrt_pkgs +- speedtest-js: https://github.com/muink/luci-app-netspeedtest -- 家长控制: https://github.com/sirpdboy/luci-theme-parentcontrol +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明2.jpg) -- 系统高级设置 : https://github.com/sirpdboy/luci-app-advanced -- ddns-go动态域名: https://github.com/sirpdboy/luci-app-ddns-go +## interface -- 进阶设置(系统高级设置+主题设置kucat/agron/opentopd): https://github.com/sirpdboy/luci-app-advancedplus +![screenshots](./演示.gif) -- 设置向导: https://github.com/sirpdboy/luci-app-wizard +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest1.png) -- 分区扩容: https://github.com/sirpdboy/luci-app-partexp +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest2.png) -- lukcy大吉: https://github.com/sirpdboy/luci-app-lukcy +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest3.png) -## 捐助 +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest4.png) -![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明3.jpg) +# My other project -| 图飞了😂 | 图飞了😂 | +- Watch Dog : https://github.com/sirpdboy/luci-app-watchdog +- Net Speedtest : https://github.com/sirpdboy/luci-app-netspeedtest +- Task Plan : https://github.com/sirpdboy/luci-app-taskplan +- Power Off Device : https://github.com/sirpdboy/luci-app-poweroffdevice +- OpentoPD Theme : https://github.com/sirpdboy/luci-theme-opentopd +- Ku Cat Theme : https://github.com/sirpdboy/luci-theme-kucat +- Ku Cat Theme Config : https://github.com/sirpdboy/luci-app-kucat-config +- NFT Time Control : https://github.com/sirpdboy/luci-app-timecontrol +- Parent Control: https://github.com/sirpdboy/luci-theme-parentcontrol +- Eqos Plus: https://github.com/sirpdboy/luci-app-eqosplus +- Advanced : https://github.com/sirpdboy/luci-app-advanced +- ddns-go : https://github.com/sirpdboy/luci-app-ddns-go +- Advanced Plus): https://github.com/sirpdboy/luci-app-advancedplus +- Net Wizard: https://github.com/sirpdboy/luci-app-netwizard +- Part Exp: https://github.com/sirpdboy/luci-app-partexp +- Lukcy: https://github.com/sirpdboy/luci-app-lukcy + +## HELP + +| 图飞了 | 图飞了 | | :-----------------: | :-------------: | |![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/支付宝.png) | ![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/微信.png) | - 图飞了😂 + no - -## 感谢 - -感谢sivel、superspeed、user1121114685、ZeaKyX、佐须之男、lean等。因为有你们珠玉在前! +![hello](https://visitor-badge-deno.deno.dev/sirpdboy.sirpdboy.svg) [![](https://img.shields.io/badge/TGGroup-ClickJoin-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ) diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..bdaf039 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,184 @@ +![hello](https://views.whatilearened.today/views/github/sirpdboy/deplives.svg) [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ) + +

+
Net Speed Test
+

+ +

+ + + + +

+ +[中文] | [English](README.md) + +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明1.jpg) + +luci-app-netspeedtest 网络速度诊断测试(包括:内网网页版测速、内网iperf3吞吐测速、外网speedtest.net网速测试、特定服务器的端口延迟测速) + +[luci-app-netspeedtest 网络速度诊断测试](https://github.com/sirpdboy/netspeedtest) + +请 **认真阅读完毕** 本页面,本页面包含注意事项和如何使用。 + +## 写在前面 + + - 一直在找OPENWRT上测试速度的插件,苦寻不到,于是有了它! 此插件可进行内外和外网网络速度测试。 + - TG群友说插件2年没更新了,花了几天时间结合时下需要,将网络测试功能升级到2.0版本。 + + + +## [菜单向导](#luci-app-netspeedtest) + - [功能说明](#功能说明) + - [注意事项](#iperf3吞吐测试注意事项) + - [版本说明](#版本说明) + - [使用方法](#使用方法) + - [源码说明](#源码说明) + - [界面](#界面) + - [其它](#其它) + - [感谢](#感谢) + - [捐助](#捐助) + + + +## 功能说明 +- 内网网页版测速插件 :基于homebox网页版,启用后再点start进行测速。网页版启动后程序会驻留内存不测速建议不启用服务。 +- 内网iperf3吞吐测试 ,服务端路由器如果没有安装请先安装此iperf3插件。 +- 外网测速使用speedtest.net测速内核,基于speedtest-cli,取消原来老python3了。 +- 特定服务器的端口延迟测速,是测试指定服务器的端口的延迟情况。 + +## iperf3吞吐测试注意事项 +- 测速的终端使用机器必须和测速服务器在同一个局域网络中! +- 客户端使用步骤:启动测速服务器端-->下载测试客户端-->运行测速客户端-->输入服务端IP地址-->查看结果。 +- 客户端运行,国内端下载中有“iperf3测速客户端”,运行它输入服务器IP即可。 + 国外原版,需要手动进入 CMD命令模式,再输入命令:iperf3.exe -c 服务器IP +- 网络测速iperf3客户端下载地址:https://sipdboy.lanzoui.com/b01c3esih 密码:cpd6 +- 需要依赖: speedtest-cli + +## 版本说明 + + +### 2025.5.13 网速测试V5.0.2: + - 统一日志,Iperf3,Homebox和speedtest-cli统一显示于日志中。 + - 修复speedtest-cli显示等问题。 + - 主打一个简单方便好用! + +### 2025.5.10 网速测试V5.0.1: + - JS版适配最新官方的OPENWRT。 + - 新增外网Ookla speedtest.net网速测试,测速更精准,测试完提供测试图片更直观。 + - 内网iperf3吞吐测速,提供实时日志显示,更方便。 + - 内网Homebox网页测速,启用服务,即可使用,同页面操作更方便。 + - speedtest-cli版本注册需要删除原来系统自带的,不然可能会冲突。 + +### 2023.3.2 网速测试V2.1.3: + - 修复测速Speedtest看不到测试报名问题。 + - 重新调试IPERF3测试页面代码,解决某些主题显示不优雅的问题。 + - 修复取消服务自动启用的问题 + +### 2023.1.15 网速测试V2.1: + - 内网测试速度WEB页采用homebox。 + - 修复WEB页内网测试自动启用问题。 + - 外网测速加入Netperf测试。 + - 外网测速Speedtest某些节点会禁止测速。要测速建议关了留国的插件。 + +### 2022.10.18 网速测试V2.0.3: + - 代码基本重写和优化。 + - Iperf3可实时体现服务状态。 + - 增加内网测试网页版。 + - 外网测速,加入更详细测试报告。 + +### 2021.3.2 网速测试V1.6: + - 升级宽带测试带2.13内核。 + - 解决1.806以上版本不能编译问题。 + +## 使用方法 + +将NetSpeedTest 主题添加至 LEDE/OpenWRT 源码的方法。 + +### 下载源码: + + # 下载源码 + + rm -rf ./feeds/packages/net/speedtest-cli #删除原来老版本 + git clone https://github.com/sirpdboy/luci-app-netspeedtest package/netspeedtest + make menuconfig + + ``` +### 配置菜单 + + ```Brach + make menuconfig + # 找到 LuCI -> Applications, 选择 luci-app-netspeedtest, 保存后退出。 + ``` + +### 编译 + + ```Brach + # 编译固件 + make package/netspeedtest/luci-app-netspeedtest/compile V=s + ``` + + +## 源码说明 + +- 源码来源和依赖: +- luci-app-netspeedtest:https://github.com/sirpdboy/luci-app-netspeedtest +- homebox:https://github.com/hay-kot/homebox +- speedtest-cli: https://github.com/sbwml/openwrt_pkgs + +- 你可以随意使用其中的源码,但请注明出处。 + +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明2.jpg) + +## 界面 + +![screenshots](./演示.gif) + +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest1.png) + +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest2.png) + +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest3.png) + +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/netspeedtest4.png) + + + +## 使用与授权相关说明 + +- 本人开源的所有源码,任何引用需注明本处出处,如需修改二次发布必告之本人,未经许可不得做于任何商用用途。 + + +# My other project + +- 路由安全看门狗 :https://github.com/sirpdboy/luci-app-watchdog +- 网络速度测试 :https://github.com/sirpdboy/luci-app-netspeedtest +- 计划任务插件(原定时设置) : https://github.com/sirpdboy/luci-app-taskplan +- 关机功能插件 : https://github.com/sirpdboy/luci-app-poweroffdevice +- opentopd主题 : https://github.com/sirpdboy/luci-theme-opentopd +- kucat酷猫主题: https://github.com/sirpdboy/luci-theme-kucat +- kucat酷猫主题设置工具: https://github.com/sirpdboy/luci-app-kucat-config +- NFT版上网时间控制插件: https://github.com/sirpdboy/luci-app-timecontrol +- 家长控制: https://github.com/sirpdboy/luci-theme-parentcontrol +- 定时限速: https://github.com/sirpdboy/luci-app-eqosplus +- 系统高级设置 : https://github.com/sirpdboy/luci-app-advanced +- ddns-go动态域名: https://github.com/sirpdboy/luci-app-ddns-go +- 进阶设置(系统高级设置+主题设置kucat/agron/opentopd): https://github.com/sirpdboy/luci-app-advancedplus +- 网络设置向导: https://github.com/sirpdboy/luci-app-netwizard +- 一键分区扩容: https://github.com/sirpdboy/luci-app-partexp +- lukcy大吉: https://github.com/sirpdboy/luci-app-lukcy + +## 捐助 + +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明3.jpg) + +| 图飞了 | 图飞了 | +| :-----------------: | :-------------: | +|![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/支付宝.png) | ![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/微信.png) | + + + 图飞了 + + +![](https://visitor-badge-deno.deno.dev/sirpdboy.sirpdboy.svg) [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ) + diff --git a/homebox/Makefile b/homebox/Makefile index 6714801..3a877e3 100644 --- a/homebox/Makefile +++ b/homebox/Makefile @@ -5,8 +5,9 @@ include $(TOPDIR)/rules.mk PKG_NAME:=homebox -PKG_VERSION:=0.0.0-dev.2023102203 -PKG_RELEASE:=3 +PKG_VERSION:=0.0.0.2024101306 +PKG_REAL_VER:=0.0.0-dev.2024101306 +PKG_RELEASE:=1 ifeq ($(ARCH),aarch64) H_ARCH:=arm64 @@ -32,9 +33,9 @@ include $(INCLUDE_DIR)/package.mk define Package/$(PKG_NAME) SECTION:=net CATEGORY:=Network - TITLE:=A Toolbox for Home Local Networks + TITLE:=A Toolbox for Home Local Networks Speed Test URL:=https://github.com/XGHeaven/homebox - DEPENDS:=@(i386||x86_64||arm||aarch64||mipsel||mips) + DEPENDS:=@(i386||x86_64||arm||aarch64||mipsel||mips) +libstdcpp endef define Package/$(PKG_NAME)/description @@ -42,7 +43,8 @@ define Package/$(PKG_NAME)/description endef define Build/Prepare - [ ! -f $(PKG_BUILD_DIR)/server_Linux_$(H_ARCH).tar.gz ] && wget https://github.com/XGHeaven/homebox/releases/download/v$(PKG_VERSION)/server-linux-$(H_ARCH).tar.gz -O $(PKG_BUILD_DIR)/server-linux-$(H_ARCH).tar.gz + 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 tar -xzvf $(PKG_BUILD_DIR)/server-linux-$(H_ARCH).tar.gz -C $(PKG_BUILD_DIR) endef diff --git a/iperf-3.1.3-win64.zip b/iperf-3.1.3-win64.zip new file mode 100644 index 0000000..ee46cd0 Binary files /dev/null and b/iperf-3.1.3-win64.zip differ diff --git a/luci-app-netspeedtest/LICENSE b/luci-app-netspeedtest/LICENSE new file mode 100644 index 0000000..574c7ac --- /dev/null +++ b/luci-app-netspeedtest/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 sirpdboy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/luci-app-netspeedtest/Makefile b/luci-app-netspeedtest/Makefile index e7181ae..c9ee7f2 100644 --- a/luci-app-netspeedtest/Makefile +++ b/luci-app-netspeedtest/Makefile @@ -1,29 +1,21 @@ - -# Copyright (C) 2020-2021 sirpdboy +# SPDX-License-Identifier: GPL-3.0-only +# +# Copyright (C) 2021-2026 sirpdboy +# https://github.com/sirpdboy/luci-app-netspeedtest +# This is free software, licensed under the Apache License, Version 2.0 . # -# This is free software, licensed under the GNU General Public License v3. -# include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-netspeedtest -PKG_VERSION:=2.2.4 -PKG_RELEASE:=20240319 +PKG_VERSION:=5.1.2 +PKG_RELEASE:=20260226 LUCI_TITLE:=LuCI Support for netspeedtest -LUCI_DEPENDS:=+python3 +iperf3-ssl +homebox +LUCI_DEPENDS:=+speedtest-cli +homebox +netspeedtest $(if $(find_package iperf3-ssl),+iperf3-ssl,+iperf3) LUCI_PKGARCH:=all -PKG_MAINTAINER:= - - -define Package/$(PKG_NAME)/conffiles -/etc/config/netspeedtest -endef - -define Package/$(PKG_NAME)/postinst -endef - +PKG_MAINTAINER:=sirpdboy include $(TOPDIR)/feeds/luci/luci.mk diff --git a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/homebox.js b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/homebox.js new file mode 100644 index 0000000..a38a4c0 --- /dev/null +++ b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/homebox.js @@ -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', {}, _('Lan Speedtest Homebox')), + E('div', { 'class': 'cbi-map-descr' }, [statusIcon, statusText]), + E('div', {'class': 'cbi-value', 'style': 'margin-top: 20px'}, [ + E('div', {'class': 'cbi-value-title'}, _('Homebox service control')), + E('div', {'class': 'cbi-value-field'}, toggleBtn), + E('div', { 'style': 'text-align: right; font-style: italic; margin-top: 20px;' }, [ + _('© github '), + E('a', { + 'href': 'https://github.com/sirpdboy', + '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 +}); \ No newline at end of file diff --git a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/iperf3.js b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/iperf3.js new file mode 100644 index 0000000..b46386d --- /dev/null +++ b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/iperf3.js @@ -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', {}, _('Lan Speedtest Iperf3')), + E('div', { 'class': 'cbi-map-descr' }, [statusIcon, statusText]), + E('div', {'class': 'cbi-value', 'style': 'margin-top: 20px'}, [ + E('div', {'class': 'cbi-value-title'}, _('Iperf3 service control')), + E('div', {'class': 'cbi-value-field'}, toggleBtn), + + E('div', {'class': 'cbi-value-title'}, _('Download iperf3 client')), + E('div', {'class': 'cbi-value-field'}, [ + E('div', { + 'class': 'cbi-value-field', + 'style': 'display: flex;' + }, [ + E('button', { + 'class': 'btn cbi-button cbi-button-save', + 'click': ui.createHandlerFn(this, () => window.open('https://iperf.fr/iperf-download.php', '_blank')) + }, _('Official Website')), + E('button', { + 'class': 'btn cbi-button cbi-button-save', + 'click': ui.createHandlerFn(this, () => window.open('https://github.com/sirpdboy/luci-app-netspeedtest/releases', '_blank')) + }, _('GitHub')) + ]) + ]) + ]), + E('div', { 'style': 'text-align: right; font-style: italic; margin-top: 20px;' }, [ + _('© github '), + E('a', { + 'href': 'https://github.com/sirpdboy/luci-app-netspeedtest', + 'target': '_blank', + 'style': 'text-decoration: none;' + }, 'by sirpdboy') + ]) + ]) +]); + + // 初始化状态检查 + checkProcess().then(res => { + state.running = res.running; + updateStatus(); + toggleBtn.disabled = false; + + // 启动轮询 + poll.add(() => { + return checkProcess().then(res => { + if (res.running !== state.running) { + state.running = res.running; + updateStatus(); + } + }); + }, 5); + }); + + + return statusSection; + } + +}); diff --git a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/logs.js b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/logs.js new file mode 100644 index 0000000..55efcf3 --- /dev/null +++ b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/logs.js @@ -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 +}); \ No newline at end of file diff --git a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/onlinespeedtest.js b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/onlinespeedtest.js new file mode 100644 index 0000000..ad6e473 --- /dev/null +++ b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/onlinespeedtest.js @@ -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 +}); \ No newline at end of file diff --git a/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/speedtest.js b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/speedtest.js new file mode 100644 index 0000000..796c595 --- /dev/null +++ b/luci-app-netspeedtest/htdocs/luci-static/resources/view/netspeedtest/speedtest.js @@ -0,0 +1,176 @@ +/* Copyright (c) 2023-2025 muink +Copyright (C) 2026 sirpdboy */ +'use strict'; +'require view'; +'require poll'; +'require dom'; +'require fs'; +'require rpc'; +'require uci'; +'require ui'; +'require form'; +// 全局变量 +var TestTimeout = 240 * 1000; // 4 Minutes +var ResultFile = '/tmp/speedtest_result'; +var SpeedtestCli = '/usr/bin/speedtest'; +var SpeedtestScript = '/usr/lib/netspeedtest/speedtest'; + +return view.extend({ + load() { + return Promise.all([ + L.resolveDefault(fs.stat(SpeedtestCli), {}), + L.resolveDefault(fs.read(ResultFile), null), + L.resolveDefault(fs.stat(ResultFile), {}), + uci.load('netspeedtest') + ]); + }, + + poll_status(nodes, res) { + var has_ookla = res[0].path, + result_content = res[1] ? res[1].trim().split("\n") : []; + var ookla_stat = nodes.querySelector('#ookla_status'), + result_stat = nodes.querySelector('#speedtest_result'), + start_btn = nodes.querySelector('.cbi-button-apply'); + + // 更新测试按钮状态 + if (start_btn) { + if (result_content.length && result_content[0] == 'Testing' && + (Date.now() - (nodes.result_mtime || 0)) < TestTimeout) { + start_btn.disabled = true; + } else { + start_btn.disabled = false; + } + } + + // 获取版本号 + var version_info = ''; + if (has_ookla) { + fs.exec(SpeedtestCli, ['--version']) + .then(function(res) { + if (res.stdout) { + // 匹配 "Speedtest by Ookla 1.2.0.84" 这样的格式 + var version_match = res.stdout.match(/Speedtest by Ookla (\d+\.\d+\.\d+\.\d+)/) || + res.stdout.match(/Speedtest (\d+\.\d+\.\d+\.\d+)/); + if (version_match) { + version_info = ' ver:' + version_match[1]; + } else { + // 如果上面没匹配到,尝试匹配更简单的版本号格式 + version_match = res.stdout.match(/(\d+\.\d+\.\d+)/); + if (version_match) { + version_info = ' ver:' + version_match[1]; + } + } + } + // 更新状态显示(包含版本号) + ookla_stat.style.color = 'green'; + dom.content(ookla_stat, [_(has_ookla ? 'Installed' + version_info : 'Not Installed')]); + }) + .catch(function() { + // 如果获取版本失败,仍显示基本状态 + ookla_stat.style.color = has_ookla ? 'green' : 'red'; + dom.content(ookla_stat, [_(has_ookla ? 'Installed' : 'Not Installed')]); + }); + } else { + ookla_stat.style.color = 'red'; + dom.content(ookla_stat, [_('Not Installed')]); + } + + if (result_content.length) { + if (result_content[0] == 'Testing') { + result_stat.innerHTML = "" + + " " + + _('SpeedTesting in progress...') + + ""; + } else if (result_content[0].match(/https?:\S+/)) { + result_stat.innerHTML = "
"; + } else if (result_content[0] == 'Test failed') { + result_stat.innerHTML = "" + + _('Test failed.') + ""; + } + } else { + result_stat.innerHTML = "" + + _('No test results yet.') + ""; + } + }, + + render(res) { + var has_ookla = res[0].path, + result_content = res[1] ? res[1].trim().split("\n") : [], + result_mtime = res[2] ? res[2].mtime * 1000 : 0; + + var m, s, o; + m = new form.Map('netspeedtest', _('Wan Ookla SpeedTest')); + + // Result display section + s = m.section(form.TypedSection, '_result'); + s.anonymous = true; + s.render = function (section_id) { + if (result_content.length) { + if (result_content[0] == 'Testing') { + return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:yellow;font-weight:bold' }, [ + E('img', { 'src': L.resource(['icons/loading.gif']), 'height': '20', 'style': 'vertical-align:middle' }, []), + _('Testing in progress...') + ]) ]) + }; + if (result_content[0].match(/https?:\S+/)) { + return E('div', { 'id': 'speedtest_result' }, [ E('div', { 'style': 'max-width:500px' }, [ + E('a', { 'href': result_content[0], 'target': '_blank' }, [ + E('img', { 'src': result_content[0] + '.png', 'style': 'max-width:100%;max-height:100%;vertical-align:middle' }, []) + ]) ]) ]) + }; + if (result_content[0] == 'Test failed') { + return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:red;font-weight:bold' }, [ _('Test failed.') ]) ]) + } + } else { + return E('div', { 'id': 'speedtest_result' }, [ E('span', { 'style': 'color:red;font-weight:bold;display:none' }, [ _('No result.') ]) ]) + } + }; + + // Configuration section + s = m.section(form.NamedSection, 'config', 'netspeedtest'); + s.anonymous = true; + + // Start test button + o = s.option(form.Button, '_start', _('Start Ookla SpeedTest')); + o.inputtitle = _('Click to execute'); + o.inputstyle = 'apply'; + if (result_content.length && result_content[0] == 'Testing' && (Date.now() - result_mtime) < TestTimeout) { + o.readonly = true; + } + o.onclick = function() { + return fs.exec_direct(SpeedtestScript) + .then(function(res) { return window.location = window.location.href.split('#')[0] }) + .catch(function(e) { ui.addNotification(null, E('p', e.message), 'error') }); + }; + + o = s.option(form.DummyValue, '_ookla_status', _('Ookla® SpeedTest-CLI')); + o.rawhtml = true; + o.cfgvalue = function () { + return E('span', { + id: 'ookla_status', + style: has_ookla ? 'color:green' : 'color:red' + }, _(has_ookla ? 'Installed' : 'Not Installed')); + }; + + return m.render() + .then(L.bind(function(m, nodes) { + nodes.result_mtime = result_mtime; // 保存结果文件修改时间 + poll.add(L.bind(function () { + return Promise.all([ + L.resolveDefault(fs.stat(SpeedtestCli), {}), + L.resolveDefault(fs.read(ResultFile), null), + L.resolveDefault(fs.stat(ResultFile), {}) + ]).then(L.bind(function(res) { + nodes.result_mtime = res[2] ? res[2].mtime * 1000 : 0; + this.poll_status(nodes, res); + }, this)); + }, this), 5); + return nodes; + }, this, m)); + }, + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/luci-app-netspeedtest/luasrc/controller/netspeedtest.lua b/luci-app-netspeedtest/luasrc/controller/netspeedtest.lua deleted file mode 100644 index d595ec1..0000000 --- a/luci-app-netspeedtest/luasrc/controller/netspeedtest.lua +++ /dev/null @@ -1,132 +0,0 @@ --- Copyright (C) 2020-2022 sirpdboy https://github.com/sirpdboy/netspeedtest - -module("luci.controller.netspeedtest", package.seeall) -local http = require "luci.http" -local fs=require"nixio.fs" -local sys=require "luci.sys" -local uci = luci.model.uci.cursor() -name='netspeedtest' -function index() - - if not nixio.fs.access("/etc/config/netspeedtest") then return end - - local e = entry({"admin","network","netspeedtest"},alias("admin", "network", "netspeedtest", "speedtestlan"),_("Net Speedtest"),90) - e.dependent = false - e.acl_depends = { "luci-app-netspeedtest" } - - entry({"admin", "network", "netspeedtest", "speedtestlan"},cbi("netspeedtest/speedtestlan"),_("Lan Speedtest Web"),20).leaf = true - entry({"admin", "network", "netspeedtest", "speedtestiperf3"},cbi("netspeedtest/speedtestiperf3", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}),_("Lan Speedtest Iperf3"),30).leaf = true - entry({"admin", "network", "netspeedtest", "speedtestwan"},cbi("netspeedtest/speedtestwan", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}),_("Broadband speed test"), 40).leaf = true - entry({"admin", "network", "netspeedtest", "speedtestport"},cbi("netspeedtest/speedtestport", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}),_("Server Port Latency Test"), 50).leaf = true - entry({"admin", "network", "netspeedtest", "log"}, form("netspeedtest/log"), _("Log"), 60).leaf = true - entry({"admin", "network", "netspeedtest", "test_port"}, call("test_port")) - entry({"admin", "network", "iperf3_status"}, call("iperf3_status")) - entry({"admin", "network", "test_iperf0"}, post("test_iperf0"), nil).leaf = true - entry({"admin", "network", "test_iperf1"}, post("test_iperf1"), nil).leaf = true - entry({"admin", "network", "netspeedtest", "speedtestwanrun"}, call("speedtestwanrun")) - entry({"admin", "network", "netspeedtest", "netcheck"}, call("netcheck")) - entry({"admin", "network", "netspeedtest", "dellog"},call("dellog")) - entry({"admin", "network", "netspeedtest", "getlog"},call("getlog")) -end - -function netcheck() - http.prepare_content("text/plain; charset=utf-8") - local f=io.open("/etc/netspeedtest/netspeedtest.log", "r+") - local fdp=fs.readfile("/etc/netspeedtest/netspeedtestpos") or 0 - f:seek("set",fdp) - local a=f:read(2048000) or "" - fdp=f:seek() - fs.writefile("/etc/netspeedtest/netspeedtestpos",tostring(fdp)) - f:close() - http.write(a) -end - -function speedtestwanrun() - local cli = luci.http.formvalue('cli') - uci:set(name, 'speedtestwan', 'speedtest_cli', cli) - uci:commit(name) - fs.writefile("/etc/netspeedtest/netspeedtestpos","0") - http.prepare_content("application/json") - http.write('') - sys.exec(string.format("/etc/init.d/netspeedtest wantest " ..cli.. " > /etc/netspeedtest/netspeedtest.log 2>&1 &" )) -end - -function test_port() - local e = {} - local domain = luci.http.formvalue('sdomain') - local port = luci.http.formvalue('sport') - local ip=sys.exec("echo "..domain.." | grep -E ^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ || nslookup "..domain.." 2>/dev/null | grep Address | awk -F' ' '{print$NF}' | grep -E ^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ | sed -n 1p") - ip=sys.exec("echo -n "..ip) - e.ping = luci.sys.exec(string.format("echo -n $(tcping -q -c 1 -i 1 -t 2 -p %s %s 2>&1 | grep -o 'time=[0-9]*.[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null", port, ip)) - e.type = "tcping" - if e.ping=="" then - e.ping=sys.call("echo -n $(ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*.[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null" % ip) - e.type = "ping" - end - if e.ping=="" then e.ping="0" end - sys.exec(string.format('echo -ne "\n 【$(date)】 服务器:%s -- 端口:%s -- TCP延时:%s ms \n">> /var/log/netspeedtest.log',domain,port,e.ping)) - uci:set(name, 'speedtestport', 'sdomain', domain) - uci:set(name, 'speedtestport', 'sport', port) - uci:set(name, 'speedtestport', 'tcpspeed', e.ping.." ms") - uci:commit(name) - luci.http.prepare_content("application/json") - luci.http.write_json(e) -end - - -function iperf3_status() - local e={} - e.run=sys.call("busybox ps -w | grep iperf3 | grep -v grep >/dev/null") == 0 - luci.http.prepare_content("application/json") - luci.http.write_json(e) -end - -function testout(cmd, addr) - luci.http.prepare_content("text/plain") - local util = io.popen(cmd) - if util then - while true do - local ln = util:read("*l") - if not ln then break end - luci.http.write(ln) - luci.http.write("\n") - end - util:close() - end -end - -function test_iperf0(addr) - sys.call("pgrep -f unblockneteasemusic | xargs kill -9 >/dev/null 2>&1 ") - sys.call("/etc/init.d/unblockneteasemusic stop ") - sys.call("/etc/init.d/unblockmusic stop ") - testout("iperf3 -s ", addr) -end - -function test_iperf1(addr) - sys.call("pgrep -f iperf3 | xargs kill -9 >/dev/null 2>&1 ") - sys.call("/etc/init.d/unblockneteasemusic restart") - sys.call("/etc/init.d/unblockmusic restart") -end - -function dellog() - fs.writefile("/var/log/netspeedtest.log","") - http.prepare_content("application/json") - http.write('') -end - - - -function getlog() - logfile="/var/log/netspeedtest.log" - if not fs.access(logfile) then - http.write("") - return - end - local f=io.open(logfile,"r") - local a=f:read("*a") or "" - f:close() - a=string.gsub(a,"\n$","") - http.prepare_content("text/plain; charset=utf-8") - http.write(a) -end - diff --git a/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/log.lua b/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/log.lua deleted file mode 100644 index eac9918..0000000 --- a/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/log.lua +++ /dev/null @@ -1,13 +0,0 @@ -local fs = require "nixio.fs" -local uci = require"luci.model.uci".cursor() -local f, t -f = SimpleForm("logview") -f.reset = false -f.submit = false -t=f:field(TextValue,"conf") - -t.rmempty=true -t.rows=20 -t.template="netspeedtest/log" -t.readonly="readonly" -return f diff --git a/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestiperf3.lua b/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestiperf3.lua deleted file mode 100644 index 212ca5a..0000000 --- a/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestiperf3.lua +++ /dev/null @@ -1,18 +0,0 @@ --- Copyright (C) 2020-2022 sirpdboy https://github.com/sirpdboy/netspeedtest -require("luci.util") -local o,s,e - - -o = Map("netspeedtest", "" .. translate("Net Speedtest") .."",translate( "Network speed diagnosis test (including intranet and extranet)
For specific usage, see:") ..translate("GitHub @sirpdboy/netspeedtest") ) -o:section(SimpleSection).template = "netspeedtest/speedtestiperf3_status" - -s = o:section(TypedSection, "speedtestiperf3", translate('Lan Speedtest Iperf3')) -s.addremove=false -s.anonymous=true - -e = s:option(DummyValue, '', '') -e.rawhtml = true -e.template ='netspeedtest/speedtestiperf3' - - -return o diff --git a/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestlan.lua b/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestlan.lua deleted file mode 100644 index e2e7c50..0000000 --- a/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestlan.lua +++ /dev/null @@ -1,21 +0,0 @@ --- Copyright (C) 2020-2022 sirpdboy https://github.com/sirpdboy/netspeedtest -local m, s ,o - -m = Map("netspeedtest", "" .. translate("Net Speedtest") .."",translate( "Network speed diagnosis test (including intranet and extranet)
For specific usage, see:") ..translate("GitHub @sirpdboy/netspeedtest") ) - -s = m:section(TypedSection, "netspeedtest", translate('Lan Speedtest Web')) -s.anonymous = true - -o=s:option(Flag,"enabled",translate("Enable Homebox service")) -o.default=0 - -o = s:option(DummyValue, '', '') -o.rawhtml = true -o.template ='netspeedtest/speedtestlan' - -m.apply_on_parse = true -m.on_after_apply = function(self,map) - io.popen("/etc/init.d/netspeedtest restart") - luci.http.redirect(luci.dispatcher.build_url("admin","network","netspeedtest","speedtestlan")) -end -return m diff --git a/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestport.lua b/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestport.lua deleted file mode 100644 index ec67a11..0000000 --- a/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestport.lua +++ /dev/null @@ -1,25 +0,0 @@ --- Copyright (C) 2020-2022 sirpdboy https://github.com/sirpdboy/netspeedtest -require("luci.util") -local o,t,e - -o = Map("netspeedtest", "" .. translate("Net Speedtest") .."",translate( "Network speed diagnosis test (including intranet and extranet)
For specific usage, see:") ..translate("GitHub @sirpdboy/netspeedtest") ) - -t = o:section(TypedSection, "speedtestport", translate('Server Port Latency Test')) -t.addremove=false -t.anonymous=true - -e = t:option(Value, 'sdomain', translate('Test server address')) -e.default = "www.baidu.com" -e.description = translate('Enter the domain name or IP address of the server that needs to be tested') - -e = t:option(Value, 'sport', translate('Test server port')) -e.default = "443" - -e = t:option(Value, 'tcpspeed', translate('Server Port Delay Value')) - -e = t:option(DummyValue, '', '') -e.rawhtml = true -e.template ='netspeedtest/speedtestport' - - -return o diff --git a/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestwan.lua b/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestwan.lua deleted file mode 100644 index b06ab50..0000000 --- a/luci-app-netspeedtest/luasrc/model/cbi/netspeedtest/speedtestwan.lua +++ /dev/null @@ -1,19 +0,0 @@ --- Copyright (C) 2020-2022 sirpdboy https://github.com/sirpdboy/netspeedtest -require("luci.util") -local o,t,e - -o = Map("netspeedtest", "" .. translate("Net Speedtest") .."",translate( "Network speed diagnosis test (including intranet and extranet)
For specific usage, see:") ..translate("GitHub @sirpdboy/netspeedtest") ) - -t=o:section(TypedSection,"speedtestwan",translate("Broadband speed test")) -t.anonymous=true - -e = t:option(ListValue, 'speedtest_cli', translate('client version selection')) -e:value("0",translate("ookla-speedtest-cli")) -e:value("1",translate("python3-speedtest-cli")) -e.default = "1" - -e=t:option(Button, "restart", translate("speedtest.net Broadband speed test")) -e.inputtitle=translate("Click to execute") -e.template ='netspeedtest/speedtestwan' - -return o diff --git a/luci-app-netspeedtest/luasrc/view/netspeedtest/log.htm b/luci-app-netspeedtest/luasrc/view/netspeedtest/log.htm deleted file mode 100644 index 580ec1e..0000000 --- a/luci-app-netspeedtest/luasrc/view/netspeedtest/log.htm +++ /dev/null @@ -1,53 +0,0 @@ -<%+cbi/valueheader%> - -<%:Reverse%> - - - -<%+cbi/valuefooter%> \ No newline at end of file diff --git a/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestiperf3.htm b/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestiperf3.htm deleted file mode 100644 index 853612e..0000000 --- a/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestiperf3.htm +++ /dev/null @@ -1,93 +0,0 @@ -<%# - Copyright 2020-2022 sirpdboy Wich - https://github.com/sirpdboy/netspeedtest - Licensed to the public under the Apache License 2.0. --%> - - -<%+cbi/valueheader%> - - -
-
- -
- - -
- <%:The speed measurement terminal must be in the same LAN as the router that starts the speed measurement%>
- <%:Operation steps: start router speed measurement service download test client run speed measurement client input IP address of router speed measurement service%> -
-
-
-
-
- -
- - -
-
-
- -
- <%:-c, --client host ................run in client mode, connecting to host%>
- <%:-s, --server .....................run in server mode%>
- <%:-u, --udp ........................use UDP rather than TCP%>
- <%:-b, --bandwidth ..[KMG]...target bandwidth in bits/sec (0 for unlimited)%>
- <%:-t, --time ...............time in seconds to transmit for (default 10 secs)%>
- <%:-i, --interval ...........seconds between periodic bandwidth reports%>
- <%:-P, --parallel ...........number of parallel client streams to run%>
- <%:-R, --reverse ....................run in reverse mode (server sends, client receives)%>
-
-
- -
- -<%+cbi/valuefooter%> diff --git a/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestiperf3_status.htm b/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestiperf3_status.htm deleted file mode 100644 index ceb5616..0000000 --- a/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestiperf3_status.htm +++ /dev/null @@ -1,27 +0,0 @@ - - - - -
- <%:Iperf3 Status%> -

- <%:Collecting data...%> -

-
\ No newline at end of file diff --git a/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestlan.htm b/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestlan.htm deleted file mode 100644 index ac7a916..0000000 --- a/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestlan.htm +++ /dev/null @@ -1,32 +0,0 @@ -<%# - Copyright 2020-2022 sirpdboy Wich - https://github.com/sirpdboy/netspeedtest - Licensed to the public under the Apache License 2.0. --%> -<% - - local running = luci.sys.exec("busybox ps -w | grep homebox | grep -v grep >/dev/null && echo -ne '1' ") -%> -<%+cbi/valueheader%> - -
-<% if running == "1" then %> - - - -<% else %> - -
- -

<%:The homebox service is not running.%>

-

<%:Please enable the Homebox service%>

-
-<% end %> -
-<%+cbi/valuefooter%> diff --git a/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestport.htm b/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestport.htm deleted file mode 100644 index 48ccf2e..0000000 --- a/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestport.htm +++ /dev/null @@ -1,81 +0,0 @@ -<%# - Copyright 2020-2024 sirpdboy Wich - https://github.com/sirpdboy/netspeedtest - Licensed to the public under the Apache License 2.0. --%> - -<%+cbi/valueheader%> - - - - -
- - -
-<%+cbi/valuefooter%> diff --git a/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestwan.htm b/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestwan.htm deleted file mode 100644 index 872b4ca..0000000 --- a/luci-app-netspeedtest/luasrc/view/netspeedtest/speedtestwan.htm +++ /dev/null @@ -1,99 +0,0 @@ -<%# - Copyright 2020-2022 sirpdboy Wich - https://github.com/sirpdboy/netspeedtest - Licensed to the public under the Apache License 2.0. --%> - -<%+cbi/valueheader%> -<%local fs=require"nixio.fs"%> - - - -<%+cbi/valuefooter%> diff --git a/luci-app-netspeedtest/po/templates/netspeedtest.pot b/luci-app-netspeedtest/po/templates/netspeedtest.pot new file mode 100644 index 0000000..6dccc29 --- /dev/null +++ b/luci-app-netspeedtest/po/templates/netspeedtest.pot @@ -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 "" diff --git a/luci-app-netspeedtest/po/zh-cn/netspeedtest.po b/luci-app-netspeedtest/po/zh-cn/netspeedtest.po deleted file mode 100644 index 91ff46d..0000000 --- a/luci-app-netspeedtest/po/zh-cn/netspeedtest.po +++ /dev/null @@ -1,169 +0,0 @@ -# -# Copyright (C) 2020-2022 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/netspeedtest -# This is free software, licensed under the GNU General Public License v3. -# -msgid "" -msgstr "" -"Project-Id-Version: LuCi Chinese Translation\n" -"Report-Msgid-Bugs-To: \n" -"Language: zh_CN\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Pootle 2.0.6\n" - -msgid "Net Speedtest" -msgstr "网速测试" - -msgid "Network speed diagnosis test (including intranet and extranet)
For specific usage, see:" -msgstr "网络速度诊断测试(包括内网、外网、特定端口服务器测速)
使用说明见:" - -msgid "Lan Speedtest Iperf3" -msgstr "本地iperf3吞吐测速" - -msgid "Lan Speedtest Web" -msgstr "本地测速网页版" - -msgid "Running state" -msgstr "运行状态" - -msgid "Enable Homebox service" -msgstr "启用homebox服务" - -msgid "The Iperf3 service is running." -msgstr "iperf3服务已启动" - -msgid "The Iperf3 service is not running." -msgstr "iperf3服务未启动" - -msgid "Iperf3 Status" -msgstr "iperf3服务状态" - -msgid "The homebox service is running." -msgstr "homebox网页测速服务已启动" - -msgid "The homebox service is not running." -msgstr "homebox网页测速服务未启动" - -msgid "Please enable the Homebox service" -msgstr "请将homebox服务启用" - -msgid "homebox Status" -msgstr "homebox网页测速服务状态" - -msgid "
For specific usage, see:" -msgstr "
具体使用方法参见:" - -msgid "iperfstart" -msgstr "iperf服务启动" - -msgid "iperfstop" -msgstr "iperf服务停止" - -msgid "Select function" -msgstr "选择功能" - -msgid "Execute selected functions" -msgstr "执行选择的功能" - -msgid "client version selection" -msgstr "客户端版本" - -msgid "python3-speedtest-cli" -msgstr "python3网络测试客户端" - -msgid "ookla-speedtest-cli" -msgstr "ookla网络测试客户端" - -msgid "iperf3 instructions" -msgstr "iperf3使用说明" - -msgid "Test speed service started" -msgstr "测试速度服务已经启动" - -msgid "The speed measurement terminal must be in the same LAN as the router that starts the speed measurement" -msgstr "测速终端机必须与启动测速的路由器在同一局域网内" - -msgid "Operation steps: start router speed measurement service download test client run speed measurement client input IP address of router speed measurement service" -msgstr "使用步骤:A.启动路由器测速服务 B.下载测试客户端 C.运行测速客户端 D.输入路由器测速服务IP地址。 " - -msgid "Github download iperf3" -msgstr "Github下载iperf3" - -msgid "Iperf3 speed measurement software download" -msgstr "iperf3测速软件下载" - -msgid "Broadband speed test" -msgstr "宽带网速测试" - -msgid "speedtest.net Broadband speed test" -msgstr "speedtest.net宽带网速测试" - - -msgid "Operation execution complete" -msgstr "操作执行完毕" - -msgid "Network speed test, please wait..." -msgstr "网速测试中,请稍侯..." - -msgid "Download from foreign official websites" -msgstr "国外官网" - -msgid "Server Port Latency Test" -msgstr "服务器端口延迟测试" - -msgid "Test server address" -msgstr "测试服务器地址" - -msgid "Test server port" -msgstr "测试服务器端口" - -msgid "Enter the domain name or IP address of the server that needs to be tested" -msgstr "输入需要测试的服务器域名或者IP地址" - -msgid "Test server port delay" -msgstr "测试端口延时" - -msgid "Click to execute" -msgstr "点击执行" - -msgid "Test failed" -msgstr "测试失败" - -msgid "Waiting (executing)..." -msgstr "努力执行中" - -msgid "Server Port Delay Value" -msgstr "服务器端口延时值" - -msgid "iperf3 commands reference" -msgstr "iperf3命令参考" - -msgid "-b, --bandwidth ..[KMG]...target bandwidth in bits/sec (0 for unlimited)" -msgstr "-b, --bandwidth ..[KMG]...目标带宽(以位/秒为单位)(0表示无限制)" - -msgid "-t, --time ...............time in seconds to transmit for (default 10 secs)" -msgstr "-t, --time ...............传输时间(以秒为单位)(默认为10秒)" - -msgid "-i, --interval ...........seconds between periodic bandwidth reports" -msgstr "-i, --interval ...........测试报告之间的秒数" - -msgid "-P, --parallel ...........number of parallel client streams to run" -msgstr "-P, --parallel ...........多线程运行的数量" - -msgid "-R, --reverse ....................run in reverse mode (server sends, client receives)" -msgstr "-R, --reverse ....................反向模式运行(服务器发送,客户端接收)" - -msgid "-c, --client host ................run in client mode, connecting to host" -msgstr "-c, --client host ................客户端模式下运行,连接到host" - -msgid "-s, --server .....................run in server mode" -msgstr "-s, --server .....................服务器模式下运行" - -msgid "-u, --udp ........................use UDP rather than TCP" -msgstr "-u, --udp ........................测试传输使用UDP而不是TCP" - -msgid "Perform OK" -msgstr "执行完成" - diff --git a/luci-app-netspeedtest/po/zh_Hans/netspeedtest.po b/luci-app-netspeedtest/po/zh_Hans/netspeedtest.po index 91ff46d..751c4ca 100644 --- a/luci-app-netspeedtest/po/zh_Hans/netspeedtest.po +++ b/luci-app-netspeedtest/po/zh_Hans/netspeedtest.po @@ -1,5 +1,5 @@ # -# Copyright (C) 2020-2022 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/netspeedtest +# 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 "" @@ -13,157 +13,123 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Pootle 2.0.6\n" -msgid "Net Speedtest" +msgid "NetSpeedtest" msgstr "网速测试" -msgid "Network speed diagnosis test (including intranet and extranet)
For specific usage, see:" -msgstr "网络速度诊断测试(包括内网、外网、特定端口服务器测速)
使用说明见:" +msgid "A tool for testing network speed in multiple aspects" +msgstr "一个用于从多方面测试本地和宽带网络速度的测速工具" msgid "Lan Speedtest Iperf3" -msgstr "本地iperf3吞吐测速" +msgstr "本地iperf3测速" -msgid "Lan Speedtest Web" -msgstr "本地测速网页版" +msgid "Lan Speedtest Homebox" +msgstr "本地homebox测速" -msgid "Running state" -msgstr "运行状态" +msgid "Wan Ookla SpeedTest" +msgstr "宽带Ookla测速" -msgid "Enable Homebox service" -msgstr "启用homebox服务" +msgid "Online SpeedTest" +msgstr "在线宽带测速" -msgid "The Iperf3 service is running." -msgstr "iperf3服务已启动" +msgid "Log" +msgstr "日志" -msgid "The Iperf3 service is not running." -msgstr "iperf3服务未启动" +msgid "Start Server" +msgstr "启动服务" -msgid "Iperf3 Status" -msgstr "iperf3服务状态" +msgid "Stop Server" +msgstr "停止服务" -msgid "The homebox service is running." -msgstr "homebox网页测速服务已启动" +msgid "Loading..." +msgstr "装载中..." -msgid "The homebox service is not running." -msgstr "homebox网页测速服务未启动" +msgid "RUNNING" +msgstr "运行中" -msgid "Please enable the Homebox service" -msgstr "请将homebox服务启用" - -msgid "homebox Status" -msgstr "homebox网页测速服务状态" - -msgid "
For specific usage, see:" -msgstr "
具体使用方法参见:" - -msgid "iperfstart" -msgstr "iperf服务启动" - -msgid "iperfstop" -msgstr "iperf服务停止" - -msgid "Select function" -msgstr "选择功能" - -msgid "Execute selected functions" -msgstr "执行选择的功能" - -msgid "client version selection" -msgstr "客户端版本" +msgid "NOT RUNNING" +msgstr "未运行" -msgid "python3-speedtest-cli" -msgstr "python3网络测试客户端" +msgid "Iperf3 Server" +msgstr "Iperf3服务端" -msgid "ookla-speedtest-cli" -msgstr "ookla网络测试客户端" +msgid "Command failed" +msgstr "命令无效" -msgid "iperf3 instructions" -msgstr "iperf3使用说明" +msgid "Failed to read log file" +msgstr "读取日志失败" -msgid "Test speed service started" -msgstr "测试速度服务已经启动" +msgid "No log content available" +msgstr "没有日志记录" -msgid "The speed measurement terminal must be in the same LAN as the router that starts the speed measurement" -msgstr "测速终端机必须与启动测速的路由器在同一局域网内" +msgid "Error: " +msgstr "错误: " -msgid "Operation steps: start router speed measurement service download test client run speed measurement client input IP address of router speed measurement service" -msgstr "使用步骤:A.启动路由器测速服务 B.下载测试客户端 C.运行测速客户端 D.输入路由器测速服务IP地址。 " +msgid "Iperf3 service control" +msgstr "Iperf3 服务控制" -msgid "Github download iperf3" -msgstr "Github下载iperf3" +msgid "Listen Port" +msgstr "监听端口" -msgid "Iperf3 speed measurement software download" -msgstr "iperf3测速软件下载" +msgid "Invalid format. Use [::]:port or ip:port" +msgstr "格式错误.如 [::]:端口 或 ip:端口" -msgid "Broadband speed test" -msgstr "宽带网速测试" +msgid "Enable Log View" +msgstr "开启日志显示" -msgid "speedtest.net Broadband speed test" -msgstr "speedtest.net宽带网速测试" +msgid "Download iperf3 client" +msgstr "下载iperf3客户端" +msgid "Run Log" +msgstr "运行日志" -msgid "Operation execution complete" -msgstr "操作执行完毕" +msgid "Refresh Log" +msgstr "刷新日志" -msgid "Network speed test, please wait..." -msgstr "网速测试中,请稍侯..." +msgid "Official Website" +msgstr "官方网站" -msgid "Download from foreign official websites" -msgstr "国外官网" +msgid "Please enable the Homebox service" +msgstr "请将homebox服务启用" -msgid "Server Port Latency Test" -msgstr "服务器端口延迟测试" +msgid "Homebox service control" +msgstr "Homebox服务控制" -msgid "Test server address" -msgstr "测试服务器地址" +msgid "Homebox Server" +msgstr "Homebox服务端" -msgid "Test server port" -msgstr "测试服务器端口" +msgid "Homebox Service Not Running" +msgstr "Homebox服务端未运行" -msgid "Enter the domain name or IP address of the server that needs to be tested" -msgstr "输入需要测试的服务器域名或者IP地址" +msgid "Open Web Interface" +msgstr "打开WEB页面" -msgid "Test server port delay" -msgstr "测试端口延时" +msgid "Start Ookla SpeedTest" +msgstr "开始宽带测速" msgid "Click to execute" msgstr "点击执行" -msgid "Test failed" -msgstr "测试失败" - -msgid "Waiting (executing)..." -msgstr "努力执行中" - -msgid "Server Port Delay Value" -msgstr "服务器端口延时值" - -msgid "iperf3 commands reference" -msgstr "iperf3命令参考" +msgid "SpeedTesting in progress..." +msgstr "测速中,请稍候..." -msgid "-b, --bandwidth ..[KMG]...target bandwidth in bits/sec (0 for unlimited)" -msgstr "-b, --bandwidth ..[KMG]...目标带宽(以位/秒为单位)(0表示无限制)" +msgid "Test failed." +msgstr "测速失败" -msgid "-t, --time ...............time in seconds to transmit for (default 10 secs)" -msgstr "-t, --time ...............传输时间(以秒为单位)(默认为10秒)" +msgid "No test results yet." +msgstr "还没有测试结果" -msgid "-i, --interval ...........seconds between periodic bandwidth reports" -msgstr "-i, --interval ...........测试报告之间的秒数" +msgid "Test Script" +msgstr "测速脚本" -msgid "-P, --parallel ...........number of parallel client streams to run" -msgstr "-P, --parallel ...........多线程运行的数量" +msgid "Homebox Control panel" +msgstr "Homebox控制台" -msgid "-R, --reverse ....................run in reverse mode (server sends, client receives)" -msgstr "-R, --reverse ....................反向模式运行(服务器发送,客户端接收)" +msgid "Due to browser security policies, the Homebox interface https cannot be embedded directly." +msgstr "由于浏览器安全策略,Homebox接口https不能直接嵌入。" -msgid "-c, --client host ................run in client mode, connecting to host" -msgstr "-c, --client host ................客户端模式下运行,连接到host" +msgid "Select speed measurement station" +msgstr "选择测速站点" -msgid "-s, --server .....................run in server mode" -msgstr "-s, --server .....................服务器模式下运行" - -msgid "-u, --udp ........................use UDP rather than TCP" -msgstr "-u, --udp ........................测试传输使用UDP而不是TCP" - -msgid "Perform OK" -msgstr "执行完成" +msgid "" +msgstr "" diff --git a/luci-app-netspeedtest/root/etc/config/netspeedtest b/luci-app-netspeedtest/root/etc/config/netspeedtest deleted file mode 100644 index 6a77ef2..0000000 --- a/luci-app-netspeedtest/root/etc/config/netspeedtest +++ /dev/null @@ -1,15 +0,0 @@ -config netspeedtest 'netspeedtest' - option enabled '0' - option port '3300' - -config homebox 'homebox' - option enabled '0' - option port '3300' - -config speedtestiperf3 'speedtestiperf3' - -config speedtestwan 'speedtestwan' - -config speedtestport 'speedtestport' - option sport '443' - option sdomain 'www.taobao.com' \ No newline at end of file diff --git a/luci-app-netspeedtest/root/etc/init.d/netspeedtest b/luci-app-netspeedtest/root/etc/init.d/netspeedtest deleted file mode 100755 index eff8cf2..0000000 --- a/luci-app-netspeedtest/root/etc/init.d/netspeedtest +++ /dev/null @@ -1,161 +0,0 @@ -#!/bin/sh /etc/rc.common - -# -# Copyright (C) 2020-2023 sirpdboy https://github.com/sirpdboy/netspeedtest - -# This is free software, licensed under the Apache License, Version 2.0 . -# - -START=99 -USE_PROCD=1 - -PROG=/usr/bin/homebox -EXTRA_COMMANDS="wantest" - -TMP_T=/var/netspeedtest_nstest.tmp -BINSPEEDTEST='/usr/bin/speedtest' -LOCK=/var/lock/netspeedtest_nstest.lock -LOGD=/etc/netspeedtest -LOG=/var/log/netspeedtest.log -LOGE=$LOGD/netspeedtest.log -LOGT=$LOGD/netspeedtestpos -[ -d "$LOGD" ] || mkdir -p $LOGD -[ -f "$LOGE" ] || echo "start" > $LOG -[ -f "$LOGT" ] || echo "start" > $LOGT -[ -f "$LOG" ] || echo "start" > $LOG - -MAX_LOG=500 -limit_log() { - local logf=$1 - local max=$2 - [ ! -f "$logf" ] && return - sc=${max:-$MAX_LOG} - local count=$(grep -c "" $logf) - if [ $count -gt $sc ];then - let count=count-$sc - sed -i "1,$count d" $logf - fi -} - - -echone() { - echo -ne $* >> $LOG -} - - -wantest() { - [ -f $LOCK ] && exit - limit_log $LOG 500 - touch $LOCK - TESTMODE=$1 - BINTEMP=$(git_bin_handle $TESTMODE) - case $TESTMODE in - 0) - echo -ne "\n —————ookla-speedtest测速—————\n" | tee -a $LOG - $BINTEMP | tee $TMP_T - RESULT=$(cat $TMP_T | grep 'URL' | cut -c15-) - if [ -n "$RESULT" ] ;then - echo -ne "\n 测服信息:`cat $TMP_T | grep 'Server'| cut -c10- | awk -F: '{printf $2$3}'` 线路:`cat $TMP_T | grep 'ISP' | awk -F: '{printf $2}' ` 延时:`cat $TMP_T | grep 'Latency' | awk -F: '{printf $2}' | awk -F '(' '{printf $1}'`" >> $LOG - echo -ne "\n 下行速率:`cat $TMP_T | grep 'Download' |awk -F: '{printf $2}' | awk -F '(' '{printf $1}'` --" >> $LOG - echo -ne "-- 上行速率:`cat $TMP_T | grep 'Upload' |awk -F: '{printf $2}' | awk -F '(' '{printf $1}'`" >> $LOG - echo -ne "\n 测速结果图片链接:`cat $TMP_T | grep 'URL' | cut -c15-`" >> $LOG - else - echo -ne "\n 因客户端在LUCI下运行错误,测试失败!" | tee -a $LOG - echo -ne "\n 请SSH运行:/etc/init.d/netspeedtest wantest 0 测试,完成后,在日志中有测试结果!" | tee -a $LOG - fi - echo -ne "\n 测试时间: `date +%Y-%m-%d' '%H:%M:%S`" | tee -a $LOG - echo -ne "\n ————————————————————\n" | tee -a $LOG - ;; - *) - echo -ne "\n —————python3-speedtest测速—————\n" | tee -a $LOG - $BINTEMP | tee $TMP_T - echo -ne "\n 测服信息:$(cat $TMP_T | grep 'Hosted by'| cut -c10- | awk -F: '{printf $1}') 延时:$(cat $TMP_T | grep 'Hosted by' | awk -F: '{printf $2}')" >> $LOG - echo -ne "\n 下行速率:$(cat $TMP_T | grep 'Download:' |awk -F: '{printf $2}' ) --" >> $LOG - echo -ne "-- 上行速率:$(cat $TMP_T | grep 'Upload:' |awk -F: '{printf $2}' )" >> $LOG - echo -ne "\n 测试结果图片链接:$(cat $TMP_T | grep 'results:' | cut -c16- )" >> $LOG - echo -ne "\n 测试时间:`date +%Y-%m-%d" "%H:%M:%S`" | tee -a $LOG - echo -ne "\n ————————————————————\n" | tee -a $LOG - - ;; - esac - rm -f $LOCK -} -getcpucore(){ -#i386, x86_64, arm32, arm32hf, and arm64. - cputype=$(uname -ms | tr ' ' '_' | tr '[A-Z]' '[a-z]') - [ -n "$(echo $cputype | grep -E "linux.*armv.*")" ] && cpucore="armel" - [ -n "$(echo $cputype | grep -E "linux.*armv7.*")" ] && [ -n "$(cat /proc/cpuinfo | grep vfp)" ] && [ ! -d /jffs/clash ] && cpucore="armhf" - [ -n "$(echo $cputype | grep -E "linux.*aarch64.*|linux.*armv8.*")" ] && cpucore="aarch64" - [ -n "$(echo $cputype | grep -E "linux.*86.*")" ] && cpucore="i386" - [ -n "$(echo $cputype | grep -E "linux.*86_64.*")" ] && cpucore="x86_64" -echo $cpucore -} - -git_bin_handle() -{ -case $1 in - - 0) - TMPDIR=/var/netspeedtest - BINSPEEDTEST=$TMPDIR/speedtest - [ ! -d $TMPDIR ] && mkdir -p $TMPDIR 2>/dev/null - if [ -x "$BINSPEEDTEST" ]; then - chmod 755 $BINSPEEDTEST >/dev/null 2>&1 - echo "$BINSPEEDTEST --accept-gdpr --accept-license --progress=no" - else - ooklaurl=`curl --connect-timeout 5 -m 60 -sSL 'https://www.speedtest.net/apps/cli' | grep 'Download for Linux' | sed 's|<|\n<|g' | sed -n '/Download for Linux/,/<\/div>/p' | sed -En "s|.*$(getcpucore)|\1|p" ` - [ -n "$ooklaurl" ] && curl -sSL $ooklaurl | tar -xvz -C /tmp >/dev/null 2>&1 || return - mv /tmp/speedtest $BINSPEEDTEST >/dev/null 2>&1 - chmod 755 $BINSPEEDTEST >/dev/null 2>&1 - [ -x "$BINSPEEDTEST" ] && echo "$BINSPEEDTEST --accept-gdpr --accept-license --progress=no" - fi - ;; - *) - BINSPEEDTEST='/usr/bin/speedtest' - [ -x "$BINSPEEDTEST" ] && echo "$BINSPEEDTEST --share" - ;; - esac -} - -speedtest_start(){ -case $1 in - 0) - speedtest_start_ookla $2 ;; - *) - speedtest_start_cli $2 ;; - esac -} - -tcptest(){ - NDATA=`date +%Y-%m-%d' '%H:%M:%S` - echo -ne "\n $NDATA 服务器:$1 ---- 端口:$2 ---- TCP延时:$3 ms \n" >> $LOG -} - -get_config() { - config_get_bool enabled $1 enabled 1 - config_get_bool logger $1 logger 1 -} - -homebox_prepare() { - pgrep -f homebox | xargs kill -9 >/dev/null 2>&1 - killall homebox - killall homebox -} - -start_service() { - config_load netspeedtest - config_foreach get_config netspeedtest - [ "x$enabled" != "x1" ] && { - homebox_prepare - exit - } - procd_open_instance - procd_set_param command $PROG - [ "x$logger" == x1 ] && procd_set_param stderr 1 - # procd_set_param respawn - procd_close_instance -} - -service_triggers() { - procd_add_reload_trigger "netspeedtest" -} diff --git a/luci-app-netspeedtest/root/etc/uci-defaults/40_luci-netspeedtest b/luci-app-netspeedtest/root/etc/uci-defaults/40_luci-netspeedtest deleted file mode 100644 index 55efe07..0000000 --- a/luci-app-netspeedtest/root/etc/uci-defaults/40_luci-netspeedtest +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh -chmod +x /etc/init.d/netspeedtest >/dev/null 2>&1 -[ `uci -q get partexp.global` ] && uci set partexp.global=global -[ `uci -q get netspeedtest.netspeedtest` ] || uci set netspeedtest.netspeedtest=netspeedtest -[ `uci -q get netspeedtest.speedtestiperf3` ] || uci set netspeedtest.speedtestiperf3=speedtestiperf3 -[ `uci -q get netspeedtest.speedtestport` ] || uci set netspeedtest.speedtestport=speedtestport -[ `uci -q get netspeedtest.speedtestwan` ] || uci set netspeedtest.speedtestport=speedtestwan -LOGD=/etc/netspeedtest -LOG=/var/log/netspeedtest.log -LOGE=$LOGD/netspeedtest.log -LOGT=$LOGD/netspeedtestpos -[ -d "$LOGD" ] || mkdir -p $LOGD -[ -f "$LOGE" ] || echo "start" > $LOG -[ -f "$LOGT" ] || echo "start" > $LOGT -[ -f "$LOG" ] || echo "start" > $LOG - -rm -rf /tmp/luci-modulecache /tmp/luci-indexcache* -exit 0 diff --git a/luci-app-netspeedtest/root/lib/functions/netspeedtest.sh b/luci-app-netspeedtest/root/lib/functions/netspeedtest.sh deleted file mode 100644 index 8bbf50f..0000000 --- a/luci-app-netspeedtest/root/lib/functions/netspeedtest.sh +++ /dev/null @@ -1,98 +0,0 @@ -################################################################################ -# (netspeedtest) functions.sh -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. -# -# Copyright (C) 2019-2024 The Sirpdboy Team -# . /lib/netspeedtest/functions.sh -################################################################################ - -# tmp speedtest -TMP_TEST=/var/netspeedtest.tmp -MAX_LOG=200 -LOG=/var/log/netspeedtest.log -limit_log() { - local logf=$1 - local max=$2 - [ ! -f "$logf" ] && return - sc=${max:-$MAX_LOG} - local count=$(grep -c "" $logf) - if [ $count -gt $sc ];then - let count=count-$sc - sed -i "1,$count d" $logf - fi -} - -init_log() { - local logf=$1 - [ ! -f "$logf" ] && echo "" > $logf -} - -echone() { - echo -ne $* >> $LOG -} - - -git_bin_handle() -{ -case $1 in - - 0) - TMPDIR=/var/netspeedtest - BINSPEEDTEST=$TMPDIR/speedtest - [ ! -d $TMPDIR ] && mkdir -p $TMPDIR 2>/dev/null - if [ -x "$BINSPEEDTEST" ]; then - chmod 755 $BINSPEEDTEST >/dev/null 2>&1 - echo "$BINSPEEDTEST --accept-gdpr --accept-license --progress=no" - else - ooklaurl=`curl --connect-timeout 5 -m 60 -sSL 'https://www.speedtest.net/apps/cli' | grep 'Download for Linux' | sed 's|<|\n<|g' | sed -n '/Download for Linux/,/<\/div>/p' | sed -En "s|.*x86_64|\1|p" ` - [ -n "$ooklaurl" ] && curl -sSL $ooklaurl | tar -xvz -C /tmp >/dev/null 2>&1 || return - mv /tmp/speedtest $BINSPEEDTEST >/dev/null 2>&1 - chmod 755 $BINSPEEDTEST >/dev/null 2>&1 - [ -x "$BINSPEEDTEST" ] && echo "$BINSPEEDTEST --accept-gdpr --accept-license --progress=no" - fi - ;; - *) - BINSPEEDTEST='/usr/bin/speedtest' - [ -x "$BINSPEEDTEST" ] && echo "$BINSPEEDTEST --share" - ;; - esac -} - -speedtest_start(){ -case $1 in - 0) - speedtest_start_ookla $2 ;; - *) - speedtest_start_cli $2 ;; - esac -} - -speedtest_start_ookla(){ - echone "\n ookla-speedtest测速" - info=$($1 > $TMP_TEST ) >/dev/null 2>&1 - echone "\n info:$info ----------------- " - echone "\n 测服信息:`cat $TMP_TEST | grep 'Server'| cut -c10- | awk -F: '{printf $2$3}'` 线路:`cat $TMP_TEST | grep 'ISP' | awk -F: '{printf $2}' ` 延时:`cat $TMP_TEST | grep 'Latency' | awk -F: '{printf $2}' | awk -F '(' '{printf $1}'`" - echone "\n 下行速率:`cat $TMP_TEST | grep 'Download' |awk -F: '{printf $2}' | awk -F '(' '{printf $1}'` --" - echone "-- 上行速率:`cat $TMP_TEST | grep 'Upload' |awk -F: '{printf $2}' | awk -F '(' '{printf $1}'`" - echo -ne "\n 测速结果图片链接:`cat $TMP_TEST | grep 'URL' | cut -c15-`" >> $LOG - echone "\n 测试时间: `date +%Y-%m-%d' '%H:%M:%S`" - echone "\n ————————————————————————————\n" - cat $TMP_TEST | grep 'URL' | cut -c15- > /var/speedtesturl.tmp - echo -ne "`cat $TMP_TEST | grep 'URL' | cut -c15- `" -} - -speedtest_start_cli(){ - echone "\n python3-speedtest测速" - info=$($1 > $TMP_TEST ) >/dev/null 2>&1 - echone "\n 测服信息:`cat $TMP_TEST | grep 'Hosted by'| cut -c10- | awk -F: '{printf $1}'` 延时:`cat $TMP_TEST | grep 'Hosted by' | awk -F: '{printf $2}'`" - echone "\n 下行速率:`cat $TMP_TEST | grep 'Download:' |awk -F: '{printf $2}' ` --" - echone "-- 上行速率:`cat $TMP_TEST | grep 'Upload:' |awk -F: '{printf $2}' `" - echo -ne "\n 测速结果图片链接:`cat $TMP_TEST | grep 'results' | cut -c16-`" >> $LOG - echone "\n 测试时间: `date +%Y-%m-%d' '%H:%M:%S`" - echone "\n ————————————————————————————\n" - cat $TMP_TEST | grep 'results:' | cut -c16- > /var/speedtesturl.tmp - echo -ne "`cat $TMP_TEST | grep 'results:' | cut -c16-`" -} diff --git a/luci-app-netspeedtest/root/usr/bin/speedtest b/luci-app-netspeedtest/root/usr/bin/speedtest deleted file mode 100755 index a33296d..0000000 --- a/luci-app-netspeedtest/root/usr/bin/speedtest +++ /dev/null @@ -1,2004 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright 2012 Matt Martz -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import os -import re -import csv -import sys -import math -import errno -import signal -import socket -import timeit -import datetime -import platform -import threading -import xml.parsers.expat - -try: - import gzip - GZIP_BASE = gzip.GzipFile -except ImportError: - gzip = None - GZIP_BASE = object - -__version__ = '2.1.3' - - -class FakeShutdownEvent(object): - """Class to fake a threading.Event.isSet so that users of this module - are not required to register their own threading.Event() - """ - - @staticmethod - def isSet(): - "Dummy method to always return false""" - return False - - -# Some global variables we use -DEBUG = False -_GLOBAL_DEFAULT_TIMEOUT = object() -PY25PLUS = sys.version_info[:2] >= (2, 5) -PY26PLUS = sys.version_info[:2] >= (2, 6) -PY32PLUS = sys.version_info[:2] >= (3, 2) - -# Begin import game to handle Python 2 and Python 3 -try: - import json -except ImportError: - try: - import simplejson as json - except ImportError: - json = None - -try: - import xml.etree.ElementTree as ET - try: - from xml.etree.ElementTree import _Element as ET_Element - except ImportError: - pass -except ImportError: - from xml.dom import minidom as DOM - from xml.parsers.expat import ExpatError - ET = None - -try: - from urllib2 import (urlopen, Request, HTTPError, URLError, - AbstractHTTPHandler, ProxyHandler, - HTTPDefaultErrorHandler, HTTPRedirectHandler, - HTTPErrorProcessor, OpenerDirector) -except ImportError: - from urllib.request import (urlopen, Request, HTTPError, URLError, - AbstractHTTPHandler, ProxyHandler, - HTTPDefaultErrorHandler, HTTPRedirectHandler, - HTTPErrorProcessor, OpenerDirector) - -try: - from httplib import HTTPConnection, BadStatusLine -except ImportError: - from http.client import HTTPConnection, BadStatusLine - -try: - from httplib import HTTPSConnection -except ImportError: - try: - from http.client import HTTPSConnection - except ImportError: - HTTPSConnection = None - -try: - from httplib import FakeSocket -except ImportError: - FakeSocket = None - -try: - from Queue import Queue -except ImportError: - from queue import Queue - -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse - -try: - from urlparse import parse_qs -except ImportError: - try: - from urllib.parse import parse_qs - except ImportError: - from cgi import parse_qs - -try: - from hashlib import md5 -except ImportError: - from md5 import md5 - -try: - from argparse import ArgumentParser as ArgParser - from argparse import SUPPRESS as ARG_SUPPRESS - PARSER_TYPE_INT = int - PARSER_TYPE_STR = str - PARSER_TYPE_FLOAT = float -except ImportError: - from optparse import OptionParser as ArgParser - from optparse import SUPPRESS_HELP as ARG_SUPPRESS - PARSER_TYPE_INT = 'int' - PARSER_TYPE_STR = 'string' - PARSER_TYPE_FLOAT = 'float' - -try: - from cStringIO import StringIO - BytesIO = None -except ImportError: - try: - from StringIO import StringIO - BytesIO = None - except ImportError: - from io import StringIO, BytesIO - -try: - import __builtin__ -except ImportError: - import builtins - from io import TextIOWrapper, FileIO - - class _Py3Utf8Output(TextIOWrapper): - """UTF-8 encoded wrapper around stdout for py3, to override - ASCII stdout - """ - def __init__(self, f, **kwargs): - buf = FileIO(f.fileno(), 'w') - super(_Py3Utf8Output, self).__init__( - buf, - encoding='utf8', - errors='strict' - ) - - def write(self, s): - super(_Py3Utf8Output, self).write(s) - self.flush() - - _py3_print = getattr(builtins, 'print') - try: - _py3_utf8_stdout = _Py3Utf8Output(sys.stdout) - _py3_utf8_stderr = _Py3Utf8Output(sys.stderr) - except OSError: - # sys.stdout/sys.stderr is not a compatible stdout/stderr object - # just use it and hope things go ok - _py3_utf8_stdout = sys.stdout - _py3_utf8_stderr = sys.stderr - - def to_utf8(v): - """No-op encode to utf-8 for py3""" - return v - - def print_(*args, **kwargs): - """Wrapper function for py3 to print, with a utf-8 encoded stdout""" - if kwargs.get('file') == sys.stderr: - kwargs['file'] = _py3_utf8_stderr - else: - kwargs['file'] = kwargs.get('file', _py3_utf8_stdout) - _py3_print(*args, **kwargs) -else: - del __builtin__ - - def to_utf8(v): - """Encode value to utf-8 if possible for py2""" - try: - return v.encode('utf8', 'strict') - except AttributeError: - return v - - def print_(*args, **kwargs): - """The new-style print function for Python 2.4 and 2.5. - - Taken from https://pypi.python.org/pypi/six/ - - Modified to set encoding to UTF-8 always, and to flush after write - """ - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - # If the file has an encoding, encode unicode with it. - encoding = 'utf8' # Always trust UTF-8 for output - if (isinstance(fp, file) and - isinstance(data, unicode) and - encoding is not None): - errors = getattr(fp, "errors", None) - if errors is None: - errors = "strict" - data = data.encode(encoding, errors) - fp.write(data) - fp.flush() - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) - -if PY32PLUS: - etree_iter = ET.Element.iter -elif PY25PLUS: - etree_iter = ET_Element.getiterator - -if PY26PLUS: - thread_is_alive = threading.Thread.is_alive -else: - thread_is_alive = threading.Thread.isAlive - - -# Exception "constants" to support Python 2 through Python 3 -try: - import ssl - try: - CERT_ERROR = (ssl.CertificateError,) - except AttributeError: - CERT_ERROR = tuple() - - HTTP_ERRORS = ( - (HTTPError, URLError, socket.error, ssl.SSLError, BadStatusLine) + - CERT_ERROR - ) -except ImportError: - ssl = None - HTTP_ERRORS = (HTTPError, URLError, socket.error, BadStatusLine) - - -class SpeedtestException(Exception): - """Base exception for this module""" - - -class SpeedtestCLIError(SpeedtestException): - """Generic exception for raising errors during CLI operation""" - - -class SpeedtestHTTPError(SpeedtestException): - """Base HTTP exception for this module""" - - -class SpeedtestConfigError(SpeedtestException): - """Configuration XML is invalid""" - - -class SpeedtestServersError(SpeedtestException): - """Servers XML is invalid""" - - -class ConfigRetrievalError(SpeedtestHTTPError): - """Could not retrieve config.php""" - - -class ServersRetrievalError(SpeedtestHTTPError): - """Could not retrieve speedtest-servers.php""" - - -class InvalidServerIDType(SpeedtestException): - """Server ID used for filtering was not an integer""" - - -class NoMatchedServers(SpeedtestException): - """No servers matched when filtering""" - - -class SpeedtestMiniConnectFailure(SpeedtestException): - """Could not connect to the provided speedtest mini server""" - - -class InvalidSpeedtestMiniServer(SpeedtestException): - """Server provided as a speedtest mini server does not actually appear - to be a speedtest mini server - """ - - -class ShareResultsConnectFailure(SpeedtestException): - """Could not connect to speedtest.net API to POST results""" - - -class ShareResultsSubmitFailure(SpeedtestException): - """Unable to successfully POST results to speedtest.net API after - connection - """ - - -class SpeedtestUploadTimeout(SpeedtestException): - """testlength configuration reached during upload - Used to ensure the upload halts when no additional data should be sent - """ - - -class SpeedtestBestServerFailure(SpeedtestException): - """Unable to determine best server""" - - -class SpeedtestMissingBestServer(SpeedtestException): - """get_best_server not called or not able to determine best server""" - - -def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, - source_address=None): - """Connect to *address* and return the socket object. - - Convenience function. Connect to *address* (a 2-tuple ``(host, - port)``) and return the socket object. Passing the optional - *timeout* parameter will set the timeout on the socket instance - before attempting to connect. If no *timeout* is supplied, the - global default timeout setting returned by :func:`getdefaulttimeout` - is used. If *source_address* is set it must be a tuple of (host, port) - for the socket to bind as a source address before making the connection. - An host of '' or port 0 tells the OS to use the default. - - Largely vendored from Python 2.7, modified to work with Python 2.4 - """ - - host, port = address - err = None - for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - sock = None - try: - sock = socket.socket(af, socktype, proto) - if timeout is not _GLOBAL_DEFAULT_TIMEOUT: - sock.settimeout(float(timeout)) - if source_address: - sock.bind(source_address) - sock.connect(sa) - return sock - - except socket.error: - err = get_exception() - if sock is not None: - sock.close() - - if err is not None: - raise err - else: - raise socket.error("getaddrinfo returns an empty list") - - -class SpeedtestHTTPConnection(HTTPConnection): - """Custom HTTPConnection to support source_address across - Python 2.4 - Python 3 - """ - def __init__(self, *args, **kwargs): - source_address = kwargs.pop('source_address', None) - timeout = kwargs.pop('timeout', 10) - - self._tunnel_host = None - - HTTPConnection.__init__(self, *args, **kwargs) - - self.source_address = source_address - self.timeout = timeout - - def connect(self): - """Connect to the host and port specified in __init__.""" - try: - self.sock = socket.create_connection( - (self.host, self.port), - self.timeout, - self.source_address - ) - except (AttributeError, TypeError): - self.sock = create_connection( - (self.host, self.port), - self.timeout, - self.source_address - ) - - if self._tunnel_host: - self._tunnel() - - -if HTTPSConnection: - class SpeedtestHTTPSConnection(HTTPSConnection): - """Custom HTTPSConnection to support source_address across - Python 2.4 - Python 3 - """ - default_port = 443 - - def __init__(self, *args, **kwargs): - source_address = kwargs.pop('source_address', None) - timeout = kwargs.pop('timeout', 10) - - self._tunnel_host = None - - HTTPSConnection.__init__(self, *args, **kwargs) - - self.timeout = timeout - self.source_address = source_address - - def connect(self): - "Connect to a host on a given (SSL) port." - try: - self.sock = socket.create_connection( - (self.host, self.port), - self.timeout, - self.source_address - ) - except (AttributeError, TypeError): - self.sock = create_connection( - (self.host, self.port), - self.timeout, - self.source_address - ) - - if self._tunnel_host: - self._tunnel() - - if ssl: - try: - kwargs = {} - if hasattr(ssl, 'SSLContext'): - if self._tunnel_host: - kwargs['server_hostname'] = self._tunnel_host - else: - kwargs['server_hostname'] = self.host - self.sock = self._context.wrap_socket(self.sock, **kwargs) - except AttributeError: - self.sock = ssl.wrap_socket(self.sock) - try: - self.sock.server_hostname = self.host - except AttributeError: - pass - elif FakeSocket: - # Python 2.4/2.5 support - try: - self.sock = FakeSocket(self.sock, socket.ssl(self.sock)) - except AttributeError: - raise SpeedtestException( - 'This version of Python does not support HTTPS/SSL ' - 'functionality' - ) - else: - raise SpeedtestException( - 'This version of Python does not support HTTPS/SSL ' - 'functionality' - ) - - -def _build_connection(connection, source_address, timeout, context=None): - """Cross Python 2.4 - Python 3 callable to build an ``HTTPConnection`` or - ``HTTPSConnection`` with the args we need - - Called from ``http(s)_open`` methods of ``SpeedtestHTTPHandler`` or - ``SpeedtestHTTPSHandler`` - """ - def inner(host, **kwargs): - kwargs.update({ - 'source_address': source_address, - 'timeout': timeout - }) - if context: - kwargs['context'] = context - return connection(host, **kwargs) - return inner - - -class SpeedtestHTTPHandler(AbstractHTTPHandler): - """Custom ``HTTPHandler`` that can build a ``HTTPConnection`` with the - args we need for ``source_address`` and ``timeout`` - """ - def __init__(self, debuglevel=0, source_address=None, timeout=10): - AbstractHTTPHandler.__init__(self, debuglevel) - self.source_address = source_address - self.timeout = timeout - - def http_open(self, req): - return self.do_open( - _build_connection( - SpeedtestHTTPConnection, - self.source_address, - self.timeout - ), - req - ) - - http_request = AbstractHTTPHandler.do_request_ - - -class SpeedtestHTTPSHandler(AbstractHTTPHandler): - """Custom ``HTTPSHandler`` that can build a ``HTTPSConnection`` with the - args we need for ``source_address`` and ``timeout`` - """ - def __init__(self, debuglevel=0, context=None, source_address=None, - timeout=10): - AbstractHTTPHandler.__init__(self, debuglevel) - self._context = context - self.source_address = source_address - self.timeout = timeout - - def https_open(self, req): - return self.do_open( - _build_connection( - SpeedtestHTTPSConnection, - self.source_address, - self.timeout, - context=self._context, - ), - req - ) - - https_request = AbstractHTTPHandler.do_request_ - - -def build_opener(source_address=None, timeout=10): - """Function similar to ``urllib2.build_opener`` that will build - an ``OpenerDirector`` with the explicit handlers we want, - ``source_address`` for binding, ``timeout`` and our custom - `User-Agent` - """ - - printer('Timeout set to %d' % timeout, debug=True) - - if source_address: - source_address_tuple = (source_address, 0) - printer('Binding to source address: %r' % (source_address_tuple,), - debug=True) - else: - source_address_tuple = None - - handlers = [ - ProxyHandler(), - SpeedtestHTTPHandler(source_address=source_address_tuple, - timeout=timeout), - SpeedtestHTTPSHandler(source_address=source_address_tuple, - timeout=timeout), - HTTPDefaultErrorHandler(), - HTTPRedirectHandler(), - HTTPErrorProcessor() - ] - - opener = OpenerDirector() - opener.addheaders = [('User-agent', build_user_agent())] - - for handler in handlers: - opener.add_handler(handler) - - return opener - - -class GzipDecodedResponse(GZIP_BASE): - """A file-like object to decode a response encoded with the gzip - method, as described in RFC 1952. - - Largely copied from ``xmlrpclib``/``xmlrpc.client`` and modified - to work for py2.4-py3 - """ - def __init__(self, response): - # response doesn't support tell() and read(), required by - # GzipFile - if not gzip: - raise SpeedtestHTTPError('HTTP response body is gzip encoded, ' - 'but gzip support is not available') - IO = BytesIO or StringIO - self.io = IO() - while 1: - chunk = response.read(1024) - if len(chunk) == 0: - break - self.io.write(chunk) - self.io.seek(0) - gzip.GzipFile.__init__(self, mode='rb', fileobj=self.io) - - def close(self): - try: - gzip.GzipFile.close(self) - finally: - self.io.close() - - -def get_exception(): - """Helper function to work with py2.4-py3 for getting the current - exception in a try/except block - """ - return sys.exc_info()[1] - - -def distance(origin, destination): - """Determine distance between 2 sets of [lat,lon] in km""" - - lat1, lon1 = origin - lat2, lon2 = destination - radius = 6371 # km - - dlat = math.radians(lat2 - lat1) - dlon = math.radians(lon2 - lon1) - a = (math.sin(dlat / 2) * math.sin(dlat / 2) + - math.cos(math.radians(lat1)) * - math.cos(math.radians(lat2)) * math.sin(dlon / 2) * - math.sin(dlon / 2)) - c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) - d = radius * c - - return d - - -def build_user_agent(): - """Build a Mozilla/5.0 compatible User-Agent string""" - - ua_tuple = ( - 'Mozilla/5.0', - '(%s; U; %s; en-us)' % (platform.platform(), - platform.architecture()[0]), - 'Python/%s' % platform.python_version(), - '(KHTML, like Gecko)', - 'speedtest-cli/%s' % __version__ - ) - user_agent = ' '.join(ua_tuple) - printer('User-Agent: %s' % user_agent, debug=True) - return user_agent - - -def build_request(url, data=None, headers=None, bump='0', secure=False): - """Build a urllib2 request object - - This function automatically adds a User-Agent header to all requests - - """ - - if not headers: - headers = {} - - if url[0] == ':': - scheme = ('http', 'https')[bool(secure)] - schemed_url = '%s%s' % (scheme, url) - else: - schemed_url = url - - if '?' in url: - delim = '&' - else: - delim = '?' - - # WHO YOU GONNA CALL? CACHE BUSTERS! - final_url = '%s%sx=%s.%s' % (schemed_url, delim, - int(timeit.time.time() * 1000), - bump) - - headers.update({ - 'Cache-Control': 'no-cache', - }) - - printer('%s %s' % (('GET', 'POST')[bool(data)], final_url), - debug=True) - - return Request(final_url, data=data, headers=headers) - - -def catch_request(request, opener=None): - """Helper function to catch common exceptions encountered when - establishing a connection with a HTTP/HTTPS request - - """ - - if opener: - _open = opener.open - else: - _open = urlopen - - try: - uh = _open(request) - if request.get_full_url() != uh.geturl(): - printer('Redirected to %s' % uh.geturl(), debug=True) - return uh, False - except HTTP_ERRORS: - e = get_exception() - return None, e - - -def get_response_stream(response): - """Helper function to return either a Gzip reader if - ``Content-Encoding`` is ``gzip`` otherwise the response itself - - """ - - try: - getheader = response.headers.getheader - except AttributeError: - getheader = response.getheader - - if getheader('content-encoding') == 'gzip': - return GzipDecodedResponse(response) - - return response - - -def get_attributes_by_tag_name(dom, tag_name): - """Retrieve an attribute from an XML document and return it in a - consistent format - - Only used with xml.dom.minidom, which is likely only to be used - with python versions older than 2.5 - """ - elem = dom.getElementsByTagName(tag_name)[0] - return dict(list(elem.attributes.items())) - - -def print_dots(shutdown_event): - """Built in callback function used by Thread classes for printing - status - """ - def inner(current, total, start=False, end=False): - if shutdown_event.isSet(): - return - - sys.stdout.write('.') - if current + 1 == total and end is True: - sys.stdout.write('\n') - sys.stdout.flush() - return inner - - -def do_nothing(*args, **kwargs): - pass - - -class HTTPDownloader(threading.Thread): - """Thread class for retrieving a URL""" - - def __init__(self, i, request, start, timeout, opener=None, - shutdown_event=None): - threading.Thread.__init__(self) - self.request = request - self.result = [0] - self.starttime = start - self.timeout = timeout - self.i = i - if opener: - self._opener = opener.open - else: - self._opener = urlopen - - if shutdown_event: - self._shutdown_event = shutdown_event - else: - self._shutdown_event = FakeShutdownEvent() - - def run(self): - try: - if (timeit.default_timer() - self.starttime) <= self.timeout: - f = self._opener(self.request) - while (not self._shutdown_event.isSet() and - (timeit.default_timer() - self.starttime) <= - self.timeout): - self.result.append(len(f.read(10240))) - if self.result[-1] == 0: - break - f.close() - except IOError: - pass - except HTTP_ERRORS: - pass - - -class HTTPUploaderData(object): - """File like object to improve cutting off the upload once the timeout - has been reached - """ - - def __init__(self, length, start, timeout, shutdown_event=None): - self.length = length - self.start = start - self.timeout = timeout - - if shutdown_event: - self._shutdown_event = shutdown_event - else: - self._shutdown_event = FakeShutdownEvent() - - self._data = None - - self.total = [0] - - def pre_allocate(self): - chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' - multiplier = int(round(int(self.length) / 36.0)) - IO = BytesIO or StringIO - try: - self._data = IO( - ('content1=%s' % - (chars * multiplier)[0:int(self.length) - 9] - ).encode() - ) - except MemoryError: - raise SpeedtestCLIError( - 'Insufficient memory to pre-allocate upload data. Please ' - 'use --no-pre-allocate' - ) - - @property - def data(self): - if not self._data: - self.pre_allocate() - return self._data - - def read(self, n=10240): - if ((timeit.default_timer() - self.start) <= self.timeout and - not self._shutdown_event.isSet()): - chunk = self.data.read(n) - self.total.append(len(chunk)) - return chunk - else: - raise SpeedtestUploadTimeout() - - def __len__(self): - return self.length - - -class HTTPUploader(threading.Thread): - """Thread class for putting a URL""" - - def __init__(self, i, request, start, size, timeout, opener=None, - shutdown_event=None): - threading.Thread.__init__(self) - self.request = request - self.request.data.start = self.starttime = start - self.size = size - self.result = 0 - self.timeout = timeout - self.i = i - - if opener: - self._opener = opener.open - else: - self._opener = urlopen - - if shutdown_event: - self._shutdown_event = shutdown_event - else: - self._shutdown_event = FakeShutdownEvent() - - def run(self): - request = self.request - try: - if ((timeit.default_timer() - self.starttime) <= self.timeout and - not self._shutdown_event.isSet()): - try: - f = self._opener(request) - except TypeError: - # PY24 expects a string or buffer - # This also causes issues with Ctrl-C, but we will concede - # for the moment that Ctrl-C on PY24 isn't immediate - request = build_request(self.request.get_full_url(), - data=request.data.read(self.size)) - f = self._opener(request) - f.read(11) - f.close() - self.result = sum(self.request.data.total) - else: - self.result = 0 - except (IOError, SpeedtestUploadTimeout): - self.result = sum(self.request.data.total) - except HTTP_ERRORS: - self.result = 0 - - -class SpeedtestResults(object): - """Class for holding the results of a speedtest, including: - - Download speed - Upload speed - Ping/Latency to test server - Data about server that the test was run against - - Additionally this class can return a result data as a dictionary or CSV, - as well as submit a POST of the result data to the speedtest.net API - to get a share results image link. - """ - - def __init__(self, download=0, upload=0, ping=0, server=None, client=None, - opener=None, secure=False): - self.download = download - self.upload = upload - self.ping = ping - if server is None: - self.server = {} - else: - self.server = server - self.client = client or {} - - self._share = None - self.timestamp = '%sZ' % datetime.datetime.utcnow().isoformat() - self.bytes_received = 0 - self.bytes_sent = 0 - - if opener: - self._opener = opener - else: - self._opener = build_opener() - - self._secure = secure - - def __repr__(self): - return repr(self.dict()) - - def share(self): - """POST data to the speedtest.net API to obtain a share results - link - """ - - if self._share: - return self._share - - download = int(round(self.download / 1000.0, 0)) - ping = int(round(self.ping, 0)) - upload = int(round(self.upload / 1000.0, 0)) - - # Build the request to send results back to speedtest.net - # We use a list instead of a dict because the API expects parameters - # in a certain order - api_data = [ - 'recommendedserverid=%s' % self.server['id'], - 'ping=%s' % ping, - 'screenresolution=', - 'promo=', - 'download=%s' % download, - 'screendpi=', - 'upload=%s' % upload, - 'testmethod=http', - 'hash=%s' % md5(('%s-%s-%s-%s' % - (ping, upload, download, '297aae72')) - .encode()).hexdigest(), - 'touchscreen=none', - 'startmode=pingselect', - 'accuracy=1', - 'bytesreceived=%s' % self.bytes_received, - 'bytessent=%s' % self.bytes_sent, - 'serverid=%s' % self.server['id'], - ] - - headers = {'Referer': 'http://c.speedtest.net/flash/speedtest.swf'} - request = build_request('://www.speedtest.net/api/api.php', - data='&'.join(api_data).encode(), - headers=headers, secure=self._secure) - f, e = catch_request(request, opener=self._opener) - if e: - raise ShareResultsConnectFailure(e) - - response = f.read() - code = f.code - f.close() - - if int(code) != 200: - raise ShareResultsSubmitFailure('Could not submit results to ' - 'speedtest.net') - - qsargs = parse_qs(response.decode()) - resultid = qsargs.get('resultid') - if not resultid or len(resultid) != 1: - raise ShareResultsSubmitFailure('Could not submit results to ' - 'speedtest.net') - - self._share = 'http://www.speedtest.net/result/%s.png' % resultid[0] - - return self._share - - def dict(self): - """Return dictionary of result data""" - - return { - 'download': self.download, - 'upload': self.upload, - 'ping': self.ping, - 'server': self.server, - 'timestamp': self.timestamp, - 'bytes_sent': self.bytes_sent, - 'bytes_received': self.bytes_received, - 'share': self._share, - 'client': self.client, - } - - @staticmethod - def csv_header(delimiter=','): - """Return CSV Headers""" - - row = ['Server ID', 'Sponsor', 'Server Name', 'Timestamp', 'Distance', - 'Ping', 'Download', 'Upload', 'Share', 'IP Address'] - out = StringIO() - writer = csv.writer(out, delimiter=delimiter, lineterminator='') - writer.writerow([to_utf8(v) for v in row]) - return out.getvalue() - - def csv(self, delimiter=','): - """Return data in CSV format""" - - data = self.dict() - out = StringIO() - writer = csv.writer(out, delimiter=delimiter, lineterminator='') - row = [data['server']['id'], data['server']['sponsor'], - data['server']['name'], data['timestamp'], - data['server']['d'], data['ping'], data['download'], - data['upload'], self._share or '', self.client['ip']] - writer.writerow([to_utf8(v) for v in row]) - return out.getvalue() - - def json(self, pretty=False): - """Return data in JSON format""" - - kwargs = {} - if pretty: - kwargs.update({ - 'indent': 4, - 'sort_keys': True - }) - return json.dumps(self.dict(), **kwargs) - - -class Speedtest(object): - """Class for performing standard speedtest.net testing operations""" - - def __init__(self, config=None, source_address=None, timeout=10, - secure=False, shutdown_event=None): - self.config = {} - - self._source_address = source_address - self._timeout = timeout - self._opener = build_opener(source_address, timeout) - - self._secure = secure - - if shutdown_event: - self._shutdown_event = shutdown_event - else: - self._shutdown_event = FakeShutdownEvent() - - self.get_config() - if config is not None: - self.config.update(config) - - self.servers = {} - self.closest = [] - self._best = {} - - self.results = SpeedtestResults( - client=self.config['client'], - opener=self._opener, - secure=secure, - ) - - @property - def best(self): - if not self._best: - self.get_best_server() - return self._best - - def get_config(self): - """Download the speedtest.net configuration and return only the data - we are interested in - """ - - headers = {} - if gzip: - headers['Accept-Encoding'] = 'gzip' - request = build_request('://www.speedtest.net/speedtest-config.php', - headers=headers, secure=self._secure) - uh, e = catch_request(request, opener=self._opener) - if e: - raise ConfigRetrievalError(e) - configxml_list = [] - - stream = get_response_stream(uh) - - while 1: - try: - configxml_list.append(stream.read(1024)) - except (OSError, EOFError): - raise ConfigRetrievalError(get_exception()) - if len(configxml_list[-1]) == 0: - break - stream.close() - uh.close() - - if int(uh.code) != 200: - return None - - configxml = ''.encode().join(configxml_list) - - printer('Config XML:\n%s' % configxml, debug=True) - - try: - try: - root = ET.fromstring(configxml) - except ET.ParseError: - e = get_exception() - raise SpeedtestConfigError( - 'Malformed speedtest.net configuration: %s' % e - ) - server_config = root.find('server-config').attrib - download = root.find('download').attrib - upload = root.find('upload').attrib - # times = root.find('times').attrib - client = root.find('client').attrib - - except AttributeError: - try: - root = DOM.parseString(configxml) - except ExpatError: - e = get_exception() - raise SpeedtestConfigError( - 'Malformed speedtest.net configuration: %s' % e - ) - server_config = get_attributes_by_tag_name(root, 'server-config') - download = get_attributes_by_tag_name(root, 'download') - upload = get_attributes_by_tag_name(root, 'upload') - # times = get_attributes_by_tag_name(root, 'times') - client = get_attributes_by_tag_name(root, 'client') - - ignore_servers = [ - int(i) for i in server_config['ignoreids'].split(',') if i - ] - - ratio = int(upload['ratio']) - upload_max = int(upload['maxchunkcount']) - up_sizes = [32768, 65536, 131072, 262144, 524288, 1048576, 7340032] - sizes = { - 'upload': up_sizes[ratio - 1:], - 'download': [350, 500, 750, 1000, 1500, 2000, 2500, - 3000, 3500, 4000] - } - - size_count = len(sizes['upload']) - - upload_count = int(math.ceil(upload_max / size_count)) - - counts = { - 'upload': upload_count, - 'download': int(download['threadsperurl']) - } - - threads = { - 'upload': int(upload['threads']), - 'download': int(server_config['threadcount']) * 2 - } - - length = { - 'upload': int(upload['testlength']), - 'download': int(download['testlength']) - } - - self.config.update({ - 'client': client, - 'ignore_servers': ignore_servers, - 'sizes': sizes, - 'counts': counts, - 'threads': threads, - 'length': length, - 'upload_max': upload_count * size_count - }) - - try: - self.lat_lon = (float(client['lat']), float(client['lon'])) - except ValueError: - raise SpeedtestConfigError( - 'Unknown location: lat=%r lon=%r' % - (client.get('lat'), client.get('lon')) - ) - - printer('Config:\n%r' % self.config, debug=True) - - return self.config - - def get_servers(self, servers=None, exclude=None): - """Retrieve a the list of speedtest.net servers, optionally filtered - to servers matching those specified in the ``servers`` argument - """ - if servers is None: - servers = [] - - if exclude is None: - exclude = [] - - self.servers.clear() - - for server_list in (servers, exclude): - for i, s in enumerate(server_list): - try: - server_list[i] = int(s) - except ValueError: - raise InvalidServerIDType( - '%s is an invalid server type, must be int' % s - ) - - urls = [ - '://www.speedtest.net/speedtest-servers-static.php', - 'http://c.speedtest.net/speedtest-servers-static.php', - '://www.speedtest.net/speedtest-servers.php', - 'http://c.speedtest.net/speedtest-servers.php', - ] - - headers = {} - if gzip: - headers['Accept-Encoding'] = 'gzip' - - errors = [] - for url in urls: - try: - request = build_request( - '%s?threads=%s' % (url, - self.config['threads']['download']), - headers=headers, - secure=self._secure - ) - uh, e = catch_request(request, opener=self._opener) - if e: - errors.append('%s' % e) - raise ServersRetrievalError() - - stream = get_response_stream(uh) - - serversxml_list = [] - while 1: - try: - serversxml_list.append(stream.read(1024)) - except (OSError, EOFError): - raise ServersRetrievalError(get_exception()) - if len(serversxml_list[-1]) == 0: - break - - stream.close() - uh.close() - - if int(uh.code) != 200: - raise ServersRetrievalError() - - serversxml = ''.encode().join(serversxml_list) - - printer('Servers XML:\n%s' % serversxml, debug=True) - - try: - try: - try: - root = ET.fromstring(serversxml) - except ET.ParseError: - e = get_exception() - raise SpeedtestServersError( - 'Malformed speedtest.net server list: %s' % e - ) - elements = etree_iter(root, 'server') - except AttributeError: - try: - root = DOM.parseString(serversxml) - except ExpatError: - e = get_exception() - raise SpeedtestServersError( - 'Malformed speedtest.net server list: %s' % e - ) - elements = root.getElementsByTagName('server') - except (SyntaxError, xml.parsers.expat.ExpatError): - raise ServersRetrievalError() - - for server in elements: - try: - attrib = server.attrib - except AttributeError: - attrib = dict(list(server.attributes.items())) - - if servers and int(attrib.get('id')) not in servers: - continue - - if (int(attrib.get('id')) in self.config['ignore_servers'] - or int(attrib.get('id')) in exclude): - continue - - try: - d = distance(self.lat_lon, - (float(attrib.get('lat')), - float(attrib.get('lon')))) - except Exception: - continue - - attrib['d'] = d - - try: - self.servers[d].append(attrib) - except KeyError: - self.servers[d] = [attrib] - - break - - except ServersRetrievalError: - continue - - if (servers or exclude) and not self.servers: - raise NoMatchedServers() - - return self.servers - - def set_mini_server(self, server): - """Instead of querying for a list of servers, set a link to a - speedtest mini server - """ - - urlparts = urlparse(server) - - name, ext = os.path.splitext(urlparts[2]) - if ext: - url = os.path.dirname(server) - else: - url = server - - request = build_request(url) - uh, e = catch_request(request, opener=self._opener) - if e: - raise SpeedtestMiniConnectFailure('Failed to connect to %s' % - server) - else: - text = uh.read() - uh.close() - - extension = re.findall('upload_?[Ee]xtension: "([^"]+)"', - text.decode()) - if not extension: - for ext in ['php', 'asp', 'aspx', 'jsp']: - try: - f = self._opener.open( - '%s/speedtest/upload.%s' % (url, ext) - ) - except Exception: - pass - else: - data = f.read().strip().decode() - if (f.code == 200 and - len(data.splitlines()) == 1 and - re.match('size=[0-9]', data)): - extension = [ext] - break - if not urlparts or not extension: - raise InvalidSpeedtestMiniServer('Invalid Speedtest Mini Server: ' - '%s' % server) - - self.servers = [{ - 'sponsor': 'Speedtest Mini', - 'name': urlparts[1], - 'd': 0, - 'url': '%s/speedtest/upload.%s' % (url.rstrip('/'), extension[0]), - 'latency': 0, - 'id': 0 - }] - - return self.servers - - def get_closest_servers(self, limit=5): - """Limit servers to the closest speedtest.net servers based on - geographic distance - """ - - if not self.servers: - self.get_servers() - - for d in sorted(self.servers.keys()): - for s in self.servers[d]: - self.closest.append(s) - if len(self.closest) == limit: - break - else: - continue - break - - printer('Closest Servers:\n%r' % self.closest, debug=True) - return self.closest - - def get_best_server(self, servers=None): - """Perform a speedtest.net "ping" to determine which speedtest.net - server has the lowest latency - """ - - if not servers: - if not self.closest: - servers = self.get_closest_servers() - servers = self.closest - - if self._source_address: - source_address_tuple = (self._source_address, 0) - else: - source_address_tuple = None - - user_agent = build_user_agent() - - results = {} - for server in servers: - cum = [] - url = os.path.dirname(server['url']) - stamp = int(timeit.time.time() * 1000) - latency_url = '%s/latency.txt?x=%s' % (url, stamp) - for i in range(0, 3): - this_latency_url = '%s.%s' % (latency_url, i) - printer('%s %s' % ('GET', this_latency_url), - debug=True) - urlparts = urlparse(latency_url) - try: - if urlparts[0] == 'https': - h = SpeedtestHTTPSConnection( - urlparts[1], - source_address=source_address_tuple - ) - else: - h = SpeedtestHTTPConnection( - urlparts[1], - source_address=source_address_tuple - ) - headers = {'User-Agent': user_agent} - path = '%s?%s' % (urlparts[2], urlparts[4]) - start = timeit.default_timer() - h.request("GET", path, headers=headers) - r = h.getresponse() - total = (timeit.default_timer() - start) - except HTTP_ERRORS: - e = get_exception() - printer('ERROR: %r' % e, debug=True) - cum.append(3600) - continue - - text = r.read(9) - if int(r.status) == 200 and text == 'test=test'.encode(): - cum.append(total) - else: - cum.append(3600) - h.close() - - avg = round((sum(cum) / 6) * 1000.0, 3) - results[avg] = server - - try: - fastest = sorted(results.keys())[0] - except IndexError: - raise SpeedtestBestServerFailure('Unable to connect to servers to ' - 'test latency.') - best = results[fastest] - best['latency'] = fastest - - self.results.ping = fastest - self.results.server = best - - self._best.update(best) - printer('Best Server:\n%r' % best, debug=True) - return best - - def download(self, callback=do_nothing, threads=None): - """Test download speed against speedtest.net - - A ``threads`` value of ``None`` will fall back to those dictated - by the speedtest.net configuration - """ - - urls = [] - for size in self.config['sizes']['download']: - for _ in range(0, self.config['counts']['download']): - urls.append('%s/random%sx%s.jpg' % - (os.path.dirname(self.best['url']), size, size)) - - request_count = len(urls) - requests = [] - for i, url in enumerate(urls): - requests.append( - build_request(url, bump=i, secure=self._secure) - ) - - max_threads = threads or self.config['threads']['download'] - in_flight = {'threads': 0} - - def producer(q, requests, request_count): - for i, request in enumerate(requests): - thread = HTTPDownloader( - i, - request, - start, - self.config['length']['download'], - opener=self._opener, - shutdown_event=self._shutdown_event - ) - while in_flight['threads'] >= max_threads: - timeit.time.sleep(0.001) - thread.start() - q.put(thread, True) - in_flight['threads'] += 1 - callback(i, request_count, start=True) - - finished = [] - - def consumer(q, request_count): - _is_alive = thread_is_alive - while len(finished) < request_count: - thread = q.get(True) - while _is_alive(thread): - thread.join(timeout=0.001) - in_flight['threads'] -= 1 - finished.append(sum(thread.result)) - callback(thread.i, request_count, end=True) - - q = Queue(max_threads) - prod_thread = threading.Thread(target=producer, - args=(q, requests, request_count)) - cons_thread = threading.Thread(target=consumer, - args=(q, request_count)) - start = timeit.default_timer() - prod_thread.start() - cons_thread.start() - _is_alive = thread_is_alive - while _is_alive(prod_thread): - prod_thread.join(timeout=0.001) - while _is_alive(cons_thread): - cons_thread.join(timeout=0.001) - - stop = timeit.default_timer() - self.results.bytes_received = sum(finished) - self.results.download = ( - (self.results.bytes_received / (stop - start)) * 8.0 - ) - if self.results.download > 100000: - self.config['threads']['upload'] = 8 - return self.results.download - - def upload(self, callback=do_nothing, pre_allocate=True, threads=None): - """Test upload speed against speedtest.net - - A ``threads`` value of ``None`` will fall back to those dictated - by the speedtest.net configuration - """ - - sizes = [] - - for size in self.config['sizes']['upload']: - for _ in range(0, self.config['counts']['upload']): - sizes.append(size) - - # request_count = len(sizes) - request_count = self.config['upload_max'] - - requests = [] - for i, size in enumerate(sizes): - # We set ``0`` for ``start`` and handle setting the actual - # ``start`` in ``HTTPUploader`` to get better measurements - data = HTTPUploaderData( - size, - 0, - self.config['length']['upload'], - shutdown_event=self._shutdown_event - ) - if pre_allocate: - data.pre_allocate() - - headers = {'Content-length': size} - requests.append( - ( - build_request(self.best['url'], data, secure=self._secure, - headers=headers), - size - ) - ) - - max_threads = threads or self.config['threads']['upload'] - in_flight = {'threads': 0} - - def producer(q, requests, request_count): - for i, request in enumerate(requests[:request_count]): - thread = HTTPUploader( - i, - request[0], - start, - request[1], - self.config['length']['upload'], - opener=self._opener, - shutdown_event=self._shutdown_event - ) - while in_flight['threads'] >= max_threads: - timeit.time.sleep(0.001) - thread.start() - q.put(thread, True) - in_flight['threads'] += 1 - callback(i, request_count, start=True) - - finished = [] - - def consumer(q, request_count): - _is_alive = thread_is_alive - while len(finished) < request_count: - thread = q.get(True) - while _is_alive(thread): - thread.join(timeout=0.001) - in_flight['threads'] -= 1 - finished.append(thread.result) - callback(thread.i, request_count, end=True) - - q = Queue(threads or self.config['threads']['upload']) - prod_thread = threading.Thread(target=producer, - args=(q, requests, request_count)) - cons_thread = threading.Thread(target=consumer, - args=(q, request_count)) - start = timeit.default_timer() - prod_thread.start() - cons_thread.start() - _is_alive = thread_is_alive - while _is_alive(prod_thread): - prod_thread.join(timeout=0.1) - while _is_alive(cons_thread): - cons_thread.join(timeout=0.1) - - stop = timeit.default_timer() - self.results.bytes_sent = sum(finished) - self.results.upload = ( - (self.results.bytes_sent / (stop - start)) * 8.0 - ) - return self.results.upload - - -def ctrl_c(shutdown_event): - """Catch Ctrl-C key sequence and set a SHUTDOWN_EVENT for our threaded - operations - """ - def inner(signum, frame): - shutdown_event.set() - printer('\nCancelling...', error=True) - sys.exit(0) - return inner - - -def version(): - """Print the version""" - - printer('speedtest-cli %s' % __version__) - printer('Python %s' % sys.version.replace('\n', '')) - sys.exit(0) - - -def csv_header(delimiter=','): - """Print the CSV Headers""" - - printer(SpeedtestResults.csv_header(delimiter=delimiter)) - sys.exit(0) - - -def parse_args(): - """Function to handle building and parsing of command line arguments""" - description = ( - 'Command line interface for testing internet bandwidth using ' - 'speedtest.net.\n' - '------------------------------------------------------------' - '--------------\n' - 'https://github.com/sivel/speedtest-cli') - - parser = ArgParser(description=description) - # Give optparse.OptionParser an `add_argument` method for - # compatibility with argparse.ArgumentParser - try: - parser.add_argument = parser.add_option - except AttributeError: - pass - parser.add_argument('--no-download', dest='download', default=True, - action='store_const', const=False, - help='Do not perform download test') - parser.add_argument('--no-upload', dest='upload', default=True, - action='store_const', const=False, - help='Do not perform upload test') - parser.add_argument('--single', default=False, action='store_true', - help='Only use a single connection instead of ' - 'multiple. This simulates a typical file ' - 'transfer.') - parser.add_argument('--bytes', dest='units', action='store_const', - const=('byte', 8), default=('bit', 1), - help='Display values in bytes instead of bits. Does ' - 'not affect the image generated by --share, nor ' - 'output from --json or --csv') - parser.add_argument('--share', action='store_true', - help='Generate and provide a URL to the speedtest.net ' - 'share results image, not displayed with --csv') - parser.add_argument('--simple', action='store_true', default=False, - help='Suppress verbose output, only show basic ' - 'information') - parser.add_argument('--csv', action='store_true', default=False, - help='Suppress verbose output, only show basic ' - 'information in CSV format. Speeds listed in ' - 'bit/s and not affected by --bytes') - parser.add_argument('--csv-delimiter', default=',', type=PARSER_TYPE_STR, - help='Single character delimiter to use in CSV ' - 'output. Default ","') - parser.add_argument('--csv-header', action='store_true', default=False, - help='Print CSV headers') - parser.add_argument('--json', action='store_true', default=False, - help='Suppress verbose output, only show basic ' - 'information in JSON format. Speeds listed in ' - 'bit/s and not affected by --bytes') - parser.add_argument('--list', action='store_true', - help='Display a list of speedtest.net servers ' - 'sorted by distance') - parser.add_argument('--server', type=PARSER_TYPE_INT, action='append', - help='Specify a server ID to test against. Can be ' - 'supplied multiple times') - parser.add_argument('--exclude', type=PARSER_TYPE_INT, action='append', - help='Exclude a server from selection. Can be ' - 'supplied multiple times') - parser.add_argument('--mini', help='URL of the Speedtest Mini server') - parser.add_argument('--source', help='Source IP address to bind to') - parser.add_argument('--timeout', default=10, type=PARSER_TYPE_FLOAT, - help='HTTP timeout in seconds. Default 10') - parser.add_argument('--secure', action='store_true', - help='Use HTTPS instead of HTTP when communicating ' - 'with speedtest.net operated servers') - parser.add_argument('--no-pre-allocate', dest='pre_allocate', - action='store_const', default=True, const=False, - help='Do not pre allocate upload data. Pre allocation ' - 'is enabled by default to improve upload ' - 'performance. To support systems with ' - 'insufficient memory, use this option to avoid a ' - 'MemoryError') - parser.add_argument('--version', action='store_true', - help='Show the version number and exit') - parser.add_argument('--debug', action='store_true', - help=ARG_SUPPRESS, default=ARG_SUPPRESS) - - options = parser.parse_args() - if isinstance(options, tuple): - args = options[0] - else: - args = options - return args - - -def validate_optional_args(args): - """Check if an argument was provided that depends on a module that may - not be part of the Python standard library. - - If such an argument is supplied, and the module does not exist, exit - with an error stating which module is missing. - """ - optional_args = { - 'json': ('json/simplejson python module', json), - 'secure': ('SSL support', HTTPSConnection), - } - - for arg, info in optional_args.items(): - if getattr(args, arg, False) and info[1] is None: - raise SystemExit('%s is not installed. --%s is ' - 'unavailable' % (info[0], arg)) - - -def printer(string, quiet=False, debug=False, error=False, **kwargs): - """Helper function print a string with various features""" - - if debug and not DEBUG: - return - - if debug: - if sys.stdout.isatty(): - out = '\033[1;30mDEBUG: %s\033[0m' % string - else: - out = 'DEBUG: %s' % string - else: - out = string - - if error: - kwargs['file'] = sys.stderr - - if not quiet: - print_(out, **kwargs) - - -def shell(): - """Run the full speedtest.net test""" - - global DEBUG - shutdown_event = threading.Event() - - signal.signal(signal.SIGINT, ctrl_c(shutdown_event)) - - args = parse_args() - - # Print the version and exit - if args.version: - version() - - if not args.download and not args.upload: - raise SpeedtestCLIError('Cannot supply both --no-download and ' - '--no-upload') - - if len(args.csv_delimiter) != 1: - raise SpeedtestCLIError('--csv-delimiter must be a single character') - - if args.csv_header: - csv_header(args.csv_delimiter) - - validate_optional_args(args) - - debug = getattr(args, 'debug', False) - if debug == 'SUPPRESSHELP': - debug = False - if debug: - DEBUG = True - - if args.simple or args.csv or args.json: - quiet = True - else: - quiet = False - - if args.csv or args.json: - machine_format = True - else: - machine_format = False - - # Don't set a callback if we are running quietly - if quiet or debug: - callback = do_nothing - else: - callback = print_dots(shutdown_event) - - printer('Retrieving speedtest.net configuration...', quiet) - try: - speedtest = Speedtest( - source_address=args.source, - timeout=args.timeout, - secure=args.secure - ) - except (ConfigRetrievalError,) + HTTP_ERRORS: - printer('Cannot retrieve speedtest configuration', error=True) - raise SpeedtestCLIError(get_exception()) - - if args.list: - try: - speedtest.get_servers() - except (ServersRetrievalError,) + HTTP_ERRORS: - printer('Cannot retrieve speedtest server list', error=True) - raise SpeedtestCLIError(get_exception()) - - for _, servers in sorted(speedtest.servers.items()): - for server in servers: - line = ('%(id)5s) %(sponsor)s (%(name)s, %(country)s) ' - '[%(d)0.2f km]' % server) - try: - printer(line) - except IOError: - e = get_exception() - if e.errno != errno.EPIPE: - raise - sys.exit(0) - - printer('Testing from %(isp)s (%(ip)s)...' % speedtest.config['client'], - quiet) - - if not args.mini: - printer('Retrieving speedtest.net server list...', quiet) - try: - speedtest.get_servers(servers=args.server, exclude=args.exclude) - except NoMatchedServers: - raise SpeedtestCLIError( - 'No matched servers: %s' % - ', '.join('%s' % s for s in args.server) - ) - except (ServersRetrievalError,) + HTTP_ERRORS: - printer('Cannot retrieve speedtest server list', error=True) - raise SpeedtestCLIError(get_exception()) - except InvalidServerIDType: - raise SpeedtestCLIError( - '%s is an invalid server type, must ' - 'be an int' % ', '.join('%s' % s for s in args.server) - ) - - if args.server and len(args.server) == 1: - printer('Retrieving information for the selected server...', quiet) - else: - printer('Selecting best server based on ping...', quiet) - speedtest.get_best_server() - elif args.mini: - speedtest.get_best_server(speedtest.set_mini_server(args.mini)) - - results = speedtest.results - - printer('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: ' - '%(latency)s ms' % results.server, quiet) - - if args.download: - printer('Testing download speed', quiet, - end=('', '\n')[bool(debug)]) - speedtest.download( - callback=callback, - threads=(None, 1)[args.single] - ) - printer('Download: %0.2f M%s/s' % - ((results.download / 1000.0 / 1000.0) / args.units[1], - args.units[0]), - quiet) - else: - printer('Skipping download test', quiet) - - if args.upload: - printer('Testing upload speed', quiet, - end=('', '\n')[bool(debug)]) - speedtest.upload( - callback=callback, - pre_allocate=args.pre_allocate, - threads=(None, 1)[args.single] - ) - printer('Upload: %0.2f M%s/s' % - ((results.upload / 1000.0 / 1000.0) / args.units[1], - args.units[0]), - quiet) - else: - printer('Skipping upload test', quiet) - - printer('Results:\n%r' % results.dict(), debug=True) - - if not args.simple and args.share: - results.share() - - if args.simple: - printer('Ping: %s ms\nDownload: %0.2f M%s/s\nUpload: %0.2f M%s/s' % - (results.ping, - (results.download / 1000.0 / 1000.0) / args.units[1], - args.units[0], - (results.upload / 1000.0 / 1000.0) / args.units[1], - args.units[0])) - elif args.csv: - printer(results.csv(delimiter=args.csv_delimiter)) - elif args.json: - printer(results.json()) - - if args.share and not machine_format: - printer('Share results: %s' % results.share()) - - -def main(): - try: - shell() - except KeyboardInterrupt: - printer('\nCancelling...', error=True) - except (SpeedtestException, SystemExit): - e = get_exception() - # Ignore a successful exit, or argparse exit - if getattr(e, 'code', 1) not in (0, 2): - msg = '%s' % e - if not msg: - msg = '%r' % e - raise SystemExit('ERROR: %s' % msg) - - -if __name__ == '__main__': - main() diff --git a/luci-app-netspeedtest/root/usr/share/luci/menu.d/luci-app-netspeedtest.json b/luci-app-netspeedtest/root/usr/share/luci/menu.d/luci-app-netspeedtest.json new file mode 100644 index 0000000..f0ca17e --- /dev/null +++ b/luci-app-netspeedtest/root/usr/share/luci/menu.d/luci-app-netspeedtest.json @@ -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": "Lan Speedtest Iperf3", + "order": 1, + "action": { + "type": "view", + "path": "netspeedtest/iperf3" + } + }, + "admin/network/netspeedtest/homebox": { + "title": "Lan Speedtest Homebox", + "order": 2, + "action": { + "type": "view", + "path": "netspeedtest/homebox" + } + }, + "admin/network/netspeedtest/speedtest": { + "title": "Wan Ookla SpeedTest", + "order": 4, + "action": { + "type": "view", + "path": "netspeedtest/speedtest" + } + }, + "admin/network/netspeedtest/onlinespeedtest": { + "title": "Online SpeedTest", + "order": 5, + "action": { + "type": "view", + "path": "netspeedtest/onlinespeedtest" + } + }, + "admin/network/netspeedtest/logs": { + "title": "Log", + "order": 6, + "action": { + "type": "view", + "path": "netspeedtest/logs" + } + } +} diff --git a/luci-app-netspeedtest/root/usr/share/rpcd/acl.d/luci-app-netspeedtest.json b/luci-app-netspeedtest/root/usr/share/rpcd/acl.d/luci-app-netspeedtest.json index 3e1fcab..661afdc 100644 --- a/luci-app-netspeedtest/root/usr/share/rpcd/acl.d/luci-app-netspeedtest.json +++ b/luci-app-netspeedtest/root/usr/share/rpcd/acl.d/luci-app-netspeedtest.json @@ -1,11 +1,28 @@ { - "luci-app-netspeedtest": { - "description": "Grant UCI access for luci-app-netspeedtest", - "read": { - "uci": [ "netspeedtest" ] - }, - "write": { - "uci": [ "netspeedtest" ] - } - } + "luci-app-netspeedtest": { + "description": "Grant access to netspeedtest procedures", + "read": { + "file": { + "/usr/lib/netspeedtest/speedtest": [ "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" ], + "/tmp/speedtest_result": [ "read" ], + "/tmp/netspeedtest.log": [ "read" ] + }, + "ubus": { + "service": [ "list" ] + }, + "uci": [ "netspeedtest" ,"netspeedtest"] + }, + "write": { + "uci": [ "netspeedtest","netspeedtest" ] + } + } } diff --git a/netspeedtest/Makefile b/netspeedtest/Makefile new file mode 100644 index 0000000..dc5518a --- /dev/null +++ b/netspeedtest/Makefile @@ -0,0 +1,39 @@ +# +# Copyright (C) 2025 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-netspeedtest +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. + +include $(TOPDIR)/rules.mk + +PKG_NAME:=netspeedtest +PKG_VERSION:=1 +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + SECTION:=utils + CATEGORY:=Utilities + TITLE:=Routing netspeedtest for OpenWrt + DEPENDS:=+curl +bash +coreutils-nohup +endef + +define Package/$(PKG_NAME)/description + Routing netspeedtest for OpenWrt @sirpdboy +endef + +define Build/Compile +endef + +define Package/$(PKG_NAME)/conffiles +/etc/config/netspeedtest +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/etc/config + $(CP) ./files/netspeedtest.config $(1)/etc/config/netspeedtest + $(INSTALL_DIR) $(1)/usr/lib/netspeedtest + $(INSTALL_BIN) $(CURDIR)/files/speedtest.lib $(1)/usr/lib/netspeedtest/speedtest +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/netspeedtest/files/netspeedtest.config b/netspeedtest/files/netspeedtest.config new file mode 100644 index 0000000..fe131ba --- /dev/null +++ b/netspeedtest/files/netspeedtest.config @@ -0,0 +1,2 @@ + +config netspeedtest 'config' diff --git a/netspeedtest/files/speedtest.lib b/netspeedtest/files/speedtest.lib new file mode 100644 index 0000000..0f4d3dc --- /dev/null +++ b/netspeedtest/files/speedtest.lib @@ -0,0 +1,56 @@ +#!/bin/bash +mkdir -p /etc/speedtest +export HOME='/etc/speedtest' +SPEEDTEST_CLI='/usr/bin/speedtest' +SPEEDTEST_RESULT='/tmp/speedtest_result' +LOG='/tmp/netspeedtest.log' + +# 记录开始时间 +echo "=== Speedtest started at $(date) ===" >> "$LOG" + +[ -n "$(pgrep -f "$SPEEDTEST_CLI")" ] && exit 1 + +LOCAL_IP=$(curl -s -4 --connect-timeout 3 http://ip.3322.net) +echo "Local IP: $LOCAL_IP" >> "$LOG" + +BAIDU_SK="LHHGlmhcb4ENvIXpR9QQ2tBYa6ooUowX hYCENCEx1nXO0Nt46ldexfG9oI49xBGh 0kKZnWWhXEPfzIkklmzAa3dZ" +if [ -n "$LOCAL_IP" ]; then + for SK in $BAIDU_SK + do + INFO=$(curl -sk --connect-timeout 3 "https://api.map.baidu.com/location/ip?ip="$LOCAL_IP"&coor=bd09ll&ak=$SK") + if [ "$(echo $INFO | jsonfilter -e "@['status']")" = 0 ]; then + status=0 + break + fi + done + if [ "$status" = 0 ]; then + lon=$(echo $INFO | jsonfilter -e "@['content']['point']['x']") + lat=$(echo $INFO | jsonfilter -e "@['content']['point']['y']") + server_id=$(curl -sk --connect-timeout 3 "https://www.speedtest.net/api/ios-config.php?lon=$lon&lat=$lat" | grep "server url" | head -n1 | sed 's/.*id="//;s/".*//') + [ -n "$server_id" ] && ARG="-s $server_id" + echo "Selected server ID: $server_id" >> "$LOG" + fi +fi + +echo "Testing" > "$SPEEDTEST_RESULT" +RUNTEST=$($SPEEDTEST_CLI --accept-gdpr --accept-license --progress=no $ARG 2>&1) +if [ $(echo $RUNTEST | grep -c "No servers defined") -ge 1 ] || [ $(echo $RUNTEST | grep -c "error") -ge 1 ]; then + echo "Fallback to default server selection" >> "$LOG" + RUNTEST=$($SPEEDTEST_CLI --accept-gdpr --accept-license --progress=no 2>&1) +fi + +# 将完整测试结果记录到日志 +echo "$RUNTEST" >> "$LOG" + +RESULT=$(echo "$RUNTEST" | grep "Result URL" | awk '{print $NF}') + +if [ -n "$RESULT" ]; then + echo "$RESULT" > "$SPEEDTEST_RESULT" +else + echo "Test failed" > "$SPEEDTEST_RESULT" + echo "Test failed" >> "$LOG" +fi + +# 记录结束时间 +echo "=== Speedtest completed at $(date) ===" >> "$LOG" +echo "" >> "$LOG" # 添加空行分隔不同测试记录 diff --git a/speedtest-cli/Makefile b/speedtest-cli/Makefile new file mode 100644 index 0000000..7e0faae --- /dev/null +++ b/speedtest-cli/Makefile @@ -0,0 +1,61 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=speedtest-cli +PKG_VERSION:=1.2.0 +PKG_RELEASE:=1 + +ifeq ($(ARCH),aarch64) + PKG_HASH:=3953d231da3783e2bf8904b6dd72767c5c6e533e163d3742fd0437affa431bd3 +else ifeq ($(ARCH),arm) + ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE)))) + ifeq ($(ARM_CPU_FEATURES),) + ARCH:=armel + PKG_HASH:=629a455a2879224bd0dbd4b36d8c721dda540717937e4660b4d2c966029466bf + else + ARCH:=armhf + PKG_HASH:=e45fcdebbd8a185553535533dd032d6b10bc8c64eee4139b1147b9c09835d08d + endif +else ifeq ($(ARCH),i386) + PKG_HASH:=9ff7e18dbae7ee0e03c66108445a2fb6ceea6c86f66482e1392f55881b772fe8 +else ifeq ($(ARCH),x86_64) + PKG_HASH:=5690596c54ff9bed63fa3732f818a05dbc2db19ad36ed68f21ca5f64d5cfeeb7 +endif + +PKG_SOURCE:=ookla-speedtest-$(PKG_VERSION)-linux-$(ARCH).tgz +PKG_SOURCE_URL:=https://install.speedtest.net/app/cli + +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) + +PKG_MAINTAINER:=sbwml + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + SECTION:=net + CATEGORY:=Network + TITLE:=Speedtest CLI by Ookla + DEPENDS:=@(aarch64||arm||i386||x86_64) +ca-certificates + URL:=https://www.speedtest.net/ +endef + +define Package/$(PKG_NAME)/description + The Global Broadband Speed Test +endef + +define Build/Prepare + ( \ + pushd $(PKG_BUILD_DIR) ; \ + $(TAR) -zxf $(DL_DIR)/ookla-speedtest-$(PKG_VERSION)-linux-$(ARCH).tgz -C . ; \ + popd ; \ + ) +endef + +define Build/Compile +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/speedtest $(1)/usr/bin +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git "a/\346\274\224\347\244\272.gif" "b/\346\274\224\347\244\272.gif" new file mode 100644 index 0000000..8060d73 Binary files /dev/null and "b/\346\274\224\347\244\272.gif" differ