Compare commits

...

63 Commits

Author SHA1 Message Date
ljw
9aad62d1e4 build tag 2024-09-29 12:47:04 +08:00
ljw
867eab40f8 build default push to docker 2024-09-29 12:39:33 +08:00
ljw
eb5c7efc4c fix build 2024-09-29 12:23:34 +08:00
857abc16e7 Merge pull request #5 from gigaion/new-build-1
build.yml - Add GHCR & Dynamic Inputs
2024-09-29 12:12:17 +08:00
ljw
28b9866c42 upgrade: init by i18n
add: batch delete peer
add: batch peer to addressbook
2024-09-29 11:53:58 +08:00
Gigaion
a27deb0a41 build.yml - Add GHCR & Dynamic Inputs
build.yml - Add GHCR & Dynamic Inputs
2024-09-28 13:30:44 -07:00
ljw
8e026de20b test 2024-09-28 11:01:23 +08:00
ljw
718ecc2372 test 2024-09-28 10:12:56 +08:00
ljw
56d46722f4 test 2024-09-28 10:05:04 +08:00
ljw
b6463cd715 test 2024-09-28 09:57:32 +08:00
ljw
bd3ae0cbfe fix build docker image 2024-09-28 09:39:26 +08:00
ljw
83c3aa894f fix build docker image 2024-09-28 09:33:59 +08:00
ljw
1b88d26fea up build 2024-09-27 22:08:36 +08:00
ljw
588287fdb4 up build 2024-09-27 22:06:04 +08:00
ljw
688e544b07 up build 2024-09-27 22:03:06 +08:00
ljw
3e3f812e83 up build 2024-09-27 21:48:48 +08:00
ljw
b551c7abe4 up build 2024-09-27 21:37:12 +08:00
ljw
6d1e7a4c05 up build 2024-09-27 21:29:19 +08:00
ljw
3341a4bc8e up build docker echo manifest 2024-09-27 21:20:54 +08:00
ljw
1c84980d36 up build docker 2024-09-27 20:10:17 +08:00
ljw
833b25881d up build docker 2024-09-27 20:06:49 +08:00
ljw
9dbf58903c up build docker 2024-09-27 19:40:48 +08:00
ljw
ad007f0d91 up build docker 2024-09-27 18:49:22 +08:00
ljw
4b06973a52 up build docker 2024-09-27 18:48:09 +08:00
ljw
159a67f15d up build docker 2024-09-27 17:49:17 +08:00
ljw
7c03b9953b up build docker 2024-09-27 17:32:25 +08:00
ljw
f90987de8d up build docker 2024-09-27 17:26:40 +08:00
ljw
70e4ff7820 up build docker 2024-09-27 17:25:30 +08:00
ljw
a99356f54b up build docker 2024-09-27 17:24:25 +08:00
ljw
c5bc9534cc up build docker 2024-09-27 17:15:06 +08:00
ljw
a40733424f up build docker 2024-09-27 16:52:02 +08:00
ljw
a937efc60e up build docker 2024-09-27 16:07:06 +08:00
ljw
6adb0e8415 up build docker 2024-09-27 15:59:12 +08:00
ljw
ff9ffb2f12 up build docker 2024-09-27 15:31:43 +08:00
ljw
9be4f472ae up build docker 2024-09-27 15:23:19 +08:00
ljw
8581d74b08 up build docker 2024-09-27 15:18:45 +08:00
ljw
dafe9bd6b6 up build docker 2024-09-27 14:58:17 +08:00
ljw
3ae5772360 up build docker 2024-09-27 14:54:11 +08:00
ljw
4628dbccfb up build docker 2024-09-27 14:46:39 +08:00
ljw
572b1d4c14 up build docker 2024-09-27 14:35:43 +08:00
ljw
bdb70e9859 up build docker 2024-09-27 14:29:55 +08:00
ljw
38bda17271 up build docker 2024-09-27 14:24:17 +08:00
ljw
455e1d2e5b up build docker 2024-09-27 14:19:56 +08:00
ljw
89cd724bab up build docker 2024-09-27 14:18:36 +08:00
ljw
b9109b4d0e up build docker 2024-09-27 14:16:59 +08:00
ljw
945958f552 up build docker 2024-09-27 14:10:50 +08:00
ljw
78eb0d5c06 up release_arm64.yml 2024-09-27 10:43:48 +08:00
ljw
bc6eae711e up release_arm64.yml 2024-09-27 10:38:14 +08:00
ljw
f0a4bf6164 up docker_arm64.yml 2024-09-26 14:36:15 +08:00
ljw
fc3b5e3ac3 up docker_arm64.yml 2024-09-26 14:33:01 +08:00
ljw
f7235ac847 up docker_arm64.yml 2024-09-26 14:26:15 +08:00
ljw
231f4ddb7f up docker_arm64.yml 2024-09-26 14:24:14 +08:00
ljw
3cad3994cb add docker_arm64.yml 2024-09-26 14:18:57 +08:00
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
55 changed files with 1401 additions and 526 deletions

290
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,290 @@
name: Build
on:
workflow_dispatch:
inputs:
BASE_IMAGE_NAMESPACE:
description: 'Base image namespace (Default: Your Github username)'
required: false
default: ''
DOCKERHUB_IMAGE_NAMESPACE:
description: 'Docker Hub image namespace (Default: Your Github username)'
required: false
default: ''
GHCR_IMAGE_NAMESPACE:
description: 'GitHub Container Registry image namespace (Default: Your Github username)'
required: false
default: ''
SKIP_DOCKER_HUB:
description: 'Set to true to skip pushing to Docker Hub (default: false)'
required: false
default: 'false'
SKIP_GHCR:
description: 'Set to true to skip pushing to GHCR (default: false)'
required: false
default: 'false'
WEBCLIENT_SOURCE_LOCATION:
description: 'Web Client API Repository'
required: true
default: 'https://github.com/lejianwen/rustdesk-api-web'
push:
tags:
- 'v*.*.*' # 当推送带有版本号的 tag例如 v1.0.0)时触发工作流
- 'test*'
env:
LATEST_TAG: latest
WEBCLIENT_SOURCE_LOCATION: ${{ github.event.inputs.WEBCLIENT_SOURCE_LOCATION || 'https://github.com/lejianwen/rustdesk-api-web' }}
BASE_IMAGE_NAMESPACE: ${{ github.event.inputs.BASE_IMAGE_NAMESPACE || github.actor }}
DOCKERHUB_IMAGE_NAMESPACE: ${{ github.event.inputs.DOCKERHUB_IMAGE_NAMESPACE || github.actor }}
GHCR_IMAGE_NAMESPACE: ${{ github.event.inputs.GHCR_IMAGE_NAMESPACE || github.actor }}
SKIP_DOCKER_HUB: ${{ github.event.inputs.SKIP_DOCKER_HUB || 'false' }}
SKIP_GHCR: ${{ github.event.inputs.SKIP_GHCR }}
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux" }
- { platform: "arm64", goos: "linux" }
- { platform: "amd64", goos: "windows" }
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: build rustdesk-api-web
run: |
git clone ${{ env.WEBCLIENT_SOURCE_LOCATION }}
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.job.goos }}-${{ matrix.job.platform }}
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.job.goos }}" = "windows" ]; then
sudo apt-get install gcc-mingw-w64-x86-64 zip -y
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} 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.job.goos}}-${{ matrix.job.platform }}.zip ./release
else
if [ "${{ matrix.job.platform }}" = "arm64" ]; then
wget https://musl.cc/aarch64-linux-musl-cross.tgz
tar -xf aarch64-linux-musl-cross.tgz
export PATH=$PATH:$PWD/aarch64-linux-musl-cross/bin
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=aarch64-linux-musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
else
sudo apt-get install musl musl-dev musl-tools -y
GOOS=${{ matrix.job.goos }} GOARCH=${{ matrix.job.platform }} CC=musl-gcc CGO_LDFLAGS="-static" CGO_ENABLED=1 go build -ldflags "-s -w" -o ./release/apimain ./cmd/apimain.go
fi
tar -czf ${{ matrix.job.goos}}-${{ matrix.job.platform }}.tar.gz ./release
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.tar.gz
${{ matrix.job.goos}}-${{ matrix.job.platform }}.zip
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
${{ matrix.job.goos}}-${{ matrix.job.platform }}.tar.gz
${{ matrix.job.goos}}-${{ matrix.job.platform }}.zip
# tag_name: ${{ env.LATEST_TAG }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
docker:
name: Push Docker Image
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- { platform: "amd64", goos: "linux", docker_platform: "linux/amd64" }
- { platform: "arm64", goos: "linux", docker_platform: "linux/arm64" }
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only log in if SKIP_DOCKER_HUB is false
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Log in to GitHub Container Registry
if: ${{ env.SKIP_GHCR == 'false' }} # Only log in if GHCR push is enabled
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: vars
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
else
echo "TAG=latest" >> $GITHUB_ENV # Default to 'latest' if not a tag
fi
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api
- name: Download binaries
uses: actions/download-artifact@v4
with:
name: rustdesk-api-${{ matrix.job.goos }}-${{ matrix.job.platform }}
path: ./
- name: Unzip binaries
run: |
mkdir -p ${{ matrix.job.platform }}
tar -xzf ${{ matrix.job.goos }}-${{ matrix.job.platform }}.tar.gz -C ${{ matrix.job.platform }}
file ${{ matrix.job.platform }}/apimain
- name: Build and push Docker image to Docker Hub ${{ matrix.job.platform }}
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only run this step if SKIP_DOCKER_HUB is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.LATEST_TAG }}-${{ matrix.job.platform }},
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push Docker image to GHCR ${{ matrix.job.platform }}
if: ${{ env.SKIP_GHCR == 'false' }} # Only run this step if SKIP_GHCR is false
uses: docker/build-push-action@v5
with:
context: "."
file: ./Dockerfile
platforms: ${{ matrix.job.docker_platform }}
push: true
provenance: false
build-args: |
BUILDARCH=${{ matrix.job.platform }}
tags: |
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.LATEST_TAG }}-${{ matrix.job.platform }},
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-${{ matrix.job.platform }}
labels: ${{ steps.meta.outputs.labels }}
#
docker-manifest:
name: Push Docker Manifest
needs: docker
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: vars
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
else
echo "TAG=latest" >> $GITHUB_ENV # Default to 'latest' if not a tag
fi
- name: Log in to Docker Hub
if: ${{ env.SKIP_DOCKER_HUB == 'false' }} # Only log in if Docker Hub push is enabled
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Log in to GitHub Container Registry
if: ${{ env.SKIP_GHCR == 'false' }} # Only log in if GHCR push is enabled
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest Docker Hub (:version)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-arm64
push: true
- name: Create and push manifest GHCR (:version)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-amd64,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:${{ env.TAG }}-arm64
push: true
amend: true
- name: Create and push manifest Docker Hub (:latest)
if: ${{ env.SKIP_DOCKER_HUB == 'false' }}
uses: Noelware/docker-manifest-action@master
with:
base-image: ${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:latest
extra-images: ${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:latest-amd64,
${{ env.DOCKERHUB_IMAGE_NAMESPACE }}/rustdesk-api:latest-arm64
push: true
- name: Create and push manifest GHCR (:latest)
if: ${{ env.SKIP_GHCR == 'false' }}
uses: Noelware/docker-manifest-action@master
with:
base-image: ghcr.io/${{ env.BASE_IMAGE_NAMESPACE }}/rustdesk-api:latest
extra-images: ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-amd64,
ghcr.io/${{ env.GHCR_IMAGE_NAMESPACE }}/rustdesk-api:latest-arm64
push: true
amend: true

View File

@@ -1,38 +0,0 @@
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 }}

