├── web ├── js │ └── tools.js ├── img │ ├── win.webp │ └── linux.jpg ├── css │ ├── fonts │ │ ├── element-icons.ttf │ │ └── element-icons.woff │ └── reset.css └── AGENTS.md ├── android-h5 ├── app │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── themes.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── layout │ │ │ │ │ └── activity_main.xml │ │ │ │ ├── values-night │ │ │ │ │ └── themes.xml │ │ │ │ ├── xml │ │ │ │ │ └── network_security_config.xml │ │ │ │ ├── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ └── drawable │ │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── cctyl │ │ │ │ └── starnode │ │ │ │ └── h5 │ │ │ │ ├── utils │ │ │ │ ├── JsResultParser.java │ │ │ │ └── JsExecUtil.java │ │ │ │ ├── AssetsWebServer.java │ │ │ │ └── MainActivity.java │ │ ├── test │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── cctyl │ │ │ │ └── starnode │ │ │ │ └── h5 │ │ │ │ └── ExampleUnitTest.java │ │ └── androidTest │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── cctyl │ │ │ └── starnode │ │ │ └── h5 │ │ │ └── ExampleInstrumentedTest.java │ ├── proguard-rules.pro │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── build.gradle ├── settings.gradle ├── gradle.properties ├── gradlew.bat └── gradlew ├── img ├── a.png ├── b.png ├── c.jpg ├── d.jpg ├── e.jpg ├── armbian 4c 4g x64.png ├── ubuntu18 2c 4g x64.png ├── ubuntu22 2c 1g x64.png ├── win10 4c 12g x64.png ├── win10 8c 16g x64.png ├── rust_armbian_4c_4g_x64.png ├── rust_debian_2c_4g_x64.png └── rust_win10_8c_16g_x64.png ├── client ├── linux-client │ ├── devinfo.cpp │ ├── config.json │ ├── README.md │ ├── CMakeLists.txt │ ├── info.h │ ├── main.cpp │ ├── devinfo.h │ ├── easywsclient.hpp │ └── reflect_json.hpp ├── qt-client │ ├── devinfo.cpp │ ├── config.json │ ├── info.h │ ├── main.cpp │ ├── CMakeLists.txt │ ├── devinfo.h │ ├── wininfo.cpp │ ├── info.cpp │ └── qtjson.hpp ├── rust-client │ ├── src │ │ ├── lib.rs │ │ ├── utils.rs │ │ ├── main.rs │ │ └── modles.rs │ ├── config.json │ └── Cargo.toml └── js-client │ ├── config.js │ ├── test.js │ ├── app │ ├── utils │ │ ├── functions.js │ │ └── os-data.js │ └── client.js │ └── package.json ├── web-h5-vue2 ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ ├── logo.png │ │ ├── 问题清单.md │ │ └── icons │ │ │ ├── other.svg │ │ │ ├── linux.svg │ │ │ ├── windows.svg │ │ │ └── server.svg │ ├── App.vue │ ├── main.js │ ├── router │ │ └── index.js │ ├── components │ │ ├── DeviceList.vue │ │ ├── MonitorHeader.vue │ │ ├── SummaryCards.vue │ │ ├── HelloWorld.vue │ │ ├── HardwareCharts.vue │ │ ├── DeviceCard.vue │ │ └── SettingsModal.vue │ ├── services │ │ └── websocket.js │ ├── mixins │ │ └── chartMixin.js │ └── views │ │ └── Monitor.vue ├── babel.config.js ├── settings_interface.png ├── .claude │ └── settings.local.json ├── .mcp.json ├── vue.config.js ├── .gitignore ├── jsconfig.json ├── README.md ├── 原型图 │ ├── 需求文档.md │ └── index.html ├── package.json └── CLAUDE.md ├── server ├── config.js ├── test │ └── test.js ├── package.json └── app │ ├── utils │ ├── functions.js │ └── os-data.js │ ├── simpleHttpServer.js │ └── server.js ├── other-client.md ├── .gitignore └── README.md /web/js/tools.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /android-h5/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /img/a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/a.png -------------------------------------------------------------------------------- /img/b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/b.png -------------------------------------------------------------------------------- /img/c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/c.jpg -------------------------------------------------------------------------------- /img/d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/d.jpg -------------------------------------------------------------------------------- /img/e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/e.jpg -------------------------------------------------------------------------------- /web/img/win.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/web/img/win.webp -------------------------------------------------------------------------------- /client/linux-client/devinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "devinfo.h" 2 | 3 | DevInfo::DevInfo() {} 4 | -------------------------------------------------------------------------------- /client/qt-client/devinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "devinfo.h" 2 | 3 | DevInfo::DevInfo() {} 4 | -------------------------------------------------------------------------------- /web/img/linux.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/web/img/linux.jpg -------------------------------------------------------------------------------- /client/rust-client/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod modles; 2 | pub mod services; 3 | pub mod utils; 4 | -------------------------------------------------------------------------------- /img/armbian 4c 4g x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/armbian 4c 4g x64.png -------------------------------------------------------------------------------- /img/ubuntu18 2c 4g x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/ubuntu18 2c 4g x64.png -------------------------------------------------------------------------------- /img/ubuntu22 2c 1g x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/ubuntu22 2c 1g x64.png -------------------------------------------------------------------------------- /img/win10 4c 12g x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/win10 4c 12g x64.png -------------------------------------------------------------------------------- /img/win10 8c 16g x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/win10 8c 16g x64.png -------------------------------------------------------------------------------- /client/linux-client/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "服务端地址", 3 | "port": 6080, 4 | "token": "你的token" 5 | } 6 | -------------------------------------------------------------------------------- /client/qt-client/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "服务端地址", 3 | "port": 6080, 4 | "token": "你的token" 5 | } 6 | -------------------------------------------------------------------------------- /client/rust-client/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "服务端地址", 3 | "port": 服务端端口, 4 | "token": "token" 5 | } 6 | -------------------------------------------------------------------------------- /img/rust_armbian_4c_4g_x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/rust_armbian_4c_4g_x64.png -------------------------------------------------------------------------------- /img/rust_debian_2c_4g_x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/rust_debian_2c_4g_x64.png -------------------------------------------------------------------------------- /img/rust_win10_8c_16g_x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/img/rust_win10_8c_16g_x64.png -------------------------------------------------------------------------------- /web-h5-vue2/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/web-h5-vue2/public/favicon.ico -------------------------------------------------------------------------------- /web-h5-vue2/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/web-h5-vue2/src/assets/logo.png -------------------------------------------------------------------------------- /web/css/fonts/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/web/css/fonts/element-icons.ttf -------------------------------------------------------------------------------- /web/css/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/web/css/fonts/element-icons.woff -------------------------------------------------------------------------------- /web-h5-vue2/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /web-h5-vue2/settings_interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/web-h5-vue2/settings_interface.png -------------------------------------------------------------------------------- /android-h5/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | starnode-h5 3 | -------------------------------------------------------------------------------- /client/linux-client/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### 开发环境依赖 3 | - sudo apt install libssl-dev 4 | - sudo apt install libcurl4-openssl-dev -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/android-h5/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/android-h5/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /client/js-client/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 6080, 3 | token: '你的token', 4 | serverHost:'服务端地址', 5 | endpointName: null 6 | }; 7 | 8 | -------------------------------------------------------------------------------- /web-h5-vue2/.claude/settings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabledMcpjsonServers": [ 3 | "chrome-devtools" 4 | ], 5 | "enableAllProjectMcpServers": true 6 | } 7 | -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/android-h5/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/android-h5/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/android-h5/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/android-h5/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/android-h5/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/android-h5/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/android-h5/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cctyl/starsnode/HEAD/android-h5/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /web-h5-vue2/.mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "chrome-devtools": { 4 | "command": "npx", 5 | "args": ["-y","--browserUrl" ,"chrome-devtools-mcp@latest"] 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 6080, 3 | token: '你的token', 4 | endpointName: null, 5 | warnApi:'告警信息api地址', 6 | warnToken:'告警信息的token' 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /web-h5-vue2/vue.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('@vue/cli-service') 2 | module.exports = defineConfig({ 3 | // publicPath:"./", 4 | transpileDependencies: true, 5 | outputDir:"D:\\project\\starsnode\\android-h5\\app\\src\\main\\assets\\www" 6 | }) 7 | -------------------------------------------------------------------------------- /android-h5/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jun 18 09:36:29 CST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /client/rust-client/src/utils.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | pub fn byte_to_gb(byte: f32) -> f32 { 4 | byte / 1024.0 / 1024.0 / 1024.0 5 | } 6 | pub fn byte_to_mb(byte: f32) -> f32 { 7 | byte / 1024.0 / 1024.0 8 | } 9 | 10 | pub fn round_f32(n: f32) -> f32 { 11 | (n * 100.0).round() / 100.0 12 | } -------------------------------------------------------------------------------- /web-h5-vue2/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /client/js-client/test.js: -------------------------------------------------------------------------------- 1 | const osu = require('node-os-utils') 2 | const os = require('os') 3 | const getOsData = require('./app/utils/os-data'); 4 | 5 | 6 | ( 7 | async function () { 8 | let result = await os.networkInterfaces(); 9 | console.log(result) 10 | } 11 | )(); 12 | -------------------------------------------------------------------------------- /android-h5/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | release -------------------------------------------------------------------------------- /server/test/test.js: -------------------------------------------------------------------------------- 1 | const osu = require('node-os-utils') 2 | const os = require('os') 3 | const getOsData = require('../app/utils/os-data'); 4 | 5 | 6 | ( 7 | async function () { 8 | let result = await os.networkInterfaces(); 9 | console.log(result) 10 | } 11 | )(); 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /android-h5/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '7.1.3' apply false 4 | id 'com.android.library' version '7.1.3' apply false 5 | } 6 | 7 | task clean(type: Delete) { 8 | delete rootProject.buildDir 9 | } -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android-h5/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web-h5-vue2/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /web-h5-vue2/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import './assets/monitor.css' 5 | import VConsole from 'vconsole'; 6 | 7 | 8 | 9 | // const vConsole = new VConsole(); 10 | 11 | Vue.config.productionTip = false 12 | 13 | new Vue({ 14 | router, 15 | render: h => h(App), 16 | }).$mount('#app') 17 | -------------------------------------------------------------------------------- /web-h5-vue2/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web-h5-vue2/src/assets/问题清单.md: -------------------------------------------------------------------------------- 1 | 2 | # 加载不了图片问题 3 | 4 | 放在public下的图片,打包后变成/img/xxx.svg, 5 | 但是在android加载时,会变成file:///img/xxx.svg。 6 | 7 | 8 | ## 排除法 9 | - 不是android webview的问题,因为simple-backup的html放在这个app里,图片可以正常加载,所以确定是web项目的问题 10 | - 在index.html用./img/xxx.svg 可以正常加载 11 | - /img 或者 img ,都不行,都是 file:///img/xxx.svg 12 | - ./img不行,会提示打包失败 13 | - 不是文件类型的问题,.svg改为.jpg也不行 14 | - 和simple-backup打包后文件对比,发现那边也是 /img作为路径,打包后也是/img,但是它的可以正常加载 -------------------------------------------------------------------------------- /web-h5-vue2/README.md: -------------------------------------------------------------------------------- 1 | # starnode-h5 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /android-h5/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /web-h5-vue2/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import Monitor from '../views/Monitor.vue'; 4 | 5 | Vue.use(VueRouter); 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'Monitor', 11 | component: Monitor 12 | }, 13 | { 14 | path: '*', 15 | redirect: '/' 16 | } 17 | ]; 18 | 19 | const router = new VueRouter({ 20 | mode: 'history', 21 | base: process.env.BASE_URL, 22 | routes 23 | }); 24 | 25 | export default router; -------------------------------------------------------------------------------- /android-h5/app/src/test/java/io/github/cctyl/starnode/h5/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package io.github.cctyl.starnode.h5; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /web-h5-vue2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 设备监控平台 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /android-h5/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 16 | -------------------------------------------------------------------------------- /client/rust-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "starnode" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | clap = { version = "4.5.39", features = ["derive"] } 8 | serde = { version = "1.0.219", features = ["derive"] } 9 | serde_json = "1.0.140" 10 | tungstenite = "0.26.2" 11 | network-interface = "2.0.1" 12 | sysinfo = "0.35.1" 13 | chrono = "0.4.41" 14 | 15 | minreq = { version = "2.13.4", features = ["https-rustls"] } 16 | 17 | [target.'cfg(target_os = "windows")'.dependencies] 18 | windows = { version = "0.54", features = ["Win32_System_RemoteDesktop", "Win32_System_StationsAndDesktops"] } 19 | 20 | 21 | -------------------------------------------------------------------------------- /web-h5-vue2/原型图/需求文档.md: -------------------------------------------------------------------------------- 1 | 2 | # 目标 3 | 制作一个手机端使用的监控平台 4 | 5 | # 数据格式 6 | data.json 就是实际数据 7 | 8 | # 要求 9 | 10 | 你需要制作一个html页面,把data.json中的数据完整的解析出来。 11 | 先出一个原型图用html来展示。 12 | 13 | 14 | ## 布局 15 | 因为是提供给手机端查看的,所以字体、缩放等要按照手机的标准设计 16 | 17 | ## 汇总 18 | 页面的最顶端,需要一个汇总,把所有设备的信息汇总在一起,方便查看。 19 | 具体来说,有这些: 20 | - 总在线设备数 21 | - win系统在线设备数 22 | - linux系统在线设备数 23 | - 其他系统在线设备数 24 | - 设备硬件汇总信息 25 | - CPU核心数汇总 26 | - cpu平均使用率 27 | - 磁盘总大小 28 | - 内存总大小 29 | 30 | 最好用图表展示 31 | 32 | 33 | ## 单个设备详情 34 | 35 | 需要首先需要展示设备的基本信息,cpu,内存,硬盘,以及该设备当前网络总上传下载速度,最好能用图表展示。 36 | 然后需要罗列该设备每个网卡信息。 37 | 然后要显示设备每个网卡的网络上传下载速度。 38 | 39 | 40 | -------------------------------------------------------------------------------- /client/js-client/app/utils/functions.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | module.exports = { 3 | 4 | guid() { 5 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 6 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); 7 | return v.toString(16); 8 | }); 9 | }, 10 | async getIpInfo() { 11 | try { 12 | let {data} = await axios.get('http://ip-api.com/json?lang=zh-CN'); 13 | console.log('getIpInfo Success: ', new Date()); 14 | return data; 15 | } catch (error) { 16 | console.log('getIpInfo Error: ', new Date(), error) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /android-h5/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | maven { url 'https://maven.aliyun.com/repository/jcenteer' } 7 | maven { url 'https://maven.aliyun.com/repository/google' } 8 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 9 | maven { url 'https://maven.aliyun.com/repository/public' } 10 | 11 | 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | rootProject.name = "starnode-h5" 22 | include ':app' 23 | -------------------------------------------------------------------------------- /android-h5/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easynode-client", 3 | "version": "1.0.0", 4 | "description": "easynode-client", 5 | "bin": "./app/websocket.js", 6 | "pkg": { 7 | "outputPath": "dist" 8 | }, 9 | "scripts": { 10 | "client": "nodemon ./app/main.js", 11 | "pkgwin": "pkg . -t node16-win-x64", 12 | "pkglinux:x86": "pkg . -t node16-linux-x64", 13 | "pkglinux:arm": "pkg . -t node16-linux-arm64" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "nodemonConfig": { 19 | "ignore": [ 20 | "*.json" 21 | ] 22 | }, 23 | "dependencies": { 24 | "axios": "^0.21.4", 25 | "node-os-utils": "^1.3.6", 26 | "node-schedule": "^2.1.0", 27 | "ws": "^8.16.0" 28 | }, 29 | "devDependencies": { 30 | "eslint": "^7.32.0", 31 | "nodemon": "^2.0.15", 32 | "pkg": "5.6" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web-h5-vue2/src/assets/icons/other.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/js-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easynode-client", 3 | "version": "1.0.0", 4 | "description": "easynode-client", 5 | "bin": "./app/websocket.js", 6 | "pkg": { 7 | "outputPath": "dist" 8 | }, 9 | "scripts": { 10 | "client": "nodemon ./app/main.js", 11 | "pkgwin": "pkg . -t node16-win-x64", 12 | "pkglinux:x86": "pkg . -t node16-linux-x64", 13 | "pkglinux:arm": "pkg . -t node16-linux-arm64" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "nodemonConfig": { 19 | "ignore": [ 20 | "*.json" 21 | ] 22 | }, 23 | "dependencies": { 24 | "axios": "^0.21.4", 25 | "node-os-utils": "^1.3.6", 26 | "node-schedule": "^2.1.0", 27 | "ws": "^8.16.0" 28 | }, 29 | "devDependencies": { 30 | "eslint": "^7.32.0", 31 | "nodemon": "^2.0.15", 32 | "pkg": "5.6" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web-h5-vue2/src/components/DeviceList.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | -------------------------------------------------------------------------------- /client/linux-client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(qt-websocket LANGUAGES CXX) 4 | 5 | 6 | set(CMAKE_CXX_STANDARD 14) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | file(GLOB fileList ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) 9 | file(GLOB jsonFileList ${CMAKE_CURRENT_SOURCE_DIR}/json/*.h) 10 | file(GLOB hppFileList ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp) 11 | set(fileList ${fileList} ${jsonFileList} ${hppFileList}) 12 | file(COPY config.json DESTINATION ${CMAKE_BINARY_DIR} ) 13 | message("库文件有:${fileList}") 14 | #add_executable(qt-websocket 15 | # main.cpp 16 | # info.h info.cpp 17 | # devinfo.h devinfo.cpp reflect.hpp 18 | # linuxinfo.cpp 19 | # reflect_json.hpp 20 | # jsoncpp.cpp 21 | # json/json.h 22 | # json/json-forwards.h 23 | # 24 | #) 25 | 26 | add_executable(qt-websocket ${fileList}) 27 | target_link_libraries(qt-websocket ssl crypto curl pthread) 28 | -------------------------------------------------------------------------------- /android-h5/app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /android-h5/app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /client/rust-client/src/main.rs: -------------------------------------------------------------------------------- 1 | #![windows_subsystem = "windows"] 2 | 3 | use clap::Parser; 4 | use std::error::Error; 5 | use std::fs::{self, OpenOptions}; 6 | use std::io::Write; 7 | use tungstenite::{Message, connect}; 8 | 9 | #[derive(Parser)] 10 | #[command( 11 | version, 12 | about = "star node", 13 | long_about = "star node 轻量级、跨平台的系统监控软件" 14 | )] 15 | struct Cli { 16 | #[arg(short, long)] 17 | config: Option, 18 | } 19 | 20 | fn main() -> Result<(), Box> { 21 | let cliparse = Cli::parse(); 22 | 23 | let mut path: String = "config.json".to_string(); 24 | match cliparse.config { 25 | Some(s) => { 26 | println!("指定的配置文件路径是:{path}"); 27 | path = s 28 | } 29 | None => { 30 | println!("未指定配置文件路径,使用默认路径 ./config.json") 31 | } 32 | } 33 | 34 | 35 | starnode::services::run(path)?; 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /android-h5/app/src/androidTest/java/io/github/cctyl/starnode/h5/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package io.github.cctyl.starnode.h5; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("io.github.cctyl.starnode.h5", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /client/linux-client/info.h: -------------------------------------------------------------------------------- 1 | #ifndef INFO_H 2 | #define INFO_H 3 | #include "devinfo.h" 4 | #include 5 | #include "easywsclient.hpp" 6 | #define GB (1024.0 * 1024.0 * 1024.0) 7 | #define MB (1024.0 * 1024.0 ) 8 | class Info { 9 | 10 | 11 | public: 12 | Info(); 13 | 14 | ~Info(); 15 | 16 | void initConfig(); 17 | 18 | DevInfo d; 19 | 20 | // 通用实现 21 | 22 | void netInterface(); 23 | 24 | void ipInfo(); 25 | 26 | void refreshDataFast(); 27 | 28 | easywsclient::WebSocket::pointer openWs(std::string &url); 29 | 30 | /* 31 | * 获取计算机名称 32 | */ 33 | const std::string localmachineName(); 34 | 35 | // 不同操作系统分别实现 36 | void memInfo(); 37 | 38 | void cpuInfo(); 39 | 40 | void driveInfo(); 41 | 42 | void netstatInfo(); 43 | 44 | void osInfo(); 45 | bool firstInit = true; 46 | easywsclient::WebSocket::pointer ws = nullptr; 47 | std::string urlStr; 48 | }; 49 | 50 | double formatDouble(double source); 51 | 52 | #endif // INFO_H 53 | -------------------------------------------------------------------------------- /android-h5/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android-h5/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdk 32 7 | 8 | defaultConfig { 9 | applicationId "io.github.cctyl.starnode.h5" 10 | minSdk 21 11 | targetSdk 32 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation 'org.nanohttpd:nanohttpd:2.3.1' 32 | implementation 'androidx.appcompat:appcompat:1.3.0' 33 | implementation 'com.google.android.material:material:1.4.0' 34 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 35 | testImplementation 'junit:junit:4.13.2' 36 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 38 | } -------------------------------------------------------------------------------- /android-h5/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10.0.8.1 6 | 192.168.1.1 7 | 192.168.0.1 8 | localhost 9 | 127.0.0.1 10 | 11 | 12 | 10.0.0.0 13 | 192.168.0.0 14 | 172.16.0.0 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /web-h5-vue2/src/assets/icons/linux.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /web-h5-vue2/src/assets/icons/windows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /web-h5-vue2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starnode-h5", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "chart.js": "^4.5.1", 12 | "core-js": "^3.8.3", 13 | "vconsole": "^3.15.1", 14 | "vue": "^2.6.14", 15 | "vue-router": "^3.6.5" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.12.16", 19 | "@babel/eslint-parser": "^7.12.16", 20 | "@vue/cli-plugin-babel": "~5.0.0", 21 | "@vue/cli-plugin-eslint": "~5.0.0", 22 | "@vue/cli-service": "~5.0.0", 23 | "eslint": "^7.32.0", 24 | "eslint-plugin-vue": "^8.0.3", 25 | "vue-template-compiler": "^2.6.14" 26 | }, 27 | "eslintConfig": { 28 | "root": true, 29 | "env": { 30 | "node": true 31 | }, 32 | "extends": [ 33 | "plugin:vue/essential", 34 | "eslint:recommended" 35 | ], 36 | "parserOptions": { 37 | "parser": "@babel/eslint-parser" 38 | }, 39 | "rules": { 40 | "no-unused-vars": "off", 41 | "vue/multi-word-component-names": "off" 42 | } 43 | }, 44 | "browserslist": [ 45 | "> 1%", 46 | "last 2 versions", 47 | "not dead" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /web-h5-vue2/src/components/MonitorHeader.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /android-h5/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /web/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v5.0.1 | 20191019 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, menu, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, main, menu, nav, section { 29 | display: block; 30 | } 31 | /* HTML5 hidden-attribute fix for newer browsers */ 32 | *[hidden] { 33 | display: none; 34 | } 35 | body { 36 | line-height: 1; 37 | } 38 | menu, ol, ul { 39 | list-style: none; 40 | } 41 | blockquote, q { 42 | quotes: none; 43 | } 44 | blockquote:before, blockquote:after, 45 | q:before, q:after { 46 | content: ''; 47 | content: none; 48 | } 49 | table { 50 | border-collapse: collapse; 51 | border-spacing: 0; 52 | } 53 | -------------------------------------------------------------------------------- /client/linux-client/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "info.h" 3 | #include 4 | #include"devinfo.h" 5 | #include 6 | #include 7 | #include "reflect_json.hpp" 8 | 9 | 10 | int main(int argc, char *argv[]) { 11 | 12 | 13 | std::shared_ptr info = std::make_shared(); 14 | 15 | 16 | // while(true){ 17 | // 18 | // std::cout<<"循环"<cpuInfo(); 22 | //// std::cout<d.cpuInfo)<memInfo(); 27 | //// std::cout<d.memInfo); 28 | // 29 | // 30 | //// driverInfo 31 | //// info->driveInfo(); 32 | //// std::cout<d.driveInfo); 33 | // 34 | //// netstatInfo 35 | //// info->netstatInfo(); 36 | //// std::cout<d.netstatInfo); 37 | // 38 | // //netInterface 39 | //// info-> netInterface(); 40 | //// std::cout<d.netInterface); 41 | // 42 | // //osInfo 43 | //// info-> osInfo(); 44 | //// std::cout<d.osInfo); 45 | //// 46 | // sleep(1); 47 | // info->firstInit = false; 48 | // } 49 | 50 | 51 | 52 | std::cout << "主线程退出" << std::endl; 53 | 54 | 55 | return 0; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /client/qt-client/info.h: -------------------------------------------------------------------------------- 1 | #ifndef INFO_H 2 | #define INFO_H 3 | #include "devinfo.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define GB (1024.0 * 1024.0 * 1024.0) 24 | #define MB (1024.0 * 1024.0 ) 25 | class Info : public QObject { 26 | 27 | Q_OBJECT 28 | public: 29 | Info(); 30 | 31 | ~Info(); 32 | 33 | void initConfig(); 34 | 35 | DevInfo d; 36 | 37 | // 通用实现 38 | 39 | void netInterface(); 40 | 41 | void ipInfo(); 42 | 43 | void onTextMessageReceived(const QString &message); 44 | 45 | void onConnected(); 46 | 47 | void onDisconnected(); 48 | 49 | /* 50 | * 获取计算机名称 51 | */ 52 | const QString localmachineName(); 53 | 54 | // 不同操作系统分别实现 55 | void memInfo(); 56 | 57 | void cpuInfo(); 58 | 59 | void driveInfo(); 60 | 61 | void netstatInfo(); 62 | 63 | void osInfo(); 64 | bool firstInit = true; 65 | private: 66 | QWebSocket *clientSocket; 67 | QString urlStr; 68 | QUrl url; 69 | }; 70 | 71 | double formatDouble(double source); 72 | 73 | #endif // INFO_H 74 | -------------------------------------------------------------------------------- /client/qt-client/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "info.h" 3 | #include 4 | #include 5 | #include"qtjson.hpp" 6 | #include"devinfo.h" 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | 16 | 17 | QCoreApplication a(argc,argv); 18 | 19 | 20 | std::shared_ptr info = std::make_shared(); 21 | 22 | 23 | // while(true){ 24 | 25 | // // //cpuInfo 的封装 26 | // // info->cpuInfo(); 27 | // // qDebug().noquote()<d.cpuInfo); 28 | 29 | 30 | // // //memInfo 31 | // //info->memInfo(); 32 | // //qDebug().noquote()<d.memInfo); 33 | 34 | 35 | // // driverInfo 36 | // //info->driveInfo(); 37 | // //qDebug().noquote()<d.driveInfo); 38 | 39 | // //netstatInfo 40 | // // info->netstatInfo(); 41 | // // qDebug().noquote()<d.netstatInfo); 42 | 43 | // //netInterface 44 | // // info-> netInterface(); 45 | // // qDebug().noquote()<d.netInterface); 46 | 47 | // //osInfo 48 | // info-> osInfo(); 49 | // qDebug().noquote()<d.osInfo); 50 | 51 | 52 | // sleep(1); 53 | // info->firstInit = false; 54 | // } 55 | 56 | 57 | 58 | int r = a.exec(); 59 | return r; 60 | 61 | 62 | // return 0; 63 | } 64 | 65 | -------------------------------------------------------------------------------- /web-h5-vue2/CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## 项目概述 6 | 7 | 这是一个基于 Vue 2 的 H5 Web 项目,名为 "starnode-h5"。项目使用 Vue CLI 5 构建工具进行开发和打包。 8 | 9 | ## 常用命令 10 | 11 | ### 开发服务器 12 | ```bash 13 | npm run serve 14 | ``` 15 | 启动开发服务器,支持热重载。 16 | 17 | ### 生产构建 18 | ```bash 19 | npm run build 20 | ``` 21 | 编译并压缩代码用于生产环境。 22 | 23 | ### 代码检查 24 | ```bash 25 | npm run lint 26 | ``` 27 | 运行 ESLint 检查并修复代码风格问题。 28 | 29 | ### 依赖安装 30 | ```bash 31 | npm install 32 | ``` 33 | 安装项目所需的所有依赖包。 34 | 35 | ## 项目结构 36 | 37 | ``` 38 | ├── public/ # 静态资源目录 39 | │ ├── index.html # HTML 模板 40 | │ └── favicon.ico # 网站图标 41 | ├── src/ # 源代码目录 42 | │ ├── assets/ # 静态资源文件 43 | │ ├── components/ # Vue 组件 44 | │ ├── App.vue # 根组件 45 | │ └── main.js # 应用入口文件 46 | ├── babel.config.js # Babel 配置 47 | ├── vue.config.js # Vue CLI 配置 48 | └── package.json # 项目配置和依赖 49 | ``` 50 | 51 | ## 技术栈 52 | 53 | - **前端框架**: Vue 2.6.14 54 | - **构建工具**: Vue CLI 5.0.0 55 | - **代码检查**: ESLint + Vue 插件 56 | - **JavaScript 编译**: Babel 57 | - **CSS 预处理**: 无(原生 CSS) 58 | 59 | ## 开发配置 60 | 61 | ### ESLint 配置 62 | - 使用 Vue Essential 规则集 63 | - 集成 ESLint 推荐规则 64 | - 使用 Babel 解析器支持现代 JavaScript 语法 65 | 66 | ### Babel 配置 67 | 项目配置了 Babel 用于转译现代 JavaScript 语法,确保浏览器兼容性。 68 | 69 | ### Vue CLI 配置 70 | 当前的 Vue CLI 配置较为简洁,启用了 `transpileDependencies` 选项来转译依赖包。 71 | 72 | ## 浏览器支持 73 | 74 | 项目支持以下浏览器: 75 | - > 1% 市场份额的浏览器 76 | - 最新 2 个版本 77 | - 不支持已停止维护的浏览器 -------------------------------------------------------------------------------- /android-h5/app/src/main/java/io/github/cctyl/starnode/h5/utils/JsResultParser.java: -------------------------------------------------------------------------------- 1 | package io.github.cctyl.starnode.h5.utils; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONException; 5 | import org.json.JSONObject; 6 | 7 | public class JsResultParser { 8 | public static Object parse(String jsonValue) { 9 | if (jsonValue == null) return null; // undefined 10 | if ("null".equals(jsonValue)) return null; // null 11 | if ("undefined".equals(jsonValue)) return null; // null 12 | try { 13 | 14 | // 尝试解析为 JSON 对象 15 | return new JSONObject(jsonValue); 16 | } catch (JSONException e1) { 17 | try { 18 | // 尝试解析为 JSON 数组 19 | return new JSONArray(jsonValue); 20 | } catch (JSONException e2) { 21 | // 尝试解析基本类型 22 | if (jsonValue.startsWith("\"") && jsonValue.endsWith("\"")) { 23 | // 字符串类型(去除引号) 24 | return jsonValue.substring(1, jsonValue.length() - 1); 25 | } else if ("true".equals(jsonValue) || "false".equals(jsonValue)) { 26 | // 布尔类型 27 | return Boolean.parseBoolean(jsonValue); 28 | } else { 29 | try { 30 | // 数字类型 31 | return Double.parseDouble(jsonValue); 32 | } catch (NumberFormatException e) { 33 | // 无法识别的格式 34 | return jsonValue; 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /server/app/utils/functions.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const path = require('path'); 3 | const config = require(path.join(process.cwd(), './config.js')); 4 | module.exports = { 5 | 6 | guid() { 7 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 8 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); 9 | return v.toString(16); 10 | }); 11 | }, 12 | async getIpInfo() { 13 | try { 14 | let {data} = await axios.get('http://ip-api.com/json?lang=zh-CN'); 15 | console.log('getIpInfo Success: ', new Date()); 16 | return data; 17 | } catch (error) { 18 | console.log('getIpInfo Error: ', new Date(), error) 19 | } 20 | }, 21 | 22 | /** 23 | * 发送告警信息 24 | * @param messgae 25 | * @returns {Promise} 26 | */ 27 | async sendAlert(messgae){ 28 | console.log("sendAlert messgae="+messgae) 29 | if (!config.warnApi){ 30 | console.log("未配置告警信息,不推送"); 31 | return; 32 | } 33 | try { 34 | const encodedMessage = encodeURIComponent(messgae); 35 | let {data} = await axios.post(`${config.warnApi}/messages?content=${encodedMessage}`,null,{ 36 | headers: { 37 | 'token': config.warnToken 38 | } 39 | }); 40 | console.log("sendAlert resp="+JSON.stringify(data)); 41 | 42 | } catch (error) { 43 | console.log(`${config.warnApi}/messages?content=${messgae}`) 44 | console.log('sendAlert Error: ', new Date(), error) 45 | } 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /android-h5/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /web-h5-vue2/src/components/SummaryCards.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | -------------------------------------------------------------------------------- /client/qt-client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(qt-websocket LANGUAGES CXX) 4 | 5 | set(CMAKE_AUTOUIC ON) 6 | set(CMAKE_AUTOMOC ON) 7 | set(CMAKE_AUTORCC ON) 8 | 9 | set(CMAKE_CXX_STANDARD 17) 10 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 | 12 | find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) 13 | find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) 14 | find_package(Qt6 REQUIRED COMPONENTS Network) 15 | find_package(Qt6 REQUIRED COMPONENTS WebSockets) 16 | 17 | #隐藏console 18 | if(WIN32) 19 | link_libraries("IPHLPAPI.lib") 20 | if(MSVC) 21 | set_target_properties(${PROJECT_NAME} PROPERTIES 22 | WIN32_EXECUTABLE YES 23 | LINK_FLAGS "/ENTRY:mainCRTStartup" 24 | ) 25 | elseif(CMAKE_COMPILER_IS_GNUCXX) 26 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mwindows") # Not tested 27 | else() 28 | message(SEND_ERROR "You are using an unsupported Windows compiler! (Not MSVC or GCC)") 29 | endif(MSVC) 30 | elseif(APPLE) 31 | set_target_properties(${PROJECT_NAME} PROPERTIES 32 | MACOSX_BUNDLE YES 33 | ) 34 | elseif(UNIX) 35 | # Nothing special required 36 | else() 37 | message(SEND_ERROR "You are on an unsupported platform! (Not Win32, Mac OS X or Unix)") 38 | endif(WIN32) 39 | 40 | add_executable(qt-websocket 41 | main.cpp 42 | info.h info.cpp 43 | devinfo.h devinfo.cpp 44 | qtjson.hpp reflect.hpp 45 | wininfo.cpp 46 | linuxinfo.cpp 47 | ) 48 | target_link_libraries(qt-websocket Qt${QT_VERSION_MAJOR}::Core Qt6::Network Qt6::WebSockets) 49 | 50 | include(GNUInstallDirs) 51 | install(TARGETS qt-websocket 52 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 53 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 54 | ) 55 | -------------------------------------------------------------------------------- /other-client.md: -------------------------------------------------------------------------------- 1 | 2 | # 其他客户端的安装配置 3 | QT客户端和C++Linux客户端已经被rust客户端统一代替,不推荐使用。这里提供之前的打包方式 4 | 5 | ## 性能展示 6 | 7 | ### qt客户端 8 | 9 | win10以及win11实测,只占用5m左右的内存,百分之0.几的cpu 10 | 11 | #### win10 4c 12g x64 12 |

