Compare commits

..

317 Commits

Author SHA1 Message Date
cppla
15fe753a0f 1.1.6修复大部分bug 2025-08-15 19:05:28 +08:00
cppla
0d185f628b update appjs 2025-08-15 18:51:01 +08:00
cppla
83977b55bf update gpt agent 2025-08-15 14:14:23 +08:00
cppla
49decdfed1 gpt commit 2025-08-15 13:59:12 +08:00
cppla
227e15fe48 Merge branch 'master' of github.com:cppla/ServerStatus 2025-08-15 13:34:55 +08:00
cppla
73b3d16ff6 fix some bug 2025-08-15 13:33:39 +08:00
cppla
c04256535d
Update README.md 2025-08-14 20:51:14 +08:00
cppla
3c8ebcf710 update fix some 2025-08-14 19:45:14 +08:00
cppla
9a19d2f4bf
Update README.md 2025-08-14 14:02:56 +08:00
cppla
d3c5e4a02a update 1.1.6 screen 2025-08-14 13:34:21 +08:00
cppla
a03cda0ef0 update 2025-08-14 10:12:23 +08:00
cppla
d25e7871ba update Dockerfile 2025-08-13 19:46:57 +08:00
cppla
fd6c3532d9 update 2025-08-13 14:16:29 +08:00
cppla
89745f3c3a 3600 2025-08-13 13:27:37 +08:00
cppla
266230f5f0 update theme 2025-08-12 19:04:44 +08:00
cppla
4c17623dff update bug 2025-08-12 17:56:55 +08:00
cppla
34e30d9873 del 2025-08-12 17:23:20 +08:00
cppla
1e190878dd update theme 2025-08-12 17:19:19 +08:00
cppla
874c122a62 del pch 2025-08-12 15:42:17 +08:00
cppla
fa984cfae6 update 2025-08-12 15:26:45 +08:00
cppla
a544f72db0 restore makefile 2025-08-12 14:55:54 +08:00
cppla
eea993c6cc update 2025-08-12 14:30:01 +08:00
cppla
a4a3734c30 update 2025-08-12 14:01:50 +08:00
cppla
a10e39b6f2 update readme 2025-08-12 13:54:17 +08:00
cppla
360d8d008d add ssl check 2025-08-12 13:49:25 +08:00
cppla
830938eac9
Update README.md 2025-04-01 10:35:26 +08:00
cppla
1a764ed104 1111 2024-12-02 14:13:00 +08:00
cppla
b0c543b44b update Dockerfile 2024-11-25 17:39:07 +08:00
cppla
f85c0a87f9 update tcp example 2024-11-25 16:50:32 +08:00
cppla
67d7c5ea0f
Merge pull request from ArashPartow/arashpartow/update_exprtk_0.0.3
Update ExprTk to 0.0.3
2024-11-25 16:42:00 +08:00
cppla
8576f81404 some update for probe tcp slow 2024-11-18 13:56:01 +08:00
cppla
fa0ee2bf57 重要线路丢包率告警例子 2024-09-18 13:24:06 +08:00
cppla
e9776b0e69 增加重要节点丢包率监控 2024-09-18 10:07:43 +08:00
cppla
e01cc118dc update css 2024-09-13 09:26:07 +08:00
cppla
784c40b153
Update README.md 2024-09-11 18:01:05 +08:00
cppla
6068a2ba17 yes 2024-09-11 17:54:24 +08:00
cppla
ced526824f 增加阿里云免费20G告警示例,限制username是aliyun1和aliyun2 2024-09-09 19:18:42 +08:00
cppla
7d2a73bf2e 修复月流量监控的bug,月流量加入表达式计算 2024-09-09 18:55:11 +08:00
cppla
51e2f664cd update theme set bug 2024-08-18 21:16:38 +08:00
cppla
937b1ea90f fix some bug 2024-08-18 21:13:53 +08:00
cppla
b95d1945bf
Update README.md 2024-08-18 19:44:57 +08:00
cppla
e8446cd52f
我震惊的是AI已经能够帮助重构前端页面
我震惊的是AI已经能够帮助重构前端页面
2024-08-18 13:09:25 +08:00
cppla
6098f0fb1a commit new css by ai 2024-08-18 13:00:45 +08:00
cppla
fc89a76cc2 resize css 2024-08-17 18:50:43 +08:00
cppla
f290947b2c update theme 2024-08-17 17:36:57 +08:00
cppla
b3adc6b782 update all 2024-08-17 17:31:40 +08:00
cppla
705a957606 bai piao copilot 2024-08-15 19:32:50 +08:00
cppla
a307ad7d19 ai niu bi 2024-08-15 15:59:26 +08:00
cppla
ad95e00723 不更新代码 无法续Copilot 2024-08-15 14:46:49 +08:00
cppla
31330168f3 ai modify 2024-08-15 14:18:34 +08:00
cppla
d9e8f8a7c1 del ie 2024-08-15 14:02:33 +08:00
cppla
08f15ebdc5 15s -> 25s 2024-04-18 15:34:43 +08:00
cppla
adb05818b9 改为15秒再次判断是否离线 2024-04-08 14:25:53 +08:00
cppla
4e73e8185e
Update README.md 2024-04-03 22:06:15 +08:00
cppla
0fe01064a4
Update README.md 2024-04-03 21:59:23 +08:00
cppla
510567eaec 修复驴头不对马嘴的bug 2024-04-03 21:31:35 +08:00
cppla
503037c7e2 add two logs 2024-04-03 19:53:04 +08:00
cppla
d75d5438a3 docker compose healthcheck 2024-04-03 17:44:43 +08:00
cppla
91f11dad76 屏蔽无用的nginx日志,增加健康检查wq 2024-04-03 17:16:32 +08:00
cppla
fdc5abacfc fix bug for offline 2024-04-03 16:42:40 +08:00
cppla
388938e02b 解决了网络闪断导致的错误报警 2024-04-03 16:39:08 +08:00
cppla
f912794068 add todo 2024-04-02 10:24:12 +08:00
cppla
6331d7d45b update 2024-03-11 15:06:47 +08:00
cppla
25f878a38a 5分钟一次probe service 2024-03-11 15:03:57 +08:00
cppla
a8b9b2d00d 默认一天在线率 2024-02-04 14:43:19 +08:00
cppla
dc3868998a 256->512 2024-02-04 14:37:39 +08:00
cppla
75d06c8666 add some ua 2024-01-27 15:27:10 +08:00
cppla
360252182d Merge branch 'dev' 2024-01-23 18:51:49 +08:00
cppla
aa0ccd254c update readme 2024-01-23 18:50:15 +08:00
cppla
f7b2e7db42
Merge pull request from cppla/dev
1.1.2 测试
2024-01-23 18:10:19 +08:00
cppla
052c75ef23 update 2024-01-23 18:09:23 +08:00
cppla
1f7827cc21 change 2024-01-23 18:00:24 +08:00
cppla
c236aee5dc todo list 2024-01-23 17:52:59 +08:00
cppla
fb3ecd1796 增加一个todo 2024-01-23 17:43:27 +08:00
cppla
810f6a0d8a fix 2024-01-23 16:47:26 +08:00
cppla
eddf37c413 fix bug 2024-01-23 10:30:43 +08:00
cppla
8dfc6d4718 update 2024-01-22 19:49:29 +08:00
cppla
03446b8f29 update some for cl 2024-01-22 15:34:44 +08:00
cppla
6ab046be51 update 2024-01-22 15:32:20 +08:00
cppla
94708c588c css resize 2024-01-22 15:21:06 +08:00
cppla
144987bcf1 .2f 2024-01-21 22:55:07 +08:00
cppla
7e46b1062e 新增说明,比较粗糙不建议更新 2024-01-21 21:34:07 +08:00
cppla
71cadcfebe 测试监控重要服务 2024-01-21 21:09:37 +08:00
cppla
d96e711a34 change ms and lost rate 2024-01-19 16:03:43 +08:00
Arash Partow
5ebf076330 Update ExprTk to 0.0.3 2024-01-01 00:00:00 +00:00
cppla
4a9f5adb6b
Update index3.html
cppla/Serverstatus version modify
2023-10-17 09:49:44 +08:00
cppla
59fa2d8eab fix light bug 2023-10-17 01:46:19 +00:00
cppla
bd4c22edff
Merge pull request from cppla/dev
内置一个light theme
2023-10-16 19:36:58 +08:00
windows11
88229b2f4a 内置一个light theme 2023-10-16 19:35:22 +08:00
cppla
d70a56a767
更加丰富的限制条件和规则
更加丰富的限制条件和规则
2023-10-16 18:35:03 +08:00
cppla
a177345858
添加静态字符串的计算支持
添加静态字符串的计算支持
username / name / type / host / location
2023-10-16 17:53:46 +08:00
cppla
11a03a020c
Merge pull request from cppla/dev
静态字符串参与计算,同时给出样例
2023-10-16 17:42:09 +08:00
windows11
ed330bd785 c style 2023-10-16 17:07:57 +08:00
windows11
77cb62ae80 测试是否支持静态字符串断言 2023-10-16 16:57:02 +08:00
cppla
7bbca5e881
Update README.md 2023-10-13 13:54:02 +08:00
cppla
2e0d1f7fb9
Update README.md 2023-10-13 13:53:20 +08:00
cppla
da83c54e5b
Update README.md 2023-10-13 13:34:18 +08:00
cppla
3ef6cb52c1
Update README.md 2023-10-13 13:13:05 +08:00
cppla
67877bacfc
Update README.md 2023-10-13 13:11:46 +08:00
cppla
a8383efe93
Update README.md 2023-10-13 12:47:24 +08:00
cppla
03682772a1
静态字符串无法参与计算
提交一个表达式说明,静态字符串无法参与计算
2023-10-13 12:46:25 +08:00
cppla
fdca4fd353
静态字符串变量无法参与计算。
静态字符串变量无法参与计算。
2023-10-13 12:30:03 +08:00
windows11
a5330dcf2f 修复username等静态变量的表达式支持 2023-10-13 11:58:46 +08:00
cppla
be78a7cc88
Update README.md 2023-10-13 11:45:12 +08:00
cppla
46d43c247f
Update light.css 2023-10-09 15:51:45 +08:00
cppla
6ae278857a
Merge pull request from cppla/cppla-patch-1
Update dark.css
2023-10-09 15:48:30 +08:00
cppla
d5e59453f1
Update dark.css 2023-10-09 15:47:45 +08:00
cppla
d242c6cea1
1.1.0
修复一些bug.
2023-10-09 14:24:17 +08:00
windows11
d7614ab168 width css bt3 2023-10-09 14:08:47 +08:00
windows11
ff06df8263 修复告警信息和URL特殊字符冲突的问题 2023-10-09 13:05:59 +08:00
cppla
9ee1516f72
Update README.md
up theme
2023-10-05 15:29:32 +08:00
cppla
7603ce37af
add light theme
add light theme link and demo .
2023-10-05 15:19:02 +08:00
cppla
5cfd533daf flush dns , every time 2023-05-31 11:06:28 +08:00
cppla
9077f1058a add layui theme. 2023-05-04 15:59:37 +08:00
cppla
e34ed05901 make -j,多核心编译docker image 2023-04-12 11:10:10 +08:00
cppla
4ab545f646 fix bug about tupt 2023-04-12 10:17:51 +08:00
cppla
a6b4248967 jquery 3.5.0 to 3.5.1 2023-04-04 19:06:35 +08:00
cppla
8ad927d88d jquery v3.3.1 to v3.5.0 2023-04-03 15:22:59 +08:00
cppla
4b1ec57c6d 增加一个月出口流量示例 2023-04-03 15:12:33 +08:00
cppla
979a9b5d87 加一个ddcc告警示例 2023-03-13 07:23:44 +00:00
cppla
52d62afa59 优化curl 2023-02-28 17:19:49 +08:00
cppla
806b60bd6b get macos infomation build 2022-11-30 11:17:12 +08:00
cppla
d5af5445df disk io not in win 2022-11-30 11:09:21 +08:00
cppla
c77e00476c get macos tudp 2022-11-30 11:02:09 +08:00
cppla
b551998eb8 get macos load 2022-11-30 10:31:04 +08:00
cppla
1c606cc79e update 2022-11-08 12:52:16 +08:00
cppla
d602791b11 macos disk info 2022-11-03 16:38:22 +08:00
cppla
a695ef8b3a update for macos 2022-11-03 16:33:16 +08:00
cppla
9f60b7962b beta macos disk info 2022-11-03 16:25:23 +08:00
cppla
34b1bca5b1 The io_counters is not available on OS X, 兼容处理 2022-11-03 14:49:47 +08:00
cppla
25917f0883 update readme 2022-11-01 13:47:36 +08:00
cppla
333bc29c88 rename old tg to plugin 2022-11-01 13:38:14 +08:00
cppla
c116067c39 get to post 2022-10-31 18:36:33 +08:00
cppla
14db7ec943 update config template 2022-10-31 16:48:54 +08:00
cppla
af8244f1d7 int memory expand 2022-10-31 14:53:12 +08:00
cppla
1004c9a6bd update 2022-10-31 14:25:36 +08:00
cppla
7f78af03f0 100 ping lost 2022-10-31 14:15:37 +08:00
cppla
02bbdb18de update lost style 2022-10-31 14:01:42 +08:00
cppla
6d813f932d
Merge pull request from zqcccc/master
展开栏添加丢包信息
2022-10-31 13:43:34 +08:00
c9cu
19d6a5ea8b 展开栏添加丢包信息 2022-10-29 19:07:33 +08:00
cppla
35de279e89
Update README.md 2022-08-26 14:53:47 +08:00
cppla
d5a047c781
Update README.md 2022-08-25 16:23:02 +08:00
cppla
10872059c5
build 1.0.9
build 1.0.9
2022-08-25 16:14:46 +08:00
cppla
44656e565f
更改watchdog callback说明
更改watchdog callback说明
2022-08-25 16:13:24 +08:00
cppla
78c7da7361
经测试邮箱 和wechat不能稳定发送
经测试邮箱 和wechat不能稳定发送
2022-08-25 16:03:46 +08:00
cppla
b5edeea057
Update README.md
添加一个邮箱服务
2022-08-25 14:40:56 +08:00
cppla
c2259f347d
Update README.md 2022-08-23 19:39:54 +08:00
cppla
bb5f03047d
Update README.md 2022-08-23 19:39:10 +08:00
cppla
6ab6e16d0f 1080 to 1200 2022-08-01 19:37:26 +08:00
cppla
615cec88c2 300s 2022-07-21 10:53:08 +08:00
cppla
94896bac80 change docker to beijing time 2022-07-18 10:36:36 +08:00
cppla
a788b5da90 . 2022-07-17 10:11:11 +08:00
cppla
de0bc9dd74
Update README.md 2022-07-17 10:08:04 +08:00
cppla
ec16fc1fac
Update README.md 2022-07-16 19:47:33 +08:00
cppla
cbd803b686
Create README.md
update tips
2022-07-16 19:46:59 +08:00
cppla
3eddb27d51 update 2022-07-16 19:30:29 +08:00
cppla
522809483e Compatible with older versions config.json 2022-07-16 19:11:29 +08:00
cppla
9d706f4da8
Update README.md
update watchdog 说明
2022-07-16 18:44:09 +08:00
cppla
a0fe8ee33c
Update README.md
callback说明
2022-07-16 18:35:08 +08:00
cppla
c5eed8e4fa
Update README.md
加入watchdog 说明
2022-07-16 18:00:10 +08:00
cppla
32d302580e update 2022-07-16 17:53:17 +08:00
cppla
b03d090a5c update readme 2022-07-16 17:47:57 +08:00
cppla
6151806141 c++ eval build use c++11 2022-07-16 14:42:32 +08:00
cppla
2703993272 add timeout for libcurl 2022-07-16 13:45:37 +08:00
cppla
022e5edb28 formate 2022-07-16 00:41:25 +08:00
cppla
135eb180c8 escape 2022-07-16 00:32:33 +08:00
cppla
2ead43a0d8 update 2022-07-15 23:14:53 +08:00
cppla
baae11de3b tg sms 2022-07-15 22:34:44 +08:00
cppla
2e62ffa593 link libcurl static lib 2022-07-15 21:14:39 +08:00
cppla
f70705d872 next add callback 2022-07-13 17:11:22 +08:00
cppla
7316dbdddb update 2022-07-13 16:55:38 +08:00
cppla
a1689acbf0 trigger msg for watchdog 2022-07-12 16:58:30 +08:00
cppla
e2a59a5465 eval 2022-07-12 15:59:24 +08:00
cppla
962d564c80 copy msg 2022-07-12 13:50:57 +08:00
cppla
77973a5309 0 and 1 2022-07-11 19:03:29 +08:00
cppla
a9128b137d read config. 2022-07-07 18:30:31 +08:00
cppla
0a2a007e24 support for python 3.10 2022-05-30 13:08:34 +08:00
cppla
ab5d6f7d2d
Update serverstatus.js
change style for io speed
2022-05-10 15:20:58 +08:00
cppla
dae7c07772 1.0.8 build 2022-05-10 11:56:38 +08:00
cppla
14447399a0
Update README.md 2022-05-06 10:26:46 +08:00
cppla
3327c5fd25 修复增删节点月流量匹配的问题 2022-05-05 10:15:06 +08:00
cppla
217b14bf55 old style 2022-04-29 16:07:56 +08:00
cppla
d7b8f27082 IO Speed as Tips 2022-04-29 13:58:38 +08:00
cppla
8f3736e4fc
增加开源的支持
just do it .
2022-04-13 15:40:46 +08:00
windows11
22c1905acb style 2022-04-02 15:55:58 +08:00
cppla
f73970262b test 2022-04-02 15:39:41 +08:00
cppla
f45d9be9fc ui 2022-04-02 15:34:01 +08:00
cppla
cb1313f5b5 test 2022-04-02 15:25:10 +08:00
cppla
b3f7ab45c5 blue 2022-04-02 14:47:23 +08:00
cppla
54451cab85 no float for IO 2022-04-02 11:39:37 +08:00
cppla
0f26eb502a 1080p change 2022-04-01 18:55:41 +08:00
cppla
472196d1ea 1.0.8 beta 2022-04-01 17:42:43 +08:00
cppla
7e84c230e6 Merge branch 'dev' 2022-04-01 17:36:22 +08:00
cppla
76cc15e84f css 2022-04-01 17:35:58 +08:00
cppla
4c0f4b94e8
Merge pull request from cppla/dev
新增实时IO统计
2022-04-01 16:29:53 +08:00
cppla
36040be11e add 2022-04-01 16:28:22 +08:00
cppla
776ad68392 io css style 2022-04-01 16:24:52 +08:00
cppla
39c92f4788 bug 2022-04-01 16:07:30 +08:00
cppla
6a15832966 bug test 2022-04-01 16:04:08 +08:00
cppla
654ecd7a3e update disk io style 2022-04-01 15:50:09 +08:00
cppla
47cf7a1818 持续获取io 2022-04-01 15:37:43 +08:00
cppla
5176cb0340 table control 2022-04-01 15:19:06 +08:00
cppla
1f47bcdb6f beta 2022-04-01 14:55:19 +08:00
cppla
7698ce0e4b 开源让编程更加美好 2022-03-31 19:53:17 +08:00
cppla
2d457c66ad push disk io for client-linux.py 2022-03-31 19:48:26 +08:00
cppla
d8c8d8fd3f add todo 2022-03-24 17:24:01 +08:00
cppla
d64beb7ba8 psutil add io total 2022-03-23 11:45:16 +08:00
cppla
30f9999fc6 rename 2022-03-23 11:13:18 +08:00
cppla
c46d0c06cd
Merge pull request from cppla/revert-135-master
Revert "修改丢包率分隔符💻为 |"
2022-03-02 18:51:29 +08:00
cppla
589c8812de
Revert "修改丢包率分隔符💻为 |" 2022-03-02 18:48:07 +08:00
cppla
110a2d97d8
Merge pull request from jwstaceyOvO/master
修改丢包率分隔符💻为 |
2022-03-02 11:02:02 +08:00
windows11
0914175200 load tofixed 2 2022-02-24 19:31:35 +08:00
windows11
830ea094a3 some change css for view 2022-02-24 19:17:20 +08:00
jwstaceyOvO
2f4611bdd8
修改丢包率分隔符💻为 |
💻太占空间,导致当丢包率位数较多时,无法正常显示全部数字
2022-02-24 12:33:31 +08:00
cppla
0b9e747cc9
Merge pull request from jwstaceyOvO/master
ServerStatus一键安装脚本
2022-02-23 15:42:29 +08:00
cppla
22847aa590
Update status.sh
auto install bash.
2022-02-23 15:41:08 +08:00
jwstaceyOvO
e8482bf8ac
Add files via upload 2022-02-22 17:39:14 +08:00
jwstaceyOvO
be95052d8f
Update status-server.service 2022-02-22 17:38:41 +08:00
jwstaceyOvO
4c8006d183
Add files via upload 2022-02-22 17:01:04 +08:00
jwstaceyOvO
f1559bfb55
Update status-server.service 2022-02-22 17:00:13 +08:00
jwstaceyOvO
cab0f8bc91
Update status.sh 2022-02-16 19:49:16 +08:00
jwstaceyOvO
4e1e92e406
Update status.sh 2022-02-16 15:47:27 +08:00
jwstaceyOvO
fcb4b3cdfe
Update status.sh 2022-02-15 18:47:44 +08:00
jwstaceyOvO
260b7b3a21
Update status-server.service 2022-02-15 18:31:39 +08:00
jwstaceyOvO
64c028f9ce
Update status.sh 2022-02-14 16:57:08 +08:00
jwstaceyOvO
ec4218f1b0
Update status.sh 2022-02-14 16:41:56 +08:00
jwstaceyOvO
00bc87c2c2
Add files via upload 2022-02-14 16:37:05 +08:00
jwstaceyOvO
f36b4e66d2
Delete status.sh 2022-02-14 16:32:02 +08:00
jwstaceyOvO
3871c2c3d9
Update status.sh 2022-02-14 16:29:32 +08:00
jwstaceyOvO
4a0b15b61d
Update status-client.service 2022-02-14 15:37:58 +08:00
jwstaceyOvO
e4fdacb2b5
Update status-client.service 2022-02-14 15:34:54 +08:00
jwstaceyOvO
d1bdec5e30
Update status-client.service 2022-02-14 15:33:39 +08:00
jwstaceyOvO
d0f3507add
Update status-client.service 2022-02-14 15:31:31 +08:00
jwstaceyOvO
a3dab0dc0f
Update status-client.service 2022-02-14 15:30:08 +08:00
jwstaceyOvO
050a6f78dc
Update status-server.service 2022-02-14 13:23:25 +08:00
jwstaceyOvO
0219d21ad5
Add files via upload 2022-02-14 12:39:10 +08:00
jwstaceyOvO
fc977367ba
Add files via upload 2022-02-14 12:30:17 +08:00
jwstaceyOvO
7d02f16fff
Create status-client.service 2022-02-14 12:30:04 +08:00
oc
4792821e8e docker compose for X86 and ARM, good luck 2022-01-27 17:49:35 +08:00
oc
6c926209bd docker compose support for X86 or ARM64 2022-01-27 17:10:07 +08:00
windows11
e274b09c0f 修复增删节点月流量错误适配,置0, todo:等待全适配 2022-01-21 15:48:49 +08:00
windows11
5078381215 copy dark theme color from jk.sunpma.com 2022-01-19 19:29:15 +08:00
oc
4e175d0a81 change max client 128 to 256 2022-01-07 18:45:17 +08:00
cppla
0dc82096f5
update 1.0.7 readme
update 1.0.7 readme
2021-12-21 19:37:44 +08:00
oc
43c1c1279f 主题默认跟随系统,直至你手动选择了模式 2021-12-21 17:51:12 +08:00
oc
ef0229b2d7 Unified style 2021-12-20 15:09:19 +08:00
aws
77443663d6 command 2021-12-17 11:21:40 +08:00
cppla
4c2bcfbe20
Update README.md 2021-12-17 11:07:57 +08:00
aws
8e06385152 docker install and docker-compose install 2021-12-17 11:02:07 +08:00
aws
cc5aec11ba update README 2021-12-17 10:51:37 +08:00
cppla
7bf4a22457
Update bot-telegram.py 2021-12-16 19:25:15 +08:00
root
f68d3a8963 telegram for serverstatus 2021-12-16 19:00:48 +08:00
windows11
0834ff138d emoji to computer 2021-11-24 11:17:27 +08:00
windows11
cbbd71d037 update 2021-11-12 15:49:25 +08:00
cppla
614c6d323c
Update README.md 2021-11-12 15:27:09 +08:00
windows11
e6d9edee2d Merge branch 'dev' 2021-11-12 15:09:21 +08:00
windows11
e00a374f03 update readme 2021-11-12 15:07:11 +08:00
windows11
feef07a6d8 Merge branch 'dev' 2021-11-11 18:06:20 +08:00
windows11
a83a2ff5a3 update 2021-11-11 18:05:51 +08:00
windows11
8925de8fc9 Merge branch 'dev' 2021-11-10 18:28:40 +08:00
windows11
700287125a default light 2021-11-10 18:28:20 +08:00
windows11
ec86ae7ed8 Merge branch 'dev' 2021-11-03 18:47:53 +08:00
windows11
0254971ec7 css 2021-11-03 18:47:32 +08:00
windows11
4966fe7daa Merge branch 'dev' 2021-11-03 18:02:49 +08:00
windows11
e5d22ad2db css 2021-11-03 18:02:31 +08:00
windows11
b348032855 Merge branch 'dev' 2021-11-03 15:00:59 +08:00
windows11
3cb0ea4777 css防止挤兑 2021-11-03 14:48:12 +08:00
windows11
f6cfdc9e6b fix 2021-11-03 14:45:00 +08:00
windows11
47a9ba60cb fix a bug 2021-11-03 13:39:55 +08:00
windows11
37dc23ee45 Merge branch 'dev' 2021-11-03 11:37:11 +08:00
windows11
ff51c48c93 update css 2021-11-03 11:36:50 +08:00
windows11
3edefd45ce Merge branch 'dev' 2021-11-03 11:34:56 +08:00
windows11
bde340a7e8 720px with 10px font 2021-11-03 11:34:27 +08:00
windows11
5323b28304 Merge branch 'dev' 2021-11-03 11:15:09 +08:00
windows11
28f61460c3 version beta 1.0.5 2021-11-03 11:13:57 +08:00
windows11
fd382957ca some change for css 2021-11-02 20:00:36 +08:00
windows11
c0ea66171a reduce month traffic diff 2021-11-02 16:35:42 +08:00
windows11
ab0a19b244 test bug for negative monthtraffic 2021-11-02 15:56:33 +08:00
cppla
d6878854b1
Update README.md 2021-10-22 11:53:39 +08:00
windows11
3aac3ccd0d 避免歧义 2021-10-22 11:51:43 +08:00
cppla
87824ce25a
update to beta 1.0.4
update to beta 1.0.4
2021-10-18 17:05:36 +08:00
windows11
9d2a2e1c94 fix some bug for month traffic 2021-10-18 17:04:04 +08:00
windows11
6ee766df87 not remove json file 2021-10-18 15:42:23 +08:00
cppla
5afdb61e88
Update README.md 2021-10-14 20:41:24 +08:00
cppla
9592ba7b1d
Update README.md 2021-10-14 20:30:25 +08:00
windows11
ecf19ae3c0 change length for css 2021-10-14 20:28:43 +08:00
windows11
c512ede1f7 add length for network 2021-10-14 19:54:16 +08:00
windows11
45a0723bea testing 2021-10-14 19:33:17 +08:00
windows11
b8a717a374 add info for change 2021-10-14 11:42:15 +08:00
windows11
3f0790db81 fix bug , no testing 2021-10-13 19:33:23 +08:00
windows11
8acb6ec0df 提交一个bug 2021-10-13 19:08:58 +08:00
windows11
7486919dbe 修复重启服务端,月流量统计丢失的问题 2021-10-13 18:45:34 +08:00
cppla
6188192951
Update README.md 2021-10-10 20:02:06 +08:00
cppla
e50ca41061
Bug说明,todo说明
Bug说明,todo说明
2021-10-10 19:01:11 +08:00
windows11
dc06c90e94 update kb 2 to 1 2021-10-10 14:14:04 +08:00
cppla
dcb2246d94
Update README.md 2021-10-10 12:46:49 +08:00
cppla
20d938401c
Merge pull request from cppla/dev
css show month_traffic
2021-10-10 11:40:05 +08:00
windows11
87895de2cf change css show month_traffic 2021-10-10 11:33:00 +08:00
cppla
427303d9ae
Update README.md 2021-10-09 23:03:30 +08:00
cppla
e2f4714641
1.0.2 版本,beta
1.0.2 版本,beta
2021-10-09 22:46:07 +08:00
windows11
34236b4c5f update readme 2021-10-09 22:22:24 +08:00
windows11
474e7addc9 update 2021-10-09 22:13:53 +08:00
windows11
4c042a3fc7 兼容旧版本,删除ip_status旧版本显示MH370 2021-10-09 22:13:13 +08:00
windows11
a0842a020b version beta 1.0.2 2021-10-09 22:07:07 +08:00
windows11
c98dc92256 去除ip_status检测,和丢包率检测重复。默认为python3, python2已经 end of life 2021-10-09 21:28:05 +08:00
windows11
60264204cb test web ui 0.7 2021-10-09 20:06:26 +08:00
windows11
e145feb1cd test web ui 0.6 2021-10-09 19:36:53 +08:00
windows11
2359890ca0 test web ui 0.5 2021-10-09 19:24:18 +08:00
windows11
430bf2d2e0 test web ui 0.4 2021-10-09 17:54:04 +08:00
windows11
cfd472357f test web ui 0.3 2021-10-09 17:35:39 +08:00
windows11
895206baa4 test web ui 0.2 2021-10-09 16:59:50 +08:00
windows11
1828a476f7 test web ui 0.1 2021-10-09 16:37:35 +08:00
windows98
7cf9d969bc dev: add month traffic , no completed 2021-10-09 14:27:31 +08:00
root
55ee9ca6fb emoji location 2021-08-09 13:00:07 +08:00
windows98
9601ef1655 update ssview.py for view ServerStatus in console when attacked 2021-07-14 12:02:35 +08:00
cppla
a77f94c8d6
Merge pull request from paulzzh/patch-1
fix 
2021-07-13 21:15:05 +08:00
paulzzh
ab65e0fc26
fix
修正依赖
2021-07-13 18:42:06 +08:00
38 changed files with 49711 additions and 1355 deletions

29
Dockerfile Normal file

