mirror of
https://github.com/XZB-1248/Spark
synced 2025-10-27 01:30:21 +08:00
support client self-upgrade
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -134,5 +134,3 @@ jobs:
|
||||
releases/server_windows_arm64.zip
|
||||
releases/server_windows_i386.zip
|
||||
releases/server_windows_amd64.zip
|
||||
env:
|
||||
GITHUB_REPOSITORY: XZB-1248/Spark
|
||||
23
CHANGELOG.md
Normal file
23
CHANGELOG.md
Normal file
@@ -0,0 +1,23 @@
|
||||
## v0.0.3
|
||||
|
||||
* Add: network IO speed monitoring.
|
||||
* Add: support client self-upgrade.
|
||||
* Fix: garbled characters when display Chinese on Unix-like OS.
|
||||
* BREAKING-CHANGE: module `Device` has changed.
|
||||
* THIS RELEASE IS **NOT** COMPATIBLE WITH LAST RELEASE.
|
||||
|
||||
|
||||
|
||||
## v0.0.2
|
||||
|
||||
* Add: latency check.
|
||||
* Add: progress bar of cpu usage, memory usage and disk usage.
|
||||
* BREAKING-CHANGE: module `Device` has changed.
|
||||
* THIS RELEASE IS **NOT** COMPATIBLE WITH LAST RELEASE.
|
||||
|
||||
|
||||
|
||||
## v0.0.1
|
||||
|
||||
* First release.
|
||||
*
|
||||
53
LICENSE
53
LICENSE
@@ -23,3 +23,56 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-------
|
||||
|
||||
utils/melody are copied and modified from olahol/melody.
|
||||
|
||||
Copyright (c) 2015 Ola Holmström. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-------
|
||||
|
||||
utils/cmap are copied and modified from orcaman/concurrent-map.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 streamrail
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
40
README.ZH.md
40
README.ZH.md
@@ -130,6 +130,46 @@ $ go build -ldflags "-s -w" -o Spark Spark/Server
|
||||
|
||||
---
|
||||
|
||||
## 项目依赖
|
||||
|
||||
Spark使用了许多第三方的开源项目。
|
||||
|
||||
依赖列表可以在 `go.mod` 和 `package.json` 里面找到。
|
||||
|
||||
一些主要的依赖项如下列所示。
|
||||
|
||||
### 后端
|
||||
|
||||
* [Go](https://github.com/golang/go) ([License](https://github.com/golang/go/blob/master/LICENSE))
|
||||
|
||||
* [gin-gonic/gin](https://github.com/gin-gonic/gin) (MIT License)
|
||||
|
||||
* [imroc/req](https://github.com/imroc/req) (MIT License)
|
||||
|
||||
* [kbinani/screenshot](https://github.com/kbinani/screenshot) (MIT License)
|
||||
|
||||
* [shirou/gopsutil](https://github.com/shirou/gopsutil) ([License](https://github.com/shirou/gopsutil/blob/master/LICENSE))
|
||||
|
||||
* [gorilla/websocket](https://github.com/gorilla/websocket) (BSD-2-Clause License)
|
||||
|
||||
* [olahol/melody](https://github.com/olahol/melody) (BSD-2-Clause License)
|
||||
|
||||
* [orcaman/concurrent-map](https://github.com/orcaman/concurrent-map) (MIT License)
|
||||
|
||||
### 前端
|
||||
|
||||
* [React](https://github.com/facebook/react) (MIT License)
|
||||
|
||||
* [Ant-Design](https://github.com/ant-design/ant-design) (MIT License)
|
||||
|
||||
* [axios](https://github.com/axios/axios) (MIT License)
|
||||
|
||||
* [xterm.js](https://github.com/xtermjs/xterm.js) (MIT License)
|
||||
|
||||
* [crypto-js](https://github.com/brix/crypto-js) (MIT License)
|
||||
|
||||
---
|
||||
|
||||
## 开源协议
|
||||
|
||||
本项目基于 [BSD-2 协议](./LICENSE) 。
|
||||
38
README.md
38
README.md
@@ -125,6 +125,44 @@ $ go build -ldflags "-s -w" -o Spark Spark/Server
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
Spark contains many third-party open-source projects.
|
||||
|
||||
Lists of dependencies can be found at `go.mod` and `package.json`.
|
||||
|
||||
Some major dependencies are listed below.
|
||||
|
||||
### Back-end
|
||||
|
||||
* [Go](https://github.com/golang/go) ([License](https://github.com/golang/go/blob/master/LICENSE))
|
||||
|
||||
* [gin-gonic/gin](https://github.com/gin-gonic/gin) (MIT License)
|
||||
|
||||
* [imroc/req](https://github.com/imroc/req) (MIT License)
|
||||
|
||||
* [kbinani/screenshot](https://github.com/kbinani/screenshot) (MIT License)
|
||||
|
||||
* [shirou/gopsutil](https://github.com/shirou/gopsutil) ([License](https://github.com/shirou/gopsutil/blob/master/LICENSE))
|
||||
|
||||
* [gorilla/websocket](https://github.com/gorilla/websocket) (BSD-2-Clause License)
|
||||
|
||||
* [orcaman/concurrent-map](https://github.com/orcaman/concurrent-map) (MIT License)
|
||||
|
||||
### Front-end
|
||||
|
||||
* [React](https://github.com/facebook/react) (MIT License)
|
||||
|
||||
* [Ant-Design](https://github.com/ant-design/ant-design) (MIT License)
|
||||
|
||||
* [axios](https://github.com/axios/axios) (MIT License)
|
||||
|
||||
* [xterm.js](https://github.com/xtermjs/xterm.js) (MIT License)
|
||||
|
||||
* [crypto-js](https://github.com/brix/crypto-js) (MIT License)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
[BSD-2 License](./LICENSE)
|
||||
@@ -1,30 +1,32 @@
|
||||
set GO111MODULE=auto
|
||||
mkdir .\built
|
||||
for /F %%i in ('git rev-parse HEAD') do ( set COMMIT=%%i)
|
||||
|
||||
|
||||
|
||||
set GOOS=linux
|
||||
|
||||
set GOARCH=arm
|
||||
go build -ldflags "-s -w" -o ./built/linux_arm Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_arm Spark/client
|
||||
set GOARCH=arm64
|
||||
go build -ldflags "-s -w" -o ./built/linux_arm64 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_arm64 Spark/client
|
||||
set GOARCH=386
|
||||
go build -ldflags "-s -w" -o ./built/linux_i386 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_i386 Spark/client
|
||||
set GOARCH=amd64
|
||||
go build -ldflags "-s -w" -o ./built/linux_amd64 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_amd64 Spark/client
|
||||
|
||||
|
||||
|
||||
set GOOS=windows
|
||||
|
||||
set GOARCH=arm
|
||||
go build -ldflags "-s -w" -o ./built/windows_arm Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_arm Spark/client
|
||||
set GOARCH=arm64
|
||||
go build -ldflags "-s -w" -o ./built/windows_arm64 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_arm64 Spark/client
|
||||
set GOARCH=386
|
||||
go build -ldflags "-s -w" -o ./built/windows_i386 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_i386 Spark/client
|
||||
set GOARCH=amd64
|
||||
go build -ldflags "-s -w" -o ./built/windows_amd64 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_amd64 Spark/client
|
||||
|
||||
|
||||
|
||||
@@ -34,23 +36,19 @@ go build -ldflags "-s -w" -o ./built/windows_amd64 Spark/client
|
||||
@REM set GOARCH=arm
|
||||
@REM set CC=armv7a-linux-androideabi21-clang
|
||||
@REM set CXX=armv7a-linux-androideabi21-clang++
|
||||
@REM go build -ldflags "-s -w" -o ./built/android_armv7a Spark/client
|
||||
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_arm Spark/client
|
||||
|
||||
@REM set GOARCH=arm64
|
||||
@REM set CC=aarch64-linux-android21-clang
|
||||
@REM set CXX=aarch64-linux-android21-clang++
|
||||
@REM go build -ldflags "-s -w" -o ./built/android_aarch64 Spark/client
|
||||
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_arm64 Spark/client
|
||||
|
||||
@REM set GOARCH=386
|
||||
@REM set CC=i686-linux-android21-clang
|
||||
@REM set CXX=i686-linux-android21-clang++
|
||||
@REM go build -ldflags "-s -w" -o ./built/android_i686 Spark/client
|
||||
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_i386 Spark/client
|
||||
|
||||
@REM set GOARCH=amd64
|
||||
@REM set CC=x86_64-linux-android21-clang
|
||||
@REM set CXX=x86_64-linux-android21-clang++
|
||||
@REM go build -ldflags "-s -w" -o ./built/android_x86_64 Spark/client
|
||||
|
||||
|
||||
|
||||
statik -m -src="./built" -f -dest="./server/embed" -include=* -p built -ns built
|
||||
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_amd64 Spark/client
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
export GO111MODULE=auto
|
||||
mkdir ./built
|
||||
export COMMIT=`git rev-parse HEAD`
|
||||
|
||||
|
||||
export GOOS=linux
|
||||
|
||||
export GOARCH=arm
|
||||
go build -ldflags "-s -w" -o ./built/linux_arm Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_arm Spark/client
|
||||
export GOARCH=arm64
|
||||
go build -ldflags "-s -w" -o ./built/linux_arm64 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_arm64 Spark/client
|
||||
export GOARCH=386
|
||||
go build -ldflags "-s -w" -o ./built/linux_i386 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_i386 Spark/client
|
||||
export GOARCH=amd64
|
||||
go build -ldflags "-s -w" -o ./built/linux_amd64 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_amd64 Spark/client
|
||||
|
||||
|
||||
|
||||
export GOOS=windows
|
||||
|
||||
export GOARCH=arm
|
||||
go build -ldflags "-s -w" -o ./built/windows_arm Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_arm Spark/client
|
||||
export GOARCH=arm64
|
||||
go build -ldflags "-s -w" -o ./built/windows_arm64 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_arm64 Spark/client
|
||||
export GOARCH=386
|
||||
go build -ldflags "-s -w" -o ./built/windows_i386 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_i386 Spark/client
|
||||
export GOARCH=amd64
|
||||
go build -ldflags "-s -w" -o ./built/windows_amd64 Spark/client
|
||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_amd64 Spark/client
|
||||
|
||||
|
||||
|
||||
@@ -34,23 +35,19 @@ go build -ldflags "-s -w" -o ./built/windows_amd64 Spark/client
|
||||
# export GOARCH=arm
|
||||
# export CC=armv7a-linux-androideabi21-clang
|
||||
# export CXX=armv7a-linux-androideabi21-clang++
|
||||
# go build -ldflags "-s -w" -o ./built/android_armv7a Spark/client
|
||||
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_arm Spark/client
|
||||
|
||||
# export GOARCH=arm64
|
||||
# export CC=aarch64-linux-android21-clang
|
||||
# export CXX=aarch64-linux-android21-clang++
|
||||
# go build -ldflags "-s -w" -o ./built/android_aarch64 Spark/client
|
||||
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_arm64 Spark/client
|
||||
|
||||
# export GOARCH=386
|
||||
# export CC=i686-linux-android21-clang
|
||||
# export CXX=i686-linux-android21-clang++
|
||||
# go build -ldflags "-s -w" -o ./built/android_i686 Spark/client
|
||||
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_i386 Spark/client
|
||||
|
||||
# export GOARCH=amd64
|
||||
# export CC=x86_64-linux-android21-clang
|
||||
# export CXX=x86_64-linux-android21-clang++
|
||||
# go build -ldflags "-s -w" -o ./built/android_x86_64 Spark/client
|
||||
|
||||
|
||||
|
||||
statik -m -src="./built" -f -dest="./server/embed" -include=* -p built -ns built
|
||||
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_amd64 Spark/client
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
statik -m -src="./web/dist" -f -dest="./server/embed" -p web -ns web
|
||||
export GO111MODULE=auto
|
||||
mkdir ./releases
|
||||
export COMMIT=`git rev-parse HEAD`
|
||||
|
||||
|
||||
|
||||
export GOOS=linux
|
||||
|
||||
export GOARCH=arm
|
||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_linux_arm Spark/server
|
||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_arm Spark/server
|
||||
export GOARCH=arm64
|
||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_linux_arm64 Spark/server
|
||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_arm64 Spark/server
|
||||
export GOARCH=386
|
||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_linux_i386 Spark/server
|
||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_i386 Spark/server
|
||||
export GOARCH=amd64
|
||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_linux_amd64 Spark/server
|
||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_amd64 Spark/server
|
||||
|
||||
|
||||
|
||||
export GOOS=windows
|
||||
|
||||
export GOARCH=arm
|
||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_windows_arm.exe Spark/server
|
||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_arm.exe Spark/server
|
||||
export GOARCH=arm64
|
||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_windows_arm64.exe Spark/server
|
||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_arm64.exe Spark/server
|
||||
export GOARCH=386
|
||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_windows_i386.exe Spark/server
|
||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_i386.exe Spark/server
|
||||
export GOARCH=amd64
|
||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_windows_amd64.exe Spark/server
|
||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_amd64.exe Spark/server
|
||||
|
||||
@@ -7,32 +7,29 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/golog"
|
||||
)
|
||||
|
||||
// localhost
|
||||
//var cfgBuffer = "\x00\xcd\x90\x50\x43\xfc\x3d\x36\x56\x6d\xf6\x01\xd1\xcd\x81\xc3\x1b\x80\xc9\x61\xd8\xdf\x5b\x76\x48\x88\xc5\xb1\x74\x22\x23\xab\x3b\xfc\x8b\xbe\x98\x27\xed\x05\xec\xbb\x40\x4f\xe9\xe7\xe5\xe0\x84\xaa\xb7\xfd\x4a\x30\x71\x08\x6c\x02\x50\xe9\xc5\x22\xcf\xcb\x89\x16\x0a\x89\x08\xd4\x26\xdc\x5c\xc1\xc9\xbf\xc4\xac\x0d\x92\x2f\x34\x7f\x45\xeb\x55\xa0\x6d\xf6\x64\xbc\xd5\x15\x40\x96\x43\x64\xe0\x24\x51\xfb\xe8\xc9\x7f\x48\x60\xcd\x30\x5e\x5e\x78\xba\xb6\x6f\x07\x64\xe8\x59\x81\x0b\x91\x13\x92\x1a\xdd\x49\x8f\x28\xe7\x74\xea\xff\x5b\x45\x0e\x4a\x2d\x60\x4e\xc9\xde\x9c\xbe\x50\xc6\x12\xc7\x45\xa2\x15\xa0\x58\x62\x45\x86\x74\x9f\xa5\x14\x5c\x17\x8a\xcc\x56\x73\xa7\x75\xb7\xf6\x6d\x52\x0f\xb8\xc1\xff\x9c\x39\x39\x00\x74\xe1\x4d\x65\x73\x9c\x02\x57\x8b\xcf\xdf\x0a\x20\x4c\xed\xe2\x25\xea\x01\x36\x12\x37\x12\x2e\x1a\x03\x41\x19\x2e\xc9\xdd\x71\xac\x73\x90\xfa\x5e\x60\x08\x43\x35\xef\x61\x45\xf9\xe3\xba\xcb\xb1\xc5\x7c\xf0\x11\xcd\x47\x57\x53\xdc\x35\x6b\x9f\xac\xad\x43\x4a\xc7\x54\x20\xb8\xd0\xf8\xb5\x0c\x45\x76\x57\xb9\xee\x4a\x3f\xd2\xda\xf7\x94\x54\x74\xf3\x91\xf3\x4d\x49\x98\xc6\xf8\x60\x80\xad\x84\x04\xef\x35\xca\x3a\xcf\xd3\x7e\x74\xc2\x4b\xb8\xb3\x9f\xb2\x83\xb8\xbd\x29\x13\x9f\x2b\xaa\x60\x47\x24\x7e\x20\xb2\x85\xdc\x47\xfe\x8f\x68\xb6\xc3\x43\xad\x61\x3d\x9b\x35\x60\x2e\x6c\x44\xf0\xaf\xb2\xf3\xdb\xe2\x1b\x8a\xec\x0a\x48\x5e\x43\xa9\xb3\x3a\x5e\xb6\x90\xa9\x3d\xee\x4f\xa1\x57\x7c\x94\xf4\xb1\x36\xda\x04\xa8\x5e\x48\x2a\xc3\xa1\xf0\x97\xf0\xe0\x10\x46\x32\x10\xe5\xd8\x36\x5a\x56\xa5\xbb\x37\x3c\x9f\xbd\xef\xf5\x2f"
|
||||
|
||||
// none
|
||||
var cfgBuffer = "\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19"
|
||||
|
||||
func init() {
|
||||
golog.SetTimeFormat(`2006/01/02 15:04:05`)
|
||||
|
||||
if len(strings.Trim(cfgBuffer, "\x19")) == 0 {
|
||||
if len(strings.Trim(config.CfgBuffer, "\x19")) == 0 {
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
dataLen := int(big.NewInt(0).SetBytes([]byte(cfgBuffer[:2])).Uint64())
|
||||
if dataLen > len(cfgBuffer)-2 {
|
||||
dataLen := int(big.NewInt(0).SetBytes([]byte(config.CfgBuffer[:2])).Uint64())
|
||||
if dataLen > len(config.CfgBuffer)-2 {
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
cfgBytes := []byte(cfgBuffer[2 : 2+dataLen])
|
||||
cfgBytes := []byte(config.CfgBuffer[2 : 2+dataLen])
|
||||
cfgBytes, err := decrypt(cfgBytes[16:], cfgBytes[:16])
|
||||
if err != nil {
|
||||
os.Exit(0)
|
||||
@@ -49,6 +46,27 @@ func init() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == `--update` {
|
||||
thisPath := os.Args[0]
|
||||
destPath := thisPath[:len(thisPath)-4]
|
||||
if len(thisPath) <= 4 {
|
||||
return
|
||||
}
|
||||
thisFile, err := ioutil.ReadFile(thisPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ioutil.WriteFile(destPath, thisFile, 0755)
|
||||
cmd := exec.Command(destPath, `--clean`)
|
||||
if cmd.Start() == nil {
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(os.Args) > 1 && os.Args[1] == `--clean` {
|
||||
<-time.After(time.Second)
|
||||
os.Remove(os.Args[0] + `.tmp`)
|
||||
}
|
||||
core.Start()
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func SendPack(pack interface{}, wsConn *Conn) error {
|
||||
return err
|
||||
}
|
||||
if len(data) > 1024 {
|
||||
_, err = req.C().R().
|
||||
_, err = req.R().
|
||||
SetBody(data).
|
||||
SetHeader(`Secret`, hex.EncodeToString(wsConn.Secret)).
|
||||
Send(`POST`, config.GetBaseURL(false)+`/ws`)
|
||||
|
||||
@@ -14,6 +14,14 @@ type Cfg struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// localhost
|
||||
//var CfgBuffer = "\x00\xcd\x90\x50\x43\xfc\x3d\x36\x56\x6d\xf6\x01\xd1\xcd\x81\xc3\x1b\x80\xc9\x61\xd8\xdf\x5b\x76\x48\x88\xc5\xb1\x74\x22\x23\xab\x3b\xfc\x8b\xbe\x98\x27\xed\x05\xec\xbb\x40\x4f\xe9\xe7\xe5\xe0\x84\xaa\xb7\xfd\x4a\x30\x71\x08\x6c\x02\x50\xe9\xc5\x22\xcf\xcb\x89\x16\x0a\x89\x08\xd4\x26\xdc\x5c\xc1\xc9\xbf\xc4\xac\x0d\x92\x2f\x34\x7f\x45\xeb\x55\xa0\x6d\xf6\x64\xbc\xd5\x15\x40\x96\x43\x64\xe0\x24\x51\xfb\xe8\xc9\x7f\x48\x60\xcd\x30\x5e\x5e\x78\xba\xb6\x6f\x07\x64\xe8\x59\x81\x0b\x91\x13\x92\x1a\xdd\x49\x8f\x28\xe7\x74\xea\xff\x5b\x45\x0e\x4a\x2d\x60\x4e\xc9\xde\x9c\xbe\x50\xc6\x12\xc7\x45\xa2\x15\xa0\x58\x62\x45\x86\x74\x9f\xa5\x14\x5c\x17\x8a\xcc\x56\x73\xa7\x75\xb7\xf6\x6d\x52\x0f\xb8\xc1\xff\x9c\x39\x39\x00\x74\xe1\x4d\x65\x73\x9c\x02\x57\x8b\xcf\xdf\x0a\x20\x4c\xed\xe2\x25\xea\x01\x36\x12\x37\x12\x2e\x1a\x03\x41\x19\x2e\xc9\xdd\x71\xac\x73\x90\xfa\x5e\x60\x08\x43\x35\xef\x61\x45\xf9\xe3\xba\xcb\xb1\xc5\x7c\xf0\x11\xcd\x47\x57\x53\xdc\x35\x6b\x9f\xac\xad\x43\x4a\xc7\x54\x20\xb8\xd0\xf8\xb5\x0c\x45\x76\x57\xb9\xee\x4a\x3f\xd2\xda\xf7\x94\x54\x74\xf3\x91\xf3\x4d\x49\x98\xc6\xf8\x60\x80\xad\x84\x04\xef\x35\xca\x3a\xcf\xd3\x7e\x74\xc2\x4b\xb8\xb3\x9f\xb2\x83\xb8\xbd\x29\x13\x9f\x2b\xaa\x60\x47\x24\x7e\x20\xb2\x85\xdc\x47\xfe\x8f\x68\xb6\xc3\x43\xad\x61\x3d\x9b\x35\x60\x2e\x6c\x44\xf0\xaf\xb2\xf3\xdb\xe2\x1b\x8a\xec\x0a\x48\x5e\x43\xa9\xb3\x3a\x5e\xb6\x90\xa9\x3d\xee\x4f\xa1\x57\x7c\x94\xf4\xb1\x36\xda\x04\xa8\x5e\x48\x2a\xc3\xa1\xf0\x97\xf0\xe0\x10\x46\x32\x10\xe5\xd8\x36\x5a\x56\xa5\xbb\x37\x3c\x9f\xbd\xef\xf5\x2f"
|
||||
|
||||
// none
|
||||
var CfgBuffer = "\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19"
|
||||
|
||||
// COMMIT means this commit hash, for auto upgrade.
|
||||
var COMMIT = ``
|
||||
var Config Cfg
|
||||
|
||||
func GetBaseURL(ws bool) string {
|
||||
|
||||
@@ -13,9 +13,13 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
ws "github.com/gorilla/websocket"
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/kataras/golog"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
@@ -48,6 +52,8 @@ func Start() {
|
||||
continue
|
||||
}
|
||||
|
||||
checkUpdate(common.WSConn)
|
||||
|
||||
go heartbeat(common.WSConn)
|
||||
|
||||
err = handleWS(common.WSConn)
|
||||
@@ -106,6 +112,44 @@ func reportWS(wsConn *common.Conn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkUpdate(wsConn *common.Conn) error {
|
||||
if len(config.COMMIT) == 0 {
|
||||
return nil
|
||||
}
|
||||
resp, err := req.R().
|
||||
SetBody(config.CfgBuffer).
|
||||
SetQueryParam(`os`, runtime.GOOS).
|
||||
SetQueryParam(`arch`, runtime.GOARCH).
|
||||
SetQueryParam(`commit`, config.COMMIT).
|
||||
SetHeader(`Secret`, hex.EncodeToString(wsConn.Secret)).
|
||||
Send(`POST`, config.GetBaseURL(false)+`/api/client/update`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp == nil {
|
||||
return errors.New(`unknown error occurred`)
|
||||
}
|
||||
if resp.GetContentType() == `application/octet-stream` {
|
||||
body := resp.Bytes()
|
||||
if len(body) > 0 {
|
||||
err = ioutil.WriteFile(os.Args[0]+`.tmp`, body, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(os.Args[0]+`.tmp`, `--update`)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stop = true
|
||||
wsConn.Close()
|
||||
os.Exit(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleWS(wsConn *common.Conn) error {
|
||||
errCount := 0
|
||||
for {
|
||||
@@ -223,16 +267,21 @@ func handleAct(pack modules.Packet, wsConn *common.Conn) {
|
||||
common.SendCb(modules.Packet{Code: 0, Data: smap{`files`: files}}, pack, wsConn)
|
||||
}
|
||||
case `removeFile`:
|
||||
path, ok := pack.Data[`path`]
|
||||
val, ok := pack.Data[`path`]
|
||||
if !ok {
|
||||
common.SendCb(modules.Packet{Code: 1, Msg: `can not find such a file or directory`}, pack, wsConn)
|
||||
return
|
||||
}
|
||||
if path == `\` || path == `/` || len(path.(string)) == 0 {
|
||||
path, ok := val.(string)
|
||||
if !ok {
|
||||
common.SendCb(modules.Packet{Code: 1, Msg: `can not find such a file or directory`}, pack, wsConn)
|
||||
return
|
||||
}
|
||||
err := os.RemoveAll(path.(string))
|
||||
if path == `\` || path == `/` || len(path) == 0 {
|
||||
common.SendCb(modules.Packet{Code: 1, Msg: `can not find such a file or directory`}, pack, wsConn)
|
||||
return
|
||||
}
|
||||
err := os.RemoveAll(path)
|
||||
if err != nil {
|
||||
common.SendCb(modules.Packet{Code: 1, Msg: err.Error()}, pack, wsConn)
|
||||
} else {
|
||||
@@ -307,15 +356,16 @@ func handleAct(pack modules.Packet, wsConn *common.Conn) {
|
||||
|
||||
func heartbeat(wsConn *common.Conn) error {
|
||||
t := 0
|
||||
for range time.NewTicker(5 * time.Second).C {
|
||||
for range time.NewTicker(2 * time.Second).C {
|
||||
t++
|
||||
// Get disk info every 30*5 seconds.
|
||||
device, err := GetPartialInfo(t >= 30)
|
||||
// GetPartialInfo always costs more than 1 second.
|
||||
// So it is actually get disk info every 200*3 seconds (10 minutes).
|
||||
device, err := GetPartialInfo(t >= 200)
|
||||
if err != nil {
|
||||
golog.Error(err)
|
||||
continue
|
||||
}
|
||||
if t >= 30 {
|
||||
if t >= 200 {
|
||||
t = 0
|
||||
}
|
||||
err = common.SendPack(modules.CommonPack{Act: `setDevice`, Data: device}, wsConn)
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"net"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
_net "net"
|
||||
"os"
|
||||
"os/user"
|
||||
"runtime"
|
||||
@@ -18,8 +19,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func isPrivateIP(ip net.IP) bool {
|
||||
var privateIPBlocks []*net.IPNet
|
||||
func isPrivateIP(ip _net.IP) bool {
|
||||
var privateIPBlocks []*_net.IPNet
|
||||
for _, cidr := range []string{
|
||||
//"127.0.0.0/8", // IPv4 loopback
|
||||
//"::1/128", // IPv6 loopback
|
||||
@@ -28,7 +29,7 @@ func isPrivateIP(ip net.IP) bool {
|
||||
"172.16.0.0/12", // RFC1918
|
||||
"192.168.0.0/16", // RFC1918
|
||||
} {
|
||||
_, block, _ := net.ParseCIDR(cidr)
|
||||
_, block, _ := _net.ParseCIDR(cidr)
|
||||
privateIPBlocks = append(privateIPBlocks, block)
|
||||
}
|
||||
for _, block := range privateIPBlocks {
|
||||
@@ -40,7 +41,7 @@ func isPrivateIP(ip net.IP) bool {
|
||||
}
|
||||
|
||||
func GetLocalIP() (string, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
ifaces, err := _net.Interfaces()
|
||||
if err != nil {
|
||||
return `Unknown`, err
|
||||
}
|
||||
@@ -51,11 +52,11 @@ func GetLocalIP() (string, error) {
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
var ip _net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
case *_net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
case *_net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
if isPrivateIP(ip) {
|
||||
@@ -71,7 +72,7 @@ func GetLocalIP() (string, error) {
|
||||
}
|
||||
|
||||
func GetMacAddress() (string, error) {
|
||||
interfaces, err := net.Interfaces()
|
||||
interfaces, err := _net.Interfaces()
|
||||
if err != nil {
|
||||
return ``, err
|
||||
}
|
||||
@@ -88,6 +89,28 @@ func GetMacAddress() (string, error) {
|
||||
return strings.ToUpper(address[0]), nil
|
||||
}
|
||||
|
||||
func GetNetIOInfo() (modules.Net, error) {
|
||||
result := modules.Net{}
|
||||
first, err := net.IOCounters(false)
|
||||
if err != nil {
|
||||
return result, nil
|
||||
}
|
||||
if len(first) == 0 {
|
||||
return result, errors.New(`failed to read network io counters`)
|
||||
}
|
||||
<-time.After(time.Second)
|
||||
second, err := net.IOCounters(false)
|
||||
if err != nil {
|
||||
return result, nil
|
||||
}
|
||||
if len(second) == 0 {
|
||||
return result, errors.New(`failed to read network io counters`)
|
||||
}
|
||||
result.Recv = second[0].BytesRecv - first[0].BytesRecv
|
||||
result.Sent = second[0].BytesSent - first[0].BytesSent
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetCPUInfo() (modules.CPU, error) {
|
||||
result := modules.CPU{}
|
||||
info, err := cpu.Info()
|
||||
@@ -109,8 +132,8 @@ func GetCPUInfo() (modules.CPU, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetMemInfo() (modules.Mem, error) {
|
||||
result := modules.Mem{}
|
||||
func GetMemInfo() (modules.IO, error) {
|
||||
result := modules.IO{}
|
||||
stat, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
return result, nil
|
||||
@@ -121,8 +144,9 @@ func GetMemInfo() (modules.Mem, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetDiskInfo() (modules.Disk, error) {
|
||||
result := modules.Disk{}
|
||||
func GetDiskInfo() (modules.IO, error) {
|
||||
result := modules.IO{}
|
||||
disk.IOCounters()
|
||||
disks, err := disk.Partitions(true)
|
||||
if err != nil {
|
||||
return result, nil
|
||||
@@ -163,9 +187,16 @@ func GetDevice() (*modules.Device, error) {
|
||||
Usage: 0,
|
||||
}
|
||||
}
|
||||
netInfo, err := GetNetIOInfo()
|
||||
if err != nil {
|
||||
netInfo = modules.Net{
|
||||
Sent: 0,
|
||||
Recv: 0,
|
||||
}
|
||||
}
|
||||
memInfo, err := GetMemInfo()
|
||||
if err != nil {
|
||||
memInfo = modules.Mem{
|
||||
memInfo = modules.IO{
|
||||
Total: 0,
|
||||
Used: 0,
|
||||
Usage: 0,
|
||||
@@ -173,7 +204,7 @@ func GetDevice() (*modules.Device, error) {
|
||||
}
|
||||
diskInfo, err := GetDiskInfo()
|
||||
if err != nil {
|
||||
diskInfo = modules.Disk{
|
||||
diskInfo = modules.IO{
|
||||
Total: 0,
|
||||
Used: 0,
|
||||
Usage: 0,
|
||||
@@ -203,6 +234,7 @@ func GetDevice() (*modules.Device, error) {
|
||||
LAN: localIP,
|
||||
Mac: macAddr,
|
||||
CPU: cpuInfo,
|
||||
Net: netInfo,
|
||||
Mem: memInfo,
|
||||
Disk: diskInfo,
|
||||
Uptime: uptime,
|
||||
@@ -211,7 +243,7 @@ func GetDevice() (*modules.Device, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetPartialInfo(getDisk bool) (*modules.Device, error) {
|
||||
func GetPartialInfo(getDisk bool) (modules.Device, error) {
|
||||
cpuInfo, err := GetCPUInfo()
|
||||
if err != nil {
|
||||
cpuInfo = modules.CPU{
|
||||
@@ -219,9 +251,16 @@ func GetPartialInfo(getDisk bool) (*modules.Device, error) {
|
||||
Usage: 0,
|
||||
}
|
||||
}
|
||||
netInfo, err := GetNetIOInfo()
|
||||
if err != nil {
|
||||
netInfo = modules.Net{
|
||||
Recv: 0,
|
||||
Sent: 0,
|
||||
}
|
||||
}
|
||||
memInfo, err := GetMemInfo()
|
||||
if err != nil {
|
||||
memInfo = modules.Mem{
|
||||
memInfo = modules.IO{
|
||||
Total: 0,
|
||||
Used: 0,
|
||||
Usage: 0,
|
||||
@@ -229,7 +268,7 @@ func GetPartialInfo(getDisk bool) (*modules.Device, error) {
|
||||
}
|
||||
diskInfo, err := GetDiskInfo()
|
||||
if err != nil {
|
||||
diskInfo = modules.Disk{
|
||||
diskInfo = modules.IO{
|
||||
Total: 0,
|
||||
Used: 0,
|
||||
Usage: 0,
|
||||
@@ -239,8 +278,9 @@ func GetPartialInfo(getDisk bool) (*modules.Device, error) {
|
||||
if err != nil {
|
||||
uptime = 0
|
||||
}
|
||||
return &modules.Device{
|
||||
return modules.Device{
|
||||
CPU: cpuInfo,
|
||||
Net: netInfo,
|
||||
Mem: memInfo,
|
||||
Disk: diskInfo,
|
||||
Uptime: uptime,
|
||||
|
||||
@@ -59,7 +59,7 @@ func InitTerminal(pack modules.Packet) error {
|
||||
buffer := make([]byte, 512)
|
||||
n, err := stdout.Read(buffer)
|
||||
buffer = buffer[:n]
|
||||
buffer, _ = gbkToUtf8(buffer)
|
||||
buffer, _ = encodeUTF8(buffer)
|
||||
common.SendCb(modules.Packet{Act: `outputTerminal`, Data: map[string]interface{}{
|
||||
`output`: hex.EncodeToString(buffer),
|
||||
}}, pack, common.WSConn)
|
||||
@@ -74,7 +74,7 @@ func InitTerminal(pack modules.Packet) error {
|
||||
buffer := make([]byte, 512)
|
||||
n, err := stderr.Read(buffer)
|
||||
buffer = buffer[:n]
|
||||
buffer, _ = gbkToUtf8(buffer)
|
||||
buffer, _ = encodeUTF8(buffer)
|
||||
common.SendCb(modules.Packet{Act: `outputTerminal`, Data: map[string]interface{}{
|
||||
`output`: hex.EncodeToString(buffer),
|
||||
}}, pack, common.WSConn)
|
||||
@@ -138,7 +138,7 @@ func InputTerminal(pack modules.Packet) error {
|
||||
terminal.cmd.Process.Signal(os.Interrupt)
|
||||
return nil
|
||||
}
|
||||
data, _ = utf8ToGbk(data)
|
||||
data, _ = decodeUTF8(data)
|
||||
(*terminal.stdin).Write(data)
|
||||
return nil
|
||||
}
|
||||
@@ -180,16 +180,34 @@ func doKillTerminal(terminal *terminal) {
|
||||
}
|
||||
|
||||
func getTerminal() string {
|
||||
switch runtime.GOOS {
|
||||
case `windows`:
|
||||
if runtime.GOOS == `windows` {
|
||||
return `cmd.exe`
|
||||
case `linux`:
|
||||
return `sh`
|
||||
case `darwin`:
|
||||
return `sh`
|
||||
default:
|
||||
}
|
||||
sh := []string{`bash`, `zsh`, `sh`}
|
||||
for i := 0; i < len(sh); i++ {
|
||||
f, err := os.Open(sh[i])
|
||||
if err == nil {
|
||||
f.Close()
|
||||
return sh[i]
|
||||
}
|
||||
}
|
||||
return `sh`
|
||||
}
|
||||
|
||||
func encodeUTF8(s []byte) ([]byte, error) {
|
||||
if runtime.GOOS == `windows` {
|
||||
return gbkToUtf8(s)
|
||||
} else {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
|
||||
func decodeUTF8(s []byte) ([]byte, error) {
|
||||
if runtime.GOOS == `windows` {
|
||||
return utf8ToGbk(s)
|
||||
} else {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
|
||||
func gbkToUtf8(s []byte) ([]byte, error) {
|
||||
|
||||
@@ -23,28 +23,28 @@ type Device struct {
|
||||
LAN string `json:"lan"`
|
||||
WAN string `json:"wan"`
|
||||
Mac string `json:"mac"`
|
||||
Net Net `json:"net"`
|
||||
CPU CPU `json:"cpu"`
|
||||
Mem Mem `json:"mem"`
|
||||
Disk Disk `json:"disk"`
|
||||
Mem IO `json:"mem"`
|
||||
Disk IO `json:"disk"`
|
||||
Uptime uint64 `json:"uptime"`
|
||||
Latency uint `json:"latency"`
|
||||
Hostname string `json:"hostname"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type IO struct {
|
||||
Total uint64 `json:"total"`
|
||||
Used uint64 `json:"used"`
|
||||
Usage float64 `json:"usage"`
|
||||
}
|
||||
|
||||
type CPU struct {
|
||||
Model string `json:"model"`
|
||||
Usage float64 `json:"usage"`
|
||||
}
|
||||
|
||||
type Mem struct {
|
||||
Total uint64 `json:"total"`
|
||||
Used uint64 `json:"used"`
|
||||
Usage float64 `json:"usage"`
|
||||
}
|
||||
|
||||
type Disk struct {
|
||||
Total uint64 `json:"total"`
|
||||
Used uint64 `json:"used"`
|
||||
Usage float64 `json:"usage"`
|
||||
type Net struct {
|
||||
Sent uint64 `json:"sent"`
|
||||
Recv uint64 `json:"recv"`
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 32 KiB |
@@ -8,6 +8,8 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/hex"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
@@ -117,6 +119,28 @@ func WSHealthCheck(container *melody.Melody) {
|
||||
}
|
||||
}
|
||||
|
||||
func CheckClientReq(ctx *gin.Context, cb func(*melody.Session)) bool {
|
||||
secret, err := hex.DecodeString(ctx.GetHeader(`Secret`))
|
||||
if err != nil || len(secret) != 32 {
|
||||
return false
|
||||
}
|
||||
find := false
|
||||
Melody.IterSessions(func(uuid string, s *melody.Session) bool {
|
||||
if val, ok := s.Get(`Secret`); ok {
|
||||
// Check if there's a connection matches this secret.
|
||||
if b, ok := val.([]byte); ok && bytes.Equal(b, secret) {
|
||||
find = true
|
||||
if cb != nil {
|
||||
cb(s)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return find
|
||||
}
|
||||
|
||||
func CheckDevice(deviceID string) (string, bool) {
|
||||
connUUID := ``
|
||||
Devices.IterCb(func(uuid string, v interface{}) bool {
|
||||
|
||||
@@ -8,3 +8,6 @@ type Cfg struct {
|
||||
}
|
||||
|
||||
var Config Cfg
|
||||
|
||||
// COMMIT means this commit hash, for auto upgrade.
|
||||
var COMMIT = ``
|
||||
|
||||
@@ -193,6 +193,7 @@ func getDeviceFile(ctx *gin.Context) {
|
||||
ctx.Header(`Content-Length`, strconv.FormatInt(req.ContentLength, 10))
|
||||
}
|
||||
ctx.Header(`Accept-Ranges`, `bytes`)
|
||||
ctx.Header(`Content-Transfer-Encoding`, `binary`)
|
||||
ctx.Header(`Content-Type`, `application/octet-stream`)
|
||||
filename := ctx.GetHeader(`FileName`)
|
||||
if len(filename) == 0 {
|
||||
|
||||
@@ -136,6 +136,7 @@ func generateClient(ctx *gin.Context) {
|
||||
} else {
|
||||
ctx.Header(`Content-Disposition`, `attachment; filename=client;`)
|
||||
}
|
||||
// Find and replace plain buffer with encrypted configuration.
|
||||
cfgBuffer := bytes.Repeat([]byte{'\x19'}, 384)
|
||||
prevBuffer := make([]byte, 0)
|
||||
for {
|
||||
@@ -173,6 +174,7 @@ func genConfig(cfg clientCfg) ([]byte, error) {
|
||||
if len(final) > 384-2 {
|
||||
return nil, errTooLargeEntity
|
||||
}
|
||||
|
||||
dataLen := big.NewInt(int64(len(final))).Bytes()
|
||||
dataLen = append(bytes.Repeat([]byte{'\x00'}, 2-len(dataLen)), dataLen...)
|
||||
|
||||
|
||||
@@ -3,20 +3,24 @@ package handler
|
||||
import (
|
||||
"Spark/modules"
|
||||
"Spark/server/common"
|
||||
"Spark/server/config"
|
||||
"Spark/utils"
|
||||
"Spark/utils/melody"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/kataras/golog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// APIRouter 负责分配各种API接口
|
||||
// APIRouter will initialize http and websocket routers.
|
||||
func APIRouter(ctx *gin.RouterGroup, auth gin.HandlerFunc) {
|
||||
ctx.PUT(`/device/screenshot/put`, putScreenshot)
|
||||
ctx.PUT(`/device/file/put`, putDeviceFile)
|
||||
ctx.Any(`/device/terminal`, initTerminal)
|
||||
ctx.PUT(`/device/screenshot/put`, putScreenshot) // Client, upload screenshot and forward to browser.
|
||||
ctx.PUT(`/device/file/put`, putDeviceFile) // Client, to upload file and forward to browser.
|
||||
ctx.Any(`/device/terminal`, initTerminal) // Browser, handle websocket events for web terminal.
|
||||
ctx.Any(`/client/update`, checkUpdate) // Client, for update.
|
||||
group := ctx.Group(`/`, auth)
|
||||
{
|
||||
group.POST(`/device/screenshot/get`, getScreenshot)
|
||||
@@ -32,7 +36,73 @@ func APIRouter(ctx *gin.RouterGroup, auth gin.HandlerFunc) {
|
||||
}
|
||||
}
|
||||
|
||||
// putScreenshot 负责获取client发送过来的屏幕截图
|
||||
// checkUpdate will check if client need update and return latest client if so.
|
||||
func checkUpdate(ctx *gin.Context) {
|
||||
var form struct {
|
||||
OS string `form:"os" binding:"required"`
|
||||
Arch string `form:"arch" binding:"required"`
|
||||
Commit string `form:"commit" binding:"required"`
|
||||
}
|
||||
if err := ctx.ShouldBind(&form); err != nil {
|
||||
golog.Error(err)
|
||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`})
|
||||
return
|
||||
}
|
||||
if form.Commit == config.COMMIT {
|
||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||
return
|
||||
}
|
||||
tpl, err := common.BuiltFS.Open(fmt.Sprintf(`/%v_%v`, form.OS, form.Arch))
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `该系统或架构的客户端尚未编译`})
|
||||
return
|
||||
}
|
||||
|
||||
const MaxBodySize = 384 // This is size of client config buffer.
|
||||
if ctx.Request.ContentLength > MaxBodySize {
|
||||
ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
|
||||
return
|
||||
}
|
||||
body, err := ctx.GetRawData()
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: 1})
|
||||
return
|
||||
}
|
||||
auth := common.CheckClientReq(ctx, nil)
|
||||
if !auth {
|
||||
ctx.JSON(http.StatusUnauthorized, modules.Packet{Code: 1})
|
||||
}
|
||||
|
||||
ctx.Header(`Accept-Ranges`, `none`)
|
||||
ctx.Header(`Content-Transfer-Encoding`, `binary`)
|
||||
ctx.Header(`Content-Type`, `application/octet-stream`)
|
||||
if stat, err := tpl.Stat(); err == nil {
|
||||
ctx.Header(`Content-Length`, strconv.FormatInt(stat.Size(), 10))
|
||||
}
|
||||
cfgBuffer := bytes.Repeat([]byte{'\x19'}, 384)
|
||||
prevBuffer := make([]byte, 0)
|
||||
for {
|
||||
thisBuffer := make([]byte, 1024)
|
||||
n, err := tpl.Read(thisBuffer)
|
||||
thisBuffer = thisBuffer[:n]
|
||||
tempBuffer := append(prevBuffer, thisBuffer...)
|
||||
bufIndex := bytes.Index(tempBuffer, cfgBuffer)
|
||||
if bufIndex > -1 {
|
||||
tempBuffer = bytes.Replace(tempBuffer, cfgBuffer, body, -1)
|
||||
}
|
||||
ctx.Writer.Write(tempBuffer[:len(prevBuffer)])
|
||||
prevBuffer = tempBuffer[len(prevBuffer):]
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(prevBuffer) > 0 {
|
||||
ctx.Writer.Write(prevBuffer)
|
||||
prevBuffer = []byte{}
|
||||
}
|
||||
}
|
||||
|
||||
// putScreenshot will forward screenshot image from client to browser.
|
||||
func putScreenshot(ctx *gin.Context) {
|
||||
errMsg := ctx.GetHeader(`Error`)
|
||||
trigger := ctx.GetHeader(`Trigger`)
|
||||
@@ -76,7 +146,7 @@ func putScreenshot(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||
}
|
||||
|
||||
// getScreenshot 负责发送指令给client,让其截图
|
||||
// getScreenshot will call client to screenshot.
|
||||
func getScreenshot(ctx *gin.Context) {
|
||||
var form struct {
|
||||
Conn string `json:"uuid" yaml:"uuid" form:"uuid"`
|
||||
@@ -125,7 +195,7 @@ func getScreenshot(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// getDevices 负责获取所有device的基本信息
|
||||
// getDevices will return all info about all clients.
|
||||
func getDevices(ctx *gin.Context) {
|
||||
devices := make(map[string]modules.Device)
|
||||
common.Devices.IterCb(func(uuid string, v interface{}) bool {
|
||||
@@ -138,7 +208,7 @@ func getDevices(ctx *gin.Context) {
|
||||
ctx.JSON(http.StatusOK, modules.CommonPack{Code: 0, Data: devices})
|
||||
}
|
||||
|
||||
// callDevice 负责把HTTP网关发送的请求转发给client
|
||||
// callDevice will call client with command from browser.
|
||||
func callDevice(ctx *gin.Context) {
|
||||
var form struct {
|
||||
Conn string `json:"uuid" yaml:"uuid" form:"uuid"`
|
||||
@@ -178,7 +248,8 @@ func callDevice(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// WSDevice 负责处理client设备信息上报的事件
|
||||
// WSDevice handles events about device info.
|
||||
// Such as websocket handshake and update device info.
|
||||
func WSDevice(data []byte, session *melody.Session) error {
|
||||
var pack struct {
|
||||
Code int `json:"code,omitempty"`
|
||||
@@ -230,6 +301,7 @@ func WSDevice(data []byte, session *melody.Session) error {
|
||||
deviceInfo, ok := val.(*modules.Device)
|
||||
if ok {
|
||||
deviceInfo.CPU = pack.Device.CPU
|
||||
deviceInfo.Net = pack.Device.Net
|
||||
deviceInfo.Mem = pack.Device.Mem
|
||||
if pack.Device.Disk.Total > 0 {
|
||||
deviceInfo.Disk = pack.Device.Disk
|
||||
@@ -243,8 +315,7 @@ func WSDevice(data []byte, session *melody.Session) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WSRouter 负责处理client回复的packet
|
||||
// WSRouter handles all packets from client.
|
||||
func WSRouter(pack modules.Packet, session *melody.Session) {
|
||||
|
||||
common.CallEvent(pack, session)
|
||||
}
|
||||
|
||||
@@ -62,11 +62,11 @@ func main() {
|
||||
}
|
||||
app := gin.New()
|
||||
auth := gin.BasicAuth(config.Config.Auth)
|
||||
handler.APIRouter(app.Group(`/api`), auth)
|
||||
app.Any(`/ws`, wsHandshake)
|
||||
app.NoRoute(auth, func(ctx *gin.Context) {
|
||||
http.FileServer(webFS).ServeHTTP(ctx.Writer, ctx.Request)
|
||||
})
|
||||
handler.APIRouter(app.Group(`/api`), auth)
|
||||
app.Any(`/ws`, wsHandshake)
|
||||
|
||||
common.Melody.Config.MaxMessageSize = 1024
|
||||
common.Melody.HandleConnect(wsOnConnect)
|
||||
@@ -126,26 +126,23 @@ func wsHandshake(ctx *gin.Context) {
|
||||
} else {
|
||||
// When message is too large to transport via websocket,
|
||||
// client will try to send these data via http.
|
||||
// Here is the data validator.
|
||||
const MaxBufferSize = 2 << 18 //524288 512KB
|
||||
secret, err := hex.DecodeString(ctx.GetHeader(`Secret`))
|
||||
if err != nil || len(secret) != 32 {
|
||||
const MaxBodySize = 2 << 18 //524288 512KB
|
||||
if ctx.Request.ContentLength > MaxBodySize {
|
||||
ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
|
||||
return
|
||||
}
|
||||
body, err := ctx.GetRawData()
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: 1})
|
||||
return
|
||||
}
|
||||
common.Melody.IterSessions(func(uuid string, s *melody.Session) bool {
|
||||
if val, ok := s.Get(`Secret`); ok {
|
||||
// Check if there's the connection with the secret.
|
||||
if b, ok := val.([]byte); ok && bytes.Equal(b, secret) {
|
||||
auth := common.CheckClientReq(ctx, func(s *melody.Session) {
|
||||
wsOnMessageBinary(s, body)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if !auth {
|
||||
ctx.JSON(http.StatusUnauthorized, modules.Packet{Code: 1})
|
||||
}
|
||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
npm run build-prod
|
||||
statik -m -src="../web/dist" -f -dest="../server/embed" -p web -ns web
|
||||
@@ -13,7 +13,6 @@ import 'dayjs/locale/zh-cn';
|
||||
import Overview from "./pages/overview";
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
console.log("%c By XZB", 'font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:64px;color:#00bbee;-webkit-text-fill-color:#00bbee;-webkit-text-stroke:1px#00bbee;');
|
||||
|
||||
axios.defaults.baseURL = '.';
|
||||
axios.interceptors.response.use(async (res) => {
|
||||
|
||||
@@ -10,6 +10,9 @@ import {QuestionCircleOutlined} from "@ant-design/icons";
|
||||
|
||||
import defaultColumnsState from "../config/columnsState.json";
|
||||
|
||||
// DO NOT EDIT OR DELETE THIS COPYRIGHT MESSAGE.
|
||||
console.log("%c By XZB %c https://github.com/XZB-1248/Spark", 'font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:64px;color:#00bbee;-webkit-text-fill-color:#00bbee;-webkit-text-stroke:1px#00bbee;', 'font-size:12px;');
|
||||
|
||||
function overview(props) {
|
||||
const [procMgr, setProcMgr] = useState(false);
|
||||
const [browser, setBrowser] = useState(false);
|
||||
@@ -33,7 +36,7 @@ function overview(props) {
|
||||
title: 'Username',
|
||||
dataIndex: 'username',
|
||||
ellipsis: true,
|
||||
width: 100
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
key: 'ping',
|
||||
@@ -48,7 +51,7 @@ function overview(props) {
|
||||
title: 'CPU Usage',
|
||||
dataIndex: 'cpu_usage',
|
||||
ellipsis: true,
|
||||
render: (_, v) => <Progress percent={v.cpu_usage} showInfo={false} strokeWidth={12} />,
|
||||
render: (_, v) => <Progress percent={v.cpu_usage} showInfo={false} strokeWidth={12} trailColor='#FFECFF'/>,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
@@ -56,7 +59,7 @@ function overview(props) {
|
||||
title: 'Mem Usage',
|
||||
dataIndex: 'mem_usage',
|
||||
ellipsis: true,
|
||||
render: (_, v) => <Progress percent={v.mem_usage} showInfo={false} strokeWidth={12} />,
|
||||
render: (_, v) => <Progress percent={v.mem_usage} showInfo={false} strokeWidth={12} trailColor='#FFECFF'/>,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
@@ -64,9 +67,17 @@ function overview(props) {
|
||||
title: 'Disk Usage',
|
||||
dataIndex: 'disk_usage',
|
||||
ellipsis: true,
|
||||
render: (_, v) => <Progress percent={v.disk_usage} showInfo={false} strokeWidth={12} />,
|
||||
render: (_, v) => <Progress percent={v.disk_usage} showInfo={false} strokeWidth={12} trailColor='#FFECFF'/>,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
key: 'mem_total',
|
||||
title: 'Mem',
|
||||
dataIndex: 'mem_total',
|
||||
ellipsis: true,
|
||||
renderText: formatSize,
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
key: 'os',
|
||||
title: 'OS',
|
||||
@@ -102,14 +113,6 @@ function overview(props) {
|
||||
ellipsis: true,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
key: 'mem_total',
|
||||
title: 'Mem',
|
||||
dataIndex: 'mem_total',
|
||||
ellipsis: true,
|
||||
renderText: formatSize,
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
key: 'uptime',
|
||||
title: 'Uptime',
|
||||
@@ -118,14 +121,21 @@ function overview(props) {
|
||||
renderText: tsToTime,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
key: 'net_stat',
|
||||
title: 'Network IO',
|
||||
ellipsis: true,
|
||||
renderText: (_, v) => renderNetworkIO(v),
|
||||
width: 170
|
||||
},
|
||||
{
|
||||
key: 'option',
|
||||
width: 180,
|
||||
title: '操作',
|
||||
dataIndex: 'id',
|
||||
valueType: 'option',
|
||||
ellipsis: true,
|
||||
render: (_, device) => renderOperation(device)
|
||||
render: (_, device) => renderOperation(device),
|
||||
width: 170
|
||||
},
|
||||
];
|
||||
const options = {
|
||||
@@ -165,6 +175,22 @@ function overview(props) {
|
||||
localStorage.setItem(`columnsState`, JSON.stringify(stateMap));
|
||||
}
|
||||
|
||||
function renderNetworkIO(device) {
|
||||
// Make unit starts with Kbps.
|
||||
let sent = device.net_sent * 8 / 1024;
|
||||
let recv = device.net_recv * 8 / 1024;
|
||||
return `${format(sent)} ↑ / ${format(recv)} ↓`;
|
||||
|
||||
function format(size) {
|
||||
if (size <= 1) return '0 Kbps';
|
||||
// Units array is large enough.
|
||||
let k = 1024,
|
||||
i = Math.floor(Math.log(size) / Math.log(k)),
|
||||
units = ['Kbps', 'Mbps', 'Gbps', 'Tbps'];
|
||||
return (size / Math.pow(k, i)).toFixed(1) + ' ' + units[i];
|
||||
}
|
||||
}
|
||||
|
||||
function renderOperation(device) {
|
||||
return [
|
||||
<a key='terminal' onClick={setTerminal.bind(null, device.id)}>终端</a>,
|
||||
|
||||
@@ -22,12 +22,12 @@ function waitTime(time) {
|
||||
});
|
||||
};
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (bytes === 0) return 'Unknown';
|
||||
function formatSize(size) {
|
||||
if (size === 0) return 'Unknown';
|
||||
let k = 1024,
|
||||
i = Math.floor(Math.log(bytes) / Math.log(k)),
|
||||
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
||||
i = Math.floor(Math.log(size) / Math.log(k)),
|
||||
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
return (size / Math.pow(k, i)).toFixed(2) + ' ' + units[i];
|
||||
}
|
||||
|
||||
function tsToTime(ts) {
|
||||
|
||||
Reference in New Issue
Block a user