13 | 14 |

15 | 16 | #### win10 8c 16g x64 17 |

18 | 19 |

20 | 21 | 22 | ### linux客户端 23 | 24 | 在ubuntu和armbian均进行过测试,约占用14m左右的内存,百分之0.几的cpu。 25 | 26 | 如果你不需要主机所在地的ip信息,甚至只占用3.9M。 27 | 28 | #### armbian 4c 4g arm64 29 |

30 | 31 |

32 | 33 | #### ubuntu18 2c 4g x64 34 |

35 | 36 |

37 | 38 | #### ubuntu22 2c 1g x64 39 |

40 | 41 |

42 | 43 | 44 | 45 | 46 | ## 安装 47 | 48 | ### qt客户端 49 | 50 | #### win平台 51 | 下载release文件,直接双击exe启动即可,启动后窗口会关闭,并在后台运行 52 | 53 | 54 | #### linux平台 55 | 下载release文件(如果我打包了的话) 56 | ``` 57 | chmod 755 ./qt-websocket 58 | ./qt-websocket 59 | ``` 60 | 61 | 62 | ### linux客户端 63 | 64 | #### 使用编译好的 65 | 直接下载解压并执行即可(如果我提供了的话) 66 | 67 | #### 本地编译 68 | 灰常简单。 69 | 70 | ##### 环境要求 71 | ``` 72 | gcc >= 5.4 73 | c++ >= 14 74 | cmake >= 3.0 75 | ``` 76 | 77 | ``` 78 | # 安装常见开发环境 79 | sudo apt-get install build-essential libgl1-mesa-dev 80 | 81 | # 安装cmake 82 | sudo apt install cmake 83 | 84 | # 安装libcurl 85 | sudo apt-get install libcurl4-openssl-dev 86 | 87 | # 安装openssl开发工具 88 | sudo apt-get install libssl-dev 89 | 90 | # 下载源码并解压 91 | git clone https://github.com/cctyl/starsnode.git 92 | cd starsnode/client/linux-client 93 | 94 | #开始编译 95 | mkdir build 96 | cd build 97 | cmake .. 98 | make 99 | 100 | #执行 101 | ./qt-websocket 102 | 103 | 104 | ``` 105 | -------------------------------------------------------------------------------- /server/app/utils/os-data.js: -------------------------------------------------------------------------------- 1 | const osu = require('node-os-utils') 2 | const os = require('os') 3 | 4 | let cpu = osu.cpu 5 | let mem = osu.mem 6 | let drive = osu.drive 7 | let netstat = osu.netstat 8 | let osuOs = osu.os 9 | let users = osu.users 10 | 11 | async function cpuInfo() { 12 | let cpuUsage = await cpu.usage(200) 13 | let cpuCount = cpu.count() 14 | let cpuModel = cpu.model() 15 | return { 16 | cpuUsage, 17 | cpuCount, 18 | cpuModel 19 | } 20 | } 21 | 22 | async function memInfo() { 23 | let memInfo = await mem.info() 24 | return { 25 | ...memInfo 26 | } 27 | } 28 | 29 | async function driveInfo() { 30 | let driveInfo = {} 31 | try { 32 | driveInfo = await drive.info() 33 | } catch { 34 | // console.log(driveInfo) 35 | } 36 | return driveInfo 37 | } 38 | 39 | async function netstatInfo() { 40 | let netstatInfo = await netstat.inOut() 41 | return netstatInfo === 'not supported' ? {} : netstatInfo 42 | } 43 | 44 | async function netInterface() { 45 | let result = await os.networkInterfaces(); 46 | return result; 47 | } 48 | 49 | async function osInfo() { 50 | let type = os.type() 51 | let platform = os.platform() 52 | let release = os.release() 53 | let uptime = osuOs.uptime() 54 | let ip = osuOs.ip() 55 | let hostname = osuOs.hostname() 56 | let arch = osuOs.arch() 57 | return { 58 | type, 59 | platform, 60 | release, 61 | ip, 62 | hostname, 63 | arch, 64 | uptime 65 | } 66 | } 67 | 68 | async function openedCount() { 69 | let openedCount = await users.openedCount() 70 | return openedCount === 'not supported' ? 0 : openedCount 71 | } 72 | 73 | module.exports = async () => { 74 | let data = {} 75 | try { 76 | data = { 77 | cpuInfo: await cpuInfo(), 78 | memInfo: await memInfo(), 79 | driveInfo: await driveInfo(), 80 | netstatInfo: await netstatInfo(), 81 | netInterface: await netInterface(), 82 | osInfo: await osInfo(), 83 | openedCount: await openedCount() 84 | } 85 | return data 86 | } catch(err){ 87 | return err.toString() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /client/js-client/app/utils/os-data.js: -------------------------------------------------------------------------------- 1 | const osu = require('node-os-utils') 2 | const os = require('os') 3 | 4 | let cpu = osu.cpu 5 | let mem = osu.mem 6 | let drive = osu.drive 7 | let netstat = osu.netstat 8 | let osuOs = osu.os 9 | let users = osu.users 10 | 11 | async function cpuInfo() { 12 | let cpuUsage = await cpu.usage(200) 13 | let cpuCount = cpu.count() 14 | let cpuModel = cpu.model() 15 | return { 16 | cpuUsage, 17 | cpuCount, 18 | cpuModel 19 | } 20 | } 21 | 22 | async function memInfo() { 23 | let memInfo = await mem.info() 24 | return { 25 | ...memInfo 26 | } 27 | } 28 | 29 | async function driveInfo() { 30 | let driveInfo = {} 31 | try { 32 | driveInfo = await drive.info() 33 | } catch { 34 | // console.log(driveInfo) 35 | } 36 | return driveInfo 37 | } 38 | 39 | async function netstatInfo() { 40 | let netstatInfo = await netstat.inOut() 41 | return netstatInfo === 'not supported' ? {} : netstatInfo 42 | } 43 | 44 | async function netInterface() { 45 | let result = await os.networkInterfaces(); 46 | return result; 47 | } 48 | 49 | async function osInfo() { 50 | let type = os.type() 51 | let platform = os.platform() 52 | let release = os.release() 53 | let uptime = osuOs.uptime() 54 | let ip = osuOs.ip() 55 | let hostname = osuOs.hostname() 56 | let arch = osuOs.arch() 57 | return { 58 | type, 59 | platform, 60 | release, 61 | ip, 62 | hostname, 63 | arch, 64 | uptime 65 | } 66 | } 67 | 68 | async function openedCount() { 69 | let openedCount = await users.openedCount() 70 | return openedCount === 'not supported' ? 0 : openedCount 71 | } 72 | 73 | module.exports = async () => { 74 | let data = {} 75 | try { 76 | data = { 77 | cpuInfo: await cpuInfo(), 78 | memInfo: await memInfo(), 79 | driveInfo: await driveInfo(), 80 | netstatInfo: await netstatInfo(), 81 | netInterface: await netInterface(), 82 | osInfo: await osInfo(), 83 | openedCount: await openedCount() 84 | } 85 | return data 86 | } catch(err){ 87 | return err.toString() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /web-h5-vue2/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /client/rust-client/src/modles.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Value; 3 | use std::{collections::HashMap}; 4 | 5 | #[derive(Debug, Serialize, Deserialize)] 6 | pub struct Config { 7 | pub server: String, 8 | pub port: u16, 9 | pub token: String, 10 | } 11 | 12 | #[derive(Debug, Serialize, Deserialize)] 13 | #[serde(rename_all = "camelCase")] 14 | pub struct CpuInfo { 15 | pub cpu_count: u16, 16 | pub cpu_model: String, 17 | pub cpu_usage: f32, 18 | } 19 | #[derive(Debug, Serialize, Deserialize)] 20 | #[serde(rename_all = "camelCase")] 21 | pub struct DriveInfo { 22 | pub free_gb: f32, 23 | pub free_percentage: f32, 24 | pub total_gb: f32, 25 | pub used_gb: f32, 26 | pub used_percentage: f32, 27 | } 28 | 29 | #[derive(Debug, Serialize, Deserialize)] 30 | #[serde(rename_all = "camelCase")] 31 | pub struct MemInfo { 32 | pub free_mem_mb: f32, 33 | pub free_mem_percentage: f32, 34 | pub total_mem_mb: f32, 35 | pub used_mem_mb: f32, 36 | pub used_mem_percentage: f32, 37 | } 38 | #[derive(Debug, Serialize, Deserialize)] 39 | #[serde(rename_all = "camelCase")] 40 | pub struct OsInfo { 41 | pub arch: String, 42 | pub hostname: Option, 43 | pub platform: String, 44 | pub release: Option, 45 | pub r#type: String, // 使用 r# 前缀因为 type 是保留字 46 | pub uptime: u64, 47 | } 48 | #[derive(Debug, Serialize, Deserialize)] 49 | #[serde(rename_all = "camelCase")] 50 | pub struct NetstatInfo { 51 | pub input_mb: f32, 52 | pub output_mb: f32, 53 | } 54 | 55 | #[derive(Debug, Default,Serialize, Deserialize)] 56 | #[serde(rename_all = "camelCase")] 57 | pub struct NetworkInterfaceInfo { 58 | pub address: String, 59 | pub boardcast: String, 60 | pub family:String, 61 | pub mac: Option, 62 | pub netmask: String, 63 | } 64 | #[derive(Debug,Default,Serialize, Deserialize)] 65 | #[serde(rename_all = "camelCase")] 66 | pub struct DevInfo<> { 67 | pub cpu_info: Option, 68 | pub drive_info: Option, 69 | pub mem_info: Option< MemInfo>, 70 | pub net_interface: Option>>, 71 | pub netstat_info: HashMap, 72 | pub opened_count: u16, 73 | pub os_info: Option, 74 | pub ip_info: Option, 75 | } 76 | -------------------------------------------------------------------------------- /web-h5-vue2/src/assets/icons/server.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/qt-client/devinfo.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVINFO_H 2 | #define DEVINFO_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include"qtjson.hpp" 8 | 9 | class CpuInfo{ 10 | 11 | public: 12 | 13 | double cpuUsage = 0; 14 | unsigned short cpuCount = 0; 15 | QString cpuModel; 16 | 17 | REFLECT(cpuUsage,cpuCount,cpuModel) 18 | }; 19 | 20 | class MemInfo{ 21 | 22 | 23 | public: 24 | double totalMemMb; 25 | double usedMemMb; 26 | double freeMemMb; 27 | double usedMemPercentage; 28 | double freeMemPercentage; 29 | 30 | REFLECT(totalMemMb,usedMemMb,freeMemMb,usedMemPercentage,freeMemPercentage) 31 | }; 32 | class DriverInfo{ 33 | 34 | public: 35 | double totalGb; 36 | double usedGb; 37 | double freeGb ; 38 | double usedPercentage; 39 | double freePercentage; 40 | REFLECT(totalGb,usedGb,freeGb,usedPercentage,freePercentage) 41 | }; 42 | 43 | /** 44 | * 网速相关信息 45 | * @brief The NetstatInfo class 46 | */ 47 | class NetstatInfo{ 48 | public: 49 | double inputMb; 50 | double outputMb; 51 | REFLECT(inputMb,outputMb) 52 | }; 53 | 54 | /** 55 | * 网卡信息 56 | * 57 | * @brief The NetInterfaceInfo class 58 | */ 59 | class NetInterfaceInfo{ 60 | public: 61 | 62 | QString address; 63 | QString netmask; 64 | QString family; 65 | QString mac; 66 | QString boardcast; 67 | bool internal; 68 | REFLECT(address,netmask,family,mac,internal,boardcast) 69 | 70 | 71 | 72 | }; 73 | 74 | /** 75 | * 操作系统相关信息 76 | * @brief The OsInfo class 77 | */ 78 | class OsInfo{ 79 | public: 80 | 81 | QString type = "Windows";//系统类型,Windows 或 Linux 82 | QString platform = "win"; 83 | QString release;//系统版本 84 | QString hostname;//机器名 85 | QString arch;//cpu架构 86 | double uptime;//开机时间 87 | 88 | 89 | REFLECT(type,platform,release,hostname,arch,uptime) 90 | }; 91 | 92 | 93 | 94 | class DevInfo 95 | { 96 | public: 97 | DevInfo(); 98 | CpuInfo cpuInfo; 99 | MemInfo memInfo; 100 | DriverInfo driveInfo; 101 | std::map netstatInfo; 102 | std::map> netInterface; 103 | OsInfo osInfo; 104 | short openedCount = 0; 105 | QJsonObject ipInfo; 106 | 107 | REFLECT(cpuInfo,memInfo,driveInfo,netstatInfo,netInterface,osInfo,openedCount,ipInfo) 108 | }; 109 | 110 | #endif // DEVINFO_H 111 | -------------------------------------------------------------------------------- /client/linux-client/devinfo.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVINFO_H 2 | #define DEVINFO_H 3 | 4 | #include 5 | #include 6 | #include "reflect.hpp" 7 | #include "reflect_json.hpp" 8 | class CpuInfo{ 9 | 10 | public: 11 | 12 | double cpuUsage = 0; 13 | unsigned short cpuCount = 0; 14 | std::string cpuModel; 15 | 16 | REFLECT(cpuUsage,cpuCount,cpuModel) 17 | }; 18 | 19 | class MemInfo{ 20 | 21 | 22 | public: 23 | double totalMemMb; 24 | double usedMemMb; 25 | double freeMemMb; 26 | double usedMemPercentage; 27 | double freeMemPercentage; 28 | 29 | REFLECT(totalMemMb,usedMemMb,freeMemMb,usedMemPercentage,freeMemPercentage) 30 | }; 31 | class DriverInfo{ 32 | 33 | public: 34 | double totalGb; 35 | double usedGb; 36 | double freeGb ; 37 | double usedPercentage; 38 | double freePercentage; 39 | REFLECT(totalGb,usedGb,freeGb,usedPercentage,freePercentage) 40 | }; 41 | 42 | /** 43 | * 网速相关信息 44 | * @brief The NetstatInfo class 45 | */ 46 | class NetstatInfo{ 47 | public: 48 | double inputMb; 49 | double outputMb; 50 | REFLECT(inputMb,outputMb) 51 | }; 52 | 53 | /** 54 | * 网卡信息 55 | * 56 | * @brief The NetInterfaceInfo class 57 | */ 58 | class NetInterfaceInfo{ 59 | public: 60 | 61 | std::string address; 62 | std::string netmask; 63 | std::string family; 64 | std::string mac; 65 | std::string boardcast; 66 | bool internal; 67 | REFLECT(address,netmask,family,mac,internal,boardcast) 68 | 69 | 70 | 71 | }; 72 | 73 | /** 74 | * 操作系统相关信息 75 | * @brief The OsInfo class 76 | */ 77 | class OsInfo{ 78 | public: 79 | 80 | std::string type = "Windows";//系统类型,Windows 或 Linux 81 | std::string platform = "win"; 82 | std::string release;//系统版本 83 | std::string hostname;//机器名 84 | std::string arch;//cpu架构 85 | double uptime;//开机时间 86 | 87 | 88 | REFLECT(type,platform,release,hostname,arch,uptime) 89 | }; 90 | 91 | 92 | 93 | class DevInfo 94 | { 95 | public: 96 | DevInfo(); 97 | CpuInfo cpuInfo; 98 | MemInfo memInfo; 99 | DriverInfo driveInfo; 100 | std::map netstatInfo; 101 | std::map> netInterface; 102 | OsInfo osInfo; 103 | short openedCount = 0; 104 | Json::Value ipInfo; 105 | 106 | REFLECT(cpuInfo,memInfo,driveInfo,netstatInfo,netInterface,osInfo,openedCount,ipInfo) 107 | }; 108 | 109 | #endif // DEVINFO_H 110 | -------------------------------------------------------------------------------- /web-h5-vue2/src/components/HardwareCharts.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | -------------------------------------------------------------------------------- /server/app/simpleHttpServer.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const url = require('url'); 3 | 4 | 5 | const HTTP_PORT = 8080; 6 | const HTTP_TOKEN = 'abcdef'; 7 | const HTTP_PATH = `/api/data`; 8 | 9 | function simpleHttpServer( handler) { 10 | let httpServer = http.createServer((req, res) => { 11 | // 只处理 POST 请求 12 | if (req.method === 'POST') { 13 | // 解析 URL 和查询参数 14 | const parsedUrl = url.parse(req.url, true); 15 | const pathname = parsedUrl.pathname; 16 | const query = parsedUrl.query; 17 | const endpointName = query.endpointName; // 从查询参数中获取 endpointName 18 | 19 | console.log(`HTTP:${endpointName}`) 20 | // 添加路径判断,例如只处理 /api/data 路径 21 | if (pathname === HTTP_PATH) { 22 | // 验证请求头 token 23 | const authHeader = req.headers['token']; 24 | if (authHeader === HTTP_TOKEN) { 25 | // 检查是否提供了 endpointName 参数 26 | if (!endpointName) { 27 | res.writeHead(200); 28 | res.end('success'); 29 | return; 30 | } 31 | 32 | let body = ''; 33 | 34 | // 收集请求体数据 35 | req.on('data', chunk => { 36 | body += chunk.toString(); 37 | }); 38 | 39 | // 数据接收完成后处理 40 | req.on('end', () => { 41 | try { 42 | 43 | handler(endpointName, body); 44 | 45 | // 发送成功响应(可选) 46 | res.writeHead(200, {'Content-Type': 'application/json'}); 47 | res.end(JSON.stringify({status: 'success'})); 48 | } catch (error) { 49 | console.error('JSON parsing error:', error); 50 | // 不返回任何内容 51 | res.writeHead(200); 52 | res.end(); 53 | } 54 | }); 55 | } else { 56 | // 不满足 token 要求,不返回任何内容 57 | res.writeHead(200); // No Content 58 | res.end(); 59 | } 60 | } else { 61 | // 路径不匹配,返回 404 62 | res.writeHead(200); 63 | res.end(); 64 | } 65 | } else { 66 | // 非 POST 请求,返回 405 Method Not Allowed 67 | res.writeHead(200); 68 | res.end(); 69 | } 70 | }); 71 | 72 | // 启动 HTTP 服务器监听 8080 端口 73 | httpServer.listen(HTTP_PORT, () => { 74 | console.log('HTTP server listening on port 8080'); 75 | }); 76 | 77 | } 78 | 79 | 80 | function start(){ 81 | 82 | } 83 | module.exports = { 84 | simpleHttpServer 85 | } -------------------------------------------------------------------------------- /client/js-client/app/client.js: -------------------------------------------------------------------------------- 1 | const wsModule = require("ws"); 2 | const path = require('path'); 3 | const config = require(path.join(process.cwd(), './config.js')); 4 | const getOsData = require('./utils/os-data'); 5 | const func = require('./utils/functions'); 6 | let receiveMsg = false; 7 | 8 | //获得设备名 更新 9 | const endpointName = config.endpointName ? config.endpointName : func.guid(); 10 | 11 | let ws; 12 | let lock=false; 13 | /** 14 | * 创建websocket 客户端 15 | */ 16 | function createWebSocketClient(){ 17 | if (ws){ 18 | ws.close(); 19 | } 20 | ws = new wsModule.WebSocket(`ws://${config.serverHost}:${config.port}?token=${config.token}&type=dev&endpointName=${endpointName}`); 21 | ws.on('error', args => { 22 | 23 | console.log("error 解锁"); 24 | 25 | //解除锁定 26 | lock = false; 27 | 28 | }); 29 | ws.on('open', function open() { 30 | receiveMsg = true; 31 | //解除锁定 32 | lock = false; 33 | console.log("连接成功 解锁") 34 | }); 35 | ws.on('message', function message(data) { 36 | receiveMsg = true; 37 | console.log('received: %s', data); 38 | }); 39 | ws.on("close",args => { 40 | console.log("close 解锁"); 41 | //解除锁定 42 | lock = false; 43 | }) 44 | 45 | } 46 | createWebSocketClient(); 47 | 48 | 49 | //2秒后没有收到服务端消息,就认为掉线,重新连接 50 | let timeOut = setInterval(()=>{ 51 | console.log("开始一轮检查"); 52 | if (receiveMsg===true){ 53 | //改为false,等待下一轮接收服务端消息后重置为true 54 | receiveMsg = false; 55 | }else { 56 | //如果已经上锁(没有多线程问题) 57 | if (lock===true){ 58 | console.log("已锁定..."); 59 | return; 60 | } 61 | //上锁 62 | lock = true; 63 | console.log("上锁"); 64 | 65 | //如果是false,说明2秒没收到服务端响应了,该重连了 66 | console.log("断开重连"); 67 | try { 68 | createWebSocketClient(); 69 | } catch (e) { 70 | console.error(e); 71 | } 72 | } 73 | },3000); 74 | 75 | 76 | 77 | 78 | let ipInfo = {}; 79 | /** 80 | * 更新ip 81 | */ 82 | setInterval(async args => { 83 | try { 84 | func.getIpInfo().then(value => { 85 | ipInfo = value; 86 | }); 87 | } catch (e) { 88 | console.error(e); 89 | } 90 | }, 30 * 60 * 1000); 91 | 92 | /** 93 | * 1秒上传一次数据 94 | */ 95 | setInterval(async args => { 96 | try { 97 | if (lock) { 98 | //lock=true时,说明还没有解锁,还在尝试连接,不应该发送数据 99 | return; 100 | } 101 | let osData = await getOsData(); 102 | Object.assign(osData, {ipInfo}); 103 | ws.send(JSON.stringify(osData)); 104 | } catch (e) { 105 | console.error(e); 106 | } 107 | }, 1000); 108 | 109 | 110 | //初始化时更新数据 111 | func.getIpInfo().then(value => { 112 | ipInfo = value; 113 | }); 114 | -------------------------------------------------------------------------------- /android-h5/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /client/linux-client/easywsclient.hpp: -------------------------------------------------------------------------------- 1 | #ifndef EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD 2 | #define EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD 3 | 4 | // This code comes from: 5 | // https://github.com/dhbaird/easywsclient 6 | // 7 | // To get the latest version: 8 | // wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp 9 | // wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp 10 | 11 | #include 12 | #include 13 | 14 | namespace easywsclient { 15 | 16 | struct Callback_Imp { virtual void operator()(const std::string& message) = 0; }; 17 | struct BytesCallback_Imp { virtual void operator()(const std::vector& message) = 0; }; 18 | 19 | class WebSocket { 20 | public: 21 | typedef WebSocket * pointer; 22 | typedef enum readyStateValues { CLOSING, CLOSED, CONNECTING, OPEN } readyStateValues; 23 | 24 | // Factories: 25 | static pointer create_dummy(); 26 | static pointer from_url(const std::string& url, const std::string& origin = std::string()); 27 | static pointer from_url_no_mask(const std::string& url, const std::string& origin = std::string()); 28 | 29 | // Interfaces: 30 | virtual ~WebSocket() { } 31 | virtual void poll(int timeout = 0) = 0; // timeout in milliseconds 32 | virtual void send(const std::string& message) = 0; 33 | virtual void sendBinary(const std::string& message) = 0; 34 | virtual void sendBinary(const std::vector& message) = 0; 35 | virtual void sendPing() = 0; 36 | virtual void close() = 0; 37 | virtual readyStateValues getReadyState() const = 0; 38 | 39 | template 40 | void dispatch(Callable callable) 41 | // For callbacks that accept a string argument. 42 | { // N.B. this is compatible with both C++11 lambdas, functors and C function pointers 43 | struct _Callback : public Callback_Imp { 44 | Callable& callable; 45 | _Callback(Callable& callable) : callable(callable) { } 46 | void operator()(const std::string& message) { callable(message); } 47 | }; 48 | _Callback callback(callable); 49 | _dispatch(callback); 50 | } 51 | 52 | template 53 | void dispatchBinary(Callable callable) 54 | // For callbacks that accept a std::vector argument. 55 | { // N.B. this is compatible with both C++11 lambdas, functors and C function pointers 56 | struct _Callback : public BytesCallback_Imp { 57 | Callable& callable; 58 | _Callback(Callable& callable) : callable(callable) { } 59 | void operator()(const std::vector& message) { callable(message); } 60 | }; 61 | _Callback callback(callable); 62 | _dispatchBinary(callback); 63 | } 64 | 65 | protected: 66 | virtual void _dispatch(Callback_Imp& callable) = 0; 67 | virtual void _dispatchBinary(BytesCallback_Imp& callable) = 0; 68 | }; 69 | 70 | } // namespace easywsclient 71 | 72 | #endif /* EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD */ 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###################### 2 | # Project Specific 3 | ###################### 4 | /target/classes/static/** 5 | /src/test/javascript/coverage/ 6 | 7 | ###################### 8 | # Node 9 | ###################### 10 | /node/ 11 | node_tmp/ 12 | node_modules/ 13 | npm-debug.log.* 14 | /.awcache/* 15 | /.cache-loader/* 16 | 17 | ###################### 18 | # SASS 19 | ###################### 20 | .sass-cache/ 21 | 22 | ###################### 23 | # Eclipse 24 | ###################### 25 | *.pydevproject 26 | .project 27 | .metadata 28 | tmp/ 29 | tmp/**/* 30 | *.tmp 31 | *.bak 32 | *.swp 33 | *~.nib 34 | local.properties 35 | .classpath 36 | .settings/ 37 | .loadpath 38 | .factorypath 39 | /src/main/resources/rebel.xml 40 | # External tool builders 41 | .externalToolBuilders/** 42 | 43 | # Locally stored "Eclipse launch configurations" 44 | *.launch 45 | 46 | # CDT-specific 47 | .cproject 48 | 49 | release 50 | *.apk 51 | 52 | 53 | # PDT-specific 54 | .buildpath 55 | 56 | # STS-specific 57 | /.sts4-cache/* 58 | 59 | ###################### 60 | # IntelliJ 61 | ###################### 62 | .idea/ 63 | *.iml 64 | *.iws 65 | *.ipr 66 | *.ids 67 | *.orig 68 | classes/ 69 | out/ 70 | 71 | ###################### 72 | # Visual Studio Code 73 | ###################### 74 | .vscode/* 75 | !.vscode/settings.json 76 | !.vscode/tasks.json 77 | !.vscode/launch.json 78 | !.vscode/extensions.json 79 | *.code-workspace 80 | package-lock.json 81 | ###################### 82 | # Maven 83 | ###################### 84 | /log/ 85 | /target/ 86 | 87 | ###################### 88 | # Gradle 89 | ###################### 90 | .gradle/ 91 | /build/ 92 | 93 | ###################### 94 | # Package Files 95 | ###################### 96 | *.jar 97 | *.war 98 | *.ear 99 | *.db 100 | 101 | ###################### 102 | # Windows 103 | ###################### 104 | # Windows image file caches 105 | Thumbs.db 106 | 107 | # Folder config file 108 | Desktop.ini 109 | 110 | ###################### 111 | # Mac OSX 112 | ###################### 113 | .DS_Store 114 | .svn 115 | 116 | # Thumbnails 117 | ._* 118 | 119 | # Files that might appear on external disk 120 | .Spotlight-V100 121 | .Trashes 122 | 123 | ###################### 124 | # Directories 125 | ###################### 126 | /bin/ 127 | /deploy/ 128 | 129 | ###################### 130 | # Logs 131 | ###################### 132 | *.log* 133 | 134 | ###################### 135 | # Others 136 | ###################### 137 | *.class 138 | *.*~ 139 | *~ 140 | .merge_file* 141 | 142 | ###################### 143 | # Gradle Wrapper 144 | ###################### 145 | !gradle/wrapper/gradle-wrapper.jar 146 | 147 | ###################### 148 | # Maven Wrapper 149 | ###################### 150 | !.mvn/wrapper/maven-wrapper.jar 151 | 152 | ###################### 153 | # ESLint 154 | ###################### 155 | .eslintcache 156 | /src/main/resources/logback-spring.xml 157 | 158 | 159 | build 160 | 161 | Android/app/release/** 162 | Android/app/debug/** 163 | android-h5/app/src/main/assets 164 | android-h5/app/debug/** 165 | android-h5/app/release/** -------------------------------------------------------------------------------- /android-h5/app/src/main/java/io/github/cctyl/starnode/h5/AssetsWebServer.java: -------------------------------------------------------------------------------- 1 | package io.github.cctyl.starnode.h5; 2 | 3 | // AssetsWebServer.java 4 | import android.content.Context; 5 | import fi.iki.elonen.NanoHTTPD; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Map; 9 | 10 | public class AssetsWebServer extends NanoHTTPD { 11 | private Context context; 12 | 13 | // 构造函数,传入上下文和端口 14 | public AssetsWebServer(Context context, int port) { 15 | super(port); 16 | this.context = context.getApplicationContext(); 17 | } 18 | 19 | @Override 20 | public Response serve(IHTTPSession session) { 21 | String uri = session.getUri(); 22 | Map headers = session.getHeaders(); 23 | 24 | // 1. 默认请求映射到 index.html 25 | if ("/".equals(uri)) { 26 | uri = "/index.html"; 27 | } 28 | 29 | // 2. 构建 assets 中的文件路径 30 | // 假设你把 Vue 打包的整个 dist 文件夹放入了 `assets/www/` 下 31 | String assetPath = "www" + uri; // 例如:请求 `/img/logo.png` -> `assets/www/img/logo.png` 32 | 33 | try { 34 | // 3. 打开 assets 中的文件流 35 | InputStream inputStream = context.getAssets().open(assetPath); 36 | // 4. 根据文件后缀确定 MIME 类型 37 | String mimeType = getMimeType(uri); 38 | 39 | // 5. 创建并返回响应 40 | // 使用 `newFixedLengthResponse` 并传入流和长度 41 | Response response = newFixedLengthResponse(Response.Status.OK, mimeType, inputStream, inputStream.available()); 42 | // 解决可能的 CORS 问题(如果 Vue 代码需要) 43 | response.addHeader("Access-Control-Allow-Origin", "*"); 44 | return response; 45 | } catch (IOException e) { 46 | e.printStackTrace(); 47 | // 文件未找到,返回 404 48 | if (uri.endsWith(".html") || uri.endsWith("/")) { 49 | // 对于单页应用 (Vue Router的history模式),所有未知路径都应返回 index.html 50 | try { 51 | InputStream fallbackStream = context.getAssets().open("www/index.html"); 52 | return newFixedLengthResponse(Response.Status.OK, "text/html", fallbackStream, fallbackStream.available()); 53 | } catch (IOException ex) { 54 | return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "404 Not Found - " + uri); 55 | } 56 | } 57 | return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "404 Not Found - " + uri); 58 | } 59 | } 60 | 61 | // 简单的 MIME 类型映射 62 | private String getMimeType(String uri) { 63 | if (uri.endsWith(".html")) return "text/html"; 64 | if (uri.endsWith(".js")) return "application/javascript"; 65 | if (uri.endsWith(".css")) return "text/css"; 66 | if (uri.endsWith(".png")) return "image/png"; 67 | if (uri.endsWith(".jpg") || uri.endsWith(".jpeg")) return "image/jpeg"; 68 | if (uri.endsWith(".gif")) return "image/gif"; 69 | if (uri.endsWith(".svg")) return "image/svg+xml"; 70 | if (uri.endsWith(".json")) return "application/json"; 71 | if (uri.endsWith(".ico")) return "image/x-icon"; 72 | // 默认类型 73 | return "application/octet-stream"; 74 | } 75 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # starsnode 2 | 3 | ## 简介 4 | 5 | [starsnode]是一个系统状态监控软件。 6 | 7 | 展示的信息包括:cpu、内存、硬盘、网速、网卡等信息。 8 | 9 | ### 优点 10 | 11 | #### 高性能 12 | 13 | rust编写的原生客户端,只占用%0.几的cpu,最低3.9M的内存(不开启ip信息的情况,开启ipinfo 需要使用openssl会带来一定的内存开销) 14 | (c++原生客户端和qt客户端不再推荐) 15 | 16 | #### 轻量级 17 | 18 | 软件小,依赖少,编译十分简单,同时提供编译好的客户端 19 | 20 | #### 跨平台 21 | 22 | nodejs客户端几乎适配所有主流系统,win、linux、macos、android。 23 | 如要求性能,也可使用rust客户端,同样支持跨平台 24 | 25 | 本库的js服务端参考了该项目:https://github.com/chaos-zhu/easynode 感谢原作者。 26 | 27 | ## 总览效果图 28 | 29 | ### pc端 30 |

