Compare commits

..
32 Commits
Author SHA1 Message Date
ljw 8c97cc8686 up README 2024-09-26 13:38:43 +08:00
ljw 7ae976ee5d up README_EN.md 2024-09-26 13:21:17 +08:00
ljw e91b53eb32 test release_arm64.yml 2024-09-26 11:49:11 +08:00
ljw 90311536a7 test release_arm64.yml 2024-09-26 11:46:17 +08:00
ljw e951b7f2f9 fix Dockerfile 2024-09-26 09:29:02 +08:00
ljw e0f94b62cf add i18n 2024-09-25 22:42:36 +08:00
ljw 8cb701ec85 add i18n 2024-09-25 22:41:57 +08:00
ljw 9afd11e3b8 up readme 2024-09-24 21:16:03 +08:00
ljw a1d495f2db fix group peers 2024-09-24 19:35:20 +08:00
ljw 652fa93910 up readme 2024-09-24 15:22:21 +08:00
ljw ef414855f0 up docs 2024-09-24 15:16:07 +08:00
ljw 76cf35cdf0 up readme 2024-09-24 15:15:26 +08:00
ljw 9d7aa05032 add personal apis 2024-09-24 14:43:27 +08:00
ljw 62a22c697d up web client can get pwd if exist 2024-09-24 10:15:04 +08:00
ljw be497a5aa7 add docker.yml 2024-09-23 14:43:43 +08:00
ljw fc3e16bc63 up README.md 2024-09-23 10:21:43 +08:00
ljw df35912461 up release.yml 2024-09-23 10:13:44 +08:00
ljw 0ddc66a854 up release.yml 2024-09-23 10:10:29 +08:00
ljw 716d557d66 up test.yml 2024-09-23 10:07:10 +08:00
ljw f9edcb9d47 up test.yml 2024-09-22 21:40:17 +08:00
ljw d4623c5bc9 up test.yml 2024-09-22 21:28:43 +08:00
ljw 02ba4a3330 up test.yml 2024-09-22 20:30:12 +08:00
ljw a72d4eaf78 up test.yml 2024-09-22 19:46:37 +08:00
ljw 4d3abb2dc0 up test.yml 2024-09-22 17:58:42 +08:00
ad6a8d1f7a Update test.yml 2024-09-21 22:49:06 +08:00
a5c8c2ac97 Update test.yml 2024-09-21 21:55:36 +08:00
1237004cb1 Update test.yml 2024-09-21 21:44:43 +08:00
c2bcd17df7 Update go.yml 2024-09-21 21:43:31 +08:00
bb49cbdd50 Update test.yml 2024-09-21 21:40:36 +08:00
6ad770a824 Create test.yml 2024-09-21 21:35:55 +08:00
593eeb3ac3 Update go.yml 2024-09-21 21:17:03 +08:00
6a7be3ef84 Create go.yml 2024-09-21 21:13:54 +08:00
208 changed files with 4883 additions and 23496 deletions
-29
View File
@@ -1,29 +0,0 @@
# Ignore Docker Compose configuration files
docker-compose.yaml
docker-compose-dev.yaml
# Ignore development Dockerfile
Dockerfile
Dockerfile.dev
docker-dev.sh
# Ignore the data directory
data/
# Ignore version control system directories
.git/
# Ignore log and temporary files
*.log
*.tmp
*.swp
# Ignore editor/IDE configuration files
.vscode/
.idea/
# Ignore binaries and build cache
release/
bin/
*.exe
*.out
-1
View File
@@ -1,2 +1 @@
resources/web/**/* linguist-vendored resources/web/**/* linguist-vendored
resources/web2/**/* linguist-vendored
-61
View File
@@ -1,61 +0,0 @@
name: Build & Push Docker Image
# Builds the Docker image and publishes it to the Gitea container registry
# at git.hackner.dev/thomas/rustdesk-api.
#
# Requires a running Gitea Actions runner (act_runner) and two repo secrets
# (Settings -> Actions -> Secrets):
# REGISTRY_USER - Gitea username (e.g. thomas)
# REGISTRY_PASSWORD - a Gitea access token with "write:package" scope
#
# (Alternatively the built-in ${{ secrets.GITHUB_TOKEN }} can be used with
# username ${{ gitea.actor }} if your Gitea grants it package write access.)
on:
push:
branches:
- master
tags:
- 'v*.*.*'
workflow_dispatch:
env:
REGISTRY: git.hackner.dev
IMAGE_NAME: ${{ github.repository }} # -> thomas/rustdesk-api
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to the Gitea container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.dev
push: true
build-args: |
GO_VERSION=1.23
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+38
View File
@@ -0,0 +1,38 @@
name: Build and Push Docker Image
on:
push:
tags:
- 'v*.*.*' # 仅当推送标签(例如 v1.0.0)时触发
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Extract version from tag
id: vars
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: lejianwen/rustdesk-api:latest, lejianwen/rustdesk-api:${{ env.TAG }}
+27
View File
@@ -0,0 +1,27 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.22'
- name: mod tidy
run: go mod tidy
- name: Build
run: go build -v -o release/apimain cmd/apimain.go
- name: Test
run: go test -v cmd/apimain.go
+95
View File
@@ -0,0 +1,95 @@
name: Build and Release
on:
push:
tags:
- 'v*.*.*' # 当推送带有版本号的 tag(例如 v1.0.0)时触发工作流
#on:
# push:
# branches: [ "master" ]
# pull_request:
# branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
goos: [ linux, windows ] # 指定要构建的操作系统
goarch: [ amd64 ] # 指定架构
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go environment
uses: actions/setup-go@v4
with:
go-version: '1.22' # 选择 Go 版本
- name: Set up npm
uses: actions/setup-node@v2
with:
node-version: '20'
- name: install gcc zip musl
run: |
if [ "${{ matrix.goos }}" = "windows" ]; then
sudo apt-get install gcc-mingw-w64-x86-64 zip -y
else
sudo apt-get install musl musl-dev musl-tools -y
fi
- name: build rustdesk-api-web
run: |
git clone https://github.com/lejianwen/rustdesk-api-web
cd rustdesk-api-web
npm install
npm run build
mkdir ../resources/admin/ -p
cp -ar dist/* ../resources/admin/
- name: tidy
run: go mod tidy
- name: swag
run: |
go install github.com/swaggo/swag/cmd/swag@latest
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
- name: Build for ${{ matrix.goos }}-${{ matrix.goarch }}
run: |
mkdir release -p
cp -ar resources release/
cp -ar docs release/
cp -ar conf release/
mkdir -p release/data
mkdir -p release/runtime
if [ "${{ matrix.goos }}" = "windows" ]; then
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} CC=x86_64-w64-mingw32-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain.exe ./cmd/apimain.go
zip -r ${{ matrix.goos}}-${{ matrix.goarch }}.zip ./release
else
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} CC=musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
tar -czf ${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz ./release
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: myapp-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz
${{ matrix.goos}}-${{ matrix.goarch }}.zip
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz
${{ matrix.goos}}-${{ matrix.goarch }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+84
View File
@@ -0,0 +1,84 @@
name: Build and Release Arm64
on:
push:
tags:
- 'v*.*.*' # 当推送带有版本号的 tag(例如 v1.0.0)时触发工作流
#on:
# push:
# branches: [ "master" ]
# pull_request:
# branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
goos: [ linux ] # 指定要构建的操作系统
goarch: [ arm64 ] # 指定架构
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go environment
uses: actions/setup-go@v4
with:
go-version: '1.22' # 选择 Go 版本
- name: Set up npm
uses: actions/setup-node@v2
with:
node-version: '20'
- name: install gcc
run: |
sudo apt-get install gcc-aarch64-linux-gnu -y
- name: build rustdesk-api-web
run: |
git clone https://github.com/lejianwen/rustdesk-api-web
cd rustdesk-api-web
npm install
npm run build
mkdir ../resources/admin/ -p
cp -ar dist/* ../resources/admin/
- name: tidy
run: go mod tidy
- name: swag
run: |
go install github.com/swaggo/swag/cmd/swag@latest
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
- name: Build for ${{ matrix.goos }}-${{ matrix.goarch }}
run: |
mkdir release -p
cp -ar resources release/
cp -ar docs release/
cp -ar conf release/
mkdir -p release/data
mkdir -p release/runtime
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} CC=aarch64-linux-gnu-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
tar -czf ${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz ./release
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: myapp-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
${{ matrix.goos}}-${{ matrix.goarch }}.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+6 -4
View File
@@ -1,8 +1,10 @@
.idea .idea
runtime/* runtime/*
!runtime !runtime/cache/.gitignore
!runtime/cache/.gitkeep
go.sum go.sum
resources/admin resources/*
!resources/public/upload/.gitignore
!resources/web
!resources/i18n
release release
data/rustdeskapi.db data
+29 -3
View File
@@ -1,9 +1,35 @@
FROM alpine FROM golang:1.22-alpine as builder
ARG BUILDARCH RUN set -eux; \
apk add --no-cache git gcc build-base sqlite-dev npm nodejs; \
git clone https://github.com/lejianwen/rustdesk-api-web; \
git clone https://github.com/lejianwen/rustdesk-api; \
#先编译后台
cd rustdesk-api-web; \
npm install; \
npm run build; \
cd ..; \
mkdir -p rustdesk-api/resources/admin; \
cp -ar rustdesk-api-web/dist/* rustdesk-api/resources/admin; \
cd rustdesk-api; \
go mod tidy; \
go install github.com/swaggo/swag/cmd/swag@latest; \
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin; \
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api; \
go env -w GO111MODULE=on;\
CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go; \
cp -ar resources release/; \
mkdir -p release/resources/public; \
cp -ar docs release/; \
cp -ar conf release/; \
mkdir -p release/data; \
mkdir -p release/runtime;
FROM alpine
WORKDIR /app WORKDIR /app
RUN apk add --no-cache tzdata RUN apk add --no-cache tzdata
COPY ./${BUILDARCH}/release /app/ COPY --from=builder /go/rustdesk-api/release /app/
VOLUME /app/data VOLUME /app/data
EXPOSE 21114 EXPOSE 21114
-94
View File
@@ -1,94 +0,0 @@
# Use build arguments for Go version and architecture
ARG GO_VERSION=1.22
ARG BUILDARCH=amd64
# Stage 1: Builder Stage
# FROM golang:${GO_VERSION}-alpine AS builder
FROM crazymax/xgo:${GO_VERSION} AS builder-backend
# Set up working directory
WORKDIR /app
# Step 1: Copy the source code
COPY . .
# use --mount=type=cache,target=/go/pkg/mod to cache the go mod
# Step 2: Download dependencies
RUN --mount=type=cache,target=/go/pkg/mod \
go mod tidy && go mod download && go install github.com/swaggo/swag/cmd/swag@latest
# Step 3: Run swag build script
RUN --mount=type=cache,target=/go/pkg/mod \
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin && \
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
# Step 4: Build the Go application with CGO enabled and specified ldflags
RUN --mount=type=cache,target=/go/pkg/mod \
CGO_ENABLED=1 GOOS=linux go build -a \
-ldflags "-s -w --extldflags '-static -fpic'" \
-installsuffix cgo -o release/apimain cmd/apimain.go
# Stage 2: Frontend Build Stage (builder2)
FROM node:18-alpine AS builder-admin-frontend
# Set working directory
WORKDIR /frontend
ARG COUNTRY
# Install required tools without caching index to minimize image size
RUN if [ "$COUNTRY" = "CN" ] ; then \
echo "It is in China, updating the repositories"; \
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories; \
fi && \
apk update && apk add --no-cache git
ARG FRONTEND_GIT_REPO=https://github.com/lejianwen/rustdesk-api-web.git
ARG FRONTEND_GIT_BRANCH=master
# Clone the frontend repository
RUN git clone -b $FRONTEND_GIT_BRANCH $FRONTEND_GIT_REPO .
# Install required tools without caching index to minimize image size
RUN if [ "$COUNTRY" = "CN" ] ; then \
echo "It is in China, updating NPM_CONFIG_REGISTRY"; \
export NPM_CONFIG_REGISTRY="https://mirrors.huaweicloud.com/repository/npm/"; \
fi && \
npm install && npm run build
# Stage 2: Final Image
FROM alpine:latest
# Set up working directory
WORKDIR /app
# Install necessary runtime dependencies
# Install required tools without caching index to minimize image size
ARG COUNTRY
RUN if [ "$COUNTRY" = "CN" ] ; then \
echo "It is in China, updating the repositories"; \
sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories; \
fi && \
apk update && apk add --no-cache tzdata file
# Copy the built application and resources from the builder stage
COPY --from=builder-backend /app/release /app/
COPY --from=builder-backend /app/conf /app/conf/
COPY --from=builder-backend /app/resources /app/resources/
COPY --from=builder-backend /app/docs /app/docs/
# Copy frontend build from builder2 stage
COPY --from=builder-admin-frontend /frontend/dist/ /app/resources/admin/
# Ensure the binary is correctly built and linked
RUN file /app/apimain && \
mkdir -p /app/data && \
mkdir -p /app/runtime
# Set up a volume for persistent data
VOLUME /app/data
# Expose the necessary port
EXPOSE 21114
# Define the command to run the application
CMD ["./apimain"]
-38
View File
@@ -1,38 +0,0 @@
FROM rustdesk/rustdesk-server-s6:latest AS server
FROM alpine
ARG BUILDARCH
WORKDIR /app
RUN apk add --no-cache tzdata
COPY ./${BUILDARCH}/release /app/
COPY --from=server /init /init
COPY --from=server /etc/s6-overlay /etc/s6-overlay
COPY --from=server /package /package
COPY --from=server /usr/bin/healthcheck.sh /usr/bin/healthcheck.sh
COPY --from=server /usr/bin/hbbr /usr/bin/hbbr
COPY --from=server /usr/bin/hbbs /usr/bin/hbbs
COPY --from=server /usr/bin/rustdesk-utils /usr/bin/rustdesk-utils
COPY --from=server /command /command
RUN \
mkdir -p /etc/s6-overlay/s6-rc.d/api && \
echo -e "key-secret\nhbbs" > /etc/s6-overlay/s6-rc.d/api/dependencies && \
echo "longrun" > /etc/s6-overlay/s6-rc.d/api/type && \
echo "#!/command/with-contenv sh" > /etc/s6-overlay/s6-rc.d/api/run && \
echo "cd /app" >> /etc/s6-overlay/s6-rc.d/api/run && \
echo "./apimain" >> /etc/s6-overlay/s6-rc.d/api/run && \
touch /etc/s6-overlay/s6-rc.d/user/contents.d/api && \
echo "/package/admin/s6/command/s6-svstat /run/s6-rc/servicedirs/api || exit 1" >> /usr/bin/healthcheck.sh && \
ln -s /run /var/run
ENV RELAY=relay.example.com
ENV ENCRYPTED_ONLY=0
VOLUME /data
VOLUME /app/data
EXPOSE 21114 21115 21116 21116/udp 21117 21118 21119
ENTRYPOINT ["/init"]
-21
View File
@@ -1,21 +0,0 @@
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.
+307 -259
View File
@@ -1,250 +1,363 @@
# RustDesk API # RustDesk API
[中文文档](README_EN.md) [English Doc](README_EN.md)
This project implements the RustDesk API using Go, and includes both a web UI and web client. RustDesk is a remote 本项目使用 Go 实现了 RustDesk API,并包含了 Web Admin 和 Web 客户端。RustDesk 是一个远程桌面软件,提供了自托管的解决方案。
desktop software that provides self-hosted solutions.
<div align=center> <div align=center>
<img src="https://img.shields.io/badge/golang-1.22-blue"/> <img src="https://img.shields.io/badge/golang-1.22-blue"/>
<img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/> <img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/>
<img src="https://img.shields.io/badge/gorm-v1.25.7-green"/> <img src="https://img.shields.io/badge/gorm-v1.25.7-green"/>
<img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/> <img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/>
<img src="https://goreportcard.com/badge/github.com/lejianwen/rustdesk-api/v2"/> <img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/release.yml/badge.svg"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/> <img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/docker.yml/badge.svg"/>
</div> </div>
## Better used with [lejianwen/rustdesk-server]. # 特性
> [lejianwen/rustdesk-server] is a fork of the official RustDesk Server repository.
> 1. Solves the API connection timeout issue.
> 2. Can enforce login before initiating a connection.
> 3. Supports client websocket.
- PC端API
# Features - 个人版API
- 登录
- PC API - 地址簿
- Personal API - 群组
- Login - 授权登录,支持`github``google`登录,支持`web后台`授权登录
- Address Book
- Groups
- Authorized login,
- supports `GitHub`, `Google` and `OIDC` login,
- supports `web admin` authorized login,
- supports LDAP(test AD and openladp) if API Server config
- i18n - i18n
- Web Admin - Web Admin
- User Management - 用户管理
- Device Management - 设备管理
- Address Book Management - 地址簿管理
- Tag Management - 标签管理
- Group Management - 群组管理
- OAuth Management - Oauth 管理
- LDAP Config by config file or ENV - 快速使用web client
- Login Logs
- Connection Logs
- File Transfer Logs
- Quick access to web client
- i18n - i18n
- Share to guest by web client
- Server control (some simple official commands [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command))
- Web Client - Web Client
- Automatically obtain API server - 自动获取API server
- Automatically obtain ID server and KEY - 自动获取ID服务器和KEY
- Automatically obtain address book - 自动获取地址簿
- Visitors are remotely to the device via a temporary sharing link
- CLI
- Reset admin password
## Overview ## 使用前准备
### API Service ### [Rustdesk](https://github.com/rustdesk/rustdesk)
Basic implementation of the PC client's primary interfaces.Supports the Personal version api, which can be enabled by configuring the `rustdesk.personal` file or the `RUSTDESK_API_RUSTDESK_PERSONAL` environment variable.
<table> 1. PC客户端使用的是 ***1.3.0***,经测试 ***1.2.6+*** 都可以
<tr> 2. server端必须指定key,不能用自带的生成的key,否则可能链接不上或者超时
<td width="50%" align="center" colspan="2"><b>Login</b></td>
</tr>
<tr>
<td width="50%" align="center" colspan="2"><img src="docs/en_img/pc_login.png"></td>
</tr>
<tr>
<td width="50%" align="center"><b>Address Book</b></td>
<td width="50%" align="center"><b>Groups</b></td>
</tr>
<tr>
<td width="50%" align="center"><img src="docs/en_img/pc_ab.png"></td>
<td width="50%" align="center"><img src="docs/en_img/pc_gr.png"></td>
</tr>
</table>
### Web Admin ```bash
hbbs -r <relay-server-ip[:port]> -k <key>
hbbr -k <key>
```
* The frontend and backend are separated to provide a user-friendly management interface, primarily for managing and 比如
displaying data.Frontend code is available at [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)
* Admin panel URL: `http://<your server[:port]>/_admin/` ```bash
* For the initial installation, the admin username is `admin`, and the password will be printed in the console. You can change the password via the [command line](#CLI). hbbs -r <relay-server-ip[:port]> -k abc1234567
hbbr -k abc1234567
```
![img.png](./docs/init_admin_pwd.png) ## 功能
### API 服务: 基本实现了PC端基础的接口。支持Personal版本接口,可以通过配置文件`rustdesk.personal`或环境变量`RUSTDESK_API_RUSTDESK_PERSONAL`来控制是否启用
1. Admin interface: #### 登录
![web_admin](docs/en_img/web_admin.png)
2. Regular user interface:
![web_user](docs/en_img/web_admin_user.png)
3. Each user can have multiple address books, which can also be shared with other users. - 添加了`github`和`google`授权登录,需要在后台配置好就可以用了,具体可看后台OAuth配置
4. Groups can be customized for easy management. Currently, two types are supported: `shared group` and `regular group`. - 添加了web后台授权登录,点击后直接登录后台就自动登录客户端了
5. You can directly launch the client or open the web client for convenience; you can also share it with guests, who can remotely access the device via the web client.
6. OAuth support: Currently, `GitHub`, `Google` and `OIDC` are supported. You need to create an `OAuth App` and configure it in
the admin panel.
- For `Google` and `Github`, you don't need to fill the `Issuer` and `Scpoes`
- For `OIDC`, you must set the `Issuer`. And `Scopes` is optional which default is `openid,email,profile`, please make sure this `Oauth App` can access `sub`, `email` and `preferred_username`
- Create a `GitHub OAuth App`
at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers).
- Set the `Authorization callback URL` to `http://<your server[:port]>/api/oidc/callback`,
e.g., `http://127.0.0.1:21114/api/oidc/callback`.
7. Login logs ![pc_login](docs/pc_login.png)
8. Connection logs
9. File transfer logs
10. Server control
- `Simple mode`, some simple commands have been GUI-ized and can be executed directly in the backend
![rustdesk_command_simple](./docs/en_img/rustdesk_command_simple.png)
- `Advanced mode`, commands can be executed directly in the backend #### 地址簿
* Official commands can be used
* Custom commands can be added
* Custom commands can be executed
11. **LDAP Support**, When you setup the LDAP(test for OpenLDAP and AD), you can login with the LDAP's user. https://github.com/lejianwen/rustdesk-api/issues/114 , if LDAP fail fallback local user ![pc_ab](docs/pc_ab.png)
#### 群组,群组分为`共享组`和`普通组`,共享组中所有人都能看到小组成员的设备,普通组只有管理员能看到所有小组成员的设备
![pc_gr](docs/pc_gr.png)
### Web Admin:
***使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)***
***后台访问地址是`http://<your server>[:port]/_admin/`初次安装管理员为用户名密码为`admin` `admin`,请即时更改密码***
1. 管理员界面
![web_admin](docs/web_admin.png)
2. 普通用户界面
![web_user](docs/web_admin_user.png)
右上角也可以更改密码
![web_resetpwd](docs/web_resetpwd.png)
3. 分组可以自定义,方便管理,暂时支持两种类型: `共享组` 和 `普通组`
![web_admin_gr](docs/web_admin_gr.png)
4. 可以直接打开webclient,方便使用
![web_webclient](docs/admin_webclient.png)
5. Oauth,暂时只支持了`Github`和`Google`, 需要创建一个`OAuth App`,然后配置到后台
![web_admin_oauth](docs/web_admin_oauth.png)
- `github oauth app`在`Settings`->`Developer settings`->`OAuth Apps`->`New OAuth App`
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
- `Authorization callback URL`填写`http://<your server[:port]>/api/oauth/callback`
,比如`http://127.0.0.1:21114/api/oauth/callback`
### Web Client: ### Web Client:
1. If you're already logged into the admin panel, the web client will log in automatically. 1. 如果已经登录了后台,web client将自动直接登录
2. If you're not logged in, simply click the login button in the top right corner, and the API server will be 2. 如果没登录后台,点击右上角登录即可,api server已经自动配置好了
pre-configured. ![webclient_conf](docs/webclient_conf.png)
3. After logging in, the ID server and key will be automatically synced. 3. 登录后,会自动同步ID服务器和KEY
4. The address book will also be automatically saved to the web client for convenient use. 4. 登录后,会将地址簿自动保存到web client中,方便使用
### Automated Documentation : API documentation is generated using Swag, making it easier for developers to understand and use the API. ### 自动化文档: 使用 Swag 生成 API 文档,方便开发者理解和使用 API
1. Admin panel docs: `<your server[:port]>/admin/swagger/index.html` 1. 后台文档 `<youer server[:port]>/admin/swagger/index.html`
2. PC client docs: `<your server[:port]>/swagger/index.html` 2. PC端文档 `<youer server[:port]>/swagger/index.html`
![api_swag](docs/api_swag.png) ![api_swag](docs/api_swag.png)
### CLI ## 安装与运行
```bash
# help ### 相关配置
./apimain -h
* 参考`conf/config.yaml`配置文件,修改相关配置。
* 如果`gorm.type`是`sqlite`,则不需要配置mysql相关配置。
* 语言如果不设置默认为`zh-CN`
```yaml
lang: "en"
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
resources-path: 'resources'
trust-proxy: ""
gorm:
type: "sqlite"
max-idle-conns: 10
max-open-conns: 100
mysql:
username: "root"
password: "111111"
addr: "192.168.1.66:3308"
dbname: "rustdesk"
rustdesk:
id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117"
api-server: "http://192.168.1.66:21114"
key: "123456789"
personal: 1
``` ```
#### Reset admin password * 环境变量,变量名前缀是RUSTDESK_API,环境变量如果存在将覆盖配置文件中的配置
| 变量名 | 说明 | 示例 |
|-------------------------------------|--------------------------------------|-----------------------------|
| TZ | 时区 | Asia/Shanghai |
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
| -----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 |
### 安装步骤
#### docker运行
1. 直接docker运行,配置可以通过挂载配置文件`/app/conf/config.yaml`来修改,或者通过环境变量覆盖配置文件中的配置
```bash ```bash
./apimain reset-admin-pwd <pwd> docker run -d --name rustdesk-api -p 21114:21114 \
-v /data/rustdesk/api:/app/data \
-e TZ=Asia/Shanghai \
-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \
-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \
-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \
-e RUSTDESK_API_RUSTDESK_KEY=123456789 \
lejianwen/rustdesk-api
``` ```
## Installation and Setup 2. 使用`docker compose`
### Configuration - 简单示例
* [Config File](./conf/config.yaml) ```docker-compose
* Modify the configuration in `conf/config.yaml`. services:
* If `gorm.type` is set to `sqlite`, MySQL-related configurations are not required. rustdesk-api:
* Language support: `en` and `zh-CN` are supported. The default is `zh-CN`. 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=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
- 根据rustdesk提供的示例加上自己的rustdesk-api
### Environment Variables ```docker-compose
The environment variables correspond one-to-one with the configurations in the `conf/config.yaml` file. The prefix for variable names is `RUSTDESK_API`. networks:
The table below does not list all configurations. Please refer to the configurations in `conf/config.yaml`. 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 123456789 # 填入个人域名或 IP + hbbr 暴露端口
volumes:
- /data/rustdesk/hbbs:/root # 自定义挂载目录
networks:
- rustdesk-net
depends_on:
- hbbr
restart: unless-stopped
deploy:
resources:
limits:
memory: 64M
hbbr:
container_name: hbbr
ports:
- 21117:21117 # 自定义 hbbr 映射端口
image: rustdesk/rustdesk-server
command: hbbr -k 123456789
#command: hbbr
volumes:
- /data/rustdesk/hbbr:/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=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
| 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` |
| RUSTDESK_API_APP_TOKEN_EXPIRE | token expire duration | `168h` |
| RUSTDESK_API_APP_DISABLE_PWD_LOGIN | disable password login | `false` |
| RUSTDESK_API_APP_REGISTER_STATUS | register user default status ; 1 enabled , 2 disabled ; default 1 | `1` |
| RUSTDESK_API_APP_CAPTCHA_THRESHOLD | captcha threshold; -1 disabled, 0 always enable, >0 threshold ;default `3` | `3` |
| RUSTDESK_API_APP_BAN_THRESHOLD | ban ip threshold; 0 disabled, >0 threshold ; 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_API_MYSQL_TLS | Whether to enable TLS, optional values: `true`, `false`, `skip-verify`, `custom` | `false` |
| ----- 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<br/>_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` |
| RUSTDESK_API_RUSTDESK_WS_HOST | Custom Websocket Host | `wss://192.168.1.123:1234` |
| ---- PROXY ----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
| ----JWT---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | Custom JWT KEY, if empty JWT is not enabled.<br/>If `MUST_LOGIN` from `lejianwen/rustdesk-server` is not used, it is recommended to leave it empty. | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT expire duration | `168h` |
### Installation Steps - 如果使用的是S6的镜像,会需要修改启动脚本,覆盖镜像中的`/etc/s6-overlay/s6-rc.d/hbbr/run`
和`/etc/s6-overlay/s6-rc.d/hbbr/run`
#### Running via Docker 1. 创建`hbbr/run`
1. Run directly with Docker. Configuration can be modified by mounting the config file `/app/conf/config.yaml`, or by ```bash
using environment variables to override settings. #!/command/with-contenv sh
cd /data
PARAMS=
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
/usr/bin/hbbr $PARAMS
```
```bash 2. 创建`hbbs/run`
docker run -d --name rustdesk-api -p 21114:21114 \ ```bash
-v /data/rustdesk/api:/app/data \ #!/command/with-contenv sh
-e RUSTDESK_API_LANG=en \ sleep 2
-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \ cd /data
-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \ PARAMS=
-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \ [ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
-e RUSTDESK_API_RUSTDESK_KEY=abc123456 \ /usr/bin/hbbs -r $RELAY $PARAMS
lejianwen/rustdesk-api ```
``` 3. 修改`docker-compose.yml`中的`s6`部分
2. Using `docker-compose`,look [WIKI](https://github.com/lejianwen/rustdesk-api/wiki) ```
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=abc123456789
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=abc123456789
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载
networks:
- rustdesk-net
restart: unless-stopped
```
#### Running from Release #### 下载release直接运行
Download the release from [release](https://github.com/lejianwen/rustdesk-api/releases). 下载地址[release](https://github.com/lejianwen/rustdesk-api/releases)
#### Source Installation #### 源码安装
1. Clone the repository: 1. 克隆仓库
```bash ```bash
git clone https://github.com/lejianwen/rustdesk-api.git git clone https://github.com/lejianwen/rustdesk-api.git
cd rustdesk-api cd rustdesk-api
``` ```
2. Install dependencies: 2. 安装依赖
```bash ```bash
go mod tidy go mod tidy
# Install Swag if you need to generate documentation; otherwise, you can skip this step #安装swag,如果不需要生成文档,可以不安装
go install github.com/swaggo/swag/cmd/swag@latest go install github.com/swaggo/swag/cmd/swag@latest
``` ```
3. Build the admin front-end (the front-end code is 3. 编译后台前端,前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)中
in [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)):
```bash ```bash
cd resources cd resources
mkdir -p admin mkdir -p admin
@@ -254,84 +367,19 @@ Download the release from [release](https://github.com/lejianwen/rustdesk-api/re
npm run build npm run build
cp -ar dist/* ../admin/ cp -ar dist/* ../admin/
``` ```
4. 运行
4. Run:
```bash ```bash
# Run directly #直接运行
go run cmd/apimain.go go run cmd/apimain.go
# Or generate and run the API using generate_api.go #或者使用generate_api.go生成api并运行
go generate generate_api.go go generate generate_api.go
``` ```
> **Note:** When using `go run` or the compiled binary, the `conf` and `resources` 5. 编译,如果想自己编译,先cd到项目根目录,然后windows下直接运行`build.bat`,linux下运行`build.sh`,编译后会在`release`
> directories must exist relative to the current working directory. If you run 目录下生成对应的可执行文件。直接运行编译后的可执行文件即可。
> the program from another location, specify absolute paths with `-c` and the
> `RUSTDESK_API_GIN_RESOURCES_PATH` environment variable. Example:
> ```bash
> RUSTDESK_API_GIN_RESOURCES_PATH=/opt/rustdesk-api/resources ./apimain -c /opt/rustdesk-api/conf/config.yaml
> ```
5. To compile, change to the project root directory. For Windows, run `build.bat`, and for Linux, run `build.sh`. After 6. 打开浏览器访问`http://<your server[:port]>/_admin/`,默认用户名密码为`admin`,请及时更改密码。
compiling, the corresponding executables will be generated in the `release` directory. Run the compiled executables
directly.
6. Open your browser and visit `http://<your server[:port]>/_admin/`, with default credentials `admin admin`. Please ## 其他
change the password promptly.
#### Running with my forked server-s6 image - [修改客户端ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [webclient](https://hub.docker.com/r/keyurbhole/flutter_web_desk)
- Connection timeout issue resolved
- Can enforce login before initiating a connection
- github https://github.com/lejianwen/rustdesk-server
```yaml
networks:
rustdesk-net:
external: false
services:
rustdesk:
ports:
- 21114:21114
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21117:21117
- 21118:21118
- 21119:21119
image: lejianwen/rustdesk-server-s6:latest
environment:
- RELAY=<relay_server[:port]>
- ENCRYPTED_ONLY=1
- MUST_LOGIN=N
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=<id_server[:21116]>
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=<relay_server[:21117]>
- RUSTDESK_API_RUSTDESK_API_SERVER=http://<api_server[:21114]>
- RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub
- RUSTDESK_API_JWT_KEY=xxxxxx # jwt key
volumes:
- /data/rustdesk/server:/data
- /data/rustdesk/api:/app/data # mount the database
networks:
- rustdesk-net
restart: unless-stopped
```
## Others
- [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
- [Connection Timeout](https://github.com/lejianwen/rustdesk-api/issues/92)
- [Change client ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [Web client source](https://hub.docker.com/r/keyurbhole/flutter_web_desk)
## Acknowledgements
Thanks to everyone who contributed!
<a href="https://github.com/lejianwen/rustdesk-api/graphs/contributors">
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
</a>
## Thanks for your support! If you find this project useful, please give it a ⭐️. Thank you!
[lejianwen/rustdesk-server]: https://github.com/lejianwen/rustdesk-server
+312 -253
View File
@@ -1,250 +1,368 @@
# RustDesk API # RustDesk API
[English Doc](README.md) This project implements the RustDesk API using Go, and includes both a web UI and web client. RustDesk is a remote
desktop software that provides self-hosted solutions.
本项目使用 Go 实现了 RustDesk 的 API,并包含了 Web Admin 和 Web 客户端。
<div align=center> <div align=center>
<img src="https://img.shields.io/badge/golang-1.22-blue"/> <img src="https://img.shields.io/badge/golang-1.22-blue"/>
<img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/> <img src="https://img.shields.io/badge/gin-v1.9.0-lightBlue"/>
<img src="https://img.shields.io/badge/gorm-v1.25.7-green"/> <img src="https://img.shields.io/badge/gorm-v1.25.7-green"/>
<img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/> <img src="https://img.shields.io/badge/swag-v1.16.3-yellow"/>
<img src="https://goreportcard.com/badge/github.com/lejianwen/rustdesk-api/v2"/> <img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/release.yml/badge.svg"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/> <img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/docker.yml/badge.svg"/>
</div> </div>
## 搭配[lejianwen/rustdesk-server]使用更佳。 # Features
> [lejianwen/rustdesk-server]fork自RustDesk Server官方仓库
> 1. 解决了使用API链接超时问题
> 2. 可以强制登录后才能发起链接
> 3. 支持客户端websocket
- PC API
- Personal API
# 特性 - Login
- Address Book
- PC端API - Groups
- 个人版API - Authorized login, supports `GitHub` and `Google` login, supports `web admin` authorized login
- 登录
- 地址簿
- 群组
- 授权登录
- 支持`github`, `google``OIDC` 登录,
- 支持`web后台`授权登录
- 支持`LDAP`(AD和OpenLDAP已测试), 如果API Server配置了LDAP
- i18n - i18n
- Web Admin - Web Admin
- 用户管理 - User Management
- 设备管理 - Device Management
- 地址簿管理 - Address Book Management
- 标签管理 - Tag Management
- 群组管理 - Group Management
- Oauth 管理 - OAuth Management
- 配置LDAP, 配置文件或者环境变量 - Quick access to web client
- 登录日志
- 链接日志
- 文件传输日志
- 快速使用web client
- i18n - i18n
- 通过 web client 分享给游客
- server控制(一些官方的简单的指令 [WIKI](https://github.com/lejianwen/rustdesk-api/wiki/Rustdesk-Command))
- Web Client - Web Client
- 自动获取API server - Automatically obtain API server
- 自动获取ID服务器和KEY - Automatically obtain ID server and KEY
- 自动获取地址簿 - Automatically obtain address book
- 游客通过临时分享链接直接远程到设备
- CLI
- 重置管理员密码
## 功能 ## Prerequisites
### [Rustdesk](https://github.com/rustdesk/rustdesk)
### API 服务 1. The PC client version used is ***1.3.0***, and versions ***1.2.6+*** have been tested to work.
基本实现了PC端基础的接口。支持Personal版本接口,可以通过配置文件`rustdesk.personal`或环境变量`RUSTDESK_API_RUSTDESK_PERSONAL`来控制是否启用 2. The server must specify a key, and not use the auto-generated key, otherwise there may be connection failures or
timeouts.
<table> ```bash
<tr> hbbs -r <relay-server-ip[:port]> -k <key>
<td width="50%" align="center" colspan="2"><b>登录</b></td> hbbr -k <key>
</tr> ```
<tr>
<td width="50%" align="center" colspan="2"><img src="docs/pc_login.png"></td>
</tr>
<tr>
<td width="50%" align="center"><b>地址簿</b></td>
<td width="50%" align="center"><b>群组</b></td>
</tr>
<tr>
<td width="50%" align="center"><img src="docs/pc_ab.png"></td>
<td width="50%" align="center"><img src="docs/pc_gr.png"></td>
</tr>
</table>
### Web Admin: Example:
* 使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web) ```bash
hbbs -r <relay-server-ip[:port]> -k abc1234567
hbbr -k abc1234567
```
* 后台访问地址是`http://<your server>[:port]/_admin/` ## Overview
* 初次安装管理员为用户名为`admin`,密码将在控制台打印,可以通过[命令行](#CLI)更改密码
![img.png](./docs/init_admin_pwd.png) ### API Service: Basic implementation of the PC client's primary interfaces.Supports the Personal version api, which can be enabled by configuring the `rustdesk.personal` file or the `RUSTDESK_API_RUSTDESK_PERSONAL` environment variable.
1. 管理员界面 #### Login
![web_admin](docs/web_admin.png)
2. 普通用户界面
![web_user](docs/web_admin_user.png)
3. 每个用户可以多个地址簿,也可以将地址簿共享给其他用户 - Added `GitHub` and `Google` login, which can be used after configuration in the admin panel. See the OAuth
4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组``普通组` configuration section for details.
5. 可以直接打开webclient,方便使用;也可以分享给游客,游客可以直接通过webclient远程到设备 - Added authorization login for the web admin panel.
6. Oauth,支持了`Github`, `Google` 以及 `OIDC`, 需要创建一个`OAuth App`,然后配置到后台
- 对于`Google``Github`, `Issuer``Scopes`不需要填写.
- 对于`OIDC`, `Issuer`是必须的。`Scopes`是可选的,默认为 `openid,profile,email`. 确保可以获取 `sub`,`email``preferred_username`
- `github oauth app``Settings`->`Developer settings`->`OAuth Apps`->`New OAuth App`
中创建,地址 [https://github.com/settings/developers](https://github.com/settings/developers)
- `Authorization callback URL`填写`http://<your server[:port]>/api/oidc/callback`
,比如`http://127.0.0.1:21114/api/oidc/callback`
7. 登录日志
8. 链接日志
9. 文件传输日志
10. server控制
- `简易模式`,已经界面化了一些简单的指令,可以直接在后台执行 ![pc_login](docs/en_img/pc_login.png)
![rustdesk_command_simple](./docs/rustdesk_command_simple.png)
- `高级模式`,直接在后台执行指令 #### Address Book
* 可以官方指令
* 可以添加自定义指令
* 可以执行自定义指令
![pc_ab](docs/en_img/pc_ab.png)
11. **LDAP 支持**, 当在API Server上设置了LDAP(已测试AD和LDAP),可以通过LDAP中的用户信息进行登录 https://github.com/lejianwen/rustdesk-api/issues/114 ,如果LDAP验证失败,返回本地用户 #### Groups: Groups are divided into `shared groups` and `regular groups`. In shared groups, everyone can see the peers of all group members, while in regular groups, only administrators can see all members' peers.
![pc_gr](docs/en_img/pc_gr.png)
### Web Admin
***The frontend and backend are separated to provide a user-friendly management interface, primarily for managing and
displaying data.Frontend code is available at [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)***
***Admin panel URL: `http://<your server[:port]>/_admin/`. The default username and password for the initial
installation are `admin` `admin`, please change the password immediately.***
1. Admin interface:
![web_admin](docs/en_img/web_admin.png)
2. Regular user interface:
![web_user](docs/en_img/web_admin_user.png)
You can change your password from the top right corner:
![web_resetpwd](docs/en_img/web_resetpwd.png)
3. Groups can be customized for easy management. Currently, two types are supported: `shared group` and `regular group`.
![web_admin_gr](docs/en_img/web_admin_gr.png)
4. You can open the web client directly for convenience:
![web_webclient](docs/en_img/admin_webclient.png)
5. OAuth support: Currently, `GitHub` and `Google` is supported. You need to create an `OAuth App` and configure it in
the admin
panel.
![web_admin_oauth](docs/en_img/web_admin_oauth.png)
- Create a `GitHub OAuth App`
at `Settings` -> `Developer settings` -> `OAuth Apps` -> `New OAuth App` [here](https://github.com/settings/developers).
- Set the `Authorization callback URL` to `http://<your server[:port]>/api/oauth/callback`,
e.g., `http://127.0.0.1:21114/api/oauth/callback`.
### Web Client: ### Web Client:
1. 如果已经登录了后台,web client将自动直接登录 1. If you're already logged into the admin panel, the web client will log in automatically.
2. 如果没登录后台,点击右上角登录即可,api server已经自动配置好了 2. If you're not logged in, simply click the login button at the top right corner, and the API server will be
3. 登录后,会自动同步ID服务器和KEY pre-configured.
4. 登录后,会将地址簿自动保存到web client中,方便使用 ![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.
### Automated Documentation : API documentation is generated using Swag, making it easier for developers to understand and use the API.
### 自动化文档: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。 1. Admin panel docs: `<your server[:port]>/admin/swagger/index.html`
2. PC client docs: `<your server[:port]>/swagger/index.html`
1. 后台文档 `<youer server[:port]>/admin/swagger/index.html`
2. PC端文档 `<youer server[:port]>/swagger/index.html`
![api_swag](docs/api_swag.png) ![api_swag](docs/api_swag.png)
### CLI ## Installation and Setup
```bash ### Configuration
# 查看帮助
./apimain -h * Modify the configuration in `conf/config.yaml`.
* If `gorm.type` is set to `sqlite`, MySQL-related configurations are not required.
* Language support: `en` and `zh-CN` are supported. The default is `zh-CN`.
```yaml
lang: "en"
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
resources-path: 'resources'
trust-proxy: ""
gorm:
type: "sqlite"
max-idle-conns: 10
max-open-conns: 100
mysql:
username: "root"
password: "111111"
addr: "192.168.1.66:3308"
dbname: "rustdesk"
rustdesk:
id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117"
api-server: "http://192.168.1.66:21114"
key: "123456789"
personal: 1
``` ```
#### 重置管理员密码 * Environment variables, with the prefix `RUSTDESK_API_RUSTDESK_PERSONAL`, will override the settings in the
configuration file if
present.
| Variable Name | Description | Example |
|------------------------------------|-----------------------------------------------------------|--------------------------------|
| TZ | timezone | Asia/Shanghai |
| RUSTDESK_API_LANG | Language | `en`,`zh-CN` |
| ----- 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 |
### Installation Steps
#### Running via Docker
1. Run directly with Docker. Configuration can be modified by mounting the config file `/app/conf/config.yaml`, or by
using environment variables to override settings.
```bash ```bash
./apimain reset-admin-pwd <pwd> docker run -d --name rustdesk-api -p 21114:21114 \
-v /data/rustdesk/api:/app/data \
-e RUSTDESK_API_LANG=en \
-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \
-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \
-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \
-e RUSTDESK_API_RUSTDESK_KEY=123456789 \
lejianwen/rustdesk-api
``` ```
## 安装与运行 2. Using `docker-compose`
### 相关配置 - Simple example:
* [配置文件](./conf/config.yaml) ```docker-compose
* 参考`conf/config.yaml`配置文件,修改相关配置。 services:
* 如果`gorm.type``sqlite`,则不需要配置mysql相关配置。 rustdesk-api:
* 语言如果不设置默认为`zh-CN` container_name: rustdesk-api
environment:
- 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=123456789
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:
环境变量和配置文件`conf/config.yaml`中的配置一一对应,变量名前缀是`RUSTDESK_API`
下面表格并未全部列出,可以参考`conf/config.yaml`中的配置。
| 变量名 | 说明 | 示例 | ```docker-compose
|--------------------------------------------------------|--------------------------------------------------------------------------------|------------------------------| networks:
| TZ | 时区 | Asia/Shanghai | rustdesk-net:
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` | external: false
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 | services:
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` | hbbs:
| RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` | container_name: hbbs
| RUSTDESK_API_APP_TOKEN_EXPIRE | token有效时长 | `168h` | ports:
| RUSTDESK_API_APP_DISABLE_PWD_LOGIN | 是否禁用密码登录; `true`, `false` 默认`false` | `false` | - 21115:21115
| RUSTDESK_API_APP_REGISTER_STATUS | 注册用户默认状态; 1 启用,2 禁用, 默认 1 | `1` | - 21116:21116 # 自定义 hbbs 映射端口
| RUSTDESK_API_APP_CAPTCHA_THRESHOLD | 验证码触发次数; -1 不启用, 0 一直启用, >0 登录错误次数后启用 ;默认 `3` | `3` | - 21116:21116/udp # 自定义 hbbs 映射端口
| RUSTDESK_API_APP_BAN_THRESHOLD | 封禁IP触发次数; 0 不启用, >0 登录错误次数后封禁IP; 默认 `0` | `0` | - 21118:21118 # web client 需要
| -----ADMIN配置----- | ---------- | ---------- | image: rustdesk/rustdesk-server
| RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` | command: hbbs -r <relay-server-ip[:port]> -k 123456789 # 填入个人域名或 IP + hbbr 暴露端口
| RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | | volumes:
| RUSTDESK_API_ADMIN_HELLO_FILE | 后台欢迎语文件,如果内容多,使用文件更方便。<br>会覆盖`RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` | - /data/rustdesk/hbbs:/root # 自定义挂载目录
| -----GIN配置----- | ---------- | ---------- | networks:
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表,以`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 | - rustdesk-net
| -----GORM配置----- | ---------- | --------------------------- | depends_on:
| RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql,默认sqlite | sqlite | - hbbr
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 | restart: unless-stopped
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 | deploy:
| RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API, 1:启用,0:不启用; 默认启用 | 1 | resources:
| -----MYSQL配置----- | ---------- | ---------- | limits:
| RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root | memory: 64M
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 | hbbr:
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 | container_name: hbbr
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk | ports:
| RUSTDESK_API_MYSQL_TLS | 是否启用TLS, 可选值: `true`, `false`, `skip-verify`, `custom` | `false` | - 21117:21117 # 自定义 hbbr 映射端口
| -----RUSTDESK配置----- | ---------- | ---------- | image: rustdesk/rustdesk-server
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 | command: hbbr -k 123456789
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 | #command: hbbr
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 | volumes:
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 | - /data/rustdesk/hbbr:/root # 自定义挂载目录
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk存放key的文件 | `./conf/data/id_ed25519.pub` | networks:
| RUSTDESK_API_RUSTDESK_WEBCLIENT<br/>_MAGIC_QUERYONLINE | Web client v2 中是否启用新的在线状态查询方法; `1`:启用,`0`:不启用,默认不启用 | `0` | - rustdesk-net
| RUSTDESK_API_RUSTDESK_WS_HOST | 自定义Websocket Host | `wss://192.168.1.123:1234` | restart: unless-stopped
| ----PROXY配置----- | ---------- | ---------- | deploy:
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` | resources:
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` | limits:
| ----JWT配置---- | -------- | -------- | memory: 64M
| RUSTDESK_API_JWT_KEY | 自定义JWT KEY,为空则不启用JWT<br/>如果没使用`lejianwen/rustdesk-server`中的`MUST_LOGIN`,建议设置为空 | | rustdesk-api:
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT有效时间 | `168h` | container_name: rustdesk-api
environment:
- 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=123456789
ports:
- 21114:21114
image: lejianwen/rustdesk-api
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
networks:
- rustdesk-net
restart: unless-stopped
```
- If you are using an S6 image, you need to modify the startup script `/etc/s6-overlay/s6-rc.d/hbbr/run`
and `/etc/s6-overlay/s6-rc.d/hbbr/run`
### 运行 1. create `hbbr/run`
#### docker运行 ```bash
#!/command/with-contenv sh
cd /data
PARAMS=
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
/usr/bin/hbbr $PARAMS
```
1. 直接docker运行,配置可以通过挂载配置文件`/app/conf/config.yaml`来修改,或者通过环境变量覆盖配置文件中的配置 2. create `hbbs/run`
```bash
#!/command/with-contenv sh
sleep 2
cd /data
PARAMS=
[ "${ENCRYPTED_ONLY}" = "1" ] && PARAMS="-k ${KEY}"
/usr/bin/hbbs -r $RELAY $PARAMS
```
3. edit `docker-compose.yml`
```
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=abc123456789
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=abc123456789
volumes:
- /data/rustdesk/api:/app/data #将数据库挂载
networks:
- rustdesk-net
restart: unless-stopped
```
```bash #### Running from Release
docker run -d --name rustdesk-api -p 21114:21114 \
-v /data/rustdesk/api:/app/data \
-e TZ=Asia/Shanghai \
-e RUSTDESK_API_LANG=zh-CN \
-e RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 \
-e RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 \
-e RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 \
-e RUSTDESK_API_RUSTDESK_KEY=<key> \
lejianwen/rustdesk-api
```
2. 使用`docker compose`,参考[WIKI](https://github.com/lejianwen/rustdesk-api/wiki) Download the release from [release](https://github.com/lejianwen/rustdesk-api/releases).
#### 下载release直接运行 #### Source Installation
[下载地址](https://github.com/lejianwen/rustdesk-api/releases) 1. Clone the repository:
#### 源码安装
1. 克隆仓库
```bash ```bash
git clone https://github.com/lejianwen/rustdesk-api.git git clone https://github.com/lejianwen/rustdesk-api.git
cd rustdesk-api cd rustdesk-api
``` ```
2. 安装依赖 2. Install dependencies:
```bash ```bash
go mod tidy go mod tidy
#安装swag,如果不需要生成文档,可以不安装 # Install Swag if you need to generate documentation; otherwise, you can skip this step
go install github.com/swaggo/swag/cmd/swag@latest go install github.com/swaggo/swag/cmd/swag@latest
``` ```
3. 编译后台前端,前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)中 3. Build the admin front-end (the front-end code is
in [rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)):
```bash ```bash
cd resources cd resources
mkdir -p admin mkdir -p admin
@@ -254,82 +372,23 @@
npm run build npm run build
cp -ar dist/* ../admin/ cp -ar dist/* ../admin/
``` ```
4. 运行
4. Run:
```bash ```bash
#直接运行 # Run directly
go run cmd/apimain.go go run cmd/apimain.go
#或者使用generate_api.go生成api并运行 # Or generate and run the API using generate_api.go
go generate generate_api.go go generate generate_api.go
``` ```
> 注意:使用 `go run` 或编译后的二进制时,当前目录下必须存在 `conf` 和 `resources`
> 目录。如果在其他目录运行,可通过 `-c` 和环境变量
> `RUSTDESK_API_GIN_RESOURCES_PATH` 指定绝对路径,例如:
> ```bash
> RUSTDESK_API_GIN_RESOURCES_PATH=/opt/rustdesk-api/resources ./apimain -c /opt/rustdesk-api/conf/config.yaml
> ```
5. 编译,如果想自己编译,先cd到项目根目录,然后windows下直接运行`build.bat`,linux下运行`build.sh`,编译后会在`release`
目录下生成对应的可执行文件。直接运行编译后的可执行文件即可。
6. 打开浏览器访问`http://<your server[:port]>/_admin/`,默认用户名密码为`admin`,请及时更改密码。 5. To compile, change to the project root directory. For Windows, run `build.bat`, and for Linux, run `build.sh`. After
compiling, the corresponding executables will be generated in the `release` directory. Run the compiled executables
directly.
6. Open your browser and visit `http://<your server[:port]>/_admin/`, with default credentials `admin admin`. Please
change the password promptly.
#### 使用`lejianwen/server-s6`镜像运行 ## Miscellaneous
- 已解决链接超时问题 - [Change client ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- 可以强制登录后才能发起链接 - [webclient](https://hub.docker.com/r/keyurbhole/flutter_web_desk)
- github https://github.com/lejianwen/rustdesk-server
```yaml
networks:
rustdesk-net:
external: false
services:
rustdesk:
ports:
- 21114:21114
- 21115:21115
- 21116:21116
- 21116:21116/udp
- 21117:21117
- 21118:21118
- 21119:21119
image: lejianwen/rustdesk-server-s6:latest
environment:
- RELAY=<relay_server[:port]>
- ENCRYPTED_ONLY=1
- MUST_LOGIN=N
- TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=<id_server[:21116]>
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=<relay_server[:21117]>
- RUSTDESK_API_RUSTDESK_API_SERVER=http://<api_server[:21114]>
- RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub
- RUSTDESK_API_JWT_KEY=xxxxxx # jwt key
volumes:
- /data/rustdesk/server:/data
- /data/rustdesk/api:/app/data #将数据库挂载
networks:
- rustdesk-net
restart: unless-stopped
```
## 其他
- [WIKI](https://github.com/lejianwen/rustdesk-api/wiki)
- [链接超时问题](https://github.com/lejianwen/rustdesk-api/issues/92)
- [修改客户端ID](https://github.com/abdullah-erturk/RustDesk-ID-Changer)
- [webclient来源](https://hub.docker.com/r/keyurbhole/flutter_web_desk)
## 鸣谢
感谢所有做过贡献的人!
<a href="https://github.com/lejianwen/rustdesk-api/graphs/contributors">
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
</a>
## 感谢你的支持!如果这个项目对你有帮助,请点个⭐️鼓励一下,谢谢!
[lejianwen/rustdesk-server]: https://github.com/lejianwen/rustdesk-server
Executable → Regular
+4 -34
View File
@@ -1,46 +1,16 @@
#!/bin/sh #!/bin/sh
set -e rm release -rf
# Automatically get the current environment's GOARCH; if not defined, use the detected system architecture
GOARCH=${GOARCH:-$(go env GOARCH)}
DOCS="true"
# Safely remove the old release directory
rm -rf release
# Set Go environment variables
go env -w GO111MODULE=on go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct go env -w GOPROXY=https://goproxy.cn,direct
go env -w CGO_ENABLED=1 go env -w CGO_ENABLED=1
go env -w GOOS=linux go env -w GOOS=linux
go env -w GOARCH=${GOARCH} go env -w GOARCH=amd64
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
# Generate Swagger documentation if DOCS is not empty
if [ -n "${DOCS}" ]; then
# Check if swag is installed
if ! command -v swag &> /dev/null; then
echo "swag command not found. Please install it using:"
echo "go install github.com/swaggo/swag/cmd/swag@latest"
echo "Skipping Swagger documentation generation due to missing swag tool."
else
echo "Generating Swagger documentation..."
swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
fi
else
echo "Skipping Swagger documentation generation due to DOCS is empty."
fi
# Compile the Go code and output it to the release directory
go build -o release/apimain cmd/apimain.go go build -o release/apimain cmd/apimain.go
# Copy resource files to the release directory
cp -ar resources release/ cp -ar resources release/
cp -ar docs release/ cp -ar docs release/
cp -ar conf release/ cp -ar conf release/
# Create necessary directory structures
mkdir -p release/data mkdir -p release/data
mkdir -p release/runtime mkdir -p release/runtime
echo "Build and setup completed successfully."
+179 -208
View File
@@ -1,33 +1,34 @@
package main package main
import ( import (
"Gwen/config"
"Gwen/global"
"Gwen/http"
"Gwen/lib/cache"
"Gwen/lib/lock"
"Gwen/lib/logger"
"Gwen/lib/orm"
"Gwen/lib/upload"
"Gwen/model"
"Gwen/service"
"fmt" "fmt"
"os" "github.com/BurntSushi/toml"
"strconv" "github.com/gin-gonic/gin"
"time" "github.com/go-playground/locales/en"
"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"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/lejianwen/rustdesk-api/v2/config"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http"
"github.com/lejianwen/rustdesk-api/v2/lib/cache"
"github.com/lejianwen/rustdesk-api/v2/lib/jwt"
"github.com/lejianwen/rustdesk-api/v2/lib/lock"
"github.com/lejianwen/rustdesk-api/v2/lib/logger"
"github.com/lejianwen/rustdesk-api/v2/lib/orm"
"github.com/lejianwen/rustdesk-api/v2/lib/upload"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/spf13/cobra" "golang.org/x/text/language"
"reflect"
) )
const DatabaseVersion = 265 // @title 管理系统API
// @title Management System API
// @version 1.0 // @version 1.0
// @description API // @description 接口
// @basePath /api // @basePath /api
// @securityDefinitions.apikey token // @securityDefinitions.apikey token
// @in header // @in header
@@ -35,94 +36,17 @@ const DatabaseVersion = 265
// @securitydefinitions.apikey BearerAuth // @securitydefinitions.apikey BearerAuth
// @in header // @in header
// @name Authorization // @name Authorization
var rootCmd = &cobra.Command{
Use: "apimain",
Short: "RUSTDESK API SERVER",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
InitGlobal()
},
Run: func(cmd *cobra.Command, args []string) {
global.Logger.Info("API SERVER START")
http.ApiInit()
},
}
var resetPwdCmd = &cobra.Command{
Use: "reset-admin-pwd [pwd]",
Example: "reset-admin-pwd 123456",
Short: "Reset Admin Password",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
pwd := args[0]
admin := service.AllService.UserService.InfoById(1)
if admin.Id == 0 {
global.Logger.Warn("user not found! ")
return
}
err := service.AllService.UserService.UpdatePassword(admin, pwd)
if err != nil {
global.Logger.Error("reset password fail! ", err)
return
}
global.Logger.Info("reset password success! ")
},
}
var resetUserPwdCmd = &cobra.Command{
Use: "reset-pwd [userId] [pwd]",
Example: "reset-pwd 2 123456",
Short: "Reset User Password",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
userId := args[0]
pwd := args[1]
uid, err := strconv.Atoi(userId)
if err != nil {
global.Logger.Warn("userId must be int!")
return
}
if uid <= 0 {
global.Logger.Warn("userId must be greater than 0! ")
return
}
u := service.AllService.UserService.InfoById(uint(uid))
if u.Id == 0 {
global.Logger.Warn("user not found! ")
return
}
err = service.AllService.UserService.UpdatePassword(u, pwd)
if err != nil {
global.Logger.Warn("reset password fail! ", err)
return
}
global.Logger.Info("reset password success!")
},
}
func init() {
rootCmd.PersistentFlags().StringVarP(&global.ConfigPath, "config", "c", "./conf/config.yaml", "choose config file")
rootCmd.AddCommand(resetPwdCmd, resetUserPwdCmd)
}
func main() { func main() {
if err := rootCmd.Execute(); err != nil { //配置解析
global.Logger.Error(err) global.Viper = config.Init(&global.Config)
os.Exit(1)
}
}
func InitGlobal() { //日志
// config parsing
global.Viper = config.Init(&global.Config, global.ConfigPath)
// logging
global.Logger = logger.New(&logger.Config{ global.Logger = logger.New(&logger.Config{
Path: global.Config.Logger.Path, Path: global.Config.Logger.Path,
Level: global.Config.Logger.Level, Level: global.Config.Logger.Level,
ReportCaller: global.Config.Logger.ReportCaller, ReportCaller: global.Config.Logger.ReportCaller,
}) })
global.InitI18n()
//redis //redis
global.Redis = redis.NewClient(&redis.Options{ global.Redis = redis.NewClient(&redis.Options{
Addr: global.Config.Redis.Addr, Addr: global.Config.Redis.Addr,
@@ -144,45 +68,23 @@ func InitGlobal() {
} }
//gorm //gorm
if global.Config.Gorm.Type == config.TypeMysql { if global.Config.Gorm.Type == config.TypeMysql {
dns := global.Config.Mysql.Username + ":" + global.Config.Mysql.Password + "@(" + global.Config.Mysql.Addr + ")/" + global.Config.Mysql.Dbname + "?charset=utf8mb4&parseTime=True&loc=Local"
dsn := fmt.Sprintf("%s:%s@(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
global.Config.Mysql.Username,
global.Config.Mysql.Password,
global.Config.Mysql.Addr,
global.Config.Mysql.Dbname,
global.Config.Mysql.Tls,
)
global.DB = orm.NewMysql(&orm.MysqlConfig{ global.DB = orm.NewMysql(&orm.MysqlConfig{
Dsn: dsn, Dns: dns,
MaxIdleConns: global.Config.Gorm.MaxIdleConns, MaxIdleConns: global.Config.Gorm.MaxIdleConns,
MaxOpenConns: global.Config.Gorm.MaxOpenConns, MaxOpenConns: global.Config.Gorm.MaxOpenConns,
}, global.Logger) })
} else if global.Config.Gorm.Type == config.TypePostgresql {
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s TimeZone=%s",
global.Config.Postgresql.Host,
global.Config.Postgresql.Port,
global.Config.Postgresql.User,
global.Config.Postgresql.Password,
global.Config.Postgresql.Dbname,
global.Config.Postgresql.Sslmode,
global.Config.Postgresql.TimeZone,
)
global.DB = orm.NewPostgresql(&orm.PostgresqlConfig{
Dsn: dsn,
MaxIdleConns: global.Config.Gorm.MaxIdleConns,
MaxOpenConns: global.Config.Gorm.MaxOpenConns,
}, global.Logger)
} else { } else {
//sqlite //sqlite
global.DB = orm.NewSqlite(&orm.SqliteConfig{ global.DB = orm.NewSqlite(&orm.SqliteConfig{
MaxIdleConns: global.Config.Gorm.MaxIdleConns, MaxIdleConns: global.Config.Gorm.MaxIdleConns,
MaxOpenConns: global.Config.Gorm.MaxOpenConns, MaxOpenConns: global.Config.Gorm.MaxOpenConns,
}, global.Logger) })
} }
DatabaseAutoUpdate()
//validator //validator
global.ApiInitValidator() ApiInitValidator()
//oss //oss
global.Oss = &upload.Oss{ global.Oss = &upload.Oss{
@@ -196,60 +98,137 @@ func InitGlobal() {
//jwt //jwt
//fmt.Println(global.Config.Jwt.PrivateKey) //fmt.Println(global.Config.Jwt.PrivateKey)
global.Jwt = jwt.NewJwt(global.Config.Jwt.Key, global.Config.Jwt.ExpireDuration) //global.Jwt = jwt.NewJwt(global.Config.Jwt.PrivateKey, global.Config.Jwt.ExpireDuration*time.Second)
//locker //locker
global.Lock = lock.NewLocal() global.Lock = lock.NewLocal()
//service InitI18n()
service.New(&global.Config, global.DB, global.Logger, global.Jwt, global.Lock) //gin
http.ApiInit()
global.LoginLimiter = utils.NewLoginLimiter(utils.SecurityPolicy{
CaptchaThreshold: global.Config.App.CaptchaThreshold,
BanThreshold: global.Config.App.BanThreshold,
AttemptsWindow: 10 * time.Minute,
BanDuration: 30 * time.Minute,
})
global.LoginLimiter.RegisterProvider(utils.B64StringCaptchaProvider{})
DatabaseAutoUpdate()
} }
func ApiInitValidator() {
validate := validator.New()
// 定义不同的语言翻译
enT := en.New()
cn := zh_Hans_CN.New()
uni := ut.New(enT, cn)
enTrans, _ := uni.GetTranslator("en")
zhTrans, _ := uni.GetTranslator("zh_Hans_CN")
err := zh_translations.RegisterDefaultTranslations(validate, zhTrans)
if err != nil {
panic(err)
}
err = en_translations.RegisterDefaultTranslations(validate, enTrans)
if err != nil {
panic(err)
}
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
label := field.Tag.Get("label")
if label == "" {
return field.Name
}
return label
})
global.Validator.Validate = validate
global.Validator.UT = uni // 存储 Universal Translator
global.Validator.VTrans = zhTrans
global.Validator.ValidStruct = func(ctx *gin.Context, i interface{}) []string {
err := global.Validator.Validate.Struct(i)
lang := ctx.GetHeader("Accept-Language")
if lang == "" {
lang = global.Config.Lang
}
trans := getTranslatorForLang(lang)
errList := make([]string, 0, 10)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
errList = append(errList, err.Error())
return errList
}
for _, err2 := range err.(validator.ValidationErrors) {
errList = append(errList, err2.Translate(trans))
}
}
return errList
}
global.Validator.ValidVar = func(ctx *gin.Context, field interface{}, tag string) []string {
err := global.Validator.Validate.Var(field, tag)
lang := ctx.GetHeader("Accept-Language")
if lang == "" {
lang = global.Config.Lang
}
trans := getTranslatorForLang(lang)
errList := make([]string, 0, 10)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
errList = append(errList, err.Error())
return errList
}
for _, err2 := range err.(validator.ValidationErrors) {
errList = append(errList, err2.Translate(trans))
}
}
return errList
}
}
func getTranslatorForLang(lang string) ut.Translator {
switch lang {
case "zh_CN":
fallthrough
case "zh-CN":
fallthrough
case "zh":
trans, _ := global.Validator.UT.GetTranslator("zh_Hans_CN")
return trans
case "en":
fallthrough
default:
trans, _ := global.Validator.UT.GetTranslator("en")
return trans
}
}
func DatabaseAutoUpdate() { func DatabaseAutoUpdate() {
version := DatabaseVersion version := 126
db := global.DB db := global.DB
if global.Config.Gorm.Type == config.TypeMysql { if global.Config.Gorm.Type == config.TypeMysql {
// check whether the database exists; create it if not //检查存不存在数据库,不存在则创建
dbName := db.Migrator().CurrentDatabase() dbName := db.Migrator().CurrentDatabase()
fmt.Println("dbName", dbName)
if dbName == "" { if dbName == "" {
dbName = global.Config.Mysql.Dbname dbName = global.Config.Mysql.Dbname
// remove the database name from the DSN so the initial connection does not specify a database // 移除 DSN 中的数据库名称,以便初始连接时不指定数据库
dsnWithoutDB := fmt.Sprintf("%s:%s@(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", dsnWithoutDB := global.Config.Mysql.Username + ":" + global.Config.Mysql.Password + "@(" + global.Config.Mysql.Addr + ")/?charset=utf8mb4&parseTime=True&loc=Local"
global.Config.Mysql.Username, //新链接
global.Config.Mysql.Password,
global.Config.Mysql.Addr,
"",
)
// new connection
dbWithoutDB := orm.NewMysql(&orm.MysqlConfig{ dbWithoutDB := orm.NewMysql(&orm.MysqlConfig{
Dsn: dsnWithoutDB, Dns: dsnWithoutDB,
}, global.Logger) })
// get the underlying *sql.DB object and ensure the connection is closed when the program exits // 获取底层的 *sql.DB 对象,并确保在程序退出时关闭连接
sqlDBWithoutDB, err := dbWithoutDB.DB() sqlDBWithoutDB, err := dbWithoutDB.DB()
if err != nil { if err != nil {
global.Logger.Errorf("failed to get underlying *sql.DB object: %v", err) fmt.Printf("获取底层 *sql.DB 对象失败: %v\n", err)
return return
} }
defer func() { defer func() {
if err := sqlDBWithoutDB.Close(); err != nil { if err := sqlDBWithoutDB.Close(); err != nil {
global.Logger.Errorf("failed to close connection: %v", err) fmt.Printf("关闭连接失败: %v\n", err)
} }
}() }()
err = dbWithoutDB.Exec("CREATE DATABASE IF NOT EXISTS " + dbName + " DEFAULT CHARSET utf8mb4").Error err = dbWithoutDB.Exec("CREATE DATABASE IF NOT EXISTS " + dbName + " DEFAULT CHARSET utf8mb4").Error
if err != nil { if err != nil {
global.Logger.Error(err) fmt.Println(err)
return return
} }
} }
@@ -258,36 +237,17 @@ func DatabaseAutoUpdate() {
if !db.Migrator().HasTable(&model.Version{}) { if !db.Migrator().HasTable(&model.Version{}) {
Migrate(uint(version)) Migrate(uint(version))
} else { } else {
// find the last version //查找最后一个version
var v model.Version var v model.Version
db.Last(&v) db.Last(&v)
if v.Version < uint(version) { if v.Version < uint(version) {
Migrate(uint(version)) Migrate(uint(version))
} }
// 245 migration
if v.Version < 245 {
// set the oauths table's oauth_type field to the same value as op
db.Exec("update oauths set oauth_type = op")
db.Exec("update oauths set issuer = 'https://accounts.google.com' where op = 'google'")
db.Exec("update user_thirds set oauth_type = third_type, op = third_type")
// migrate old google authorizations by email
uts := make([]model.UserThird, 0)
db.Where("oauth_type = ?", "google").Find(&uts)
for _, ut := range uts {
if ut.UserId > 0 {
db.Model(&model.User{}).Where("id = ?", ut.UserId).Update("email", ut.OpenId)
}
}
}
if v.Version < 246 {
db.Exec("update oauths set issuer = 'https://accounts.google.com' where op = 'google' and issuer is null")
}
} }
} }
func Migrate(version uint) { func Migrate(version uint) {
global.Logger.Info("Migrating....", version) fmt.Println("migrating....", version)
err := global.DB.AutoMigrate( err := global.DB.AutoMigrate(
&model.Version{}, &model.Version{},
&model.User{}, &model.User{},
@@ -299,59 +259,70 @@ func Migrate(version uint) {
&model.UserThird{}, &model.UserThird{},
&model.Oauth{}, &model.Oauth{},
&model.LoginLog{}, &model.LoginLog{},
&model.ShareRecord{},
&model.AuditConn{},
&model.AuditFile{},
&model.AddressBookCollection{},
&model.AddressBookCollectionRule{},
&model.ServerCmd{},
&model.DeviceGroup{},
) )
if err != nil { if err != nil {
global.Logger.Error("migrate err :=>", err) fmt.Println("migrate err :=>", err)
} }
global.DB.Create(&model.Version{Version: version}) global.DB.Create(&model.Version{Version: version})
// if this is the first run, create a default user //如果是初次则创建一个默认用户
var vc int64 var vc int64
global.DB.Model(&model.Version{}).Count(&vc) global.DB.Model(&model.Version{}).Count(&vc)
if vc == 1 { if vc == 1 {
localizer := global.Localizer("")
defaultGroup, _ := localizer.LocalizeMessage(&i18n.Message{
ID: "DefaultGroup",
})
group := &model.Group{ group := &model.Group{
Name: defaultGroup, Name: "默认组",
Type: model.GroupTypeDefault, Type: model.GroupTypeDefault,
} }
service.AllService.GroupService.Create(group) service.AllService.GroupService.Create(group)
shareGroup, _ := localizer.LocalizeMessage(&i18n.Message{
ID: "ShareGroup",
})
groupShare := &model.Group{ groupShare := &model.Group{
Name: shareGroup, Name: "共享组",
Type: model.GroupTypeShare, Type: model.GroupTypeShare,
} }
service.AllService.GroupService.Create(groupShare) service.AllService.GroupService.Create(groupShare)
// is true //true
is_admin := true is_admin := true
admin := &model.User{ admin := &model.User{
Username: "admin", Username: "admin",
Nickname: "Admin", Nickname: "管理员",
Status: model.COMMON_STATUS_ENABLE, Status: model.COMMON_STATUS_ENABLE,
IsAdmin: &is_admin, IsAdmin: &is_admin,
GroupId: 1, GroupId: 1,
} }
admin.Password = service.AllService.UserService.EncryptPassword("admin")
// generate a random password
pwd := utils.RandomString(8)
global.Logger.Info("Admin Password Is: ", pwd)
var err error
admin.Password, err = utils.EncryptPassword(pwd)
if err != nil {
global.Logger.Fatalf("failed to generate admin password: %v", err)
}
global.DB.Create(admin) global.DB.Create(admin)
} }
} }
func InitI18n() {
bundle := i18n.NewBundle(language.English)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
bundle.LoadMessageFile(global.Config.Gin.ResourcesPath + "/i18n/en.toml")
bundle.LoadMessageFile(global.Config.Gin.ResourcesPath + "/i18n/zh_CN.toml")
global.Localizer = func(ctx *gin.Context) *i18n.Localizer {
lang := ctx.GetHeader("Accept-Language")
if lang == "" {
lang = global.Config.Lang
}
if lang == "en" {
return i18n.NewLocalizer(bundle, "en")
} else {
return i18n.NewLocalizer(bundle, lang, "en")
}
}
//personUnreadEmails := localizer.MustLocalize(&i18n.LocalizeConfig{
// DefaultMessage: &i18n.Message{
// ID: "PersonUnreadEmails",
// },
// PluralCount: 6,
// TemplateData: map[string]interface{}{
// "Name": "LE",
// "PluralCount": 6,
// },
//})
//personUnreadEmails, err := global.Localizer.LocalizeMessage(&i18n.Message{
// ID: "ParamsError",
//})
//fmt.Println(err, personUnreadEmails)
}
+24 -63
View File
@@ -1,26 +1,8 @@
lang: "en" lang: "zh-CN"
app:
web-client: 1 # 1:enable 0:disable
register: false #whether registration is enabled
register-status: 1 # default status for registered users 1:enable 2:disable
captcha-threshold: 3 # <0:disabled, 0 always, >0:enabled
ban-threshold: 0 # 0:disabled, >0:enabled
show-swagger: 0 # 1:enable 0:disable
token-expire: 168h
web-sso: true #web auth sso
disable-pwd-login: false #disable password login
admin:
title: "RustDesk API Admin"
hello-file: "./conf/admin/hello.html" #prefer to use a file for the welcome message
hello: ""
# ID Server and Relay Server ports https://github.com/lejianwen/rustdesk-api/issues/257
id-server-port: 21116 # ID Server port (for server cmd)
relay-server-port: 21117 # ID Server port (for server cmd)
gin: gin:
api-addr: "0.0.0.0:21114" api-addr: "0.0.0.0:21114"
mode: "release" #release,debug,test mode: "release" #release,debug,test
resources-path: 'resources' #public static file directory resources-path: 'resources' #对外静态文件目录
trust-proxy: "" trust-proxy: ""
gorm: gorm:
type: "sqlite" type: "sqlite"
@@ -31,54 +13,33 @@ mysql:
password: "" password: ""
addr: "" addr: ""
dbname: "" dbname: ""
tls: "false" # true / false / skip-verify / custom
postgresql:
host: "127.0.0.1"
port: "5432"
user: ""
password: ""
dbname: "postgres"
sslmode: "disable" # disable, require, verify-ca, verify-full
time-zone: "Asia/Shanghai" # Time zone for PostgreSQL connection
rustdesk: rustdesk:
id-server: "192.168.1.66:21116" id-server: "192.168.1.66:21116"
relay-server: "192.168.1.66:21117" relay-server: "192.168.1.66:21117"
api-server: "http://127.0.0.1:21114" api-server: "http://192.168.1.66:21114"
key: "" key: "123456789"
key-file: "/data/id_ed25519.pub"
personal: 1 personal: 1
webclient-magic-queryonline: 0
ws-host: "" #eg: wss://192.168.1.3:4443
logger: logger:
path: "./runtime/log.txt" path: "./runtime/log.txt"
level: "info" #trace,debug,info,warn,error,fatal level: "warn" #trace,debug,info,warn,error,fatal
report-caller: true report-caller: true
proxy: redis:
enable: false addr: "127.0.0.1:6379"
host: "http://127.0.0.1:1080" password: ""
db: 0
cache:
type: "file"
file-dir: "./runtime/cache"
redis-addr: "127.0.0.1:6379"
redis-pwd: ""
redis-db: 0
oss:
access-key-id: ""
access-key-secret: ""
host: ""
callback-url: ""
expire-time: 30
max-byte: 10240
jwt: jwt:
key: "" private-key: "./conf/jwt_pri.pem"
expire-duration: 168h expire-duration: 360000
ldap:
enable: false
url: "ldap://ldap.example.com:389"
tls-ca-file: ""
tls-verify: false
base-dn: "dc=example,dc=com"
bind-dn: "cn=admin,dc=example,dc=com"
bind-password: "password"
user:
base-dn: "ou=users,dc=example,dc=com"
enable-attr: "" #The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled
enable-attr-value: "" # The value of the enable attribute when the user is enabled. If you are using AD, just set random value, it will be ignored.
filter: "(cn=*)"
username: "uid" # The attribute name of the user for usernamem if you are using AD, it should be "sAMAccountName"
email: "mail"
first-name: "givenName"
last-name: "sn"
sync: false # If true, the user will be synchronized to the database when the user logs in. If false, the user will be synchronized to the database when the user be created.
admin-group: "cn=admin,dc=example,dc=com" # The group name of the admin group, if the user is in this group, the user will be an admin.
allow-group: "cn=users,dc=example,dc=com" # The group name of the users group, if the user is in this group, the user will be an login.
+31 -68
View File
@@ -1,10 +1,11 @@
package config package config
import ( import (
"flag"
"fmt" "fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper" "github.com/spf13/viper"
"strings" "strings"
"time"
) )
const ( const (
@@ -13,90 +14,52 @@ const (
DefaultConfig = "conf/config.yaml" DefaultConfig = "conf/config.yaml"
) )
type App struct {
WebClient int `mapstructure:"web-client"`
Register bool `mapstructure:"register"`
RegisterStatus int `mapstructure:"register-status"`
ShowSwagger int `mapstructure:"show-swagger"`
TokenExpire time.Duration `mapstructure:"token-expire"`
WebSso bool `mapstructure:"web-sso"`
DisablePwdLogin bool `mapstructure:"disable-pwd-login"`
CaptchaThreshold int `mapstructure:"captcha-threshold"`
BanThreshold int `mapstructure:"ban-threshold"`
}
type Admin struct {
Title string `mapstructure:"title"`
Hello string `mapstructure:"hello"`
HelloFile string `mapstructure:"hello-file"`
IdServerPort int `mapstructure:"id-server-port"`
RelayServerPort int `mapstructure:"relay-server-port"`
}
type Config struct { type Config struct {
Lang string `mapstructure:"lang"` Lang string `mapstructure:"lang"`
App App Gorm Gorm
Admin Admin Mysql Mysql
Gorm Gorm Gin Gin
Mysql Mysql Logger Logger
Postgresql Postgresql Redis Redis
Gin Gin Cache Cache
Logger Logger Oss Oss
Redis Redis Jwt Jwt
Cache Cache Rustdesk Rustdesk
Oss Oss
Jwt Jwt
Rustdesk Rustdesk
Proxy Proxy
Ldap Ldap
} }
func (a *Admin) Init() { // Init 初始化配置
if a.IdServerPort == 0 { func Init(rowVal interface{}) *viper.Viper {
a.IdServerPort = DefaultIdServerPort var config string
flag.StringVar(&config, "c", "", "choose config file.")
flag.Parse()
if config == "" { // 优先级: 命令行 > 默认值
config = DefaultConfig
} }
if a.RelayServerPort == 0 { v := viper.New()
a.RelayServerPort = DefaultRelayServerPort
}
}
// Init initializes the configuration
func Init(rowVal *Config, path string) *viper.Viper {
if path == "" {
path = DefaultConfig
}
v := viper.GetViper()
v.AutomaticEnv() v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
v.SetEnvPrefix("RUSTDESK_API") v.SetEnvPrefix("RUSTDESK_API")
v.SetConfigFile(path) v.SetConfigFile(config)
v.SetConfigType("yaml") v.SetConfigType("yaml")
err := v.ReadInConfig() err := v.ReadInConfig()
if err != nil { if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err)) panic(fmt.Errorf("Fatal error config file: %s \n", err))
} }
/* v.WatchConfig()
v.WatchConfig() v.OnConfigChange(func(e fsnotify.Event) {
//配置文件修改监听
fmt.Println("config file changed:", e.Name)
// watching for config changes is not really necessary if err2 := v.Unmarshal(rowVal); err2 != nil {
v.OnConfigChange(func(e fsnotify.Event) { fmt.Println(err2)
// watch for config file changes }
fmt.Println("config file changed:", e.Name) })
if err2 := v.Unmarshal(rowVal); err2 != nil {
fmt.Println(err2)
}
rowVal.Rustdesk.LoadKeyFile()
rowVal.Rustdesk.ParsePort()
})
*/
if err := v.Unmarshal(rowVal); err != nil { if err := v.Unmarshal(rowVal); err != nil {
panic(fmt.Errorf("Fatal error config: %s \n", err)) fmt.Println(err)
} }
rowVal.Rustdesk.LoadKeyFile()
rowVal.Admin.Init()
return v return v
} }
// ReadEnv reads environment variables // ReadEnv 读取环境变量
func ReadEnv(rowVal interface{}) *viper.Viper { func ReadEnv(rowVal interface{}) *viper.Viper {
v := viper.New() v := viper.New()
v.AutomaticEnv() v.AutomaticEnv()
+2 -14
View File
@@ -1,9 +1,8 @@
package config package config
const ( const (
TypeSqlite = "sqlite" TypeSqlite = "sqlite"
TypeMysql = "mysql" TypeMysql = "mysql"
TypePostgresql = "postgresql"
) )
type Gorm struct { type Gorm struct {
@@ -17,15 +16,4 @@ type Mysql struct {
Username string `mapstructure:"username"` Username string `mapstructure:"username"`
Password string `mapstructure:"password"` Password string `mapstructure:"password"`
Dbname string `mapstructure:"dbname"` Dbname string `mapstructure:"dbname"`
Tls string `mapstructure:"tls"` // true / false / skip-verify / custom
}
type Postgresql struct {
Host string `mapstructure:"host"`
Port string `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Dbname string `mapstructure:"dbname"`
Sslmode string `mapstructure:"sslmode"` // "disable", "require", "verify-ca", "verify-full"
TimeZone string `mapstructure:"time-zone"` // e.g., "Asia/Shanghai"
} }
+1 -1
View File
@@ -3,6 +3,6 @@ package config
import "time" import "time"
type Jwt struct { type Jwt struct {
Key string `mapstructure:"key"` PrivateKey string `mapstructure:"private-key"`
ExpireDuration time.Duration `mapstructure:"expire-duration"` ExpireDuration time.Duration `mapstructure:"expire-duration"`
} }
-37
View File
@@ -1,37 +0,0 @@
package config
type LdapUser struct {
BaseDn string `mapstructure:"base-dn"` // The base DN of the user for searching
EnableAttr string `mapstructure:"enable-attr"` // The attribute name of the user for enabling, in AD it is "userAccountControl", empty means no enable attribute, all users are enabled
EnableAttrValue string `mapstructure:"enable-attr-value"` // The value of the enable attribute when the user is enabled. If you are using AD, just leave it random str, it will be ignored.
Filter string `mapstructure:"filter"`
Username string `mapstructure:"username"`
Email string `mapstructure:"email"`
FirstName string `mapstructure:"first-name"`
LastName string `mapstructure:"last-name"`
Sync bool `mapstructure:"sync"` // Will sync the user's information to the internal database
AdminGroup string `mapstructure:"admin-group"` // Which group is the admin group
AllowGroup string `mapstructure:"allow-group"` // Which group is allowed to login
}
// type LdapGroup struct {
// BaseDn string `mapstructure:"base-dn"` // The base DN of the group for searching
// Name string `mapstructure:"name"` // The attribute name of the group
// Filter string `mapstructure:"filter"`
// Admin string `mapstructure:"admin"` // Which group is the admin group
// Member string `mapstructure:"member"` // How to get the member of the group: member, uniqueMember, or memberOf (default: member)
// Mode string `mapstructure:"mode"`
// Map map[string]string `mapstructure:"map"` // If mode is "map", map the LDAP group to the internal group
// }
type Ldap struct {
Enable bool `mapstructure:"enable"`
Url string `mapstructure:"url"`
TlsCaFile string `mapstructure:"tls-ca-file"`
TlsVerify bool `mapstructure:"tls-verify"`
BaseDn string `mapstructure:"base-dn"`
BindDn string `mapstructure:"bind-dn"`
BindPassword string `mapstructure:"bind-password"`
User LdapUser `mapstructure:"user"`
// Group LdapGroup `mapstructure:"group"`
}
+2 -11
View File
@@ -3,20 +3,11 @@ package config
type GithubOauth struct { type GithubOauth struct {
ClientId string `mapstructure:"client-id"` ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"` ClientSecret string `mapstructure:"client-secret"`
RedirectUrl string `mapstructure:"redirect-url"`
} }
type GoogleOauth struct { type GoogleOauth struct {
ClientId string `mapstructure:"client-id"` ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"` ClientSecret string `mapstructure:"client-secret"`
} RedirectUrl string `mapstructure:"redirect-url"`
type OidcOauth struct {
Issuer string `mapstructure:"issuer"`
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
}
type LinuxdoOauth struct {
ClientId string `mapstructure:"client-id"`
ClientSecret string `mapstructure:"client-secret"`
} }
-6
View File
@@ -1,6 +0,0 @@
package config
type Proxy struct {
Enable bool `mapstructure:"enable"`
Host string `mapstructure:"host"`
}
+5 -36
View File
@@ -1,40 +1,9 @@
package config package config
import (
"os"
)
const (
DefaultIdServerPort = 21116
DefaultRelayServerPort = 21117
)
type Rustdesk struct { type Rustdesk struct {
IdServer string `mapstructure:"id-server"` IdServer string `mapstructure:"id-server"`
IdServerPort int `mapstructure:"-"` RelayServer string `mapstructure:"relay-server"`
RelayServer string `mapstructure:"relay-server"` ApiServer string `mapstructure:"api-server"`
RelayServerPort int `mapstructure:"-"` Key string `mapstructure:"key"`
ApiServer string `mapstructure:"api-server"` Personal int `mapstructure:"personal"`
Key string `mapstructure:"key"`
KeyFile string `mapstructure:"key-file"`
Personal int `mapstructure:"personal"`
//webclient-magic-queryonline
WebclientMagicQueryonline int `mapstructure:"webclient-magic-queryonline"`
WsHost string `mapstructure:"ws-host"`
}
func (rd *Rustdesk) LoadKeyFile() {
// Load key file
if rd.Key != "" {
return
}
if rd.KeyFile != "" {
// Load key from file
b, err := os.ReadFile(rd.KeyFile)
if err != nil {
return
}
rd.Key = string(b)
return
}
} }
View File
-5
View File
@@ -1,5 +0,0 @@
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
View File
@@ -1 +0,0 @@
10
-13
View File
@@ -1,13 +0,0 @@
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
View File
@@ -1,21 +0,0 @@
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
View File
@@ -1,6 +0,0 @@
#!/usr/bin/make -f
%:
dh $@
override_dh_builddeb:
dh_builddeb -- -Zgzip
-6
View File
@@ -1,6 +0,0 @@
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
View File
@@ -1,28 +0,0 @@
#!/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
View File
@@ -1,18 +0,0 @@
#!/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
View File
@@ -1,13 +0,0 @@
#!/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
View File
@@ -1 +0,0 @@
3.0 (native)
-24
View File
@@ -1,24 +0,0 @@
services:
rustdesk-api:
build:
context: .
dockerfile: Dockerfile.dev
args:
COUNTRY: CN
FRONTEND_GIT_REPO: https://github.com/lejianwen/rustdesk-api-web.git
FRONTEND_GIT_BRANCH: master
# image: lejianwen/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://127.0.0.1:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789
ports:
- 21114:21114
volumes:
- ./data/rustdesk/api:/app/data #mount the database out for easy backup
- ./conf:/app/conf # config
# - ./resources:/app/resources # static resources
restart: unless-stopped
+2 -4
View File
@@ -6,12 +6,10 @@ services:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 - RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 - RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114 - RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114
- RUSTDESK_API_RUSTDESK_KEY=123456789 - RUSTDESK_API_RUSTDESK_KEY=123456789
ports: ports:
- 21114:21114 - 21114:21114
volumes: volumes:
- ./data/rustdesk/api:/app/data # database - /data/rustdesk/api:/app/data #将数据库挂载出来方便备份
# - ./conf:/app/conf # config
# - ./resources:/app/resources # static resources
restart: unless-stopped restart: unless-stopped
-64
View File
@@ -1,64 +0,0 @@
# RustDesk full stack (server + relay + API/Web Admin) — self-contained example.
#
# Copy to docker-compose.yml and adjust the public IP / keys / secrets:
# cp docker-compose.yml.example docker-compose.yml
#
# Everything is inline (no .env). The Ed25519 key below is an EXAMPLE — generate
# your own for production. The two keys belong together:
# - hbbs/hbbr get the SECRET key via -k
# - rustdesk-api gets the matching PUBLIC key via RUSTDESK_API_RUSTDESK_KEY
# (the public key is also what clients see as their "Key").
services:
hbbs:
container_name: hbbs
image: rustdesk/rustdesk-server:latest
command: hbbs -r 194.164.204.114:21117 -k 1jqLELJaIjaRtt1Tcj6BhqJ7DhT9WszBH/XUHrqJdhdg4K98HmANQFOpUx/vPZFRWnHPTYYHyIB8uH3Xxr8PTw==
environment:
- TZ=Europe/Berlin
- ENCRYPTED_ONLY=1
- MUST_LOGIN=Y
volumes:
- ./data/server:/root
ports:
- "21115:21115"
- "21116:21116"
- "21116:21116/udp"
- "21118:21118"
depends_on:
- hbbr
restart: unless-stopped
hbbr:
container_name: hbbr
image: rustdesk/rustdesk-server:latest
command: hbbr -k 1jqLELJaIjaRtt1Tcj6BhqJ7DhT9WszBH/XUHrqJdhdg4K98HmANQFOpUx/vPZFRWnHPTYYHyIB8uH3Xxr8PTw==
environment:
- TZ=Europe/Berlin
volumes:
- ./data/server:/root
ports:
- "21117:21117"
- "21119:21119"
restart: unless-stopped
rustdesk-api:
container_name: rustdesk-api
image: git.hackner.dev/thomas/rustdesk-api:latest
command: ["./apimain"]
environment:
- TZ=Europe/Berlin
- RUSTDESK_API_LANG=en
- RUSTDESK_API_RUSTDESK_ID_SERVER=194.164.204.114:21116
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=194.164.204.114:21117
- RUSTDESK_API_RUSTDESK_API_SERVER=http://194.164.204.114:21114
- RUSTDESK_API_RUSTDESK_KEY=YOCvfB5gDUBTqVMf7z2RUVpxz02GB8iAfLh918a/D08=
- RUSTDESK_API_JWT_KEY=z5rt4z465rtzdh5sudtuzrutd66zz6
ports:
- "21114:21114"
volumes:
- ./api:/app/data
depends_on:
- hbbs
- hbbr
restart: unless-stopped
-35
View File
@@ -1,35 +0,0 @@
#!/bin/bash
set -e
# Define Docker Compose file and cache option
COMPOSE_FILE_NAME="docker-compose-dev.yaml"
CACHE=""
# Uncomment the next line to enable no-cache option
# CACHE="--no-cache"
# Define the base Docker Compose command
DCS="docker compose -f ${COMPOSE_FILE_NAME}"
# Function to build and start services
build_and_run() {
echo "Building services..."
if ! $DCS build ${CACHE}; then
echo "Error: Failed to build services"
exit 1
fi
echo "Starting services..."
if ! $DCS up -d; then
echo "Error: Failed to start services"
exit 1
fi
echo "Services started successfully"
echo "If you want to stop the services, run"
echo "docker compose -f ${COMPOSE_FILE_NAME} down"
echo "If you want to see the logs, run"
echo "docker compose -f ${COMPOSE_FILE_NAME} logs -f"
}
# Execute build and start function
build_and_run
+204 -3855
View File
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: 97 KiB

After

Width:  |  Height:  |  Size: 44 KiB

+193 -549
View File
File diff suppressed because it is too large Load Diff
+193 -549
View File
File diff suppressed because it is too large Load Diff
+155 -389
View File
@@ -6,44 +6,6 @@ definitions:
example: '{"tags":["tag1","tag2","tag3"],"peers":[{"id":"abc","username":"abv-l","hostname":"","platform":"Windows","alias":"","tags":["tag1","tag2"],"hash":"hash"}],"tag_colors":"{\"tag1\":4288585374,\"tag2\":4278238420,\"tag3\":4291681337}"}' example: '{"tags":["tag1","tag2","tag3"],"peers":[{"id":"abc","username":"abv-l","hostname":"","platform":"Windows","alias":"","tags":["tag1","tag2"],"hash":"hash"}],"tag_colors":"{\"tag1\":4288585374,\"tag2\":4278238420,\"tag3\":4291681337}"}'
type: string type: string
type: object type: object
api.AuditConnForm:
properties:
action:
type: string
conn_id:
type: integer
id:
type: string
ip:
type: string
peer:
items:
type: string
type: array
session_id:
type: number
type:
type: integer
uuid:
type: string
type: object
api.AuditFileForm:
properties:
id:
type: string
info:
type: string
is_file:
type: boolean
path:
type: string
peer_id:
type: string
type:
type: integer
uuid:
type: string
type: object
api.DeviceInfoInLogin: api.DeviceInfoInLogin:
properties: properties:
name: name:
@@ -62,14 +24,14 @@ definitions:
id: id:
type: string type: string
password: password:
maxLength: 32 maxLength: 20
minLength: 4 minLength: 4
type: string type: string
type: type:
type: string type: string
username: username:
maxLength: 32 maxLength: 10
minLength: 2 minLength: 4
type: string type: string
uuid: uuid:
type: string type: string
@@ -124,30 +86,11 @@ definitions:
status: status:
type: integer type: integer
type: object type: object
model.AddressBookCollection:
properties:
created_at:
type: string
id:
type: integer
name:
type: string
updated_at:
type: string
user_id:
type: integer
required:
- name
type: object
model.Tag: model.Tag:
properties: properties:
collection:
$ref: '#/definitions/model.AddressBookCollection'
collection_id:
type: integer
color: color:
description: color is flutter's color value,from 0x00000000 to 0xFFFFFFFF; the first two digits represent transparency, the following 6 digits represent the color, description: color flutter的颜色值,从0x00000000 0xFFFFFFFF; 前两位表示透明度,后面6位表示颜色,
can be converted to rgba 可以转成rgba
type: integer type: integer
created_at: created_at:
type: string type: string
@@ -194,15 +137,15 @@ definitions:
type: object type: object
info: info:
contact: {} contact: {}
description: API description: 接口
title: management systemAPI title: 管理系统API
version: "1.0" version: "1.0"
paths: paths:
/: /:
get: get:
consumes: consumes:
- application/json - application/json
description: home description: 首页
produces: produces:
- application/json - application/json
responses: responses:
@@ -214,14 +157,14 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
summary: home summary: 首页
tags: tags:
- home - 首页
/ab: /ab:
get: get:
consumes: consumes:
- application/json - application/json
description: address list description: 地址列表
produces: produces:
- application/json - application/json
responses: responses:
@@ -235,15 +178,15 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: address list summary: 地址列表
tags: tags:
- address - 地址
post: post:
consumes: consumes:
- application/json - application/json
description: address update description: 地址更新
parameters: parameters:
- description: address form - description: 地址表单
in: body in: body
name: body name: body
required: true required: true
@@ -262,18 +205,39 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: address update summary: 地址更新
tags: tags:
- address - 地址
/ab/add:
post:
consumes:
- application/json
description: 标签
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: 标签添加
tags:
- 地址[Personal]
/ab/peer/add/{guid}: /ab/peer/add/{guid}:
delete: delete:
consumes: consumes:
- application/json - application/json
description: delete address description: 删除地址
parameters: parameters:
- description: guid - description: id
in: path in: path
name: guid name: id
required: true required: true
type: string type: string
produces: produces:
@@ -289,17 +253,17 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: delete address summary: 删除地址
tags: tags:
- address[Personal] - 地址[Personal]
post: post:
consumes: consumes:
- application/json - application/json
description: add address description: 添加地址
parameters: parameters:
- description: guid - description: id
in: path in: path
name: guid name: id
required: true required: true
type: string type: string
produces: produces:
@@ -315,18 +279,18 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: add address summary: 添加地址
tags: tags:
- address[Personal] - 地址[Personal]
/ab/peer/update/{guid}: /ab/peer/update/{guid}:
put: put:
consumes: consumes:
- application/json - application/json
description: update address description: 更新地址
parameters: parameters:
- description: guid - description: id
in: path in: path
name: guid name: id
required: true required: true
type: string type: string
produces: produces:
@@ -342,27 +306,20 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: update address summary: 更新地址
tags: tags:
- address[Personal] - 地址[Personal]
/ab/peers: /ab/peers:
post: post:
consumes: consumes:
- application/json - application/json
description: address description: 地址
parameters: parameters:
- description: page number - description: string valid
in: query in: body
name: current name: string
type: integer schema:
- description: items per page type: string
in: query
name: pageSize
type: integer
- description: guid
in: query
name: ab
type: string
produces: produces:
- application/json - application/json
responses: responses:
@@ -376,14 +333,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: address list summary: 地址列表
tags: tags:
- address[Personal] - 地址[Personal]
/ab/personal: /ab/personal:
post: post:
consumes: consumes:
- application/json - application/json
description: personal address description: 个人地址
parameters: parameters:
- description: string valid - description: string valid
in: body in: body
@@ -403,14 +360,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: personal address summary: 个人地址
tags: tags:
- address[Personal] - 地址[Personal]
/ab/settings: /ab/settings:
post: post:
consumes: consumes:
- application/json - application/json
description: settings description: 设置
parameters: parameters:
- description: string valid - description: string valid
in: body in: body
@@ -430,23 +387,20 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: settings summary: 设置
tags: tags:
- address[Personal] - 地址[Personal]
/ab/shared/profiles: /ab/shared/profiles:
post: post:
consumes: consumes:
- application/json - application/json
description: shared description: 共享
parameters: parameters:
- description: page number - description: string valid
in: query in: body
name: current name: string
type: integer schema:
- description: items per page type: string
in: query
name: pageSize
type: integer
produces: produces:
- application/json - application/json
responses: responses:
@@ -460,20 +414,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: shared address book summary: 共享地址簿
tags: tags:
- address[Personal] - 地址[Personal]
/ab/tag/{guid}: /ab/tag/{guid}:
delete: delete:
consumes: consumes:
- application/json - application/json
description: tag description: 标签
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces: produces:
- application/json - application/json
responses: responses:
@@ -487,47 +435,14 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: delete tag summary: 标签删除
tags: tags:
- address[Personal] - 地址[Personal]
/ab/tag/add/{guid}:
post:
consumes:
- application/json
description: tag
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
security:
- BearerAuth: []
summary: add tag
tags:
- address[Personal]
/ab/tag/rename/{guid}: /ab/tag/rename/{guid}:
put: put:
consumes: consumes:
- application/json - application/json
description: tag description: 标签
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces: produces:
- application/json - application/json
responses: responses:
@@ -541,20 +456,14 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: rename tag summary: 标签重命名
tags: tags:
- address[Personal] - 地址[Personal]
/ab/tag/update/{guid}: /ab/tag/update/{guid}:
put: put:
consumes: consumes:
- application/json - application/json
description: tag description: 标签
parameters:
- description: guid
in: path
name: guid
required: true
type: string
produces: produces:
- application/json - application/json
responses: responses:
@@ -568,18 +477,18 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: change tag color summary: 标签修改颜色
tags: tags:
- address[Personal] - 地址[Personal]
/ab/tags/{guid}: /ab/tags/{guid}:
post: post:
consumes: consumes:
- application/json - application/json
description: tag description: 标签
parameters: parameters:
- description: guid - description: id
in: path in: path
name: guid name: id
required: true required: true
type: string type: string
produces: produces:
@@ -595,66 +504,14 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: tag summary: 标签
tags: tags:
- address[Personal] - 地址[Personal]
/audit/conn: /api:
post:
consumes:
- application/json
description: audit connection
parameters:
- description: audit connection
in: body
name: body
required: true
schema:
$ref: '#/definitions/api.AuditConnForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: audit connection
tags:
- audit
/audit/file:
post:
consumes:
- application/json
description: audit file
parameters:
- description: audit file
in: body
name: body
required: true
schema:
$ref: '#/definitions/api.AuditFileForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: audit file
tags:
- audit
/currentUser:
get: get:
consumes: consumes:
- application/json - application/json
description: user info description: 用户信息
produces: produces:
- application/json - application/json
responses: responses:
@@ -668,52 +525,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: user info summary: 用户信息
tags: tags:
- user - 用户
/device-group/accessible:
get:
consumes:
- application/json
description: machine
parameters:
- description: page number
in: query
name: page
type: integer
- description: items per page
in: query
name: pageSize
type: integer
- description: status
in: query
name: status
type: integer
- description: accessible
in: query
name: accessible
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.DataResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
security:
- BearerAuth: []
summary: device
tags:
- group
/heartbeat: /heartbeat:
post: post:
consumes: consumes:
- application/json - application/json
description: heartbeat description: 心跳
produces: produces:
- application/json - application/json
responses: responses:
@@ -723,16 +542,16 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
summary: heartbeat summary: 心跳
tags: tags:
- home - 首页
/login: /login:
post: post:
consumes: consumes:
- application/json - application/json
description: login description: 登录
parameters: parameters:
- description: login form - description: 登录表单
in: body in: body
name: body name: body
required: true required: true
@@ -749,14 +568,14 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
summary: login summary: 登录
tags: tags:
- login - 登录
/login-options: /login-options:
get: post:
consumes: consumes:
- application/json - application/json
description: login options description: 登录选项
produces: produces:
- application/json - application/json
responses: responses:
@@ -770,14 +589,14 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
summary: login options summary: 登录选项
tags: tags:
- login - 登录
/logout: /logout:
post: post:
consumes: consumes:
- application/json - application/json
description: logout description: 登出
produces: produces:
- application/json - application/json
responses: responses:
@@ -789,9 +608,28 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
summary: logout summary: 登出
tags: tags:
- login - 登录
/oauth/callback:
get:
consumes:
- application/json
description: OauthCallback
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.LoginRes'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: OauthCallback
tags:
- Oauth
/oidc/auth: /oidc/auth:
post: post:
consumes: consumes:
@@ -830,40 +668,21 @@ paths:
summary: OidcAuthQuery summary: OidcAuthQuery
tags: tags:
- Oauth - Oauth
/oidc/callback:
get:
consumes:
- application/json
description: OauthCallback
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.LoginRes'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: OauthCallback
tags:
- Oauth
/peers: /peers:
get: get:
consumes: consumes:
- application/json - application/json
description: machine description: 机器
parameters: parameters:
- description: page number - description: 页码
in: query in: query
name: page name: page
type: integer type: integer
- description: items per page - description: 每页数量
in: query in: query
name: pageSize name: pageSize
type: integer type: integer
- description: status - description: 状态
in: query in: query
name: status name: status
type: integer type: integer
@@ -884,14 +703,14 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: machine summary: 机器
tags: tags:
- group - 群组
/server-config: /server-config:
get: get:
consumes: consumes:
- application/json - application/json
description: service config, provides an api-server for the webclient description: 服务配置,给webclient提供api-server
produces: produces:
- application/json - application/json
responses: responses:
@@ -905,56 +724,16 @@ paths:
$ref: '#/definitions/response.Response' $ref: '#/definitions/response.Response'
security: security:
- token: [] - token: []
summary: service config summary: 服务配置
tags:
- WEBCLIENT
/server-config-v2:
get:
consumes:
- application/json
description: service config, provides an api-server for the webclient
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: service config
tags:
- WEBCLIENT_V2
/shared-peer:
post:
consumes:
- application/json
description: shared peer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: shared peer
tags: tags:
- WEBCLIENT - WEBCLIENT
/sysinfo: /sysinfo:
post: post:
consumes: consumes:
- application/json - application/json
description: submit system info description: 提交系统信息
parameters: parameters:
- description: system info form - description: 系统信息表单
in: body in: body
name: body name: body
required: true required: true
@@ -971,43 +750,49 @@ paths:
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
summary: submit system info security:
- BearerAuth: []
summary: 提交系统信息
tags: tags:
- System - 地址
/sysinfo_ver: /tags:
post: post:
consumes: consumes:
- application/json - application/json
description: get system version info description: 标签
produces: produces:
- application/json - application/json
responses: responses:
"200": "200":
description: OK description: OK
schema: schema:
type: string items:
$ref: '#/definitions/model.Tag'
type: array
"500": "500":
description: Internal Server Error description: Internal Server Error
schema: schema:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
summary: get system version info security:
- BearerAuth: []
summary: 标签
tags: tags:
- System - 地址
/users: /users:
get: get:
consumes: consumes:
- application/json - application/json
description: user list description: 用户列表
parameters: parameters:
- description: page number - description: 页码
in: query in: query
name: page name: page
type: integer type: integer
- description: items per page - description: 每页数量
in: query in: query
name: pageSize name: pageSize
type: integer type: integer
- description: status - description: 状态
in: query in: query
name: status name: status
type: integer type: integer
@@ -1035,28 +820,9 @@ paths:
$ref: '#/definitions/response.ErrorResponse' $ref: '#/definitions/response.ErrorResponse'
security: security:
- BearerAuth: [] - BearerAuth: []
summary: user list summary: 用户列表
tags: tags:
- group - 群组
/version:
get:
consumes:
- application/json
description: version
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.Response'
summary: version
tags:
- home
securityDefinitions: securityDefinitions:
BearerAuth: BearerAuth:
in: header in: header
Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
package main package Gwen
//go:generate swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin //go:generate swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin
//go:generate swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api //go:generate swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api
+1 -1
View File
@@ -1,3 +1,3 @@
package main package Gwen
//go:generate go run cmd/apimain.go //go:generate go run cmd/apimain.go
-160
View File
@@ -1,160 +0,0 @@
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/fr"
"github.com/go-playground/locales/ko"
"github.com/go-playground/locales/ru"
"github.com/go-playground/locales/zh_Hans_CN"
"github.com/go-playground/locales/zh_Hant"
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"
fr_translations "github.com/go-playground/validator/v10/translations/fr"
ko_translations "github.com/go-playground/validator/v10/translations/ko"
ru_translations "github.com/go-playground/validator/v10/translations/ru"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
zh_tw_translations "github.com/go-playground/validator/v10/translations/zh_tw"
"reflect"
)
func ApiInitValidator() {
validate := validator.New()
// define translations for different languages
enT := en.New()
cn := zh_Hans_CN.New()
koT := ko.New()
ruT := ru.New()
esT := es.New()
frT := fr.New()
zhTwT := zh_Hant.New()
uni := ut.New(enT, cn, koT, ruT, esT, frT, zhTwT)
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")
zhTwTrans, _ := uni.GetTranslator("zh_Hant")
err := zh_translations.RegisterDefaultTranslations(validate, zhTrans)
if err != nil {
panic(err)
}
err = en_translations.RegisterDefaultTranslations(validate, enTrans)
if err != nil {
panic(err)
}
err = ko_translations.RegisterDefaultTranslations(validate, koTrans)
if err != nil {
panic(err)
}
err = ru_translations.RegisterDefaultTranslations(validate, ruTrans)
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)
}
err = zh_tw_translations.RegisterDefaultTranslations(validate, zhTwTrans)
if err != nil {
panic(err)
}
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
label := field.Tag.Get("label")
if label == "" {
return field.Name
}
return label
})
Validator.Validate = validate
Validator.UT = uni // store the Universal Translator
Validator.VTrans = zhTrans
Validator.ValidStruct = func(ctx *gin.Context, i interface{}) []string {
err := Validator.Validate.Struct(i)
lang := ctx.GetHeader("Accept-Language")
if lang == "" {
lang = Config.Lang
}
trans := getTranslatorForLang(lang)
errList := make([]string, 0, 10)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
errList = append(errList, err.Error())
return errList
}
for _, err2 := range err.(validator.ValidationErrors) {
errList = append(errList, err2.Translate(trans))
}
}
return errList
}
Validator.ValidVar = func(ctx *gin.Context, field interface{}, tag string) []string {
err := Validator.Validate.Var(field, tag)
lang := ctx.GetHeader("Accept-Language")
if lang == "" {
lang = Config.Lang
}
trans := getTranslatorForLang(lang)
errList := make([]string, 0, 10)
if err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
errList = append(errList, err.Error())
return errList
}
for _, err2 := range err.(validator.ValidationErrors) {
errList = append(errList, err2.Translate(trans))
}
}
return errList
}
}
func getTranslatorForLang(lang string) ut.Translator {
switch lang {
case "zh_CN":
fallthrough
case "zh-CN":
fallthrough
case "zh":
trans, _ := Validator.UT.GetTranslator("zh_Hans_CN")
return trans
case "zh_TW":
fallthrough
case "zh-TW":
fallthrough
case "zh-tw":
trans, _ := Validator.UT.GetTranslator("zh_Hant")
return trans
case "ko":
trans, _ := Validator.UT.GetTranslator("ko")
return trans
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:
trans, _ := Validator.UT.GetTranslator("en")
return trans
}
}
+16 -19
View File
@@ -1,16 +1,15 @@
package global package global
import ( import (
"Gwen/config"
"Gwen/lib/cache"
"Gwen/lib/jwt"
"Gwen/lib/lock"
"Gwen/lib/upload"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
ut "github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/lejianwen/rustdesk-api/v2/config"
"github.com/lejianwen/rustdesk-api/v2/lib/cache"
"github.com/lejianwen/rustdesk-api/v2/lib/jwt"
"github.com/lejianwen/rustdesk-api/v2/lib/lock"
"github.com/lejianwen/rustdesk-api/v2/lib/upload"
"github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n" "github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper" "github.com/spf13/viper"
@@ -18,23 +17,21 @@ import (
) )
var ( var (
DB *gorm.DB DB *gorm.DB
Logger *logrus.Logger Logger *logrus.Logger
ConfigPath string = "" Config config.Config
Config config.Config Viper *viper.Viper
Viper *viper.Viper Redis *redis.Client
Redis *redis.Client Cache cache.Handler
Cache cache.Handler Validator struct {
Validator struct {
Validate *validator.Validate Validate *validator.Validate
UT *ut.UniversalTranslator UT *ut.UniversalTranslator
VTrans ut.Translator VTrans ut.Translator
ValidStruct func(*gin.Context, interface{}) []string ValidStruct func(*gin.Context, interface{}) []string
ValidVar func(ctx *gin.Context, field interface{}, tag string) []string ValidVar func(ctx *gin.Context, field interface{}, tag string) []string
} }
Oss *upload.Oss Oss *upload.Oss
Jwt *jwt.Jwt Jwt *jwt.Jwt
Lock lock.Locker Lock lock.Locker
Localizer func(lang string) *i18n.Localizer Localizer func(ctx *gin.Context) *i18n.Localizer
LoginLimiter *utils.LoginLimiter
) )
-52
View File
@@ -1,52 +0,0 @@
package global
import (
"github.com/BurntSushi/toml"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
"os"
)
func InitI18n() {
bundle := i18n.NewBundle(language.English)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
// read all language files under global.Config.Gin.ResourcesPath
dir := Config.Gin.ResourcesPath + "/i18n"
fileInfos, err := os.ReadDir(dir)
if err != nil {
panic(err)
}
for _, fileInfo := range fileInfos {
// if the file name does not end with .toml
if fileInfo.IsDir() || fileInfo.Name()[len(fileInfo.Name())-5:] != ".toml" {
continue
}
bundle.LoadMessageFile(Config.Gin.ResourcesPath + "/i18n/" + fileInfo.Name())
}
Localizer = func(lang string) *i18n.Localizer {
if lang == "" {
lang = Config.Lang
}
if lang == "en" {
return i18n.NewLocalizer(bundle, "en")
} else {
return i18n.NewLocalizer(bundle, lang, "en")
}
}
//personUnreadEmails := localizer.MustLocalize(&i18n.LocalizeConfig{
// DefaultMessage: &i18n.Message{
// ID: "PersonUnreadEmails",
// },
// PluralCount: 6,
// TemplateData: map[string]interface{}{
// "Name": "LE",
// "PluralCount": 6,
// },
//})
//personUnreadEmails, err := global.Localizer.LocalizeMessage(&i18n.Message{
// ID: "ParamsError",
//})
//fmt.Println(err, personUnreadEmails)
}
+12 -32
View File
@@ -1,41 +1,33 @@
module github.com/lejianwen/rustdesk-api/v2 module Gwen
go 1.23 go 1.22
toolchain go1.23.10
require ( require (
github.com/BurntSushi/toml v1.3.2 github.com/BurntSushi/toml v1.3.2
github.com/antonfisher/nested-logrus-formatter v1.3.1 github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/coreos/go-oidc/v3 v3.12.0 github.com/fsnotify/fsnotify v1.5.1
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
github.com/gin-gonic/gin v1.9.0 github.com/gin-gonic/gin v1.9.0
github.com/go-ldap/ldap/v3 v3.4.10
github.com/go-playground/locales v0.14.1 github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1 github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.26.0 github.com/go-playground/validator/v10 v10.11.2
github.com/go-redis/redis/v8 v8.11.4 github.com/go-redis/redis/v8 v8.11.4
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/mojocn/base64Captcha v1.3.6
github.com/nicksnyder/go-i18n/v2 v2.4.0 github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.9.0 github.com/spf13/viper v1.9.0
github.com/swaggo/files v1.0.1 github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3 github.com/swaggo/swag v1.16.3
golang.org/x/crypto v0.33.0
golang.org/x/oauth2 v0.23.0 golang.org/x/oauth2 v0.23.0
golang.org/x/text v0.22.0 golang.org/x/text v0.18.0
gorm.io/driver/mysql v1.5.7 gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.5.6 gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.10 gorm.io/gorm v1.25.7
) )
require ( require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect cloud.google.com/go/compute/metadata v0.5.1 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
@@ -43,30 +35,20 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.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/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
@@ -76,7 +58,6 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/spf13/afero v1.6.0 // indirect github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
@@ -85,11 +66,10 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/image v0.13.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.34.0 // indirect golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.25.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
+60 -231
View File
@@ -1,13 +1,12 @@
package admin package admin
import ( import (
"encoding/json" "Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
_ "encoding/json" _ "encoding/json"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm" "gorm.io/gorm"
"strconv" "strconv"
) )
@@ -15,10 +14,10 @@ import (
type AddressBook struct { type AddressBook struct {
} }
// Detail address book // Detail 地址簿
// @Tags address book // @Tags 地址簿
// @Summary address book details // @Summary 地址簿详情
// @Description address book details // @Description 地址簿详情
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -30,6 +29,11 @@ func (ct *AddressBook) Detail(c *gin.Context) {
id := c.Param("id") id := c.Param("id")
iid, _ := strconv.Atoi(id) iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.InfoByRowId(uint(iid)) 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 { if t.RowId > 0 {
response.Success(c, t) response.Success(c, t)
return return
@@ -38,13 +42,13 @@ func (ct *AddressBook) Detail(c *gin.Context) {
return return
} }
// Create create address book // Create 创建地址簿
// @Tags address book // @Tags 地址簿
// @Summary create address book // @Summary 创建地址簿
// @Description create address book // @Description 创建地址簿
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "address book info" // @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response{data=model.AddressBook} // @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book/create [post] // @Router /admin/address_book/create [post]
@@ -61,103 +65,28 @@ func (ct *AddressBook) Create(c *gin.Context) {
return return
} }
t := f.ToAddressBook() t := f.ToAddressBook()
if t.UserId == 0 { u := service.AllService.UserService.CurUser(c)
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")) if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
return 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) err := service.AllService.AddressBookService.Create(t)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
} }
response.Success(c, nil) response.Success(c, u)
} }
// BatchCreate batch create address books // List 列表
// @Tags address book // @Tags 地址簿
// @Summary batch create address books // @Summary 地址簿列表
// @Description batch create address books // @Description 地址簿列表
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "address book info" // @Param page query int false "页码"
// @Success 200 {object} response.Response{data=model.AddressBook} // @Param page_size query int false "页大小"
// @Failure 500 {object} response.Response // @Param user_id query int false "用户id"
// @Router /admin/address_book/batchCreate [post] // @Param is_my query int false "是否是我的"
// @Security token
func (ct *AddressBook) BatchCreate(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
}
ul := len(f.UserIds)
if ul == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if ul > 1 {
//clear tags in multi-user mode
f.Tags = []string{}
//in multi-user mode, can only create in the default address book
f.CollectionId = 0
}
//create tag
/*for _, fu := range f.UserIds {
if fu == 0 {
continue
}
for _, ft := range f.Tags {
exTag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(fu, ft, 0)
if exTag.Id == 0 {
service.AllService.TagService.Create(&model.Tag{
UserId: fu,
Name: ft,
})
}
}
}*/
ts := f.ToAddressBooks()
for _, t := range ts {
if t.UserId == 0 {
continue
}
ex := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(t.UserId, t.Id, t.CollectionId)
if ex.RowId == 0 {
service.AllService.AddressBookService.Create(t)
}
}
response.Success(c, nil)
}
// List list
// @Tags address book
// @Summary address book list
// @Description address book list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param user_id query int false "userid"
// @Param is_my query int false "whether it is mine"
// @Success 200 {object} response.Response{data=model.AddressBookList} // @Success 200 {object} response.Response{data=model.AddressBookList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book/list [get] // @Router /admin/address_book/list [get]
@@ -168,41 +97,25 @@ func (ct *AddressBook) List(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return 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) { 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+"%")
}
if query.UserId > 0 { if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId) 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) response.Success(c, res)
} }
// Update edit // Update 编辑
// @Tags address book // @Tags 地址簿
// @Summary edit address book // @Summary 地址簿编辑
// @Description edit address book // @Description 地址簿编辑
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "address book info" // @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response{data=model.AddressBook} // @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book/update [post] // @Router /admin/address_book/update [post]
@@ -219,20 +132,16 @@ func (ct *AddressBook) Update(c *gin.Context) {
return return
} }
if f.RowId == 0 { if f.RowId == 0 {
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()
if t.CollectionId > 0 && !service.AllService.AddressBookService.CheckCollectionOwner(t.UserId, t.CollectionId) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return return
} }
err := service.AllService.AddressBookService.UpdateAll(t) 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
}
err := service.AllService.AddressBookService.Update(t)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
@@ -240,13 +149,13 @@ func (ct *AddressBook) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete delete // Delete 删除
// @Tags address book // @Tags 地址簿
// @Summary delete address book // @Summary 地址簿删除
// @Description delete address book // @Description 地址簿删除
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.AddressBookForm true "address book info" // @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/address_book/delete [post] // @Router /admin/address_book/delete [post]
@@ -264,99 +173,19 @@ func (ct *AddressBook) Delete(c *gin.Context) {
return return
} }
t := service.AllService.AddressBookService.InfoByRowId(f.RowId) t := service.AllService.AddressBookService.InfoByRowId(f.RowId)
if t.RowId == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.AddressBookService.Delete(t)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}
// ShareByWebClient
// @Tags address book
// @Summary share address book
// @Description share address book
// @Accept json
// @Produce json
// @Param body body admin.ShareByWebClientForm true "address book info"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/address_book/share [post]
// @Security token
func (ct *AddressBook) ShareByWebClient(c *gin.Context) {
f := &admin.ShareByWebClientForm{}
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) u := service.AllService.UserService.CurUser(c)
ab := service.AllService.AddressBookService.InfoByUserIdAndId(u.Id, f.Id) if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
if ab.RowId == 0 { response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return return
} }
m := f.ToShareRecord() if u.Id > 0 {
m.UserId = u.Id err := service.AllService.AddressBookService.Delete(t)
err := service.AllService.AddressBookService.ShareByWebClient(m) if err == nil {
if err != nil { response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
} }
response.Success(c, &gin.H{ response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
"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)
} }
@@ -1,172 +0,0 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)
type AddressBookCollection struct {
}
// Detail address book name
// @Tags address book name
// @Summary address book name details
// @Description address book name details
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection/detail/{id} [get]
// @Security token
func (abc *AddressBookCollection) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.CollectionInfoById(uint(iid))
if t.Id > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create create address book name
// @Tags address book name
// @Summary create address book name
// @Description create address book name
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "address book name info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/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
}
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())
return
}
response.Success(c, nil)
}
// List list
// @Tags address book name
// @Summary address book name list
// @Description address book name list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param is_my query int false "whether it is mine"
// @Param user_id query int false "userid"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response
// @Router /admin/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
}
res := service.AllService.AddressBookService.ListCollection(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
})
response.Success(c, res)
}
// Update edit
// @Tags address book name
// @Summary edit address book name
// @Description edit address book name
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "address book name info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/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
}
t := f //f.ToAddressBookCollection()
err := service.AllService.AddressBookService.UpdateCollection(t)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete delete
// @Tags address book name
// @Summary delete address book name
// @Description delete address book name
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "address book name info"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/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
}
err := service.AllService.AddressBookService.DeleteCollection(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}
@@ -1,222 +0,0 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv"
)
type AddressBookCollectionRule struct {
}
// List list
// @Tags address book rules
// @Summary address book rule list
// @Description address book rule list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param is_my query int false "whether it is mine"
// @Param user_id query int false "userid"
// @Param collection_id query int false "address book collectionid"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionList}
// @Failure 500 {object} response.Response
// @Router /admin/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
}
res := service.AllService.AddressBookService.ListRules(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
if query.CollectionId > 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
})
response.Success(c, res)
}
// Detail address book rules
// @Tags address book rules
// @Summary address book rule details
// @Description address book rule details
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.AddressBookCollectionRule}
// @Failure 500 {object} response.Response
// @Router /admin/address_book_collection_rule/detail/{id} [get]
// @Security token
func (abcr *AddressBookCollectionRule) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
t := service.AllService.AddressBookService.RuleInfoById(uint(iid))
if t.Id > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// Create create address book rule
// @Tags address book rules
// @Summary create address book rule
// @Description create address book rule
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "address book rule info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/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
msg, res := abcr.CheckForm(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(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
}
//check to_id
if t.Type == model.ShareAddressBookRuleTypePersonal {
if t.ToId == t.UserId {
return "CannotShareToSelf", false
}
tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 {
return "ItemNotFound", false
}
} else if t.Type == model.ShareAddressBookRuleTypeGroup {
tog := service.AllService.GroupService.InfoById(t.ToId)
if tog.Id == 0 {
return "ItemNotFound", false
}
} else {
return "ParamsError", false
}
// duplicate check
ex := service.AllService.AddressBookService.RuleInfoByToIdAndCid(t.Type, 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 edit
// @Tags address book rules
// @Summary edit address book rule
// @Description edit address book rule
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "address book rule info"
// @Success 200 {object} response.Response{data=model.AddressBookCollection}
// @Failure 500 {object} response.Response
// @Router /admin/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
}
t := f
msg, res := abcr.CheckForm(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 delete
// @Tags address book rules
// @Summary delete address book rule
// @Description delete address book rule
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "address book rule info"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/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
}
err := service.AllService.AddressBookService.DeleteRule(ex)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
}
-212
View File
@@ -1,212 +0,0 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type Audit struct {
}
// ConnList list
// @Tags connection log
// @Summary connection log list
// @Description connection log list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param peer_id query int false "target device"
// @Param from_peer query int false "source device"
// @Success 200 {object} response.Response{data=model.AuditConnList}
// @Failure 500 {object} response.Response
// @Router /admin/audit_conn/list [get]
// @Security token
func (a *Audit) ConnList(c *gin.Context) {
query := &admin.AuditQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.AuditService.AuditConnList(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.PeerId != "" {
tx.Where("peer_id like ?", "%"+query.PeerId+"%")
}
if query.FromPeer != "" {
tx.Where("from_peer like ?", "%"+query.FromPeer+"%")
}
tx.Order("id desc")
})
response.Success(c, res)
}
// ConnDelete delete
// @Tags connection log
// @Summary delete connection log
// @Description delete connection log
// @Accept json
// @Produce json
// @Param body body model.AuditConn true "connection log info"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/audit_conn/delete [post]
// @Security token
func (a *Audit) ConnDelete(c *gin.Context) {
f := &model.AuditConn{}
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
}
l := service.AllService.AuditService.ConnInfoById(f.Id)
if l.Id > 0 {
err := service.AllService.AuditService.DeleteAuditConn(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchConnDelete delete
// @Tags connection log
// @Summary batch delete connection logs
// @Description batch delete connection logs
// @Accept json
// @Produce json
// @Param body body admin.AuditConnLogIds true "connection log"
// @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 list
// @Tags file log
// @Summary file log list
// @Description file log list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param peer_id query int false "target device"
// @Param from_peer query int false "source device"
// @Success 200 {object} response.Response{data=model.AuditFileList}
// @Failure 500 {object} response.Response
// @Router /admin/audit_file/list [get]
// @Security token
func (a *Audit) FileList(c *gin.Context) {
query := &admin.AuditQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.AuditService.AuditFileList(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.PeerId != "" {
tx.Where("peer_id like ?", "%"+query.PeerId+"%")
}
if query.FromPeer != "" {
tx.Where("from_peer like ?", "%"+query.FromPeer+"%")
}
tx.Order("id desc")
})
response.Success(c, res)
}
// FileDelete delete
// @Tags file log
// @Summary delete file log
// @Description delete file log
// @Accept json
// @Produce json
// @Param body body model.AuditFile true "file log info"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/audit_file/delete [post]
// @Security token
func (a *Audit) FileDelete(c *gin.Context) {
f := &model.AuditFile{}
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
}
l := service.AllService.AuditService.FileInfoById(f.Id)
if l.Id > 0 {
err := service.AllService.AuditService.DeleteAuditFile(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchFileDelete delete
// @Tags file log
// @Summary batch delete file logs
// @Description batch delete file logs
// @Accept json
// @Produce json
// @Param body body admin.AuditFileLogIds true "file log"
// @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
}
-97
View File
@@ -1,97 +0,0 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"os"
"strings"
)
type Config struct {
}
// ServerConfig RUSTDESKservice config
// @Tags ADMIN
// @Summary RUSTDESKservice config
// @Description service config, provides an api-server for the webclient
// @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 APPservice config
// @Tags ADMIN
// @Summary APPservice config
// @Description APPservice config
// @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 ADMINservice config
// @Tags ADMIN
// @Summary ADMINservice config
// @Description ADMINservice config
// @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 := &model.User{}
token := c.GetHeader("api-token")
if token != "" {
u, _ = service.AllService.UserService.InfoByAccessToken(token)
if !service.AllService.UserService.CheckUserEnable(u) {
u.Id = 0
}
}
if u.Id == 0 {
response.Success(c, &gin.H{
"title": global.Config.Admin.Title,
})
return
}
hello := global.Config.Admin.Hello
if 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,
})
}
-160
View File
@@ -1,160 +0,0 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"strconv"
)
type DeviceGroup struct {
}
// Detail device group
// @Tags device group
// @Summary device group details
// @Description device group details
// @Accept json
// @Produce json
// @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/detail/{id} [get]
// @Security token
func (ct *DeviceGroup) Detail(c *gin.Context) {
id := c.Param("id")
iid, _ := strconv.Atoi(id)
u := service.AllService.GroupService.DeviceGroupInfoById(uint(iid))
if u.Id > 0 {
response.Success(c, u)
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create create device group
// @Tags device group
// @Summary create device group
// @Description create device group
// @Accept json
// @Produce json
// @Param body body admin.DeviceGroupForm true "device group info"
// @Success 200 {object} response.Response{data=model.DeviceGroup}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/create [post]
// @Security token
func (ct *DeviceGroup) Create(c *gin.Context) {
f := &admin.DeviceGroupForm{}
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 := f.ToDeviceGroup()
err := service.AllService.GroupService.DeviceGroupCreate(u)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// List list
// @Tags group
// @Summary group list
// @Description group list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Success 200 {object} response.Response{data=model.GroupList}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/list [get]
// @Security token
func (ct *DeviceGroup) 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
}
res := service.AllService.GroupService.DeviceGroupList(query.Page, query.PageSize, nil)
response.Success(c, res)
}
// Update edit
// @Tags device group
// @Summary edit device group
// @Description edit device group
// @Accept json
// @Produce json
// @Param body body admin.DeviceGroupForm true "group info"
// @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response
// @Router /admin/device_group/update [post]
// @Security token
func (ct *DeviceGroup) Update(c *gin.Context) {
f := &admin.DeviceGroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := f.ToDeviceGroup()
err := service.AllService.GroupService.DeviceGroupUpdate(u)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete delete
// @Tags device group
// @Summary delete device group
// @Description delete device group
// @Accept json
// @Produce json
// @Param body body admin.DeviceGroupForm true "group info"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/device_group/delete [post]
// @Security token
func (ct *DeviceGroup) Delete(c *gin.Context) {
f := &admin.DeviceGroupForm{}
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.GroupService.DeviceGroupInfoById(f.Id)
if u.Id > 0 {
err := service.AllService.GroupService.DeviceGroupDelete(u)
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"))
}
+16 -16
View File
@@ -1,11 +1,11 @@
package admin package admin
import ( import (
"Gwen/global"
"Gwen/http/response"
"Gwen/lib/upload"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/lib/upload"
"os" "os"
"time" "time"
) )
@@ -13,10 +13,10 @@ import (
type File struct { type File struct {
} }
// OssToken file // OssToken 文件
// @Tags file // @Tags 文件
// @Summary getossToken // @Summary 获取ossToken
// @Description getossToken // @Description 获取ossToken
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -33,12 +33,12 @@ type FileBack struct {
Url string `json:"url"` Url string `json:"url"`
} }
// Notify callback after successful upload // Notify 上传成功后回调
func (f *File) Notify(c *gin.Context) { func (f *File) Notify(c *gin.Context) {
res := global.Oss.Verify(c.Request) res := global.Oss.Verify(c.Request)
if !res { if !res {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess")) response.Fail(c, 101, "权限错误")
return return
} }
fm := &FileBack{} fm := &FileBack{}
@@ -50,13 +50,13 @@ func (f *File) Notify(c *gin.Context) {
} }
// Upload upload file to local // Upload 上传文件到本地
// @Tags file // @Tags 文件
// @Summary upload file to local // @Summary 上传文件到本地
// @Description upload file to local // @Description 上传文件到本地
// @Accept multipart/form-data // @Accept multipart/form-data
// @Produce json // @Produce json
// @Param file formData file true "file upload example" // @Param file formData file true "上传文件示例"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/file/upload [post] // @Router /admin/file/upload [post]
@@ -71,12 +71,12 @@ func (f *File) Upload(c *gin.Context) {
if err != nil { if err != nil {
return return
} }
// upload the file to the specified directory // 上传文件至指定目录
err = c.SaveUploadedFile(file, dst) err = c.SaveUploadedFile(file, dst)
if err != nil { if err != nil {
return return
} }
// return filewebaddress // 返回文件web地址
response.Success(c, gin.H{ response.Success(c, gin.H{
"url": webPath + file.Filename, "url": webPath + file.Filename,
}) })
+30 -30
View File
@@ -1,21 +1,21 @@
package admin package admin
import ( import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"strconv" "strconv"
) )
type Group struct { type Group struct {
} }
// Detail group // Detail 群组
// @Tags group // @Tags 群组
// @Summary group details // @Summary 群组详情
// @Description group details // @Description 群组详情
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -35,13 +35,13 @@ func (ct *Group) Detail(c *gin.Context) {
return return
} }
// Create create group // Create 创建群组
// @Tags group // @Tags 群组
// @Summary create group // @Summary 创建群组
// @Description create group // @Description 创建群组
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.GroupForm true "group info" // @Param body body admin.GroupForm true "群组信息"
// @Success 200 {object} response.Response{data=model.Group} // @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/group/create [post] // @Router /admin/group/create [post]
@@ -63,17 +63,17 @@ func (ct *Group) Create(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
} }
response.Success(c, nil) response.Success(c, u)
} }
// List list // List 列表
// @Tags group // @Tags 群组
// @Summary group list // @Summary 群组列表
// @Description group list // @Description 群组列表
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "page number" // @Param page query int false "页码"
// @Param page_size query int false "page size" // @Param page_size query int false "页大小"
// @Success 200 {object} response.Response{data=model.GroupList} // @Success 200 {object} response.Response{data=model.GroupList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/group/list [get] // @Router /admin/group/list [get]
@@ -88,13 +88,13 @@ func (ct *Group) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update edit // Update 编辑
// @Tags group // @Tags 群组
// @Summary edit group // @Summary 群组编辑
// @Description edit group // @Description 群组编辑
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.GroupForm true "group info" // @Param body body admin.GroupForm true "群组信息"
// @Success 200 {object} response.Response{data=model.Group} // @Success 200 {object} response.Response{data=model.Group}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/group/update [post] // @Router /admin/group/update [post]
@@ -123,13 +123,13 @@ func (ct *Group) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete delete // Delete 删除
// @Tags group // @Tags 群组
// @Summary delete group // @Summary 群组删除
// @Description delete group // @Description 群组删除
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.GroupForm true "group info" // @Param body body admin.GroupForm true "群组信息"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/group/delete [post] // @Router /admin/group/delete [post]
+25 -187
View File
@@ -1,138 +1,70 @@
package admin package admin
import ( import (
"fmt" "Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/controller/api"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
apiReq "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
) )
type Login struct { type Login struct {
} }
// Login login // Login 登录
// @Tags login // @Tags 登录
// @Summary login // @Summary 登录
// @Description login // @Description 登录
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.Login true "login info" // @Param body body admin.Login true "登录信息"
// @Success 200 {object} response.Response{data=adResp.LoginPayload} // @Success 200 {object} response.Response{data=adResp.LoginPayload}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/login [post] // @Router /admin/login [post]
// @Security token // @Security token
func (ct *Login) Login(c *gin.Context) { func (ct *Login) Login(c *gin.Context) {
if global.Config.App.DisablePwdLogin {
response.Fail(c, 101, response.TranslateMsg(c, "PwdLoginDisabled"))
return
}
// check login limit
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP()
_, needCaptcha := loginLimiter.CheckSecurityStatus(clientIp)
f := &admin.Login{} f := &admin.Login{}
err := c.ShouldBindJSON(f) err := c.ShouldBindJSON(f)
if err != nil { if err != nil {
loginLimiter.RecordFailedAttempt(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()) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
errList := global.Validator.ValidStruct(c, f) errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 { if len(errList) > 0 {
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), clientIp))
response.Fail(c, 101, errList[0]) response.Fail(c, 101, errList[0])
return return
} }
// check whether a captcha is required
if needCaptcha {
if f.CaptchaId == "" || f.Captcha == "" || !loginLimiter.VerifyCaptcha(f.CaptchaId, f.Captcha) {
response.Fail(c, 101, response.TranslateMsg(c, "CaptchaError"))
return
}
}
u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password) u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password)
if u.Id == 0 { if u.Id == 0 {
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), clientIp)) response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError"))
loginLimiter.RecordFailedAttempt(clientIp)
if _, needCaptcha = loginLimiter.CheckSecurityStatus(clientIp); needCaptcha {
response.Fail(c, 110, response.TranslateMsg(c, "UsernameOrPasswordError"))
} else {
response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError"))
}
return
}
if !service.AllService.UserService.CheckUserEnable(u) {
if needCaptcha {
response.Fail(c, 110, response.TranslateMsg(c, "UserDisabled"))
return
}
response.Fail(c, 101, response.TranslateMsg(c, "UserDisabled"))
return return
} }
ut := service.AllService.UserService.Login(u, &model.LoginLog{ ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id, UserId: u.Id,
Client: model.LoginLogClientWebAdmin, Client: "webadmin",
Uuid: "", //must be empty Uuid: "",
Ip: clientIp, Ip: c.ClientIP(),
Type: model.LoginLogTypeAccount, Type: "account",
Platform: f.Platform, Platform: f.Platform,
}) })
// login successful, clear the login limit response.Success(c, &adResp.LoginPayload{
loginLimiter.RemoveAttempts(clientIp) Token: ut.Token,
responseLoginSuccess(c, u, ut.Token) Username: u.Username,
} RouteNames: service.AllService.UserService.RouteNames(u),
func (ct *Login) Captcha(c *gin.Context) { Nickname: u.Nickname,
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP()
banned, needCaptcha := loginLimiter.CheckSecurityStatus(clientIp)
if banned {
response.Fail(c, 101, response.TranslateMsg(c, "LoginBanned"))
return
}
if !needCaptcha {
response.Fail(c, 101, response.TranslateMsg(c, "NoCaptchaRequired"))
return
}
err, captcha := loginLimiter.RequireCaptcha()
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "CaptchaError")+err.Error())
return
}
err, b64 := loginLimiter.DrawCaptcha(captcha.Content)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "CaptchaError")+err.Error())
return
}
response.Success(c, gin.H{
"captcha": gin.H{
"id": captcha.Id,
"b64": b64,
},
}) })
} }
// Logout logout // Logout 登出
// @Tags login // @Tags 登录
// @Summary logout // @Summary 登出
// @Description logout // @Description 登出
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -146,97 +78,3 @@ func (ct *Login) Logout(c *gin.Context) {
} }
response.Success(c, nil) response.Success(c, nil)
} }
// LoginOptions
// @Tags login
// @Summary login options
// @Description login options
// @Accept json
// @Produce json
// @Success 200 {object} []string
// @Failure 500 {object} response.ErrorResponse
// @Router /admin/login-options [post]
func (ct *Login) LoginOptions(c *gin.Context) {
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP()
banned, needCaptcha := loginLimiter.CheckSecurityStatus(clientIp)
if banned {
response.Fail(c, 101, response.TranslateMsg(c, "LoginBanned"))
return
}
ops := service.AllService.OauthService.GetOauthProviders()
response.Success(c, gin.H{
"ops": ops,
"register": global.Config.App.Register,
"need_captcha": needCaptcha,
"disable_pwd": global.Config.App.DisablePwdLogin,
"auto_oidc": global.Config.App.DisablePwdLogin && len(ops) == 1,
})
}
// OidcAuth
// @Tags Oauth
// @Summary OidcAuth
// @Description OidcAuth
// @Accept json
// @Produce json
// @Router /admin/oidc/auth [post]
func (ct *Login) OidcAuth(c *gin.Context) {
// o := &api.Oauth{}
// o.OidcAuth(c)
f := &apiReq.OidcAuthRequest{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
service.AllService.OauthService.SetOauthCache(state, &service.OauthCacheItem{
Action: service.OauthActionTypeLogin,
Op: f.Op,
Id: f.Id,
DeviceType: "webadmin",
// DeviceOs: ct.Platform(c),
DeviceOs: f.DeviceInfo.Os,
Uuid: f.Uuid,
Verifier: verifier,
Nonce: nonce,
}, 5*60)
response.Success(c, gin.H{
"code": state,
"url": url,
})
}
// OidcAuthQuery
// @Tags Oauth
// @Summary OidcAuthQuery
// @Description OidcAuthQuery
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=adResp.LoginPayload}
// @Failure 500 {object} response.Response
// @Router /admin/oidc/auth-query [get]
func (ct *Login) OidcAuthQuery(c *gin.Context) {
o := &api.Oauth{}
u, ut := o.OidcAuthQueryPre(c)
if ut == nil {
return
}
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)
}
+39 -62
View File
@@ -1,12 +1,12 @@
package admin package admin
import ( import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm" "gorm.io/gorm"
"strconv" "strconv"
) )
@@ -14,16 +14,16 @@ import (
type LoginLog struct { type LoginLog struct {
} }
// Detail login log // Detail 登录日志
// @Tags login log // @Tags 登录日志
// @Summary login log details // @Summary 登录日志详情
// @Description login log details // @Description 登录日志详情
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
// @Success 200 {object} response.Response{data=model.LoginLog} // @Success 200 {object} response.Response{data=model.LoginLog}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/login_log/detail/{id} [get] // @Router /admin/loginLog/detail/{id} [get]
// @Security token // @Security token
func (ct *LoginLog) Detail(c *gin.Context) { func (ct *LoginLog) Detail(c *gin.Context) {
id := c.Param("id") id := c.Param("id")
@@ -37,18 +37,18 @@ func (ct *LoginLog) Detail(c *gin.Context) {
return return
} }
// List list // List 列表
// @Tags login log // @Tags 登录日志
// @Summary login log list // @Summary 登录日志列表
// @Description login log list // @Description 登录日志列表
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "page number" // @Param page query int false "页码"
// @Param page_size query int false "page size" // @Param page_size query int false "页大小"
// @Param user_id query int false "userID" // @Param user_id query int false "用户ID"
// @Success 200 {object} response.Response{data=model.LoginLogList} // @Success 200 {object} response.Response{data=model.LoginLogList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/login_log/list [get] // @Router /admin/loginLog/list [get]
// @Security token // @Security token
func (ct *LoginLog) List(c *gin.Context) { func (ct *LoginLog) List(c *gin.Context) {
query := &admin.LoginLogQuery{} query := &admin.LoginLogQuery{}
@@ -56,25 +56,28 @@ func (ct *LoginLog) List(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) || query.IsMy == 1 {
query.UserId = int(u.Id)
}
res := service.AllService.LoginLogService.List(query.Page, query.PageSize, func(tx *gorm.DB) { res := service.AllService.LoginLogService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 { if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId) tx.Where("user_id = ?", query.UserId)
} }
tx.Order("id desc")
}) })
response.Success(c, res) response.Success(c, res)
} }
// Delete delete // Delete 删除
// @Tags login log // @Tags 登录日志
// @Summary delete login log // @Summary 登录日志删除
// @Description delete login log // @Description 登录日志删除
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body model.LoginLog true "login log info" // @Param body body model.LoginLog true "登录日志信息"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/login_log/delete [post] // @Router /admin/loginLog/delete [post]
// @Security token // @Security token
func (ct *LoginLog) Delete(c *gin.Context) { func (ct *LoginLog) Delete(c *gin.Context) {
f := &model.LoginLog{} f := &model.LoginLog{}
@@ -89,45 +92,19 @@ func (ct *LoginLog) Delete(c *gin.Context) {
return return
} }
l := service.AllService.LoginLogService.InfoById(f.Id) l := service.AllService.LoginLogService.InfoById(f.Id)
if l.Id == 0 { u := service.AllService.UserService.CurUser(c)
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) if !service.AllService.UserService.IsAdmin(u) && l.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return return
} }
err := service.AllService.LoginLogService.Delete(l) if l.Id > 0 {
if err == nil { err := service.AllService.LoginLogService.Delete(l)
response.Success(c, nil) if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return return
} }
response.Fail(c, 101, err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchDelete delete
// @Tags login log
// @Summary batch delete login logs
// @Description batch delete login logs
// @Accept json
// @Produce json
// @Param body body admin.LoginLogIds true "login log"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/login_log/batchDelete [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
} }
-271
View File
@@ -1,271 +0,0 @@
package my
import (
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type AddressBook struct{}
// List list
// @Tags my address book
// @Summary address book list
// @Description address book list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param user_id query int false "userid"
// @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) {
//preload address book name
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 create address book
// @Tags my address book
// @Summary create address book
// @Description create address book
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "address book info"
// @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 edit
// @Tags my address book
// @Summary edit address book
// @Description edit address book
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "address book info"
// @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 delete
// @Tags my address book
// @Summary delete address book
// @Description delete address book
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "address book info"
// @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)
}
@@ -1,162 +0,0 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type AddressBookCollection struct {
}
// Create create address book name
// @Tags my address book name
// @Summary create address book name
// @Description create address book name
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "address book name info"
// @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 list
// @Tags my address book name
// @Summary address book name list
// @Description address book name list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @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 edit
// @Tags my address book name
// @Summary edit address book name
// @Description edit address book name
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "address book name info"
// @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 delete
// @Tags my address book name
// @Summary delete address book name
// @Description delete address book name
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollection true "address book name info"
// @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())
}
@@ -1,228 +0,0 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type AddressBookCollectionRule struct {
}
// List list
// @Tags my address book rules
// @Summary address book rule list
// @Description address book rule list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param is_my query int false "whether it is mine"
// @Param user_id query int false "userid"
// @Param collection_id query int false "address book collectionid"
// @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 create address book rule
// @Tags my address book rules
// @Summary create address book rule
// @Description create address book rule
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "address book rule info"
// @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 "CannotShareToSelf", false
}
tou := service.AllService.UserService.InfoById(t.ToId)
if tou.Id == 0 {
return "ItemNotFound", false
}
//non-admins cannot share with users outside their own organization
//if tou.GroupId != u.GroupId {
// return "NoAccess", false
//}
} else if t.Type == model.ShareAddressBookRuleTypeGroup {
//non-admins cannot share with other groups
//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
}
// duplicate check
ex := service.AllService.AddressBookService.RuleInfoByToIdAndCid(t.Type, 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 edit
// @Tags my address book rules
// @Summary edit address book rule
// @Description edit address book rule
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "address book rule info"
// @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 delete
// @Tags my address book rules
// @Summary delete address book rule
// @Description delete address book rule
// @Accept json
// @Produce json
// @Param body body model.AddressBookCollectionRule true "address book rule info"
// @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())
}
-113
View File
@@ -1,113 +0,0 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type LoginLog struct {
}
// List list
// @Tags my login logs
// @Summary login log list
// @Description login log list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param user_id query int false "userID"
// @Success 200 {object} response.Response{data=model.LoginLogList}
// @Failure 500 {object} response.Response
// @Router /admin/my/login_log/list [get]
// @Security token
func (ct *LoginLog) List(c *gin.Context) {
query := &admin.LoginLogQuery{}
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.LoginLogService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
tx.Where("user_id = ? and is_deleted = ?", u.Id, model.IsDeletedNo)
tx.Order("id desc")
})
response.Success(c, res)
}
// Delete delete
// @Tags my login logs
// @Summary delete login log
// @Description delete login log
// @Accept json
// @Produce json
// @Param body body model.LoginLog true "login log info"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/login_log/delete [post]
// @Security token
func (ct *LoginLog) Delete(c *gin.Context) {
f := &model.LoginLog{}
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
}
l := service.AllService.LoginLogService.InfoById(f.Id)
if l.Id == 0 || l.IsDeleted == model.IsDeletedYes {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
u := service.AllService.UserService.CurUser(c)
if l.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.LoginLogService.SoftDelete(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
}
// BatchDelete delete
// @Tags my login logs
// @Summary batch delete login logs
// @Description batch delete login logs
// @Accept json
// @Produce json
// @Param body body admin.LoginLogIds true "login log"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/my/login_log/batchDelete [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
}
u := service.AllService.UserService.CurUser(c)
err := service.AllService.LoginLogService.BatchSoftDelete(u.Id, f.Ids)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
-59
View File
@@ -1,59 +0,0 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"time"
)
type Peer struct {
}
// List list
// @Tags my devices
// @Summary device list
// @Description device list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param time_ago query int false "time"
// @Param id query string false "ID"
// @Param hostname query string false "hostname"
// @Param uuids query string false "uuids separated by commas"
// @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)
}
-119
View File
@@ -1,119 +0,0 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type ShareRecord struct {
}
// List share record list
// @Tags my share records
// @Summary share record list
// @Description share record list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @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 delete share record
// @Tags my share records
// @Summary delete share record
// @Description delete share record
// @Accept json
// @Produce json
// @Param body body admin.ShareRecordForm true "share record info"
// @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 batch delete my share records
// @Tags my
// @Summary batch delete my share records
// @Description batch delete my share records
// @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)
}
-176
View File
@@ -1,176 +0,0 @@
package my
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type Tag struct{}
// List list
// @Tags my tags
// @Summary tag list
// @Description tag list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param is_my query int false "whether it is mine"
// @Param user_id query int false "userid"
// @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 create tag
// @Tags my tags
// @Summary create tag
// @Description create tag
// @Accept json
// @Produce json
// @Param body body admin.TagForm true "tag info"
// @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 edit
// @Tags my tags
// @Summary edit tag
// @Description edit tag
// @Accept json
// @Produce json
// @Param body body admin.TagForm true "tag info"
// @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 delete
// @Tags tag
// @Summary delete tag
// @Description delete tag
// @Accept json
// @Produce json
// @Param body body admin.TagForm true "tag info"
// @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
}
+63 -60
View File
@@ -1,14 +1,14 @@
package admin package admin
import ( import (
"strconv" "Gwen/global"
"Gwen/http/request/admin"
adminReq "Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global" "strconv"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
adminReq "github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
) )
type Oauth struct { type Oauth struct {
@@ -44,41 +44,39 @@ func (o *Oauth) ToBind(c *gin.Context) {
return return
} }
err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(f.Op) err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error())) response.Error(c, response.TranslateMsg(c, err.Error()))
return return
} }
service.AllService.OauthService.SetOauthCache(state, &service.OauthCacheItem{ service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{
Action: service.OauthActionTypeBind, Action: service.OauthActionTypeBind,
Op: f.Op, Op: f.Op,
UserId: u.Id, UserId: u.Id,
Verifier: verifier,
Nonce: nonce,
}, 5*60) }, 5*60)
response.Success(c, gin.H{ response.Success(c, gin.H{
"code": state, "code": code,
"url": url, "url": url,
}) })
} }
// Confirm confirm authorized login // Confirm 确认授权登录
func (o *Oauth) Confirm(c *gin.Context) { func (o *Oauth) Confirm(c *gin.Context) {
j := &adminReq.OauthConfirmForm{} j := &adminReq.OauthConfirmForm{}
err := c.ShouldBindJSON(j) err := c.ShouldBindJSON(j)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Fail(c, 101, "参数错误"+err.Error())
return return
} }
if j.Code == "" { if j.Code == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")) response.Fail(c, 101, "参数错误: code 不存在")
return return
} }
v := service.AllService.OauthService.GetOauthCache(j.Code) v := service.AllService.OauthService.GetOauthCache(j.Code)
if v == nil { if v == nil {
response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired")) response.Fail(c, 101, "授权已过期")
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
@@ -98,23 +96,21 @@ func (o *Oauth) BindConfirm(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return return
} }
oauthService := service.AllService.OauthService v := service.AllService.OauthService.GetOauthCache(j.Code)
oauthCache := oauthService.GetOauthCache(j.Code) if v == nil {
if oauthCache == nil {
response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired")) response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired"))
return return
} }
oauthUser := oauthCache.ToOauthUser() u := service.AllService.UserService.CurUser(c)
user := service.AllService.UserService.CurUser(c) err = service.AllService.OauthService.BindGithubUser(v.ThirdOpenId, v.ThirdOpenId, u.Id)
err = oauthService.BindOauthUser(user.Id, oauthUser, oauthCache.Op)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "BindFail")) response.Fail(c, 101, response.TranslateMsg(c, "BindFail"))
return return
} }
oauthCache.UserId = user.Id v.UserId = u.Id
oauthService.SetOauthCache(j.Code, oauthCache, 0) service.AllService.OauthService.SetOauthCache(j.Code, v, 0)
response.Success(c, oauthCache) response.Success(c, v)
} }
func (o *Oauth) Unbind(c *gin.Context) { func (o *Oauth) Unbind(c *gin.Context) {
@@ -130,18 +126,28 @@ func (o *Oauth) Unbind(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return return
} }
err = service.AllService.OauthService.UnBindOauthUser(u.Id, f.Op) if f.Op == model.OauthTypeGithub {
if err != nil { err = service.AllService.OauthService.UnBindGithubUser(u.Id)
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) if err != nil {
return response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
} }
if f.Op == model.OauthTypeGoogle {
err = service.AllService.OauthService.UnBindGoogleUser(u.Id)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
}
response.Success(c, nil) response.Success(c, nil)
} }
// Detail Oauth // Detail Oauth
// @Tags Oauth // @Tags Oauth
// @Summary Oauthdetails // @Summary Oauth详情
// @Description Oauthdetails // @Description Oauth详情
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -161,13 +167,13 @@ func (o *Oauth) Detail(c *gin.Context) {
return return
} }
// Create createOauth // Create 创建Oauth
// @Tags Oauth // @Tags Oauth
// @Summary createOauth // @Summary 创建Oauth
// @Description createOauth // @Description 创建Oauth
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.OauthForm true "Oauthinfo" // @Param body body admin.OauthForm true "Oauth信息"
// @Success 200 {object} response.Response{data=model.Oauth} // @Success 200 {object} response.Response{data=model.Oauth}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/oauth/create [post] // @Router /admin/oauth/create [post]
@@ -183,33 +189,30 @@ func (o *Oauth) Create(c *gin.Context) {
response.Fail(c, 101, errList[0]) response.Fail(c, 101, errList[0])
return return
} }
u := f.ToOauth()
err := u.FormatOauthInfo() ex := service.AllService.OauthService.InfoByOp(f.Op)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
ex := service.AllService.OauthService.InfoByOp(u.Op)
if ex.Id > 0 { if ex.Id > 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemExists")) response.Fail(c, 101, response.TranslateMsg(c, "ItemExists"))
return return
} }
err = service.AllService.OauthService.Create(u)
u := f.ToOauth()
err := service.AllService.OauthService.Create(u)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
} }
response.Success(c, nil) response.Success(c, u)
} }
// List list // List 列表
// @Tags Oauth // @Tags Oauth
// @Summary Oauthlist // @Summary Oauth列表
// @Description Oauthlist // @Description Oauth列表
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "page number" // @Param page query int false "页码"
// @Param page_size query int false "page size" // @Param page_size query int false "页大小"
// @Success 200 {object} response.Response{data=model.OauthList} // @Success 200 {object} response.Response{data=model.OauthList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/oauth/list [get] // @Router /admin/oauth/list [get]
@@ -224,13 +227,13 @@ func (o *Oauth) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update edit // Update 编辑
// @Tags Oauth // @Tags Oauth
// @Summary Oauthedit // @Summary Oauth编辑
// @Description Oauthedit // @Description Oauth编辑
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.OauthForm true "Oauthinfo" // @Param body body admin.OauthForm true "Oauth信息"
// @Success 200 {object} response.Response{data=model.OauthList} // @Success 200 {object} response.Response{data=model.OauthList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/oauth/update [post] // @Router /admin/oauth/update [post]
@@ -259,13 +262,13 @@ func (o *Oauth) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete delete // Delete 删除
// @Tags Oauth // @Tags Oauth
// @Summary Oauthdelete // @Summary Oauth删除
// @Description Oauthdelete // @Description Oauth删除
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.OauthForm true "Oauthinfo" // @Param body body admin.OauthForm true "Oauth信息"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/oauth/delete [post] // @Router /admin/oauth/delete [post]
+34 -114
View File
@@ -1,23 +1,21 @@
package admin package admin
import ( import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
"strconv" "strconv"
"time"
) )
type Peer struct { type Peer struct {
} }
// Detail device // Detail 设备
// @Tags device // @Tags 设备
// @Summary device details // @Summary 设备详情
// @Description device details // @Description 设备详情
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -37,13 +35,13 @@ func (ct *Peer) Detail(c *gin.Context) {
return return
} }
// Create create device // Create 创建设备
// @Tags device // @Tags 设备
// @Summary create device // @Summary 创建设备
// @Description create device // @Description 创建设备
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.PeerForm true "device info" // @Param body body admin.PeerForm true "设备信息"
// @Success 200 {object} response.Response{data=model.Peer} // @Success 200 {object} response.Response{data=model.Peer}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/create [post] // @Router /admin/peer/create [post]
@@ -59,75 +57,44 @@ func (ct *Peer) Create(c *gin.Context) {
response.Fail(c, 101, errList[0]) response.Fail(c, 101, errList[0])
return return
} }
p := f.ToPeer() u := f.ToPeer()
err := service.AllService.PeerService.Create(p) err := service.AllService.PeerService.Create(u)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
} }
response.Success(c, nil) response.Success(c, u)
} }
// List list // List 列表
// @Tags device // @Tags 设备
// @Summary device list // @Summary 设备列表
// @Description device list // @Description 设备列表
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "page number" // @Param page query int false "页码"
// @Param page_size query int false "page size" // @Param page_size query int false "页大小"
// @Param time_ago query int false "time"
// @Param id query string false "ID"
// @Param hostname query string false "hostname"
// @Param uuids query string false "uuids separated by commas"
// @Success 200 {object} response.Response{data=model.PeerList} // @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/list [get] // @Router /admin/peer/list [get]
// @Security token // @Security token
func (ct *Peer) List(c *gin.Context) { func (ct *Peer) List(c *gin.Context) {
query := &admin.PeerQuery{} query := &admin.PageQuery{}
if err := c.ShouldBindQuery(query); err != nil { if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
res := service.AllService.PeerService.List(query.Page, query.PageSize, func(tx *gorm.DB) { res := service.AllService.PeerService.List(query.Page, query.PageSize, nil)
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)
}
if query.Username != "" {
tx.Where("username like ?", "%"+query.Username+"%")
}
if query.Ip != "" {
tx.Where("last_online_ip like ?", "%"+query.Ip+"%")
}
if query.Alias != "" {
tx.Where("alias like ?", "%"+query.Alias+"%")
}
})
response.Success(c, res) response.Success(c, res)
} }
// Update edit // Update 编辑
// @Tags device // @Tags 设备
// @Summary edit device // @Summary 设备编辑
// @Description edit device // @Description 设备编辑
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.PeerForm true "device info" // @Param body body admin.PeerForm true "设备信息"
// @Success 200 {object} response.Response{data=model.Peer} // @Success 200 {object} response.Response{data=model.Peer}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/update [post] // @Router /admin/peer/update [post]
@@ -156,13 +123,13 @@ func (ct *Peer) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete delete // Delete 删除
// @Tags device // @Tags 设备
// @Summary delete device // @Summary 设备删除
// @Description delete device // @Description 设备删除
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.PeerForm true "device info" // @Param body body admin.PeerForm true "设备信息"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/peer/delete [post] // @Router /admin/peer/delete [post]
@@ -191,50 +158,3 @@ func (ct *Peer) Delete(c *gin.Context) {
} }
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
// BatchDelete batch delete
// @Tags device
// @Summary batch delete devices
// @Description batch delete devices
// @Accept json
// @Produce json
// @Param body body admin.PeerBatchDeleteForm true "deviceid"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/peer/batchDelete [post]
// @Security token
func (ct *Peer) BatchDelete(c *gin.Context) {
f := &admin.PeerBatchDeleteForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if len(f.RowIds) == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
err := service.AllService.PeerService.BatchDelete(f.RowIds)
if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
func (ct *Peer) SimpleData(c *gin.Context) {
f := &admin.SimpleDataQuery{}
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
}
res := service.AllService.PeerService.List(1, 99999, func(tx *gorm.DB) {
//information that can be made public
tx.Select("id,version")
tx.Where("id in (?)", f.Ids)
})
response.Success(c, res)
}
+20 -127
View File
@@ -1,137 +1,30 @@
package admin package admin
import ( import (
"Gwen/global"
"Gwen/http/response"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
) )
type Rustdesk struct { type Rustdesk struct {
} }
type RustdeskCmd struct { // ServerConfig 服务配置
Cmd string `json:"cmd"` // @Tags ADMIN
Option string `json:"option"` // @Summary 服务配置
Target string `json:"target"` // @Description 服务配置,给webclient提供api-server
} // @Accept json
// @Produce json
func (r *Rustdesk) CmdList(c *gin.Context) { // @Success 200 {object} response.Response
q := &admin.PageQuery{} // @Failure 500 {object} response.Response
if err := c.ShouldBindQuery(q); err != nil { // @Router /admin/server-config [get]
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error()) // @Security token
return func (r *Rustdesk) ServerConfig(c *gin.Context) {
} cf := &response.ServerConfigResponse{
res := service.AllService.ServerCmdService.List(q.Page, 9999) IdServer: global.Config.Rustdesk.IdServer,
//add system commands before the list Key: global.Config.Rustdesk.Key,
list := make([]*model.ServerCmd, 0) RelayServer: global.Config.Rustdesk.RelayServer,
list = append(list, model.SysIdServerCmds...) ApiServer: global.Config.Rustdesk.ApiServer,
list = append(list, model.SysRelayServerCmds...) }
list = append(list, res.ServerCmds...) response.Success(c, cf)
res.ServerCmds = list
response.Success(c, res)
}
func (r *Rustdesk) CmdDelete(c *gin.Context) {
f := &model.ServerCmd{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
ex := service.AllService.ServerCmdService.Info(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.ServerCmdService.Delete(ex)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, nil)
}
func (r *Rustdesk) CmdCreate(c *gin.Context) {
f := &model.ServerCmd{}
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
}
err := service.AllService.ServerCmdService.Create(f)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, nil)
}
func (r *Rustdesk) CmdUpdate(c *gin.Context) {
f := &model.ServerCmd{}
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
}
ex := service.AllService.ServerCmdService.Info(f.Id)
if ex.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.ServerCmdService.Update(f)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, nil)
}
func (r *Rustdesk) SendCmd(c *gin.Context) {
rc := &RustdeskCmd{}
if err := c.ShouldBindJSON(rc); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if rc.Cmd == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if rc.Target == "" {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
if rc.Target != model.ServerCmdTargetIdServer && rc.Target != model.ServerCmdTargetRelayServer {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
port := 0
switch rc.Target {
case model.ServerCmdTargetIdServer:
port = global.Config.Admin.IdServerPort - 1
case model.ServerCmdTargetRelayServer:
port = global.Config.Admin.RelayServerPort
}
res, err := service.AllService.ServerCmdService.SendCmd(port, rc.Cmd, rc.Option)
if err != nil {
response.Fail(c, 101, err.Error())
return
}
response.Success(c, res)
} }
-105
View File
@@ -1,105 +0,0 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type ShareRecord struct {
}
// List list
// @Tags share record
// @Summary share record list
// @Description share record list
// @Accept json
// @Produce json
// @Param user_id query int false "userID"
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @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 delete
// @Tags share record
// @Summary delete share record
// @Description delete share record
// @Accept json
// @Produce json
// @Param body body admin.ShareRecordForm true "share record info"
// @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 batch delete
// @Tags share record
// @Summary batch share records
// @Description batch share records
// @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)
}
+55 -52
View File
@@ -1,11 +1,11 @@
package admin package admin
import ( import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm" "gorm.io/gorm"
"strconv" "strconv"
) )
@@ -13,10 +13,10 @@ import (
type Tag struct { type Tag struct {
} }
// Detail tag // Detail 标签
// @Tags tag // @Tags 标签
// @Summary tag details // @Summary 标签详情
// @Description tag details // @Description 标签详情
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -41,13 +41,13 @@ func (ct *Tag) Detail(c *gin.Context) {
return return
} }
// Create create tag // Create 创建标签
// @Tags tag // @Tags 标签
// @Summary create tag // @Summary 创建标签
// @Description create tag // @Description 创建标签
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.TagForm true "tag info" // @Param body body admin.TagForm true "标签信息"
// @Success 200 {object} response.Response{data=model.Tag} // @Success 200 {object} response.Response{data=model.Tag}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/tag/create [post] // @Router /admin/tag/create [post]
@@ -64,28 +64,28 @@ func (ct *Tag) Create(c *gin.Context) {
return return
} }
t := f.ToTag() t := f.ToTag()
if t.UserId == 0 { u := service.AllService.UserService.CurUser(c)
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")) if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
return t.UserId = u.Id
} }
err := service.AllService.TagService.Create(t) err := service.AllService.TagService.Create(t)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
} }
response.Success(c, nil) response.Success(c, u)
} }
// List list // List 列表
// @Tags tag // @Tags 标签
// @Summary tag list // @Summary 标签列表
// @Description tag list // @Description 标签列表
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "page number" // @Param page query int false "页码"
// @Param page_size query int false "page size" // @Param page_size query int false "页大小"
// @Param is_my query int false "whether it is mine" // @Param is_my query int false "是否是我的"
// @Param user_id query int false "userid" // @Param user_id query int false "用户id"
// @Success 200 {object} response.Response{data=model.TagList} // @Success 200 {object} response.Response{data=model.TagList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/tag/list [get] // @Router /admin/tag/list [get]
@@ -96,27 +96,25 @@ func (ct *Tag) List(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return 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) { 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")
})
if query.UserId > 0 { if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId) tx.Where("user_id = ?", query.UserId)
} }
if query.CollectionId != nil && *query.CollectionId >= 0 {
tx.Where("collection_id = ?", query.CollectionId)
}
}) })
response.Success(c, res) response.Success(c, res)
} }
// Update edit // Update 编辑
// @Tags tag // @Tags 标签
// @Summary edit tag // @Summary 标签编辑
// @Description edit tag // @Description 标签编辑
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.TagForm true "tag info" // @Param body body admin.TagForm true "标签信息"
// @Success 200 {object} response.Response{data=model.Tag} // @Success 200 {object} response.Response{data=model.Tag}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/tag/update [post] // @Router /admin/tag/update [post]
@@ -136,12 +134,12 @@ func (ct *Tag) Update(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")) response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return return
} }
ex := service.AllService.TagService.InfoById(f.Id) t := f.ToTag()
if ex.Id == 0 { u := service.AllService.UserService.CurUser(c)
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return return
} }
t := f.ToTag()
err := service.AllService.TagService.Update(t) err := service.AllService.TagService.Update(t)
if err != nil { if err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
@@ -150,13 +148,13 @@ func (ct *Tag) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete delete // Delete 删除
// @Tags tag // @Tags 标签
// @Summary delete tag // @Summary 标签删除
// @Description delete tag // @Description 标签删除
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.TagForm true "tag info" // @Param body body admin.TagForm true "标签信息"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/tag/delete [post] // @Router /admin/tag/delete [post]
@@ -173,15 +171,20 @@ func (ct *Tag) Delete(c *gin.Context) {
response.Fail(c, 101, errList[0]) response.Fail(c, 101, errList[0])
return return
} }
ex := service.AllService.TagService.InfoById(f.Id) t := service.AllService.TagService.InfoById(f.Id)
if ex.Id == 0 { u := service.AllService.UserService.CurUser(c)
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return return
} }
err := service.AllService.TagService.Delete(ex) if u.Id > 0 {
if err == nil { err := service.AllService.TagService.Delete(t)
response.Success(c, nil) if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return return
} }
response.Fail(c, 101, err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
+61 -114
View File
@@ -1,14 +1,12 @@
package admin package admin
import ( import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
adResp "Gwen/http/response/admin"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
adResp "github.com/lejianwen/rustdesk-api/v2/http/response/admin"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"gorm.io/gorm" "gorm.io/gorm"
"strconv" "strconv"
) )
@@ -16,10 +14,10 @@ import (
type User struct { type User struct {
} }
// Detail administrator // Detail 管理员
// @Tags user // @Tags 用户
// @Summary administrator details // @Summary 管理员详情
// @Description administrator details // @Description 管理员详情
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "ID" // @Param id path int true "ID"
@@ -39,13 +37,13 @@ func (ct *User) Detail(c *gin.Context) {
return return
} }
// Create administrator // Create 管理员
// @Tags user // @Tags 用户
// @Summary create administrator // @Summary 创建管理员
// @Description create administrator // @Description 创建管理员
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.UserForm true "administrator info" // @Param body body admin.UserForm true "管理员信息"
// @Success 200 {object} response.Response{data=model.User} // @Success 200 {object} response.Response{data=model.User}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/create [post] // @Router /admin/user/create [post]
@@ -67,18 +65,18 @@ func (ct *User) Create(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
} }
response.Success(c, nil) response.Success(c, u)
} }
// List list // List 列表
// @Tags user // @Tags 用户
// @Summary administrator list // @Summary 管理员列表
// @Description administrator list // @Description 管理员列表
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "page number" // @Param page query int false "页码"
// @Param page_size query int false "page size" // @Param page_size query int false "页大小"
// @Param username query int false "account" // @Param username query int false "账户"
// @Success 200 {object} response.Response{data=model.UserList} // @Success 200 {object} response.Response{data=model.UserList}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/list [get] // @Router /admin/user/list [get]
@@ -97,13 +95,13 @@ func (ct *User) List(c *gin.Context) {
response.Success(c, res) response.Success(c, res)
} }
// Update edit // Update 编辑
// @Tags user // @Tags 用户
// @Summary edit administrator // @Summary 管理员编辑
// @Description edit administrator // @Description 管理员编辑
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.UserForm true "user info" // @Param body body admin.UserForm true "用户信息"
// @Success 200 {object} response.Response{data=model.User} // @Success 200 {object} response.Response{data=model.User}
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/update [post] // @Router /admin/user/update [post]
@@ -132,13 +130,13 @@ func (ct *User) Update(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Delete delete // Delete 删除
// @Tags user // @Tags 用户
// @Summary delete administrator // @Summary 管理员删除
// @Description delete administrator // @Description 管理员编删除
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.UserForm true "user info" // @Param body body admin.UserForm true "用户信息"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/delete [post] // @Router /admin/user/delete [post]
@@ -168,13 +166,13 @@ func (ct *User) Delete(c *gin.Context) {
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound")) response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
} }
// UpdatePassword change password // UpdatePassword 修改密码
// @Tags user // @Tags 用户
// @Summary change password // @Summary 修改密码
// @Description change password // @Description 修改密码
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.UserPasswordForm true "user info" // @Param body body admin.UserPasswordForm true "用户信息"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/updatePassword [post] // @Router /admin/user/updatePassword [post]
@@ -203,10 +201,10 @@ func (ct *User) UpdatePassword(c *gin.Context) {
response.Success(c, nil) response.Success(c, nil)
} }
// Current current user // Current 当前用户
// @Tags user // @Tags 用户
// @Summary current user // @Summary 当前用户
// @Description current user // @Description 当前用户
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response{data=adResp.LoginPayload} // @Success 200 {object} response.Response{data=adResp.LoginPayload}
@@ -217,16 +215,21 @@ func (ct *User) Current(c *gin.Context) {
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
token, _ := c.Get("token") token, _ := c.Get("token")
t := token.(string) t := token.(string)
responseLoginSuccess(c, u, t) response.Success(c, &adResp.LoginPayload{
Token: t,
Username: u.Username,
RouteNames: service.AllService.UserService.RouteNames(u),
Nickname: u.Nickname,
})
} }
// ChangeCurPwd change the current user's password // ChangeCurPwd 修改当前用户密码
// @Tags user // @Tags 用户
// @Summary change the current user's password // @Summary 修改当前用户密码
// @Description change the current user's password // @Description 修改当前用户密码
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body admin.ChangeCurPasswordForm true "user info" // @Param body body admin.ChangeCurPasswordForm true "用户信息"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /admin/user/changeCurPwd [post] // @Router /admin/user/changeCurPwd [post]
@@ -244,13 +247,10 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
// Verify the old password only when the account already has one set oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
if !service.AllService.UserService.IsPasswordEmptyByUser(u) { if u.Password != oldPwd {
ok, _, err := utils.VerifyPassword(u.Password, f.OldPassword) response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
if err != nil || !ok { return
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
return
}
} }
err := service.AllService.UserService.UpdatePassword(u, f.NewPassword) err := service.AllService.UserService.UpdatePassword(u, f.NewPassword)
if err != nil { if err != nil {
@@ -261,9 +261,9 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
} }
// MyOauth // MyOauth
// @Tags user // @Tags 用户
// @Summary my authorizations // @Summary 我的授权
// @Description my authorizations // @Description 我的授权
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response{data=[]adResp.UserOauthItem} // @Success 200 {object} response.Response{data=[]adResp.UserOauthItem}
@@ -281,10 +281,10 @@ func (ct *User) MyOauth(c *gin.Context) {
var res []*adResp.UserOauthItem var res []*adResp.UserOauthItem
for _, oa := range oal.Oauths { for _, oa := range oal.Oauths {
item := &adResp.UserOauthItem{ item := &adResp.UserOauthItem{
Op: oa.Op, ThirdType: oa.Op,
} }
for _, ut := range uts { for _, ut := range uts {
if ut.Op == oa.Op { if ut.ThirdType == oa.Op {
item.Status = 1 item.Status = 1
break break
} }
@@ -293,56 +293,3 @@ func (ct *User) MyOauth(c *gin.Context) {
} }
response.Success(c, res) response.Success(c, res)
} }
// groupUsers
func (ct *User) GroupUsers(c *gin.Context) {
aG := service.AllService.GroupService.List(1, 999, nil)
aU := service.AllService.UserService.List(1, 9999, nil)
response.Success(c, gin.H{
"groups": aG.Groups,
"users": aU.Users,
})
}
// Register
func (ct *User) Register(c *gin.Context) {
if !global.Config.App.Register {
response.Fail(c, 101, response.TranslateMsg(c, "RegisterClosed"))
return
}
f := &admin.RegisterForm{}
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
}
regStatus := model.StatusCode(global.Config.App.RegisterStatus)
// the registration status may not be configured; enabled by default
if regStatus != model.COMMON_STATUS_DISABLED && regStatus != model.COMMON_STATUS_ENABLE {
regStatus = model.COMMON_STATUS_ENABLE
}
u := service.AllService.UserService.Register(f.Username, f.Email, f.Password, regStatus)
if u == nil || u.Id == 0 {
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed"))
return
}
if regStatus == model.COMMON_STATUS_DISABLED {
// requires administrator approval
response.Fail(c, 101, response.TranslateMsg(c, "RegisterSuccessWaitAdminConfirm"))
return
}
// automatically log in after successful registration
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: model.LoginLogClientWebAdmin,
Uuid: "",
Ip: c.ClientIP(),
Type: model.LoginLogTypeAccount,
})
responseLoginSuccess(c, u, ut.Token)
}
-113
View File
@@ -1,113 +0,0 @@
package admin
import (
"github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/admin"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"gorm.io/gorm"
)
type UserToken struct {
}
// List list
// @Tags login credential
// @Summary login credential list
// @Description login credential list
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param page_size query int false "page size"
// @Param user_id query int false "userID"
// @Success 200 {object} response.Response{data=model.UserTokenList}
// @Failure 500 {object} response.Response
// @Router /admin/user_token/list [get]
// @Security token
func (ct *UserToken) List(c *gin.Context) {
query := &admin.LoginTokenQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.UserService.TokenList(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
tx.Order("id desc")
})
response.Success(c, res)
}
// Delete delete
// @Tags login credential
// @Summary delete login credential
// @Description delete login credential
// @Accept json
// @Produce json
// @Param body body model.UserToken true "login credential info"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/user_token/delete [post]
// @Security token
func (ct *UserToken) Delete(c *gin.Context) {
f := &model.UserToken{}
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
}
l := service.AllService.UserService.TokenInfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && l.UserId != u.Id {
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if l.Id > 0 {
err := service.AllService.UserService.DeleteToken(l)
if err == nil {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchDelete batch delete
// @Tags login credential
// @Summary batch delete login credentials
// @Description batch delete login credentials
// @Accept json
// @Produce json
// @Param body body admin.UserTokenBatchDeleteForm true "login credential info"
// @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())
}
+134 -374
View File
@@ -1,28 +1,26 @@
package api package api
import ( import (
"Gwen/global"
requstform "Gwen/http/request/api"
"Gwen/http/response"
"Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"encoding/json" "encoding/json"
"errors" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"net/http" "net/http"
"strconv" "strconv"
"strings"
) )
type Ab struct { type Ab struct {
} }
// Ab // Ab
// @Tags address // @Tags 地址
// @Summary address list // @Summary 地址列表
// @Description address list // @Description 地址列表
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -32,11 +30,11 @@ type Ab struct {
func (a *Ab) Ab(c *gin.Context) { func (a *Ab) Ab(c *gin.Context) {
user := service.AllService.UserService.CurUser(c) user := service.AllService.UserService.CurUser(c)
al := service.AllService.AddressBookService.ListByUserIdAndCollectionId(user.Id, 0, 1, 1000) al := service.AllService.AddressBookService.ListByUserId(user.Id, 1, 1000)
tags := service.AllService.TagService.ListByUserIdAndCollectionId(user.Id, 0) tags := service.AllService.TagService.ListByUserId(user.Id)
tagColors := map[string]uint{} tagColors := map[string]uint{}
// convert the name in tags into a comma-separated string //将tags中的name转成一个以逗号分割的字符串
var tagNames []string var tagNames []string
for _, tag := range tags.Tags { for _, tag := range tags.Tags {
tagNames = append(tagNames, tag.Name) tagNames = append(tagNames, tag.Name)
@@ -56,12 +54,12 @@ func (a *Ab) Ab(c *gin.Context) {
} }
// UpAb // UpAb
// @Tags address // @Tags 地址
// @Summary address update // @Summary 地址更新
// @Description address update // @Description 地址更新
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body requstform.AddressBookForm true "address form" // @Param body body requstform.AddressBookForm true "地址表单"
// @Success 200 {string} string "null" // @Success 200 {string} string "null"
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /ab [post] // @Router /ab [post]
@@ -98,76 +96,47 @@ func (a *Ab) UpAb(c *gin.Context) {
c.JSON(http.StatusOK, nil) c.JSON(http.StatusOK, nil)
} }
// PTags // Tags
// @Tags address[Personal] // @Tags 地址
// @Summary tag // @Summary 标签
// @Description tag // @Description 标签
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Success 200 {object} []model.Tag
// @Success 200 {object} model.TagList
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /ab/tags/{guid} [post] // @Router /tags [post]
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) PTags(c *gin.Context) { func (a *Ab) Tags(c *gin.Context) {
u := service.AllService.UserService.CurUser(c) user := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges tags := service.AllService.TagService.ListByUserId(user.Id)
if !service.AllService.AddressBookService.CheckUserReadPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tags := service.AllService.TagService.ListByUserIdAndCollectionId(uid, cid)
c.JSON(http.StatusOK, tags.Tags) c.JSON(http.StatusOK, tags.Tags)
} }
// TagAdd // TagAdd
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary add tag // @Summary 标签添加
// @Description tag // @Description 标签
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string // @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /ab/tag/add/{guid} [post] // @Router /ab/add [post]
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) TagAdd(c *gin.Context) { func (a *Ab) TagAdd(c *gin.Context) {
t := &model.Tag{} t := &model.Tag{}
err := c.ShouldBindJSON(t) err := c.ShouldBindJSON(t)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid") tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.Name)
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.Name, cid)
if tag != nil && tag.Id != 0 { if tag != nil && tag.Id != 0 {
response.Error(c, response.TranslateMsg(c, "ItemExists")) response.Error(c, response.TranslateMsg(c, "ItemExists"))
return return
} }
t.UserId = uid t.UserId = u.Id
t.CollectionId = cid
err = service.AllService.TagService.Create(t) err = service.AllService.TagService.Create(t)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
@@ -177,18 +146,16 @@ func (a *Ab) TagAdd(c *gin.Context) {
} }
// TagRename // TagRename
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary rename tag // @Summary 标签重命名
// @Description tag // @Description 标签
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string // @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /ab/tag/rename/{guid} [put] // @Router /ab/tag/rename/{guid} [put]
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) TagRename(c *gin.Context) { func (a *Ab) TagRename(c *gin.Context) {
t := &requstform.TagRenameForm{} t := &requstform.TagRenameForm{}
err := c.ShouldBindJSON(t) err := c.ShouldBindJSON(t)
if err != nil { if err != nil {
@@ -196,25 +163,12 @@ func (a *Ab) TagRename(c *gin.Context) {
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid") tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.Old)
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.Old, cid)
if tag == nil || tag.Id == 0 { if tag == nil || tag.Id == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound")) response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return return
} }
ntag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.New, cid) ntag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.New)
if ntag != nil && ntag.Id != 0 { if ntag != nil && ntag.Id != 0 {
response.Error(c, response.TranslateMsg(c, "ItemExists")) response.Error(c, response.TranslateMsg(c, "ItemExists"))
return return
@@ -229,12 +183,11 @@ func (a *Ab) TagRename(c *gin.Context) {
} }
// TagUpdate // TagUpdate
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary change tag color // @Summary 标签修改颜色
// @Description tag // @Description 标签
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string // @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /ab/tag/update/{guid} [put] // @Router /ab/tag/update/{guid} [put]
@@ -247,20 +200,7 @@ func (a *Ab) TagUpdate(c *gin.Context) {
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid") tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.Name)
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, t.Name, cid)
if tag == nil || tag.Id == 0 { if tag == nil || tag.Id == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound")) response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return return
@@ -275,18 +215,16 @@ func (a *Ab) TagUpdate(c *gin.Context) {
} }
// TagDel // TagDel
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary delete tag // @Summary 标签删除
// @Description tag // @Description 标签
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid"
// @Success 200 {string} string // @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /ab/tag/{guid} [delete] // @Router /ab/tag/{guid} [delete]
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) TagDel(c *gin.Context) { func (a *Ab) TagDel(c *gin.Context) {
t := &[]string{} t := &[]string{}
err := c.ShouldBind(t) err := c.ShouldBind(t)
if err != nil { if err != nil {
@@ -295,21 +233,8 @@ func (a *Ab) TagDel(c *gin.Context) {
} }
//fmt.Println(t) //fmt.Println(t)
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserFullControlPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
for _, name := range *t { for _, name := range *t {
tag := service.AllService.TagService.InfoByUserIdAndNameAndCollectionId(uid, name, cid) tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, name)
if tag == nil || tag.Id == 0 { if tag == nil || tag.Id == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound")) response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return return
@@ -324,9 +249,9 @@ func (a *Ab) TagDel(c *gin.Context) {
} }
// Personal // Personal
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary personal address // @Summary 个人地址
// @Description personal address // @Description 个人地址
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param string body string false "string valid" // @Param string body string false "string valid"
@@ -344,12 +269,12 @@ func (a *Ab) Personal(c *gin.Context) {
rule = json['rule'] ?? 0; rule = json['rule'] ?? 0;
*/ */
if global.Config.Rustdesk.Personal == 1 { if global.Config.Rustdesk.Personal == 1 {
guid := a.ComposeGuid(user.GroupId, user.Id, 0) guid := strconv.Itoa(int(user.GroupId)) + "-" + strconv.Itoa(int(user.Id))
//if it returns guid, subsequent requests will change //如果返回了guid,后面的请求会有变化
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"guid": guid, "guid": guid,
"name": user.Username, "name": user.Username,
"rule": 3, "rule": 0,
}) })
} else { } else {
c.JSON(http.StatusOK, nil) c.JSON(http.StatusOK, nil)
@@ -358,9 +283,9 @@ func (a *Ab) Personal(c *gin.Context) {
} }
// Settings // Settings
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary settings // @Summary 设置
// @Description settings // @Description 设置
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param string body string false "string valid" // @Param string body string false "string valid"
@@ -370,185 +295,68 @@ func (a *Ab) Personal(c *gin.Context) {
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) Settings(c *gin.Context) { func (a *Ab) Settings(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"max_peer_one_ab": 0, //max peer count, 0 means no limit "max_peer_one_ab": 0, //最大peer数,0表示不限制
}) })
} }
// SharedProfiles // SharedProfiles
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary shared address book // @Summary 共享地址簿
// @Description shared // @Description 共享
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param current query int false "page number" // @Param string body string false "string valid"
// @Param pageSize query int false "items per page"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /ab/shared/profiles [post] // @Router /ab/shared/profiles [post]
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) SharedProfiles(c *gin.Context) { func (a *Ab) SharedProfiles(c *gin.Context) {
//AbProfile.fromJson(Map<String, dynamic> json)
var res []*api.SharedProfilesPayload //: guid = json['guid'] ?? '',
// name = json['name'] ?? '',
user := service.AllService.UserService.CurUser(c) // owner = json['owner'] ?? '',
myAbCollectionList := service.AllService.AddressBookService.ListCollectionByUserId(user.Id) // note = json['note'] ?? '',
for _, ab := range myAbCollectionList.AddressBookCollection { // rule = json['rule'] ?? 0;
res = append(res, &api.SharedProfilesPayload{ //暂时没必要返回数据,可能是为了共享地址簿
Guid: a.ComposeGuid(user.GroupId, user.Id, ab.Id), /*item := map[string]interface{}{
Name: ab.Name, "guid": "1",
Owner: user.Username, "name": "admin",
Rule: model.ShareAddressBookRuleRuleFullControl, "owner": "admin",
}) "note": "admin11",
"rule": 0,
} }
item2 := map[string]interface{}{
allAbIds := make(map[uint]int) //use map to deduplicate and keep the maximum Rule "guid": "2",
allUserIds := make(map[uint]*model.User) "name": "admin2",
rules := service.AllService.AddressBookService.CollectionReadRules(user) "owner": "admin2",
for _, rule := range rules { "note": "admin22",
//first check whether it exists "rule": 0,
r, ok := allAbIds[rule.CollectionId]
if ok {
//then check the permission level
if r < rule.Rule {
allAbIds[rule.CollectionId] = rule.Rule
}
} else {
allAbIds[rule.CollectionId] = rule.Rule
allUserIds[rule.UserId] = nil
}
}
abids := utils.Keys(allAbIds)
collections := service.AllService.AddressBookService.ListCollectionByIds(abids)
ids := utils.Keys(allUserIds)
allUsers := service.AllService.UserService.ListByIds(ids)
for _, u := range allUsers {
allUserIds[u.Id] = u
}
for _, collection := range collections {
_u, ok := allUserIds[collection.UserId]
if !ok {
continue
}
res = append(res, &api.SharedProfilesPayload{
Guid: a.ComposeGuid(_u.GroupId, _u.Id, collection.Id),
Name: collection.Name,
Owner: _u.Username,
Rule: allAbIds[collection.Id],
})
} }
c.JSON(http.StatusOK, gin.H{
"total": 2,
"data": []interface{}{item, item2},
})*/
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"total": 0, //len(res), "total": 0,
"data": res, "data": nil,
}) })
} }
// ParseGuid
func (a *Ab) ParseGuid(guid string) (gid, uid, cid uint) {
// split guid by -
guids := strings.Split(guid, "-")
if len(guids) < 2 {
return 0, 0, 0
}
if len(guids) != 3 {
cid = 0
} else {
s, err := strconv.Atoi(guids[2])
if err != nil {
return 0, 0, 0
}
cid = uint(s)
}
g, err := strconv.Atoi(guids[0])
if err != nil {
return 0, 0, 0
}
gid = uint(g)
u, err := strconv.Atoi(guids[1])
if err != nil {
return 0, 0, 0
}
uid = uint(u)
return
}
// ComposeGuid
func (a *Ab) ComposeGuid(gid, uid, cid uint) string {
return strconv.Itoa(int(gid)) + "-" + strconv.Itoa(int(uid)) + "-" + strconv.Itoa(int(cid))
}
// CheckGuid
func (a *Ab) CheckGuid(cu *model.User, guid string) (gid, uid, cid uint, err error) {
gid, uid, cid = a.ParseGuid(guid)
err = nil
if gid == 0 || uid == 0 {
err = errors.New("ParamsError")
return
}
u := &model.User{}
if cu.Id == uid {
u = cu
} else {
u = service.AllService.UserService.InfoById(uid)
}
if u == nil || u.Id == 0 {
err = errors.New("ParamsError")
return
}
if u.GroupId != gid {
err = errors.New("ParamsError")
return
}
if cid == 0 && cu.Id != uid {
err = errors.New("ParamsError")
return
}
if cid > 0 {
c := service.AllService.AddressBookService.CollectionInfoById(cid)
if c == nil || c.Id == 0 {
err = errors.New("ParamsError")
return
}
if c.UserId != uid {
err = errors.New("ParamsError")
return
}
}
return
}
// Peers // Peers
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary address list // @Summary 地址列表
// @Description address // @Description 地址
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param current query int false "page number" // @Param string body string false "string valid"
// @Param pageSize query int false "items per page"
// @Param ab query string false "guid"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /ab/peers [post] // @Router /ab/peers [post]
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) Peers(c *gin.Context) { func (a *Ab) Peers(c *gin.Context) {
u := service.AllService.UserService.CurUser(c) user := service.AllService.UserService.CurUser(c)
guid := c.Query("ab") al := service.AllService.AddressBookService.ListByUserId(user.Id, 1, 1000)
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserReadPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
al := service.AllService.AddressBookService.ListByUserIdAndCollectionId(uid, cid, 1, 1000)
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"total": al.Total, "total": al.Total,
"data": al.AddressBooks, "data": al.AddressBooks,
@@ -556,19 +364,37 @@ func (a *Ab) Peers(c *gin.Context) {
}) })
} }
// PeerAdd // PTags
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary add address // @Summary 标签
// @Description add address // @Description 标签
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param id path string true "id"
// @Success 200 {object} model.TagList
// @Failure 500 {object} response.ErrorResponse
// @Router /ab/tags/{guid} [post]
// @Security BearerAuth
func (a *Ab) PTags(c *gin.Context) {
user := service.AllService.UserService.CurUser(c)
tags := service.AllService.TagService.ListByUserId(user.Id)
c.JSON(http.StatusOK, tags.Tags)
}
// PeerAdd
// @Tags 地址[Personal]
// @Summary 添加地址
// @Description 添加地址
// @Accept json
// @Produce json
// @Param id path string true "id"
// @Success 200 {string} string // @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /ab/peer/add/{guid} [post] // @Router /ab/peer/add/{guid} [post]
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) PeerAdd(c *gin.Context) { func (a *Ab) PeerAdd(c *gin.Context) {
// forceAlwaysRelay is always the string "false" // forceAlwaysRelay永远是字符串"false",真是坑
//f := &gin.H{} //f := &gin.H{}
f := &requstform.PersonalAddressBookForm{} f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(f) err := c.ShouldBindJSON(f)
@@ -576,34 +402,10 @@ func (a *Ab) PeerAdd(c *gin.Context) {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
fmt.Println(f)
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid") f.UserId = u.Id
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
//fmt.Println(f)
f.UserId = uid
ab := f.ToAddressBook() ab := f.ToAddressBook()
ab.CollectionId = cid
if ab.Platform == "" || ab.Username == "" || ab.Hostname == "" {
peer := service.AllService.PeerService.FindById(ab.Id)
if peer.RowId != 0 {
ab.Platform = service.AllService.AddressBookService.PlatformFromOs(peer.Os)
ab.Username = peer.Username
ab.Hostname = peer.Hostname
}
}
err = service.AllService.AddressBookService.AddAddressBook(ab) err = service.AllService.AddressBookService.AddAddressBook(ab)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
@@ -613,12 +415,12 @@ func (a *Ab) PeerAdd(c *gin.Context) {
} }
// PeerDel // PeerDel
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary delete address // @Summary 删除地址
// @Description delete address // @Description 删除地址
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param id path string true "id"
// @Success 200 {string} string // @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /ab/peer/add/{guid} [delete] // @Router /ab/peer/add/{guid} [delete]
@@ -631,21 +433,8 @@ func (a *Ab) PeerDel(c *gin.Context) {
return return
} }
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserFullControlPrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
for _, id := range *f { for _, id := range *f {
ab := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(uid, id, cid) ab := service.AllService.AddressBookService.InfoByUserIdAndId(u.Id, id)
if ab == nil || ab.RowId == 0 { if ab == nil || ab.RowId == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound")) response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return return
@@ -661,64 +450,35 @@ func (a *Ab) PeerDel(c *gin.Context) {
} }
// PeerUpdate // PeerUpdate
// @Tags address[Personal] // @Tags 地址[Personal]
// @Summary update address // @Summary 更新地址
// @Description update address // @Description 更新地址
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param guid path string true "guid" // @Param id path string true "id"
// @Success 200 {string} string // @Success 200 {string} string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /ab/peer/update/{guid} [put] // @Router /ab/peer/update/{guid} [put]
// @Security BearerAuth // @Security BearerAuth
func (a *Ab) PeerUpdate(c *gin.Context) { func (a *Ab) PeerUpdate(c *gin.Context) {
f := gin.H{} //f := &gin.H{}
//f := &requstform.PersonalAddressBookForm{} f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(&f) err := c.ShouldBindJSON(f)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
u := service.AllService.UserService.CurUser(c)
guid := c.Param("guid")
_, uid, cid, err := a.CheckGuid(u, guid)
if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
//check privileges
if !service.AllService.AddressBookService.CheckUserWritePrivilege(u, uid, cid) {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
//fmt.Println(f) //fmt.Println(f)
// determine whether f["Id"] exists //return
fid, ok := f["id"] u := service.AllService.UserService.CurUser(c)
if !ok { ab := service.AllService.AddressBookService.InfoByUserIdAndId(u.Id, f.Id)
response.Error(c, response.TranslateMsg(c, "ParamsError"))
return
}
fidstr := fid.(string)
ab := service.AllService.AddressBookService.InfoByUserIdAndIdAndCid(uid, fidstr, cid)
if ab == nil || ab.RowId == 0 { if ab == nil || ab.RowId == 0 {
response.Error(c, response.TranslateMsg(c, "ItemNotFound")) response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return return
} }
//allowed fields nab := f.ToAddressBook()
allowUp := []string{"password", "hash", "tags", "alias"} nab.RowId = ab.RowId
// if a field in f is not in allowUp, delete it err = service.AllService.AddressBookService.Update(nab)
for k := range f {
if !utils.InArray(k, allowUp) {
delete(f, k)
}
}
//fmt.Println(f)
if tags, _ok := f["tags"]; _ok {
f["tags"], _ = json.Marshal(tags)
}
err = service.AllService.AddressBookService.UpdateByMap(ab, f)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
-84
View File
@@ -1,84 +0,0 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
request "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"time"
)
type Audit struct {
}
// AuditConn
// @Tags audit
// @Summary audit connection
// @Description audit connection
// @Accept json
// @Produce json
// @Param body body request.AuditConnForm true "audit connection"
// @Success 200 {string} string ""
// @Failure 500 {object} response.Response
// @Router /audit/conn [post]
func (a *Audit) AuditConn(c *gin.Context) {
af := &request.AuditConnForm{}
err := c.ShouldBindBodyWith(af, binding.JSON)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
/*ttt := &gin.H{}
c.ShouldBindBodyWith(ttt, binding.JSON)
fmt.Println(ttt)*/
ac := af.ToAuditConn()
if af.Action == model.AuditActionNew {
service.AllService.AuditService.CreateAuditConn(ac)
} else if af.Action == model.AuditActionClose {
ex := service.AllService.AuditService.InfoByPeerIdAndConnId(af.Id, af.ConnId)
if ex.Id != 0 {
ex.CloseTime = time.Now().Unix()
service.AllService.AuditService.UpdateAuditConn(ex)
}
} else if af.Action == "" {
ex := service.AllService.AuditService.InfoByPeerIdAndConnId(af.Id, af.ConnId)
if ex.Id != 0 {
up := &model.AuditConn{
IdModel: model.IdModel{Id: ex.Id},
FromPeer: ac.FromPeer,
FromName: ac.FromName,
SessionId: ac.SessionId,
Type: ac.Type,
}
service.AllService.AuditService.UpdateAuditConn(up)
}
}
response.Success(c, "")
}
// AuditFile
// @Tags audit
// @Summary audit file
// @Description audit file
// @Accept json
// @Produce json
// @Param body body request.AuditFileForm true "audit file"
// @Success 200 {string} string ""
// @Failure 500 {object} response.Response
// @Router /audit/file [post]
func (a *Audit) AuditFile(c *gin.Context) {
aff := &request.AuditFileForm{}
err := c.ShouldBindBodyWith(aff, binding.JSON)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
//ttt := &gin.H{}
//c.ShouldBindBodyWith(ttt, binding.JSON)
//fmt.Println(ttt)
af := aff.ToAuditFile()
service.AllService.AuditService.CreateAuditFile(af)
response.Success(c, "")
}
+44 -79
View File
@@ -1,51 +1,51 @@
package api package api
import ( import (
apiReq "Gwen/http/request/api"
"Gwen/http/response"
apiResp "Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
apiReq "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http" "net/http"
) )
type Group struct { type Group struct {
} }
// Users user list // Users 用户列表
// @Tags group // @Tags 群组
// @Summary user list // @Summary 用户列表
// @Description user list // @Description 用户列表
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "page number" // @Param page query int false "页码"
// @Param pageSize query int false "items per page" // @Param pageSize query int false "每页数量"
// @Param status query int false "status" // @Param status query int false "状态"
// @Param accessible query string false "accessible" // @Param accessible query string false "accessible"
// @Success 200 {object} response.DataResponse{data=[]apiResp.UserPayload} // @Success 200 {object} response.DataResponse{data=[]apiResp.UserPayload}
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /users [get] // @Router /users [get]
// @Security BearerAuth // @Security BearerAuth
func (g *Group) Users(c *gin.Context) { func (g *Group) Users(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
if !*u.IsAdmin {
gr := service.AllService.GroupService.InfoById(u.GroupId)
if gr.Type != model.GroupTypeShare {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
}
q := &apiReq.UserListQuery{} q := &apiReq.UserListQuery{}
err := c.ShouldBindQuery(&q) err := c.ShouldBindQuery(&q)
if err != nil { if err != nil {
response.Error(c, err.Error()) response.Error(c, err.Error())
return return
} }
u := service.AllService.UserService.CurUser(c) userList := service.AllService.UserService.ListByGroupId(u.GroupId, q.Page, q.PageSize)
gr := service.AllService.GroupService.InfoById(u.GroupId) var data []*apiResp.UserPayload
userList := &model.UserList{}
if !*u.IsAdmin && gr.Type != model.GroupTypeShare {
//can only retrieve one's own
userList.Users = append(userList.Users, u)
userList.Total = 1
} else {
userList = service.AllService.UserService.ListByGroupId(u.GroupId, q.Page, q.PageSize)
}
data := make([]*apiResp.UserPayload, 0, len(userList.Users))
for _, user := range userList.Users { for _, user := range userList.Users {
up := &apiResp.UserPayload{} up := &apiResp.UserPayload{}
up.FromUser(user) up.FromUser(user)
@@ -58,14 +58,14 @@ func (g *Group) Users(c *gin.Context) {
} }
// Peers // Peers
// @Tags group // @Tags 群组
// @Summary machine // @Summary 机器
// @Description machine // @Description 机器
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "page number" // @Param page query int false "页码"
// @Param pageSize query int false "items per page" // @Param pageSize query int false "每页数量"
// @Param status query int false "status" // @Param status query int false "状态"
// @Param accessible query string false "accessible" // @Param accessible query string false "accessible"
// @Success 200 {object} response.DataResponse // @Success 200 {object} response.DataResponse
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
@@ -73,45 +73,38 @@ func (g *Group) Users(c *gin.Context) {
// @Security BearerAuth // @Security BearerAuth
func (g *Group) Peers(c *gin.Context) { func (g *Group) Peers(c *gin.Context) {
u := service.AllService.UserService.CurUser(c) u := service.AllService.UserService.CurUser(c)
if !*u.IsAdmin {
gr := service.AllService.GroupService.InfoById(u.GroupId)
if gr.Type != model.GroupTypeShare {
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
}
q := &apiReq.PeerListQuery{} q := &apiReq.PeerListQuery{}
err := c.ShouldBindQuery(&q) err := c.ShouldBindQuery(&q)
if err != nil { if err != nil {
response.Error(c, err.Error()) response.Error(c, err.Error())
return return
} }
gr := service.AllService.GroupService.InfoById(u.GroupId)
users := make([]*model.User, 0, 1)
if !*u.IsAdmin && gr.Type != model.GroupTypeShare {
//can only retrieve one's own
users = append(users, u)
} else {
users = service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId)
}
namesById := make(map[uint]string, len(users)) users := service.AllService.UserService.ListIdAndNameByGroupId(u.GroupId)
userIds := make([]uint, 0, len(users)) namesById := make(map[uint]string)
userIds := make([]uint, 0)
for _, user := range users { for _, user := range users {
namesById[user.Id] = user.Username namesById[user.Id] = user.Username
userIds = append(userIds, user.Id) userIds = append(userIds, user.Id)
} }
dGroupNameById := make(map[uint]string)
allGroup := service.AllService.GroupService.DeviceGroupList(1, 999, nil)
for _, group := range allGroup.DeviceGroups {
dGroupNameById[group.Id] = group.Name
}
peerList := service.AllService.PeerService.ListByUserIds(userIds, q.Page, q.PageSize) peerList := service.AllService.PeerService.ListByUserIds(userIds, q.Page, q.PageSize)
data := make([]*apiResp.GroupPeerPayload, 0, len(peerList.Peers)) var data []*apiResp.GroupPeerPayload
for _, peer := range peerList.Peers { for _, peer := range peerList.Peers {
uname, ok := namesById[peer.UserId] uname, ok := namesById[peer.UserId]
if !ok { if !ok {
uname = "" uname = ""
} }
dGroupName, ok2 := dGroupNameById[peer.GroupId]
if !ok2 {
dGroupName = ""
}
pp := &apiResp.GroupPeerPayload{} pp := &apiResp.GroupPeerPayload{}
pp.FromPeer(peer, uname, dGroupName) pp.FromPeer(peer, uname)
data = append(data, pp) data = append(data, pp)
} }
@@ -120,31 +113,3 @@ func (g *Group) Peers(c *gin.Context) {
Data: data, Data: data,
}) })
} }
// Device
// @Tags group
// @Summary device
// @Description machine
// @Accept json
// @Produce json
// @Param page query int false "page number"
// @Param pageSize query int false "items per page"
// @Param status query int false "status"
// @Param accessible query string false "accessible"
// @Success 200 {object} response.DataResponse
// @Failure 500 {object} response.Response
// @Router /device-group/accessible [get]
// @Security BearerAuth
func (g *Group) Device(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) {
response.Error(c, "Permission denied")
return
}
allGroup := service.AllService.GroupService.DeviceGroupList(1, 999, nil)
c.JSON(http.StatusOK, response.DataResponse{
Total: 0,
Data: allGroup.DeviceGroups,
})
}
+14 -51
View File
@@ -1,22 +1,18 @@
package api package api
import ( import (
"Gwen/http/response"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http" "net/http"
"time"
) )
type Index struct { type Index struct {
} }
// Index home // Index 首页
// @Tags home // @Tags 首页
// @Summary home // @Summary 首页
// @Description home // @Description 首页
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
@@ -29,53 +25,20 @@ func (i *Index) Index(c *gin.Context) {
) )
} }
// Heartbeat heartbeat // Heartbeat 心跳
// @Tags home // @Tags 首页
// @Summary heartbeat // @Summary 心跳
// @Description heartbeat // @Description 心跳
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} nil // @Success 200 {object} nil
// @Failure 500 {object} response.Response // @Failure 500 {object} response.Response
// @Router /heartbeat [post] // @Router /heartbeat [post]
func (i *Index) Heartbeat(c *gin.Context) { func (i *Index) Heartbeat(c *gin.Context) {
info := &requstform.PeerInfoInHeartbeat{} //b := &gin.H{}
err := c.ShouldBindJSON(info) //err := c.BindJSON(b)
if err != nil { //body : &map[id:xxx modified_at:0 uuid:NGIxZTZjM2YtNmNkMy00YTMwLWFiNjQtMzQ0MTA0NGE5ZDgz ver:1.003e+06]
c.JSON(http.StatusOK, gin.H{}) //fmt.Println(b, err, c.Request.Header)
return //header : map[Accept:[*/*] Accept-Encoding:[gzip] Content-Length:[105] Content-Type:[application/json]]
}
if info.Uuid == "" {
c.JSON(http.StatusOK, gin.H{})
return
}
peer := service.AllService.PeerService.FindById(info.Id)
if peer == nil || peer.RowId == 0 {
c.JSON(http.StatusOK, gin.H{})
return
}
// do not update if within 40s
if time.Now().Unix()-peer.LastOnlineTime >= 30 {
upp := &model.Peer{RowId: peer.RowId, LastOnlineTime: time.Now().Unix(), LastOnlineIp: c.ClientIP()}
service.AllService.PeerService.Update(upp)
}
c.JSON(http.StatusOK, gin.H{}) c.JSON(http.StatusOK, gin.H{})
} }
// Version version
// @Tags home
// @Summary version
// @Description version
// @Accept json
// @Produce json
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /version [get]
func (i *Index) Version(c *gin.Context) {
// read the resources/version file
v := service.AllService.AppService.GetAppVersion()
response.Success(
c,
v,
)
}
+31 -47
View File
@@ -1,55 +1,41 @@
package api package api
import ( import (
"Gwen/global"
"Gwen/http/request/api"
"Gwen/http/response"
apiResp "Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"encoding/json" "encoding/json"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global"
"github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http" "net/http"
) )
type Login struct { type Login struct {
} }
// Login login // Login 登录
// @Tags login // @Tags 登录
// @Summary login // @Summary 登录
// @Description login // @Description 登录
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body api.LoginForm true "login form" // @Param body body api.LoginForm true "登录表单"
// @Success 200 {object} apiResp.LoginRes // @Success 200 {object} apiResp.LoginRes
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /login [post] // @Router /login [post]
func (l *Login) Login(c *gin.Context) { func (l *Login) Login(c *gin.Context) {
if global.Config.App.DisablePwdLogin {
response.Error(c, response.TranslateMsg(c, "PwdLoginDisabled"))
return
}
// check login limit
loginLimiter := global.LoginLimiter
clientIp := c.ClientIP()
f := &api.LoginForm{} f := &api.LoginForm{}
err := c.ShouldBindJSON(f) err := c.ShouldBindJSON(f)
//fmt.Println(f) //fmt.Println(f)
if err != nil { if err != nil {
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), c.ClientIP()))
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
errList := global.Validator.ValidStruct(c, f) errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 { if len(errList) > 0 {
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "ParamsError", c.RemoteIP(), c.ClientIP()))
response.Error(c, errList[0]) response.Error(c, errList[0])
return return
} }
@@ -57,27 +43,19 @@ func (l *Login) Login(c *gin.Context) {
u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password) u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password)
if u.Id == 0 { if u.Id == 0 {
loginLimiter.RecordFailedAttempt(clientIp)
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), c.ClientIP()))
response.Error(c, response.TranslateMsg(c, "UsernameOrPasswordError")) response.Error(c, response.TranslateMsg(c, "UsernameOrPasswordError"))
return return
} }
if !service.AllService.UserService.CheckUserEnable(u) { //根据refer判断是webclient还是app
response.Error(c, response.TranslateMsg(c, "UserDisabled"))
return
}
// determine whether it is webclient or app based on refer
ref := c.GetHeader("referer") ref := c.GetHeader("referer")
if ref != "" { if ref != "" {
f.DeviceInfo.Type = model.LoginLogClientWeb f.DeviceInfo.Type = "webclient"
} }
ut := service.AllService.UserService.Login(u, &model.LoginLog{ ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id, UserId: u.Id,
Client: f.DeviceInfo.Type, Client: f.DeviceInfo.Type,
DeviceId: f.Id,
Uuid: f.Uuid, Uuid: f.Uuid,
Ip: c.ClientIP(), Ip: c.ClientIP(),
Type: model.LoginLogTypeAccount, Type: model.LoginLogTypeAccount,
@@ -92,21 +70,27 @@ func (l *Login) Login(c *gin.Context) {
} }
// LoginOptions // LoginOptions
// @Tags login // @Tags 登录
// @Summary login options // @Summary 登录选项
// @Description login options // @Description 登录选项
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {object} []string // @Success 200 {object} []string
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /login-options [get] // @Router /login-options [post]
func (l *Login) LoginOptions(c *gin.Context) { func (l *Login) LoginOptions(c *gin.Context) {
ops := service.AllService.OauthService.GetOauthProviders() oauthOks := []string{}
if global.Config.App.WebSso { err, _ := service.AllService.OauthService.GetOauthConfig(model.OauthTypeGithub)
ops = append(ops, model.OauthTypeWebauth) if err == nil {
oauthOks = append(oauthOks, model.OauthTypeGithub)
} }
err, _ = service.AllService.OauthService.GetOauthConfig(model.OauthTypeGoogle)
if err == nil {
oauthOks = append(oauthOks, model.OauthTypeGoogle)
}
oauthOks = append(oauthOks, model.OauthTypeWebauth)
var oidcItems []map[string]string var oidcItems []map[string]string
for _, v := range ops { for _, v := range oauthOks {
oidcItems = append(oidcItems, map[string]string{"name": v}) oidcItems = append(oidcItems, map[string]string{"name": v})
} }
common, err := json.Marshal(oidcItems) common, err := json.Marshal(oidcItems)
@@ -116,16 +100,16 @@ func (l *Login) LoginOptions(c *gin.Context) {
} }
var res []string var res []string
res = append(res, "common-oidc/"+string(common)) res = append(res, "common-oidc/"+string(common))
for _, v := range ops { for _, v := range oauthOks {
res = append(res, "oidc/"+v) res = append(res, "oidc/"+v)
} }
c.JSON(http.StatusOK, res) c.JSON(http.StatusOK, res)
} }
// Logout // Logout
// @Tags login // @Tags 登录
// @Summary logout // @Summary 登出
// @Description logout // @Description 登出
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 200 {string} string // @Success 200 {string} string
+161 -207
View File
@@ -1,17 +1,16 @@
package api package api
import ( import (
"net/http" "Gwen/global"
"Gwen/http/request/api"
"Gwen/http/response"
apiResp "Gwen/http/response/api"
"Gwen/model"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/lejianwen/rustdesk-api/v2/global" "net/http"
"github.com/lejianwen/rustdesk-api/v2/http/request/api" "strconv"
"github.com/lejianwen/rustdesk-api/v2/http/response" "strings"
apiResp "github.com/lejianwen/rustdesk-api/v2/http/response/api"
"github.com/lejianwen/rustdesk-api/v2/model"
"github.com/lejianwen/rustdesk-api/v2/service"
"github.com/lejianwen/rustdesk-api/v2/utils"
"github.com/nicksnyder/go-i18n/v2/i18n"
) )
type Oauth struct { type Oauth struct {
@@ -33,16 +32,18 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub {
response.Error(c, response.TranslateMsg(c, "ParamsError"))
return
}
oauthService := service.AllService.OauthService err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
err, state, verifier, nonce, url := oauthService.BeginAuth(f.Op)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, err.Error())) response.Error(c, response.TranslateMsg(c, err.Error()))
return return
} }
service.AllService.OauthService.SetOauthCache(state, &service.OauthCacheItem{ service.AllService.OauthService.SetOauthCache(code, &service.OauthCacheItem{
Action: service.OauthActionTypeLogin, Action: service.OauthActionTypeLogin,
Id: f.Id, Id: f.Id,
Op: f.Op, Op: f.Op,
@@ -50,71 +51,14 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
DeviceName: f.DeviceInfo.Name, DeviceName: f.DeviceInfo.Name,
DeviceOs: f.DeviceInfo.Os, DeviceOs: f.DeviceInfo.Os,
DeviceType: f.DeviceInfo.Type, DeviceType: f.DeviceInfo.Type,
Verifier: verifier,
Nonce: nonce,
}, 5*60) }, 5*60)
//fmt.Println("code url", code, url) //fmt.Println("code url", code, url)
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"code": state, "code": code,
"url": url, "url": url,
}) })
} }
func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken) {
var u *model.User
var ut *model.UserToken
q := &api.OidcAuthQuery{}
// parse query parameters and handle errors
if err := c.ShouldBindQuery(q); err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+": "+err.Error())
return nil, nil
}
// get OAuth cache
v := service.AllService.OauthService.GetOauthCache(q.Code)
if v == nil {
response.Error(c, response.TranslateMsg(c, "OauthExpired"))
return nil, nil
}
// if UserId is 0, authorization is still in progress
if v.UserId == 0 {
//fix: 1.4.2 webclient oidc
c.JSON(http.StatusOK, gin.H{"message": "Authorization in progress, please login and bind", "error": "No authed oidc is found"})
return nil, nil
}
// get user info
u = service.AllService.UserService.InfoById(v.UserId)
if u == nil {
response.Error(c, response.TranslateMsg(c, "UserNotFound"))
return nil, nil
}
// delete OAuth cache
service.AllService.OauthService.DeleteOauthCache(q.Code)
// create a login log and generate a user token
ut = service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: v.DeviceType,
DeviceId: v.Id,
Uuid: v.Uuid,
Ip: c.ClientIP(),
Type: model.LoginLogTypeOauth,
Platform: v.DeviceOs,
})
if ut == nil {
response.Error(c, response.TranslateMsg(c, "LoginFailed"))
return nil, nil
}
// return user token
return u, ut
}
// OidcAuthQuery // OidcAuthQuery
// @Tags Oauth // @Tags Oauth
// @Summary OidcAuthQuery // @Summary OidcAuthQuery
@@ -125,10 +69,33 @@ func (o *Oauth) OidcAuthQueryPre(c *gin.Context) (*model.User, *model.UserToken)
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /oidc/auth-query [get] // @Router /oidc/auth-query [get]
func (o *Oauth) OidcAuthQuery(c *gin.Context) { func (o *Oauth) OidcAuthQuery(c *gin.Context) {
u, ut := o.OidcAuthQueryPre(c) q := &api.OidcAuthQuery{}
if u == nil || ut == nil { err := c.ShouldBindQuery(q)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
v := service.AllService.OauthService.GetOauthCache(q.Code)
if v == nil {
response.Error(c, response.TranslateMsg(c, "OauthExpired"))
return
}
if v.UserId == 0 {
//正在授权
c.JSON(http.StatusOK, gin.H{})
return
}
u := service.AllService.UserService.InfoById(v.UserId)
//fmt.Println("auth success u", u)
service.AllService.OauthService.DeleteOauthCache(q.Code)
ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id,
Client: v.DeviceType,
Uuid: v.Uuid,
Ip: c.ClientIP(),
Type: model.LoginLogTypeOauth,
Platform: v.DeviceOs,
})
c.JSON(http.StatusOK, apiResp.LoginRes{ c.JSON(http.StatusOK, apiResp.LoginRes{
AccessToken: ut.Token, AccessToken: ut.Token,
Type: "access_token", Type: "access_token",
@@ -136,7 +103,7 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
}) })
} }
// OauthCallback callback // OauthCallback 回调
// @Tags Oauth // @Tags Oauth
// @Summary OauthCallback // @Summary OauthCallback
// @Description OauthCallback // @Description OauthCallback
@@ -144,162 +111,149 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
// @Produce json // @Produce json
// @Success 200 {object} apiResp.LoginRes // @Success 200 {object} apiResp.LoginRes
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /oidc/callback [get] // @Router /oauth/callback [get]
func (o *Oauth) OauthCallback(c *gin.Context) { func (o *Oauth) OauthCallback(c *gin.Context) {
state := c.Query("state") state := c.Query("state")
if state == "" { if state == "" {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.String(http.StatusInternalServerError, response.TranslateParamMsg(c, "ParamIsEmpty", "state"))
"message": "ParamIsEmpty",
"sub_message": "state",
})
return return
} }
cacheKey := state cacheKey := state
oauthService := service.AllService.OauthService //从缓存中获取
//get from cache v := service.AllService.OauthService.GetOauthCache(cacheKey)
oauthCache := oauthService.GetOauthCache(cacheKey) if v == nil {
if oauthCache == nil { c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthExpired"))
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "OauthExpired",
})
return return
} }
nonce := oauthCache.Nonce
op := oauthCache.Op
action := oauthCache.Action
verifier := oauthCache.Verifier
var user *model.User
// get user info
code := c.Query("code")
err, oauthUser := oauthService.Callback(code, verifier, op, nonce)
if err != nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "OauthFailed",
"sub_message": err.Error(),
})
return
}
userId := oauthCache.UserId
openid := oauthUser.OpenId
if action == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData) ty := v.Op
// check whether this openid has already been bound ac := v.Action
utr := oauthService.UserThirdInfo(op, openid) //fmt.Println("ty ac ", ty, ac)
if utr.UserId > 0 { if ty == model.OauthTypeGithub {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ code := c.Query("code")
"message": "OauthHasBindOtherUser", err, userData := service.AllService.OauthService.GithubCallback(code)
})
return
}
//bind
user = service.AllService.UserService.InfoById(userId)
if user == nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "ItemNotFound",
})
return
}
//bind
err := oauthService.BindOauthUser(userId, oauthUser, op)
if err != nil { if err != nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
"message": "BindFail",
})
return return
} }
c.HTML(http.StatusOK, "oauth_success.html", gin.H{ if ac == service.OauthActionTypeBind {
"message": "BindSuccess", //fmt.Println("bind", ty, userData)
}) utr := service.AllService.OauthService.UserThirdInfo(ty, strconv.Itoa(userData.Id))
return if utr.UserId > 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
} else if action == service.OauthActionTypeLogin {
//login
if userId != 0 {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "OauthHasBeenSuccess",
})
return
}
user = service.AllService.UserService.InfoByOauthId(op, openid)
if user == nil {
oauthConfig := oauthService.InfoByOp(op)
if !*oauthConfig.AutoRegister {
//c.String(http.StatusInternalServerError, "not yet bound to a user, please bind first")
oauthCache.UpdateFromOauthUser(oauthUser)
c.Redirect(http.StatusFound, "/_admin/#/oauth/bind/"+cacheKey)
return return
} }
//绑定
//auto register u := service.AllService.UserService.InfoById(v.UserId)
err, user = service.AllService.UserService.RegisterByOauth(oauthUser, op) if u == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
return
}
//绑定github
err = service.AllService.OauthService.BindGithubUser(strconv.Itoa(userData.Id), userData.Login, v.UserId)
if err != nil { if err != nil {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
"message": err.Error(),
})
return return
} }
} c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
oauthCache.UserId = user.Id return
oauthService.SetOauthCache(cacheKey, oauthCache, 0) } else if ac == service.OauthActionTypeLogin {
// if it is webadmin, after successful login redirect to webadmin //登录
if oauthCache.DeviceType == model.LoginLogClientWebAdmin { if v.UserId != 0 {
/*service.AllService.UserService.Login(u, &model.LoginLog{ c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
UserId: u.Id, return
Client: "webadmin", }
Uuid: "", //must be empty u := service.AllService.UserService.InfoByGithubId(strconv.Itoa(userData.Id))
Ip: c.ClientIP(), if u == nil {
Type: model.LoginLogTypeOauth, oa := service.AllService.OauthService.InfoByOp(ty)
Platform: oauthService.DeviceOs, if !*oa.AutoRegister {
})*/ //c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
c.Redirect(http.StatusFound, "/_admin/#/") v.ThirdName = userData.Login
v.ThirdOpenId = strconv.Itoa(userData.Id)
url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
c.Redirect(http.StatusFound, url)
return
}
//自动注册
u = service.AllService.UserService.RegisterByGithub(userData.Login, strconv.Itoa(userData.Id))
if u.Id == 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed"))
return
}
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
return return
} }
c.HTML(http.StatusOK, "oauth_success.html", gin.H{
"message": "OauthSuccess",
})
return
} else {
c.HTML(http.StatusOK, "oauth_fail.html", gin.H{
"message": "ParamsError",
})
return
} }
} if ty == model.OauthTypeGoogle {
code := c.Query("code")
type MessageParams struct { err, userData := service.AllService.OauthService.GoogleCallback(code)
Lang string `json:"lang" form:"lang"` if err != nil {
Title string `json:"title" form:"title"` c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
Msg string `json:"msg" form:"msg"` return
}
func (o *Oauth) Message(c *gin.Context) {
mp := &MessageParams{}
if err := c.ShouldBindQuery(mp); err != nil {
return
}
localizer := global.Localizer(mp.Lang)
res := ""
if mp.Title != "" {
title, err := localizer.LocalizeMessage(&i18n.Message{
ID: mp.Title,
})
if err == nil {
res = utils.StringConcat(";title='", title, "';")
} }
//将空格替换成_
googleName := strings.Replace(userData.Name, " ", "_", -1)
if ac == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData)
utr := service.AllService.OauthService.UserThirdInfo(ty, userData.Email)
if utr.UserId > 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
//绑定
u := service.AllService.UserService.InfoById(v.UserId)
if u == nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound"))
return
}
//绑定
err = service.AllService.OauthService.BindGoogleUser(userData.Email, googleName, v.UserId)
if err != nil {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
return
}
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
return
} else if ac == service.OauthActionTypeLogin {
if v.UserId != 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
return
}
u := service.AllService.UserService.InfoByGoogleEmail(userData.Email)
if u == nil {
oa := service.AllService.OauthService.InfoByOp(ty)
if !*oa.AutoRegister {
//c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定")
} v.ThirdName = googleName
if mp.Msg != "" { v.ThirdOpenId = userData.Email
msg, err := localizer.LocalizeMessage(&i18n.Message{ url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey
ID: mp.Msg, c.Redirect(http.StatusFound, url)
}) return
if err == nil { }
res = utils.StringConcat(res, "msg = '", msg, "';")
//自动注册
u = service.AllService.UserService.RegisterByGoogle(googleName, userData.Email)
if u.Id == 0 {
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed"))
return
}
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
return
} }
} }
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "SystemError"))
//returnjscontent
c.Header("Content-Type", "application/javascript")
c.String(http.StatusOK, res)
} }
+14 -42
View File
@@ -1,12 +1,11 @@
package api package api
import ( import (
"fmt" requstform "Gwen/http/request/api"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
requstform "github.com/lejianwen/rustdesk-api/v2/http/request/api"
"github.com/lejianwen/rustdesk-api/v2/http/response"
"github.com/lejianwen/rustdesk-api/v2/service"
"net/http" "net/http"
) )
@@ -14,15 +13,16 @@ type Peer struct {
} }
// SysInfo // SysInfo
// @Tags System // @Tags 地址
// @Summary submit system info // @Summary 提交系统信息
// @Description submit system info // @Description 提交系统信息
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param body body requstform.PeerForm true "system info form" // @Param body body requstform.PeerForm true "系统信息表单"
// @Success 200 {string} string "SYSINFO_UPDATED,ID_NOT_FOUND" // @Success 200 {string} string "SYSINFO_UPDATED,ID_NOT_FOUND"
// @Failure 500 {object} response.ErrorResponse // @Failure 500 {object} response.ErrorResponse
// @Router /sysinfo [post] // @Router /sysinfo [post]
// @Security BearerAuth
func (p *Peer) SysInfo(c *gin.Context) { func (p *Peer) SysInfo(c *gin.Context) {
f := &requstform.PeerForm{} f := &requstform.PeerForm{}
err := c.ShouldBindBodyWith(f, binding.JSON) err := c.ShouldBindBodyWith(f, binding.JSON)
@@ -30,47 +30,19 @@ func (p *Peer) SysInfo(c *gin.Context) {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return return
} }
fpe := f.ToPeer()
pe := service.AllService.PeerService.FindById(f.Id) pe := service.AllService.PeerService.FindById(f.Id)
if pe.RowId == 0 { if pe == nil || pe.RowId == 0 {
pe = f.ToPeer() pe = f.ToPeer()
pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid, pe.Id)
err = service.AllService.PeerService.Create(pe) err = service.AllService.PeerService.Create(pe)
if err != nil { if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error()) response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return return
} }
} else {
if pe.UserId == 0 {
pe.UserId = service.AllService.UserService.FindLatestUserIdFromLoginLogByUuid(pe.Uuid, pe.Id)
}
fpe.RowId = pe.RowId
fpe.UserId = pe.UserId
err = service.AllService.PeerService.Update(fpe)
if err != nil {
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
} }
//SYSINFO_UPDATED upload successful
//ID_NOT_FOUND will be uploaded on the next heartbeat
//respond with text directly
c.String(http.StatusOK, "SYSINFO_UPDATED")
}
// SysInfoVer //SYSINFO_UPDATED 上传成功
// @Tags System //ID_NOT_FOUND 下次心跳会上传
// @Summary get system version info //直接响应文本
// @Description get system version info c.String(http.StatusOK, "")
// @Accept json
// @Produce json
// @Success 200 {string} string ""
// @Failure 500 {object} response.ErrorResponse
// @Router /sysinfo_ver [post]
func (p *Peer) SysInfoVer(c *gin.Context) {
// read the resources/version file
v := service.AllService.AppService.GetAppVersion()
// add the start time so the client can conveniently upload info
v = fmt.Sprintf("%s\n%s", v, service.AllService.AppService.GetStartTime())
c.String(http.StatusOK, v)
} }

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