diff --git a/component/validator.go b/component/validator.go
index f1bf270..8f18311 100644
--- a/component/validator.go
+++ b/component/validator.go
@@ -36,6 +36,18 @@ func Error(err error) string {
return errMsg
}
+// Validate
+// @description: 校验
+// @param data
+// @return string
+func Validate(data any) error {
+ if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
+ return v.Struct(data)
+ }
+
+ return nil
+}
+
// initValidatorTranslator
// @description: 初始化翻译机
// @receiver vli
diff --git a/http/api/setting.go b/http/api/setting.go
index 3450e47..636ee91 100644
--- a/http/api/setting.go
+++ b/http/api/setting.go
@@ -2,9 +2,13 @@ package api
import (
"encoding/json"
+ "errors"
+ "fmt"
"gitee.ltd/lxh/logger/log"
"github.com/gin-gonic/gin"
+ "os"
"slices"
+ "wireguard-ui/component"
"wireguard-ui/http/param"
"wireguard-ui/http/response"
"wireguard-ui/http/vo"
@@ -152,11 +156,62 @@ func (setting) Export(c *gin.Context) {
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())
- //}
+ 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()
}
diff --git a/http/param/setting.go b/http/param/setting.go
index 401ae99..8314ad5 100644
--- a/http/param/setting.go
+++ b/http/param/setting.go
@@ -1,5 +1,7 @@
package param
+import "mime/multipart"
+
// SetSetting
// @description: 添加/编辑设置
type SetSetting struct {
@@ -7,3 +9,9 @@ type SetSetting struct {
Data string `json:"data" form:"data" binding:"required"`
Describe string `json:"describe" form:"describe" binding:"omitempty"`
}
+
+// Import
+// @description: 导入
+type Import struct {
+ File *multipart.FileHeader `json:"file" form:"file" binding:"required"`
+}
diff --git a/http/router/setting.go b/http/router/setting.go
index 9cd3a3e..fd80496 100644
--- a/http/router/setting.go
+++ b/http/router/setting.go
@@ -18,5 +18,6 @@ func SettingApi(r *gin.RouterGroup) {
setting.GET("/all", api.Setting().GetAllSetting) // 获取全部配置
setting.GET("/public-addr", api.Setting().GetPublicAddr) // 获取公网IP
setting.GET("/export", api.Setting().Export) // 导出配置文件
+ setting.POST("/import", api.Setting().Import) // 导入配置
}
}
diff --git a/http/vo/setting.go b/http/vo/setting.go
index 6925e6f..a45c4d9 100644
--- a/http/vo/setting.go
+++ b/http/vo/setting.go
@@ -1,5 +1,7 @@
package vo
+import "wireguard-ui/global/constant"
+
// SettingItem
// @description: 设置单项
type SettingItem struct {
@@ -9,44 +11,44 @@ type SettingItem struct {
}
type Export struct {
- Global Global `json:"global"`
- Server Server `json:"server"`
- Clients []Client `json:"clients"`
+ 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"`
- ConfigFilePath string `json:"configFilePath"`
- DnsServer []string `json:"dnsServer"`
- EndpointAddress string `json:"endpointAddress"`
- FirewallMark string `json:"firewallMark"`
- PersistentKeepalive int `json:"persistentKeepalive"`
- Table string `json:"table"`
+ 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"`
- ListenPort int `json:"listenPort"`
- PrivateKey string `json:"privateKey"`
- PublicKey string `json:"publicKey"`
- PostUpScript string `json:"postUpScript"`
- PostDownScript string `json:"postDownScript"`
+ 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"`
- Email string `json:"email"`
- SubnetRange string `json:"subnetRange"`
- IpAllocation []string `json:"ipAllocation"`
- AllowedIps []string `json:"allowedIps"`
- ExtraAllowedIps []string `json:"extraAllowedIps"`
- Endpoint string `json:"endpoint"`
- UseServerDns int `json:"useServerDns"`
+ 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"`
- PrivateKey string `json:"privateKey"`
- PublicKey string `json:"publicKey"`
- } `json:"keys"`
- Enabled int `json:"enabled"`
- OfflineMonitoring int `json:"offlineMonitoring"`
+ 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"`
}
diff --git a/service/setting.go b/service/setting.go
index 6ca7296..c833c3e 100644
--- a/service/setting.go
+++ b/service/setting.go
@@ -2,9 +2,11 @@ package service
import (
"encoding/json"
+ slog "gitee.ltd/lxh/logger/log"
"gorm.io/gorm"
"strings"
gdb "wireguard-ui/global/client"
+ "wireguard-ui/http/param"
"wireguard-ui/http/vo"
"wireguard-ui/model"
"wireguard-ui/template/render_data"
@@ -142,3 +144,60 @@ func (s setting) Export() (data vo.Export, err error) {
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
+}
diff --git a/web/package.json b/web/package.json
index 6244c83..1c90319 100644
--- a/web/package.json
+++ b/web/package.json
@@ -32,6 +32,7 @@
},
"dependencies": {
"@unocss/eslint-config": "^0.55.7",
+ "@vicons/ionicons5": "^0.12.0",
"@vueuse/core": "^10.4.1",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "5.1.12",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index eec360c..bebf0f7 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -11,6 +11,9 @@ importers:
'@unocss/eslint-config':
specifier: ^0.55.7
version: 0.55.7(eslint@8.50.0)(typescript@5.2.2)
+ '@vicons/ionicons5':
+ specifier: ^0.12.0
+ version: 0.12.0
'@vueuse/core':
specifier: ^10.4.1
version: 10.4.1(vue@3.3.4)
@@ -1046,6 +1049,9 @@ packages:
'@vavt/util@1.4.0':
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':
resolution: {integrity: sha512-Tv9JeRZQ6KDwSkOQJvXc5TBcc4fkSazA96GDhi99v4VCthTgXjnhaah41CeZD3hFDKnNS0MHKFFqN+RHAgYDyQ==}
peerDependencies:
@@ -5490,6 +5496,8 @@ snapshots:
'@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))':
dependencies:
'@babel/parser': 7.23.0
diff --git a/web/src/api/setting.js b/web/src/api/setting.js
index a8cdf78..f692d55 100644
--- a/web/src/api/setting.js
+++ b/web/src/api/setting.js
@@ -1,5 +1,10 @@
import { request } from '@/utils'
export default {
- exportConfig: () => request.get('/setting/export'), // 获取当前登陆用户信息
+ exportConfig: () => request.get('/setting/export'), // 导出配置
+ importConfig: (data) => request.post('/setting/import',data,{
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ }),
}
diff --git a/web/src/layout/components/header/components/Export.vue b/web/src/layout/components/header/components/Export.vue
index 6ee6e41..b65e848 100644
--- a/web/src/layout/components/header/components/Export.vue
+++ b/web/src/layout/components/header/components/Export.vue
@@ -5,13 +5,55 @@
+
+
+
+
+
+ 点击或者拖动文件到该区域来上传
+
+
+ 注意:上传后将覆盖原有的配置以及客户端数据等,请谨慎操作!
+
+
+
+ 确定
+