31 | 32 |

33 | 34 |

35 | 36 |

37 | 38 | ### 移动端 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | ## 平台构成 49 | 50 | 由1个服务端+n个客户端+web展示页面组成。三个部分每部分都可以单独部署,通过websocket进行数据传输。 51 | 52 | 各客户端首先将信息通过ws上报给服务端,服务端进行汇总。用户可以通过web页面查看到汇总后数据。 53 | 54 | ## 客户端选择 55 | 56 | ### 如果你不考虑性能问题 57 | 58 | 选择js客户端,兼容性好,bug少,稳定性强。 59 | 60 | ### 如果对性能敏感 61 | 62 | 统一推荐rust客户端。有良好的跨平台支持 63 | 64 | ~~win平台,使用qt客户端。~~ 65 | 66 | ~~linux平台,如果内核版本比较新,比如ubuntu22以上,建议使用qt客户端。~~ 67 | 68 | ~~如果是历史平台,比如centos系列,建议使用linux客户端,缺点是稳定性可能稍差。~~ 69 | 70 | ~~macos,理论上也可以使用linux客户端,但是编译是个问题~~ 71 | 72 | 73 | ## 性能展示 74 | 75 | ### rust客户端 76 | 77 | 78 | 79 | 80 | #### win10 8c 16g x64 81 | win10实测,软件大小只有3.67M, 只占用6m左右的内存,百分之0.3的cpu。 82 |

