Compare commits
No commits in common. "v2" and "v2.0.8" have entirely different histories.
56
.gitignore
vendored
@ -233,33 +233,33 @@ fabric.properties
|
|||||||
# vendor/
|
# vendor/
|
||||||
|
|
||||||
# Go workspace file
|
# Go workspace file
|
||||||
./go.work
|
go.work
|
||||||
|
|
||||||
./.idea
|
.idea
|
||||||
./web/.idea
|
web/.idea
|
||||||
|
|
||||||
./web/node_modules
|
web/node_modules
|
||||||
./web/.DS_Store
|
web/.DS_Store
|
||||||
./web/dist
|
web/dist
|
||||||
./web/dist-ssr
|
web/dist-ssr
|
||||||
./web/*.local
|
web/*.local
|
||||||
./web/.eslintcache
|
web/.eslintcache
|
||||||
./web/report.html
|
web/report.html
|
||||||
./web/vite.config.*.timestamp*
|
web/vite.config.*.timestamp*
|
||||||
|
|
||||||
./web/yarn.lock
|
web/yarn.lock
|
||||||
./web/npm-debug.log*
|
web/npm-debug.log*
|
||||||
./web/.pnpm-error.log*
|
web/.pnpm-error.log*
|
||||||
./web/.pnpm-debug.log
|
web/.pnpm-debug.log
|
||||||
./web/tests/**/coverage/
|
web/tests/**/coverage/
|
||||||
./web/.vscode/
|
web/.vscode/
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
./web/*.suo
|
web/*.suo
|
||||||
./web/*.ntvs*
|
web/*.ntvs*
|
||||||
./web/*.njsproj
|
web/*.njsproj
|
||||||
./web/*.sln
|
web/*.sln
|
||||||
./web/tsconfig.tsbuildinfo
|
web/tsconfig.tsbuildinfo
|
||||||
|
|
||||||
dist/assets
|
dist/assets
|
||||||
dist/resource
|
dist/resource
|
||||||
@ -267,10 +267,10 @@ dist/favicon.png
|
|||||||
dist/favicon.svg
|
dist/favicon.svg
|
||||||
dist/index.html
|
dist/index.html
|
||||||
|
|
||||||
./template/tmp/*
|
template/tmp/*
|
||||||
./logs/*
|
logs/*
|
||||||
./app.yaml
|
app.yaml
|
||||||
./*.db
|
*.db
|
||||||
./.env
|
.env
|
||||||
./*.env
|
*.env
|
||||||
|
|
||||||
|
12
Dockerfile
@ -1,9 +1,9 @@
|
|||||||
# 打包前端
|
# 打包前端
|
||||||
FROM node:18-alpine AS build-front
|
FROM node:18-alpine as build-front
|
||||||
|
|
||||||
WORKDIR /front
|
WORKDIR front
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR ./web
|
WORKDIR web
|
||||||
|
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
RUN corepack prepare pnpm@8.6.10 --activate
|
RUN corepack prepare pnpm@8.6.10 --activate
|
||||||
@ -13,7 +13,7 @@ RUN pnpm build
|
|||||||
RUN ls -lh && pwd
|
RUN ls -lh && pwd
|
||||||
|
|
||||||
# 前后端集成打包
|
# 前后端集成打包
|
||||||
FROM golang:alpine AS build-backend
|
FROM golang:alpine as build-backend
|
||||||
|
|
||||||
RUN apk add upx
|
RUN apk add upx
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
@ -23,7 +23,7 @@ COPY --from=build-front /front/web/dist/ /build/dist
|
|||||||
ENV GO111MODULE=on
|
ENV GO111MODULE=on
|
||||||
ENV GOPROXY=https://goproxy.cn,direct
|
ENV GOPROXY=https://goproxy.cn,direct
|
||||||
|
|
||||||
RUN go version && go build -ldflags="-s -w" -o wgui && upx -9 wgui
|
RUN go build -ldflags="-s -w" -o wgui && upx -9 wgui
|
||||||
|
|
||||||
RUN ls -lh && chmod +x ./wgui
|
RUN ls -lh && chmod +x ./wgui
|
||||||
|
|
||||||
@ -43,4 +43,4 @@ COPY --from=build-backend /build/template/* /app/template/
|
|||||||
|
|
||||||
RUN chmod +x wgui
|
RUN chmod +x wgui
|
||||||
|
|
||||||
ENTRYPOINT ["./wgui","http:serve"]
|
ENTRYPOINT ["./wgui"]
|
94
README.md
@ -1,94 +0,0 @@
|
|||||||
# 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"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 页面展示
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
@ -1,10 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"wireguard-ui/cli/tui"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Kernel() error {
|
|
||||||
tui.NewApp().Run()
|
|
||||||
return nil
|
|
||||||
}
|
|
165
cli/tui/app.go
@ -1,165 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,520 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
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, "")
|
|
||||||
}
|
|
@ -1,440 +0,0 @@
|
|||||||
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
@ -1,136 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
@ -36,7 +36,7 @@ func JWT() JwtComponent {
|
|||||||
// @return token
|
// @return token
|
||||||
// @return expireTime
|
// @return expireTime
|
||||||
// @return err
|
// @return err
|
||||||
func (JwtComponent) GenerateToken(userId, secret, source string, times ...time.Time) (token string, expireTime *jwt.NumericDate, err error) {
|
func (JwtComponent) GenerateToken(userId, secret string, times ...time.Time) (token string, expireTime *jwt.NumericDate, err error) {
|
||||||
var notBefore, issuedAt *jwt.NumericDate
|
var notBefore, issuedAt *jwt.NumericDate
|
||||||
if len(times) != 0 {
|
if len(times) != 0 {
|
||||||
expireTime = jwt.NewNumericDate(times[0])
|
expireTime = jwt.NewNumericDate(times[0])
|
||||||
@ -68,19 +68,10 @@ func (JwtComponent) GenerateToken(userId, secret, source string, times ...time.T
|
|||||||
return "", nil, errors.New("生成token失败")
|
return "", nil, errors.New("生成token失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch source {
|
client.Redis.Set(context.Background(),
|
||||||
case "http":
|
fmt.Sprintf("%s:%s", constant.UserToken, userId),
|
||||||
client.Redis.Set(context.Background(),
|
token,
|
||||||
fmt.Sprintf("%s:%s", constant.UserToken, userId),
|
time.Duration(expireTime.Sub(time.Now()).Abs().Seconds())*time.Second)
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +81,7 @@ func (JwtComponent) GenerateToken(userId, secret, source string, times ...time.T
|
|||||||
// @param token
|
// @param token
|
||||||
// @return *JwtComponent
|
// @return *JwtComponent
|
||||||
// @return error
|
// @return error
|
||||||
func (JwtComponent) ParseToken(token, secret, source string) (*JwtComponent, error) {
|
func (JwtComponent) ParseToken(token, secret string) (*JwtComponent, error) {
|
||||||
tokenStr := strings.Split(token, "Bearer ")[1]
|
tokenStr := strings.Split(token, "Bearer ")[1]
|
||||||
|
|
||||||
t, err := jwt.ParseWithClaims(tokenStr, &JwtComponent{}, func(token *jwt.Token) (any, error) {
|
t, err := jwt.ParseWithClaims(tokenStr, &JwtComponent{}, func(token *jwt.Token) (any, error) {
|
||||||
@ -98,20 +89,10 @@ func (JwtComponent) ParseToken(token, secret, source string) (*JwtComponent, err
|
|||||||
})
|
})
|
||||||
|
|
||||||
if claims, ok := t.Claims.(*JwtComponent); ok && t.Valid {
|
if claims, ok := t.Claims.(*JwtComponent); ok && t.Valid {
|
||||||
var userToken string
|
userToken, err := client.Redis.Get(context.Background(), fmt.Sprintf("%s:%s", constant.UserToken, claims.ID)).Result()
|
||||||
switch source {
|
if err != nil {
|
||||||
case "http":
|
log.Errorf("缓存中用户[%s]的token查找失败: %v", claims.ID, err.Error())
|
||||||
userToken, err = client.Redis.Get(context.Background(), fmt.Sprintf("%s:%s", constant.UserToken, claims.ID)).Result()
|
return nil, errors.New("token不存在")
|
||||||
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 {
|
if userToken != tokenStr {
|
||||||
|
@ -36,18 +36,6 @@ func Error(err error) string {
|
|||||||
return errMsg
|
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
|
// initValidatorTranslator
|
||||||
// @description: 初始化翻译机
|
// @description: 初始化翻译机
|
||||||
// @receiver vli
|
// @receiver vli
|
||||||
|
@ -97,8 +97,8 @@ func (w WireguardComponent) GenerateClientFile(clientInfo *model.Client, server
|
|||||||
var outPath = "/tmp/" + fmt.Sprintf("%s.conf", clientInfo.Name)
|
var outPath = "/tmp/" + fmt.Sprintf("%s.conf", clientInfo.Name)
|
||||||
var templatePath = "./template/wg.client.conf"
|
var templatePath = "./template/wg.client.conf"
|
||||||
if os.Getenv("GIN_MODE") != "release" {
|
if os.Getenv("GIN_MODE") != "release" {
|
||||||
outPath = "E:\\Workspace\\Go\\wireguard-ui\\template\\tmp\\" + fmt.Sprintf("%s.conf", clientInfo.Name)
|
outPath = "E:\\Workspace\\Go\\wireguard-dashboard\\template\\" + fmt.Sprintf("%s.conf", clientInfo.Name)
|
||||||
templatePath = "E:\\Workspace\\Go\\wireguard-ui\\template\\conf\\wg.client.conf"
|
templatePath = "E:\\Workspace\\Go\\wireguard-dashboard\\template\\wg.client.conf"
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Template().Execute(templatePath, outPath, execData)
|
err = Template().Execute(templatePath, outPath, execData)
|
||||||
|
@ -7,5 +7,6 @@ type config struct {
|
|||||||
Database *database `yaml:"database"`
|
Database *database `yaml:"database"`
|
||||||
Cache *cache `yaml:"redis"`
|
Cache *cache `yaml:"redis"`
|
||||||
File *file `yaml:"file"`
|
File *file `yaml:"file"`
|
||||||
|
Mail *mail `yaml:"email"`
|
||||||
Wireguard *wireguard `yaml:"wireguard"`
|
Wireguard *wireguard `yaml:"wireguard"`
|
||||||
}
|
}
|
||||||
|
9
config/mail.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
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"`
|
||||||
|
}
|
24
cron/cron.go
@ -1,24 +0,0 @@
|
|||||||
package cron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gitee.ltd/lxh/logger/log"
|
|
||||||
"github.com/go-co-op/gocron/v2"
|
|
||||||
"github.com/spf13/cast"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
"wireguard-ui/cron/task"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Task() {
|
|
||||||
if !cast.ToBool(os.Getenv("ENABLED_CRON")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
BIN
document/img.png
Before Width: | Height: | Size: 209 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 55 KiB |
@ -1,8 +1,6 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Captcha = "captcha"
|
Captcha = "captcha"
|
||||||
UserToken = "token"
|
UserToken = "token"
|
||||||
TUIUserToken = "tui:token"
|
|
||||||
ClientOffline = "client:offline:"
|
|
||||||
)
|
)
|
||||||
|
137
go.mod
@ -1,76 +1,53 @@
|
|||||||
module wireguard-ui
|
module wireguard-ui
|
||||||
|
|
||||||
go 1.22.7
|
go 1.21
|
||||||
|
|
||||||
toolchain go1.23.0
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitee.ltd/lxh/logger v1.0.19
|
gitee.ltd/lxh/logger v1.0.15
|
||||||
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/cowardmrx/go_aliyun_oss v1.0.7
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
|
github.com/gin-contrib/pprof v1.5.0
|
||||||
github.com/fsnotify/fsnotify v1.8.0
|
|
||||||
github.com/gin-contrib/pprof v1.5.2
|
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
github.com/go-co-op/gocron/v2 v2.12.4
|
github.com/go-resty/resty/v2 v2.13.1
|
||||||
github.com/go-playground/locales v0.14.1
|
github.com/redis/go-redis/v9 v9.5.3
|
||||||
github.com/go-playground/universal-translator v0.18.1
|
|
||||||
github.com/go-playground/validator/v10 v10.23.0
|
|
||||||
github.com/go-resty/resty/v2 v2.15.3
|
|
||||||
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.7.0
|
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
|
||||||
github.com/spf13/cast v1.6.0
|
|
||||||
github.com/spf13/viper v1.19.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
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7
|
|
||||||
gorm.io/driver/mysql v1.5.7
|
gorm.io/driver/mysql v1.5.7
|
||||||
gorm.io/driver/postgres v1.5.11
|
gorm.io/driver/postgres v1.5.9
|
||||||
gorm.io/gorm v1.25.10
|
gorm.io/gorm v1.25.10
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible // indirect
|
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/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bytedance/sonic v1.12.9 // indirect
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.2 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
github.com/caarlos0/env/v6 v6.10.1 // indirect
|
github.com/caarlos0/env/v6 v6.10.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/charmbracelet/bubbletea v1.1.0 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.2.3 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.0 // indirect
|
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
|
||||||
github.com/dennwc/varint v1.0.0 // indirect
|
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/cors v1.7.2 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/go-kit/kit v0.12.0 // indirect
|
github.com/go-kit/kit v0.12.0 // indirect
|
||||||
github.com/go-kit/log v0.2.1 // indirect
|
github.com/go-kit/log v0.2.1 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.5.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.22.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.4 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/grafana/loki-client-go v0.0.0-20240913122146-e119d400c3a5 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/grafana/loki/pkg/push v0.0.0-20240912152814-63e84b476a9a // indirect
|
|
||||||
github.com/grafana/regexp v0.0.0-20220304095617-2e8d9baf4ac2 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/jackc/pgpassfile 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/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
@ -78,71 +55,63 @@ require (
|
|||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/joho/godotenv v1.5.1 // indirect
|
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
|
||||||
github.com/jonboulle/clockwork v0.4.0 // indirect
|
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/jpillora/backoff v1.0.0 // indirect
|
github.com/jpillora/backoff v1.0.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lixh00/loki-client-go v1.0.1 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
||||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
github.com/mdlayher/socket v0.4.1 // indirect
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/mojocn/base64Captcha v1.3.6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||||
github.com/natefinch/lumberjack v2.0.0+incompatible // 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/pelletier/go-toml/v2 v2.2.3 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/prometheus/client_golang v1.13.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
github.com/prometheus/common v0.61.0 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/prometheus v1.8.2-0.20201028100903-3245b3267b24 // indirect
|
||||||
github.com/prometheus/prometheus v0.35.0 // indirect
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // 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/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/afero v1.11.0 // indirect
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/stretchr/testify v1.10.0 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // 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/atomic v1.11.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
go.uber.org/goleak v1.3.0 // indirect
|
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
|
||||||
go.uber.org/zap v1.23.0 // indirect
|
go.uber.org/zap v1.23.0 // indirect
|
||||||
golang.org/x/arch v0.12.0 // indirect
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
golang.org/x/image v0.18.0 // indirect
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
golang.org/x/oauth2 v0.24.0 // indirect
|
golang.org/x/image v0.13.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/net v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/oauth2 v0.18.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
golang.org/x/time v0.8.0 // indirect
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
golang.org/x/text v0.15.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/grpc v1.69.0 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
google.golang.org/grpc v1.62.1 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.22.5 // indirect
|
modernc.org/libc v1.22.5 // indirect
|
||||||
|
@ -243,14 +243,7 @@ func (ClientApi) Download(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取邮箱配置
|
err = utils.Mail().SendMail(data.Email, fmt.Sprintf("客户端: %s", data.Name), "请查收附件", outPath)
|
||||||
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 {
|
if err != nil {
|
||||||
response.R(c).FailedWithError("发送邮件失败")
|
response.R(c).FailedWithError("发送邮件失败")
|
||||||
return
|
return
|
||||||
|
@ -76,7 +76,7 @@ func (LoginApi) Login(c *gin.Context) {
|
|||||||
|
|
||||||
secret := component.JWT().GenerateSecret(p.Password, uuid.NewString(), time.Now().Local().String())
|
secret := component.JWT().GenerateSecret(p.Password, uuid.NewString(), time.Now().Local().String())
|
||||||
// 生成token
|
// 生成token
|
||||||
token, expireAt, err := component.JWT().GenerateToken(user.Id, secret, "http")
|
token, expireAt, err := component.JWT().GenerateToken(user.Id, secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("用户[%s]生成token失败: %v", user.Account, err.Error())
|
log.Errorf("用户[%s]生成token失败: %v", user.Account, err.Error())
|
||||||
response.R(c).FailedWithError("登陆失败!")
|
response.R(c).FailedWithError("登陆失败!")
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
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,17 +1,11 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"gitee.ltd/lxh/logger/log"
|
"gitee.ltd/lxh/logger/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"os"
|
|
||||||
"slices"
|
"slices"
|
||||||
"wireguard-ui/component"
|
|
||||||
"wireguard-ui/http/param"
|
"wireguard-ui/http/param"
|
||||||
"wireguard-ui/http/response"
|
"wireguard-ui/http/response"
|
||||||
"wireguard-ui/http/vo"
|
|
||||||
"wireguard-ui/model"
|
"wireguard-ui/model"
|
||||||
"wireguard-ui/script"
|
"wireguard-ui/script"
|
||||||
"wireguard-ui/service"
|
"wireguard-ui/service"
|
||||||
@ -67,7 +61,7 @@ func (setting) Delete(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if err := service.Setting().Model(&model.Setting{}).Where("code = ?", code).Delete(&model.Setting{}).Error; err != nil {
|
||||||
response.R(c).FailedWithError("删除失败")
|
response.R(c).FailedWithError("删除失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -119,99 +113,3 @@ func (setting) GetAllSetting(c *gin.Context) {
|
|||||||
func (setting) GetPublicAddr(c *gin.Context) {
|
func (setting) GetPublicAddr(c *gin.Context) {
|
||||||
response.R(c).OkWithData(utils.Network().GetHostPublicIP())
|
response.R(c).OkWithData(utils.Network().GetHostPublicIP())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export
|
|
||||||
// @description: 导出配置
|
|
||||||
// @receiver setting
|
|
||||||
// @param c
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"gitee.ltd/lxh/logger/log"
|
|
||||||
"github.com/gin-contrib/pprof"
|
|
||||||
"github.com/spf13/cast"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"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)
|
|
||||||
|
|
||||||
if cast.ToBool(os.Getenv("ENABLED_PPROF")) {
|
|
||||||
pprof.Register(handler, "/monitoring")
|
|
||||||
}
|
|
||||||
|
|
||||||
httpServer := http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
Handler: handler,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("[HTTP] server runing in %s", addr)
|
|
||||||
return httpServer.ListenAndServe()
|
|
||||||
}
|
|
@ -33,7 +33,7 @@ func Authorization() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userClaims, err := component.JWT().ParseToken(token, hashPassword, "http")
|
userClaims, err := component.JWT().ParseToken(token, hashPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.R(c).AuthorizationFailed("未登陆")
|
response.R(c).AuthorizationFailed("未登陆")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
@ -70,7 +70,7 @@ func Authorization() gin.HandlerFunc {
|
|||||||
|
|
||||||
// 生成一个新token
|
// 生成一个新token
|
||||||
secret := component.JWT().GenerateSecret(user.Password, uuid.NewString(), time.Now().Local().String())
|
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())
|
tokenStr, _, err := component.JWT().GenerateToken(user.Id, secret, userClaims.ExpiresAt.Time, time.Now().Local())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.R(c).AuthorizationFailed("校验失败")
|
response.R(c).AuthorizationFailed("校验失败")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package param
|
package param
|
||||||
|
|
||||||
import "mime/multipart"
|
|
||||||
|
|
||||||
// SetSetting
|
// SetSetting
|
||||||
// @description: 添加/编辑设置
|
// @description: 添加/编辑设置
|
||||||
type SetSetting struct {
|
type SetSetting struct {
|
||||||
@ -9,9 +7,3 @@ type SetSetting struct {
|
|||||||
Data string `json:"data" form:"data" binding:"required"`
|
Data string `json:"data" form:"data" binding:"required"`
|
||||||
Describe string `json:"describe" form:"describe" binding:"omitempty"`
|
Describe string `json:"describe" form:"describe" binding:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import
|
|
||||||
// @description: 导入
|
|
||||||
type Import struct {
|
|
||||||
File *multipart.FileHeader `json:"file" form:"file" binding:"required"`
|
|
||||||
}
|
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
// @description: 登陆相关API
|
// @description: 登陆相关API
|
||||||
// @param r
|
// @param r
|
||||||
func ClientApi(r *gin.RouterGroup) {
|
func ClientApi(r *gin.RouterGroup) {
|
||||||
client := r.Group("client", middleware.RequestLog(), middleware.Authorization())
|
client := r.Group("client", middleware.Authorization(), middleware.RequestLog())
|
||||||
{
|
{
|
||||||
client.POST("", api.Client().Save) // 新增/编辑客户端
|
client.POST("", api.Client().Save) // 新增/编辑客户端
|
||||||
client.DELETE("/:id", api.Client().Delete) // 删除客户端
|
client.DELETE("/:id", api.Client().Delete) // 删除客户端
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
// @description: 控制台相关接口
|
// @description: 控制台相关接口
|
||||||
// @param r
|
// @param r
|
||||||
func DashboardApi(r *gin.RouterGroup) {
|
func DashboardApi(r *gin.RouterGroup) {
|
||||||
dashboard := r.Group("dashboard", middleware.RequestLog(), middleware.Authorization())
|
dashboard := r.Group("dashboard", middleware.Authorization(), middleware.RequestLog())
|
||||||
{
|
{
|
||||||
dashboard.GET("/request/list", api.Dashboard().List) // 请求日志
|
dashboard.GET("/request/list", api.Dashboard().List) // 请求日志
|
||||||
dashboard.GET("/daily-poetry", api.Dashboard().DailyPoetry) // 每日诗词
|
dashboard.GET("/daily-poetry", api.Dashboard().DailyPoetry) // 每日诗词
|
||||||
|
@ -10,14 +10,12 @@ import (
|
|||||||
// @description: 设置相关API
|
// @description: 设置相关API
|
||||||
// @param r
|
// @param r
|
||||||
func SettingApi(r *gin.RouterGroup) {
|
func SettingApi(r *gin.RouterGroup) {
|
||||||
setting := r.Group("setting", middleware.RequestLog(), middleware.Authorization())
|
setting := r.Group("setting", middleware.Authorization(), middleware.RequestLog())
|
||||||
{
|
{
|
||||||
setting.POST("", api.Setting().Set) // 新增/编辑设置
|
setting.POST("", api.Setting().Set) // 新增/编辑设置
|
||||||
setting.DELETE("/:code", api.Setting().Delete) // 删除配置
|
setting.DELETE("/:code", api.Setting().Delete) // 删除配置
|
||||||
setting.GET("", api.Setting().GetSetting) // 获取指定配置
|
setting.GET("", api.Setting().GetSetting) // 获取指定配置
|
||||||
setting.GET("/all", api.Setting().GetAllSetting) // 获取全部配置
|
setting.GET("/all", api.Setting().GetAllSetting) // 获取全部配置
|
||||||
setting.GET("/public-addr", api.Setting().GetPublicAddr) // 获取公网IP
|
setting.GET("/public-addr", api.Setting().GetPublicAddr) // 获取公网IP
|
||||||
setting.GET("/export", api.Setting().Export) // 导出配置文件
|
|
||||||
setting.POST("/import", api.Setting().Import) // 导入配置
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
// @description: 用户相关API
|
// @description: 用户相关API
|
||||||
// @param r
|
// @param r
|
||||||
func UserApi(r *gin.RouterGroup) {
|
func UserApi(r *gin.RouterGroup) {
|
||||||
userApi := r.Group("user", middleware.RequestLog(), middleware.Authorization())
|
userApi := r.Group("user", middleware.Authorization(), middleware.RequestLog())
|
||||||
{
|
{
|
||||||
userApi.GET("/info", api.User().GetLoginUser) // 获取当前登陆用户信息
|
userApi.GET("/info", api.User().GetLoginUser) // 获取当前登陆用户信息
|
||||||
userApi.POST("", api.User().SaveUser) // 新增/编辑用户
|
userApi.POST("", api.User().SaveUser) // 新增/编辑用户
|
||||||
|
@ -1,74 +1,9 @@
|
|||||||
package vo
|
package vo
|
||||||
|
|
||||||
import (
|
|
||||||
"wireguard-ui/global/constant"
|
|
||||||
"wireguard-ui/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SettingItem
|
// SettingItem
|
||||||
// @description: 设置单项
|
// @description: 设置单项
|
||||||
type SettingItem struct {
|
type SettingItem struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Data string `json:"data"`
|
Data string `json:"data"`
|
||||||
Describe string `json:"describe"`
|
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"`
|
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,8 @@ import (
|
|||||||
"gitee.ltd/lxh/logger/log"
|
"gitee.ltd/lxh/logger/log"
|
||||||
"github.com/cowardmrx/go_aliyun_oss"
|
"github.com/cowardmrx/go_aliyun_oss"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl"
|
"golang.zx2c4.com/wireguard/wgctrl"
|
||||||
@ -17,7 +15,6 @@ import (
|
|||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
gl "gorm.io/gorm/logger"
|
gl "gorm.io/gorm/logger"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
"wireguard-ui/config"
|
"wireguard-ui/config"
|
||||||
"wireguard-ui/global/client"
|
"wireguard-ui/global/client"
|
||||||
@ -28,7 +25,6 @@ import (
|
|||||||
func Init() {
|
func Init() {
|
||||||
initLogger() // 初始化日志
|
initLogger() // 初始化日志
|
||||||
initConfig() // 读取配置文件
|
initConfig() // 读取配置文件
|
||||||
initEnv() // 加载环境变量文件
|
|
||||||
initWireguard() // 初始化wireguard客户端
|
initWireguard() // 初始化wireguard客户端
|
||||||
initDatabase() // 初始化数据库
|
initDatabase() // 初始化数据库
|
||||||
initRedis() // 初始化redis
|
initRedis() // 初始化redis
|
||||||
@ -144,22 +140,8 @@ func initOSS() {
|
|||||||
// initLogger
|
// initLogger
|
||||||
// @description: 初始化日志
|
// @description: 初始化日志
|
||||||
func initLogger() {
|
func initLogger() {
|
||||||
|
|
||||||
mode := logger.Dev
|
|
||||||
if os.Getenv("GIN_MODE") == gin.ReleaseMode {
|
|
||||||
mode = logger.Prod
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.InitLogger(logger.LogConfig{
|
logger.InitLogger(logger.LogConfig{
|
||||||
Mode: mode,
|
Mode: logger.Dev,
|
||||||
FileEnable: true,
|
FileEnable: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// initEnv
|
|
||||||
// @description: 初始化环境变量
|
|
||||||
func initEnv() {
|
|
||||||
if err := godotenv.Load(".env"); err != nil {
|
|
||||||
log.Errorf("加载.env文件失败: %v", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
44
main.go
@ -1,15 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"gitee.ltd/lxh/logger/log"
|
"gitee.ltd/lxh/logger/log"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/gin-contrib/pprof"
|
||||||
|
"github.com/spf13/cast"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"time"
|
"time"
|
||||||
tui "wireguard-ui/cli"
|
"wireguard-ui/config"
|
||||||
"wireguard-ui/cron"
|
"wireguard-ui/http/router"
|
||||||
"wireguard-ui/http"
|
|
||||||
"wireguard-ui/initialize"
|
"wireguard-ui/initialize"
|
||||||
"wireguard-ui/script"
|
"wireguard-ui/script"
|
||||||
)
|
)
|
||||||
@ -19,38 +20,23 @@ func init() {
|
|||||||
if err := script.New().Do(); err != nil {
|
if err := script.New().Do(); err != nil {
|
||||||
log.Errorf("执行脚本失败: %v", err.Error())
|
log.Errorf("执行脚本失败: %v", err.Error())
|
||||||
}
|
}
|
||||||
cron.Task()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rand.New(rand.NewSource(time.Now().Local().UnixNano()))
|
rand.New(rand.NewSource(time.Now().Local().UnixNano()))
|
||||||
|
router.Rooters()
|
||||||
|
handler := router.InitRouter()
|
||||||
|
|
||||||
app := &cli.App{
|
if cast.ToBool(os.Getenv("ENABLED_PPROF")) {
|
||||||
Name: "wireguard-ui",
|
pprof.Register(handler, "/monitoring")
|
||||||
Usage: "wireguard-manager-ui",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Commands = []*cli.Command{
|
httpServe := http.Server{
|
||||||
{
|
Addr: fmt.Sprintf(":%d", config.Config.Http.Port),
|
||||||
Name: "http:serve",
|
Handler: handler,
|
||||||
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 := httpServe.ListenAndServe(); err != nil {
|
||||||
if err := app.Run(os.Args); err != nil {
|
log.Panicf("启动http服务端失败: %v", err.Error())
|
||||||
log.Fatalf("服务启动失败: %v", err.Error())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
model/oauth_client.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// AuthClient
|
||||||
|
// @description: 认证客户端
|
||||||
|
type AuthClient struct {
|
||||||
|
Base
|
||||||
|
Name string `json:"name" gorm:"type:varchar(255);not null;comment: '客户端名称'"`
|
||||||
|
ClientID string `json:"clientID" gorm:"type:varchar(255);not null;comment: '客户端ID'"`
|
||||||
|
ClientKey string `json:"clientKey" gorm:"type:varchar(255);not null;comment: '客户端key'"`
|
||||||
|
ExpireAt string `json:"expireAt" gorm:"type:varchar(255);not null;comment: '过期时间'"`
|
||||||
|
IsEnabled int `json:"isEnabled" gorm:"type:int(1);not null;comment: '是否启用'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AuthClient) TableName() string {
|
||||||
|
return "t_oauth_client"
|
||||||
|
}
|
@ -3,7 +3,7 @@ package service
|
|||||||
import "gorm.io/gorm"
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
func Paginate(current, size int64) func(db *gorm.DB) *gorm.DB {
|
func Paginate(current, size int64) func(db *gorm.DB) *gorm.DB {
|
||||||
// 如果页码是-1,就不分页👋
|
// 如果页码是-1,就不分页
|
||||||
if current == -1 {
|
if current == -1 {
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
return db
|
return db
|
||||||
|
@ -2,16 +2,11 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
slog "gitee.ltd/lxh/logger/log"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"strings"
|
|
||||||
gdb "wireguard-ui/global/client"
|
gdb "wireguard-ui/global/client"
|
||||||
"wireguard-ui/http/param"
|
|
||||||
"wireguard-ui/http/vo"
|
"wireguard-ui/http/vo"
|
||||||
"wireguard-ui/model"
|
"wireguard-ui/model"
|
||||||
"wireguard-ui/template/render_data"
|
"wireguard-ui/template/render_data"
|
||||||
"wireguard-ui/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type setting struct{ *gorm.DB }
|
type setting struct{ *gorm.DB }
|
||||||
@ -86,157 +81,6 @@ func (s setting) GetWGServerForConfig() (data *render_data.Server, err error) {
|
|||||||
// @return data
|
// @return data
|
||||||
// @return err
|
// @return err
|
||||||
func (s setting) GetAllSetting(blackList []string) (data []vo.SettingItem, err error) {
|
func (s setting) GetAllSetting(blackList []string) (data []vo.SettingItem, err error) {
|
||||||
err = s.Model(&model.Setting{}).Select("code, data, describe,created_at,updated_at").Where("code not in ?", blackList).Find(&data).Error
|
err = s.Model(&model.Setting{}).Select("code, data, describe").Where("code not in ?", blackList).Find(&data).Error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByCode
|
|
||||||
// @description: 获取指定code的配置
|
|
||||||
// @receiver s
|
|
||||||
// @param code
|
|
||||||
// @return data
|
|
||||||
// @return err
|
|
||||||
func (s setting) GetByCode(code string) (data *model.Setting, err error) {
|
|
||||||
err = s.Model(&model.Setting{}).Where("code = ?", code).Take(&data).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export
|
|
||||||
// @description: 导出
|
|
||||||
// @receiver s
|
|
||||||
// @return data
|
|
||||||
// @return err
|
|
||||||
func (s setting) Export() (data vo.Export, err error) {
|
|
||||||
// 先查询global配置
|
|
||||||
var gs, ss *model.Setting
|
|
||||||
if err = s.Model(&model.Setting{}).Where("code = ?", "WG_SETTING").Take(&gs).Error; err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal([]byte(gs.Data), &data.Global); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询server配置
|
|
||||||
if err = s.Model(&model.Setting{}).Where("code = ?", "WG_SERVER").Take(&ss).Error; err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal([]byte(ss.Data), &data.Server); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询client配置
|
|
||||||
var clients []vo.ClientItem
|
|
||||||
if err = s.Model(&model.Client{}).Select("id,name,email,ip_allocation as ip_allocation_str," +
|
|
||||||
"allowed_ips as allowed_ips_str,extra_allowed_ips as extra_allowed_ips_str," +
|
|
||||||
"endpoint,use_server_dns,keys as keys_str," +
|
|
||||||
"enabled,offline_monitoring").Order("created_at DESC").Find(&clients).Error; err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, v := range clients {
|
|
||||||
if v.KeysStr != "" {
|
|
||||||
_ = json.Unmarshal([]byte(v.KeysStr), &clients[i].Keys)
|
|
||||||
}
|
|
||||||
if v.IpAllocationStr != "" {
|
|
||||||
clients[i].IpAllocation = strings.Split(v.IpAllocationStr, ",")
|
|
||||||
}
|
|
||||||
if v.AllowedIpsStr != "" {
|
|
||||||
clients[i].AllowedIps = strings.Split(v.AllowedIpsStr, ",")
|
|
||||||
} else {
|
|
||||||
clients[i].AllowedIps = []string{}
|
|
||||||
}
|
|
||||||
if v.ExtraAllowedIpsStr != "" {
|
|
||||||
clients[i].ExtraAllowedIps = strings.Split(v.ExtraAllowedIpsStr, ",")
|
|
||||||
} else {
|
|
||||||
clients[i].ExtraAllowedIps = []string{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询其他配置
|
|
||||||
var others []vo.Other
|
|
||||||
if err = s.Model(&model.Setting{}).Where("code not in ?", []string{"WG_SETTING", "WG_SERVER"}).Find(&others).Error; err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data.Other = others
|
|
||||||
cj, _ := json.Marshal(clients)
|
|
||||||
_ = json.Unmarshal(cj, &data.Clients)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import
|
|
||||||
// @description: 导入
|
|
||||||
// @receiver s
|
|
||||||
// @param data
|
|
||||||
// @return err
|
|
||||||
func (s setting) Import(data *vo.Export, loginUser *vo.User) (err error) {
|
|
||||||
// 获取导入系统的公网IP地址
|
|
||||||
pubAddr := utils.Network().GetHostPublicIP()
|
|
||||||
data.Global.EndpointAddress = pubAddr
|
|
||||||
// 先更新global配置
|
|
||||||
gst, _ := json.Marshal(data.Global)
|
|
||||||
gs := &model.Setting{
|
|
||||||
Code: "WG_SETTING",
|
|
||||||
Data: string(gst),
|
|
||||||
Describe: "服务端全局配置",
|
|
||||||
}
|
|
||||||
if err = s.SetData(gs); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
st, _ := json.Marshal(data.Server)
|
|
||||||
ss := &model.Setting{
|
|
||||||
Code: "WG_SERVER",
|
|
||||||
Data: string(st),
|
|
||||||
Describe: "服务端配置",
|
|
||||||
}
|
|
||||||
if err = s.SetData(ss); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新client配置
|
|
||||||
for _, v := range data.Clients {
|
|
||||||
keys := ¶m.Keys{
|
|
||||||
PrivateKey: v.Keys.PrivateKey,
|
|
||||||
PublicKey: v.Keys.PublicKey,
|
|
||||||
PresharedKey: v.Keys.PresharedKey,
|
|
||||||
}
|
|
||||||
cc := param.SaveClient{
|
|
||||||
Name: v.Name,
|
|
||||||
Email: v.Email,
|
|
||||||
IpAllocation: v.IpAllocation,
|
|
||||||
AllowedIps: v.AllowedIps,
|
|
||||||
ExtraAllowedIps: v.ExtraAllowedIps,
|
|
||||||
UseServerDns: v.UseServerDns,
|
|
||||||
Keys: keys,
|
|
||||||
Enabled: v.Enabled,
|
|
||||||
OfflineMonitoring: v.OfflineMonitoring,
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Endpoint != "" {
|
|
||||||
port := strings.Split(v.Endpoint, ":")[1]
|
|
||||||
endpoint := fmt.Sprintf("%s:%s", pubAddr, port)
|
|
||||||
cc.Endpoint = endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := Client().SaveClient(cc, loginUser); err != nil {
|
|
||||||
slog.Errorf("客户端[%s]导入失败: %v", v.Name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 其他配置写入
|
|
||||||
for _, v := range data.Other {
|
|
||||||
if err = s.SetData(&model.Setting{
|
|
||||||
Code: v.Code,
|
|
||||||
Data: v.Data,
|
|
||||||
Describe: v.Describe,
|
|
||||||
}); err != nil {
|
|
||||||
slog.Errorf("其他配置[%s]导入失败: %v", v.Code, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
gdb "wireguard-ui/global/client"
|
gdb "wireguard-ui/global/client"
|
||||||
"wireguard-ui/global/constant"
|
"wireguard-ui/global/constant"
|
||||||
@ -40,11 +39,6 @@ func (s user) CreateUser(user *model.User) (err error) {
|
|||||||
return s.Model(&model.User{}).Where("id = ?", user.Id).Updates(&updates).Error
|
return s.Model(&model.User{}).Where("id = ?", user.Id).Updates(&updates).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断账号是否已经存在
|
|
||||||
if _, err = s.GetUserByAccount(user.Account); err == nil {
|
|
||||||
return errors.New("账号已经存在,请勿重复创建!")
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultPassword := utils.Password().GenerateHashPassword("admin123")
|
defaultPassword := utils.Password().GenerateHashPassword("admin123")
|
||||||
if user.Password == "" { // 没有密码给一个默认密码
|
if user.Password == "" { // 没有密码给一个默认密码
|
||||||
user.Password = defaultPassword
|
user.Password = defaultPassword
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fileUtils struct{}
|
|
||||||
|
|
||||||
func FileUtils() fileUtils {
|
|
||||||
return fileUtils{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateFile
|
|
||||||
// @description: 生成文件
|
|
||||||
// @receiver fileUtils
|
|
||||||
// @param filename 文件名称
|
|
||||||
// @param content 文件内容
|
|
||||||
// @return error
|
|
||||||
func (fileUtils) GenerateFile(filename string, content []byte) (filepath string, err error) {
|
|
||||||
path := "/tmp/"
|
|
||||||
if os.Getenv("GIN_MODE") != "release" {
|
|
||||||
path = "E:\\Workspace\\Go\\wireguard-ui\\template\\tmp\\"
|
|
||||||
}
|
|
||||||
filepath = fmt.Sprintf("%s%s", path, filename)
|
|
||||||
err = os.WriteFile(filepath, content, 0777)
|
|
||||||
return filepath, err
|
|
||||||
}
|
|
@ -2,35 +2,28 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jordan-wright/email"
|
"github.com/jordan-wright/email"
|
||||||
"github.com/spf13/cast"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"wireguard-ui/model"
|
"wireguard-ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mail struct {
|
type mail struct {
|
||||||
*email.Email
|
*email.Email
|
||||||
addr string
|
addr string
|
||||||
auth smtp.Auth
|
auth smtp.Auth
|
||||||
conf map[string]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Mail(conf *model.Setting) *mail {
|
func Mail() *mail {
|
||||||
// 解析配置文件
|
|
||||||
var mailConf = make(map[string]string)
|
|
||||||
_ = json.Unmarshal([]byte(conf.Data), &mailConf)
|
|
||||||
var m mail
|
var m mail
|
||||||
em := email.NewEmail()
|
em := email.NewEmail()
|
||||||
m.Email = em
|
m.Email = em
|
||||||
m.auth = smtp.PlainAuth("", mailConf["user"], mailConf["password"], mailConf["host"])
|
m.auth = smtp.PlainAuth("", config.Config.Mail.User, config.Config.Mail.Password, config.Config.Mail.Host)
|
||||||
m.addr = fmt.Sprintf("%s:%s", mailConf["host"], mailConf["port"])
|
m.addr = fmt.Sprintf("%s:%d", config.Config.Mail.Host, config.Config.Mail.Port)
|
||||||
m.conf = mailConf
|
|
||||||
return &m
|
return &m
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +47,7 @@ func (m *mail) VerifyConfig() (err error) {
|
|||||||
// @param attacheFilePath
|
// @param attacheFilePath
|
||||||
// @return err
|
// @return err
|
||||||
func (m *mail) SendMail(to, subject, content, attacheFilePath string) (err error) {
|
func (m *mail) SendMail(to, subject, content, attacheFilePath string) (err error) {
|
||||||
m.From = fmt.Sprintf("wg-dashboard <%s>", m.conf["user"])
|
m.From = fmt.Sprintf("wg-dashboard <%s>", config.Config.Mail.User)
|
||||||
m.To = []string{to}
|
m.To = []string{to}
|
||||||
m.Subject = subject
|
m.Subject = subject
|
||||||
m.Text = []byte(content)
|
m.Text = []byte(content)
|
||||||
@ -68,13 +61,13 @@ func (m *mail) SendMail(to, subject, content, attacheFilePath string) (err error
|
|||||||
atch.Header = emHeader
|
atch.Header = emHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
if cast.ToBool(m.conf["skipTls"]) {
|
if config.Config.Mail.SkipTls {
|
||||||
return m.Send(m.addr, m.auth)
|
return m.Send(m.addr, m.auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{}
|
tlsConfig := &tls.Config{}
|
||||||
tlsConfig.InsecureSkipVerify = cast.ToBool(m.conf["skipTls"])
|
tlsConfig.InsecureSkipVerify = config.Config.Mail.SkipTls
|
||||||
tlsConfig.ServerName = m.conf["host"]
|
tlsConfig.ServerName = config.Config.Mail.Host
|
||||||
|
|
||||||
return m.SendWithTLS(m.addr, m.auth, tlsConfig)
|
return m.SendWithTLS(m.addr, m.auth, tlsConfig)
|
||||||
}
|
}
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"gitee.ltd/lxh/logger/log"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
"strings"
|
|
||||||
"wireguard-ui/global/client"
|
|
||||||
"wireguard-ui/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wxid_472vas3av5ug22 /api/sendTextMsg
|
|
||||||
type wechatNotify struct {
|
|
||||||
Addr string
|
|
||||||
Path string
|
|
||||||
Method string
|
|
||||||
toUserId string
|
|
||||||
}
|
|
||||||
|
|
||||||
func WechatNotify(setting *model.Setting) wechatNotify {
|
|
||||||
var sm = make(map[string]string)
|
|
||||||
_ = json.Unmarshal([]byte(setting.Data), &sm)
|
|
||||||
|
|
||||||
return wechatNotify{
|
|
||||||
Addr: sm["addr"],
|
|
||||||
Path: sm["path"],
|
|
||||||
Method: sm["method"],
|
|
||||||
toUserId: sm["toUserWxId"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendTextMessage
|
|
||||||
// @description: 发送文字通知
|
|
||||||
// @receiver website
|
|
||||||
// @param msg
|
|
||||||
// @return error
|
|
||||||
func (w wechatNotify) SendTextMessage(msg string) error {
|
|
||||||
log.Debugf("发送通知到微信: %v", msg)
|
|
||||||
req := client.HttpClient.R()
|
|
||||||
|
|
||||||
req.SetHeader("Content-Type", "application/json")
|
|
||||||
req.SetBody(map[string]string{
|
|
||||||
"wxid": w.toUserId,
|
|
||||||
"msg": msg,
|
|
||||||
})
|
|
||||||
|
|
||||||
req.URL = fmt.Sprintf("%s:%s", w.Addr, w.Path)
|
|
||||||
switch strings.ToUpper(w.Method) {
|
|
||||||
case "POST":
|
|
||||||
req.Method = "POST"
|
|
||||||
}
|
|
||||||
result, err := req.SetDebug(true).Send()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if jsoniter.Get(result.Body(), "code").ToInt() != 1 {
|
|
||||||
log.Errorf("发送通知到微信失败: %v", jsoniter.Get(result.Body(), "msg").ToString())
|
|
||||||
return errors.New("发送通知到微信失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -32,7 +32,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@unocss/eslint-config": "^0.55.7",
|
"@unocss/eslint-config": "^0.55.7",
|
||||||
"@vicons/ionicons5": "^0.12.0",
|
|
||||||
"@vueuse/core": "^10.4.1",
|
"@vueuse/core": "^10.4.1",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "5.1.12",
|
"@wangeditor/editor-for-vue": "5.1.12",
|
||||||
@ -41,7 +40,6 @@
|
|||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"md-editor-v3": "^4.7.0",
|
"md-editor-v3": "^4.7.0",
|
||||||
"mitt": "^3.0.1",
|
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.6",
|
||||||
"vite": "^4.4.11",
|
"vite": "^4.4.11",
|
||||||
|
16
web/pnpm-lock.yaml
generated
@ -11,9 +11,6 @@ importers:
|
|||||||
'@unocss/eslint-config':
|
'@unocss/eslint-config':
|
||||||
specifier: ^0.55.7
|
specifier: ^0.55.7
|
||||||
version: 0.55.7(eslint@8.50.0)(typescript@5.2.2)
|
version: 0.55.7(eslint@8.50.0)(typescript@5.2.2)
|
||||||
'@vicons/ionicons5':
|
|
||||||
specifier: ^0.12.0
|
|
||||||
version: 0.12.0
|
|
||||||
'@vueuse/core':
|
'@vueuse/core':
|
||||||
specifier: ^10.4.1
|
specifier: ^10.4.1
|
||||||
version: 10.4.1(vue@3.3.4)
|
version: 10.4.1(vue@3.3.4)
|
||||||
@ -38,9 +35,6 @@ importers:
|
|||||||
md-editor-v3:
|
md-editor-v3:
|
||||||
specifier: ^4.7.0
|
specifier: ^4.7.0
|
||||||
version: 4.7.0(@codemirror/state@6.2.1)(@codemirror/view@6.21.3)(@lezer/common@1.1.0)(vue@3.3.4)
|
version: 4.7.0(@codemirror/state@6.2.1)(@codemirror/view@6.21.3)(@lezer/common@1.1.0)(vue@3.3.4)
|
||||||
mitt:
|
|
||||||
specifier: ^3.0.1
|
|
||||||
version: 3.0.1
|
|
||||||
mockjs:
|
mockjs:
|
||||||
specifier: ^1.1.0
|
specifier: ^1.1.0
|
||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
@ -1052,9 +1046,6 @@ packages:
|
|||||||
'@vavt/util@1.4.0':
|
'@vavt/util@1.4.0':
|
||||||
resolution: {integrity: sha512-qLhaokwifMTFqoo4UE2JZUyaxCzX9T4WcIt2KzznbtBrCM4CG119pY/cKqq6jDa3c1phUvPCoIfjWfnF9nj4NA==}
|
resolution: {integrity: sha512-qLhaokwifMTFqoo4UE2JZUyaxCzX9T4WcIt2KzznbtBrCM4CG119pY/cKqq6jDa3c1phUvPCoIfjWfnF9nj4NA==}
|
||||||
|
|
||||||
'@vicons/ionicons5@0.12.0':
|
|
||||||
resolution: {integrity: sha512-Iy1EUVRpX0WWxeu1VIReR1zsZLMc4fqpt223czR+Rpnrwu7pt46nbnC2ycO7ItI/uqDLJxnbcMC7FujKs9IfFA==}
|
|
||||||
|
|
||||||
'@vite-plugin-vue-devtools/core@1.0.0-rc.7':
|
'@vite-plugin-vue-devtools/core@1.0.0-rc.7':
|
||||||
resolution: {integrity: sha512-Tv9JeRZQ6KDwSkOQJvXc5TBcc4fkSazA96GDhi99v4VCthTgXjnhaah41CeZD3hFDKnNS0MHKFFqN+RHAgYDyQ==}
|
resolution: {integrity: sha512-Tv9JeRZQ6KDwSkOQJvXc5TBcc4fkSazA96GDhi99v4VCthTgXjnhaah41CeZD3hFDKnNS0MHKFFqN+RHAgYDyQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3017,9 +3008,6 @@ packages:
|
|||||||
minimist@1.2.8:
|
minimist@1.2.8:
|
||||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
|
|
||||||
mitt@3.0.1:
|
|
||||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
|
||||||
|
|
||||||
mixin-deep@1.3.2:
|
mixin-deep@1.3.2:
|
||||||
resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
|
resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -5502,8 +5490,6 @@ snapshots:
|
|||||||
|
|
||||||
'@vavt/util@1.4.0': {}
|
'@vavt/util@1.4.0': {}
|
||||||
|
|
||||||
'@vicons/ionicons5@0.12.0': {}
|
|
||||||
|
|
||||||
'@vite-plugin-vue-devtools/core@1.0.0-rc.7(vite@4.4.11(@types/node@20.5.1)(sass@1.69.0)(terser@5.21.0))':
|
'@vite-plugin-vue-devtools/core@1.0.0-rc.7(vite@4.4.11(@types/node@20.5.1)(sass@1.69.0)(terser@5.21.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.23.0
|
'@babel/parser': 7.23.0
|
||||||
@ -7605,8 +7591,6 @@ snapshots:
|
|||||||
|
|
||||||
minimist@1.2.8: {}
|
minimist@1.2.8: {}
|
||||||
|
|
||||||
mitt@3.0.1: {}
|
|
||||||
|
|
||||||
mixin-deep@1.3.2:
|
mixin-deep@1.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
for-in: 1.0.2
|
for-in: 1.0.2
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { request } from '@/utils'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
exportConfig: () => request.get('/setting/export'), // 导出配置
|
|
||||||
importConfig: (data) => request.post('/setting/import',data,{
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
<template>
|
|
||||||
<n-icon mr-20 size="18" style="cursor: pointer" @click="importConfig()">
|
|
||||||
<icon-gg-import />
|
|
||||||
</n-icon>
|
|
||||||
<n-icon mr-20 size="18" style="cursor: pointer" @click="exportConfig()">
|
|
||||||
<icon-ph-export-bold />
|
|
||||||
</n-icon>
|
|
||||||
<n-modal
|
|
||||||
v-model:show="showImportUploader"
|
|
||||||
transform-origin="center"
|
|
||||||
preset="card"
|
|
||||||
title="导入配置"
|
|
||||||
:bordered="false"
|
|
||||||
size="large"
|
|
||||||
style="width: 400px"
|
|
||||||
header-style="text-align: center"
|
|
||||||
>
|
|
||||||
<n-upload
|
|
||||||
ref="uploadRef"
|
|
||||||
:multiple="false"
|
|
||||||
directory-dnd
|
|
||||||
:max="1"
|
|
||||||
:custom-request="customRequest"
|
|
||||||
name="file"
|
|
||||||
accept=".json"
|
|
||||||
:default-upload="false"
|
|
||||||
@before-upload="uploadCheck"
|
|
||||||
>
|
|
||||||
<n-upload-dragger>
|
|
||||||
<div style="margin-bottom: 12px">
|
|
||||||
<n-icon size="48" :depth="3">
|
|
||||||
<ArchiveIcon />
|
|
||||||
</n-icon>
|
|
||||||
</div>
|
|
||||||
<n-text style="font-size: 16px">
|
|
||||||
点击或者拖动文件到该区域来上传
|
|
||||||
</n-text>
|
|
||||||
<n-p depth="3" style="margin: 8px 0 0 0">
|
|
||||||
注意:上传后将覆盖原有的配置以及客户端数据等,请谨慎操作!
|
|
||||||
</n-p>
|
|
||||||
</n-upload-dragger>
|
|
||||||
</n-upload>
|
|
||||||
<n-button type="primary" style="margin-left: 40%" @click="submitUpload">确定</n-button>
|
|
||||||
</n-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import api from '@/api/setting'
|
|
||||||
import { ArchiveOutline as ArchiveIcon } from "@vicons/ionicons5";
|
|
||||||
import { router } from '@/router'
|
|
||||||
import event from '@/utils/event/event'
|
|
||||||
|
|
||||||
const { $bus } = event();
|
|
||||||
|
|
||||||
const showImportUploader = ref(false)
|
|
||||||
const uploadRef = ref(null)
|
|
||||||
|
|
||||||
// 导入
|
|
||||||
function importConfig() {
|
|
||||||
showImportUploader.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出
|
|
||||||
function exportConfig() {
|
|
||||||
$dialog.confirm({
|
|
||||||
type: 'warning',
|
|
||||||
title: '导出配置',
|
|
||||||
content: `是否需要导出系统全部配置?`,
|
|
||||||
async confirm() {
|
|
||||||
api.exportConfig().then(response => {
|
|
||||||
const blob = new Blob([JSON.stringify(response.data)], {
|
|
||||||
type: "text/plain"
|
|
||||||
});
|
|
||||||
const link = document.createElement("a"); // 创建a标签
|
|
||||||
link.download = "config.json"; // a标签添加属性
|
|
||||||
link.style.display = "none";
|
|
||||||
link.href = URL.createObjectURL(blob);
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click(); // 执行下载
|
|
||||||
URL.revokeObjectURL(link.href); // 释放url
|
|
||||||
document.body.removeChild(link); // 释放标签
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传前检查
|
|
||||||
function uploadCheck(data) {
|
|
||||||
if (data.file.file?.name !== "config.json") {
|
|
||||||
$message.error("导入文件只能是[config.json]");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (data.file.file?.type !== "application/json") {
|
|
||||||
$message.error("只能上传json类型文件,请重新上传");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义上传
|
|
||||||
function customRequest(file) {
|
|
||||||
api.importConfig({
|
|
||||||
file: file.file.file,
|
|
||||||
}).then(response => {
|
|
||||||
if (response.data.code === 200) {
|
|
||||||
showImportUploader.value = false;
|
|
||||||
switch (router.options.history.location) {
|
|
||||||
case "/client":
|
|
||||||
$bus.emit('refreshClients',true);
|
|
||||||
break;
|
|
||||||
case "/setting":
|
|
||||||
$bus.emit('refreshSetting',true)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$message.error(response.data.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// 点击按钮上传
|
|
||||||
function submitUpload() {
|
|
||||||
uploadRef.value?.submit();
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -72,11 +72,8 @@
|
|||||||
import { useUserStore } from '@/store'
|
import { useUserStore } from '@/store'
|
||||||
import { renderIcon } from '@/utils'
|
import { renderIcon } from '@/utils'
|
||||||
import api from '@/api/user'
|
import api from '@/api/user'
|
||||||
import event from '@/utils/event/event'
|
|
||||||
import { router } from '@/router'
|
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const { $bus } = event();
|
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
@ -215,9 +212,6 @@ async function updateInfo() {
|
|||||||
infoFormModel.value = ref(null)
|
infoFormModel.value = ref(null)
|
||||||
showInfoModel.value = false
|
showInfoModel.value = false
|
||||||
await useUserStore().getUserInfo()
|
await useUserStore().getUserInfo()
|
||||||
if (router.options.history.location === '/user') {
|
|
||||||
$bus.emit('refreshUserInfo',true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,7 @@
|
|||||||
<MenuCollapse />
|
<MenuCollapse />
|
||||||
<BreadCrumb ml-15 hidden sm:block />
|
<BreadCrumb ml-15 hidden sm:block />
|
||||||
</div>
|
</div>
|
||||||
<div ml-auto flex items-center v-if="loginUser.account === 'admin'">
|
<div ml-auto flex items-center>
|
||||||
<Export/>
|
|
||||||
<FullScreen />
|
|
||||||
<UserAvatar />
|
|
||||||
</div>
|
|
||||||
<div ml-auto flex items-center v-else>
|
|
||||||
<FullScreen />
|
<FullScreen />
|
||||||
<UserAvatar />
|
<UserAvatar />
|
||||||
</div>
|
</div>
|
||||||
@ -19,7 +14,4 @@ import BreadCrumb from './components/BreadCrumb.vue'
|
|||||||
import MenuCollapse from './components/MenuCollapse.vue'
|
import MenuCollapse from './components/MenuCollapse.vue'
|
||||||
import FullScreen from './components/FullScreen.vue'
|
import FullScreen from './components/FullScreen.vue'
|
||||||
import UserAvatar from './components/UserAvatar.vue'
|
import UserAvatar from './components/UserAvatar.vue'
|
||||||
import Export from './components/Export.vue'
|
|
||||||
import { useUserStore } from '@/store'
|
|
||||||
const loginUser = useUserStore()
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -9,14 +9,10 @@ import { setupRouter } from '@/router'
|
|||||||
import { setupStore } from '@/store'
|
import { setupStore } from '@/store'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { setupNaiveDiscreteApi } from './utils'
|
import { setupNaiveDiscreteApi } from './utils'
|
||||||
import mitt from 'mitt'
|
|
||||||
|
|
||||||
const EventMitt = mitt();
|
|
||||||
|
|
||||||
async function setupApp() {
|
async function setupApp() {
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
setupStore(app)
|
setupStore(app)
|
||||||
app.config.globalProperties.$bus = EventMitt;
|
|
||||||
await setupRouter(app)
|
await setupRouter(app)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
setupNaiveDiscreteApi()
|
setupNaiveDiscreteApi()
|
||||||
|
@ -37,6 +37,7 @@ export async function addDynamicRoutes() {
|
|||||||
// 有token的情况
|
// 有token的情况
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
!userStore.id && (await userStore.getUserInfo())
|
!userStore.id && (await userStore.getUserInfo())
|
||||||
|
|
||||||
@ -48,8 +49,8 @@ export async function addDynamicRoutes() {
|
|||||||
router.addRoute(NOT_FOUND_ROUTE)
|
router.addRoute(NOT_FOUND_ROUTE)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
userStore.logout()
|
|
||||||
$message.error('初始化用户信息失败: ' + error)
|
$message.error('初始化用户信息失败: ' + error)
|
||||||
|
userStore.logout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { getCurrentInstance } from "vue";
|
|
||||||
|
|
||||||
export default function event() {
|
|
||||||
const instance = getCurrentInstance();
|
|
||||||
const globalProperties = instance?.appContext.config.globalProperties;
|
|
||||||
return { ...globalProperties };
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './event'
|
|
@ -2,4 +2,3 @@ export * from './common'
|
|||||||
export * from './storage'
|
export * from './storage'
|
||||||
export * from './http'
|
export * from './http'
|
||||||
export * from './auth'
|
export * from './auth'
|
||||||
export * from './event'
|
|
@ -85,14 +85,14 @@
|
|||||||
{{ cip }}
|
{{ cip }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<!-- <n-form-item label="可访问IP:">-->
|
<n-form-item label="可访问IP:">
|
||||||
<!-- <n-button v-if="row.allowedIps.length <= 0" dashed size="small">-->
|
<n-button v-if="row.allowedIps.length <= 0" dashed size="small">
|
||||||
<!-- - -->
|
-
|
||||||
<!-- </n-button>-->
|
</n-button>
|
||||||
<!-- <n-button v-else dashed mr-2 type="warning" v-for="aip in row.allowedIps" size="small">-->
|
<n-button v-else dashed mr-2 type="warning" v-for="aip in row.allowedIps" size="small">
|
||||||
<!-- {{ aip }}-->
|
{{ aip }}
|
||||||
<!-- </n-button>-->
|
</n-button>
|
||||||
<!-- </n-form-item>-->
|
</n-form-item>
|
||||||
<n-form-item label="创建人:">
|
<n-form-item label="创建人:">
|
||||||
<n-button color="#54150F" dashed size="small">
|
<n-button color="#54150F" dashed size="small">
|
||||||
{{ row.createUser }}
|
{{ row.createUser }}
|
||||||
@ -106,7 +106,7 @@
|
|||||||
禁用
|
禁用
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="离线通知:">
|
<n-form-item label="离线监听:">
|
||||||
<n-button v-if="row.offlineMonitoring === 1" color="#067748" round :bordered="false" size="small">
|
<n-button v-if="row.offlineMonitoring === 1" color="#067748" round :bordered="false" size="small">
|
||||||
启用
|
启用
|
||||||
</n-button>
|
</n-button>
|
||||||
@ -114,12 +114,12 @@
|
|||||||
禁用
|
禁用
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<!-- <n-form-item class="timeItem" label="时间:">-->
|
<n-form-item class="timeItem" label="时间:">
|
||||||
<!-- <n-space vertical>-->
|
<n-space vertical>
|
||||||
<!-- <span> 创建时间: {{ row.createdAt }}</span>-->
|
<span> 创建时间: {{ row.createdAt }}</span>
|
||||||
<!-- <span> 更新时间: {{ row.updatedAt }}</span>-->
|
<span> 更新时间: {{ row.updatedAt }}</span>
|
||||||
<!-- </n-space>-->
|
</n-space>
|
||||||
<!-- </n-form-item>-->
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
@ -228,18 +228,12 @@
|
|||||||
<n-radio :value="0" :checked="editModalForm.enabled === 0" @change="editModalForm.enabled = 0">禁用</n-radio>
|
<n-radio :value="0" :checked="editModalForm.enabled === 0" @change="editModalForm.enabled = 0">禁用</n-radio>
|
||||||
</n-radio-group>
|
</n-radio-group>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="离线通知" path="offlineMonitoring">
|
<n-form-item label="离线监听" path="offlineMonitoring">
|
||||||
<n-radio-group :value="editModalForm.offlineMonitoring">
|
<n-radio-group :value="editModalForm.offlineMonitoring">
|
||||||
<n-radio :value="1" :checked="editModalForm.offlineMonitoring === 1" @change="editModalForm.offlineMonitoring = 1">启用</n-radio>
|
<n-radio :value="1" :checked="editModalForm.offlineMonitoring === 1" @change="editModalForm.offlineMonitoring = 1">启用</n-radio>
|
||||||
<n-radio :value="0" :checked="editModalForm.offlineMonitoring === 0" @change="editModalForm.offlineMonitoring = 0">禁用</n-radio>
|
<n-radio :value="0" :checked="editModalForm.offlineMonitoring === 0" @change="editModalForm.offlineMonitoring = 0">禁用</n-radio>
|
||||||
</n-radio-group>
|
</n-radio-group>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item class="timeItem" label="时间:">
|
|
||||||
<n-space vertical>
|
|
||||||
<span> 创建时间: {{ editModalForm.createdAt }}</span>
|
|
||||||
<span> 更新时间: {{ editModalForm.updatedAt }}</span>
|
|
||||||
</n-space>
|
|
||||||
</n-form-item>
|
|
||||||
<n-button type="info" style="margin-left: 40%" @click="updateClient()">确认</n-button>
|
<n-button type="info" style="margin-left: 40%" @click="updateClient()">确认</n-button>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
@ -325,7 +319,7 @@
|
|||||||
<n-radio :value="0" :checked="addModalForm.enabled === 0" @change="addModalForm.enabled = 0">禁用</n-radio>
|
<n-radio :value="0" :checked="addModalForm.enabled === 0" @change="addModalForm.enabled = 0">禁用</n-radio>
|
||||||
</n-radio-group>
|
</n-radio-group>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="离线通知" path="offlineMonitoring">
|
<n-form-item label="离线监听" path="offlineMonitoring">
|
||||||
<n-radio-group :value="addModalForm.offlineMonitoring">
|
<n-radio-group :value="addModalForm.offlineMonitoring">
|
||||||
<n-radio :value="1" :checked="addModalForm.offlineMonitoring === 1" @change="addModalForm.offlineMonitoring = 1">启用</n-radio>
|
<n-radio :value="1" :checked="addModalForm.offlineMonitoring === 1" @change="addModalForm.offlineMonitoring = 1">启用</n-radio>
|
||||||
<n-radio :value="0" :checked="addModalForm.offlineMonitoring === 0" @change="addModalForm.offlineMonitoring = 0">禁用</n-radio>
|
<n-radio :value="0" :checked="addModalForm.offlineMonitoring === 0" @change="addModalForm.offlineMonitoring = 0">禁用</n-radio>
|
||||||
@ -341,9 +335,6 @@ import { NButton } from 'naive-ui'
|
|||||||
import { debounce, ellipsis } from '@/utils'
|
import { debounce, ellipsis } from '@/utils'
|
||||||
import clientApi from '@/views/client/api'
|
import clientApi from '@/views/client/api'
|
||||||
import QueryBar from '@/components/query-bar/QueryBar.vue'
|
import QueryBar from '@/components/query-bar/QueryBar.vue'
|
||||||
import event from '@/utils/event/event'
|
|
||||||
|
|
||||||
const { $bus } = event();
|
|
||||||
|
|
||||||
const selOptions = [
|
const selOptions = [
|
||||||
{
|
{
|
||||||
@ -446,9 +437,7 @@ const editModalForm = ref({
|
|||||||
presharedKey: ''
|
presharedKey: ''
|
||||||
},
|
},
|
||||||
enabled: 1,
|
enabled: 1,
|
||||||
offlineMonitoring: 1,
|
offlineMonitoring: 1
|
||||||
createdAt: '',
|
|
||||||
updatedAt: ''
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 添加模态框的数据集
|
// 添加模态框的数据集
|
||||||
@ -584,8 +573,6 @@ function openEditModal(row) {
|
|||||||
editModalForm.value.keys.presharedKey = row.keys.presharedKey
|
editModalForm.value.keys.presharedKey = row.keys.presharedKey
|
||||||
editModalForm.value.enabled = row.enabled
|
editModalForm.value.enabled = row.enabled
|
||||||
editModalForm.value.offlineMonitoring = row.offlineMonitoring
|
editModalForm.value.offlineMonitoring = row.offlineMonitoring
|
||||||
editModalForm.value.createdAt = row.createdAt
|
|
||||||
editModalForm.value.updatedAt = row.updatedAt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更改客户端信息
|
// 更改客户端信息
|
||||||
@ -671,13 +658,6 @@ function search() {
|
|||||||
getClientList()
|
getClientList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听事件
|
|
||||||
$bus.on("refreshClients",value => {
|
|
||||||
if (value) {
|
|
||||||
getClientList();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
getClientList()
|
getClientList()
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -6,7 +6,6 @@ export default {
|
|||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/client',
|
redirect: '/client',
|
||||||
meta: {
|
meta: {
|
||||||
title: '客户端',
|
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
@ -174,9 +174,6 @@
|
|||||||
<n-radio :value="false" :checked="editFormModel.data[index] === false" @change="editFormModel.data[index] = false">否</n-radio>
|
<n-radio :value="false" :checked="editFormModel.data[index] === false" @change="editFormModel.data[index] = false">否</n-radio>
|
||||||
</n-radio-group>
|
</n-radio-group>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="配置描述">
|
|
||||||
<n-input v-model:value="editFormModel.describe" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item>
|
<n-form-item>
|
||||||
<n-button type="info" @click="updateSetting">确认</n-button>
|
<n-button type="info" @click="updateSetting">确认</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@ -212,10 +209,6 @@ import AppPage from '@/components/page/AppPage.vue'
|
|||||||
import api from '@/views/setting/api'
|
import api from '@/views/setting/api'
|
||||||
import { NButton } from 'naive-ui'
|
import { NButton } from 'naive-ui'
|
||||||
import { renderIcon } from '@/utils'
|
import { renderIcon } from '@/utils'
|
||||||
import event from '@/utils/event/event'
|
|
||||||
|
|
||||||
const { $bus } = event();
|
|
||||||
const tabCode = ref("")
|
|
||||||
|
|
||||||
// 表头
|
// 表头
|
||||||
const tableColumns = [
|
const tableColumns = [
|
||||||
@ -408,15 +401,12 @@ function tabChange(code) {
|
|||||||
switch (code) {
|
switch (code) {
|
||||||
case "Server":
|
case "Server":
|
||||||
getServerConfig()
|
getServerConfig()
|
||||||
tabCode.value = "Server"
|
|
||||||
break;
|
break;
|
||||||
case "Global":
|
case "Global":
|
||||||
getGlobalConfig()
|
getGlobalConfig()
|
||||||
tabCode.value = "Global"
|
|
||||||
break;
|
break;
|
||||||
case "Other":
|
case "Other":
|
||||||
allSetting()
|
allSetting()
|
||||||
tabCode.value = "Other"
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -490,22 +480,6 @@ async function addSetting() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$bus.on("refreshSetting",value => {
|
|
||||||
if (value) {
|
|
||||||
if (tabCode.value === "" || tabCode.value === undefined) {
|
|
||||||
getServerConfig()
|
|
||||||
} else {
|
|
||||||
switch (tabCode.value) {
|
|
||||||
case "Server":
|
|
||||||
getServerConfig()
|
|
||||||
break;
|
|
||||||
case "Global":
|
|
||||||
getGlobalConfig()
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
getServerConfig()
|
getServerConfig()
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -6,7 +6,6 @@ export default {
|
|||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/setting',
|
redirect: '/setting',
|
||||||
meta: {
|
meta: {
|
||||||
title: '设置',
|
|
||||||
order: 3,
|
order: 3,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
@ -106,9 +106,6 @@ import userApi from '@/api/user'
|
|||||||
import { NAvatar,NTag,NButton } from 'naive-ui'
|
import { NAvatar,NTag,NButton } from 'naive-ui'
|
||||||
import { renderIcon } from '@/utils'
|
import { renderIcon } from '@/utils'
|
||||||
import { useUserStore } from '@/store'
|
import { useUserStore } from '@/store'
|
||||||
import event from '@/utils/event/event'
|
|
||||||
|
|
||||||
const { $bus } = event();
|
|
||||||
|
|
||||||
const infoFormRef = ref()
|
const infoFormRef = ref()
|
||||||
|
|
||||||
@ -403,12 +400,6 @@ function addUser() {
|
|||||||
showInfoModel.value = true
|
showInfoModel.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
$bus.on('refreshUserInfo',value => {
|
|
||||||
if (value) {
|
|
||||||
getUserList();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
getUserList()
|
getUserList()
|
||||||
</script>
|
</script>
|
||||||
<style></style>
|
<style></style>
|
@ -6,7 +6,6 @@ export default {
|
|||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/user',
|
redirect: '/user',
|
||||||
meta: {
|
meta: {
|
||||||
title: '管理员',
|
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mt-40 text-14 opacity-60">{{ dailyPoetry.content || '莫向外求,但从心觅,行有不得,反求诸己。' }}</p>
|
<p class="mt-40 text-14 opacity-60" style="cursor: pointer" @click="dailyPoe">{{ dailyPoetry.content || '莫向外求,但从心觅,行有不得,反求诸己。' }}</p>
|
||||||
<p class="mt-32 text-right text-12 opacity-40">—— {{ dailyPoetry.author || '佚名' }}</p>
|
<p class="mt-32 text-right text-12 opacity-40">—— {{ dailyPoetry.author || '佚名' }}</p>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="ml-12 w-70%">
|
<n-card class="ml-12 w-70%">
|
||||||
@ -27,18 +27,6 @@
|
|||||||
remote
|
remote
|
||||||
:columns="connectionsColumns"
|
:columns="connectionsColumns"
|
||||||
:data="connectionsData.data"
|
:data="connectionsData.data"
|
||||||
:row-props="rowProps"
|
|
||||||
/>
|
|
||||||
<n-dropdown
|
|
||||||
placement="bottom-start"
|
|
||||||
trigger="manual"
|
|
||||||
size="small"
|
|
||||||
:x="xRef"
|
|
||||||
:y="yRef"
|
|
||||||
:options="rightMenuOpts"
|
|
||||||
@select="rowSelect"
|
|
||||||
@clickoutside="rowClick"
|
|
||||||
:show="showDropdownRef"
|
|
||||||
/>
|
/>
|
||||||
</n-card>
|
</n-card>
|
||||||
</AppPage>
|
</AppPage>
|
||||||
@ -47,7 +35,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useUserStore } from '@/store'
|
import { useUserStore } from '@/store'
|
||||||
import api from '@/views/workbench/api'
|
import api from '@/views/workbench/api'
|
||||||
import { debounce, renderIcon } from '@/utils'
|
import { debounce } from '@/utils'
|
||||||
import { NTag } from 'naive-ui'
|
import { NTag } from 'naive-ui'
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
@ -164,49 +152,6 @@ const connectionsColumns = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// 链接列表邮件刷新菜单
|
|
||||||
const rightMenuOpts = [
|
|
||||||
{
|
|
||||||
label: () => h('span',{ style: { color: 'green' }}, '刷新'),
|
|
||||||
key: "refresh",
|
|
||||||
icon: renderIcon('tabler:refresh',{ size: 14 })
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 右键菜单的设置
|
|
||||||
const showDropdownRef = ref(false);
|
|
||||||
const xRef = ref(0);
|
|
||||||
const yRef = ref(0);
|
|
||||||
|
|
||||||
// 右键菜单的基本位置逻辑
|
|
||||||
function rowProps(row) {
|
|
||||||
return {
|
|
||||||
onContextmenu: (e) => {
|
|
||||||
// $message.info(JSON.stringify(row, null, 2));
|
|
||||||
e.preventDefault();
|
|
||||||
showDropdownRef.value = false;
|
|
||||||
nextTick().then(() => {
|
|
||||||
showDropdownRef.value = true;
|
|
||||||
xRef.value = e.clientX;
|
|
||||||
yRef.value = e.clientY;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右键菜单的逻辑
|
|
||||||
function rowSelect(row) {
|
|
||||||
switch (row) {
|
|
||||||
case "refresh":
|
|
||||||
getClientConnections()
|
|
||||||
showDropdownRef.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function rowClick() {
|
|
||||||
showDropdownRef.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表格数据
|
// 表格数据
|
||||||
const tableData = ref({
|
const tableData = ref({
|
||||||
data: []
|
data: []
|
||||||
@ -288,7 +233,7 @@ async function getClientConnections() {
|
|||||||
|
|
||||||
const initFunc = debounce(() => {
|
const initFunc = debounce(() => {
|
||||||
getClientConnections()
|
getClientConnections()
|
||||||
// dailyPoe()
|
dailyPoe()
|
||||||
// connectionList()
|
// connectionList()
|
||||||
},500)
|
},500)
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ export default {
|
|||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/workbench',
|
redirect: '/workbench',
|
||||||
meta: {
|
meta: {
|
||||||
title: '工作台',
|
|
||||||
order: 0,
|
order: 0,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|