@ -0,0 +1,29 @@
# The Dockerfile for build localhost source, not git repo
FROM debian:bookworm AS builder
LABEL maintainer="cppla <https://cpp.la>"
RUN apt-get update -y && apt-get -y install gcc g++ make libcurl4-openssl-dev
COPY . .
WORKDIR /server
RUN make -j
RUN pwd && ls -a
# glibc env run
FROM nginx:latest
RUN mkdir -p /ServerStatus/server/ && ln -sf /dev/null /var/log/nginx/access.log && ln -sf /dev/null /var/log/nginx/error.log
COPY --from=builder server /ServerStatus/server/
COPY --from=builder web /usr/share/nginx/html/
# china time
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
EXPOSE 80 35601
HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD curl --fail http://localhost:80 || bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'
CMD ["sh", "-c", "/etc/init.d/nginx start && /ServerStatus/server/sergate --config=/ServerStatus/server/config.json --web-dir=/usr/share/nginx/html"]

225
README.md

@ -1,140 +1,211 @@
# ServerStatus中文版 # ServerStatus中文版
* ServerStatus中文版是一个酷炫高逼格的云探针、云监控、服务器云监控、多服务器探针~。 * ServerStatus中文版是一个酷炫高逼格的云探针、云监控、服务器云监控、多服务器探针~。
* 在线演示https://tz.cloudcpp.com * 在线演示https://tz.cloudcpp.com
[![Python Support](https://img.shields.io/badge/python-2.7%2B%20-blue.svg)](https://github.com/cppla/ServerStatus) [![Python Support](https://img.shields.io/badge/python-3.6%2B%20-blue.svg)](https://github.com/cppla/ServerStatus)
[![C++ Compiler](http://img.shields.io/badge/C++-GNU-blue.svg?style=flat&logo=cplusplus)](https://github.com/cppla/ServerStatus) [![C++ Compiler](http://img.shields.io/badge/C++-GNU-blue.svg?style=flat&logo=cplusplus)](https://github.com/cppla/ServerStatus)
[![License](https://img.shields.io/badge/license-MIT-4EB1BA.svg?style=flat-square)](https://github.com/cppla/ServerStatus) [![License](https://img.shields.io/badge/license-MIT-4EB1BA.svg?style=flat-square)](https://github.com/cppla/ServerStatus)
[![Version](https://img.shields.io/badge/Version-Build%201.1.6-red)](https://github.com/cppla/ServerStatus)
![Latest Version](http://dl.cpp.la/Archive/serverstatus.png) ![Latest Host Version](https://dl.cpp.la/Archive/serverstatus_1_1_6_1.png)
# 目录介绍: `Watchdog触发式告警interval只是为了防止频繁收到报警信息造成的骚扰并不是探测间隔。值得注意的是Exprtk库默认使用窄字符类型中文等Unicode字符无法解析计算等待修复。 `
* autodeploy 自动部署. # 目录:
* clients 客户端文件
* server 服务端文件
* web 网站文件      
# 自动部署: * clients 客户端文件
* server 服务端文件
* web 网站文件
* server/config.json 探针配置文件
* web/json 探针月流量
# 部署:
【服务端】: 【服务端】:
```bash ```bash
`x86_64`: docker pull cppla/serverstatus:latest
`arm64`: docker pull cppla/serverstatus:arm
`Docker`:
wget https://raw.githubusercontent.com/cppla/ServerStatus/master/autodeploy/config.json wget --no-check-certificate -qO ~/serverstatus-config.json https://raw.githubusercontent.com/cppla/ServerStatus/master/server/config.json && mkdir ~/serverstatus-monthtraffic
docker run -d --restart=always --name=serverstatus -v {$path}/config.json:/ServerStatus/server/config.json -p {$port}:80 -p {$port}:35601 cppla/serverstatus:latest docker run -d --restart=always --name=serverstatus -v ~/serverstatus-config.json:/ServerStatus/server/config.json -v ~/serverstatus-monthtraffic:/usr/share/nginx/html/json -p 80:80 -p 35601:35601 cppla/serverstatus:latest
eg: `Docker-compose(推荐)`: docker-compose up -d
docker run -d --restart=always --name=serverstatus -v ~/config.json:/ServerStatus/server/config.json -p 80:80 -p 35601:35601 cppla/serverstatus:latest
``` ```
【客户端】: 【客户端】:
```bash ```bash
wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python client-linux.py SERVER={$SERVER} USER={$USER} PASSWORD={$PASSWORD} >/dev/null 2>&1 & wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python3 client-linux.py SERVER={$SERVER} USER={$USER} PASSWORD={$PASSWORD} >/dev/null 2>&1 &
eg: eg:
wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python client-linux.py SERVER=45.79.67.132 USER=s04 >/dev/null 2>&1 & wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python3 client-linux.py SERVER=45.79.67.132 USER=s04 >/dev/null 2>&1 &
``` ```
# 主题:
* layuihttps://github.com/zeyudada/StatusServerLayui 预览https://sslt.8zyw.cn
<img src=https://dl.cpp.la/Archive/serverstatus_layui.png width=200 height=100 />
* lighthttps://github.com/orilights/ServerStatus-Theme-Light 预览https://tz.cloudcpp.com/index3.html
<img src=https://dl.cpp.la/Archive/serverstatus_light.png width=200 height=100 />
# 手动安装教程: # 手动安装教程:
【克隆代码】: **【服务端配置】**
```
git clone https://github.com/cppla/ServerStatus.git
```
【服务端配置】服务端程序在ServerStatus/web下:
一、生成服务端程序 #### 一、生成服务端程序
``` ```
cd ServerStatus/server `Debian/Ubuntu`: apt-get -y install gcc g++ make libcurl4-openssl-dev
make `Centos/Redhat`: yum -y install gcc gcc-c++ make libcurl-devel
cd ServerStatus/server && make
./sergate ./sergate
``` ```
如果没错误提示OKctrl+c关闭如果有错误提示检查35601端口是否被占用 如果没错误提示OKctrl+c关闭如果有错误提示检查35601端口是否被占用
二、修改配置文件 #### 二、修改配置文件
修改config.json文件注意username, password的值需要和客户端对应一致     ```diff
! watchdog rule 可以为任何已知字段的表达式。注意Exprtk库默认使用窄字符类型中文等Unicode字符无法解析计算等待修复
! watchdog interval 最小通知间隔
! watchdog callback 可自定义为Post方法的URL告警内容将拼接其后并发起回调
! Telegram: https://api.telegram.org/bot你自己的密钥/sendMessage?parse_mode=HTML&disable_web_page_preview=true&chat_id=你自己的标识&text=
! Server酱: https://sctapi.ftqq.com/你自己的密钥.send?title=ServerStatus&desp=
! PushDeer: https://api2.pushdeer.com/message/push?pushkey=你自己的密钥&text=
! HttpBasicAuth: https://用户名:密码@你自己的域名/api/push?message=
``` ```
{"servers":
```
{
"servers":
[ [
{ {
"username": "s01", "username": "s01",
"name": "Mainserver 1", "name": "vps-1",
"type": "Dedicated Server", "type": "kvm",
"host": "GenericServerHost123", "host": "chengdu",
"location": "Austria", "location": "🇨🇳",
"password": "some-hard-to-guess-copy-paste-password" "password": "USER_DEFAULT_PASSWORD",
"monthstart": 1
}
],
"monitors": [
{
"name": "监测网站,默认为一天在线率",
"host": "https://www.baidu.com",
"interval": 1200,
"type": "https"
}, },
{
"name": "监测tcp服务端口",
"host": "1.1.1.1:80",
"interval": 1200,
"type": "tcp"
}
],
"sslcerts": [
{
"name": "demo域名",
"domain": "https://demo.example.com",
"port": 443,
"interval": 600,
"callback": "https://yourSMSurl"
}
],
"watchdog":
[
{
"name": "服务器负载高监控排除内存大于32G物理机同时排除node1机器",
"rule": "cpu>90&load_1>4&memory_total<33554432&name!='node1'",
"interval": 600,
"callback": "https://yourSMSurl"
},
{
"name": "服务器内存使用率过高监控排除小于1G的机器",
"rule": "(memory_used/memory_total)*100>90&memory_total>1048576",
"interval": 600,
"callback": "https://yourSMSurl"
},
{
"name": "服务器宕机告警",
"rule": "online4=0&online6=0",
"interval": 600,
"callback": "https://yourSMSurl"
},
{
"name": "DDOS和CC攻击监控限制甲骨文机器",
"rule": "tcp_count>600&type='Oracle'",
"interval": 300,
"callback": "https://yourSMSurl"
},
{
"name": "服务器月出口流量999GB告警",
"rule": "(network_out-last_network_out)/1024/1024/1024>999",
"interval": 3600,
"callback": "https://yourSMSurl"
},
{
"name": "阿里云服务器流量18GB告警,限制username为乌兰察布",
"rule": "(network_out-last_network_out)/1024/1024/1024>18&(username='wlcb1'|username='wlcb2'|username='wlcb3'|username='wlcb4')",
"interval": 3600,
"callback": "https://yourSMSurl"
},
{
"name": "重要线路丢包率过高检查",
"rule": "(ping_10010>10|ping_189>10|ping_10086>10)&(host='sgp'|host='qqhk'|host='hk-21-x'|host='hk-31-x')",
"interval": 600,
"callback": "https://yourSMSurl"
},
{
"name": "你可以组合任何已知字段的表达式",
"rule": "(hdd_used/hdd_total)*100>95",
"interval": 1800,
"callback": "https://yourSMSurl"
}
] ]
} }
``` ```
三、拷贝ServerStatus/status到你的网站目录 #### 三、拷贝ServerStatus/status到你的网站目录
例如: 例如:
``` ```
sudo cp -r ServerStatus/web/* /home/wwwroot/default sudo cp -r ServerStatus/web/* /home/wwwroot/default
``` ```
四、运行服务端: #### 四、运行服务端:
web-dir参数为上一步设置的网站根目录务必修改成自己网站的路径 web-dir参数为上一步设置的网站根目录务必修改成自己网站的路径
``` ```
./sergate --config=config.json --web-dir=/home/wwwroot/default ./sergate --config=config.json --web-dir=/home/wwwroot/default
``` ```
【客户端配置】客户端程序在ServerStatus/clients下 **【客户端配置】**
客户端有两个版本client-linux为普通linuxclient-psutil为跨平台版普通版不成功换成跨平台版即可。 客户端有两个版本client-linux为普通linuxclient-psutil为跨平台版普通版不成功换成跨平台版即可。
一、client-linux版配置 #### 一、client-linux版配置
1、vim client-linux.py, 修改SERVER地址username帐号 password密码 1、vim client-linux.py, 修改SERVER地址username帐号 password密码
2、python client-linux.py 运行即可。 2、python3 client-linux.py 运行即可。
二、client-psutil版配置: #### 二、client-psutil版配置:
1、安装psutil跨平台依赖库 1、安装psutil跨平台依赖库
```
`Debian/Ubuntu`: apt -y install python3-pip && pip3 install psutil
`Centos/Redhat`: yum -y install python3-pip gcc python3-devel && pip3 install psutil
`Windows`: https://pypi.org/project/psutil/
```
2、vim client-psutil.py, 修改SERVER地址username帐号 password密码 2、vim client-psutil.py, 修改SERVER地址username帐号 password密码
3、python client-psutil.py 运行即可。 3、python3 client-psutil.py 运行即可。
```
### for Centos
sudo yum -y install epel-release
sudo yum -y install python-pip
sudo yum clean all
sudo yum -y install gcc
sudo yum -y install python-devel
sudo pip install psutil
### for Ubuntu/Debian:
sudo root
apt-get -y install python-setuptools python-dev build-essential
apt-get -y install python-pip
pip install psutil
### for Windows:
打开网址https://pypi.python.org/pypi?:action=display&name=psutil#downloads
下载psutil for windows程序包
安装即可
```
打开云探针页面,就可以正常的监控。接下来把服务器和客户端脚本自行加入开机启动,或者进程守护,或以后台方式运行即可!例如: nohup python client-linux.py & 服务器和客户端自行加入开机启动,或进程守护,或后台方式运行。 例如: nohup python3 client-linux.py &
### 如何快速跟随系统启动呢?其实好多人都搞复杂化了 `extra scene (run web/ssview.py)`
1、chmod 755 /root/client-linux.py ![Shell View](https://dl.cpp.la/Archive/serverstatus-shell.png?version=2023)
2、vim /etc/crontab尾部追加
```diff
@reboot root /root/client-linux.py SERVER=$server USER=$user
```
# 为什么会有ServerStatus中文版
* 有些功能确实没用 # Make Better
* 原版本部署,英文说明复杂
* 不符合中文版的习惯
* 没有一次又一次的轮子,哪来如此优秀的云探针
# 相关开源项目,感谢: * BotoXhttps://github.com/BotoX/ServerStatus
* ServerStatushttps://github.com/BotoX/ServerStatus
* mojeda: https://github.com/mojeda * mojeda: https://github.com/mojeda
* mojeda's ServerStatus: https://github.com/mojeda/ServerStatus * mojeda's ServerStatus: https://github.com/mojeda/ServerStatus
* BlueVM's project: http://www.lowendtalk.com/discussion/comment/169690#Comment_169690 * BlueVM's project: http://www.lowendtalk.com/discussion/comment/169690#Comment_169690

@ -1,24 +0,0 @@
FROM debian:latest as builder
MAINTAINER cppla https://cpp.la
RUN apt-get update
RUN apt-get -y install gcc g++ make git
RUN git clone https://github.com/cppla/ServerStatus
WORKDIR /ServerStatus/server
RUN make
RUN pwd && ls -a
# glibc env run
FROM nginx:latest
RUN mkdir -p /ServerStatus/server/
COPY --from=builder /ServerStatus/server /ServerStatus/server/
COPY --from=builder /ServerStatus/web /usr/share/nginx/html/
EXPOSE 80 35601
CMD nohup sh -c '/etc/init.d/nginx start && /ServerStatus/server/sergate --config=/ServerStatus/server/config.json --web-dir=/usr/share/nginx/html'

@ -1,37 +0,0 @@
{"servers":
[
{
"username": "s01",
"name": "node1",
"type": "xen",
"host": "host1",
"location": "cn",
"password": "USER_DEFAULT_PASSWORD"
},
{
"username": "s02",
"name": "node2",
"type": "vmware",
"host": "host2",
"location": "jp",
"password": "USER_DEFAULT_PASSWORD"
},
{
"disabled": true,
"username": "s03",
"name": "node3",
"type": "Nothing",
"host": "host3",
"location": "fr",
"password": "USER_DEFAULT_PASSWORD"
},
{
"username": "s04",
"name": "ssss",
"type": "ssss",
"host": "ssss",
"location": "ssss",
"password": "USER_DEFAULT_PASSWORD"
}
]
}

@ -1,15 +0,0 @@
服务端:
docker build -f Dockerfile -t sss .
docker run -d --restart=always --name=sss -v {$path}/config.json:/ServerStatus/server/config.json -p {$port}:80 -p {$port}:35601 sss
客户端:
wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python client-linux.py SERVER={$SERVER} USER={$USER} PASSWORD={$PASSWORD} >/dev/null 2>&1 &
附: docker安装
curl -sSL https://get.docker.com/ | sh

@ -1,27 +1,28 @@
#!/usr/bin/env python #!/usr/bin/env python3
# coding: utf-8 # coding: utf-8
# Update by : https://github.com/cppla/ServerStatus # Update by : https://github.com/cppla/ServerStatus, Update date: 20220530
# 支持Python版本2.7 to 3.7 # 版本1.0.3, 支持Python版本2.7 to 3.10
# 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures # 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
# 时间: 20200407 # ONLINE_PACKET_HISTORY_LEN 探测间隔1200s记录24小时在线率72探测时间300s记录24小时288探测间隔60s记录7天10080
# 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义例如CU = "www.facebook.com"。 # 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义例如CU = "www.facebook.com"。
SERVER = "127.0.0.1" SERVER = "127.0.0.1"
USER = "s01" USER = "s01"
PORT = 35601
PASSWORD = "USER_DEFAULT_PASSWORD" PASSWORD = "USER_DEFAULT_PASSWORD"
INTERVAL = 1 PORT = 35601
PROBEPORT = 80
PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6
PING_PACKET_HISTORY_LEN = 100
CU = "cu.tz.cloudcpp.com" CU = "cu.tz.cloudcpp.com"
CT = "ct.tz.cloudcpp.com" CT = "ct.tz.cloudcpp.com"
CM = "cm.tz.cloudcpp.com" CM = "cm.tz.cloudcpp.com"
PROBEPORT = 80
PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6
PING_PACKET_HISTORY_LEN = 100
ONLINE_PACKET_HISTORY_LEN = 72
INTERVAL = 1
import socket import socket
import ssl
import time import time
import timeit import timeit
import re import re
@ -31,10 +32,10 @@ import json
import errno import errno
import subprocess import subprocess
import threading import threading
try: if sys.version_info.major == 3:
from queue import Queue # python3 from queue import Queue
except ImportError: elif sys.version_info.major == 2:
from Queue import Queue # python2 from Queue import Queue
def get_uptime(): def get_uptime():
with open('/proc/uptime', 'r') as f: with open('/proc/uptime', 'r') as f:
@ -119,18 +120,6 @@ def tupd():
d = int(s[:-1])-2 d = int(s[:-1])-2
return t,u,p,d return t,u,p,d
def ip_status():
ip_check = 0
for i in [CU, CT, CM]:
try:
socket.create_connection((i, PROBEPORT), timeout=1).close()
except:
ip_check += 1
if ip_check >= 2:
return False
else:
return True
def get_network(ip_version): def get_network(ip_version):
if(ip_version == 4): if(ip_version == 4):
HOST = "ipv4.google.com" HOST = "ipv4.google.com"
@ -160,22 +149,28 @@ netSpeed = {
'avgrx': 0, 'avgrx': 0,
'avgtx': 0 'avgtx': 0
} }
diskIO = {
'read': 0,
'write': 0
}
monitorServer = {}
def _ping_thread(host, mark, port): def _ping_thread(host, mark, port):
lostPacket = 0 lostPacket = 0
packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN) packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN)
IP = host while True:
if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname # flush dns , every time.
try: IP = host
if PROBE_PROTOCOL_PREFER == 'ipv4': if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname
IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0] try:
else: if PROBE_PROTOCOL_PREFER == 'ipv4':
IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0] IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0]
except Exception: else:
IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0]
except Exception:
pass pass
while True:
if packet_queue.full(): if packet_queue.full():
if packet_queue.get() == 0: if packet_queue.get() == 0:
lostPacket -= 1 lostPacket -= 1
@ -223,7 +218,72 @@ def _net_speed():
netSpeed["avgtx"] = avgtx netSpeed["avgtx"] = avgtx
time.sleep(INTERVAL) time.sleep(INTERVAL)
def get_realtime_date(): def _disk_io():
'''
good luck for opensource! by: cpp.la
磁盘IO因为IOPS原因SSD和HDD包括RAID卡ZFS等阵列技术IO对性能的影响还需要结合自身服务器情况来判断
比如我这里是机械硬盘大量做随机小文件读写那么很低的读写也就能造成硬盘长时间的等待
如果这里做连续性IO那么普通机械硬盘写入到100Mb/s那么也能造成硬盘长时间的等待
磁盘读写有误差4k8k https://stackoverflow.com/questions/34413926/psutil-vs-dd-monitoring-disk-i-o
:return:
'''
while True:
# pre pid snapshot
snapshot_first = {}
# next pid snapshot
snapshot_second = {}
# read count snapshot
snapshot_read = 0
# write count snapshot
snapshot_write = 0
# process snapshot
pid_snapshot = [str(i) for i in os.listdir("/proc") if i.isdigit() is True]
for pid in pid_snapshot:
try:
with open("/proc/{}/io".format(pid)) as f:
pid_io = {}
for line in f.readlines():
if "read_bytes" in line:
pid_io["read"] = int(line.split("read_bytes:")[-1].strip())
elif "write_bytes" in line and "cancelled_write_bytes" not in line:
pid_io["write"] = int(line.split("write_bytes:")[-1].strip())
pid_io["name"] = open("/proc/{}/comm".format(pid), "r").read().strip()
snapshot_first[pid] = pid_io
except:
if pid in snapshot_first:
snapshot_first.pop(pid)
time.sleep(INTERVAL)
for pid in pid_snapshot:
try:
with open("/proc/{}/io".format(pid)) as f:
pid_io = {}
for line in f.readlines():
if "read_bytes" in line:
pid_io["read"] = int(line.split("read_bytes:")[-1].strip())
elif "write_bytes" in line and "cancelled_write_bytes" not in line:
pid_io["write"] = int(line.split("write_bytes:")[-1].strip())
pid_io["name"] = open("/proc/{}/comm".format(pid), "r").read().strip()
snapshot_second[pid] = pid_io
except:
if pid in snapshot_first:
snapshot_first.pop(pid)
if pid in snapshot_second:
snapshot_second.pop(pid)
for k, v in snapshot_first.items():
if snapshot_first[k]["name"] == snapshot_second[k]["name"] and snapshot_first[k]["name"] != "bash":
snapshot_read += (snapshot_second[k]["read"] - snapshot_first[k]["read"])
snapshot_write += (snapshot_second[k]["write"] - snapshot_first[k]["write"])
diskIO["read"] = snapshot_read
diskIO["write"] = snapshot_write
def get_realtime_data():
'''
real time get system data
:return:
'''
t1 = threading.Thread( t1 = threading.Thread(
target=_ping_thread, target=_ping_thread,
kwargs={ kwargs={
@ -251,14 +311,97 @@ def get_realtime_date():
t4 = threading.Thread( t4 = threading.Thread(
target=_net_speed, target=_net_speed,
) )
t1.setDaemon(True) t5 = threading.Thread(
t2.setDaemon(True) target=_disk_io,
t3.setDaemon(True) )
t4.setDaemon(True) for ti in [t1, t2, t3, t4, t5]:
t1.start() ti.daemon = True
t2.start() ti.start()
t3.start()
t4.start()
def _monitor_thread(name, host, interval, type):
lostPacket = 0
packet_queue = Queue(maxsize=ONLINE_PACKET_HISTORY_LEN)
while True:
if name not in monitorServer.keys():
break
if packet_queue.full():
if packet_queue.get() == 0:
lostPacket -= 1
try:
if type == "http":
address = host.replace("http://", "")
m = timeit.default_timer()
if PROBE_PROTOCOL_PREFER == 'ipv4':
IP = socket.getaddrinfo(address, None, socket.AF_INET)[0][4][0]
else:
IP = socket.getaddrinfo(address, None, socket.AF_INET6)[0][4][0]
monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
k = socket.create_connection((IP, 80), timeout=6)
monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
k.sendall("GET / HTTP/1.2\r\nHost:{}\r\nUser-Agent:ServerStatus/cppla\r\nConnection:close\r\n\r\n".format(address).encode('utf-8'))
response = b""
while True:
data = k.recv(4096)
if not data:
break
response += data
http_code = response.decode('utf-8').split('\r\n')[0].split()[1]
monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
k.close()
if http_code not in ['200', '204', '301', '302', '401']:
raise Exception("http code not in 200, 204, 301, 302, 401")
elif type == "https":
context = ssl._create_unverified_context()
address = host.replace("https://", "")
m = timeit.default_timer()
if PROBE_PROTOCOL_PREFER == 'ipv4':
IP = socket.getaddrinfo(address, None, socket.AF_INET)[0][4][0]
else:
IP = socket.getaddrinfo(address, None, socket.AF_INET6)[0][4][0]
monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
k = socket.create_connection((IP, 443), timeout=6)
monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
kk = context.wrap_socket(k, server_hostname=address)
kk.sendall("GET / HTTP/1.2\r\nHost:{}\r\nUser-Agent:ServerStatus/cppla\r\nConnection:close\r\n\r\n".format(address).encode('utf-8'))
response = b""
while True:
data = kk.recv(4096)
if not data:
break
response += data
http_code = response.decode('utf-8').split('\r\n')[0].split()[1]
monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
kk.close()
k.close()
if http_code not in ['200', '204', '301', '302', '401']:
raise Exception("http code not in 200, 204, 301, 302, 401")
elif type == "tcp":
m = timeit.default_timer()
if PROBE_PROTOCOL_PREFER == 'ipv4':
IP = socket.getaddrinfo(host.split(":")[0], None, socket.AF_INET)[0][4][0]
else:
IP = socket.getaddrinfo(host.split(":")[0], None, socket.AF_INET6)[0][4][0]
monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
k = socket.create_connection((IP, int(host.split(":")[1])), timeout=6)
monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
k.send(b"GET / HTTP/1.2\r\n\r\n")
k.recv(1024)
monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
k.close()
packet_queue.put(1)
except Exception as e:
lostPacket += 1
packet_queue.put(0)
if packet_queue.qsize() > 5:
monitorServer[name]["online_rate"] = 1 - float(lostPacket) / packet_queue.qsize()
time.sleep(interval)
def byte_str(object): def byte_str(object):
''' '''
@ -286,7 +429,7 @@ if __name__ == '__main__':
elif 'INTERVAL' in argc: elif 'INTERVAL' in argc:
INTERVAL = int(argc.split('INTERVAL=')[-1]) INTERVAL = int(argc.split('INTERVAL=')[-1])
socket.setdefaulttimeout(30) socket.setdefaulttimeout(30)
get_realtime_date() get_realtime_data()
while True: while True:
try: try:
print("Connecting...") print("Connecting...")
@ -306,6 +449,28 @@ if __name__ == '__main__':
if data.find("You are connecting via") < 0: if data.find("You are connecting via") < 0:
data = byte_str(s.recv(1024)) data = byte_str(s.recv(1024))
print(data) print(data)
monitorServer.clear()
for i in data.split('\n'):
if "monitor" in i and "type" in i and "{" in i and "}" in i:
jdata = json.loads(i[i.find("{"):i.find("}")+1])
monitorServer[jdata.get("name")] = {
"type": jdata.get("type"),
"dns_time": 0,
"connect_time": 0,
"download_time": 0,
"online_rate": 1
}
t = threading.Thread(
target=_monitor_thread,
kwargs={
'name': jdata.get("name"),
'host': jdata.get("host"),
'interval': jdata.get("interval"),
'type': jdata.get("type")
}
)
t.daemon = True
t.start()
timer = 0 timer = 0
check_ip = 0 check_ip = 0
@ -324,8 +489,6 @@ if __name__ == '__main__':
Load_1, Load_5, Load_15 = os.getloadavg() Load_1, Load_5, Load_15 = os.getloadavg()
MemoryTotal, MemoryUsed, SwapTotal, SwapFree = get_memory() MemoryTotal, MemoryUsed, SwapTotal, SwapFree = get_memory()
HDDTotal, HDDUsed = get_hdd() HDDTotal, HDDUsed = get_hdd()
IP_STATUS = ip_status()
array = {} array = {}
if not timer: if not timer:
array['online' + str(check_ip)] = get_network(check_ip) array['online' + str(check_ip)] = get_network(check_ip)
@ -348,7 +511,6 @@ if __name__ == '__main__':
array['network_tx'] = netSpeed.get("nettx") array['network_tx'] = netSpeed.get("nettx")
array['network_in'] = NET_IN array['network_in'] = NET_IN
array['network_out'] = NET_OUT array['network_out'] = NET_OUT
array['ip_status'] = IP_STATUS
array['ping_10010'] = lostRate.get('10010') * 100 array['ping_10010'] = lostRate.get('10010') * 100
array['ping_189'] = lostRate.get('189') * 100 array['ping_189'] = lostRate.get('189') * 100
array['ping_10086'] = lostRate.get('10086') * 100 array['ping_10086'] = lostRate.get('10086') * 100
@ -356,16 +518,20 @@ if __name__ == '__main__':
array['time_189'] = pingTime.get('189') array['time_189'] = pingTime.get('189')
array['time_10086'] = pingTime.get('10086') array['time_10086'] = pingTime.get('10086')
array['tcp'], array['udp'], array['process'], array['thread'] = tupd() array['tcp'], array['udp'], array['process'], array['thread'] = tupd()
array['io_read'] = diskIO.get("read")
array['io_write'] = diskIO.get("write")
array['custom'] = "<br>".join(f"{k}\\t解析: {v['dns_time']}\\t连接: {v['connect_time']}\\t下载: {v['download_time']}\\t在线率: <code>{v['online_rate']*100:.1f}%</code>" for k, v in monitorServer.items())
s.send(byte_str("update " + json.dumps(array) + "\n")) s.send(byte_str("update " + json.dumps(array) + "\n"))
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except socket.error: except socket.error:
monitorServer.clear()
print("Disconnected...") print("Disconnected...")
if 's' in locals().keys(): if 's' in locals().keys():
del s del s
time.sleep(3) time.sleep(3)
except Exception as e: except Exception as e:
monitorServer.clear()
print("Caught Exception:", e) print("Caught Exception:", e)
if 's' in locals().keys(): if 's' in locals().keys():
del s del s

@ -1,40 +1,41 @@
#!/usr/bin/env python #!/usr/bin/env python3
# coding: utf-8 # coding: utf-8
# Update by : https://github.com/cppla/ServerStatus # Update by : https://github.com/cppla/ServerStatus, Update date: 20220530
# 依赖于psutil跨平台库 # 依赖于psutil跨平台库
# 支持Python版本2.7 to 3.7 # 版本1.0.3, 支持Python版本2.7 to 3.10
# 支持操作系统: Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures # 支持操作系统: Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
# 时间: 20200407 # ONLINE_PACKET_HISTORY_LEN 探测间隔1200s记录24小时在线率72探测时间300s记录24小时288探测间隔60s记录7天10080
# 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义例如CU = "www.facebook.com"。 # 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义例如CU = "www.facebook.com"。
SERVER = "127.0.0.1" SERVER = "127.0.0.1"
USER = "s01" USER = "s01"
PORT = 35601
PASSWORD = "USER_DEFAULT_PASSWORD" PASSWORD = "USER_DEFAULT_PASSWORD"
INTERVAL = 1 PORT = 35601
PROBEPORT = 80
PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6
PING_PACKET_HISTORY_LEN = 100
CU = "cu.tz.cloudcpp.com" CU = "cu.tz.cloudcpp.com"
CT = "ct.tz.cloudcpp.com" CT = "ct.tz.cloudcpp.com"
CM = "cm.tz.cloudcpp.com" CM = "cm.tz.cloudcpp.com"
PROBEPORT = 80
PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6
PING_PACKET_HISTORY_LEN = 100
ONLINE_PACKET_HISTORY_LEN = 72
INTERVAL = 1
import socket import socket
import ssl
import time import time
import timeit import timeit
import os import os
import json
import psutil
import sys import sys
import json
import errno
import psutil
import threading import threading
import threading if sys.version_info.major == 3:
try: from queue import Queue
from queue import Queue # python3 elif sys.version_info.major == 2:
except ImportError: from Queue import Queue
from Queue import Queue # python2
def get_uptime(): def get_uptime():
return int(time.time() - psutil.boot_time()) return int(time.time() - psutil.boot_time())
@ -48,18 +49,22 @@ def get_swap():
return int(Mem.total/1024.0), int(Mem.used/1024.0) return int(Mem.total/1024.0), int(Mem.used/1024.0)
def get_hdd(): def get_hdd():
valid_fs = [ "ext4", "ext3", "ext2", "reiserfs", "jfs", "btrfs", "fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs" ] if "darwin" in sys.platform:
disks = dict() return int(psutil.disk_usage("/").total/1024.0/1024.0), int((psutil.disk_usage("/").total-psutil.disk_usage("/").free)/1024.0/1024.0)
size = 0 else:
used = 0 valid_fs = ["ext4", "ext3", "ext2", "reiserfs", "jfs", "btrfs", "fuseblk", "zfs", "simfs", "ntfs", "fat32",
for disk in psutil.disk_partitions(): "exfat", "xfs"]
if not disk.device in disks and disk.fstype.lower() in valid_fs: disks = dict()
disks[disk.device] = disk.mountpoint size = 0
for disk in disks.values(): used = 0
usage = psutil.disk_usage(disk) for disk in psutil.disk_partitions():
size += usage.total if not disk.device in disks and disk.fstype.lower() in valid_fs:
used += usage.used disks[disk.device] = disk.mountpoint
return int(size/1024.0/1024.0), int(used/1024.0/1024.0) for disk in disks.values():
usage = psutil.disk_usage(disk)
size += usage.total
used += usage.used
return int(size/1024.0/1024.0), int(used/1024.0/1024.0)
def get_cpu(): def get_cpu():
return psutil.cpu_percent(interval=INTERVAL) return psutil.cpu_percent(interval=INTERVAL)
@ -90,31 +95,29 @@ def tupd():
u = int(os.popen('ss -u|wc -l').read()[:-1])-1 u = int(os.popen('ss -u|wc -l').read()[:-1])-1
p = int(os.popen('ps -ef|wc -l').read()[:-1])-2 p = int(os.popen('ps -ef|wc -l').read()[:-1])-2
d = int(os.popen('ps -eLf|wc -l').read()[:-1])-2 d = int(os.popen('ps -eLf|wc -l').read()[:-1])-2
elif sys.platform.startswith("darwin") is True:
t = int(os.popen('lsof -nP -iTCP | wc -l').read()[:-1]) - 1
u = int(os.popen('lsof -nP -iUDP | wc -l').read()[:-1]) - 1
p = len(psutil.pids())
d = 0
for k in psutil.pids():
try:
d += psutil.Process(k).num_threads()
except:
pass
elif sys.platform.startswith("win") is True: elif sys.platform.startswith("win") is True:
t = int(os.popen('netstat -an|find "TCP" /c').read()[:-1])-1 t = int(os.popen('netstat -an|find "TCP" /c').read()[:-1])-1
u = int(os.popen('netstat -an|find "UDP" /c').read()[:-1])-1 u = int(os.popen('netstat -an|find "UDP" /c').read()[:-1])-1
p = len(psutil.pids()) p = len(psutil.pids())
d = 0 # if you find cpu is high, please set d=0
# cpu is high, default: 0 d = sum([psutil.Process(k).num_threads() for k in psutil.pids()])
# d = sum([psutil.Process(k).num_threads() for k in [x for x in psutil.pids()]])
else: else:
t,u,p,d = 0,0,0,0 t,u,p,d = 0,0,0,0
return t,u,p,d return t,u,p,d
except: except:
return 0,0,0,0 return 0,0,0,0
def ip_status():
ip_check = 0
for i in [CU, CT, CM]:
try:
socket.create_connection((i, PROBEPORT), timeout=1).close()
except:
ip_check += 1
if ip_check >= 2:
return False
else:
return True
def get_network(ip_version): def get_network(ip_version):
if(ip_version == 4): if(ip_version == 4):
HOST = "ipv4.google.com" HOST = "ipv4.google.com"
@ -144,22 +147,28 @@ netSpeed = {
'avgrx': 0, 'avgrx': 0,
'avgtx': 0 'avgtx': 0
} }
diskIO = {
'read': 0,
'write': 0
}
monitorServer = {}
def _ping_thread(host, mark, port): def _ping_thread(host, mark, port):
lostPacket = 0 lostPacket = 0
packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN) packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN)
IP = host while True:
if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname # flush dns, every time.
try: IP = host
if PROBE_PROTOCOL_PREFER == 'ipv4': if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname
IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0] try:
else: if PROBE_PROTOCOL_PREFER == 'ipv4':
IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0] IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0]
except Exception: else:
IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0]
except Exception:
pass pass
while True:
if packet_queue.full(): if packet_queue.full():
if packet_queue.get() == 0: if packet_queue.get() == 0:
lostPacket -= 1 lostPacket -= 1
@ -203,7 +212,67 @@ def _net_speed():
netSpeed["avgtx"] = avgtx netSpeed["avgtx"] = avgtx
time.sleep(INTERVAL) time.sleep(INTERVAL)
def get_realtime_date(): def _disk_io():
"""
the code is by: https://github.com/giampaolo/psutil/blob/master/scripts/iotop.py
good luck for opensource! modify: cpp.la
Calculate IO usage by comparing IO statics before and
after the interval.
Return a tuple including all currently running processes
sorted by IO activity and total disks I/O activity.
磁盘IO因为IOPS原因SSD和HDD包括RAID卡ZFS等IO对性能的影响还需要结合自身服务器情况来判断
比如我这里是机械硬盘大量做随机小文件读写那么很低的读写也就能造成硬盘长时间的等待
如果这里做连续性IO那么普通机械硬盘写入到100Mb/s那么也能造成硬盘长时间的等待
磁盘读写有误差4k8k https://stackoverflow.com/questions/34413926/psutil-vs-dd-monitoring-disk-i-o
macos/win暂不处理
"""
if "darwin" in sys.platform or "win" in sys.platform:
diskIO["read"] = 0
diskIO["write"] = 0
else:
while True:
# first get a list of all processes and disk io counters
procs = [p for p in psutil.process_iter()]
for p in procs[:]:
try:
p._before = p.io_counters()
except psutil.Error:
procs.remove(p)
continue
disks_before = psutil.disk_io_counters()
# sleep some time, only when INTERVAL==1 , io read/write per_sec.
# when INTERVAL > 1, io read/write per_INTERVAL
time.sleep(INTERVAL)
# then retrieve the same info again
for p in procs[:]:
with p.oneshot():
try:
p._after = p.io_counters()
p._cmdline = ' '.join(p.cmdline())
if not p._cmdline:
p._cmdline = p.name()
p._username = p.username()
except (psutil.NoSuchProcess, psutil.ZombieProcess):
procs.remove(p)
disks_after = psutil.disk_io_counters()
# finally calculate results by comparing data before and
# after the interval
for p in procs:
p._read_per_sec = p._after.read_bytes - p._before.read_bytes
p._write_per_sec = p._after.write_bytes - p._before.write_bytes
p._total = p._read_per_sec + p._write_per_sec
diskIO["read"] = disks_after.read_bytes - disks_before.read_bytes
diskIO["write"] = disks_after.write_bytes - disks_before.write_bytes
def get_realtime_data():
'''
real time get system data
:return:
'''
t1 = threading.Thread( t1 = threading.Thread(
target=_ping_thread, target=_ping_thread,
kwargs={ kwargs={
@ -231,14 +300,97 @@ def get_realtime_date():
t4 = threading.Thread( t4 = threading.Thread(
target=_net_speed, target=_net_speed,
) )
t1.setDaemon(True) t5 = threading.Thread(
t2.setDaemon(True) target=_disk_io,
t3.setDaemon(True) )
t4.setDaemon(True) for ti in [t1, t2, t3, t4, t5]:
t1.start() ti.daemon = True
t2.start() ti.start()
t3.start()
t4.start() def _monitor_thread(name, host, interval, type):
lostPacket = 0
packet_queue = Queue(maxsize=ONLINE_PACKET_HISTORY_LEN)
while True:
if name not in monitorServer.keys():
break
if packet_queue.full():
if packet_queue.get() == 0:
lostPacket -= 1
try:
if type == "http":
address = host.replace("http://", "")
m = timeit.default_timer()
if PROBE_PROTOCOL_PREFER == 'ipv4':
IP = socket.getaddrinfo(address, None, socket.AF_INET)[0][4][0]
else:
IP = socket.getaddrinfo(address, None, socket.AF_INET6)[0][4][0]
monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
k = socket.create_connection((IP, 80), timeout=6)
monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
k.sendall("GET / HTTP/1.2\r\nHost:{}\r\nUser-Agent:ServerStatus/cppla\r\nConnection:close\r\n\r\n".format(address).encode('utf-8'))
response = b""
while True:
data = k.recv(4096)
if not data:
break
response += data
http_code = response.decode('utf-8').split('\r\n')[0].split()[1]
monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
k.close()
if http_code not in ['200', '204', '301', '302', '401']:
raise Exception("http code not in 200, 204, 301, 302, 401")
elif type == "https":
context = ssl._create_unverified_context()
address = host.replace("https://", "")
m = timeit.default_timer()
if PROBE_PROTOCOL_PREFER == 'ipv4':
IP = socket.getaddrinfo(address, None, socket.AF_INET)[0][4][0]
else:
IP = socket.getaddrinfo(address, None, socket.AF_INET6)[0][4][0]
monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
k = socket.create_connection((IP, 443), timeout=6)
monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
kk = context.wrap_socket(k, server_hostname=address)
kk.sendall("GET / HTTP/1.2\r\nHost:{}\r\nUser-Agent:ServerStatus/cppla\r\nConnection:close\r\n\r\n".format(address).encode('utf-8'))
response = b""
while True:
data = kk.recv(4096)
if not data:
break
response += data
http_code = response.decode('utf-8').split('\r\n')[0].split()[1]
monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
kk.close()
k.close()
if http_code not in ['200', '204', '301', '302', '401']:
raise Exception("http code not in 200, 204, 301, 302, 401")
elif type == "tcp":
m = timeit.default_timer()
if PROBE_PROTOCOL_PREFER == 'ipv4':
IP = socket.getaddrinfo(host.split(":")[0], None, socket.AF_INET)[0][4][0]
else:
IP = socket.getaddrinfo(host.split(":")[0], None, socket.AF_INET6)[0][4][0]
monitorServer[name]["dns_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
k = socket.create_connection((IP, int(host.split(":")[1])), timeout=6)
monitorServer[name]["connect_time"] = int((timeit.default_timer() - m) * 1000)
m = timeit.default_timer()
k.send(b"GET / HTTP/1.2\r\n\r\n")
k.recv(1024)
monitorServer[name]["download_time"] = int((timeit.default_timer() - m) * 1000)
k.close()
packet_queue.put(1)
except Exception as e:
lostPacket += 1
packet_queue.put(0)
if packet_queue.qsize() > 5:
monitorServer[name]["online_rate"] = 1 - float(lostPacket) / packet_queue.qsize()
time.sleep(interval)
def byte_str(object): def byte_str(object):
''' '''
@ -266,7 +418,7 @@ if __name__ == '__main__':
elif 'INTERVAL' in argc: elif 'INTERVAL' in argc:
INTERVAL = int(argc.split('INTERVAL=')[-1]) INTERVAL = int(argc.split('INTERVAL=')[-1])
socket.setdefaulttimeout(30) socket.setdefaulttimeout(30)
get_realtime_date() get_realtime_data()
while 1: while 1:
try: try:
print("Connecting...") print("Connecting...")
@ -286,6 +438,27 @@ if __name__ == '__main__':
if data.find("You are connecting via") < 0: if data.find("You are connecting via") < 0:
data = byte_str(s.recv(1024)) data = byte_str(s.recv(1024))
print(data) print(data)
for i in data.split('\n'):
if "monitor" in i and "type" in i and "{" in i and "}" in i:
jdata = json.loads(i[i.find("{"):i.find("}")+1])
monitorServer[jdata.get("name")] = {
"type": jdata.get("type"),
"dns_time": 0,
"connect_time": 0,
"download_time": 0,
"online_rate": 1
}
t = threading.Thread(
target=_monitor_thread,
kwargs={
'name': jdata.get("name"),
'host': jdata.get("host"),
'interval': jdata.get("interval"),
'type': jdata.get("type")
}
)
t.daemon = True
t.start()
timer = 0 timer = 0
check_ip = 0 check_ip = 0
@ -301,12 +474,10 @@ if __name__ == '__main__':
CPU = get_cpu() CPU = get_cpu()
NET_IN, NET_OUT = liuliang() NET_IN, NET_OUT = liuliang()
Uptime = get_uptime() Uptime = get_uptime()
Load_1, Load_5, Load_15 = os.getloadavg() if 'linux' in sys.platform else (0.0, 0.0, 0.0) Load_1, Load_5, Load_15 = os.getloadavg() if 'linux' in sys.platform or 'darwin' in sys.platform else (0.0, 0.0, 0.0)
MemoryTotal, MemoryUsed = get_memory() MemoryTotal, MemoryUsed = get_memory()
SwapTotal, SwapUsed = get_swap() SwapTotal, SwapUsed = get_swap()
HDDTotal, HDDUsed = get_hdd() HDDTotal, HDDUsed = get_hdd()
IP_STATUS = ip_status()
array = {} array = {}
if not timer: if not timer:
array['online' + str(check_ip)] = get_network(check_ip) array['online' + str(check_ip)] = get_network(check_ip)
@ -329,7 +500,6 @@ if __name__ == '__main__':
array['network_tx'] = netSpeed.get("nettx") array['network_tx'] = netSpeed.get("nettx")
array['network_in'] = NET_IN array['network_in'] = NET_IN
array['network_out'] = NET_OUT array['network_out'] = NET_OUT
array['ip_status'] = IP_STATUS
array['ping_10010'] = lostRate.get('10010') * 100 array['ping_10010'] = lostRate.get('10010') * 100
array['ping_189'] = lostRate.get('189') * 100 array['ping_189'] = lostRate.get('189') * 100
array['ping_10086'] = lostRate.get('10086') * 100 array['ping_10086'] = lostRate.get('10086') * 100
@ -337,16 +507,20 @@ if __name__ == '__main__':
array['time_189'] = pingTime.get('189') array['time_189'] = pingTime.get('189')
array['time_10086'] = pingTime.get('10086') array['time_10086'] = pingTime.get('10086')
array['tcp'], array['udp'], array['process'], array['thread'] = tupd() array['tcp'], array['udp'], array['process'], array['thread'] = tupd()
array['io_read'] = diskIO.get("read")
array['io_write'] = diskIO.get("write")
array['custom'] = "<br>".join(f"{k}\\t解析: {v['dns_time']}\\t连接: {v['connect_time']}\\t下载: {v['download_time']}\\t在线率: <code>{v['online_rate']*100:.2f}%</code>" for k, v in monitorServer.items())
s.send(byte_str("update " + json.dumps(array) + "\n")) s.send(byte_str("update " + json.dumps(array) + "\n"))
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except socket.error: except socket.error:
monitorServer.clear()
print("Disconnected...") print("Disconnected...")
if 's' in locals().keys(): if 's' in locals().keys():
del s del s
time.sleep(3) time.sleep(3)
except Exception as e: except Exception as e:
monitorServer.clear()
print("Caught Exception:", e) print("Caught Exception:", e)
if 's' in locals().keys(): if 's' in locals().keys():
del s del s

29
docker-compose.yml Normal file

@ -0,0 +1,29 @@
version: "3"
services:
serverstatus:
build:
context: .
dockerfile: Dockerfile
image: cppla/serverstatus:latest
healthcheck:
test: curl --fail http://localhost:80 || bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'
interval: 30s
timeout: 10s
retries: 5
container_name: serverstatus
restart: unless-stopped
networks:
serverstatus-network:
ipv4_address: 172.23.0.2
volumes:
- ./server/config.json:/ServerStatus/server/config.json
- ./web/json:/usr/share/nginx/html/json
ports:
- 35601:35601
- 8080:80
networks:
serverstatus-network:
ipam:
config:
- subnet: 172.23.0.0/24

@ -0,0 +1,10 @@
FROM python:alpine
LABEL maintainer="lidalao"
LABEL version="0.0.1"
LABEL description="Telegram Bot for ServerStatus"
WORKDIR /app
RUN pip install requests
COPY ./bot-telegram.py .
CMD [ "python", "./bot-telegram.py" ]

73
plugin/bot-telegram.py Normal file

@ -0,0 +1,73 @@
#!/usr/bin/env python3
# coding: utf-8
# Create by : https://github.com/lidalao/ServerStatus
# 版本0.0.1, 支持Python版本2.7 to 3.9
# 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
import os
import sys
import requests
import time
import traceback
NODE_STATUS_URL = 'http://serverstatus/json/stats.json'
offs = []
counterOff = {}
counterOn = {}
def _send(text):
chat_id = os.getenv('TG_CHAT_ID')
bot_token = os.environ.get('TG_BOT_TOKEN')
url = f"https://api.telegram.org/bot{bot_token}/sendMessage?parse_mode=HTML&disable_web_page_preview=true&chat_id=" + chat_id + "&text=" + text
try:
requests.get(url)
except Exception as e:
print("catch exception: ", traceback.format_exc())
def send2tg(srv, flag):
if srv not in counterOff:
counterOff[srv] = 0
if srv not in counterOn:
counterOn[srv] = 0
if flag == 1 : # online
if srv in offs:
if counterOn[srv] < 10:
counterOn[srv] += 1
return
#1. Remove srv from offs; 2. Send to tg: I am online
offs.remove(srv)
counterOn[srv] = 0
text = '<b>Server Status</b>' + '\n主机上线: ' + srv
_send(text)
else: #offline
if srv not in offs:
if counterOff[srv] < 10:
counterOff[srv] += 1
return
#1. Append srv to offs; 2. Send to tg: I am offline
offs.append(srv)
counterOff[srv] = 0
text = '<b>Server Status</b>' + '\n主机下线: ' + srv
_send(text)
def sscmd(address):
while True:
r = requests.get(url=address, headers={"User-Agent": "ServerStatus/20211116"})
try:
jsonR = r.json()
except Exception as e:
print('未发现任何节点')
continue
for i in jsonR["servers"]:
if i["online4"] is False and i["online6"] is False:
send2tg(i["name"], 0)
else:
send2tg(i["name"], 1)
time.sleep(3)
if __name__ == '__main__':
sscmd(NODE_STATUS_URL)

@ -0,0 +1,38 @@
version: "3"
services:
serverstatus:
build:
context: ..
dockerfile: Dockerfile
image: serverstatus_server
container_name: serverstatus
restart: unless-stopped
networks:
serverstatus-network:
ipv4_address: 172.23.0.2
volumes:
- ../server/config.json:/ServerStatus/server/config.json
- ../web/json:/usr/share/nginx/html/json
ports:
- 35601:35601
- 8080:80
bot:
build:
context: .
dockerfile: Dockerfile-telegram
image: serverstatus_bot
container_name: bot4sss
restart: unless-stopped
networks:
serverstatus-network:
ipv4_address: 172.23.0.3
environment:
- TG_CHAT_ID=${TG_CHAT_ID}
- TG_BOT_TOKEN=${TG_BOT_TOKEN}
networks:
serverstatus-network:
name: serverstatus-network
ipam:
config:
- subnet: 172.23.0.0/24

@ -6,7 +6,7 @@ CFLAGS = -Wall -O2
#CXX = clang++ #CXX = clang++
CXX = g++ CXX = g++
CXXFLAGS = -Wall -O2 CXXFLAGS = -Wall -O2 -std=c++11
ODIR = obj ODIR = obj
SDIR = src SDIR = src
@ -26,9 +26,9 @@ $(ODIR)/%.o: $(SDIR)/%.cpp
$(CXX) -c $(INC) $(CXXFLAGS) $< -o $@ $(CXX) -c $(INC) $(CXXFLAGS) $< -o $@
$(OUT): $(OBJS) $(OUT): $(OBJS)
$(CXX) $(LIBS) $^ -o $(OUT) $(CXX) $(LIBS) $^ -o $(OUT) -lcurl
.PHONY: clean .PHONY: clean
clean: clean:
rm -f $(ODIR)/*.o $(OUT) rm -f $(ODIR)/*.o $(OUT)

@ -1,37 +1,128 @@
{"servers": {
[ "servers": [
{ {
"username": "s01", "username": "s01",
"name": "node1", "name": "node1",
"type": "xen", "type": "xen",
"host": "host1", "host": "host1",
"location": "cn", "location": "🇨🇳",
"password": "USER_DEFAULT_PASSWORD" "password": "USER_DEFAULT_PASSWORD",
"monthstart": 1
}, },
{ {
"username": "s02", "username": "s02",
"name": "node2", "name": "node2",
"type": "vmware", "type": "vmware",
"host": "host2", "host": "host2",
"location": "jp", "location": "🇯🇵",
"password": "USER_DEFAULT_PASSWORD" "password": "USER_DEFAULT_PASSWORD",
"monthstart": 1
}, },
{ {
"disabled": true, "disabled": true,
"username": "s03", "username": "s03",
"name": "node3", "name": "node3",
"type": "Nothing", "type": "hyper",
"host": "host3", "host": "host3",
"location": "fr", "location": "🇫🇷",
"password": "USER_DEFAULT_PASSWORD" "password": "USER_DEFAULT_PASSWORD",
"monthstart": 1
}, },
{ {
"username": "s04", "username": "s04",
"name": "node4", "name": "node4",
"type": "kvm", "type": "kvm",
"host": "host4", "host": "host4",
"location": "kr", "location": "🇰🇷",
"password": "USER_DEFAULT_PASSWORD" "password": "USER_DEFAULT_PASSWORD",
"monthstart": 1
}
],
"monitors": [
{
"name": "baidu",
"host": "https://www.baidu.com",
"interval": 1200,
"type": "https"
},
{
"name": "1111",
"host": "1.1.1.1:80",
"interval": 1200,
"type": "tcp"
}
],
"sslcerts": [
{
"name": "my.cloudcpp.com",
"domain": "https://my.cloudcpp.com",
"port": 443,
"interval": 3600,
"callback": "https://yourSMSurl"
},
{
"name": "tz.cloudcpp.com",
"domain": "https://tz.cloudcpp.com",
"port": 443,
"interval": 3600,
"callback": "https://yourSMSurl"
},
{
"name": "3.0.2.1",
"domain": "https://3.0.2.1",
"port": 443,
"interval": 3600,
"callback": "https://yourSMSurl"
}
],
"watchdog": [
{
"name": "cpu high warning,exclude username s01",
"rule": "cpu>90&load_1>5&username!='s01'",
"interval": 600,
"callback": "https://yourSMSurl"
},
{
"name": "memory high warning, exclude less than 1GB vps",
"rule": "(memory_used/memory_total)*100>90&memory_total>1048576",
"interval": 300,
"callback": "https://yourSMSurl"
},
{
"name": "offline warning",
"rule": "online4=0&online6=0",
"interval": 600,
"callback": "https://yourSMSurl"
},
{
"name": "ddcc attack,limit type Oracle",
"rule": "tcp_count>600&type='Oracle'",
"interval": 300,
"callback": "https://yourSMSurl"
},
{
"name": "month 999GB traffic warning",
"rule": "(network_out-last_network_out)/1024/1024/1024>999",
"interval": 3600,
"callback": "https://yourSMSurl"
},
{
"name": "aliyun china free 18GB traffic warning",
"rule": "(network_out-last_network_out)/1024/1024/1024>18&(username='aliyun1'|username='aliyun2')",
"interval": 3600,
"callback": "https://yourSMSurl"
},
{
"name": "packet loss rate warning",
"rule": "(ping_10010>10|ping_189>10|ping_10086>10)&(host='sgp'|host='qqhk'|host='hk-21-x'|host='hk-31-x')",
"interval": 3600,
"callback": "https://yourSMSurl"
},
{
"name": "you can parse an expression combining any known field",
"rule": "load_5>3",
"interval": 900,
"callback": "https://yourSMSurl"
} }
] ]
} }

46066
server/src/exprtk.hpp Normal file

File diff suppressed because it is too large Load Diff

@ -131,13 +131,15 @@ static int new_value
values_size = sizeof (*value->u.object.values) * value->u.object.length; values_size = sizeof (*value->u.object.values) * value->u.object.length;
if (! ((*(void **) &value->u.object.values) = json_alloc void *tmp_alloc = json_alloc(state, values_size + ((unsigned long) value->u.object.values), 0);
(state, values_size + ((unsigned long) value->u.object.values), 0)) ) if (!tmp_alloc)
{ {
return 0; return 0;
} }
/* 避免违反严格别名:通过中间变量复制 */
value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size; memcpy(&value->u.object.values, &tmp_alloc, sizeof(void*));
char *obj_mem = (char*)value->u.object.values + values_size;
memcpy(&value->_reserved.object_mem, &obj_mem, sizeof(char*));
value->u.object.length = 0; value->u.object.length = 0;
break; break;
@ -361,16 +363,18 @@ json_value * json_parse_ex (json_settings * settings,
case json_object: case json_object:
if (state.first_pass) if (state.first_pass)
(*(json_char **) &top->u.object.values) += string_length + 1; {
json_char *adv = (json_char*)top->u.object.values;
adv += string_length + 1;
memcpy(&top->u.object.values, &adv, sizeof(json_char*));
}
else else
{ {
top->u.object.values [top->u.object.length].name top->u.object.values[top->u.object.length].name = (json_char *)top->_reserved.object_mem;
= (json_char *) top->_reserved.object_mem; top->u.object.values[top->u.object.length].name_length = string_length;
json_char *adv2 = (json_char*)top->_reserved.object_mem;
top->u.object.values [top->u.object.length].name_length adv2 += string_length + 1;
= string_length; memcpy(&top->_reserved.object_mem, &adv2, sizeof(json_char*));
(*(json_char **) &top->_reserved.object_mem) += string_length + 1;
} }
flags |= flag_seek_value | flag_need_colon; flags |= flag_seek_value | flag_need_colon;

@ -7,6 +7,186 @@
#include <json.h> #include <json.h>
#include "server.h" #include "server.h"
#include "main.h" #include "main.h"
#include "exprtk.hpp"
#include "curl/curl.h"
#include <stdio.h>
#include <stdlib.h>
#include <string>
// 全局运行标志(需在 SSLCheckThread 定义前初始化)
static volatile int gs_Running = 1;
static volatile int gs_ReloadConfig = 0;
static int64_t ParseOpenSSLEnddate(const char *line)
{
// line format: notAfter=Aug 12 23:59:59 2025 GMT
const char *p = strstr(line, "notAfter=");
if(!p) return 0;
p += 9;
struct tm tmv; memset(&tmv,0,sizeof(tmv));
char month[4]={0};
int day, hour, min, sec, year;
if(sscanf(p, "%3s %d %d:%d:%d %d GMT", month, &day, &hour, &min, &sec, &year)!=6) return 0;
const char *months="JanFebMarAprMayJunJulAugSepOctNovDec";
const char *mpos = strstr(months, month);
if(!mpos) return 0;
int mon = (int)((mpos - months)/3);
tmv.tm_year = year - 1900;
tmv.tm_mon = mon;
tmv.tm_mday = day;
tmv.tm_hour = hour; tmv.tm_min = min; tmv.tm_sec = sec;
time_t t = timegm(&tmv);
return (int64_t)t;
}
struct SSLCheckThreadData { CMain *pMain; };
static void SSLCheckThread(void *pUser)
{
SSLCheckThreadData *pData = (SSLCheckThreadData*)pUser;
while(gs_Running){
for(int i=0;i<NET_MAX_CLIENTS;i++){
if(!pData->pMain->SSLCert(i) || !strcmp(pData->pMain->SSLCert(i)->m_aName, "NULL")) break;
CMain::CSSLCerts *cert = pData->pMain->SSLCert(i);
time_t nowt = time(0);
if(cert->m_aLastCheck !=0 && (nowt - cert->m_aLastCheck) < cert->m_aInterval) continue;
cert->m_aLastCheck = nowt;
char cmd[1024];
// 说明: 通过 s_client 获取证书,再用 x509 解析到期时间;统一屏蔽 stderr 以防握手失败/非 TLS 端口时刷屏。
// 若配置中写成 https://domain/path 则需要清洗。
char cleanHost[256];
str_copy(cleanHost, cert->m_aDomain, sizeof(cleanHost));
// 去协议
if(!strncasecmp(cleanHost, "https://", 8)) memmove(cleanHost, cleanHost+8, strlen(cleanHost+8)+1);
else if(!strncasecmp(cleanHost, "http://", 7)) memmove(cleanHost, cleanHost+7, strlen(cleanHost+7)+1);
// 去路径
char *slash = strchr(cleanHost, '/'); if(slash) *slash='\0';
// 若含 :port 再截取主机部分(端口由配置提供)
char *colon = strchr(cleanHost, ':'); if(colon) *colon='\0';
int n = snprintf(cmd,sizeof(cmd),"echo | openssl s_client -servername %s -connect %s:%d </dev/null 2>/dev/null | openssl x509 -noout -enddate -text 2>/dev/null", cleanHost, cleanHost, cert->m_aPort);
if(n <= 0 || n >= (int)sizeof(cmd)) continue; // 避免截断执行
FILE *fp = popen(cmd, "r");
if(!fp) continue;
char line[1024]={0};
int foundEnddate=0;
int mismatch = 1; // 默认视为不匹配发现任一匹配域名再置0
int haveNames = 0;
// 将目标域名转为小写
char target[256]; str_copy(target, cleanHost, sizeof(target));
for(char *p=target; *p; ++p) *p=tolower(*p);
while(fgets(line,sizeof(line),fp)){
if(!foundEnddate){
int64_t expire = ParseOpenSSLEnddate(line);
if(expire>0){ cert->m_aExpireTS = expire; foundEnddate=1; }
}
// 解析 subjectAltName
// 解析 Subject 中的 CN备用
char *subj = strstr(line, "Subject:");
if(subj){
char *cn = strstr(subj, " CN=");
if(cn){
cn += 4; // 跳过 ' CN='
char name[256]={0}; int ni=0;
while(*cn && *cn!='/' && *cn!=',' && *cn!='\n' && ni<(int)sizeof(name)-1){ name[ni++]=*cn++; }
name[ni]='\0';
while(ni>0 && (name[ni-1]==' '||name[ni-1]=='\r'||name[ni-1]=='\t')){ name[--ni]='\0'; }
for(char *q=name; *q; ++q) *q=tolower(*q);
if(ni>0){
haveNames=1;
int match=0;
if(name[0]=='*' && name[1]=='.'){
const char *sub = strchr(target,'.');
if(sub && !strcmp(sub+1, name+2)) match=1;
}else if(!strcmp(name,target)) match=1;
if(match){ mismatch=0; }
}
}
}
if(strstr(line, "DNS:")){
char *p = line;
while((p = strstr(p, "DNS:"))){
p += 4; while(*p==' '){p++;}
char name[256]={0}; int ni=0;
while(*p && *p!=',' && *p!='\n' && ni<(int)sizeof(name)-1){ name[ni++]=*p++; }
name[ni]='\0';
// 去空白
while(ni>0 && (name[ni-1]==' '||name[ni-1]=='\r'||name[ni-1]=='\t')){ name[--ni]='\0'; }
for(char *q=name; *q; ++q) *q=tolower(*q);
haveNames=1;
// 通配符匹配 *.example.com
int match=0;
if(name[0]=='*' && name[1]=='.'){
const char *sub = strchr(target,'.');
if(sub && !strcmp(sub+1, name+2)) match=1;
}else if(!strcmp(name,target)) match=1;
if(match){ mismatch=0; goto names_done; }
}
}
}
names_done:
pclose(fp);
if(haveNames){ cert->m_aHostnameMismatch = mismatch ? 1 : 0; }
else { /* 未能提取任何域名,保留原状态,不触发误报 */ }
// 告警: 仅在不匹配且 24h 冷却
if(cert->m_aHostnameMismatch==1){
if(cert->m_aLastAlarmMismatch==0 || nowt - cert->m_aLastAlarmMismatch > 24*3600){
if(strlen(cert->m_aCallback)>0){
CURL *curl = curl_easy_init();
if(curl){
char msg[1024];
snprintf(msg,sizeof(msg),"【SSL证书域名不匹配】%s(%s) 证书域名与配置不一致", cert->m_aName, cert->m_aDomain);
char *enc = curl_easy_escape(curl,msg,0);
char url[1500]; snprintf(url,sizeof(url),"%s%s", cert->m_aCallback, enc?enc:"");
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "signature=ServerStatusSSL");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 6L);
curl_easy_perform(curl);
if(enc) curl_free(enc);
curl_easy_cleanup(curl);
}
}
cert->m_aLastAlarmMismatch = nowt;
}
}
// alarm logic
if(cert->m_aExpireTS>0){
int days = (int)((cert->m_aExpireTS - nowt)/86400);
int64_t *lastAlarm = NULL; int need=0; int target=0;
if(days <=7 && days >3){ lastAlarm=&cert->m_aLastAlarm7; target=7; }
else if(days <=3 && days >1){ lastAlarm=&cert->m_aLastAlarm3; target=3; }
else if(days <=1){ lastAlarm=&cert->m_aLastAlarm1; target=1; }
if(lastAlarm && (*lastAlarm==0 || nowt - *lastAlarm > 20*3600)) need=1; // avoid spam, 20h
if(need && strlen(cert->m_aCallback)>0){
CURL *curl = curl_easy_init();
if(curl){
char msg[1024];
char timebuf[32];
time_t expt = (time_t)cert->m_aExpireTS;
strftime(timebuf,sizeof(timebuf),"%Y-%m-%d %H:%M:%S", gmtime(&expt));
snprintf(msg,sizeof(msg),"【SSL证书提醒】%s(%s) 将在 %d 天后(%s UTC) 到期", cert->m_aName, cert->m_aDomain, target, timebuf);
char *enc = curl_easy_escape(curl,msg,0);
char url[1500]; snprintf(url,sizeof(url),"%s%s", cert->m_aCallback, enc?enc:"");
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "signature=ServerStatusSSL");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 6L);
curl_easy_perform(curl);
if(enc) curl_free(enc);
curl_easy_cleanup(curl);
}
*lastAlarm = nowt;
}
}
}
thread_sleep(5000);
}
}
#if defined(CONF_FAMILY_UNIX) #if defined(CONF_FAMILY_UNIX)
#include <signal.h> #include <signal.h>
@ -16,9 +196,6 @@
#define PRId64 "I64d" #define PRId64 "I64d"
#endif #endif
static volatile int gs_Running = 1;
static volatile int gs_ReloadConfig = 0;
static void ExitFunc(int Signal) static void ExitFunc(int Signal)
{ {
printf("[EXIT] Caught signal %d\n", Signal); printf("[EXIT] Caught signal %d\n", Signal);
@ -90,6 +267,18 @@ void CMain::OnNewClient(int ClientNetID, int ClientID)
Client(ClientID)->m_Stats.m_Online4 = true; Client(ClientID)->m_Stats.m_Online4 = true;
else if(Client(ClientID)->m_ClientNetType == NETTYPE_IPV6) else if(Client(ClientID)->m_ClientNetType == NETTYPE_IPV6)
Client(ClientID)->m_Stats.m_Online6 = true; Client(ClientID)->m_Stats.m_Online6 = true;
// Send monitor to client
// support by cpp.la
int ID = 0;
char monitorBuffer[2048];
while (strcmp(Monitors(ID)->m_aName, "NULL"))
{
memset(monitorBuffer, 0, sizeof(monitorBuffer));
sprintf(monitorBuffer, "{\"name\":\"%s\",\"host\":\"%s\",\"interval\":%d,\"type\":\"%s\",\"monitor\":%d}", Monitors(ID)->m_aName, Monitors(ID)->m_aHost, Monitors(ID)->m_aInterval, Monitors(ID)->m_aType, ID);
m_Server.Network()->Send(ClientNetID, monitorBuffer);
ID++;
}
} }
void CMain::OnDelClient(int ClientNetID) void CMain::OnDelClient(int ClientNetID)
@ -103,6 +292,10 @@ void CMain::OnDelClient(int ClientNetID)
Client(ClientID)->m_ClientNetType = NETTYPE_INVALID; Client(ClientID)->m_ClientNetType = NETTYPE_INVALID;
mem_zero(&Client(ClientID)->m_Stats, sizeof(CClient::CStats)); mem_zero(&Client(ClientID)->m_Stats, sizeof(CClient::CStats));
} }
m_OfflineAlarmThreadData.pClients = m_aClients;
m_OfflineAlarmThreadData.pWatchDogs = m_aCWatchDogs;
m_OfflineAlarmThreadData.m_ReloadRequired = ClientID;
thread_create(offlineAlarmThread, &m_OfflineAlarmThreadData);
} }
int CMain::HandleMessage(int ClientNetID, char *pMessage) int CMain::HandleMessage(int ClientNetID, char *pMessage)
@ -178,36 +371,49 @@ int CMain::HandleMessage(int ClientNetID, char *pMessage)
pClient->m_Stats.m_HDDTotal = rStart["hdd_total"].u.integer; pClient->m_Stats.m_HDDTotal = rStart["hdd_total"].u.integer;
if(rStart["hdd_used"].type) if(rStart["hdd_used"].type)
pClient->m_Stats.m_HDDUsed = rStart["hdd_used"].u.integer; pClient->m_Stats.m_HDDUsed = rStart["hdd_used"].u.integer;
if(rStart["io_read"].type)
pClient->m_Stats.m_IORead = rStart["io_read"].u.integer;
if(rStart["io_write"].type)
pClient->m_Stats.m_IOWrite = rStart["io_write"].u.integer;
if(rStart["cpu"].type) if(rStart["cpu"].type)
pClient->m_Stats.m_CPU = rStart["cpu"].u.dbl; pClient->m_Stats.m_CPU = rStart["cpu"].u.dbl;
if(rStart["online4"].type && pClient->m_ClientNetType == NETTYPE_IPV6) if(rStart["online4"].type && pClient->m_ClientNetType == NETTYPE_IPV6)
pClient->m_Stats.m_Online4 = rStart["online4"].u.boolean; pClient->m_Stats.m_Online4 = rStart["online4"].u.boolean;
if(rStart["online6"].type && pClient->m_ClientNetType == NETTYPE_IPV4) if(rStart["online6"].type && pClient->m_ClientNetType == NETTYPE_IPV4)
pClient->m_Stats.m_Online6 = rStart["online6"].u.boolean; pClient->m_Stats.m_Online6 = rStart["online6"].u.boolean;
if(rStart["ip_status"].type)
pClient->m_Stats.m_IpStatus = rStart["ip_status"].u.boolean;
if(rStart["custom"].type == json_string) if(rStart["custom"].type == json_string)
str_copy(pClient->m_Stats.m_aCustom, rStart["custom"].u.string.ptr, sizeof(pClient->m_Stats.m_aCustom)); str_copy(pClient->m_Stats.m_aCustom, rStart["custom"].u.string.ptr, sizeof(pClient->m_Stats.m_aCustom));
//copy message for watchdog to analysis
WatchdogMessage(ClientNetID,
pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15,
pClient->m_Stats.m_ping_10010, pClient->m_Stats.m_ping_189, pClient->m_Stats.m_ping_10086,
pClient->m_Stats.m_time_10010, pClient->m_Stats.m_time_189, pClient->m_Stats.m_time_10086,
pClient->m_Stats.m_tcpCount, pClient->m_Stats.m_udpCount, pClient->m_Stats.m_processCount,
pClient->m_Stats.m_threadCount, pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx,
pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT,
pClient->m_LastNetworkIN, pClient->m_LastNetworkOUT, pClient->m_Stats.m_MemTotal,
pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed,
pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_IORead,
pClient->m_Stats.m_IOWrite, pClient->m_Stats.m_CPU, pClient->m_Stats.m_Online4,
pClient->m_Stats.m_Online6);
if(m_Config.m_Verbose) if(m_Config.m_Verbose)
{ {
if(rStart["online4"].type) if(rStart["online4"].type)
dbg_msg("main", "Online4: %s\nUptime: %" PRId64 "\nIpStatus: %s\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nPing_10010: %f\nPing_189: %f\nPing_10086: %f\nTime_10010: %" PRId64 "\nTime_189: %" PRId64 "\nTime_10086: %" PRId64 "\nTcp_count: %" PRId64 "\nUdp_count: %" PRId64 "\nprocess_count: %" PRId64 "\nthread_count: %" PRId64 "\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\n", dbg_msg("main", "Online4: %s\nUptime: %" PRId64 "\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nPing_10010: %f\nPing_189: %f\nPing_10086: %f\nTime_10010: %" PRId64 "\nTime_189: %" PRId64 "\nTime_10086: %" PRId64 "\nTcp_count: %" PRId64 "\nUdp_count: %" PRId64 "\nprocess_count: %" PRId64 "\nthread_count: %" PRId64 "\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\nIORead: %" PRId64 "\nIOWrite: %" PRId64 "\n",
rStart["online4"].u.boolean ? "true" : "false", rStart["online4"].u.boolean ? "true" : "false",
pClient->m_Stats.m_Uptime, pClient->m_Stats.m_Uptime,
pClient->m_Stats.m_IpStatus ? "true" : "false", pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_ping_10010, pClient->m_Stats.m_ping_189, pClient->m_Stats.m_ping_10086, pClient->m_Stats.m_time_10010, pClient->m_Stats.m_time_189, pClient->m_Stats.m_time_10086,pClient->m_Stats.m_tcpCount,pClient->m_Stats.m_udpCount,pClient->m_Stats.m_processCount,pClient->m_Stats.m_threadCount,pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU, pClient->m_Stats.m_IORead, pClient->m_Stats.m_IOWrite);
pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_ping_10010, pClient->m_Stats.m_ping_189, pClient->m_Stats.m_ping_10086, pClient->m_Stats.m_time_10010, pClient->m_Stats.m_time_189, pClient->m_Stats.m_time_10086,pClient->m_Stats.m_tcpCount,pClient->m_Stats.m_udpCount,pClient->m_Stats.m_processCount,pClient->m_Stats.m_threadCount,pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU);
else if(rStart["online6"].type) else if(rStart["online6"].type)
dbg_msg("main", "Online6: %s\nUptime: %" PRId64 "\nIpStatus: %s\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nPing_10010: %f\nPing_189: %f\nPing_10086: %f\nTime_10010: %" PRId64 "\nTime_189: %" PRId64 "\nTime_10086: %" PRId64 "\nTcp_count: %" PRId64 "\nUdp_count: %" PRId64 "\nprocess_count: %" PRId64 "\nthread_count: %" PRId64 "\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\n", dbg_msg("main", "Online6: %s\nUptime: %" PRId64 "\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nPing_10010: %f\nPing_189: %f\nPing_10086: %f\nTime_10010: %" PRId64 "\nTime_189: %" PRId64 "\nTime_10086: %" PRId64 "\nTcp_count: %" PRId64 "\nUdp_count: %" PRId64 "\nprocess_count: %" PRId64 "\nthread_count: %" PRId64 "\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\nIORead: %" PRId64 "\nIOWrite: %" PRId64 "\n",
rStart["online6"].u.boolean ? "true" : "false", rStart["online6"].u.boolean ? "true" : "false",
pClient->m_Stats.m_Uptime, pClient->m_Stats.m_Uptime,
pClient->m_Stats.m_IpStatus ? "true" : "false", pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_ping_10010, pClient->m_Stats.m_ping_189, pClient->m_Stats.m_ping_10086, pClient->m_Stats.m_time_10010, pClient->m_Stats.m_time_189, pClient->m_Stats.m_time_10086,pClient->m_Stats.m_tcpCount,pClient->m_Stats.m_udpCount,pClient->m_Stats.m_processCount,pClient->m_Stats.m_threadCount,pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU, pClient->m_Stats.m_IORead, pClient->m_Stats.m_IOWrite);
pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_ping_10010, pClient->m_Stats.m_ping_189, pClient->m_Stats.m_ping_10086, pClient->m_Stats.m_time_10010, pClient->m_Stats.m_time_189, pClient->m_Stats.m_time_10086,pClient->m_Stats.m_tcpCount,pClient->m_Stats.m_udpCount,pClient->m_Stats.m_processCount,pClient->m_Stats.m_threadCount,pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU);
else else
dbg_msg("main", "Uptime: %" PRId64 "\nIpStatus: %s\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nPing_10010: %f\nPing_189: %f\nPing_10086: %f\nTime_10010: %" PRId64 "\nTime_189: %" PRId64 "\nTime_10086: %" PRId64 "\nTcp_count: %" PRId64 "\nUdp_count: %" PRId64 "\nprocess_count: %" PRId64 "\nthread_count: %" PRId64 "\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\n", dbg_msg("main", "Uptime: %" PRId64 "\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nPing_10010: %f\nPing_189: %f\nPing_10086: %f\nTime_10010: %" PRId64 "\nTime_189: %" PRId64 "\nTime_10086: %" PRId64 "\nTcp_count: %" PRId64 "\nUdp_count: %" PRId64 "\nprocess_count: %" PRId64 "\nthread_count: %" PRId64 "\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\nIORead: %" PRId64 "\nIOWrite: %" PRId64 "\n",
pClient->m_Stats.m_Uptime, pClient->m_Stats.m_Uptime,
pClient->m_Stats.m_IpStatus ? "true" : "false", pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_ping_10010, pClient->m_Stats.m_ping_189, pClient->m_Stats.m_ping_10086, pClient->m_Stats.m_time_10010, pClient->m_Stats.m_time_189, pClient->m_Stats.m_time_10086,pClient->m_Stats.m_tcpCount,pClient->m_Stats.m_udpCount,pClient->m_Stats.m_processCount,pClient->m_Stats.m_threadCount,pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU, pClient->m_Stats.m_IORead, pClient->m_Stats.m_IOWrite);
pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_ping_10010, pClient->m_Stats.m_ping_189, pClient->m_Stats.m_ping_10086, pClient->m_Stats.m_time_10010, pClient->m_Stats.m_time_189, pClient->m_Stats.m_time_10086,pClient->m_Stats.m_tcpCount,pClient->m_Stats.m_udpCount,pClient->m_Stats.m_processCount,pClient->m_Stats.m_threadCount,pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU);
} }
// clean up // clean up
@ -235,6 +441,151 @@ int CMain::HandleMessage(int ClientNetID, char *pMessage)
return 1; return 1;
} }
void CMain::WatchdogMessage(int ClientNetID, double load_1, double load_5, double load_15, double ping_10010, double ping_189, double ping_10086,
double time_10010, double time_189, double time_10086, double tcp_count, double udp_count, double process_count, double thread_count,
double network_rx, double network_tx, double network_in, double network_out, double last_network_in, double last_network_out, double memory_total, double memory_used,
double swap_total, double swap_used, double hdd_total, double hdd_used, double io_read, double io_write, double cpu,
double online4, double online6)
{
int ID = 0;
while (strcmp(Watchdog(ID)->m_aName, "NULL"))
{
// Exprtk库默认使用窄字符类型但可能会出现中文等Unicode字符无法正确解析的问题。
// todo: 为解决此问题可以使用宽字符类型替换Exprtk库中默认的窄字符类型。
// #include <string>
// #include <vector>
// #include <exprtk.hpp>
// typedef exprtk::expression<wchar_t> expression_type;
// typedef exprtk::parser<wchar_t> parser_type;
// int main()
// {
// std::wstring expression_string = L"sin(x)";
// expression_type expression;
// parser_type parser;
// parser.compile(expression_string, expression);
// double x = 3.14;
// double result = expression.value();
// return 0;
// }
typedef exprtk::symbol_table<double> symbol_table_t;
typedef exprtk::expression<double> expression_t;
typedef exprtk::parser<double> parser_t;
const std::string expression_string = Watchdog(ID)->m_aRule;
int ClientID = ClientNetToClient(ClientNetID);
if(ClientID < 0 || ClientID >= NET_MAX_CLIENTS) {
ID++;
continue; // 无效客户端,跳过当前 watchdog 规则
}
std::string username = Client(ClientID)->m_aUsername;
std::string name = Client(ClientID)->m_aName;
std::string type = Client(ClientID)->m_aType;
std::string host = Client(ClientID)->m_aHost;
std::string location = Client(ClientID)->m_aLocation;
symbol_table_t symbol_table;
symbol_table.add_stringvar("username", username);
symbol_table.add_stringvar("name", name);
symbol_table.add_stringvar("type", type);
symbol_table.add_stringvar("host", host);
symbol_table.add_stringvar("location", location);
symbol_table.add_variable("load_1",load_1);
symbol_table.add_variable("load_5",load_5);
symbol_table.add_variable("load_15",load_15);
symbol_table.add_variable("ping_10010",ping_10010);
symbol_table.add_variable("ping_189",ping_189);
symbol_table.add_variable("ping_10086",ping_10086);
symbol_table.add_variable("time_10010",time_10010);
symbol_table.add_variable("time_189",time_189);
symbol_table.add_variable("time_10086",time_10086);
symbol_table.add_variable("tcp_count",tcp_count);
symbol_table.add_variable("udp_count",udp_count);
symbol_table.add_variable("process_count",process_count);
symbol_table.add_variable("thread_count",thread_count);
symbol_table.add_variable("network_rx",network_rx);
symbol_table.add_variable("network_tx",network_tx);
symbol_table.add_variable("network_in",network_in);
symbol_table.add_variable("network_out",network_out);
symbol_table.add_variable("last_network_in",last_network_in);
symbol_table.add_variable("last_network_out",last_network_out);
symbol_table.add_variable("memory_total",memory_total);
symbol_table.add_variable("memory_used",memory_used);
symbol_table.add_variable("swap_total",swap_total);
symbol_table.add_variable("swap_used",swap_used);
symbol_table.add_variable("hdd_total",hdd_total);
symbol_table.add_variable("hdd_used",hdd_used);
symbol_table.add_variable("io_read",io_read);
symbol_table.add_variable("io_write",io_write);
symbol_table.add_variable("cpu",cpu);
symbol_table.add_variable("online4",online4);
symbol_table.add_variable("online6",online6);
symbol_table.add_constants();
expression_t expression;
expression.register_symbol_table(symbol_table);
parser_t parser;
parser.compile(expression_string,expression);
if (expression.value() > 0)
{
time_t currentStamp = (long long)time(/*ago*/0);
if ((currentStamp-Client(ClientID)->m_AlarmLastTime) > Watchdog(ID)->m_aInterval)
{
if (!Client(ClientID)->m_Stats.m_Online4 && !Client(ClientID)->m_Stats.m_Online6)
{
//休眠5分钟如果5分钟后状态发生了变更消息不发出。
printf("download\n");
}
Client(ClientID)->m_AlarmLastTime = currentStamp;
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
//standard time
char standardTime[32]= { 0 };
strftime(standardTime, sizeof(standardTime), "%Y-%m-%d %H:%M:%S",localtime(&currentStamp));
//url encode, Rules conflict with url special characterseg&, del rules, by https://cpp.la, 2023-10-09
char encodeBuffer[2048] = { 0 };
sprintf(encodeBuffer, "【告警名称】 %s \n\n【告警时间】 %s \n\n【用户名】 %s \n\n【节点名】 %s \n\n【虚拟化】 %s \n\n【主机名】 %s \n\n【位 置】 %s",
Watchdog(ID)->m_aName,
standardTime,
username.c_str(),
name.c_str(),
type.c_str(),
host.c_str(),
location.c_str());
char *encodeUrl = curl_easy_escape(curl, encodeBuffer, strlen(encodeBuffer));
//standard url
char urlBuffer[2048] = { 0 };
sprintf(urlBuffer, "%s%s",Watchdog(ID)->m_aCallback, encodeUrl);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_URL, urlBuffer);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS,"signature=ServerStatus");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 6L);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "watchdog failed: %s\n", curl_easy_strerror(res));
if(encodeUrl)
curl_free(encodeUrl);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
}
}
ID++;
}
}
void CMain::JSONUpdateThread(void *pUser) void CMain::JSONUpdateThread(void *pUser)
{ {
CJSONUpdateThreadData *m_pJSONUpdateThreadData = (CJSONUpdateThreadData *)pUser; CJSONUpdateThreadData *m_pJSONUpdateThreadData = (CJSONUpdateThreadData *)pUser;
@ -269,27 +620,55 @@ void CMain::JSONUpdateThread(void *pUser)
else else
str_format(aUptime, sizeof(aUptime), "%02d:%02d:%02d", (int)(pClients[i].m_Stats.m_Uptime/60.0/60.0), (int)((pClients[i].m_Stats.m_Uptime/60)%60), (int)((pClients[i].m_Stats.m_Uptime)%60)); str_format(aUptime, sizeof(aUptime), "%02d:%02d:%02d", (int)(pClients[i].m_Stats.m_Uptime/60.0/60.0), (int)((pClients[i].m_Stats.m_Uptime/60)%60), (int)((pClients[i].m_Stats.m_Uptime)%60));
// track month network traffic, diff: 2021-10-01 00:05, 5minutes
// last_network_in/out is last record flag.
time_t currentStamp = (long long)time(/*ago*/0);
if(0 == pClients[i].m_LastNetworkIN || (0 != pClients[i].m_Stats.m_NetworkIN && pClients[i].m_LastNetworkIN > pClients[i].m_Stats.m_NetworkIN) || (localtime(&currentStamp)->tm_mday == pClients[i].m_aMonthStart && localtime(&currentStamp)->tm_hour == 0 && localtime(&currentStamp)->tm_min < 5))
{
pClients[i].m_LastNetworkIN = pClients[i].m_Stats.m_NetworkIN;
pClients[i].m_LastNetworkOUT = pClients[i].m_Stats.m_NetworkOUT;
}
str_format(pBuf, sizeof(aFileBuf) - (pBuf - aFileBuf), str_format(pBuf, sizeof(aFileBuf) - (pBuf - aFileBuf),
"{ \"name\": \"%s\",\"type\": \"%s\",\"host\": \"%s\",\"location\": \"%s\",\"online4\": %s, \"online6\": %s,\"ip_status\": %s,\"uptime\": \"%s\",\"load_1\": %.2f, \"load_5\": %.2f, \"load_15\": %.2f,\"ping_10010\": %.2f, \"ping_189\": %.2f, \"ping_10086\": %.2f,\"time_10010\": %" PRId64 ", \"time_189\": %" PRId64 ", \"time_10086\": %" PRId64 ", \"tcp_count\": %" PRId64 ", \"udp_count\": %" PRId64 ", \"process_count\": %" PRId64 ", \"thread_count\": %" PRId64 ", \"network_rx\": %" PRId64 ", \"network_tx\": %" PRId64 ", \"network_in\": %" PRId64 ", \"network_out\": %" PRId64 ", \"cpu\": %d, \"memory_total\": %" PRId64 ", \"memory_used\": %" PRId64 ", \"swap_total\": %" PRId64 ", \"swap_used\": %" PRId64 ", \"hdd_total\": %" PRId64 ", \"hdd_used\": %" PRId64 ", \"custom\": \"%s\" },\n", "{ \"name\": \"%s\",\"type\": \"%s\",\"host\": \"%s\",\"location\": \"%s\",\"online4\": %s, \"online6\": %s, \"uptime\": \"%s\",\"load_1\": %.2f, \"load_5\": %.2f, \"load_15\": %.2f,\"ping_10010\": %.2f, \"ping_189\": %.2f, \"ping_10086\": %.2f,\"time_10010\": %" PRId64 ", \"time_189\": %" PRId64 ", \"time_10086\": %" PRId64 ", \"tcp_count\": %" PRId64 ", \"udp_count\": %" PRId64 ", \"process_count\": %" PRId64 ", \"thread_count\": %" PRId64 ", \"network_rx\": %" PRId64 ", \"network_tx\": %" PRId64 ", \"network_in\": %" PRId64 ", \"network_out\": %" PRId64 ", \"cpu\": %d, \"memory_total\": %" PRId64 ", \"memory_used\": %" PRId64 ", \"swap_total\": %" PRId64 ", \"swap_used\": %" PRId64 ", \"hdd_total\": %" PRId64 ", \"hdd_used\": %" PRId64 ", \"last_network_in\": %" PRId64 ", \"last_network_out\": %" PRId64 ",\"io_read\": %" PRId64 ", \"io_write\": %" PRId64 ",\"custom\": \"%s\" },\n",
pClients[i].m_aName,pClients[i].m_aType,pClients[i].m_aHost,pClients[i].m_aLocation, pClients[i].m_aName,pClients[i].m_aType,pClients[i].m_aHost,pClients[i].m_aLocation,
pClients[i].m_Stats.m_Online4 ? "true" : "false",pClients[i].m_Stats.m_Online6 ? "true" : "false",pClients[i].m_Stats.m_IpStatus ? "true": "false", pClients[i].m_Stats.m_Online4 ? "true" : "false",pClients[i].m_Stats.m_Online6 ? "true" : "false",
aUptime, pClients[i].m_Stats.m_Load_1, pClients[i].m_Stats.m_Load_5, pClients[i].m_Stats.m_Load_15, pClients[i].m_Stats.m_ping_10010, pClients[i].m_Stats.m_ping_189, pClients[i].m_Stats.m_ping_10086, pClients[i].m_Stats.m_time_10010, pClients[i].m_Stats.m_time_189, pClients[i].m_Stats.m_time_10086,pClients[i].m_Stats.m_tcpCount,pClients[i].m_Stats.m_udpCount,pClients[i].m_Stats.m_processCount,pClients[i].m_Stats.m_threadCount,pClients[i].m_Stats.m_NetworkRx, pClients[i].m_Stats.m_NetworkTx, pClients[i].m_Stats.m_NetworkIN, pClients[i].m_Stats.m_NetworkOUT, (int)pClients[i].m_Stats.m_CPU, pClients[i].m_Stats.m_MemTotal, pClients[i].m_Stats.m_MemUsed, pClients[i].m_Stats.m_SwapTotal, pClients[i].m_Stats.m_SwapUsed, pClients[i].m_Stats.m_HDDTotal, pClients[i].m_Stats.m_HDDUsed, pClients[i].m_Stats.m_aCustom); aUptime, pClients[i].m_Stats.m_Load_1, pClients[i].m_Stats.m_Load_5, pClients[i].m_Stats.m_Load_15, pClients[i].m_Stats.m_ping_10010, pClients[i].m_Stats.m_ping_189, pClients[i].m_Stats.m_ping_10086,
pClients[i].m_Stats.m_time_10010, pClients[i].m_Stats.m_time_189, pClients[i].m_Stats.m_time_10086,pClients[i].m_Stats.m_tcpCount,pClients[i].m_Stats.m_udpCount,pClients[i].m_Stats.m_processCount,pClients[i].m_Stats.m_threadCount,
pClients[i].m_Stats.m_NetworkRx, pClients[i].m_Stats.m_NetworkTx, pClients[i].m_Stats.m_NetworkIN, pClients[i].m_Stats.m_NetworkOUT, (int)pClients[i].m_Stats.m_CPU, pClients[i].m_Stats.m_MemTotal, pClients[i].m_Stats.m_MemUsed,
pClients[i].m_Stats.m_SwapTotal, pClients[i].m_Stats.m_SwapUsed, pClients[i].m_Stats.m_HDDTotal, pClients[i].m_Stats.m_HDDUsed,
pClients[i].m_Stats.m_NetworkIN == 0 || pClients[i].m_LastNetworkIN == 0 ? pClients[i].m_Stats.m_NetworkIN : pClients[i].m_LastNetworkIN,
pClients[i].m_Stats.m_NetworkOUT == 0 || pClients[i].m_LastNetworkOUT == 0 ? pClients[i].m_Stats.m_NetworkOUT : pClients[i].m_LastNetworkOUT,
pClients[i].m_Stats.m_IORead, pClients[i].m_Stats.m_IOWrite,
pClients[i].m_Stats.m_aCustom);
pBuf += strlen(pBuf); pBuf += strlen(pBuf);
} }
else else
{ {
str_format(pBuf, sizeof(aFileBuf) - (pBuf - aFileBuf), "{ \"name\": \"%s\", \"type\": \"%s\", \"host\": \"%s\", \"location\": \"%s\", \"online4\": false, \"online6\": false },\n", // sava network traffic record to json when close client
pClients[i].m_aName, pClients[i].m_aType, pClients[i].m_aHost, pClients[i].m_aLocation); // last_network_in == last network in record, last_network_out == last network out record
str_format(pBuf, sizeof(aFileBuf) - (pBuf - aFileBuf), "{ \"name\": \"%s\", \"type\": \"%s\", \"host\": \"%s\", \"location\": \"%s\", \"online4\": false, \"online6\": false, \"last_network_in\": %" PRId64 ", \"last_network_out\": %" PRId64 " },\n",
pClients[i].m_aName, pClients[i].m_aType, pClients[i].m_aHost, pClients[i].m_aLocation, pClients[i].m_LastNetworkIN, pClients[i].m_LastNetworkOUT);
pBuf += strlen(pBuf); pBuf += strlen(pBuf);
} }
} }
if(!m_pJSONUpdateThreadData->m_ReloadRequired) // append ssl certs data
str_format(pBuf - 2, sizeof(aFileBuf) - (pBuf - aFileBuf), "\n],\n\"updated\": \"%lld\"\n}", (long long)time(/*ago*/0)); str_format(pBuf - 2, sizeof(aFileBuf) - (pBuf - aFileBuf), "\n],\n\"sslcerts\": [\n");
else pBuf += strlen(pBuf);
for(int si = 0; si < NET_MAX_CLIENTS; si++)
{ {
str_format(pBuf - 2, sizeof(aFileBuf) - (pBuf - aFileBuf), "\n],\n\"updated\": \"%lld\",\n\"reload\": true\n}", (long long)time(/*ago*/0)); if(!m_pJSONUpdateThreadData->pMain->SSLCert(si) || !strcmp(m_pJSONUpdateThreadData->pMain->SSLCert(si)->m_aName, "NULL")) break;
m_pJSONUpdateThreadData->m_ReloadRequired--; int64_t expire_ts = m_pJSONUpdateThreadData->pMain->SSLCert(si)->m_aExpireTS;
int expire_days = 0;
if(expire_ts>0){
int64_t nowts = (long long)time(/*ago*/0);
expire_days = (int)((expire_ts - nowts)/86400);
}
str_format(pBuf, sizeof(aFileBuf) - (pBuf - aFileBuf), "{ \"name\": \"%s\", \"domain\": \"%s\", \"port\": %d, \"expire_ts\": %lld, \"expire_days\": %d, \"mismatch\": %s },\n", m_pJSONUpdateThreadData->pMain->SSLCert(si)->m_aName, m_pJSONUpdateThreadData->pMain->SSLCert(si)->m_aDomain, m_pJSONUpdateThreadData->pMain->SSLCert(si)->m_aPort, (long long)expire_ts, expire_days, m_pJSONUpdateThreadData->pMain->SSLCert(si)->m_aHostnameMismatch?"true":"false");
pBuf += strlen(pBuf);
} }
if(pBuf - aFileBuf >= 2) str_format(pBuf - 2, sizeof(aFileBuf) - (pBuf - aFileBuf), "\n],\n\"updated\": \"%lld\"%s\n}", (long long)time(/*ago*/0), m_pJSONUpdateThreadData->m_ReloadRequired?",\n\"reload\": true":"");
if(m_pJSONUpdateThreadData->m_ReloadRequired) m_pJSONUpdateThreadData->m_ReloadRequired--;
pBuf += strlen(pBuf); pBuf += strlen(pBuf);
char aJSONFileTmp[1024]; char aJSONFileTmp[1024];
@ -306,7 +685,114 @@ void CMain::JSONUpdateThread(void *pUser)
fs_rename(aJSONFileTmp, pConfig->m_aJSONFile); fs_rename(aJSONFileTmp, pConfig->m_aJSONFile);
thread_sleep(1000); thread_sleep(1000);
} }
fs_remove(pConfig->m_aJSONFile); // support by: https://cpp.la. don't remove month traffic record, storage as "stats.json~", remark: 2021-10-18
// fs_remove(pConfig->m_aJSONFile);
char aJSONFileTmp[1024];
str_format(aJSONFileTmp, sizeof(aJSONFileTmp), "%s~", pConfig->m_aJSONFile);
fs_rename(pConfig->m_aJSONFile, aJSONFileTmp);
}
void CMain::offlineAlarmThread(void *pUser)
{
CJSONUpdateThreadData *m_OfflineAlarmThreadData = (CJSONUpdateThreadData *)pUser;
CClient *pClients = m_OfflineAlarmThreadData->pClients;
CWatchDog *pWatchDogs = m_OfflineAlarmThreadData->pWatchDogs;
volatile short ClientID = m_OfflineAlarmThreadData->m_ReloadRequired;
thread_sleep(25000);
if(!pClients[ClientID].m_Connected)
{
int ID = 0;
while (strcmp(pWatchDogs[ID].m_aName, "NULL"))
{
typedef exprtk::symbol_table<double> symbol_table_t;
typedef exprtk::expression<double> expression_t;
typedef exprtk::parser<double> parser_t;
const std::string expression_string = pWatchDogs[ID].m_aRule;
std::string username = pClients[ClientID].m_aUsername;
std::string name = pClients[ClientID].m_aName;
std::string type = pClients[ClientID].m_aType;
std::string host = pClients[ClientID].m_aHost;
std::string location = pClients[ClientID].m_aLocation;
std::double_t online4 = pClients[ClientID].m_Stats.m_Online4;
std::double_t online6 = pClients[ClientID].m_Stats.m_Online6;
symbol_table_t symbol_table;
symbol_table.add_stringvar("username", username);
symbol_table.add_stringvar("name", name);
symbol_table.add_stringvar("type", type);
symbol_table.add_stringvar("host", host);
symbol_table.add_stringvar("location", location);
symbol_table.add_variable("online4",online4);
symbol_table.add_variable("online6",online6);
symbol_table.add_constants();
expression_t expression;
expression.register_symbol_table(symbol_table);
parser_t parser;
parser.compile(expression_string,expression);
if (expression.value() > 0)
{
time_t currentStamp = (long long)time(/*ago*/0);
if ((currentStamp-pClients[ClientID].m_AlarmLastTime) > pWatchDogs[ID].m_aInterval)
{
printf("客户端下线且超过阈值, Client disconnects and sends alert information\n");
pClients[ClientID].m_AlarmLastTime = currentStamp;
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
//standard time
char standardTime[32]= { 0 };
strftime(standardTime, sizeof(standardTime), "%Y-%m-%d %H:%M:%S",localtime(&currentStamp));
//url encode, Rules conflict with url special characterseg&, del rules, by https://cpp.la, 2023-10-09
char encodeBuffer[2048] = { 0 };
sprintf(encodeBuffer, "【告警名称】 %s \n\n【告警时间】 %s \n\n【用户名】 %s \n\n【节点名】 %s \n\n【虚拟化】 %s \n\n【主机名】 %s \n\n【位 置】 %s",
pWatchDogs[ID].m_aName,
standardTime,
pClients[ClientID].m_aUsername,
pClients[ClientID].m_aName,
pClients[ClientID].m_aType,
pClients[ClientID].m_aHost,
pClients[ClientID].m_aLocation);
char *encodeUrl = curl_easy_escape(curl, encodeBuffer, strlen(encodeBuffer));
//standard url
char urlBuffer[2048] = { 0 };
sprintf(urlBuffer, "%s%s",pWatchDogs[ID].m_aCallback, encodeUrl);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_URL, urlBuffer);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS,"signature=ServerStatus");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 6L);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
fprintf(stderr, "watchdog failed: %s\n", curl_easy_strerror(res));
if(encodeUrl)
curl_free(encodeUrl);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
}
else
printf("客户端下线但未超过阈值No alarm if the threshold is not exceeded\n");
}
ID++;
}
}
else
{
printf("网络波动No alarm information is sent due to network fluctuations\n");
}
fflush(stdout);
} }
int CMain::ReadConfig() int CMain::ReadConfig()
@ -367,21 +853,155 @@ int CMain::ReadConfig()
str_copy(Client(ID)->m_aHost, rStart[i]["host"].u.string.ptr, sizeof(Client(ID)->m_aHost)); str_copy(Client(ID)->m_aHost, rStart[i]["host"].u.string.ptr, sizeof(Client(ID)->m_aHost));
str_copy(Client(ID)->m_aLocation, rStart[i]["location"].u.string.ptr, sizeof(Client(ID)->m_aLocation)); str_copy(Client(ID)->m_aLocation, rStart[i]["location"].u.string.ptr, sizeof(Client(ID)->m_aLocation));
str_copy(Client(ID)->m_aPassword, rStart[i]["password"].u.string.ptr, sizeof(Client(ID)->m_aPassword)); str_copy(Client(ID)->m_aPassword, rStart[i]["password"].u.string.ptr, sizeof(Client(ID)->m_aPassword));
//if month start day > 28, diff: 3days(29,30,31)
Client(ID)->m_aMonthStart = rStart[i]["monthstart"].u.integer;
if(Client(ID)->m_aMonthStart > 28)
{
Client(ID)->m_aMonthStart = 28;
}
Client(ID)->m_LastNetworkIN = 0;
Client(ID)->m_LastNetworkOUT = 0;
if(m_Config.m_Verbose) if(m_Config.m_Verbose)
{ {
if(Client(ID)->m_Disabled) if(Client(ID)->m_Disabled)
dbg_msg("main", "[#%d: Name: \"%s\", Username: \"%s\", Type: \"%s\", Host: \"%s\", Location: \"%s\", Password: \"%s\"]", dbg_msg("main", "[#%d: Name: \"%s\", Username: \"%s\", Type: \"%s\", Host: \"%s\", Location: \"%s\", Password: \"%s\", MonthStart: %\" PRId64 \"]",
ID, Client(ID)->m_aName, Client(ID)->m_aUsername, Client(ID)->m_aType, Client(ID)->m_aHost, Client(ID)->m_aLocation, Client(ID)->m_aPassword); ID, Client(ID)->m_aName, Client(ID)->m_aUsername, Client(ID)->m_aType, Client(ID)->m_aHost, Client(ID)->m_aLocation, Client(ID)->m_aPassword, Client(ID)->m_aMonthStart);
else else
dbg_msg("main", "#%d: Name: \"%s\", Username: \"%s\", Type: \"%s\", Host: \"%s\", Location: \"%s\", Password: \"%s\"", dbg_msg("main", "#%d: Name: \"%s\", Username: \"%s\", Type: \"%s\", Host: \"%s\", Location: \"%s\", Password: \"%s\", MonthStart: %\" PRId64 \"",
ID, Client(ID)->m_aName, Client(ID)->m_aUsername, Client(ID)->m_aType, Client(ID)->m_aHost, Client(ID)->m_aLocation, Client(ID)->m_aPassword); ID, Client(ID)->m_aName, Client(ID)->m_aUsername, Client(ID)->m_aType, Client(ID)->m_aHost, Client(ID)->m_aLocation, Client(ID)->m_aPassword, Client(ID)->m_aMonthStart);
} }
ID++; ID++;
} }
} }
// watch dog
// support by: https://cpp.la
ID = 0;
const json_value &jStart = (*pJsonData)["watchdog"];
if(jStart.type == json_array)
{
for(unsigned i = 0; i < jStart.u.array.length; i++)
{
if(ID < 0 || ID >= NET_MAX_CLIENTS)
continue;
str_copy(Watchdog(ID)->m_aName, jStart[i]["name"].u.string.ptr, sizeof(Watchdog(ID)->m_aName));
str_copy(Watchdog(ID)->m_aRule, jStart[i]["rule"].u.string.ptr, sizeof(Watchdog(ID)->m_aRule));
Watchdog(ID)->m_aInterval = jStart[i]["interval"].u.integer;
str_copy(Watchdog(ID)->m_aCallback, jStart[i]["callback"].u.string.ptr, sizeof(Watchdog(ID)->m_aCallback));
ID++;
}
str_copy(Watchdog(ID)->m_aName, "NULL", sizeof(Watchdog(ID)->m_aName));
}
else
{
str_copy(Watchdog(ID)->m_aName, "NULL", sizeof(Watchdog(ID)->m_aName));
}
// monitor
// support by: https://cpp.la
ID = 0;
const json_value &mStart = (*pJsonData)["monitors"];
if(mStart.type == json_array)
{
for(unsigned i = 0; i < mStart.u.array.length; i++)
{
if(ID < 0 || ID >= NET_MAX_CLIENTS)
continue;
str_copy(Monitors(ID)->m_aName, mStart[i]["name"].u.string.ptr, sizeof(Monitors(ID)->m_aName));
str_copy(Monitors(ID)->m_aHost, mStart[i]["host"].u.string.ptr, sizeof(Monitors(ID)->m_aHost));
Monitors(ID)->m_aInterval = mStart[i]["interval"].u.integer;
str_copy(Monitors(ID)->m_aType, mStart[i]["type"].u.string.ptr, sizeof(Monitors(ID)->m_aType));
ID++;
}
str_copy(Monitors(ID)->m_aName, "NULL", sizeof(Monitors(ID)->m_aName));
}
else
{
str_copy(Monitors(ID)->m_aName, "NULL", sizeof(Monitors(ID)->m_aName));
}
// sslcerts
ID = 0;
const json_value &sStart = (*pJsonData)["sslcerts"];
if(sStart.type == json_array)
{
for(unsigned i = 0; i < sStart.u.array.length; i++)
{
if(ID < 0 || ID >= NET_MAX_CLIENTS)
continue;
str_copy(SSLCert(ID)->m_aName, sStart[i]["name"].u.string.ptr, sizeof(SSLCert(ID)->m_aName));
str_copy(SSLCert(ID)->m_aDomain, sStart[i]["domain"].u.string.ptr, sizeof(SSLCert(ID)->m_aDomain));
SSLCert(ID)->m_aPort = sStart[i]["port"].u.integer;
SSLCert(ID)->m_aInterval = sStart[i]["interval"].u.integer;
str_copy(SSLCert(ID)->m_aCallback, sStart[i]["callback"].u.string.ptr, sizeof(SSLCert(ID)->m_aCallback));
SSLCert(ID)->m_aExpireTS = 0; // reset
SSLCert(ID)->m_aLastCheck = 0;
SSLCert(ID)->m_aLastAlarm7 = 0;
SSLCert(ID)->m_aLastAlarm3 = 0;
SSLCert(ID)->m_aLastAlarm1 = 0;
ID++;
}
str_copy(SSLCert(ID)->m_aName, "NULL", sizeof(SSLCert(ID)->m_aName));
}else
str_copy(SSLCert(ID)->m_aName, "NULL", sizeof(SSLCert(ID)->m_aName));
// if file exists, read last network traffic recordreset m_LastNetworkIN and m_LastNetworkOUT
// support by: https://cpp.la
IOHANDLE nFile = io_open(m_Config.m_aJSONFile, IOFLAG_READ);
if(!nFile)
{
char aJSONFileTmp[1024];
str_format(aJSONFileTmp, sizeof(aJSONFileTmp), "%s~", m_Config.m_aJSONFile);
nFile = io_open(aJSONFileTmp, IOFLAG_READ);
}
if(nFile)
{
int nFileSize = (int)io_length(nFile);
char *pNFileData = (char *)mem_alloc(nFileSize + 1, 1);
io_read(nFile, pNFileData, nFileSize);
pNFileData[nFileSize] = 0;
io_close(nFile);
json_settings nJsonSettings;
mem_zero(&nJsonSettings, sizeof(nJsonSettings));
json_value *pNJsonData = json_parse_ex(&nJsonSettings, pNFileData, strlen(pNFileData), aError);
if(pNJsonData)
{
const json_value &cStart = (*pNJsonData)["servers"];
if(rStart.type == json_array)
{
int ID = 0;
for(unsigned i = 0; i < rStart.u.array.length; i++)
{
if(ID < 0 || ID >= NET_MAX_CLIENTS)
continue;
for(unsigned j = 0; j < cStart.u.array.length; j++)
{
if(strcmp(Client(ID)->m_aName, cStart[j]["name"].u.string.ptr)==0 &&
strcmp(Client(ID)->m_aType, cStart[j]["type"].u.string.ptr)==0 &&
strcmp(Client(ID)->m_aHost, cStart[j]["host"].u.string.ptr)==0 &&
strcmp(Client(ID)->m_aLocation, cStart[j]["location"].u.string.ptr)==0)
{
Client(ID)->m_LastNetworkIN = cStart[j]["last_network_in"].u.integer;
Client(ID)->m_LastNetworkOUT = cStart[j]["last_network_out"].u.integer;
break;
}
}
ID++;
}
}
json_value_free(pNJsonData);
}
mem_free(pNFileData);
}
// clean up // clean up
json_value_free(pJsonData); json_value_free(pJsonData);
mem_free(pFileData); mem_free(pFileData);
@ -404,7 +1024,11 @@ int CMain::Run()
m_JSONUpdateThreadData.m_ReloadRequired = 2; m_JSONUpdateThreadData.m_ReloadRequired = 2;
m_JSONUpdateThreadData.pClients = m_aClients; m_JSONUpdateThreadData.pClients = m_aClients;
m_JSONUpdateThreadData.pConfig = &m_Config; m_JSONUpdateThreadData.pConfig = &m_Config;
m_JSONUpdateThreadData.pWatchDogs = m_aCWatchDogs;
m_JSONUpdateThreadData.pMain = this;
void *LoadThread = thread_create(JSONUpdateThread, &m_JSONUpdateThreadData); void *LoadThread = thread_create(JSONUpdateThread, &m_JSONUpdateThreadData);
// Start SSL check thread
static SSLCheckThreadData sslData; sslData.pMain = this; thread_create(SSLCheckThread, &sslData);
//thread_detach(LoadThread); //thread_detach(LoadThread);
while(gs_Running) while(gs_Running)
@ -486,3 +1110,4 @@ int main(int argc, const char *argv[])
return RetVal; return RetVal;
} }