83 | 84 |

85 | 86 | 87 | #### debian 2c 4g x64 88 | 开启ip信息后,占11M左右,0.3%cpu 89 | 90 |

91 | 92 |

93 | 94 | #### armbian 4c 4g x64 95 | 96 | 开启ip信息后,占7M左右,0.3%cpu 97 | 98 |

99 | 100 |

101 | ## 部署方式 102 | 103 | 104 | 105 | 106 | ### 配置 107 | 服务端的配置文件在 `server/config.js`,只需要设置port和token即可。 108 | 109 | 110 | 111 | 112 | nodejs客户端的配置文件,在其目录下的config.js 中,需要设置服务端地址(serverHost),端口(port),token。 113 | endpointName可省略,这是用于给客户端命名的。 114 | 115 | 116 | 117 | rust配置文件,在各自目录下的 config.json 文件中,需要设置服务端地址(server),端口(port),token。 118 | 使用时,只需要把这个config.json,放在可执行文件的旁边即可。 119 | 120 | 121 | 122 | web端的配置写到了 simple-list.html中的625行: 123 | ``` 124 | const ws = new WebSocket("ws://服务端地址:服务器端口?token=你的token&type=view&endpointName=web"); 125 | ``` 126 | 127 | ### web端 128 | 129 | 你可以选择部署到nginx中,也可以直接双击simple-list.html 在本地打开,如果你能直接访问服务端的话 130 | 131 | ### 服务端 132 | 133 | 134 | ``` 135 | # 需要安装 nodejs16 及以上 136 | # 下载release文件中的server并解压 137 | cd server 138 | npm install 139 | #直接启动 140 | node app/server.js 141 | 142 | #或使用pm2启动,需要提前安装pm2,win平台似乎是不支持pm2的 143 | #pm2 start app/server.js --name starsnode 144 | 145 | ``` 146 | 147 | ### js客户端 148 | 149 | ``` 150 | # 需要安装 nodejs16 及以上 151 | # 下载release文件中js-client并解压 152 | cd js-client 153 | npm install 154 | #直接启动 155 | node app/client.js 156 | 157 | #或使用pm2启动,需要提前安装pm2,win平台似乎是不支持pm2的 158 | #pm2 start app/client.js --name starsnode 159 | 160 | ``` 161 | 162 | ### Rust客户端 163 | 164 | #### win环境 165 | 我已经提供了打包后的exe文件,直接到release下载即可,双击启动即可 166 | 167 | #### 其他平台 168 | - 安装rust 169 | 170 | - 执行 `cargo build --release` ,然后执行生成的可执行文件 171 | 注意,国内依赖下载可能比较慢,可以配置一下国内镜像来加速下载 172 | 173 | 174 | 175 | 176 | 177 | 178 | ### qt客户端 179 | 不再推荐使用,如有需要,参考 other-client.md 180 | ### linux客户端 181 | 不再推荐使用,如有需要,参考 other-client.md 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /web-h5-vue2/原型图/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 设备监控平台 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |

