Compare commits

...

58 Commits

Author SHA1 Message Date
lejianwen
0dbab182e9 fix(webclient): share fail when expire is 0
Closes: #88
2024-12-25 15:01:03 +08:00
lejianwen
512f3f99fd fix(build): fix build_test.yml 2024-12-25 14:06:29 +08:00
lejianwen
6fb4fad705 fix(build): up build.yml to build deb 2024-12-25 13:20:12 +08:00
lejianwen
fa92529e9b chore(build): up build.yml to build deb 2024-12-25 12:57:11 +08:00
Follow the wind
d6c6051a6c feat(build): 添加构建deb包相关基础 (#87)
* 添加构建deb包相关基础

* 补齐工作流,等待验证

* 修复构建时没有创建的data目录保障deb包构建

* 修复其余架构deb包构建中的依赖错误

* 修复:由于小改工作流导致写错架构的问题

* 修复拼写错误导致的目录错误

* 添加上传工件,和发布rel工作流,完成相关事务

---------

Co-authored-by: ymwl <ymwlpoolc@qq.com>
2024-12-25 12:28:51 +08:00
lejianwen
ce063bd3ac feat(webclient): v1.3.5 -> v1.3.6 2024-12-24 10:25:47 +08:00
lejianwen
4468894dfb chore(changelog): up build.yml to generate changelog 2024-12-22 14:14:51 +08:00
lejianwen
8b00b919ad chore(changelog): up build.yml to generate changelog 2024-12-22 14:04:23 +08:00
64f4a6dfac feat(i18n): Merge pull request #85 from jimmyGALLAND/trans-fr
add locale french
2024-12-22 13:57:56 +08:00
jimmyGALLAND
6faa5153b6 add locale french 2024-12-21 22:56:48 +01:00
lejianwen
a771b1e9b0 fix(webclient): remove console.log when query online by new 2024-12-21 21:49:03 +08:00
lejianwen
7750f9c54d chore(changelog): up build.yml to add changelog 2024-12-21 21:39:11 +08:00
lejianwen
b2d24ee67b docs(webclient): up readme 2024-12-21 21:31:54 +08:00
lejianwen
589a2a5123 feat(webclient): add new query_online function
There may be a loss of performance
Therefore, it is not enabled by default
2024-12-21 21:15:06 +08:00
lejianwen
184d3d357d optimize add ab from peer #84 2024-12-20 11:11:32 +08:00
lejianwen
50b3d85270 up docs and readme 2024-12-18 14:28:06 +08:00
lejianwen
09fdd34ba3 fix captcha 2024-12-18 13:51:06 +08:00
lejianwen
bba10261c5 fix captcha 2024-12-18 13:36:01 +08:00
lejianwen
46bfe54097 add show-swagger config #83 2024-12-18 12:50:09 +08:00
lejianwen
503e7a307e up docs 2024-12-18 12:44:06 +08:00
lejianwen
821b0a6faf add captcha #82 2024-12-18 12:43:55 +08:00
lejianwen
d60fdff179 split my from admin 2024-12-17 21:41:56 +08:00
lejianwen
fdd841e82a add batch add ab from peer and up my 2024-12-13 16:27:12 +08:00
lejianwen
2d6f0a116a add share record manage 2024-12-13 12:32:36 +08:00
lejianwen
bd13fe4ef4 up web client v2 2024-12-10 18:26:08 +08:00
lejianwen
6e1b208464 up README.md 2024-12-10 15:28:05 +08:00
lejianwen
76433a409e up README.md 2024-12-09 13:43:04 +08:00
lejianwen
9b4fa679c2 add batch add ab from peer
add batch update ab tags
2024-12-06 19:45:41 +08:00
lejianwen
c2ae95c4cc up api docs 2024-12-06 10:36:40 +08:00
lejianwen
b2b7f60fd5 add batch delete user token 2024-12-06 10:36:27 +08:00
lejianwen
a465888b31 up username length to 32 #70 2024-12-06 10:22:28 +08:00
lejianwen
d368bdc84c up web client v2 2024-12-04 13:43:04 +08:00
lejianwen
cdc1150505 up readme 2024-11-28 12:42:15 +08:00
32d525c53c Create LICENSE 2024-11-26 17:29:46 +08:00
lejianwen
a89b40c607 add es lang 2024-11-26 10:43:01 +08:00
lejianwen
b6bd9150d9 up web client v2 from rustdesk 2024-11-22 19:54:19 +08:00
lejianwen
96e3e3bc86 up docs 2024-11-22 19:53:49 +08:00
lejianwen
41377f41bb Split the language 2024-11-22 19:39:28 +08:00
lejianwen
fb744f81e2 up img 2024-11-20 19:33:27 +08:00
lejianwen
750c3bcbcd fix #62 2024-11-20 19:32:44 +08:00
lejianwen
d4015d7284 fix 2024-11-20 09:20:29 +08:00
lejianwen
a9bf3fda73 fix https://github.com/lejianwen/rustdesk-api/discussions/59#discussioncomment-11306760 2024-11-20 09:17:29 +08:00
lejianwen
7f467a4814 up web client v2 2024-11-18 21:39:18 +08:00
lejianwen
9f10b5e983 up readme 2024-11-17 18:27:15 +08:00
lejianwen
5291270e6a up ws connect in https #12 2024-11-17 17:34:51 +08:00
lejianwen
56bba381d8 fix 2024-11-16 22:08:06 +08:00
lejianwen
2ff276b162 up package lock 2024-11-16 20:56:26 +08:00
lejianwen
d77191ce0f up ws js 2024-11-16 20:03:30 +08:00
lejianwen
f99803aef4 up 2024-11-16 19:42:13 +08:00
lejianwen
e09fa17013 add webclientv2 2024-11-16 18:33:02 +08:00
lejianwen
c7e69aa8a5 fix gitignore 2024-11-15 21:43:23 +08:00
ljw
9548068283 up readme 2024-11-12 11:54:21 +08:00
ljw
09958c78f3 up readme 2024-11-12 11:53:57 +08:00
ljw
aced098982 add batch delete log #57 2024-11-12 09:08:10 +08:00
ljw
7862a34760 up admin conf 2024-11-12 08:43:49 +08:00
ljw
1384d28cac add admin conf 2024-11-11 22:26:15 +08:00
ljw
24b7338153 up readme 2024-11-09 20:38:11 +08:00
ljw
30d254eaef fix #55 2024-11-09 20:14:14 +08:00
177 changed files with 222669 additions and 1074 deletions

3
.gitattributes vendored
View File

@@ -1 +1,2 @@
resources/web/**/* linguist-vendored
resources/web/**/* linguist-vendored
resources/web2/**/* linguist-vendored

View File

@@ -55,6 +55,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/checkout@v4
with:
repository: lejianwen/rustdesk-api-web
path: rustdesk-api-web
ref: master
- name: Set up Go environment
uses: actions/setup-go@v4
@@ -66,14 +73,12 @@ jobs:
with:
node-version: '20'
- name: build rustdesk-api-web
working-directory: rustdesk-api-web
run: |
git clone ${{ env.WEBCLIENT_SOURCE_LOCATION }}
cd rustdesk-api-web
npm install
npm run build
mkdir ../resources/admin/ -p
mkdir -p ../resources/admin/
cp -ar dist/* ../resources/admin/
- name: tidy
@@ -121,15 +126,92 @@ jobs:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
# tag_name: ${{ env.LATEST_TAG }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate Changelog
run: npx changelogithub # or changelogithub@0.12 if ensure the stable result
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
deb-package:
name: debian package - ${{ matrix.job.platform }}
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", debian_platform: "amd64", crossbuild_package: ""}
- { platform: "arm64", goos: "linux", debian_platform: "arm64", crossbuild_package: "crossbuild-essential-arm64" }
- { platform: "armv7l", goos: "linux", debian_platform: "armhf", crossbuild_package: "crossbuild-essential-armhf" }
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Create packaging env
run: |
sudo apt update
DEBIAN_FRONTEND=noninteractive sudo apt install -y devscripts build-essential debhelper pkg-config ${{ matrix.job.crossbuild_package }}
mkdir -p debian-build/${{ matrix.job.platform }}/bin
- name: Get tag version
id: get_tag
run: |
TAG_VERSION="${GITHUB_REF##*/}"
VERSION="${TAG_VERSION#v}"
echo "TAG_VERSION=$TAG_VERSION" >> $GITHUB_ENV
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Update changelog
run: |
DATE=$(date -R)
sed -i "1i rustdesk-api-server (${VERSION}) stable; urgency=medium\n\n * Automatically generated release for version ${VERSION}.\n\n -- GitHub Actions <actions@github.com> ${DATE}\n" debian/changelog
- name: Download binaries
uses: actions/download-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: .
- name: Unzip binaries
run: |
mkdir -p ${{ matrix.job.platform }}
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
- name: Build package for ${{ matrix.job.platform }} arch
run: |
mv ${{ matrix.job.platform }}/release/apimain debian-build/${{ matrix.job.platform }}/bin/rustdesk-api
chmod -v a+x debian-build/${{ matrix.job.platform }}/bin/*
mkdir -p data
cp -vr debian systemd conf data resources runtime debian-build/${{ matrix.job.platform }}/
cat debian/control.tpl | sed 's/{{ ARCH }}/${{ matrix.job.debian_platform }}/' > debian-build/${{ matrix.job.platform }}/debian/control
cd debian-build/${{ matrix.job.platform }}/
debuild -i -us -uc -b -a${{ matrix.job.debian_platform}}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.debian_platform }}
path: |
debian-build/*.deb
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: |
debian-build/rustdesk-api-server_*_${{ matrix.job.debian_platform }}.deb
docker:
name: Push Docker Image
needs: build
@@ -191,7 +273,6 @@ jobs:
run: |
mkdir -p ${{ matrix.job.platform }}
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
file ${{ matrix.job.platform }}/apimain
- name: Build and push Docker image to Docker Hub ${{ matrix.job.platform }}
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false

View File

@@ -52,6 +52,12 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: lejianwen/rustdesk-api-web
path: rustdesk-api-web
ref: master
- name: Set up Go environment
uses: actions/setup-go@v4
with:
@@ -62,14 +68,12 @@ jobs:
with:
node-version: '20'
- name: build rustdesk-api-web
working-directory: rustdesk-api-web
run: |
git clone ${{ env.WEBCLIENT_SOURCE_LOCATION }}
cd rustdesk-api-web
npm install
npm run build
mkdir ../resources/admin/ -p
mkdir -p ../resources/admin/
cp -ar dist/* ../resources/admin/
- name: tidy
@@ -117,6 +121,7 @@ jobs:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.${{matrix.job.file_ext}}
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
@@ -126,6 +131,66 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
deb-package:
name: debian package - ${{ matrix.job.platform }}
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", debian_platform: "amd64", crossbuild_package: ""}
- { platform: "arm64", goos: "linux", debian_platform: "arm64", crossbuild_package: "crossbuild-essential-arm64" }
- { platform: "armv7l", goos: "linux", debian_platform: "armhf", crossbuild_package: "crossbuild-essential-armhf" }
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Create packaging env
run: |
sudo apt update
DEBIAN_FRONTEND=noninteractive sudo apt install -y devscripts build-essential debhelper pkg-config ${{ matrix.job.crossbuild_package }}
mkdir -p debian-build/${{ matrix.job.platform }}/bin
- name: Download binaries
uses: actions/download-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: .
- name: Unzip binaries
run: |
mkdir -p ${{ matrix.job.platform }}
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
- name: Build package for ${{ matrix.job.platform }} arch
run: |
mv ${{ matrix.job.platform }}/release/apimain debian-build/${{ matrix.job.platform }}/bin/rustdesk-api
chmod -v a+x debian-build/${{ matrix.job.platform }}/bin/*
mkdir -p data
cp -vr debian systemd conf data resources runtime debian-build/${{ matrix.job.platform }}/
cat debian/control.tpl | sed 's/{{ ARCH }}/${{ matrix.job.debian_platform }}/' > debian-build/${{ matrix.job.platform }}/debian/control
cd debian-build/${{ matrix.job.platform }}/
debuild -i -us -uc -b -a${{ matrix.job.debian_platform}}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.debian_platform }}
path: |
debian-build/*.deb
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: test
files: |
debian-build/rustdesk-api-server_*_${{ matrix.job.debian_platform }}.deb
docker:
name: Push Docker Image
needs: build
@@ -187,7 +252,6 @@ jobs:
run: |
mkdir -p ${{ matrix.job.platform }}
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
file ${{ matrix.job.platform }}/apimain
- name: Build and push Docker image to Docker Hub ${{ matrix.job.platform }}
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false

5
.gitignore vendored
View File

@@ -1,10 +1,13 @@
.idea
runtime/*
!runtime/cache/.gitignore
!runtime
!runtime/cache
!runtime/cache/.gitkeep
go.sum
resources/*
!resources/public/upload/.gitignore
!resources/web
!resources/web2
!resources/i18n
release
data

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024-present Lejianwen and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

278
README.md
View File

@@ -39,6 +39,9 @@
- 自动获取ID服务器和KEY
- 自动获取地址簿
- 游客通过临时分享链接直接远程到设备
- v2 Preview
- CLI
- 重置管理员密码
## 使用前准备
@@ -137,9 +140,12 @@
1. 如果已经登录了后台web client将自动直接登录
2. 如果没登录后台点击右上角登录即可api server已经自动配置好了
![webclient_conf](docs/webclient_conf.png)
3. 登录后会自动同步ID服务器和KEY
4. 登录后会将地址簿自动保存到web client中方便使用
5. 现已支持`v2 Preview`,访问路径是`/webclient2`
![webclientv2](./docs/webclientv2.png)
6. `v2 preview` 部署,参考[WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
### 自动化文档: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。
@@ -147,6 +153,17 @@
2. PC端文档 `<youer server[:port]>/swagger/index.html`
![api_swag](docs/api_swag.png)
### CLI
```bash
# 查看帮助
./apimain -h
```
#### 重置管理员密码
```bash
./apimain reset-admin-pwd <pwd>
```
## 安装与运行
### 相关配置
@@ -160,6 +177,7 @@ lang: "en"
app:
web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
show-swagger: 0 #是否显示swagger文档
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
@@ -192,32 +210,39 @@ proxy:
### 环境变量
变量名前缀是`RUSTDESK_API`,环境变量如果存在将覆盖配置文件中的配置
| 变量名 | 说明 | 示例 |
|------------------------------------|--------------------------------------|-----------------------------|
| TZ | 时区 | Asia/Shanghai |
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
| -----GIN配置----- | ---------- | ---------- |
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
| -----------GORM配置---------------- | ------------------------------------ | --------------------------- |
| RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql默认sqlite | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API 1:启用,0:不启用; 默认启用 | 1 |
| -----MYSQL配置----- | ---------- | ---------- |
| RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root |
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk |
| -----RUSTDESK配置----- | --------------- | ---------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 |
| ----PROXY配置----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |
| 变量名 | 说明 | 示例 |
|---------------------------------------------------|---------------------------------------------------------|------------------------------|
| TZ | 时区 | Asia/Shanghai |
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` |
| -----ADMIN配置----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | |
| RUSTDESK_API_ADMIN_HELLO_FILE | 后台欢迎语文件,如果内容多,使用文件更方便。<br>会覆盖`RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| -----GIN配置----- | ---------- | ---------- |
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
| -----------GORM配置---------------- | ------------------------------------ | --------------------------- |
| RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql默认sqlite | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API 1:启用,0:不启用; 默认启用 | 1 |
| -----MYSQL配置----- | ---------- | ---------- |
| RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root |
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk |
| -----RUSTDESK配置----- | --------------- | ---------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk存放key的文件 | `./conf/data/id_ed25519.pub` |
| RUSTDESK_API_RUSTDESK_WEBCLIENT_MAGIC_QUERYONLINE | Web client v2 中是否启用新的在线状态查询方法; `1`:启用,`0`:不启用,默认不启用 | `0` |
| ----PROXY配置----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |
### 运行
@@ -238,189 +263,8 @@ proxy:
lejianwen/rustdesk-api
```
2. 使用`docker compose`
- 简单示例
```yaml
services:
rustdesk-api:
container_name: rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=<key>
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
2. 使用`docker compose`,参考[WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
- 根据rustdesk官方提供的示例加上自己的rustdesk-api
- 如果是使用的系统生成的KEY去掉`-k <key>`参数,在启动后运行`docker-compose logs hbbs`或者`cat ./data/id_ed25519.pub`查看KEY然后再修改`RUSTDESK_API_RUSTDESK_KEY=<key>`再执行`docker-compose up -d`
```yaml
networks:
rustdesk-net:
external: false
services:
hbbs:
container_name: hbbs
ports:
- 21115:21115
- 21116:21116 # 自定义 hbbs 映射端口
- 21116:21116/udp # 自定义 hbbs 映射端口
- 21118:21118 # web client
image: rustdesk/rustdesk-server
command: hbbs -r <relay-server-ip[:port]> -k <key> # 填入个人域名或 IP + hbbr 暴露端口
volumes:
- ./data:/root # 自定义挂载目录
networks:
- rustdesk-net
depends_on:
- hbbr
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
hbbr:
container_name: hbbr
ports:
- 21117:21117 # 自定义 hbbr 映射端口
- 21119:21119 # web client
image: rustdesk/rustdesk-server
command: hbbr -k <key>
volumes:
- ./data:/root
networks:
- rustdesk-net
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
rustdesk-api:
container_name: rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=<key>
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
- S6的镜像
- 如果使用***自定义KEY***,会需要修改启动脚本,覆盖镜像中的`/etc/s6-overlay/s6-rc.d/hbbr/run`和`/etc/s6-overlay/s6-rc.d/hbbr/run`
1. 创建`hbbr/run`自定义KEY才需要
```bash
#!/command/with-contenv sh
cd /data
PARAMS=
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
/usr/bin/hbbr $PARAMS
```
2. 创建`hbbs/run`自定义KEY才需要
```bash
#!/command/with-contenv sh
sleep 2
cd /data
PARAMS=
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
/usr/bin/hbbs -r $RELAY $PARAMS
```
3. 修改`docker-compose.yml`中的`s6`部分
```yaml
networks:
rustdesk-net:
external: false
services:
rustdesk-server:
container_name: rustdesk-server
ports:
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21117:21117
- 21118:21118
- 21119:21119
image: rustdesk/rustdesk-server-s6:latest
environment:
- RELAY=192.168.1.66:21117
- ENCRYPTED_ONLY=1
- KEY=<key> #自定义KEY
volumes:
- ./data:/data
- ./hbbr/run:/etc/s6-overlay/s6-rc.d/hbbr/run
- ./hbbs/run:/etc/s6-overlay/s6-rc.d/hbbs/run
restart: unless-stopped
rustdesk-api:
container_name: rustdesk-api
ports:
- 21114:21114
image: lejianwen/rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=<key>
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载
networks:
- rustdesk-net
restart: unless-stopped
```
- 如果使用***系统生成的KEY***或者***自定义KEY_PUB,KEY_PRIV***不需要修改启动脚本但要在生成KEY后获取到KEY再`docker-compose up -d`
```yaml
networks:
rustdesk-net:
external: false
services:
rustdesk-server:
container_name: rustdesk-server
ports:
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21117:21117
- 21118:21118
- 21119:21119
image: rustdesk/rustdesk-server-s6:latest
environment:
- RELAY=192.168.1.66:21117
- ENCRYPTED_ONLY=1
volumes:
- ./data:/data
restart: unless-stopped
rustdesk-api:
container_name: rustdesk-api
ports:
- 21114:21114
image: lejianwen/rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=<key> #系统生成的KEY
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载
networks:
- rustdesk-net
restart: unless-stopped
```
#### 下载release直接运行
[下载地址](https://github.com/lejianwen/rustdesk-api/releases)
@@ -463,21 +307,7 @@ proxy:
6. 打开浏览器访问`http://<your server[:port]>/_admin/`,默认用户名密码为`admin`,请及时更改密码。
#### nginx反代
在`nginx`中配置反代
```
server {
listen <your port>;
server_name <your server>;
location / {
proxy_pass http://<api-server[:port]>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
## 其他
- [修改客户端ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)

View File

@@ -38,7 +38,8 @@ desktop software that provides self-hosted solutions.
- Automatically obtain ID server and KEY
- Automatically obtain address book
- Visitors are remotely to the device via a temporary sharing link
- CLI
- Reset admin password
## Prerequisites
### [Rustdesk](https://github.com/rustdesk/rustdesk)
@@ -143,9 +144,11 @@ installation are `admin` `admin`, please change the password immediately.
1. If you're already logged into the admin panel, the web client will log in automatically.
2. If you're not logged in, simply click the login button in the top right corner, and the API server will be
pre-configured.
![webclient_conf](docs/webclient_conf.png)
3. After logging in, the ID server and key will be automatically synced.
4. The address book will also be automatically saved to the web client for convenient use.
5. Now supports `v2 Preview`, accessible at `/webclient2`
![webclientv2](./docs/webclientv2.png)
6. `v2 preview` deployment, [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
### Automated Documentation : API documentation is generated using Swag, making it easier for developers to understand and use the API.
@@ -153,6 +156,17 @@ installation are `admin` `admin`, please change the password immediately.
2. PC client docs: `<your server[:port]>/swagger/index.html`
![api_swag](docs/api_swag.png)
### CLI
```bash
# help
./apimain -h
```
#### Reset admin password
```bash
./apimain reset-admin-pwd <pwd>
```
## Installation and Setup
### Configuration
@@ -166,6 +180,7 @@ lang: "en"
app:
web-client: 1 # web client route 1:open 0:close
register: false #register enable
show-swagger: 0 #show swagger 1:open 0:close
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
@@ -198,32 +213,39 @@ proxy:
### Environment Variables
The prefix for variable names is `RUSTDESK_API`. If environment variables exist, they will override the configurations in the configuration file.
| Variable Name | Description | Example |
|------------------------------------|-----------------------------------------------------------|-------------------------------|
| TZ | timezone | Asia/Shanghai |
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, deault 1 | 1 |
| RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` |
| ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 |
| ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
| ---- PROXY ----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
| Variable Name | Description | Example |
|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------------|-------------------------------|
| TZ | timezone | Asia/Shanghai |
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | web client on/off; 1: on, 0 off, default: 1 | 1 |
| RUSTDESK_API_APP_REGISTER | register enable; `true`, `false`; default:`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | swagger visible; 1: yes, 0: no; default: 0 | `0` |
| ----- ADMIN Configuration----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | Admin Title | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | Admin welcome message, you can use `html` | |
| RUSTDESK_API_ADMIN_HELLO_FILE | Admin welcome message file,<br>will override `RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| ----- GIN Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GIN_TRUST_PROXY | Trusted proxy IPs, separated by commas. | 192.168.1.2,192.168.1.3 |
| ----- GORM Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_GORM_TYPE | Database type (`sqlite` or `mysql`). Default is `sqlite`. | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | Maximum idle connections | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | Maximum open connections | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 |
| ----- MYSQL Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_MYSQL_USERNAME | MySQL username | root |
| RUSTDESK_API_MYSQL_PASSWORD | MySQL password | 111111 |
| RUSTDESK_API_MYSQL_ADDR | MySQL address | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | MySQL database name | rustdesk |
| ----- RUSTDESK Configuration ----- | --------------------------------------- | ----------------------------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk ID server address | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk relay server address | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` |
| RUSTDESK_API_RUSTDESK_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` |
| ---- PROXY ----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
### Installation Steps
@@ -243,189 +265,7 @@ The prefix for variable names is `RUSTDESK_API`. If environment variables exist,
lejianwen/rustdesk-api
```
2. Using `docker-compose`
- Simple example:
```yaml
services:
rustdesk-api:
container_name: rustdesk-api
environment:
- RUSTDESK_API_LANG=en
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=<key>
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data # Mount the database for easy backup
networks:
- rustdesk-net
restart: unless-stopped
```
- Example with RustDesk's official Docker Compose file, adding your `rustdesk-api` service:
- If you are using a system-generated KEY, remove the `-k <key>` parameter. However, after the first startup, run `docker-compose logs hbbs` or `cat ./data/id_ed25519.pub` to view the KEY, then modify `RUSTDESK_API_RUSTDESK_KEY=<key>` and execute `docker-compose up -d` again.
```yaml
networks:
rustdesk-net:
external: false
services:
hbbs:
container_name: hbbs
ports:
- 21115:21115
- 21116:21116 # 自定义 hbbs 映射端口
- 21116:21116/udp # 自定义 hbbs 映射端口
- 21118:21118 # web client
image: rustdesk/rustdesk-server
command: hbbs -r <relay-server-ip[:port]> -k <key> # 填入个人域名或 IP + hbbr 暴露端口
volumes:
- ./data:/root # 自定义挂载目录
networks:
- rustdesk-net
depends_on:
- hbbr
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
hbbr:
container_name: hbbr
ports:
- 21117:21117 # 自定义 hbbr 映射端口
- 21119:21119 # web client
image: rustdesk/rustdesk-server
command: hbbr -k <key>
volumes:
- ./data:/root
networks:
- rustdesk-net
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
rustdesk-api:
container_name: rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=<key>
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
- S6 image
- - If using ***custom KEY***, you will need to modify the startup script to override the `/etc/s6-overlay/s6-rc.d/hbbr/run` and `/etc/s6-overlay/s6-rc.d/hbbr/run` in the image.
1. Create `hbbr/run`, only needed for custom KEY
```bash
#!/command/with-contenv sh
cd /data
PARAMS=
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
/usr/bin/hbbr $PARAMS
```
2. Create `hbbs/run`, only needed for custom KEY
```bash
#!/command/with-contenv sh
sleep 2
cd /data
PARAMS=
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
/usr/bin/hbbs -r $RELAY $PARAMS
```
3. Modify the `s6` section in `docker-compose.yml`
```yaml
networks:
rustdesk-net:
external: false
services:
rustdesk-server:
container_name: rustdesk-server
ports:
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21117:21117
- 21118:21118
- 21119:21119
image: rustdesk/rustdesk-server-s6:latest
environment:
- RELAY=192.168.1.66:21117
- ENCRYPTED_ONLY=1
- KEY=<key> #KEY
volumes:
- ./data:/data
- ./hbbr/run:/etc/s6-overlay/s6-rc.d/hbbr/run
- ./hbbs/run:/etc/s6-overlay/s6-rc.d/hbbs/run
restart: unless-stopped
rustdesk-api:
container_name: rustdesk-api
ports:
- 21114:21114
image: lejianwen/rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=<key>
volumes:
- /data/rustdesk/api:/app/data
networks:
- rustdesk-net
restart: unless-stopped
```
- If using ***system-generated KEY*** or ***custom KEY_PUB, KEY_PRIV***, you do not need to modify the startup script, but you need to obtain the KEY after it is generated and then run `docker-compose up -d`
```yaml
networks:
rustdesk-net:
external: false
services:
rustdesk-server:
container_name: rustdesk-server
ports:
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21117:21117
- 21118:21118
- 21119:21119
image: rustdesk/rustdesk-server-s6:latest
environment:
- RELAY=192.168.1.66:21117
- ENCRYPTED_ONLY=1
volumes:
- ./data:/data
restart: unless-stopped
rustdesk-api:
container_name: rustdesk-api
ports:
- 21114:21114
image: lejianwen/rustdesk-api
environment:
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=<key>
volumes:
- /data/rustdesk/api:/app/data
networks:
- rustdesk-net
restart: unless-stopped
```
2. Using `docker-compose`,look [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
#### Running from Release
@@ -474,21 +314,7 @@ Download the release from [release](https://github.com/lejianwen/rustdesk-api/re
6. Open your browser and visit `http://<your server[:port]>/_admin/`, with default credentials `admin admin`. Please
change the password promptly.
#### nginx reverse proxy
Configure reverse proxy in `nginx`
```
server {
listen <your port>;
server_name <your server>;
location / {
proxy_pass http://<api-server[:port]>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
## Others
- [Change client ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)

1
conf/admin/hello.html Normal file
View File

@@ -0,0 +1 @@
👏👏👏 你好 <strong>{{username}}</strong>, 欢迎使用 <a href='https://github.com/lejianwen/rustdesk-api' target='_blank'>RustDesk Api Admin</a>

View File

@@ -2,6 +2,11 @@ lang: "zh-CN"
app:
web-client: 1 # 1:启用 0:禁用
register: false #是否开启注册
show-swagger: 0 # 1:启用 0:禁用
admin:
title: "RustDesk Api Admin"
hello-file: "./conf/admin/hello.html" #优先使用file
hello: ""
gin:
api-addr: "0.0.0.0:21114"
mode: "release" #release,debug,test
@@ -23,6 +28,7 @@ rustdesk:
key: ""
key-file: "./conf/data/id_ed25519.pub"
personal: 1
webclient-magic-queryonline: 0
logger:
path: "./runtime/log.txt"
level: "warn" #trace,debug,info,warn,error,fatal

View File

@@ -14,13 +14,19 @@ const (
)
type App struct {
WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"`
WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"`
ShowSwagger int `mapstructure:"show-swagger"`
}
type Admin struct {
Title string `mapstructure:"title"`
Hello string `mapstructure:"hello"`
HelloFile string `mapstructure:"hello-file"`
}
type Config struct {
Lang string `mapstructure:"lang"`
App App
Admin Admin
Gorm Gorm
Mysql Mysql
Gin Gin

View File

@@ -11,6 +11,8 @@ type Rustdesk struct {
Key string `mapstructure:"key"`
KeyFile string `mapstructure:"key-file"`
Personal int `mapstructure:"personal"`
//webclient-magic-queryonline
WebclientMagicQueryonline int `mapstructure:"webclient-magic-queryonline"`
}
func LoadKeyFile(rustdesk *Rustdesk) {

5
debian/changelog vendored Normal file
View File

@@ -0,0 +1,5 @@
rustdesk-api-server (1.3.6) UNRELEASED; urgency=medium
* Update the version to 1.3.6 to match the client.
-- rustdesk-api <ymwlpoolc@qq.com> Tue, 24 Dec 2024 13:48:34 +0800

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
10

13
debian/control.tpl vendored Normal file
View File

@@ -0,0 +1,13 @@
Source: rustdesk-api-server
Section: net
Priority: optional
Maintainer: ymwl <ymwlpoolc@qq.com>
Build-Depends: debhelper (>= 10), pkg-config
Standards-Version: 4.5.0
Homepage: https://github.com/lejianwen/rustdesk-api/
Package: rustdesk-api-server
Architecture: {{ ARCH }}
Depends: systemd ${misc:Depends}
Description: RustDesk api server
RustDesk api server, it is free and open source.

21
debian/copyright vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024-present Lejianwen and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

6
debian/rules vendored Normal file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/make -f
%:
dh $@
override_dh_builddeb:
dh_builddeb -- -Zgzip

6
debian/rustdesk-api-server.install vendored Normal file
View File

@@ -0,0 +1,6 @@
bin/rustdesk-api usr/bin
systemd/rustdesk-api.service lib/systemd/system
conf var/lib/rustdesk-api
data var/lib/rustdesk-api
resources var/lib/rustdesk-api
runtime var/lib/rustdesk-api

28
debian/rustdesk-api-server.postinst vendored Normal file
View File

@@ -0,0 +1,28 @@
#!/bin/sh
set -e
SERVICE=rustdesk-api.service
if [ "$1" = "configure" ]; then
mkdir -p /var/log/rustdesk-api
fi
case "$1" in
configure|abort-upgrade|abort-deconfigure|abort-remove)
mkdir -p /var/lib/rustdesk-api/
deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
if deb-systemd-helper --quiet was-enabled "${SERVICE}"; then
deb-systemd-invoke enable "${SERVICE}" >/dev/null || true
else
deb-systemd-invoke update-state "${SERVICE}" >/dev/null || true
fi
systemctl --system daemon-reload >/dev/null || true
if [ -n "$2" ]; then
deb-systemd-invoke restart "${SERVICE}" >/dev/null || true
else
deb-systemd-invoke start "${SERVICE}" >/dev/null || true
fi
;;
esac
exit 0

18
debian/rustdesk-api-server.postrm vendored Normal file
View File

@@ -0,0 +1,18 @@
#!/bin/sh
set -e
SERVICE=rustdesk-api.service
systemctl --system daemon-reload >/dev/null || true
if [ "$1" = "purge" ]; then
rm -rf /var/log/rustdesk-api/rustdesk-api.*
deb-systemd-helper purge "${SERVICE}" >/dev/null || true
deb-systemd-helper unmask "${SERVICE}" >/dev/null || true
fi
if [ "$1" = "remove" ]; then
deb-systemd-helper mask "${SERVICE}" >/dev/null || true
fi
exit 0

13
debian/rustdesk-api-server.prerm vendored Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/sh
set -e
SERVICE=rustdesk-api.service
case "$1" in
remove|deconfigure)
deb-systemd-invoke stop "${SERVICE}" >/dev/null || true
deb-systemd-invoke disable "${SERVICE}" >/dev/null || true
;;
esac
exit 0

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
3.0 (native)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -1075,6 +1075,40 @@ const docTemplateapi = `{
}
}
},
"/server-config-v2": {
"get": {
"security": [
{
"token": []
}
],
"description": "服务配置,给webclient提供api-server",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"WEBCLIENT_V2"
],
"summary": "服务配置",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/shared-peer": {
"post": {
"description": "分享的peer",
@@ -1356,7 +1390,7 @@ const docTemplateapi = `{
},
"password": {
"type": "string",
"maxLength": 20,
"maxLength": 32,
"minLength": 4
},
"type": {
@@ -1364,7 +1398,7 @@ const docTemplateapi = `{
},
"username": {
"type": "string",
"maxLength": 10,
"maxLength": 32,
"minLength": 2
},
"uuid": {

View File

@@ -1068,6 +1068,40 @@
}
}
},
"/server-config-v2": {
"get": {
"security": [
{
"token": []
}
],
"description": "服务配置,给webclient提供api-server",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"WEBCLIENT_V2"
],
"summary": "服务配置",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.Response"
}
}
}
}
},
"/shared-peer": {
"post": {
"description": "分享的peer",
@@ -1349,7 +1383,7 @@
},
"password": {
"type": "string",
"maxLength": 20,
"maxLength": 32,
"minLength": 4
},
"type": {
@@ -1357,7 +1391,7 @@
},
"username": {
"type": "string",
"maxLength": 10,
"maxLength": 32,
"minLength": 2
},
"uuid": {

View File

@@ -62,13 +62,13 @@ definitions:
id:
type: string
password:
maxLength: 20
maxLength: 32
minLength: 4
type: string
type:
type: string
username:
maxLength: 10
maxLength: 32
minLength: 2
type: string
uuid:
@@ -870,6 +870,27 @@ paths:
summary: 服务配置
tags:
- WEBCLIENT
/server-config-v2:
get:
consumes:
- application/json
description: 服务配置,给webclient提供api-server
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 服务配置
tags:
- WEBCLIENT_V2
/shared-peer:
post:
consumes:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/webclientv2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -3,15 +3,19 @@ package global
import (
"github.com/gin-gonic/gin"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/es"
"github.com/go-playground/locales/ko"
"github.com/go-playground/locales/ru"
"github.com/go-playground/locales/fr"
"github.com/go-playground/locales/zh_Hans_CN"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
en_translations "github.com/go-playground/validator/v10/translations/en"
es_translations "github.com/go-playground/validator/v10/translations/es"
ru_translations "github.com/go-playground/validator/v10/translations/ru"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
fr_translations "github.com/go-playground/validator/v10/translations/fr"
"reflect"
)
@@ -23,13 +27,17 @@ func ApiInitValidator() {
cn := zh_Hans_CN.New()
koT := ko.New()
ruT := ru.New()
esT := es.New()
frT := fr.New()
uni := ut.New(enT, cn, koT, ruT)
uni := ut.New(enT, cn, koT, ruT, esT, frT)
enTrans, _ := uni.GetTranslator("en")
zhTrans, _ := uni.GetTranslator("zh_Hans_CN")
koTrans, _ := uni.GetTranslator("ko")
ruTrans, _ := uni.GetTranslator("ru")
esTrans, _ := uni.GetTranslator("es")
frTrans, _ := uni.GetTranslator("fr")
err := zh_translations.RegisterDefaultTranslations(validate, zhTrans)
if err != nil {
@@ -49,6 +57,14 @@ func ApiInitValidator() {
if err != nil {
panic(err)
}
err = es_translations.RegisterDefaultTranslations(validate, esTrans)
if err != nil {
panic(err)
}
err = fr_translations.RegisterDefaultTranslations(validate, frTrans)
if err != nil {
panic(err)
}
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
label := field.Tag.Get("label")
@@ -115,6 +131,12 @@ func getTranslatorForLang(lang string) ut.Translator {
case "ru":
trans, _ := Validator.UT.GetTranslator("ru")
return trans
case "es":
trans, _ := Validator.UT.GetTranslator("es")
return trans
case "fr":
trans, _ := Validator.UT.GetTranslator("fr")
return trans
case "en":
fallthrough
default:

3
go.mod
View File

@@ -43,6 +43,7 @@ require (
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
@@ -58,6 +59,7 @@ require (
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mojocn/base64Captcha v1.3.6 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/spf13/afero v1.6.0 // indirect
@@ -69,6 +71,7 @@ require (
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect

View File

@@ -4,8 +4,8 @@ import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"encoding/json"
_ "encoding/json"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -30,11 +30,6 @@ func (ct *AddressBook) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.InfoByRowId(uint(iid))
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if t.RowId > 0 {
response.Success(c, t)
return
@@ -66,9 +61,9 @@ func (ct *AddressBook) Create(c *gin.Context) {
return
}
t := f.ToAddressBook()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
t.UserId = u.Id
if t.UserId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
@@ -98,7 +93,7 @@ func (ct *AddressBook) Create(c *gin.Context) {
// @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response
// @Router /admin/address_book/create [post]
// @Router /admin/address_book/batchCreate [post]
// @Security token
func (ct *AddressBook) BatchCreate(c *gin.Context) {
f := &admin.AddressBookForm{}
@@ -111,9 +106,21 @@ func (ct *AddressBook) BatchCreate(c *gin.Context) {
response.Fail(c, 101, errList[0])
return
}
ul := len(f.UserIds)
if ul == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if ul > 1 {
//多用户置空标签
f.Tags = []string{}
//多用户只能创建到默认地址簿
f.CollectionId = 0
}
//创建标签
for _, fu := range f.UserIds {
/*for _, fu := range f.UserIds {
if fu == 0 {
continue
}
@@ -126,13 +133,13 @@ func (ct *AddressBook) BatchCreate(c *gin.Context) {
})
}
}
}
}*/
ts := f.ToAddressBooks()
for _, t := range ts {
if t.UserId == 0 {
continue
}
ex := service.AllService.AddressBookService.InfoByUserIdAndId(t.UserId, t.Id)
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(t.UserId, t.Id, t.CollectionId)
if ex.RowId == 0 {
service.AllService.AddressBookService.Create(t)
}
@@ -161,10 +168,6 @@ func (ct *AddressBook) List(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
query.UserId = int(u.Id)
}
res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name")
@@ -190,11 +193,6 @@ func (ct *AddressBook) List(c *gin.Context) {
for _, ab := range res.AddressBooks {
abCIds = append(abCIds, ab.CollectionId)
}
//获取地址簿名称
//cRes := service.AllService.AddressBookService.ListCollection(1, 999, func(tx *gorm.DB) {
// tx.Where("id in ?", abCIds)
//})
//
response.Success(c, res)
}
@@ -221,15 +219,15 @@ func (ct *AddressBook) Update(c *gin.Context) {
return
}
if f.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
ex := service.AllService.AddressBookService.InfoByRowId(f.RowId)
if ex.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
t := f.ToAddressBook()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
@@ -270,21 +268,12 @@ func (ct *AddressBook) Delete(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
err := service.AllService.AddressBookService.Delete(t)
if err == nil {
response.Success(c, nil)
return
}
if u.Id > 0 {
err := service.AllService.AddressBookService.Delete(t)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}
// ShareByWebClient
@@ -327,3 +316,47 @@ func (ct *AddressBook) ShareByWebClient(c *gin.Context) {
"share_token": m.ShareToken,
})
}
func (ct *AddressBook) BatchCreateFromPeers(c *gin.Context) {
f := &admin.BatchCreateFromPeersForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.UserId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if f.CollectionId != 0 {
collection := service.AllService.AddressBookService.CollectionInfoById(f.CollectionId)
if collection.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
}
pl := int64(len(f.PeerIds))
peers := service.AllService.PeerService.List(1, uint(pl), func(tx *gorm.DB) {
tx.Where("row_id in ?", f.PeerIds)
})
if peers.Total == 0 || pl != peers.Total {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
tags, _ := json.Marshal(f.Tags)
for _, peer := range peers.Peers {
ab := service.AllService.AddressBookService.FromPeer(peer)
ab.Tags = tags
ab.CollectionId = f.CollectionId
ab.UserId = f.UserId
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(f.UserId, ab.Id, ab.CollectionId)
if ex.RowId != 0 {
continue
}
service.AllService.AddressBookService.Create(ab)
}
response.Success(c, nil)
}

View File

@@ -29,11 +29,6 @@ func (abc *AddressBookCollection) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.CollectionInfoById(uint(iid))
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if t.Id > 0 {
response.Success(c, t)
return
@@ -64,12 +59,11 @@ func (abc *AddressBookCollection) Create(c *gin.Context) {
response.Fail(c, 101, errList[0])
return
}
//t := f.ToAddressBookCollection()
t := f
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
t.UserId = u.Id
if f.UserId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f
err := service.AllService.AddressBookService.CreateCollection(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
@@ -98,10 +92,6 @@ func (abc *AddressBookCollection) List(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
query.UserId = int(u.Id)
}
res := service.AllService.AddressBookService.ListCollection(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
@@ -137,11 +127,6 @@ func (abc *AddressBookCollection) Update(c *gin.Context) {
return
}
t := f //f.ToAddressBookCollection()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.UpdateCollection(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
@@ -173,20 +158,15 @@ func (abc *AddressBookCollection) Delete(c *gin.Context) {
response.Fail(c, 101, errList[0])
return
}
t := service.AllService.AddressBookService.CollectionInfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if u.Id > 0 {
err := service.AllService.AddressBookService.DeleteCollection(t)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
err := service.AllService.AddressBookService.DeleteCollection(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}

View File

@@ -35,10 +35,6 @@ func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
query.UserId = int(u.Id)
}
res := service.AllService.AddressBookService.ListRules(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
@@ -66,17 +62,11 @@ func (abcr *AddressBookCollectionRule) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.RuleInfoById(uint(iid))
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if t.Id > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create 创建地址簿规则
@@ -105,13 +95,8 @@ func (abcr *AddressBookCollectionRule) Create(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
//t := f.ToAddressBookCollection()
t := f
u := service.AllService.UserService.CurUser(c)
if t.UserId == 0 {
t.UserId = u.Id
}
msg, res := abcr.CheckForm(u, t)
msg, res := abcr.CheckForm(t)
if !res {
response.Fail(c, 101, response.TranslateMsg(c, msg))
return
@@ -124,9 +109,9 @@ func (abcr *AddressBookCollectionRule) Create(c *gin.Context) {
response.Success(c, nil)
}
func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.AddressBookCollectionRule) (string, bool) {
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
return "NoAccess", false
func (abcr *AddressBookCollectionRule) CheckForm(t *model.AddressBookCollectionRule) (string, bool) {
if t.UserId == 0 {
return "ParamsError", false
}
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
return "ParamsError", false
@@ -141,15 +126,7 @@ func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.Address
if tou.Id == 0 {
return "ItemNotFound", false
}
//非管理员不能分享给非本组织用户
if tou.GroupId != u.GroupId && !service.AllService.UserService.IsAdmin(u) {
return "NoAccess", false
}
} else if t.Type == model.ShareAddressBookRuleTypeGroup {
if t.ToId != u.GroupId && !service.AllService.UserService.IsAdmin(u) {
return "NoAccess", false
}
tog := service.AllService.GroupService.InfoById(t.ToId)
if tog.Id == 0 {
return "ItemNotFound", false
@@ -194,9 +171,8 @@ func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f //f.ToAddressBookCollection()
u := service.AllService.UserService.CurUser(c)
msg, res := abcr.CheckForm(u, t)
t := f
msg, res := abcr.CheckForm(t)
if !res {
response.Fail(c, 101, response.TranslateMsg(c, msg))
return
@@ -232,20 +208,15 @@ func (abcr *AddressBookCollectionRule) Delete(c *gin.Context) {
response.Fail(c, 101, errList[0])
return
}
t := service.AllService.AddressBookService.RuleInfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
ex := service.AllService.AddressBookService.RuleInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if t.Id > 0 {
err := service.AllService.AddressBookService.DeleteRule(t)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
err := service.AllService.AddressBookService.DeleteRule(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}

View File

@@ -81,6 +81,37 @@ func (a *Audit) ConnDelete(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchConnDelete 删除
// @Tags 链接日志
// @Summary 链接日志批量删除
// @Description 链接日志批量删除
// @Accept json
// @Produce json
// @Param body body admin.AuditConnLogIds true "链接日志"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/audit_conn/batchDelete [post]
// @Security token
func (a *Audit) BatchConnDelete(c *gin.Context) {
f := &admin.AuditConnLogIds{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.AuditService.BatchDeleteAuditConn(f.Ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
// FileList 列表
// @Tags 文件日志
// @Summary 文件日志列表
@@ -93,7 +124,7 @@ func (a *Audit) ConnDelete(c *gin.Context) {
// @Param from_peer query int false "来源设备"
// @Success 200 {object} response.Response{data=model.AuditFileList}
// @Failure 500 {object} response.Response
// @Router /admin/audit_conn/list [get]
// @Router /admin/audit_file/list [get]
// @Security token
func (a *Audit) FileList(c *gin.Context) {
query := &admin.AuditQuery{}
@@ -122,7 +153,7 @@ func (a *Audit) FileList(c *gin.Context) {
// @Param body body model.AuditFile true "文件日志信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/audit_conn/delete [post]
// @Router /admin/audit_file/delete [post]
// @Security token
func (a *Audit) FileDelete(c *gin.Context) {
f := &model.AuditFile{}
@@ -148,3 +179,34 @@ func (a *Audit) FileDelete(c *gin.Context) {
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchFileDelete 删除
// @Tags 文件日志
// @Summary 文件日志批量删除
// @Description 文件日志批量删除
// @Accept json
// @Produce json
// @Param body body admin.AuditFileLogIds true "文件日志"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/audit_file/batchDelete [post]
// @Security token
func (a *Audit) BatchFileDelete(c *gin.Context) {
f := &admin.AuditFileLogIds{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.AuditService.BatchDeleteAuditFile(f.Ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}

View File

@@ -0,0 +1,79 @@
package admin
import (
"Gwen/global"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"os"
"strings"
)
type Config struct {
}
// ServerConfig RUSTDESK服务配置
// @Tags ADMIN
// @Summary RUSTDESK服务配置
// @Description 服务配置,给webclient提供api-server
// @Accept json
// @Produce json
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/config/server [get]
// @Security token
func (co *Config) ServerConfig(c *gin.Context) {
cf := &response.ServerConfigResponse{
IdServer: global.Config.Rustdesk.IdServer,
Key: global.Config.Rustdesk.Key,
RelayServer: global.Config.Rustdesk.RelayServer,
ApiServer: global.Config.Rustdesk.ApiServer,
}
response.Success(c, cf)
}
// AppConfig APP服务配置
// @Tags ADMIN
// @Summary APP服务配置
// @Description APP服务配置
// @Accept json
// @Produce json
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/config/app [get]
// @Security token
func (co *Config) AppConfig(c *gin.Context) {
response.Success(c, &gin.H{
"web_client": global.Config.App.WebClient,
})
}
// AdminConfig ADMIN服务配置
// @Tags ADMIN
// @Summary ADMIN服务配置
// @Description ADMIN服务配置
// @Accept json
// @Produce json
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/config/admin [get]
// @Security token
func (co *Config) AdminConfig(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
hello := global.Config.Admin.Hello
helloFile := global.Config.Admin.HelloFile
if helloFile != "" {
b, err := os.ReadFile(helloFile)
if err == nil && len(b) > 0 {
hello = string(b)
}
}
//replace {{username}} to username
hello = strings.Replace(hello, "{{username}}", u.Username, -1)
response.Success(c, &gin.H{
"title": global.Config.Admin.Title,
"hello": hello,
})
}

View File

@@ -11,11 +11,135 @@ import (
"Gwen/service"
"fmt"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
"sync"
"time"
)
type Login struct {
}
// Captcha 验证码结构
type Captcha struct {
Id string `json:"id"` // 验证码 ID
B64 string `json:"b64"` // base64 验证码
Code string `json:"-"` // 验证码内容
ExpiresAt time.Time `json:"-"` // 过期时间
}
type LoginLimiter struct {
mu sync.RWMutex
failCount map[string]int // 记录每个 IP 的失败次数
timestamp map[string]time.Time // 记录每个 IP 的最后失败时间
captchas map[string]Captcha // 每个 IP 的验证码
threshold int // 失败阈值
expiry time.Duration // 失败记录过期时间
}
func NewLoginLimiter(threshold int, expiry time.Duration) *LoginLimiter {
return &LoginLimiter{
failCount: make(map[string]int),
timestamp: make(map[string]time.Time),
captchas: make(map[string]Captcha),
threshold: threshold,
expiry: expiry,
}
}
// RecordFailure 记录登录失败
func (l *LoginLimiter) RecordFailure(ip string) {
l.mu.Lock()
defer l.mu.Unlock()
// 如果该 IP 的记录已经过期,重置计数
if lastTime, exists := l.timestamp[ip]; exists && time.Since(lastTime) > l.expiry {
l.failCount[ip] = 0
}
// 更新失败次数和时间戳
l.failCount[ip]++
l.timestamp[ip] = time.Now()
}
// NeedsCaptcha 检查是否需要验证码
func (l *LoginLimiter) NeedsCaptcha(ip string) bool {
l.mu.RLock()
defer l.mu.RUnlock()
// 检查记录是否存在且未过期
if lastTime, exists := l.timestamp[ip]; exists && time.Since(lastTime) <= l.expiry {
return l.failCount[ip] >= l.threshold
}
return false
}
// GenerateCaptcha 为指定 IP 生成验证码
func (l *LoginLimiter) GenerateCaptcha(ip string) Captcha {
l.mu.Lock()
defer l.mu.Unlock()
capd := base64Captcha.NewDriverString(50, 150, 5, 10, 4, "1234567890abcdefghijklmnopqrstuvwxyz", nil, nil, nil)
b64cap := base64Captcha.NewCaptcha(capd, base64Captcha.DefaultMemStore)
id, b64s, answer, err := b64cap.Generate()
if err != nil {
global.Logger.Error("Generate captcha failed: " + err.Error())
return Captcha{}
}
// 保存验证码到对应 IP
l.captchas[ip] = Captcha{
Id: id,
B64: b64s,
Code: answer,
ExpiresAt: time.Now().Add(5 * time.Minute),
}
return l.captchas[ip]
}
// VerifyCaptcha 验证指定 IP 的验证码
func (l *LoginLimiter) VerifyCaptcha(ip, code string) bool {
l.mu.RLock()
defer l.mu.RUnlock()
// 检查验证码是否存在且未过期
if captcha, exists := l.captchas[ip]; exists && time.Now().Before(captcha.ExpiresAt) {
return captcha.Code == code
}
return false
}
// RemoveCaptcha 移除指定 IP 的验证码
func (l *LoginLimiter) RemoveCaptcha(ip string) {
l.mu.Lock()
defer l.mu.Unlock()
delete(l.captchas, ip)
}
// CleanupExpired 清理过期的记录
func (l *LoginLimiter) CleanupExpired() {
l.mu.Lock()
defer l.mu.Unlock()
now := time.Now()
for ip, lastTime := range l.timestamp {
if now.Sub(lastTime) > l.expiry {
delete(l.failCount, ip)
delete(l.timestamp, ip)
delete(l.captchas, ip)
}
}
}
func (l *LoginLimiter) RemoveRecord(ip string) {
l.mu.Lock()
defer l.mu.Unlock()
delete(l.failCount, ip)
delete(l.timestamp, ip)
delete(l.captchas, ip)
}
var loginLimiter = NewLoginLimiter(3, 5*time.Minute)
// Login 登录
// @Tags 登录
// @Summary 登录
@@ -30,22 +154,39 @@ type Login struct {
func (ct *Login) Login(c *gin.Context) {
f := &admin.Login{}
err := c.ShouldBindJSON(f)
clientIp := c.ClientIP()
if err != nil {
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), c.ClientIP()))
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), clientIp))
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), c.ClientIP()))
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), clientIp))
response.Fail(c, 101, errList[0])
return
}
// 检查是否需要验证码
if loginLimiter.NeedsCaptcha(clientIp) {
if f.Captcha == "" || !loginLimiter.VerifyCaptcha(clientIp, f.Captcha) {
response.Fail(c, 101, response.TranslateMsg(c, "CaptchaError"))
return
}
}
u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password)
if u.Id == 0 {
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), c.ClientIP()))
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), clientIp))
loginLimiter.RecordFailure(clientIp)
if loginLimiter.NeedsCaptcha(clientIp) {
// 移除原验证码,重新生成
loginLimiter.RemoveCaptcha(clientIp)
response.Fail(c, 110, response.TranslateMsg(c, "UsernameOrPasswordError"))
return
}
response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError"))
return
}
@@ -54,13 +195,30 @@ func (ct *Login) Login(c *gin.Context) {
UserId: u.Id,
Client: model.LoginLogClientWebAdmin,
Uuid: "", //must be empty
Ip: c.ClientIP(),
Ip: clientIp,
Type: model.LoginLogTypeAccount,
Platform: f.Platform,
})
// 成功后清除记录
loginLimiter.RemoveRecord(clientIp)
// 清理过期记录
go loginLimiter.CleanupExpired()
responseLoginSuccess(c, u, ut.Token)
}
func (ct *Login) Captcha(c *gin.Context) {
clientIp := c.ClientIP()
if !loginLimiter.NeedsCaptcha(clientIp) {
response.Fail(c, 101, response.TranslateMsg(c, "NoCaptchaRequired"))
return
}
captcha := loginLimiter.GenerateCaptcha(clientIp)
response.Success(c, gin.H{
"captcha": captcha,
})
}
// Logout 登出
// @Tags 登录
@@ -90,10 +248,12 @@ func (ct *Login) Logout(c *gin.Context) {
// @Failure 500 {object} response.ErrorResponse
// @Router /admin/login-options [post]
func (ct *Login) LoginOptions(c *gin.Context) {
ip := c.ClientIP()
ops := service.AllService.OauthService.GetOauthProviders()
response.Success(c, gin.H{
"ops": ops,
"register": global.Config.App.Register,
"ops": ops,
"register": global.Config.App.Register,
"need_captcha": loginLimiter.NeedsCaptcha(ip),
})
}
@@ -154,11 +314,10 @@ func (ct *Login) OidcAuthQuery(c *gin.Context) {
responseLoginSuccess(c, u, ut.Token)
}
func responseLoginSuccess(c *gin.Context, u *model.User, token string) {
lp := &adResp.LoginPayload{}
lp.FromUser(u)
lp.Token = token
lp.RouteNames = service.AllService.UserService.RouteNames(u)
response.Success(c, lp)
}
}

View File

@@ -109,3 +109,34 @@ func (ct *LoginLog) Delete(c *gin.Context) {
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchDelete 删除
// @Tags 登录日志
// @Summary 登录日志批量删除
// @Description 登录日志批量删除
// @Accept json
// @Produce json
// @Param body body admin.LoginLogIds true "登录日志"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/login_log/delete [post]
// @Security token
func (ct *LoginLog) BatchDelete(c *gin.Context) {
f := &admin.LoginLogIds{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.LoginLogService.BatchDelete(f.Ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}

View File

@@ -0,0 +1,271 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"encoding/json"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type AddressBook struct{}
// List 列表
// @Tags 我的地址簿
// @Summary 地址簿列表
// @Description 地址簿列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param user_id query int false "用户id"
// @Success 200 {object} response.Response{data=model.AddressBookList}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book/list [get]
// @Security token
func (ct *AddressBook) List(c *gin.Context) {
query := &admin.AddressBookQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
query.UserId = int(u.Id)
res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
//预加载地址簿名称
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name")
})
if query.Id != "" {
tx.Where("id like ?", "%"+query.Id+"%")
}
tx.Where("user_id = ?", query.UserId)
if query.Username != "" {
tx.Where("username like ?", "%"+query.Username+"%")
}
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.CollectionId != nil && *query.CollectionId >= 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
abCIds := make([]uint, 0)
for _, ab := range res.AddressBooks {
abCIds = append(abCIds, ab.CollectionId)
}
response.Success(c, res)
}
// Create 创建地址簿
// @Tags 我的地址簿
// @Summary 创建地址簿
// @Description 创建地址簿
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book/create [post]
// @Security token
func (ct *AddressBook) Create(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
t := f.ToAddressBook()
u := service.AllService.UserService.CurUser(c)
t.UserId = u.Id
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(t.UserId, t.Id, t.CollectionId)
if ex.RowId > 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemExists"))
return
}
err := service.AllService.AddressBookService.Create(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Update 编辑
// @Tags 我的地址簿
// @Summary 地址簿编辑
// @Description 地址簿编辑
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book/update [post]
// @Security token
func (ct *AddressBook) Update(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
if f.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
ex := service.AllService.AddressBookService.InfoByRowId(f.RowId)
if ex.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
t := f.ToAddressBook()
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.AddressBookService.UpdateAll(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 我的地址簿
// @Summary 地址簿删除
// @Description 地址簿删除
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book/delete [post]
// @Security token
func (ct *AddressBook) Delete(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.RowId
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.AddressBookService.InfoByRowId(f.RowId)
if ex.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.Delete(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
func (ct *AddressBook) BatchCreateFromPeers(c *gin.Context) {
f := &admin.BatchCreateFromPeersForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if f.CollectionId != 0 {
collection := service.AllService.AddressBookService.CollectionInfoById(f.CollectionId)
if collection.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if collection.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
}
if len(f.PeerIds) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
pl := int64(len(f.PeerIds))
peers := service.AllService.PeerService.List(1, uint(pl), func(tx *gorm.DB) {
tx.Where("row_id in ?", f.PeerIds)
tx.Where("user_id = ?", u.Id)
})
if peers.Total == 0 || pl != peers.Total {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
tags, _ := json.Marshal(f.Tags)
for _, peer := range peers.Peers {
ab := service.AllService.AddressBookService.FromPeer(peer)
ab.Tags = tags
ab.CollectionId = f.CollectionId
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(u.Id, ab.Id, ab.CollectionId)
if ex.RowId != 0 {
continue
}
service.AllService.AddressBookService.Create(ab)
}
response.Success(c, nil)
}
func (ct *AddressBook) BatchUpdateTags(c *gin.Context) {
f := &admin.BatchUpdateTagsForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
abs := service.AllService.AddressBookService.List(1, 999, func(tx *gorm.DB) {
tx.Where("row_id in ?", f.RowIds)
tx.Where("user_id = ?", u.Id)
})
if abs.Total == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.AddressBookService.BatchUpdateTags(abs.AddressBooks, f.Tags)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}

View File

@@ -0,0 +1,162 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type AddressBookCollection struct {
}
// Create 创建地址簿名称
// @Tags 我的地址簿名称
// @Summary 创建地址簿名称
// @Description 创建地址簿名称
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/create [post]
// @Security token
func (abc *AddressBookCollection) Create(c *gin.Context) {
f := &model.AddressBookCollection{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.CurUser(c)
f.UserId = u.Id
err := service.AllService.AddressBookService.CreateCollection(f)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// List 列表
// @Tags 我的地址簿名称
// @Summary 地址簿名称列表
// @Description 地址簿名称列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/list [get]
// @Security token
func (abc *AddressBookCollection) List(c *gin.Context) {
query := &admin.AddressBookCollectionQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
query.UserId = int(u.Id)
res := service.AllService.AddressBookService.ListCollection(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Where("user_id = ?", query.UserId)
})
response.Success(c, res)
}
// Update 编辑
// @Tags 我的地址簿名称
// @Summary 地址簿名称编辑
// @Description 地址簿名称编辑
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/update [post]
// @Security token
func (abc *AddressBookCollection) Update(c *gin.Context) {
f := &model.AddressBookCollection{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
if f.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.UpdateCollection(f)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 我的地址簿名称
// @Summary 地址簿名称删除
// @Description 地址簿名称删除
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "地址簿名称信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection/delete [post]
// @Security token
func (abc *AddressBookCollection) Delete(c *gin.Context) {
f := &model.AddressBookCollection{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.AddressBookService.CollectionInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.DeleteCollection(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}

View File

@@ -0,0 +1,228 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type AddressBookCollectionRule struct {
}
// List 列表
// @Tags 我的地址簿规则
// @Summary 地址簿规则列表
// @Description 地址簿规则列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param is_my query int false "是否是我的"
// @Param user_id query int false "用户id"
// @Param collection_id query int false "地址簿集合id"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/list [get]
// @Security token
func (abcr *AddressBookCollectionRule) List(c *gin.Context) {
query := &admin.AddressBookCollectionRuleQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
query.UserId = int(u.Id)
res := service.AllService.AddressBookService.ListRules(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Where("user_id = ?", query.UserId)
if query.CollectionId > 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
response.Success(c, res)
}
// Create 创建地址簿规则
// @Tags 我的地址簿规则
// @Summary 创建地址簿规则
// @Description 创建地址簿规则
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/create [post]
// @Security token
func (abcr *AddressBookCollectionRule) Create(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Type != model.ShareAddressBookRuleTypePersonal && f.Type != model.ShareAddressBookRuleTypeGroup {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
//t := f.ToAddressBookCollection()
t := f
u := service.AllService.UserService.CurUser(c)
t.UserId = u.Id
msg, res := abcr.CheckForm(u, t)
if !res {
response.Fail(c, 101, response.TranslateMsg(c, msg))
return
}
err := service.AllService.AddressBookService.CreateRule(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
func (abcr *AddressBookCollectionRule) CheckForm(u *model.User, t *model.AddressBookCollectionRule) (string, bool) {
if t.UserId != u.Id {
return "NoAccess", false
}
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
return "ParamsError", false
}
//check to_id
if t.Type == model.ShareAddressBookRuleTypePersonal {
if t.ToId == t.UserId {
return "ParamsError", false
}
tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 {
return "ItemNotFound", false
}
//非管理员不能分享给非本组织用户
if tou.GroupId != u.GroupId {
return "NoAccess", false
}
} else if t.Type == model.ShareAddressBookRuleTypeGroup {
//非管理员不能分享给其他组
if t.ToId != u.GroupId {
return "NoAccess", false
}
tog := service.AllService.GroupService.InfoById(t.ToId)
if tog.Id == 0 {
return "ItemNotFound", false
}
} else {
return "ParamsError", false
}
// 重复检查
ex := service.AllService.AddressBookService.RulePersonalInfoByToIdAndCid(t.ToId, t.CollectionId)
if t.Id == 0 && ex.Id > 0 {
return "ItemExists", false
}
if t.Id > 0 && ex.Id > 0 && t.Id != ex.Id {
return "ItemExists", false
}
return "", true
}
// Update 编辑
// @Tags 我的地址簿规则
// @Summary 地址簿规则编辑
// @Description 地址簿规则编辑
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/update [post]
// @Security token
func (abcr *AddressBookCollectionRule) Update(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
ex := service.AllService.AddressBookService.RuleInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
t := f
msg, res := abcr.CheckForm(u, t)
if !res {
response.Fail(c, 101, response.TranslateMsg(c, msg))
return
}
err := service.AllService.AddressBookService.UpdateRule(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 我的地址簿规则
// @Summary 地址簿规则删除
// @Description 地址簿规则删除
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "地址簿规则信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/address_book_collection_rule/delete [post]
// @Security token
func (abcr *AddressBookCollectionRule) Delete(c *gin.Context) {
f := &model.AddressBookCollectionRule{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.AddressBookService.RuleInfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.DeleteRule(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}

View File

@@ -0,0 +1,59 @@
package my
import (
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"time"
)
type Peer struct {
}
// List 列表
// @Tags 我的设备
// @Summary 设备列表
// @Description 设备列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param time_ago query int false "时间"
// @Param id query string false "ID"
// @Param hostname query string false "主机名"
// @Param uuids query string false "uuids 用逗号分隔"
// @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response
// @Router /admin/my/peer/list [get]
// @Security token
func (ct *Peer) List(c *gin.Context) {
query := &admin.PeerQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
res := service.AllService.PeerService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Where("user_id = ?", u.Id)
if query.TimeAgo > 0 {
lt := time.Now().Unix() - int64(query.TimeAgo)
tx.Where("last_online_time < ?", lt)
}
if query.TimeAgo < 0 {
lt := time.Now().Unix() + int64(query.TimeAgo)
tx.Where("last_online_time > ?", lt)
}
if query.Id != "" {
tx.Where("id like ?", "%"+query.Id+"%")
}
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.Uuids != "" {
tx.Where("uuid in (?)", query.Uuids)
}
})
response.Success(c, res)
}

View File

@@ -0,0 +1,119 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type ShareRecord struct {
}
// List 分享记录列表
// @Tags 我的分享记录
// @Summary 分享记录列表
// @Description 分享记录列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/share_record/list [get]
// @Security token
func (sr *ShareRecord) List(c *gin.Context) {
query := &admin.PageQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
res := service.AllService.ShareRecordService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Where("user_id = ?", u.Id)
})
response.Success(c, res)
}
// Delete 分享记录删除
// @Tags 我的分享记录
// @Summary 分享记录删除
// @Description 分享记录删除
// @Accept json
// @Produce json
// @Param body body admin.ShareRecordForm true "分享记录信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/share_record/delete [post]
// @Security token
func (sr *ShareRecord) Delete(c *gin.Context) {
f := &admin.ShareRecordForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.CurUser(c)
i := service.AllService.ShareRecordService.InfoById(f.Id)
if i.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if i.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.ShareRecordService.Delete(i)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}
// BatchDelete 批量删除我的分享记录
// @Tags 我的
// @Summary 批量删除我的分享记录
// @Description 批量删除我的分享记录
// @Accept json
// @Produce json
// @Param body body admin.PeerShareRecordBatchDeleteForm true "id"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/share_record/batchDelete [post]
// @Security token
func (sr *ShareRecord) BatchDelete(c *gin.Context) {
f := &admin.PeerShareRecordBatchDeleteForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
var l int64
l = int64(len(f.Ids))
res := service.AllService.ShareRecordService.List(1, uint(l), func(tx *gorm.DB) {
tx.Where("user_id = ?", u.Id)
tx.Where("id in ?", f.Ids)
})
if res.Total != l {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.ShareRecordService.BatchDelete(f.Ids)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}

View File

@@ -0,0 +1,176 @@
package my
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type Tag struct{}
// List 列表
// @Tags 我的标签
// @Summary 标签列表
// @Description 标签列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param is_my query int false "是否是我的"
// @Param user_id query int false "用户id"
// @Success 200 {object} response.Response{data=model.TagList}
// @Failure 500 {object} response.Response
// @Router /admin/my/tag/list [get]
// @Security token
func (ct *Tag) List(c *gin.Context) {
query := &admin.TagQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
query.UserId = int(u.Id)
res := service.AllService.TagService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name")
})
tx.Where("user_id = ?", query.UserId)
if query.CollectionId != nil && *query.CollectionId >= 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
response.Success(c, res)
}
// Create 创建标签
// @Tags 我的标签
// @Summary 创建标签
// @Description 创建标签
// @Accept json
// @Produce json
// @Param body body admin.TagForm true "标签信息"
// @Success 200 {object} response.Response{data=model.Tag}
// @Failure 500 {object} response.Response
// @Router /admin/my/tag/create [post]
// @Security token
func (ct *Tag) Create(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
t := f.ToTag()
u := service.AllService.UserService.CurUser(c)
t.UserId = u.Id
err := service.AllService.TagService.Create(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Update 编辑
// @Tags 我的标签
// @Summary 标签编辑
// @Description 标签编辑
// @Accept json
// @Produce json
// @Param body body admin.TagForm true "标签信息"
// @Success 200 {object} response.Response{data=model.Tag}
// @Failure 500 {object} response.Response
// @Router /admin/my/tag/update [post]
// @Security token
func (ct *Tag) Update(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
u := service.AllService.UserService.CurUser(c)
if f.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
ex := service.AllService.TagService.InfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
t := f.ToTag()
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.TagService.Update(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 标签
// @Summary 标签删除
// @Description 标签删除
// @Accept json
// @Produce json
// @Param body body admin.TagForm true "标签信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/tag/delete [post]
// @Security token
func (ct *Tag) Delete(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
ex := service.AllService.TagService.InfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if ex.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.TagService.Delete(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}

View File

@@ -0,0 +1,105 @@
package admin
import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type ShareRecord struct {
}
// List 列表
// @Tags 分享记录
// @Summary 分享记录列表
// @Description 分享记录列表
// @Accept json
// @Produce json
// @Param user_id query int false "用户ID"
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/share_record/list [get]
// @Security token
func (sr *ShareRecord) List(c *gin.Context) {
query := &admin.ShareRecordQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.ShareRecordService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
})
response.Success(c, res)
}
// Delete 删除
// @Tags 分享记录
// @Summary 分享记录删除
// @Description 分享记录删除
// @Accept json
// @Produce json
// @Param body body admin.ShareRecordForm true "分享记录信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/share_record/delete [post]
// @Security token
func (sr *ShareRecord) Delete(c *gin.Context) {
f := &admin.ShareRecordForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
i := service.AllService.ShareRecordService.InfoById(f.Id)
if i.Id > 0 {
err := service.AllService.ShareRecordService.Delete(i)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchDelete 批量删除
// @Tags 分享记录
// @Summary 批量分享记录
// @Description 批量分享记录
// @Accept json
// @Produce json
// @Param body body admin.PeerShareRecordBatchDeleteForm true "id"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/share_record/batchDelete [post]
// @Security token
func (sr *ShareRecord) BatchDelete(c *gin.Context) {
f := &admin.PeerShareRecordBatchDeleteForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.Ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.ShareRecordService.BatchDelete(f.Ids)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}

View File

@@ -64,9 +64,9 @@ func (ct *Tag) Create(c *gin.Context) {
return
}
t := f.ToTag()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
t.UserId = u.Id
if t.UserId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.TagService.Create(t)
if err != nil {
@@ -96,10 +96,6 @@ func (ct *Tag) List(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
query.UserId = int(u.Id)
}
res := service.AllService.TagService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Preload("Collection", func(txc *gorm.DB) *gorm.DB {
return txc.Select("id,name")
@@ -140,12 +136,12 @@ func (ct *Tag) Update(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f.ToTag()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
ex := service.AllService.TagService.InfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
t := f.ToTag()
err := service.AllService.TagService.Update(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
@@ -177,20 +173,15 @@ func (ct *Tag) Delete(c *gin.Context) {
response.Fail(c, 101, errList[0])
return
}
t := service.AllService.TagService.InfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
ex := service.AllService.TagService.InfoById(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if u.Id > 0 {
err := service.AllService.TagService.Delete(t)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
err := service.AllService.TagService.Delete(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
response.Fail(c, 101, err.Error())
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strconv"
"time"
)
type User struct {
@@ -295,51 +294,6 @@ func (ct *User) MyOauth(c *gin.Context) {
response.Success(c, res)
}
// List 列表
// @Tags 设备
// @Summary 设备列表
// @Description 设备列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param time_ago query int false "时间"
// @Param id query string false "ID"
// @Param hostname query string false "主机名"
// @Param uuids query string false "uuids 用逗号分隔"
// @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response
// @Router /admin/user/myPeer [get]
// @Security token
func (ct *User) MyPeer(c *gin.Context) {
query := &admin.PeerQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
res := service.AllService.PeerService.ListFilterByUserId(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.TimeAgo > 0 {
lt := time.Now().Unix() - int64(query.TimeAgo)
tx.Where("last_online_time < ?", lt)
}
if query.TimeAgo < 0 {
lt := time.Now().Unix() + int64(query.TimeAgo)
tx.Where("last_online_time > ?", lt)
}
if query.Id != "" {
tx.Where("id like ?", "%"+query.Id+"%")
}
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
if query.Uuids != "" {
tx.Where("uuid in (?)", query.Uuids)
}
}, u.Id)
response.Success(c, res)
}
// groupUsers
func (ct *User) GroupUsers(c *gin.Context) {
q := &admin.GroupUsersQuery{}

View File

@@ -81,3 +81,33 @@ func (ct *UserToken) Delete(c *gin.Context) {
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchDelete 批量删除
// @Tags 登录凭证
// @Summary 登录凭证批量删除
// @Description 登录凭证批量删除
// @Accept json
// @Produce json
// @Param body body admin.UserTokenBatchDeleteForm true "登录凭证信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/user_token/batchDelete [post]
// @Security token
func (ct *UserToken) BatchDelete(c *gin.Context) {
f := &admin.UserTokenBatchDeleteForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
ids := f.Ids
if len(ids) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.UserService.BatchDeleteUserToken(ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
}

View File

@@ -64,12 +64,15 @@ func (i *WebClient) SharedPeer(c *gin.Context) {
response.Fail(c, 101, "share not found")
return
}
//判断是否过期,created_at + expire > now
ca := time.Time(sr.CreatedAt)
if ca.Add(time.Second * time.Duration(sr.Expire)).Before(time.Now()) {
response.Fail(c, 101, "share expired")
return
if sr.Expire != 0 {
//判断是否过期,created_at + expire > now
ca := time.Time(sr.CreatedAt)
if ca.Add(time.Second * time.Duration(sr.Expire)).Before(time.Now()) {
response.Fail(c, 101, "share expired")
return
}
}
ab := service.AllService.AddressBookService.InfoByUserIdAndId(sr.UserId, sr.PeerId)
if ab.RowId == 0 {
response.Fail(c, 101, "peer not found")
@@ -85,3 +88,23 @@ func (i *WebClient) SharedPeer(c *gin.Context) {
"peer": pp,
})
}
// ServerConfigV2 服务配置
// @Tags WEBCLIENT_V2
// @Summary 服务配置
// @Description 服务配置,给webclient提供api-server
// @Accept json
// @Produce json
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /server-config-v2 [get]
// @Security token
func (i *WebClient) ServerConfigV2(c *gin.Context) {
response.Success(
c,
gin.H{
"id_server": global.Config.Rustdesk.IdServer,
"key": global.Config.Rustdesk.Key,
},
)
}

View File

@@ -3,6 +3,7 @@ package web
import (
"Gwen/global"
"github.com/gin-gonic/gin"
"strconv"
)
type Index struct {
@@ -14,9 +15,13 @@ func (i *Index) Index(c *gin.Context) {
func (i *Index) ConfigJs(c *gin.Context) {
apiServer := global.Config.Rustdesk.ApiServer
magicQueryonline := strconv.Itoa(global.Config.Rustdesk.WebclientMagicQueryonline)
tmp := `
localStorage.setItem('api-server', "` + apiServer + `")
`
const ws2_prefix = 'wc-'
localStorage.setItem(ws2_prefix+'api-server', "` + apiServer + `")
window.webclient_magic_queryonline = ` + magicQueryonline + ``
c.String(200, tmp)
}

View File

@@ -122,3 +122,14 @@ type AddressBookCollectionRuleQuery struct {
IsMy int `form:"is_my"`
PageQuery
}
type BatchCreateFromPeersForm struct {
CollectionId uint `json:"collection_id"`
PeerIds []uint `json:"peer_ids"`
Tags []string `json:"tags"`
UserId uint `json:"user_id"`
}
type BatchUpdateTagsForm struct {
RowIds []uint `json:"row_ids"`
Tags []string `json:"tags"`
}

View File

@@ -5,3 +5,10 @@ type AuditQuery struct {
FromPeer string `form:"from_peer"`
PageQuery
}
type AuditConnLogIds struct {
Ids []uint `json:"ids" validate:"required"`
}
type AuditFileLogIds struct {
Ids []uint `json:"ids" validate:"required"`
}

View File

@@ -4,6 +4,7 @@ type Login struct {
Username string `json:"username" validate:"required" label:"用户名"`
Password string `json:"password,omitempty" validate:"required" label:"密码"`
Platform string `json:"platform" label:"平台"`
Captcha string `json:"captcha,omitempty" label:"验证码"`
}
type LoginLogQuery struct {
@@ -15,3 +16,7 @@ type LoginTokenQuery struct {
UserId int `form:"user_id"`
PageQuery
}
type LoginLogIds struct {
Ids []uint `json:"ids" validate:"required"`
}

View File

@@ -0,0 +1,15 @@
package admin
type ShareRecordQuery struct {
UserId uint `json:"user_id" form:"user_id"`
PageQuery
}
type ShareRecordForm struct {
Id uint `json:"id" form:"id"`
UserId uint `json:"user_id" form:"user_id"`
}
type PeerShareRecordBatchDeleteForm struct {
Ids []uint `json:"ids" validate:"required"`
}

View File

@@ -6,7 +6,7 @@ import (
type UserForm struct {
Id uint `json:"id"`
Username string `json:"username" validate:"required,gte=2,lte=10"`
Username string `json:"username" validate:"required,gte=2,lte=32"`
Email string `json:"email"` //validate:"required,email" email不强制
//Password string `json:"password" validate:"required,gte=4,lte=20"`
Nickname string `json:"nickname"`
@@ -51,12 +51,12 @@ type UserQuery struct {
}
type UserPasswordForm struct {
Id uint `json:"id" validate:"required"`
Password string `json:"password" validate:"required,gte=4,lte=20"`
Password string `json:"password" validate:"required,gte=4,lte=32"`
}
type ChangeCurPasswordForm struct {
OldPassword string `json:"old_password" validate:"required,gte=4,lte=20"`
NewPassword string `json:"new_password" validate:"required,gte=4,lte=20"`
OldPassword string `json:"old_password" validate:"required,gte=4,lte=32"`
NewPassword string `json:"new_password" validate:"required,gte=4,lte=32"`
}
type GroupUsersQuery struct {
IsMy int `json:"is_my"`
@@ -64,8 +64,12 @@ type GroupUsersQuery struct {
}
type RegisterForm struct {
Username string `json:"username" validate:"required,gte=2,lte=10"`
Username string `json:"username" validate:"required,gte=2,lte=32"`
Email string `json:"email"` // validate:"required,email"
Password string `json:"password" validate:"required,gte=4,lte=20"`
ConfirmPassword string `json:"confirm_password" validate:"required,gte=4,lte=20"`
Password string `json:"password" validate:"required,gte=4,lte=32"`
ConfirmPassword string `json:"confirm_password" validate:"required,gte=4,lte=32"`
}
type UserTokenBatchDeleteForm struct {
Ids []uint `json:"ids" validate:"required"`
}

View File

@@ -34,8 +34,8 @@ type LoginForm struct {
Id string `json:"id" label:"id"`
Type string `json:"type" label:"type"`
Uuid string `json:"uuid" label:"uuid"`
Username string `json:"username" validate:"required,gte=2,lte=10" label:"用户名"`
Password string `json:"password,omitempty" validate:"gte=4,lte=20" label:"密码"`
Username string `json:"username" validate:"required,gte=2,lte=32" label:"用户名"`
Password string `json:"password,omitempty" validate:"gte=4,lte=32" label:"密码"`
}
type UserListQuery struct {

View File

@@ -4,8 +4,8 @@ import "Gwen/model"
type LoginPayload struct {
Username string `json:"username"`
Email string `json:"email"`
Avatar string `json:"avatar"`
Email string `json:"email"`
Avatar string `json:"avatar"`
Token string `json:"token"`
RouteNames []string `json:"route_names"`
Nickname string `json:"nickname"`
@@ -19,13 +19,13 @@ func (lp *LoginPayload) FromUser(user *model.User) {
}
var UserRouteNames = []string{
"MyTagList", "MyAddressBookList", "MyInfo", "MyAddressBookCollection", "MyPeer",
"MyTagList", "MyAddressBookList", "MyInfo", "MyAddressBookCollection", "MyPeer", "MyShareRecordList",
}
var AdminRouteNames = []string{"*"}
type UserOauthItem struct {
Op string `json:"op"`
Status int `json:"status"`
Op string `json:"op"`
Status int `json:"status"`
}
type GroupUsersPayload struct {

View File

@@ -2,7 +2,9 @@ package router
import (
_ "Gwen/docs/admin"
"Gwen/global"
"Gwen/http/controller/admin"
"Gwen/http/controller/admin/my"
"Gwen/http/middleware"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
@@ -13,7 +15,9 @@ func Init(g *gin.Engine) {
//swagger
//g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
g.GET("/admin/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("admin")))
if global.Config.App.ShowSwagger == 1 {
g.GET("/admin/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("admin")))
}
adg := g.Group("/api/admin")
LoginBind(adg)
@@ -31,15 +35,24 @@ func Init(g *gin.Engine) {
AddressBookCollectionBind(adg)
AddressBookCollectionRuleBind(adg)
UserTokenBind(adg)
rs := &admin.Rustdesk{}
adg.GET("/server-config", rs.ServerConfig)
adg.GET("/app-config", rs.AppConfig)
ConfigBind(adg)
//deprecated by ConfigBind
//rs := &admin.Rustdesk{}
//adg.GET("/server-config", rs.ServerConfig)
//adg.GET("/app-config", rs.AppConfig)
//deprecated end
ShareRecordBind(adg)
MyBind(adg)
//访问静态文件
//g.StaticFS("/upload", http.Dir(global.Config.Gin.ResourcesPath+"/upload"))
}
func LoginBind(rg *gin.RouterGroup) {
cont := &admin.Login{}
rg.POST("/login", cont.Login)
rg.GET("/captcha", cont.Captcha)
rg.POST("/logout", cont.Logout)
rg.GET("/login-options", cont.LoginOptions)
rg.POST("/oidc/auth", cont.OidcAuth)
@@ -53,7 +66,7 @@ func UserBind(rg *gin.RouterGroup) {
aR.GET("/current", cont.Current)
aR.POST("/changeCurPwd", cont.ChangeCurPwd)
aR.POST("/myOauth", cont.MyOauth)
aR.GET("/myPeer", cont.MyPeer)
//aR.GET("/myPeer", cont.MyPeer)
aR.POST("/groupUsers", cont.GroupUsers)
}
aRP := rg.Group("/user").Use(middleware.AdminPrivilege())
@@ -81,7 +94,7 @@ func GroupBind(rg *gin.RouterGroup) {
}
func TagBind(rg *gin.RouterGroup) {
aR := rg.Group("/tag")
aR := rg.Group("/tag").Use(middleware.AdminPrivilege())
{
cont := &admin.Tag{}
aR.GET("/list", cont.List)
@@ -96,15 +109,17 @@ func AddressBookBind(rg *gin.RouterGroup) {
aR := rg.Group("/address_book")
{
cont := &admin.AddressBook{}
aR.GET("/list", cont.List)
aR.GET("/detail/:id", cont.Detail)
aR.POST("/create", cont.Create)
aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete)
aR.POST("/shareByWebClient", cont.ShareByWebClient)
arp := aR.Use(middleware.AdminPrivilege())
arp.GET("/list", cont.List)
//arp.GET("/detail/:id", cont.Detail)
arp.POST("/create", cont.Create)
arp.POST("/update", cont.Update)
arp.POST("/delete", cont.Delete)
arp.POST("/batchCreate", cont.BatchCreate)
arp.POST("/batchCreateFromPeers", cont.BatchCreateFromPeers)
}
}
func PeerBind(rg *gin.RouterGroup) {
@@ -149,18 +164,21 @@ func LoginLogBind(rg *gin.RouterGroup) {
cont := &admin.LoginLog{}
aR.GET("/list", cont.List)
aR.POST("/delete", cont.Delete)
aR.POST("/batchDelete", cont.BatchDelete)
}
func AuditBind(rg *gin.RouterGroup) {
cont := &admin.Audit{}
aR := rg.Group("/audit_conn").Use(middleware.AdminPrivilege())
aR.GET("/list", cont.ConnList)
aR.POST("/delete", cont.ConnDelete)
aR.POST("/batchDelete", cont.BatchConnDelete)
afR := rg.Group("/audit_file").Use(middleware.AdminPrivilege())
afR.GET("/list", cont.FileList)
afR.POST("/delete", cont.FileDelete)
afR.POST("/batchDelete", cont.BatchFileDelete)
}
func AddressBookCollectionBind(rg *gin.RouterGroup) {
aR := rg.Group("/address_book_collection")
aR := rg.Group("/address_book_collection").Use(middleware.AdminPrivilege())
{
cont := &admin.AddressBookCollection{}
aR.GET("/list", cont.List)
@@ -172,7 +190,7 @@ func AddressBookCollectionBind(rg *gin.RouterGroup) {
}
func AddressBookCollectionRuleBind(rg *gin.RouterGroup) {
aR := rg.Group("/address_book_collection_rule")
aR := rg.Group("/address_book_collection_rule").Use(middleware.AdminPrivilege())
{
cont := &admin.AddressBookCollectionRule{}
aR.GET("/list", cont.List)
@@ -187,6 +205,14 @@ func UserTokenBind(rg *gin.RouterGroup) {
cont := &admin.UserToken{}
aR.GET("/list", cont.List)
aR.POST("/delete", cont.Delete)
aR.POST("/batchDelete", cont.BatchDelete)
}
func ConfigBind(rg *gin.RouterGroup) {
aR := rg.Group("/config")
rs := &admin.Config{}
aR.GET("/server", rs.ServerConfig)
aR.GET("/app", rs.AppConfig)
aR.GET("/admin", rs.AdminConfig)
}
/*
@@ -201,3 +227,62 @@ func FileBind(rg *gin.RouterGroup) {
aR.POST("/upload", cont.Upload)
}
}*/
func MyBind(rg *gin.RouterGroup) {
{
cont := &my.ShareRecord{}
rg.GET("/my/share_record/list", cont.List)
rg.POST("/my/share_record/delete", cont.Delete)
rg.POST("/my/share_record/batchDelete", cont.BatchDelete)
}
{
cont := &my.AddressBook{}
rg.GET("/my/address_book/list", cont.List)
rg.POST("/my/address_book/create", cont.Create)
rg.POST("/my/address_book/update", cont.Update)
rg.POST("/my/address_book/delete", cont.Delete)
rg.POST("/my/address_book/batchCreateFromPeers", cont.BatchCreateFromPeers)
rg.POST("/my/address_book/batchUpdateTags", cont.BatchUpdateTags)
}
{
cont := &my.Tag{}
rg.GET("/my/tag/list", cont.List)
rg.POST("/my/tag/create", cont.Create)
rg.POST("/my/tag/update", cont.Update)
rg.POST("/my/tag/delete", cont.Delete)
}
{
cont := &my.AddressBookCollection{}
rg.GET("/my/address_book_collection/list", cont.List)
rg.POST("/my/address_book_collection/create", cont.Create)
rg.POST("/my/address_book_collection/update", cont.Update)
rg.POST("/my/address_book_collection/delete", cont.Delete)
}
{
cont := &my.AddressBookCollectionRule{}
rg.GET("/my/address_book_collection_rule/list", cont.List)
rg.POST("/my/address_book_collection_rule/create", cont.Create)
rg.POST("/my/address_book_collection_rule/update", cont.Update)
rg.POST("/my/address_book_collection_rule/delete", cont.Delete)
}
{
cont := &my.Peer{}
rg.GET("/my/peer/list", cont.List)
}
}
func ShareRecordBind(rg *gin.RouterGroup) {
aR := rg.Group("/share_record").Use(middleware.AdminPrivilege())
{
cont := &admin.ShareRecord{}
aR.GET("/list", cont.List)
aR.POST("/delete", cont.Delete)
aR.POST("/batchDelete", cont.BatchDelete)
}
}

View File

@@ -15,7 +15,9 @@ func ApiInit(g *gin.Engine) {
//g.Use(middleware.Cors())
//swagger
g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api")))
if global.Config.App.ShowSwagger == 1 {
g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.InstanceName("api")))
}
frg := g.Group("/api")
@@ -121,6 +123,7 @@ func WebClientRoutes(frg *gin.RouterGroup) {
}
{
frg.POST("/server-config", middleware.RustAuth(), w.ServerConfig)
frg.POST("/server-config-v2", middleware.RustAuth(), w.ServerConfigV2)
}
}

View File

@@ -10,3 +10,9 @@ type ShareRecord struct {
Expire int64 `json:"expire" gorm:"default:0;not null;"`
TimeModel
}
// ShareRecordList 分享记录列表
type ShareRecordList struct {
ShareRecords []*ShareRecord `json:"list,omitempty"`
Pagination
}

View File

@@ -123,3 +123,13 @@ other = "Share Group"
description = "Register closed."
one = "Register closed."
other = "Register closed."
[CaptchaRequired]
description = "Captcha required."
one = "Captcha required."
other = "Captcha required."
[CaptchaError]
description = "Captcha error."
one = "Captcha error."
other = "Captcha error."

144
resources/i18n/es.toml Normal file
View File

@@ -0,0 +1,144 @@
[Test]
description = "test"
one = "prueba1"
other = "Prueba2 {{.P0}}"
[ParamsError]
description = "Params validation failed."
one = "La validación de los parámetros falló."
other = "La validación de los parámetros falló."
[OperationFailed]
description = "OperationFailed."
one = "La operación falló."
other = "La operación falló."
[OperationSuccess]
description = "OperationSuccess."
one = "La operación fue exitosa."
other = "La operación fue exitosa."
[ItemExists]
description = "Item already exists."
one = "El elemento ya existe."
other = "El elemento ya existe."
[ItemNotFound]
description = "Item not found."
one = "El elemento no fue encontrado."
other = "El elemento no fue encontrado."
[NoAccess]
description = "No access."
one = "Sin acceso."
other = "Sin acceso."
[UsernameOrPasswordError]
description = "Username or password error."
one = "Error de usuario o contraseña."
other = "Error de usuario o contraseña."
[SystemError]
description = "System error."
one = "Error del sistema."
other = "Error del sistema."
[ConfigNotFound]
description = "Config not found."
one = "Configuración no encontrada."
other = "Configuración no encontrada."
[OauthExpired]
description = "Oauth expired."
one = "Oauth expirado, por favor intente nuevamente."
other = "Oauth expirado, por favor intente nuevamente."
[OauthFailed]
description = "Oauth failed."
one = "Oauth falló."
other = "Oauth falló."
[OauthHasBindOtherUser]
description = "Oauth has bind other user."
one = "Oauth está vinculado a otro usuario."
other = "Oauth está vinculado a otro usuario."
[ParamIsEmpty]
description = "Param is empty."
one = "{{.P0}} está vacío."
other = "{{.P0}} está vacío."
[BindFail]
description = "Bind fail."
one = "Fallo al vincular."
other = "Fallo al vincular."
[BindSuccess]
description = "Bind success."
one = "Vinculación exitosa."
other = "Vinculación exitosa."
[OauthHasBeenSuccess]
description = "Oauth has been success."
one = "Oauth fue exitoso."
other = "Oauth fue exitoso."
[OauthSuccess]
description = "Oauth success."
one = "Oauth exitoso."
other = "Oauth exitoso."
[OauthRegisterSuccess]
description = "Oauth register success."
one = "Registro de Oauth exitoso."
other = "Registro de Oauth exitoso."
[OauthRegisterFailed]
description = "Oauth register failed."
one = "Registro de Oauth falló."
other = "Registro de Oauth falló."
[GetOauthTokenError]
description = "Get oauth token error."
one = "Error al obtener el token de Oauth."
other = "Error al obtener el token de Oauth."
[GetOauthUserInfoError]
description = "Get oauth user info error."
one = "Error al obtener la información del usuario de Oauth."
other = "Error al obtener la información del usuario de Oauth."
[DecodeOauthUserInfoError]
description = "Decode oauth user info error."
one = "Error al decodificar la información del usuario de Oauth."
other = "Error al decodificar la información del usuario de Oauth."
[OldPasswordError]
description = "Old password error."
one = "Error con la contraseña anterior."
other = "Error con la contraseña anterior."
[DefaultGroup]
description = "Default group"
one = "Grupo predeterminado"
other = "Grupo predeterminado"
[ShareGroup]
description = "Share group"
one = "Grupo compartido"
other = "Grupo compartido"
[RegisterClosed]
description = "Register closed."
one = "Registro cerrado."
other = "Registro cerrado."
[CaptchaRequired]
description = "Captcha required."
one = "Captcha requerido."
other = "Captcha requerido."
[CaptchaError]
description = "Captcha error."
one = "Error de captcha."
other = "Error de captcha."

144
resources/i18n/fr.toml Normal file
View File

@@ -0,0 +1,144 @@
[Test]
description = "test"
one = "test1 "
other = "Test2 {{.P0}}"
[ParamsError]
description = "Params validation failed."
one = "La validation des paramètres a échoué."
other = "La validation des paramètres a échoué."
[OperationFailed]
description = "OperationFailed."
one = "l'opération a échoué."
other = "l'opération a échoué."
[OperationSuccess]
description = "OperationSuccess."
one = "Opération réussie"
other = "Opération réussie"
[ItemExists]
description = "Item already exists."
one = "L'élément existe déjà."
other = "L'élément existe déjà."
[ItemNotFound]
description = "Item not found."
one = "Article introuvable."
other = "Article introuvable."
[NoAccess]
description = "No access."
one = "Aucun d'access."
other = "Aucun d'access."
[UsernameOrPasswordError]
description = "Username or password error."
one = "Nom d'utilisateur ou de mot de passe incorrect."
other = "Nom d'utilisateur ou de mot de passe incorrect."
[SystemError]
description = "System error."
one = "Erreur system."
other = "Erreur system."
[ConfigNotFound]
description = "Config not found."
one = "Configuration introuvable."
other = "Configuration introuvable."
[OauthExpired]
description = "Oauth expired."
one = "Oauth a expiré, veuillez réessayer."
other = "Oauth a expiré, veuillez réessayer."
[OauthFailed]
description = "Oauth failed."
one = "Oauth a échoué."
other = "Oauth a échoué."
[OauthHasBindOtherUser]
description = "Oauth has bind other user."
one = "Oauth a lié un autre utilisateur."
other = "Oauth a lié un autre utilisateur."
[ParamIsEmpty]
description = "Param is empty."
one = "{{.P0}} est vide."
other = "{{.P0}} est vide."
[BindFail]
description = "Bind fail."
one = "Échec de la liaison."
other = "Échec de la liaison."
[BindSuccess]
description = "Bind success."
one = "Succès de la liaison."
other = "Succès de la liaison."
[OauthHasBeenSuccess]
description = "Oauth has been success."
one = "Oauth a été réussi avec Succès."
other = "Oauth a été réussi avec Succès."
[OauthSuccess]
description = "Oauth success."
one = "Oauh réussi avec succès."
other = "Oauh réussi avec succès."
[OauthRegisterSuccess]
description = "Oauth register success."
one = "Succès de l'enregistrement Oauth."
other = "Succès de l'enregistrement Oauth."
[OauthRegisterFailed]
description = "Oauth register failed."
one = "L'inscription Oauth a échoué."
other = "L'inscription Oauth a échoué."
[GetOauthTokenError]
description = "Get oauth token error."
one = "Erreur de l'obtention du jeton oauth."
other = "Erreur de l'obtention du jeton oauth."
[GetOauthUserInfoError]
description = "Get oauth user info error."
one = "Erreur d'obtention d'informations sur l'utilisateur oauth."
other = "Erreur d'obtention d'informations sur l'utilisateur oauth."
[DecodeOauthUserInfoError]
description = "Decode oauth user info error."
one = "Erreur de décodage des informations utilisateur oauth."
other = "Erreur de décodage des informations utilisateur oauth."
[OldPasswordError]
description = "Old password error."
one = "Ancien mot de passe incorrect."
other = "Ancien mot de passe incorrect."
[DefaultGroup]
description = "Default group"
one = "Groupe Défaut"
other = "Groupe Défaut"
[ShareGroup]
description = "Share group"
one = "Groupe partagé"
other = "Groupe partagé"
[RegisterClosed]
description = "Register closed."
one = "Inscription fermée."
other = "Inscription fermée."
[CaptchaRequired]
description = "Captcha required."
one = "Captcha requis."
other = "Captcha requis."
[CaptchaError]
description = "Captcha error."
one = "Erreur de captcha."
other = "Erreur de captcha."

View File

@@ -125,4 +125,14 @@ other = "공유 그룹"
[RegisterClosed]
description = "Register closed."
one = "가입이 종료되었습니다."
other = "가입이 종료되었습니다."
other = "가입이 종료되었습니다."
[CaptchaRequired]
description = "Captcha required."
one = "Captcha가 필요합니다."
other = "Captcha가 필요합니다."
[CaptchaError]
description = "Captcha error."
one = "Captcha 오류."
other = "Captcha 오류."

View File

@@ -131,4 +131,14 @@ other = "Общая группа"
[RegisterClosed]
description = "Register closed."
one = "Регистрация закрыта."
other = "Регистрация закрыта."
other = "Регистрация закрыта."
[CaptchaRequired]
description = "Captcha required."
one = "Требуется капча."
other = "Требуется капча."
[CaptchaError]
description = "Captcha error."
one = "Ошибка капчи."
other = "Ошибка капчи."

View File

@@ -124,4 +124,14 @@ other = "共享组"
[RegisterClosed]
description = "Register closed."
one = "注册已关闭。"
other = "注册已关闭。"
other = "注册已关闭。"
[CaptchaRequired]
description = "Captcha required."
one = "需要验证码。"
other = "需要验证码。"
[CaptchaError]
description = "Captcha error."
one = "验证码错误。"
other = "验证码错误。"

542
resources/web/js/package-lock.json generated vendored
View File

@@ -1,114 +1,159 @@
{
"name": "web_hbb",
"version": "1.0.0",
"lockfileVersion": 1,
"lockfileVersion": 3,
"requires": true,
"dependencies": {
"@esbuild/linux-loong64": {
"packages": {
"": {
"name": "web_hbb",
"version": "1.0.0",
"dependencies": {
"fast-sha256": "^1.3.0",
"libsodium": "^0.7.9",
"libsodium-wrappers": "^0.7.9",
"pcm-player": "^0.0.11",
"ts-proto": "^1.141.1",
"wasm-feature-detect": "^1.2.11",
"zstddec": "^0.0.2"
},
"devDependencies": {
"typescript": "4.4.4",
"vite": "2.8"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz",
"integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"@protobufjs/aspromise": {
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
},
"@protobufjs/base64": {
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
},
"@protobufjs/codegen": {
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
},
"@protobufjs/eventemitter": {
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
},
"@protobufjs/fetch": {
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"requires": {
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"@protobufjs/float": {
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
},
"@protobufjs/inquire": {
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
},
"@protobufjs/path": {
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
},
"@protobufjs/pool": {
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
},
"@protobufjs/utf8": {
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
},
"@types/long": {
"node_modules/@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
},
"@types/node": {
"node_modules/@types/node": {
"version": "18.7.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.6.tgz",
"integrity": "sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A=="
},
"@types/object-hash": {
"node_modules/@types/object-hash": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-1.3.4.tgz",
"integrity": "sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA=="
},
"case-anything": {
"node_modules/case-anything": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz",
"integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ=="
"integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ==",
"engines": {
"node": ">=12.13"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"dataloader": {
"node_modules/dataloader": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz",
"integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw=="
},
"detect-libc": {
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"dprint-node": {
"node_modules/dprint-node": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.7.tgz",
"integrity": "sha512-NTZOW9A7ipb0n7z7nC3wftvsbceircwVHSgzobJsEQa+7RnOMbhrfX5IflA6CtC4GA63DSAiHYXa4JKEy9F7cA==",
"requires": {
"dependencies": {
"detect-libc": "^1.0.3"
}
},
"esbuild": {
"node_modules/esbuild": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz",
"integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==",
"dev": true,
"requires": {
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/linux-loong64": "0.14.54",
"esbuild-android-64": "0.14.54",
"esbuild-android-arm64": "0.14.54",
@@ -132,244 +177,460 @@
"esbuild-windows-arm64": "0.14.54"
}
},
"esbuild-android-64": {
"node_modules/esbuild-android-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz",
"integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"esbuild-android-arm64": {
"node_modules/esbuild-android-arm64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz",
"integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"esbuild-darwin-64": {
"node_modules/esbuild-darwin-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz",
"integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==",
"cpu": [
"x64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"esbuild-darwin-arm64": {
"node_modules/esbuild-darwin-arm64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz",
"integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"esbuild-freebsd-64": {
"node_modules/esbuild-freebsd-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz",
"integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"esbuild-freebsd-arm64": {
"node_modules/esbuild-freebsd-arm64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz",
"integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"esbuild-linux-32": {
"node_modules/esbuild-linux-32": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz",
"integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"esbuild-linux-64": {
"node_modules/esbuild-linux-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz",
"integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"esbuild-linux-arm": {
"node_modules/esbuild-linux-arm": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz",
"integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"esbuild-linux-arm64": {
"node_modules/esbuild-linux-arm64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz",
"integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"esbuild-linux-mips64le": {
"node_modules/esbuild-linux-mips64le": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz",
"integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"esbuild-linux-ppc64le": {
"node_modules/esbuild-linux-ppc64le": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz",
"integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"esbuild-linux-riscv64": {
"node_modules/esbuild-linux-riscv64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz",
"integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"esbuild-linux-s390x": {
"node_modules/esbuild-linux-s390x": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz",
"integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"esbuild-netbsd-64": {
"node_modules/esbuild-netbsd-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz",
"integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"esbuild-openbsd-64": {
"node_modules/esbuild-openbsd-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz",
"integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"esbuild-sunos-64": {
"node_modules/esbuild-sunos-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz",
"integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"esbuild-windows-32": {
"node_modules/esbuild-windows-32": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz",
"integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"esbuild-windows-64": {
"node_modules/esbuild-windows-64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz",
"integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"esbuild-windows-arm64": {
"node_modules/esbuild-windows-arm64": {
"version": "0.14.54",
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz",
"integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"fast-sha256": {
"node_modules/fast-sha256": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
"integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="
},
"fsevents": {
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"function-bind": {
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"has": {
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"is-core-module": {
"node_modules/is-core-module": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz",
"integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==",
"dev": true,
"requires": {
"dependencies": {
"has": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"libsodium": {
"node_modules/libsodium": {
"version": "0.7.10",
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz",
"integrity": "sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ=="
},
"libsodium-wrappers": {
"node_modules/libsodium-wrappers": {
"version": "0.7.10",
"resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz",
"integrity": "sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg==",
"requires": {
"dependencies": {
"libsodium": "^0.7.0"
}
},
"long": {
"node_modules/long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"nanoid": {
"node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"object-hash": {
"node_modules/object-hash": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz",
"integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA=="
"integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==",
"engines": {
"node": ">= 0.10.0"
}
},
"path-parse": {
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"pcm-player": {
"node_modules/pcm-player": {
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/pcm-player/-/pcm-player-0.0.11.tgz",
"integrity": "sha512-+FmX62jiqZa7wDCqSRQ1g3DuU6JNgpymgOLCWhmiE/Lj/M+rOUNqgNwVQX509LdA9dtBtVD3EQQUSp9JqU6upw=="
},
"picocolors": {
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"postcss": {
"node_modules/postcss": {
"version": "8.4.16",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
"dev": true,
"requires": {
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"protobufjs": {
"node_modules/protobufjs": {
"version": "6.11.3",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz",
"integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==",
"requires": {
"hasInstallScript": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
@@ -383,53 +644,78 @@
"@types/long": "^4.0.1",
"@types/node": ">=13.7.0",
"long": "^4.0.0"
},
"bin": {
"pbjs": "bin/pbjs",
"pbts": "bin/pbts"
}
},
"resolve": {
"node_modules/resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
"dev": true,
"requires": {
"dependencies": {
"is-core-module": "^2.9.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"rollup": {
"node_modules/rollup": {
"version": "2.77.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.3.tgz",
"integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==",
"dev": true,
"requires": {
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=10.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"source-map-js": {
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"supports-preserve-symlinks-flag": {
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"ts-poet": {
"node_modules/ts-poet": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.4.1.tgz",
"integrity": "sha512-AjZEs4h2w4sDfwpHMxQKHrTlNh2wRbM5NRXmLz0RiH+yPGtSQFbe9hBpNocU8vqVNgfh0BIOiXR80xDz3kKxUQ==",
"requires": {
"dependencies": {
"dprint-node": "^1.0.7"
}
},
"ts-proto": {
"node_modules/ts-proto": {
"version": "1.141.1",
"resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.141.1.tgz",
"integrity": "sha512-1b7Ka6R96FvFZldHnYPTFy4rzwOo+OTpIP1mBFW0dDwq4WWtSkIVlZ+SokOQSC1TiccNshOJwQC9soVyWfQ7Zg==",
"requires": {
"dependencies": {
"@types/object-hash": "^1.3.0",
"case-anything": "^2.1.10",
"dataloader": "^1.4.0",
@@ -437,42 +723,76 @@
"protobufjs": "^6.11.3",
"ts-poet": "^6.2.0",
"ts-proto-descriptors": "1.7.1"
},
"bin": {
"protoc-gen-ts_proto": "protoc-gen-ts_proto"
}
},
"ts-proto-descriptors": {
"node_modules/ts-proto-descriptors": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-1.7.1.tgz",
"integrity": "sha512-oIKUh3K4Xts4v29USGLfUG+2mEk32MsqpgZAOUyUlkrcIdv34yE+k2oZ2Nzngm6cV/JgFdOxRCqeyvmWHuYAyw==",
"requires": {
"dependencies": {
"long": "^4.0.0",
"protobufjs": "^6.8.8"
}
},
"typescript": {
"node_modules/typescript": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
"dev": true
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"vite": {
"node_modules/vite": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.8.6.tgz",
"integrity": "sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==",
"dev": true,
"requires": {
"dependencies": {
"esbuild": "^0.14.14",
"fsevents": "~2.3.2",
"postcss": "^8.4.6",
"resolve": "^1.22.0",
"rollup": "^2.59.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": ">=12.2.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"less": "*",
"sass": "*",
"stylus": "*"
},
"peerDependenciesMeta": {
"less": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
}
}
},
"wasm-feature-detect": {
"node_modules/wasm-feature-detect": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
},
"zstddec": {
"node_modules/zstddec": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz",
"integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA=="

View File

@@ -753,12 +753,22 @@ function getDefaultUri(isRelay: Boolean = false): string {
const host = localStorage.getItem("custom-rendezvous-server");
return getrUriFromRs(host || HOST, isRelay);
}
/*
function isHttps() {
return window.location.protocol === "https:"
}
function domain(uri: string) {
return uri.indexOf(":") > 0 ? uri.split(":")[0] : uri
}*/
function getrUriFromRs(
uri: string,
isRelay: Boolean = false,
roffset: number = 0
): string {
//v2
//if (isHttps()) return "wss://" + domain(uri) + "/ws/" + (isRelay ? "relay" : "id");
if (uri.indexOf(":") > 0) {
const tmp = uri.split(":");
const port = parseInt(tmp[1]);

View File

@@ -92,10 +92,10 @@ dprint-node@^1.0.7:
dependencies:
detect-libc "^1.0.3"
esbuild-linux-64@0.14.54:
esbuild-windows-64@0.14.54:
version "0.14.54"
resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz"
integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz"
integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
esbuild@^0.14.14:
version "0.14.54"

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
{"assets/actions.svg":["assets/actions.svg"],"assets/actions_mobile.svg":["assets/actions_mobile.svg"],"assets/address_book.ttf":["assets/address_book.ttf"],"assets/android.svg":["assets/android.svg"],"assets/arrow.svg":["assets/arrow.svg"],"assets/auth-apple.svg":["assets/auth-apple.svg"],"assets/auth-auth0.svg":["assets/auth-auth0.svg"],"assets/auth-azure.svg":["assets/auth-azure.svg"],"assets/auth-default.svg":["assets/auth-default.svg"],"assets/auth-facebook.svg":["assets/auth-facebook.svg"],"assets/auth-github.svg":["assets/auth-github.svg"],"assets/auth-gitlab.svg":["assets/auth-gitlab.svg"],"assets/auth-google.svg":["assets/auth-google.svg"],"assets/auth-okta.svg":["assets/auth-okta.svg"],"assets/call_end.svg":["assets/call_end.svg"],"assets/call_wait.svg":["assets/call_wait.svg"],"assets/chat.svg":["assets/chat.svg"],"assets/chat2.svg":["assets/chat2.svg"],"assets/checkbox-outline.svg":["assets/checkbox-outline.svg"],"assets/chevron_up_chevron_down.svg":["assets/chevron_up_chevron_down.svg"],"assets/close.svg":["assets/close.svg"],"assets/display.svg":["assets/display.svg"],"assets/dots.svg":["assets/dots.svg"],"assets/file.svg":["assets/file.svg"],"assets/file_transfer.svg":["assets/file_transfer.svg"],"assets/folder.svg":["assets/folder.svg"],"assets/folder_new.svg":["assets/folder_new.svg"],"assets/fullscreen.svg":["assets/fullscreen.svg"],"assets/fullscreen_exit.svg":["assets/fullscreen_exit.svg"],"assets/gestures.ttf":["assets/gestures.ttf"],"assets/home.svg":["assets/home.svg"],"assets/icon.svg":["assets/icon.svg"],"assets/insecure.svg":["assets/insecure.svg"],"assets/insecure_relay.svg":["assets/insecure_relay.svg"],"assets/kb_layout_iso.svg":["assets/kb_layout_iso.svg"],"assets/kb_layout_not_iso.svg":["assets/kb_layout_not_iso.svg"],"assets/keyboard.svg":["assets/keyboard.svg"],"assets/linux.svg":["assets/linux.svg"],"assets/mac.svg":["assets/mac.svg"],"assets/message_24dp_5F6368.svg":["assets/message_24dp_5F6368.svg"],"assets/peer_searchbar.ttf":["assets/peer_searchbar.ttf"],"assets/pinned.svg":["assets/pinned.svg"],"assets/rec.svg":["assets/rec.svg"],"assets/record_screen.svg":["assets/record_screen.svg"],"assets/refresh.svg":["assets/refresh.svg"],"assets/scam.png":["assets/scam.png"],"assets/screen.svg":["assets/screen.svg"],"assets/search.svg":["assets/search.svg"],"assets/secure.svg":["assets/secure.svg"],"assets/secure_relay.svg":["assets/secure_relay.svg"],"assets/tabbar.ttf":["assets/tabbar.ttf"],"assets/transfer.svg":["assets/transfer.svg"],"assets/trash.svg":["assets/trash.svg"],"assets/unpinned.svg":["assets/unpinned.svg"],"assets/voice_call.svg":["assets/voice_call.svg"],"assets/voice_call_waiting.svg":["assets/voice_call_waiting.svg"],"assets/win.svg":["assets/win.svg"],"packages/dash_chat_2/assets/placeholder.png":["packages/dash_chat_2/assets/placeholder.png"],"packages/dash_chat_2/assets/profile_placeholder.png":["packages/dash_chat_2/assets/profile_placeholder.png"],"packages/flex_color_picker/assets/opacity.png":["packages/flex_color_picker/assets/opacity.png"],"packages/wakelock_plus/assets/no_sleep.js":["packages/wakelock_plus/assets/no_sleep.js"],"packages/window_manager/images/ic_chrome_close.png":["packages/window_manager/images/ic_chrome_close.png"],"packages/window_manager/images/ic_chrome_maximize.png":["packages/window_manager/images/ic_chrome_maximize.png"],"packages/window_manager/images/ic_chrome_minimize.png":["packages/window_manager/images/ic_chrome_minimize.png"],"packages/window_manager/images/ic_chrome_unmaximize.png":["packages/window_manager/images/ic_chrome_unmaximize.png"]}

42
resources/web2/assets/FontManifest.json vendored Normal file
View File

@@ -0,0 +1,42 @@
[
{
"family": "MaterialIcons",
"fonts": [
{
"asset": "fonts/MaterialIcons-Regular.otf"
}
]
},
{
"family": "GestureIcons",
"fonts": [
{
"asset": "assets/gestures.ttf"
}
]
},
{
"family": "Tabbar",
"fonts": [
{
"asset": "assets/tabbar.ttf"
}
]
},
{
"family": "PeerSearchbar",
"fonts": [
{
"asset": "assets/peer_searchbar.ttf"
}
]
},
{
"family": "AddressBook",
"fonts": [
{
"asset": "assets/address_book.ttf"
}
]
}
]

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" style="isolation:isolate" viewBox="496.063 521.772 32 32"><path fill="none" d="M496.063 521.772h32v32h-32v-32Z"/><path d="m513.817 535.858.87-8.052c.136-1.26-.279-1.399-.927-.31l-6.856 11.532c-.216.363-.049.658.374.658h3.031l-.87 8.052c-.136 1.26.279 1.399.927.309l6.856-11.531c.216-.363.048-.658-.374-.658h-3.031Z"/></svg>

After

Width:  |  Height:  |  Size: 386 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" style="isolation:isolate" viewBox="91.748 41.508 32 32"><path fill="none" d="M91.748 41.508h32v32h-32v-32Z"/><path fill-rule="evenodd" d="M104.884 48.3h5.728c1.363 0 2.47.94 2.47 2.098v11.448c0 1.158-1.107 2.098-2.47 2.098h-5.728c-1.363 0-2.469-.94-2.469-2.098V50.398c0-1.158 1.106-2.098 2.469-2.098Zm-.004-2.692h5.729a5.162 5.162 0 0 1 5.164 5.164v13.472a5.164 5.164 0 0 1-1.514 3.649 5.143 5.143 0 0 1-3.65 1.515h-5.729a5.164 5.164 0 0 1-5.157-5.164V50.772c0-1.374.538-2.687 1.508-3.656a5.182 5.182 0 0 1 3.649-1.508Zm1.407 21.076a1.462 1.462 0 0 1 2.922 0 1.461 1.461 0 0 1-2.922 0Z"/></svg>

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

View File

@@ -0,0 +1 @@
<svg width="553" height="553"><path fill="#fff" d="M77 179a33 33 0 0 0-25 10 33 33 0 0 0-9 24v143a33 33 0 0 0 10 24 33 33 0 0 0 24 10c9 0 17-3 24-10a33 33 0 0 0 10-24V213c0-9-4-17-10-24a33 33 0 0 0-24-10zM352 51l24-44c1-3 1-5-2-6-3-2-5-1-7 2l-24 43a163 163 0 0 0-133 0L186 3c-2-3-4-4-7-2-2 1-3 3-1 6l23 44c-24 12-43 29-57 51a129 129 0 0 0-21 72h307c0-26-7-50-21-72a146 146 0 0 0-57-51zm-136 63a13 13 0 0 1-10 4 13 13 0 0 1-12-13c0-4 1-7 3-9 3-3 6-4 9-4s7 1 10 4c2 2 3 5 3 9s-1 7-3 9zm140 0a12 12 0 0 1-9 4c-4 0-7-1-9-4a12 12 0 0 1-4-9c0-4 1-7 4-9 2-3 5-4 9-4a12 12 0 0 1 9 4c2 2 3 5 3 9s-1 7-3 9zM124 407c0 10 4 19 11 26s15 10 26 10h24v76c0 9 4 17 10 24s15 10 24 10c10 0 18-3 25-10s10-15 10-24v-76h45v76c0 9 4 17 10 24s15 10 25 10c9 0 17-3 24-10s10-15 10-24v-76h25a35 35 0 0 0 25-10c7-7 11-16 11-26V185H124v222zm352-228a33 33 0 0 0-24 10 33 33 0 0 0-10 24v143a34 34 0 0 0 34 34c10 0 18-3 25-10s10-15 10-24V213c0-9-4-17-10-24a33 33 0 0 0-25-10z"/></svg>

After

Width:  |  Height:  |  Size: 952 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" style="isolation:isolate" viewBox="305.118 110.478 32 32"><path fill="none" d="M305.118 110.478h32v32h-32v-32Z"/><path d="m322.26 126.052-5.963-5.962a1.48 1.48 0 1 1 2.093-2.093l7.63 7.63c.47.47.47 1.233 0 1.703l-7.63 7.629a1.48 1.48 0 0 1-2.093 0 1.482 1.482 0 0 1 0-2.093l5.963-5.962a.603.603 0 0 0 0-.852Z"/></svg>

After

Width:  |  Height:  |  Size: 380 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><circle cx="512" cy="512" r="512" fill="#999"/><path fill="#fff" d="M407.2 722.1c-10.1-6.7-19-15-26.5-24.5-8.2-9.9-15.7-20.3-22.7-31-16.3-23.9-29.1-50-38-77.5-10.7-32-15.8-62.7-15.8-92.7 0-33.5 7.2-62.7 21.4-87.2 10.4-19.2 26-35.2 44.8-46.5 18.1-11.3 39.2-17.5 60.6-17.9 7.5 0 15.6 1.1 24.1 3.2 6.2 1.7 13.6 4.5 22.8 7.9 11.7 4.5 18.1 7.2 20.3 7.9 6.8 2.6 12.6 3.6 17.1 3.6 3.4 0 8.3-1.1 13.8-2.8 3.1-1.1 9-3 17.3-6.6 8.2-3 14.8-5.5 19.9-7.5 7.9-2.3 15.5-4.5 22.4-5.5 8.3-1.3 16.6-1.7 24.5-1.1 15.1 1.1 29 4.3 41.4 9 21.7 8.7 39.3 22.4 52.4 41.8-5.5 3.4-10.7 7.4-15.5 11.7-10.4 9.2-19.2 20-26.2 32.1-9.2 16.4-13.9 35-13.7 53.7.3 23.1 6.2 43.4 17.9 61 8.3 12.8 19.3 23.8 32.7 32.7 6.6 4.5 12.4 7.6 17.9 9.6-2.6 8-5.4 15.8-8.6 23.5-7.4 17.2-16.2 33.7-26.7 49.3-9.2 13.4-16.5 23.5-22 30.1-8.6 10.2-16.8 17.9-25.2 23.4-9.2 6.1-19.9 9.3-31 9.3-7.5.3-14.9-.6-22-2.7-6.2-2-12.3-4.3-18.3-6.9-6.2-2.9-12.7-5.3-19.3-7.2-8.1-2.1-16.4-3.2-24.8-3.1-8.5 0-16.8 1.1-24.7 3.1-6.6 1.9-13 4.2-19.3 6.9-9 3.7-14.8 6.2-18.2 7.2-6.9 2-14 3.3-21.1 3.7-11.1 0-21.4-3.2-31.7-9.6zm146.1-393.6c-14.5 7.2-28.3 10.3-42.1 9.3-2.1-13.8 0-27.9 5.8-43.4 5.1-13.2 11.9-25.2 21.3-35.8 9.8-11.1 21.5-20.3 34.8-26.9 14.1-7.2 27.5-11.1 40.3-11.7 1.7 14.5 0 28.8-5.3 44.1-4.9 13.6-12.1 26.2-21.3 37.5-9.3 11.1-20.8 20.3-33.8 26.9z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg width="32" height="32"><path d="M29.307 9.932 26.161 0H5.796L2.692 9.932c-1.802 5.75.042 12.271 5.089 16.021L16.01 32l8.208-6.068c5.005-3.75 6.911-10.25 5.089-16.021l-8.214 6.104 3.12 9.938-8.208-6.13-8.208 6.104 3.141-9.911-8.25-6.063 10.177-.063 3.146-9.891 3.141 9.87z"/></svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="199"><path fill="#0089d6" d="M118.432 187.698c32.89-5.81 60.055-10.618 60.367-10.684l.568-.12-31.052-36.935c-17.078-20.314-31.051-37.014-31.051-37.11 0-.182 32.063-88.477 32.243-88.792.06-.105 21.88 37.567 52.893 91.32 29.035 50.323 52.973 91.815 53.195 92.203l.405.707-98.684-.012-98.684-.013 59.8-10.564zM0 176.435c0-.052 14.631-25.451 32.514-56.442l32.514-56.347 37.891-31.799C123.76 14.358 140.867.027 140.935.001c.069-.026-.205.664-.609 1.534s-18.919 40.582-41.145 88.25l-40.41 86.67-29.386.037c-16.162.02-29.385-.005-29.385-.057z"/></svg>

After

Width:  |  Height:  |  Size: 604 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120"><path d="M142.554 52.81c0-4.113 1.078-6.374 5.369-11.26 17.207-19.593 57.193-19.593 74.4 0 4.291 4.886 5.37 7.147 5.37 11.26v5.145h-85.14zm71.239-42.863 6.676-6.692 10.462 10.74 25.49-25.453 6.133 6.543-31.536 32.356-17.225-17.494Zm-34.474 3.377c-15.027-5.337-19.348-22.264-8.57-33.575 10.85-11.387 29.85-6.099 34.149 9.503 2.523 9.161-4.38 21.951-12.951 23.995-4.39 1.58-8.73 1.433-12.628.077z" style="fill:#024eff;fill-opacity:1;stroke-width:.999998" transform="translate(-142.554 44.365)"/></svg>

After

Width:  |  Height:  |  Size: 564 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><rect width="512" height="512" fill="#1877f2" rx="76.8"/><path fill="#fff" d="m355.6 330 11.4-74h-71v-48c0-20.2 9.9-40 41.7-40H370v-63s-29.3-5-57.3-5c-58.5 0-96.7 35.4-96.7 99.6V256h-65v74h65v182h80V330z"/></svg>

After

Width:  |  Height:  |  Size: 274 B

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 24 24"><path fill="#231f20" d="M12 1A10.89 10.89 0 0 0 1 11.77 10.79 10.79 0 0 0 8.52 22c.55.1.75-.23.75-.52v-1.83c-3.06.65-3.71-1.44-3.71-1.44a2.86 2.86 0 0 0-1.22-1.58c-1-.66.08-.65.08-.65a2.31 2.31 0 0 1 1.68 1.11 2.37 2.37 0 0 0 3.2.89 2.33 2.33 0 0 1 .7-1.44c-2.44-.27-5-1.19-5-5.32a4.15 4.15 0 0 1 1.11-2.91 3.78 3.78 0 0 1 .11-2.84s.93-.29 3 1.1a10.68 10.68 0 0 1 5.5 0c2.1-1.39 3-1.1 3-1.1a3.78 3.78 0 0 1 .11 2.84A4.15 4.15 0 0 1 19 11.2c0 4.14-2.58 5.05-5 5.32a2.5 2.5 0 0 1 .75 2v2.95s.2.63.75.52A10.8 10.8 0 0 0 23 11.77 10.89 10.89 0 0 0 12 1"/></svg>

After

Width:  |  Height:  |  Size: 582 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="236" preserveAspectRatio="xMidYMid"><path fill="#e24329" d="m128.075 236.075 47.104-144.97H80.97z"/><path fill="#fc6d26" d="M128.075 236.074 80.97 91.104H14.956z"/><path fill="#fca326" d="M14.956 91.104.642 135.16a9.752 9.752 0 0 0 3.542 10.903l123.891 90.012z"/><path fill="#e24329" d="M14.956 91.105H80.97L52.601 3.79c-1.46-4.493-7.816-4.492-9.275 0z"/><path fill="#fc6d26" d="m128.075 236.074 47.104-144.97h66.015z"/><path fill="#fca326" d="m241.194 91.104 14.314 44.056a9.752 9.752 0 0 1-3.543 10.903l-123.89 90.012z"/><path fill="#e24329" d="M241.194 91.105h-66.015l28.37-87.315c1.46-4.493 7.816-4.492 9.275 0z"/></svg>

After

Width:  |  Height:  |  Size: 684 B

View File

@@ -0,0 +1 @@
<svg width="48" height="48"><path fill="#ffc107" d="M43.611 20.083H42V20H24v8h11.303c-1.649 4.657-6.08 8-11.303 8-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 12.955 4 4 12.955 4 24s8.955 20 20 20 20-8.955 20-20c0-1.341-.138-2.65-.389-3.917z"/><path fill="#ff3d00" d="m6.306 14.691 6.571 4.819C14.655 15.108 18.961 12 24 12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 16.318 4 9.656 8.337 6.306 14.691z"/><path fill="#4caf50" d="M24 44c5.166 0 9.86-1.977 13.409-5.192l-6.19-5.238A11.91 11.91 0 0 1 24 36c-5.202 0-9.619-3.317-11.283-7.946l-6.522 5.025C9.505 39.556 16.227 44 24 44z"/><path fill="#1976d2" d="M43.611 20.083H42V20H24v8h11.303a12.04 12.04 0 0 1-4.087 5.571l.003-.002 6.19 5.238C36.971 39.205 44 34 44 24c0-1.341-.138-2.65-.389-3.917z"/></svg>

After

Width:  |  Height:  |  Size: 846 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="293.333" height="98.667" version="1.0" viewBox="0 0 220 74"><path fill="#fff" d="M62.7.6c-.4.4-.7 16.5-.7 35.9 0 26 .3 35.4 1.2 36.3 1.3 1.3 8.5 1.6 11.2.6 1.3-.5 1.6-2.4 1.6-10 0-13.3.7-13.3 13.8-.1L100.3 74h6.7c11.5 0 11.3-1-2.8-15.4-6.7-6.7-12.2-13-12.2-13.9 0-1 3.7-5.8 8.3-10.8 13.3-14.7 13.3-14.9 2.4-14.9h-7.2l-7.9 8.6c-4.8 5.2-8.5 8.4-9.5 8.2-1.4-.3-1.7-2.7-2.1-17.8L75.5.5l-6-.3c-3.4-.2-6.4 0-6.8.4zm55.7.7c-.3.8-.4 12.7-.2 26.4l.3 25 3 5.8c5 9.8 16.3 16.5 26 15.3 2.8-.3 3-.6 3.3-4.7.5-7.5-.3-9.1-4.6-9.1-5 0-10.2-2.9-12.4-7.1-2.1-4-2.6-17.7-.6-19.7.7-.7 4.2-1.2 8.5-1.2h7.3V19h-7.2c-4 0-7.8-.4-8.5-.8-.7-.5-1.3-4-1.5-9.2l-.3-8.5-6.3-.3c-4.7-.2-6.4.1-6.8 1.1zm-99.9 19C6.6 25.1.6 33.6.6 46c-.1 6.2.4 8.4 2.6 12.5 5.2 9.9 13.4 14.9 24.3 14.8 8.2 0 12.8-1.8 18.6-7 5.4-4.9 8.1-10.4 8.7-17.9.8-11-4.1-20.5-13.5-26.1-3.7-2.1-6.1-2.7-12.2-3-4.5-.1-8.9.2-10.6 1zm15.2 13.6c7 3.2 9.6 11.7 5.9 18.9-3.4 6.6-11.2 8.9-18.3 5.5-10-4.7-9.5-19.4.7-24.5 4.4-2.3 6.5-2.2 11.7.1zm137.6-13.2c-5.1 1.8-12.2 8.3-14.7 13.7-4.5 9.6-2.7 21.8 4.5 29.9 5.6 6.4 11.7 9.1 20.4 9.1 5.9 0 7.9-.4 12.8-3.1 3.1-1.7 5.7-2.7 5.7-2.3 0 .5 1.5 1.8 3.4 2.9 4.2 2.7 11.6 3.7 14.5 2.2 1.9-1.1 2.2-1.9 1.9-6.9-.3-5.5-.4-5.6-3.8-6.5-6.3-1.6-6.4-1.9-7-22l-.5-18.2-6.3-.3c-4.6-.2-6.4.1-6.8 1.1-.4 1.2-1.1 1.2-4.2.1-4.9-1.7-14.7-1.6-19.9.3zm15 12.7c5.4 2.2 8.7 7 8.7 12.4 0 10.9-9.3 17-19.5 12.8-7.5-3.2-10.1-14.4-4.8-20.7 2.7-3.2 7.4-5.8 10.6-5.9 1 0 3.2.6 5 1.4z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" style="isolation:isolate" viewBox="587.811 560.459 32 32"><path fill="none" d="M587.811 560.459h32v32h-32v-32Z"/><path fill-rule="evenodd" d="m601.469 580.246-3.859 3.858a1.02 1.02 0 1 1-1.444-1.444l3.858-3.859 2.554-2.553 3.85-3.851a1.022 1.022 0 0 1 1.445 1.445l-3.851 3.85 2.299 2.299a1.23 1.23 0 0 0 1.717.014q3.089-2.988 6.218-.117.03.026.089.083c.34.34.527.8.527 1.276a1.78 1.78 0 0 1-.528 1.277l-1.366 1.366a5.557 5.557 0 0 1-7.861.004l-3.648-3.648Zm-.923-6.031-.267-.266a1.23 1.23 0 0 1-.014-1.717q2.988-3.09.117-6.218-.026-.031-.083-.089c-.341-.34-.8-.527-1.276-.527a1.78 1.78 0 0 0-1.277.528l-1.366 1.366a5.555 5.555 0 0 0-.004 7.861l1.616 1.616a.832.832 0 0 0 1.175 0l1.379-1.378a.833.833 0 0 0 0-1.176Z"/></svg>

After

Width:  |  Height:  |  Size: 786 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" style="isolation:isolate" viewBox="587.811 592.459 32 32"><path fill="none" d="M587.811 592.459h32v32h-32v-32Z"/><path fill-rule="evenodd" d="M595.385 608.153a5.553 5.553 0 0 1 .004-7.852l1.365-1.365c.34-.34.795-.531 1.275-.527.476 0 .935.187 1.275.526l.083.089q2.868 3.125-.117 6.212a1.226 1.226 0 0 0 .014 1.714l6.036 6.036a1.226 1.226 0 0 0 1.714.014q3.086-2.985 6.212-.117l.089.083c.339.34.526.799.526 1.275.004.48-.187.935-.527 1.275l-1.365 1.365a5.553 5.553 0 0 1-7.852.004l-8.732-8.732Zm7.426-1.824a1.215 1.215 0 1 1 2.43 0 1.215 1.215 0 0 1-2.43 0Zm3.811 0a1.215 1.215 0 1 1 2.43 0 1.215 1.215 0 0 1-2.43 0Zm3.811 0a1.215 1.215 0 1 1 2.43 0 1.215 1.215 0 0 1-2.43 0Z"/></svg>

After

Width:  |  Height:  |  Size: 746 B

1
resources/web2/assets/assets/chat.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" style="isolation:isolate" viewBox="587.811 521.772 32 32"><path fill="none" d="M587.811 521.772h32v32h-32v-32Z"/><path fill-rule="evenodd" d="M601.822 543.465h7.624a3.94 3.94 0 0 0 3.942-3.938v-7.213a3.944 3.944 0 0 0-3.942-3.942h-11.27a3.944 3.944 0 0 0-3.942 3.942v7.213a3.944 3.944 0 0 0 2.901 3.798v2.543c0 1.413.787 1.726 1.757.699l2.93-3.102Zm-1.593-7.668a2.203 2.203 0 0 1 .001-3.112l.541-.541a.714.714 0 0 1 1.011 0l.033.035q1.136 1.239-.047 2.462a.486.486 0 0 0 .006.679l2.392 2.392c.186.186.49.189.679.006q1.224-1.183 2.462-.046l.035.033a.71.71 0 0 1 0 1.01l-.541.541a2.199 2.199 0 0 1-3.112.002l-3.46-3.461Z"/></svg>

After

Width:  |  Height:  |  Size: 690 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#fff"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.96 9.96 0 0 1-4.644-1.142l-4.29 1.117a.85.85 0 0 1-1.037-1.036l1.116-4.289A9.959 9.959 0 0 1 2 12C2 6.477 6.477 2 12 2Zm1.252 11H8.75l-.102.007a.75.75 0 0 0 0 1.486l.102.007h4.502l.101-.007a.75.75 0 0 0 0-1.486L13.252 13Zm1.998-3.5h-6.5l-.102.007a.75.75 0 0 0 0 1.486L8.75 11h6.5l.102-.007a.75.75 0 0 0 0-1.486L15.25 9.5Z"/></svg>

After

Width:  |  Height:  |  Size: 462 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1696255389449" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1922" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M435.2 704c-9 0-17.8-3.8-23.8-10.6l-115.2-128c-11.8-13.2-10.8-33.4 2.4-45.2 13.2-11.8 33.4-10.8 45.2 2.4l90.6 100.6 245.2-291.8c11.4-13.6 31.6-15.2 45-4 13.6 11.4 15.2 31.6 4 45l-268.8 320c-6 7-14.6 11.2-24 11.4-0.2 0.2-0.4 0.2-0.6 0.2z" p-id="1923"></path><path d="M800 928H224c-70.6 0-128-57.4-128-128V224c0-70.6 57.4-128 128-128h576c70.6 0 128 57.4 128 128v576c0 70.6-57.4 128-128 128zM224 160c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h576c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64H224z" p-id="1924"></path></svg>

After

Width:  |  Height:  |  Size: 856 B

Some files were not shown because too many files have changed in this diff Show More