View File

@@ -1,27 +0,0 @@
# 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

View File

@@ -1,9 +1,9 @@
name: Build and Release
on:
push:
tags:
- 'v*.*.*' # 当推送带有版本号的 tag例如 v1.0.0)时触发工作流
workflow_dispatch:
# tags:
# - 'v*.*.*' # 当推送带有版本号的 tag例如 v1.0.0)时触发工作流
#on:
# push:
# branches: [ "master" ]

1
.gitignore vendored
View File

@@ -5,5 +5,6 @@ go.sum
resources/*
!resources/public/upload/.gitignore
!resources/web
!resources/i18n
release
data

View File

@@ -1,35 +1,11 @@
FROM golang:1.22-alpine as builder
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;
VOLUME /app/data
FROM alpine
ARG BUILDARCH
WORKDIR /app
RUN apk add --no-cache tzdata
COPY --from=builder /go/rustdesk-api/release /app/
RUN apk add --no-cache tzdata file
COPY ./${BUILDARCH}/release /app/
RUN file /app/apimain
VOLUME /app/data
EXPOSE 21114
CMD ["./apimain"]

View File

@@ -9,8 +9,7 @@
<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/swag-v1.16.3-yellow"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/release.yml/badge.svg"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/docker.yml/badge.svg"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
</div>
# 特性
@@ -21,6 +20,7 @@
- 地址簿
- 群组
- 授权登录,支持`github``google`登录,支持`web后台`授权登录
- i18n
- Web Admin
- 用户管理
- 设备管理
@@ -29,6 +29,7 @@
- 群组管理
- Oauth 管理
- 快速使用web client
- i18n
- Web Client
- 自动获取API server
- 自动获取ID服务器和KEY
@@ -55,7 +56,7 @@
## 功能
### API 服务: 基本实现了PC端基础的接口。支持Personal版本接口可以通过配置文件`rustdesk.personal`或环境变量`RUSTDESK_PERSONAL`来控制是否启用
### API 服务: 基本实现了PC端基础的接口。支持Personal版本接口可以通过配置文件`rustdesk.personal`或环境变量`RUSTDESK_API_RUSTDESK_PERSONAL`来控制是否启用
#### 登录
@@ -68,13 +69,13 @@
![pc_ab](docs/pc_ab.png)
#### 群组,群组分为`共享组`和`普通组`,共享组中所有人都能看到小组成员的地址,普通组只有管理员能看到所有小组成员的地址
#### 群组,群组分为`共享组`和`普通组`,共享组中所有人都能看到小组成员的设备,普通组只有管理员能看到所有小组成员的设备
![pc_gr](docs/pc_gr.png)
### **Web Admin**: 使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。
### Web Admin:
***前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)***
***使用前后端分离,提供用户友好的管理界面,主要用来管理和展示。前端代码在[rustdesk-api-web](https://github.com/lejianwen/rustdesk-api-web)***
***后台访问地址是`http://<your server>[:port]/_admin/`初次安装管理员为用户名密码为`admin` `admin`,请即时更改密码***
@@ -82,21 +83,21 @@
![web_admin](docs/web_admin.png)
2. 普通用户界面
![web_user](docs/web_admin_user.png)
3. 右上角也可以更改密码
右上角也可以更改密码
![web_resetpwd](docs/web_resetpwd.png)
4. 分组可以自定义,方便管理,暂时支持两种类型: `共享组` 和 `普通组`
3. 分组可以自定义,方便管理,暂时支持两种类型: `共享组` 和 `普通组`
![web_admin_gr](docs/web_admin_gr.png)
5. 可以直接打开webclient方便使用
4. 可以直接打开webclient方便使用
![web_webclient](docs/admin_webclient.png)
6. Oauth,暂时只支持了`Github`和`Google`, 需要创建一个`OAuth App`,然后配置到后台
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. 如果已经登录了后台web client将自动直接登录
2. 如果没登录后台点击右上角登录即可api server已经自动配置好了
@@ -104,19 +105,22 @@
3. 登录后会自动同步ID服务器和KEY
4. 登录后会将地址簿自动保存到web client中方便使用
### **自动化文档**: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。
### 自动化文档: 使用 Swag 生成 API 文档,方便开发者理解和使用 API。
1. 后台文档 `<youer server>/admin/swagger/index.html`
2. PC端文档 `<youer server>/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)
## 安装与运行
### 相关配置
* 参考`conf/config.yaml`配置文件,修改相关配置。如果`gorm.type`是`sqlite`则不需要配置mysql相关配置。
* 参考`conf/config.yaml`配置文件,修改相关配置。
* 如果`gorm.type`是`sqlite`则不需要配置mysql相关配置。
* 语言如果不设置默认为`zh-CN`
```yaml
lang: "en"
gin:
api-addr: "0.0.0.0:21114"
mode: "release"
@@ -136,6 +140,7 @@ rustdesk:
relay-server: "192.168.1.66:21117"
api-server: "http://192.168.1.66:21114"
key: "123456789"
personal: 1
```
* 环境变量,变量名前缀是RUSTDESK_API环境变量如果存在将覆盖配置文件中的配置
@@ -143,13 +148,14 @@ rustdesk:
| 变量名 | 说明 | 示例 |
|-------------------------------------|--------------------------------------|-----------------------------|
| 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_PERSONAL | 是否启用个人版API 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API 1:启用,0:不启用; 默认启用 | 1 |
| -----MYSQL配置----- | ---------- | ---------- |
| RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root |
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
@@ -370,7 +376,7 @@ lejianwen/rustdesk-api
5. 编译,如果想自己编译,先cd到项目根目录然后windows下直接运行`build.bat`,linux下运行`build.sh`,编译后会在`release`
目录下生成对应的可执行文件。直接运行编译后的可执行文件即可。
6. 打开浏览器访问`http://<your server>:21114/_admin/`,默认用户名密码为`admin`,请及时更改密码。
6. 打开浏览器访问`http://<your server[:port]>/_admin/`,默认用户名密码为`admin`,请及时更改密码。
## 其他

View File

@@ -8,8 +8,7 @@ desktop software that provides self-hosted solutions.
<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/swag-v1.16.3-yellow"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/release.yml/badge.svg"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/docker.yml/badge.svg"/>
<img src="https://github.com/lejianwen/rustdesk-api/actions/workflows/build.yml/badge.svg"/>
</div>
# Features
@@ -20,6 +19,7 @@ desktop software that provides self-hosted solutions.
- Address Book
- Groups
- Authorized login, supports `GitHub` and `Google` login, supports `web admin` authorized login
- i18n
- Web Admin
- User Management
- Device Management
@@ -28,6 +28,7 @@ desktop software that provides self-hosted solutions.
- Group Management
- OAuth Management
- Quick access to web client
- i18n
- Web Client
- Automatically obtain API server
- Automatically obtain ID server and KEY
@@ -53,57 +54,54 @@ desktop software that provides self-hosted solutions.
hbbr -k abc1234567
```
## Features
## Overview
### 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_PERSONAL` environment variable.
### 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.
#### Login
- Added `GitHub` and `Google` login, which can be used after configuration in the admin panel. See the OAuth
configuration section
for details.
configuration section for details.
- Added authorization login for the web admin panel.
![pc_login](docs/pc_login.png)
![pc_login](docs/en_img/pc_login.png)
#### Address Book
![pc_ab](docs/pc_ab.png)
![pc_ab](docs/en_img/pc_ab.png)
#### Groups: Groups are divided into `shared groups` and `regular groups`. In shared groups, everyone can see the addresses of all group members, while in regular groups, only administrators can see all members' addresses.
#### 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/pc_gr.png)
![pc_gr](docs/en_img/pc_gr.png)
### **Web UI
### Web Admin
**: The frontend and backend are separated to provide a user-friendly management interface, primarily for managing and
displaying data.
***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)***
***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
***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/web_admin.png)
![web_admin](docs/en_img/web_admin.png)
2. Regular user interface:
![web_user](docs/web_admin_user.png)
3. You can change your password from the top right corner:
![web_resetpwd](docs/web_resetpwd.png)
4. Groups can be customized for easy management. Currently, two types are supported: `shared group` and `regular group`.
![web_admin_gr](docs/web_admin_gr.png)
5. You can open the web client directly for convenience:
![web_webclient](docs/admin_webclient.png)
6. OAuth support: Currently, `GitHub` and `Google` is supported. You need to create an `OAuth App` and configure it in
![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/web_admin_oauth.png)
![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. If you're already logged into the admin panel, the web client will log in automatically.
2. If you're not logged in, simply click the login button at the top right corner, and the API server will be
@@ -112,22 +110,22 @@ installation are `admin` `admin`, please change the password immediately.***
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
### Automated Documentation : API documentation is generated using Swag, making it easier for developers to understand and use the API.
** : API documentation is generated using Swag, making it easier for developers to understand and use the API.
1. Admin panel docs: `<your server>/admin/swagger/index.html`
2. PC client docs: `<your server>/swagger/index.html`
1. Admin panel docs: `<your server[:port]>/admin/swagger/index.html`
2. PC client docs: `<your server[:port]>/swagger/index.html`
![api_swag](docs/api_swag.png)
## Installation and Setup
### Configuration
* Modify the configuration in `conf/config.yaml`. If `gorm.type` is set to `sqlite`, MySQL-related configurations are
not required.
* 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"
@@ -147,21 +145,24 @@ rustdesk:
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`, will override the settings in the configuration file if
* 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 ----- | --------------------------------------- | ------------------------------ |
| TZ | 时区 | Asia/Shanghai |
| 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_PERSONAL | Open Personal Api 1:Enable,0:Disable | 1 |
| 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 |
@@ -183,6 +184,7 @@ rustdesk:
```bash
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 \
@@ -382,7 +384,7 @@ Download the release from [release](https://github.com/lejianwen/rustdesk-api/re
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>:21114/_admin/`, with default credentials `admin admin`. Please
6. Open your browser and visit `http://<your server[:port]>/_admin/`, with default credentials `admin admin`. Please
change the password promptly.
## Miscellaneous

View File

@@ -12,12 +12,18 @@ import (
"Gwen/model"
"Gwen/service"
"fmt"
"github.com/BurntSushi/toml"
"github.com/gin-gonic/gin"
"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/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
nethttp "net/http"
"reflect"
)
@@ -42,6 +48,8 @@ func main() {
ReportCaller: global.Config.Logger.ReportCaller,
})
InitI18n()
//redis
global.Redis = redis.NewClient(&redis.Options{
Addr: global.Config.Redis.Addr,
@@ -105,15 +113,25 @@ func main() {
func ApiInitValidator() {
validate := validator.New()
// 定义不同的语言翻译
enT := en.New()
cn := zh_Hans_CN.New()
uni := ut.New(enT, cn)
trans, _ := uni.GetTranslator("cn")
err := zh_translations.RegisterDefaultTranslations(validate, trans)
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 == "" {
@@ -122,10 +140,16 @@ func ApiInitValidator() {
return label
})
global.Validator.Validate = validate
global.Validator.VTrans = trans
global.Validator.UT = uni // 存储 Universal Translator
global.Validator.VTrans = zhTrans
global.Validator.ValidStruct = func(i interface{}) []string {
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 {
@@ -133,14 +157,18 @@ func ApiInitValidator() {
return errList
}
for _, err2 := range err.(validator.ValidationErrors) {
errList = append(errList, err2.Translate(global.Validator.VTrans))
errList = append(errList, err2.Translate(trans))
}
}
return errList
}
global.Validator.ValidVar = func(field interface{}, tag string) []string {
global.Validator.ValidVar = func(ctx *gin.Context, field interface{}, tag string) []string {
err := global.Validator.Validate.Var(field, tag)
fmt.Println(err)
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 {
@@ -148,16 +176,31 @@ func ApiInitValidator() {
return errList
}
for _, err2 := range err.(validator.ValidationErrors) {
errList = append(errList, err2.Translate(global.Validator.VTrans))
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() {
version := 126
version := 212
db := global.DB
@@ -227,13 +270,23 @@ func Migrate(version uint) {
var vc int64
global.DB.Model(&model.Version{}).Count(&vc)
if vc == 1 {
localizer := global.Localizer(&gin.Context{
Request: &nethttp.Request{},
})
defaultGroup, _ := localizer.LocalizeMessage(&i18n.Message{
ID: "DefaultGroup",
})
group := &model.Group{
Name: "默认组",
Name: defaultGroup,
Type: model.GroupTypeDefault,
}
service.AllService.GroupService.Create(group)
shareGroup, _ := localizer.LocalizeMessage(&i18n.Message{
ID: "ShareGroup",
})
groupShare := &model.Group{
Name: "共享组",
Name: shareGroup,
Type: model.GroupTypeShare,
}
service.AllService.GroupService.Create(groupShare)
@@ -241,7 +294,7 @@ func Migrate(version uint) {
is_admin := true
admin := &model.User{
Username: "admin",
Nickname: "管理员",
Nickname: "Admin",
Status: model.COMMON_STATUS_ENABLE,
IsAdmin: &is_admin,
GroupId: 1,
@@ -251,3 +304,37 @@ func Migrate(version uint) {
}
}
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)
}

View File

@@ -1,3 +1,4 @@
lang: "zh-CN"
gin:
api-addr: "0.0.0.0:21114"
mode: "release" #release,debug,test

View File

@@ -15,6 +15,7 @@ const (
)
type Config struct {
Lang string `mapstructure:"lang"`
Gorm Gorm
Mysql Mysql
Gin Gin

View File

@@ -22,7 +22,7 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "创建地址簿",
"description": "批量创建地址簿",
"consumes": [
"application/json"
],
@@ -32,7 +32,7 @@ const docTemplateadmin = `{
"tags": [
"地址簿"
],
"summary": "创建地址簿",
"summary": "批量创建地址簿",
"parameters": [
{
"description": "地址簿信息",
@@ -1185,7 +1185,7 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "创建机器",
"description": "创建设备",
"consumes": [
"application/json"
],
@@ -1193,12 +1193,12 @@ const docTemplateadmin = `{
"application/json"
],
"tags": [
"机器"
"设备"
],
"summary": "创建机器",
"summary": "创建设备",
"parameters": [
{
"description": "机器信息",
"description": "设备信息",
"name": "body",
"in": "body",
"required": true,
@@ -1242,7 +1242,7 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "机器删除",
"description": "批量设备删除",
"consumes": [
"application/json"
],
@@ -1250,17 +1250,17 @@ const docTemplateadmin = `{
"application/json"
],
"tags": [
"机器"
"设备"
],
"summary": "机器删除",
"summary": "批量设备删除",
"parameters": [
{
"description": "机器信息",
"description": "设备id",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.PeerForm"
"$ref": "#/definitions/admin.PeerBatchDeleteForm"
}
}
],
@@ -1287,7 +1287,7 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "机器详情",
"description": "设备详情",
"consumes": [
"application/json"
],
@@ -1295,9 +1295,9 @@ const docTemplateadmin = `{
"application/json"
],
"tags": [
"机器"
"设备"
],
"summary": "机器详情",
"summary": "设备详情",
"parameters": [
{
"type": "integer",
@@ -1342,7 +1342,7 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "机器列表",
"description": "设备列表",
"consumes": [
"application/json"
],
@@ -1350,9 +1350,9 @@ const docTemplateadmin = `{
"application/json"
],
"tags": [
"机器"
"设备"
],
"summary": "机器列表",
"summary": "设备列表",
"parameters": [
{
"type": "integer",
@@ -1365,6 +1365,12 @@ const docTemplateadmin = `{
"description": "页大小",
"name": "page_size",
"in": "query"
},
{
"type": "integer",
"description": "时间",
"name": "time_ago",
"in": "query"
}
],
"responses": {
@@ -1402,7 +1408,7 @@ const docTemplateadmin = `{
"token": []
}
],
"description": "机器编辑",
"description": "设备编辑",
"consumes": [
"application/json"
],
@@ -1410,12 +1416,12 @@ const docTemplateadmin = `{
"application/json"
],
"tags": [
"机器"
"设备"
],
"summary": "机器编辑",
"summary": "设备编辑",
"parameters": [
{
"description": "机器信息",
"description": "设备信息",
"name": "body",
"in": "body",
"required": true,
@@ -2278,7 +2284,7 @@ const docTemplateadmin = `{
"id": {
"type": "string"
},
"login_name": {
"loginName": {
"type": "string"
},
"online": {
@@ -2290,16 +2296,16 @@ const docTemplateadmin = `{
"platform": {
"type": "string"
},
"rdp_port": {
"rdpPort": {
"type": "string"
},
"rdp_username": {
"rdpUsername": {
"type": "string"
},
"row_id": {
"type": "integer"
},
"same_server": {
"sameServer": {
"type": "boolean"
},
"tags": {
@@ -2311,6 +2317,12 @@ const docTemplateadmin = `{
"user_id": {
"type": "integer"
},
"user_ids": {
"type": "array",
"items": {
"type": "integer"
}
},
"username": {
"type": "string"
}
@@ -2398,6 +2410,20 @@ const docTemplateadmin = `{
}
}
},
"admin.PeerBatchDeleteForm": {
"type": "object",
"required": [
"row_ids"
],
"properties": {
"row_ids": {
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"admin.PeerForm": {
"type": "object",
"properties": {
@@ -2455,7 +2481,6 @@ const docTemplateadmin = `{
"type": "object",
"required": [
"group_id",
"nickname",
"status",
"username"
],
@@ -2760,6 +2785,9 @@ const docTemplateadmin = `{
"id": {
"type": "string"
},
"last_online_time": {
"type": "integer"
},
"memory": {
"type": "string"
},

View File

@@ -15,7 +15,7 @@
"token": []
}
],
"description": "创建地址簿",
"description": "批量创建地址簿",
"consumes": [
"application/json"
],
@@ -25,7 +25,7 @@
"tags": [
"地址簿"
],
"summary": "创建地址簿",
"summary": "批量创建地址簿",
"parameters": [
{
"description": "地址簿信息",
@@ -1178,7 +1178,7 @@
"token": []
}
],
"description": "创建机器",
"description": "创建设备",
"consumes": [
"application/json"
],
@@ -1186,12 +1186,12 @@
"application/json"
],
"tags": [
"机器"
"设备"
],
"summary": "创建机器",
"summary": "创建设备",
"parameters": [
{
"description": "机器信息",
"description": "设备信息",
"name": "body",
"in": "body",
"required": true,
@@ -1235,7 +1235,7 @@
"token": []
}
],
"description": "机器删除",
"description": "批量设备删除",
"consumes": [
"application/json"
],
@@ -1243,17 +1243,17 @@
"application/json"
],
"tags": [
"机器"
"设备"
],
"summary": "机器删除",
"summary": "批量设备删除",
"parameters": [
{
"description": "机器信息",
"description": "设备id",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/admin.PeerForm"
"$ref": "#/definitions/admin.PeerBatchDeleteForm"
}
}
],
@@ -1280,7 +1280,7 @@
"token": []
}
],
"description": "机器详情",
"description": "设备详情",
"consumes": [
"application/json"
],
@@ -1288,9 +1288,9 @@
"application/json"
],
"tags": [
"机器"
"设备"
],
"summary": "机器详情",
"summary": "设备详情",
"parameters": [
{
"type": "integer",
@@ -1335,7 +1335,7 @@
"token": []
}
],
"description": "机器列表",
"description": "设备列表",
"consumes": [
"application/json"
],
@@ -1343,9 +1343,9 @@
"application/json"
],
"tags": [
"机器"
"设备"
],
"summary": "机器列表",
"summary": "设备列表",
"parameters": [
{
"type": "integer",
@@ -1358,6 +1358,12 @@
"description": "页大小",
"name": "page_size",
"in": "query"
},
{
"type": "integer",
"description": "时间",
"name": "time_ago",
"in": "query"
}
],
"responses": {
@@ -1395,7 +1401,7 @@
"token": []
}
],
"description": "机器编辑",
"description": "设备编辑",
"consumes": [
"application/json"
],
@@ -1403,12 +1409,12 @@
"application/json"
],
"tags": [
"机器"
"设备"
],
"summary": "机器编辑",
"summary": "设备编辑",
"parameters": [
{
"description": "机器信息",
"description": "设备信息",
"name": "body",
"in": "body",
"required": true,
@@ -2271,7 +2277,7 @@
"id": {
"type": "string"
},
"login_name": {
"loginName": {
"type": "string"
},
"online": {
@@ -2283,16 +2289,16 @@
"platform": {
"type": "string"
},
"rdp_port": {
"rdpPort": {
"type": "string"
},
"rdp_username": {
"rdpUsername": {
"type": "string"
},
"row_id": {
"type": "integer"
},
"same_server": {
"sameServer": {
"type": "boolean"
},
"tags": {
@@ -2304,6 +2310,12 @@
"user_id": {
"type": "integer"
},
"user_ids": {
"type": "array",
"items": {
"type": "integer"
}
},
"username": {
"type": "string"
}
@@ -2391,6 +2403,20 @@
}
}
},
"admin.PeerBatchDeleteForm": {
"type": "object",
"required": [
"row_ids"
],
"properties": {
"row_ids": {
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"admin.PeerForm": {
"type": "object",
"properties": {
@@ -2448,7 +2474,6 @@
"type": "object",
"required": [
"group_id",
"nickname",
"status",
"username"
],
@@ -2753,6 +2778,9 @@
"id": {
"type": "string"
},
"last_online_time": {
"type": "integer"
},
"memory": {
"type": "string"
},

View File

@@ -24,7 +24,7 @@ definitions:
type: string
id:
type: string
login_name:
loginName:
type: string
online:
type: boolean
@@ -32,13 +32,13 @@ definitions:
type: string
platform:
type: string
rdp_port:
rdpPort:
type: string
rdp_username:
rdpUsername:
type: string
row_id:
type: integer
same_server:
sameServer:
type: boolean
tags:
items:
@@ -46,6 +46,10 @@ definitions:
type: array
user_id:
type: integer
user_ids:
items:
type: integer
type: array
username:
type: string
required:
@@ -107,6 +111,15 @@ definitions:
- op
- redirect_url
type: object
admin.PeerBatchDeleteForm:
properties:
row_ids:
items:
type: integer
type: array
required:
- row_ids
type: object
admin.PeerForm:
properties:
cpu:
@@ -165,7 +178,6 @@ definitions:
type: string
required:
- group_id
- nickname
- status
- username
type: object
@@ -347,6 +359,8 @@ definitions:
type: string
id:
type: string
last_online_time:
type: integer
memory:
type: string
os:
@@ -472,7 +486,7 @@ paths:
post:
consumes:
- application/json
description: 创建地址簿
description: 批量创建地址簿
parameters:
- description: 地址簿信息
in: body
@@ -498,7 +512,7 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建地址簿
summary: 批量创建地址簿
tags:
- 地址簿
/admin/address_book/delete:
@@ -1162,9 +1176,9 @@ paths:
post:
consumes:
- application/json
description: 创建机器
description: 创建设备
parameters:
- description: 机器信息
- description: 设备信息
in: body
name: body
required: true
@@ -1188,21 +1202,21 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 创建机器
summary: 创建设备
tags:
- 机器
- 设备
/admin/peer/delete:
post:
consumes:
- application/json
description: 机器删除
description: 批量设备删除
parameters:
- description: 机器信息
- description: 设备id
in: body
name: body
required: true
schema:
$ref: '#/definitions/admin.PeerForm'
$ref: '#/definitions/admin.PeerBatchDeleteForm'
produces:
- application/json
responses:
@@ -1216,14 +1230,14 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 机器删除
summary: 批量设备删除
tags:
- 机器
- 设备
/admin/peer/detail/{id}:
get:
consumes:
- application/json
description: 机器详情
description: 设备详情
parameters:
- description: ID
in: path
@@ -1248,14 +1262,14 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 机器详情
summary: 设备详情
tags:
- 机器
- 设备
/admin/peer/list:
get:
consumes:
- application/json
description: 机器列表
description: 设备列表
parameters:
- description: 页码
in: query
@@ -1265,6 +1279,10 @@ paths:
in: query
name: page_size
type: integer
- description: 时间
in: query
name: time_ago
type: integer
produces:
- application/json
responses:
@@ -1283,16 +1301,16 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 机器列表
summary: 设备列表
tags:
- 机器
- 设备
/admin/peer/update:
post:
consumes:
- application/json
description: 机器编辑
description: 设备编辑
parameters:
- description: 机器信息
- description: 设备信息
in: body
name: body
required: true
@@ -1316,9 +1334,9 @@ paths:
$ref: '#/definitions/response.Response'
security:
- token: []
summary: 机器编辑
summary: 设备编辑
tags:
- 机器
- 设备
/admin/server-config:
get:
consumes:

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
docs/en_img/pc_ab.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
docs/en_img/pc_gr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/en_img/pc_login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
docs/en_img/web_admin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -6,9 +6,11 @@ import (
"Gwen/lib/jwt"
"Gwen/lib/lock"
"Gwen/lib/upload"
"github.com/gin-gonic/gin"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
"github.com/go-redis/redis/v8"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"gorm.io/gorm"
@@ -23,11 +25,13 @@ var (
Cache cache.Handler
Validator struct {
Validate *validator.Validate
UT *ut.UniversalTranslator
VTrans ut.Translator
ValidStruct func(interface{}) []string
ValidVar func(field interface{}, tag string) []string
ValidStruct func(*gin.Context, interface{}) []string
ValidVar func(ctx *gin.Context, field interface{}, tag string) []string
}
Oss *upload.Oss
Jwt *jwt.Jwt
Lock lock.Locker
Oss *upload.Oss
Jwt *jwt.Jwt
Lock lock.Locker
Localizer func(ctx *gin.Context) *i18n.Localizer
)

8
go.mod
View File

@@ -3,6 +3,7 @@ module Gwen
go 1.22
require (
github.com/BurntSushi/toml v1.3.2
github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/fsnotify/fsnotify v1.5.1
github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
@@ -12,12 +13,14 @@ require (
github.com/go-playground/validator/v10 v10.11.2
github.com/go-redis/redis/v8 v8.11.4
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/sirupsen/logrus v1.8.1
github.com/spf13/viper v1.9.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3
golang.org/x/oauth2 v0.23.0
golang.org/x/text v0.18.0
gorm.io/driver/mysql v1.5.7
gorm.io/driver/sqlite v1.5.6
gorm.io/gorm v1.25.7
@@ -64,10 +67,9 @@ require (
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

View File

@@ -4,6 +4,7 @@ import (
"Gwen/global"
"Gwen/http/request/admin"
"Gwen/http/response"
"Gwen/model"
"Gwen/service"
_ "encoding/json"
"github.com/gin-gonic/gin"
@@ -31,14 +32,14 @@ func (ct *AddressBook) Detail(c *gin.Context) {
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.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if t.RowId > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
@@ -56,10 +57,10 @@ func (ct *AddressBook) Detail(c *gin.Context) {
func (ct *AddressBook) Create(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -69,14 +70,72 @@ func (ct *AddressBook) Create(c *gin.Context) {
if !service.AllService.UserService.IsAdmin(u) || t.UserId == 0 {
t.UserId = u.Id
}
ex := service.AllService.AddressBookService.InfoByUserIdAndId(t.UserId, t.Id)
if ex.RowId > 0 {
response.Fail(c, 101, response.TranslateMsg(c, "ItemExist"))
return
}
err := service.AllService.AddressBookService.Create(t)
if err != nil {
response.Fail(c, 101, "创建失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
}
// BatchCreate 批量创建地址簿
// @Tags 地址簿
// @Summary 批量创建地址簿
// @Description 批量创建地址簿
// @Accept json
// @Produce json
// @Param body body admin.AddressBookForm true "地址簿信息"
// @Success 200 {object} response.Response{data=model.AddressBook}
// @Failure 500 {object} response.Response
// @Router /admin/address_book/create [post]
// @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
}
//创建标签
for _, fu := range f.UserIds {
if fu == 0 {
continue
}
for _, ft := range f.Tags {
exTag := service.AllService.TagService.InfoByUserIdAndName(fu, ft)
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.InfoByUserIdAndId(t.UserId, t.Id)
if ex.RowId == 0 {
service.AllService.AddressBookService.Create(t)
}
}
response.Success(c, nil)
}
// List 列表
// @Tags 地址簿
// @Summary 地址簿列表
@@ -94,7 +153,7 @@ func (ct *AddressBook) Create(c *gin.Context) {
func (ct *AddressBook) List(c *gin.Context) {
query := &admin.AddressBookQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
@@ -102,9 +161,18 @@ func (ct *AddressBook) List(c *gin.Context) {
query.UserId = int(u.Id)
}
res := service.AllService.AddressBookService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.Id != "" {
tx.Where("id like ?", "%"+query.Id+"%")
}
if query.UserId > 0 {
tx.Where("user_id = ?", query.UserId)
}
if query.Username != "" {
tx.Where("username like ?", "%"+query.Username+"%")
}
if query.Hostname != "" {
tx.Where("hostname like ?", "%"+query.Hostname+"%")
}
})
response.Success(c, res)
}
@@ -123,27 +191,27 @@ func (ct *AddressBook) List(c *gin.Context) {
func (ct *AddressBook) Update(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
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.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f.ToAddressBook()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.AddressBookService.Update(t)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -163,11 +231,11 @@ func (ct *AddressBook) Update(c *gin.Context) {
func (ct *AddressBook) Delete(c *gin.Context) {
f := &admin.AddressBookForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.RowId
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -175,7 +243,7 @@ func (ct *AddressBook) Delete(c *gin.Context) {
t := service.AllService.AddressBookService.InfoByRowId(f.RowId)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if u.Id > 0 {
@@ -184,8 +252,8 @@ func (ct *AddressBook) Delete(c *gin.Context) {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}

View File

@@ -31,7 +31,7 @@ func (ct *Group) Detail(c *gin.Context) {
response.Success(c, u)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
@@ -49,10 +49,10 @@ func (ct *Group) Detail(c *gin.Context) {
func (ct *Group) Create(c *gin.Context) {
f := &admin.GroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -60,7 +60,7 @@ func (ct *Group) Create(c *gin.Context) {
u := f.ToGroup()
err := service.AllService.GroupService.Create(u)
if err != nil {
response.Fail(c, 101, "创建失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
@@ -81,7 +81,7 @@ func (ct *Group) Create(c *gin.Context) {
func (ct *Group) List(c *gin.Context) {
query := &admin.PageQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.GroupService.List(query.Page, query.PageSize, nil)
@@ -102,14 +102,14 @@ func (ct *Group) List(c *gin.Context) {
func (ct *Group) Update(c *gin.Context) {
f := &admin.GroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -117,7 +117,7 @@ func (ct *Group) Update(c *gin.Context) {
u := f.ToGroup()
err := service.AllService.GroupService.Update(u)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -137,11 +137,11 @@ func (ct *Group) Update(c *gin.Context) {
func (ct *Group) Delete(c *gin.Context) {
f := &admin.GroupForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -153,8 +153,8 @@ func (ct *Group) Delete(c *gin.Context) {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}

View File

@@ -28,11 +28,11 @@ func (ct *Login) Login(c *gin.Context) {
f := &admin.Login{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -40,7 +40,7 @@ func (ct *Login) Login(c *gin.Context) {
u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password)
if u.Id == 0 {
response.Fail(c, 101, "用户名或密码错误")
response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError"))
return
}

View File

@@ -33,7 +33,7 @@ func (ct *LoginLog) Detail(c *gin.Context) {
response.Success(c, u)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
@@ -53,7 +53,7 @@ func (ct *LoginLog) Detail(c *gin.Context) {
func (ct *LoginLog) List(c *gin.Context) {
query := &admin.LoginLogQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
@@ -82,11 +82,11 @@ func (ct *LoginLog) List(c *gin.Context) {
func (ct *LoginLog) Delete(c *gin.Context) {
f := &model.LoginLog{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -94,7 +94,7 @@ func (ct *LoginLog) Delete(c *gin.Context) {
l := service.AllService.LoginLogService.InfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && l.UserId != u.Id {
response.Fail(c, 101, "无权限")
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if l.Id > 0 {
@@ -106,5 +106,5 @@ func (ct *LoginLog) Delete(c *gin.Context) {
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}

View File

@@ -18,12 +18,12 @@ type Oauth struct {
func (o *Oauth) Info(c *gin.Context) {
code := c.Query("code")
if code == "" {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
v := service.AllService.OauthService.GetOauthCache(code)
if v == nil {
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
response.Success(c, v)
@@ -33,20 +33,20 @@ func (o *Oauth) ToBind(c *gin.Context) {
f := &adminReq.BindOauthForm{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
utr := service.AllService.UserService.UserThirdInfo(u.Id, f.Op)
if utr.Id > 0 {
response.Fail(c, 101, "已绑定过了")
response.Fail(c, 101, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil {
response.Error(c, err.Error())
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
@@ -89,22 +89,22 @@ func (o *Oauth) BindConfirm(c *gin.Context) {
j := &adminReq.OauthConfirmForm{}
err := c.ShouldBindJSON(j)
if err != nil {
response.Fail(c, 101, "参数错误"+err.Error())
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if j.Code == "" {
response.Fail(c, 101, "参数错误: code 不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
v := service.AllService.OauthService.GetOauthCache(j.Code)
if v == nil {
response.Fail(c, 101, "授权已过期")
response.Fail(c, 101, response.TranslateMsg(c, "OauthExpired"))
return
}
u := service.AllService.UserService.CurUser(c)
err = service.AllService.OauthService.BindGithubUser(v.ThirdOpenId, v.ThirdOpenId, u.Id)
if err != nil {
response.Fail(c, 101, "绑定失败,请重试")
response.Fail(c, 101, response.TranslateMsg(c, "BindFail"))
return
}
@@ -117,22 +117,30 @@ func (o *Oauth) Unbind(c *gin.Context) {
f := &adminReq.UnBindOauthForm{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
utr := service.AllService.UserService.UserThirdInfo(u.Id, f.Op)
if utr.Id == 0 {
response.Fail(c, 101, "未绑定")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
if f.Op == model.OauthTypeGithub {
err = service.AllService.OauthService.UnBindGithubUser(u.Id)
if err != nil {
response.Fail(c, 101, "解绑失败")
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)
}
@@ -155,7 +163,7 @@ func (o *Oauth) Detail(c *gin.Context) {
response.Success(c, u)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
@@ -173,10 +181,10 @@ func (o *Oauth) Detail(c *gin.Context) {
func (o *Oauth) Create(c *gin.Context) {
f := &admin.OauthForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误"+err.Error())
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -184,14 +192,14 @@ func (o *Oauth) Create(c *gin.Context) {
ex := service.AllService.OauthService.InfoByOp(f.Op)
if ex.Id > 0 {
response.Fail(c, 101, "已存在"+f.Op)
response.Fail(c, 101, response.TranslateMsg(c, "ItemExists"))
return
}
u := f.ToOauth()
err := service.AllService.OauthService.Create(u)
if err != nil {
response.Fail(c, 101, "创建失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
@@ -212,7 +220,7 @@ func (o *Oauth) Create(c *gin.Context) {
func (o *Oauth) List(c *gin.Context) {
query := &admin.PageQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.OauthService.List(query.Page, query.PageSize, nil)
@@ -233,14 +241,14 @@ func (o *Oauth) List(c *gin.Context) {
func (o *Oauth) Update(c *gin.Context) {
f := &admin.OauthForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -248,7 +256,7 @@ func (o *Oauth) Update(c *gin.Context) {
u := f.ToOauth()
err := service.AllService.OauthService.Update(u)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -268,11 +276,11 @@ func (o *Oauth) Update(c *gin.Context) {
func (o *Oauth) Delete(c *gin.Context) {
f := &admin.OauthForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -287,5 +295,5 @@ func (o *Oauth) Delete(c *gin.Context) {
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}

View File

@@ -6,16 +6,18 @@ import (
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strconv"
"time"
)
type Peer struct {
}
// Detail 机器
// @Tags 机器
// @Summary 机器详情
// @Description 机器详情
// Detail 设备
// @Tags 设备
// @Summary 设备详情
// @Description 设备详情
// @Accept json
// @Produce json
// @Param id path int true "ID"
@@ -31,17 +33,17 @@ func (ct *Peer) Detail(c *gin.Context) {
response.Success(c, u)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
// Create 创建机器
// @Tags 机器
// @Summary 创建机器
// @Description 创建机器
// Create 创建设备
// @Tags 设备
// @Summary 创建设备
// @Description 创建设备
// @Accept json
// @Produce json
// @Param body body admin.PeerForm true "机器信息"
// @Param body body admin.PeerForm true "设备信息"
// @Success 200 {object} response.Response{data=model.Peer}
// @Failure 500 {object} response.Response
// @Router /admin/peer/create [post]
@@ -49,10 +51,10 @@ func (ct *Peer) Detail(c *gin.Context) {
func (ct *Peer) Create(c *gin.Context) {
f := &admin.PeerForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -60,41 +62,51 @@ func (ct *Peer) Create(c *gin.Context) {
u := f.ToPeer()
err := service.AllService.PeerService.Create(u)
if err != nil {
response.Fail(c, 101, "创建失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
}
// List 列表
// @Tags 机器
// @Summary 机器列表
// @Description 机器列表
// @Tags 设备
// @Summary 设备列表
// @Description 设备列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param page_size query int false "页大小"
// @Param time_ago query int false "时间"
// @Success 200 {object} response.Response{data=model.PeerList}
// @Failure 500 {object} response.Response
// @Router /admin/peer/list [get]
// @Security token
func (ct *Peer) List(c *gin.Context) {
query := &admin.PageQuery{}
query := &admin.PeerQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.PeerService.List(query.Page, query.PageSize, nil)
res := service.AllService.PeerService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
if query.TimeAgo > 0 {
lt := time.Now().Unix() - int64(query.TimeAgo)
tx.Where("last_online_time < ?", lt)
}
if query.TimeAgo < 0 {
lt := time.Now().Unix() + int64(query.TimeAgo)
tx.Where("last_online_time > ?", lt)
}
})
response.Success(c, res)
}
// Update 编辑
// @Tags 机器
// @Summary 机器编辑
// @Description 机器编辑
// @Tags 设备
// @Summary 设备编辑
// @Description 设备编辑
// @Accept json
// @Produce json
// @Param body body admin.PeerForm true "机器信息"
// @Param body body admin.PeerForm true "设备信息"
// @Success 200 {object} response.Response{data=model.Peer}
// @Failure 500 {object} response.Response
// @Router /admin/peer/update [post]
@@ -102,14 +114,14 @@ func (ct *Peer) List(c *gin.Context) {
func (ct *Peer) Update(c *gin.Context) {
f := &admin.PeerForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.RowId == 0 {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -117,19 +129,19 @@ func (ct *Peer) Update(c *gin.Context) {
u := f.ToPeer()
err := service.AllService.PeerService.Update(u)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
}
// Delete 删除
// @Tags 机器
// @Summary 机器删除
// @Description 机器删除
// @Tags 设备
// @Summary 设备删除
// @Description 设备删除
// @Accept json
// @Produce json
// @Param body body admin.PeerForm true "机器信息"
// @Param body body admin.PeerForm true "设备信息"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/peer/delete [post]
@@ -137,11 +149,11 @@ func (ct *Peer) Update(c *gin.Context) {
func (ct *Peer) Delete(c *gin.Context) {
f := &admin.PeerForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.RowId
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -153,8 +165,37 @@ func (ct *Peer) Delete(c *gin.Context) {
response.Success(c, nil)
return
}
response.Fail(c, 101, err.Error())
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// BatchDelete 批量删除
// @Tags 设备
// @Summary 批量设备删除
// @Description 批量设备删除
// @Accept json
// @Produce json
// @Param body body admin.PeerBatchDeleteForm true "设备id"
// @Success 200 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /admin/peer/delete [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)
}

View File

@@ -30,14 +30,14 @@ func (ct *Tag) Detail(c *gin.Context) {
t := service.AllService.TagService.InfoById(uint(iid))
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if t.Id > 0 {
response.Success(c, t)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
@@ -55,10 +55,10 @@ func (ct *Tag) Detail(c *gin.Context) {
func (ct *Tag) Create(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -70,7 +70,7 @@ func (ct *Tag) Create(c *gin.Context) {
}
err := service.AllService.TagService.Create(t)
if err != nil {
response.Fail(c, 101, "创建失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
@@ -93,7 +93,7 @@ func (ct *Tag) Create(c *gin.Context) {
func (ct *Tag) List(c *gin.Context) {
query := &admin.TagQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
@@ -122,27 +122,27 @@ func (ct *Tag) List(c *gin.Context) {
func (ct *Tag) Update(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
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.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
t := f.ToTag()
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
err := service.AllService.TagService.Update(t)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -162,11 +162,11 @@ func (ct *Tag) Update(c *gin.Context) {
func (ct *Tag) Delete(c *gin.Context) {
f := &admin.TagForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -174,7 +174,7 @@ func (ct *Tag) Delete(c *gin.Context) {
t := service.AllService.TagService.InfoById(f.Id)
u := service.AllService.UserService.CurUser(c)
if !service.AllService.UserService.IsAdmin(u) && t.UserId != u.Id {
response.Fail(c, 101, "无权限")
response.Fail(c, 101, response.TranslateMsg(c, "NoAccess"))
return
}
if u.Id > 0 {
@@ -186,5 +186,5 @@ func (ct *Tag) Delete(c *gin.Context) {
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}

View File

@@ -33,7 +33,7 @@ func (ct *User) Detail(c *gin.Context) {
response.Success(c, u)
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
@@ -51,10 +51,10 @@ func (ct *User) Detail(c *gin.Context) {
func (ct *User) Create(c *gin.Context) {
f := &admin.UserForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -62,7 +62,7 @@ func (ct *User) Create(c *gin.Context) {
u := f.ToUser()
err := service.AllService.UserService.Create(u)
if err != nil {
response.Fail(c, 101, "创建失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, u)
@@ -84,7 +84,7 @@ func (ct *User) Create(c *gin.Context) {
func (ct *User) List(c *gin.Context) {
query := &admin.UserQuery{}
if err := c.ShouldBindQuery(query); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
res := service.AllService.UserService.List(query.Page, query.PageSize, func(tx *gorm.DB) {
@@ -109,14 +109,14 @@ func (ct *User) List(c *gin.Context) {
func (ct *User) Update(c *gin.Context) {
f := &admin.UserForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误:"+err.Error())
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Id == 0 {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError"))
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -124,7 +124,7 @@ func (ct *User) Update(c *gin.Context) {
u := f.ToUser()
err := service.AllService.UserService.Update(u)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -144,11 +144,11 @@ func (ct *User) Update(c *gin.Context) {
func (ct *User) Delete(c *gin.Context) {
f := &admin.UserForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "系统错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
id := f.Id
errList := global.Validator.ValidVar(id, "required,gt=0")
errList := global.Validator.ValidVar(c, id, "required,gt=0")
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -163,7 +163,7 @@ func (ct *User) Delete(c *gin.Context) {
response.Fail(c, 101, err.Error())
return
}
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
}
// UpdatePassword 修改密码
@@ -180,22 +180,22 @@ func (ct *User) Delete(c *gin.Context) {
func (ct *User) UpdatePassword(c *gin.Context) {
f := &admin.UserPasswordForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
}
u := service.AllService.UserService.InfoById(f.Id)
if u.Id == 0 {
response.Fail(c, 101, "信息不存在")
response.Fail(c, 101, response.TranslateMsg(c, "ItemNotFound"))
return
}
err := service.AllService.UserService.UpdatePassword(u, f.Password)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)
@@ -237,11 +237,11 @@ func (ct *User) Current(c *gin.Context) {
func (ct *User) ChangeCurPwd(c *gin.Context) {
f := &admin.ChangeCurPasswordForm{}
if err := c.ShouldBindJSON(f); err != nil {
response.Fail(c, 101, "参数错误")
response.Fail(c, 101, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Fail(c, 101, errList[0])
return
@@ -249,12 +249,12 @@ func (ct *User) ChangeCurPwd(c *gin.Context) {
u := service.AllService.UserService.CurUser(c)
oldPwd := service.AllService.UserService.EncryptPassword(f.OldPassword)
if u.Password != oldPwd {
response.Fail(c, 101, "旧密码错误")
response.Fail(c, 101, response.TranslateMsg(c, "OldPasswordError"))
return
}
err := service.AllService.UserService.UpdatePassword(u, f.NewPassword)
if err != nil {
response.Fail(c, 101, "更新失败")
response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
response.Success(c, nil)

View File

@@ -68,37 +68,30 @@ func (a *Ab) UpAb(c *gin.Context) {
abf := &requstform.AddressBookForm{}
err := c.ShouldBindJSON(&abf)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
abd := &requstform.AddressBookFormData{}
err = json.Unmarshal([]byte(abf.Data), abd)
if err != nil {
response.Error(c, "系统错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
tc := map[string]uint{}
err = json.Unmarshal([]byte(abd.TagColors), &tc)
if err != nil {
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
//fmt.Println(abd)
//for _, peer := range abd.Peers {
// fmt.Println(peer)
//}
user := service.AllService.UserService.CurUser(c)
err = service.AllService.AddressBookService.UpdateAddressBook(abd.Peers, user.Id)
if err != nil {
c.Abort()
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
tc := map[string]uint{}
err = json.Unmarshal([]byte(abd.TagColors), &tc)
if err != nil {
response.Error(c, "系统错误")
return
} else {
service.AllService.TagService.UpdateTags(user.Id, tc)
}
service.AllService.TagService.UpdateTags(user.Id, tc)
c.JSON(http.StatusOK, nil)
}
@@ -134,19 +127,19 @@ func (a *Ab) TagAdd(c *gin.Context) {
t := &model.Tag{}
err := c.ShouldBindJSON(t)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.Name)
if tag != nil && tag.Id != 0 {
response.Error(c, "已存在")
response.Error(c, response.TranslateMsg(c, "ItemExists"))
return
}
t.UserId = u.Id
err = service.AllService.TagService.Create(t)
if err != nil {
response.Error(c, "操作失败")
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
c.String(http.StatusOK, "")
@@ -166,24 +159,24 @@ func (a *Ab) TagRename(c *gin.Context) {
t := &requstform.TagRenameForm{}
err := c.ShouldBindJSON(t)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.Old)
if tag == nil || tag.Id == 0 {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
ntag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.New)
if ntag != nil && ntag.Id != 0 {
response.Error(c, "已存在")
response.Error(c, response.TranslateMsg(c, "ItemExists"))
return
}
tag.Name = t.New
err = service.AllService.TagService.Update(tag)
if err != nil {
response.Error(c, "操作失败")
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
c.String(http.StatusOK, "")
@@ -203,19 +196,19 @@ func (a *Ab) TagUpdate(c *gin.Context) {
t := &requstform.TagColorForm{}
err := c.ShouldBindJSON(t)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, t.Name)
if tag == nil || tag.Id == 0 {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
tag.Color = t.Color
err = service.AllService.TagService.Update(tag)
if err != nil {
response.Error(c, "操作失败")
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
c.String(http.StatusOK, "")
@@ -235,7 +228,7 @@ func (a *Ab) TagDel(c *gin.Context) {
t := &[]string{}
err := c.ShouldBind(t)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
//fmt.Println(t)
@@ -243,12 +236,12 @@ func (a *Ab) TagDel(c *gin.Context) {
for _, name := range *t {
tag := service.AllService.TagService.InfoByUserIdAndName(u.Id, name)
if tag == nil || tag.Id == 0 {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
err = service.AllService.TagService.Delete(tag)
if err != nil {
response.Error(c, "操作失败")
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
}
@@ -406,7 +399,7 @@ func (a *Ab) PeerAdd(c *gin.Context) {
f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Error(c, "参数错误"+err.Error())
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
fmt.Println(f)
@@ -415,7 +408,7 @@ func (a *Ab) PeerAdd(c *gin.Context) {
ab := f.ToAddressBook()
err = service.AllService.AddressBookService.AddAddressBook(ab)
if err != nil {
response.Error(c, "操作失败")
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
c.String(http.StatusOK, "")
@@ -436,19 +429,19 @@ func (a *Ab) PeerDel(c *gin.Context) {
f := &[]string{}
err := c.ShouldBind(f)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
u := service.AllService.UserService.CurUser(c)
for _, id := range *f {
ab := service.AllService.AddressBookService.InfoByUserIdAndId(u.Id, id)
if ab == nil || ab.RowId == 0 {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
err = service.AllService.AddressBookService.Delete(ab)
if err != nil {
response.Error(c, "操作失败")
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
}
@@ -472,22 +465,22 @@ func (a *Ab) PeerUpdate(c *gin.Context) {
f := &requstform.PersonalAddressBookForm{}
err := c.ShouldBindJSON(f)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
fmt.Println(f)
//fmt.Println(f)
//return
u := service.AllService.UserService.CurUser(c)
ab := service.AllService.AddressBookService.InfoByUserIdAndId(u.Id, f.Id)
if ab == nil || ab.RowId == 0 {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ItemNotFound"))
return
}
nab := f.ToAddressBook()
nab.RowId = ab.RowId
err = service.AllService.AddressBookService.Update(nab)
if err != nil {
response.Error(c, "操作失败")
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
c.String(http.StatusOK, "")

View File

@@ -33,7 +33,7 @@ func (g *Group) Users(c *gin.Context) {
if !*u.IsAdmin {
gr := service.AllService.GroupService.InfoById(u.GroupId)
if gr.Type != model.GroupTypeShare {
response.Error(c, "不是管理员也不在分享组")
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
}
@@ -77,7 +77,7 @@ func (g *Group) Peers(c *gin.Context) {
if !*u.IsAdmin {
gr := service.AllService.GroupService.InfoById(u.GroupId)
if gr.Type != model.GroupTypeShare {
response.Error(c, "不是管理员也不在分享组")
response.Error(c, response.TranslateMsg(c, "NoAccess"))
return
}
}
@@ -96,15 +96,15 @@ func (g *Group) Peers(c *gin.Context) {
namesById[user.Id] = user.Username
userIds = append(userIds, user.Id)
}
peerList := service.AllService.AddressBookService.ListByUserIds(userIds, q.Page, q.PageSize)
peerList := service.AllService.PeerService.ListByUserIds(userIds, q.Page, q.PageSize)
var data []*apiResp.GroupPeerPayload
for _, ab := range peerList.AddressBooks {
uname, ok := namesById[ab.UserId]
for _, peer := range peerList.Peers {
uname, ok := namesById[peer.UserId]
if !ok {
uname = ""
}
pp := &apiResp.GroupPeerPayload{}
pp.FromAddressBook(ab, uname)
pp.FromPeer(peer, uname)
data = append(data, pp)
}

View File

@@ -1,9 +1,12 @@
package api
import (
requstform "Gwen/http/request/api"
"Gwen/http/response"
"Gwen/service"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
type Index struct {
@@ -35,5 +38,22 @@ func (i *Index) Index(c *gin.Context) {
// @Failure 500 {object} response.Response
// @Router /heartbeat [post]
func (i *Index) Heartbeat(c *gin.Context) {
info := &requstform.PeerInfoInHeartbeat{}
err := c.ShouldBindJSON(info)
if err != nil {
c.JSON(http.StatusOK, gin.H{})
return
}
if info.Uuid == "" {
c.JSON(http.StatusOK, gin.H{})
return
}
peer := service.AllService.PeerService.FindByUuid(info.Uuid)
if peer == nil {
c.JSON(http.StatusOK, gin.H{})
return
}
peer.LastOnlineTime = time.Now().Unix()
service.AllService.PeerService.Update(peer)
c.JSON(http.StatusOK, gin.H{})
}

View File

@@ -30,11 +30,11 @@ func (l *Login) Login(c *gin.Context) {
err := c.ShouldBindJSON(f)
//fmt.Println(f)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
errList := global.Validator.ValidStruct(f)
errList := global.Validator.ValidStruct(c, f)
if len(errList) > 0 {
response.Error(c, errList[0])
return
@@ -43,7 +43,7 @@ func (l *Login) Login(c *gin.Context) {
u := service.AllService.UserService.InfoByUsernamePassword(f.Username, f.Password)
if u.Id == 0 {
response.Error(c, "用户名或密码错误")
response.Error(c, response.TranslateMsg(c, "UsernameOrPasswordError"))
return
}
@@ -95,7 +95,7 @@ func (l *Login) LoginOptions(c *gin.Context) {
}
common, err := json.Marshal(oidcItems)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "SystemError")+err.Error())
return
}
var res []string

View File

@@ -29,17 +29,17 @@ func (o *Oauth) OidcAuth(c *gin.Context) {
f := &api.OidcAuthRequest{}
err := c.ShouldBindJSON(&f)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError"))
return
}
err, code, url := service.AllService.OauthService.BeginAuth(f.Op)
if err != nil {
response.Error(c, err.Error())
response.Error(c, response.TranslateMsg(c, err.Error()))
return
}
@@ -72,12 +72,12 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
q := &api.OidcAuthQuery{}
err := c.ShouldBindQuery(q)
if err != nil {
response.Error(c, "参数错误")
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
v := service.AllService.OauthService.GetOauthCache(q.Code)
if v == nil {
response.Error(c, "授权已过期,请重新授权")
response.Error(c, response.TranslateMsg(c, "OauthExpired"))
return
}
if v.UserId == 0 {
@@ -87,24 +87,20 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
}
u := service.AllService.UserService.InfoById(v.UserId)
//fmt.Println("auth success u", u)
if u.Id > 0 {
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{
AccessToken: ut.Token,
Type: "access_token",
User: *(&apiResp.UserPayload{}).FromUser(u),
})
return
}
response.Error(c, "用户不存在")
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{
AccessToken: ut.Token,
Type: "access_token",
User: *(&apiResp.UserPayload{}).FromUser(u),
})
}
// OauthCallback 回调
@@ -119,7 +115,7 @@ func (o *Oauth) OidcAuthQuery(c *gin.Context) {
func (o *Oauth) OauthCallback(c *gin.Context) {
state := c.Query("state")
if state == "" {
c.String(http.StatusInternalServerError, "state为空")
c.String(http.StatusInternalServerError, response.TranslateParamMsg(c, "ParamIsEmpty", "state"))
return
}
@@ -127,7 +123,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
//从缓存中获取
v := service.AllService.OauthService.GetOauthCache(cacheKey)
if v == nil {
c.String(http.StatusInternalServerError, "授权已过期,请重新授权")
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthExpired"))
return
}
@@ -138,34 +134,34 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
code := c.Query("code")
err, userData := service.AllService.OauthService.GithubCallback(code)
if err != nil {
c.String(http.StatusInternalServerError, "授权失败:"+err.Error())
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
return
}
if ac == service.OauthActionTypeBind {
//fmt.Println("bind", ty, userData)
utr := service.AllService.OauthService.UserThirdInfo(ty, strconv.Itoa(userData.Id))
if utr.UserId > 0 {
c.String(http.StatusInternalServerError, "已经绑定其他账号")
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
//绑定
u := service.AllService.UserService.InfoById(v.UserId)
if u == nil {
c.String(http.StatusInternalServerError, "用户不存在")
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 {
c.String(http.StatusInternalServerError, "绑定失败")
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
return
}
c.String(http.StatusOK, "绑定成功")
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
return
} else if ac == service.OauthActionTypeLogin {
//登录
if v.UserId != 0 {
c.String(http.StatusInternalServerError, "授权已经成功")
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
return
}
u := service.AllService.UserService.InfoByGithubId(strconv.Itoa(userData.Id))
@@ -183,19 +179,16 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
//自动注册
u = service.AllService.UserService.RegisterByGithub(userData.Login, strconv.Itoa(userData.Id))
if u.Id == 0 {
c.String(http.StatusInternalServerError, "注册失败")
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed"))
return
}
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
c.String(http.StatusOK, "授权成功")
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
return
}
//返回js
c.Header("Content-Type", "text/html; charset=utf-8")
c.String(http.StatusOK, "授权错误")
}
@@ -203,7 +196,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
code := c.Query("code")
err, userData := service.AllService.OauthService.GoogleCallback(code)
if err != nil {
c.String(http.StatusInternalServerError, "授权失败:"+err.Error())
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error()))
return
}
//将空格替换成_
@@ -212,26 +205,26 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
//fmt.Println("bind", ty, userData)
utr := service.AllService.OauthService.UserThirdInfo(ty, userData.Email)
if utr.UserId > 0 {
c.String(http.StatusInternalServerError, "已经绑定其他账号")
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser"))
return
}
//绑定
u := service.AllService.UserService.InfoById(v.UserId)
if u == nil {
c.String(http.StatusInternalServerError, "用户不存在")
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, "绑定失败")
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail"))
return
}
c.String(http.StatusOK, "绑定成功")
c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess"))
return
} else if ac == service.OauthActionTypeLogin {
if v.UserId != 0 {
c.String(http.StatusInternalServerError, "授权已经成功")
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess"))
return
}
u := service.AllService.UserService.InfoByGoogleEmail(userData.Email)
@@ -250,17 +243,17 @@ func (o *Oauth) OauthCallback(c *gin.Context) {
//自动注册
u = service.AllService.UserService.RegisterByGoogle(googleName, userData.Email)
if u.Id == 0 {
c.String(http.StatusInternalServerError, "注册失败")
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed"))
return
}
}
v.UserId = u.Id
service.AllService.OauthService.SetOauthCache(cacheKey, v, 0)
c.String(http.StatusOK, "授权成功")
c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess"))
return
}
}
c.String(http.StatusInternalServerError, "授权配置错误,请联系管理员")
c.String(http.StatusInternalServerError, response.TranslateMsg(c, "SystemError"))
}

View File

@@ -27,7 +27,7 @@ func (p *Peer) SysInfo(c *gin.Context) {
f := &requstform.PeerForm{}
err := c.ShouldBindBodyWith(f, binding.JSON)
if err != nil {
response.Error(c, err.Error())
response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error())
return
}
@@ -36,7 +36,7 @@ func (p *Peer) SysInfo(c *gin.Context) {
pe = f.ToPeer()
err = service.AllService.PeerService.Create(pe)
if err != nil {
response.Error(c, err.Error())
response.Error(c, response.TranslateMsg(c, "OperationFailed")+err.Error())
return
}
}

View File

@@ -7,7 +7,7 @@ import (
func RustAuth() gin.HandlerFunc {
return func(c *gin.Context) {
//fmt.Println(c.Request.Header)
//获取HTTP_AUTHORIZATION
token := c.GetHeader("Authorization")
if token == "" {

View File

@@ -16,12 +16,13 @@ type AddressBookForm struct {
Tags []string `json:"tags"`
Hash string `json:"hash"`
UserId uint `json:"user_id"`
UserIds []uint `json:"user_ids"`
ForceAlwaysRelay bool `json:"forceAlwaysRelay"`
RdpPort string `json:"rdp_port"`
RdpUsername string `json:"rdp_username"`
RdpPort string `json:"rdpPort"`
RdpUsername string `json:"rdpUsername"`
Online bool `json:"online"`
LoginName string `json:"login_name" `
SameServer bool `json:"same_server"`
LoginName string `json:"loginName" `
SameServer bool `json:"sameServer"`
}
func (a AddressBookForm) ToAddressBook() *model.AddressBook {
@@ -48,9 +49,39 @@ func (a AddressBookForm) ToAddressBook() *model.AddressBook {
}
}
func (a AddressBookForm) ToAddressBooks() []*model.AddressBook {
//tags转换
tags, _ := json.Marshal(a.Tags)
abs := make([]*model.AddressBook, 0, len(a.UserIds))
for _, userId := range a.UserIds {
abs = append(abs, &model.AddressBook{
RowId: a.RowId,
Id: a.Id,
Username: a.Username,
Password: a.Password,
Hostname: a.Hostname,
Alias: a.Alias,
Platform: a.Platform,
Tags: tags,
Hash: a.Hash,
UserId: userId,
ForceAlwaysRelay: a.ForceAlwaysRelay,
RdpPort: a.RdpPort,
RdpUsername: a.RdpUsername,
Online: a.Online,
LoginName: a.LoginName,
SameServer: a.SameServer,
})
}
return abs
}
type AddressBookQuery struct {
UserId int `form:"user_id"`
IsMy int `form:"is_my"`
UserId int `form:"user_id"`
IsMy int `form:"is_my"`
Username string `form:"username"`
Hostname string `form:"hostname"`
Id string `form:"id"`
PageQuery
}

View File

@@ -14,6 +14,10 @@ type PeerForm struct {
Version string `json:"version"`
}
type PeerBatchDeleteForm struct {
RowIds []uint `json:"row_ids" validate:"required"`
}
// ToPeer
func (f *PeerForm) ToPeer() *model.Peer {
return &model.Peer{
@@ -28,3 +32,8 @@ func (f *PeerForm) ToPeer() *model.Peer {
Version: f.Version,
}
}
type PeerQuery struct {
PageQuery
TimeAgo int `json:"time_ago" form:"time_ago"`
}

View File

@@ -8,7 +8,7 @@ type UserForm struct {
Id uint `json:"id"`
Username string `json:"username" validate:"required,gte=4,lte=10"`
//Password string `json:"password" validate:"required,gte=4,lte=20"`
Nickname string `json:"nickname" validate:"required"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
GroupId uint `json:"group_id" validate:"required"`
IsAdmin *bool `json:"is_admin" `

View File

@@ -71,3 +71,9 @@ type TagColorForm struct {
Name string `json:"name"`
Color uint `json:"color"`
}
type PeerInfoInHeartbeat struct {
Id string `json:"id"`
Uuid string `json:"uuid"`
Ver int `json:"ver"`
}

View File

@@ -59,7 +59,7 @@ func (gpp *GroupPeerPayload) FromAddressBook(a *model.AddressBook, username stri
gpp.UserName = username
}
func (gpp *GroupPeerPayload) FromPeer(p *model.Peer) {
func (gpp *GroupPeerPayload) FromPeer(p *model.Peer, username string) {
gpp.Id = p.Id
gpp.Info = &PeerPayloadInfo{
DeviceName: p.Hostname,
@@ -67,5 +67,5 @@ func (gpp *GroupPeerPayload) FromPeer(p *model.Peer) {
Username: p.Username,
}
gpp.Note = ""
gpp.UserName = p.User.Username
gpp.UserName = username
}

View File

@@ -1,7 +1,10 @@
package response
import (
"Gwen/global"
"fmt"
"github.com/gin-gonic/gin"
"github.com/nicksnyder/go-i18n/v2/i18n"
"net/http"
)
@@ -51,3 +54,48 @@ type ServerConfigResponse struct {
RelayServer string `json:"relay_server"`
ApiServer string `json:"api_server"`
}
func TranslateMsg(c *gin.Context, messageId string) string {
localizer := global.Localizer(c)
errMsg, err := localizer.LocalizeMessage(&i18n.Message{
ID: messageId,
})
if err != nil {
global.Logger.Warn("LocalizeMessage Error: " + err.Error())
errMsg = messageId
}
return errMsg
}
func TranslateTempMsg(c *gin.Context, messageId string, templateData map[string]interface{}) string {
localizer := global.Localizer(c)
errMsg, err := localizer.Localize(&i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{
ID: messageId,
},
TemplateData: templateData,
})
if err != nil {
global.Logger.Warn("LocalizeMessage Error: " + err.Error())
errMsg = messageId
}
return errMsg
}
func TranslateParamMsg(c *gin.Context, messageId string, params ...string) string {
localizer := global.Localizer(c)
templateData := make(map[string]interface{})
for i, v := range params {
k := fmt.Sprintf("P%d", i)
templateData[k] = v
}
errMsg, err := localizer.Localize(&i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{
ID: messageId,
},
TemplateData: templateData,
})
if err != nil {
global.Logger.Warn("LocalizeMessage Error: " + err.Error())
errMsg = messageId
}
return errMsg
}

View File

@@ -93,6 +93,9 @@ func AddressBookBind(rg *gin.RouterGroup) {
aR.POST("/create", cont.Create)
aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete)
arp := aR.Use(middleware.AdminPrivilege())
arp.POST("/batchCreate", cont.BatchCreate)
}
}
func PeerBind(rg *gin.RouterGroup) {
@@ -104,6 +107,9 @@ func PeerBind(rg *gin.RouterGroup) {
aR.POST("/create", cont.Create)
aR.POST("/update", cont.Update)
aR.POST("/delete", cont.Delete)
arp := aR.Use(middleware.AdminPrivilege())
arp.POST("/batchDelete", cont.BatchDelete)
}
}

View File

@@ -1,17 +1,18 @@
package model
type Peer struct {
RowId uint `json:"row_id" gorm:"primaryKey;"`
Id string `json:"id" gorm:"default:'';not null;index"`
Cpu string `json:"cpu" gorm:"default:'';not null;"`
Hostname string `json:"hostname" gorm:"default:'';not null;"`
Memory string `json:"memory" gorm:"default:'';not null;"`
Os string `json:"os" gorm:"default:'';not null;"`
Username string `json:"username" gorm:"default:'';not null;"`
Uuid string `json:"uuid" gorm:"default:'';not null;index"`
Version string `json:"version" gorm:"default:'';not null;"`
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
User User `json:"user,omitempty" gorm:""`
RowId uint `json:"row_id" gorm:"primaryKey;"`
Id string `json:"id" gorm:"default:'';not null;index"`
Cpu string `json:"cpu" gorm:"default:'';not null;"`
Hostname string `json:"hostname" gorm:"default:'';not null;"`
Memory string `json:"memory" gorm:"default:'';not null;"`
Os string `json:"os" gorm:"default:'';not null;"`
Username string `json:"username" gorm:"default:'';not null;"`
Uuid string `json:"uuid" gorm:"default:'';not null;index"`
Version string `json:"version" gorm:"default:'';not null;"`
UserId uint `json:"user_id" gorm:"default:0;not null;index"`
User User `json:"user,omitempty" gorm:""`
LastOnlineTime int64 `json:"last_online_time" gorm:"default:0;not null;"`
TimeModel
}

121
resources/i18n/en.toml Normal file
View File

@@ -0,0 +1,121 @@
[Test]
description = "test"
one = "test1 "
other = "Test2 {{.P0}}"
[ParamsError]
description = "Params validation failed."
one = "Params validation failed."
other = "Params validation failed."
[OperationFailed]
description = "OperationFailed."
one = "the operation failed."
other = "the operation failed."
[OperationSuccess]
description = "OperationSuccess."
one = "the operation success."
other = "the operation success."
[ItemExists]
description = "Item already exists."
one = "Item already exists."
other = "Item already exists."
[ItemNotFound]
description = "Item not found."
one = "Item not found."
other = "Item not found."
[NoAccess]
description = "No access."
one = "No access."
other = "No access."
[UsernameOrPasswordError]
description = "Username or password error."
one = "Username or password error."
other = "Username or password error."
[SystemError]
description = "System error."
one = "System error."
other = "System error."
[ConfigNotFound]
description = "Config not found."
one = "Config not found."
other = "Config not found."
[OauthExpired]
description = "Oauth expired."
one = "Oauth expired, please try again."
other = "Oauth expired,please try again."
[OauthFailed]
description = "Oauth failed."
one = "Oauth failed."
other = "Oauth failed."
[OauthHasBindOtherUser]
description = "Oauth has bind other user."
one = "Oauth has bind other user."
other = "Oauth has bind other user."
[ParamIsEmpty]
description = "Param is empty."
one = "{{.P0}} is empty."
other = "{{.P0}} is empty."
[BindFail]
description = "Bind fail."
one = "Bind fail."
other = "Bind fail."
[BindSuccess]
description = "Bind success."
one = "Bind success."
other = "Bind success."
[OauthHasBeenSuccess]
description = "Oauth has been success."
one = "Oauth has been success."
other = "Oauth has been success."
[OauthSuccess]
description = "Oauth success."
one = "Oauth success."
other = "Oauth success."
[OauthRegisterSuccess]
description = "Oauth register success."
one = "Oauth register success."
other = "Oauth register success."
[OauthRegisterFailed]
description = "Oauth register failed."
one = "Oauth register failed."
other = "Oauth register failed."
[GetOauthTokenError]
description = "Get oauth token error."
one = "Get oauth token error."
other = "Get oauth token error."
[GetOauthUserInfoError]
description = "Get oauth user info error."
one = "Get oauth user info error."
other = "Get oauth user info error."
[DecodeOauthUserInfoError]
description = "Decode oauth user info error."
one = "Decode oauth user info error."
other = "Decode oauth user info error."
[OldPasswordError]
description = "Old password error."
one = "Old password error."
other = "Old password error."
[DefaultGroup]
description = "Default group"
one = "Default Group"
other = "Default Group"
[ShareGroup]
description = "Share group"
one = "Share Group"
other = "Share Group"

123
resources/i18n/zh_CN.toml Normal file
View File

@@ -0,0 +1,123 @@
[Test]
description = "test"
one = "测试1 {{.P0}}"
other = "测试2 {{.P0}}"
[ParamsError]
description = "Params validation failed."
one = "参数错误。"
other = "参数错误。"
[OperationFailed]
description = "OperationFailed."
one = "操作失败。"
other = "操作失败。"
[OperationSuccess]
description = "OperationSuccess."
one = "操作成功。"
other = "操作成功。"
[ItemExists]
description = "Item already exists."
one = "数据已存在。"
other = "数据已存在。"
[ItemNotFound]
description = "Item not found."
one = "数据不存在。"
other = "数据不存在。"
[NoAccess]
description = "No access."
one = "无权限。"
other = "无权限。"
[UsernameOrPasswordError]
description = "Username or password error."
one = "用户名或密码错误。"
other = "用户名或密码错误。"
[SystemError]
description = "System error."
one = "系统错误。"
other = "系统错误。"
[ConfigNotFound]
description = "Config not found."
one = "配置不存在。"
other = "配置不存在。"
#授权过期
[OauthExpired]
description = "Oauth expired."
one = "授权过期,请重新授权。"
other = "授权过期,请重新授权。"
[OauthFailed]
description = "Oauth failed."
one = "授权失败。"
other = "授权失败。"
[OauthHasBindOtherUser]
description = "Oauth has bind other user."
one = "授权已绑定其他用户。"
other = "授权已绑定其他用户。"
[ParamIsEmpty]
description = "Param is empty."
one = "{{.P0}} 为空。"
other = "{{.P0}} 为空。"
[BindFail]
description = "Bind fail."
one = "绑定失败。"
other = "绑定失败。"
[BindSuccess]
description = "Bind success."
one = "绑定成功。"
other = "绑定成功。"
[OauthHasBeenSuccess]
description = "Oauth has been success."
one = "授权已成功。"
other = "授权已成功。"
[OauthSuccess]
description = "Oauth success."
one = "授权成功。"
other = "授权成功。"
[OauthRegisterSuccess]
description = "Oauth register success."
one = "授权注册成功。"
other = "授权注册成功。"
[OauthRegisterFailed]
description = "Oauth register failed."
one = "授权注册失败。"
other = "授权注册失败。"
[GetOauthTokenError]
description = "Get oauth token error."
one = "获取授权token失败。"
other = "获取授权token失败。"
[GetOauthUserInfoError]
description = "Get oauth user info error."
one = "获取授权用户信息失败。"
other = "获取授权用户信息失败。"
[DecodeOauthUserInfoError]
description = "Decode oauth user info error."
one = "解析授权用户信息失败。"
other = "解析授权用户信息失败。"
[OldPasswordError]
description = "Old password error."
one = "旧密码错误。"
other = "旧密码错误。"
[DefaultGroup]
description = "Default group."
one = "默认组"
other = "默认组"
[ShareGroup]
description = "Share group."
one = "共享组"
other = "共享组"

View File

@@ -133,7 +133,7 @@ func (os *OauthService) BeginAuth(op string) (error error, code, url string) {
return err, code, conf.AuthCodeURL(code)
}
return errors.New("op错误"), code, ""
return err, code, ""
}
// GetOauthConfig 获取配置
@@ -141,7 +141,7 @@ func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) {
if op == model.OauthTypeGithub {
g := os.InfoByOp(model.OauthTypeGithub)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("配置不存在"), nil
return errors.New("ConfigNotFound"), nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
@@ -154,7 +154,7 @@ func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) {
if op == model.OauthTypeGoogle {
g := os.InfoByOp(model.OauthTypeGoogle)
if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" {
return errors.New("配置不存在"), nil
return errors.New("ConfigNotFound"), nil
}
return nil, &oauth2.Config{
ClientID: g.ClientId,
@@ -164,7 +164,7 @@ func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) {
Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"},
}
}
return errors.New("op错误"), nil
return errors.New("ConfigNotFound"), nil
}
func (os *OauthService) GithubCallback(code string) (error error, userData *GithubUserdata) {
@@ -175,7 +175,7 @@ func (os *OauthService) GithubCallback(code string) (error error, userData *Gith
token, err := oauthConfig.Exchange(context.Background(), code)
if err != nil {
global.Logger.Warn(fmt.Printf("oauthConfig.Exchange() failed: %s\n", err))
error = errors.New("获取token失败")
error = errors.New("GetOauthTokenError")
return
}
@@ -184,7 +184,7 @@ func (os *OauthService) GithubCallback(code string) (error error, userData *Gith
resp, err := client.Get("https://api.github.com/user")
if err != nil {
global.Logger.Warn("failed getting user info: %s\n", err)
error = errors.New("获取user info失败")
error = errors.New("GetOauthUserInfoError")
return
}
defer func(Body io.ReadCloser) {
@@ -197,7 +197,7 @@ func (os *OauthService) GithubCallback(code string) (error error, userData *Gith
// 在这里处理 GitHub 用户信息
if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil {
global.Logger.Warn("failed decoding user info: %s\n", err)
error = errors.New("解析user info失败")
error = errors.New("DecodeOauthUserInfoError")
return
}
return
@@ -208,7 +208,7 @@ func (os *OauthService) GoogleCallback(code string) (error error, userData *Goog
token, err := oauthConfig.Exchange(context.Background(), code)
if err != nil {
global.Logger.Warn(fmt.Printf("oauthConfig.Exchange() failed: %s\n", err))
error = errors.New("获取token失败")
error = errors.New("GetOauthTokenError")
return
}
// 创建 HTTP 客户端,并将 access_token 添加到 Authorization 头中
@@ -216,7 +216,7 @@ func (os *OauthService) GoogleCallback(code string) (error error, userData *Goog
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
global.Logger.Warn("failed getting user info: %s\n", err)
error = errors.New("获取user info失败: " + err.Error())
error = errors.New("GetOauthUserInfoError")
return
}
defer func(Body io.ReadCloser) {
@@ -228,7 +228,7 @@ func (os *OauthService) GoogleCallback(code string) (error error, userData *Goog
if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil {
global.Logger.Warn("failed decoding user info: %s\n", err)
error = errors.New("解析user info失败:" + err.Error())
error = errors.New("DecodeOauthUserInfoError")
return
}
return
@@ -258,7 +258,13 @@ func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId
}
func (os *OauthService) UnBindGithubUser(userid uint) error {
return global.DB.Where("user_id = ? and third_type = ?", userid, model.OauthTypeGithub).Delete(&model.UserThird{}).Error
return os.UnBindThird(model.OauthTypeGithub, userid)
}
func (os *OauthService) UnBindGoogleUser(userid uint) error {
return os.UnBindThird(model.OauthTypeGoogle, userid)
}
func (os *OauthService) UnBindThird(thirdType string, userid uint) error {
return global.DB.Where("user_id = ? and third_type = ?", userid, thirdType).Delete(&model.UserThird{}).Error
}
// InfoById 根据id取用户信息

View File

@@ -15,24 +15,38 @@ func (ps *PeerService) FindById(id string) *model.Peer {
global.DB.Where("id = ?", id).First(p)
return p
}
func (ps *PeerService) FindByUuid(uuid string) *model.Peer {
p := &model.Peer{}
global.DB.Where("uuid = ?", uuid).First(p)
return p
}
func (ps *PeerService) InfoByRowId(id uint) *model.Peer {
p := &model.Peer{}
global.DB.Where("row_id = ?", id).First(p)
return p
}
//// ListByUserIds 根据用户id取列表
//func (ps *PeerService) ListByUserIds(userIds []uint, page, pageSize uint) (res *model.PeerList) {
// res = &model.PeerList{}
// res.Page = int64(page)
// res.PageSize = int64(pageSize)
// tx := global.DB.Model(&model.Peer{}).Preload("User")
// tx.Where("user_id in (?)", userIds)
// tx.Count(&res.Total)
// tx.Scopes(Paginate(page, pageSize))
// tx.Find(&res.Peers)
// return
//}
// UuidBindUserId 绑定用户id
func (ps *PeerService) UuidBindUserId(uuid string, userId uint) {
peer := ps.FindByUuid(uuid)
if peer.RowId > 0 {
peer.UserId = userId
ps.Update(peer)
}
}
// ListByUserIds 根据用户id取列表
func (ps *PeerService) ListByUserIds(userIds []uint, page, pageSize uint) (res *model.PeerList) {
res = &model.PeerList{}
res.Page = int64(page)
res.PageSize = int64(pageSize)
tx := global.DB.Model(&model.Peer{})
tx.Where("user_id in (?)", userIds)
tx.Count(&res.Total)
tx.Scopes(Paginate(page, pageSize))
tx.Find(&res.Peers)
return
}
func (ps *PeerService) List(page, pageSize uint, where func(tx *gorm.DB)) (res *model.PeerList) {
res = &model.PeerList{}
@@ -57,6 +71,11 @@ func (ps *PeerService) Delete(u *model.Peer) error {
return global.DB.Delete(u).Error
}
// BatchDelete
func (ps *PeerService) BatchDelete(ids []uint) error {
return global.DB.Where("row_id in (?)", ids).Delete(&model.Peer{}).Error
}
// Update 更新
func (ps *PeerService) Update(u *model.Peer) error {
return global.DB.Model(u).Updates(u).Error

View File

@@ -66,6 +66,9 @@ func (us *UserService) Login(u *model.User, llog *model.LoginLog) *model.UserTok
}
global.DB.Create(ut)
global.DB.Create(llog)
if llog.Uuid != "" {
AllService.PeerService.UuidBindUserId(llog.Uuid, u.Id)
}
return ut
}