📊 设备监控平台

15 |
最后更新: --
16 |
17 | 18 | 19 |
20 |

📈 数据汇总

21 | 22 | 23 |
24 |
25 |
🖥️
26 |
27 |
在线设备总数
28 |
0
29 |
30 |
31 | 32 |
33 |
🪟
34 |
35 |
Windows 设备
36 |
0
37 |
38 |
39 | 40 |
41 |
🐧
42 |
43 |
Linux 设备
44 |
0
45 |
46 |
47 | 48 |
49 |
💻
50 |
51 |
其他系统
52 |
0
53 |
54 |
55 |
56 | 57 | 58 |
59 |
60 |

CPU 核心数汇总

61 |
0 核心
62 |
平均使用率: 0%
63 |
64 | 65 |
66 |

内存汇总

67 |
0 GB
68 | 69 |
70 | 71 |
72 |

磁盘汇总

73 |
0 GB
74 | 75 |
76 |
77 |
78 | 79 | 80 |
81 |

🖥️ 设备详情

82 |
83 |
84 |
85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /android-h5/app/src/main/java/io/github/cctyl/starnode/h5/utils/JsExecUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.cctyl.starnode.h5.utils; 2 | 3 | import android.util.Log; 4 | import android.webkit.ValueCallback; 5 | import android.webkit.WebView; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | public class JsExecUtil { 12 | 13 | private WebView webView; 14 | 15 | public JsExecUtil(WebView webView) { 16 | this.webView = webView; 17 | } 18 | 19 | public void exec(String methodName, ValueCallback resultCallback) { 20 | execCommon(methodName, Collections.emptyList(), resultCallback); 21 | } 22 | 23 | public void exec(String methodName, Object args, ValueCallback resultCallback) { 24 | execCommon(methodName, Arrays.asList(args), resultCallback); 25 | } 26 | 27 | public void exec(String methodName, Object arg1, Object arg2, ValueCallback resultCallback) { 28 | execCommon(methodName, Arrays.asList(arg1, arg2), resultCallback); 29 | } 30 | 31 | public void exec(String methodName, Object arg1, Object arg2, Object arg3, ValueCallback resultCallback) { 32 | execCommon(methodName, Arrays.asList(arg1, arg2, arg3), resultCallback); 33 | } 34 | 35 | public void exec(String methodName, Object arg1, Object arg2, Object arg3, Object arg4, ValueCallback resultCallback) { 36 | execCommon(methodName, Arrays.asList(arg1, arg2, arg3, arg4), resultCallback); 37 | } 38 | 39 | public void execCommon(String methodName, List args, ValueCallback resultCallback) { 40 | StringBuilder sb = new StringBuilder(); 41 | sb.append("vue.") 42 | .append(methodName) 43 | .append("("); 44 | for (int i = 0; i < args.size(); i++) { 45 | 46 | Object value = args.get(i); 47 | 48 | //字符串类型的处理 49 | if ( 50 | value != null && 51 | String.class.equals(value.getClass()) 52 | 53 | ) { 54 | value = handleStringTransfer((String) value); 55 | } 56 | 57 | //json对象的处理 58 | // if (value!=null && 59 | // ( JSONArray.class.equals(value.getClass()) 60 | // || 61 | // JSONObject.class.equals(value.getClass()) 62 | // ) 63 | // ){ 64 | // value = value.toString(); 65 | // } 66 | 67 | sb.append(value); 68 | if (i != args.size() - 1) { 69 | sb.append(","); 70 | } 71 | } 72 | sb.append(")"); 73 | Log.d("----> JsExecUtil", sb.toString()); 74 | webView.evaluateJavascript( 75 | sb.toString(), 76 | resultCallback 77 | ); 78 | } 79 | 80 | 81 | public static String handleStringTransfer(String s) { 82 | if (s != null) { 83 | String escapedArg = s.replace("'", "\\'").replace("\"", "\\\""); 84 | return "'" + escapedArg + "'"; 85 | } else { 86 | return null; 87 | } 88 | 89 | } 90 | 91 | public void setData(String key, Object value) { 92 | //字符串类型的处理 93 | if ( 94 | value != null && 95 | String.class.equals(value.getClass()) 96 | 97 | ) { 98 | Log.d("----> JsExecUtil","是string"+value.getClass().getName() ); 99 | value = handleStringTransfer((String) value); 100 | } 101 | String ling = "vue.setData('" + key + "'," + value + ")"; 102 | Log.d("----> JsExecUtil", ling); 103 | webView.evaluateJavascript( 104 | ling, 105 | null 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /client/linux-client/reflect_json.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "json/json.h" 8 | #include "reflect.hpp" 9 | 10 | namespace reflect_json { 11 | 12 | inline std::string jsonToStr(Json::Value root) { 13 | Json::StreamWriterBuilder builder; 14 | builder["indentation"] = ""; 15 | std::unique_ptr writer(builder.newStreamWriter()); 16 | std::ostringstream os; 17 | writer->write(root, &os); 18 | return os.str(); 19 | } 20 | 21 | inline Json::Value strToJson(std::string const &json) { 22 | Json::Value root; 23 | Json::Reader reader; 24 | reader.parse(json, root); 25 | return root; 26 | } 27 | 28 | template 29 | struct special_traits { 30 | static constexpr bool value = false; 31 | }; 32 | 33 | template () && !special_traits::value, int> = 0> 34 | Json::Value objToJson(T const &object) { 35 | return object; 36 | } 37 | 38 | template () && special_traits::value, int> = 0> 39 | Json::Value objToJson(T const &object) { 40 | return special_traits::objToJson(object); 41 | } 42 | 43 | template (), int> = 0> 44 | Json::Value objToJson(T const &object) { 45 | Json::Value root; 46 | reflect::foreach_member(object, [&](const char *key, auto &value) { 47 | root[key] = objToJson(value); 48 | }); 49 | return root; 50 | } 51 | 52 | template () && !special_traits::value, int> = 0> 53 | T jsonToObj(Json::Value const &root) { 54 | return root.as(); 55 | } 56 | 57 | template () && special_traits::value, int> = 0> 58 | T jsonToObj(Json::Value const &root) { 59 | return special_traits::jsonToObj(root); 60 | } 61 | 62 | template (), int> = 0> 63 | T jsonToObj(Json::Value const &root) { 64 | T object; 65 | reflect::foreach_member(object, [&](const char *key, auto &value) { 66 | value = jsonToObj>(root[key]); 67 | }); 68 | return object; 69 | } 70 | 71 | template 72 | struct special_traits> { 73 | static constexpr bool value = true; 74 | 75 | static Json::Value objToJson(std::vector const &object) { 76 | Json::Value root; 77 | for (auto const &elem: object) { 78 | root.append(reflect_json::objToJson(elem)); 79 | } 80 | return root; 81 | } 82 | 83 | static std::vector jsonToObj(Json::Value const &root) { 84 | std::vector object; 85 | for (auto const &elem: root) { 86 | object.push_back(reflect_json::jsonToObj(elem)); 87 | } 88 | return object; 89 | } 90 | }; 91 | 92 | template 93 | struct special_traits> { 94 | static constexpr bool value = true; 95 | 96 | static Json::Value objToJson(std::map const &object) { 97 | Json::Value root; 98 | for (auto const &elem: object) { 99 | root[elem.first] = reflect_json::objToJson(elem.second); 100 | } 101 | return root; 102 | } 103 | 104 | static std::map jsonToObj(Json::Value const &root) { 105 | std::map object; 106 | for (auto const &key: root.getMemberNames()) { 107 | object[key] = reflect_json::jsonToObj(root[key]); 108 | } 109 | return object; 110 | } 111 | }; 112 | 113 | template 114 | std::string serialize(T const &object) { 115 | return jsonToStr(objToJson(object)); 116 | } 117 | 118 | template 119 | T deserialize(std::string const &json) { 120 | return jsonToObj(strToJson(json)); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /web-h5-vue2/src/services/websocket.js: -------------------------------------------------------------------------------- 1 | // WebSocket服务类 2 | class WebSocketService { 3 | constructor() { 4 | this.ws = null; 5 | this.reconnectTimer = null; 6 | this.reconnectDelay = 5000; 7 | this.wsUrl = ""; 8 | this.callbacks = {}; 9 | this.connectionStatus = 'disconnected'; 10 | this.autoReconnect = true; 11 | this.isManualDisconnect = false; 12 | } 13 | 14 | // 连接WebSocket 15 | connect(settings = {}) { 16 | try { 17 | // 应用用户设置 18 | if (settings.wsUrl) { 19 | this.wsUrl = settings.wsUrl; 20 | } 21 | if (settings.reconnectInterval) { 22 | this.reconnectDelay = settings.reconnectInterval * 1000; 23 | } 24 | if (settings.autoReconnect !== undefined) { 25 | this.autoReconnect = settings.autoReconnect; 26 | } 27 | 28 | // 重置手动断开标志 29 | this.isManualDisconnect = false; 30 | this.updateConnectionStatus('connecting', '正在连接...'); 31 | 32 | this.ws = new WebSocket(this.wsUrl); 33 | 34 | this.ws.onopen = () => { 35 | console.log('WebSocket连接已建立'); 36 | this.updateConnectionStatus('connected', '已连接'); 37 | 38 | if (this.reconnectTimer) { 39 | clearTimeout(this.reconnectTimer); 40 | this.reconnectTimer = null; 41 | } 42 | }; 43 | 44 | this.ws.onmessage = (event) => { 45 | try { 46 | const data = JSON.parse(event.data); 47 | console.log('收到数据:', data); 48 | 49 | if (Array.isArray(data)) { 50 | this.processDevicesData(data); 51 | } else { 52 | console.warn('数据格式不正确,期望数组格式'); 53 | } 54 | } catch (error) { 55 | console.error('数据解析失败:', error); 56 | this.updateConnectionStatus('error', '数据解析错误'); 57 | } 58 | }; 59 | 60 | this.ws.onerror = (error) => { 61 | console.error('WebSocket错误:', error); 62 | this.updateConnectionStatus('error', '连接错误'); 63 | }; 64 | 65 | this.ws.onclose = () => { 66 | console.log('WebSocket连接已关闭'); 67 | 68 | // 只有在非手动断开时才显示断开连接通知 69 | if (!this.isManualDisconnect) { 70 | this.updateConnectionStatus('disconnected', '连接已断开'); 71 | } 72 | 73 | // 根据用户设置决定是否自动重连 74 | if (this.autoReconnect) { 75 | if (!this.isManualDisconnect) { 76 | this.updateConnectionStatus('disconnected', '连接已断开,准备重连...'); 77 | } 78 | this.reconnectTimer = setTimeout(() => { 79 | console.log('尝试重新连接...'); 80 | this.connect(); 81 | }, this.reconnectDelay); 82 | } else { 83 | this.updateConnectionStatus('disconnected', '连接已断开,自动重连已禁用'); 84 | } 85 | }; 86 | 87 | } catch (error) { 88 | console.error('WebSocket连接失败:', error); 89 | this.updateConnectionStatus('error', '连接失败'); 90 | 91 | // 根据用户设置决定是否自动重连 92 | if (this.autoReconnect) { 93 | this.reconnectTimer = setTimeout(() => { 94 | this.connect(); 95 | }, this.reconnectDelay); 96 | } 97 | } 98 | } 99 | 100 | // 更新连接状态 101 | updateConnectionStatus(status, message) { 102 | this.connectionStatus = status; 103 | if (this.callbacks.onStatusChange) { 104 | this.callbacks.onStatusChange(status, message); 105 | } 106 | } 107 | 108 | // 处理设备数据 109 | processDevicesData(devices) { 110 | if (this.callbacks.onData) { 111 | this.callbacks.onData(devices); 112 | } 113 | } 114 | 115 | // 注册回调函数 116 | on(eventName, callback) { 117 | this.callbacks[eventName] = callback; 118 | } 119 | 120 | // 关闭连接 121 | disconnect() { 122 | // 设置手动断开标志 123 | this.isManualDisconnect = true; 124 | 125 | if (this.ws && this.ws.readyState === WebSocket.OPEN) { 126 | this.ws.close(); 127 | } 128 | if (this.reconnectTimer) { 129 | clearTimeout(this.reconnectTimer); 130 | this.reconnectTimer = null; 131 | } 132 | } 133 | } 134 | 135 | export default new WebSocketService(); -------------------------------------------------------------------------------- /server/app/server.js: -------------------------------------------------------------------------------- 1 | const getOsData = require('./utils/os-data') 2 | const ws = require("ws"); 3 | const url = require('url'); 4 | const path = require('path'); 5 | const config = require(path.join(process.cwd(), './config.js')); 6 | const func = require('./utils/functions'); 7 | var server = new ws.Server({ 8 | host: "0.0.0.0",//绑定所有网卡 9 | port: config.port 10 | }); 11 | /** 12 | * 上传信息的设备 13 | * @type {{}} 14 | */ 15 | const devMap = {}; 16 | /** 17 | * 获取信息的设备 18 | * @type {{}} 19 | */ 20 | const viewMap = {}; 21 | 22 | /** 23 | * 设备上次传输数据的时间 24 | * 25 | */ 26 | const lastDataTimeMap = {}; 27 | 28 | /** 29 | * os 数据map 30 | * @type {{}} 31 | */ 32 | const devDataMap = {}; 33 | let ipInfo = {}; 34 | 35 | 36 | // 监听接入进来的客户端事件 37 | function websocket_add_listener(socket, request) { 38 | 39 | const query = url.parse(request.url, true).query; 40 | const token = query.token; 41 | const type = query.type; 42 | const endpointName = query.endpointName; 43 | 44 | if (!token || !type || !endpointName) { 45 | console.log("参数异常") 46 | socket.terminate(); 47 | } 48 | if (token !== config.token) { 49 | console.log(endpointName + "鉴权失败:" + token); 50 | socket.terminate(); 51 | } else { 52 | console.log(endpointName + "鉴权通过:" + token); 53 | } 54 | 55 | let targetMap = null; 56 | switch (type) { 57 | case 'dev': 58 | targetMap = devMap; 59 | break; 60 | case 'view': 61 | targetMap = viewMap; 62 | break; 63 | default: 64 | console.log(`非法类型${type},断开连接`); 65 | socket.terminate(); 66 | } 67 | targetMap[endpointName] = socket; 68 | 69 | if (type === 'dev') { 70 | lastDataTimeMap[endpointName] = Math.floor(Date.now() / 1000); 71 | } 72 | 73 | const clientIp = socket._socket.remoteAddress; 74 | console.log(`Client connected with IP: ${clientIp}`); 75 | 76 | func.sendAlert(`[starnode] 来自${clientIp} - ${endpointName} 上线了`); 77 | // close事件 78 | socket.on("close", function () { 79 | console.log(`client:${endpointName} close`); 80 | func.sendAlert(`[starnode] ${endpointName} close 掉线`); 81 | //置空socket 82 | destory(endpointName); 83 | }); 84 | 85 | // error事件 86 | socket.on("error", function (err) { 87 | console.log(`client:${endpointName} error`); 88 | func.sendAlert(`[starnode] ${endpointName} error 掉线`); 89 | //置空socket 90 | destory(endpointName); 91 | }); 92 | 93 | 94 | /** 95 | * 处理响应 96 | */ 97 | socket.on("message", function (data) { 98 | //console.log(`message from ${endpointName}`); 99 | if (!devMap[endpointName]) { 100 | console.log(`${endpointName} 掉线后仍在发送数据 `) 101 | socket.close(1000, '服务端主动断开连接'); 102 | return; 103 | } 104 | firstConn = false; 105 | lastDataTimeMap[endpointName] = Math.floor(Date.now() / 1000); 106 | if (type === 'dev') { 107 | try { 108 | handleMessage(socket, endpointName, JSON.parse(data.toString())); 109 | } catch (e) { 110 | console.error(e) 111 | } 112 | } 113 | }); 114 | 115 | 116 | } 117 | 118 | /** 119 | * 销毁连接 120 | * @param endpointName 121 | */ 122 | function destory(endpointName) { 123 | 124 | delete devMap[endpointName]; 125 | delete viewMap[endpointName]; 126 | delete devDataMap[endpointName]; 127 | delete lastDataTimeMap[endpointName]; 128 | } 129 | 130 | /** 131 | * 处理数据 132 | * @param socket 133 | * @param endpointName 134 | * @param data Object 135 | */ 136 | function handleMessage(socket, endpointName, data) { 137 | devDataMap[endpointName] = data; 138 | } 139 | 140 | 141 | server.on("connection", websocket_add_listener); 142 | server.on("error", (err) => { 143 | console.error(err) 144 | }); 145 | 146 | /** 147 | * 心跳包 148 | */ 149 | setInterval(args => { 150 | 151 | let socketArr = Object.values(devMap) 152 | for (let item of socketArr) { 153 | item.send(JSON.stringify({})) 154 | } 155 | 156 | }, 1000); 157 | 158 | /** 159 | * 超时检测 160 | */ 161 | setInterval(args => { 162 | const now = Math.floor(Date.now() / 1000); 163 | for (const endpointName in lastDataTimeMap) { 164 | let time = now - lastDataTimeMap[endpointName]; 165 | if (time > 20) { 166 | console.log(`${endpointName}检查心跳后掉线`); 167 | func.sendAlert(`[starnode] ${endpointName} 检查心跳后掉线,超时${time}秒`); 168 | destory(endpointName); 169 | } 170 | } 171 | }, 2000); 172 | 173 | /** 174 | * 发送数据给观察的设备 175 | */ 176 | setInterval(async args => { 177 | let osDataArr = Object.values(devDataMap); 178 | let osData = await getOsData(); 179 | Object.assign(osData, {ipInfo}) 180 | osDataArr.push(osData); 181 | for (let item of Object.values(viewMap)) { 182 | item.send(JSON.stringify(osDataArr)); 183 | } 184 | }, 1000); 185 | /** 186 | * 更新ip 187 | */ 188 | setInterval(async args => { 189 | func.getIpInfo().then(value => { 190 | ipInfo = value; 191 | }); 192 | }, 30 * 60 * 1000); 193 | 194 | console.log(`监听:${config.port}`); 195 | func.sendAlert(`[starnode] 服务端启动`) 196 | //初始化时更新数据 197 | func.getIpInfo().then(value => { 198 | ipInfo = value; 199 | }); 200 | 201 | 202 | 203 | //额外接收http请求 204 | const {simpleHttpServer} = require('./simpleHttpServer'); 205 | simpleHttpServer((endpointName, body) => { 206 | console.log(`将${endpointName}加入集合`) 207 | devDataMap[endpointName] = JSON.parse(body); 208 | lastDataTimeMap[endpointName] = Math.floor(Date.now() / 1000); 209 | }); 210 | 211 | -------------------------------------------------------------------------------- /android-h5/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /android-h5/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /web/AGENTS.md: -------------------------------------------------------------------------------- 1 | # AGENTS.md - System Monitoring Dashboard 2 | 3 | ## Project Overview 4 | 5 | This is a **real-time system monitoring dashboard** built with Vue.js that displays server/device health information including CPU usage, memory, disk space, and network statistics for both Windows and Linux systems. 6 | 7 | ### Core Technologies 8 | - **Frontend Framework**: Vue.js (CDN version) 9 | - **UI Components**: Element UI (for progress circles and components) 10 | - **Communication**: WebSocket for real-time data updates 11 | - **Languages**: HTML, JavaScript, CSS 12 | - **Architecture**: Single-page application (SPA) with real-time data visualization 13 | 14 | ### Project Purpose 15 | - Monitor multiple servers/devices in real-time 16 | - Display system metrics (CPU, memory, disk, network) 17 | - Support both Windows and Linux platforms 18 | - Provide geographic IP information for devices 19 | - Show network interface details with IPv4/IPv6 support 20 | 21 | ## File Structure 22 | 23 | ### Core Files 24 | - `simple-list.html` - Main application file containing HTML structure, CSS styles, and Vue.js logic 25 | - `css/ele.css` - Element UI component styles (minified) 26 | - `css/reset.css` - CSS reset styles (Meyer's reset v5.0.1) 27 | - `js/vue.js` - Vue.js framework (CDN version) 28 | - `js/ele.js` - Element UI JavaScript components 29 | - `js/axios.js` - HTTP client library (currently unused but available) 30 | - `js/tools.js` - Empty utility file (placeholder) 31 | 32 | ### Assets 33 | - `img/linux.jpg` - Linux system icon 34 | - `img/win.webp` - Windows system icon 35 | 36 | ## Development Guidelines 37 | 38 | ### Code Structure Patterns 39 | - **Single File Application**: All Vue.js code is embedded in `simple-list.html` 40 | - **Inline Styles**: CSS is primarily defined within ` -------------------------------------------------------------------------------- /client/qt-client/qtjson.hpp: -------------------------------------------------------------------------------- 1 | #ifndef QTJSON_H 2 | #define QTJSON_H 3 | 4 | #include"reflect.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | namespace qtjson { 17 | 18 | 19 | // 1.===========================对象与json对象的转换================================================= 20 | template 21 | struct special_traits { 22 | static constexpr bool value = false; 23 | }; 24 | 25 | template () && !special_traits::value, int> = 0> 26 | QJsonValue objToJson(T const &object) { 27 | return object; 28 | } 29 | 30 | template () && special_traits::value, int> = 0> 31 | QJsonValue objToJson(T const &object) { 32 | return special_traits::objToJson(object); 33 | } 34 | 35 | template (), int> = 0> 36 | QJsonObject objToJson(T const &object) { 37 | QJsonObject root; 38 | reflect::foreach_member(object, [&](const char *key, auto &value) { 39 | root[key] = objToJson(value); 40 | }); 41 | return root; 42 | } 43 | 44 | 45 | 46 | template () && !special_traits::value, int> = 0> 47 | T jsonToObj(QJsonValue const &root) { 48 | 49 | static_assert(std::is_same::value, "Type not supported"); 50 | qDebug()<<"不支持转换的类型:"<() && special_traits::value, int> = 0> 57 | T jsonToObj(QJsonValue const &root) { 58 | return special_traits::jsonToObj(root); 59 | } 60 | 61 | template (), int> = 0> 62 | T jsonToObj(QJsonValue const &root) { 63 | T object; 64 | reflect::foreach_member(object, [&](const char *key, auto &value) { 65 | qDebug()<<"jsonToObj==》 key="<>(root[key]); 67 | 68 | }); 69 | return object; 70 | } 71 | 72 | /** 73 | * @brief jsonArrayToVector 74 | * @param arr 75 | * @return 76 | */ 77 | template 78 | std::vector jsonArrayToVector(QJsonArray & arr){ 79 | std::vector v; 80 | 81 | for (const QJsonValue &value : arr) { 82 | T t = jsonToObj(value); 83 | v.push_back(t); 84 | } 85 | return v; 86 | } 87 | 88 | 89 | // 2.============================字符串与json对象的转换========================================= 90 | 91 | 92 | /** 93 | * QJsonObject -> QString 94 | * @brief jsonToStr 95 | * @param obj 96 | * @return 97 | */ 98 | inline QString jsonToStr(QJsonObject obj) { 99 | // 转换为 QJsonDocument 100 | QJsonDocument doc(obj); 101 | 102 | // 将 QJsonDocument 转换为 QByteArray 103 | QByteArray jsonData = doc.toJson(QJsonDocument::Indented); // 使用 Indented 格式化输出 104 | 105 | // 将 QByteArray 转换为 QString 106 | return QString::fromUtf8(jsonData); 107 | } 108 | 109 | inline QString jsonToStr(QJsonArray array) { 110 | 111 | // 创建一个 QJsonDocument,并将 QJsonArray 设置为其根节点 112 | QJsonDocument doc(array); 113 | 114 | // 将 QJsonDocument 转换为 QByteArray 115 | QByteArray jsonData = doc.toJson(QJsonDocument::Indented); // 使用 Indented 格式化输出 116 | 117 | // 将 QByteArray 转换为 QString 118 | return QString::fromUtf8(jsonData); 119 | } 120 | inline QString jsonToStr(QJsonValue value) { 121 | if(value.isObject()){ 122 | return jsonToStr(value.toObject()); 123 | }else { 124 | return jsonToStr(value.toArray()); 125 | } 126 | } 127 | /** 128 | * QString -> QJsonObject 129 | * @brief strToJson 130 | * @param jsonString 131 | * @return 132 | */ 133 | inline QJsonValue strToJson(QString const &jsonString) { 134 | QJsonParseError parseError; 135 | QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8(), &parseError); 136 | 137 | // 检查是否解析成功 138 | if (parseError.error != QJsonParseError::NoError) { 139 | qDebug() << "Parse error at offset" << parseError.offset << ":" << parseError.errorString(); 140 | return QJsonObject(); 141 | } 142 | 143 | if(jsonDoc.isObject()){ 144 | // 获取 QJsonObject 145 | return jsonDoc.object(); 146 | }else if(jsonDoc.isArray()){ 147 | 148 | return jsonDoc.array(); 149 | }else{ 150 | return QJsonValue(); 151 | } 152 | 153 | } 154 | 155 | 156 | // 3.==================自定义特化扩展============================================ 157 | // 特化 QString 158 | template <> 159 | struct special_traits { 160 | static constexpr bool value = true; 161 | static QString jsonToObj(QJsonValue const &root) { 162 | if (root.isString()) { 163 | return root.toString(); 164 | } 165 | qDebug()<<"转换QString失败:"< 177 | struct special_traits { 178 | static constexpr bool value = true; 179 | static QJsonArray jsonToObj(QJsonValue const &root) { 180 | if (root.isArray()) { 181 | qDebug()<<"特化QJsonArray:"; 182 | return root.toArray(); 183 | } 184 | qDebug()<<"转换QJsonArray失败:"< 196 | struct special_traits { 197 | static constexpr bool value = true; 198 | static int jsonToObj(QJsonValue const &root) { 199 | if (root.isDouble()) { 200 | return root.toInt(); 201 | }else if(root.isString()){ 202 | return root.toString().toInt(); 203 | } 204 | qDebug()<<"转换int失败:"< 215 | struct special_traits { 216 | static constexpr bool value = true; 217 | static long jsonToObj(QJsonValue const &root) { 218 | if (root.isDouble()) { 219 | return static_cast(root.toInt()); 220 | } 221 | else if(root.isString()){ 222 | return root.toString().toLong(); 223 | } 224 | qDebug()<<"转换long失败:"<(object)); 232 | } 233 | }; 234 | 235 | // 特化 unsigned short 236 | template <> 237 | struct special_traits { 238 | static constexpr bool value = true; 239 | static unsigned short jsonToObj(QJsonValue const &root) { 240 | if (root.isDouble()) { 241 | return static_cast(root.toInt()); 242 | }else if(root.isString()){ 243 | return root.toString().toUShort(); 244 | } 245 | qDebug()<<"转换unsigned short失败:"< 258 | struct special_traits { 259 | static constexpr bool value = true; 260 | static short jsonToObj(QJsonValue const &root) { 261 | if (root.isDouble()) { 262 | return static_cast(root.toInt()); 263 | }else if(root.isString()){ 264 | return root.toString().toShort(); 265 | } 266 | qDebug()<<"转换short失败:"< 277 | struct special_traits { 278 | static constexpr bool value = true; 279 | static bool jsonToObj(QJsonValue const &root) { 280 | if (root.isBool()) { 281 | return root.toBool(); 282 | } 283 | qDebug()<<"转换bool失败:"< 295 | struct special_traits { 296 | static constexpr bool value = true; 297 | static double jsonToObj(QJsonValue const &root) { 298 | if (root.isDouble()) { 299 | return root.toDouble(); 300 | }else if(root.isString()){ 301 | return root.toString().toDouble(); 302 | } 303 | qDebug()<<"转换double失败:"< 314 | struct special_traits { 315 | static constexpr bool value = true; 316 | static QJsonObject jsonToObj(QJsonValue const &root) { 317 | if (root.isObject()) { 318 | return root.toObject(); 319 | } 320 | qDebug()<<"转换QJsonObject失败:"< 330 | struct special_traits> { 331 | static constexpr bool value = true; 332 | 333 | static QJsonValue objToJson(std::vector const &object) { 334 | QJsonArray root; 335 | for (auto const &elem: object) { 336 | 337 | // qDebug()<< elem; 338 | //qDebug()<< qtjson::objToJson(elem); 339 | root.append(qtjson::objToJson(elem)); 340 | } 341 | 342 | //qDebug()< jsonToObj(QJsonValue const &root) { 347 | std::vector object; 348 | for (auto const &elem : root.toArray()) { 349 | object.push_back(qtjson::jsonToObj(elem)); 350 | } 351 | return object; 352 | } 353 | }; 354 | 355 | 356 | template 357 | struct special_traits> { 358 | static constexpr bool value = true; 359 | 360 | static QJsonValue objToJson(std::map const &object) { 361 | QJsonObject root; 362 | for (auto const &elem: object) { 363 | root[elem.first] = qtjson::objToJson(elem.second); 364 | } 365 | return root; 366 | } 367 | 368 | static std::map jsonToObj(QJsonValue const &root) { 369 | 370 | std::map object; 371 | QJsonObject jsonObject = root.toObject(); 372 | 373 | for (auto it = jsonObject.begin(); it != jsonObject.end(); ++it) { 374 | const QString &key = it.key(); 375 | const QJsonValue &value = it.value(); 376 | 377 | object[key] = qtjson::jsonToObj(value); 378 | 379 | 380 | } 381 | return object; 382 | } 383 | }; 384 | 385 | 386 | 387 | template 388 | QString serialize(T const &object) { 389 | return jsonToStr(objToJson(object)); 390 | } 391 | 392 | template 393 | T deserialize(QString const &json) { 394 | return jsonToObj(strToJson(json)); 395 | } 396 | 397 | 398 | } 399 | 400 | #endif // QTJSON_H 401 | -------------------------------------------------------------------------------- /android-h5/app/src/main/java/io/github/cctyl/starnode/h5/MainActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.cctyl.starnode.h5; 2 | 3 | 4 | import androidx.annotation.Nullable; 5 | import androidx.annotation.RequiresApi; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import androidx.documentfile.provider.DocumentFile; 8 | 9 | import android.annotation.SuppressLint; 10 | import android.content.Context; 11 | import android.content.Intent; 12 | import android.net.Uri; 13 | import android.os.Build; 14 | import android.os.Bundle; 15 | import android.os.storage.StorageManager; 16 | import android.os.storage.StorageVolume; 17 | import android.provider.DocumentsContract; 18 | import android.util.Log; 19 | import android.view.View; 20 | import android.view.Window; 21 | import android.view.WindowManager; 22 | import android.webkit.ConsoleMessage; 23 | import android.webkit.WebChromeClient; 24 | import android.webkit.WebResourceError; 25 | import android.webkit.WebResourceRequest; 26 | import android.webkit.WebResourceResponse; 27 | import android.webkit.WebSettings; 28 | import android.webkit.WebView; 29 | import android.webkit.WebViewClient; 30 | 31 | 32 | import org.json.JSONArray; 33 | import org.json.JSONException; 34 | import org.json.JSONObject; 35 | 36 | import java.io.File; 37 | import java.io.IOException; 38 | import java.io.InputStream; 39 | 40 | import fi.iki.elonen.NanoHTTPD; 41 | import io.github.cctyl.starnode.h5.utils.JsExecUtil; 42 | 43 | public class MainActivity extends AppCompatActivity { 44 | 45 | 46 | private WebView webView; 47 | private JsExecUtil jsExecUtil; 48 | // 定义一个本地端口,避免与系统服务冲突 49 | private static final int LOCAL_PORT = 8080; 50 | private AssetsWebServer server; 51 | @RequiresApi(api = Build.VERSION_CODES.M) 52 | @SuppressLint("SetJavaScriptEnabled") 53 | @Override 54 | protected void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | 57 | // 隐藏 ActionBar 58 | if (getSupportActionBar() != null) { 59 | getSupportActionBar().hide(); 60 | } 61 | 62 | // 设置沉浸式状态栏 63 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 64 | Window window = getWindow(); 65 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 66 | window.getDecorView().setSystemUiVisibility( 67 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 68 | ); 69 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 70 | window.setStatusBarColor(android.graphics.Color.TRANSPARENT); 71 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 72 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 73 | } 74 | 75 | setContentView(R.layout.activity_main); 76 | // 启动本地服务器 77 | startLocalWebServer(); 78 | webviewInit(); 79 | jsExecUtil = new JsExecUtil(webView); 80 | // 加载本地文件(确保文件在 assets 目录) 81 | // webView.loadUrl("file:///android_asset/index.html"); 82 | // webView.loadUrl("http://192.168.31.151:8080/"); 83 | webView.loadUrl("http://localhost:" + LOCAL_PORT); 84 | 85 | 86 | } 87 | 88 | 89 | @Override 90 | protected void onDestroy() { 91 | super.onDestroy(); 92 | 93 | // 销毁WebView 94 | if (webView != null) { 95 | webView.destroy(); 96 | webView = null; 97 | } 98 | // 停止服务器,释放资源 99 | if (server != null) { 100 | server.stop(); 101 | } 102 | } 103 | 104 | private void startLocalWebServer() { 105 | try { 106 | server = new AssetsWebServer(this, LOCAL_PORT); 107 | server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); // 启动服务器,非守护模式 108 | } catch (IOException e) { 109 | e.printStackTrace(); 110 | // 处理启动失败,例如端口被占用 111 | } 112 | } 113 | 114 | 115 | 116 | @RequiresApi(api = Build.VERSION_CODES.M) 117 | @SuppressLint({"JavascriptInterface", "SetJavaScriptEnabled"}) 118 | private void webviewInit() { 119 | WebView.setWebContentsDebuggingEnabled(true); 120 | webView = findViewById(R.id.web_view); 121 | // ===== 关键修复部分 ===== 122 | WebSettings settings = webView.getSettings(); 123 | settings.setJavaScriptEnabled(true); 124 | // 允许文件协议访问 125 | settings.setAllowFileAccess(true); 126 | // 允许文件协议中的JS访问其他文件 127 | settings.setAllowFileAccessFromFileURLs(true); 128 | settings.setAllowUniversalAccessFromFileURLs(true); 129 | settings.setDomStorageEnabled(true); 130 | settings.setDatabaseEnabled(true); 131 | settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); 132 | settings.setSupportZoom(false); 133 | settings.setTextZoom(100); 134 | settings.setUseWideViewPort(true); 135 | settings.setLoadWithOverviewMode(true); 136 | settings.setCacheMode(WebSettings.LOAD_DEFAULT); 137 | settings.setOffscreenPreRaster(true); // 预渲染离屏内容(API 24+) 138 | 139 | 140 | 141 | // 添加 WebChromeClient 捕获 JS 错误 142 | webView.setWebChromeClient(new WebChromeClient() { 143 | @Override 144 | public boolean onConsoleMessage(ConsoleMessage consoleMessage) { 145 | Log.e("WebViewConsole", 146 | consoleMessage.message() + " at " + 147 | consoleMessage.sourceId() + ":" + 148 | consoleMessage.lineNumber()); 149 | return true; 150 | } 151 | }); 152 | 153 | webView.setWebViewClient(new WebViewClient() { 154 | @RequiresApi(api = Build.VERSION_CODES.M) 155 | @Override 156 | public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { 157 | Log.e("WebViewError", "Error: " + error.getDescription()); 158 | Log.d("----> MainActivity", 159 | request.getUrl().toString() 160 | 161 | ); 162 | Log.d("----> MainActivity", String.valueOf(error.getErrorCode())); 163 | } 164 | 165 | //防止加载网页时调起系统浏览器 166 | @Override 167 | public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { 168 | view.loadUrl(request.getUrl().toString()); 169 | return true; 170 | } 171 | 172 | @Override 173 | public void onPageFinished(WebView view, String url) { 174 | // 注入脚本 175 | //view.evaluateJavascript( 176 | // "document.body.style.backgroundColor = 'red';" + // 临时设置背景色 177 | // "console.log('Body dimensions:', document.body.clientWidth, document.body.clientHeight);", 178 | // null 179 | //); 180 | } 181 | 182 | // @Override 183 | // public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { 184 | // String url = request.getUrl().toString(); 185 | // 186 | // // 处理本地 asset 文件的 MIME 类型 187 | // if (url.startsWith("file:///android_asset/")) { 188 | // String mimeType = getMimeType(url); 189 | // try { 190 | // InputStream inputStream = getAssets().open(url.replace("file:///android_asset/", "")); 191 | // return new WebResourceResponse(mimeType, "UTF-8", inputStream); 192 | // } catch (IOException e) { 193 | // e.printStackTrace(); 194 | // } 195 | // } 196 | // return super.shouldInterceptRequest(view, request); 197 | // } 198 | // 199 | // private String getMimeType(String url) { 200 | // if (url.endsWith(".js")) return "application/javascript"; 201 | // if (url.endsWith(".css")) return "text/css"; 202 | // if (url.endsWith(".html")) return "text/html"; 203 | // if (url.endsWith(".json")) return "application/json"; 204 | // return "text/plain"; 205 | // } 206 | 207 | }); 208 | // 添加 JS 接口,"Android" 是接口名,JS 中通过这个名称调用 209 | webView.addJavascriptInterface(new WebAppInterface(), "Android"); 210 | 211 | 212 | 213 | 214 | } 215 | 216 | 217 | @SuppressLint("WrongConstant") 218 | @Override 219 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 220 | super.onActivityResult(requestCode, resultCode, data); 221 | if (requestCode == 123 && resultCode == RESULT_OK) { 222 | if (data != null) { 223 | Uri uri = data.getData(); 224 | // 获取持久化权限 225 | getContentResolver().takePersistableUriPermission( 226 | uri, 227 | Intent.FLAG_GRANT_READ_URI_PERMISSION 228 | ); 229 | 230 | // 处理选择的文件/目录 231 | Log.d("onActivityResult: ", uri.toString()); 232 | sendFilesToWebView(uri); 233 | } 234 | } 235 | } 236 | 237 | public String getUserFriendlyPath(Context context, Uri treeUri) { 238 | String baseName = "External Storage"; 239 | 240 | // Android 10+ 获取卷名称 241 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 242 | StorageManager sm = (StorageManager) context.getSystemService(STORAGE_SERVICE); 243 | StorageVolume volume = sm.getStorageVolume(treeUri); 244 | if (volume != null) baseName = volume.getDescription(context); 245 | } 246 | 247 | // 解析路径段(示例:primary:Android/data → Android/data) 248 | String documentId = DocumentsContract.getTreeDocumentId(treeUri); 249 | String subPath = documentId.contains(":") 250 | ? documentId.split(":")[1] 251 | : documentId; 252 | 253 | return baseName + File.separator + subPath; 254 | } 255 | 256 | // 在获取文件列表后,将数据转换为JSON格式传递给WebView 257 | private void sendFilesToWebView(Uri folderUri) { 258 | DocumentFile file = DocumentFile.fromTreeUri(this, folderUri); 259 | JSONArray fileArray = new JSONArray(); 260 | 261 | JSONObject fileObj = new JSONObject(); 262 | try { 263 | fileObj.put("name", getUserFriendlyPath(this, folderUri)); 264 | fileObj.put("uri", file.getUri().toString()); 265 | fileObj.put("type", file.getType()); 266 | fileObj.put("size", file.length()); 267 | fileObj.put("lastModified", file.lastModified()); 268 | fileArray.put(fileObj); 269 | } catch (JSONException e) { 270 | e.printStackTrace(); 271 | } 272 | 273 | // 将数据传递给WebView 274 | String jsonData = fileArray.toString(); 275 | Log.d("sendFilesToWebView", jsonData); 276 | jsExecUtil.setData("files", fileArray); 277 | 278 | 279 | } 280 | 281 | @Override 282 | public void onBackPressed() { 283 | if (webView != null && webView.canGoBack()) { 284 | webView.goBack(); // 如果 WebView 可以返回上一页,则返回 285 | } else { 286 | super.onBackPressed(); // 否则按默认行为退出 Activity 287 | } 288 | } 289 | 290 | 291 | class WebAppInterface { 292 | // @JavascriptInterface 293 | // public void openFolderPicker() { 294 | // // 启动目录选择器 295 | // Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); 296 | // Uri rootUri = Uri.parse("content://com.android.externalstorage.documents/root/primary"); 297 | // intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, rootUri); 298 | // startActivityForResult(intent, 123); 299 | // } 300 | // 301 | // 302 | // @JavascriptInterface 303 | // public void toast(String toast) { 304 | // Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show(); 305 | // } 306 | // 307 | // @JavascriptInterface 308 | // public void userClick(String jsonStr) throws JSONException { 309 | // Log.d("userClick", "userClick: " + jsonStr); 310 | // JSONArray jsonArray = new JSONArray(jsonStr); 311 | // } 312 | 313 | } 314 | } --------------------------------------------------------------------------------