mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 增加 mfa 设置
This commit is contained in:
parent
d28697b63a
commit
e1bf00cb34
@ -1,10 +1,13 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||||
"github.com/1Panel-dev/1Panel/app/dto"
|
"github.com/1Panel-dev/1Panel/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/constant"
|
"github.com/1Panel-dev/1Panel/constant"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/utils/mfa"
|
||||||
"github.com/1Panel-dev/1Panel/utils/ntp"
|
"github.com/1Panel-dev/1Panel/utils/ntp"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -70,3 +73,55 @@ func (b *BaseApi) SyncTime(c *gin.Context) {
|
|||||||
|
|
||||||
helper.SuccessWithData(c, ts)
|
helper.SuccessWithData(c, ts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) CleanMonitor(c *gin.Context) {
|
||||||
|
if err := global.DB.Exec("DELETE FROM monitor_bases").Error; err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.DB.Exec("DELETE FROM monitor_ios").Error; err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.DB.Exec("DELETE FROM monitor_networks").Error; err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) GetMFA(c *gin.Context) {
|
||||||
|
otp, err := mfa.GetOtp("admin")
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, otp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) MFABind(c *gin.Context) {
|
||||||
|
var req dto.MfaCredential
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
success := mfa.ValidCode(req.Code, req.Secret)
|
||||||
|
if !success {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, errors.New("code is not valid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := settingService.Update(c, "MFAStatus", "enable"); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := settingService.Update(c, "MFASecret", req.Secret); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ type SettingInfo struct {
|
|||||||
PasswordTimeOut string `json:"passwordTimeOut"`
|
PasswordTimeOut string `json:"passwordTimeOut"`
|
||||||
ComplexityVerification string `json:"complexityVerification"`
|
ComplexityVerification string `json:"complexityVerification"`
|
||||||
MFAStatus string `json:"mfaStatus"`
|
MFAStatus string `json:"mfaStatus"`
|
||||||
|
MFASecret string `json:"mfaSecret"`
|
||||||
|
|
||||||
MonitorStatus string `json:"monitorStatus"`
|
MonitorStatus string `json:"monitorStatus"`
|
||||||
MonitorStoreDays string `json:"monitorStoreDays"`
|
MonitorStoreDays string `json:"monitorStoreDays"`
|
||||||
|
@ -9,3 +9,8 @@ type UserLoginInfo struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MfaCredential struct {
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
|
||||||
|
|
||||||
type Setting struct {
|
type Setting struct {
|
||||||
gorm.Model
|
BaseModel
|
||||||
Key string `json:"key" gorm:"type:varchar(256);not null;"`
|
Key string `json:"key" gorm:"type:varchar(256);not null;"`
|
||||||
Value string `json:"value" gorm:"type:varchar(256)"`
|
Value string `json:"value" gorm:"type:varchar(256)"`
|
||||||
About string `json:"about" gorm:"type:longText"`
|
About string `json:"about" gorm:"type:longText"`
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/app/model"
|
"github.com/1Panel-dev/1Panel/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/global"
|
"github.com/1Panel-dev/1Panel/global"
|
||||||
"github.com/shirou/gopsutil/cpu"
|
"github.com/shirou/gopsutil/cpu"
|
||||||
"github.com/shirou/gopsutil/disk"
|
"github.com/shirou/gopsutil/disk"
|
||||||
@ -19,6 +21,11 @@ func NewMonitorJob() *monitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *monitor) Run() {
|
func (m *monitor) Run() {
|
||||||
|
settingRepo := repo.NewISettingRepo()
|
||||||
|
monitorStatus, _ := settingRepo.Get(settingRepo.WithByKey("MonitorStatus"))
|
||||||
|
if monitorStatus.Value == "disable" {
|
||||||
|
return
|
||||||
|
}
|
||||||
var itemModel model.MonitorBase
|
var itemModel model.MonitorBase
|
||||||
totalPercent, _ := cpu.Percent(3*time.Second, false)
|
totalPercent, _ := cpu.Percent(3*time.Second, false)
|
||||||
if len(totalPercent) == 1 {
|
if len(totalPercent) == 1 {
|
||||||
@ -35,7 +42,9 @@ func (m *monitor) Run() {
|
|||||||
memoryInfo, _ := mem.VirtualMemory()
|
memoryInfo, _ := mem.VirtualMemory()
|
||||||
itemModel.Memory = memoryInfo.UsedPercent
|
itemModel.Memory = memoryInfo.UsedPercent
|
||||||
|
|
||||||
_ = global.DB.Create(&itemModel)
|
if err := global.DB.Create(&itemModel).Error; err != nil {
|
||||||
|
global.LOG.Debug("create monitor base failed, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
ioStat, _ := disk.IOCounters()
|
ioStat, _ := disk.IOCounters()
|
||||||
for _, v := range ioStat {
|
for _, v := range ioStat {
|
||||||
@ -67,7 +76,9 @@ func (m *monitor) Run() {
|
|||||||
if writeTime > itemIO.Time {
|
if writeTime > itemIO.Time {
|
||||||
itemIO.Time = writeTime
|
itemIO.Time = writeTime
|
||||||
}
|
}
|
||||||
_ = global.DB.Create(&itemIO)
|
if err := global.DB.Create(&itemIO).Error; err != nil {
|
||||||
|
global.LOG.Debug("create monitor io failed, err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
netStat, _ := net.IOCounters(true)
|
netStat, _ := net.IOCounters(true)
|
||||||
@ -84,7 +95,9 @@ func (m *monitor) Run() {
|
|||||||
stime := time.Since(aheadData.CreatedAt).Seconds()
|
stime := time.Since(aheadData.CreatedAt).Seconds()
|
||||||
itemNet.Up = float64(v.BytesSent-aheadData.BytesSent) / 1024 / stime
|
itemNet.Up = float64(v.BytesSent-aheadData.BytesSent) / 1024 / stime
|
||||||
itemNet.Down = float64(v.BytesRecv-aheadData.BytesRecv) / 1024 / stime
|
itemNet.Down = float64(v.BytesRecv-aheadData.BytesRecv) / 1024 / stime
|
||||||
_ = global.DB.Create(&itemNet)
|
if err := global.DB.Create(&itemNet).Error; err != nil {
|
||||||
|
global.LOG.Debug("create monitor network failed, err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
netStatAll, _ := net.IOCounters(false)
|
netStatAll, _ := net.IOCounters(false)
|
||||||
if len(netStatAll) != 0 {
|
if len(netStatAll) != 0 {
|
||||||
@ -100,6 +113,18 @@ func (m *monitor) Run() {
|
|||||||
stime := time.Since(aheadData.CreatedAt).Seconds()
|
stime := time.Since(aheadData.CreatedAt).Seconds()
|
||||||
itemNet.Up = float64(netStatAll[0].BytesSent-aheadData.BytesSent) / 1024 / stime
|
itemNet.Up = float64(netStatAll[0].BytesSent-aheadData.BytesSent) / 1024 / stime
|
||||||
itemNet.Down = float64(netStatAll[0].BytesRecv-aheadData.BytesRecv) / 1024 / stime
|
itemNet.Down = float64(netStatAll[0].BytesRecv-aheadData.BytesRecv) / 1024 / stime
|
||||||
_ = global.DB.Create(&itemNet)
|
if err := global.DB.Create(&itemNet).Error; err != nil {
|
||||||
|
global.LOG.Debug("create monitor network all failed, err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MonitorStoreDays, err := settingRepo.Get(settingRepo.WithByKey("MonitorStoreDays"))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
storeDays, _ := strconv.Atoi(MonitorStoreDays.Value)
|
||||||
|
timeForDelete := time.Now().AddDate(0, 0, -storeDays)
|
||||||
|
_ = global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorBase{}).Error
|
||||||
|
_ = global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorIO{}).Error
|
||||||
|
_ = global.DB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,13 @@ require (
|
|||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/spf13/afero v1.8.2
|
github.com/spf13/afero v1.8.2
|
||||||
github.com/spf13/viper v1.12.0
|
github.com/spf13/viper v1.12.0
|
||||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
|
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
|
||||||
github.com/swaggo/gin-swagger v1.5.1
|
github.com/swaggo/gin-swagger v1.5.1
|
||||||
github.com/swaggo/swag v1.8.4
|
github.com/swaggo/swag v1.8.4
|
||||||
|
github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f
|
||||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/text v0.3.7
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
@ -371,6 +371,8 @@ github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMT
|
|||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
@ -430,6 +432,8 @@ github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4A
|
|||||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
|
github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f h1:C8De+7emQKojPBC+mXA0fr39XN5mKjRm9IUzdxI4whI=
|
||||||
|
github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
@ -93,6 +93,9 @@ var AddTableSetting = &gormigrate.Migration{
|
|||||||
if err := tx.Create(&model.Setting{Key: "MFAStatus", Value: "disable"}).Error; err != nil {
|
if err := tx.Create(&model.Setting{Key: "MFAStatus", Value: "disable"}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := tx.Create(&model.Setting{Key: "MFASecret", Value: ""}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := tx.Create(&model.Setting{Key: "MonitorStatus", Value: "enable"}).Error; err != nil {
|
if err := tx.Create(&model.Setting{Key: "MonitorStatus", Value: "enable"}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -17,5 +17,8 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
|
|||||||
withRecordRouter.PUT("", baseApi.UpdateSetting)
|
withRecordRouter.PUT("", baseApi.UpdateSetting)
|
||||||
settingRouter.PUT("/password", baseApi.UpdatePassword)
|
settingRouter.PUT("/password", baseApi.UpdatePassword)
|
||||||
settingRouter.POST("/time/sync", baseApi.SyncTime)
|
settingRouter.POST("/time/sync", baseApi.SyncTime)
|
||||||
|
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)
|
||||||
|
settingRouter.GET("/mfa", baseApi.GetMFA)
|
||||||
|
settingRouter.POST("/mfa/bind", baseApi.MFABind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
40
backend/utils/mfa/mfa.go
Normal file
40
backend/utils/mfa/mfa.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package mfa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/skip2/go-qrcode"
|
||||||
|
"github.com/xlzd/gotp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const secretLength = 16
|
||||||
|
|
||||||
|
type Otp struct {
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
QrImage string `json:"qrImage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOtp(username string) (otp Otp, err error) {
|
||||||
|
secret := gotp.RandomSecret(secretLength)
|
||||||
|
otp.Secret = secret
|
||||||
|
totp := gotp.NewDefaultTOTP(secret)
|
||||||
|
uri := totp.ProvisioningUri(username, "1Panel")
|
||||||
|
subImg, err := qrcode.Encode(uri, qrcode.Medium, 256)
|
||||||
|
dist := make([]byte, 3000)
|
||||||
|
base64.StdEncoding.Encode(dist, subImg)
|
||||||
|
index := bytes.IndexByte(dist, 0)
|
||||||
|
baseImage := dist[0:index]
|
||||||
|
otp.QrImage = "data:image/png;base64," + string(baseImage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidCode(code string, secret string) bool {
|
||||||
|
totp := gotp.NewDefaultTOTP(secret)
|
||||||
|
now := time.Now().Unix()
|
||||||
|
strInt64 := strconv.FormatInt(now, 10)
|
||||||
|
id16, _ := strconv.Atoi(strInt64)
|
||||||
|
return totp.Verify(code, int64(id16))
|
||||||
|
}
|
@ -16,6 +16,7 @@ export namespace Setting {
|
|||||||
passwordTimeOut: string;
|
passwordTimeOut: string;
|
||||||
complexityVerification: string;
|
complexityVerification: string;
|
||||||
mfaStatus: string;
|
mfaStatus: string;
|
||||||
|
mfaSecret: string;
|
||||||
|
|
||||||
monitorStatus: string;
|
monitorStatus: string;
|
||||||
monitorStoreDays: string;
|
monitorStoreDays: string;
|
||||||
@ -34,4 +35,12 @@ export namespace Setting {
|
|||||||
newPassword: string;
|
newPassword: string;
|
||||||
retryPassword: string;
|
retryPassword: string;
|
||||||
}
|
}
|
||||||
|
export interface MFAInfo {
|
||||||
|
secret: string;
|
||||||
|
qrImage: string;
|
||||||
|
}
|
||||||
|
export interface MFABind {
|
||||||
|
secret: string;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,3 +16,15 @@ export const updatePassword = (param: Setting.PasswordUpdate) => {
|
|||||||
export const syncTime = () => {
|
export const syncTime = () => {
|
||||||
return http.post(`/settings/time/sync`, {});
|
return http.post(`/settings/time/sync`, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cleanMonitors = () => {
|
||||||
|
return http.post(`/settings/monitor/clean`, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMFA = () => {
|
||||||
|
return http.get<Setting.MFAInfo>(`/settings/mfa`, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bindMFA = (param: Setting.MFABind) => {
|
||||||
|
return http.post(`/settings/mfa/bind`, param);
|
||||||
|
};
|
||||||
|
@ -20,6 +20,7 @@ interface CommonRule {
|
|||||||
requiredSelect: FormItemRule;
|
requiredSelect: FormItemRule;
|
||||||
name: FormItemRule;
|
name: FormItemRule;
|
||||||
email: FormItemRule;
|
email: FormItemRule;
|
||||||
|
number: FormItemRule;
|
||||||
ip: FormItemRule;
|
ip: FormItemRule;
|
||||||
port: FormItemRule;
|
port: FormItemRule;
|
||||||
}
|
}
|
||||||
@ -48,6 +49,13 @@ export const Rules: CommonRule = {
|
|||||||
message: i18n.global.t('commons.rule.email'),
|
message: i18n.global.t('commons.rule.email'),
|
||||||
trigger: 'blur',
|
trigger: 'blur',
|
||||||
},
|
},
|
||||||
|
number: {
|
||||||
|
required: true,
|
||||||
|
trigger: 'blur',
|
||||||
|
min: 0,
|
||||||
|
type: 'number',
|
||||||
|
message: i18n.global.t('commons.rule.number'),
|
||||||
|
},
|
||||||
ip: {
|
ip: {
|
||||||
validator: checkIp,
|
validator: checkIp,
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -11,8 +11,11 @@ export default {
|
|||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
login: 'Login',
|
|
||||||
conn: 'Connect',
|
conn: 'Connect',
|
||||||
|
clean: 'Clean',
|
||||||
|
login: 'Login',
|
||||||
|
close: 'Close',
|
||||||
|
saveAndEnable: 'Save and enable',
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
timeStart: 'Time start',
|
timeStart: 'Time start',
|
||||||
@ -268,5 +271,35 @@ export default {
|
|||||||
oldPassword: 'Original password',
|
oldPassword: 'Original password',
|
||||||
newPassword: 'New password',
|
newPassword: 'New password',
|
||||||
retryPassword: 'Confirm password',
|
retryPassword: 'Confirm password',
|
||||||
|
safe: 'Safe',
|
||||||
|
panelPort: 'Panel port',
|
||||||
|
portHelper:
|
||||||
|
'The recommended port range is 8888 to 65535. Note: If the server has a security group, permit the new port from the security group in advance',
|
||||||
|
safeEntrance: 'Security entrance',
|
||||||
|
safeEntranceHelper:
|
||||||
|
'Panel management portal. You can log in to the panel only through a specified security portal, for example, / 89DC6AE8',
|
||||||
|
passwordTimeout: 'Password expiration Time',
|
||||||
|
timeoutHelper:
|
||||||
|
'[ {0} days ] The panel password is about to expire. After the expiration, you need to reset the password',
|
||||||
|
complexity: 'Password complexity verification',
|
||||||
|
complexityHelper:
|
||||||
|
'The password must contain at least eight characters and contain at least three uppercase letters, lowercase letters, digits, and special characters',
|
||||||
|
mfa: 'MFA',
|
||||||
|
mfaHelper1: 'Download a MFA verification mobile app such as:',
|
||||||
|
mfaHelper2: 'Scan the following QR code using the mobile app to obtain the 6-digit verification code',
|
||||||
|
mfaHelper3: 'Enter six digits from the app',
|
||||||
|
enableMonitor: 'Enable',
|
||||||
|
storeDays: 'Expiration time (day)',
|
||||||
|
cleanMonitor: 'Clearing monitoring records',
|
||||||
|
message: 'Message',
|
||||||
|
messageType: 'Message type',
|
||||||
|
email: 'Email',
|
||||||
|
wechat: 'WeChat',
|
||||||
|
dingding: 'DingDing',
|
||||||
|
closeMessage: 'Turning off Message Notification',
|
||||||
|
emailServer: 'Service name',
|
||||||
|
emailAddr: 'Service address',
|
||||||
|
emailSMTP: 'SMTP code',
|
||||||
|
secret: 'Secret',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -8,11 +8,16 @@ export default {
|
|||||||
sync: '同步',
|
sync: '同步',
|
||||||
delete: '删除',
|
delete: '删除',
|
||||||
edit: '编辑',
|
edit: '编辑',
|
||||||
|
enable: '启用',
|
||||||
|
disable: '禁用',
|
||||||
confirm: '确认',
|
confirm: '确认',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
conn: '连接',
|
conn: '连接',
|
||||||
|
clean: '清空',
|
||||||
login: '登录',
|
login: '登录',
|
||||||
|
close: '关闭',
|
||||||
|
saveAndEnable: '保存并启用',
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
timeStart: '开始时间',
|
timeStart: '开始时间',
|
||||||
@ -64,7 +69,8 @@ export default {
|
|||||||
requiredInput: '请填写必填项',
|
requiredInput: '请填写必填项',
|
||||||
requiredSelect: '请选择必选项',
|
requiredSelect: '请选择必选项',
|
||||||
commonName: '支持英文、中文、数字、.-_,长度1-30',
|
commonName: '支持英文、中文、数字、.-_,长度1-30',
|
||||||
email: '邮箱格式错误',
|
email: '请输入正确的邮箱',
|
||||||
|
number: '请输入正确的数字',
|
||||||
ip: '请输入正确的 IP 地址',
|
ip: '请输入正确的 IP 地址',
|
||||||
port: '请输入正确的端口',
|
port: '请输入正确的端口',
|
||||||
},
|
},
|
||||||
@ -277,5 +283,31 @@ export default {
|
|||||||
oldPassword: '原密码',
|
oldPassword: '原密码',
|
||||||
newPassword: '新密码',
|
newPassword: '新密码',
|
||||||
retryPassword: '确认密码',
|
retryPassword: '确认密码',
|
||||||
|
safe: '安全',
|
||||||
|
panelPort: '面板端口',
|
||||||
|
portHelper: '建议端口范围8888 - 65535,注意:有安全组的服务器请提前在安全组放行新端口',
|
||||||
|
safeEntrance: '安全入口',
|
||||||
|
safeEntranceHelper: '面板管理入口,设置后只能通过指定安全入口登录面板,如: /89dc6ae8',
|
||||||
|
passwordTimeout: '密码过期时间',
|
||||||
|
timeoutHelper: '【 {0} 天后 】面板密码即将过期,过期后需要重新设置密码',
|
||||||
|
complexity: '密码复杂度验证',
|
||||||
|
complexityHelper: '密码必须满足密码长度大于8位且大写字母、小写字母、数字、特殊字符至少3项组合',
|
||||||
|
mfa: '两步验证',
|
||||||
|
mfaHelper1: '下载两步验证手机应用 如:',
|
||||||
|
mfaHelper2: '使用手机应用扫描以下二维码,获取 6 位验证码',
|
||||||
|
mfaHelper3: '输入手机应用上的 6 位数字',
|
||||||
|
enableMonitor: '监控状态',
|
||||||
|
storeDays: '过期时间 (天)',
|
||||||
|
cleanMonitor: '清空监控记录',
|
||||||
|
message: '通知',
|
||||||
|
messageType: '通知方式',
|
||||||
|
email: '邮箱',
|
||||||
|
wechat: '企业微信',
|
||||||
|
dingding: '钉钉',
|
||||||
|
closeMessage: '关闭消息通知',
|
||||||
|
emailServer: '邮箱服务名称',
|
||||||
|
emailAddr: '邮箱地址',
|
||||||
|
emailSMTP: '邮箱 SMTP 授权码',
|
||||||
|
secret: '密钥',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -29,7 +29,7 @@ const hostRouter = {
|
|||||||
{
|
{
|
||||||
path: '/hosts/monitor',
|
path: '/hosts/monitor',
|
||||||
name: 'Monitor',
|
name: 'Monitor',
|
||||||
component: () => import('@/views/monitor/index.vue'),
|
component: () => import('@/views/host/monitor/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: 'menu.monitor',
|
title: 'menu.monitor',
|
||||||
},
|
},
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
@change="search('load')"
|
@change="search('load')"
|
||||||
v-model="timeRangeLoad"
|
v-model="timeRangeLoad"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
size="small"
|
|
||||||
:range-separator="$t('commons.search.timeRange')"
|
:range-separator="$t('commons.search.timeRange')"
|
||||||
:start-placeholder="$t('commons.search.timeStart')"
|
:start-placeholder="$t('commons.search.timeStart')"
|
||||||
:end-placeholder="$t('commons.search.timeEnd')"
|
:end-placeholder="$t('commons.search.timeEnd')"
|
||||||
@ -30,7 +29,6 @@
|
|||||||
@change="search('cpu')"
|
@change="search('cpu')"
|
||||||
v-model="timeRangeCpu"
|
v-model="timeRangeCpu"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
size="small"
|
|
||||||
:range-separator="$t('commons.search.timeRange')"
|
:range-separator="$t('commons.search.timeRange')"
|
||||||
:start-placeholder="$t('commons.search.timeStart')"
|
:start-placeholder="$t('commons.search.timeStart')"
|
||||||
:end-placeholder="$t('commons.search.timeEnd')"
|
:end-placeholder="$t('commons.search.timeEnd')"
|
||||||
@ -49,7 +47,6 @@
|
|||||||
@change="search('memory')"
|
@change="search('memory')"
|
||||||
v-model="timeRangeMemory"
|
v-model="timeRangeMemory"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
size="small"
|
|
||||||
:range-separator="$t('commons.search.timeRange')"
|
:range-separator="$t('commons.search.timeRange')"
|
||||||
:start-placeholder="$t('commons.search.timeStart')"
|
:start-placeholder="$t('commons.search.timeStart')"
|
||||||
:end-placeholder="$t('commons.search.timeEnd')"
|
:end-placeholder="$t('commons.search.timeEnd')"
|
||||||
@ -70,7 +67,6 @@
|
|||||||
@change="search('io')"
|
@change="search('io')"
|
||||||
v-model="timeRangeIO"
|
v-model="timeRangeIO"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
size="small"
|
|
||||||
:range-separator="$t('commons.search.timeRange')"
|
:range-separator="$t('commons.search.timeRange')"
|
||||||
:start-placeholder="$t('commons.search.timeStart')"
|
:start-placeholder="$t('commons.search.timeStart')"
|
||||||
:end-placeholder="$t('commons.search.timeEnd')"
|
:end-placeholder="$t('commons.search.timeEnd')"
|
||||||
@ -92,7 +88,6 @@
|
|||||||
@change="search('network')"
|
@change="search('network')"
|
||||||
style="margin-left: 20px"
|
style="margin-left: 20px"
|
||||||
placeholder="Select"
|
placeholder="Select"
|
||||||
size="small"
|
|
||||||
>
|
>
|
||||||
<el-option v-for="item in netOptions" :key="item" :label="item" :value="item" />
|
<el-option v-for="item in netOptions" :key="item" :label="item" :value="item" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@ -100,7 +95,6 @@
|
|||||||
@change="search('network')"
|
@change="search('network')"
|
||||||
v-model="timeRangeNetwork"
|
v-model="timeRangeNetwork"
|
||||||
type="datetimerange"
|
type="datetimerange"
|
||||||
size="small"
|
|
||||||
:range-separator="$t('commons.search.timeRange')"
|
:range-separator="$t('commons.search.timeRange')"
|
||||||
:start-placeholder="$t('commons.search.timeStart')"
|
:start-placeholder="$t('commons.search.timeStart')"
|
||||||
:end-placeholder="$t('commons.search.timeEnd')"
|
:end-placeholder="$t('commons.search.timeEnd')"
|
||||||
|
@ -3,33 +3,21 @@
|
|||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-card class="el-card">
|
<el-card class="el-card">
|
||||||
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.createConn')" placement="top-start">
|
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.createConn')" placement="top-start">
|
||||||
<el-button icon="Plus" @click="restHostForm" size="small" />
|
<el-button icon="Plus" @click="restHostForm" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.createGroup')" placement="top-start">
|
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.createGroup')" placement="top-start">
|
||||||
<el-button icon="FolderAdd" @click="onGroupCreate" size="small" />
|
<el-button icon="FolderAdd" @click="onGroupCreate" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.expand')" placement="top-start">
|
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.expand')" placement="top-start">
|
||||||
<el-button icon="Expand" @click="setTreeStatus(true)" size="small" />
|
<el-button icon="Expand" @click="setTreeStatus(true)" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.fold')" placement="top-start">
|
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.fold')" placement="top-start">
|
||||||
<el-button icon="Fold" @click="setTreeStatus(false)" size="small" />
|
<el-button icon="Fold" @click="setTreeStatus(false)" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-input
|
<el-input @input="loadHostTree" clearable style="margin-top: 5px" v-model="searcConfig.info">
|
||||||
size="small"
|
|
||||||
@input="loadHostTree"
|
|
||||||
clearable
|
|
||||||
style="margin-top: 5px"
|
|
||||||
v-model="searcConfig.info"
|
|
||||||
>
|
|
||||||
<template #append><el-button icon="search" @click="loadHostTree" /></template>
|
<template #append><el-button icon="search" @click="loadHostTree" /></template>
|
||||||
</el-input>
|
</el-input>
|
||||||
<el-input
|
<el-input v-if="groupInputShow" clearable style="margin-top: 5px" v-model="groupInputValue">
|
||||||
size="small"
|
|
||||||
v-if="groupInputShow"
|
|
||||||
clearable
|
|
||||||
style="margin-top: 5px"
|
|
||||||
v-model="groupInputValue"
|
|
||||||
>
|
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button-group>
|
<el-button-group>
|
||||||
<el-button icon="Check" @click="onCreateGroup" />
|
<el-button icon="Check" @click="onCreateGroup" />
|
||||||
@ -53,8 +41,8 @@
|
|||||||
<el-button-group
|
<el-button-group
|
||||||
v-if="!(node.level === 1 && data.label === 'default') && data.id === hover"
|
v-if="!(node.level === 1 && data.label === 'default') && data.id === hover"
|
||||||
>
|
>
|
||||||
<el-button icon="Edit" size="small" @click="onEdit(node, data)" />
|
<el-button icon="Edit" @click="onEdit(node, data)" />
|
||||||
<el-button icon="Delete" size="small" @click="onDelete(node, data)" />
|
<el-button icon="Delete" @click="onDelete(node, data)" />
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
@change="quickInput"
|
@change="quickInput"
|
||||||
style="width: 25%"
|
style="width: 25%"
|
||||||
:placeholder="$t('terminal.quickCommand')"
|
:placeholder="$t('terminal.quickCommand')"
|
||||||
size="small"
|
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="cmd in commandList"
|
v-for="cmd in commandList"
|
||||||
@ -61,10 +61,10 @@
|
|||||||
v-model="batchVal"
|
v-model="batchVal"
|
||||||
@keyup.enter="batchInput"
|
@keyup.enter="batchInput"
|
||||||
style="width: 75%"
|
style="width: 75%"
|
||||||
size="small"
|
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-switch size="small" v-model="isBatch" class="ml-2" />
|
<el-switch v-model="isBatch" class="ml-2" />
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
@ -81,7 +81,7 @@
|
|||||||
<el-button @click="onNewSsh">New ssh</el-button>
|
<el-button @click="onNewSsh">New ssh</el-button>
|
||||||
<el-button @click="onNewTab">New tab</el-button>
|
<el-button @click="onNewTab">New tab</el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
<el-input size="small" clearable style="margin-top: 5px" v-model="hostfilterInfo">
|
<el-input clearable style="margin-top: 5px" v-model="hostfilterInfo">
|
||||||
<template #append><el-button icon="search" /></template>
|
<template #append><el-button icon="search" /></template>
|
||||||
</el-input>
|
</el-input>
|
||||||
<el-tree
|
<el-tree
|
||||||
|
@ -1,70 +1,67 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form size="small" :model="form" label-position="left" label-width="120px">
|
<el-form :model="mesForm" label-position="left" label-width="160px">
|
||||||
<el-card style="margin-top: 10px">
|
<el-card style="margin-top: 10px">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>通知</span>
|
<span>{{ $t('setting.message') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="1"><br /></el-col>
|
<el-col :span="1"><br /></el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="10">
|
||||||
<el-form-item label="通知方式">
|
<el-form-item :label="$t('setting.messageType')">
|
||||||
<el-radio-group v-model="form.settingInfo.messageType">
|
<el-radio-group v-model="mesForm.messageType">
|
||||||
<el-radio-button label="none">关闭</el-radio-button>
|
<el-radio-button label="none">{{ $t('commons.button.close') }}</el-radio-button>
|
||||||
<el-radio-button label="email">email</el-radio-button>
|
<el-radio-button label="email">{{ $t('setting.email') }}</el-radio-button>
|
||||||
<el-radio-button label="wechat">企业微信</el-radio-button>
|
<el-radio-button label="wechat">{{ $t('setting.wechat') }}</el-radio-button>
|
||||||
<el-radio-button label="dingding">钉钉</el-radio-button>
|
<el-radio-button label="dingding">{{ $t('setting.dingding') }}</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div v-if="form.settingInfo.messageType === 'none'">
|
<div v-if="mesForm.messageType === 'none'">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="SaveSetting()">关闭消息通知</el-button>
|
<el-button @click="SaveSetting()">{{ $t('setting.closeMessage') }}</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="form.settingInfo.messageType === 'email'">
|
<div v-if="mesForm.messageType === 'email'">
|
||||||
<el-form-item label="邮箱服务名称">
|
<el-form-item :label="$t('setting.emailServer')">
|
||||||
<el-input clearable v-model="emailVars.serverName" />
|
<el-input clearable v-model="mesForm.emailVars.serverName" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="邮箱地址">
|
<el-form-item :label="$t('setting.emailAddr')">
|
||||||
<el-input clearable v-model="emailVars.serverAddr" />
|
<el-input clearable v-model="mesForm.emailVars.serverAddr" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="邮箱SMTP授权码">
|
<el-form-item :label="$t('setting.emailSMTP')">
|
||||||
<el-input clearable v-model="emailVars.serverSMTP" />
|
<el-input clearable v-model="mesForm.emailVars.serverSMTP" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="SaveSetting()">保存并启用</el-button>
|
<el-button @click="SaveSetting()">{{ $t('commons.button.saveAndEnable') }}</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="form.settingInfo.messageType === 'wechat'">
|
<div v-if="mesForm.messageType === 'wechat'">
|
||||||
<el-form-item label="orpid">
|
<el-form-item label="orpid">
|
||||||
<el-input clearable v-model="weChatVars.orpid" />
|
<el-input clearable v-model="mesForm.weChatVars.orpid" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="corpsecret">
|
<el-form-item label="corpsecret">
|
||||||
<el-input clearable v-model="weChatVars.corpsecret" />
|
<el-input clearable v-model="mesForm.weChatVars.corpsecret" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="touser">
|
<el-form-item label="touser">
|
||||||
<el-input clearable v-model="weChatVars.touser" />
|
<el-input clearable v-model="mesForm.weChatVars.touser" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="agentid">
|
<el-form-item label="agentid">
|
||||||
<el-input clearable v-model="weChatVars.agentid" />
|
<el-input clearable v-model="mesForm.weChatVars.agentid" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="SaveSetting()">保存并启用</el-button>
|
<el-button @click="SaveSetting()">{{ $t('commons.button.saveAndEnable') }}</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="form.settingInfo.messageType === 'dingding'">
|
<div v-if="mesForm.messageType === 'dingding'">
|
||||||
<el-form-item label="webhook token">
|
<el-form-item label="webhook token">
|
||||||
<el-input clearable v-model="dingVars.webhookToken" />
|
<el-input clearable v-model="mesForm.dingVars.webhookToken" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="密钥">
|
<el-form-item :label="$t('setting.secret')">
|
||||||
<el-input clearable v-model="dingVars.secret" />
|
<el-input clearable v-model="mesForm.dingVars.secret" />
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="邮箱 SMTP 授权码">
|
|
||||||
<el-input clearable v-model="emailVars.serverSMTP" />
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="SaveSetting()">保存并启用</el-button>
|
<el-button @click="SaveSetting()">{{ $t('commons.button.saveAndEnable') }}</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -74,7 +71,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { reactive, watch } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { updateSetting } from '@/api/modules/setting';
|
import { updateSetting } from '@/api/modules/setting';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
@ -91,39 +88,57 @@ const form = withDefaults(defineProps<Props>(), {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emailVars = ref({
|
const mesForm = reactive({
|
||||||
serverName: '',
|
messageType: '',
|
||||||
serverAddr: '',
|
emailVars: {
|
||||||
serverSMTP: '',
|
serverName: '',
|
||||||
|
serverAddr: '',
|
||||||
|
serverSMTP: '',
|
||||||
|
},
|
||||||
|
weChatVars: {
|
||||||
|
orpid: '',
|
||||||
|
corpsecret: '',
|
||||||
|
touser: '',
|
||||||
|
agentid: '',
|
||||||
|
},
|
||||||
|
dingVars: {
|
||||||
|
webhookToken: '',
|
||||||
|
secret: '',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const weChatVars = ref({
|
|
||||||
orpid: '',
|
watch(form, (val: any) => {
|
||||||
corpsecret: '',
|
if (val.settingInfo.messageType) {
|
||||||
touser: '',
|
mesForm.messageType = form.settingInfo.messageType;
|
||||||
agentid: '',
|
mesForm.emailVars = val.settingInfo.emailVars
|
||||||
});
|
? JSON.parse(val.settingInfo.emailVars)
|
||||||
const dingVars = ref({
|
: { serverName: '', serverAddr: '', serverSMTP: '' };
|
||||||
webhookToken: '',
|
mesForm.weChatVars = val.settingInfo.weChatVars
|
||||||
secret: '',
|
? JSON.parse(val.settingInfo.weChatVars)
|
||||||
|
: { orpid: '', corpsecret: '', touser: '', agentid: '' };
|
||||||
|
mesForm.dingVars = val.settingInfo.dingVars
|
||||||
|
? JSON.parse(val.settingInfo.dingVars)
|
||||||
|
: { webhookToken: '', secret: '' };
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const SaveSetting = async () => {
|
const SaveSetting = async () => {
|
||||||
let settingKey = '';
|
let settingKey = '';
|
||||||
let settingVal = '';
|
let settingVal = '';
|
||||||
switch (form.settingInfo.messageType) {
|
switch (mesForm.messageType) {
|
||||||
case 'none':
|
case 'none':
|
||||||
settingVal = '';
|
settingVal = '';
|
||||||
break;
|
break;
|
||||||
case 'email':
|
case 'email':
|
||||||
settingVal = JSON.stringify(emailVars.value);
|
settingVal = JSON.stringify(mesForm.emailVars);
|
||||||
settingKey = 'EmailVars';
|
settingKey = 'EmailVars';
|
||||||
break;
|
break;
|
||||||
case 'wechat':
|
case 'wechat':
|
||||||
settingVal = JSON.stringify(emailVars.value);
|
settingVal = JSON.stringify(mesForm.weChatVars);
|
||||||
settingKey = 'WeChatVars';
|
settingKey = 'WeChatVars';
|
||||||
break;
|
break;
|
||||||
case 'dingding':
|
case 'dingding':
|
||||||
settingVal = JSON.stringify(emailVars.value);
|
settingVal = JSON.stringify(mesForm.dingVars);
|
||||||
settingKey = 'DingVars';
|
settingKey = 'DingVars';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -131,22 +146,8 @@ const SaveSetting = async () => {
|
|||||||
key: settingKey,
|
key: settingKey,
|
||||||
value: settingVal,
|
value: settingVal,
|
||||||
};
|
};
|
||||||
await updateSetting({ key: 'MessageType', value: form.settingInfo.messageType });
|
await updateSetting({ key: 'MessageType', value: mesForm.messageType });
|
||||||
await updateSetting(param);
|
await updateSetting(param);
|
||||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
|
||||||
switch (form.settingInfo.messageType) {
|
|
||||||
case 'email':
|
|
||||||
emailVars.value = JSON.parse(form.settingInfo.emailVars);
|
|
||||||
console.log(emailVars.value);
|
|
||||||
break;
|
|
||||||
case 'wechat':
|
|
||||||
weChatVars.value = JSON.parse(form.settingInfo.weChatVars);
|
|
||||||
break;
|
|
||||||
case 'dingding':
|
|
||||||
dingVars.value = JSON.parse(form.settingInfo.dingVars);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,36 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form size="small" :model="form" label-position="left" label-width="120px">
|
<el-form :model="form" label-position="left" label-width="160px">
|
||||||
<el-card style="margin-top: 10px">
|
<el-card style="margin-top: 10px">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>监控</span>
|
<span>{{ $t('menu.monitor') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="1"><br /></el-col>
|
<el-col :span="1"><br /></el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="10">
|
||||||
<el-form-item label="开启监控">
|
<el-form-item :label="$t('setting.enableMonitor')">
|
||||||
<el-switch
|
<el-radio-group
|
||||||
v-model="form.settingInfo.monitorStatus"
|
|
||||||
active-value="enable"
|
|
||||||
inactive-value="disable"
|
|
||||||
@change="SaveSetting('MonitorStatus', form.settingInfo.monitorStatus)"
|
@change="SaveSetting('MonitorStatus', form.settingInfo.monitorStatus)"
|
||||||
/>
|
v-model="form.settingInfo.monitorStatus"
|
||||||
|
>
|
||||||
|
<el-radio-button label="enable">{{ $t('commons.button.enable') }}</el-radio-button>
|
||||||
|
<el-radio-button label="disable">{{ $t('commons.button.disable') }}</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="过期时间">
|
<el-form-item :label="$t('setting.storeDays')">
|
||||||
<el-input clearable v-model="form.settingInfo.monitorStoreDays">
|
<el-input clearable v-model="form.settingInfo.monitorStoreDays">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button
|
<el-button
|
||||||
@click="SaveSetting('MonitorStoreDays', form.settingInfo.monitorStoreDays)"
|
@click="SaveSetting('MonitorStoreDays', form.settingInfo.monitorStoreDays)"
|
||||||
icon="Collection"
|
icon="Collection"
|
||||||
>
|
>
|
||||||
保存
|
{{ $t('commons.button.save') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button icon="Delete">清空监控记录</el-button>
|
<el-button @click="onClean()" icon="Delete">{{ $t('setting.cleanMonitor') }}</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -40,8 +41,9 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { updateSetting } from '@/api/modules/setting';
|
import { updateSetting, cleanMonitors } from '@/api/modules/setting';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
|
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settingInfo: any;
|
settingInfo: any;
|
||||||
@ -61,4 +63,8 @@ const SaveSetting = async (key: string, val: string) => {
|
|||||||
await updateSetting(param);
|
await updateSetting(param);
|
||||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onClean = async () => {
|
||||||
|
await useDeleteData(cleanMonitors, {}, 'commons.msg.delete', true);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form size="small" :model="form" label-position="left" label-width="160px">
|
<el-form :model="form" label-position="left" label-width="160px">
|
||||||
<el-card style="margin-top: 20px">
|
<el-card style="margin-top: 20px">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@ -9,7 +9,7 @@
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="1"><br /></el-col>
|
<el-col :span="1"><br /></el-col>
|
||||||
<el-col :span="10">
|
<el-col :span="10">
|
||||||
<el-form-item :label="$t('auth.username')">
|
<el-form-item :label="$t('auth.username')" prop="settingInfo.userName">
|
||||||
<el-input clearable v-model="form.settingInfo.userName">
|
<el-input clearable v-model="form.settingInfo.userName">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button
|
<el-button
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('auth.password')">
|
<el-form-item :label="$t('auth.password')" prop="settingInfo.password">
|
||||||
<el-input type="password" clearable disabled v-model="form.settingInfo.password">
|
<el-input type="password" clearable disabled v-model="form.settingInfo.password">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button icon="Setting" @click="passwordVisiable = true">
|
<el-button icon="Setting" @click="passwordVisiable = true">
|
||||||
@ -30,7 +30,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('auth.email')">
|
<el-form-item :label="$t('auth.email')" prop="settingInfo.email">
|
||||||
<el-input clearable v-model="form.settingInfo.email">
|
<el-input clearable v-model="form.settingInfo.email">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button @click="SaveSetting('Email', form.settingInfo.email)" icon="Collection">
|
<el-button @click="SaveSetting('Email', form.settingInfo.email)" icon="Collection">
|
||||||
@ -42,7 +42,7 @@
|
|||||||
<span class="input-help">{{ $t('setting.emailHelper') }}</span>
|
<span class="input-help">{{ $t('setting.emailHelper') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('setting.title')">
|
<el-form-item :label="$t('setting.title')" prop="settingInfo.panelName">
|
||||||
<el-input clearable v-model="form.settingInfo.panelName">
|
<el-input clearable v-model="form.settingInfo.panelName">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button
|
<el-button
|
||||||
@ -54,7 +54,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('setting.theme')">
|
<el-form-item :label="$t('setting.theme')" prop="settingInfo.theme">
|
||||||
<el-radio-group
|
<el-radio-group
|
||||||
@change="SaveSetting('Theme', form.settingInfo.theme)"
|
@change="SaveSetting('Theme', form.settingInfo.theme)"
|
||||||
v-model="form.settingInfo.theme"
|
v-model="form.settingInfo.theme"
|
||||||
@ -69,7 +69,7 @@
|
|||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('setting.language')">
|
<el-form-item :label="$t('setting.language')" prop="settingInfo.language">
|
||||||
<el-radio-group
|
<el-radio-group
|
||||||
@change="SaveSetting('Language', form.settingInfo.language)"
|
@change="SaveSetting('Language', form.settingInfo.language)"
|
||||||
v-model="form.settingInfo.language"
|
v-model="form.settingInfo.language"
|
||||||
@ -83,8 +83,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('setting.sessionTimeout')">
|
<el-form-item :label="$t('setting.sessionTimeout')" prop="settingInfo.sessionTimeout">
|
||||||
<el-input v-model="form.settingInfo.sessionTimeout">
|
<el-input v-model.number="form.settingInfo.sessionTimeout">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-button
|
<el-button
|
||||||
@click="SaveSetting('SessionTimeout', form.settingInfo.sessionTimeout)"
|
@click="SaveSetting('SessionTimeout', form.settingInfo.sessionTimeout)"
|
||||||
@ -114,14 +114,7 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-dialog v-model="passwordVisiable" :title="$t('setting.changePassword')" width="30%">
|
<el-dialog v-model="passwordVisiable" :title="$t('setting.changePassword')" width="30%">
|
||||||
<el-form
|
<el-form ref="passFormRef" label-width="80px" label-position="left" :model="passForm" :rules="passRules">
|
||||||
size="small"
|
|
||||||
ref="passFormRef"
|
|
||||||
label-width="80px"
|
|
||||||
label-position="left"
|
|
||||||
:model="passForm"
|
|
||||||
:rules="passRules"
|
|
||||||
>
|
|
||||||
<el-form-item :label="$t('setting.oldPassword')" prop="oldPassword">
|
<el-form-item :label="$t('setting.oldPassword')" prop="oldPassword">
|
||||||
<el-input type="password" show-password clearable v-model="passForm.oldPassword" />
|
<el-input type="password" show-password clearable v-model="passForm.oldPassword" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -135,7 +128,7 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="passwordVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
<el-button @click="passwordVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
<el-button @click="submitChangePassword()">
|
<el-button @click="submitChangePassword(passFormRef)">
|
||||||
{{ $t('commons.button.confirm') }}
|
{{ $t('commons.button.confirm') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
@ -151,8 +144,8 @@ import { Setting } from '@/api/interface/setting';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { GlobalStore } from '@/store';
|
import { GlobalStore } from '@/store';
|
||||||
import { useTheme } from '@/hooks/use-theme';
|
import { useTheme } from '@/hooks/use-theme';
|
||||||
import { Rules } from '@/global/form-rues';
|
|
||||||
import router from '@/routers/router';
|
import router from '@/routers/router';
|
||||||
|
import { Rules } from '@/global/form-rues';
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const globalStore = GlobalStore();
|
const globalStore = GlobalStore();
|
||||||
@ -191,6 +184,9 @@ const form = withDefaults(defineProps<Props>(), {
|
|||||||
const { switchDark } = useTheme();
|
const { switchDark } = useTheme();
|
||||||
|
|
||||||
const SaveSetting = async (key: string, val: string) => {
|
const SaveSetting = async (key: string, val: string) => {
|
||||||
|
if (val === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'Language':
|
case 'Language':
|
||||||
i18n.locale.value = val;
|
i18n.locale.value = val;
|
||||||
@ -218,12 +214,16 @@ function checkPassword(rule: any, value: any, callback: any) {
|
|||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
const submitChangePassword = async () => {
|
const submitChangePassword = async (formEl: FormInstance | undefined) => {
|
||||||
await updatePassword(passForm);
|
if (!formEl) return;
|
||||||
passwordVisiable.value = false;
|
formEl.validate(async (valid) => {
|
||||||
ElMessage.success(i18n.t('commons.msg.operationSuccess'));
|
if (!valid) return;
|
||||||
router.push({ name: 'login' });
|
await updatePassword(passForm);
|
||||||
globalStore.setLogStatus(false);
|
passwordVisiable.value = false;
|
||||||
|
ElMessage.success(i18n.t('commons.msg.operationSuccess'));
|
||||||
|
router.push({ name: 'login' });
|
||||||
|
globalStore.setLogStatus(false);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const onSyncTime = async () => {
|
const onSyncTime = async () => {
|
||||||
const res = await syncTime();
|
const res = await syncTime();
|
||||||
|
@ -1,102 +1,150 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form size="small" :model="form" label-position="left" label-width="120px">
|
<div>
|
||||||
<el-card style="margin-top: 10px">
|
<el-form :model="form" label-position="left" label-width="160px">
|
||||||
<template #header>
|
<el-card style="margin-top: 10px">
|
||||||
<div class="card-header">
|
<template #header>
|
||||||
<span>安全</span>
|
<div class="card-header">
|
||||||
</div>
|
<span>{{ $t('setting.safe') }}</span>
|
||||||
</template>
|
</div>
|
||||||
<el-row>
|
</template>
|
||||||
<el-col :span="1"><br /></el-col>
|
<el-row>
|
||||||
<el-col :span="8">
|
<el-col :span="1"><br /></el-col>
|
||||||
<el-form-item label="面板端口">
|
<el-col :span="10">
|
||||||
<el-input clearable v-model="form.settingInfo.serverPort">
|
<el-form-item :label="$t('setting.panelPort')">
|
||||||
<template #append>
|
<el-input clearable v-model="form.settingInfo.serverPort">
|
||||||
<el-button
|
<template #append>
|
||||||
@click="SaveSetting('ServerPort', form.settingInfo.serverPort)"
|
<el-button
|
||||||
icon="Collection"
|
@click="SaveSetting('ServerPort', form.settingInfo.serverPort)"
|
||||||
|
icon="Collection"
|
||||||
|
>
|
||||||
|
{{ $t('commons.button.save') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Top Left prompts info"
|
||||||
|
placement="top-start"
|
||||||
>
|
>
|
||||||
保存
|
<el-icon style="font-size: 14px; margin-top: 8px"><WarningFilled /></el-icon>
|
||||||
</el-button>
|
</el-tooltip>
|
||||||
</template>
|
</el-input>
|
||||||
<el-tooltip
|
<div>
|
||||||
class="box-item"
|
<span class="input-help">
|
||||||
effect="dark"
|
{{ $t('setting.portHelper') }}
|
||||||
content="Top Left prompts info"
|
</span>
|
||||||
placement="top-start"
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="$t('setting.safeEntrance')">
|
||||||
|
<el-input clearable v-model="form.settingInfo.securityEntrance">
|
||||||
|
<template #append>
|
||||||
|
<el-button
|
||||||
|
@click="SaveSetting('SecurityEntrance', form.settingInfo.securityEntrance)"
|
||||||
|
icon="Collection"
|
||||||
|
>
|
||||||
|
{{ $t('commons.button.save') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<div>
|
||||||
|
<span class="input-help">
|
||||||
|
{{ $t('setting.safeEntranceHelper') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.passwordTimeout')">
|
||||||
|
<el-input clearable v-model="form.settingInfo.passwordTimeOut">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="timeoutVisiable = true" icon="Collection">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<div>
|
||||||
|
<span class="input-help">
|
||||||
|
{{ $t('setting.timeoutHelper', [loadTimeOut()]) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.complexity')">
|
||||||
|
<el-radio-group
|
||||||
|
@change="SaveSetting('ComplexityVerification', form.settingInfo.complexityVerification)"
|
||||||
|
v-model="form.settingInfo.complexityVerification"
|
||||||
>
|
>
|
||||||
<el-icon style="font-size: 14px; margin-top: 8px"><WarningFilled /></el-icon>
|
<el-radio-button label="enable">{{ $t('commons.button.enable') }}</el-radio-button>
|
||||||
</el-tooltip>
|
<el-radio-button label="disable">{{ $t('commons.button.disable') }}</el-radio-button>
|
||||||
</el-input>
|
</el-radio-group>
|
||||||
<div>
|
<div>
|
||||||
<span class="input-help">
|
<span class="input-help">
|
||||||
建议端口范围8888 - 65535,注意:有安全组的服务器请提前在安全组放行新端口
|
{{ $t('setting.complexityHelper') }}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('setting.mfa')">
|
||||||
|
<el-radio-group @change="handleMFA()" v-model="form.settingInfo.mfaStatus">
|
||||||
|
<el-radio-button label="enable">{{ $t('commons.button.enable') }}</el-radio-button>
|
||||||
|
<el-radio-button label="disable">{{ $t('commons.button.disable') }}</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-if="isMFAShow">
|
||||||
|
<el-card>
|
||||||
|
<ul style="margin-left: 120px; line-height: 24px">
|
||||||
|
<li>
|
||||||
|
{{ $t('setting.mfaHelper1') }}
|
||||||
|
<ul>
|
||||||
|
<li>Google Authenticator</li>
|
||||||
|
<li>Microsoft Authenticator</li>
|
||||||
|
<li>1Password</li>
|
||||||
|
<li>LastPass</li>
|
||||||
|
<li>Authenticator</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>{{ $t('setting.mfaHelper2') }}</li>
|
||||||
|
<el-image
|
||||||
|
style="margin-left: 15px; width: 100px; height: 100px"
|
||||||
|
:src="otp.qrImage"
|
||||||
|
/>
|
||||||
|
<li>{{ $t('setting.mfaHelper3') }}</li>
|
||||||
|
<el-input v-model="mfaCode"></el-input>
|
||||||
|
<div style="margin-top: 10px; margin-bottom: 10px; float: right">
|
||||||
|
<el-button @click="form.settingInfo.mfaStatus = 'disable'">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="onBind">{{ $t('commons.button.saveAndEnable') }}</el-button>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-col>
|
||||||
<el-form-item label="安全入口">
|
</el-row>
|
||||||
<el-input clearable v-model="form.settingInfo.securityEntrance">
|
</el-card>
|
||||||
<template #append>
|
</el-form>
|
||||||
<el-button
|
<el-dialog v-model="timeoutVisiable" :title="$t('setting.changePassword')" width="30%">
|
||||||
@click="SaveSetting('SecurityEntrance', form.settingInfo.securityEntrance)"
|
<el-form ref="timeoutFormRef" label-width="80px" label-position="left" :model="timeoutForm">
|
||||||
icon="Collection"
|
<el-form-item :label="$t('setting.oldPassword')" prop="days" :rules="Rules.number">
|
||||||
>
|
<el-input clearable v-model.number="timeoutForm.days" />
|
||||||
保存
|
</el-form-item>
|
||||||
</el-button>
|
</el-form>
|
||||||
</template>
|
<template #footer>
|
||||||
</el-input>
|
<span class="dialog-footer">
|
||||||
<div>
|
<el-button @click="timeoutVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
<span class="input-help">
|
<el-button @click="submitTimeout(timeoutFormRef)">
|
||||||
面板管理入口,设置后只能通过指定安全入口登录面板,如: /89dc6ae8
|
{{ $t('commons.button.confirm') }}
|
||||||
</span>
|
</el-button>
|
||||||
</div>
|
</span>
|
||||||
</el-form-item>
|
</template>
|
||||||
<el-form-item label="密码过期时间">
|
</el-dialog>
|
||||||
<el-input clearable v-model="form.settingInfo.passwordTimeOut">
|
</div>
|
||||||
<template #append>
|
|
||||||
<el-button
|
|
||||||
@click="SaveSetting('Password', form.settingInfo.passwordTimeOut)"
|
|
||||||
icon="Collection"
|
|
||||||
>
|
|
||||||
保存
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
<div>
|
|
||||||
<span class="input-help">为面板密码设置过期时间,过期后需要重新设置密码</span>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="密码复杂度验证">
|
|
||||||
<el-switch
|
|
||||||
v-model="form.settingInfo.complexityVerification"
|
|
||||||
active-value="enable"
|
|
||||||
inactive-value="disable"
|
|
||||||
@change="SaveSetting('ComplexityVerification', form.settingInfo.complexityVerification)"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<span class="input-help">
|
|
||||||
密码必须满足密码长度大于8位且大写字母、小写字母、数字、特殊字符至少3项组合
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="两步验证">
|
|
||||||
<el-switch
|
|
||||||
v-model="form.settingInfo.mfaStatus"
|
|
||||||
active-value="enable"
|
|
||||||
inactive-value="disable"
|
|
||||||
@change="SaveSetting('MFAStatus', form.settingInfo.mfaStatus)"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</el-card>
|
|
||||||
</el-form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
import { ElMessage, ElForm } from 'element-plus';
|
import { ElMessage, ElForm } from 'element-plus';
|
||||||
import { updateSetting } from '@/api/modules/setting';
|
import { Setting } from '@/api/interface/setting';
|
||||||
|
import { updateSetting, getMFA, bindMFA } from '@/api/modules/setting';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
|
import { Rules } from '@/global/form-rues';
|
||||||
|
import { dateFromat } from '@/utils/util';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settingInfo: any;
|
settingInfo: any;
|
||||||
@ -108,8 +156,22 @@ const form = withDefaults(defineProps<Props>(), {
|
|||||||
passwordTimeOut: '',
|
passwordTimeOut: '',
|
||||||
complexityVerification: '',
|
complexityVerification: '',
|
||||||
mfaStatus: '',
|
mfaStatus: '',
|
||||||
|
mfaSecret: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const timeoutFormRef = ref<FormInstance>();
|
||||||
|
const timeoutVisiable = ref<boolean>(false);
|
||||||
|
const timeoutForm = reactive({
|
||||||
|
days: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isMFAShow = ref<boolean>(false);
|
||||||
|
const otp = reactive<Setting.MFAInfo>({
|
||||||
|
secret: '',
|
||||||
|
qrImage: '',
|
||||||
|
});
|
||||||
|
const mfaCode = ref();
|
||||||
|
|
||||||
const SaveSetting = async (key: string, val: string) => {
|
const SaveSetting = async (key: string, val: string) => {
|
||||||
let param = {
|
let param = {
|
||||||
@ -119,4 +181,38 @@ const SaveSetting = async (key: string, val: string) => {
|
|||||||
await updateSetting(param);
|
await updateSetting(param);
|
||||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMFA = async () => {
|
||||||
|
if (form.settingInfo.mfaStatus === 'enable') {
|
||||||
|
const res = await getMFA();
|
||||||
|
otp.secret = res.data.secret;
|
||||||
|
otp.qrImage = res.data.qrImage;
|
||||||
|
isMFAShow.value = true;
|
||||||
|
} else {
|
||||||
|
await updateSetting({ key: 'MFAStatus', value: 'disable' });
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBind = async () => {
|
||||||
|
await bindMFA({ code: mfaCode.value, secret: otp.secret });
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
isMFAShow.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitTimeout = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
let time = new Date(new Date().getTime() + 3600 * 1000 * 24 * timeoutForm.days);
|
||||||
|
SaveSetting('PasswordTimeOut', dateFromat(0, 0, time));
|
||||||
|
form.settingInfo.passwordTimeOut = dateFromat(0, 0, time);
|
||||||
|
timeoutVisiable.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadTimeOut() {
|
||||||
|
let staytimeGap = new Date(form.settingInfo.passwordTimeOut).getTime() - new Date().getTime();
|
||||||
|
return Math.floor(staytimeGap / (3600 * 1000 * 24));
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user