@ -36,15 +36,20 @@ class CMain
char m_aHost[128]; char m_aHost[128];
char m_aLocation[128]; char m_aLocation[128];
char m_aPassword[128]; char m_aPassword[128];
int m_aMonthStart; //track month network traffic. by: https://cpp.la
int64 m_TimeConnected; int64_t m_LastNetworkIN; //restore month traffic info.
int64 m_LastUpdate; int64_t m_LastNetworkOUT; //restore month traffic info.
int64_t m_TimeConnected;
int64_t m_LastUpdate;
int64_t m_AlarmLastTime; //record last alarm time.
struct CStats struct CStats
{ {
bool m_Online4; bool m_Online4;
bool m_Online6; bool m_Online6;
bool m_IpStatus; //mh361 or mh370, mourn mh370, 2014-03-08 01:20 lost from all over the world. by:cpp.la // bool m_IpStatus delete ip_status check, Duplicate packet loss rate detection
// mh361 or mh370, mourn mh370, 2014-03-08 01:20 lost from all over the world. by:https://cpp.la
int64_t m_Uptime; int64_t m_Uptime;
double m_Load_1; double m_Load_1;
double m_Load_5; double m_Load_5;
@ -69,21 +74,55 @@ class CMain
int64_t m_udpCount; int64_t m_udpCount;
int64_t m_processCount; int64_t m_processCount;
int64_t m_threadCount; int64_t m_threadCount;
int64_t m_IORead;
int64_t m_IOWrite;
double m_CPU; double m_CPU;
char m_aCustom[512]; char m_aCustom[1024];
// Options // Options
bool m_Pong; bool m_Pong;
} m_Stats; } m_Stats;
} m_aClients[NET_MAX_CLIENTS]; } m_aClients[NET_MAX_CLIENTS];
struct CWatchDog{
char m_aName[128];
char m_aRule[128];
int m_aInterval;
char m_aCallback[1024];
} m_aCWatchDogs[NET_MAX_CLIENTS];
struct CMonitors{
char m_aName[128];
char m_aHost[128];
int m_aInterval;
char m_aType[128];
} m_aCMonitors[NET_MAX_CLIENTS];
public:
struct CSSLCerts{
char m_aName[128];
char m_aDomain[256];
int m_aPort;
int m_aInterval; // seconds
char m_aCallback[1024];
int64_t m_aExpireTS; // epoch seconds cache
int64_t m_aLastCheck; // last check time
int64_t m_aLastAlarm7;
int64_t m_aLastAlarm3;
int64_t m_aLastAlarm1;
int m_aHostnameMismatch; // 1: 域名与证书不匹配
int64_t m_aLastAlarmMismatch; // 上次不匹配告警时间
} m_aCSSLCerts[NET_MAX_CLIENTS];
struct CJSONUpdateThreadData struct CJSONUpdateThreadData
{ {
CClient *pClients; CClient *pClients;
CConfig *pConfig; CConfig *pConfig;
CWatchDog *pWatchDogs;
CMain *pMain;
volatile short m_ReloadRequired; volatile short m_ReloadRequired;
} m_JSONUpdateThreadData; } m_JSONUpdateThreadData, m_OfflineAlarmThreadData;
static void JSONUpdateThread(void *pUser); static void JSONUpdateThread(void *pUser);
static void offlineAlarmThread(void *pUser);
public: public:
CMain(CConfig Config); CMain(CConfig Config);
@ -93,6 +132,17 @@ public:
int ReadConfig(); int ReadConfig();
int Run(); int Run();
CWatchDog *Watchdog(int ruleID) { return &m_aCWatchDogs[ruleID]; }
CMonitors *Monitors(int ruleID) { return &m_aCMonitors[ruleID]; }
CSSLCerts *SSLCert(int ruleID) { return &m_aCSSLCerts[ruleID]; }
void WatchdogMessage(int ClientNetID,
double load_1, double load_5, double load_15, double ping_10010, double ping_189, double ping_10086,
double time_10010, double time_189, double time_10086, double tcp_count, double udp_count, double process_count, double thread_count,
double network_rx, double network_tx, double network_in, double network_out, double last_network_in, double last_network_out,
double memory_total, double memory_used,double swap_total, double swap_used, double hdd_total,
double hdd_used, double io_read, double io_write, double cpu,double online4, double online6);
CClient *Client(int ClientID) { return &m_aClients[ClientID]; } CClient *Client(int ClientID) { return &m_aClients[ClientID]; }
CClient *ClientNet(int ClientNetID); CClient *ClientNet(int ClientNetID);
const CConfig *Config() const { return &m_Config; } const CConfig *Config() const { return &m_Config; }

