Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
13e4006592 | |||
f2dcb13e0d | |||
edaf9ba770 |
@@ -36,6 +36,18 @@ 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-dashboard\\template\\" + fmt.Sprintf("%s.conf", clientInfo.Name)
|
outPath = "E:\\Workspace\\Go\\wireguard-ui\\template\\tmp\\" + fmt.Sprintf("%s.conf", clientInfo.Name)
|
||||||
templatePath = "E:\\Workspace\\Go\\wireguard-dashboard\\template\\wg.client.conf"
|
templatePath = "E:\\Workspace\\Go\\wireguard-ui\\template\\conf\\wg.client.conf"
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Template().Execute(templatePath, outPath, execData)
|
err = Template().Execute(templatePath, outPath, execData)
|
||||||
|
1
cron/cron.go
Normal file
1
cron/cron.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package cron
|
@@ -1,11 +1,17 @@
|
|||||||
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"
|
||||||
@@ -113,3 +119,99 @@ 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,5 +1,7 @@
|
|||||||
package param
|
package param
|
||||||
|
|
||||||
|
import "mime/multipart"
|
||||||
|
|
||||||
// SetSetting
|
// SetSetting
|
||||||
// @description: 添加/编辑设置
|
// @description: 添加/编辑设置
|
||||||
type SetSetting struct {
|
type SetSetting struct {
|
||||||
@@ -7,3 +9,9 @@ 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"`
|
||||||
|
}
|
||||||
|
@@ -17,5 +17,7 @@ func SettingApi(r *gin.RouterGroup) {
|
|||||||
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) // 导入配置
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package vo
|
package vo
|
||||||
|
|
||||||
|
import "wireguard-ui/global/constant"
|
||||||
|
|
||||||
// SettingItem
|
// SettingItem
|
||||||
// @description: 设置单项
|
// @description: 设置单项
|
||||||
type SettingItem struct {
|
type SettingItem struct {
|
||||||
@@ -7,3 +9,46 @@ type SettingItem struct {
|
|||||||
Data string `json:"data"`
|
Data string `json:"data"`
|
||||||
Describe string `json:"describe"`
|
Describe string `json:"describe"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Export struct {
|
||||||
|
Global *Global `json:"global" label:"全局配置" binding:"required"`
|
||||||
|
Server *Server `json:"server" label:"服务端配置" binding:"required"`
|
||||||
|
Clients []Client `json:"clients" label:"客户端" binding:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
@@ -22,3 +22,16 @@ type Client struct {
|
|||||||
func (Client) TableName() string {
|
func (Client) TableName() string {
|
||||||
return "t_client"
|
return "t_client"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watcher
|
||||||
|
// @description: 监听日志
|
||||||
|
type Watcher struct {
|
||||||
|
Base
|
||||||
|
ClientId string `json:"clientId" gorm:"type:char(36);not null;comment:'客户端id'"`
|
||||||
|
NotifyResult string `json:"notifyResult" gorm:"type:text;default null;comment:'通知结果'"`
|
||||||
|
IsSend int `json:"isSend" gorm:"type:tinyint(1);default 0;comment:'是否已通知'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Watcher) TableName() string {
|
||||||
|
return "t_watcher"
|
||||||
|
}
|
||||||
|
@@ -2,8 +2,11 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
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"
|
||||||
@@ -84,3 +87,117 @@ func (s setting) GetAllSetting(blackList []string) (data []vo.SettingItem, err e
|
|||||||
err = s.Model(&model.Setting{}).Select("code, data, describe").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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// 先更新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,
|
||||||
|
Endpoint: v.Endpoint,
|
||||||
|
UseServerDns: v.UseServerDns,
|
||||||
|
Keys: keys,
|
||||||
|
Enabled: v.Enabled,
|
||||||
|
OfflineMonitoring: v.OfflineMonitoring,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Client().SaveClient(cc, loginUser); err != nil {
|
||||||
|
slog.Errorf("客户端[%s]导入失败: %v", v.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
28
utils/file.go
Normal file
28
utils/file.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
}
|
@@ -32,6 +32,7 @@
|
|||||||
},
|
},
|
||||||
"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",
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
"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
16
web/pnpm-lock.yaml
generated
@@ -11,6 +11,9 @@ 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)
|
||||||
@@ -35,6 +38,9 @@ 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
|
||||||
@@ -1046,6 +1052,9 @@ 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:
|
||||||
@@ -3008,6 +3017,9 @@ 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'}
|
||||||
@@ -5490,6 +5502,8 @@ 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
|
||||||
@@ -7591,6 +7605,8 @@ 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
|
||||||
|
10
web/src/api/setting.js
Normal file
10
web/src/api/setting.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { request } from '@/utils'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
exportConfig: () => request.get('/setting/export'), // 导出配置
|
||||||
|
importConfig: (data) => request.post('/setting/import',data,{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
126
web/src/layout/components/header/components/Export.vue
Normal file
126
web/src/layout/components/header/components/Export.vue
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<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,8 +72,11 @@
|
|||||||
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 = [
|
||||||
{
|
{
|
||||||
@@ -212,6 +215,9 @@ 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,7 +3,12 @@
|
|||||||
<MenuCollapse />
|
<MenuCollapse />
|
||||||
<BreadCrumb ml-15 hidden sm:block />
|
<BreadCrumb ml-15 hidden sm:block />
|
||||||
</div>
|
</div>
|
||||||
<div ml-auto flex items-center>
|
<div ml-auto flex items-center v-if="loginUser.account === 'admin'">
|
||||||
|
<Export/>
|
||||||
|
<FullScreen />
|
||||||
|
<UserAvatar />
|
||||||
|
</div>
|
||||||
|
<div ml-auto flex items-center v-else>
|
||||||
<FullScreen />
|
<FullScreen />
|
||||||
<UserAvatar />
|
<UserAvatar />
|
||||||
</div>
|
</div>
|
||||||
@@ -14,4 +19,7 @@ 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,10 +9,14 @@ 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()
|
||||||
|
7
web/src/utils/event/event.js
Normal file
7
web/src/utils/event/event.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { getCurrentInstance } from "vue";
|
||||||
|
|
||||||
|
export default function event() {
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
const globalProperties = instance?.appContext.config.globalProperties;
|
||||||
|
return { ...globalProperties };
|
||||||
|
}
|
1
web/src/utils/event/index.js
Normal file
1
web/src/utils/event/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './event'
|
@@ -2,3 +2,4 @@ export * from './common'
|
|||||||
export * from './storage'
|
export * from './storage'
|
||||||
export * from './http'
|
export * from './http'
|
||||||
export * from './auth'
|
export * from './auth'
|
||||||
|
export * from './event'
|
@@ -335,6 +335,9 @@ 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 = [
|
||||||
{
|
{
|
||||||
@@ -658,6 +661,13 @@ function search() {
|
|||||||
getClientList()
|
getClientList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听事件
|
||||||
|
$bus.on("refreshClients",value => {
|
||||||
|
if (value) {
|
||||||
|
getClientList();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
getClientList()
|
getClientList()
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@@ -209,6 +209,10 @@ 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 = [
|
||||||
@@ -401,12 +405,15 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -480,6 +487,22 @@ 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">
|
||||||
|
@@ -106,6 +106,9 @@ 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()
|
||||||
|
|
||||||
@@ -400,6 +403,12 @@ function addUser() {
|
|||||||
showInfoModel.value = true
|
showInfoModel.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$bus.on('refreshUserInfo',value => {
|
||||||
|
if (value) {
|
||||||
|
getUserList();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
getUserList()
|
getUserList()
|
||||||
</script>
|
</script>
|
||||||
<style></style>
|
<style></style>
|
Reference in New Issue
Block a user