mirror of
https://github.com/XZB-1248/Spark
synced 2025-10-06 16:46:50 +08:00
optimize: performance of front-end and back-end
optimize: security vulnerability
This commit is contained in:
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@@ -123,16 +123,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd ./releases
|
cd ./releases
|
||||||
sudo apt install zip tar -y
|
sudo apt install zip tar -y
|
||||||
tar -zcvf server_darwin_arm64.tar.gz server_darwin_arm64
|
tar -zcf server_darwin_arm64.tar.gz server_darwin_arm64 ../built
|
||||||
tar -zcvf server_darwin_amd64.tar.gz server_darwin_amd64
|
tar -zcf server_darwin_amd64.tar.gz server_darwin_amd64 ../built
|
||||||
tar -zcvf server_linux_arm.tar.gz server_linux_arm
|
tar -zcf server_linux_arm.tar.gz server_linux_arm ../built
|
||||||
tar -zcvf server_linux_arm64.tar.gz server_linux_arm64
|
tar -zcf server_linux_i386.tar.gz server_linux_i386 ../built
|
||||||
tar -zcvf server_linux_i386.tar.gz server_linux_i386
|
tar -zcf server_linux_arm64.tar.gz server_linux_arm64 ../built
|
||||||
tar -zcvf server_linux_amd64.tar.gz server_linux_amd64
|
tar -zcf server_linux_amd64.tar.gz server_linux_amd64 ../built
|
||||||
zip -r server_windows_arm.zip server_windows_arm.exe
|
zip -r -9 -q server_windows_arm.zip server_windows_arm.exe ../built
|
||||||
zip -r server_windows_arm64.zip server_windows_arm64.exe
|
zip -r -9 -q server_windows_i386.zip server_windows_i386.exe ../built
|
||||||
zip -r server_windows_i386.zip server_windows_i386.exe
|
zip -r -9 -q server_windows_arm64.zip server_windows_arm64.exe ../built
|
||||||
zip -r server_windows_amd64.zip server_windows_amd64.exe
|
zip -r -9 -q server_windows_amd64.zip server_windows_amd64.exe ../built
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
@@ -142,12 +142,12 @@ jobs:
|
|||||||
releases/server_darwin_arm64.tar.gz
|
releases/server_darwin_arm64.tar.gz
|
||||||
releases/server_darwin_amd64.tar.gz
|
releases/server_darwin_amd64.tar.gz
|
||||||
releases/server_linux_arm.tar.gz
|
releases/server_linux_arm.tar.gz
|
||||||
releases/server_linux_arm64.tar.gz
|
|
||||||
releases/server_linux_i386.tar.gz
|
releases/server_linux_i386.tar.gz
|
||||||
|
releases/server_linux_arm64.tar.gz
|
||||||
releases/server_linux_amd64.tar.gz
|
releases/server_linux_amd64.tar.gz
|
||||||
releases/server_windows_arm.zip
|
releases/server_windows_arm.zip
|
||||||
releases/server_windows_arm64.zip
|
|
||||||
releases/server_windows_i386.zip
|
releases/server_windows_i386.zip
|
||||||
|
releases/server_windows_arm64.zip
|
||||||
releases/server_windows_amd64.zip
|
releases/server_windows_amd64.zip
|
||||||
|
|
||||||
- name: Clean up
|
- name: Clean up
|
||||||
|
@@ -5,7 +5,9 @@
|
|||||||
## 通用
|
## 通用
|
||||||
|
|
||||||
所有请求均为`POST`。
|
所有请求均为`POST`。
|
||||||
<br />
|
|
||||||
|
### 鉴权
|
||||||
|
|
||||||
每次请求都必须在Header中带上`Authorization`。
|
每次请求都必须在Header中带上`Authorization`。
|
||||||
<br />
|
<br />
|
||||||
`Authorization`请求头格式:`Basic <token>`(basic auth)。
|
`Authorization`请求头格式:`Basic <token>`(basic auth)。
|
||||||
@@ -18,6 +20,10 @@ Authorization: Basic <base64('username:password')>
|
|||||||
Authorization: Basic WFpCOjEyNDg=
|
Authorization: Basic WFpCOjEyNDg=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
在最初的Basic Authentication之后,服务端会分配一个`Authorization`的Cookie。
|
||||||
|
<br />
|
||||||
|
该Cookie可用于请求的后续鉴权,可以不再附带Authorization头。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 响应
|
## 响应
|
||||||
|
8
API.md
8
API.md
@@ -5,7 +5,9 @@
|
|||||||
## Common
|
## Common
|
||||||
|
|
||||||
Only `POST` requests are allowed.
|
Only `POST` requests are allowed.
|
||||||
<br />
|
|
||||||
|
### Authenticate
|
||||||
|
|
||||||
For every request, you should have `Authorization` on its header.
|
For every request, you should have `Authorization` on its header.
|
||||||
<br />
|
<br />
|
||||||
Authorization header is a string like `Basic <token>`(basic auth).
|
Authorization header is a string like `Basic <token>`(basic auth).
|
||||||
@@ -18,6 +20,10 @@ Example:
|
|||||||
Authorization: Basic WFpCOjEyNDg=
|
Authorization: Basic WFpCOjEyNDg=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
After basic authentication, server will assign you a `Authorization` cookie.
|
||||||
|
<br />
|
||||||
|
You can use this token cookie to authenticate rest of your requests.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Response
|
## Response
|
||||||
|
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,3 +1,13 @@
|
|||||||
|
## v0.0.9
|
||||||
|
|
||||||
|
* Optimize: performance of front-end and back-end.
|
||||||
|
* Optimize: security vulnerability.
|
||||||
|
|
||||||
|
* 优化:前后端性能。
|
||||||
|
* 优化:安全问题。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## v0.0.8
|
## v0.0.8
|
||||||
|
|
||||||
* Add: file upload.
|
* Add: file upload.
|
||||||
|
16
README.ZH.md
16
README.ZH.md
@@ -8,6 +8,18 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
||||
|
||||||
|
|-|-|-|
|
||||||
|
|
||||||
|
|[](https://github.com/XZB-1248/Spark/releases)|[](https://github.com/XZB-1248/Spark/releases/latest)|
|
||||||
|
|-|-|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### **免责声明**
|
### **免责声明**
|
||||||
|
|
||||||
**本项目及其源代码和发行版,旨在用于学习和交流。使用本项目所带来的风险由使用者本人承担。作者和开发者不会对你的错误使用而造成的损害承担任何责任。**
|
**本项目及其源代码和发行版,旨在用于学习和交流。使用本项目所带来的风险由使用者本人承担。作者和开发者不会对你的错误使用而造成的损害承担任何责任。**
|
||||||
@@ -123,12 +135,12 @@ $ statik -m -src="./web/dist" -f -dest="./server/embed" -p web -ns web
|
|||||||
# 在使用类Unix系统时,运行以下命令。
|
# 在使用类Unix系统时,运行以下命令。
|
||||||
$ go mod tidy
|
$ go mod tidy
|
||||||
$ go mod download
|
$ go mod download
|
||||||
$ ./build.client.sh
|
$ ./scripts/build.client.sh
|
||||||
$ statik -m -src="./built" -f -dest="./server/embed" -include=* -p built -ns built
|
$ statik -m -src="./built" -f -dest="./server/embed" -include=* -p built -ns built
|
||||||
|
|
||||||
|
|
||||||
# 最终开始编译服务端。
|
# 最终开始编译服务端。
|
||||||
$ ./build.server.sh
|
$ ./scripts/build.server.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
然后打开`releases`目录,放入上文提到的配置文件,选择对应平台的服务端运行即可。
|
然后打开`releases`目录,放入上文提到的配置文件,选择对应平台的服务端运行即可。
|
||||||
|
23
README.md
23
README.md
@@ -3,15 +3,30 @@
|
|||||||
**Spark** is a free, safe, open-source, web-based, cross-platform and full-featured RAT (Remote Administration Tool)
|
**Spark** is a free, safe, open-source, web-based, cross-platform and full-featured RAT (Remote Administration Tool)
|
||||||
that allow you to control all your devices via browser anywhere.
|
that allow you to control all your devices via browser anywhere.
|
||||||
|
|
||||||
We **won't** collect any data, thus the server will never self-upgrade. Your clients will only communicate with your server forever.
|
We **won't** collect any data, thus the server will never self-upgrade. Your clients will only communicate with your
|
||||||
|
server forever.
|
||||||
|
|
||||||
### [English] [[中文]](./README.ZH.md) [[API Document]](./API.md) [[API文档]](./API.ZH.md)
|
### [English] [[中文]](./README.ZH.md) [[API Document]](./API.md) [[API文档]](./API.ZH.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
||||
|
||||||
|
|-|-|-|
|
||||||
|
|
||||||
|
|[](https://github.com/XZB-1248/Spark/releases)|[](https://github.com/XZB-1248/Spark/releases/latest)|
|
||||||
|
|-|-|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## **Disclaimer**
|
## **Disclaimer**
|
||||||
|
|
||||||
**THIS PROJECT, ITS SOURCE CODE, AND ITS RELEASES SHOULD ONLY BE USED FOR EDUCATIONAL PURPOSES.YOU SHALL USE THIS PROJECT AT YOUR OWN RISK.THE AUTHORS AND DEVELOPERS ARE NOT RESPONSIBLE FOR ANY DAMAGE CAUSED BY YOUR MISUSE OF THIS PROJECT.**
|
**THIS PROJECT, ITS SOURCE CODE, AND ITS RELEASES SHOULD ONLY BE USED FOR EDUCATIONAL PURPOSES.YOU SHALL USE THIS
|
||||||
|
PROJECT AT YOUR OWN RISK.THE AUTHORS AND DEVELOPERS ARE NOT RESPONSIBLE FOR ANY DAMAGE CAUSED BY YOUR MISUSE OF THIS
|
||||||
|
PROJECT.**
|
||||||
|
|
||||||
**YOUR DATA IS PRICELESS. THINK TWICE BEFORE YOU CLICK ANY BUTTON OR ENTER ANY COMMAND.**
|
**YOUR DATA IS PRICELESS. THINK TWICE BEFORE YOU CLICK ANY BUTTON OR ENTER ANY COMMAND.**
|
||||||
|
|
||||||
@@ -123,12 +138,12 @@ $ statik -m -src="./web/dist" -f -dest="./server/embed" -p web -ns web
|
|||||||
# When you're using unix-like OS, you can use this.
|
# When you're using unix-like OS, you can use this.
|
||||||
$ go mod tidy
|
$ go mod tidy
|
||||||
$ go mod download
|
$ go mod download
|
||||||
$ ./build.client.sh
|
$ ./scripts/build.client.sh
|
||||||
$ statik -m -src="./built" -f -dest="./server/embed" -include=* -p built -ns built
|
$ statik -m -src="./built" -f -dest="./server/embed" -include=* -p built -ns built
|
||||||
|
|
||||||
|
|
||||||
# Finally we're compiling the server side.
|
# Finally we're compiling the server side.
|
||||||
$ ./build.server.sh
|
$ ./scripts/build.server.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can find executable files in `releases` directory.
|
Then you can find executable files in `releases` directory.
|
||||||
|
@@ -16,7 +16,7 @@ type Cfg struct {
|
|||||||
|
|
||||||
// Localhost for my development only.
|
// Localhost for my development only.
|
||||||
// Shall be commented out when development is done.
|
// Shall be commented out when development is done.
|
||||||
//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"
|
//var CfgBuffer = "\x00\xcd\xc6\x68\x5d\xf5\x83\x53\x1c\x49\xa2\x35\x7b\x5b\xaf\xf2\x9e\x6d\x74\x00\x95\x23\x73\x00\x77\xa0\xe1\x46\x64\xd2\x33\x2b\x04\xb2\xca\x70\xda\x4b\xed\xec\x43\x6b\xeb\x6e\x10\x53\x6e\x62\x13\x3c\xb1\x0a\xdd\xc0\x48\x2d\x77\xfa\x4a\x9b\x26\xb5\x1b\x50\x62\x05\xcc\xc9\x3b\x22\xf5\x19\x5b\xac\x41\x74\xc9\x9e\x02\x9f\xe8\x75\xce\x3a\xe0\x50\x67\x0f\x81\x01\xca\x47\x0d\xb2\x09\x8b\x74\x6c\xfd\xc5\x73\xf9\x2a\xf0\x13\x52\xb7\x79\xff\xeb\xab\xcd\x9f\xe8\xb7\xae\xff\xa9\x50\xb2\x90\x11\x35\x4d\x94\x6e\x67\x55\x37\x66\x58\x21\xc0\x0d\xab\x3b\x6f\xc4\x00\x56\xd6\x06\xa0\x7e\x73\xdf\x46\x76\xe0\xb3\x89\x0d\xa2\x33\x07\x39\x81\x2b\x59\x30\x24\xc7\x4f\xe9\xb9\xf6\x3c\xb6\x24\xc5\x44\xde\xe6\x66\x66\x92\x49\xe1\x38\x50\xff\xb5\xf3\x20\xb9\x15\x60\x4a\xdf\xba\xd5\xae\x85\x7e\x3f\x8a\xf0\xb8\xf5\x23\x39\xf0\x46\x11\x64\x42\x04\x8c\xf0\x8a\x5e\xc7\x43\xd2\x0c\x89\xd1\xc4\x14\x26\xb1\x67\x64\x28\x77\xf4\xc8\xf3\x51\x69\xba\xf2\xca\xfa\x2f\x11\xe0\x8d\x6c\x4e\x8c\xb7\x28\xf5\x2a\x67\xe3\x8f\xf0\x7f\x79\xc5\xa5\x1a\xb5\xa1\x22\xe9\x55\x61\xdd\xce\x39\x13\x4b\xdd\x19\xf1\x5c\x86\x9b\x16\x89\x45\xba\x16\x68\xfc\x88\x4b\xd5\x13\xa4\x7e\x26\xce\x35\x2d\x42\x4d\x21\xf1\xc3\x6d\xf5\x64\x16\xc9\x05\xed\x9b\x6c\xbf\x26\xe3\xad\x40\x1d\xc6\x64\x03\xb9\xcb\xca\x3c\x62\x5d\x07\x6b\x07\x8b\xa9\x86\x60\x27\x28\xe7\xa3\xc2\x8d\x6f\xc0\x3d\x8e\x14\xa6\xcc\xe0\x50\x51\x22\x20\x6b\x16\x10\xe9\xe0\x4a\xd2\x4e\x77\xc8\xd1\xf7\x60\x4c\xed\xca\x3f\x1e\x13\x0a\x2e\x84\x15\xd3\xf6\x3e\x13\x4e\x68\xaf\xfd\x7a\xd7\x5b\xaa\x5b\x28\x7c\x3f\xb3\xd0\xd0"
|
||||||
|
|
||||||
// None
|
// 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"
|
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"
|
||||||
|
@@ -27,6 +27,7 @@ var (
|
|||||||
errNoSecretHeader = errors.New(`can not find secret header`)
|
errNoSecretHeader = errors.New(`can not find secret header`)
|
||||||
)
|
)
|
||||||
var handlers = map[string]func(pack modules.Packet, wsConn *common.Conn){
|
var handlers = map[string]func(pack modules.Packet, wsConn *common.Conn){
|
||||||
|
`ping`: ping,
|
||||||
`offline`: offline,
|
`offline`: offline,
|
||||||
`lock`: lock,
|
`lock`: lock,
|
||||||
`logoff`: logoff,
|
`logoff`: logoff,
|
||||||
@@ -69,8 +70,6 @@ func Start() {
|
|||||||
|
|
||||||
checkUpdate(common.WSConn)
|
checkUpdate(common.WSConn)
|
||||||
|
|
||||||
go heartbeat(common.WSConn)
|
|
||||||
|
|
||||||
err = handleWS(common.WSConn)
|
err = handleWS(common.WSConn)
|
||||||
if err != nil && !stop {
|
if err != nil && !stop {
|
||||||
golog.Error(`Execution error: `, err)
|
golog.Error(`Execution error: `, err)
|
||||||
@@ -212,25 +211,3 @@ func handleAct(pack modules.Packet, wsConn *common.Conn) {
|
|||||||
act(pack, wsConn)
|
act(pack, wsConn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func heartbeat(wsConn *common.Conn) error {
|
|
||||||
t := 0
|
|
||||||
for range time.NewTicker(2 * time.Second).C {
|
|
||||||
t++
|
|
||||||
// GetPartialInfo always costs more than 1 second.
|
|
||||||
// So it is actually get disk info every 20*3 seconds (1 minute).
|
|
||||||
device, err := GetPartialInfo(t >= 20)
|
|
||||||
if err != nil {
|
|
||||||
golog.Error(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if t >= 20 {
|
|
||||||
t = 0
|
|
||||||
}
|
|
||||||
err = common.SendPack(modules.CommonPack{Act: `setDevice`, Data: *device}, wsConn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@@ -245,7 +245,7 @@ func GetDevice() (*modules.Device, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPartialInfo(getDisk bool) (*modules.Device, error) {
|
func GetPartialInfo() (*modules.Device, error) {
|
||||||
cpuInfo, err := GetCPUInfo()
|
cpuInfo, err := GetCPUInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cpuInfo = modules.CPU{
|
cpuInfo = modules.CPU{
|
||||||
|
@@ -8,11 +8,22 @@ import (
|
|||||||
Screenshot "Spark/client/service/screenshot"
|
Screenshot "Spark/client/service/screenshot"
|
||||||
"Spark/client/service/terminal"
|
"Spark/client/service/terminal"
|
||||||
"Spark/modules"
|
"Spark/modules"
|
||||||
|
"github.com/kataras/golog"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ping(pack modules.Packet, wsConn *common.Conn) {
|
||||||
|
common.SendCb(modules.Packet{Code: 0}, pack, wsConn)
|
||||||
|
device, err := GetPartialInfo()
|
||||||
|
if err != nil {
|
||||||
|
golog.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SendPack(modules.CommonPack{Act: `setDevice`, Data: *device}, wsConn)
|
||||||
|
}
|
||||||
|
|
||||||
func offline(pack modules.Packet, wsConn *common.Conn) {
|
func offline(pack modules.Packet, wsConn *common.Conn) {
|
||||||
common.SendCb(modules.Packet{Code: 0}, pack, wsConn)
|
common.SendCb(modules.Packet{Code: 0}, pack, wsConn)
|
||||||
stop = true
|
stop = true
|
||||||
|
@@ -3,13 +3,13 @@ package file
|
|||||||
import (
|
import (
|
||||||
"Spark/client/config"
|
"Spark/client/config"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/imroc/req/v3"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"unicode/utf8"
|
||||||
"github.com/imroc/req/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
@@ -41,6 +41,53 @@ func listFiles(path string) ([]File, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReadText(path, bridge string) error {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
uploadReq := req.R()
|
||||||
|
stat, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
size := stat.Size()
|
||||||
|
// Check if size larger than 2MB.
|
||||||
|
if size > 2<<20 {
|
||||||
|
return errors.New(`${i18n|fileTooLarge}`)
|
||||||
|
}
|
||||||
|
headers := map[string]string{
|
||||||
|
`FileName`: stat.Name(),
|
||||||
|
`FileSize`: strconv.FormatInt(size, 10),
|
||||||
|
}
|
||||||
|
uploadReq.RawRequest.ContentLength = size
|
||||||
|
|
||||||
|
// Check file if is a text file.
|
||||||
|
// UTF-8 and GBK are only supported yet.
|
||||||
|
buf := make([]byte, size)
|
||||||
|
_, err = file.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if utf8.Valid(buf) {
|
||||||
|
headers[`FileEncoding`] = `utf-8`
|
||||||
|
} else if gbkValidate(buf) {
|
||||||
|
headers[`FileEncoding`] = `gbk`
|
||||||
|
} else {
|
||||||
|
return errors.New(`${i18n|fileEncodingUnsupported}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Seek(0, 0)
|
||||||
|
url := config.GetBaseURL(false) + `/api/bridge/push`
|
||||||
|
_, err = uploadReq.
|
||||||
|
SetBody(file).
|
||||||
|
SetHeaders(headers).
|
||||||
|
SetQueryParam(`bridge`, bridge).
|
||||||
|
Send(`PUT`, url)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// FetchFile saves file from bridge to local.
|
// FetchFile saves file from bridge to local.
|
||||||
// Save body as temp file and when done, rename it to file.
|
// Save body as temp file and when done, rename it to file.
|
||||||
func FetchFile(dir, file, bridge string) error {
|
func FetchFile(dir, file, bridge string) error {
|
||||||
@@ -162,6 +209,26 @@ func UploadFile(path, bridge string, start, end int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gbkValidate(b []byte) bool {
|
||||||
|
length := len(b)
|
||||||
|
var i int = 0
|
||||||
|
for i < length {
|
||||||
|
if b[i] <= 0x7f {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
if i+1 < length {
|
||||||
|
if b[i] >= 0x81 && b[i] <= 0xfe && b[i+1] >= 0x40 && b[i+1] <= 0xfe && b[i+1] != 0xf7 {
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func getTempFileName(dir, file string) string {
|
func getTempFileName(dir, file string) string {
|
||||||
exists := true
|
exists := true
|
||||||
tempFile := ``
|
tempFile := ``
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
package screenshot
|
package screenshot
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
func GetScreenshot(bridge string) error {
|
func GetScreenshot(bridge string) error {
|
||||||
return utils.ErrUnsupported
|
return errors.New(`${i18n|operationNotSupported}`)
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ go 1.17
|
|||||||
require (
|
require (
|
||||||
github.com/creack/pty v1.1.18
|
github.com/creack/pty v1.1.18
|
||||||
github.com/denisbrodbeck/machineid v1.0.1
|
github.com/denisbrodbeck/machineid v1.0.1
|
||||||
|
github.com/gin-contrib/pprof v1.3.0
|
||||||
github.com/gin-gonic/gin v1.7.7
|
github.com/gin-gonic/gin v1.7.7
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/imroc/req/v3 v3.8.2
|
github.com/imroc/req/v3 v3.8.2
|
||||||
|
4
go.sum
4
go.sum
@@ -7,8 +7,11 @@ github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMS
|
|||||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
|
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
|
||||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||||
|
github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0=
|
||||||
|
github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
@@ -19,6 +22,7 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c
|
|||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||||
|
@@ -7,10 +7,10 @@ set GOOS=linux
|
|||||||
|
|
||||||
set GOARCH=arm
|
set GOARCH=arm
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -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 -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_arm64 Spark/client
|
|
||||||
set GOARCH=386
|
set GOARCH=386
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -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=arm64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_arm64 Spark/client
|
||||||
set GOARCH=amd64
|
set GOARCH=amd64
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_amd64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_amd64 Spark/client
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ set GOOS=windows
|
|||||||
|
|
||||||
set GOARCH=arm
|
set GOARCH=arm
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -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 -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_arm64 Spark/client
|
|
||||||
set GOARCH=386
|
set GOARCH=386
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -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=arm64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_arm64 Spark/client
|
||||||
set GOARCH=amd64
|
set GOARCH=amd64
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_amd64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_amd64 Spark/client
|
||||||
|
|
||||||
@@ -37,16 +37,16 @@ go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/wi
|
|||||||
@REM set CXX=armv7a-linux-androideabi21-clang++
|
@REM set CXX=armv7a-linux-androideabi21-clang++
|
||||||
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_arm 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 -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_arm64 Spark/client
|
|
||||||
|
|
||||||
@REM set GOARCH=386
|
@REM set GOARCH=386
|
||||||
@REM set CC=i686-linux-android21-clang
|
@REM set CC=i686-linux-android21-clang
|
||||||
@REM set CXX=i686-linux-android21-clang++
|
@REM set CXX=i686-linux-android21-clang++
|
||||||
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_i386 Spark/client
|
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_i386 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 -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_arm64 Spark/client
|
||||||
|
|
||||||
@REM set GOARCH=amd64
|
@REM set GOARCH=amd64
|
||||||
@REM set CC=x86_64-linux-android21-clang
|
@REM set CC=x86_64-linux-android21-clang
|
||||||
@REM set CXX=x86_64-linux-android21-clang++
|
@REM set CXX=x86_64-linux-android21-clang++
|
||||||
|
@@ -7,10 +7,10 @@ export GOOS=linux
|
|||||||
|
|
||||||
export GOARCH=arm
|
export GOARCH=arm
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -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 -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_arm64 Spark/client
|
|
||||||
export GOARCH=386
|
export GOARCH=386
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -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=arm64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_arm64 Spark/client
|
||||||
export GOARCH=amd64
|
export GOARCH=amd64
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_amd64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_amd64 Spark/client
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ export GOOS=windows
|
|||||||
|
|
||||||
export GOARCH=arm
|
export GOARCH=arm
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -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 -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_arm64 Spark/client
|
|
||||||
export GOARCH=386
|
export GOARCH=386
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -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=arm64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_arm64 Spark/client
|
||||||
export GOARCH=amd64
|
export GOARCH=amd64
|
||||||
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_amd64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_amd64 Spark/client
|
||||||
|
|
||||||
@@ -37,16 +37,16 @@ go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/win
|
|||||||
# export CXX=armv7a-linux-androideabi21-clang++
|
# export CXX=armv7a-linux-androideabi21-clang++
|
||||||
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_arm 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 -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_arm64 Spark/client
|
|
||||||
|
|
||||||
# export GOARCH=386
|
# export GOARCH=386
|
||||||
# export CC=i686-linux-android21-clang
|
# export CC=i686-linux-android21-clang
|
||||||
# export CXX=i686-linux-android21-clang++
|
# export CXX=i686-linux-android21-clang++
|
||||||
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_i386 Spark/client
|
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_i386 Spark/client
|
||||||
|
|
||||||
|
# export GOARCH=arm64
|
||||||
|
# export CC=aarch64-linux-android21-clang
|
||||||
|
# export CXX=aarch64-linux-android21-clang++
|
||||||
|
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_arm64 Spark/client
|
||||||
|
|
||||||
# export GOARCH=amd64
|
# export GOARCH=amd64
|
||||||
# export CC=x86_64-linux-android21-clang
|
# export CC=x86_64-linux-android21-clang
|
||||||
# export CXX=x86_64-linux-android21-clang++
|
# export CXX=x86_64-linux-android21-clang++
|
||||||
|
@@ -1,38 +1,37 @@
|
|||||||
cd ..
|
set GO111MODULE=auto
|
||||||
mkdir .\releases
|
|
||||||
for /F %%i in ('git rev-parse HEAD') do ( set COMMIT=%%i)
|
for /F %%i in ('git rev-parse HEAD') do ( set COMMIT=%%i)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set GOOS=linux
|
|
||||||
|
|
||||||
set GOARCH=arm
|
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_linux_arm Spark/Server
|
|
||||||
set GOARCH=arm64
|
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_linux_arm64 Spark/Server
|
|
||||||
set GOARCH=386
|
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_linux_i386 Spark/Server
|
|
||||||
set GOARCH=amd64
|
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_linux_amd64 Spark/Server
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set GOOS=windows
|
|
||||||
|
|
||||||
set GOARCH=arm
|
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_windows_arm.exe Spark/Server
|
|
||||||
set GOARCH=arm64
|
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_windows_arm64.exe Spark/Server
|
|
||||||
set GOARCH=386
|
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_windows_i386.exe Spark/Server
|
|
||||||
set GOARCH=amd64
|
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_windows_amd64.exe Spark/Server
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set GOOS=darwin
|
set GOOS=darwin
|
||||||
|
|
||||||
set GOARCH=arm64
|
set GOARCH=arm64
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_darwin_arm64 Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_darwin_arm64 Spark/server
|
||||||
set GOARCH=amd64
|
set GOARCH=amd64
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_darwin_amd64 Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_darwin_amd64 Spark/server
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set GOOS=linux
|
||||||
|
|
||||||
|
set GOARCH=arm
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_linux_arm Spark/Server
|
||||||
|
set GOARCH=386
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_linux_i386 Spark/Server
|
||||||
|
set GOARCH=arm64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_linux_arm64 Spark/Server
|
||||||
|
set GOARCH=amd64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_linux_amd64 Spark/Server
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set GOOS=windows
|
||||||
|
|
||||||
|
set GOARCH=arm
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_windows_arm.exe Spark/Server
|
||||||
|
set GOARCH=386
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_windows_i386.exe Spark/Server
|
||||||
|
set GOARCH=arm64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_windows_arm64.exe Spark/Server
|
||||||
|
set GOARCH=amd64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=%COMMIT%'" -tags=jsoniter -o ./releases/server_windows_amd64.exe Spark/Server
|
||||||
|
@@ -3,35 +3,35 @@ export COMMIT=`git rev-parse HEAD`
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
export GOOS=linux
|
|
||||||
|
|
||||||
export GOARCH=arm
|
|
||||||
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 -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_arm64 Spark/server
|
|
||||||
export GOARCH=386
|
|
||||||
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 -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 -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_arm.exe Spark/server
|
|
||||||
export GOARCH=arm64
|
|
||||||
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 -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_i386.exe Spark/server
|
|
||||||
export GOARCH=amd64
|
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_amd64.exe Spark/server
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export GOOS=darwin
|
export GOOS=darwin
|
||||||
|
|
||||||
export GOARCH=arm64
|
export GOARCH=arm64
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_darwin_arm64 Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_darwin_arm64 Spark/server
|
||||||
export GOARCH=amd64
|
export GOARCH=amd64
|
||||||
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_darwin_amd64 Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_darwin_amd64 Spark/server
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export GOOS=linux
|
||||||
|
|
||||||
|
export GOARCH=arm
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_arm Spark/server
|
||||||
|
export GOARCH=386
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_i386 Spark/server
|
||||||
|
export GOARCH=arm64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_arm64 Spark/server
|
||||||
|
export GOARCH=amd64
|
||||||
|
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 -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_arm.exe Spark/server
|
||||||
|
export GOARCH=386
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_i386.exe Spark/server
|
||||||
|
export GOARCH=arm64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_arm64.exe Spark/server
|
||||||
|
export GOARCH=amd64
|
||||||
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_amd64.exe Spark/server
|
||||||
|
@@ -11,14 +11,11 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var Melody = melody.New()
|
var Melody = melody.New()
|
||||||
var Devices = cmap.New()
|
var Devices = cmap.New()
|
||||||
var BuiltFS http.FileSystem
|
|
||||||
|
|
||||||
func SendPackByUUID(pack modules.Packet, uuid string) bool {
|
func SendPackByUUID(pack modules.Packet, uuid string) bool {
|
||||||
session, ok := Melody.GetSessionByUUID(uuid)
|
session, ok := Melody.GetSessionByUUID(uuid)
|
||||||
@@ -70,54 +67,6 @@ func Decrypt(data []byte, session *melody.Session) ([]byte, bool) {
|
|||||||
return dec, true
|
return dec, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func HealthCheckWS(maxIdleSeconds int64, container *melody.Melody) {
|
|
||||||
go func() {
|
|
||||||
// ping client and update latency every 3 seconds
|
|
||||||
ping := func(uuid string, s *melody.Session) {
|
|
||||||
t := time.Now().UnixMilli()
|
|
||||||
trigger := utils.GetStrUUID()
|
|
||||||
SendPack(modules.Packet{Act: `ping`, Event: trigger}, s)
|
|
||||||
AddEventOnce(func(packet modules.Packet, session *melody.Session) {
|
|
||||||
val, ok := Devices.Get(uuid)
|
|
||||||
if ok {
|
|
||||||
deviceInfo := val.(*modules.Device)
|
|
||||||
deviceInfo.Latency = uint(time.Now().UnixMilli()-t) / 2
|
|
||||||
}
|
|
||||||
}, uuid, trigger, 3*time.Second)
|
|
||||||
}
|
|
||||||
for range time.NewTicker(3 * time.Second).C {
|
|
||||||
container.IterSessions(func(uuid string, s *melody.Session) bool {
|
|
||||||
go ping(uuid, s)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for now := range time.NewTicker(30 * time.Second).C {
|
|
||||||
timestamp := now.Unix()
|
|
||||||
// stores sessions to be disconnected
|
|
||||||
queue := make([]*melody.Session, 0)
|
|
||||||
container.IterSessions(func(uuid string, s *melody.Session) bool {
|
|
||||||
val, ok := s.Get(`LastPack`)
|
|
||||||
if !ok {
|
|
||||||
queue = append(queue, s)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
lastPack, ok := val.(int64)
|
|
||||||
if !ok {
|
|
||||||
queue = append(queue, s)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if timestamp-lastPack > maxIdleSeconds {
|
|
||||||
queue = append(queue, s)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
for i := 0; i < len(queue); i++ {
|
|
||||||
queue[i].Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRemoteAddr(ctx *gin.Context) string {
|
func GetRemoteAddr(ctx *gin.Context) string {
|
||||||
if remote, ok := ctx.RemoteIP(); ok {
|
if remote, ok := ctx.RemoteIP(); ok {
|
||||||
if remote.IsLoopback() {
|
if remote.IsLoopback() {
|
||||||
|
@@ -48,9 +48,9 @@ func AddEventOnce(fn EventCallback, connUUID, trigger string, timeout time.Durat
|
|||||||
remove: make(chan bool),
|
remove: make(chan bool),
|
||||||
}
|
}
|
||||||
events.Set(trigger, ev)
|
events.Set(trigger, ev)
|
||||||
defer events.Remove(trigger)
|
|
||||||
defer close(ev.finish)
|
|
||||||
defer close(ev.remove)
|
defer close(ev.remove)
|
||||||
|
defer close(ev.finish)
|
||||||
|
defer events.Remove(trigger)
|
||||||
select {
|
select {
|
||||||
case ok := <-ev.finish:
|
case ok := <-ev.finish:
|
||||||
return ok
|
return ok
|
||||||
@@ -79,11 +79,16 @@ func RemoveEvent(trigger string, ok ...bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
events.Remove(trigger)
|
events.Remove(trigger)
|
||||||
if ev := v.(*event); ev.remove != nil {
|
ev := v.(*event)
|
||||||
|
if ev.remove != nil {
|
||||||
if len(ok) > 0 {
|
if len(ok) > 0 {
|
||||||
ev.remove <- ok[0]
|
ev.remove <- ok[0]
|
||||||
|
} else {
|
||||||
|
ev.remove <- false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
v = nil
|
||||||
|
ev = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasEvent returns if the event exists.
|
// HasEvent returns if the event exists.
|
||||||
|
14
server/common/time.go
Normal file
14
server/common/time.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
var Unix int64 = time.Now().Unix()
|
||||||
|
|
||||||
|
// To prevent call time.Now().Unix() too often.
|
||||||
|
func init() {
|
||||||
|
go func() {
|
||||||
|
for now := range time.NewTicker(time.Second).C {
|
||||||
|
Unix = now.Unix()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
@@ -1,6 +1,10 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
type Cfg struct {
|
type Cfg struct {
|
||||||
|
Debug struct {
|
||||||
|
Pprof bool `json:"pprof"`
|
||||||
|
Gin bool `json:"gin"`
|
||||||
|
} `json:"debug,omitempty"`
|
||||||
Listen string `json:"listen"`
|
Listen string `json:"listen"`
|
||||||
Salt string `json:"salt"`
|
Salt string `json:"salt"`
|
||||||
Auth map[string]string `json:"auth"`
|
Auth map[string]string `json:"auth"`
|
||||||
@@ -8,6 +12,7 @@ type Cfg struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var Config Cfg
|
var Config Cfg
|
||||||
|
var BuiltPath = `./built/%v_%v`
|
||||||
|
|
||||||
// COMMIT means this commit hash, for auto upgrade.
|
// COMMIT means this commit hash, for auto upgrade.
|
||||||
var COMMIT = ``
|
var COMMIT = ``
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
// Code generated by statik. DO NOT EDIT.
|
|
||||||
|
|
||||||
package built
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rakyll/statik/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
const Built = "built" // static asset namespace
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
data := "PK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
||||||
fs.RegisterWithNamespace("built", data)
|
|
||||||
}
|
|
||||||
|
|
@@ -2,6 +2,7 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"Spark/modules"
|
"Spark/modules"
|
||||||
|
"Spark/server/common"
|
||||||
"Spark/utils/cmap"
|
"Spark/utils/cmap"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/kataras/golog"
|
"github.com/kataras/golog"
|
||||||
@@ -31,17 +32,12 @@ var bridges = cmap.New()
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
go func() {
|
go func() {
|
||||||
for now := range time.NewTicker(10 * time.Second).C {
|
for now := range time.NewTicker(15 * time.Second).C {
|
||||||
var queue []*bridge
|
var queue []string
|
||||||
|
timestamp := now.Unix()
|
||||||
bridges.IterCb(func(k string, v interface{}) bool {
|
bridges.IterCb(func(k string, v interface{}) bool {
|
||||||
b := v.(*bridge)
|
b := v.(*bridge)
|
||||||
if b.creation < now.Unix()-60 && !b.using {
|
if timestamp-b.creation > 60 && !b.using {
|
||||||
queue = append(queue, b)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
for _, b := range queue {
|
|
||||||
bridges.Remove(b.uuid)
|
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
if b.src != nil && b.src.Request.Body != nil {
|
if b.src != nil && b.src.Request.Body != nil {
|
||||||
b.src.Request.Body.Close()
|
b.src.Request.Body.Close()
|
||||||
@@ -50,7 +46,11 @@ func init() {
|
|||||||
b.dest = nil
|
b.dest = nil
|
||||||
b.lock.Unlock()
|
b.lock.Unlock()
|
||||||
b = nil
|
b = nil
|
||||||
|
queue = append(queue, b.uuid)
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
bridges.Remove(queue...)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -61,12 +61,12 @@ func checkBridge(ctx *gin.Context) *bridge {
|
|||||||
}
|
}
|
||||||
if err := ctx.ShouldBind(&form); err != nil {
|
if err := ctx.ShouldBind(&form); err != nil {
|
||||||
golog.Error(err)
|
golog.Error(err)
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
val, ok := bridges.Get(form.Bridge)
|
val, ok := bridges.Get(form.Bridge)
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidBridgeID}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidBridgeID}`})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return val.(*bridge)
|
return val.(*bridge)
|
||||||
@@ -80,7 +80,7 @@ func bridgePush(ctx *gin.Context) {
|
|||||||
bridge.lock.Lock()
|
bridge.lock.Lock()
|
||||||
if bridge.using || (bridge.src != nil && bridge.dest != nil) {
|
if bridge.using || (bridge.src != nil && bridge.dest != nil) {
|
||||||
bridge.lock.Unlock()
|
bridge.lock.Unlock()
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: 1, Msg: `${i18n|bridgeAlreadyInUse}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: 1, Msg: `${i18n|bridgeAlreadyInUse}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bridge.src = ctx
|
bridge.src = ctx
|
||||||
@@ -108,7 +108,7 @@ func bridgePull(ctx *gin.Context) {
|
|||||||
bridge.lock.Lock()
|
bridge.lock.Lock()
|
||||||
if bridge.using || (bridge.src != nil && bridge.dest != nil) {
|
if bridge.using || (bridge.src != nil && bridge.dest != nil) {
|
||||||
bridge.lock.Unlock()
|
bridge.lock.Unlock()
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: 1, Msg: `${i18n|bridgeAlreadyInUse}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: 1, Msg: `${i18n|bridgeAlreadyInUse}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bridge.dest = ctx
|
bridge.dest = ctx
|
||||||
@@ -130,7 +130,7 @@ func bridgePull(ctx *gin.Context) {
|
|||||||
|
|
||||||
func addBridge(ext interface{}, uuid string) *bridge {
|
func addBridge(ext interface{}, uuid string) *bridge {
|
||||||
bridge := &bridge{
|
bridge := &bridge{
|
||||||
creation: time.Now().Unix(),
|
creation: common.Unix,
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
using: false,
|
using: false,
|
||||||
lock: &sync.Mutex{},
|
lock: &sync.Mutex{},
|
||||||
@@ -142,7 +142,7 @@ func addBridge(ext interface{}, uuid string) *bridge {
|
|||||||
|
|
||||||
func addBridgeWithSrc(ext interface{}, uuid string, src *gin.Context) *bridge {
|
func addBridgeWithSrc(ext interface{}, uuid string, src *gin.Context) *bridge {
|
||||||
bridge := &bridge{
|
bridge := &bridge{
|
||||||
creation: time.Now().Unix(),
|
creation: common.Unix,
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
using: false,
|
using: false,
|
||||||
lock: &sync.Mutex{},
|
lock: &sync.Mutex{},
|
||||||
@@ -155,7 +155,7 @@ func addBridgeWithSrc(ext interface{}, uuid string, src *gin.Context) *bridge {
|
|||||||
|
|
||||||
func addBridgeWithDest(ext interface{}, uuid string, dest *gin.Context) *bridge {
|
func addBridgeWithDest(ext interface{}, uuid string, dest *gin.Context) *bridge {
|
||||||
bridge := &bridge{
|
bridge := &bridge{
|
||||||
creation: time.Now().Unix(),
|
creation: common.Unix,
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
using: false,
|
using: false,
|
||||||
lock: &sync.Mutex{},
|
lock: &sync.Mutex{},
|
||||||
|
@@ -29,13 +29,13 @@ func removeDeviceFile(ctx *gin.Context) {
|
|||||||
common.SendPackByUUID(modules.Packet{Code: 0, Act: `removeFile`, Data: gin.H{`file`: form.File}, Event: trigger}, target)
|
common.SendPackByUUID(modules.Packet{Code: 0, Act: `removeFile`, Data: gin.H{`file`: form.File}, Event: trigger}, target)
|
||||||
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
||||||
if p.Code != 0 {
|
if p.Code != 0 {
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||||
} else {
|
} else {
|
||||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||||
}
|
}
|
||||||
}, target, trigger, 5*time.Second)
|
}, target, trigger, 5*time.Second)
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,13 +52,13 @@ func listDeviceFiles(ctx *gin.Context) {
|
|||||||
common.SendPackByUUID(modules.Packet{Act: `listFiles`, Data: gin.H{`path`: form.Path}, Event: trigger}, target)
|
common.SendPackByUUID(modules.Packet{Act: `listFiles`, Data: gin.H{`path`: form.Path}, Event: trigger}, target)
|
||||||
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
||||||
if p.Code != 0 {
|
if p.Code != 0 {
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||||
} else {
|
} else {
|
||||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0, Data: p.Data})
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0, Data: p.Data})
|
||||||
}
|
}
|
||||||
}, target, trigger, 5*time.Second)
|
}, target, trigger, 5*time.Second)
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +67,7 @@ func listDeviceFiles(ctx *gin.Context) {
|
|||||||
func getDeviceFile(ctx *gin.Context) {
|
func getDeviceFile(ctx *gin.Context) {
|
||||||
var form struct {
|
var form struct {
|
||||||
File string `json:"file" yaml:"file" form:"file" binding:"required"`
|
File string `json:"file" yaml:"file" form:"file" binding:"required"`
|
||||||
|
Preview bool `json:"preview" yaml:"preview" form:"preview"`
|
||||||
}
|
}
|
||||||
target, ok := checkForm(ctx, &form)
|
target, ok := checkForm(ctx, &form)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -82,29 +83,29 @@ func getDeviceFile(ctx *gin.Context) {
|
|||||||
rangeHeader := ctx.GetHeader(`Range`)
|
rangeHeader := ctx.GetHeader(`Range`)
|
||||||
if len(rangeHeader) > 6 {
|
if len(rangeHeader) > 6 {
|
||||||
if rangeHeader[:6] != `bytes=` {
|
if rangeHeader[:6] != `bytes=` {
|
||||||
ctx.Status(http.StatusRequestedRangeNotSatisfiable)
|
ctx.AbortWithStatus(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rangeHeader = strings.TrimSpace(rangeHeader[6:])
|
rangeHeader = strings.TrimSpace(rangeHeader[6:])
|
||||||
rangesList := strings.Split(rangeHeader, `,`)
|
rangesList := strings.Split(rangeHeader, `,`)
|
||||||
if len(rangesList) > 1 {
|
if len(rangesList) > 1 {
|
||||||
ctx.Status(http.StatusRequestedRangeNotSatisfiable)
|
ctx.AbortWithStatus(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r := strings.Split(rangesList[0], `-`)
|
r := strings.Split(rangesList[0], `-`)
|
||||||
rangeStart, err = strconv.ParseInt(r[0], 10, 64)
|
rangeStart, err = strconv.ParseInt(r[0], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Status(http.StatusRequestedRangeNotSatisfiable)
|
ctx.AbortWithStatus(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(r[1]) > 0 {
|
if len(r[1]) > 0 {
|
||||||
rangeEnd, err = strconv.ParseInt(r[1], 10, 64)
|
rangeEnd, err = strconv.ParseInt(r[1], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Status(http.StatusRequestedRangeNotSatisfiable)
|
ctx.AbortWithStatus(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if rangeEnd < rangeStart {
|
if rangeEnd < rangeStart {
|
||||||
ctx.Status(http.StatusRequestedRangeNotSatisfiable)
|
ctx.AbortWithStatus(http.StatusRequestedRangeNotSatisfiable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
command[`end`] = rangeEnd
|
command[`end`] = rangeEnd
|
||||||
@@ -121,16 +122,22 @@ func getDeviceFile(ctx *gin.Context) {
|
|||||||
called = true
|
called = true
|
||||||
removeBridge(bridgeID)
|
removeBridge(bridgeID)
|
||||||
common.RemoveEvent(trigger)
|
common.RemoveEvent(trigger)
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||||
}, target, trigger)
|
}, target, trigger)
|
||||||
instance := addBridgeWithDest(nil, bridgeID, ctx)
|
instance := addBridgeWithDest(nil, bridgeID, ctx)
|
||||||
instance.OnPush = func(bridge *bridge) {
|
instance.OnPush = func(bridge *bridge) {
|
||||||
called = true
|
called = true
|
||||||
common.RemoveEvent(trigger)
|
common.RemoveEvent(trigger)
|
||||||
src := bridge.src
|
src := bridge.src
|
||||||
|
for k, v := range src.Request.Header {
|
||||||
|
if strings.HasPrefix(k, `File`) {
|
||||||
|
ctx.Header(k, v[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
if src.Request.ContentLength > 0 {
|
if src.Request.ContentLength > 0 {
|
||||||
ctx.Header(`Content-Length`, strconv.FormatInt(src.Request.ContentLength, 10))
|
ctx.Header(`Content-Length`, strconv.FormatInt(src.Request.ContentLength, 10))
|
||||||
}
|
}
|
||||||
|
if !form.Preview {
|
||||||
ctx.Header(`Accept-Ranges`, `bytes`)
|
ctx.Header(`Accept-Ranges`, `bytes`)
|
||||||
ctx.Header(`Content-Transfer-Encoding`, `binary`)
|
ctx.Header(`Content-Transfer-Encoding`, `binary`)
|
||||||
ctx.Header(`Content-Type`, `application/octet-stream`)
|
ctx.Header(`Content-Type`, `application/octet-stream`)
|
||||||
@@ -140,6 +147,7 @@ func getDeviceFile(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
filename = url.PathEscape(filename)
|
filename = url.PathEscape(filename)
|
||||||
ctx.Header(`Content-Disposition`, `attachment; filename* = UTF-8''`+filename+`;`)
|
ctx.Header(`Content-Disposition`, `attachment; filename* = UTF-8''`+filename+`;`)
|
||||||
|
}
|
||||||
|
|
||||||
if partial {
|
if partial {
|
||||||
if rangeEnd == 0 {
|
if rangeEnd == 0 {
|
||||||
@@ -164,11 +172,12 @@ func getDeviceFile(ctx *gin.Context) {
|
|||||||
if !called {
|
if !called {
|
||||||
removeBridge(bridgeID)
|
removeBridge(bridgeID)
|
||||||
common.RemoveEvent(trigger)
|
common.RemoveEvent(trigger)
|
||||||
ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||||
} else {
|
} else {
|
||||||
<-wait
|
<-wait
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
close(wait)
|
||||||
}
|
}
|
||||||
|
|
||||||
// uploadToDevice handles file from browser
|
// uploadToDevice handles file from browser
|
||||||
@@ -191,7 +200,7 @@ func uploadToDevice(ctx *gin.Context) {
|
|||||||
called = true
|
called = true
|
||||||
removeBridge(bridgeID)
|
removeBridge(bridgeID)
|
||||||
common.RemoveEvent(trigger)
|
common.RemoveEvent(trigger)
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||||
}, target, trigger)
|
}, target, trigger)
|
||||||
instance := addBridgeWithSrc(nil, bridgeID, ctx)
|
instance := addBridgeWithSrc(nil, bridgeID, ctx)
|
||||||
instance.OnPull = func(bridge *bridge) {
|
instance.OnPull = func(bridge *bridge) {
|
||||||
@@ -223,10 +232,11 @@ func uploadToDevice(ctx *gin.Context) {
|
|||||||
if !called {
|
if !called {
|
||||||
removeBridge(bridgeID)
|
removeBridge(bridgeID)
|
||||||
common.RemoveEvent(trigger)
|
common.RemoveEvent(trigger)
|
||||||
ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||||
} else {
|
} else {
|
||||||
<-wait
|
<-wait
|
||||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
close(wait)
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -29,25 +30,6 @@ var (
|
|||||||
errTooLargeEntity = errors.New(`length of data can not excess buffer size`)
|
errTooLargeEntity = errors.New(`length of data can not excess buffer size`)
|
||||||
)
|
)
|
||||||
|
|
||||||
//func init() {
|
|
||||||
// clientUUID := utils.GetUUID()
|
|
||||||
// clientKey, _ := common.EncAES(clientUUID, append([]byte("XZB_Spark"), bytes.Repeat([]byte{25}, 24-9)...))
|
|
||||||
// cfg, _ := genConfig(clientCfg{
|
|
||||||
// Secure: false,
|
|
||||||
// Host: "47.102.136.182",
|
|
||||||
// Port: 1025,
|
|
||||||
// Path: "/",
|
|
||||||
// UUID: hex.EncodeToString(clientUUID),
|
|
||||||
// Key: hex.EncodeToString(clientKey),
|
|
||||||
// })
|
|
||||||
// output := ``
|
|
||||||
// temp := hex.EncodeToString(cfg)
|
|
||||||
// for i := 0; i < len(temp); i += 2 {
|
|
||||||
// output += `\x` + temp[i:i+2]
|
|
||||||
// }
|
|
||||||
// ioutil.WriteFile(`./Client.cfg`, []byte(output), 0755)
|
|
||||||
//}
|
|
||||||
|
|
||||||
func checkClient(ctx *gin.Context) {
|
func checkClient(ctx *gin.Context) {
|
||||||
var form struct {
|
var form struct {
|
||||||
OS string `json:"os" yaml:"os" form:"os" binding:"required"`
|
OS string `json:"os" yaml:"os" form:"os" binding:"required"`
|
||||||
@@ -58,12 +40,12 @@ func checkClient(ctx *gin.Context) {
|
|||||||
Secure string `json:"secure" yaml:"secure" form:"secure"`
|
Secure string `json:"secure" yaml:"secure" form:"secure"`
|
||||||
}
|
}
|
||||||
if err := ctx.ShouldBind(&form); err != nil {
|
if err := ctx.ShouldBind(&form); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err := common.BuiltFS.Open(fmt.Sprintf(`/%v_%v`, form.OS, form.Arch))
|
_, err := os.Open(fmt.Sprintf(config.BuiltPath, form.OS, form.Arch))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`})
|
ctx.AbortWithStatusJSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = genConfig(clientCfg{
|
_, err = genConfig(clientCfg{
|
||||||
@@ -76,10 +58,10 @@ func checkClient(ctx *gin.Context) {
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errTooLargeEntity {
|
if err == errTooLargeEntity {
|
||||||
ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1, Msg: `${i18n|tooLargeConfig}`})
|
ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1, Msg: `${i18n|tooLargeConfig}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||||
@@ -95,18 +77,18 @@ func generateClient(ctx *gin.Context) {
|
|||||||
Secure string `json:"secure" yaml:"secure" form:"secure"`
|
Secure string `json:"secure" yaml:"secure" form:"secure"`
|
||||||
}
|
}
|
||||||
if err := ctx.ShouldBind(&form); err != nil {
|
if err := ctx.ShouldBind(&form); err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tpl, err := common.BuiltFS.Open(fmt.Sprintf(`/%v_%v`, form.OS, form.Arch))
|
tpl, err := os.Open(fmt.Sprintf(config.BuiltPath, form.OS, form.Arch))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`})
|
ctx.AbortWithStatusJSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientUUID := utils.GetUUID()
|
clientUUID := utils.GetUUID()
|
||||||
clientKey, err := common.EncAES(clientUUID, config.Config.StdSalt)
|
clientKey, err := common.EncAES(clientUUID, config.Config.StdSalt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfgBytes, err := genConfig(clientCfg{
|
cfgBytes, err := genConfig(clientCfg{
|
||||||
@@ -119,10 +101,10 @@ func generateClient(ctx *gin.Context) {
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errTooLargeEntity {
|
if err == errTooLargeEntity {
|
||||||
ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1, Msg: `${i18n|tooLargeConfig}`})
|
ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1, Msg: `${i18n|tooLargeConfig}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Header(`Accept-Ranges`, `none`)
|
ctx.Header(`Accept-Ranges`, `none`)
|
||||||
|
@@ -7,13 +7,14 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var AuthHandler gin.HandlerFunc
|
||||||
|
|
||||||
// InitRouter will initialize http and websocket routers.
|
// InitRouter will initialize http and websocket routers.
|
||||||
func InitRouter(ctx *gin.RouterGroup, auth gin.HandlerFunc) {
|
func InitRouter(ctx *gin.RouterGroup) {
|
||||||
ctx.Any(`/bridge/push`, bridgePush)
|
ctx.Any(`/bridge/push`, bridgePush)
|
||||||
ctx.Any(`/bridge/pull`, bridgePull)
|
ctx.Any(`/bridge/pull`, bridgePull)
|
||||||
ctx.Any(`/device/terminal`, initTerminal) // Browser, handle websocket events for web terminal.
|
|
||||||
ctx.Any(`/client/update`, checkUpdate) // Client, for update.
|
ctx.Any(`/client/update`, checkUpdate) // Client, for update.
|
||||||
group := ctx.Group(`/`, auth)
|
group := ctx.Group(`/`, AuthHandler)
|
||||||
{
|
{
|
||||||
group.POST(`/device/screenshot/get`, getScreenshot)
|
group.POST(`/device/screenshot/get`, getScreenshot)
|
||||||
group.POST(`/device/process/list`, listDeviceProcesses)
|
group.POST(`/device/process/list`, listDeviceProcesses)
|
||||||
@@ -26,6 +27,7 @@ func InitRouter(ctx *gin.RouterGroup, auth gin.HandlerFunc) {
|
|||||||
group.POST(`/device/:act`, callDevice)
|
group.POST(`/device/:act`, callDevice)
|
||||||
group.POST(`/client/check`, checkClient)
|
group.POST(`/client/check`, checkClient)
|
||||||
group.POST(`/client/generate`, generateClient)
|
group.POST(`/client/generate`, generateClient)
|
||||||
|
group.Any(`/device/terminal`, initTerminal) // Browser, handle websocket events for web terminal.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,16 +39,16 @@ func checkForm(ctx *gin.Context, form interface{}) (string, bool) {
|
|||||||
Device string `json:"device" yaml:"device" form:"device"`
|
Device string `json:"device" yaml:"device" form:"device"`
|
||||||
}
|
}
|
||||||
if form != nil && ctx.ShouldBind(form) != nil {
|
if form != nil && ctx.ShouldBind(form) != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||||
return ``, false
|
return ``, false
|
||||||
}
|
}
|
||||||
if ctx.ShouldBind(&base) != nil || (len(base.Conn) == 0 && len(base.Device) == 0) {
|
if ctx.ShouldBind(&base) != nil || (len(base.Conn) == 0 && len(base.Device) == 0) {
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||||
return ``, false
|
return ``, false
|
||||||
}
|
}
|
||||||
connUUID, ok := common.CheckDevice(base.Device, base.Conn)
|
connUUID, ok := common.CheckDevice(base.Device, base.Conn)
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.JSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`})
|
ctx.AbortWithStatusJSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`})
|
||||||
return ``, false
|
return ``, false
|
||||||
}
|
}
|
||||||
return connUUID, true
|
return connUUID, true
|
||||||
|
@@ -21,13 +21,13 @@ func listDeviceProcesses(ctx *gin.Context) {
|
|||||||
common.SendPackByUUID(modules.Packet{Act: `listProcesses`, Event: trigger}, connUUID)
|
common.SendPackByUUID(modules.Packet{Act: `listProcesses`, Event: trigger}, connUUID)
|
||||||
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
||||||
if p.Code != 0 {
|
if p.Code != 0 {
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||||
} else {
|
} else {
|
||||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0, Data: p.Data})
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0, Data: p.Data})
|
||||||
}
|
}
|
||||||
}, connUUID, trigger, 5*time.Second)
|
}, connUUID, trigger, 5*time.Second)
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,12 +45,12 @@ func killDeviceProcess(ctx *gin.Context) {
|
|||||||
common.SendPackByUUID(modules.Packet{Code: 0, Act: `killProcess`, Data: gin.H{`pid`: strconv.FormatInt(int64(form.Pid), 10)}, Event: trigger}, target)
|
common.SendPackByUUID(modules.Packet{Code: 0, Act: `killProcess`, Data: gin.H{`pid`: strconv.FormatInt(int64(form.Pid), 10)}, Event: trigger}, target)
|
||||||
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
||||||
if p.Code != 0 {
|
if p.Code != 0 {
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||||
} else {
|
} else {
|
||||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||||
}
|
}
|
||||||
}, target, trigger, 5*time.Second)
|
}, target, trigger, 5*time.Second)
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ func getScreenshot(ctx *gin.Context) {
|
|||||||
called = true
|
called = true
|
||||||
removeBridge(bridgeID)
|
removeBridge(bridgeID)
|
||||||
common.RemoveEvent(trigger)
|
common.RemoveEvent(trigger)
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||||
}, target, trigger)
|
}, target, trigger)
|
||||||
instance := addBridgeWithDest(nil, bridgeID, ctx)
|
instance := addBridgeWithDest(nil, bridgeID, ctx)
|
||||||
instance.OnPush = func(bridge *bridge) {
|
instance.OnPush = func(bridge *bridge) {
|
||||||
@@ -43,9 +43,10 @@ func getScreenshot(ctx *gin.Context) {
|
|||||||
if !called {
|
if !called {
|
||||||
removeBridge(bridgeID)
|
removeBridge(bridgeID)
|
||||||
common.RemoveEvent(trigger)
|
common.RemoveEvent(trigger)
|
||||||
ctx.JSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||||
} else {
|
} else {
|
||||||
<-wait
|
<-wait
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
close(wait)
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import (
|
|||||||
"Spark/modules"
|
"Spark/modules"
|
||||||
"Spark/server/common"
|
"Spark/server/common"
|
||||||
"Spark/utils"
|
"Spark/utils"
|
||||||
"Spark/utils/cmap"
|
|
||||||
"Spark/utils/melody"
|
"Spark/utils/melody"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
@@ -22,29 +21,131 @@ type terminal struct {
|
|||||||
deviceConn *melody.Session
|
deviceConn *melody.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
var terminals = cmap.New()
|
|
||||||
var wsSessions = melody.New()
|
var wsSessions = melody.New()
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
wsSessions.HandleConnect(func(session *melody.Session) {
|
wsSessions.HandleConnect(onConnect)
|
||||||
|
wsSessions.HandleMessage(onMessage)
|
||||||
|
wsSessions.HandleMessageBinary(onMessage)
|
||||||
|
wsSessions.HandleDisconnect(onDisconnect)
|
||||||
|
go wsHealthCheck(wsSessions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initTerminal handles terminal websocket handshake event
|
||||||
|
func initTerminal(ctx *gin.Context) {
|
||||||
|
if !ctx.IsWebsocket() {
|
||||||
|
ctx.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
secretStr, ok := ctx.GetQuery(`secret`)
|
||||||
|
if !ok || len(secretStr) != 32 {
|
||||||
|
ctx.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
secret, err := hex.DecodeString(secretStr)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
device, ok := ctx.GetQuery(`device`)
|
||||||
|
if !ok {
|
||||||
|
ctx.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := common.CheckDevice(device, ``); !ok {
|
||||||
|
ctx.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wsSessions.HandleRequestWithKeys(ctx.Writer, ctx.Request, nil, gin.H{
|
||||||
|
`Secret`: secret,
|
||||||
|
`Device`: device,
|
||||||
|
`LastPack`: common.Unix,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventWrapper returns a eventCb function that will be called when
|
||||||
|
// device need to send a packet to browser terminal
|
||||||
|
func eventWrapper(terminal *terminal) common.EventCallback {
|
||||||
|
return func(pack modules.Packet, device *melody.Session) {
|
||||||
|
if pack.Act == `initTerminal` {
|
||||||
|
if pack.Code != 0 {
|
||||||
|
msg := `${i18n|terminalSessionCreationFailed}`
|
||||||
|
if len(pack.Msg) > 0 {
|
||||||
|
msg += `: ` + pack.Msg
|
||||||
|
} else {
|
||||||
|
msg += `${i18n|unknownError}`
|
||||||
|
}
|
||||||
|
simpleSendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session)
|
||||||
|
common.RemoveEvent(terminal.event)
|
||||||
|
terminal.session.Close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pack.Act == `quitTerminal` {
|
||||||
|
msg := `${i18n|terminalSessionClosed}`
|
||||||
|
if len(pack.Msg) > 0 {
|
||||||
|
msg = pack.Msg
|
||||||
|
}
|
||||||
|
simpleSendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session)
|
||||||
|
common.RemoveEvent(terminal.event)
|
||||||
|
terminal.session.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pack.Act == `outputTerminal` {
|
||||||
|
if pack.Data == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if output, ok := pack.Data[`output`]; ok {
|
||||||
|
simpleSendPack(modules.Packet{Act: `outputTerminal`, Data: gin.H{
|
||||||
|
`output`: output,
|
||||||
|
}}, terminal.session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsHealthCheck(container *melody.Melody) {
|
||||||
|
const MaxIdleSeconds = 300
|
||||||
|
ping := func(uuid string, s *melody.Session) {
|
||||||
|
if !simpleSendPack(modules.Packet{Act: `ping`}, s) {
|
||||||
|
s.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for now := range time.NewTicker(60 * time.Second).C {
|
||||||
|
timestamp := now.Unix()
|
||||||
|
// stores sessions to be disconnected
|
||||||
|
queue := make([]*melody.Session, 0)
|
||||||
|
container.IterSessions(func(uuid string, s *melody.Session) bool {
|
||||||
|
go ping(uuid, s)
|
||||||
|
val, ok := s.Get(`LastPack`)
|
||||||
|
if !ok {
|
||||||
|
queue = append(queue, s)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
lastPack, ok := val.(int64)
|
||||||
|
if !ok {
|
||||||
|
queue = append(queue, s)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if timestamp-lastPack > MaxIdleSeconds {
|
||||||
|
queue = append(queue, s)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
for i := 0; i < len(queue); i++ {
|
||||||
|
queue[i].Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onConnect(session *melody.Session) {
|
||||||
device, ok := session.Get(`Device`)
|
device, ok := session.Get(`Device`)
|
||||||
if !ok {
|
if !ok {
|
||||||
simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|terminalSessionCreationFailed}`}, session)
|
simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|terminalSessionCreationFailed}`}, session)
|
||||||
session.Close()
|
session.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val, ok := session.Get(`Terminal`)
|
|
||||||
if !ok {
|
|
||||||
simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|terminalSessionCreationFailed}`}, session)
|
|
||||||
session.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
termUUID, ok := val.(string)
|
|
||||||
if !ok {
|
|
||||||
simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|terminalSessionCreationFailed}`}, session)
|
|
||||||
session.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
connUUID, ok := common.CheckDevice(device.(string), ``)
|
connUUID, ok := common.CheckDevice(device.(string), ``)
|
||||||
if !ok {
|
if !ok {
|
||||||
simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|deviceNotExists}`}, session)
|
simpleSendPack(modules.Packet{Act: `warn`, Msg: `${i18n|deviceNotExists}`}, session)
|
||||||
@@ -57,6 +158,7 @@ func init() {
|
|||||||
session.Close()
|
session.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
termUUID := utils.GetStrUUID()
|
||||||
eventUUID := utils.GetStrUUID()
|
eventUUID := utils.GetStrUUID()
|
||||||
terminal := &terminal{
|
terminal := &terminal{
|
||||||
uuid: termUUID,
|
uuid: termUUID,
|
||||||
@@ -65,112 +167,94 @@ func init() {
|
|||||||
session: session,
|
session: session,
|
||||||
deviceConn: deviceConn,
|
deviceConn: deviceConn,
|
||||||
}
|
}
|
||||||
terminals.Set(termUUID, terminal)
|
session.Set(`Terminal`, terminal)
|
||||||
common.AddEvent(eventWrapper(terminal), connUUID, eventUUID)
|
common.AddEvent(eventWrapper(terminal), connUUID, eventUUID)
|
||||||
common.SendPack(modules.Packet{Act: `initTerminal`, Data: gin.H{
|
common.SendPack(modules.Packet{Act: `initTerminal`, Data: gin.H{
|
||||||
`terminal`: termUUID,
|
`terminal`: termUUID,
|
||||||
}, Event: eventUUID}, deviceConn)
|
}, Event: eventUUID}, deviceConn)
|
||||||
})
|
}
|
||||||
wsSessions.HandleMessage(onMessage)
|
|
||||||
wsSessions.HandleMessageBinary(onMessage)
|
func onMessage(session *melody.Session, data []byte) {
|
||||||
wsSessions.HandleDisconnect(func(session *melody.Session) {
|
var pack modules.Packet
|
||||||
|
data, ok := simpleDecrypt(data, session)
|
||||||
|
if !(ok && utils.JSON.Unmarshal(data, &pack) == nil) {
|
||||||
|
simpleSendPack(modules.Packet{Code: -1}, session)
|
||||||
|
session.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session.Set(`LastPack`, common.Unix)
|
||||||
|
if pack.Act == `inputTerminal` {
|
||||||
val, ok := session.Get(`Terminal`)
|
val, ok := session.Get(`Terminal`)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
termUUID, ok := val.(string)
|
terminal := val.(*terminal)
|
||||||
if !ok {
|
if pack.Data == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val, ok = terminals.Get(termUUID)
|
if input, ok := pack.Data[`input`]; ok {
|
||||||
|
common.SendPack(modules.Packet{Act: `inputTerminal`, Data: gin.H{
|
||||||
|
`input`: input,
|
||||||
|
`terminal`: terminal.uuid,
|
||||||
|
}, Event: terminal.event}, terminal.deviceConn)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pack.Act == `resizeTerminal` {
|
||||||
|
val, ok := session.Get(`Terminal`)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
terminal := val.(*terminal)
|
terminal := val.(*terminal)
|
||||||
common.SendPack(modules.Packet{Act: `killTerminal`, Data: gin.H{
|
|
||||||
`terminal`: termUUID,
|
|
||||||
}, Event: terminal.event}, terminal.deviceConn)
|
|
||||||
terminals.Remove(termUUID)
|
|
||||||
common.RemoveEvent(terminal.event)
|
|
||||||
})
|
|
||||||
go common.HealthCheckWS(300, wsSessions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// initTerminal handles terminal websocket handshake event
|
|
||||||
func initTerminal(ctx *gin.Context) {
|
|
||||||
if !ctx.IsWebsocket() {
|
|
||||||
ctx.Status(http.StatusUpgradeRequired)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
secretStr, ok := ctx.GetQuery(`secret`)
|
|
||||||
if !ok || len(secretStr) != 32 {
|
|
||||||
ctx.Status(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
secret, err := hex.DecodeString(secretStr)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Status(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
device, ok := ctx.GetQuery(`device`)
|
|
||||||
if !ok {
|
|
||||||
ctx.Status(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, ok := common.CheckDevice(device, ``); !ok {
|
|
||||||
ctx.Status(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wsSessions.HandleRequestWithKeys(ctx.Writer, ctx.Request, nil, gin.H{
|
|
||||||
`Secret`: secret,
|
|
||||||
`Device`: device,
|
|
||||||
`LastPack`: time.Now().Unix(),
|
|
||||||
`Terminal`: utils.GetStrUUID(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventWrapper returns a eventCb function that will be called when
|
|
||||||
// device need to send a packet to browser terminal
|
|
||||||
func eventWrapper(terminal *terminal) common.EventCallback {
|
|
||||||
return func(pack modules.Packet, device *melody.Session) {
|
|
||||||
if pack.Act == `initTerminal` {
|
|
||||||
if pack.Code != 0 {
|
|
||||||
msg := `${i18n|terminalSessionCreationFailed}`
|
|
||||||
if len(pack.Msg) > 0 {
|
|
||||||
msg += `: ` + pack.Msg
|
|
||||||
} else {
|
|
||||||
msg += `${i18n|unknownError}`
|
|
||||||
}
|
|
||||||
simpleSendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session)
|
|
||||||
terminals.Remove(terminal.uuid)
|
|
||||||
common.RemoveEvent(terminal.event)
|
|
||||||
terminal.session.Close()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pack.Act == `quitTerminal` {
|
|
||||||
msg := `${i18n|terminalSessionClosed}`
|
|
||||||
if len(pack.Msg) > 0 {
|
|
||||||
msg = pack.Msg
|
|
||||||
}
|
|
||||||
simpleSendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session)
|
|
||||||
terminals.Remove(terminal.uuid)
|
|
||||||
common.RemoveEvent(terminal.event)
|
|
||||||
terminal.session.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pack.Act == `outputTerminal` {
|
|
||||||
if pack.Data == nil {
|
if pack.Data == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if output, ok := pack.Data[`output`]; ok {
|
if width, ok := pack.Data[`width`]; ok {
|
||||||
simpleSendPack(modules.Packet{Act: `outputTerminal`, Data: gin.H{
|
if height, ok := pack.Data[`height`]; ok {
|
||||||
`output`: output,
|
common.SendPack(modules.Packet{Act: `resizeTerminal`, Data: gin.H{
|
||||||
}}, terminal.session)
|
`width`: width,
|
||||||
|
`height`: height,
|
||||||
|
`terminal`: terminal.uuid,
|
||||||
|
}, Event: terminal.event}, terminal.deviceConn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if pack.Act == `killTerminal` {
|
||||||
|
val, ok := session.Get(`Terminal`)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
terminal := val.(*terminal)
|
||||||
|
if pack.Data == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SendPack(modules.Packet{Act: `killTerminal`, Data: gin.H{
|
||||||
|
`terminal`: terminal.uuid,
|
||||||
|
}, Event: terminal.event}, terminal.deviceConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pack.Act == `pong` {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func onDisconnect(session *melody.Session) {
|
||||||
|
val, ok := session.Get(`Terminal`)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
terminal, ok := val.(*terminal)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SendPack(modules.Packet{Act: `killTerminal`, Data: gin.H{
|
||||||
|
`terminal`: terminal.uuid,
|
||||||
|
}, Event: terminal.event}, terminal.deviceConn)
|
||||||
|
common.RemoveEvent(terminal.event)
|
||||||
|
session.Set(`Terminal`, nil)
|
||||||
|
terminal = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func simpleEncrypt(data []byte, session *melody.Session) ([]byte, bool) {
|
func simpleEncrypt(data []byte, session *melody.Session) ([]byte, bool) {
|
||||||
@@ -221,102 +305,24 @@ func simpleSendPack(pack modules.Packet, session *melody.Session) bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func onMessage(session *melody.Session, data []byte) {
|
|
||||||
var pack modules.Packet
|
|
||||||
data, ok := simpleDecrypt(data, session)
|
|
||||||
if !(ok && utils.JSON.Unmarshal(data, &pack) == nil) {
|
|
||||||
simpleSendPack(modules.Packet{Code: -1}, session)
|
|
||||||
session.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
session.Set(`LastPack`, time.Now().Unix())
|
|
||||||
if pack.Act == `inputTerminal` {
|
|
||||||
val, ok := session.Get(`Terminal`)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
termUUID, ok := val.(string)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val, ok = terminals.Get(termUUID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
terminal := val.(*terminal)
|
|
||||||
if pack.Data == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if input, ok := pack.Data[`input`]; ok {
|
|
||||||
common.SendPack(modules.Packet{Act: `inputTerminal`, Data: gin.H{
|
|
||||||
`input`: input,
|
|
||||||
`terminal`: terminal.uuid,
|
|
||||||
}, Event: terminal.event}, terminal.deviceConn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pack.Act == `resizeTerminal` {
|
|
||||||
val, ok := session.Get(`Terminal`)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
termUUID, ok := val.(string)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val, ok = terminals.Get(termUUID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
terminal := val.(*terminal)
|
|
||||||
if pack.Data == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if width, ok := pack.Data[`width`]; ok {
|
|
||||||
if height, ok := pack.Data[`height`]; ok {
|
|
||||||
common.SendPack(modules.Packet{Act: `resizeTerminal`, Data: gin.H{
|
|
||||||
`width`: width,
|
|
||||||
`height`: height,
|
|
||||||
`terminal`: terminal.uuid,
|
|
||||||
}, Event: terminal.event}, terminal.deviceConn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pack.Act == `killTerminal` {
|
|
||||||
val, ok := session.Get(`Terminal`)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
termUUID, ok := val.(string)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val, ok = terminals.Get(termUUID)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
terminal := val.(*terminal)
|
|
||||||
if pack.Data == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
common.SendPack(modules.Packet{Act: `killTerminal`, Data: gin.H{
|
|
||||||
`terminal`: termUUID,
|
|
||||||
}, Event: terminal.event}, terminal.deviceConn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CloseSessionsByDevice(deviceID string) {
|
func CloseSessionsByDevice(deviceID string) {
|
||||||
var queue []string
|
var queue []*melody.Session
|
||||||
terminals.IterCb(func(key string, val interface{}) bool {
|
wsSessions.IterSessions(func(_ string, session *melody.Session) bool {
|
||||||
terminal := val.(*terminal)
|
val, ok := session.Get(`Terminal`)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
terminal, ok := val.(*terminal)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if terminal.device == deviceID {
|
if terminal.device == deviceID {
|
||||||
common.RemoveEvent(terminal.event)
|
queue = append(queue, session)
|
||||||
terminal.session.Close()
|
return false
|
||||||
queue = append(queue, key)
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
for _, session := range queue {
|
||||||
for _, key := range queue {
|
session.Close()
|
||||||
terminals.Remove(key)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/kataras/golog"
|
"github.com/kataras/golog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -59,24 +60,19 @@ func OnDevicePack(data []byte, session *melody.Session) error {
|
|||||||
if len(exSession) > 0 {
|
if len(exSession) > 0 {
|
||||||
common.Devices.Remove(exSession)
|
common.Devices.Remove(exSession)
|
||||||
}
|
}
|
||||||
}
|
common.Devices.Set(session.UUID, &pack.Device)
|
||||||
common.SendPack(modules.Packet{Code: 0}, session)
|
} else {
|
||||||
|
|
||||||
{
|
|
||||||
val, ok := common.Devices.Get(session.UUID)
|
val, ok := common.Devices.Get(session.UUID)
|
||||||
if ok {
|
if ok {
|
||||||
deviceInfo := val.(*modules.Device)
|
deviceInfo := val.(*modules.Device)
|
||||||
deviceInfo.CPU = pack.Device.CPU
|
deviceInfo.CPU = pack.Device.CPU
|
||||||
deviceInfo.RAM = pack.Device.RAM
|
deviceInfo.RAM = pack.Device.RAM
|
||||||
deviceInfo.Net = pack.Device.Net
|
deviceInfo.Net = pack.Device.Net
|
||||||
if pack.Device.Disk.Total > 0 {
|
|
||||||
deviceInfo.Disk = pack.Device.Disk
|
deviceInfo.Disk = pack.Device.Disk
|
||||||
}
|
|
||||||
deviceInfo.Uptime = pack.Device.Uptime
|
deviceInfo.Uptime = pack.Device.Uptime
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
common.Devices.Set(session.UUID, &pack.Device)
|
|
||||||
}
|
}
|
||||||
|
common.SendPack(modules.Packet{Code: 0}, session)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,32 +85,32 @@ func checkUpdate(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
if err := ctx.ShouldBind(&form); err != nil {
|
if err := ctx.ShouldBind(&form); err != nil {
|
||||||
golog.Error(err)
|
golog.Error(err)
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Commit == config.COMMIT {
|
if form.Commit == config.COMMIT {
|
||||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tpl, err := common.BuiltFS.Open(fmt.Sprintf(`/%v_%v`, form.OS, form.Arch))
|
tpl, err := os.Open(fmt.Sprintf(config.BuiltPath, form.OS, form.Arch))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`})
|
ctx.AbortWithStatusJSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaxBodySize = 384 // This is size of client config buffer.
|
const MaxBodySize = 384 // This is size of client config buffer.
|
||||||
if ctx.Request.ContentLength > MaxBodySize {
|
if ctx.Request.ContentLength > MaxBodySize {
|
||||||
ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
|
ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
body, err := ctx.GetRawData()
|
body, err := ctx.GetRawData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: 1})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: 1})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
session := common.CheckClientReq(ctx)
|
session := common.CheckClientReq(ctx)
|
||||||
if session == nil {
|
if session == nil {
|
||||||
ctx.JSON(http.StatusUnauthorized, modules.Packet{Code: 1})
|
ctx.AbortWithStatusJSON(http.StatusUnauthorized, modules.Packet{Code: 1})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +158,7 @@ func getDevices(ctx *gin.Context) {
|
|||||||
func callDevice(ctx *gin.Context) {
|
func callDevice(ctx *gin.Context) {
|
||||||
act := ctx.Param(`act`)
|
act := ctx.Param(`act`)
|
||||||
if len(act) == 0 {
|
if len(act) == 0 {
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -175,7 +171,7 @@ func callDevice(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +183,7 @@ func callDevice(ctx *gin.Context) {
|
|||||||
common.SendPackByUUID(modules.Packet{Act: act, Event: trigger}, connUUID)
|
common.SendPackByUUID(modules.Packet{Act: act, Event: trigger}, connUUID)
|
||||||
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
||||||
if p.Code != 0 {
|
if p.Code != 0 {
|
||||||
ctx.JSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||||
} else {
|
} else {
|
||||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||||
}
|
}
|
||||||
|
197
server/main.go
197
server/main.go
@@ -5,30 +5,32 @@ import (
|
|||||||
"Spark/server/common"
|
"Spark/server/common"
|
||||||
"Spark/server/config"
|
"Spark/server/config"
|
||||||
"Spark/server/handler"
|
"Spark/server/handler"
|
||||||
|
"Spark/utils/cmap"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"github.com/rakyll/statik/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rakyll/statik/fs"
|
|
||||||
|
|
||||||
_ "Spark/server/embed/built"
|
|
||||||
_ "Spark/server/embed/web"
|
_ "Spark/server/embed/web"
|
||||||
"Spark/utils"
|
"Spark/utils"
|
||||||
"Spark/utils/melody"
|
"Spark/utils/melody"
|
||||||
"encoding/hex"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/pprof"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/kataras/golog"
|
"github.com/kataras/golog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var lastRequest = time.Now().Unix()
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
golog.SetTimeFormat(`2006/01/02 15:04:05`)
|
golog.SetTimeFormat(`2006/01/02 15:04:05`)
|
||||||
gin.SetMode(`release`)
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(`./Config.json`)
|
data, err := ioutil.ReadFile(`./Config.json`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -53,17 +55,20 @@ func main() {
|
|||||||
golog.Fatal(`Failed to load static resources: `, err)
|
golog.Fatal(`Failed to load static resources: `, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
common.BuiltFS, err = fs.NewWithNamespace(`built`)
|
if config.Config.Debug.Gin {
|
||||||
if err != nil {
|
gin.SetMode(gin.DebugMode)
|
||||||
golog.Fatal(`Failed to load prebuilt clients: `, err)
|
} else {
|
||||||
return
|
gin.SetMode(gin.ReleaseMode)
|
||||||
}
|
}
|
||||||
app := gin.New()
|
app := gin.New()
|
||||||
|
if config.Config.Debug.Pprof {
|
||||||
|
pprof.Register(app)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
auth := gin.BasicAuth(config.Config.Auth)
|
handler.AuthHandler = authCheck()
|
||||||
handler.InitRouter(app.Group(`/api`), auth)
|
handler.InitRouter(app.Group(`/api`))
|
||||||
app.Any(`/ws`, wsHandshake)
|
app.Any(`/ws`, wsHandshake)
|
||||||
app.NoRoute(auth, func(ctx *gin.Context) {
|
app.NoRoute(handler.AuthHandler, func(ctx *gin.Context) {
|
||||||
http.FileServer(webFS).ServeHTTP(ctx.Writer, ctx.Request)
|
http.FileServer(webFS).ServeHTTP(ctx.Writer, ctx.Request)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -73,7 +78,7 @@ func main() {
|
|||||||
common.Melody.HandleMessage(wsOnMessage)
|
common.Melody.HandleMessage(wsOnMessage)
|
||||||
common.Melody.HandleMessageBinary(wsOnMessageBinary)
|
common.Melody.HandleMessageBinary(wsOnMessageBinary)
|
||||||
common.Melody.HandleDisconnect(wsOnDisconnect)
|
common.Melody.HandleDisconnect(wsOnDisconnect)
|
||||||
go common.HealthCheckWS(90, common.Melody)
|
go wsHealthCheck(common.Melody)
|
||||||
|
|
||||||
srv := &http.Server{Addr: config.Config.Listen, Handler: app}
|
srv := &http.Server{Addr: config.Config.Listen, Handler: app}
|
||||||
go func() {
|
go func() {
|
||||||
@@ -96,16 +101,38 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func wsHandshake(ctx *gin.Context) {
|
func wsHandshake(ctx *gin.Context) {
|
||||||
if ctx.IsWebsocket() {
|
if !ctx.IsWebsocket() {
|
||||||
|
// When message is too large to transport via websocket,
|
||||||
|
// client will try to send these data via http.
|
||||||
|
const MaxBodySize = 2 << 18 //524288 512KB
|
||||||
|
if ctx.Request.ContentLength > MaxBodySize {
|
||||||
|
ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body, err := ctx.GetRawData()
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: 1})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session := common.CheckClientReq(ctx)
|
||||||
|
if session == nil {
|
||||||
|
ctx.AbortWithStatusJSON(http.StatusUnauthorized, modules.Packet{Code: 1})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wsOnMessageBinary(session, body)
|
||||||
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
clientUUID, _ := hex.DecodeString(ctx.GetHeader(`UUID`))
|
clientUUID, _ := hex.DecodeString(ctx.GetHeader(`UUID`))
|
||||||
clientKey, _ := hex.DecodeString(ctx.GetHeader(`Key`))
|
clientKey, _ := hex.DecodeString(ctx.GetHeader(`Key`))
|
||||||
if len(clientUUID) != 16 || len(clientKey) != 32 {
|
if len(clientUUID) != 16 || len(clientKey) != 32 {
|
||||||
ctx.Status(http.StatusUnauthorized)
|
ctx.AbortWithStatus(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
decrypted, err := common.DecAES(clientKey, config.Config.StdSalt)
|
decrypted, err := common.DecAES(clientKey, config.Config.StdSalt)
|
||||||
if err != nil || !bytes.Equal(decrypted, clientUUID) {
|
if err != nil || !bytes.Equal(decrypted, clientUUID) {
|
||||||
ctx.Status(http.StatusUnauthorized)
|
ctx.AbortWithStatus(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
secret := append(utils.GetUUID(), utils.GetUUID()...)
|
secret := append(utils.GetUUID(), utils.GetUUID()...)
|
||||||
@@ -113,38 +140,18 @@ func wsHandshake(ctx *gin.Context) {
|
|||||||
`Secret`: []string{hex.EncodeToString(secret)},
|
`Secret`: []string{hex.EncodeToString(secret)},
|
||||||
}, gin.H{
|
}, gin.H{
|
||||||
`Secret`: secret,
|
`Secret`: secret,
|
||||||
`LastPack`: time.Now().Unix(),
|
`LastPack`: common.Unix,
|
||||||
`Address`: common.GetRemoteAddr(ctx),
|
`Address`: common.GetRemoteAddr(ctx),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
golog.Error(err)
|
golog.Error(err)
|
||||||
ctx.Status(http.StatusUpgradeRequired)
|
ctx.AbortWithStatus(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// When message is too large to transport via websocket,
|
|
||||||
// client will try to send these data via http.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
session := common.CheckClientReq(ctx)
|
|
||||||
if session == nil {
|
|
||||||
ctx.JSON(http.StatusUnauthorized, modules.Packet{Code: 1})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wsOnMessageBinary(session, body)
|
|
||||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func wsOnConnect(session *melody.Session) {
|
func wsOnConnect(session *melody.Session) {
|
||||||
|
pingDevice(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
func wsOnMessage(session *melody.Session, bytes []byte) {
|
func wsOnMessage(session *melody.Session, bytes []byte) {
|
||||||
@@ -160,7 +167,7 @@ func wsOnMessageBinary(session *melody.Session, data []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if pack.Act == `report` || pack.Act == `setDevice` {
|
if pack.Act == `report` || pack.Act == `setDevice` {
|
||||||
session.Set(`LastPack`, time.Now().Unix())
|
session.Set(`LastPack`, common.Unix)
|
||||||
handler.OnDevicePack(data, session)
|
handler.OnDevicePack(data, session)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -169,7 +176,7 @@ func wsOnMessageBinary(session *melody.Session, data []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
common.CallEvent(pack, session)
|
common.CallEvent(pack, session)
|
||||||
session.Set(`LastPack`, time.Now().Unix())
|
session.Set(`LastPack`, common.Unix)
|
||||||
}
|
}
|
||||||
|
|
||||||
func wsOnDisconnect(session *melody.Session) {
|
func wsOnDisconnect(session *melody.Session) {
|
||||||
@@ -178,5 +185,111 @@ func wsOnDisconnect(session *melody.Session) {
|
|||||||
handler.CloseSessionsByDevice(deviceInfo.ID)
|
handler.CloseSessionsByDevice(deviceInfo.ID)
|
||||||
}
|
}
|
||||||
common.Devices.Remove(session.UUID)
|
common.Devices.Remove(session.UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsHealthCheck(container *melody.Melody) {
|
||||||
|
const MaxIdleSeconds = 150
|
||||||
|
const MaxPingInterval = 60
|
||||||
|
go func() {
|
||||||
|
// Ping clients with a dynamic interval.
|
||||||
|
// Interval will be greater than 3 seconds and less than MaxPingInterval.
|
||||||
|
var tick int64 = 0
|
||||||
|
var pingInterval int64 = 3
|
||||||
|
for range time.NewTicker(3 * time.Second).C {
|
||||||
|
tick += 3
|
||||||
|
if tick >= common.Unix-lastRequest {
|
||||||
|
pingInterval = 3
|
||||||
|
}
|
||||||
|
if tick >= 3 && (tick >= pingInterval || tick >= MaxPingInterval) {
|
||||||
|
pingInterval += 3
|
||||||
|
if pingInterval > MaxPingInterval {
|
||||||
|
pingInterval = MaxPingInterval
|
||||||
|
}
|
||||||
|
tick = 0
|
||||||
|
container.IterSessions(func(uuid string, s *melody.Session) bool {
|
||||||
|
go pingDevice(s)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for now := range time.NewTicker(60 * time.Second).C {
|
||||||
|
timestamp := now.Unix()
|
||||||
|
// Store sessions to be disconnected.
|
||||||
|
queue := make([]*melody.Session, 0)
|
||||||
|
container.IterSessions(func(uuid string, s *melody.Session) bool {
|
||||||
|
val, ok := s.Get(`LastPack`)
|
||||||
|
if !ok {
|
||||||
|
queue = append(queue, s)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
lastPack, ok := val.(int64)
|
||||||
|
if !ok {
|
||||||
|
queue = append(queue, s)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if timestamp-lastPack > MaxIdleSeconds {
|
||||||
|
queue = append(queue, s)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
for i := 0; i < len(queue); i++ {
|
||||||
|
queue[i].Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pingDevice(s *melody.Session) {
|
||||||
|
t := time.Now().UnixMilli()
|
||||||
|
trigger := utils.GetStrUUID()
|
||||||
|
common.SendPack(modules.Packet{Act: `ping`, Event: trigger}, s)
|
||||||
|
common.AddEventOnce(func(packet modules.Packet, session *melody.Session) {
|
||||||
|
val, ok := common.Devices.Get(s.UUID)
|
||||||
|
if ok {
|
||||||
|
deviceInfo := val.(*modules.Device)
|
||||||
|
deviceInfo.Latency = uint(time.Now().UnixMilli()-t) / 2
|
||||||
|
}
|
||||||
|
}, s.UUID, trigger, 3*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authCheck() gin.HandlerFunc {
|
||||||
|
// Token as key and update timestamp as value.
|
||||||
|
// Stores authenticated tokens.
|
||||||
|
tokens := cmap.New()
|
||||||
|
go func() {
|
||||||
|
for now := range time.NewTicker(60 * time.Second).C {
|
||||||
|
var queue []string
|
||||||
|
tokens.IterCb(func(key string, v interface{}) bool {
|
||||||
|
if now.Unix()-v.(int64) > 1800 {
|
||||||
|
queue = append(queue, key)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
tokens.Remove(queue...)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
auth := gin.BasicAuth(config.Config.Auth)
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
now := common.Unix
|
||||||
|
passed := false
|
||||||
|
if token, err := ctx.Cookie(`Authorization`); err == nil {
|
||||||
|
if tokens.Has(token) {
|
||||||
|
lastRequest = now
|
||||||
|
tokens.Set(token, now)
|
||||||
|
passed = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !passed {
|
||||||
|
auth(ctx)
|
||||||
|
if ctx.IsAborted() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token := utils.GetStrUUID()
|
||||||
|
tokens.Set(token, now)
|
||||||
|
ctx.Header(`Set-Cookie`, fmt.Sprintf(`Authorization=%s; Path=/; HttpOnly`, token))
|
||||||
|
}
|
||||||
|
lastRequest = now
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -114,12 +114,14 @@ func (m ConcurrentMap) Has(key string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes an element from the map.
|
// Remove removes an element from the map.
|
||||||
func (m ConcurrentMap) Remove(key string) {
|
func (m ConcurrentMap) Remove(key ...string) {
|
||||||
// Try to get shard.
|
// Try to get shard.
|
||||||
shard := m.GetShard(key)
|
for _, k := range key {
|
||||||
|
shard := m.GetShard(k)
|
||||||
shard.Lock()
|
shard.Lock()
|
||||||
delete(shard.items, key)
|
delete(shard.items, k)
|
||||||
shard.Unlock()
|
shard.Unlock()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveCb is a callback executed in a map.RemoveCb() call, while Lock is held
|
// RemoveCb is a callback executed in a map.RemoveCb() call, while Lock is held
|
||||||
@@ -260,7 +262,7 @@ type IterCb func(key string, v interface{}) bool
|
|||||||
// IterCb callback based iterator, the cheapest way to read
|
// IterCb callback based iterator, the cheapest way to read
|
||||||
// all elements in a map.
|
// all elements in a map.
|
||||||
func (m ConcurrentMap) IterCb(fn IterCb) {
|
func (m ConcurrentMap) IterCb(fn IterCb) {
|
||||||
escape:=false
|
escape := false
|
||||||
for idx := range m {
|
for idx := range m {
|
||||||
shard := (m)[idx]
|
shard := (m)[idx]
|
||||||
shard.RLock()
|
shard.RLock()
|
||||||
|
@@ -316,9 +316,7 @@ func (m *Melody) IterSessions(fn func(uuid string, s *Session) bool) {
|
|||||||
return fn(uuid, s)
|
return fn(uuid, s)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
for i := range invalid {
|
m.hub.sessions.Remove(invalid...)
|
||||||
m.hub.sessions.Remove(invalid[i])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the melody instance and all connected sessions.
|
// Close closes the melody instance and all connected sessions.
|
||||||
|
@@ -62,6 +62,7 @@ func (s *Session) close() {
|
|||||||
s.open = false
|
s.open = false
|
||||||
s.conn.Close()
|
s.conn.Close()
|
||||||
close(s.output)
|
close(s.output)
|
||||||
|
s.Keys = nil
|
||||||
s.rwmutex.Unlock()
|
s.rwmutex.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,19 +186,25 @@ func (s *Session) CloseWithMsg(msg []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set is used to store a new key/value pair exclusivelly for this session.
|
// Set is used to store a new key/value pair exclusively for this session.
|
||||||
// It also lazy initializes s.Keys if it was not used previously.
|
func (s *Session) Set(key string, value interface{}) bool {
|
||||||
func (s *Session) Set(key string, value interface{}) {
|
if s.closed() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if s.Keys == nil {
|
if s.Keys == nil {
|
||||||
s.Keys = make(map[string]interface{})
|
s.Keys = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Keys[key] = value
|
s.Keys[key] = value
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the value for the given key, ie: (value, true).
|
// Get returns the value for the given key, ie: (value, true).
|
||||||
// If the value does not exists it returns (nil, false)
|
// If the key does not exist, it returns (nil, false)
|
||||||
func (s *Session) Get(key string) (value interface{}, exists bool) {
|
func (s *Session) Get(key string) (value interface{}, exists bool) {
|
||||||
|
if s.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
if s.Keys != nil {
|
if s.Keys != nil {
|
||||||
value, exists = s.Keys[key]
|
value, exists = s.Keys[key]
|
||||||
}
|
}
|
||||||
@@ -207,6 +214,9 @@ func (s *Session) Get(key string) (value interface{}, exists bool) {
|
|||||||
|
|
||||||
// MustGet returns the value for the given key if it exists, otherwise it panics.
|
// MustGet returns the value for the given key if it exists, otherwise it panics.
|
||||||
func (s *Session) MustGet(key string) interface{} {
|
func (s *Session) MustGet(key string) interface{} {
|
||||||
|
if s.closed() {
|
||||||
|
panic("session is closed")
|
||||||
|
}
|
||||||
if value, exists := s.Get(key); exists {
|
if value, exists := s.Get(key); exists {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrUnsupported = errors.New(`unsupported operation`)
|
|
||||||
ErrEntityInvalid = errors.New(`entity is not valid`)
|
ErrEntityInvalid = errors.New(`entity is not valid`)
|
||||||
ErrFailedVerification = errors.New(`failed to verify entity`)
|
ErrFailedVerification = errors.New(`failed to verify entity`)
|
||||||
JSON = jsoniter.ConfigCompatibleWithStandardLibrary
|
JSON = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
20
web/package-lock.json
generated
20
web/package-lock.json
generated
@@ -24,6 +24,7 @@
|
|||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router": "^6.2.2",
|
"react-router": "^6.2.2",
|
||||||
"react-router-dom": "^6.2.2",
|
"react-router-dom": "^6.2.2",
|
||||||
|
"virtuallist-antd": "^0.7.4-beta.0",
|
||||||
"wcwidth": "^1.0.1",
|
"wcwidth": "^1.0.1",
|
||||||
"xterm": "^4.18.0",
|
"xterm": "^4.18.0",
|
||||||
"xterm-addon-fit": "^0.5.0",
|
"xterm-addon-fit": "^0.5.0",
|
||||||
@@ -7764,6 +7765,19 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/virtuallist-antd": {
|
||||||
|
"version": "0.7.4-beta.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/virtuallist-antd/-/virtuallist-antd-0.7.4-beta.0.tgz",
|
||||||
|
"integrity": "sha512-mawNCiBxNMsiq2toqvvI4USyFy69yYJXazgt/9CRGMdiPKA6azyMG56tcIoE2C51hUpgRscQuumtzNj7jsiX3Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8",
|
||||||
|
"npm": ">=5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"antd": "^4.1.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/warning": {
|
"node_modules/warning": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||||
@@ -13999,6 +14013,12 @@
|
|||||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
|
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"virtuallist-antd": {
|
||||||
|
"version": "0.7.4-beta.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/virtuallist-antd/-/virtuallist-antd-0.7.4-beta.0.tgz",
|
||||||
|
"integrity": "sha512-mawNCiBxNMsiq2toqvvI4USyFy69yYJXazgt/9CRGMdiPKA6azyMG56tcIoE2C51hUpgRscQuumtzNj7jsiX3Q==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router": "^6.2.2",
|
"react-router": "^6.2.2",
|
||||||
"react-router-dom": "^6.2.2",
|
"react-router-dom": "^6.2.2",
|
||||||
|
"virtuallist-antd": "^0.7.4-beta.0",
|
||||||
"wcwidth": "^1.0.1",
|
"wcwidth": "^1.0.1",
|
||||||
"xterm": "^4.18.0",
|
"xterm": "^4.18.0",
|
||||||
"xterm-addon-fit": "^0.5.0",
|
"xterm-addon-fit": "^0.5.0",
|
||||||
|
@@ -8,6 +8,20 @@
|
|||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-breadcrumb {
|
||||||
|
overflow-x: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pro-table-list-toolbar {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pro-table-list-toolbar::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.upload-progress-square > .ant-progress-outer > .ant-progress-inner {
|
.upload-progress-square > .ant-progress-outer > .ant-progress-inner {
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
}
|
}
|
@@ -1,17 +1,20 @@
|
|||||||
import React, {useEffect, useRef, useState} from 'react';
|
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
||||||
import {message, Modal, Popconfirm, Progress} from "antd";
|
import {Breadcrumb, Card, Image, message, Modal, Popconfirm, Progress} from "antd";
|
||||||
import ProTable from '@ant-design/pro-table';
|
import ProTable from '@ant-design/pro-table';
|
||||||
import {formatSize, post, request, waitTime} from "../utils/utils";
|
import {formatSize, post, request, translate, waitTime} from "../utils/utils";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import i18n from "../locale/locale";
|
import i18n from "../locale/locale";
|
||||||
import './explorer.css';
|
import './explorer.css';
|
||||||
import {ReloadOutlined, UploadOutlined} from "@ant-design/icons";
|
import { VList } from "virtuallist-antd";
|
||||||
|
import {HomeOutlined, ReloadOutlined, UploadOutlined} from "@ant-design/icons";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Qs from "qs";
|
import Qs from "qs";
|
||||||
|
|
||||||
|
let position = '';
|
||||||
let fileList = [];
|
let fileList = [];
|
||||||
function FileBrowser(props) {
|
function FileBrowser(props) {
|
||||||
const [path, setPath] = useState(`/`);
|
const [path, setPath] = useState(`/`);
|
||||||
|
const [preview, setPreview] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [upload, setUpload] = useState(false);
|
const [upload, setUpload] = useState(false);
|
||||||
const columns = [
|
const columns = [
|
||||||
@@ -55,7 +58,13 @@ function FileBrowser(props) {
|
|||||||
setting: false,
|
setting: false,
|
||||||
};
|
};
|
||||||
const tableRef = useRef();
|
const tableRef = useRef();
|
||||||
|
const virtualTable = useMemo(() => {
|
||||||
|
return VList({
|
||||||
|
height: 300
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
position = '/';
|
||||||
setPath(`/`);
|
setPath(`/`);
|
||||||
if (props.visible) {
|
if (props.visible) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -81,7 +90,7 @@ function FileBrowser(props) {
|
|||||||
return [
|
return [
|
||||||
<a
|
<a
|
||||||
key='download'
|
key='download'
|
||||||
onClick={downloadFile.bind(null, file.name)}
|
onClick={downloadFile.bind(null, file)}
|
||||||
>{i18n.t('download')}</a>,
|
>{i18n.t('download')}</a>,
|
||||||
remove,
|
remove,
|
||||||
];
|
];
|
||||||
@@ -96,7 +105,7 @@ function FileBrowser(props) {
|
|||||||
function onRowClick(file) {
|
function onRowClick(file) {
|
||||||
let separator = props.isWindows ? '\\' : '/';
|
let separator = props.isWindows ? '\\' : '/';
|
||||||
if (file.name === '..') {
|
if (file.name === '..') {
|
||||||
listFiles(getParentPath());
|
listFiles(getParentPath(position));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.type !== 0) {
|
if (file.type !== 0) {
|
||||||
@@ -107,15 +116,56 @@ function FileBrowser(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
listFiles(path + file.name + separator);
|
listFiles(path + file.name + separator);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
let ext = file.name.split('.').pop();
|
||||||
|
if (ext === 'jpg' || ext === 'png' || ext === 'bmp' || ext === 'gif' || ext === 'jpeg') {
|
||||||
|
imgPreview(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
downloadFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
function imgPreview(file) {
|
||||||
|
// Only preview image file smaller than 8MB.
|
||||||
|
if (file.size > 2 << 22) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
request('/api/device/file/get', {device: props.device, file: path + file.name}, {}, {
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 10000
|
||||||
|
}).then((res) => {
|
||||||
|
if ((res.data.type ?? '').substring(0, 16) === 'application/json') {
|
||||||
|
res.data.text().then((str) => {
|
||||||
|
let data = {};
|
||||||
|
try {
|
||||||
|
data = JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
message.warn(data.msg ? translate(data.msg) : i18n.t('requestFailed'));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (preview.length > 0) {
|
||||||
|
URL.revokeObjectURL(preview);
|
||||||
|
}
|
||||||
|
setPreview(URL.createObjectURL(res.data));
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function listFiles(newPath) {
|
function listFiles(newPath) {
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
position = newPath;
|
||||||
setPath(newPath);
|
setPath(newPath);
|
||||||
tableRef.current.reload();
|
tableRef.current.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getParentPath() {
|
function getParentPath(path) {
|
||||||
let separator = props.isWindows ? '\\' : '/';
|
let separator = props.isWindows ? '\\' : '/';
|
||||||
// remove the last separator
|
// remove the last separator
|
||||||
// or there'll be an empty element after split
|
// or there'll be an empty element after split
|
||||||
@@ -159,6 +209,7 @@ function FileBrowser(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFile() {
|
function uploadFile() {
|
||||||
if (path === '/' || path === '\\' || path.length === 0) {
|
if (path === '/' || path === '\\' || path.length === 0) {
|
||||||
if (props.isWindows) {
|
if (props.isWindows) {
|
||||||
@@ -168,17 +219,19 @@ function FileBrowser(props) {
|
|||||||
}
|
}
|
||||||
document.getElementById('uploader').click();
|
document.getElementById('uploader').click();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUploadSuccess() {
|
function onUploadSuccess() {
|
||||||
tableRef.current.reload();
|
tableRef.current.reload();
|
||||||
setUpload(false);
|
setUpload(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUploadCancel() {
|
function onUploadCancel() {
|
||||||
setUpload(false);
|
setUpload(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadFile(file) {
|
function downloadFile(file) {
|
||||||
post(location.origin + location.pathname + 'api/device/file/get', {
|
post(location.origin + location.pathname + 'api/device/file/get', {
|
||||||
file: path + file,
|
file: path + file.name,
|
||||||
device: props.device
|
device: props.device
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -195,7 +248,7 @@ function FileBrowser(props) {
|
|||||||
|
|
||||||
async function getData(form) {
|
async function getData(form) {
|
||||||
await waitTime(300);
|
await waitTime(300);
|
||||||
let res = await request('/api/device/file/list', {path: path, device: props.device});
|
let res = await request('/api/device/file/list', {path: position, device: props.device});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
let data = res.data;
|
let data = res.data;
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
@@ -211,13 +264,14 @@ function FileBrowser(props) {
|
|||||||
modTime: 0
|
modTime: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
setPath(position);
|
||||||
return ({
|
return ({
|
||||||
data: data.data.files,
|
data: data.data.files,
|
||||||
success: true,
|
success: true,
|
||||||
total: data.data.files.length - (addParentShortcut ? 1 : 0)
|
total: data.data.files.length - (addParentShortcut ? 1 : 0)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setPath(getParentPath());
|
setPath(getParentPath(position));
|
||||||
return ({data: [], success: false, total: 0});
|
return ({data: [], success: false, total: 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +296,7 @@ function FileBrowser(props) {
|
|||||||
toolbar={{
|
toolbar={{
|
||||||
settings: [
|
settings: [
|
||||||
{
|
{
|
||||||
icon: <UploadOutlined />,
|
icon: <UploadOutlined/>,
|
||||||
tooltip: i18n.t('upload'),
|
tooltip: i18n.t('upload'),
|
||||||
key: 'upload',
|
key: 'upload',
|
||||||
onClick: uploadFile
|
onClick: uploadFile
|
||||||
@@ -268,12 +322,13 @@ function FileBrowser(props) {
|
|||||||
request={getData}
|
request={getData}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
actionRef={tableRef}
|
actionRef={tableRef}
|
||||||
|
components={virtualTable}
|
||||||
>
|
>
|
||||||
</ProTable>
|
</ProTable>
|
||||||
<input
|
<input
|
||||||
id='uploader'
|
id='uploader'
|
||||||
type='file'
|
type='file'
|
||||||
style={{ display: 'none' }}
|
style={{display: 'none'}}
|
||||||
onChange={onFileChange}
|
onChange={onFileChange}
|
||||||
/>
|
/>
|
||||||
<UploadModal
|
<UploadModal
|
||||||
@@ -283,10 +338,81 @@ function FileBrowser(props) {
|
|||||||
onSuccess={onUploadSuccess}
|
onSuccess={onUploadSuccess}
|
||||||
onCanel={onUploadCancel}
|
onCanel={onUploadCancel}
|
||||||
/>
|
/>
|
||||||
|
<Image
|
||||||
|
preview={{
|
||||||
|
visible: preview,
|
||||||
|
src: preview,
|
||||||
|
onVisibleChange: () => {
|
||||||
|
URL.revokeObjectURL(preview);
|
||||||
|
setPreview('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Navigator(props) {
|
||||||
|
let separator = props.isWindows ? '\\' : '/';
|
||||||
|
let path = [];
|
||||||
|
let pathItems = [];
|
||||||
|
let tempPath = props.path;
|
||||||
|
if (tempPath.endsWith(separator)) {
|
||||||
|
tempPath = tempPath.substring(0, tempPath.length - 1);
|
||||||
|
}
|
||||||
|
if (tempPath.length > 0 && tempPath !== '/' && tempPath !== '\\') {
|
||||||
|
path = tempPath.split(separator);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < path.length; i++) {
|
||||||
|
let name = path[i];
|
||||||
|
if (i === 0 && props.isWindows) {
|
||||||
|
if (name.endsWith(':')) {
|
||||||
|
name = name.substring(0, name.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pathItems.push({
|
||||||
|
name: name,
|
||||||
|
path: path.slice(0, i + 1).join(separator) + separator
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (path.length > 0 && props.isWindows) {
|
||||||
|
let first = path[0];
|
||||||
|
if (first.endsWith(':')) {
|
||||||
|
first = first.substring(0, first.length - 1);
|
||||||
|
}
|
||||||
|
path[0] = first;
|
||||||
|
}
|
||||||
|
pathItems.pop();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Breadcrumb
|
||||||
|
style={{marginLeft: '10px', marginRight: '10px'}}
|
||||||
|
disabled={props.loading}
|
||||||
|
>
|
||||||
|
<Breadcrumb.Item
|
||||||
|
style={{cursor: 'pointer'}}
|
||||||
|
onClick={props.onClick.bind(null, '/')}
|
||||||
|
>
|
||||||
|
<HomeOutlined/>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
{pathItems.map(item => (
|
||||||
|
<Breadcrumb.Item
|
||||||
|
key={item.path}
|
||||||
|
style={{cursor: 'pointer'}}
|
||||||
|
onClick={props.onClick.bind(null, item.path)}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
))}
|
||||||
|
{path.length > 0 ? (
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
{path[path.length - 1]}
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
) : null}
|
||||||
|
</Breadcrumb>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let abortController = null;
|
let abortController = null;
|
||||||
function UploadModal(props) {
|
function UploadModal(props) {
|
||||||
const [visible, setVisible] = useState(!!props.file);
|
const [visible, setVisible] = useState(!!props.file);
|
||||||
@@ -364,6 +490,7 @@ function UploadModal(props) {
|
|||||||
}, 1500);
|
}, 1500);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCancel() {
|
function onCancel() {
|
||||||
if (status === 0) {
|
if (status === 0) {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
@@ -413,7 +540,7 @@ function UploadModal(props) {
|
|||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
destroyOnClose={true}
|
destroyOnClose={true}
|
||||||
confirmLoading={status === 1}
|
confirmLoading={status === 1}
|
||||||
okText={i18n.t(status===1?'uploading':'upload')}
|
okText={i18n.t(status === 1 ? 'uploading' : 'upload')}
|
||||||
onOk={onConfirm}
|
onOk={onConfirm}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
okButtonProps={{disabled: status !== 0}}
|
okButtonProps={{disabled: status !== 0}}
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import React, {useEffect, useRef, useState} from 'react';
|
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
||||||
import {message, Modal, Popconfirm} from "antd";
|
import {message, Modal, Popconfirm} from "antd";
|
||||||
import ProTable from '@ant-design/pro-table';
|
import ProTable from '@ant-design/pro-table';
|
||||||
import {request, waitTime} from "../utils/utils";
|
import {request, waitTime} from "../utils/utils";
|
||||||
import i18n from "../locale/locale";
|
import i18n from "../locale/locale";
|
||||||
|
import {VList} from "virtuallist-antd";
|
||||||
|
|
||||||
function ProcessMgr(props) {
|
function ProcessMgr(props) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -37,6 +38,11 @@ function ProcessMgr(props) {
|
|||||||
setting: false,
|
setting: false,
|
||||||
};
|
};
|
||||||
const tableRef = useRef();
|
const tableRef = useRef();
|
||||||
|
const virtualTable = useMemo(() => {
|
||||||
|
return VList({
|
||||||
|
height: 300
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.visible) {
|
if (props.visible) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -113,6 +119,7 @@ function ProcessMgr(props) {
|
|||||||
request={getData}
|
request={getData}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
actionRef={tableRef}
|
actionRef={tableRef}
|
||||||
|
components={virtualTable}
|
||||||
>
|
>
|
||||||
</ProTable>
|
</ProTable>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@@ -192,6 +192,9 @@ class TerminalModal extends React.Component {
|
|||||||
if (data?.act === 'warn') {
|
if (data?.act === 'warn') {
|
||||||
message.warn(data.msg ? translate(data.msg) : i18n.t('unknownError'));
|
message.warn(data.msg ? translate(data.msg) : i18n.t('unknownError'));
|
||||||
}
|
}
|
||||||
|
if (data?.act === 'ping') {
|
||||||
|
this.sendData({act: 'pong'});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.ws.onclose = (e) => {
|
this.ws.onclose = (e) => {
|
||||||
|
@@ -60,6 +60,8 @@
|
|||||||
"fileOrDirNotExist": "File or folder does not exist",
|
"fileOrDirNotExist": "File or folder does not exist",
|
||||||
"fileOverwriteConfirm": "File [ {0} ] already exists, overwrite?",
|
"fileOverwriteConfirm": "File [ {0} ] already exists, overwrite?",
|
||||||
"fileOverwrite": "Overwrite",
|
"fileOverwrite": "Overwrite",
|
||||||
|
"fileTooLarge": "File is too large to read",
|
||||||
|
"fileEncodingUnsupported": "File encoding is not supported",
|
||||||
|
|
||||||
"host": "Host",
|
"host": "Host",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
|
@@ -61,6 +61,8 @@
|
|||||||
"fileOrDirNotExist": "文件或目录不存在",
|
"fileOrDirNotExist": "文件或目录不存在",
|
||||||
"fileOverwriteConfirm": "文件 [ {0} ] 已经存在,是否覆盖?",
|
"fileOverwriteConfirm": "文件 [ {0} ] 已经存在,是否覆盖?",
|
||||||
"fileOverwrite": "覆盖",
|
"fileOverwrite": "覆盖",
|
||||||
|
"fileTooLarge": "文件太大,读取失败",
|
||||||
|
"fileEncodingUnsupported": "不支持该文件编码",
|
||||||
|
|
||||||
"registryEditor": "注册表编辑器",
|
"registryEditor": "注册表编辑器",
|
||||||
"unknownRegistryKey": "注册表键有误",
|
"unknownRegistryKey": "注册表键有误",
|
||||||
|
@@ -103,11 +103,11 @@ module.exports = (env, args) => {
|
|||||||
hot: true,
|
hot: true,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api/': {
|
'/api/': {
|
||||||
target: 'http://localhost:8000/',
|
target: 'http://localhost:8001/',
|
||||||
secure: false
|
secure: false
|
||||||
},
|
},
|
||||||
'/api/device/terminal': {
|
'/api/device/terminal': {
|
||||||
target: 'ws://localhost:8000/',
|
target: 'ws://localhost:8001/',
|
||||||
ws: true
|
ws: true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user