@ -10,7 +10,7 @@ enum
NET_CONNSTATE_ERROR=4, NET_CONNSTATE_ERROR=4,
NET_MAX_PACKETSIZE = 1400, NET_MAX_PACKETSIZE = 1400,
NET_MAX_CLIENTS = 128 NET_MAX_CLIENTS = 512
}; };
typedef int (*NETFUNC_DELCLIENT)(int ClientID, const char* pReason, void *pUser); typedef int (*NETFUNC_DELCLIENT)(int ClientID, const char* pReason, void *pUser);

@ -0,0 +1,9 @@
[Unit]
Description=ServerStatus-Client
After=network.target
[Service]
ExecStart=/usr/bin/python3 /usr/local/ServerStatus/clients/client-linux.py
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target

@ -0,0 +1,9 @@
[Unit]
Description=ServerStatus-Server
After=network.target
[Service]
ExecStart=/usr/local/ServerStatus/server/sergate --config=/usr/local/ServerStatus/server/config.json --web-dir=/usr/local/ServerStatus/web
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target

1041
status.sh Normal file

File diff suppressed because it is too large Load Diff

262
web/css/app.css Normal file

@ -0,0 +1,262 @@
:root{--bg:#0f1115;--bg-alt:#171a21;--border:#262a33;--text:#e2e8f0;--text-dim:#7a899d;--accent:#3b82f6;--accent-glow:#60a5fa;--danger:#ef4444;--warn:#f59e0b;--ok:#10b981;--radius:10px;--radius-sm:4px;--shadow:0 4px 12px -2px rgba(0,0,0,.4);--font:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,'Noto Sans SC',sans-serif;--trans:.25s cubic-bezier(.4,0,.2,1);--logo-start:#3b82f6;--logo-end:#2563eb;--logo-accent-grad-start:#5fa8ff;--logo-accent-grad-end:#93c5fd}
body.light{--bg:#f6f7f9;--bg-alt:#ffffff;--border:#e2e8f0;--text:#1e293b;--text-dim:#64748b;--accent:#2563eb;--accent-glow:#3b82f6;--shadow:0 4px 20px -4px rgba(0,0,0,.08);--logo-start:#2563eb;--logo-end:#1d4ed8;--logo-accent-grad-start:#1d4ed8;--logo-accent-grad-end:#60a5fa}
*{box-sizing:border-box}
html,body{height:100%;margin:0;padding:0;font-family:var(--font);background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased}
html.light{background:var(--bg)}
body,button{font-size:14px;line-height:1.35}
a{color:var(--accent);text-decoration:none}
a:hover{color:var(--accent-glow)}
.topbar{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:1rem;padding:.75rem 1.25rem;background:var(--bg-alt);border-bottom:1px solid var(--border)}
.brand{font-weight:600;letter-spacing:.5px;font-size:15px}
.nav{display:flex;gap:.5rem}
.nav button{background:transparent;border:1px solid var(--border);color:var(--text-dim);padding:.45rem .9rem;border-radius:var(--radius-sm);cursor:pointer;display:flex;align-items:center;gap:.35rem;transition:var(--trans);font-weight:500}
.nav button.active,.nav button:hover{color:var(--text);background:var(--accent);border-color:var(--accent);box-shadow:0 0 0 1px var(--accent-glow),0 4px 10px -2px rgba(0,0,0,.5)}
.actions{margin-left:auto;display:flex;align-items:center;gap:.75rem}
.actions button{background:var(--bg);border:1px solid var(--border);color:var(--text-dim);height:32px;width:38px;border-radius:8px;cursor:pointer;display:grid;place-items:center;transition:var(--trans)}
.actions button:hover{color:var(--text);border-color:var(--accent);background:var(--accent)}
.wrapper{max-width:1680px;margin:1.2rem auto;padding:0 1.2rem;display:flex;flex-direction:column;gap:1.25rem}
.notice{padding:.9rem 1rem;border:1px solid var(--border);background:linear-gradient(145deg,var(--bg-alt),var(--bg));border-radius:var(--radius);display:flex;align-items:center;gap:.75rem;font-size:13px}
.notice.info:before{content:"";width:8px;height:8px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 20%,transparent)}
.panel{display:none;flex-direction:column;gap:.75rem;animation:fade .4s ease}
.panel.active{display:flex}
.table-wrap{overflow:auto;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-alt);box-shadow:var(--shadow)}
table.data{width:100%;border-collapse:separate;border-spacing:0;min-width:960px}
table.data thead th{position:sticky;top:0;background:var(--bg-alt);font-weight:500;text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-dim);padding:.7rem .75rem;border-bottom:1px solid var(--border);white-space:nowrap}
table.data tbody td{padding:.55rem .75rem;border-bottom:1px solid var(--border);font-size:13px;vertical-align:middle;white-space:nowrap}
/* 防止数值变化导致列抖动:为月流量(7)/当前网络(8)/总流量(9)设置固定宽度并使用等宽数字 */
table.data th,table.data td{font-variant-numeric:tabular-nums}
/* 月流量(2) 左对齐;当前网络(8)/总流量(9) 居中 */
#serversTable thead th:nth-child(2),#serversTable tbody td:nth-child(2){
/* 月流量列:向左贴近协议(减小左 padding同时加大右 padding 拉开与节点距离 */
width:128px;min-width:128px;max-width:128px;font-variant-numeric:tabular-nums;letter-spacing:.3px;text-align:center;padding:0 1.05rem 0 .15rem;
}
/* 节点列加宽,避免被月流量胶囊视觉挤压 */
#serversTable thead th:nth-child(3),#serversTable tbody td:nth-child(3){
width:160px;min-width:160px;max-width:160px;
}
/* 协议列继续收紧右侧 padding 与固定宽度 */
#serversTable thead th:nth-child(1),#serversTable tbody td:nth-child(1){
padding-right:.14rem;width:78px;min-width:78px;max-width:78px; /* 扩大协议列并恢复适度间距 */
}
/* 让双色胶囊更靠近协议列 */
#serversTable tbody td:nth-child(2) .caps-traffic.duo{margin-left:-6px;} /* 向协议方向微移,视觉更靠近;右侧 padding 增大避免靠近节点 */
#serversTable thead th:nth-child(8),#serversTable tbody td:nth-child(8),
#serversTable thead th:nth-child(9),#serversTable tbody td:nth-child(9){
width:132px;min-width:132px;max-width:132px;font-variant-numeric:tabular-nums;letter-spacing:.3px;text-align:center;
/* 进一步拉开与 CPU/内存/硬盘 组的视觉距离 */
padding-right:1.95rem;
}
/* CPU / 内存 / 硬盘 列:居中 + 固定宽度 与仪表盘一致 */
#serversTable thead th:nth-child(10),#serversTable tbody td:nth-child(10),
#serversTable thead th:nth-child(11),#serversTable tbody td:nth-child(11),
#serversTable thead th:nth-child(12),#serversTable tbody td:nth-child(12){
width:70px;min-width:70px;max-width:70px;text-align:center;padding-left:1.1rem;padding-right:0; /* 继续右移并贴近右侧列 */
}
/* 月流量胶囊 */
.caps-traffic{display:inline-flex;align-items:center;gap:6px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));border:1px solid var(--border);padding:3px 12px 3px 10px;border-radius:999px;font-size:12px;line-height:1;font-weight:500;position:relative;box-shadow:0 2px 4px -2px rgba(0,0,0,.35),0 0 0 1px rgba(255,255,255,.03);}
.caps-traffic:before{content:"";position:absolute;inset:0;border-radius:inherit;background:radial-gradient(circle at 20% 20%,rgba(255,255,255,.06),transparent 70%);pointer-events:none;}
.caps-traffic .io{display:inline-flex;align-items:center;gap:2px;font-variant-numeric:tabular-nums;letter-spacing:.3px;}
.caps-traffic .io.in{color:var(--ok);}
.caps-traffic .io.out{color:var(--accent);}
.caps-traffic .sep{opacity:.4;font-size:11px;display:none;}
.caps-traffic.sm{padding:2px 8px 2px 7px;font-size:11px;gap:5px;}
.caps-traffic.sm .io{font-size:11px;}
/* 双色胶囊:左红右黄 */
.caps-traffic.duo{background:none;border:0;gap:0;padding:0;box-shadow:none;position:relative;border-radius:999px;overflow:hidden;font-size:12px;}
/* 宽度按内容自适应(不再拉伸占满列),每半边仅为其文本 + padding可容纳最大 111.1MB */
.caps-traffic.duo .half{flex:0 0 auto;display:flex;align-items:center;justify-content:center;padding:2px 4px;font-variant-numeric:tabular-nums;font-weight:600;line-height:1.25; /* 与 .pill 保持一致高度 */ letter-spacing:.25px;color:#fff;font-size:12px;white-space:nowrap;}
/* 双色胶囊配色
normal (默认): 左白(#fff) 右蓝(accent)
heavy (>=500GB 任一方向): 左黄(warn) 右红(danger)
*/
/* normal 初始:淡绿色(入) + 淡蓝色(出) */
.caps-traffic.duo.normal .half.in{background:#d1fae5;color:#065f46;} /* emerald-100 / text-emerald-800 */
body.light .caps-traffic.duo.normal .half.in{background:#d1fae5;color:#065f46;}
.caps-traffic.duo.normal .half.out{background:#bfdbfe;color:#1e3a8a;} /* blue-200 / text-blue-900 */
body.light .caps-traffic.duo.normal .half.out{background:#bfdbfe;color:#1e3a8a;}
.caps-traffic.duo.heavy .half.in{background:var(--warn);color:#111;}
body.light .caps-traffic.duo.heavy .half.in{background:var(--warn);color:#111;}
.caps-traffic.duo.heavy .half.out{background:var(--danger);color:#fff;}
body.light .caps-traffic.duo.heavy .half.out{color:#fff;}
/* 半之间分隔线 */
.caps-traffic.duo .half + .half{border-left:1px solid rgba(0,0,0,.18);}
body.light .caps-traffic.duo .half + .half{border-left:1px solid rgba(0,0,0,.08);}
.caps-traffic.duo.sm .half{padding:1px 4px;font-size:10px;min-width:0;}
table.data tbody tr:last-child td{border-bottom:none}
table.data tbody tr:hover{background:rgba(255,255,255,.04)}
.badge{display:inline-block;padding:2px 6px;font-size:11px;border-radius:12px;font-weight:500;line-height:1.2;background:var(--bg);border:1px solid var(--border);color:var(--text-dim)}
.badge.ok{background:rgba(16,185,129,.15);color:var(--ok);border-color:rgba(16,185,129,.3)}
.badge.warn{background:rgba(245,158,11,.15);color:var(--warn);border-color:rgba(245,158,11,.4)}
.badge.err{background:rgba(239,68,68,.15);color:var(--danger);border-color:rgba(239,68,68,.4)}
.footer{margin:2rem 0 2.5rem;display:flex;align-items:center;justify-content:center;gap:.5rem;font-size:12px;color:var(--text-dim)}
.footer a{color:var(--text-dim)}
.footer a:hover{color:var(--accent)}
.muted{color:var(--text-dim)}
.status-off{color:var(--danger);font-weight:600}
.status-on{color:var(--ok);font-weight:600}
@media (max-width:1100px){.nav{flex-wrap:wrap}.table-wrap{border-radius:8px}}
@keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
/* modal styles */
.modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.55);display:flex;align-items:flex-start;justify-content:center;padding:5vh 1rem;z-index:50;backdrop-filter:blur(4px)}
.modal-box{position:relative;width:100%;max-width:560px;background:var(--bg-alt);border:1px solid var(--border);border-radius:16px;box-shadow:0 8px 30px -6px rgba(0,0,0,.6);padding:1.25rem 1.35rem;display:flex;flex-direction:column;gap:.9rem;animation:fade .25s ease}
.modal-title{margin:0;font-size:16px;font-weight:600;letter-spacing:.5px}
.modal-close{position:absolute;top:10px;right:12px;background:transparent;border:0;color:var(--text-dim);font-size:20px;line-height:1;cursor:pointer;padding:4px;border-radius:8px;transition:var(--trans)}
.modal-close:hover{color:var(--text);background:var(--bg)}
.modal-content{font-size:13px;line-height:1.5;display:grid;gap:.6rem}
.kv{display:flex;justify-content:space-between;gap:1rem;padding:.5rem .75rem;background:linear-gradient(145deg,var(--bg),var(--bg-alt));border:1px solid var(--border);border-radius:10px}
.kv span{white-space:nowrap}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}
/* 资源使用百分比色彩标签 */
/* 回退:移除资源使用百分比彩色标签样式 */
/* 详情弹窗三列信息行 */
/* 旧 spark 样式已移除,现使用半圆仪表盘 */
/* 全圆旧样式(保留以便回退) */
.gauge{--p:0;--col:var(--accent);width:74px;height:74px;position:relative;display:grid;place-items:center;font-size:11px;font-family:ui-monospace,monospace;font-weight:600;color:var(--text);}
.gauge:before{content:"";position:absolute;inset:0;border-radius:50%;background:conic-gradient(var(--col) calc(var(--p)*1turn),rgba(255,255,255,0.06) 0);mask:radial-gradient(circle at 50% 50%,transparent 58%,#000 59%);-webkit-mask:radial-gradient(circle at 50% 50%,transparent 58%,#000 59%);border:1px solid var(--border);box-shadow:0 2px 6px -2px rgba(0,0,0,.4),0 0 0 1px rgba(255,255,255,.05);}
.gauge span{position:relative;z-index:1}
/* 半圆仪表盘 */
.gauge-half{--p:0;width:60px;height:34px;position:relative;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;font-family:ui-monospace,monospace;font-size:10px;font-weight:600;gap:0;color:var(--text);}
.gauge-half svg{width:100%;height:28px;overflow:visible;}
.gauge-half path{fill:none;stroke-linecap:round;}
/* 基础颜色 (暗色/亮色自动过渡) */
.gauge-half path.track{stroke:color-mix(in srgb,var(--text-dim) 18%,transparent);stroke-width:6;}
.gauge-half path.arc{stroke:var(--gauge-base,#3b82f6);stroke-width:8;stroke-dasharray:126;stroke-dashoffset:calc(126*(1 - var(--p)));transition:stroke-dashoffset .8s cubic-bezier(.4,0,.2,1),stroke .35s;filter:drop-shadow(0 1px 2px rgba(0,0,0,.45));}
.gauge-half[data-type=mem] path.arc{--gauge-base:#10b981}
.gauge-half[data-type=hdd] path.arc{--gauge-base:#f59e0b}
/* 阈值颜色:>=50% 警告黄,>=90% 危险红 */
.gauge-half[data-warn] path.arc{stroke:var(--warn)}
.gauge-half[data-bad] path.arc{stroke:var(--danger)}
/* 指针:以中心(50,50)为原点旋转;半圆角度范围 180deg -> 从 180deg (左) 到 0deg(右) */
.gauge-half span{line-height:1;position:relative;top:-6px;font-size:12px;}
/* 亮色模式细化对比度 */
body.light .gauge-half path.track{stroke:color-mix(in srgb,var(--text-dim) 28%,transparent)}
body.light .gauge-half path.arc{filter:none}
body.light .gauge-half .needle{background:linear-gradient(var(--text),var(--text-dim))}
/* status pill */
.pill{display:inline-block;padding:2px 8px;font-size:12px;font-weight:600;border-radius:999px;letter-spacing:.45px;min-width:48px;text-align:center;line-height:1.25;border:0;box-shadow:0 2px 4px -1px rgba(0,0,0,.4),0 0 0 1px rgba(255,255,255,.04);transition:var(--trans);color:#fff}
.pill.on{background:var(--ok)}
.pill.off{background:var(--danger)}
.pill.on:hover{filter:brightness(1.1)}
.pill.off:hover{filter:brightness(1.1)}
/* buckets CU/CT/CM (simple version) */
.buckets{display:flex;align-items:flex-end;gap:8px;min-width:140px}
.bucket{position:relative;width:26px;height:34px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));border:1px solid var(--border);border-radius:8px;padding:4px 4px 16px;box-sizing:border-box;display:flex;justify-content:flex-end}
.bucket span{display:block;width:100%;background:var(--accent);border-radius:4px 4px 6px 6px;height:var(--h);align-self:flex-end;transition:height .8s cubic-bezier(.4,0,.2,1),background .3s}
.bucket[data-lv=warn] span{background:var(--warn)}
.bucket[data-lv=bad] span{background:var(--danger)}
.bucket label{position:absolute;left:0;right:0;bottom:2px;font-size:10px;text-align:center;color:var(--text-dim);pointer-events:none}
.bucket:hover label{color:var(--text)}
/* 居中联通电信移动列 */
#serversTable thead th:last-child, #serversTable tbody td:last-child { text-align:center; }
/* 放大第13列宽度以容纳更宽水桶 */
#serversTable thead th:nth-child(13),#serversTable tbody td:nth-child(13){
width:150px;min-width:150px;max-width:150px;padding-left:0;padding-right:.55rem; /* 去除左 padding 进一步贴近 */
}
/* 调整“总流量”表头第9列padding 使标题文字居中,不受正文额外右 padding 影响 */
#serversTable thead th:nth-child(9){padding-left:.75rem;padding-right:.75rem;}
.buckets{justify-content:center}
/* 响应式隐藏非关键列,保持可读性 */
@media (max-width:1100px){
#serversTable{min-width:100%;}
#serversTable thead th:nth-child(3),
#serversTable tbody td:nth-child(3), /* 节点 */
#serversTable thead th:nth-child(4),
#serversTable tbody td:nth-child(4), /* 虚拟化 */
#serversTable thead th:nth-child(8),
#serversTable tbody td:nth-child(8) /* 当前网络 */ {display:none}
}
@media (max-width:820px){
#serversTable thead th:nth-child(5),
#serversTable tbody td:nth-child(5), /* 在线 */
#serversTable thead th:nth-child(6),
#serversTable tbody td:nth-child(6) /* 负载 */ {display:none}
.buckets{gap:6px;min-width:100px}
}
@media (max-width:640px){
#serversTable thead th:nth-child(4),
#serversTable tbody td:nth-child(4) /* 位置 */ {display:none}
.topbar{flex-wrap:wrap;padding:.6rem .8rem}
.actions{width:100%;justify-content:flex-end;margin-top:.4rem}
.wrapper{padding:0 .7rem}
#panel-servers .table-wrap{display:none;}
#serversCards{display:grid!important;grid-template-columns:1fr;gap:.75rem;margin-top:.5rem;}
/* 服务与证书移动端卡片 */
#panel-monitors .table-wrap, #panel-ssl .table-wrap{display:none;}
#monitorsCards,#sslCards{display:grid!important;grid-template-columns:1fr;gap:.75rem;margin-top:.5rem;}
.modal-box{max-width:100%;border-radius:14px;padding:1rem .95rem;}
.modal-content{max-height:65vh;overflow:auto;}
}
/* SSL 表(证书)列宽与换行修正,避免复用服务器列宽导致名称与域名重叠 */
#sslTable th,#sslTable td{white-space:nowrap;padding:.55rem .75rem;}
#sslTable th:nth-child(1),#sslTable td:nth-child(1){width:140px;min-width:120px;}
/* 域名允许换行以防过长挤压 */
#sslTable th:nth-child(2),#sslTable td:nth-child(2){white-space:normal;max-width:320px;overflow-wrap:anywhere;}
/* === 覆盖:要求三个表 (servers / monitors / ssl) 全部改为自动宽度 === */
/* 1. 取消全局 table.data 的 min-width 对这三个表的影响 */
#serversTable,#monitorsTable,#sslTable{min-width:0;}
/* 2. 统一去除之前为 serversTable 设定的列固定宽度,允许浏览器自动分配 */
#serversTable thead th,#serversTable tbody td,
#monitorsTable thead th,#monitorsTable tbody td,
#sslTable thead th,#sslTable tbody td{
width:auto!important;min-width:0!important;max-width:none!important;
padding:.55rem .7rem;
}
/* 3. 允许证书域名不受 max-width 限制(如仍需换行可保留 overflow-wrap */
#sslTable th:nth-child(2),#sslTable td:nth-child(2){max-width:none;white-space:normal;overflow-wrap:anywhere;}
/* 4. 取消“联通|电信|移动”列固定宽度 */
#serversTable thead th:nth-child(13),#serversTable tbody td:nth-child(13){width:auto!important;min-width:0!important;}
/* 5. 如需稍微限制仪表盘相关列最小可读宽度,可设定一个较小下限 (可选) -- 暂不设置,完全交由自动布局 */
/* === 新增:为“月流量 / 当前网络 / 总流量”三列设置最小宽度,防止内容被压缩换行或挤压 === */
/* 目标最小宽度按示例 "111.1GB|111.1GB" 设计13 个字符左右),取 16ch 留余量 */
#serversTable thead th:nth-child(2),#serversTable tbody td:nth-child(2),
#serversTable thead th:nth-child(8),#serversTable tbody td:nth-child(8),
#serversTable thead th:nth-child(9),#serversTable tbody td:nth-child(9){
min-width:22ch !important; /* 保留自动宽度,但不小于此值 */
text-align:center;
font-variant-numeric:tabular-nums;
}
.cards .card{border:1px solid var(--border);border-radius:12px;padding:.75rem .85rem;background:linear-gradient(145deg,var(--bg),var(--bg-alt));display:flex;flex-direction:column;gap:.45rem;position:relative;}
.cards .card.offline{opacity:.6;}
.cards .card.high-load{border-color:rgba(239,68,68,.55);box-shadow:0 0 0 1px rgba(239,68,68,.4),0 4px 16px -4px rgba(239,68,68,.3);}
table.data tbody tr.high-load{background:rgba(239,68,68,.10);}
table.data tbody tr.high-load:hover{background:rgba(239,68,68,.18);}
/* 旧进度条相关样式已清理 */
.cards .card-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;}
.cards .card-title{font-weight:600;font-size:.95rem;}
.cards .tag{font-size:.65rem;padding:.15rem .4rem;border-radius:4px;background:var(--border);letter-spacing:.5px;}
.cards .status-pill{font-size:.6rem;padding:.2rem .45rem;border-radius:999px;font-weight:500;}
.cards .status-pill.on{background:var(--ok);color:#fff;}
.cards .status-pill.off{background:var(--danger);color:#fff;}
.cards .kvlist{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.35rem .75rem;font-size:.7rem;line-height:1.25;}
.cards .kvlist div{display:flex;flex-direction:column;}
.cards .kvlist span.key{opacity:.6;}
.cards .buckets{margin-top:.25rem;}
.cards .expand-btn{position:absolute;top:.5rem;right:.5rem;background:transparent;border:0;color:var(--text-dim);cursor:pointer;font-size:.9rem;padding:.2rem;}
.cards .expand-btn:focus, .cards .expand-btn:hover{color:var(--text);}
.cards .expand-area{margin-top:.4rem;display:none;animation:fadeIn .25s ease;}
.cards .card.expanded .expand-area{display:block;}
/* 旧移动端 latency spark 样式移除 */
/* 新 Logo 样式 */
.brand{display:flex;align-items:center;gap:.55rem;font-weight:600;letter-spacing:.5px;font-size:16px;position:relative}
.brand .logo-mark{display:inline-flex;width:34px;height:34px;border-radius:10px;background:linear-gradient(145deg,var(--logo-start) 0%,var(--logo-end) 90%);color:#fff;align-items:center;justify-content:center;box-shadow:0 4px 12px -2px rgba(0,0,0,.45),0 0 0 1px rgba(255,255,255,.08);transition:var(--trans)}
.brand .logo-mark svg{display:block}
.brand .logo-text{font-size:17px;font-weight:700;line-height:1;display:flex;align-items:baseline;gap:2px;letter-spacing:1px}
.brand .logo-text .logo-accent{background:linear-gradient(90deg,var(--logo-accent-grad-start),var(--logo-accent-grad-end));-webkit-background-clip:text;background-clip:text;color:transparent;filter:drop-shadow(0 2px 4px rgba(0,0,0,.3))}
.brand:hover .logo-mark{transform:translateY(-2px) scale(1.05)}
.brand:hover .logo-text{color:var(--text)}
@media (max-width:640px){.brand .logo-text{font-size:15px}.brand .logo-mark{width:30px;height:30px}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,54 +0,0 @@
body { background: #222 url('../img/dark.png'); color: #fff; }
.navbar { min-height: 40px; }
.navbar-brand { color: #FFF !important; padding: 10px; font-size: 20px; }
.dropdown .dropdown-toggle { padding-bottom: 10px; padding-top: 10px; }
.dropdown-menu > li > a { color: #FFF !important; background-color: #222222 !important; }
.dropdown-menu > li > a:hover { color: #FFF !important; background: #000 !important; }
.dropdown-menu { background: #222 !important; background-color: #222222 !important; }
.navbar-inverse .navbar-inner { background-color:#1B1B1B; background-image:-moz-linear-gradient(top, #222222, #111111); background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111)); background-image:-webkit-linear-gradient(top, #222222, #111111); background-image:-o-linear-gradient(top, #222222, #111111); background-image:linear-gradient(to bottom, #222222, #111111); background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0); border-color: #252525; }
.content { background: #222; padding: 20px; border-radius: 5px; border: 1px #000 solid; -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); box-shadow: 0 1px 10px rgba(0, 0, 0, .1); margin-bottom: 20px; }
.table { background: #000; margin-bottom: 0; border-collapse: collapse; border-radius: 3px; }
.table th { text-align: center; }
.table-striped tbody > tr.even > td, .table-striped tbody > tr.even > th { background-color: #2F2F2F; }
.table-striped tbody > tr.odd > td, .table-striped tbody > tr.odd > th { background-color: #000; }
.table td { text-align: center; border-color: #2F2F2F; }
.progress { margin-bottom: 0; background: #222; }
.table-hover > tbody > tr:hover > td { background: #414141; }
tr.even.expandRow > :hover { background: #2F2F2F !important; }
tr.odd.expandRow > :hover { background: #000 !important; }
.expandRow > td { padding: 0 !important; border-top: 0px !important; }
#cpu, #ram, #hdd, #network { min-width: 55px; max-width: 100px; }
#ping { min-width: 55px; max-width: 130px; }
@media only screen and (max-width: 992px) {
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
}
@media only screen and (max-width: 720px) {
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
#ping, tr td:nth-child(13) { display:none; visibility:hidden; }
}
@media only screen and (max-width: 600px) {
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
#ping, tr td:nth-child(13) { display:none; visibility:hidden; }
}
@media only screen and (max-width: 533px) {
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
#network, tr td:nth-child(8) { display:none; visibility:hidden; }
#ping, tr td:nth-child(13) { display:none; visibility:hidden; }
}
@media only screen and (max-width: 450px) {
body { font-size: 10px; }
.content { padding: 0; }
#name, tr td:nth-child(3) { min-width: 55px; max-width: 85px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
#network, tr td:nth-child(8) { display:none; visibility:hidden; }
#cpu, #ram, #hdd { min-width: 25px; max-width: 50px; }
#ping, tr td:nth-child(13) { display:none; visibility:hidden; }
}

@ -1,51 +0,0 @@
body { background: #ebebeb url('../img/light.png'); }
.navbar { min-height: 40px; }
.navbar-brand { color: #fff; padding: 10px; font-size: 20px; }
.dropdown .dropdown-toggle { padding-bottom: 10px; padding-top: 10px; }
.navbar-inverse .navbar-brand { color: #fff; padding: 10px; font-size: 20px; }
.content { background: #ffffff; padding: 20px; border-radius: 5px; border: 1px #cecece solid; -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); box-shadow: 0 1px 10px rgba(0, 0, 0, .1); margin-bottom: 20px; }
.table { background: #ffffff; margin-bottom: 0; border-collapse: collapse; border-radius: 3px; }
.table th, .table td { text-align: center; }
.table-striped tbody > tr.even > td, .table-striped tbody > tr.even > th { background-color: #F9F9F9; }
.table-striped tbody > tr.odd > td, .table-striped tbody > tr.odd > th { background-color: #FFF; }
.progress { margin-bottom: 0; }
.progress-bar { color: #000; }
.table-hover > tbody > tr:hover > td { background: #E6E6E6; }
tr.even.expandRow > :hover { background: #F9F9F9 !important; }
tr.odd.expandRow > :hover { background: #FFF !important; }
.expandRow > td { padding: 0 !important; border-top: 0px !important; }
#cpu, #ram, #hdd, #network { min-width: 55px; max-width: 100px; }
#ping { min-width: 55px; max-width: 130px; }
@media only screen and (max-width: 992px) {
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
}
@media only screen and (max-width: 720px) {
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
#ping, tr td:nth-child(13) { display:none; visibility:hidden; }
}
@media only screen and (max-width: 600px) {
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
#ping, tr td:nth-child(13) { display:none; visibility:hidden; }
}
@media only screen and (max-width: 533px) {
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
#network, tr td:nth-child(8) { display:none; visibility:hidden; }
#ping, tr td:nth-child(13) { display:none; visibility:hidden; }
}
@media only screen and (max-width: 450px) {
body { font-size: 10px; }
.content { padding: 0; }
#name, tr td:nth-child(3) { min-width: 55px; max-width: 85px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
#network, tr td:nth-child(8) { display:none; visibility:hidden; }
#cpu, #ram, #hdd { min-width: 25px; max-width: 50px; }
#ping, tr td:nth-child(13) { display:none; visibility:hidden; }
}

Binary file not shown.

Before

Width: 128px  |  Height: 86px  |  Size: 44 KiB

11
web/favicon.svg Normal file

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
<defs>
<linearGradient id="g" x1="8" y1="8" x2="56" y2="56" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#3b82f6" />
<stop offset="1" stop-color="#2563eb" />
</linearGradient>
</defs>
<rect x="8" y="12" width="48" height="40" rx="12" fill="url(#g)" />
<path stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" d="M22 38v-8l6 4 8-10 6 6v8" />
<circle cx="22" cy="22" r="4" fill="#fff" />
</svg>

After

(image error) Size: 539 B

Binary file not shown.

Before

(image error) Size: 600 B

Binary file not shown.

Before

(image error) Size: 19 KiB

@ -1,111 +1,119 @@
<!DOCTYPE html> <!DOCTYPE html>
<!-- <html lang="zh-CN">
json字段保持完整, 后期更新会向下兼容 <head>
可以自定义前端展示 <meta charset="utf-8" />
ლ(•̀ _ •́ ლ) <meta name="viewport" content="width=device-width,initial-scale=1" />
ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ) <meta name="description" content="云监控,ServerStatus中文版,ServerStatus,ServerStatus cppla" />
ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ) <title>云监控</title>
ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ) <link rel="icon" type="image/svg+xml" href="favicon.svg" />
ლ(•̀ _ •́ ლ) <link rel="alternate icon" href="favicon.ico" />
by:https://www.cpp.la <link rel="stylesheet" href="css/app.css" />
--> </head>
<html> <body>
<head> <header class="topbar">
<title>云监控</title> <div class="brand" title="云监控">
<meta charset="utf-8"> <span class="logo-mark" aria-hidden="true">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <path d="M7.5 17a4.5 4.5 0 0 1-.9-8.92A6 6 0 0 1 18.4 9.6 4 4 0 0 1 18 17H7.5Z" />
<meta name="description" content="云监控"> <rect x="9" y="11" width="6" height="4" rx="1" />
<meta name="author" content="BotoX"> <path d="M11 15v2.5a.5.5 0 0 0 .5.5h1" />
<link rel="stylesheet" href="css/bootstrap.min.css"> </svg>
<link rel="stylesheet" href="css/bootstrap-theme.min.css"> </span>
<link rel="stylesheet" href="css/dark.css" title="dark"> <span class="logo-text"><span class="logo-accent"></span>监控</span>
<link rel="stylesheet" href="css/light.css" title="light"> </div>
<style> <nav class="nav" id="navTabs">
body { <button data-tab="servers" class="active">主机</button>
padding-top: 70px; <button data-tab="monitors">服务</button>
padding-bottom: 30px; <button data-tab="ssl">证书</button>
} </nav>
</style> <div class="actions">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <button id="themeToggle" title="切换主题 (当前: 自动或手动)" aria-label="切换主题">🌓</button>
<!--[if lt IE 9]> <span id="lastUpdate" class="muted">更新中...</span>
<script src="js/html5shiv.js"></script> </div>
<script src="js/respond.min.js"></script> </header>
<![endif]-->
</head>
<body>
<div role="navigation" class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<div class="navbar-header">
<button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="#" class="navbar-brand">云监控</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="dropdown">
<a data-toggle="dropdown" class="dropdown-toggle" href="#">风格<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#" onclick="setActiveStyleSheet('dark')">黑夜</a></li>
<li><a href="#" onclick="setActiveStyleSheet('light')">白天</a></li>
</ul>
</li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
</div>
<div class="container content"> <main class="wrapper">
<div id="loading-notice"> <div id="notice" class="notice info">初始化中...</div>
<noscript>
<div class="alert alert-danger" style="text-align: center;">
<strong>Enable JavaScript</strong> , please do it.
</div>
</noscript>
<div style="text-align: center;">
警告:如果出现此消息请确保您已启用Javascript! <br />否则云监控主服务没启动或已关闭.
</div>
<p></p>
</div>
<table class="table table-striped table-condensed table-hover">
<thead>
<tr>
<th id="status4" style="text-align: center;">协议</th>
<th id="ipstatus" style="text-align: center;">Flight</th>
<th id="name">节点名</th>
<th id="type">虚拟化</th>
<th id="location">位置</th>
<th id="uptime">在线时间</th>
<th id="load">负载</th>
<th id="network">网络 ↓|↑</th>
<th id="traffic">流量 ↓|↑</th>
<th id="cpu">处理器</th>
<th id="ram">内存</th>
<th id="hdd">硬盘</th>
<th id="ping">丢包率(CU|CT|CM)</th>
</tr>
</thead>
<tbody id="servers">
<!-- Servers here \o/ -->
</tbody>
</table>
<br />
<div id="updated">Updating...</div>
</div>
<div class="container"> <section id="panel-servers" class="panel active" aria-labelledby="主机">
<p style="text-align: center; font-size: 10px;"> <div class="table-wrap">
<a href="https://github.com/cppla/ServerStatus">ServerStatus中文版</a> <table class="data" id="serversTable">
</p> <thead>
</div> <tr>
<script src="js/jquery.min.js"></script> <th>协议</th>
<script src="js/bootstrap.min.js"></script> <th>月流量 ↓|↑</th>
<script src="js/serverstatus.js"></script> <th>节点</th>
</body> <th>虚拟化</th>
</html> <th>位置</th>
<th>在线</th>
<th>负载</th>
<th>当前网络 ↓|↑</th>
<th>总流量 ↓|↑</th>
<th>CPU</th>
<th>内存</th>
<th>硬盘</th>
<th style="text-align:center;">联通|电信|移动</th>
</tr>
</thead>
<tbody id="serversBody"></tbody>
</table>
</div>
<!-- 移动端卡片布局 -->
<div id="serversCards" class="cards" style="display:none;"></div>
</section>
<section id="panel-monitors" class="panel" aria-labelledby="服务">
<div class="table-wrap">
<table class="data" id="monitorsTable">
<thead>
<tr>
<th>协议</th>
<th>监测节点</th>
<th>监测位置</th>
<th>监测内容</th>
</tr>
</thead>
<tbody id="monitorsBody"></tbody>
</table>
</div>
<!-- 移动端卡片布局 (服务) -->
<div id="monitorsCards" class="cards" style="display:none;"></div>
</section>
<section id="panel-ssl" class="panel" aria-labelledby="证书">
<div class="table-wrap">
<table class="data" id="sslTable">
<thead>
<tr>
<th>名称</th>
<th>域名</th>
<th>端口</th>
<th>剩余(天)</th>
<th>到期(UTC)</th>
<th>状态</th>
</tr>
</thead>
<tbody id="sslBody"></tbody>
</table>
</div>
<!-- 移动端卡片布局 (证书) -->
<div id="sslCards" class="cards" style="display:none;"></div>
</section>
</main>
<!-- 详情弹窗 -->
<div id="detailModal" class="modal-backdrop" style="display:none;">
<div class="modal-box" role="dialog" aria-modal="true" aria-labelledby="detailTitle">
<button class="modal-close" id="detailClose" aria-label="关闭">×</button>
<h3 id="detailTitle" class="modal-title">节点详情</h3>
<div id="detailContent" class="modal-content">加载中...</div>
</div>
</div>
<footer class="footer">
<a href="https://github.com/cppla/ServerStatus" target="_blank" rel="noopener">ServerStatus中文版</a>
</footer>
<script src="js/app.js" defer></script>
</body>
</html>

537
web/js/app.js Normal file

@ -0,0 +1,537 @@
// 简洁现代前端 - 仅使用原生 JS
const S = { updated:0, servers:[], ssl:[], error:false, hist:{}, metricHist:{}, loadHist:{} };// hist latency; metricHist: {key:{cpu:[],mem:[],hdd:[]}}; loadHist: {key:[]}
const els = {
notice: ()=>document.getElementById('notice'),
last: ()=>document.getElementById('lastUpdate'),
serversBody: ()=>document.getElementById('serversBody'),
monitorsBody: ()=>document.getElementById('monitorsBody'),
sslBody: ()=>document.getElementById('sslBody')
};
// (清理) 已移除 bytes / humanAuto 等未使用的通用进位函数
// 最小单位 MB
function humanMinMBFromKB(kb){ if(kb==null||isNaN(kb)) return '-'; // 输入单位: KB
let mb = kb/1000; const units=['MB','GB','TB','PB']; let i=0; while(mb>=1000 && i<units.length-1){ mb/=1000;i++; }
const out = mb>=100? mb.toFixed(0): mb.toFixed(1); return out+units[i]; }
function humanMinMBFromMB(mbVal){ if(mbVal==null||isNaN(mbVal)) return '-'; // 输入单位: MB
let v=mbVal; const units=['MB','GB','TB','PB']; let i=0; while(v>=1000 && i<units.length-1){ v/=1000;i++; }
const out = v>=100? v.toFixed(0): v.toFixed(1); return out+units[i]; }
function humanMinMBFromB(bytes){ if(bytes==null||isNaN(bytes)) return '-'; // 输入单位: B
let mb = bytes/1000/1000; const units=['MB','GB','TB','PB']; let i=0; while(mb>=1000 && i<units.length-1){ mb/=1000;i++; }
const out = mb>=100? mb.toFixed(0): mb.toFixed(1); return out+units[i]; }
function humanRateMinMBFromB(bytes){ if(bytes==null||isNaN(bytes)) return '-'; if(bytes<=0) return '0.0MB'; return humanMinMBFromB(bytes); }
function humanMinKBFromB(bytes){ if(bytes==null||isNaN(bytes)) return '-'; // 输入单位: B; 最小单位 KB
let kb = bytes/1000; const units=['KB','MB','GB','TB','PB']; let i=0; while(kb>=1000 && i<units.length-1){ kb/=1000; i++; }
const out = kb>=100? kb.toFixed(0): kb.toFixed(1); return out+units[i]; }
// (清理) pct / clsBy 已不再使用
function humanAgo(ts){ if(!ts) return '-'; const s=Math.floor((Date.now()/1000 - ts)); const m=Math.floor(s/60); return m>0? m+' 分钟前':'几秒前'; }
function num(v){ return (typeof v==='number' && !isNaN(v)) ? v : '-'; }
async function fetchData(){
try {
const r = await fetch('json/stats.json?_='+Date.now());
if(!r.ok) throw new Error(r.status);
const j = await r.json();
if(j.reload) location.reload();
S.updated = j.updated; S.servers = j.servers||[]; S.ssl = j.sslcerts||[]; S.error=false;
// 更新延迟历史 (按节点名聚合)
S.servers.forEach(s=>{
const key = s.name || s.location || 'node';
if(!S.hist[key]) S.hist[key] = {cu:[],ct:[],cm:[]};
const H = S.hist[key];
// 使用 time_ 字段 (ms) 若不存在则跳过
if(typeof s.time_10010 === 'number') H.cu.push(s.time_10010);
if(typeof s.time_189 === 'number') H.ct.push(s.time_189);
if(typeof s.time_10086 === 'number') H.cm.push(s.time_10086);
const MAX=120; // 保留约 120*4s ≈ 8 分钟
['cu','ct','cm'].forEach(k=>{ if(H[k].length>MAX) H[k].splice(0,H[k].length-MAX); });
// 指标历史 (仅在线时记录)
if(!S.metricHist[key]) S.metricHist[key] = {cpu:[],mem:[],hdd:[]};
const MH = S.metricHist[key];
if(s.online4||s.online6){
const memPct = s.memory_total? (s.memory_used/s.memory_total*100):0;
const hddPct = s.hdd_total? (s.hdd_used/s.hdd_total*100):0;
MH.cpu.push(s.cpu||0);
MH.mem.push(memPct||0);
MH.hdd.push(hddPct||0);
const MAXM=120; ['cpu','mem','hdd'].forEach(k=>{ if(MH[k].length>MAXM) MH[k].splice(0,MH[k].length-MAXM); });
}
// 负载历史 (记录 load_1 / load_5 / load_15)
if(!S.loadHist[key]) S.loadHist[key] = {l1:[],l5:[],l15:[]};
const LH = S.loadHist[key];
const pushLoad = (arr,val)=>{ if(typeof val === 'number' && val >= 0){ arr.push(val); if(arr.length>120) arr.splice(0,arr.length-120); } };
pushLoad(LH.l1, s.load_1);
pushLoad(LH.l5, s.load_5);
pushLoad(LH.l15, s.load_15);
});
render();
}catch(e){ S.error=true; els.notice().textContent = '数据获取失败'; console.error(e); }
}
function render(){
els.notice().style.display='none';
renderServers();
renderServersCards();
renderMonitors();
renderMonitorsCards();
renderSSL();
renderSSLCards();
updateTime();
}
function renderServers(){
const tbody = els.serversBody();
let html='';
S.servers.forEach((s,idx)=>{
const online = s.online4||s.online6;
const proto = online ? (s.online4 && s.online6? '双栈': s.online4? 'IPv4':'IPv6') : '离线';
const statusPill = online ? `<span class="pill on">${proto}</span>` : `<span class="pill off">${proto}</span>`;
const memPct = s.memory_total? (s.memory_used/s.memory_total*100):0;
const hddPct = s.hdd_total? (s.hdd_used/s.hdd_total*100):0;
const monthInBytes = (s.network_in - s.last_network_in) || 0; // 原始: B
const monthOutBytes = (s.network_out - s.last_network_out) || 0;
const monthIn = humanMinMBFromB(monthInBytes); // 最小单位 MB
const monthOut = humanMinMBFromB(monthOutBytes);
const HEAVY_THRESHOLD = 500 * 1000 * 1000 * 1000; // 500GB
const heavy = monthInBytes >= HEAVY_THRESHOLD || monthOutBytes >= HEAVY_THRESHOLD;
const trafficCls = heavy ? 'caps-traffic duo heavy' : 'caps-traffic duo normal';
const netNow = humanMinKBFromB(s.network_rx) + ' | ' + humanMinKBFromB(s.network_tx); // 最小单位 KB
const netTotal = humanMinMBFromB(s.network_in)+' | '+humanMinMBFromB(s.network_out); // 最小单位 MB
const p1 = (s.ping_10010||0); const p2 = (s.ping_189||0); const p3 = (s.ping_10086||0);
function bucket(p){ const v = Math.max(0, Math.min(100, p)); const level = v>=20?'bad':(v>=10?'warn':'ok'); return `<div class=\"bucket\" data-lv=\"${level}\"><span style=\"--h:${v}%\"></span><label>${v.toFixed(0)}%</label></div>`; }
const pingBuckets = `<div class=\"buckets\" title=\"CU/CT/CM\">${bucket(p1)}${bucket(p2)}${bucket(p3)}</div>`;
const key = s.name || s.location || 'node';
const rowCursor = online? 'pointer':'default';
const highLoad = online && ( (s.cpu||0)>=90 || (memPct)>=90 || (hddPct)>=90 );
html += `<tr data-idx="${idx}" data-online="${online?1:0}" class="row-server${highLoad?' high-load':''}" style="cursor:${rowCursor};${online?'':'opacity:.65;'}">
<td>${statusPill}</td>
<td><span class="${trafficCls}" title="本月下行 | 上行 (≥500GB 触发红黄)"><span class="half in">${monthIn}</span><span class="half out">${monthOut}</span></span></td>
<td>${s.name||'-'}</td>
<td>${s.type||'-'}</td>
<td>${s.location||'-'}</td>
<td>${s.uptime||'-'}</td>
<td>${s.load_1==-1?'':Math.max(0,(s.load_1||0)).toFixed(2)}</td>
<td>${netNow}</td>
<td>${netTotal}</td>
<td>${online?gaugeHTML('cpu', s.cpu||0):'-'}</td>
<td>${online?gaugeHTML('mem', memPct):'-'}</td>
<td>${online?gaugeHTML('hdd', hddPct):'-'}</td>
<td>${pingBuckets}</td>
</tr>`;
});
tbody.innerHTML = html || `<tr><td colspan="13" class="muted" style="text-align:center;padding:1rem;">无数据</td></tr>`;
// 绑定行点击
tbody.querySelectorAll('tr.row-server').forEach(tr=>{
tr.addEventListener('click',()=>{
if(tr.getAttribute('data-online')!=='1') return; // 离线不弹出
const i = parseInt(tr.getAttribute('data-idx'));
openDetail(i);
});
});
// 仪表盘无需历史 spark 小图
}
// 生成仪表盘 (圆形 conic-gradient)
function gaugeHTML(type,val){
const pct = Math.max(0,Math.min(100,val));
const p = (pct/100).toFixed(3);
const warnAttr = pct>=90? 'data-bad' : (pct>=50? 'data-warn' : '');
return `<div class="gauge-half" data-type="${type}" ${warnAttr} style="--p:${p}" title="${labelOf(type)} ${pct.toFixed(0)}%">
<svg viewBox="0 0 100 50" preserveAspectRatio="xMidYMid meet" aria-hidden="true">
<path class="track" d="M10 50 A40 40 0 0 1 90 50" />
<path class="arc" d="M10 50 A40 40 0 0 1 90 50" />
</svg>
<span>${pct.toFixed(0)}%</span>
</div>`;
}
function labelOf(t){ return t==='cpu'?'CPU': t==='mem'?'内存':'硬盘'; }
function renderServersCards(){
const wrap = document.getElementById('serversCards');
if(!wrap) return;
// 仅在窄屏时显示 (和 CSS 一致判断, 可稍放宽避免闪烁)
if(window.innerWidth>700){ wrap.innerHTML=''; return; }
let html='';
S.servers.forEach((s,idx)=>{
const online = s.online4||s.online6;
const proto = online ? (s.online4 && s.online6? '双栈': s.online4? 'IPv4':'IPv6') : '离线';
const pill = `<span class="status-pill ${online?'on':'off'}">${proto}</span>`;
const memPct = s.memory_total? (s.memory_used/s.memory_total*100):0;
const hddPct = s.hdd_total? (s.hdd_used/s.hdd_total*100):0;
// 月流量(移动端)并应用 500GB 阈值配色逻辑
const monthInBytes = (s.network_in - s.last_network_in) || 0; // B
const monthOutBytes = (s.network_out - s.last_network_out) || 0;
const monthIn = humanMinMBFromB(monthInBytes);
const monthOut = humanMinMBFromB(monthOutBytes);
const HEAVY_THRESHOLD = 500 * 1000 * 1000 * 1000; // 500GB
const heavy = monthInBytes >= HEAVY_THRESHOLD || monthOutBytes >= HEAVY_THRESHOLD;
const trafficCls = heavy ? 'caps-traffic duo heavy sm' : 'caps-traffic duo normal sm';
const netNow = humanMinKBFromB(s.network_rx)+' | '+humanMinKBFromB(s.network_tx);
const netTotal = humanMinMBFromB(s.network_in)+' | '+humanMinMBFromB(s.network_out);
const p1 = (s.ping_10010||0); const p2=(s.ping_189||0); const p3=(s.ping_10086||0);
function bucket(p){ const v=Math.max(0,Math.min(100,p)); const level = v>=20?'bad':(v>=10?'warn':'ok'); return `<div class=\"bucket\" data-lv=\"${level}\"><span style=\"--h:${v}%\"></span><label>${v.toFixed(0)}%</label></div>`; }
const buckets = `<div class=\"buckets\">${bucket(p1)}${bucket(p2)}${bucket(p3)}</div>`;
const key = s.name || s.location || 'node';
const highLoad = online && ( (s.cpu||0)>=90 || (memPct)>=90 || (hddPct)>=90 );
html += `<div class=\"card${online?'':' offline'}${highLoad?' high-load':''}\" data-idx=\"${idx}\" data-online=\"${online?1:0}\">\n <button class=\"expand-btn\" aria-label=\"展开\">▼</button>\n <div class=\"card-header\">\n <div class=\"card-title\">${s.name||'-'} <span class=\"tag\">${s.location||'-'}</span></div>\n ${pill}\n </div>\n <div class=\"kvlist\">\n <div><span class=\"key\">负载</span><span>${s.load_1==-1?'':s.load_1?.toFixed(2)}</span></div>\n <div><span class=\"key\">在线</span><span>${s.uptime||'-'}</span></div>\n <div><span class=\"key\">月流量</span><span><span class=\"${trafficCls}\" title=\"本月下行 | 上行 (≥500GB 触发红黄)\"><span class=\"half in\">${monthIn}</span><span class=\"half out\">${monthOut}</span></span></span></div>\n <div><span class=\"key\">网络</span><span>${netNow}</span></div>\n <div><span class=\"key\">总流量</span><span>${netTotal}</span></div>\n <div><span class=\"key\">CPU</span><span>${s.cpu||0}%</span></div>\n <div><span class=\"key\">内存</span><span>${memPct.toFixed(0)}%</span></div>\n <div><span class=\"key\">硬盘</span><span>${hddPct.toFixed(0)}%</span></div>\n </div>\n ${buckets}\n <div class=\"expand-area\">\n <div style=\"font-size:.65rem;opacity:.7;margin-top:.3rem\">${online?'点击卡片可查看详情':'离线,不可查看详情'}</div>\n </div>\n </div>`;
});
wrap.innerHTML = html || '<div class="muted" style="font-size:.75rem;text-align:center;padding:1rem;">无数据</div>';
wrap.querySelectorAll('.card').forEach(card=>{
const idx = parseInt(card.getAttribute('data-idx'));
card.addEventListener('click', e=>{
if(e.target.classList.contains('expand-btn')){ card.classList.toggle('expanded'); e.stopPropagation(); return;}
if(card.getAttribute('data-online')!=='1') return; // 离线不弹
openDetail(idx);
});
});
}
function renderMonitors(){
const tbody = els.monitorsBody();
let html='';
S.servers.forEach(s=>{
html += `<tr>
<td>${(s.online4||s.online6)?'在线':'离线'}</td>
<td>${s.name||'-'}</td>
<td>${s.location||'-'}</td>
<td>${s.custom||'-'}</td>
</tr>`;
});
tbody.innerHTML = html || `<tr><td colspan="4" class="muted" style="text-align:center;padding:1rem;">无数据</td></tr>`;
}
// 服务卡片 (移动端)
function renderMonitorsCards(){
const wrap = document.getElementById('monitorsCards');
if(!wrap) return; if(window.innerWidth>700){ wrap.innerHTML=''; return; }
let html='';
S.servers.forEach(s=>{
const online = (s.online4||s.online6)?'在线':'离线';
const pill = `<span class="status-pill ${online==='在线'?'on':'off'}">${online}</span>`;
html += `<div class="card">
<div class="card-header"><div class="card-title">${s.name||'-'} <span class="tag">${s.location||'-'}</span></div>${pill}</div>
<div class="kvlist" style="grid-template-columns:repeat(2,minmax(0,1fr));">
<div><span class="key">监测内容</span><span>${s.custom||'-'}</span></div>
<div><span class="key">协议</span><span>${online}</span></div>
</div>
</div>`;
});
wrap.innerHTML = html || '<div class="muted" style="font-size:.75rem;text-align:center;padding:1rem;">无数据</div>';
}
function renderSSL(){
const tbody = els.sslBody();
let html='';
S.ssl.forEach(c=>{
const cls = c.expire_days<=0? 'err': c.expire_days<=7? 'warn':'ok';
const status = c.expire_days<=0? '已过期': c.expire_days<=7? '将到期':'正常';
const dt = c.expire_ts? new Date(c.expire_ts*1000).toISOString().replace('T',' ').replace(/\.\d+Z/,''):'-';
html += `<tr>
<td>${c.name||'-'}</td>
<td>${(c.domain||'').replace(/^https?:\/\//,'')}</td>
<td>${c.port||443}</td>
<td><span class="badge ${cls}">${c.expire_days??'-'}</span></td>
<td>${dt}</td>
<td><span class="badge ${cls}">${status}</span></td>
</tr>`;
});
tbody.innerHTML = html || `<tr><td colspan="6" class="muted" style="text-align:center;padding:1rem;">无证书数据</td></tr>`;
}
// 证书卡片 (移动端)
function renderSSLCards(){
const wrap = document.getElementById('sslCards');
if(!wrap) return; if(window.innerWidth>700){ wrap.innerHTML=''; return; }
let html='';
S.ssl.forEach(c=>{
const cls = c.expire_days<=0? 'err': c.expire_days<=7? 'warn':'ok';
const status = c.expire_days<=0? '已过期': c.expire_days<=7? '将到期':'正常';
const dt = c.expire_ts? new Date(c.expire_ts*1000).toISOString().replace('T',' ').replace(/\.\d+Z/,''):'-';
html += `<div class="card">
<div class="card-header"><div class="card-title">${c.name||'-'}</div><span class="status-pill ${cls==='err'?'off':'on'}">${status}</span></div>
<div class="kvlist" style="grid-template-columns:repeat(2,minmax(0,1fr));">
<div><span class="key">域名</span><span>${(c.domain||'').replace(/^https?:\/\//,'')}</span></div>
<div><span class="key">端口</span><span>${c.port||443}</span></div>
<div><span class="key">剩余()</span><span>${c.expire_days??'-'}</span></div>
<div><span class="key">到期</span><span>${dt.split(' ')[0]||dt}</span></div>
</div>
</div>`;
});
wrap.innerHTML = html || '<div class="muted" style="font-size:.75rem;text-align:center;padding:1rem;">无证书数据</div>';
}
function updateTime(){
const el = els.last();
if(S.updated){ el.textContent = '最后更新: '+ humanAgo(S.updated); }
}
function bindTabs(){
document.getElementById('navTabs').addEventListener('click',e=>{
if(e.target.tagName!=='BUTTON') return; const tab=e.target.dataset.tab;
document.querySelectorAll('.nav button').forEach(b=>b.classList.toggle('active',b===e.target));
document.querySelectorAll('.panel').forEach(p=>p.classList.toggle('active', p.id==='panel-'+tab));
});
}
function bindTheme(){
const btn = document.getElementById('themeToggle');
const mql = window.matchMedia('(prefers-color-scheme: light)');
const saved = localStorage.getItem('theme'); // 'light' | 'dark' | null (auto)
const apply = (isLight)=>{ document.body.classList.toggle('light', isLight); document.documentElement.classList.toggle('light', isLight); };
if(!saved){
// 自动跟随系统
apply(mql.matches);
// 监听系统偏好变化(仅在未手动选择时)
mql.addEventListener('change', e=>{ if(!localStorage.getItem('theme')) apply(e.matches); });
} else {
apply(saved==='light');
}
btn.addEventListener('click',()=>{
// 用户手动切换后即固定,不再自动
const toLight = !document.body.classList.contains('light');
apply(toLight);
localStorage.setItem('theme', toLight?'light':'dark');
});
}
bindTabs();
bindTheme();
fetchData();
setInterval(fetchData, 1000);
setInterval(updateTime, 60000);
// 详情弹窗逻辑
function openDetail(i){
const s = S.servers[i]; if(!s) return;
const box = document.getElementById('detailContent');
const modal = document.getElementById('detailModal');
document.getElementById('detailTitle').textContent = s.name + ' 详情';
const offline = !(s.online4||s.online6);
const memPct = s.memory_total? (s.memory_used/s.memory_total*100):0;
const swapPct = s.swap_total? (s.swap_used/s.swap_total*100):0;
const hddPct = s.hdd_total? (s.hdd_used/s.hdd_total*100):0;
const ioRead = (typeof s.io_read==='number')? s.io_read:0;
const ioWrite = (typeof s.io_write==='number')? s.io_write:0;
const procLine = `${num(s.tcp_count)} / ${num(s.udp_count)} / ${num(s.process_count)} / ${num(s.thread_count)}`;
// 保留延迟数据用于图表,但不再展示当前延迟文字行
const latText = offline ? '离线' : `CU/CT/CM: ${num(s.time_10010)}ms (${(s.ping_10010||0).toFixed(0)}%) / ${num(s.time_189)}ms (${(s.ping_189||0).toFixed(0)}%) / ${num(s.time_10086)}ms (${(s.ping_10086||0).toFixed(0)}%)`;
const key = s.name || s.location || 'node';
let latencyBlock = '';
if(!offline){
latencyBlock = `
<div style="display:flex;flex-direction:column;gap:.4rem;">
<canvas id="latChart" height="150" style="width:100%;border:1px solid var(--border);border-radius:10px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));"></canvas>
<div class="mono" style="font-size:11px;display:flex;gap:1rem;flex-wrap:wrap;">
<span style="color:#3b82f6"> 联通 (<span id="lat-cu">${num(s.time_10010)}ms</span>)</span>
<span style="color:#10b981"> 电信 (<span id="lat-ct">${num(s.time_189)}ms</span>)</span>
<span style="color:#f59e0b"> 移动 (<span id="lat-cm">${num(s.time_10086)}ms</span>)</span>
<span style="opacity:.6"> (~${S.hist[key]?S.hist[key].cu.length:0} )</span>
</div>
</div>`;
} else {
latencyBlock = `
<div style="display:flex;flex-direction:column;gap:.4rem;">
<canvas id="latChart" height="150" style="width:100%;border:1px solid var(--border);border-radius:10px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));"></canvas>
<div class="mono" style="font-size:11px;opacity:.6;">离线无联通/电信/移动延迟数据</div>
</div>`;
}
// 旧进度条函数 barHTML/ioBar 已弃用
// 资源行(移除百分比显示,仅显示 已用 / 总量)
// 资源行(单独拆分 span 便于后续动态刷新)
// 单位来源memory/swap: KB; hdd: MB; io: B (速率) -> 统一最小单位显示为 MB并向上进位 (MB/GB/TB)
const memUsed = s.memory_total!=null? humanMinMBFromKB(s.memory_used||0):'-';
const memTotal = s.memory_total!=null? humanMinMBFromKB(s.memory_total):'-';
const swapUsed = s.swap_total!=null? humanMinMBFromKB(s.swap_used||0):'-';
const swapTotal = s.swap_total!=null? humanMinMBFromKB(s.swap_total):'-';
const hddUsed = s.hdd_total!=null? humanMinMBFromMB(s.hdd_used||0):'-';
const hddTotal = s.hdd_total!=null? humanMinMBFromMB(s.hdd_total):'-';
const ioReadLine = (ioRead!=null)? humanRateMinMBFromB(ioRead):'-';
const ioWriteLine = (ioWrite!=null)? humanRateMinMBFromB(ioWrite):'-';
const memColor = memPct>80? ' style="color:var(--danger)"':''; // 已用/总量显示为单一块,所以对已用着色
const swapColor = swapPct>80? ' style="color:var(--danger)"':'';
const hddColor = hddPct>80? ' style="color:var(--danger)"':'';
// IO 阈值:>100MB (原始单位 B) -> >100*1000*1000 B
const readColor = ioRead>100*1000*1000? ' style="color:var(--danger)"':'';
const writeColor = ioWrite>100*1000*1000? ' style="color:var(--danger)"':'';
box.innerHTML = `
<div class="kv"><span>TCP/UDP//线</span><span class="mono" id="detail-proc">${procLine}</span></div>
<div class="kv"><span>内存 / 虚存</span><span class="mono">
<span id="mem-line"${memColor}><span id="mem-used">${memUsed}</span> / <span id="mem-total">${memTotal}</span></span>
| <span id="swap-line"${swapColor}><span id="swap-used">${swapUsed}</span> / <span id="swap-total">${swapTotal}</span></span>
</span></div>
<div class="kv"><span>硬盘 / 读写</span><span class="mono">
<span id="disk-line"${hddColor}><span id="hdd-used">${hddUsed}</span> / <span id="hdd-total">${hddTotal}</span></span>
| <span id="io-read"${readColor}>${ioReadLine}</span> / <span id="io-write"${writeColor}>${ioWriteLine}</span>
</span></div>
<div style="display:flex;flex-direction:column;gap:.35rem;">
<canvas id="loadChart" height="120" style="width:100%;border:1px solid var(--border);border-radius:10px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));"></canvas>
<div class="mono" style="font-size:11px;display:flex;gap:.9rem;flex-wrap:wrap;align-items:center;opacity:.8;">
<span style="color:#8b5cf6"> load1</span>
<span style="color:#10b981"> load5</span>
<span style="color:#f59e0b"> load15</span>
<span style="opacity:.6">(~${(S.loadHist[key]?S.loadHist[key].l1.length:0)} )</span>
</div>
</div>
<!-- 进度条移除//虚存以文本形式显示于上方合并行 -->
${latencyBlock}
`;
modal.style.display='flex';
document.addEventListener('keydown', escCloseOnce);
if(!offline){
drawLatencyChart(key);
drawLoadChart(key);
S._openDetailKey = key; // 记录当前弹窗对应节点
startDetailAutoUpdate();
} else {
S._openDetailKey = null;
stopDetailAutoUpdate();
}
}
function escCloseOnce(e){ if(e.key==='Escape'){ closeDetail(); } }
function closeDetail(){ const m=document.getElementById('detailModal'); m.style.display='none'; document.removeEventListener('keydown', escCloseOnce); stopDetailAutoUpdate(); }
document.getElementById('detailClose').addEventListener('click', closeDetail);
document.getElementById('detailModal').addEventListener('click', e=>{ if(e.target.id==='detailModal') closeDetail(); });
// 绘制三网延迟折线图 (简易实现)
function drawLatencyChart(key){
const data = S.hist[key];
const canvas = document.getElementById('latChart');
if(!canvas || !data) return;
const ctx = canvas.getContext('2d');
const W = canvas.clientWidth; const H = canvas.height; canvas.width = W; // 适配宽度
ctx.clearRect(0,0,W,H);
const padL=40, padR=10, padT=10, padB=18;
const isLight = document.body.classList.contains('light');
const axisColor = isLight? 'rgba(0,0,0,0.22)' : 'rgba(255,255,255,0.18)';
const gridColor = isLight? 'rgba(0,0,0,0.08)' : 'rgba(255,255,255,0.10)';
const textColor = isLight? 'var(--text-dim)' : 'rgba(226,232,240,0.85)';
const series = [ {arr:data.cu,color:'#3b82f6'}, {arr:data.ct,color:'#10b981'}, {arr:data.cm,color:'#f59e0b'} ];
const allVals = series.flatMap(s=>s.arr);
if(!allVals.length){ ctx.fillStyle='var(--text-dim)'; ctx.font='12px system-ui'; ctx.fillText('暂无数据', W/2-30, H/2); return; }
const max = Math.max(...allVals);
const min = Math.min(...allVals);
const range = Math.max(1, max-min);
const n = Math.max(...series.map(s=>s.arr.length));
const xStep = (W - padL - padR) / Math.max(1,n-1);
// 网格与轴 (增强暗色对比)
ctx.strokeStyle=axisColor; ctx.lineWidth=1.1;
ctx.beginPath(); ctx.moveTo(padL,padT); ctx.lineTo(padL,H-padB); ctx.lineTo(W-padR,H-padB); ctx.stroke();
ctx.fillStyle=textColor; ctx.font='10px system-ui';
const yMarks=4; for(let i=0;i<=yMarks;i++){ const y = padT + (H-padT-padB)*i/yMarks; const val = (max - range*i/yMarks).toFixed(0)+'ms'; ctx.fillText(val,4,y+3); ctx.strokeStyle=gridColor; ctx.beginPath(); ctx.moveTo(padL,y); ctx.lineTo(W-padR,y); ctx.stroke(); }
// 绘制线
series.forEach(s=>{
if(s.arr.length<2) return;
ctx.strokeStyle = s.color; ctx.lineWidth=1.6; ctx.beginPath();
s.arr.forEach((v,i)=>{ const x = padL + xStep*i; const y = padT + (H-padT-padB)*(1-(v-min)/range); if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y); });
ctx.stroke();
});
}
// 在每次 render 后若弹窗打开则重绘最新图
const _oldRender = render;
render = function(){ _oldRender(); if(S._openDetailKey){ drawLatencyChart(S._openDetailKey); drawLoadChart(S._openDetailKey); } };
window.addEventListener('resize', ()=>{
if(S._openDetailKey){ drawLatencyChart(S._openDetailKey); drawLoadChart(S._openDetailKey); }
renderServersCards();
renderMonitorsCards();
renderSSLCards();
});
// (清理) drawSparks 已移除
// 负载折线图 (load1 历史)
function drawLoadChart(key){
const L = S.loadHist[key];
const canvas = document.getElementById('loadChart');
if(!canvas) return; const ctx = canvas.getContext('2d');
if(!L){ ctx.clearRect(0,0,canvas.width,canvas.height); return; }
const l1=L.l1||[], l5=L.l5||[], l15=L.l15||[];
const canvasW = canvas.clientWidth; const H = canvas.height; canvas.width = canvasW; const W=canvasW;
ctx.clearRect(0,0,W,H);
if(l1.length<2){ ctx.fillStyle='var(--text-dim)'; ctx.font='12px system-ui'; ctx.fillText('暂无负载数据', W/2-42, H/2); return; }
const all = [...l1,...l5,...l15];
const padL=38,padR=8,padT=8,padB=16;
// 修正:纵轴下限不小于 0且当真实 range <0.5 时向上扩展 max 而不是向下产生负刻度
const rawMax = all.length? Math.max(...all):0;
const rawMin = all.length? Math.min(...all):0;
const min = 0; // 我们只显示 >=0
let max = Math.max(rawMax,0);
let range = max - min;
const MIN_RANGE = 0.5;
if(range < MIN_RANGE){ max = MIN_RANGE; range = MIN_RANGE; }
const n = Math.max(l1.length,l5.length,l15.length); const xStep=(W-padL-padR)/Math.max(1,n-1);
const isLight = document.body.classList.contains('light');
const axisColor = isLight? 'rgba(0,0,0,0.22)' : 'rgba(255,255,255,0.18)';
const gridColor = isLight? 'rgba(0,0,0,0.08)' : 'rgba(255,255,255,0.10)';
const textColor = isLight? 'var(--text-dim)' : 'rgba(226,232,240,0.85)';
// 轴 & 网格 (增强暗色对比)
ctx.strokeStyle=axisColor; ctx.lineWidth=1.1; ctx.beginPath(); ctx.moveTo(padL,padT); ctx.lineTo(padL,H-padB); ctx.lineTo(W-padR,H-padB); ctx.stroke();
ctx.fillStyle=textColor; ctx.font='10px system-ui';
const yMarks=4; for(let i=0;i<=yMarks;i++){
const y=padT+(H-padT-padB)*i/yMarks;
const val=(max - range*i/yMarks); // top -> bottom
const labelVal = (Math.abs(val) < 0.005 ? 0 : val).toFixed(2);
ctx.fillText(labelVal,4,y+3);
ctx.strokeStyle=gridColor; ctx.beginPath(); ctx.moveTo(padL,y); ctx.lineTo(W-padR,y); ctx.stroke();
}
const series=[{arr:l1,color:'#8b5cf6',fill:true},{arr:l5,color:'#10b981'},{arr:l15,color:'#f59e0b'}];
// 面积先画 load1
series.forEach(s=>{
if(s.arr.length<2) return;
ctx.beginPath(); ctx.lineWidth=1.5; ctx.strokeStyle=s.color;
s.arr.forEach((v,i)=>{ const vClamped = Math.max(0, v); const x=padL+xStep*i; const y=padT+(H-padT-padB)*(1-(vClamped-min)/range); if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y); });
ctx.stroke();
if(s.fill){
const lastX = padL + xStep*(s.arr.length-1);
ctx.lineTo(lastX,H-padB); ctx.lineTo(padL,H-padB); ctx.closePath();
const grd = ctx.createLinearGradient(0,padT,0,H-padB); grd.addColorStop(0,'rgba(139,92,246,0.25)'); grd.addColorStop(1,'rgba(139,92,246,0)');
ctx.fillStyle=grd; ctx.fill();
}
});
}
//# sourceMappingURL=app.js.map
// ====== 详情动态刷新 ======
function findServerByKey(key){ return S.servers.find(x=> (x.name||x.location||'node')===key); }
function updateDetailMetrics(key){
const s = findServerByKey(key); if(!s) return; if(!(s.online4||s.online6)) return; // 离线不更新
const procLine = `${num(s.tcp_count)} / ${num(s.udp_count)} / ${num(s.process_count)} / ${num(s.thread_count)}`;
const procEl = document.getElementById('detail-proc'); if(procEl) procEl.textContent = procLine;
const cuEl=document.getElementById('lat-cu'); if(cuEl) cuEl.textContent = num(s.time_10010)+'ms';
const ctEl=document.getElementById('lat-ct'); if(ctEl) ctEl.textContent = num(s.time_189)+'ms';
const cmEl=document.getElementById('lat-cm'); if(cmEl) cmEl.textContent = num(s.time_10086)+'ms';
// 延迟动态刷新 (若存在)
const cuE1=document.getElementById('lat-cu'); if(cuE1) cuE1.textContent = num(s.time_10010)+'ms';
const ctE1=document.getElementById('lat-ct'); if(ctE1) ctE1.textContent = num(s.time_189)+'ms';
const cmE1=document.getElementById('lat-cm'); if(cmE1) cmE1.textContent = num(s.time_10086)+'ms';
// 资源动态刷新
const memLineEl = document.getElementById('mem-line');
if(memLineEl){
const pct = s.memory_total? (s.memory_used/s.memory_total*100):0;
document.getElementById('mem-used').textContent = s.memory_total!=null? humanMinMBFromKB(s.memory_used||0):'-';
document.getElementById('mem-total').textContent = s.memory_total!=null? humanMinMBFromKB(s.memory_total):'-';
if(pct>80) memLineEl.style.color='var(--danger)'; else memLineEl.style.color='';
}
const swapLineEl = document.getElementById('swap-line');
if(swapLineEl){
const pct = s.swap_total? (s.swap_used/s.swap_total*100):0;
document.getElementById('swap-used').textContent = s.swap_total!=null? humanMinMBFromKB(s.swap_used||0):'-';
document.getElementById('swap-total').textContent = s.swap_total!=null? humanMinMBFromKB(s.swap_total):'-';
if(pct>80) swapLineEl.style.color='var(--danger)'; else swapLineEl.style.color='';
}
const diskLineEl = document.getElementById('disk-line');
if(diskLineEl){
const pct = s.hdd_total? (s.hdd_used/s.hdd_total*100):0;
document.getElementById('hdd-used').textContent = s.hdd_total!=null? humanMinMBFromMB(s.hdd_used||0):'-';
document.getElementById('hdd-total').textContent = s.hdd_total!=null? humanMinMBFromMB(s.hdd_total):'-';
if(pct>80) diskLineEl.style.color='var(--danger)'; else diskLineEl.style.color='';
}
const ioReadEl = document.getElementById('io-read'); if(ioReadEl){ const v = (typeof s.io_read==='number')? s.io_read:0; ioReadEl.textContent = humanRateMinMBFromB(v); ioReadEl.style.color = v>100*1000*1000? 'var(--danger)':''; }
const ioWriteEl = document.getElementById('io-write'); if(ioWriteEl){ const v = (typeof s.io_write==='number')? s.io_write:0; ioWriteEl.textContent = humanRateMinMBFromB(v); ioWriteEl.style.color = v>100*1000*1000? 'var(--danger)':''; }
}
function startDetailAutoUpdate(){ stopDetailAutoUpdate(); S._detailTimer = setInterval(()=>{ if(S._openDetailKey) updateDetailMetrics(S._openDetailKey); }, 1000); }
function stopDetailAutoUpdate(){ if(S._detailTimer){ clearInterval(S._detailTimer); S._detailTimer=null; } }

File diff suppressed because one or more lines are too long

326
web/js/html5shiv.js vendored

@ -1,326 +0,0 @@
/**
* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
;(function(window, document) {
/*jshint evil:true */
/** version */
var version = '3.7.3';
/** Preset options */
var options = window.html5 || {};
/** Used to skip problem elements */
var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
/** Not all elements can be cloned in IE **/
var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
/** Detect whether the browser supports default html5 styles */
var supportsHtml5Styles;
/** Name of the expando, to work with multiple documents or to re-shiv one document */
var expando = '_html5shiv';
/** The id for the the documents expando */
var expanID = 0;
/** Cached data for each document */
var expandoData = {};
/** Detect whether the browser supports unknown elements */
var supportsUnknownElements;
(function() {
try {
var a = document.createElement('a');
a.innerHTML = '<xyz></xyz>';
//if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
supportsHtml5Styles = ('hidden' in a);
supportsUnknownElements = a.childNodes.length == 1 || (function() {
// assign a false positive if unable to shiv
(document.createElement)('a');
var frag = document.createDocumentFragment();
return (
typeof frag.cloneNode == 'undefined' ||
typeof frag.createDocumentFragment == 'undefined' ||
typeof frag.createElement == 'undefined'
);
}());
} catch(e) {
// assign a false positive if detection fails => unable to shiv
supportsHtml5Styles = true;
supportsUnknownElements = true;
}
}());
/*--------------------------------------------------------------------------*/
/**
* Creates a style sheet with the given CSS text and adds it to the document.
* @private
* @param {Document} ownerDocument The document.
* @param {String} cssText The CSS text.
* @returns {StyleSheet} The style element.
*/
function addStyleSheet(ownerDocument, cssText) {
var p = ownerDocument.createElement('p'),
parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
p.innerHTML = 'x<style>' + cssText + '</style>';
return parent.insertBefore(p.lastChild, parent.firstChild);
}
/**
* Returns the value of `html5.elements` as an array.
* @private
* @returns {Array} An array of shived element node names.
*/
function getElements() {
var elements = html5.elements;
return typeof elements == 'string' ? elements.split(' ') : elements;
}
/**
* Extends the built-in list of html5 elements
* @memberOf html5
* @param {String|Array} newElements whitespace separated list or array of new element names to shiv
* @param {Document} ownerDocument The context document.
*/
function addElements(newElements, ownerDocument) {
var elements = html5.elements;
if(typeof elements != 'string'){
elements = elements.join(' ');
}
if(typeof newElements != 'string'){
newElements = newElements.join(' ');
}
html5.elements = elements +' '+ newElements;
shivDocument(ownerDocument);
}
/**
* Returns the data associated to the given document
* @private
* @param {Document} ownerDocument The document.
* @returns {Object} An object of data.
*/
function getExpandoData(ownerDocument) {
var data = expandoData[ownerDocument[expando]];
if (!data) {
data = {};
expanID++;
ownerDocument[expando] = expanID;
expandoData[expanID] = data;
}
return data;
}
/**
* returns a shived element for the given nodeName and document
* @memberOf html5
* @param {String} nodeName name of the element
* @param {Document|DocumentFragment} ownerDocument The context document.
* @returns {Object} The shived element.
*/
function createElement(nodeName, ownerDocument, data){
if (!ownerDocument) {
ownerDocument = document;
}
if(supportsUnknownElements){
return ownerDocument.createElement(nodeName);
}
if (!data) {
data = getExpandoData(ownerDocument);
}
var node;
if (data.cache[nodeName]) {
node = data.cache[nodeName].cloneNode();
} else if (saveClones.test(nodeName)) {
node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
} else {
node = data.createElem(nodeName);
}
// Avoid adding some elements to fragments in IE < 9 because
// * Attributes like `name` or `type` cannot be set/changed once an element
// is inserted into a document/fragment
// * Link elements with `src` attributes that are inaccessible, as with
// a 403 response, will cause the tab/window to crash
// * Script elements appended to fragments will execute when their `src`
// or `text` property is set
return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
}
/**
* returns a shived DocumentFragment for the given document
* @memberOf html5
* @param {Document} ownerDocument The context document.
* @returns {Object} The shived DocumentFragment.
*/
function createDocumentFragment(ownerDocument, data){
if (!ownerDocument) {
ownerDocument = document;
}
if(supportsUnknownElements){
return ownerDocument.createDocumentFragment();
}
data = data || getExpandoData(ownerDocument);
var clone = data.frag.cloneNode(),
i = 0,
elems = getElements(),
l = elems.length;
for(;i<l;i++){
clone.createElement(elems[i]);
}
return clone;
}
/**
* Shivs the `createElement` and `createDocumentFragment` methods of the document.
* @private
* @param {Document|DocumentFragment} ownerDocument The document.
* @param {Object} data of the document.
*/
function shivMethods(ownerDocument, data) {
if (!data.cache) {
data.cache = {};
data.createElem = ownerDocument.createElement;
data.createFrag = ownerDocument.createDocumentFragment;
data.frag = data.createFrag();
}
ownerDocument.createElement = function(nodeName) {
//abort shiv
if (!html5.shivMethods) {
return data.createElem(nodeName);
}
return createElement(nodeName, ownerDocument, data);
};
ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
'var n=f.cloneNode(),c=n.createElement;' +
'h.shivMethods&&(' +
// unroll the `createElement` calls
getElements().join().replace(/[\w\-:]+/g, function(nodeName) {
data.createElem(nodeName);
data.frag.createElement(nodeName);
return 'c("' + nodeName + '")';
}) +
');return n}'
)(html5, data.frag);
}
/*--------------------------------------------------------------------------*/
/**
* Shivs the given document.
* @memberOf html5
* @param {Document} ownerDocument The document to shiv.
* @returns {Document} The shived document.
*/
function shivDocument(ownerDocument) {
if (!ownerDocument) {
ownerDocument = document;
}
var data = getExpandoData(ownerDocument);
if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
data.hasCSS = !!addStyleSheet(ownerDocument,
// corrects block display not defined in IE6/7/8/9
'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
// adds styling not present in IE6/7/8/9
'mark{background:#FF0;color:#000}' +
// hides non-rendered elements
'template{display:none}'
);
}
if (!supportsUnknownElements) {
shivMethods(ownerDocument, data);
}
return ownerDocument;
}
/*--------------------------------------------------------------------------*/
/**
* The `html5` object is exposed so that more elements can be shived and
* existing shiving can be detected on iframes.
* @type Object
* @example
*
* // options can be changed before the script is included
* html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
*/
var html5 = {
/**
* An array or space separated string of node names of the elements to shiv.
* @memberOf html5
* @type Array|String
*/
'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video',
/**
* current version of html5shiv
*/
'version': version,
/**
* A flag to indicate that the HTML5 style sheet should be inserted.
* @memberOf html5
* @type Boolean
*/
'shivCSS': (options.shivCSS !== false),
/**
* Is equal to true if a browser supports creating unknown/HTML5 elements
* @memberOf html5
* @type boolean
*/
'supportsUnknownElements': supportsUnknownElements,
/**
* A flag to indicate that the document's `createElement` and `createDocumentFragment`
* methods should be overwritten.
* @memberOf html5
* @type Boolean
*/
'shivMethods': (options.shivMethods !== false),
/**
* A string to describe the type of `html5` object ("default" or "default print").
* @memberOf html5
* @type String
*/
'type': 'default',
// shivs the document according to the specified `html5` object options
'shivDocument': shivDocument,
//creates a shived element
createElement: createElement,
//creates a shived documentFragment
createDocumentFragment: createDocumentFragment,
//extends list of elements
addElements: addElements
};
/*--------------------------------------------------------------------------*/
// expose html5
window.html5 = html5;
// shiv the document
shivDocument(document);
if(typeof module == 'object' && module.exports){
module.exports = html5;
}
}(typeof window !== "undefined" ? window : this, document));

File diff suppressed because one or more lines are too long

@ -1,5 +0,0 @@
/*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl
* Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT
* */
!function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='&shy;<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b<s.length;b++){var c=s[b],e=c.href,f=c.media,g=c.rel&&"stylesheet"===c.rel.toLowerCase();e&&g&&!o[e]&&(c.styleSheet&&c.styleSheet.rawCssText?(v(c.styleSheet.rawCssText,e,f),o[e]=!0):(!/^([a-zA-Z:]*\/\/)/.test(e)&&!r||e.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&("//"===e.substring(0,2)&&(e=a.location.protocol+e),d.push({href:e,media:f})))}w()};x(),c.update=x,c.getEmValue=t,a.addEventListener?a.addEventListener("resize",b,!1):a.attachEvent&&a.attachEvent("onresize",b)}}(this);

@ -1,427 +0,0 @@
// serverstatus.js
var error = 0;
var d = 0;
var server_status = new Array();
function timeSince(date) {
if(date == 0)
return "从未.";
var seconds = Math.floor((new Date() - date) / 1000);
var interval = Math.floor(seconds / 31536000);
if (interval > 1)
return interval + " 年前.";
interval = Math.floor(seconds / 2592000);
if (interval > 1)
return interval + " 月前.";
interval = Math.floor(seconds / 86400);
if (interval > 1)
return interval + " 日前.";
interval = Math.floor(seconds / 3600);
if (interval > 1)
return interval + " 小时前.";
interval = Math.floor(seconds / 60);
if (interval > 1)
return interval + " 分钟前.";
/*if(Math.floor(seconds) >= 5)
return Math.floor(seconds) + " seconds";*/
else
return "几秒前.";
}
function bytesToSize(bytes, precision, si)
{
var ret;
si = typeof si !== 'undefined' ? si : 0;
if(si != 0) {
var kilobyte = 1000;
var megabyte = kilobyte * 1000;
var gigabyte = megabyte * 1000;
var terabyte = gigabyte * 1000;
} else {
var kilobyte = 1024;
var megabyte = kilobyte * 1024;
var gigabyte = megabyte * 1024;
var terabyte = gigabyte * 1024;
}
if ((bytes >= 0) && (bytes < kilobyte)) {
return bytes + ' B';
} else if ((bytes >= kilobyte) && (bytes < megabyte)) {
ret = (bytes / kilobyte).toFixed(precision) + ' K';
} else if ((bytes >= megabyte) && (bytes < gigabyte)) {
ret = (bytes / megabyte).toFixed(precision) + ' M';
} else if ((bytes >= gigabyte) && (bytes < terabyte)) {
ret = (bytes / gigabyte).toFixed(precision) + ' G';
} else if (bytes >= terabyte) {
ret = (bytes / terabyte).toFixed(precision) + ' T';
} else {
return bytes + ' B';
}
if(si != 0) {
return ret + 'B';
} else {
return ret + 'iB';
}
}
function uptime() {
$.getJSON("json/stats.json", function(result) {
$("#loading-notice").remove();
if(result.reload)
setTimeout(function() { location.reload(true) }, 1000);
for (var i = 0, rlen=result.servers.length; i < rlen; i++) {
var TableRow = $("#servers tr#r" + i);
var ExpandRow = $("#servers #rt" + i);
var hack; // fuck CSS for making me do this
if(i%2) hack="odd"; else hack="even";
if (!TableRow.length) {
$("#servers").append(
"<tr id=\"r" + i + "\" data-toggle=\"collapse\" data-target=\"#rt" + i + "\" class=\"accordion-toggle " + hack + "\">" +
"<td id=\"online4\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
"<td id=\"ip_status\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
"<td id=\"name\">加载中</td>" +
"<td id=\"type\">加载中</td>" +
"<td id=\"location\">加载中</td>" +
"<td id=\"uptime\">加载中</td>" +
"<td id=\"load\">加载中</td>" +
"<td id=\"network\">加载中</td>" +
"<td id=\"traffic\">加载中</td>" +
"<td id=\"cpu\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
"<td id=\"memory\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
"<td id=\"hdd\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
"<td id=\"ping\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
"</tr>" +
"<tr class=\"expandRow " + hack + "\"><td colspan=\"16\"><div class=\"accordian-body collapse\" id=\"rt" + i + "\">" +
"<div id=\"expand_mem\">加载中</div>" +
"<div id=\"expand_swap\">加载中</div>" +
"<div id=\"expand_hdd\">加载中</div>" +
"<div id=\"expand_tupd\">加载中</div>" +
"<div id=\"expand_ping\">加载中</div>" +
"<div id=\"expand_custom\">加载中</div>" +
"</div></td></tr>"
);
TableRow = $("#servers tr#r" + i);
ExpandRow = $("#servers #rt" + i);
server_status[i] = true;
}
TableRow = TableRow[0];
if(error) {
TableRow.setAttribute("data-target", "#rt" + i);
server_status[i] = true;
}
// Online4
if (result.servers[i].online4 && !result.servers[i].online6) {
TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-success";
TableRow.children["online4"].children[0].children[0].innerHTML = "<small>IPv4</small>";
} else if (result.servers[i].online4 && result.servers[i].online6) {
TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-success";
TableRow.children["online4"].children[0].children[0].innerHTML = "<small>双栈</small>";
} else if (!result.servers[i].online4 && result.servers[i].online6) {
TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-success";
TableRow.children["online4"].children[0].children[0].innerHTML = "<small>IPv6</small>";
} else {
TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-danger";
TableRow.children["online4"].children[0].children[0].innerHTML = "<small>关闭</small>";
}
// Online6
//if (result.servers[i].online6) {
// TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-success";
// TableRow.children["online6"].children[0].children[0].innerHTML = "<small>开启</small>";
//} else {
// TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-danger";
// TableRow.children["online6"].children[0].children[0].innerHTML = "<small>关闭</small>";
//}
// Ipstatus
// mh361 or mh370, mourn mh370, 2014-03-08 01:20 lost from all over the world.
if (result.servers[i].ip_status) {
TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-success";
TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>MH361</small>";
} else {
TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-danger";
TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>MH370</small>";
}
// Name
TableRow.children["name"].innerHTML = result.servers[i].name;
// Type
TableRow.children["type"].innerHTML = result.servers[i].type;
// Location
TableRow.children["location"].innerHTML = result.servers[i].location;
if (!result.servers[i].online4 && !result.servers[i].online6) {
if (server_status[i]) {
TableRow.children["uptime"].innerHTML = "";
TableRow.children["load"].innerHTML = "";
TableRow.children["network"].innerHTML = "";
TableRow.children["traffic"].innerHTML = "";
TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-danger";
TableRow.children["cpu"].children[0].children[0].style.width = "100%";
TableRow.children["cpu"].children[0].children[0].innerHTML = "<small>关闭</small>";
TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-danger";
TableRow.children["memory"].children[0].children[0].style.width = "100%";
TableRow.children["memory"].children[0].children[0].innerHTML = "<small>关闭</small>";
TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-danger";
TableRow.children["hdd"].children[0].children[0].style.width = "100%";
TableRow.children["hdd"].children[0].children[0].innerHTML = "<small>关闭</small>";
TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-danger";
TableRow.children["ping"].children[0].children[0].style.width = "100%";
TableRow.children["ping"].children[0].children[0].innerHTML = "<small>关闭</small>";
if(ExpandRow.hasClass("in")) {
ExpandRow.collapse("hide");
}
TableRow.setAttribute("data-target", "");
server_status[i] = false;
}
} else {
if (!server_status[i]) {
TableRow.setAttribute("data-target", "#rt" + i);
server_status[i] = true;
}
// Uptime
TableRow.children["uptime"].innerHTML = result.servers[i].uptime;
// Load: default load_1, you can change show: load_1, load_5, load_15
if(result.servers[i].load == -1) {
TableRow.children["load"].innerHTML = "";
} else {
TableRow.children["load"].innerHTML = result.servers[i].load_1.toFixed(2);
}
// Network
var netstr = "";
if(result.servers[i].network_rx < 1024)
netstr += result.servers[i].network_rx.toFixed(0) + "B";
else if(result.servers[i].network_rx < 1024*1024)
netstr += (result.servers[i].network_rx/1024).toFixed(0) + "K";
else
netstr += (result.servers[i].network_rx/1024/1024).toFixed(1) + "M";
netstr += " | "
if(result.servers[i].network_tx < 1024)
netstr += result.servers[i].network_tx.toFixed(0) + "B";
else if(result.servers[i].network_tx < 1024*1024)
netstr += (result.servers[i].network_tx/1024).toFixed(0) + "K";
else
netstr += (result.servers[i].network_tx/1024/1024).toFixed(1) + "M";
TableRow.children["network"].innerHTML = netstr;
//Traffic
var trafficstr = "";
if(result.servers[i].network_in < 1024)
trafficstr += result.servers[i].network_in.toFixed(0) + "B";
else if(result.servers[i].network_in < 1024*1024)
trafficstr += (result.servers[i].network_in/1024).toFixed(0) + "K";
else if(result.servers[i].network_in < 1024*1024*1024)
trafficstr += (result.servers[i].network_in/1024/1024).toFixed(1) + "M";
else if(result.servers[i].network_in < 1024*1024*1024*1024)
trafficstr += (result.servers[i].network_in/1024/1024/1024).toFixed(2) + "G";
else
trafficstr += (result.servers[i].network_in/1024/1024/1024/1024).toFixed(2) + "T";
trafficstr += " | "
if(result.servers[i].network_out < 1024)
trafficstr += result.servers[i].network_out.toFixed(0) + "B";
else if(result.servers[i].network_out < 1024*1024)
trafficstr += (result.servers[i].network_out/1024).toFixed(0) + "K";
else if(result.servers[i].network_out < 1024*1024*1024)
trafficstr += (result.servers[i].network_out/1024/1024).toFixed(1) + "M";
else if(result.servers[i].network_out < 1024*1024*1024*1024)
trafficstr += (result.servers[i].network_out/1024/1024/1024).toFixed(2) + "G";
else
trafficstr += (result.servers[i].network_out/1024/1024/1024/1024).toFixed(2) + "T";
TableRow.children["traffic"].innerHTML = trafficstr;
// CPU
if (result.servers[i].cpu >= 90)
TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-danger";
else if (result.servers[i].cpu >= 80)
TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-warning";
else
TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-success";
TableRow.children["cpu"].children[0].children[0].style.width = result.servers[i].cpu + "%";
TableRow.children["cpu"].children[0].children[0].innerHTML = result.servers[i].cpu + "%";
// Memory
var Mem = ((result.servers[i].memory_used/result.servers[i].memory_total)*100.0).toFixed(0);
if (Mem >= 90)
TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-danger";
else if (Mem >= 80)
TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-warning";
else
TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-success";
TableRow.children["memory"].children[0].children[0].style.width = Mem + "%";
TableRow.children["memory"].children[0].children[0].innerHTML = Mem + "%";
ExpandRow[0].children["expand_mem"].innerHTML = "内存: " + bytesToSize(result.servers[i].memory_used*1024, 2) + " / " + bytesToSize(result.servers[i].memory_total*1024, 2);
// Swap
ExpandRow[0].children["expand_swap"].innerHTML = "交换分区: " + bytesToSize(result.servers[i].swap_used*1024, 2) + " / " + bytesToSize(result.servers[i].swap_total*1024, 2);
// HDD
var HDD = ((result.servers[i].hdd_used/result.servers[i].hdd_total)*100.0).toFixed(0);
if (HDD >= 90)
TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-danger";
else if (HDD >= 80)
TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-warning";
else
TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-success";
TableRow.children["hdd"].children[0].children[0].style.width = HDD + "%";
TableRow.children["hdd"].children[0].children[0].innerHTML = HDD + "%";
ExpandRow[0].children["expand_hdd"].innerHTML = "硬盘: " + bytesToSize(result.servers[i].hdd_used*1024*1024, 2) + " / " + bytesToSize(result.servers[i].hdd_total*1024*1024, 2);
// delay time
// tcp, udp, process, thread count
ExpandRow[0].children["expand_tupd"].innerHTML = "TCP/UDP/进/线: " + result.servers[i].tcp_count + " / " + result.servers[i].udp_count + " / " + result.servers[i].process_count+ " / " + result.servers[i].thread_count;
ExpandRow[0].children["expand_ping"].innerHTML = "联通/电信/移动: " + result.servers[i].time_10010 + "ms / " + result.servers[i].time_189 + "ms / " + result.servers[i].time_10086 + "ms"
// ping
var PING_10010 = result.servers[i].ping_10010.toFixed(0);
var PING_189 = result.servers[i].ping_189.toFixed(0);
var PING_10086 = result.servers[i].ping_10086.toFixed(0);
if (PING_10010 >= 20 || PING_189 >= 20 || PING_10086 >= 20)
TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-danger";
else
TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-success";
TableRow.children["ping"].children[0].children[0].innerHTML = PING_10010 + "%💻" + PING_189 + "%💻" + PING_10086 + "%";
// Custom
if (result.servers[i].custom) {
ExpandRow[0].children["expand_custom"].innerHTML = result.servers[i].custom
} else {
ExpandRow[0].children["expand_custom"].innerHTML = ""
}
}
};
d = new Date(result.updated*1000);
error = 0;
}).fail(function(update_error) {
if (!error) {
$("#servers > tr.accordion-toggle").each(function(i) {
var TableRow = $("#servers tr#r" + i)[0];
var ExpandRow = $("#servers #rt" + i);
TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-error";
TableRow.children["online4"].children[0].children[0].innerHTML = "<small>错误</small>";
//TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-error";
//TableRow.children["online6"].children[0].children[0].innerHTML = "<small>错误</small>";
TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-error";
TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>错误</small>";
TableRow.children["uptime"].children[0].children[0].className = "progress-bar progress-bar-error";
TableRow.children["uptime"].children[0].children[0].innerHTML = "<small>错误</small>";
TableRow.children["load"].children[0].children[0].className = "progress-bar progress-bar-error";
TableRow.children["load"].children[0].children[0].innerHTML = "<small>错误</small>";
TableRow.children["network"].children[0].children[0].className = "progress-bar progress-bar-error";
TableRow.children["network"].children[0].children[0].innerHTML = "<small>错误</small>";
TableRow.children["traffic"].children[0].children[0].className = "progress-bar progress-bar-error";
TableRow.children["traffic"].children[0].children[0].innerHTML = "<small>错误</small>";
TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-error";
TableRow.children["cpu"].children[0].children[0].style.width = "100%";
TableRow.children["cpu"].children[0].children[0].innerHTML = "<small>错误</small>";
TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-error";
TableRow.children["memory"].children[0].children[0].style.width = "100%";
TableRow.children["memory"].children[0].children[0].innerHTML = "<small>错误</small>";
TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-error";
TableRow.children["hdd"].children[0].children[0].style.width = "100%";
TableRow.children["hdd"].children[0].children[0].innerHTML = "<small>错误</small>";
TableRow.children["ping"].children[0].children[0].className = "progress-bar progress-bar-error";
TableRow.children["ping"].children[0].children[0].style.width = "100%";
TableRow.children["ping"].children[0].children[0].innerHTML = "<small>错误</small>";
if(ExpandRow.hasClass("in")) {
ExpandRow.collapse("hide");
}
TableRow.setAttribute("data-target", "");
server_status[i] = false;
});
}
error = 1;
$("#updated").html("更新错误.");
});
}
function updateTime() {
if (!error)
$("#updated").html("最后更新: " + timeSince(d));
}
uptime();
updateTime();
setInterval(uptime, 2000);
setInterval(updateTime, 2000);
// styleswitcher.js
function setActiveStyleSheet(title) {
var i, a, main;
for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
a.disabled = true;
if(a.getAttribute("title") == title) a.disabled = false;
}
}
}
function getActiveStyleSheet() {
var i, a;
for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title") && !a.disabled)
return a.getAttribute("title");
}
return null;
}
function getPreferredStyleSheet() {
var i, a;
for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("rel").indexOf("alt") == -1 && a.getAttribute("title"))
return a.getAttribute("title");
}
return null;
}
function createCookie(name,value,days) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else expires = "";
document.cookie = name+"="+value+expires+"; path=/";
}
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ')
c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0)
return c.substring(nameEQ.length,c.length);
}
return null;
}
window.onload = function(e) {
var cookie = readCookie("style");
var title = cookie ? cookie : getPreferredStyleSheet();
setActiveStyleSheet(title);
}
window.onunload = function(e) {
var title = getActiveStyleSheet();
createCookie("style", title, 365);
}
var cookie = readCookie("style");
var title = cookie ? cookie : getPreferredStyleSheet();
setActiveStyleSheet(title);

@ -1,11 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python3
# coding: utf-8 # coding: utf-8
# Update by : https://github.com/cppla/ServerStatus # Update by : https://github.com/cppla/ServerStatus, Update date: 20211009
# 支持Python版本2.7 to 3.5; requirements.txt: requests, PrettyTable # 支持Python版本2.7 to 3.9; requirements.txt: requests, PrettyTable
# 时间: 20180828 # 主要是为了受到CC attack时候方便查看机器状态
'''
maybe better by youself
'''
import os import os
import sys import sys
@ -13,9 +10,9 @@ import requests
import time import time
from prettytable import PrettyTable from prettytable import PrettyTable
# todo: 程序在非gui环境下目前有闪屏的bug
scroll = True scroll = True
clear = lambda: os.system('clear' if 'linux' in sys.platform else 'cls') clear = lambda: os.system('clear' if 'linux' in sys.platform or 'darwin' in sys.platform else 'cls')
def sscmd(address): def sscmd(address):
while True: while True:
@ -29,42 +26,58 @@ def sscmd(address):
ss = PrettyTable( ss = PrettyTable(
[ [
"Flight", "月流量 ↓|↑",
"节点名", "节点名",
# "虚拟化",
"位置", "位置",
"在线时间", "在线时间",
"负载", "负载",
"网络", "网络 ↓|↑",
"流量", "流量 ↓|↑",
"处理器", "处理器",
"内存", "内存",
"硬盘" "硬盘"
], ],
) )
for i in jsonR["servers"]: for i in jsonR["servers"]:
ss.add_row( if i["online4"] is False and i["online6"] is False:
[ ss.add_row(
"%s" % 'MH361' if i["ip_status"] is True else 'MH370', [
"%s" % i["name"], '0.00G',
# "%s" % i["type"], "%s" % i["name"],
"%s" % i["location"], "%s" % i["location"],
"%s" % i["uptime"], '-',
"%s" % (i["load_1"]), '-',
"%.2fM|%.2fM" % (float(i["network_rx"]) / 1000 / 1000, float(i["network_tx"]) / 1000 / 1000), '-',
"%.2fG|%.2fG" % ( '-',
float(i["network_in"]) / 1024 / 1024 / 1024, float(i["network_out"]) / 1024 / 1024 / 1024), '-',
"%d%%" % (i["cpu"]), '-',
"%d%%" % (float(i["memory_used"]) / i["memory_total"] * 100), '-',
"%d%%" % (float(i["hdd_used"]) / i["hdd_total"] * 100), ]
] )
) else:
ss.add_row(
[
"%.2fG|%.2fG" % (float(i["last_network_in"]) / 1024 / 1024 / 1024, float(i["last_network_out"]) / 1024 / 1024 / 1024),
"%s" % i["name"],
# "%s" % i["type"],
"%s" % i["location"],
"%s" % i["uptime"],
"%s" % (i["load_1"]),
"%.2fM|%.2fM" % (float(i["network_rx"]) / 1000 / 1000, float(i["network_tx"]) / 1000 / 1000),
"%.2fG|%.2fG" % (
float(i["network_in"]) / 1024 / 1024 / 1024, float(i["network_out"]) / 1024 / 1024 / 1024),
"%d%%" % (i["cpu"]),
"%d%%" % (float(i["memory_used"]) / i["memory_total"] * 100),
"%d%%" % (float(i["hdd_used"]) / i["hdd_total"] * 100),
]
)
if scroll is True: if scroll is True:
clear() clear()
print(ss) print(ss)
time.sleep(1) time.sleep(1)
if __name__ == '__main__': if __name__ == '__main__':
default = 'https://tz.cloudcpp.com/json/stats.json' default = 'https://tz.cloudcpp.com/json/stats.json'
ads = sys.argv[1] if len(sys.argv)==2 else default ads = sys.argv[1] if len(sys.argv) == 2 else default
sscmd(ads) sscmd(ads)