Compare commits
No commits in common. "main" and "v2" have entirely different histories.
@ -1,6 +1,6 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: wireguard-dashboard
|
||||
name: wireguard-srv
|
||||
|
||||
trigger:
|
||||
event: [tag]
|
||||
@ -10,7 +10,7 @@ steps:
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: gitea.mrx.ltd # 镜像仓库地址
|
||||
repo: gitea.mrx.ltd/go-pkg/wireguard-dashboard # 镜像仓库地址
|
||||
repo: gitea.mrx.ltd/go-pkg/wireguard-srv # 镜像仓库地址
|
||||
username:
|
||||
from_secret: docker_user
|
||||
password:
|
||||
|
269
.gitignore
vendored
@ -1,5 +1,84 @@
|
||||
### GoLand+all template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Go template
|
||||
# If you prefer the allow list template_data instead of the deny list, see community template_data:
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
@ -17,21 +96,181 @@
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
.idea
|
||||
logs
|
||||
web/assets
|
||||
web/static
|
||||
web/*.ico
|
||||
web/*.gz
|
||||
web/*.br
|
||||
web/*.html
|
||||
web/*.svg
|
||||
web/*.json
|
||||
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
wg.db
|
||||
wg0.conf
|
||||
|
||||
.idea
|
||||
web/.idea
|
||||
|
||||
web/node_modules
|
||||
web/.DS_Store
|
||||
web/dist
|
||||
web/dist-ssr
|
||||
web/*.local
|
||||
web/.eslintcache
|
||||
web/report.html
|
||||
web/vite.config.*.timestamp*
|
||||
|
||||
web/yarn.lock
|
||||
web/npm-debug.log*
|
||||
web/.pnpm-error.log*
|
||||
web/.pnpm-debug.log
|
||||
web/tests/**/coverage/
|
||||
web/.vscode/
|
||||
|
||||
# Editor directories and files
|
||||
web/*.suo
|
||||
web/*.ntvs*
|
||||
web/*.njsproj
|
||||
web/*.sln
|
||||
web/tsconfig.tsbuildinfo
|
||||
|
||||
template/tmp/*
|
||||
logs/*
|
||||
app.yaml
|
||||
*.db
|
||||
*.yaml
|
||||
.env
|
||||
*.env
|
||||
|
||||
### GoLand+all template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Go template
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
./go.work
|
||||
|
||||
./.idea
|
||||
./web/.idea
|
||||
|
||||
./web/node_modules
|
||||
./web/.DS_Store
|
||||
./web/dist
|
||||
./web/dist-ssr
|
||||
./web/*.local
|
||||
./web/.eslintcache
|
||||
./web/report.html
|
||||
./web/vite.config.*.timestamp*
|
||||
|
||||
./web/yarn.lock
|
||||
./web/npm-debug.log*
|
||||
./web/.pnpm-error.log*
|
||||
./web/.pnpm-debug.log
|
||||
./web/tests/**/coverage/
|
||||
./web/.vscode/
|
||||
|
||||
# Editor directories and files
|
||||
./web/*.suo
|
||||
./web/*.ntvs*
|
||||
./web/*.njsproj
|
||||
./web/*.sln
|
||||
./web/tsconfig.tsbuildinfo
|
||||
|
||||
dist/assets
|
||||
dist/resource
|
||||
dist/favicon.png
|
||||
dist/favicon.svg
|
||||
dist/index.html
|
||||
|
||||
./template/tmp/*
|
||||
./logs/*
|
||||
./app.yaml
|
||||
./*.db
|
||||
./.env
|
||||
./*.env
|
||||
|
||||
|
17
Dockerfile
@ -1,23 +1,24 @@
|
||||
# 打包前端
|
||||
FROM node:18-alpine as build-stage
|
||||
FROM node:18-alpine AS build-front
|
||||
|
||||
WORKDIR front
|
||||
WORKDIR /front
|
||||
COPY . .
|
||||
WORKDIR web-src
|
||||
WORKDIR ./web
|
||||
|
||||
RUN corepack enable
|
||||
RUN corepack prepare pnpm@8.6.10 --activate
|
||||
RUN npm config set registry https://registry.npmmirror.com
|
||||
RUN pnpm install
|
||||
RUN pnpm build
|
||||
RUN ls -lh && pwd
|
||||
|
||||
# 前后端集成打包
|
||||
FROM golang:alpine as build
|
||||
FROM golang:alpine AS build-backend
|
||||
|
||||
RUN apk add upx
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
COPY --from=build-stage /front/web/ /build/web
|
||||
COPY --from=build-front /front/web/dist/ /build/dist
|
||||
# sqlite必须
|
||||
ENV GO111MODULE=on
|
||||
ENV GOPROXY=https://goproxy.cn,direct
|
||||
@ -37,9 +38,9 @@ RUN apk --no-cache add ca-certificates wireguard-tools jq iptables
|
||||
WORKDIR /app
|
||||
RUN mkdir -p db
|
||||
|
||||
COPY --from=build --chown=wgui:wgui /build/wgui /app
|
||||
COPY --from=build /build/template/* /app/template/
|
||||
COPY --from=build-backend --chown=wgui:wgui /build/wgui /app
|
||||
COPY --from=build-backend /build/template/* /app/template/
|
||||
|
||||
RUN chmod +x wgui
|
||||
|
||||
ENTRYPOINT ["./wgui"]
|
||||
ENTRYPOINT ["./wgui","http:serve"]
|
94
README.md
@ -0,0 +1,94 @@
|
||||
# Wireguard-UI
|
||||
> wireguard的管理面板UI
|
||||
|
||||
## 安装(仅提供docker方式)
|
||||
|
||||
OS X & Linux & Windows:
|
||||
|
||||
```sh
|
||||
# 先要创建一个配置文件 app.yaml
|
||||
http:
|
||||
port: 6687
|
||||
endpoint: localhost:3100,localhost:6687
|
||||
|
||||
database:
|
||||
driver: sqlite # sqlite时只填写db即可,目前仅支持sqlite | mysql | pgsql
|
||||
host:
|
||||
port:
|
||||
user:
|
||||
password:
|
||||
db: wg
|
||||
|
||||
cache:
|
||||
type: redis # 缓存类型 暂时仅支持redis
|
||||
host: 192.168.1.1
|
||||
port: 6379
|
||||
password: pGhQKwj7DE7FbFL1
|
||||
db: 15
|
||||
|
||||
file:
|
||||
type: oss # 文件类型支持本地文件存储与阿里云oss存储
|
||||
path: test/ # oss填写前缀目录
|
||||
endpoint: # oss必填
|
||||
accessId: # oss必填
|
||||
accessSecret: # oss必填
|
||||
bucketName: # oss必填
|
||||
|
||||
# 一些系统配置
|
||||
wireguard:
|
||||
restartMode: DELAY
|
||||
delayTime: 20
|
||||
|
||||
# 其中依赖了redis等,自行安装一个即可
|
||||
```
|
||||
|
||||
```sh
|
||||
# 创建docker-compose.yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
wg:
|
||||
image: gitea.mrx.ltd/go-pkg/wireguard-srv:2.1.0
|
||||
container_name: wg-srv
|
||||
restart: always
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
network_mode: host
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: 50m
|
||||
volumes:
|
||||
- ./app.yaml:/app/app.yaml
|
||||
- ./db:/app/db
|
||||
- ./logs:/app/logs
|
||||
- /etc/wireguard:/etc/wireguard
|
||||
- /etc/localtime:/etc/localtime
|
||||
```
|
||||
```sh
|
||||
默认账户密码
|
||||
账户: admin
|
||||
密码: admin123
|
||||
```
|
||||
## 配置示例
|
||||
```text
|
||||
1. 邮箱配置如下:
|
||||
code: EMAIL_SMTP
|
||||
配置项:
|
||||
1. host: "xxxx.xxx"
|
||||
2. port: "123"
|
||||
3. user: "haha"
|
||||
4. password: "haha123"
|
||||
5. skipTls: "false"
|
||||
```
|
||||
|
||||
## 页面展示
|
||||
![img.png](document/img.png)
|
||||
![img_1.png](document/img_1.png)
|
||||
![img_7.png](document/img_7.png)
|
||||
![img_8.png](document/img_8.png)
|
||||
![img_2.png](document/img_2.png)
|
||||
![img_3.png](document/img_3.png)
|
||||
![img_4.png](document/img_4.png)
|
||||
![img_5.png](document/img_5.png)
|
||||
![img_6.png](document/img_6.png)
|
10
cli/kernal.go
Normal file
@ -0,0 +1,10 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"wireguard-ui/cli/tui"
|
||||
)
|
||||
|
||||
func Kernel() error {
|
||||
tui.NewApp().Run()
|
||||
return nil
|
||||
}
|
165
cli/tui/app.go
Normal file
@ -0,0 +1,165 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/global/client"
|
||||
"wireguard-ui/global/constant"
|
||||
"wireguard-ui/http/vo"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
TokenSecret string // token密钥
|
||||
User *vo.User // 登陆用户
|
||||
Client *ClientComponent // 客户端组件
|
||||
Server *ServerComponent // 服务端组件
|
||||
Setting *SettingComponent // 设置组件
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
app := &App{}
|
||||
if _, err := app.Login(); err != nil {
|
||||
fmt.Println("登陆失败: ", err)
|
||||
return nil
|
||||
}
|
||||
err := app.AuthLogin()
|
||||
if err != nil {
|
||||
fmt.Println("登陆失败: ", err)
|
||||
return nil
|
||||
}
|
||||
fmt.Println("\n=============== 登陆成功 ==============================================================================")
|
||||
app.Client = NewClientComponent(app.User)
|
||||
app.Server = NewServerComponent(app.User)
|
||||
app.Setting = NewSettingComponent(app.User)
|
||||
fmt.Println("=============== 欢迎使用wireguard-tui =================================================================")
|
||||
fmt.Println("=============== 当前用户: ", app.User.Nickname, " ================================================================")
|
||||
fmt.Println("=============== 当前时间: ", time.Now().Format("2006-01-02 15:04:05"), " =======================================================")
|
||||
fmt.Println("=============== 注意事项如下: =========================================================================")
|
||||
fmt.Println("=============== 1. 请确保服务端已经安装wireguard ======================================================")
|
||||
fmt.Println("=============== 2. 请确保服务端和客户端配置文件路径正确 ===============================================")
|
||||
fmt.Println("=============== 3. 请确保服务端和客户端配置文件权限正确 ===============================================")
|
||||
fmt.Println("=============== 4. 请确保服务端和客户端配置文件内容正确 ===============================================")
|
||||
fmt.Println("=============== 5. 请勿泄露配置文件内容 ===============================================================")
|
||||
fmt.Println("=============== 6. 每次修改客户端、服务端配置或者全局配置过后,请使用重启功能重启服务端,以保证生效 ===")
|
||||
fmt.Println("=============== 7. 当使用重启无效时,请手动执行对应命令 ===============================================")
|
||||
fmt.Println("=============== 8. 请勿随意删除客户端,删除后无法恢复 =================================================")
|
||||
fmt.Println("=============== 9. 手动命令 ===========================================================================")
|
||||
fmt.Println("=============== 10. 启动wireguard服务端: wg-quick up wg0 ==============================================")
|
||||
fmt.Println("=============== 11. 停止wireguard服务端: wg-quick down wg0 ============================================")
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func (a *App) Run() {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
|
||||
PrintMenu()
|
||||
chooseMenu := readInput("请选择菜单: ")
|
||||
switch chooseMenu {
|
||||
case "1":
|
||||
a.Client.ConnectList()
|
||||
case "2":
|
||||
a.Client.Menus()
|
||||
case "3":
|
||||
a.Server.Menus()
|
||||
case "4":
|
||||
a.Setting.Menus()
|
||||
case "q":
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AuthLogin
|
||||
// @description: 登陆认证
|
||||
// @receiver a
|
||||
// @return string
|
||||
func (a *App) AuthLogin() error {
|
||||
// 先判断token是否存在
|
||||
tokenStr, err := client.Redis.Get(context.Background(), fmt.Sprintf("%s:%s", constant.TUIUserToken, a.User.Id)).Result()
|
||||
if err != nil {
|
||||
// 不存在,去登陆
|
||||
tokenStr, err = a.Login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 存在,不必要再次登陆,解析token
|
||||
claims, err := component.JWT().ParseToken("Bearer "+tokenStr, a.TokenSecret, "tui")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := service.User().GetUserById(claims.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user.Status != constant.Enabled {
|
||||
return errors.New("用户状态异常,请联系管理员处理")
|
||||
}
|
||||
|
||||
a.User = &vo.User{
|
||||
Id: user.Id,
|
||||
Account: user.Account,
|
||||
Nickname: user.Nickname,
|
||||
Avatar: user.Avatar,
|
||||
Contact: user.Contact,
|
||||
IsAdmin: user.IsAdmin,
|
||||
Status: user.Status,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Login
|
||||
// @description: 登陆
|
||||
// @receiver a
|
||||
// @return string
|
||||
func (a *App) Login() (tokenStr string, err error) {
|
||||
fmt.Println("============== 登陆 ==============")
|
||||
|
||||
username := readInput("请输入用户名: ")
|
||||
password := readInput("请输入密码: ")
|
||||
// 验证码正确,查询用户信息
|
||||
user, err := service.User().GetUserByAccount(username)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("用户不存在: %v", err)
|
||||
}
|
||||
|
||||
// 对比密码
|
||||
if !utils.Password().ComparePassword(user.Password, password) {
|
||||
return "", errors.New("密码错误")
|
||||
}
|
||||
|
||||
secret := component.JWT().GenerateSecret(password, uuid.NewString(), time.Now().Local().String())
|
||||
// 生成token
|
||||
token, _, err := component.JWT().GenerateToken(user.Id, secret, "tui")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("登陆失败: %v", err)
|
||||
}
|
||||
|
||||
a.User = &vo.User{
|
||||
Id: user.Id,
|
||||
Account: user.Account,
|
||||
Nickname: user.Nickname,
|
||||
Avatar: user.Avatar,
|
||||
Contact: user.Contact,
|
||||
IsAdmin: user.IsAdmin,
|
||||
Status: user.Status,
|
||||
}
|
||||
|
||||
a.TokenSecret = secret
|
||||
|
||||
return "Bearer " + token, nil
|
||||
}
|
520
cli/tui/client.go
Normal file
@ -0,0 +1,520 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
"github.com/spf13/cast"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/global/constant"
|
||||
"wireguard-ui/http/param"
|
||||
"wireguard-ui/http/vo"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type ClientComponent struct {
|
||||
LoginUser *vo.User
|
||||
Menu []string
|
||||
Clients [][]string
|
||||
}
|
||||
|
||||
func NewClientComponent(loginUser *vo.User) *ClientComponent {
|
||||
ccp := &ClientComponent{
|
||||
LoginUser: loginUser,
|
||||
Menu: []string{"[1] 客户端列表", "[2] 查看客户端配置", "[3] 添加客户端", "[4] 编辑客户端", "[5] 删除客户端", "[q] 返回上一级菜单"},
|
||||
}
|
||||
|
||||
return ccp
|
||||
}
|
||||
|
||||
// Menus
|
||||
// @description: 客户端菜单
|
||||
// @receiver c
|
||||
func (c *ClientComponent) Menus() {
|
||||
fmt.Println("")
|
||||
for _, r := range c.Menu {
|
||||
fmt.Println(" -> " + r)
|
||||
}
|
||||
|
||||
chooseMenu := readInput("\n请选择: ")
|
||||
switch chooseMenu {
|
||||
case "1":
|
||||
c.List(true)
|
||||
case "2":
|
||||
c.ShowConfig()
|
||||
case "3":
|
||||
c.Add()
|
||||
case "4":
|
||||
c.Edit()
|
||||
case "5":
|
||||
c.Delete()
|
||||
case "q":
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectList
|
||||
// @description: 客户端链接列表
|
||||
// @receiver c
|
||||
// @return string
|
||||
func (c *ClientComponent) ConnectList() {
|
||||
fmt.Println("\n客户端链接列表")
|
||||
connectList, err := component.Wireguard().GetClients()
|
||||
if err != nil {
|
||||
fmt.Println("获取客户端链接列表失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var data [][]string
|
||||
for _, peer := range connectList {
|
||||
// 获取客户端链接信息
|
||||
clientInfo, err := service.Client().GetByPublicKey(peer.PublicKey.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var ipAllocation string
|
||||
for _, iaip := range peer.AllowedIPs {
|
||||
ipAllocation += iaip.String() + ","
|
||||
}
|
||||
// 去除一下最右边的逗号
|
||||
if len(ipAllocation) > 0 {
|
||||
ipAllocation = strings.TrimRight(ipAllocation, ",")
|
||||
}
|
||||
var isOnline = "否"
|
||||
if time.Since(peer.LastHandshakeTime).Minutes() < 3 {
|
||||
isOnline = "是"
|
||||
}
|
||||
data = append(data, []string{clientInfo.Name,
|
||||
clientInfo.Email,
|
||||
ipAllocation,
|
||||
isOnline,
|
||||
utils.FlowCalculation().Parse(peer.TransmitBytes),
|
||||
utils.FlowCalculation().Parse(peer.ReceiveBytes),
|
||||
peer.Endpoint.String(),
|
||||
peer.LastHandshakeTime.Format("2006-01-02 15:04:05")})
|
||||
}
|
||||
|
||||
//if len(data) <= 0 {
|
||||
// //data = append(data, []string{"暂无数据"})
|
||||
// // data = append(data, []string{"名称1", "12345678910@qq.com", "192.168.100.1", "是", "10G", "20G", "1.14.30.133:51280", "2024-12-20 15:07:36"}, []string{"名称2", "12345678910@qq.com", "192.168.100.2", "否", "20G", "40G", "1.14.30.133:51280", "2024-12-22 15:07:36"})
|
||||
//}
|
||||
|
||||
title := []table.Column{
|
||||
{
|
||||
Title: "客户端名称",
|
||||
Width: 20,
|
||||
}, {
|
||||
Title: "联系邮箱",
|
||||
Width: 20,
|
||||
}, {
|
||||
Title: "分配的IP",
|
||||
Width: 30,
|
||||
}, {
|
||||
Title: "是否在线",
|
||||
Width: 10,
|
||||
}, {
|
||||
Title: "接收流量",
|
||||
Width: 10,
|
||||
}, {
|
||||
Title: "传输流量",
|
||||
Width: 10,
|
||||
}, {
|
||||
Title: "链接端点",
|
||||
Width: 30,
|
||||
}, {
|
||||
Title: "最后握手时间",
|
||||
Width: 30,
|
||||
}}
|
||||
|
||||
Show(GenerateTable(title, data))
|
||||
return
|
||||
}
|
||||
|
||||
// List
|
||||
// @description: 客户端列表
|
||||
// @receiver c
|
||||
func (c *ClientComponent) List(showMenu bool) {
|
||||
|
||||
title := []table.Column{
|
||||
{
|
||||
Title: "序号",
|
||||
Width: 5,
|
||||
},
|
||||
{
|
||||
Title: "ID",
|
||||
Width: 35,
|
||||
},
|
||||
{
|
||||
Title: "名称",
|
||||
Width: 25,
|
||||
},
|
||||
{
|
||||
Title: "联系邮箱",
|
||||
Width: 30,
|
||||
},
|
||||
{
|
||||
Title: "客户端IP",
|
||||
Width: 20,
|
||||
},
|
||||
{
|
||||
Title: "状态",
|
||||
Width: 10,
|
||||
},
|
||||
{
|
||||
Title: "离线通知",
|
||||
Width: 10,
|
||||
},
|
||||
}
|
||||
|
||||
records, _, err := service.Client().List(param.ClientList{
|
||||
Page: param.Page{
|
||||
Current: -1,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("获取客户端列表失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n客户端列表")
|
||||
var data [][]string
|
||||
for i, client := range records {
|
||||
var status, offlineNotify string
|
||||
if client.Enabled == 1 {
|
||||
status = "启用"
|
||||
} else {
|
||||
status = "禁用"
|
||||
}
|
||||
|
||||
if client.OfflineMonitoring == 1 {
|
||||
offlineNotify = "开启"
|
||||
} else {
|
||||
offlineNotify = "关闭"
|
||||
}
|
||||
|
||||
data = append(data, []string{
|
||||
cast.ToString(i + 1),
|
||||
client.Id,
|
||||
client.Name,
|
||||
client.Email,
|
||||
client.IpAllocationStr,
|
||||
status,
|
||||
offlineNotify,
|
||||
})
|
||||
}
|
||||
|
||||
Show(GenerateTable(title, data))
|
||||
c.Clients = data
|
||||
if showMenu {
|
||||
c.Menus()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ShowConfig
|
||||
// @description: 显示配置
|
||||
// @receiver c
|
||||
func (c *ClientComponent) ShowConfig() {
|
||||
c.List(false)
|
||||
|
||||
clientIdx := readInput("请输入客户端序号: ")
|
||||
downloadType := readInput("请输入下载类型(FILE - 文件 | EMAIL - 邮件): ")
|
||||
idx, err := strconv.Atoi(clientIdx)
|
||||
if err != nil {
|
||||
fmt.Println("输入有误: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if idx < 1 || idx > len(c.Clients) {
|
||||
fmt.Println("输入有误")
|
||||
return
|
||||
}
|
||||
|
||||
client := c.Clients[idx-1]
|
||||
// 取到id
|
||||
clientID := client[1]
|
||||
// 查询客户端信息
|
||||
clientInfo, err := service.Client().GetByID(clientID)
|
||||
if err != nil {
|
||||
fmt.Println("获取客户端信息失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 渲染配置
|
||||
var keys vo.Keys
|
||||
_ = json.Unmarshal([]byte(clientInfo.Keys), &keys)
|
||||
|
||||
globalSet, err := service.Setting().GetWGSetForConfig()
|
||||
if err != nil {
|
||||
fmt.Println("获取全局配置失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
serverConf, err := service.Setting().GetWGServerForConfig()
|
||||
if err != nil {
|
||||
fmt.Println("获取服务器配置失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
outPath, err := component.Wireguard().GenerateClientFile(clientInfo, serverConf, globalSet)
|
||||
if err != nil {
|
||||
fmt.Println("生成客户端配置失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 根据不同下载类型执行不同逻辑
|
||||
switch downloadType {
|
||||
case "FILE": // 二维码
|
||||
// 读取文件内容
|
||||
fileContent, err := os.ReadFile(outPath)
|
||||
if err != nil {
|
||||
fmt.Println("读取文件失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n#请将以下内容复制到客户端配置文件中【不包含本行提示语】")
|
||||
fmt.Println("\n" + string(fileContent))
|
||||
|
||||
if err = os.Remove(outPath); err != nil {
|
||||
log.Errorf("删除临时文件失败: %s", err.Error())
|
||||
}
|
||||
|
||||
case "EMAIL": // 邮件
|
||||
if clientInfo.Email == "" {
|
||||
fmt.Println("当前客户端并未配置通知邮箱")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取邮箱配置
|
||||
emailConf, err := service.Setting().GetByCode("EMAIL_SMTP")
|
||||
if err != nil {
|
||||
fmt.Println("获取邮箱配置失败,请先到设置页面的【其他】里面添加code为【EMAIL_SMTP】的具体配置")
|
||||
return
|
||||
}
|
||||
|
||||
err = utils.Mail(emailConf).SendMail(clientInfo.Email, fmt.Sprintf("客户端: %s", clientInfo.Name), "请查收附件", outPath)
|
||||
if err != nil {
|
||||
fmt.Println("发送邮件失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.Remove(outPath); err != nil {
|
||||
log.Errorf("删除临时文件失败: %s", err.Error())
|
||||
}
|
||||
|
||||
fmt.Println("发送邮件成功,请注意查收!")
|
||||
}
|
||||
|
||||
c.Menus()
|
||||
}
|
||||
|
||||
// Add
|
||||
// @description: 添加客户端
|
||||
// @receiver c
|
||||
func (c *ClientComponent) Add() {
|
||||
fmt.Println("\n添加客户端")
|
||||
clientIP, serverIP, err := GenerateIP()
|
||||
if err != nil {
|
||||
fmt.Println("生成客户端IP失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
keys, err := GenerateKeys()
|
||||
if err != nil {
|
||||
fmt.Println("生成密钥对失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var p param.SaveClient
|
||||
p.Name = readInput("请输入客户端名称: ")
|
||||
p.Email = readInput("请输入联系邮箱: ")
|
||||
clientIPIn := readInput("请输入客户端IP(默认自动生成,多个采用 ',' 分割,例如 10.10.0.1/32,10.10.0.2/32): ")
|
||||
if clientIPIn == "" {
|
||||
p.IpAllocation = clientIP
|
||||
} else {
|
||||
p.IpAllocation = strings.Split(clientIPIn, ",")
|
||||
}
|
||||
|
||||
p.AllowedIps = serverIP
|
||||
p.Keys = ¶m.Keys{
|
||||
PrivateKey: keys.PrivateKey,
|
||||
PublicKey: keys.PublicKey,
|
||||
PresharedKey: keys.PresharedKey,
|
||||
}
|
||||
|
||||
var useServerDNS, enabled, offlineNotify constant.Status
|
||||
|
||||
useServerDNSIn := readInput("是否使用服务器DNS(默认不使用 1 - 是 | 0 - 否 ): ")
|
||||
switch useServerDNSIn {
|
||||
case "1":
|
||||
useServerDNS = constant.Enabled
|
||||
case "0":
|
||||
useServerDNS = constant.Disabled
|
||||
default:
|
||||
useServerDNS = constant.Disabled
|
||||
}
|
||||
|
||||
enabledIn := readInput("是否启用(默认启用 1 - 是 | 0 - 否 ): ")
|
||||
switch enabledIn {
|
||||
case "1":
|
||||
enabled = constant.Enabled
|
||||
case "0":
|
||||
enabled = constant.Disabled
|
||||
default:
|
||||
enabled = constant.Enabled
|
||||
}
|
||||
|
||||
offlineNotifyIn := readInput("是否开启离线通知(默认关闭 1 - 是 | 0 - 否 ): ")
|
||||
switch offlineNotifyIn {
|
||||
case "1":
|
||||
offlineNotify = constant.Enabled
|
||||
case "0":
|
||||
offlineNotify = constant.Disabled
|
||||
default:
|
||||
offlineNotify = constant.Disabled
|
||||
}
|
||||
p.UseServerDns = &useServerDNS
|
||||
p.Enabled = &enabled
|
||||
p.OfflineMonitoring = &offlineNotify
|
||||
|
||||
err = service.Client().SaveClient(p, c.LoginUser)
|
||||
if err != nil {
|
||||
fmt.Println("添加客户端失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("添加客户端成功")
|
||||
c.List(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit
|
||||
// @description: 编辑客户端
|
||||
// @receiver c
|
||||
func (c *ClientComponent) Edit() {
|
||||
fmt.Println("\n编辑客户端")
|
||||
c.List(false)
|
||||
|
||||
clientIdx := readInput("请输入客户端序号: ")
|
||||
idx, err := strconv.Atoi(clientIdx)
|
||||
if err != nil {
|
||||
fmt.Println("输入有误: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if idx < 1 || idx > len(c.Clients) {
|
||||
fmt.Println("输入有误")
|
||||
return
|
||||
}
|
||||
|
||||
client := c.Clients[idx-1]
|
||||
// 取到id
|
||||
clientID := client[1]
|
||||
// 查询客户端信息
|
||||
clientInfo, err := service.Client().GetByID(clientID)
|
||||
if err != nil {
|
||||
fmt.Println("获取客户端信息失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var p param.SaveClient
|
||||
p.Id = clientID
|
||||
p.Name = readInput("请输入客户端名称[无需改变请回车跳过,下同]: ")
|
||||
p.Email = readInput("请输入联系邮箱: ")
|
||||
clientIPIn := readInput("请输入客户端IP(默认自动生成,多个采用 ',' 分割,例如 10.10.0.1/32,10.10.0.2/32): ")
|
||||
if clientIPIn == "" {
|
||||
p.IpAllocation = strings.Split(clientInfo.IpAllocation, ",")
|
||||
} else {
|
||||
p.IpAllocation = strings.Split(clientIPIn, ",")
|
||||
}
|
||||
|
||||
p.AllowedIps = strings.Split(clientInfo.AllowedIps, ",")
|
||||
var keys *param.Keys
|
||||
_ = json.Unmarshal([]byte(clientInfo.Keys), &keys)
|
||||
|
||||
p.Keys = keys
|
||||
|
||||
var useServerDNS, enabled, offlineNotify constant.Status
|
||||
|
||||
useServerDNSIn := readInput("是否使用服务器DNS(默认不使用 1 - 是 | 0 - 否 ): ")
|
||||
switch useServerDNSIn {
|
||||
case "1":
|
||||
useServerDNS = constant.Enabled
|
||||
case "0":
|
||||
useServerDNS = constant.Disabled
|
||||
default:
|
||||
useServerDNS = clientInfo.UseServerDns
|
||||
}
|
||||
|
||||
enabledIn := readInput("是否启用(默认启用 1 - 是 | 0 - 否 ): ")
|
||||
switch enabledIn {
|
||||
case "1":
|
||||
enabled = constant.Enabled
|
||||
case "0":
|
||||
enabled = constant.Disabled
|
||||
default:
|
||||
enabled = clientInfo.Enabled
|
||||
}
|
||||
|
||||
offlineNotifyIn := readInput("是否开启离线通知(默认关闭 1 - 是 | 0 - 否 ): ")
|
||||
switch offlineNotifyIn {
|
||||
case "1":
|
||||
offlineNotify = constant.Enabled
|
||||
case "0":
|
||||
offlineNotify = constant.Disabled
|
||||
default:
|
||||
offlineNotify = clientInfo.OfflineMonitoring
|
||||
}
|
||||
p.UseServerDns = &useServerDNS
|
||||
p.Enabled = &enabled
|
||||
p.OfflineMonitoring = &offlineNotify
|
||||
|
||||
err = service.Client().SaveClient(p, c.LoginUser)
|
||||
if err != nil {
|
||||
fmt.Println("编辑客户端失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("编辑客户端成功")
|
||||
c.List(true)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @description: 删除客户端
|
||||
// @receiver c
|
||||
func (c *ClientComponent) Delete() {
|
||||
fmt.Println("\n删除客户端")
|
||||
|
||||
c.List(false)
|
||||
|
||||
clientIdx := readInput("请输入客户端序号: ")
|
||||
idx, err := strconv.Atoi(clientIdx)
|
||||
if err != nil {
|
||||
fmt.Println("输入有误: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if idx < 1 || idx > len(c.Clients) {
|
||||
fmt.Println("输入有误")
|
||||
return
|
||||
}
|
||||
|
||||
client := c.Clients[idx-1]
|
||||
// 取到id
|
||||
clientID := client[1]
|
||||
if err := service.Client().Delete(clientID); err != nil {
|
||||
fmt.Println("删除客户端失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.List(true)
|
||||
fmt.Println("删除客户端成功")
|
||||
return
|
||||
}
|
62
cli/tui/server.go
Normal file
@ -0,0 +1,62 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"wireguard-ui/command"
|
||||
"wireguard-ui/http/vo"
|
||||
)
|
||||
|
||||
type ServerComponent struct {
|
||||
LoginUser *vo.User
|
||||
Menu []string
|
||||
}
|
||||
|
||||
func NewServerComponent(loginUser *vo.User) *ServerComponent {
|
||||
return &ServerComponent{
|
||||
LoginUser: loginUser,
|
||||
Menu: []string{"[1] 启动服务", "[2] 关闭服务", "[3] 重启服务", "[q] 返回上一级菜单"},
|
||||
}
|
||||
}
|
||||
|
||||
// Menus
|
||||
// @description: 服务端菜单
|
||||
// @receiver s
|
||||
func (s *ServerComponent) Menus() {
|
||||
fmt.Println("")
|
||||
for _, r := range s.Menu {
|
||||
fmt.Println(" -> " + r)
|
||||
}
|
||||
|
||||
chooseMenu := readInput("\n请选择: ")
|
||||
switch chooseMenu {
|
||||
case "1":
|
||||
s.Start()
|
||||
case "2":
|
||||
s.Stop()
|
||||
case "3":
|
||||
s.Restart()
|
||||
case "q":
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Start
|
||||
// @description: 启动服务
|
||||
// @receiver s
|
||||
func (s *ServerComponent) Start() {
|
||||
command.StartWireguard("")
|
||||
}
|
||||
|
||||
// Stop
|
||||
// @description: 停止服务
|
||||
// @receiver s
|
||||
func (s *ServerComponent) Stop() {
|
||||
command.StopWireguard("")
|
||||
}
|
||||
|
||||
// Restart
|
||||
// @description: 重启服务
|
||||
// @receiver s
|
||||
func (s *ServerComponent) Restart() {
|
||||
command.RestartWireguard(false, "")
|
||||
}
|
440
cli/tui/setting.go
Normal file
@ -0,0 +1,440 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
"github.com/eiannone/keyboard"
|
||||
"github.com/spf13/cast"
|
||||
"strings"
|
||||
"wireguard-ui/http/vo"
|
||||
"wireguard-ui/model"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type SettingComponent struct {
|
||||
LoginUser *vo.User
|
||||
Menu []string
|
||||
Other *OtherSettingComponent
|
||||
}
|
||||
|
||||
func NewSettingComponent(loginUser *vo.User) *SettingComponent {
|
||||
return &SettingComponent{
|
||||
LoginUser: loginUser,
|
||||
Menu: []string{"[1] 服务端配置", "[2] 全局设置", "[3] 其他配置", "[q] 返回上一级菜单"},
|
||||
Other: NewOtherSettingComponent(),
|
||||
}
|
||||
}
|
||||
|
||||
// Menus
|
||||
// @description: 设置菜单
|
||||
// @receiver s
|
||||
func (s *SettingComponent) Menus() {
|
||||
fmt.Println("")
|
||||
for _, r := range s.Menu {
|
||||
fmt.Println(" -> " + r)
|
||||
}
|
||||
|
||||
chooseMenu := readInput("\n请选择: ")
|
||||
switch chooseMenu {
|
||||
case "1":
|
||||
s.ServerSetting()
|
||||
case "2":
|
||||
s.GlobalSetting()
|
||||
case "3":
|
||||
s.Other.Menus(s)
|
||||
case "q":
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ServerSetting
|
||||
// @description: 服务端配置
|
||||
// @receiver s
|
||||
func (s *SettingComponent) ServerSetting() {
|
||||
fmt.Println("\n服务端配置")
|
||||
// 先读取一下服务端配置
|
||||
servConf, err := service.Setting().GetByCode("WG_SERVER")
|
||||
if err != nil {
|
||||
fmt.Println("获取服务端配置失败: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
type serverConf struct {
|
||||
IpScope []string `json:"ipScope"`
|
||||
ListenPort int `json:"listenPort"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
PostUpScript string `json:"postUpScript"`
|
||||
PostDownScript string `json:"postDownScript"`
|
||||
}
|
||||
|
||||
// 解析出来好渲染
|
||||
var conf serverConf
|
||||
if err = json.Unmarshal([]byte(servConf.Data), &conf); err != nil {
|
||||
fmt.Println("解析服务端配置失败: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
ipScopeIn := readInput(fmt.Sprintf("请输入IP段多个采用 ',[英文逗号]' 分割,不填写默认当前值,下同,当前值[%s] :", strings.Replace(strings.Join(conf.IpScope, ","), " ", "", -1)))
|
||||
listenPortIn := readInput(fmt.Sprintf("请输入监听端口,当前值[%d]: ", conf.ListenPort))
|
||||
privateKeyIn := readInput(fmt.Sprintf("请输入私钥,当前值[%s]: ", conf.PrivateKey))
|
||||
publicKeyIn := readInput(fmt.Sprintf("请输入公钥,当前值[%s]: ", conf.PublicKey))
|
||||
postUpScriptIn := readInput(fmt.Sprintf("请输入PostUp脚本,当前值[%s]: ", conf.PostUpScript))
|
||||
postDownScriptIn := readInput(fmt.Sprintf("请输入PostDown脚本,当前值[%s]: ", conf.PostDownScript))
|
||||
|
||||
if ipScopeIn != "" {
|
||||
conf.IpScope = strings.Split(ipScopeIn, ",")
|
||||
}
|
||||
if listenPortIn != "" {
|
||||
conf.ListenPort = cast.ToInt(listenPortIn)
|
||||
}
|
||||
if privateKeyIn != "" {
|
||||
conf.PrivateKey = privateKeyIn
|
||||
}
|
||||
if publicKeyIn != "" {
|
||||
conf.PublicKey = publicKeyIn
|
||||
}
|
||||
if postUpScriptIn != "" {
|
||||
conf.PostUpScript = postUpScriptIn
|
||||
}
|
||||
if postDownScriptIn != "" {
|
||||
conf.PostDownScript = postDownScriptIn
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(conf)
|
||||
|
||||
if err := service.Setting().SetData(&model.Setting{
|
||||
Code: "WG_SERVER",
|
||||
Data: string(data),
|
||||
}); err != nil {
|
||||
fmt.Println("保存服务端配置失败: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("修改服务端配置成功")
|
||||
return
|
||||
}
|
||||
|
||||
// GlobalSetting
|
||||
// @description: 全局设置
|
||||
// @receiver s
|
||||
func (s *SettingComponent) GlobalSetting() {
|
||||
fmt.Println("\n服务端配置")
|
||||
// 先读取一下服务端配置
|
||||
globalConf, err := service.Setting().GetByCode("WG_SETTING")
|
||||
if err != nil {
|
||||
fmt.Println("获取服务端配置失败: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
type gConf struct {
|
||||
MTU int `json:"MTU"`
|
||||
ConfigFilePath string `json:"configFilePath"`
|
||||
DnsServer []string `json:"dnsServer"`
|
||||
EndpointAddress string `json:"endpointAddress"`
|
||||
FirewallMark string `json:"firewallMark"`
|
||||
PersistentKeepalive int `json:"persistentKeepalive"`
|
||||
Table string `json:"table"`
|
||||
}
|
||||
|
||||
// 解析出来好渲染
|
||||
var conf gConf
|
||||
if err = json.Unmarshal([]byte(globalConf.Data), &conf); err != nil {
|
||||
fmt.Println("解析全局配置失败: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
mtu := readInput(fmt.Sprintf("请输入mtu,不填写默认当前值,下同,当前值[%d] :", conf.MTU))
|
||||
configFilePath := readInput(fmt.Sprintf("请输入配置文件地址,当前值[%s]: ", conf.ConfigFilePath))
|
||||
dnsServer := readInput(fmt.Sprintf("请输入dns,多个采用 ',[英文逗号]' 分割,当前值[%s]: ", strings.Replace(strings.Join(conf.DnsServer, ","), " ", "", -1)))
|
||||
endpointAddress := readInput(fmt.Sprintf("请输入公网IP,默认系统自动获取,当前值[%s]: ", conf.EndpointAddress))
|
||||
firewallMark := readInput(fmt.Sprintf("请输入FirewallMark,当前值[%s]: ", conf.FirewallMark))
|
||||
persistentKeepalive := readInput(fmt.Sprintf("请输入PersistentKeepalive,当前值[%d]: ", conf.PersistentKeepalive))
|
||||
tableRule := readInput(fmt.Sprintf("请输入Table,当前值[%s]: ", conf.Table))
|
||||
|
||||
if mtu != "" {
|
||||
conf.MTU = cast.ToInt(mtu)
|
||||
}
|
||||
if configFilePath != "" {
|
||||
conf.ConfigFilePath = configFilePath
|
||||
}
|
||||
if dnsServer != "" {
|
||||
conf.DnsServer = strings.Split(dnsServer, ",")
|
||||
}
|
||||
if endpointAddress != "" {
|
||||
conf.EndpointAddress = endpointAddress
|
||||
} else {
|
||||
conf.EndpointAddress = utils.Network().GetHostPublicIP()
|
||||
}
|
||||
if firewallMark != "" {
|
||||
conf.FirewallMark = firewallMark
|
||||
}
|
||||
if persistentKeepalive != "" {
|
||||
conf.PersistentKeepalive = cast.ToInt(persistentKeepalive)
|
||||
}
|
||||
if tableRule != "" {
|
||||
conf.Table = tableRule
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(conf)
|
||||
|
||||
if err := service.Setting().SetData(&model.Setting{
|
||||
Code: "WG_SETTING",
|
||||
Data: string(data),
|
||||
}); err != nil {
|
||||
fmt.Println("保存全局配置失败: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("修改全局配置成功")
|
||||
return
|
||||
}
|
||||
|
||||
// OtherSettingComponent
|
||||
// @description: 其他配置杂项
|
||||
type OtherSettingComponent struct {
|
||||
Setting *SettingComponent
|
||||
Menu []string
|
||||
}
|
||||
|
||||
func NewOtherSettingComponent() *OtherSettingComponent {
|
||||
return &OtherSettingComponent{
|
||||
Menu: []string{"[1] 列表", "[2] 添加", "[3] 编辑", "[4] 删除", "[q] 返回上一级菜单"},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OtherSettingComponent) Menus(setting *SettingComponent) {
|
||||
if s.Setting == nil {
|
||||
s.Setting = setting
|
||||
}
|
||||
fmt.Println("")
|
||||
for _, r := range s.Menu {
|
||||
fmt.Println(" -> " + r)
|
||||
}
|
||||
|
||||
chooseMenu := readInput("\n请选择: ")
|
||||
switch chooseMenu {
|
||||
case "1":
|
||||
s.List(true)
|
||||
case "2":
|
||||
s.Add()
|
||||
case "3":
|
||||
s.Edit()
|
||||
case "4":
|
||||
s.Delete()
|
||||
|
||||
case "q":
|
||||
s.Setting.Menus()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// List
|
||||
// @description: 其他配置
|
||||
// @receiver s
|
||||
// @param showMenu
|
||||
func (s *OtherSettingComponent) List(showMenu bool) {
|
||||
fmt.Println("\n其他配置列表")
|
||||
// 不查询的配置
|
||||
var blackList = []string{"WG_SETTING", "WG_SERVER"}
|
||||
|
||||
data, err := service.Setting().GetAllSetting(blackList)
|
||||
if err != nil {
|
||||
fmt.Println("获取配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
title := []table.Column{
|
||||
{
|
||||
Title: "序号",
|
||||
Width: 10,
|
||||
},
|
||||
{
|
||||
Title: "编码",
|
||||
Width: 40,
|
||||
},
|
||||
{
|
||||
Title: "描述",
|
||||
Width: 50,
|
||||
},
|
||||
{
|
||||
Title: "创建时间",
|
||||
Width: 30,
|
||||
},
|
||||
{
|
||||
Title: "更新时间",
|
||||
Width: 30,
|
||||
},
|
||||
}
|
||||
|
||||
var result [][]string
|
||||
for i, v := range data {
|
||||
result = append(result, []string{
|
||||
cast.ToString(i + 1),
|
||||
v.Code,
|
||||
v.Describe,
|
||||
v.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
v.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
Show(GenerateTable(title, result))
|
||||
if showMenu {
|
||||
s.Menus(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Add
|
||||
// @description: 新增其他配置
|
||||
// @receiver s
|
||||
func (s *OtherSettingComponent) Add() {
|
||||
fmt.Println("\n新增其他配置")
|
||||
|
||||
code := readInput("请输入配置编码,此编码是唯一编码不可重复:")
|
||||
desc := readInput("请输入配置描述:")
|
||||
|
||||
// 监听键盘事件,只监听 + 和 - 和 enter
|
||||
if err := keyboard.Open(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = keyboard.Close()
|
||||
}()
|
||||
|
||||
// + <=> 43 | - <=> 45 | enter <=> 0
|
||||
fmt.Println("请按下 + 或者 - 进行配置项的新增和删除")
|
||||
fmt.Println("每一项配置如此: key=val ")
|
||||
fmt.Println("确认输入完成后 enter[按一次代表当前配置项输入完成,两次代表新增完成]")
|
||||
fmt.Println("首先进入时请输入 + 进行第一个配置项填写")
|
||||
var breakCycle bool
|
||||
var keyVal []string
|
||||
for {
|
||||
char, _, err := keyboard.GetKey()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if breakCycle {
|
||||
break
|
||||
}
|
||||
|
||||
switch char {
|
||||
case 0:
|
||||
// 收到enter事件触发,执行后面的
|
||||
var dm = make(map[string]any)
|
||||
for _, kv := range keyVal {
|
||||
kvs := strings.Split(kv, "=")
|
||||
key := kvs[0]
|
||||
val := kvs[1]
|
||||
dm[key] = val
|
||||
}
|
||||
|
||||
dms, err := json.Marshal(dm)
|
||||
if err != nil {
|
||||
breakCycle = true
|
||||
continue
|
||||
}
|
||||
|
||||
if err = service.Setting().SetData(&model.Setting{
|
||||
Code: code,
|
||||
Data: string(dms),
|
||||
Describe: desc,
|
||||
}); err != nil {
|
||||
breakCycle = true
|
||||
fmt.Println("保存配置失败: ", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println("保存配置成功")
|
||||
s.List(true)
|
||||
|
||||
breakCycle = true
|
||||
case 43:
|
||||
keyVal = append(keyVal, readInput("请输入配置项:"))
|
||||
case 45:
|
||||
keyVal = keyVal[:len(keyVal)-1]
|
||||
fmt.Println("已删除最后一个配置项,当前配置项为:", keyVal)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Edit
|
||||
// @description: 编辑
|
||||
// @receiver s
|
||||
func (s *OtherSettingComponent) Edit() {
|
||||
fmt.Println("\n编辑其他配置")
|
||||
|
||||
s.List(false)
|
||||
|
||||
code := readInput("请输入需要编辑的配置编码:")
|
||||
|
||||
// 通过编码查询配置
|
||||
setting, err := service.Setting().GetByCode(code)
|
||||
if err != nil {
|
||||
fmt.Println("查找["+code+"]配置失败:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
desc := readInput("请输入需要编辑的配置描述:")
|
||||
if desc != "" {
|
||||
setting.Describe = desc
|
||||
}
|
||||
|
||||
var kvm = make(map[string]any)
|
||||
if err = json.Unmarshal([]byte(setting.Data), &kvm); err != nil {
|
||||
fmt.Println("配置解析失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range kvm {
|
||||
valIn := readInput(fmt.Sprintf("请输入配置项值,仅能修改值,当前键值对:%s=%v :", k, v))
|
||||
if valIn != "" {
|
||||
kvm[k] = valIn
|
||||
}
|
||||
}
|
||||
|
||||
// 处理以下数据
|
||||
kvmStr, err := json.Marshal(kvm)
|
||||
if err != nil {
|
||||
fmt.Println("序列化数据失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
setting.Data = string(kvmStr)
|
||||
|
||||
if err = service.Setting().SetData(setting); err != nil {
|
||||
fmt.Println("修改配置失败: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("修改配置成功")
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @description: 删除
|
||||
// @receiver s
|
||||
func (s *OtherSettingComponent) Delete() {
|
||||
fmt.Println("\n 删除指定配置")
|
||||
|
||||
s.List(false)
|
||||
|
||||
code := readInput("请输入要删除的配置项编码:")
|
||||
// 查询配置是否存在
|
||||
if err := service.Setting().Model(&model.Setting{}).
|
||||
Where("code NOT IN (?)", []string{"WG_SETTING", "WG_SERVER"}).
|
||||
Where("code = ?", code).Delete(&model.Setting{}).Error; err != nil {
|
||||
fmt.Println("删除[" + code + "]配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("删除成功")
|
||||
|
||||
s.List(true)
|
||||
|
||||
}
|
136
cli/tui/utils.go
Normal file
@ -0,0 +1,136 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/spf13/cast"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"os"
|
||||
"strings"
|
||||
"wireguard-ui/http/vo"
|
||||
"wireguard-ui/model"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
var BaseStyle = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("240"))
|
||||
|
||||
var menus = []string{"[1] 客户端链接列表", "[2] 客户端", "[3] 服务端", "[4] 设置", "[q] 退出"}
|
||||
|
||||
// PrintMenu
|
||||
// @description: 打印一下菜单
|
||||
func PrintMenu() {
|
||||
fmt.Println("\n菜单:")
|
||||
for _, menu := range menus {
|
||||
fmt.Println(menu)
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateTable
|
||||
// @description: 生成数据表
|
||||
// @param column
|
||||
// @param rows
|
||||
// @return string
|
||||
func GenerateTable(column []table.Column, rows [][]string) string {
|
||||
var data []table.Row
|
||||
for _, v := range rows {
|
||||
data = append(data, v)
|
||||
}
|
||||
|
||||
tl := table.New(
|
||||
table.WithColumns(column),
|
||||
table.WithRows(data),
|
||||
table.WithHeight(len(data)+1),
|
||||
)
|
||||
|
||||
s := table.DefaultStyles()
|
||||
s.Header = s.Header.
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("240")).
|
||||
BorderBottom(true).
|
||||
Bold(false)
|
||||
s.Selected = lipgloss.NewStyle()
|
||||
|
||||
tl.SetStyles(s)
|
||||
|
||||
return BaseStyle.Render(tl.View()+"\n") + "\n一共有 【" + fmt.Sprintf("%d", len(data)) + "】 条数据"
|
||||
}
|
||||
|
||||
// GenerateIP
|
||||
// @description: 生成IP
|
||||
// @return clientIPS
|
||||
// @return serverIPS
|
||||
// @return err
|
||||
func GenerateIP() (clientIPS, serverIPS []string, err error) {
|
||||
// 获取一下服务端信息,因为IP分配需要根据服务端的IP制定
|
||||
serverInfo, err := service.Setting().GetWGServerForConfig()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var assignIPS []string
|
||||
// 只获取最新的一个
|
||||
var clientInfo *model.Client
|
||||
if err = service.Client().Order("created_at DESC").Take(&clientInfo).Error; err == nil {
|
||||
// 遍历每一个ip是否可允许再分配
|
||||
for _, ip := range strings.Split(clientInfo.IpAllocation, ",") {
|
||||
if cast.ToInt64(utils.Network().GetIPSuffix(ip)) >= 255 {
|
||||
log.Errorf("IP:[%s]已无法分配新IP", ip)
|
||||
continue
|
||||
} else {
|
||||
assignIPS = append(assignIPS, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ips := utils.Network().GenerateIPByIPS(serverInfo.Address, assignIPS...)
|
||||
|
||||
return ips, serverInfo.Address, nil
|
||||
}
|
||||
|
||||
// GenerateKeys
|
||||
// @description: 生成密钥对
|
||||
// @return keys
|
||||
// @return err
|
||||
func GenerateKeys() (keys *vo.Keys, err error) {
|
||||
// 为空,新增
|
||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
publicKey := privateKey.PublicKey().String()
|
||||
presharedKey, err := wgtypes.GenerateKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成预共享密钥失败: %s", err.Error())
|
||||
}
|
||||
keys = &vo.Keys{
|
||||
PrivateKey: privateKey.String(),
|
||||
PublicKey: publicKey,
|
||||
PresharedKey: presharedKey.String(),
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// Show
|
||||
// @description: 展示
|
||||
// @param data
|
||||
func Show(data any) {
|
||||
fmt.Println(data)
|
||||
}
|
||||
|
||||
// readInput
|
||||
// @description: 读取输入
|
||||
// @param prompt
|
||||
// @return string
|
||||
func readInput(prompt string) string {
|
||||
fmt.Print(prompt)
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
return scanner.Text()
|
||||
}
|
@ -3,16 +3,23 @@ package command
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"wireguard-dashboard/repository"
|
||||
"wireguard-ui/service"
|
||||
)
|
||||
|
||||
// 分隔符
|
||||
// getConfigFileName
|
||||
// @description: 获取服务端配置文件名称
|
||||
// @return string
|
||||
func getConfigFileName() string {
|
||||
data, err := repository.System().GetServerSetting()
|
||||
func getConfigFileName(filePath string) string {
|
||||
if filePath != "" {
|
||||
filePath = strings.Split(filePath, string(os.PathSeparator))[len(strings.Split(filePath, string(os.PathSeparator)))-1] // 这里取到的是wg0.conf
|
||||
filePath = strings.Split(filePath, ".conf")[0] // 这里取到的就是wg0
|
||||
return filePath
|
||||
}
|
||||
data, err := service.Setting().GetWGSetForConfig()
|
||||
if err != nil {
|
||||
log.Errorf("获取服务端配置失败: %v", err.Error())
|
||||
return ""
|
||||
@ -29,23 +36,23 @@ func getConfigFileName() string {
|
||||
// RestartWireguard
|
||||
// @description: 是否重启
|
||||
// @param isAsync // 是否异步执行
|
||||
func RestartWireguard(isAsync bool) {
|
||||
func RestartWireguard(isAsync bool, filePath string) {
|
||||
if isAsync {
|
||||
go func() {
|
||||
StopWireguard() // 停止
|
||||
StartWireguard() // 启动
|
||||
StopWireguard(filePath) // 停止
|
||||
StartWireguard(filePath) // 启动
|
||||
}()
|
||||
} else {
|
||||
StopWireguard() // 停止
|
||||
StartWireguard() // 启动
|
||||
StopWireguard(filePath) // 停止
|
||||
StartWireguard(filePath) // 启动
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// StopWireguard
|
||||
// @description: 停止服务端
|
||||
func StopWireguard() {
|
||||
configFileName := getConfigFileName()
|
||||
func StopWireguard(filePath string) {
|
||||
configFileName := getConfigFileName(filePath)
|
||||
|
||||
cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("wg-quick down %s", configFileName))
|
||||
if err := cmd.Run(); err == nil {
|
||||
@ -57,12 +64,13 @@ func StopWireguard() {
|
||||
|
||||
// StartWireguard
|
||||
// @description: 启动服务端
|
||||
func StartWireguard() {
|
||||
configFileName := getConfigFileName()
|
||||
func StartWireguard(filePath string) {
|
||||
configFileName := getConfigFileName(filePath)
|
||||
|
||||
cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("wg-quick up %s", configFileName))
|
||||
if err := cmd.Run(); err == nil {
|
||||
log.Infof("启动wireguard[%s]服务端成功", configFileName)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -6,29 +6,29 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/constant"
|
||||
"wireguard-ui/global/client"
|
||||
"wireguard-ui/global/constant"
|
||||
)
|
||||
|
||||
type CaptchaStore struct{}
|
||||
type Captcha struct{}
|
||||
|
||||
// Set
|
||||
// @description: 验证码放入指定存储
|
||||
// @receiver CaptchaStore
|
||||
// @receiver Captcha
|
||||
// @param id
|
||||
// @param value
|
||||
// @return error
|
||||
func (CaptchaStore) Set(id string, value string) error {
|
||||
func (Captcha) Set(id string, value string) error {
|
||||
return client.Redis.Set(context.Background(), fmt.Sprintf("%s:%s", constant.Captcha, id), value, 2*time.Minute).Err()
|
||||
}
|
||||
|
||||
// Get
|
||||
// @description: 获取验证码信息
|
||||
// @receiver CaptchaStore
|
||||
// @receiver Captcha
|
||||
// @param id
|
||||
// @param clear
|
||||
// @return string
|
||||
func (CaptchaStore) Get(id string, clear bool) string {
|
||||
func (Captcha) Get(id string, clear bool) string {
|
||||
val, err := client.Redis.Get(context.Background(), fmt.Sprintf("%s:%s", constant.Captcha, id)).Result()
|
||||
if err != nil {
|
||||
return ""
|
||||
@ -44,12 +44,12 @@ func (CaptchaStore) Get(id string, clear bool) string {
|
||||
|
||||
// Verify
|
||||
// @description: 校验
|
||||
// @receiver CaptchaStore
|
||||
// @receiver Captcha
|
||||
// @param id
|
||||
// @param answer
|
||||
// @param clear
|
||||
// @return bool
|
||||
func (c CaptchaStore) Verify(id, answer string, clear bool) bool {
|
||||
func (c Captcha) Verify(id, answer string, clear bool) bool {
|
||||
if os.Getenv("GIN_MODE") != "release" {
|
||||
return true
|
||||
}
|
133
component/jwt.go
@ -7,74 +7,111 @@ import (
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/config"
|
||||
"wireguard-dashboard/constant"
|
||||
"wireguard-ui/config"
|
||||
"wireguard-ui/global/client"
|
||||
"wireguard-ui/global/constant"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
const Secret = "IK8MSs76Pb2VJxleTDadf1Wzu3h9QROLv0XtmnCUErYgBG5wAyjk4cioqFZHNpZG"
|
||||
|
||||
type JwtClaims struct {
|
||||
type JwtComponent struct {
|
||||
ID string `json:"id"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func JWT() JwtClaims {
|
||||
return JwtClaims{}
|
||||
// JWT
|
||||
// @description: 初始化JWT组件
|
||||
// @return JwtComponent
|
||||
func JWT() JwtComponent {
|
||||
return JwtComponent{}
|
||||
}
|
||||
|
||||
// GenerateToken
|
||||
// @description: 生成token
|
||||
// @receiver Jwt
|
||||
// @receiver JwtComponent
|
||||
// @param userId
|
||||
// @param password
|
||||
// @return token
|
||||
// @return expireTime
|
||||
// @return err
|
||||
func (j JwtClaims) GenerateToken(userId string) (token string, expireTime *jwt.NumericDate, err error) {
|
||||
timeNow := time.Now().Local()
|
||||
expireTime = jwt.NewNumericDate(timeNow.Add(7 * time.Hour))
|
||||
notBefore := jwt.NewNumericDate(timeNow)
|
||||
issuedAt := jwt.NewNumericDate(timeNow)
|
||||
claims := JwtClaims{
|
||||
func (JwtComponent) GenerateToken(userId, secret, source string, times ...time.Time) (token string, expireTime *jwt.NumericDate, err error) {
|
||||
var notBefore, issuedAt *jwt.NumericDate
|
||||
if len(times) != 0 {
|
||||
expireTime = jwt.NewNumericDate(times[0])
|
||||
notBefore = jwt.NewNumericDate(times[1])
|
||||
issuedAt = jwt.NewNumericDate(times[1])
|
||||
} else {
|
||||
timeNow := time.Now().Local()
|
||||
expireTime = jwt.NewNumericDate(timeNow.Add(7 * time.Hour))
|
||||
notBefore = jwt.NewNumericDate(timeNow)
|
||||
issuedAt = jwt.NewNumericDate(timeNow)
|
||||
}
|
||||
|
||||
claims := JwtComponent{
|
||||
ID: userId,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: config.Config.Http.Endpoint, // 颁发站点
|
||||
Subject: "wg-dashboard",
|
||||
ExpiresAt: expireTime,
|
||||
NotBefore: notBefore,
|
||||
IssuedAt: issuedAt,
|
||||
ID: uuid.NewString(),
|
||||
Issuer: config.Config.Http.Endpoint, // 颁发站点
|
||||
Subject: "you can you up,no can no bb", // 发布主题
|
||||
ExpiresAt: expireTime, // 过期时间
|
||||
NotBefore: notBefore, // token不得早于该时间
|
||||
IssuedAt: issuedAt, // token颁发时间
|
||||
ID: strings.ReplaceAll(uuid.NewString(), "-", ""), // 该token的id
|
||||
},
|
||||
}
|
||||
|
||||
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
token, err = t.SignedString([]byte(Secret))
|
||||
t := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
|
||||
token, err = t.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
log.Errorf("生成token失败: %v", err.Error())
|
||||
return "", nil, errors.New("生成token失败")
|
||||
}
|
||||
|
||||
client.Redis.Set(context.Background(), fmt.Sprintf("%s:%s", constant.Token, userId), token, 7*time.Hour)
|
||||
switch source {
|
||||
case "http":
|
||||
client.Redis.Set(context.Background(),
|
||||
fmt.Sprintf("%s:%s", constant.UserToken, userId),
|
||||
token,
|
||||
time.Duration(expireTime.Sub(time.Now()).Abs().Seconds())*time.Second)
|
||||
case "tui":
|
||||
client.Redis.Set(context.Background(),
|
||||
fmt.Sprintf("%s:%s", constant.TUIUserToken, userId),
|
||||
token,
|
||||
time.Duration(expireTime.Sub(time.Now()).Abs().Seconds())*time.Second)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ParseToken
|
||||
// @description: 解析token
|
||||
// @receiver Jwt
|
||||
// @return Jwt
|
||||
// @receiver JwtComponent
|
||||
// @param token
|
||||
// @return *JwtComponent
|
||||
// @return error
|
||||
func (JwtClaims) ParseToken(token string) (*JwtClaims, error) {
|
||||
func (JwtComponent) ParseToken(token, secret, source string) (*JwtComponent, error) {
|
||||
tokenStr := strings.Split(token, "Bearer ")[1]
|
||||
|
||||
t, err := jwt.ParseWithClaims(tokenStr, &JwtClaims{}, func(token *jwt.Token) (any, error) {
|
||||
return []byte(Secret), nil
|
||||
t, err := jwt.ParseWithClaims(tokenStr, &JwtComponent{}, func(token *jwt.Token) (any, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
|
||||
if claims, ok := t.Claims.(*JwtClaims); ok && t.Valid {
|
||||
userToken, err := client.Redis.Get(context.Background(), fmt.Sprintf("%s:%s", constant.Token, claims.ID)).Result()
|
||||
if err != nil {
|
||||
log.Errorf("缓存中用户[%s]的token查找失败: %v", claims.ID, err.Error())
|
||||
return nil, errors.New("token不存在")
|
||||
if claims, ok := t.Claims.(*JwtComponent); ok && t.Valid {
|
||||
var userToken string
|
||||
switch source {
|
||||
case "http":
|
||||
userToken, err = client.Redis.Get(context.Background(), fmt.Sprintf("%s:%s", constant.UserToken, claims.ID)).Result()
|
||||
if err != nil {
|
||||
log.Errorf("缓存中用户[%s]的token查找失败: %v", claims.ID, err.Error())
|
||||
return nil, errors.New("token不存在")
|
||||
}
|
||||
case "tui":
|
||||
userToken, err = client.Redis.Get(context.Background(), fmt.Sprintf("%s:%s", constant.TUIUserToken, claims.ID)).Result()
|
||||
if err != nil {
|
||||
log.Errorf("缓存中用户[%s]的token查找失败: %v", claims.ID, err.Error())
|
||||
return nil, errors.New("token不存在")
|
||||
}
|
||||
}
|
||||
|
||||
if userToken != tokenStr {
|
||||
@ -88,11 +125,31 @@ func (JwtClaims) ParseToken(token string) (*JwtClaims, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSecret
|
||||
// @description: 生成token解析密钥【每个用户的secret不一样,提高安全性】
|
||||
// @receiver JwtComponent
|
||||
// @param secret
|
||||
// @return string
|
||||
func (JwtComponent) GenerateSecret(secret ...string) string {
|
||||
// 添加10个元素,增加随机性
|
||||
for i := 0; i <= 10; i++ {
|
||||
secret = append(secret, uuid.NewString())
|
||||
}
|
||||
// 混淆一下明文secret的顺序
|
||||
n := len(secret)
|
||||
for i := n - 1; i > 0; i-- {
|
||||
j := rand.Intn(i + 1)
|
||||
secret[i], secret[j] = secret[j], secret[i]
|
||||
}
|
||||
secretStr := strings.Join(secret, ".")
|
||||
return utils.Hash().MD5(utils.Hash().SHA256(utils.Hash().SHA512(secretStr)))
|
||||
}
|
||||
|
||||
// Logout
|
||||
// @description: 退出登陆
|
||||
// @receiver JwtClaims
|
||||
// @receiver JwtComponent
|
||||
// @param userId
|
||||
// @return err
|
||||
func (j JwtClaims) Logout(userId string) (err error) {
|
||||
return client.Redis.Del(context.Background(), fmt.Sprintf("%s:%s", constant.Token, userId)).Err()
|
||||
// @return error
|
||||
func (JwtComponent) Logout(userId string) error {
|
||||
return client.Redis.Del(context.Background(), fmt.Sprintf("%s:%s", constant.UserToken, userId)).Err()
|
||||
}
|
||||
|
92
component/template.go
Normal file
@ -0,0 +1,92 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"html/template"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TemplateComponent struct{}
|
||||
|
||||
func Template() TemplateComponent {
|
||||
return TemplateComponent{}
|
||||
}
|
||||
|
||||
// Execute
|
||||
// @description: 渲染数据模板并生成对应文件
|
||||
// @receiver t
|
||||
// @param templateFilePath
|
||||
// @param outFilePath
|
||||
// @param data
|
||||
// @return err
|
||||
func (t TemplateComponent) Execute(templateFilePath, outFilePath string, data any) (err error) {
|
||||
parseTemplate, err := t.ParseTemplate(templateFilePath)
|
||||
if err != nil {
|
||||
log.Errorf("解析模板信息失败:%v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.Render(parseTemplate, data, outFilePath)
|
||||
if err != nil {
|
||||
log.Errorf("渲染模板失败: %v", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseTemplate
|
||||
// @description: 解析模板
|
||||
// @receiver t
|
||||
// @param filepath
|
||||
// @return t
|
||||
// @return err
|
||||
func (t TemplateComponent) ParseTemplate(filepath string) (tp *template.Template, err error) {
|
||||
file, err := os.ReadFile(filepath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tp, err = template.New("wg.conf").Funcs(t.FuncMap()).Parse(string(file))
|
||||
return
|
||||
}
|
||||
|
||||
// Render
|
||||
// @description: 渲染模板
|
||||
// @receiver t
|
||||
// @param tp
|
||||
// @param data
|
||||
// @param filepath
|
||||
// @return err
|
||||
func (t TemplateComponent) Render(tp *template.Template, data any, filepath string) (err error) {
|
||||
wg0Conf, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
log.Errorf("创建文件[%s]失败: %v", filepath, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err = wg0Conf.Close(); err != nil {
|
||||
log.Errorf("关闭文件[%s]失败: %v", filepath, err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return tp.Execute(wg0Conf, data)
|
||||
}
|
||||
|
||||
// FuncMap
|
||||
// @description: 模板内的操作方法
|
||||
// @receiver t
|
||||
// @return template.FuncMap
|
||||
// @return error
|
||||
func (t TemplateComponent) FuncMap() template.FuncMap {
|
||||
sliceToString := func(str []string) string {
|
||||
return strings.Join(str, ",")
|
||||
}
|
||||
|
||||
return template.FuncMap{
|
||||
"sliceToStr": sliceToString,
|
||||
}
|
||||
}
|
104
component/validator.go
Normal file
@ -0,0 +1,104 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-playground/locales/en"
|
||||
"github.com/go-playground/locales/zh"
|
||||
ut "github.com/go-playground/universal-translator"
|
||||
"github.com/go-playground/validator/v10"
|
||||
zhTranslations "github.com/go-playground/validator/v10/translations/zh"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var validatorTrans ut.Translator
|
||||
|
||||
func init() {
|
||||
initValidatorTranslator()
|
||||
}
|
||||
|
||||
func Error(err error) string {
|
||||
var errs validator.ValidationErrors
|
||||
ok := errors.As(err, &errs)
|
||||
if !ok {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
errMap := errs.Translate(validatorTrans)
|
||||
|
||||
var errMsg string
|
||||
for _, v := range errMap {
|
||||
errMsg = v
|
||||
}
|
||||
|
||||
return errMsg
|
||||
}
|
||||
|
||||
// Validate
|
||||
// @description: 校验
|
||||
// @param data
|
||||
// @return string
|
||||
func Validate(data any) error {
|
||||
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||
return v.Struct(data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initValidatorTranslator
|
||||
// @description: 初始化翻译机
|
||||
// @receiver vli
|
||||
func initValidatorTranslator() {
|
||||
//修改gin框架中的Validator属性,实现自定制
|
||||
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||
// 注册一个获取json tag的自定义方法
|
||||
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
||||
name := strings.SplitN(fld.Tag.Get("label"), ",", 2)[0]
|
||||
if name == "-" {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
})
|
||||
|
||||
zhT := zh.New() //中文翻译器
|
||||
enT := en.New() //英文翻译器
|
||||
|
||||
// 第一个参数是备用(fallback)的语言环境
|
||||
// 后面的参数是应该支持的语言环境(支持多个)
|
||||
// uni := ut.New(zhT, zhT) 也是可以的
|
||||
uni := ut.New(enT, zhT, enT)
|
||||
|
||||
// locale 通常取决于 http 请求头的 'Accept-Language'
|
||||
var ok bool
|
||||
// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
|
||||
validatorTrans, ok = uni.GetTranslator("zh")
|
||||
if !ok {
|
||||
log.Errorf("获取翻译机失败")
|
||||
return
|
||||
}
|
||||
|
||||
err := overrideTranslator(v, validatorTrans)
|
||||
if err != nil {
|
||||
log.Errorf("覆盖原有翻译失败: %v", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// overrideTranslator
|
||||
// @description: 覆盖原有翻译
|
||||
// @param v
|
||||
// @param translator
|
||||
// @return error
|
||||
func overrideTranslator(v *validator.Validate, translator ut.Translator) error {
|
||||
err := zhTranslations.RegisterDefaultTranslations(v, translator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,51 +1,135 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"gopkg.in/fsnotify.v1"
|
||||
"wireguard-dashboard/command"
|
||||
"wireguard-dashboard/config"
|
||||
"wireguard-dashboard/utils"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"gopkg.in/fsnotify/fsnotify.v1"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"wireguard-ui/command"
|
||||
"wireguard-ui/config"
|
||||
"wireguard-ui/global/client"
|
||||
"wireguard-ui/model"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/template/render_data"
|
||||
)
|
||||
|
||||
type wireguard struct{}
|
||||
type WireguardComponent struct{}
|
||||
|
||||
func Wireguard() wireguard {
|
||||
return wireguard{}
|
||||
func Wireguard() WireguardComponent {
|
||||
return WireguardComponent{}
|
||||
}
|
||||
|
||||
// Apply
|
||||
// @description: 应用配置
|
||||
// @receiver wireguard
|
||||
// GetClients
|
||||
// @description: 获取所有链接的客户端信息
|
||||
// @receiver w
|
||||
// @return peers
|
||||
// @return err
|
||||
func (w wireguard) Apply(templateFilePath, configFilePath string, data any) (err error) {
|
||||
|
||||
parseTemplate, err := utils.Template().Parse(templateFilePath)
|
||||
func (w WireguardComponent) GetClients() (peers []wgtypes.Peer, err error) {
|
||||
device, err := client.WireguardClient.Devices()
|
||||
if err != nil {
|
||||
log.Errorf("解析模板信息失败")
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
err = utils.Template().Execute(parseTemplate, data, configFilePath)
|
||||
if err != nil {
|
||||
log.Errorf("应用配置失败: %v", err.Error())
|
||||
return err
|
||||
for _, v := range device {
|
||||
return v.Peers, nil
|
||||
}
|
||||
|
||||
// 判断服务端重启规则
|
||||
switch config.Config.Wireguard.ListenConfig {
|
||||
case "auto":
|
||||
w.watchListConfig(configFilePath)
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// watchListConfig
|
||||
// @description: 监听配置文件变化
|
||||
// @receiver wireguard
|
||||
// GetClientByPublicKey
|
||||
// @description: 根据公钥获取指定客户端信息
|
||||
// @receiver w
|
||||
// @return peer
|
||||
// @return err
|
||||
func (wireguard) watchListConfig(filePath string) {
|
||||
func (w WireguardComponent) GetClientByPublicKey(pk string) (peer *wgtypes.Peer, err error) {
|
||||
peers, err := w.GetClients()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range peers {
|
||||
if v.PublicKey.String() == pk {
|
||||
return &v, nil
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GenerateClientFile
|
||||
// @description: 生成客户端文件
|
||||
// @receiver w
|
||||
// @param clientInfo
|
||||
// @param server
|
||||
// @param setting
|
||||
// @return filePath
|
||||
// @return err
|
||||
func (w WireguardComponent) GenerateClientFile(clientInfo *model.Client, server *render_data.Server, setting *render_data.ServerSetting) (filePath string, err error) {
|
||||
var keys render_data.Keys
|
||||
_ = json.Unmarshal([]byte(clientInfo.Keys), &keys)
|
||||
|
||||
var serverDNS []string
|
||||
if clientInfo.UseServerDns == 1 {
|
||||
serverDNS = setting.DnsServer
|
||||
}
|
||||
|
||||
// 处理一下数据
|
||||
execData := render_data.ClientConfig{
|
||||
PrivateKey: keys.PrivateKey,
|
||||
IpAllocation: clientInfo.IpAllocation,
|
||||
MTU: setting.MTU,
|
||||
DNS: strings.Join(serverDNS, ","),
|
||||
PublicKey: server.PublicKey,
|
||||
PresharedKey: keys.PresharedKey,
|
||||
AllowedIPS: clientInfo.AllowedIps,
|
||||
Endpoint: setting.EndpointAddress,
|
||||
ListenPort: int(server.ListenPort),
|
||||
PersistentKeepalive: setting.PersistentKeepalive,
|
||||
}
|
||||
|
||||
// 不同环境下处理文件路径
|
||||
var outPath = "/tmp/" + fmt.Sprintf("%s.conf", clientInfo.Name)
|
||||
var templatePath = "./template/wg.client.conf"
|
||||
if os.Getenv("GIN_MODE") != "release" {
|
||||
outPath = "E:\\Workspace\\Go\\wireguard-ui\\template\\tmp\\" + fmt.Sprintf("%s.conf", clientInfo.Name)
|
||||
templatePath = "E:\\Workspace\\Go\\wireguard-ui\\template\\conf\\wg.client.conf"
|
||||
}
|
||||
|
||||
err = Template().Execute(templatePath, outPath, execData)
|
||||
if err != nil {
|
||||
return "", errors.New("文件渲染失败")
|
||||
}
|
||||
|
||||
return outPath, nil
|
||||
}
|
||||
|
||||
// ServerControl
|
||||
// @description: 服务端控制
|
||||
// @receiver w
|
||||
// @return error
|
||||
func (w WireguardComponent) ServerControl(filePath string) {
|
||||
if filePath == "" {
|
||||
data, err := service.Setting().GetWGSetForConfig()
|
||||
if err != nil {
|
||||
log.Errorf("获取服务端配置失败: %v", err.Error())
|
||||
return
|
||||
}
|
||||
filePath = data.ConfigFilePath
|
||||
}
|
||||
w.watchConfigFile(filePath, config.Config.Wireguard.RestartMode, config.Config.Wireguard.DelayTime)
|
||||
}
|
||||
|
||||
// watchConfigFile
|
||||
// @description: 监听并重新操作配置文件
|
||||
// @receiver w
|
||||
// @param filepath
|
||||
func (w WireguardComponent) watchConfigFile(filepath string, mode string, delay int64) {
|
||||
go func() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
@ -65,7 +149,14 @@ func (wireguard) watchListConfig(filePath string) {
|
||||
}
|
||||
|
||||
if event.Op == fsnotify.Write {
|
||||
command.RestartWireguard(true)
|
||||
switch mode {
|
||||
case "NOW":
|
||||
command.RestartWireguard(false, filepath)
|
||||
case "DELAY":
|
||||
time.Sleep(time.Duration(delay) * time.Second)
|
||||
command.RestartWireguard(true, filepath)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 打印监听事件
|
||||
@ -78,8 +169,8 @@ func (wireguard) watchListConfig(filePath string) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err = watcher.Add(filePath); err != nil {
|
||||
log.Errorf("添加[%s]监听失败: %v", filePath, err.Error())
|
||||
if err = watcher.Add(filepath); err != nil {
|
||||
log.Errorf("添加[%s]监听失败: %v", filepath, err.Error())
|
||||
return
|
||||
}
|
||||
<-done
|
||||
|
@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
type redis struct {
|
||||
type cache struct {
|
||||
Type string `yaml:"type"`
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
Password string `yaml:"password"`
|
@ -5,8 +5,7 @@ var Config *config
|
||||
type config struct {
|
||||
Http *http `yaml:"http"`
|
||||
Database *database `yaml:"database"`
|
||||
Redis *redis `yaml:"redis"`
|
||||
Cache *cache `yaml:"redis"`
|
||||
File *file `yaml:"file"`
|
||||
Mail *mail `yaml:"email"`
|
||||
Wireguard *wireguard `yaml:"wireguard"`
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
package config
|
||||
|
||||
type mail struct {
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Port int `json:"port" yaml:"port"`
|
||||
User string `json:"user" yaml:"user"`
|
||||
Password string `json:"password" yaml:"password"`
|
||||
SkipTls bool `json:"skipTls" yaml:"skipTls"`
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package config
|
||||
|
||||
type wireguard struct {
|
||||
ListenConfig string `json:"listenConfig" yaml:"listenConfig"`
|
||||
RestartMode string `json:"restartMode"` // 重启模式 NOW - 立即重启 | DELAY - 延时 | HAND - 手动重启
|
||||
DelayTime int64 `json:"delayTime"` // 延时重启的间隔(单位:秒)
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
Token = "token" // 登陆token
|
||||
Captcha = "captcha" // 验证码
|
||||
)
|
||||
|
||||
const (
|
||||
SyncWgConfigFile = "queues:wg:sync-file"
|
||||
)
|
@ -1 +0,0 @@
|
||||
package constant
|
19
cron/cron.go
Normal file
@ -0,0 +1,19 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/go-co-op/gocron/v2"
|
||||
"time"
|
||||
"wireguard-ui/cron/task"
|
||||
)
|
||||
|
||||
func Task() {
|
||||
sch, err := gocron.NewScheduler(gocron.WithLocation(time.Local))
|
||||
if err != nil {
|
||||
log.Errorf("初始化定时任务失败")
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = sch.NewJob(gocron.DurationJob(1*time.Minute), gocron.NewTask(task.NetworkClient().ClientOfflineNotify)) // 每分钟执行一次
|
||||
sch.Start()
|
||||
}
|
119
cron/task/client.go
Normal file
@ -0,0 +1,119 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"golang.org/x/exp/slices"
|
||||
"strings"
|
||||
"time"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/global/client"
|
||||
"wireguard-ui/global/constant"
|
||||
"wireguard-ui/model"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type NetworkClientImpl interface {
|
||||
ClientOfflineNotify() // 客户端离线通知
|
||||
}
|
||||
|
||||
type networkClient struct{}
|
||||
|
||||
func NetworkClient() NetworkClientImpl {
|
||||
return networkClient{}
|
||||
}
|
||||
|
||||
// ClientOfflineNotify
|
||||
// @description: 客户端离线通知
|
||||
// @receiver c
|
||||
// @return error
|
||||
func (c networkClient) ClientOfflineNotify() {
|
||||
log.Debugf("开始执行离线通知任务")
|
||||
// 开始扫描已经链接过的客户端
|
||||
connectedPeers, err := component.Wireguard().GetClients()
|
||||
if err != nil {
|
||||
log.Errorf("获取已连接客户端失败: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 查询一下通知配置
|
||||
code, err := service.Setting().GetByCode("WECHAT_NOTIFY")
|
||||
if err != nil {
|
||||
log.Errorf("获取微信通知配置失败: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 查询出所有配置了离线通知的客户端
|
||||
var clients []model.Client
|
||||
if err := client.DB.Where("offline_monitoring = ?", 1).Find(&clients).Error; err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(clients) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, peer := range connectedPeers {
|
||||
var clientName string
|
||||
if !slices.ContainsFunc(clients, func(cli model.Client) bool {
|
||||
isExist := peer.PublicKey.String() == jsoniter.Get([]byte(cli.Keys), "publicKey").ToString()
|
||||
if isExist {
|
||||
clientName = cli.Name
|
||||
}
|
||||
return isExist
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
|
||||
online := time.Since(peer.LastHandshakeTime).Minutes() < 3
|
||||
log.Debugf("客户端[%v]在线状态: %v,离线时间: %v", clientName, online, time.Since(peer.LastHandshakeTime).Minutes())
|
||||
|
||||
var ipAllocation string
|
||||
for _, iaip := range peer.AllowedIPs {
|
||||
ipAllocation += iaip.String() + ","
|
||||
}
|
||||
// 去除一下最右边的逗号
|
||||
if len(ipAllocation) > 0 {
|
||||
ipAllocation = strings.TrimRight(ipAllocation, ",")
|
||||
}
|
||||
|
||||
// 如果存在,判断离线时间
|
||||
if !online {
|
||||
// 已经离线,发送通知
|
||||
msg := fmt.Sprintf(`[离线通知]
|
||||
客户端名称 : %v
|
||||
客户端IP : %v
|
||||
最后在线时间 : %v`, clientName, ipAllocation, peer.LastHandshakeTime.Format("2006-01-02 15:04:05"))
|
||||
err := utils.WechatNotify(code).SendTextMessage(msg)
|
||||
if err != nil {
|
||||
log.Errorf("微信消息[%v]通知失败: %v", msg, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// 离线了,设置离线标识
|
||||
client.Redis.Set(context.Background(), fmt.Sprintf("%s%s", constant.ClientOffline, utils.Hash().MD5(ipAllocation)), true, 0)
|
||||
} else {
|
||||
// 判断是否存在缓存
|
||||
if client.Redis.Exists(context.Background(), fmt.Sprintf("%s%s", constant.ClientOffline, utils.Hash().MD5(ipAllocation))).Val() > 0 {
|
||||
// 存在,删除离线标识
|
||||
client.Redis.Del(context.Background(), fmt.Sprintf("%s%s", constant.ClientOffline, utils.Hash().MD5(ipAllocation)))
|
||||
// 微信通知该客户端已经上线
|
||||
msg := fmt.Sprintf(`[上线通知]
|
||||
客户端名称 : %v
|
||||
客户端IP : %v
|
||||
最后在线时间 : %v`, clientName, ipAllocation, peer.LastHandshakeTime.Format("2006-01-02 15:04:05"))
|
||||
err := utils.WechatNotify(code).SendTextMessage(msg)
|
||||
if err != nil {
|
||||
log.Errorf("微信消息[%v]通知失败: %v", msg, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package cron_task
|
||||
|
||||
import (
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/go-co-op/gocron/v2"
|
||||
"time"
|
||||
)
|
||||
|
||||
func StartCronTask() {
|
||||
s, err := gocron.NewScheduler()
|
||||
if err != nil {
|
||||
log.Errorf("初始化定时任务失败: %v", err.Error())
|
||||
return
|
||||
}
|
||||
_, _ = s.NewJob(gocron.DurationJob(time.Hour), gocron.NewTask(offlineMonitoring)) // 每小时执行一次
|
||||
s.Start()
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package cron_task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"strings"
|
||||
"time"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/repository"
|
||||
"wireguard-dashboard/utils"
|
||||
)
|
||||
|
||||
// offlineMonitoring
|
||||
// @description: 离线监听任务
|
||||
func offlineMonitoring() {
|
||||
devices, err := client.WireguardClient.Devices()
|
||||
if err != nil {
|
||||
time.Sleep(5 * time.Minute) // 休眠五分钟再执行
|
||||
offlineMonitoring()
|
||||
return
|
||||
}
|
||||
|
||||
// 遍历客户端数据,并渲染数据信息
|
||||
for _, d := range devices {
|
||||
for _, p := range d.Peers {
|
||||
var ipAllocation string
|
||||
for _, iaip := range p.AllowedIPs {
|
||||
ipAllocation += iaip.String() + ","
|
||||
}
|
||||
ipAllocation = strings.TrimRight(ipAllocation, ",")
|
||||
isOnline := time.Since(p.LastHandshakeTime).Minutes() < 3
|
||||
|
||||
// 未离线
|
||||
if isOnline {
|
||||
continue
|
||||
}
|
||||
|
||||
clientInfo, err := repository.Client().GetByPublicKey(p.PublicKey.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 没有启用离线监听时,即使客户端已经离线则也不执行
|
||||
if clientInfo.OfflineMonitoring == nil || *clientInfo.OfflineMonitoring != 1 || clientInfo.Email == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
content := fmt.Sprintf("客户端:%s\r\n", clientInfo.Name)
|
||||
content += fmt.Sprintf("客户端IP:%s\r\n", ipAllocation)
|
||||
content += fmt.Sprintf("端点IP:%s\r\n", p.Endpoint.String())
|
||||
content += fmt.Sprintf("最后握手时间:%s\r\n", p.LastHandshakeTime.Format("2006-01-02 15:04:05"))
|
||||
|
||||
// 离线并且配置了邮箱,准备发送邮件
|
||||
err = utils.Mail().SendMail(clientInfo.Email, fmt.Sprintf("客户端[%s]离线通知", clientInfo.Name), content, "")
|
||||
if err != nil {
|
||||
log.Errorf("发送离线通知邮件失败: %v", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
6
dist/static.go
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
package dist
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed index.html favicon.png favicon.svg assets resource
|
||||
var Static embed.FS
|
BIN
document/img.png
Normal file
After Width: | Height: | Size: 209 KiB |
BIN
document/img_1.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
document/img_2.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
document/img_3.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
document/img_4.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
document/img_5.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
document/img_6.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
document/img_7.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
document/img_8.png
Normal file
After Width: | Height: | Size: 55 KiB |
22
global/constant/common_constant.go
Normal file
@ -0,0 +1,22 @@
|
||||
package constant
|
||||
|
||||
// Status 启用禁用
|
||||
type Status int
|
||||
|
||||
const (
|
||||
Disabled Status = iota
|
||||
Enabled
|
||||
)
|
||||
|
||||
var StatusMap = map[Status]string{
|
||||
Disabled: "禁用",
|
||||
Enabled: "启用",
|
||||
}
|
||||
|
||||
func (u Status) String() string {
|
||||
if v, ok := StatusMap[u]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return "未知类型"
|
||||
}
|
8
global/constant/redis_key.go
Normal file
@ -0,0 +1,8 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
Captcha = "captcha"
|
||||
UserToken = "token"
|
||||
TUIUserToken = "tui:token"
|
||||
ClientOffline = "client:offline:"
|
||||
)
|
@ -1,5 +1,6 @@
|
||||
package constant
|
||||
|
||||
// UserType 用户类型
|
||||
type UserType int
|
||||
|
||||
const (
|
||||
@ -19,23 +20,3 @@ func (u UserType) String() string {
|
||||
|
||||
return "未知类型"
|
||||
}
|
||||
|
||||
type UserStatus int
|
||||
|
||||
const (
|
||||
Disabled UserStatus = iota
|
||||
Normal
|
||||
)
|
||||
|
||||
var UserStatusMap = map[UserStatus]string{
|
||||
Disabled: "禁用",
|
||||
Normal: "正常",
|
||||
}
|
||||
|
||||
func (u UserStatus) String() string {
|
||||
if v, ok := UserStatusMap[u]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return "未知类型"
|
||||
}
|
7
global/constant/wireguard_constant.go
Normal file
@ -0,0 +1,7 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
DefaultPostUpScript = "iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE"
|
||||
DefaultPostDownScript = "iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE"
|
||||
DefaultPreDownScript = ""
|
||||
)
|
101
go.mod
@ -1,61 +1,71 @@
|
||||
module wireguard-dashboard
|
||||
module wireguard-ui
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
gitee.ltd/lxh/logger v1.0.15
|
||||
gitee.ltd/lxh/logger v1.0.18
|
||||
github.com/charmbracelet/bubbles v0.20.0
|
||||
github.com/charmbracelet/lipgloss v0.13.0
|
||||
github.com/cowardmrx/go_aliyun_oss v1.0.7
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-co-op/gocron/v2 v2.5.0
|
||||
github.com/go-resty/resty/v2 v2.11.0
|
||||
github.com/go-co-op/gocron/v2 v2.12.4
|
||||
github.com/go-playground/locales v0.14.1
|
||||
github.com/go-playground/universal-translator v0.18.1
|
||||
github.com/go-playground/validator/v10 v10.22.0
|
||||
github.com/go-resty/resty/v2 v2.13.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/mojocn/base64Captcha v1.3.6
|
||||
github.com/redis/go-redis/v9 v9.5.1
|
||||
github.com/redis/go-redis/v9 v9.7.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/spf13/cast v1.6.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
gopkg.in/fsnotify.v1 v1.4.7
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/mysql v1.5.4
|
||||
gorm.io/driver/postgres v1.5.6
|
||||
gorm.io/gorm v1.25.7
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/gorm v1.25.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.5 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.0 // indirect
|
||||
github.com/caarlos0/env/v6 v6.10.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.1.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.2.3 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/pprof v1.5.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-kit/kit v0.12.0 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||
@ -68,7 +78,11 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lixh00/loki-client-go v1.0.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
@ -76,8 +90,12 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
|
||||
github.com/panjf2000/ants/v2 v2.10.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||
@ -86,28 +104,37 @@ require (
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/prometheus/prometheus v1.8.2-0.20201028100903-3245b3267b24 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
|
||||
golang.org/x/image v0.15.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/oauth2 v0.1.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/oauth2 v0.18.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 // indirect
|
||||
google.golang.org/grpc v1.50.1 // indirect
|
||||
google.golang.org/protobuf v1.34.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||
google.golang.org/grpc v1.62.1 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
|
238
go.sum
@ -35,8 +35,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gitee.ltd/lxh/logger v1.0.15 h1:x/HIHujq01ofltBK6WduYs890hiJVlU2ES6ytKusVZA=
|
||||
gitee.ltd/lxh/logger v1.0.15/go.mod h1:Ef3o9duDaGApRUlibvkG92QBFM3HcndI0U30n/SMfYk=
|
||||
gitee.ltd/lxh/logger v1.0.18 h1:LKc0Glk+jx08qDaX1kYIEHZUxUJtV3uqP5ceYK352YY=
|
||||
gitee.ltd/lxh/logger v1.0.18/go.mod h1:hzd8prYXbac9YMaEDFMHYHMKKm1LqiFVtHjENWi+qQE=
|
||||
github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
@ -60,8 +60,8 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
@ -106,7 +106,13 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.35.5/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@ -120,13 +126,11 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic v1.12.5 h1:hoZxY8uW+mT+OpkcUWw4k0fDINtOcVavEsGfzwzFU/w=
|
||||
github.com/bytedance/sonic v1.12.5/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
|
||||
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
|
||||
github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
|
||||
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
|
||||
@ -139,9 +143,18 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
|
||||
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
||||
github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c=
|
||||
github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
|
||||
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
||||
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
||||
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
|
||||
github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
|
||||
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@ -163,11 +176,14 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
|
||||
github.com/cowardmrx/go_aliyun_oss v1.0.7 h1:MCSKUWi4RZnHhwe4fd7VAsgeRXL0kT9z56TTde+1lME=
|
||||
github.com/cowardmrx/go_aliyun_oss v1.0.7/go.mod h1:xz6B8H840TX7yPcgSLUbK7q6nnEsxFutaltR08Aetdg=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
@ -189,6 +205,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
|
||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
@ -196,6 +214,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
@ -209,18 +229,14 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/pprof v1.5.0 h1:E/Oy7g+kNw94KfdCy3bZxQFtyDnAX2V7axRS7sNYVrU=
|
||||
github.com/gin-contrib/pprof v1.5.0/go.mod h1:GqFL6LerKoCQ/RSWnkYczkTJ+tOAUVN/8sbnEtaqOKs=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
@ -229,8 +245,8 @@ github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||
github.com/go-co-op/gocron/v2 v2.5.0 h1:ff/TJX9GdTJBDL1il9cyd/Sj3WnS+BB7ZzwHKSNL5p8=
|
||||
github.com/go-co-op/gocron/v2 v2.5.0/go.mod h1:ckPQw96ZuZLRUGu88vVpd9a6d9HakI14KWahFZtGvNw=
|
||||
github.com/go-co-op/gocron/v2 v2.12.4 h1:h1HWApo3T+61UrZqEY2qG1LUpDnB7tkYITxf6YIK354=
|
||||
github.com/go-co-op/gocron/v2 v2.12.4/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@ -314,12 +330,10 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
||||
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
|
||||
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
@ -391,8 +405,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
@ -411,8 +426,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@ -474,6 +489,8 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||
@ -548,8 +565,6 @@ github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
|
||||
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
|
||||
@ -570,8 +585,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
@ -579,7 +592,11 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lixh00/loki-client-go v1.0.1 h1:y/ePf/s66N77eikIujRS/QQAKvbMmPmesMxAuMuP8lM=
|
||||
github.com/lixh00/loki-client-go v1.0.1/go.mod h1:JSeu3fIBPjnmf5bBq6I8hvJlhYum2eLQEzwU149vyfQ=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
@ -598,12 +615,14 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@ -645,6 +664,12 @@ github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DG
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
@ -688,13 +713,13 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8=
|
||||
github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
@ -709,8 +734,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/prometheus/alertmanager v0.21.0/go.mod h1:h7tJ81NA0VLWvWEayi1QltevFkLF3KxmC/malTcT8Go=
|
||||
@ -758,12 +784,15 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua
|
||||
github.com/prometheus/prometheus v1.8.2-0.20201028100903-3245b3267b24 h1:V/4Cj2GytqdqK7OMEz6c4LNjey3SNyfw3pg5jPKtJvQ=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20201028100903-3245b3267b24/go.mod h1:MDRkz271loM/PrYN+wUNEaTMDGSP760MQzB0yEjdgSQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
|
||||
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
@ -771,12 +800,18 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
@ -799,8 +834,12 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
@ -808,7 +847,10 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||
@ -829,10 +871,11 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
@ -841,12 +884,12 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||
@ -854,6 +897,8 @@ github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHM
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
||||
github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@ -883,17 +928,14 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@ -914,11 +956,10 @@ golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -932,14 +973,14 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@ -1008,11 +1049,10 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -1021,8 +1061,8 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y=
|
||||
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -1037,8 +1077,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -1102,24 +1143,24 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -1127,20 +1168,21 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -1248,8 +1290,8 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@ -1284,8 +1326,8 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55 h1:U1u4KB2kx6KR/aJDjQ97hZ15wQs8ZPvDcGcRynBhkvg=
|
||||
google.golang.org/genproto v0.0.0-20221018160656-63c7b68cfc55/go.mod h1:45EK0dUbEZ2NHjCeAd2LXmyjAgGUGrpGROgjhC3ADck=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
@ -1305,8 +1347,8 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
|
||||
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -1319,10 +1361,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
|
||||
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@ -1331,11 +1371,13 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo=
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
@ -1355,13 +1397,13 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.4 h1:igQmHfKcbaTVyAIHNhhB888vvxh8EdQ2uSUT0LPcBso=
|
||||
gorm.io/driver/mysql v1.5.4/go.mod h1:9rYxJph/u9SWkWc9yY4XJ1F/+xO0S/ChOmbk3+Z5Tvs=
|
||||
gorm.io/driver/postgres v1.5.6 h1:ydr9xEd5YAM0vxVDY0X139dyzNz10spDiDlC7+ibLeU=
|
||||
gorm.io/driver/postgres v1.5.6/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
20
http/api/api.go
Normal file
@ -0,0 +1,20 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-ui/http/response"
|
||||
"wireguard-ui/http/vo"
|
||||
)
|
||||
|
||||
// GetCurrentLoginUser
|
||||
// @description: 获取当前登陆用户
|
||||
// @param c
|
||||
// @return *vo.User
|
||||
func GetCurrentLoginUser(c *gin.Context) *vo.User {
|
||||
if user, ok := c.Get("user"); ok {
|
||||
return user.(*vo.User)
|
||||
}
|
||||
response.R(c).AuthorizationFailed("暂未登陆")
|
||||
c.Abort()
|
||||
return nil
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
"wireguard-dashboard/component"
|
||||
"wireguard-dashboard/utils"
|
||||
)
|
||||
|
||||
type captcha struct{}
|
||||
|
||||
func Captcha() captcha {
|
||||
return captcha{}
|
||||
}
|
||||
|
||||
// GenerateCaptcha
|
||||
// @description: 生成验证码
|
||||
// @receiver captcha
|
||||
// @param c
|
||||
func (captcha) GenerateCaptcha(c *gin.Context) {
|
||||
math := base64Captcha.DriverMath{Height: 120, Width: 480, Fonts: []string{"ApothecaryFont.ttf", "3Dumb.ttf"}}
|
||||
mathDriver := math.ConvertFonts()
|
||||
|
||||
capt := base64Captcha.NewCaptcha(mathDriver, component.CaptchaStore{})
|
||||
|
||||
id, base64Str, _, err := capt.Generate()
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("生成验证码失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OKWithData(map[string]any{
|
||||
"id": id,
|
||||
"captcha": base64Str,
|
||||
})
|
||||
}
|
@ -9,417 +9,257 @@ import (
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/http/param"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/model/template_data"
|
||||
"wireguard-dashboard/model/vo"
|
||||
"wireguard-dashboard/queues"
|
||||
"wireguard-dashboard/repository"
|
||||
"wireguard-dashboard/utils"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/http/param"
|
||||
"wireguard-ui/http/response"
|
||||
"wireguard-ui/http/vo"
|
||||
"wireguard-ui/model"
|
||||
"wireguard-ui/script"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type clients struct{}
|
||||
type ClientApi struct{}
|
||||
|
||||
func Client() clients {
|
||||
return clients{}
|
||||
}
|
||||
|
||||
// List
|
||||
// @description: 客户端列表
|
||||
// @receiver clients
|
||||
// @param c
|
||||
func (clients) List(c *gin.Context) {
|
||||
var p param.ClientList
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
return
|
||||
}
|
||||
|
||||
data, total, err := repository.Client().List(p)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("获取失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OkWithPage(data, total, p.Current, p.Size)
|
||||
func Client() ClientApi {
|
||||
return ClientApi{}
|
||||
}
|
||||
|
||||
// Save
|
||||
// @description: 新增/更新客户端
|
||||
// @receiver clients
|
||||
// @description: 新增/编辑客户端
|
||||
// @param c
|
||||
func (clients) Save(c *gin.Context) {
|
||||
func (ClientApi) Save(c *gin.Context) {
|
||||
var p param.SaveClient
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
response.R(c).Validator(err)
|
||||
return
|
||||
}
|
||||
|
||||
info, ok := c.Get("user")
|
||||
if !ok {
|
||||
utils.GinResponse(c).FailedWithMsg("获取信息失败")
|
||||
var loginUser *vo.User
|
||||
if loginUser = GetCurrentLoginUser(c); c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
_, err := repository.Client().Save(p, info.(*entity.User).Id)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("操作失败", err)
|
||||
if err := service.Client().SaveClient(p, loginUser); err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err = queues.PutAsyncWireguardConfigFile(p.ServerId); err != nil {
|
||||
log.Errorf("[新增/编辑客户端]同步配置文件失败: %v", err.Error())
|
||||
if err := script.New().GenerateConfig(); err != nil {
|
||||
log.Errorf("执行脚本失败")
|
||||
}
|
||||
}()
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
||||
// AssignIPAndAllowedIP
|
||||
// @description: 分配客户端IP和允许访问的IP段
|
||||
// @receiver clients
|
||||
// Delete
|
||||
// @description: 删除客户端
|
||||
// @receiver ClientApi
|
||||
// @param c
|
||||
func (clients) AssignIPAndAllowedIP(c *gin.Context) {
|
||||
var p param.AssignIPAndAllowedIP
|
||||
func (ClientApi) Delete(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" || id == "undefined" {
|
||||
response.R(c).FailedWithError("id不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if err := service.Client().Delete(id); err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := script.New().GenerateConfig(); err != nil {
|
||||
log.Errorf("执行脚本失败")
|
||||
}
|
||||
}()
|
||||
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
||||
// List
|
||||
// @description: 客户端分页列表
|
||||
// @receiver ClientApi
|
||||
// @param c
|
||||
func (ClientApi) List(c *gin.Context) {
|
||||
var p param.ClientList
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
response.R(c).Validator(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取一下服务端信息,因为IP分配需要根据服务端的IP制定
|
||||
serverInfo, err := repository.Server().GetServer()
|
||||
data, total, err := service.Client().List(p)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("获取服务端信息失败", err)
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
var assignIPS []string
|
||||
assignIPS = append(assignIPS, serverInfo.IpScope)
|
||||
switch p.Rule {
|
||||
case "AUTO":
|
||||
// 只获取最新的一个
|
||||
var clientInfo *entity.Client
|
||||
if err = repository.Client().Order("created_at DESC").Take(&clientInfo).Error; err == nil {
|
||||
if cast.ToInt64(utils.Wireguard().GetIPSuffix(clientInfo.IpAllocation)) >= 255 {
|
||||
utils.GinResponse(c).FailedWithMsg("当前IP分配错误,请手动进行分配")
|
||||
return
|
||||
}
|
||||
assignIPS = append(assignIPS, clientInfo.IpAllocation)
|
||||
}
|
||||
|
||||
case "RANDOM":
|
||||
// 查询全部客户端不管是禁用还是没禁用的
|
||||
var clientsInfo []entity.Client
|
||||
if err = repository.Client().Find(&clientsInfo).Error; err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("获取失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range clientsInfo {
|
||||
assignIPS = append(assignIPS, v.IpAllocation)
|
||||
}
|
||||
}
|
||||
|
||||
clientIP := utils.Wireguard().GenerateClientIP(serverInfo.IpScope, p.Rule, assignIPS...)
|
||||
|
||||
utils.GinResponse(c).OKWithData(map[string]any{
|
||||
"clientIP": []string{fmt.Sprintf("%s/32", clientIP)},
|
||||
"serverIP": []string{serverInfo.IpScope},
|
||||
})
|
||||
response.R(c).Paginate(data, total, p.Current, p.Size)
|
||||
}
|
||||
|
||||
// GenerateKeys
|
||||
// @description: 生成密钥对
|
||||
// @receiver clients
|
||||
// @description: 生成客户端密钥信息
|
||||
// @receiver ClientApi
|
||||
// @param c
|
||||
func (clients) GenerateKeys(c *gin.Context) {
|
||||
func (ClientApi) GenerateKeys(c *gin.Context) {
|
||||
// 为空,新增
|
||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("生成密钥对失败", err)
|
||||
response.R(c).FailedWithError(fmt.Errorf("生成密钥失败: %v", err.Error()))
|
||||
return
|
||||
}
|
||||
publicKey := privateKey.PublicKey().String()
|
||||
presharedKey, err := wgtypes.GenerateKey()
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError(fmt.Errorf("生成密钥失败: %v", err.Error()))
|
||||
return
|
||||
}
|
||||
keys := template_data.Keys{
|
||||
keys := vo.Keys{
|
||||
PrivateKey: privateKey.String(),
|
||||
PublicKey: publicKey,
|
||||
PresharedKey: presharedKey.String(),
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OKWithData(keys)
|
||||
response.R(c).OkWithData(keys)
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @description: 删除客户端
|
||||
// @receiver clients
|
||||
// GenerateIP
|
||||
// @description: 生成客户端IP
|
||||
// @receiver ClientApi
|
||||
// @param c
|
||||
func (clients) Delete(c *gin.Context) {
|
||||
var id = c.Param("id")
|
||||
if id == "" || id == "undefined" {
|
||||
utils.GinResponse(c).FailedWithMsg("参数错误")
|
||||
func (ClientApi) GenerateIP(c *gin.Context) {
|
||||
// 获取一下服务端信息,因为IP分配需要根据服务端的IP制定
|
||||
serverInfo, err := service.Setting().GetWGServerForConfig()
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("获取服务端信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err := repository.Client().Delete(id); err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("操作失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 再同步一下配置文件
|
||||
go func() {
|
||||
if err := queues.PutAsyncWireguardConfigFile(""); err != nil {
|
||||
log.Errorf("[下线客户端]同步配置文件失败: %v", err.Error())
|
||||
var assignIPS []string
|
||||
// 只获取最新的一个
|
||||
var clientInfo *model.Client
|
||||
if err = service.Client().Order("created_at DESC").Take(&clientInfo).Error; err == nil {
|
||||
// 遍历每一个ip是否可允许再分配
|
||||
for _, ip := range strings.Split(clientInfo.IpAllocation, ",") {
|
||||
if cast.ToInt64(utils.Network().GetIPSuffix(ip)) >= 255 {
|
||||
log.Errorf("IP:[%s]已无法分配新IP", ip)
|
||||
continue
|
||||
} else {
|
||||
assignIPS = append(assignIPS, ip)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
}
|
||||
|
||||
// Download
|
||||
// @description: 下载配置文件
|
||||
// @receiver clients
|
||||
// @param c
|
||||
func (clients) Download(c *gin.Context) {
|
||||
var id = c.Param("id")
|
||||
if id == "" || id == "undefined" {
|
||||
utils.GinResponse(c).FailedWithMsg("参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
data, err := repository.Client().GetById(id)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("获取失败")
|
||||
return
|
||||
}
|
||||
ips := utils.Network().GenerateIPByIPS(serverInfo.Address, assignIPS...)
|
||||
|
||||
var keys template_data.Keys
|
||||
_ = json.Unmarshal([]byte(data.Keys), &keys)
|
||||
|
||||
serverSetting, err := repository.System().GetServerSetting()
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("获取设置失败")
|
||||
return
|
||||
}
|
||||
|
||||
outPath, err := utils.Wireguard().GenerateClientFile(&data, serverSetting)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("生成失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 输出文件流
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", "attachment; filename="+outPath)
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
c.Header("Connection", "keep-alive")
|
||||
c.File(outPath)
|
||||
if err = os.Remove(outPath); err != nil {
|
||||
log.Errorf("删除临时文件失败: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateQrCode
|
||||
// @description: 生成客户端信息二维码
|
||||
// @receiver clients
|
||||
// @param c
|
||||
func (clients) GenerateQrCode(c *gin.Context) {
|
||||
var id = c.Param("id")
|
||||
if id == "" || id == "undefined" {
|
||||
utils.GinResponse(c).FailedWithMsg("参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
data, err := repository.Client().GetById(id)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("获取失败")
|
||||
return
|
||||
}
|
||||
|
||||
var keys template_data.Keys
|
||||
_ = json.Unmarshal([]byte(data.Keys), &keys)
|
||||
|
||||
serverSetting, err := repository.System().GetServerSetting()
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("获取设置失败")
|
||||
return
|
||||
}
|
||||
|
||||
outPath, err := utils.Wireguard().GenerateClientFile(&data, serverSetting)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("生成失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
fileContent, err := os.ReadFile(outPath)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("读取文件失败")
|
||||
return
|
||||
}
|
||||
|
||||
png, err := utils.QRCode().GenerateQrCodeBase64(fileContent, 256)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("生成二维码失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.Remove(outPath); err != nil {
|
||||
log.Errorf("删除临时文件失败: %s", err.Error())
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OKWithData(map[string]interface{}{
|
||||
"qrCode": png,
|
||||
clientIPS := ips
|
||||
serverIPS := serverInfo.Address
|
||||
response.R(c).OkWithData(map[string]any{
|
||||
"clientIPS": clientIPS,
|
||||
"serverIPS": serverIPS,
|
||||
})
|
||||
}
|
||||
|
||||
// SendEmail
|
||||
// @description: 发送邮件
|
||||
// @receiver clients
|
||||
// Download
|
||||
// @description: 下载客户端配置文件
|
||||
// @receiver ClientApi
|
||||
// @param c
|
||||
func (clients) SendEmail(c *gin.Context) {
|
||||
func (ClientApi) Download(c *gin.Context) {
|
||||
var id = c.Param("id")
|
||||
if id == "" || id == "undefined" {
|
||||
utils.GinResponse(c).FailedWithMsg("id不能为空")
|
||||
response.R(c).FailedWithError("id不能为空")
|
||||
return
|
||||
}
|
||||
var downloadType = c.Param("type")
|
||||
if downloadType == "" {
|
||||
response.R(c).FailedWithError("参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 先校验一下邮箱发送是否可用
|
||||
if err := utils.Mail().VerifyConfig(); err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 获取该客户端信息
|
||||
clientInfo, err := repository.Client().GetById(id)
|
||||
data, err := service.Client().GetByID(id)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("获取失败", err)
|
||||
response.R(c).FailedWithError("获取客户端信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
if clientInfo.Email == "" {
|
||||
utils.GinResponse(c).FailedWithMsg("当前客户端未配置联系邮箱!")
|
||||
return
|
||||
}
|
||||
var keys vo.Keys
|
||||
_ = json.Unmarshal([]byte(data.Keys), &keys)
|
||||
|
||||
serverSetting, err := repository.System().GetServerSetting()
|
||||
globalSet, err := service.Setting().GetWGSetForConfig()
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("获取设置失败")
|
||||
response.R(c).FailedWithError("获取失败")
|
||||
return
|
||||
}
|
||||
|
||||
outPath, err := utils.Wireguard().GenerateClientFile(&clientInfo, serverSetting)
|
||||
serverConf, err := service.Setting().GetWGServerForConfig()
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("生成失败", err)
|
||||
response.R(c).FailedWithError("获取失败")
|
||||
return
|
||||
}
|
||||
|
||||
err = utils.Mail().SendMail(clientInfo.Email, fmt.Sprintf("客户端: %s", clientInfo.Name), "请查收附件", outPath)
|
||||
outPath, err := component.Wireguard().GenerateClientFile(data, serverConf, globalSet)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("发送邮件失败", err)
|
||||
response.R(c).FailedWithError(fmt.Errorf("生成失败: %v", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.Remove(outPath); err != nil {
|
||||
log.Errorf("删除临时文件失败: %s", err.Error())
|
||||
}
|
||||
utils.GinResponse(c).OK()
|
||||
}
|
||||
|
||||
// Status
|
||||
// @description: 获取客户端状态信息,链接状态等
|
||||
// @receiver clients
|
||||
// @param c
|
||||
func (clients) Status(c *gin.Context) {
|
||||
// 使用sdk拉取一下客户端信息
|
||||
devices, err := client.WireguardClient.Devices()
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("获取客户端信息失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
var data []vo.ClientStatus
|
||||
// 遍历客户端数据,并渲染数据信息
|
||||
for _, d := range devices {
|
||||
for _, p := range d.Peers {
|
||||
clientInfo, err := repository.Client().GetByPublicKey(p.PublicKey.String())
|
||||
if err != nil {
|
||||
log.Errorf("没有找到公钥匹配的客户端: %s", p.PublicKey.String())
|
||||
continue
|
||||
}
|
||||
var ipAllocation string
|
||||
for _, iaip := range p.AllowedIPs {
|
||||
ipAllocation += iaip.String() + ","
|
||||
}
|
||||
ipAllocation = strings.TrimRight(ipAllocation, ",")
|
||||
isOnline := time.Since(p.LastHandshakeTime).Minutes() < 3
|
||||
data = append(data, vo.ClientStatus{
|
||||
ID: clientInfo.Id,
|
||||
Name: clientInfo.Name,
|
||||
Email: clientInfo.Email,
|
||||
IpAllocation: ipAllocation,
|
||||
Endpoint: p.Endpoint.String(),
|
||||
Received: utils.FlowCalculation().Parse(p.ReceiveBytes),
|
||||
Transmitted: utils.FlowCalculation().Parse(p.TransmitBytes),
|
||||
IsOnline: isOnline,
|
||||
LastHandShake: p.LastHandshakeTime.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
// 根据不同下载类型执行不同逻辑
|
||||
switch downloadType {
|
||||
case "QRCODE": // 二维码
|
||||
// 读取文件内容
|
||||
fileContent, err := os.ReadFile(outPath)
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("读取文件失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OKWithData(data)
|
||||
}
|
||||
|
||||
// Offline
|
||||
// @description: 强制下线指定客户端
|
||||
// @receiver clients
|
||||
// @param c
|
||||
func (clients) Offline(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" || id == "undefined" {
|
||||
utils.GinResponse(c).FailedWithMsg("参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 查询一下客户端信息
|
||||
clientInfo, err := repository.Client().GetById(id)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("获取信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
keys := template_data.Keys{}
|
||||
_ = json.Unmarshal([]byte(clientInfo.Keys), &keys)
|
||||
|
||||
connectInfo, err := utils.Wireguard().GetSpecClient(keys.PublicKey)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("获取客户端信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
if connectInfo == nil {
|
||||
utils.GinResponse(c).FailedWithMsg("未获取到该客户端链接信息")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取到了,执行踢下线操作。此处踢下线就是禁用该客户端
|
||||
if err = repository.Client().Disabled(clientInfo.Id); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("客户端下线失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 再同步一下配置文件
|
||||
go func() {
|
||||
if err = queues.PutAsyncWireguardConfigFile(clientInfo.ServerId); err != nil {
|
||||
log.Errorf("[下线客户端]同步配置文件失败: %v", err.Error())
|
||||
png, err := utils.QRCode().GenerateQrCodeBase64(fileContent, 256)
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("生成二维码失败")
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
if err = os.Remove(outPath); err != nil {
|
||||
log.Errorf("删除临时文件失败: %s", err.Error())
|
||||
}
|
||||
|
||||
response.R(c).OkWithData(map[string]interface{}{
|
||||
"qrCode": png,
|
||||
})
|
||||
case "FILE": // 文件
|
||||
// 输出文件流
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", "attachment; filename="+outPath)
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
c.Header("Connection", "keep-alive")
|
||||
c.File(outPath)
|
||||
if err = os.Remove(outPath); err != nil {
|
||||
log.Errorf("删除临时文件失败: %s", err.Error())
|
||||
}
|
||||
case "EMAIL": // 邮件
|
||||
if data.Email == "" {
|
||||
response.R(c).FailedWithError("当前客户端并未配置通知邮箱!")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取邮箱配置
|
||||
emailConf, err := service.Setting().GetByCode("EMAIL_SMTP")
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("获取邮箱配置失败,请先到设置页面的【其他】里面添加code为【EMAIL_SMTP】的具体配置")
|
||||
return
|
||||
}
|
||||
|
||||
err = utils.Mail(emailConf).SendMail(data.Email, fmt.Sprintf("客户端: %s", data.Name), "请查收附件", outPath)
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("发送邮件失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.Remove(outPath); err != nil {
|
||||
log.Errorf("删除临时文件失败: %s", err.Error())
|
||||
}
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,42 +1,102 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-dashboard/http/param"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/repository"
|
||||
"wireguard-dashboard/utils"
|
||||
"strings"
|
||||
"time"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/http/param"
|
||||
"wireguard-ui/http/response"
|
||||
"wireguard-ui/http/vo"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type dashboard struct{}
|
||||
type DashboardApi struct{}
|
||||
|
||||
func Dashboard() dashboard {
|
||||
return dashboard{}
|
||||
func Dashboard() DashboardApi {
|
||||
return DashboardApi{}
|
||||
}
|
||||
|
||||
// List
|
||||
// @description: 操作日志分页列表
|
||||
// @receiver d
|
||||
// @description: 操作日志
|
||||
// @receiver DashboardApi
|
||||
// @param c
|
||||
func (d dashboard) List(c *gin.Context) {
|
||||
var p param.OnlyPage
|
||||
func (DashboardApi) List(c *gin.Context) {
|
||||
var p param.Page
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
response.R(c).Validator(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果不是超级管理员只能看自己的
|
||||
userInfo, ok := c.Get("user")
|
||||
if !ok {
|
||||
utils.GinResponse(c).AuthorizationFailed()
|
||||
var loginUser *vo.User
|
||||
if loginUser = GetCurrentLoginUser(c); c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
data, count, err := repository.SystemLog().List(p, userInfo.(*entity.User))
|
||||
data, total, err := service.Log().List(p, loginUser)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("获取失败", err)
|
||||
response.R(c).FailedWithError(fmt.Errorf("获取操作日志失败: %v", err.Error()))
|
||||
return
|
||||
}
|
||||
response.R(c).Paginate(data, total, p.Current, p.Size)
|
||||
}
|
||||
|
||||
// DailyPoetry
|
||||
// @description: 每日诗词
|
||||
// @receiver DashboardApi
|
||||
// @param c
|
||||
func (DashboardApi) DailyPoetry(c *gin.Context) {
|
||||
data, err := utils.DailyPoetry().HitokotoPoetry()
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("获取失败")
|
||||
return
|
||||
}
|
||||
response.R(c).OkWithData(data)
|
||||
}
|
||||
|
||||
// ConnectionList
|
||||
// @description: 客户端链接信息列表
|
||||
// @receiver DashboardApi
|
||||
// @param c
|
||||
func (DashboardApi) ConnectionList(c *gin.Context) {
|
||||
peers, err := component.Wireguard().GetClients()
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("获取失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OkWithPage(data, count, p.Current, p.Size)
|
||||
var connections []vo.DataTraffic
|
||||
for _, peer := range peers {
|
||||
// 获取客户端链接信息
|
||||
clientInfo, err := service.Client().GetByPublicKey(peer.PublicKey.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var ipAllocation string
|
||||
for _, iaip := range peer.AllowedIPs {
|
||||
ipAllocation += iaip.String() + ","
|
||||
}
|
||||
// 去除一下最右边的逗号
|
||||
if len(ipAllocation) > 0 {
|
||||
ipAllocation = strings.TrimRight(ipAllocation, ",")
|
||||
}
|
||||
connections = append(connections, vo.DataTraffic{
|
||||
Name: clientInfo.Name,
|
||||
Email: clientInfo.Email,
|
||||
IpAllocation: ipAllocation,
|
||||
Online: time.Since(peer.LastHandshakeTime).Minutes() < 3,
|
||||
ReceiveBytes: utils.FlowCalculation().Parse(peer.TransmitBytes),
|
||||
TransmitBytes: utils.FlowCalculation().Parse(peer.ReceiveBytes),
|
||||
ConnectEndpoint: peer.Endpoint.String(),
|
||||
LastHandAt: peer.LastHandshakeTime.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
if len(connections) <= 0 {
|
||||
connections = []vo.DataTraffic{}
|
||||
}
|
||||
|
||||
response.R(c).OkWithData(connections)
|
||||
}
|
||||
|
111
http/api/login.go
Normal file
@ -0,0 +1,111 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
"time"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/http/param"
|
||||
"wireguard-ui/http/response"
|
||||
"wireguard-ui/http/vo"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type LoginApi struct{}
|
||||
|
||||
func Login() LoginApi {
|
||||
return LoginApi{}
|
||||
}
|
||||
|
||||
// Captcha
|
||||
// @description: 获取验证码
|
||||
// @receiver login
|
||||
// @param c
|
||||
func (LoginApi) Captcha(c *gin.Context) {
|
||||
math := base64Captcha.DriverMath{Height: 120, Width: 480, Fonts: []string{"ApothecaryFont.ttf", "3Dumb.ttf"}}
|
||||
mathDriver := math.ConvertFonts()
|
||||
|
||||
capt := base64Captcha.NewCaptcha(mathDriver, component.Captcha{})
|
||||
|
||||
id, base64Str, _, err := capt.Generate()
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError(fmt.Errorf("生成验证码失败: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
response.R(c).OkWithData(map[string]any{
|
||||
"id": id,
|
||||
"captcha": base64Str,
|
||||
})
|
||||
}
|
||||
|
||||
// Login
|
||||
// @description: 登陆
|
||||
// @receiver login
|
||||
// @param c
|
||||
func (LoginApi) Login(c *gin.Context) {
|
||||
var p param.Login
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
response.R(c).Validator(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证验证码是否正确
|
||||
ok := component.Captcha{}.Verify(p.CaptchaId, p.CaptchaCode, true)
|
||||
if !ok {
|
||||
response.R(c).FailedWithError("验证码错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证码正确,查询用户信息
|
||||
user, err := service.User().GetUserByAccount(p.Account)
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 对比密码
|
||||
if !utils.Password().ComparePassword(user.Password, p.Password) {
|
||||
response.R(c).FailedWithError("密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
secret := component.JWT().GenerateSecret(p.Password, uuid.NewString(), time.Now().Local().String())
|
||||
// 生成token
|
||||
token, expireAt, err := component.JWT().GenerateToken(user.Id, secret, "http")
|
||||
if err != nil {
|
||||
log.Errorf("用户[%s]生成token失败: %v", user.Account, err.Error())
|
||||
response.R(c).FailedWithError("登陆失败!")
|
||||
return
|
||||
}
|
||||
|
||||
c.Writer.Header().Set("X-TOKEN", secret)
|
||||
response.R(c).OkWithData(map[string]any{
|
||||
"token": token,
|
||||
"type": "Bearer",
|
||||
"expireAt": expireAt,
|
||||
})
|
||||
}
|
||||
|
||||
// Logout
|
||||
// @description: 退出登陆
|
||||
// @receiver LoginApi
|
||||
// @param c
|
||||
func (LoginApi) Logout(c *gin.Context) {
|
||||
loginUser, ok := c.Get("user")
|
||||
if !ok {
|
||||
response.R(c).AuthorizationFailed("未登陆")
|
||||
return
|
||||
}
|
||||
|
||||
if err := component.JWT().Logout(loginUser.(*vo.User).Id); err != nil {
|
||||
response.R(c).FailedWithError("退出登陆失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.R(c).OK()
|
||||
}
|
33
http/api/remote.go
Normal file
@ -0,0 +1,33 @@
|
||||
package api
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type remote struct{}
|
||||
|
||||
func Remote() remote {
|
||||
return remote{}
|
||||
}
|
||||
|
||||
// SaveAuthClient
|
||||
// @description: 添加授权客户端
|
||||
// @receiver remote
|
||||
// @param c
|
||||
func (remote) SaveAuthClient(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteAuthClient
|
||||
// @description: 删除授权客户端
|
||||
// @receiver remote
|
||||
// @param c
|
||||
func (remote) DeleteAuthClient(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetClientNodes
|
||||
// @description: 获取客户端节点
|
||||
// @receiver remote
|
||||
// @param c
|
||||
func (remote) GetClientNodes(c *gin.Context) {
|
||||
return
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"wireguard-dashboard/command"
|
||||
"wireguard-dashboard/http/param"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/queues"
|
||||
"wireguard-dashboard/repository"
|
||||
"wireguard-dashboard/utils"
|
||||
)
|
||||
|
||||
type server struct{}
|
||||
|
||||
func Server() server {
|
||||
return server{}
|
||||
}
|
||||
|
||||
// SaveServer
|
||||
// @description: 新增/更新服务端信息
|
||||
// @receiver server
|
||||
// @param c
|
||||
func (server) SaveServer(c *gin.Context) {
|
||||
var p param.SaveServer
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
var serverId string
|
||||
if p.Id != "" {
|
||||
serverId = p.Id
|
||||
if err = repository.Server().Update(p); err != nil {
|
||||
log.Errorf("更改服务端信息失败: %v", err.Error())
|
||||
}
|
||||
} else {
|
||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("生成密钥失败")
|
||||
return
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
serverInfo := &entity.Server{
|
||||
IpScope: p.IpScope,
|
||||
ListenPort: p.ListenPort,
|
||||
PrivateKey: privateKey.String(),
|
||||
PublicKey: publicKey.String(),
|
||||
PostUpScript: p.PostUpScript,
|
||||
PreDownScript: p.PreDownScript,
|
||||
PostDownScript: p.PostDownScript,
|
||||
}
|
||||
if err = repository.Server().Save(serverInfo); err != nil {
|
||||
log.Errorf("新增服务端失败: %v", err.Error())
|
||||
}
|
||||
|
||||
serverId = serverInfo.Id
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("操作失败")
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err = queues.PutAsyncWireguardConfigFile(serverId); err != nil {
|
||||
log.Errorf("[新增/编辑]投递同步配置文件任务失败: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
}
|
||||
|
||||
// GetServer
|
||||
// @description: 获取服务端信息
|
||||
// @receiver wireguard
|
||||
// @param c
|
||||
func (server) GetServer(c *gin.Context) {
|
||||
data, err := repository.Server().GetServer()
|
||||
if err != nil {
|
||||
log.Errorf("获取服务端信息失败: %v", err.Error())
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OKWithData(data)
|
||||
}
|
||||
|
||||
// ControlServer
|
||||
// @description: 服务端控制器
|
||||
// @receiver server
|
||||
// @param c
|
||||
func (server) ControlServer(c *gin.Context) {
|
||||
var p param.ControlServer
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch p.Status {
|
||||
case "START":
|
||||
command.StartWireguard()
|
||||
case "STOP":
|
||||
command.StopWireguard()
|
||||
case "RESTART":
|
||||
command.RestartWireguard(false)
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
}
|
@ -2,14 +2,20 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-dashboard/config"
|
||||
"wireguard-dashboard/http/param"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/queues"
|
||||
"wireguard-dashboard/repository"
|
||||
"wireguard-dashboard/utils"
|
||||
"os"
|
||||
"slices"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/http/param"
|
||||
"wireguard-ui/http/response"
|
||||
"wireguard-ui/http/vo"
|
||||
"wireguard-ui/model"
|
||||
"wireguard-ui/script"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type setting struct{}
|
||||
@ -18,88 +24,194 @@ func Setting() setting {
|
||||
return setting{}
|
||||
}
|
||||
|
||||
// SetSetting
|
||||
// @description: 添加/更改设置
|
||||
// Set
|
||||
// @description: 设置配置
|
||||
// @receiver setting
|
||||
// @param c
|
||||
func (setting) SetSetting(c *gin.Context) {
|
||||
func (setting) Set(c *gin.Context) {
|
||||
var p param.SetSetting
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
response.R(c).Validator(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := repository.System().Save(&entity.Setting{
|
||||
if err := service.Setting().SetData(&model.Setting{
|
||||
Code: p.Code,
|
||||
Data: p.Data,
|
||||
Describe: p.Describe,
|
||||
}); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("操作失败", err)
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
var whiteCodes = []string{"WG_SETTING", "WG_SERVER"}
|
||||
if slices.Contains(whiteCodes, p.Code) {
|
||||
go func() {
|
||||
if err := script.New().GenerateConfig(); err != nil {
|
||||
log.Errorf("执行脚本失败")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
||||
// SetServerGlobal
|
||||
// @description: 设置服务端的全局设定
|
||||
// Delete
|
||||
// @description: 删除配置
|
||||
// @receiver setting
|
||||
// @param c
|
||||
func (setting) SetServerGlobal(c *gin.Context) {
|
||||
var p param.SetServerGlobal
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
func (setting) Delete(c *gin.Context) {
|
||||
code := c.Param("code")
|
||||
if code == "" || code == "undefined" {
|
||||
response.R(c).FailedWithError("code不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(p)
|
||||
|
||||
var ent entity.Setting
|
||||
ent.Code = "SERVER_SETTING"
|
||||
ent.Data = string(data)
|
||||
if err := repository.System().Save(&ent); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("操作失败", err)
|
||||
if err := service.Setting().Model(&model.Setting{}).Where("code NOT IN (?)", []string{"WG_SETTING", "WG_SERVER"}).Where("code = ?", code).Delete(&model.Setting{}).Error; err != nil {
|
||||
response.R(c).FailedWithError("删除失败")
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := queues.PutAsyncWireguardConfigFile(""); err != nil {
|
||||
log.Errorf("[设置服务端],发起同步配置文件失败: %v", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
||||
// GetGlobalSetting
|
||||
// @description: 获取全局设置配置
|
||||
// GetSetting
|
||||
// @description: 获取指定配置
|
||||
// @receiver setting
|
||||
// @param c
|
||||
func (setting) GetGlobalSetting(c *gin.Context) {
|
||||
data, err := repository.System().GetServerSetting()
|
||||
func (setting) GetSetting(c *gin.Context) {
|
||||
code := c.Query("code")
|
||||
if code == "" {
|
||||
response.R(c).FailedWithError("code不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var data *model.Setting
|
||||
if err := service.Setting().Model(&model.Setting{}).Where("code = ?", code).Take(&data).Error; err != nil {
|
||||
response.R(c).FailedWithError("获取指定配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.R(c).OkWithData(data.Data)
|
||||
}
|
||||
|
||||
// GetAllSetting
|
||||
// @description: 获取全部配置
|
||||
// @receiver setting
|
||||
// @param c
|
||||
func (setting) GetAllSetting(c *gin.Context) {
|
||||
// 不查询的配置
|
||||
var blackList = []string{"WG_SETTING", "WG_SERVER"}
|
||||
|
||||
data, err := service.Setting().GetAllSetting(blackList)
|
||||
if err != nil {
|
||||
log.Errorf("获取配置失败: %v", err.Error())
|
||||
response.R(c).FailedWithError("获取配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OKWithData(data)
|
||||
response.R(c).OkWithData(data)
|
||||
}
|
||||
|
||||
// GetPublicNetworkIP
|
||||
// @description: 获取当前机器的公网IP
|
||||
// GetPublicAddr
|
||||
// @description: 获取公网地址
|
||||
// @receiver setting
|
||||
// @param c
|
||||
func (setting) GetPublicNetworkIP(c *gin.Context) {
|
||||
utils.GinResponse(c).OKWithData(map[string]string{
|
||||
"IP": utils.Network().GetHostPublicIP(),
|
||||
})
|
||||
func (setting) GetPublicAddr(c *gin.Context) {
|
||||
response.R(c).OkWithData(utils.Network().GetHostPublicIP())
|
||||
}
|
||||
|
||||
// GetServerRestartRule
|
||||
// @description: 获取服务重启规则
|
||||
// Export
|
||||
// @description: 导出配置
|
||||
// @receiver setting
|
||||
// @param c
|
||||
func (setting) GetServerRestartRule(c *gin.Context) {
|
||||
utils.GinResponse(c).OKWithData(map[string]string{
|
||||
"rule": config.Config.Wireguard.ListenConfig,
|
||||
})
|
||||
func (setting) Export(c *gin.Context) {
|
||||
// 获取当前登陆用户
|
||||
var loginUser *vo.User
|
||||
if loginUser = GetCurrentLoginUser(c); c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
if loginUser.Account != "admin" {
|
||||
response.R(c).FailedWithError("非法操作,你被捕啦!")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
data, err := service.Setting().Export()
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成配置文件
|
||||
dataBytes, _ := json.Marshal(data)
|
||||
filepath, err := utils.FileUtils().GenerateFile("config.json", dataBytes)
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", "attachment; filename="+filepath)
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
c.Header("Connection", "keep-alive")
|
||||
c.File(filepath)
|
||||
if err = os.Remove(filepath); err != nil {
|
||||
log.Errorf("删除临时文件失败: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Import
|
||||
// @description: 导入配置
|
||||
// @receiver setting
|
||||
// @param c
|
||||
func (setting) Import(c *gin.Context) {
|
||||
var p param.Import
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
response.R(c).Validator(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 校验文件是否合规
|
||||
if p.File.Filename != "config.json" {
|
||||
response.R(c).Validator(errors.New("文件名不合规"))
|
||||
return
|
||||
}
|
||||
|
||||
// 校验文件内容是否符合
|
||||
fileBytes, err := p.File.Open()
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
var data vo.Export
|
||||
if err := json.NewDecoder(fileBytes).Decode(&data); err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 校验json串是否合规
|
||||
if err = component.Validate(&data); err != nil {
|
||||
response.R(c).Validator(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前登陆用户
|
||||
var loginUser *vo.User
|
||||
if loginUser = GetCurrentLoginUser(c); c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
if loginUser.Account != "admin" {
|
||||
response.R(c).FailedWithError("非法操作,你被捕啦!")
|
||||
return
|
||||
}
|
||||
|
||||
if err = service.Setting().Import(&data, loginUser); err != nil {
|
||||
response.R(c).FailedWithError(fmt.Errorf("导入失败: %v", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
406
http/api/user.go
@ -2,292 +2,288 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/component"
|
||||
"wireguard-dashboard/constant"
|
||||
"wireguard-dashboard/http/param"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/model/vo"
|
||||
"wireguard-dashboard/repository"
|
||||
"wireguard-dashboard/utils"
|
||||
"wireguard-ui/global/constant"
|
||||
"wireguard-ui/http/param"
|
||||
"wireguard-ui/http/response"
|
||||
"wireguard-ui/model"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type user struct{}
|
||||
type UserApi struct{}
|
||||
|
||||
func UserApi() user {
|
||||
return user{}
|
||||
func User() UserApi {
|
||||
return UserApi{}
|
||||
}
|
||||
|
||||
// Login
|
||||
// @description: 登陆
|
||||
// @receiver u
|
||||
// @param c
|
||||
func (user) Login(c *gin.Context) {
|
||||
var p param.Login
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 校验验证码
|
||||
pass := component.CaptchaStore{}.Verify(p.CaptchaId, p.CaptchaAnswer, true)
|
||||
if !pass {
|
||||
utils.GinResponse(c).FailedWithMsg("验证码错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 校验用户是否存在
|
||||
user, err := repository.User().GetUserByAccount(p.Account)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("账户不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if user.Status != constant.Normal {
|
||||
utils.GinResponse(c).FailedWithMsg("账户状态异常")
|
||||
return
|
||||
}
|
||||
|
||||
// 校验密码
|
||||
if !utils.Password().ComparePassword(user.Password, p.Password) {
|
||||
utils.GinResponse(c).FailedWithMsg("密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 生成token
|
||||
token, expireTime, err := component.JWT().GenerateToken(user.Id)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("登陆失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OKWithData(map[string]any{
|
||||
"token": token,
|
||||
"type": "Bearer",
|
||||
"expireAt": expireTime.Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
// Logout
|
||||
// @description: 退出登陆
|
||||
// @receiver u
|
||||
// @param c
|
||||
func (user) Logout(c *gin.Context) {
|
||||
data, ok := c.Get("user")
|
||||
if !ok {
|
||||
utils.GinResponse(c).FailedWithMsg("你还没有登陆")
|
||||
return
|
||||
}
|
||||
if err := component.JWT().Logout(data.(*entity.User).Id); err != nil {
|
||||
log.Errorf("退出登陆失败: %v", err.Error())
|
||||
utils.GinResponse(c).FailedWithMsg("退出登陆失败")
|
||||
return
|
||||
}
|
||||
utils.GinResponse(c).OK()
|
||||
}
|
||||
|
||||
// List
|
||||
// @description: 用户列表
|
||||
// @receiver u
|
||||
// @param c
|
||||
func (user) List(c *gin.Context) {
|
||||
var p param.UserList
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
return
|
||||
}
|
||||
|
||||
data, total, err := repository.User().List(p)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("获取失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OkWithPage(data, total, p.Current, p.Size)
|
||||
}
|
||||
|
||||
// GetUser
|
||||
// GetLoginUser
|
||||
// @description: 获取登陆用户信息
|
||||
// @receiver u
|
||||
// @receiver UserApi
|
||||
// @param c
|
||||
func (user) GetUser(c *gin.Context) {
|
||||
info, ok := c.Get("user")
|
||||
func (UserApi) GetLoginUser(c *gin.Context) {
|
||||
loginUser, ok := c.Get("user")
|
||||
if !ok {
|
||||
utils.GinResponse(c).FailedWithMsg("获取信息失败")
|
||||
response.R(c).AuthorizationFailed("未登陆")
|
||||
return
|
||||
}
|
||||
data := &vo.User{
|
||||
Id: info.(*entity.User).Id,
|
||||
Name: info.(*entity.User).Name,
|
||||
Avatar: info.(*entity.User).Avatar,
|
||||
Account: info.(*entity.User).Account,
|
||||
Email: info.(*entity.User).Email,
|
||||
IsAdmin: info.(*entity.User).IsAdmin,
|
||||
Status: info.(*entity.User).Status,
|
||||
CreatedAt: info.(*entity.User).CreatedAt,
|
||||
UpdatedAt: info.(*entity.User).UpdatedAt,
|
||||
}
|
||||
utils.GinResponse(c).OKWithData(data)
|
||||
|
||||
response.R(c).OkWithData(loginUser)
|
||||
}
|
||||
|
||||
// Save
|
||||
// @description: 新增/更改用户信息
|
||||
// @receiver u
|
||||
// SaveUser
|
||||
// @description: 新增/编辑用户信息
|
||||
// @receiver UserApi
|
||||
// @param c
|
||||
func (user) Save(c *gin.Context) {
|
||||
func (UserApi) SaveUser(c *gin.Context) {
|
||||
var p param.SaveUser
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
response.R(c).Validator(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 只有新增才会判断
|
||||
if p.ID == "" {
|
||||
// 判断用户是否已经存在
|
||||
// 如果是新增用户判断该用户是否已经存在
|
||||
if p.Id == "" {
|
||||
if len(p.Account) < 2 || len(p.Account) > 20 {
|
||||
response.R(c).FailedWithError(errors.New("账号长度在2-20位"))
|
||||
return
|
||||
}
|
||||
if (len(p.Password) < 8 || len(p.Password) > 32) && p.Password != "" {
|
||||
response.R(c).FailedWithError(errors.New("密码长度在8-32位"))
|
||||
return
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := client.DB.Model(&entity.User{}).Where("account = ?", p.Account).Count(&count).Error; err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("查询失败")
|
||||
if err := service.User().Model(&model.User{}).Where("account = ?", p.Account).Count(&count).Error; err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
utils.GinResponse(c).FailedWithMsg("用户已存在!")
|
||||
response.R(c).FailedWithError(errors.New("该账号已存在"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 只有修改才有头像值
|
||||
if p.Avatar != "" && p.ID != "" {
|
||||
// 判断头像是base64开头的就需要重新上传更新
|
||||
if strings.HasPrefix(p.Avatar, "data:image/png;base64,") {
|
||||
avatar := strings.Replace(p.Avatar, "data:image/png;base64,", "", -1)
|
||||
avatarByte, err := base64.StdEncoding.DecodeString(avatar)
|
||||
if err != nil {
|
||||
log.Errorf("反解析头像失败: %v", err.Error())
|
||||
utils.GinResponse(c).FailedWithMsg("上传头像失败")
|
||||
return
|
||||
}
|
||||
|
||||
file, err := utils.FileSystem().UploadFile(avatarByte, ".png")
|
||||
if err != nil {
|
||||
log.Errorf("上传头像失败: %v", err.Error())
|
||||
utils.GinResponse(c).FailedWithMsg("上传头像失败")
|
||||
return
|
||||
}
|
||||
|
||||
p.Avatar = file
|
||||
if strings.HasPrefix(p.Avatar, "data:image/png;base64,") {
|
||||
avatar := strings.Replace(p.Avatar, "data:image/png;base64,", "", -1)
|
||||
avatarByte, err := base64.StdEncoding.DecodeString(avatar)
|
||||
if err != nil {
|
||||
log.Errorf("反解析头像失败: %v", err.Error())
|
||||
response.R(c).FailedWithError("上传头像失败")
|
||||
return
|
||||
}
|
||||
|
||||
file, err := utils.FileSystem().UploadFile(avatarByte, ".png")
|
||||
if err != nil {
|
||||
log.Errorf("上传头像失败: %v", err.Error())
|
||||
response.R(c).FailedWithError("上传头像失败")
|
||||
return
|
||||
}
|
||||
|
||||
p.Avatar = file
|
||||
}
|
||||
|
||||
if err := repository.User().Save(&entity.User{
|
||||
Base: entity.Base{
|
||||
Id: p.ID,
|
||||
userEnt := &model.User{
|
||||
Base: model.Base{
|
||||
Id: p.Id,
|
||||
},
|
||||
Avatar: p.Avatar,
|
||||
Name: p.Name,
|
||||
Account: p.Account,
|
||||
Email: p.Email,
|
||||
Password: p.Password,
|
||||
Nickname: p.Nickname,
|
||||
Avatar: p.Avatar,
|
||||
Contact: p.Contact,
|
||||
IsAdmin: *p.IsAdmin,
|
||||
Status: *p.Status,
|
||||
}); err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg(err.Error())
|
||||
}
|
||||
|
||||
if err := service.User().CreateUser(userEnt); err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
||||
// List
|
||||
// @description: 用户列表
|
||||
// @receiver UserApi
|
||||
// @param c
|
||||
func (UserApi) List(c *gin.Context) {
|
||||
var p param.Page
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
response.R(c).Validator(err)
|
||||
return
|
||||
}
|
||||
|
||||
data, total, err := service.User().List(p)
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.R(c).Paginate(data, total, p.Current, p.Size)
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @description: 删除用户
|
||||
// @receiver UserApi
|
||||
// @param c
|
||||
func (UserApi) Delete(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" || id == "undefined" {
|
||||
response.R(c).FailedWithError("id不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 是不是自己删除自己
|
||||
if id == GetCurrentLoginUser(c).Id && c.IsAborted() {
|
||||
response.R(c).FailedWithError("非法操作")
|
||||
return
|
||||
}
|
||||
|
||||
// 先查询一下
|
||||
user, err := service.User().GetUserById(id)
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
// admin用户不能被删除
|
||||
if user.Account == "admin" {
|
||||
response.R(c).FailedWithError("当前用户不能被删除")
|
||||
return
|
||||
}
|
||||
|
||||
if err = service.User().Delete(id); err != nil {
|
||||
response.R(c).FailedWithError("删除用户失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
||||
// Status
|
||||
// @description: 设置用户状态
|
||||
// @receiver UserApi
|
||||
// @param c
|
||||
func (UserApi) Status(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if id == "" || id == "undefined" {
|
||||
response.R(c).FailedWithError("id不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 是不是自己删除自己
|
||||
if id == GetCurrentLoginUser(c).Id && c.IsAborted() {
|
||||
response.R(c).FailedWithError("非法操作")
|
||||
return
|
||||
}
|
||||
|
||||
// 先查询一下
|
||||
user, err := service.User().GetUserById(id)
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
// admin用户不能被删除
|
||||
if user.Account == "admin" {
|
||||
response.R(c).FailedWithError("当前用户状态不可被变更")
|
||||
return
|
||||
}
|
||||
|
||||
var state = constant.Enabled
|
||||
if user.Status == constant.Enabled {
|
||||
state = constant.Disabled
|
||||
}
|
||||
|
||||
if err := service.User().Status(id, state); err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
||||
// ChangePassword
|
||||
// @description: 更改密码
|
||||
// @receiver u
|
||||
// @description: 修改密码
|
||||
// @receiver UserApi
|
||||
// @param c
|
||||
func (user) ChangePassword(c *gin.Context) {
|
||||
func (UserApi) ChangePassword(c *gin.Context) {
|
||||
var p param.ChangePassword
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
response.R(c).Validator(err)
|
||||
return
|
||||
}
|
||||
|
||||
user, ok := c.Get("user")
|
||||
if !ok {
|
||||
utils.GinResponse(c).AuthorizationFailed()
|
||||
user := GetCurrentLoginUser(c)
|
||||
if user == nil {
|
||||
response.R(c).FailedWithError("用户信息错误")
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.Password().ComparePassword(user.(*entity.User).Password, p.OriginPassword) {
|
||||
utils.GinResponse(c).FailedWithMsg("原密码错误")
|
||||
// 判断原密码是否对
|
||||
if !utils.Password().ComparePassword(user.Password, p.OriginalPassword) {
|
||||
response.R(c).FailedWithError("原密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 开始变更密码
|
||||
if err := repository.User().ChangePassword(p, user.(*entity.User).Id); err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("更改密码失败")
|
||||
// 修改密码
|
||||
if err := service.User().ChangePassword(user.Id, p.NewPassword); err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
||||
// ChangeUserState
|
||||
// @description: 改变用户状态
|
||||
// @receiver u
|
||||
// ResetPassword
|
||||
// @description: 重置密码
|
||||
// @receiver UserApi
|
||||
// @param c
|
||||
func (user) ChangeUserState(c *gin.Context) {
|
||||
var p param.ChangeUserState
|
||||
if err := c.ShouldBind(&p); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("参数错误", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := repository.User().ChangeUserState(p); err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("操作失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
}
|
||||
|
||||
// DeleteUser
|
||||
// @description: 删除用户
|
||||
// @receiver user
|
||||
// @param c
|
||||
func (user) DeleteUser(c *gin.Context) {
|
||||
func (UserApi) ResetPassword(c *gin.Context) {
|
||||
var id = c.Param("id")
|
||||
if id == "" || id == "undefined" {
|
||||
utils.GinResponse(c).FailedWithMsg("参数错误")
|
||||
response.R(c).FailedWithError("id不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
loginUser, ok := c.Get("user")
|
||||
if !ok {
|
||||
utils.GinResponse(c).FailedWithMsg("获取信息失败")
|
||||
// 先查询一下
|
||||
user, err := service.User().GetUserById(id)
|
||||
if err != nil {
|
||||
response.R(c).FailedWithError("获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
if err := repository.User().DeleteUser(loginUser.(*entity.User), id); err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("操作失败", err)
|
||||
if user.Status != constant.Enabled {
|
||||
response.R(c).FailedWithError("当前用户不可重置密码")
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OK()
|
||||
// 修改密码
|
||||
if err := service.User().ChangePassword(user.Id, "admin123"); err != nil {
|
||||
response.R(c).FailedWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
response.R(c).OK()
|
||||
}
|
||||
|
||||
// ChangeAvatar
|
||||
// @description: 切换头像
|
||||
// @receiver user
|
||||
// GenerateAvatar
|
||||
// @description: 生成头像
|
||||
// @receiver UserApi
|
||||
// @param c
|
||||
func (user) ChangeAvatar(c *gin.Context) {
|
||||
func (UserApi) GenerateAvatar(c *gin.Context) {
|
||||
avatar, err := utils.Avatar().GenerateAvatar(false)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithErr("生成头像失败", err)
|
||||
response.R(c).FailedWithError(fmt.Errorf("生成头像失败: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
utils.GinResponse(c).OKWithData(fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString([]byte(avatar))))
|
||||
response.R(c).OkWithData(fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString([]byte(avatar))))
|
||||
}
|
||||
|
26
http/kernel.go
Normal file
@ -0,0 +1,26 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"net/http"
|
||||
"wireguard-ui/config"
|
||||
"wireguard-ui/http/router"
|
||||
)
|
||||
|
||||
// Kernel
|
||||
// @description: http启动
|
||||
// @return error
|
||||
func Kernel() error {
|
||||
router.Rooters()
|
||||
handler := router.InitRouter()
|
||||
addr := fmt.Sprintf(":%d", config.Config.Http.Port)
|
||||
|
||||
httpServer := http.Server{
|
||||
Addr: addr,
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
log.Infof("[HTTP] server runing in %s", addr)
|
||||
return httpServer.ListenAndServe()
|
||||
}
|
84
http/middleware/authorization.go
Normal file
@ -0,0 +1,84 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/global/constant"
|
||||
"wireguard-ui/http/response"
|
||||
"wireguard-ui/service"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
// Authorization
|
||||
// @description: 授权中间件
|
||||
// @return gin.HandlerFunc
|
||||
func Authorization() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" || !strings.HasPrefix(token, "Bearer ") {
|
||||
response.R(c).AuthorizationFailed("未登陆")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
hashPassword := c.Request.Header.Get("X-TOKEN")
|
||||
if hashPassword == "" {
|
||||
response.R(c).AuthorizationFailed("未登陆")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
userClaims, err := component.JWT().ParseToken(token, hashPassword, "http")
|
||||
if err != nil {
|
||||
response.R(c).AuthorizationFailed("未登陆")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果token的颁发者与请求的站点不一致那么就给它抬出去
|
||||
if !slices.Contains(strings.Split(userClaims.Issuer, ","), utils.WebSite().GetHost(c.Request.Header.Get("Referer"))) {
|
||||
response.R(c).AuthorizationFailed("未登陆")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 查询用户
|
||||
user, err := service.User().GetUserById(userClaims.ID)
|
||||
if err != nil {
|
||||
response.R(c).AuthorizationFailed("用户不存在")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if user.Status != constant.Enabled {
|
||||
response.R(c).AuthorizationFailed("用户状态异常,请联系管理员处理!")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息放入上下文
|
||||
c.Set("user", &user)
|
||||
|
||||
if c.Request.RequestURI == "/api/user/logout" {
|
||||
c.Next()
|
||||
}
|
||||
|
||||
// 生成一个新token
|
||||
secret := component.JWT().GenerateSecret(user.Password, uuid.NewString(), time.Now().Local().String())
|
||||
tokenStr, _, err := component.JWT().GenerateToken(user.Id, secret, "http", userClaims.ExpiresAt.Time, time.Now().Local())
|
||||
if err != nil {
|
||||
response.R(c).AuthorizationFailed("校验失败")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Writer.Header().Set("Authorization", fmt.Sprintf("Bearer %s", tokenStr))
|
||||
c.Writer.Header().Set("X-TOKEN", secret)
|
||||
c.Next()
|
||||
}
|
||||
}
|
@ -9,8 +9,9 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/repository"
|
||||
"wireguard-ui/http/vo"
|
||||
"wireguard-ui/model"
|
||||
"wireguard-ui/service"
|
||||
)
|
||||
|
||||
// bodyWriter
|
||||
@ -20,11 +21,11 @@ type bodyWriter struct {
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
func SystemLogRequest() gin.HandlerFunc {
|
||||
func RequestLog() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var userId string
|
||||
if userInfo, ok := c.Get("user"); ok {
|
||||
userId = userInfo.(*entity.User).Id
|
||||
userId = userInfo.(*vo.User).Id
|
||||
}
|
||||
|
||||
// 开始时间
|
||||
@ -32,7 +33,7 @@ func SystemLogRequest() gin.HandlerFunc {
|
||||
host := c.Request.Host // 请求域名
|
||||
path := c.Request.URL.Path // 接口地址
|
||||
query := c.Request.URL.RawQuery // 参数
|
||||
if strings.Contains(path, "/api/dashboard/list") {
|
||||
if strings.Contains(path, "/api/dashboard/request/list") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
@ -51,16 +52,16 @@ func SystemLogRequest() gin.HandlerFunc {
|
||||
method := c.Request.Method // 请求方式
|
||||
ip := c.ClientIP() // 取出IP
|
||||
// 处理实际客户端IP
|
||||
if c.Request.Header.Get("U-Real-Ip") != "" {
|
||||
ip = c.Request.Header.Get("U-Real-Ip") // 这个是网关Nginx自定义的Header头
|
||||
} else if c.Request.Header.Get("U-Forwarded-For") != "" {
|
||||
ip = c.Request.Header.Get("U-Forwarded-For") // 这个是网关Nginx自定义的Header头
|
||||
if c.Request.Header.Get("X-Real-Ip") != "" {
|
||||
ip = c.Request.Header.Get("X-Real-Ip") // 这个是网关Nginx自定义的Header头
|
||||
} else if c.Request.Header.Get("X-Forwarded-For") != "" {
|
||||
ip = c.Request.Header.Get("X-Forwarded-For") // 这个是网关Nginx自定义的Header头
|
||||
}
|
||||
ua := c.Request.UserAgent() // UA
|
||||
|
||||
// 重写客户端IP
|
||||
c.Request.Header.Set("X-Forwarded-For", ip)
|
||||
c.Request.Header.Set("X-Real-Ip", ip)
|
||||
c.Request.Header.Set("X-Real-IP", ip)
|
||||
|
||||
// 拦截response
|
||||
bw := &bodyWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
|
||||
@ -75,7 +76,7 @@ func SystemLogRequest() gin.HandlerFunc {
|
||||
cost := time.Since(start).Milliseconds()
|
||||
|
||||
// 组装实体
|
||||
l := entity.SystemLog{
|
||||
l := model.RequestLog{
|
||||
UserId: userId,
|
||||
ClientIP: ip,
|
||||
Host: host,
|
||||
@ -98,7 +99,7 @@ func SystemLogRequest() gin.HandlerFunc {
|
||||
}
|
||||
|
||||
go func() {
|
||||
if er := repository.SystemLog().SaveLog(&l); er != nil {
|
||||
if er := service.Log().CreateLog(l); er != nil {
|
||||
log.Debugf("请求日志: %+v", l)
|
||||
log.Errorf("保存请求日志失败: %v", er)
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package param
|
||||
|
||||
type page struct {
|
||||
Current int `json:"current" form:"current" binding:"required"`
|
||||
Size int `json:"size" form:"size" binding:"required"`
|
||||
}
|
||||
|
||||
type OnlyPage struct {
|
||||
page
|
||||
}
|
@ -1,51 +1,40 @@
|
||||
package param
|
||||
|
||||
import "wireguard-dashboard/model/template_data"
|
||||
|
||||
// ClientList
|
||||
// @description: 客户端列表
|
||||
type ClientList struct {
|
||||
Name string `json:"name" form:"name"`
|
||||
Email string `json:"email" form:"email"`
|
||||
Ip string `json:"ip" form:"ip"`
|
||||
CreateUser string `json:"createUser" form:"createUser"`
|
||||
Enabled *int `json:"enabled" form:"enabled"`
|
||||
page
|
||||
}
|
||||
|
||||
// ClientStatusList
|
||||
// @description: 客户端状态列表
|
||||
type ClientStatusList struct {
|
||||
page
|
||||
}
|
||||
import (
|
||||
"wireguard-ui/global/constant"
|
||||
)
|
||||
|
||||
// SaveClient
|
||||
// @description: 新增/编辑客户端
|
||||
type SaveClient struct {
|
||||
Id string `json:"id" form:"id" binding:"omitempty"`
|
||||
ServerId string `json:"serverId" form:"serverId" binding:"required"`
|
||||
Name string `json:"name" form:"name" binding:"required"`
|
||||
Email string `json:"email" form:"email" binding:"omitempty"`
|
||||
SubnetRange string `json:"subnetRange" form:"subnetRange" binding:"omitempty"`
|
||||
IpAllocation []string `json:"ipAllocation" form:"ipAllocation" binding:"required"`
|
||||
AllowedIPS []string `json:"allowedIPS" form:"allowedIPS" binding:"required"`
|
||||
ExtraAllowedIPS []string `json:"extraAllowedIPS" form:"extraAllowedIPS" binding:"omitempty"`
|
||||
Endpoint string `json:"endpoint" form:"endpoint" binding:"omitempty"`
|
||||
UseServerDNS *int `json:"useServerDNS" form:"useServerDNS" binding:"required,oneof=1 0"`
|
||||
EnabledAfterCreation *int `json:"enableAfterCreation" form:"enableAfterCreation" binding:"required,oneof=1 0"`
|
||||
Keys *template_data.Keys `json:"keys" form:"keys" binding:"omitempty"`
|
||||
Enabled *int `json:"enabled" form:"enabled" binding:"required,oneof=1 0"`
|
||||
OfflineMonitoring *int `json:"offlineMonitoring" form:"offlineMonitoring" binding:"required,oneof=1 0"`
|
||||
Id string `json:"id" form:"id" label:"id" binding:"omitempty"` // id
|
||||
Name string `json:"name" form:"name" label:"名称" binding:"required,min=1,max=64"` // 名称
|
||||
Email string `json:"email" form:"email" label:"联系邮箱" binding:"omitempty"` // 联系邮箱
|
||||
SubnetRange string `json:"subnetRange" form:"subnetRange" label:"子网范围" binding:"omitempty"` // 子网范围
|
||||
IpAllocation []string `json:"ipAllocation" form:"ipAllocation" label:"客户端IP" binding:"required,dive"` // IP地址
|
||||
AllowedIps []string `json:"allowedIps" form:"allowedIps" label:"allowedIps" binding:"omitempty,dive"` // 允许访问的IP段
|
||||
ExtraAllowedIps []string `json:"extraAllowedIps" form:"extraAllowedIps" label:"extraAllowedIps" binding:"omitempty,dive"` // 其他允许访问的IP段
|
||||
Endpoint string `json:"endpoint" form:"endpoint" label:"endpoint" binding:"omitempty"` // 服务端地址
|
||||
UseServerDns *constant.Status `json:"useServerDns" form:"useServerDns" label:"useServerDns" binding:"required,oneof=0 1"` // 是否使用服务端DNS 1 - 是 | 0 - 否
|
||||
Keys *Keys `json:"keys" form:"keys" label:"密钥信息" binding:"required"` // 密钥
|
||||
Enabled *constant.Status `json:"enabled" form:"enabled" label:"状态" binding:"required,oneof=0 1"` // 状态 1 - 启用 | 0 - 禁用
|
||||
OfflineMonitoring *constant.Status `json:"offlineMonitoring" form:"offlineMonitoring" label:"离线通知" binding:"required,oneof=0 1"` // 离线通知 1 - 启用 | 0 - 禁用
|
||||
}
|
||||
|
||||
// ControlServer
|
||||
// @description: 服务端控制
|
||||
type ControlServer struct {
|
||||
Status string `json:"status" form:"status" binding:"required,oneof=START STOP RESTART"`
|
||||
// Keys
|
||||
// @description: 客户端密钥信息
|
||||
type Keys struct {
|
||||
PrivateKey string `json:"privateKey" form:"privateKey" label:"私钥" binding:"required"`
|
||||
PublicKey string `json:"publicKey" form:"publicKey" label:"公钥" binding:"required"`
|
||||
PresharedKey string `json:"presharedKey" form:"presharedKey" label:"共享密钥" binding:"required"`
|
||||
}
|
||||
|
||||
// AssignIPAndAllowedIP
|
||||
// @description: 分配IP和允许访问的IP段
|
||||
type AssignIPAndAllowedIP struct {
|
||||
Rule string `json:"rule" form:"rule" binding:"required,oneof=RANDOM AUTO"` // 分配IP的规则 RANDOM - 固定 | AUTO - 自动生成
|
||||
// ClientList
|
||||
// @description: 客户端列表
|
||||
type ClientList struct {
|
||||
Name string `json:"name" form:"name" label:"名称" binding:"omitempty"` // 客户端名称
|
||||
Email string `json:"email" form:"email" label:"邮箱" binding:"omitempty,email"` // 联系邮箱
|
||||
IpAllocation string `json:"ipAllocation" form:"ipAllocation" label:"IP范围段" binding:"omitempty"` // 客户端IP
|
||||
Enabled *int `json:"enabled" form:"enabled" label:"状态" binding:"omitempty,oneof=0 1"` // 客户端状态
|
||||
Page
|
||||
}
|
||||
|
10
http/param/login.go
Normal file
@ -0,0 +1,10 @@
|
||||
package param
|
||||
|
||||
// Login
|
||||
// @description: 登陆
|
||||
type Login struct {
|
||||
Account string `json:"account" form:"account" label:"账号" binding:"required,min=2,max=20"`
|
||||
Password string `json:"password" form:"password" label:"密码" binding:"required,min=8,max=32"`
|
||||
CaptchaId string `json:"captchaId" form:"captchaId" label:"验证码ID" binding:"required"`
|
||||
CaptchaCode string `json:"captchaCode" form:"captchaCode" label:"验证码" binding:"required,max=4"`
|
||||
}
|
6
http/param/request.go
Normal file
@ -0,0 +1,6 @@
|
||||
package param
|
||||
|
||||
type Page struct {
|
||||
Current int64 `json:"current" form:"current" label:"页码数" binding:"required"`
|
||||
Size int64 `json:"size" form:"size" label:"每页数量" binging:"required"`
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
package param
|
||||
|
||||
type SaveServer struct {
|
||||
Id string `json:"id" form:"id" binding:"omitempty"` // id
|
||||
IpScope string `json:"ipScope" form:"ipScope" binding:"required"` // 内网ip范围段
|
||||
ListenPort int `json:"listenPort" form:"listenPort" binding:"required"` // 监听端口
|
||||
PrivateKey string `json:"privateKey" form:"privateKey"` // 私钥
|
||||
PublicKey string `json:"publicKey" form:"publicKey"` // 密钥
|
||||
PostUpScript string `json:"postUpScript" form:"postUpScript" binding:"omitempty"`
|
||||
PreDownScript string `json:"preDownScript" form:"preDownScript" binding:"omitempty"`
|
||||
PostDownScript string `json:"postDownScript" form:"postDownScript" binding:"omitempty"`
|
||||
IPScope []string `json:"ipScope" form:"IPScope" label:"IPScope" binding:"required"`
|
||||
ListenPort uint64 `json:"listenPort" form:"listenPort" label:"listenPort" binding:"required"`
|
||||
PrivateKey string `json:"privateKey" form:"privateKey" label:"privateKey" binding:"required"`
|
||||
PublicKey string `json:"publicKey" form:"publicKey" label:"publicKey" binding:"required"`
|
||||
PostUpScript string `json:"postUpScript,omitempty" form:"postUpScript" label:"postUpScript" binding:"omitempty"`
|
||||
PreDownScript string `json:"preDownScript,omitempty" form:"preDownScript" label:"preDownScript" binding:"omitempty"`
|
||||
PostDownScript string `json:"postDownScript,omitempty" form:"postDownScript" label:"postDownScript" binding:"omitempty"`
|
||||
}
|
||||
|
@ -1,21 +1,17 @@
|
||||
package param
|
||||
|
||||
import "mime/multipart"
|
||||
|
||||
// SetSetting
|
||||
// @description: 设置
|
||||
// @description: 添加/编辑设置
|
||||
type SetSetting struct {
|
||||
Code string `json:"code" form:"code" binding:"required"` // 设置的唯一编码
|
||||
Data string `json:"data" form:"data" binding:"required"` // 数据
|
||||
Describe string `json:"describe" form:"describe" binding:"omitempty"` // 描述
|
||||
Code string `json:"code" form:"code" binding:"required"`
|
||||
Data string `json:"data" form:"data" binding:"required"`
|
||||
Describe string `json:"describe" form:"describe" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// SetServerGlobal
|
||||
// @description: 设置服务端全局配置
|
||||
type SetServerGlobal struct {
|
||||
EndpointAddress string `json:"endpointAddress" binding:"required"` // 服务公网IP
|
||||
DnsServer []string `json:"dnsServer" binding:"required"` // DNS列表
|
||||
MTU int `json:"MTU" binding:"required"`
|
||||
PersistentKeepalive int `json:"persistentKeepalive" binding:"omitempty"`
|
||||
FirewallMark string `json:"firewallMark" binding:"omitempty"`
|
||||
Table string `json:"table" binding:"omitempty"`
|
||||
ConfigFilePath string `json:"configFilePath" binding:"required"` // 配置文件对外输出目录
|
||||
// Import
|
||||
// @description: 导入
|
||||
type Import struct {
|
||||
File *multipart.FileHeader `json:"file" form:"file" binding:"required"`
|
||||
}
|
||||
|
@ -1,46 +1,24 @@
|
||||
package param
|
||||
|
||||
import "wireguard-dashboard/constant"
|
||||
|
||||
// Login
|
||||
// @description: 登陆
|
||||
type Login struct {
|
||||
Account string `json:"account" form:"account" binding:"required"` // 账号
|
||||
Password string `json:"password" form:"password" binding:"required"` // 密码
|
||||
CaptchaId string `json:"captchaId" form:"captchaId" binding:"required"` // 验证码id
|
||||
CaptchaAnswer string `json:"captchaAnswer" form:"captchaAnswer" binding:"required"` // 验证码
|
||||
}
|
||||
import "wireguard-ui/global/constant"
|
||||
|
||||
// SaveUser
|
||||
// @description: 新增/编辑用户信息
|
||||
type SaveUser struct {
|
||||
ID string `json:"id" form:"id" binding:"omitempty"`
|
||||
Name string `json:"name" form:"name" binding:"required"` // 用户名
|
||||
Account string `json:"account" form:"account" binding:"required"` // 账号 唯一
|
||||
Avatar string `json:"avatar" form:"avatar" binding:"omitempty"` // 头像
|
||||
Email string `json:"email" form:"email" binding:"omitempty"` // 联系邮箱
|
||||
Password string `json:"password" form:"password" binding:"omitempty"` // 密码
|
||||
IsAdmin *constant.UserType `json:"isAdmin" form:"isAdmin" binding:"omitempty"` // 是否为管理员 0 - 否 | 1 - 是
|
||||
Status *constant.UserStatus `json:"status" form:"status" binding:"required"` // 用户状态 0 - 禁用 | 1 - 正常
|
||||
Id string `json:"id" form:"id" label:"id" binding:"omitempty"` // id
|
||||
Account string `json:"account" form:"account" label:"账户号" binding:"required_without=Id"` // 账户号
|
||||
Password string `json:"password" form:"password" label:"密码" binding:"omitempty"` // 密码
|
||||
Nickname string `json:"nickname" form:"nickname" label:"昵称" binding:"required,min=2"` // 昵称
|
||||
Avatar string `json:"avatar" form:"avatar" label:"头像" binding:"omitempty"` // 头像
|
||||
Contact string `json:"contact" form:"contact" label:"联系方式" binding:"omitempty"` // 联系方式
|
||||
IsAdmin *constant.UserType `json:"isAdmin" form:"isAdmin" label:"是否为管理员" binding:"required,oneof=0 1"` // 是否为管理员 0 - 否 | 1 - 是
|
||||
Status *constant.Status `json:"status" form:"status" label:"状态" binding:"required,oneof=0 1"` // 用户状态 0 - 禁用 | 1 - 启用
|
||||
}
|
||||
|
||||
// ChangePassword
|
||||
// @description: 更改密码
|
||||
// @description: 修改密码
|
||||
type ChangePassword struct {
|
||||
OriginPassword string `json:"originPassword" form:"originPassword" binding:"required"` // 原密码
|
||||
NewPassword string `json:"newPassword" form:"newPassword" binding:"required"` // 新密码
|
||||
ConfirmPassword string `json:"confirmPassword" form:"confirmPassword" binding:"required,eqfield=NewPassword"` // 确认密码
|
||||
}
|
||||
|
||||
// UserList
|
||||
// @description: 用户列表
|
||||
type UserList struct {
|
||||
page
|
||||
}
|
||||
|
||||
// ChangeUserState
|
||||
// @description: 变更状态
|
||||
type ChangeUserState struct {
|
||||
ID string `json:"id" form:"id" binding:"required"` // 用户id
|
||||
Status string `json:"status" form:"status" binding:"required,oneof=0 1"` // 用户状态
|
||||
OriginalPassword string `json:"originalPassword" form:"originalPassword" label:"原密码" binding:"required,min=8,max=32"` // 原密码
|
||||
NewPassword string `json:"newPassword" form:"newPassword" label:"新密码" binding:"required,min=8,max=32"` // 新密码
|
||||
ConfirmPassword string `json:"confirmPassword" form:"confirmPassword" label:"确认密码" binding:"eqfield=NewPassword"` // 确认密码
|
||||
}
|
||||
|
109
http/response/response.go
Normal file
@ -0,0 +1,109 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"wireguard-ui/component"
|
||||
"wireguard-ui/utils"
|
||||
)
|
||||
|
||||
type PageData[T any] struct {
|
||||
Current int `json:"current"` // 当前页码
|
||||
Size int `json:"size"` // 每页数量
|
||||
Total int64 `json:"total"` // 总数
|
||||
TotalPage int `json:"totalPage"` // 总页数
|
||||
Records T `json:"records"` // 返回数据
|
||||
}
|
||||
|
||||
type response struct {
|
||||
c *gin.Context
|
||||
}
|
||||
|
||||
func R(c *gin.Context) response {
|
||||
return response{c}
|
||||
}
|
||||
|
||||
func (r response) OK() {
|
||||
r.c.JSON(http.StatusOK, gin.H{
|
||||
"code": http.StatusOK,
|
||||
"message": "success",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r response) OkWithData(data any) {
|
||||
r.c.JSON(http.StatusOK, gin.H{
|
||||
"code": http.StatusOK,
|
||||
"message": "success",
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
// Paginate
|
||||
// @description: 页码数
|
||||
// @receiver r
|
||||
// @param data
|
||||
// @param total
|
||||
// @param current
|
||||
// @param size
|
||||
func (r response) Paginate(data any, total int64, current, size int64) {
|
||||
// 处理一下页码、页数量
|
||||
if current == -1 {
|
||||
current = 1
|
||||
size = total
|
||||
}
|
||||
// 计算总页码
|
||||
totalPage := utils.Paginate().Generate(total, int(size))
|
||||
// 返回结果
|
||||
r.c.JSON(http.StatusOK, map[string]any{
|
||||
"code": http.StatusOK,
|
||||
"data": &PageData[any]{Current: int(current), Size: int(size), Total: total, TotalPage: totalPage, Records: data},
|
||||
"message": "success",
|
||||
})
|
||||
}
|
||||
|
||||
func (r response) AuthorizationFailed(msg string) {
|
||||
if msg == "" {
|
||||
msg = "authorized failed"
|
||||
}
|
||||
r.c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"code": http.StatusUnauthorized,
|
||||
"message": msg,
|
||||
})
|
||||
}
|
||||
|
||||
func (r response) Failed() {
|
||||
r.c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": http.StatusBadRequest,
|
||||
"message": "failed",
|
||||
})
|
||||
}
|
||||
|
||||
func (r response) FailedWithError(err any) {
|
||||
var errStr string
|
||||
switch err.(type) {
|
||||
case error:
|
||||
errStr = err.(error).Error()
|
||||
case string:
|
||||
errStr = err.(string)
|
||||
}
|
||||
|
||||
r.c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": http.StatusBadRequest,
|
||||
"message": errStr,
|
||||
})
|
||||
}
|
||||
|
||||
func (r response) Validator(err error) {
|
||||
r.c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": http.StatusBadRequest,
|
||||
"message": component.Error(err),
|
||||
})
|
||||
}
|
||||
|
||||
func (r response) Internal() {
|
||||
r.c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": http.StatusInternalServerError,
|
||||
"message": "server error",
|
||||
})
|
||||
}
|
23
http/router/client.go
Normal file
@ -0,0 +1,23 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-ui/http/api"
|
||||
"wireguard-ui/http/middleware"
|
||||
)
|
||||
|
||||
// ClientApi
|
||||
// @description: 登陆相关API
|
||||
// @param r
|
||||
func ClientApi(r *gin.RouterGroup) {
|
||||
client := r.Group("client", middleware.Authorization(), middleware.RequestLog())
|
||||
{
|
||||
client.POST("", api.Client().Save) // 新增/编辑客户端
|
||||
client.DELETE("/:id", api.Client().Delete) // 删除客户端
|
||||
client.GET("/list", api.Client().List) // 客户端列表
|
||||
client.POST("/generate-keys", api.Client().GenerateKeys) // 生成客户端密钥
|
||||
client.POST("/generate-ip", api.Client().GenerateIP) // 生成客户端IP
|
||||
client.GET("/download/:id/:type", api.Client().Download) // 下载客户端配置文件
|
||||
}
|
||||
|
||||
}
|
19
http/router/dashboard.go
Normal file
@ -0,0 +1,19 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-ui/http/api"
|
||||
"wireguard-ui/http/middleware"
|
||||
)
|
||||
|
||||
// DashboardApi
|
||||
// @description: 控制台相关接口
|
||||
// @param r
|
||||
func DashboardApi(r *gin.RouterGroup) {
|
||||
dashboard := r.Group("dashboard", middleware.Authorization(), middleware.RequestLog())
|
||||
{
|
||||
dashboard.GET("/request/list", api.Dashboard().List) // 请求日志
|
||||
dashboard.GET("/daily-poetry", api.Dashboard().DailyPoetry) // 每日诗词
|
||||
dashboard.GET("/connections", api.Dashboard().ConnectionList) // 客户端列表列表
|
||||
}
|
||||
}
|
19
http/router/login.go
Normal file
@ -0,0 +1,19 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-ui/http/api"
|
||||
"wireguard-ui/http/middleware"
|
||||
)
|
||||
|
||||
// LoginApi
|
||||
// @description: 登陆相关API
|
||||
// @param r
|
||||
func LoginApi(r *gin.RouterGroup) {
|
||||
login := r.Group("/login", middleware.RequestLog())
|
||||
{
|
||||
login.GET("/captcha", api.Login().Captcha) // 获取登陆验证码
|
||||
login.POST("", api.Login().Login) // 登陆
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +1,17 @@
|
||||
package route
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"wireguard-dashboard/web"
|
||||
"wireguard-ui/config"
|
||||
"wireguard-ui/dist"
|
||||
)
|
||||
|
||||
type Option func(engine *gin.RouterGroup)
|
||||
|
||||
var options []Option
|
||||
|
||||
func IncludeRouters(opts ...Option) {
|
||||
func includeRouters(opts ...Option) {
|
||||
options = append(options, opts...)
|
||||
}
|
||||
|
||||
@ -21,11 +22,11 @@ func InitRouter() *gin.Engine {
|
||||
// 将请求打印至控制台
|
||||
r.Use(gin.Logger())
|
||||
|
||||
//r.GET("/", func(c *gin.Context) {
|
||||
// c.Redirect(http.StatusMovedPermanently, "/web/")
|
||||
//})
|
||||
if config.Config.File.Type == "local" {
|
||||
r.Static("/assets", config.Config.File.Path)
|
||||
}
|
||||
|
||||
r.StaticFS("/web", http.FS(web.Static))
|
||||
r.StaticFS("/web", http.FS(dist.Static))
|
||||
|
||||
for _, opt := range options {
|
||||
opt(r.Group("api"))
|
||||
@ -33,3 +34,13 @@ func InitRouter() *gin.Engine {
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func Rooters() {
|
||||
includeRouters(
|
||||
LoginApi,
|
||||
UserApi,
|
||||
ClientApi,
|
||||
SettingApi,
|
||||
DashboardApi,
|
||||
)
|
||||
}
|
23
http/router/setting.go
Normal file
@ -0,0 +1,23 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-ui/http/api"
|
||||
"wireguard-ui/http/middleware"
|
||||
)
|
||||
|
||||
// SettingApi
|
||||
// @description: 设置相关API
|
||||
// @param r
|
||||
func SettingApi(r *gin.RouterGroup) {
|
||||
setting := r.Group("setting", middleware.Authorization(), middleware.RequestLog())
|
||||
{
|
||||
setting.POST("", api.Setting().Set) // 新增/编辑设置
|
||||
setting.DELETE("/:code", api.Setting().Delete) // 删除配置
|
||||
setting.GET("", api.Setting().GetSetting) // 获取指定配置
|
||||
setting.GET("/all", api.Setting().GetAllSetting) // 获取全部配置
|
||||
setting.GET("/public-addr", api.Setting().GetPublicAddr) // 获取公网IP
|
||||
setting.GET("/export", api.Setting().Export) // 导出配置文件
|
||||
setting.POST("/import", api.Setting().Import) // 导入配置
|
||||
}
|
||||
}
|
25
http/router/user.go
Normal file
@ -0,0 +1,25 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-ui/http/api"
|
||||
"wireguard-ui/http/middleware"
|
||||
)
|
||||
|
||||
// UserApi
|
||||
// @description: 用户相关API
|
||||
// @param r
|
||||
func UserApi(r *gin.RouterGroup) {
|
||||
userApi := r.Group("user", middleware.Authorization(), middleware.RequestLog())
|
||||
{
|
||||
userApi.GET("/info", api.User().GetLoginUser) // 获取当前登陆用户信息
|
||||
userApi.POST("", api.User().SaveUser) // 新增/编辑用户
|
||||
userApi.DELETE("/:id", api.User().Delete) // 删除用户
|
||||
userApi.GET("/list", api.User().List) // 分页列表
|
||||
userApi.PUT("/status/:id", api.User().Status) // 修改用户状态
|
||||
userApi.PUT("/change-password", api.User().ChangePassword) // 修改用户密码
|
||||
userApi.PUT("/reset-password/:id", api.User().ResetPassword) // 重置用户密码
|
||||
userApi.POST("/generate-avatar", api.User().GenerateAvatar) // 生成头像
|
||||
userApi.POST("/logout", api.Login().Logout) // 退出登陆
|
||||
}
|
||||
}
|
46
http/vo/client.go
Normal file
@ -0,0 +1,46 @@
|
||||
package vo
|
||||
|
||||
import "wireguard-ui/model"
|
||||
|
||||
// ClientItem
|
||||
// @description: 客户端信息
|
||||
type ClientItem struct {
|
||||
Id string `json:"id"` // id
|
||||
Name string `json:"name"` // 名称
|
||||
Email string `json:"email"` // 通知邮箱
|
||||
IpAllocation []string `json:"ipAllocation" gorm:"-"` // 分配的IP
|
||||
IpAllocationStr string `json:"-" gorm:"ipAllocationStr"`
|
||||
AllowedIps []string `json:"allowedIps" gorm:"-"` // 允许访问的IP
|
||||
AllowedIpsStr string `json:"-" gorm:"allowedIpsStr"`
|
||||
ExtraAllowedIps []string `json:"extraAllowedIps" gorm:"-"` // 其他允许访问的IP
|
||||
ExtraAllowedIpsStr string `json:"-" gorm:"extraAllowedIpsStr"`
|
||||
Endpoint string `json:"endpoint"` // 服务端点
|
||||
UseServerDns int `json:"useServerDns"` // 是否使用服务端DNS
|
||||
Keys *Keys `json:"keys" gorm:"-"` // 密钥等
|
||||
KeysStr string `json:"-" gorm:"keys_str"`
|
||||
CreateUser string `json:"createUser"` // 创建人
|
||||
Enabled int `json:"enabled"` // 是否启用
|
||||
OfflineMonitoring int `json:"offlineMonitoring"` // 离线通知
|
||||
DataTraffic *DataTraffic `json:"dataTraffic" gorm:"-"` // 数据流量
|
||||
CreatedAt model.JsonTime `json:"createdAt"` // 创建时间
|
||||
UpdatedAt model.JsonTime `json:"updatedAt"` // 更新时间
|
||||
}
|
||||
|
||||
type Keys struct {
|
||||
PrivateKey string `json:"privateKey"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
PresharedKey string `json:"presharedKey"`
|
||||
}
|
||||
|
||||
// DataTraffic
|
||||
// @description: 数据流量
|
||||
type DataTraffic struct {
|
||||
Name string `json:"name"` // 客户端名称
|
||||
Email string `json:"email"` // 联系邮箱
|
||||
IpAllocation string `json:"ipAllocation"` // 分配的IP
|
||||
Online bool `json:"online"` // 是否在线
|
||||
ReceiveBytes string `json:"receiveBytes"` // 接收流量
|
||||
TransmitBytes string `json:"transmitBytes"` // 传输流量
|
||||
ConnectEndpoint string `json:"connectEndpoint"` // 链接端点
|
||||
LastHandAt string `json:"lastHandAt"` // 最后握手时间
|
||||
}
|
6
http/vo/daily_poetry.go
Normal file
@ -0,0 +1,6 @@
|
||||
package vo
|
||||
|
||||
type Poetry struct {
|
||||
Content string `json:"content"`
|
||||
Author string `json:"author"`
|
||||
}
|
14
http/vo/log.go
Normal file
@ -0,0 +1,14 @@
|
||||
package vo
|
||||
|
||||
import "wireguard-ui/model"
|
||||
|
||||
type SystemLogItem struct {
|
||||
Id string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
ClientIP string `json:"clientIP"`
|
||||
Method string `json:"method"`
|
||||
Host string `json:"host"`
|
||||
Uri string `json:"uri"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
CreatedAt model.JsonTime `json:"createdAt"`
|
||||
}
|
74
http/vo/setting.go
Normal file
@ -0,0 +1,74 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
"wireguard-ui/global/constant"
|
||||
"wireguard-ui/model"
|
||||
)
|
||||
|
||||
// SettingItem
|
||||
// @description: 设置单项
|
||||
type SettingItem struct {
|
||||
Code string `json:"code"`
|
||||
Data string `json:"data"`
|
||||
Describe string `json:"describe"`
|
||||
CreatedAt model.JsonTime `json:"createdAt"`
|
||||
UpdatedAt model.JsonTime `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type Export struct {
|
||||
Global *Global `json:"global" label:"全局配置" binding:"required"`
|
||||
Server *Server `json:"server" label:"服务端配置" binding:"required"`
|
||||
Clients []Client `json:"clients" label:"客户端" binding:"omitempty"`
|
||||
Other []Other `json:"other" label:"其他" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// Global
|
||||
// @description: 全局配置
|
||||
type Global struct {
|
||||
MTU int `json:"MTU" label:"MTU" binding:"required"`
|
||||
ConfigFilePath string `json:"configFilePath" label:"配置文件路径" binding:"required"`
|
||||
DnsServer []string `json:"dnsServer" label:"DNS" binding:"omitempty"`
|
||||
EndpointAddress string `json:"endpointAddress" label:"公网地址" binding:"required"`
|
||||
FirewallMark string `json:"firewallMark" label:"firewallMark" binding:"omitempty"`
|
||||
PersistentKeepalive int `json:"persistentKeepalive" label:"persistentKeepalive" binding:"required"`
|
||||
Table string `json:"table" label:"table" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// Server
|
||||
// @description: 服务端信息
|
||||
type Server struct {
|
||||
IpScope []string `json:"ipScope" label:"ipScope" binding:"min=1,dive,required"`
|
||||
ListenPort int `json:"listenPort" label:"listenPort" binding:"required"`
|
||||
PrivateKey string `json:"privateKey" label:"privateKey" binding:"required"`
|
||||
PublicKey string `json:"publicKey" label:"publicKey" binding:"required"`
|
||||
PostUpScript string `json:"postUpScript" label:"postUpScript" binding:"omitempty"`
|
||||
PostDownScript string `json:"postDownScript" label:"postDownScript" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// Client
|
||||
// @description: 客户端信息
|
||||
type Client struct {
|
||||
Name string `json:"name" label:"name" binding:"required"`
|
||||
Email string `json:"email" label:"email" binding:"omitempty"`
|
||||
SubnetRange string `json:"subnetRange" label:"subnetRange" binding:"omitempty"`
|
||||
IpAllocation []string `json:"ipAllocation" label:"ipAllocation" binding:"min=1,dive,required"`
|
||||
AllowedIps []string `json:"allowedIps" label:"allowedIps" binding:"min=1,dive,required"`
|
||||
ExtraAllowedIps []string `json:"extraAllowedIps" label:"extraAllowedIps" binding:"omitempty"`
|
||||
Endpoint string `json:"endpoint" label:"endpoint" binding:"endpoint"`
|
||||
UseServerDns *constant.Status `json:"useServerDns" label:"useServerDns" binding:"omitempty"`
|
||||
Keys struct {
|
||||
PresharedKey string `json:"presharedKey" label:"presharedKey" binding:"required"`
|
||||
PrivateKey string `json:"privateKey" label:"privateKey" binding:"required"`
|
||||
PublicKey string `json:"publicKey" label:"publicKey" binding:"required"`
|
||||
} `json:"keys" label:"keys" binding:"required"`
|
||||
Enabled *constant.Status `json:"enabled" label:"enabled" binding:"required"`
|
||||
OfflineMonitoring *constant.Status `json:"offlineMonitoring" label:"offlineMonitoring" binding:"required"`
|
||||
}
|
||||
|
||||
// Other
|
||||
// @description: 其他配置
|
||||
type Other struct {
|
||||
Code string `json:"code" label:"code" binding:"required"`
|
||||
Data string `json:"data" label:"data" binding:"required"`
|
||||
Describe string `json:"describe" label:"describe" binding:"omitempty"`
|
||||
}
|
33
http/vo/user.go
Normal file
@ -0,0 +1,33 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
"wireguard-ui/global/constant"
|
||||
"wireguard-ui/model"
|
||||
)
|
||||
|
||||
// UserItem
|
||||
// @description: 用户列表的数据
|
||||
type UserItem struct {
|
||||
Id string `json:"id"`
|
||||
Account string `json:"account"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Contact string `json:"contact"`
|
||||
IsAdmin constant.UserType `json:"isAdmin"`
|
||||
Status constant.Status `json:"status"`
|
||||
CreatedAt model.JsonTime `json:"createdAt"`
|
||||
UpdatedAt model.JsonTime `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// User
|
||||
// @description: 用户信息
|
||||
type User struct {
|
||||
Id string `json:"id"`
|
||||
Account string `json:"account"`
|
||||
Password string `json:"-"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Contact string `json:"contact"`
|
||||
IsAdmin constant.UserType `json:"isAdmin"`
|
||||
Status constant.Status `json:"status"`
|
||||
}
|
@ -3,21 +3,23 @@ package initialize
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/cowardmrx/go_aliyun_oss"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/spf13/viper"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"gopkg.in/yaml.v3"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
gl "gorm.io/gorm/logger"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/config"
|
||||
"wireguard-ui/config"
|
||||
"wireguard-ui/global/client"
|
||||
)
|
||||
|
||||
// Init
|
||||
@ -35,15 +37,27 @@ func Init() {
|
||||
// initConfig
|
||||
// @description: 初始化配置
|
||||
func initConfig() {
|
||||
configBytes, err := os.ReadFile("app.yaml")
|
||||
if err != nil {
|
||||
vp := viper.New()
|
||||
vp.SetConfigFile("app.yaml")
|
||||
if err := vp.ReadInConfig(); err != nil {
|
||||
log.Panicf("读取配置文件失败: %v", err.Error())
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(configBytes, &config.Config)
|
||||
if err != nil {
|
||||
if err := vp.Unmarshal(&config.Config); err != nil {
|
||||
log.Panicf("解析配置文件失败: %v", err.Error())
|
||||
}
|
||||
|
||||
vp.OnConfigChange(func(in fsnotify.Event) {
|
||||
if err := vp.Unmarshal(&config.Config); err != nil {
|
||||
log.Errorf("配置文件变动,读取失败: %v", err.Error())
|
||||
} else {
|
||||
initDatabase()
|
||||
initRedis()
|
||||
initOSS()
|
||||
}
|
||||
})
|
||||
|
||||
vp.WatchConfig()
|
||||
}
|
||||
|
||||
// InitWireguard
|
||||
@ -72,9 +86,6 @@ func initDatabase() {
|
||||
}
|
||||
|
||||
logLevel := gl.Info
|
||||
//if os.Getenv("GIN_MODE") == "release" {
|
||||
// logLevel = gl.Error
|
||||
//}
|
||||
|
||||
db, err := gorm.Open(dbDialector, &gorm.Config{
|
||||
Logger: logger.NewGormLoggerWithConfig(gl.Config{
|
||||
@ -96,9 +107,9 @@ func initDatabase() {
|
||||
// @description: 初始化redis
|
||||
func initRedis() {
|
||||
c := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", config.Config.Redis.Host, config.Config.Redis.Port),
|
||||
Password: config.Config.Redis.Password,
|
||||
DB: config.Config.Redis.Db,
|
||||
Addr: fmt.Sprintf("%s:%d", config.Config.Cache.Host, config.Config.Cache.Port),
|
||||
Password: config.Config.Cache.Password,
|
||||
DB: config.Config.Cache.Db,
|
||||
})
|
||||
|
||||
client.Redis = c
|
||||
@ -131,8 +142,14 @@ func initOSS() {
|
||||
// initLogger
|
||||
// @description: 初始化日志
|
||||
func initLogger() {
|
||||
|
||||
mode := logger.Dev
|
||||
if os.Getenv("GIN_MODE") == gin.ReleaseMode {
|
||||
mode = logger.Prod
|
||||
}
|
||||
|
||||
logger.InitLogger(logger.LogConfig{
|
||||
Mode: logger.Dev,
|
||||
Mode: mode,
|
||||
FileEnable: true,
|
||||
})
|
||||
}
|
||||
|
65
main.go
@ -1,49 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/gin-contrib/pprof"
|
||||
"github.com/urfave/cli/v2"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
"wireguard-dashboard/config"
|
||||
"wireguard-dashboard/cron_task"
|
||||
"wireguard-dashboard/initialize"
|
||||
"wireguard-dashboard/queues"
|
||||
"wireguard-dashboard/route"
|
||||
"wireguard-dashboard/script"
|
||||
tui "wireguard-ui/cli"
|
||||
"wireguard-ui/cron"
|
||||
"wireguard-ui/http"
|
||||
"wireguard-ui/initialize"
|
||||
"wireguard-ui/script"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.Init() // 初始化
|
||||
if err := script.NewScript().Do(); err != nil {
|
||||
initialize.Init()
|
||||
if err := script.New().Do(); err != nil {
|
||||
log.Errorf("执行脚本失败: %v", err.Error())
|
||||
}
|
||||
go queues.StartConsumer() // 启动队列
|
||||
go cron_task.StartCronTask() // 启动定时任务
|
||||
cron.Task()
|
||||
}
|
||||
|
||||
func main() {
|
||||
rand.New(rand.NewSource(time.Now().Local().UnixNano()))
|
||||
route.IncludeRouters(
|
||||
route.CaptchaApi,
|
||||
route.UserApi,
|
||||
route.ServerApi,
|
||||
route.ClientApi,
|
||||
route.SettingApi,
|
||||
route.DashboardApi,
|
||||
)
|
||||
handler := route.InitRouter()
|
||||
|
||||
pprof.Register(handler)
|
||||
|
||||
httpServe := http.Server{
|
||||
Addr: fmt.Sprintf(":%d", config.Config.Http.Port),
|
||||
Handler: handler,
|
||||
app := &cli.App{
|
||||
Name: "wireguard-ui",
|
||||
Usage: "wireguard-manager-ui",
|
||||
}
|
||||
|
||||
if err := httpServe.ListenAndServe(); err != nil {
|
||||
log.Panicf("启动http服务端失败: %v", err.Error())
|
||||
app.Commands = []*cli.Command{
|
||||
{
|
||||
Name: "http:serve",
|
||||
Aliases: []string{"app:serve"},
|
||||
Usage: "",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
return http.Kernel()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "cmd:serve",
|
||||
Aliases: []string{"command:serve"},
|
||||
Usage: "use command exec",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
return tui.Kernel()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatalf("服务启动失败: %v", err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
"wireguard-dashboard/component"
|
||||
"wireguard-dashboard/constant"
|
||||
"wireguard-dashboard/repository"
|
||||
"wireguard-dashboard/utils"
|
||||
)
|
||||
|
||||
// Authorization
|
||||
// @description: 授权中间件
|
||||
// @return gin.HandlerFunc
|
||||
func Authorization() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" || !strings.HasPrefix(token, "Bearer ") {
|
||||
utils.GinResponse(c).AuthorizationFailed()
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
userClaims, err := component.JWT().ParseToken(token)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).AuthorizationFailed()
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果token的颁发者与请求的站点不一致,则直接给它狗日的丢出去
|
||||
if userClaims.Issuer != utils.GetHost(c.Request.Header.Get("Referer")) {
|
||||
utils.GinResponse(c).AuthorizationFailed()
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 查询用户
|
||||
user, err := repository.User().GetUserById(userClaims.ID)
|
||||
if err != nil {
|
||||
utils.GinResponse(c).FailedWithMsg("用户不存在")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if user.Status != constant.Normal {
|
||||
utils.GinResponse(c).FailedWithMsg("用户状态异常,请联系管理员处理!")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息放入上下文
|
||||
c.Set("user", user)
|
||||
c.Next()
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-dashboard/constant"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/utils"
|
||||
)
|
||||
|
||||
// Permission
|
||||
// @description: 权限验证,一些操作权限
|
||||
// @return gin.HandlerFunc
|
||||
func Permission() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
userInfo, ok := c.Get("user")
|
||||
if !ok {
|
||||
utils.GinResponse(c).AuthorizationFailed()
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if userInfo.(*entity.User).IsAdmin != constant.SuperAdmin {
|
||||
utils.GinResponse(c).FailedWithMsg("你暂无权限操作")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package entity
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
@ -9,6 +9,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Base
|
||||
// @description: 数据模型基类
|
||||
type Base struct {
|
||||
Id string `json:"id" gorm:"primaryKey;type:varchar(36);not null;comment:'主键'"`
|
||||
Timestamp
|
||||
@ -47,7 +49,7 @@ func (jt JsonTime) Value() (driver.Value, error) {
|
||||
return jt.Time.Format("2006-01-02 15:04:05"), nil
|
||||
}
|
||||
|
||||
func (jt *JsonTime) Scan(v interface{}) error {
|
||||
func (jt *JsonTime) Scan(v any) error {
|
||||
value, ok := v.(time.Time)
|
||||
if ok {
|
||||
*jt = JsonTime{Time: value}
|
@ -1,20 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "wireguard-dashboard/constant"
|
||||
|
||||
// User
|
||||
// @description: 用户信息
|
||||
type User struct {
|
||||
Base
|
||||
Avatar string `json:"avatar" gorm:"type:varchar(255);not null;comment:'头像'"`
|
||||
Name string `json:"name" gorm:"type:varchar(50);not null;comment:'用户名'"`
|
||||
Account string `json:"account" gorm:"type:varchar(50);not null;comment:'账号'"`
|
||||
Email string `json:"email" gorm:"type:varchar(255);default null;comment:'联系邮箱'"`
|
||||
Password string `json:"password" gorm:"type:varchar(255);not null;comment:'密码'"`
|
||||
IsAdmin constant.UserType `json:"isAdmin" gorm:"type:int(1);not null;comment:'是否为管理员'"`
|
||||
Status constant.UserStatus `json:"status" gorm:"type:tinyint(1);not null;comment:'用户状态(0 - 禁用 | 1 - 正常)'"`
|
||||
}
|
||||
|
||||
func (*User) TableName() string {
|
||||
return "t_user"
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package entity
|
||||
|
||||
// Server
|
||||
// @description: 服务端
|
||||
type Server struct {
|
||||
Base
|
||||
IpScope string `json:"ipScope" gorm:"type:varchar(255);not null;comment:'ip范围'"`
|
||||
ListenPort int `json:"listenPort" gorm:"type:int(10);not null;comment:'服务监听端口'"`
|
||||
PrivateKey string `json:"privateKey" gorm:"type:text;not null;comment:'密钥'"`
|
||||
PublicKey string `json:"publicKey" gorm:"type:text;not null;comment:'公钥'"`
|
||||
PostUpScript string `json:"postUpScript" gorm:"type:text;default null;comment:'postUpScript'"`
|
||||
PreDownScript string `json:"preDownScript" gorm:"type:text;default null;comment:'preDownScript'"`
|
||||
PostDownScript string `json:"postDownScript" gorm:"type:text;default null;comment:postDownScript"`
|
||||
Clients []Client `json:"clients" gorm:"foreignKey:ServerId"`
|
||||
}
|
||||
|
||||
func (*Server) TableName() string {
|
||||
return "t_wg_server"
|
||||
}
|
||||
|
||||
// Client
|
||||
// @description: 客户端
|
||||
type Client struct {
|
||||
Base
|
||||
ServerId string `json:"serverId" gorm:"type:varchar(36);not null;comment:'服务端id'"`
|
||||
Name string `json:"name" gorm:"type:varchar(100);not null;comment:'客户端名称'"`
|
||||
Email string `json:"email" gorm:"type:varchar(100);default null;comment:'联系邮箱'"`
|
||||
SubnetRange string `json:"subnetRange" gorm:"type:varchar(255);default null;comment:'子网范围'"`
|
||||
IpAllocation string `json:"ipAllocation" gorm:"type:varchar(255);not null;comment:'客户端ip'"`
|
||||
AllowedIps string `json:"allowedIps" gorm:"type:varchar(255);not null;comment:'允许访问的ip'"`
|
||||
ExtraAllowedIps string `json:"extraAllowedIps" gorm:"type:varchar(255);default null;comment:'额外允许的ip范围'"`
|
||||
Endpoint string `json:"endpoint" gorm:"type:varchar(255);default null;comment:'端点'"`
|
||||
UseServerDns *int `json:"useServerDns" gorm:"type:int(1);default 1;comment:'是否使用服务端dns'"`
|
||||
EnableAfterCreation *int `json:"enableAfterCreation" gorm:"type:int(1);default 1;comment:'是否创建后启用'"`
|
||||
Keys string `json:"keys" gorm:"type:text;default null;comment:'公钥和密钥的json串'"`
|
||||
UserId string `json:"userId" gorm:"type:char(36);not null;comment:'创建人id'"`
|
||||
Enabled *int `json:"enabled" gorm:"type:tinyint(1);default 1;comment:'状态(0 - 禁用 | 1 - 正常)'"`
|
||||
OfflineMonitoring *int `json:"offlineMonitoring" gorm:"tinyint(1);default 0;comment:'是否启用离线监听(0 - 禁用 | 1 - 启用)"`
|
||||
User *User `json:"user" gorm:"foreignKey:UserId"`
|
||||
Server *Server `json:"server" gorm:"foreignKey:ServerId"`
|
||||
}
|
||||
|
||||
func (*Client) TableName() string {
|
||||
return "t_wg_client"
|
||||
}
|
@ -1,19 +1,6 @@
|
||||
package entity
|
||||
package model
|
||||
|
||||
type Setting struct {
|
||||
Base
|
||||
Code string `json:"code" gorm:"type:char(20);not null;comment:'设定code'"`
|
||||
Data string `json:"data" gorm:"type:text;not null;comment:'值'"`
|
||||
Describe string `json:"describe" gorm:"type:text;default null;comment:'默认值'"`
|
||||
}
|
||||
|
||||
func (Setting) TableName() string {
|
||||
return "t_setting"
|
||||
}
|
||||
|
||||
// SystemLog
|
||||
// @description: 系统日志
|
||||
type SystemLog struct {
|
||||
type RequestLog struct {
|
||||
Base
|
||||
UserId string `json:"userId" gorm:"type:char(40);comment:'用户id'"`
|
||||
ClientIP string `json:"clientIP" gorm:"type:varchar(60);not null;comment:'客户端IP'"`
|
||||
@ -30,6 +17,6 @@ type SystemLog struct {
|
||||
Response string `json:"response" gorm:"type:text;comment:'返回数据'"`
|
||||
}
|
||||
|
||||
func (SystemLog) TableName() string {
|
||||
return "t_system_log"
|
||||
func (RequestLog) TableName() string {
|
||||
return "t_request_log"
|
||||
}
|
12
model/setting.go
Normal file
@ -0,0 +1,12 @@
|
||||
package model
|
||||
|
||||
type Setting struct {
|
||||
Base
|
||||
Code string `json:"code" gorm:"type:char(20);not null;index:idx_code; comment:'设定code'"`
|
||||
Data string `json:"render_data" gorm:"type:text;not null; comment:'值'"`
|
||||
Describe string `json:"describe" gorm:"type:text;default null;comment:'配置说明'"`
|
||||
}
|
||||
|
||||
func (Setting) TableName() string {
|
||||
return "t_setting"
|
||||
}
|
18
model/user.go
Normal file
@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
import "wireguard-ui/global/constant"
|
||||
|
||||
type User struct {
|
||||
Base
|
||||
Account string `json:"account" gorm:"type:varchar(50);not null;index:idx_account;comment: '登陆账号'"`
|
||||
Password string `json:"password" gorm:"type:varchar(255);not null;comment: '密码'"`
|
||||
Nickname string `json:"nickname" gorm:"type:varchar(50);not null;comment: '昵称'"`
|
||||
Avatar string `json:"avatar" gorm:"type:varchar(255);not null;comment: '头像'"`
|
||||
Contact string `json:"contact" gorm:"type:varchar(255);default null;comment: '联系方式(邮箱|电话)'"`
|
||||
IsAdmin constant.UserType `json:"isAdmin" gorm:"type:tinyint(1);not null;comment: '是否为管理员(0 - 否 | 1 - 是)'"`
|
||||
Status constant.Status `json:"status" gorm:"type:tinyint(1);not null;comment: '用户状态(0 - 否 | 1 - 是)'"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
return "t_user"
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/model/template_data"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
SubnetRange string `json:"subnetRange"`
|
||||
IpAllocation []string `json:"ipAllocation" gorm:"-"`
|
||||
IpAllocationStr string `json:"-" gorm:"ipAllocationStr"`
|
||||
AllowedIps []string `json:"allowedIPS" gorm:"-"`
|
||||
AllowedIpsStr string `json:"-" gorm:"allowedIPSStr"`
|
||||
ExtraAllowedIps []string `json:"extraAllowedIPS" gorm:"-"`
|
||||
ExtraAllowedIpsStr string `json:"-" gorm:"extraAllowedIPSStr"` // extra_allowed_ips_str
|
||||
Endpoint string `json:"endpoint"`
|
||||
UseServerDNS int `json:"useServerDNS"`
|
||||
EnableAfterCreation int `json:"enableAfterCreation"`
|
||||
KeysStr string `json:"-" gorm:"keys_str"`
|
||||
Keys template_data.Keys `json:"keys" gorm:"-"`
|
||||
CreateUser string `json:"createUser"`
|
||||
Enabled bool `json:"enabled"`
|
||||
OfflineMonitoring int `json:"offlineMonitoring"`
|
||||
CreatedAt entity.JsonTime `json:"createdAt"`
|
||||
UpdatedAt entity.JsonTime `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type ClientStatus struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
IpAllocation string `json:"ipAllocation"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Received string `json:"received"`
|
||||
Transmitted string `json:"transmitted"`
|
||||
IsOnline bool `json:"isOnline"` // 是否在线 1 - 在线 | 0 - 不在线
|
||||
LastHandShake string `json:"lastHandShake"`
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package vo
|
||||
|
||||
import "wireguard-dashboard/model/entity"
|
||||
|
||||
type SystemLogItem struct {
|
||||
Id string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
ClientIP string `json:"clientIP"`
|
||||
Method string `json:"method"`
|
||||
Host string `json:"host"`
|
||||
Uri string `json:"uri"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
CreatedAt entity.JsonTime `json:"createdAt"`
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package vo
|
||||
|
||||
type ServerSetting struct {
|
||||
EndpointAddress string `json:"endpointAddress"`
|
||||
DnsServer []string `json:"dnsServer"`
|
||||
MTU int `json:"MTU"`
|
||||
PersistentKeepalive int `json:"persistentKeepalive"`
|
||||
FirewallMark string `json:"firewallMark"`
|
||||
Table string `json:"table"`
|
||||
ConfigFilePath string `json:"configFilePath"`
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
"wireguard-dashboard/constant"
|
||||
"wireguard-dashboard/model/entity"
|
||||
)
|
||||
|
||||
// User
|
||||
// @description: 用户信息
|
||||
type User struct {
|
||||
Id string `json:"id"` // id
|
||||
Name string `json:"name"` // 用户名
|
||||
Avatar string `json:"avatar"` // 头像
|
||||
Account string `json:"account"` // 账户
|
||||
Email string `json:"email"` // 联系邮箱
|
||||
IsAdmin constant.UserType `json:"isAdmin"` // 管理员
|
||||
Status constant.UserStatus `json:"status"` // 状态
|
||||
CreatedAt entity.JsonTime `json:"createdAt"` // 创建时间
|
||||
UpdatedAt entity.JsonTime `json:"updatedAt"` // 更新时间
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package vo
|
||||
|
||||
// Server
|
||||
// @description: 服务端返回信息
|
||||
type Server struct {
|
||||
Id string `json:"id"` // id
|
||||
IpScope string `json:"ipScope"` // ip范围
|
||||
ListenPort int `json:"listenPort"` // 服务监听端口
|
||||
PrivateKey string `json:"privateKey"` // 私钥
|
||||
PublicKey string `json:"publicKey"` // 公钥
|
||||
PostUpScript string `json:"postUpScript"`
|
||||
PreDownScript string `json:"preDownScript"`
|
||||
PostDownScript string `json:"postDownScript"`
|
||||
}
|
37
model/wireguard.go
Normal file
@ -0,0 +1,37 @@
|
||||
package model
|
||||
|
||||
import "wireguard-ui/global/constant"
|
||||
|
||||
type Client struct {
|
||||
Base
|
||||
Name string `json:"name" gorm:"type:varchar(100);not null;comment:'客户端名称'"`
|
||||
Email string `json:"email" gorm:"type:varchar(100);default null;comment:'联系邮箱'"`
|
||||
SubnetRange string `json:"subnetRange" gorm:"type:varchar(255);default null;comment:'子网范围'"`
|
||||
IpAllocation string `json:"ipAllocation" gorm:"type:varchar(255);not null;comment:'客户端ip'"`
|
||||
AllowedIps string `json:"allowedIps" gorm:"type:varchar(255);not null;comment:'允许访问的ip'"`
|
||||
ExtraAllowedIps string `json:"extraAllowedIps" gorm:"type:varchar(255);default null;comment:'额外允许的ip范围'"`
|
||||
Endpoint string `json:"endpoint" gorm:"type:varchar(255);default null;comment:'端点'"`
|
||||
UseServerDns constant.Status `json:"useServerDns" gorm:"type:tinyint(1);default 1;comment:'是否使用服务端dns'"`
|
||||
Keys string `json:"keys" gorm:"type:text;default null;comment:'公钥和密钥的json串'"`
|
||||
UserId string `json:"userId" gorm:"type:char(36);not null;comment:'创建人id'"`
|
||||
Enabled constant.Status `json:"enabled" gorm:"type:tinyint(1);default 1;comment:'状态(0 - 禁用 | 1 - 正常)'"`
|
||||
OfflineMonitoring constant.Status `json:"offlineMonitoring" gorm:"tinyint(1);default 0;comment:'是否启用离线监听(0 - 禁用 | 1 - 启用)"`
|
||||
User *User `json:"user" gorm:"foreignKey:UserId"`
|
||||
}
|
||||
|
||||
func (Client) TableName() string {
|
||||
return "t_client"
|
||||
}
|
||||
|
||||
// Watcher
|
||||
// @description: 监听日志
|
||||
type Watcher struct {
|
||||
Base
|
||||
ClientId string `json:"clientId" gorm:"type:char(36);not null;comment:'客户端id'"`
|
||||
NotifyResult string `json:"notifyResult" gorm:"type:text;default null;comment:'通知结果'"`
|
||||
IsSend int `json:"isSend" gorm:"type:tinyint(1);default 0;comment:'是否已通知'"`
|
||||
}
|
||||
|
||||
func (Watcher) TableName() string {
|
||||
return "t_watcher"
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
package queues
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"github.com/spf13/cast"
|
||||
"os"
|
||||
"strconv"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/component"
|
||||
"wireguard-dashboard/constant"
|
||||
"wireguard-dashboard/model/template_data"
|
||||
"wireguard-dashboard/repository"
|
||||
)
|
||||
|
||||
// asyncWireguardConfigFile
|
||||
// @description: 同步配置文件
|
||||
func asyncWireguardConfigFile() {
|
||||
for {
|
||||
result, err := client.Redis.BRPop(context.Background(), 0, fmt.Sprintf("%s", constant.SyncWgConfigFile)).Result()
|
||||
if err != nil {
|
||||
log.Errorf("获取任务失败")
|
||||
continue
|
||||
}
|
||||
|
||||
serverId := result[1]
|
||||
if serverId == "" {
|
||||
serverInfo, err := repository.Server().GetServer()
|
||||
if err != nil {
|
||||
log.Errorf("没有找到服务端: %v", err.Error())
|
||||
continue
|
||||
}
|
||||
serverId = serverInfo.Id
|
||||
}
|
||||
|
||||
// 使用serverId获取服务信息
|
||||
serverEnt, err := repository.Server().GetServerWithClient(serverId)
|
||||
if err != nil {
|
||||
log.Errorf("获取服务端信息失败: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取服务端全局配置
|
||||
globalSetting, err := repository.System().GetServerSetting()
|
||||
if err != nil {
|
||||
log.Errorf("获取服务端配置失败: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if globalSetting.ConfigFilePath == "" {
|
||||
globalSetting.ConfigFilePath = "/etc/wireguard/wg0.conf"
|
||||
}
|
||||
|
||||
// 获取模板文件和输出目录
|
||||
var templatePath, outFilePath string
|
||||
if os.Getenv("GIN_MODE") != "release" {
|
||||
templatePath = "E:\\Workspace\\Go\\wireguard-dashboard\\template\\wg.conf"
|
||||
outFilePath = "E:\\Workspace\\Go\\wireguard-dashboard\\wg0.conf"
|
||||
} else {
|
||||
templatePath = "./template/wg.conf"
|
||||
outFilePath = globalSetting.ConfigFilePath
|
||||
}
|
||||
|
||||
// 组装数据
|
||||
renderServer := template_data.Server{
|
||||
Address: serverEnt.IpScope,
|
||||
ListenPort: serverEnt.ListenPort,
|
||||
PrivateKey: serverEnt.PrivateKey,
|
||||
MTU: globalSetting.MTU,
|
||||
PostUp: serverEnt.PostUpScript,
|
||||
PreDown: serverEnt.PreDownScript,
|
||||
PostDown: serverEnt.PostDownScript,
|
||||
Table: globalSetting.Table,
|
||||
}
|
||||
|
||||
// 客户端数据
|
||||
var renderClients []template_data.Client
|
||||
for _, v := range serverEnt.Clients {
|
||||
// 如果不是确认后创建或者未启用就不写入到wireguard配置文件当中
|
||||
if *v.Enabled != 1 {
|
||||
continue
|
||||
}
|
||||
var clientKey template_data.Keys
|
||||
_ = json.Unmarshal([]byte(v.Keys), &clientKey)
|
||||
var createUserName string
|
||||
if v.User != nil {
|
||||
createUserName = v.User.Name
|
||||
}
|
||||
renderClients = append(renderClients, template_data.Client{
|
||||
ID: v.Id,
|
||||
Name: v.Name,
|
||||
Email: v.Email,
|
||||
PublicKey: clientKey.PublicKey,
|
||||
PresharedKey: clientKey.PresharedKey,
|
||||
AllowedIPS: v.IpAllocation,
|
||||
PersistentKeepalive: strconv.Itoa(globalSetting.PersistentKeepalive),
|
||||
Endpoint: v.Endpoint,
|
||||
CreatedAt: v.CreatedAt.String(),
|
||||
UpdatedAt: v.UpdatedAt.String(),
|
||||
CreateUser: createUserName,
|
||||
Enabled: cast.ToBool(v.Enabled),
|
||||
})
|
||||
}
|
||||
|
||||
renderData := map[string]any{
|
||||
"Server": renderServer,
|
||||
"Clients": renderClients,
|
||||
}
|
||||
|
||||
err = component.Wireguard().Apply(templatePath, outFilePath, renderData)
|
||||
if err != nil {
|
||||
log.Errorf("同步配置文件失败: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package queues
|
||||
|
||||
// StartConsumer
|
||||
// @description: 启动消费者
|
||||
func StartConsumer() {
|
||||
// 同步配置文件
|
||||
go asyncWireguardConfigFile()
|
||||
// 离线监听
|
||||
//go offlineMonitoring()
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package queues
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/constant"
|
||||
)
|
||||
|
||||
// PutAsyncWireguardConfigFile
|
||||
// @description: 启动生产者
|
||||
// @param serverId
|
||||
// @return error
|
||||
func PutAsyncWireguardConfigFile(serverId string) error {
|
||||
return client.Redis.LPush(context.Background(), fmt.Sprintf("%s", constant.SyncWgConfigFile), serverId).Err()
|
||||
}
|
@ -1,235 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.ltd/lxh/logger/log"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"gorm.io/gorm"
|
||||
"strings"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/http/param"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/model/template_data"
|
||||
"wireguard-dashboard/model/vo"
|
||||
"wireguard-dashboard/utils"
|
||||
)
|
||||
|
||||
type clientRepo struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
func Client() clientRepo {
|
||||
return clientRepo{
|
||||
client.DB,
|
||||
}
|
||||
}
|
||||
|
||||
// List
|
||||
// @description: 列表
|
||||
// @receiver r
|
||||
// @param p
|
||||
// @return data
|
||||
// @return total
|
||||
// @return err
|
||||
func (r clientRepo) List(p param.ClientList) (data []vo.Client, total int64, err error) {
|
||||
sel := r.Table("t_wg_client as twc").
|
||||
Scopes(utils.Page(p.Current, p.Size)).
|
||||
Joins("LEFT JOIN t_user as tu ON twc.user_id = tu.id").
|
||||
Select("twc.id", "twc.created_at", "twc.updated_at", "twc.name", "twc.email", "twc.subnet_range", "twc.ip_allocation as ip_allocation_str", "twc.allowed_ips as allowed_ips_str",
|
||||
"twc.extra_allowed_ips as extra_allowed_ips_str", "twc.endpoint", "twc.use_server_dns", "twc.enable_after_creation", "twc.enabled", "twc.keys as keys_str", "tu.name as create_user", "twc.offline_monitoring")
|
||||
|
||||
if p.Name != "" {
|
||||
sel.Where("twc.name LIKE ?", "%"+p.Name+"%")
|
||||
}
|
||||
|
||||
if p.Email != "" {
|
||||
sel.Where("twc.email = ?", p.Email)
|
||||
}
|
||||
|
||||
if p.Ip != "" {
|
||||
sel.Where("twc.ip_allocation LIKE ?", "%"+p.Ip+"%")
|
||||
}
|
||||
|
||||
if p.Enabled != nil {
|
||||
sel.Where("twc.enabled = ?", p.Enabled)
|
||||
}
|
||||
|
||||
err = sel.Order("twc.created_at DESC").Find(&data).Offset(-1).Limit(-1).Count(&total).Error
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range data {
|
||||
if v.KeysStr != "" {
|
||||
_ = json.Unmarshal([]byte(v.KeysStr), &data[i].Keys)
|
||||
}
|
||||
if v.IpAllocationStr != "" {
|
||||
data[i].IpAllocation = strings.Split(v.IpAllocationStr, ",")
|
||||
}
|
||||
if v.AllowedIpsStr != "" {
|
||||
data[i].AllowedIps = strings.Split(v.AllowedIpsStr, ",")
|
||||
}
|
||||
if v.ExtraAllowedIpsStr != "" {
|
||||
data[i].ExtraAllowedIps = strings.Split(v.ExtraAllowedIpsStr, ",")
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Save
|
||||
// @description: 新增/编辑客户端
|
||||
// @receiver r
|
||||
// @param p
|
||||
// @param adminId
|
||||
// @return err
|
||||
func (r clientRepo) Save(p param.SaveClient, adminId string) (client *entity.Client, err error) {
|
||||
|
||||
ent := &entity.Client{
|
||||
Base: entity.Base{
|
||||
Id: p.Id,
|
||||
},
|
||||
ServerId: p.ServerId,
|
||||
Name: p.Name,
|
||||
Email: p.Email,
|
||||
SubnetRange: p.SubnetRange,
|
||||
IpAllocation: strings.Join(p.IpAllocation, ","),
|
||||
AllowedIps: strings.Join(p.AllowedIPS, ","),
|
||||
ExtraAllowedIps: strings.Join(p.ExtraAllowedIPS, ","),
|
||||
Endpoint: p.Endpoint,
|
||||
UseServerDns: p.UseServerDNS,
|
||||
EnableAfterCreation: p.EnabledAfterCreation,
|
||||
UserId: adminId,
|
||||
Enabled: p.Enabled,
|
||||
OfflineMonitoring: p.OfflineMonitoring,
|
||||
}
|
||||
|
||||
// id不为空,更新信息
|
||||
if p.Id != "" {
|
||||
keys, _ := json.Marshal(p.Keys)
|
||||
ent.Keys = string(keys)
|
||||
if err = r.Model(&entity.Client{}).
|
||||
Where("id = ?", p.Id).Select("name", "email", "subnet_range", "ip_allocation",
|
||||
"allowed_ips", "extra_allowed_ips", "endpoint", "use_server_dns", "enable_after_creation",
|
||||
"user_id", "enabled", "offline_monitoring").
|
||||
Updates(ent).Error; err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 查询新增的ip地址是否已经存在了
|
||||
var count int64
|
||||
if err = r.Model(&entity.Client{}).Where("ip_allocation in (?)", p.IpAllocation).Count(&count).Error; err != nil {
|
||||
log.Errorf("查询IP地址是否存在失败: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
return nil, errors.New("该客户端的IP已经存在,请检查后再添加!")
|
||||
}
|
||||
|
||||
var privateKey, presharedKey wgtypes.Key
|
||||
var publicKey string
|
||||
|
||||
if p.Keys.PrivateKey == "" {
|
||||
// 为空,新增
|
||||
privateKey, err = wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
log.Errorf("生成密钥对失败: %v", err.Error())
|
||||
return nil, errors.New("解析密钥失败")
|
||||
}
|
||||
} else {
|
||||
privateKey, err = wgtypes.ParseKey(p.Keys.PrivateKey)
|
||||
if err != nil {
|
||||
log.Errorf("解析密钥对失败: %v", err.Error())
|
||||
return nil, errors.New("解析密钥失败")
|
||||
}
|
||||
}
|
||||
|
||||
publicKey = privateKey.PublicKey().String()
|
||||
|
||||
if p.Keys.PresharedKey == "" {
|
||||
presharedKey, err = wgtypes.GenerateKey()
|
||||
if err != nil {
|
||||
log.Errorf("生成共享密钥失败: %v", err.Error())
|
||||
return nil, errors.New("解析密钥失败")
|
||||
}
|
||||
} else {
|
||||
presharedKey, err = wgtypes.ParseKey(p.Keys.PresharedKey)
|
||||
if err != nil {
|
||||
log.Errorf("解析共享密钥失败: %v", err.Error())
|
||||
return nil, errors.New("解析密钥失败")
|
||||
}
|
||||
}
|
||||
|
||||
keys := template_data.Keys{
|
||||
PrivateKey: privateKey.String(),
|
||||
PublicKey: publicKey,
|
||||
PresharedKey: presharedKey.String(),
|
||||
}
|
||||
keysStr, _ := json.Marshal(keys)
|
||||
|
||||
ent = &entity.Client{
|
||||
ServerId: p.ServerId,
|
||||
Name: p.Name,
|
||||
Email: p.Email,
|
||||
SubnetRange: p.SubnetRange,
|
||||
IpAllocation: strings.Join(p.IpAllocation, ","),
|
||||
AllowedIps: strings.Join(p.AllowedIPS, ","),
|
||||
ExtraAllowedIps: strings.Join(p.ExtraAllowedIPS, ","),
|
||||
Endpoint: p.Endpoint,
|
||||
UseServerDns: p.UseServerDNS,
|
||||
EnableAfterCreation: p.EnabledAfterCreation,
|
||||
Keys: string(keysStr),
|
||||
UserId: adminId,
|
||||
Enabled: p.Enabled,
|
||||
OfflineMonitoring: p.OfflineMonitoring,
|
||||
}
|
||||
|
||||
err = r.Model(&entity.Client{}).Create(ent).Error
|
||||
return
|
||||
}
|
||||
|
||||
// Delete
|
||||
// @description: 删除客户端
|
||||
// @receiver r
|
||||
// @param id
|
||||
// @return err
|
||||
func (r clientRepo) Delete(id string) (err error) {
|
||||
return r.Model(&entity.Client{}).Where("id = ?", id).Delete(&entity.Client{}).Error
|
||||
}
|
||||
|
||||
// GetById
|
||||
// @description: 根据id获取客户端详情
|
||||
// @receiver r
|
||||
// @param id
|
||||
// @return data
|
||||
// @return err
|
||||
func (r clientRepo) GetById(id string) (data entity.Client, err error) {
|
||||
err = r.Model(&entity.Client{}).Where("id = ?", id).Preload("Server").First(&data).Error
|
||||
return
|
||||
}
|
||||
|
||||
// GetByPublicKey
|
||||
// @description: 根据公钥获取客户端信息
|
||||
// @receiver r
|
||||
// @param publicKey
|
||||
// @return data
|
||||
// @return err
|
||||
func (r clientRepo) GetByPublicKey(publicKey string) (data entity.Client, err error) {
|
||||
err = r.Model(&entity.Client{}).Where(fmt.Sprintf("json_extract(keys, '$.publicKey') = '%s'", publicKey)).Preload("Server").First(&data).Error
|
||||
return
|
||||
}
|
||||
|
||||
// Disabled
|
||||
// @description: 禁用客户端
|
||||
// @receiver r
|
||||
// @param id
|
||||
// @return err
|
||||
func (r clientRepo) Disabled(id string) (err error) {
|
||||
return r.Model(&entity.Client{}).Where("id = ?", id).Update("enabled", 0).Error
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/http/param"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/model/vo"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
func Server() server {
|
||||
return server{
|
||||
client.DB,
|
||||
}
|
||||
}
|
||||
|
||||
// GetServer
|
||||
// @description: 获取服务端信息
|
||||
// @receiver r
|
||||
// @return data
|
||||
// @return err
|
||||
func (r server) GetServer() (data *vo.Server, err error) {
|
||||
err = r.Model(&entity.Server{}).Select("id", "ip_scope", "listen_port", "private_key", "public_key", "post_up_script", "pre_down_script", "post_down_script").First(&data).Error
|
||||
return
|
||||
}
|
||||
|
||||
// GetServerWithClient
|
||||
// @description: 获取服务端信息以及所属客户端
|
||||
// @receiver r
|
||||
// @param data
|
||||
// @param err
|
||||
func (r server) GetServerWithClient(id string) (data *entity.Server, err error) {
|
||||
err = r.Model(&entity.Server{}).Preload("Clients").Preload("Clients.User").Where("id = ?", id).First(&data).Error
|
||||
return
|
||||
}
|
||||
|
||||
// Save
|
||||
// @description: 新增服务端信息
|
||||
// @receiver r
|
||||
// @param ent
|
||||
// @return err
|
||||
func (r server) Save(ent *entity.Server) (err error) {
|
||||
return r.Model(&entity.Server{}).Create(&ent).Error
|
||||
}
|
||||
|
||||
// Update
|
||||
// @description: 更新服务端信息
|
||||
// @receiver r
|
||||
// @param p
|
||||
// @return err
|
||||
func (r server) Update(p param.SaveServer) (err error) {
|
||||
update := map[string]any{
|
||||
"ip_scope": p.IpScope,
|
||||
"listen_port": p.ListenPort,
|
||||
"post_up_script": p.PostUpScript,
|
||||
"pre_down_script": p.PreDownScript,
|
||||
"post_down_script": p.PostDownScript,
|
||||
"private_key": p.PrivateKey,
|
||||
"public_key": p.PublicKey,
|
||||
}
|
||||
return r.Model(&entity.Server{}).Where("id = ?", p.Id).Updates(&update).Error
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gorm.io/gorm"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/model/vo"
|
||||
)
|
||||
|
||||
type system struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
func System() system {
|
||||
return system{
|
||||
client.DB,
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfigByCode
|
||||
// @description:
|
||||
// @receiver r
|
||||
// @param code
|
||||
// @return data
|
||||
// @return err
|
||||
func (r system) GetConfigByCode(code string) (data *entity.Setting, err error) {
|
||||
err = r.Model(&entity.Setting{}).Where("code = ?", code).First(&data).Error
|
||||
return
|
||||
}
|
||||
|
||||
// GetServerSetting
|
||||
// @description: 获取服务端全局配置
|
||||
// @receiver r
|
||||
// @return data
|
||||
// @return err
|
||||
func (r system) GetServerSetting() (data *vo.ServerSetting, err error) {
|
||||
config, err := r.GetConfigByCode("SERVER_SETTING")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(config.Data), &data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Save
|
||||
// @description: 新增/编辑配置
|
||||
// @receiver r
|
||||
// @param ent
|
||||
// @return err
|
||||
func (r system) Save(ent *entity.Setting) (err error) {
|
||||
conf, err := r.GetConfigByCode(ent.Code)
|
||||
// 新增
|
||||
if err != nil || conf == nil {
|
||||
return r.Model(&entity.Setting{}).Create(ent).Error
|
||||
}
|
||||
|
||||
// 更新
|
||||
return r.Model(&entity.Setting{}).Where("code = ?", ent.Code).Updates(&ent).Error
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/constant"
|
||||
"wireguard-dashboard/http/param"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/model/vo"
|
||||
"wireguard-dashboard/utils"
|
||||
)
|
||||
|
||||
type systemLog struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
func SystemLog() systemLog {
|
||||
return systemLog{
|
||||
client.DB,
|
||||
}
|
||||
}
|
||||
|
||||
// SaveLog
|
||||
// @description: 保存记录
|
||||
// @receiver systemLog
|
||||
// @param data
|
||||
// @return err
|
||||
func (s systemLog) SaveLog(data *entity.SystemLog) (err error) {
|
||||
return s.Create(&data).Error
|
||||
}
|
||||
|
||||
// List
|
||||
// @description: 分页列表
|
||||
// @receiver s
|
||||
// @param p
|
||||
// @return data
|
||||
// @return total
|
||||
// @return err
|
||||
func (s systemLog) List(p param.OnlyPage, loginUser *entity.User) (data []vo.SystemLogItem, total int64, err error) {
|
||||
sel := s.Scopes(utils.Page(p.Current, p.Size)).Table("t_system_log as tsl").
|
||||
Joins("LEFT JOIN t_user as tu ON tu.id = tsl.user_id").
|
||||
Select("tsl.id", "tu.name as username", "tsl.client_ip", "tsl.method", "tsl.status_code", "tsl.host", "tsl.uri", "tsl.created_at").
|
||||
Order("tsl.created_at DESC")
|
||||
|
||||
if loginUser.IsAdmin == constant.NormalAdmin {
|
||||
sel.Where("tsl.user_id = ?", loginUser.Id)
|
||||
}
|
||||
err = sel.Find(&data).Offset(-1).Limit(-1).Count(&total).Error
|
||||
return
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gorm.io/gorm"
|
||||
"wireguard-dashboard/client"
|
||||
"wireguard-dashboard/constant"
|
||||
"wireguard-dashboard/http/param"
|
||||
"wireguard-dashboard/model/entity"
|
||||
"wireguard-dashboard/model/vo"
|
||||
"wireguard-dashboard/utils"
|
||||
)
|
||||
|
||||
type user struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
func User() user {
|
||||
return user{
|
||||
client.DB,
|
||||
}
|
||||
}
|
||||
|
||||
// List
|
||||
// @description: 用户列表
|
||||
// @receiver r
|
||||
// @param p
|
||||
// @return data
|
||||
// @return total
|
||||
// @return err
|
||||
func (r user) List(p param.UserList) (data []vo.User, total int64, err error) {
|
||||
err = r.Model(&entity.User{}).Scopes(utils.Page(p.Current, p.Size)).
|
||||
Select("id", "created_at", "updated_at", "avatar", "email", "name", "account", "is_admin", "status").Order("created_at DESC").
|
||||
Find(&data).Offset(-1).Limit(-1).Count(&total).Error
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserById
|
||||
// @description: 根据id获取用户信息
|
||||
// @receiver r
|
||||
// @param id
|
||||
// @return *entity.User
|
||||
// @return error
|
||||
func (r user) GetUserById(id string) (data *entity.User, err error) {
|
||||
err = r.Where("id = ?", id).First(&data).Error
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserByAccount
|
||||
// @description: 通过账户号获取用户信息
|
||||
// @receiver r
|
||||
// @param account
|
||||
// @return data
|
||||
// @return err
|
||||
func (r user) GetUserByAccount(account string) (data *entity.User, err error) {
|
||||
err = r.Where("account = ?", account).First(&data).Error
|
||||
return
|
||||
}
|
||||
|
||||
// Save
|
||||
// @description: 创建/更新用户
|
||||
// @receiver r
|
||||
// @param ent
|
||||
// @return err
|
||||
func (r user) Save(ent *entity.User) (err error) {
|
||||
// 更新
|
||||
if ent.Id != "" {
|
||||
updates := map[string]any{
|
||||
"name": ent.Name,
|
||||
"avatar": ent.Avatar,
|
||||
"email": ent.Email,
|
||||
"is_admin": ent.IsAdmin,
|
||||
"status": ent.Status,
|
||||
}
|
||||
|
||||
return r.Model(&entity.User{}).Where("id = ?", ent.Id).Updates(&updates).Error
|
||||
}
|
||||
|
||||
defaultPassword := utils.Password().GenerateHashPassword("admin123")
|
||||
if ent.Password == "" { // 没有密码给一个默认密码
|
||||
ent.Password = defaultPassword
|
||||
} else {
|
||||
ent.Password = utils.Password().GenerateHashPassword(ent.Password)
|
||||
}
|
||||
|
||||
// 没有头像就生成一个头像
|
||||
if ent.Avatar == "" {
|
||||
ent.Avatar, _ = utils.Avatar().GenerateAvatar(true)
|
||||
}
|
||||
|
||||
// 创建
|
||||
return r.Create(&ent).Error
|
||||
}
|
||||
|
||||
// ChangePassword
|
||||
// @description: 变更密码
|
||||
// @receiver r
|
||||
// @param p
|
||||
// @param userId
|
||||
// @return err
|
||||
func (r user) ChangePassword(p param.ChangePassword, userId string) (err error) {
|
||||
password := utils.Password().GenerateHashPassword(p.NewPassword)
|
||||
return r.Model(&entity.User{}).Where("id = ?", userId).Update("password", password).Error
|
||||
}
|
||||
|
||||
// ChangeUserState
|
||||
// @description: 变更用户状态
|
||||
// @receiver r
|
||||
// @param p
|
||||
// @return err
|
||||
func (r user) ChangeUserState(p param.ChangeUserState) (err error) {
|
||||
return r.Model(&entity.User{}).Where("id = ?", p.ID).Update("status", p.Status).Error
|
||||
}
|
||||
|
||||
// DeleteUser
|
||||
// @description: 删除管理员
|
||||
// @receiver r
|
||||
// @param id
|
||||
// @return err
|
||||
func (r user) DeleteUser(loginUser *entity.User, id string) (err error) {
|
||||
// 不能删除自身以及超级管理员,超级管理员只有 名为admin的管理员可以删除
|
||||
userInfo, err := r.GetUserById(id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if userInfo.Id == loginUser.Id {
|
||||
return errors.New("不可删除自己")
|
||||
}
|
||||
|
||||
if userInfo.IsAdmin == constant.SuperAdmin && loginUser.Account != "admin" {
|
||||
return errors.New("非无敌管理员不可清空超管")
|
||||
}
|
||||
|
||||
if userInfo.Account == "admin" {
|
||||
return errors.New("不可删除宇宙第一无敌管理员,删了你就G了!")
|
||||
}
|
||||
|
||||
// 可删除
|
||||
return r.Model(&entity.User{}).Where("id = ?", id).Delete(userInfo).Error
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-dashboard/http/api"
|
||||
"wireguard-dashboard/middleware"
|
||||
)
|
||||
|
||||
// CaptchaApi
|
||||
// @description: 验证码
|
||||
// @param r
|
||||
func CaptchaApi(r *gin.RouterGroup) {
|
||||
captcha := r.Group("captcha", middleware.SystemLogRequest())
|
||||
{
|
||||
captcha.GET("", api.Captcha().GenerateCaptcha) // 生成验证码
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"wireguard-dashboard/http/api"
|
||||
"wireguard-dashboard/middleware"
|
||||
)
|
||||
|
||||
func ClientApi(r *gin.RouterGroup) {
|
||||
apiGroup := r.Group("client", middleware.Authorization(), middleware.SystemLogRequest())
|
||||
{
|
||||
apiGroup.GET("list", api.Client().List) // 客户端列表
|
||||
apiGroup.POST("save", middleware.Permission(), api.Client().Save) // 新增/编辑客户端
|
||||
apiGroup.DELETE(":id", middleware.Permission(), api.Client().Delete) // 删除客户端
|
||||
apiGroup.POST("download/:id", api.Client().Download) // 下载客户端配置文件
|
||||
apiGroup.POST("generate-qrcode/:id", api.Client().GenerateQrCode) // 生成客户端二维码
|
||||
apiGroup.POST("to-email/:id", api.Client().SendEmail) //发送邮件
|
||||
apiGroup.GET("status", api.Client().Status) // 获取客户端链接状态监听列表
|
||||
apiGroup.POST("offline/:id", api.Client().Offline) // 强制下线指定客户端
|
||||
apiGroup.POST("assignIP", api.Client().AssignIPAndAllowedIP) // 分配IP
|
||||
apiGroup.POST("generate-keys", api.Client().GenerateKeys) // 生成密钥对
|
||||
}
|
||||
}
|