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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/utils/mfa"
|
||||
"github.com/1Panel-dev/1Panel/utils/ntp"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -70,3 +73,55 @@ func (b *BaseApi) SyncTime(c *gin.Context) {
|
||||
|
||||
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"`
|
||||
ComplexityVerification string `json:"complexityVerification"`
|
||||
MFAStatus string `json:"mfaStatus"`
|
||||
MFASecret string `json:"mfaSecret"`
|
||||
|
||||
MonitorStatus string `json:"monitorStatus"`
|
||||
MonitorStoreDays string `json:"monitorStoreDays"`
|
||||
|
@ -9,3 +9,8 @@ type UserLoginInfo struct {
|
||||
Name string `json:"name"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type MfaCredential struct {
|
||||
Secret string `json:"secret"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
package model
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Setting struct {
|
||||
gorm.Model
|
||||
BaseModel
|
||||
Key string `json:"key" gorm:"type:varchar(256);not null;"`
|
||||
Value string `json:"value" gorm:"type:varchar(256)"`
|
||||
About string `json:"about" gorm:"type:longText"`
|
||||
|
@ -1,9 +1,11 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
@ -19,6 +21,11 @@ func NewMonitorJob() *monitor {
|
||||
}
|
||||
|
||||
func (m *monitor) Run() {
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
monitorStatus, _ := settingRepo.Get(settingRepo.WithByKey("MonitorStatus"))
|
||||
if monitorStatus.Value == "disable" {
|
||||
return
|
||||
}
|
||||
var itemModel model.MonitorBase
|
||||
totalPercent, _ := cpu.Percent(3*time.Second, false)
|
||||
if len(totalPercent) == 1 {
|
||||
@ -35,7 +42,9 @@ func (m *monitor) Run() {
|
||||
memoryInfo, _ := mem.VirtualMemory()
|
||||
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()
|
||||
for _, v := range ioStat {
|
||||
@ -67,7 +76,9 @@ func (m *monitor) Run() {
|
||||
if writeTime > itemIO.Time {
|
||||
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)
|
||||
@ -84,7 +95,9 @@ func (m *monitor) Run() {
|
||||
stime := time.Since(aheadData.CreatedAt).Seconds()
|
||||
itemNet.Up = float64(v.BytesSent-aheadData.BytesSent) / 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)
|
||||
if len(netStatAll) != 0 {
|
||||
@ -100,6 +113,18 @@ func (m *monitor) Run() {
|
||||
stime := time.Since(aheadData.CreatedAt).Seconds()
|
||||
itemNet.Up = float64(netStatAll[0].BytesSent-aheadData.BytesSent) / 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/shirou/gopsutil v3.21.11+incompatible
|
||||
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/viper v1.12.0
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
|
||||
github.com/swaggo/gin-swagger v1.5.1
|
||||
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/text v0.3.7
|
||||
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/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
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/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=
|
||||
@ -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/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
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/yuin/goldmark v1.1.25/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 {
|
||||
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 {
|
||||
return err
|
||||
|
@ -17,5 +17,8 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
|
||||
withRecordRouter.PUT("", baseApi.UpdateSetting)
|
||||
settingRouter.PUT("/password", baseApi.UpdatePassword)
|
||||
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;
|
||||
complexityVerification: string;
|
||||
mfaStatus: string;
|
||||
mfaSecret: string;
|
||||
|
||||
monitorStatus: string;
|
||||
monitorStoreDays: string;
|
||||
@ -34,4 +35,12 @@ export namespace Setting {
|
||||
newPassword: 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 = () => {
|
||||
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;
|
||||
name: FormItemRule;
|
||||
email: FormItemRule;
|
||||
number: FormItemRule;
|
||||
ip: FormItemRule;
|
||||
port: FormItemRule;
|
||||
}
|
||||
@ -48,6 +49,13 @@ export const Rules: CommonRule = {
|
||||
message: i18n.global.t('commons.rule.email'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
number: {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
min: 0,
|
||||
type: 'number',
|
||||
message: i18n.global.t('commons.rule.number'),
|
||||
},
|
||||
ip: {
|
||||
validator: checkIp,
|
||||
required: true,
|
||||
|
@ -11,8 +11,11 @@ export default {
|
||||
confirm: 'Confirm',
|
||||
cancel: 'Cancel',
|
||||
reset: 'Reset',
|
||||
login: 'Login',
|
||||
conn: 'Connect',
|
||||
clean: 'Clean',
|
||||
login: 'Login',
|
||||
close: 'Close',
|
||||
saveAndEnable: 'Save and enable',
|
||||
},
|
||||
search: {
|
||||
timeStart: 'Time start',
|
||||
@ -268,5 +271,35 @@ export default {
|
||||
oldPassword: 'Original password',
|
||||
newPassword: 'New 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: '同步',
|
||||
delete: '删除',
|
||||
edit: '编辑',
|
||||
enable: '启用',
|
||||
disable: '禁用',
|
||||
confirm: '确认',
|
||||
cancel: '取消',
|
||||
reset: '重置',
|
||||
conn: '连接',
|
||||
clean: '清空',
|
||||
login: '登录',
|
||||
close: '关闭',
|
||||
saveAndEnable: '保存并启用',
|
||||
},
|
||||
search: {
|
||||
timeStart: '开始时间',
|
||||
@ -64,7 +69,8 @@ export default {
|
||||
requiredInput: '请填写必填项',
|
||||
requiredSelect: '请选择必选项',
|
||||
commonName: '支持英文、中文、数字、.-_,长度1-30',
|
||||
email: '邮箱格式错误',
|
||||
email: '请输入正确的邮箱',
|
||||
number: '请输入正确的数字',
|
||||
ip: '请输入正确的 IP 地址',
|
||||
port: '请输入正确的端口',
|
||||
},
|
||||
@ -277,5 +283,31 @@ export default {
|
||||
oldPassword: '原密码',
|
||||
newPassword: '新密码',
|
||||
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',
|
||||
name: 'Monitor',
|
||||
component: () => import('@/views/monitor/index.vue'),
|
||||
component: () => import('@/views/host/monitor/index.vue'),
|
||||
meta: {
|
||||
title: 'menu.monitor',
|
||||
},
|
||||
|
@ -9,7 +9,6 @@
|
||||
@change="search('load')"
|
||||
v-model="timeRangeLoad"
|
||||
type="datetimerange"
|
||||
size="small"
|
||||
:range-separator="$t('commons.search.timeRange')"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
@ -30,7 +29,6 @@
|
||||
@change="search('cpu')"
|
||||
v-model="timeRangeCpu"
|
||||
type="datetimerange"
|
||||
size="small"
|
||||
:range-separator="$t('commons.search.timeRange')"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
@ -49,7 +47,6 @@
|
||||
@change="search('memory')"
|
||||
v-model="timeRangeMemory"
|
||||
type="datetimerange"
|
||||
size="small"
|
||||
:range-separator="$t('commons.search.timeRange')"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
@ -70,7 +67,6 @@
|
||||
@change="search('io')"
|
||||
v-model="timeRangeIO"
|
||||
type="datetimerange"
|
||||
size="small"
|
||||
:range-separator="$t('commons.search.timeRange')"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
@ -92,7 +88,6 @@
|
||||
@change="search('network')"
|
||||
style="margin-left: 20px"
|
||||
placeholder="Select"
|
||||
size="small"
|
||||
>
|
||||
<el-option v-for="item in netOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
@ -100,7 +95,6 @@
|
||||
@change="search('network')"
|
||||
v-model="timeRangeNetwork"
|
||||
type="datetimerange"
|
||||
size="small"
|
||||
:range-separator="$t('commons.search.timeRange')"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
|
@ -3,33 +3,21 @@
|
||||
<el-col :span="8">
|
||||
<el-card class="el-card">
|
||||
<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 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 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 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-input
|
||||
size="small"
|
||||
@input="loadHostTree"
|
||||
clearable
|
||||
style="margin-top: 5px"
|
||||
v-model="searcConfig.info"
|
||||
>
|
||||
<el-input @input="loadHostTree" clearable style="margin-top: 5px" v-model="searcConfig.info">
|
||||
<template #append><el-button icon="search" @click="loadHostTree" /></template>
|
||||
</el-input>
|
||||
<el-input
|
||||
size="small"
|
||||
v-if="groupInputShow"
|
||||
clearable
|
||||
style="margin-top: 5px"
|
||||
v-model="groupInputValue"
|
||||
>
|
||||
<el-input v-if="groupInputShow" clearable style="margin-top: 5px" v-model="groupInputValue">
|
||||
<template #append>
|
||||
<el-button-group>
|
||||
<el-button icon="Check" @click="onCreateGroup" />
|
||||
@ -53,8 +41,8 @@
|
||||
<el-button-group
|
||||
v-if="!(node.level === 1 && data.label === 'default') && data.id === hover"
|
||||
>
|
||||
<el-button icon="Edit" size="small" @click="onEdit(node, data)" />
|
||||
<el-button icon="Delete" size="small" @click="onDelete(node, data)" />
|
||||
<el-button icon="Edit" @click="onEdit(node, data)" />
|
||||
<el-button icon="Delete" @click="onDelete(node, data)" />
|
||||
</el-button-group>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -47,7 +47,7 @@
|
||||
@change="quickInput"
|
||||
style="width: 25%"
|
||||
:placeholder="$t('terminal.quickCommand')"
|
||||
size="small"
|
||||
|
||||
>
|
||||
<el-option
|
||||
v-for="cmd in commandList"
|
||||
@ -61,10 +61,10 @@
|
||||
v-model="batchVal"
|
||||
@keyup.enter="batchInput"
|
||||
style="width: 75%"
|
||||
size="small"
|
||||
|
||||
>
|
||||
<template #append>
|
||||
<el-switch size="small" v-model="isBatch" class="ml-2" />
|
||||
<el-switch v-model="isBatch" class="ml-2" />
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
@ -81,7 +81,7 @@
|
||||
<el-button @click="onNewSsh">New ssh</el-button>
|
||||
<el-button @click="onNewTab">New tab</el-button>
|
||||
</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>
|
||||
</el-input>
|
||||
<el-tree
|
||||
|
@ -1,70 +1,67 @@
|
||||
<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">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>通知</span>
|
||||
<span>{{ $t('setting.message') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="通知方式">
|
||||
<el-radio-group v-model="form.settingInfo.messageType">
|
||||
<el-radio-button label="none">关闭</el-radio-button>
|
||||
<el-radio-button label="email">email</el-radio-button>
|
||||
<el-radio-button label="wechat">企业微信</el-radio-button>
|
||||
<el-radio-button label="dingding">钉钉</el-radio-button>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="$t('setting.messageType')">
|
||||
<el-radio-group v-model="mesForm.messageType">
|
||||
<el-radio-button label="none">{{ $t('commons.button.close') }}</el-radio-button>
|
||||
<el-radio-button label="email">{{ $t('setting.email') }}</el-radio-button>
|
||||
<el-radio-button label="wechat">{{ $t('setting.wechat') }}</el-radio-button>
|
||||
<el-radio-button label="dingding">{{ $t('setting.dingding') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div v-if="form.settingInfo.messageType === 'none'">
|
||||
<div v-if="mesForm.messageType === 'none'">
|
||||
<el-form-item>
|
||||
<el-button @click="SaveSetting()">关闭消息通知</el-button>
|
||||
<el-button @click="SaveSetting()">{{ $t('setting.closeMessage') }}</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="form.settingInfo.messageType === 'email'">
|
||||
<el-form-item label="邮箱服务名称">
|
||||
<el-input clearable v-model="emailVars.serverName" />
|
||||
<div v-if="mesForm.messageType === 'email'">
|
||||
<el-form-item :label="$t('setting.emailServer')">
|
||||
<el-input clearable v-model="mesForm.emailVars.serverName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱地址">
|
||||
<el-input clearable v-model="emailVars.serverAddr" />
|
||||
<el-form-item :label="$t('setting.emailAddr')">
|
||||
<el-input clearable v-model="mesForm.emailVars.serverAddr" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱SMTP授权码">
|
||||
<el-input clearable v-model="emailVars.serverSMTP" />
|
||||
<el-form-item :label="$t('setting.emailSMTP')">
|
||||
<el-input clearable v-model="mesForm.emailVars.serverSMTP" />
|
||||
</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>
|
||||
</div>
|
||||
<div v-if="form.settingInfo.messageType === 'wechat'">
|
||||
<div v-if="mesForm.messageType === 'wechat'">
|
||||
<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 label="corpsecret">
|
||||
<el-input clearable v-model="weChatVars.corpsecret" />
|
||||
<el-input clearable v-model="mesForm.weChatVars.corpsecret" />
|
||||
</el-form-item>
|
||||
<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 label="agentid">
|
||||
<el-input clearable v-model="weChatVars.agentid" />
|
||||
<el-input clearable v-model="mesForm.weChatVars.agentid" />
|
||||
</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>
|
||||
</div>
|
||||
<div v-if="form.settingInfo.messageType === 'dingding'">
|
||||
<div v-if="mesForm.messageType === 'dingding'">
|
||||
<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 label="密钥">
|
||||
<el-input clearable v-model="dingVars.secret" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱 SMTP 授权码">
|
||||
<el-input clearable v-model="emailVars.serverSMTP" />
|
||||
<el-form-item :label="$t('setting.secret')">
|
||||
<el-input clearable v-model="mesForm.dingVars.secret" />
|
||||
</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>
|
||||
</div>
|
||||
</el-col>
|
||||
@ -74,7 +71,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { reactive, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { updateSetting } from '@/api/modules/setting';
|
||||
import i18n from '@/lang';
|
||||
@ -91,39 +88,57 @@ const form = withDefaults(defineProps<Props>(), {
|
||||
},
|
||||
});
|
||||
|
||||
const emailVars = ref({
|
||||
serverName: '',
|
||||
serverAddr: '',
|
||||
serverSMTP: '',
|
||||
const mesForm = reactive({
|
||||
messageType: '',
|
||||
emailVars: {
|
||||
serverName: '',
|
||||
serverAddr: '',
|
||||
serverSMTP: '',
|
||||
},
|
||||
weChatVars: {
|
||||
orpid: '',
|
||||
corpsecret: '',
|
||||
touser: '',
|
||||
agentid: '',
|
||||
},
|
||||
dingVars: {
|
||||
webhookToken: '',
|
||||
secret: '',
|
||||
},
|
||||
});
|
||||
const weChatVars = ref({
|
||||
orpid: '',
|
||||
corpsecret: '',
|
||||
touser: '',
|
||||
agentid: '',
|
||||
});
|
||||
const dingVars = ref({
|
||||
webhookToken: '',
|
||||
secret: '',
|
||||
|
||||
watch(form, (val: any) => {
|
||||
if (val.settingInfo.messageType) {
|
||||
mesForm.messageType = form.settingInfo.messageType;
|
||||
mesForm.emailVars = val.settingInfo.emailVars
|
||||
? JSON.parse(val.settingInfo.emailVars)
|
||||
: { serverName: '', serverAddr: '', serverSMTP: '' };
|
||||
mesForm.weChatVars = val.settingInfo.weChatVars
|
||||
? JSON.parse(val.settingInfo.weChatVars)
|
||||
: { orpid: '', corpsecret: '', touser: '', agentid: '' };
|
||||
mesForm.dingVars = val.settingInfo.dingVars
|
||||
? JSON.parse(val.settingInfo.dingVars)
|
||||
: { webhookToken: '', secret: '' };
|
||||
}
|
||||
});
|
||||
|
||||
const SaveSetting = async () => {
|
||||
let settingKey = '';
|
||||
let settingVal = '';
|
||||
switch (form.settingInfo.messageType) {
|
||||
switch (mesForm.messageType) {
|
||||
case 'none':
|
||||
settingVal = '';
|
||||
break;
|
||||
case 'email':
|
||||
settingVal = JSON.stringify(emailVars.value);
|
||||
settingVal = JSON.stringify(mesForm.emailVars);
|
||||
settingKey = 'EmailVars';
|
||||
break;
|
||||
case 'wechat':
|
||||
settingVal = JSON.stringify(emailVars.value);
|
||||
settingVal = JSON.stringify(mesForm.weChatVars);
|
||||
settingKey = 'WeChatVars';
|
||||
break;
|
||||
case 'dingding':
|
||||
settingVal = JSON.stringify(emailVars.value);
|
||||
settingVal = JSON.stringify(mesForm.dingVars);
|
||||
settingKey = 'DingVars';
|
||||
break;
|
||||
}
|
||||
@ -131,22 +146,8 @@ const SaveSetting = async () => {
|
||||
key: settingKey,
|
||||
value: settingVal,
|
||||
};
|
||||
await updateSetting({ key: 'MessageType', value: form.settingInfo.messageType });
|
||||
await updateSetting({ key: 'MessageType', value: mesForm.messageType });
|
||||
await updateSetting(param);
|
||||
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>
|
||||
|
@ -1,36 +1,37 @@
|
||||
<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">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>监控</span>
|
||||
<span>{{ $t('menu.monitor') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="开启监控">
|
||||
<el-switch
|
||||
v-model="form.settingInfo.monitorStatus"
|
||||
active-value="enable"
|
||||
inactive-value="disable"
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="$t('setting.enableMonitor')">
|
||||
<el-radio-group
|
||||
@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 label="过期时间">
|
||||
<el-form-item :label="$t('setting.storeDays')">
|
||||
<el-input clearable v-model="form.settingInfo.monitorStoreDays">
|
||||
<template #append>
|
||||
<el-button
|
||||
@click="SaveSetting('MonitorStoreDays', form.settingInfo.monitorStoreDays)"
|
||||
icon="Collection"
|
||||
>
|
||||
保存
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</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-col>
|
||||
</el-row>
|
||||
@ -40,8 +41,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { updateSetting } from '@/api/modules/setting';
|
||||
import { updateSetting, cleanMonitors } from '@/api/modules/setting';
|
||||
import i18n from '@/lang';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
|
||||
interface Props {
|
||||
settingInfo: any;
|
||||
@ -61,4 +63,8 @@ const SaveSetting = async (key: string, val: string) => {
|
||||
await updateSetting(param);
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
};
|
||||
|
||||
const onClean = async () => {
|
||||
await useDeleteData(cleanMonitors, {}, 'commons.msg.delete', true);
|
||||
};
|
||||
</script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<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">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
@ -9,7 +9,7 @@
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<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">
|
||||
<template #append>
|
||||
<el-button
|
||||
@ -21,7 +21,7 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</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">
|
||||
<template #append>
|
||||
<el-button icon="Setting" @click="passwordVisiable = true">
|
||||
@ -30,7 +30,7 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</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">
|
||||
<template #append>
|
||||
<el-button @click="SaveSetting('Email', form.settingInfo.email)" icon="Collection">
|
||||
@ -42,7 +42,7 @@
|
||||
<span class="input-help">{{ $t('setting.emailHelper') }}</span>
|
||||
</div>
|
||||
</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">
|
||||
<template #append>
|
||||
<el-button
|
||||
@ -54,7 +54,7 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.theme')">
|
||||
<el-form-item :label="$t('setting.theme')" prop="settingInfo.theme">
|
||||
<el-radio-group
|
||||
@change="SaveSetting('Theme', form.settingInfo.theme)"
|
||||
v-model="form.settingInfo.theme"
|
||||
@ -69,7 +69,7 @@
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.language')">
|
||||
<el-form-item :label="$t('setting.language')" prop="settingInfo.language">
|
||||
<el-radio-group
|
||||
@change="SaveSetting('Language', form.settingInfo.language)"
|
||||
v-model="form.settingInfo.language"
|
||||
@ -83,8 +83,8 @@
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.sessionTimeout')">
|
||||
<el-input v-model="form.settingInfo.sessionTimeout">
|
||||
<el-form-item :label="$t('setting.sessionTimeout')" prop="settingInfo.sessionTimeout">
|
||||
<el-input v-model.number="form.settingInfo.sessionTimeout">
|
||||
<template #append>
|
||||
<el-button
|
||||
@click="SaveSetting('SessionTimeout', form.settingInfo.sessionTimeout)"
|
||||
@ -114,14 +114,7 @@
|
||||
</el-card>
|
||||
</el-form>
|
||||
<el-dialog v-model="passwordVisiable" :title="$t('setting.changePassword')" width="30%">
|
||||
<el-form
|
||||
size="small"
|
||||
ref="passFormRef"
|
||||
label-width="80px"
|
||||
label-position="left"
|
||||
:model="passForm"
|
||||
:rules="passRules"
|
||||
>
|
||||
<el-form ref="passFormRef" label-width="80px" label-position="left" :model="passForm" :rules="passRules">
|
||||
<el-form-item :label="$t('setting.oldPassword')" prop="oldPassword">
|
||||
<el-input type="password" show-password clearable v-model="passForm.oldPassword" />
|
||||
</el-form-item>
|
||||
@ -135,7 +128,7 @@
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="passwordVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button @click="submitChangePassword()">
|
||||
<el-button @click="submitChangePassword(passFormRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@ -151,8 +144,8 @@ import { Setting } from '@/api/interface/setting';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
import { Rules } from '@/global/form-rues';
|
||||
import router from '@/routers/router';
|
||||
import { Rules } from '@/global/form-rues';
|
||||
|
||||
const i18n = useI18n();
|
||||
const globalStore = GlobalStore();
|
||||
@ -191,6 +184,9 @@ const form = withDefaults(defineProps<Props>(), {
|
||||
const { switchDark } = useTheme();
|
||||
|
||||
const SaveSetting = async (key: string, val: string) => {
|
||||
if (val === '') {
|
||||
return;
|
||||
}
|
||||
switch (key) {
|
||||
case 'Language':
|
||||
i18n.locale.value = val;
|
||||
@ -218,12 +214,16 @@ function checkPassword(rule: any, value: any, callback: any) {
|
||||
}
|
||||
callback();
|
||||
}
|
||||
const submitChangePassword = async () => {
|
||||
await updatePassword(passForm);
|
||||
passwordVisiable.value = false;
|
||||
ElMessage.success(i18n.t('commons.msg.operationSuccess'));
|
||||
router.push({ name: 'login' });
|
||||
globalStore.setLogStatus(false);
|
||||
const submitChangePassword = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
await updatePassword(passForm);
|
||||
passwordVisiable.value = false;
|
||||
ElMessage.success(i18n.t('commons.msg.operationSuccess'));
|
||||
router.push({ name: 'login' });
|
||||
globalStore.setLogStatus(false);
|
||||
});
|
||||
};
|
||||
const onSyncTime = async () => {
|
||||
const res = await syncTime();
|
||||
|
@ -1,102 +1,150 @@
|
||||
<template>
|
||||
<el-form size="small" :model="form" label-position="left" label-width="120px">
|
||||
<el-card style="margin-top: 10px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>安全</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="面板端口">
|
||||
<el-input clearable v-model="form.settingInfo.serverPort">
|
||||
<template #append>
|
||||
<el-button
|
||||
@click="SaveSetting('ServerPort', form.settingInfo.serverPort)"
|
||||
icon="Collection"
|
||||
<div>
|
||||
<el-form :model="form" label-position="left" label-width="160px">
|
||||
<el-card style="margin-top: 10px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('setting.safe') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="$t('setting.panelPort')">
|
||||
<el-input clearable v-model="form.settingInfo.serverPort">
|
||||
<template #append>
|
||||
<el-button
|
||||
@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-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-tooltip>
|
||||
</el-input>
|
||||
<div>
|
||||
<span class="input-help">
|
||||
{{ $t('setting.portHelper') }}
|
||||
</span>
|
||||
</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-tooltip>
|
||||
</el-input>
|
||||
<div>
|
||||
<span class="input-help">
|
||||
建议端口范围8888 - 65535,注意:有安全组的服务器请提前在安全组放行新端口
|
||||
</span>
|
||||
<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>
|
||||
<div>
|
||||
<span class="input-help">
|
||||
{{ $t('setting.complexityHelper') }}
|
||||
</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>
|
||||
</el-form-item>
|
||||
<el-form-item label="安全入口">
|
||||
<el-input clearable v-model="form.settingInfo.securityEntrance">
|
||||
<template #append>
|
||||
<el-button
|
||||
@click="SaveSetting('SecurityEntrance', form.settingInfo.securityEntrance)"
|
||||
icon="Collection"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<div>
|
||||
<span class="input-help">
|
||||
面板管理入口,设置后只能通过指定安全入口登录面板,如: /89dc6ae8
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码过期时间">
|
||||
<el-input clearable v-model="form.settingInfo.passwordTimeOut">
|
||||
<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>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-form>
|
||||
<el-dialog v-model="timeoutVisiable" :title="$t('setting.changePassword')" width="30%">
|
||||
<el-form ref="timeoutFormRef" label-width="80px" label-position="left" :model="timeoutForm">
|
||||
<el-form-item :label="$t('setting.oldPassword')" prop="days" :rules="Rules.number">
|
||||
<el-input clearable v-model.number="timeoutForm.days" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="timeoutVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button @click="submitTimeout(timeoutFormRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
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 { Rules } from '@/global/form-rues';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
|
||||
interface Props {
|
||||
settingInfo: any;
|
||||
@ -108,8 +156,22 @@ const form = withDefaults(defineProps<Props>(), {
|
||||
passwordTimeOut: '',
|
||||
complexityVerification: '',
|
||||
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) => {
|
||||
let param = {
|
||||
@ -119,4 +181,38 @@ const SaveSetting = async (key: string, val: string) => {
|
||||
await updateSetting(param);
|
||||
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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user