feat: 增加主机工具箱管理 (#3001)

This commit is contained in:
ssongliu 2023-11-20 15:28:09 +08:00 committed by GitHub
parent 6c7e9d0a52
commit c260b858bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 2212 additions and 413 deletions

View File

@ -0,0 +1,183 @@
package v1
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/gin-gonic/gin"
)
// @Tags Device
// @Summary Load device base info
// @Description 获取设备基础信息
// @Success 200 {object} dto.DeviceBaseInfo
// @Security ApiKeyAuth
// @Router /toolbox/device/base [get]
func (b *BaseApi) LoadDeviceBaseInfo(c *gin.Context) {
data, err := deviceService.LoadBaseInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Device
// @Summary list time zone options
// @Description 获取系统可用时区选项
// @Accept json
// @Success 200 {Array} string
// @Security ApiKeyAuth
// @Router /toolbox/device/zone/options [get]
func (b *BaseApi) LoadTimeOption(c *gin.Context) {
list, err := deviceService.LoadTimeZone()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Device
// @Summary load conf
// @Description 获取系统配置文件
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/device/conf [post]
func (b *BaseApi) LoadDeviceConf(c *gin.Context) {
var req dto.OperationWithName
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
list, err := deviceService.LoadConf(req.Name)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, list)
}
// @Tags Device
// @Summary Update device conf by file
// @Description 通过文件修改配置
// @Accept json
// @Param request body dto.UpdateByNameAndFile true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/device/update/byconf [post]
func (b *BaseApi) UpdateDevicByFile(c *gin.Context) {
var req dto.UpdateByNameAndFile
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := deviceService.UpdateByConf(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Device
// @Summary Update device
// @Description 修改系统参数
// @Accept json
// @Param request body dto.SettingUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/device/update/conf [post]
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改主机参数 [key] => [value]","formatEN":"update device conf [key] => [value]"}
func (b *BaseApi) UpdateDeviceConf(c *gin.Context) {
var req dto.SettingUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := deviceService.Update(req.Key, req.Value); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Device
// @Summary Update device hosts
// @Description 修改系统 hosts
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/device/update/host [post]
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改主机 Host [key] => [value]","formatEN":"update device host [key] => [value]"}
func (b *BaseApi) UpdateDeviceHost(c *gin.Context) {
var req []dto.HostHelper
if err := helper.CheckBind(&req, c); err != nil {
return
}
if err := deviceService.UpdateHosts(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Device
// @Summary Update device passwd
// @Description 修改系统密码
// @Accept json
// @Param request body dto.ChangePasswd true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/device/update/passwd [post]
func (b *BaseApi) UpdateDevicPasswd(c *gin.Context) {
var req dto.ChangePasswd
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Passwd) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Passwd)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Passwd = string(password)
}
if err := deviceService.UpdatePasswd(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Device
// @Summary Check device DNS conf
// @Description 检查系统 DNS 配置可用性
// @Accept json
// @Param request body dto.SettingUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /toolbox/device/check/dns [post]
func (b *BaseApi) CheckDNS(c *gin.Context) {
var req dto.SettingUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := deviceService.CheckDNS(req.Key, req.Value)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}

View File

@ -33,6 +33,7 @@ var (
sshService = service.NewISSHService() sshService = service.NewISSHService()
firewallService = service.NewIFirewallService() firewallService = service.NewIFirewallService()
deviceService = service.NewIDeviceService()
fail2banService = service.NewIFail2BanService() fail2banService = service.NewIFail2BanService()
settingService = service.NewISettingService() settingService = service.NewISettingService()

View File

@ -217,43 +217,6 @@ func (b *BaseApi) HandlePasswordExpired(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags System Setting
// @Summary Load time zone options
// @Description 加载系统可用时区
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/time/option [get]
func (b *BaseApi) LoadTimeZone(c *gin.Context) {
zones, err := settingService.LoadTimeZone()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, zones)
}
// @Tags System Setting
// @Summary Sync system time
// @Description 系统时间同步
// @Accept json
// @Param request body dto.SyncTime true "request"
// @Success 200 {string} ntime
// @Security ApiKeyAuth
// @Router /settings/time/sync [post]
// @x-panel-log {"bodyKeys":["ntpSite"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"系统时间同步[ntpSite]","formatEN":"sync system time [ntpSite]"}
func (b *BaseApi) SyncTime(c *gin.Context) {
var req dto.SyncTime
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := settingService.SyncTime(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags System Setting // @Tags System Setting
// @Summary Load local backup dir // @Summary Load local backup dir
// @Description 获取安装根目录 // @Description 获取安装根目录

View File

@ -45,6 +45,11 @@ type UpdateByFile struct {
File string `json:"file"` File string `json:"file"`
} }
type UpdateByNameAndFile struct {
Name string `json:"name"`
File string `json:"file"`
}
type OperationWithNameAndType struct { type OperationWithNameAndType struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type" validate:"required"` Type string `json:"type" validate:"required"`

26
backend/app/dto/device.go Normal file
View File

@ -0,0 +1,26 @@
package dto
type DeviceBaseInfo struct {
DNS []string `json:"dns"`
Hosts []HostHelper `json:"hosts"`
Hostname string `json:"hostname"`
TimeZone string `json:"timeZone"`
LocalTime string `json:"localTime"`
Ntp string `json:"ntp"`
User string `json:"user"`
}
type HostHelper struct {
IP string `json:"ip"`
Host string `json:"host"`
}
type TimeZoneOptions struct {
From string `json:"from"`
Zones []string `json:"zones"`
}
type ChangePasswd struct {
User string `json:"user"`
Passwd string `json:"passwd"`
}

View File

@ -0,0 +1,293 @@
package service
import (
"bufio"
"errors"
"fmt"
"net"
"os"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
)
const defaultDNSPath = "/etc/resolv.conf"
const defaultHostPath = "/etc/hosts"
type DeviceService struct{}
type IDeviceService interface {
LoadBaseInfo() (dto.DeviceBaseInfo, error)
Update(key, value string) error
UpdateHosts(req []dto.HostHelper) error
UpdatePasswd(req dto.ChangePasswd) error
UpdateByConf(req dto.UpdateByNameAndFile) error
LoadTimeZone() ([]string, error)
CheckDNS(key, value string) (bool, error)
LoadConf(name string) (string, error)
}
func NewIDeviceService() IDeviceService {
return &DeviceService{}
}
func (u *DeviceService) LoadBaseInfo() (dto.DeviceBaseInfo, error) {
var baseInfo dto.DeviceBaseInfo
baseInfo.LocalTime = time.Now().Format("2006-01-02 15:04:05 MST -0700")
baseInfo.TimeZone = common.LoadTimeZoneByCmd()
baseInfo.DNS = loadDNS()
baseInfo.Hosts = loadHosts()
baseInfo.Hostname = loadHostname()
baseInfo.User = loadUser()
ntp, _ := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
baseInfo.Ntp = ntp.Value
return baseInfo, nil
}
func (u *DeviceService) LoadTimeZone() ([]string, error) {
std, err := cmd.Exec("timedatectl list-timezones")
if err != nil {
return []string{}, err
}
return strings.Split(std, "\n"), nil
}
func (u *DeviceService) CheckDNS(key, value string) (bool, error) {
content, err := os.ReadFile(defaultDNSPath)
if err != nil {
return false, err
}
defer func() { _ = u.UpdateByConf(dto.UpdateByNameAndFile{Name: "DNS", File: string(content)}) }()
if key == "form" {
if err := u.Update("DNS", value); err != nil {
return false, err
}
} else {
if err := u.UpdateByConf(dto.UpdateByNameAndFile{Name: "DNS", File: value}); err != nil {
return false, err
}
}
conn, err := net.DialTimeout("ip4:icmp", "www.baidu.com", time.Second*2)
if err != nil {
return false, err
}
defer conn.Close()
return true, nil
}
func (u *DeviceService) Update(key, value string) error {
switch key {
case "TimeZone":
if err := ntp.UpdateSystemTimeZone(value); err != nil {
return err
}
go func() {
_, err := cmd.Exec("systemctl restart 1panel.service")
if err != nil {
global.LOG.Errorf("restart system for new time zone failed, err: %v", err)
}
}()
case "DNS":
if err := updateDNS(strings.Split(value, ",")); err != nil {
return err
}
case "Hostname":
std, err := cmd.Execf("%s hostnamectl set-hostname %s", cmd.SudoHandleCmd(), value)
if err != nil {
return errors.New(std)
}
case "LocalTime":
if err := settingRepo.Update("NtpSite", value); err != nil {
return err
}
ntime, err := ntp.GetRemoteTime(value)
if err != nil {
return err
}
ts := ntime.Format("2006-01-02 15:04:05")
if err := ntp.UpdateSystemTime(ts); err != nil {
return err
}
default:
return fmt.Errorf("not support such key %s", key)
}
return nil
}
func (u *DeviceService) UpdateHosts(req []dto.HostHelper) error {
conf, err := os.ReadFile(defaultHostPath)
if err != nil {
return fmt.Errorf("read namesever conf of %s failed, err: %v", defaultHostPath, err)
}
lines := strings.Split(string(conf), "\n")
newFile := ""
for _, line := range lines {
if len(line) == 0 {
continue
}
parts := strings.Fields(line)
if len(parts) < 2 {
newFile += line + "\n"
continue
}
for index, item := range req {
if item.IP == parts[0] && item.Host == strings.Join(parts[1:], " ") {
newFile += line + "\n"
req = append(req[:index], req[index+1:]...)
break
}
}
}
for _, item := range req {
newFile += fmt.Sprintf("%s %s \n", item.IP, item.Host)
}
file, err := os.OpenFile(defaultHostPath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(newFile)
write.Flush()
return nil
}
func (u *DeviceService) UpdatePasswd(req dto.ChangePasswd) error {
std, err := cmd.Execf("%s echo '%s:%s' | %s chpasswd", cmd.SudoHandleCmd(), req.User, req.Passwd, cmd.SudoHandleCmd())
if err != nil {
return errors.New(std)
}
return nil
}
func (u *DeviceService) LoadConf(name string) (string, error) {
pathItem := ""
switch name {
case "DNS":
pathItem = defaultDNSPath
case "Hosts":
pathItem = defaultHostPath
default:
return "", fmt.Errorf("not support such name %s", name)
}
if _, err := os.Stat(pathItem); err != nil {
return "", err
}
content, err := os.ReadFile(pathItem)
if err != nil {
return "", err
}
return string(content), nil
}
func (u *DeviceService) UpdateByConf(req dto.UpdateByNameAndFile) error {
if req.Name != "DNS" && req.Name != "Hosts" {
return fmt.Errorf("not support such name %s", req.Name)
}
path := defaultDNSPath
if req.Name == "Hosts" {
path = defaultHostPath
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(req.File)
write.Flush()
return nil
}
func loadDNS() []string {
var list []string
dnsConf, err := os.ReadFile(defaultDNSPath)
if err == nil {
lines := strings.Split(string(dnsConf), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "nameserver ") {
list = append(list, strings.TrimPrefix(line, "nameserver "))
}
}
}
return list
}
func updateDNS(list []string) error {
conf, err := os.ReadFile(defaultDNSPath)
if err != nil {
return fmt.Errorf("read nameserver conf of %s failed, err: %v", defaultDNSPath, err)
}
lines := strings.Split(string(conf), "\n")
newFile := ""
for _, line := range lines {
if len(line) == 0 {
continue
}
parts := strings.Fields(line)
if len(parts) < 2 || parts[0] != "nameserver" {
newFile += line + "\n"
continue
}
itemNs := strings.Join(parts[1:], " ")
for index, item := range list {
if item == itemNs {
newFile += line + "\n"
list = append(list[:index], list[index+1:]...)
break
}
}
}
for _, item := range list {
newFile += fmt.Sprintf("nameserver %s \n", item)
}
file, err := os.OpenFile(defaultDNSPath, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(newFile)
write.Flush()
return nil
}
func loadHosts() []dto.HostHelper {
var list []dto.HostHelper
hostConf, err := os.ReadFile(defaultHostPath)
if err == nil {
lines := strings.Split(string(hostConf), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) < 2 {
continue
}
list = append(list, dto.HostHelper{IP: parts[0], Host: strings.Join(parts[1:], " ")})
}
}
return list
}
func loadHostname() string {
std, err := cmd.Exec("hostname")
if err != nil {
return ""
}
return strings.ReplaceAll(std, "\n", "")
}
func loadUser() string {
std, err := cmd.Exec("whoami")
if err != nil {
return ""
}
return strings.ReplaceAll(std, "\n", "")
}

View File

@ -21,7 +21,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt" "github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
"github.com/1Panel-dev/1Panel/backend/utils/ssl" "github.com/1Panel-dev/1Panel/backend/utils/ssl"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
@ -32,7 +31,6 @@ type SettingService struct{}
type ISettingService interface { type ISettingService interface {
GetSettingInfo() (*dto.SettingInfo, error) GetSettingInfo() (*dto.SettingInfo, error)
LoadInterfaceAddr() ([]string, error) LoadInterfaceAddr() ([]string, error)
LoadTimeZone() ([]string, error)
Update(key, value string) error Update(key, value string) error
UpdatePassword(c *gin.Context, old, new string) error UpdatePassword(c *gin.Context, old, new string) error
UpdatePort(port uint) error UpdatePort(port uint) error
@ -40,7 +38,6 @@ type ISettingService interface {
UpdateSSL(c *gin.Context, req dto.SSLUpdate) error UpdateSSL(c *gin.Context, req dto.SSLUpdate) error
LoadFromCert() (*dto.SSLInfo, error) LoadFromCert() (*dto.SSLInfo, error)
HandlePasswordExpired(c *gin.Context, old, new string) error HandlePasswordExpired(c *gin.Context, old, new string) error
SyncTime(req dto.SyncTime) error
SystemScan() dto.CleanData SystemScan() dto.CleanData
SystemClean(req []dto.Clean) SystemClean(req []dto.Clean)
@ -72,14 +69,6 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
return &info, err return &info, err
} }
func (u *SettingService) LoadTimeZone() ([]string, error) {
std, err := cmd.Exec("timedatectl list-timezones")
if err != nil {
return []string{}, nil
}
return strings.Split(std, "\n"), err
}
func (u *SettingService) Update(key, value string) error { func (u *SettingService) Update(key, value string) error {
switch key { switch key {
case "MonitorStatus": case "MonitorStatus":
@ -107,10 +96,6 @@ func (u *SettingService) Update(key, value string) error {
return err return err
} }
} }
case "TimeZone":
if err := ntp.UpdateSystemTimeZone(value); err != nil {
return err
}
case "AppStoreLastModified": case "AppStoreLastModified":
exist, _ := settingRepo.Get(settingRepo.WithByKey("AppStoreLastModified")) exist, _ := settingRepo.Get(settingRepo.WithByKey("AppStoreLastModified"))
if exist.ID == 0 { if exist.ID == 0 {
@ -129,13 +114,6 @@ func (u *SettingService) Update(key, value string) error {
if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format("2006-01-02 15:04:05")); err != nil { if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format("2006-01-02 15:04:05")); err != nil {
return err return err
} }
case "TimeZone":
go func() {
_, err := cmd.Exec("systemctl restart 1panel.service")
if err != nil {
global.LOG.Errorf("restart system for new time zone failed, err: %v", err)
}
}()
case "BindDomain": case "BindDomain":
if len(value) != 0 { if len(value) != 0 {
_ = global.SESSION.Clean() _ = global.SESSION.Clean()
@ -148,21 +126,6 @@ func (u *SettingService) Update(key, value string) error {
return nil return nil
} }
func (u *SettingService) SyncTime(req dto.SyncTime) error {
if err := settingRepo.Update("NtpSite", req.NtpSite); err != nil {
return err
}
ntime, err := ntp.GetRemoteTime(req.NtpSite)
if err != nil {
return err
}
ts := ntime.Format("2006-01-02 15:04:05")
if err := ntp.UpdateSystemTime(ts); err != nil {
return err
}
return nil
}
func (u *SettingService) LoadInterfaceAddr() ([]string, error) { func (u *SettingService) LoadInterfaceAddr() ([]string, error) {
addrMap := make(map[string]struct{}) addrMap := make(map[string]struct{})
addrs, err := net.InterfaceAddrs() addrs, err := net.InterfaceAddrs()

View File

@ -29,8 +29,6 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
settingRouter.GET("/ssl/info", baseApi.LoadFromCert) settingRouter.GET("/ssl/info", baseApi.LoadFromCert)
settingRouter.POST("/ssl/download", baseApi.DownloadSSL) settingRouter.POST("/ssl/download", baseApi.DownloadSSL)
settingRouter.POST("/password/update", baseApi.UpdatePassword) settingRouter.POST("/password/update", baseApi.UpdatePassword)
settingRouter.GET("/time/option", baseApi.LoadTimeZone)
settingRouter.POST("/time/sync", baseApi.SyncTime)
settingRouter.POST("/monitor/clean", baseApi.CleanMonitor) settingRouter.POST("/monitor/clean", baseApi.CleanMonitor)
settingRouter.POST("/mfa", baseApi.LoadMFA) settingRouter.POST("/mfa", baseApi.LoadMFA)
settingRouter.POST("/mfa/bind", baseApi.MFABind) settingRouter.POST("/mfa/bind", baseApi.MFABind)

View File

@ -16,6 +16,15 @@ func (s *ToolboxRouter) InitToolboxRouter(Router *gin.RouterGroup) {
Use(middleware.PasswordExpired()) Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi baseApi := v1.ApiGroupApp.BaseApi
{ {
toolboxRouter.GET("/device/base", baseApi.LoadDeviceBaseInfo)
toolboxRouter.GET("/device/zone/options", baseApi.LoadTimeOption)
toolboxRouter.POST("/device/update/conf", baseApi.UpdateDeviceConf)
toolboxRouter.POST("/device/update/host", baseApi.UpdateDeviceHost)
toolboxRouter.POST("/device/update/passwd", baseApi.UpdateDevicPasswd)
toolboxRouter.POST("/device/update/byconf", baseApi.UpdateDevicByFile)
toolboxRouter.POST("/device/check/dns", baseApi.CheckDNS)
toolboxRouter.POST("/device/conf", baseApi.LoadDeviceConf)
toolboxRouter.GET("/fail2ban/base", baseApi.LoadFail2BanBaseInfo) toolboxRouter.GET("/fail2ban/base", baseApi.LoadFail2BanBaseInfo)
toolboxRouter.GET("/fail2ban/load/conf", baseApi.LoadFail2BanConf) toolboxRouter.GET("/fail2ban/load/conf", baseApi.LoadFail2BanConf)
toolboxRouter.POST("/fail2ban/search", baseApi.SearchFail2Ban) toolboxRouter.POST("/fail2ban/search", baseApi.SearchFail2Ban)

View File

@ -1,61 +0,0 @@
package toolbox
import (
"fmt"
"os"
"strings"
"testing"
"github.com/1Panel-dev/1Panel/backend/utils/ssh"
)
func TestCds(t *testing.T) {
kk := ssh.ConnInfo{
Port: 22,
AuthMode: "password",
User: "root",
}
sd, err := kk.Run("fail2ban-client get sshd ignoreip")
if err != nil {
fmt.Println(err)
}
sd = strings.ReplaceAll(sd, "|", "")
sd = strings.ReplaceAll(sd, "`", "")
sd = strings.ReplaceAll(sd, "\n", "")
addrs := strings.Split(sd, "-")
for _, addr := range addrs {
if !strings.HasPrefix(addr, " ") {
continue
}
fmt.Println(strings.TrimPrefix(addr, " "))
}
}
func TestCdsxx(t *testing.T) {
initFile := `#DEFAULT-START
[DEFAULT]
ignoreip = 127.0.0.1/8,172.16.10.114,172.16.10.116
bantime = 600
findtime = 300
maxretry = 5
banaction = firewallcmd-ipset
action = %(action_mwl)s
#DEFAULT-END
#sshd-START
[sshd]
enabled = true
filter = sshd
port = 22
maxretry = 5
findtime = 300
bantime = 86400
action = %(action_mwl)s
logpath = /var/log/secure
#sshd-END`
if err := os.WriteFile("/Users/slooop/Downloads/tex.txt", []byte(initFile), 0640); err != nil {
fmt.Println(err)
}
}

View File

@ -1,5 +1,5 @@
// Package docs GENERATED BY SWAG; DO NOT EDIT // Code generated by swaggo/swag. DO NOT EDIT.
// This file was generated by swaggo/swag
package docs package docs
import "github.com/swaggo/swag" import "github.com/swaggo/swag"
@ -9906,70 +9906,6 @@ const docTemplate = `{
} }
} }
}, },
"/settings/time/option": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "加载系统可用时区",
"tags": [
"System Setting"
],
"summary": "Load time zone options",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/settings/time/sync": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "系统时间同步",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Sync system time",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SyncTime"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"ntpSite"
],
"formatEN": "sync system time [ntpSite]",
"formatZH": "系统时间同步[ntpSite]",
"paramKeys": []
}
}
},
"/settings/update": { "/settings/update": {
"post": { "post": {
"security": [ "security": [
@ -10086,6 +10022,257 @@ const docTemplate = `{
} }
} }
}, },
"/toolbox/device/base": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取设备基础信息",
"tags": [
"Device"
],
"summary": "Load device base info",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.DeviceBaseInfo"
}
}
}
}
},
"/toolbox/device/check/dns": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "检查系统 DNS 配置可用性",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "Check device DNS conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SettingUpdate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/toolbox/device/conf": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取系统配置文件",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "load conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithName"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/toolbox/device/update/byconf": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "通过文件修改配置",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "Update device conf by file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UpdateByNameAndFile"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/toolbox/device/update/conf": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改系统参数",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "Update device",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SettingUpdate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"key",
"value"
],
"formatEN": "update device conf [key] =\u003e [value]",
"formatZH": "修改主机参数 [key] =\u003e [value]",
"paramKeys": []
}
}
},
"/toolbox/device/update/host": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改系统 hosts",
"tags": [
"Device"
],
"summary": "Update device hosts",
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"key",
"value"
],
"formatEN": "update device host [key] =\u003e [value]",
"formatZH": "修改主机 Host [key] =\u003e [value]",
"paramKeys": []
}
}
},
"/toolbox/device/update/passwd": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改系统密码",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "Update device passwd",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ChangePasswd"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/toolbox/device/zone/options": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取系统可用时区选项",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "list time zone options",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "Array"
}
}
}
}
},
"/toolbox/fail2ban/base": { "/toolbox/fail2ban/base": {
"get": { "get": {
"security": [ "security": [
@ -13094,6 +13281,17 @@ const docTemplate = `{
} }
} }
}, },
"dto.ChangePasswd": {
"type": "object",
"properties": {
"passwd": {
"type": "string"
},
"user": {
"type": "string"
}
}
},
"dto.ChangeRedisPass": { "dto.ChangeRedisPass": {
"type": "object", "type": "object",
"required": [ "required": [
@ -14202,6 +14400,38 @@ const docTemplate = `{
} }
} }
}, },
"dto.DeviceBaseInfo": {
"type": "object",
"properties": {
"dns": {
"type": "array",
"items": {
"type": "string"
}
},
"hostname": {
"type": "string"
},
"hosts": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.HostHelper"
}
},
"localTime": {
"type": "string"
},
"ntp": {
"type": "string"
},
"timeZone": {
"type": "string"
},
"user": {
"type": "string"
}
}
},
"dto.DiskInfo": { "dto.DiskInfo": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -14546,6 +14776,17 @@ const docTemplate = `{
} }
} }
}, },
"dto.HostHelper": {
"type": "object",
"properties": {
"host": {
"type": "string"
},
"ip": {
"type": "string"
}
}
},
"dto.HostOperate": { "dto.HostOperate": {
"type": "object", "type": "object",
"required": [ "required": [
@ -16290,17 +16531,6 @@ const docTemplate = `{
} }
} }
}, },
"dto.SyncTime": {
"type": "object",
"required": [
"ntpSite"
],
"properties": {
"ntpSite": {
"type": "string"
}
}
},
"dto.TreeChild": { "dto.TreeChild": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -16320,6 +16550,17 @@ const docTemplate = `{
} }
} }
}, },
"dto.UpdateByNameAndFile": {
"type": "object",
"properties": {
"file": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"dto.UpdateDescription": { "dto.UpdateDescription": {
"type": "object", "type": "object",
"required": [ "required": [

View File

@ -9899,70 +9899,6 @@
} }
} }
}, },
"/settings/time/option": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "加载系统可用时区",
"tags": [
"System Setting"
],
"summary": "Load time zone options",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/settings/time/sync": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "系统时间同步",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Sync system time",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SyncTime"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"ntpSite"
],
"formatEN": "sync system time [ntpSite]",
"formatZH": "系统时间同步[ntpSite]",
"paramKeys": []
}
}
},
"/settings/update": { "/settings/update": {
"post": { "post": {
"security": [ "security": [
@ -10079,6 +10015,257 @@
} }
} }
}, },
"/toolbox/device/base": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取设备基础信息",
"tags": [
"Device"
],
"summary": "Load device base info",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.DeviceBaseInfo"
}
}
}
}
},
"/toolbox/device/check/dns": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "检查系统 DNS 配置可用性",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "Check device DNS conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SettingUpdate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/toolbox/device/conf": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取系统配置文件",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "load conf",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.OperationWithName"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/toolbox/device/update/byconf": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "通过文件修改配置",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "Update device conf by file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UpdateByNameAndFile"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/toolbox/device/update/conf": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改系统参数",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "Update device",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SettingUpdate"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"key",
"value"
],
"formatEN": "update device conf [key] =\u003e [value]",
"formatZH": "修改主机参数 [key] =\u003e [value]",
"paramKeys": []
}
}
},
"/toolbox/device/update/host": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改系统 hosts",
"tags": [
"Device"
],
"summary": "Update device hosts",
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"key",
"value"
],
"formatEN": "update device host [key] =\u003e [value]",
"formatZH": "修改主机 Host [key] =\u003e [value]",
"paramKeys": []
}
}
},
"/toolbox/device/update/passwd": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "修改系统密码",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "Update device passwd",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.ChangePasswd"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/toolbox/device/zone/options": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取系统可用时区选项",
"consumes": [
"application/json"
],
"tags": [
"Device"
],
"summary": "list time zone options",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "Array"
}
}
}
}
},
"/toolbox/fail2ban/base": { "/toolbox/fail2ban/base": {
"get": { "get": {
"security": [ "security": [
@ -13087,6 +13274,17 @@
} }
} }
}, },
"dto.ChangePasswd": {
"type": "object",
"properties": {
"passwd": {
"type": "string"
},
"user": {
"type": "string"
}
}
},
"dto.ChangeRedisPass": { "dto.ChangeRedisPass": {
"type": "object", "type": "object",
"required": [ "required": [
@ -14195,6 +14393,38 @@
} }
} }
}, },
"dto.DeviceBaseInfo": {
"type": "object",
"properties": {
"dns": {
"type": "array",
"items": {
"type": "string"
}
},
"hostname": {
"type": "string"
},
"hosts": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.HostHelper"
}
},
"localTime": {
"type": "string"
},
"ntp": {
"type": "string"
},
"timeZone": {
"type": "string"
},
"user": {
"type": "string"
}
}
},
"dto.DiskInfo": { "dto.DiskInfo": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -14539,6 +14769,17 @@
} }
} }
}, },
"dto.HostHelper": {
"type": "object",
"properties": {
"host": {
"type": "string"
},
"ip": {
"type": "string"
}
}
},
"dto.HostOperate": { "dto.HostOperate": {
"type": "object", "type": "object",
"required": [ "required": [
@ -16283,17 +16524,6 @@
} }
} }
}, },
"dto.SyncTime": {
"type": "object",
"required": [
"ntpSite"
],
"properties": {
"ntpSite": {
"type": "string"
}
}
},
"dto.TreeChild": { "dto.TreeChild": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -16313,6 +16543,17 @@
} }
} }
}, },
"dto.UpdateByNameAndFile": {
"type": "object",
"properties": {
"file": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"dto.UpdateDescription": { "dto.UpdateDescription": {
"type": "object", "type": "object",
"required": [ "required": [

View File

@ -176,6 +176,13 @@ definitions:
- groupID - groupID
- id - id
type: object type: object
dto.ChangePasswd:
properties:
passwd:
type: string
user:
type: string
type: object
dto.ChangeRedisPass: dto.ChangeRedisPass:
properties: properties:
value: value:
@ -923,6 +930,27 @@ definitions:
- username - username
- version - version
type: object type: object
dto.DeviceBaseInfo:
properties:
dns:
items:
type: string
type: array
hostname:
type: string
hosts:
items:
$ref: '#/definitions/dto.HostHelper'
type: array
localTime:
type: string
ntp:
type: string
timeZone:
type: string
user:
type: string
type: object
dto.DiskInfo: dto.DiskInfo:
properties: properties:
device: device:
@ -1158,6 +1186,13 @@ definitions:
- port - port
- user - user
type: object type: object
dto.HostHelper:
properties:
host:
type: string
ip:
type: string
type: object
dto.HostOperate: dto.HostOperate:
properties: properties:
addr: addr:
@ -2334,13 +2369,6 @@ definitions:
required: required:
- id - id
type: object type: object
dto.SyncTime:
properties:
ntpSite:
type: string
required:
- ntpSite
type: object
dto.TreeChild: dto.TreeChild:
properties: properties:
id: id:
@ -2353,6 +2381,13 @@ definitions:
file: file:
type: string type: string
type: object type: object
dto.UpdateByNameAndFile:
properties:
file:
type: string
name:
type: string
type: object
dto.UpdateDescription: dto.UpdateDescription:
properties: properties:
description: description:
@ -10774,46 +10809,6 @@ paths:
formatEN: update system ssl => [ssl] formatEN: update system ssl => [ssl]
formatZH: 修改系统 ssl => [ssl] formatZH: 修改系统 ssl => [ssl]
paramKeys: [] paramKeys: []
/settings/time/option:
get:
description: 加载系统可用时区
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Load time zone options
tags:
- System Setting
/settings/time/sync:
post:
consumes:
- application/json
description: 系统时间同步
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SyncTime'
responses:
"200":
description: OK
schema:
type: string
security:
- ApiKeyAuth: []
summary: Sync system time
tags:
- System Setting
x-panel-log:
BeforeFunctions: []
bodyKeys:
- ntpSite
formatEN: sync system time [ntpSite]
formatZH: 系统时间同步[ntpSite]
paramKeys: []
/settings/update: /settings/update:
post: post:
consumes: consumes:
@ -10888,6 +10883,161 @@ paths:
formatEN: upgrade service => [version] formatEN: upgrade service => [version]
formatZH: 更新系统 => [version] formatZH: 更新系统 => [version]
paramKeys: [] paramKeys: []
/toolbox/device/base:
get:
description: 获取设备基础信息
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.DeviceBaseInfo'
security:
- ApiKeyAuth: []
summary: Load device base info
tags:
- Device
/toolbox/device/check/dns:
post:
consumes:
- application/json
description: 检查系统 DNS 配置可用性
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SettingUpdate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Check device DNS conf
tags:
- Device
/toolbox/device/conf:
post:
consumes:
- application/json
description: 获取系统配置文件
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.OperationWithName'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: load conf
tags:
- Device
/toolbox/device/update/byconf:
post:
consumes:
- application/json
description: 通过文件修改配置
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.UpdateByNameAndFile'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update device conf by file
tags:
- Device
/toolbox/device/update/conf:
post:
consumes:
- application/json
description: 修改系统参数
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SettingUpdate'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update device
tags:
- Device
x-panel-log:
BeforeFunctions: []
bodyKeys:
- key
- value
formatEN: update device conf [key] => [value]
formatZH: 修改主机参数 [key] => [value]
paramKeys: []
/toolbox/device/update/host:
post:
description: 修改系统 hosts
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update device hosts
tags:
- Device
x-panel-log:
BeforeFunctions: []
bodyKeys:
- key
- value
formatEN: update device host [key] => [value]
formatZH: 修改主机 Host [key] => [value]
paramKeys: []
/toolbox/device/update/passwd:
post:
consumes:
- application/json
description: 修改系统密码
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.ChangePasswd'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update device passwd
tags:
- Device
/toolbox/device/zone/options:
get:
consumes:
- application/json
description: 获取系统可用时区选项
responses:
"200":
description: OK
schema:
type: Array
security:
- ApiKeyAuth: []
summary: list time zone options
tags:
- Device
/toolbox/fail2ban/base: /toolbox/fail2ban/base:
get: get:
description: 获取 Fail2Ban 基础信息 description: 获取 Fail2Ban 基础信息

View File

@ -1,4 +1,22 @@
export namespace Toolbox { export namespace Toolbox {
export interface DeviceBaseInfo {
dns: Array<string>;
hosts: Array<HostHelper>;
hostname: string;
ntp: string;
user: string;
timeZone: string;
localTime: string;
}
export interface HostHelper {
ip: string;
host: string;
}
export interface TimeZoneOptions {
from: string;
zones: Array<string>;
}
export interface Fail2banBaseInfo { export interface Fail2banBaseInfo {
isEnable: boolean; isEnable: boolean;
isActive: boolean; isActive: boolean;

View File

@ -1,6 +1,33 @@
import http from '@/api'; import http from '@/api';
import { UpdateByFile } from '../interface'; import { UpdateByFile } from '../interface';
import { Toolbox } from '../interface/toolbox'; import { Toolbox } from '../interface/toolbox';
import { Base64 } from 'js-base64';
// device
export const getDeviceBase = () => {
return http.get<Toolbox.DeviceBaseInfo>(`/toolbox/device/base`);
};
export const loadTimeZoneOptions = () => {
return http.get<Array<string>>(`/toolbox/device/zone/options`);
};
export const updateDevice = (key: string, value: string) => {
return http.post(`/toolbox/device/update/conf`, { key: key, value: value });
};
export const updateDeviceHost = (param: Array<Toolbox.TimeZoneOptions>) => {
return http.post(`/toolbox/device/update/host`, param);
};
export const updateDevicePasswd = (user: string, passwd: string) => {
return http.post(`/toolbox/device/update/passwd`, { user: user, passwd: Base64.encode(passwd) });
};
export const updateDeviceByConf = (name: string, file: string) => {
return http.post(`/toolbox/device/update/byconf`, { name: name, file: file });
};
export const checkDNS = (key: string, value: string) => {
return http.post(`/toolbox/device/check/dns`, { key: key, value: value });
};
export const loadDeviceConf = (name: string) => {
return http.post(`/toolbox/device/conf`, { name: name });
};
// fail2ban // fail2ban
export const getFail2banBase = () => { export const getFail2banBase = () => {

View File

@ -873,6 +873,37 @@ const message = {
emptyTerminal: 'No terminal is currently connected', emptyTerminal: 'No terminal is currently connected',
}, },
toolbox: { toolbox: {
device: {
dnsHelper: 'Server Address Domain Resolution',
hostsHelper: 'Hostname Resolution',
hosts: 'Domain',
toolbox: 'Toolbox',
hostname: 'Hostname',
passwd: 'Host Password',
passwdHelper: 'Input characters cannot include $ and &',
timeZone: 'System Time Zone',
localTime: 'Server Time',
timeZoneChangeHelper: 'Modifying the system time zone requires restarting the service. Continue?',
timeZoneHelper:
'Time zone modification depends on the timedatectl command. If not installed, the modification may fail.',
timeZoneCN: 'Beijing',
timeZoneAM: 'Los Angeles',
timeZoneNY: 'New York',
ntpALi: 'Alibaba',
ntpGoogle: 'Google',
syncSite: 'NTP Server',
hostnameHelper:
'Hostname modification depends on the hostnamectl command. If not installed, the modification may fail.',
userHelper:
'The username depends on the whoami command for retrieval. If not installed, retrieval may fail.',
passwordHelper:
'Password modification depends on the chpasswd command. If not installed, the modification may fail.',
hostHelper:
'There is an empty value in the provided content. Please check and try again after modification!',
dnsCheck: 'Test Availability',
dnsOK: 'DNS configuration information is available!',
dnsTestFailed: 'DNS configuration information is not available. Please modify and try again!',
},
fail2ban: { fail2ban: {
noFail2ban: 'Fail2Ban service not detected, please refer to the official documentation for installation', noFail2ban: 'Fail2Ban service not detected, please refer to the official documentation for installation',
unActive: 'The Fail2Ban service is not enabled at present, please enable it first!', unActive: 'The Fail2Ban service is not enabled at present, please enable it first!',

View File

@ -837,6 +837,32 @@ const message = {
emptyTerminal: '暫無終端連接', emptyTerminal: '暫無終端連接',
}, },
toolbox: { toolbox: {
device: {
dnsHelper: '伺服器地址域名解析',
hostsHelper: '主機名解析',
hosts: '域名',
toolbox: '工具箱',
hostname: '主機名',
passwd: '主機密碼',
passwdHelper: '輸入的字符不能包含 $ &',
timeZone: '系統時區',
localTime: '伺服器時間',
timeZoneChangeHelper: '修改系統時區需要重新啟動服務是否繼續',
timeZoneHelper: '時區修改依賴於 timedatectl 命令如未安裝可能導致修改失敗',
timeZoneCN: '北京',
timeZoneAM: '洛杉磯',
timeZoneNY: '紐約',
ntpALi: '阿里',
ntpGoogle: '谷歌',
syncSite: 'NTP 伺服器',
hostnameHelper: '主機名修改依賴於 hostnamectl 命令如未安裝可能導致修改失敗',
userHelper: '用戶名依賴於 whoami 命令獲取如未安裝可能導致獲取失敗',
passwordHelper: '密碼修改依賴於 chpasswd 命令如未安裝可能導致修改失敗',
hostHelper: '填寫的內容中存在空值請檢查修改後重試',
dnsCheck: '測試可用性',
dnsOK: 'DNS 配置信息可用',
dnsTestFailed: 'DNS 配置信息不可用請修改後重試',
},
fail2ban: { fail2ban: {
noFail2ban: '未檢測到 Fail2Ban 服務請參考官方文檔進行安裝', noFail2ban: '未檢測到 Fail2Ban 服務請參考官方文檔進行安裝',
unActive: '當前未開啟 Fail2Ban 服務請先開啟', unActive: '當前未開啟 Fail2Ban 服務請先開啟',
@ -1055,16 +1081,6 @@ const message = {
systemIP: '服務器地址', systemIP: '服務器地址',
systemIPWarning: '當前未設置服務器地址請先在面板設置中設置', systemIPWarning: '當前未設置服務器地址請先在面板設置中設置',
defaultNetwork: '默認網卡', defaultNetwork: '默認網卡',
syncTime: '服務器時間',
timeZone: '系統時區',
timeZoneChangeHelper: '系統時區修改需要重啟服務是否繼續',
timeZoneHelper: '時區修改依賴於系統 timedatectl 服務重啟 1Panel 服務後生效',
timeZoneCN: '北京',
timeZoneAM: '洛杉磯',
timeZoneNY: '紐約',
ntpALi: '阿裏',
ntpGoogle: '谷歌',
syncSite: 'NTP 服務器',
syncSiteHelper: '該操作將使用 {0} 作為源進行系統時間同步是否繼續', syncSiteHelper: '該操作將使用 {0} 作為源進行系統時間同步是否繼續',
changePassword: '密碼修改', changePassword: '密碼修改',
oldPassword: '原密碼', oldPassword: '原密碼',

View File

@ -838,6 +838,32 @@ const message = {
emptyTerminal: '暂无终端连接', emptyTerminal: '暂无终端连接',
}, },
toolbox: { toolbox: {
device: {
dnsHelper: '服务器地址域名解析',
hostsHelper: '主机名解析',
hosts: '域名',
toolbox: '工具箱',
hostname: '主机名',
passwd: '主机密码',
passwdHelper: '输入字符不能包含 $ &',
timeZone: '系统时区',
localTime: '服务器时间',
timeZoneChangeHelper: '系统时区修改需要重启服务是否继续',
timeZoneHelper: '时区修改依赖于 timedatectl 命令如未安装可能导致修改失败',
timeZoneCN: '北京',
timeZoneAM: '洛杉矶',
timeZoneNY: '纽约',
ntpALi: '阿里',
ntpGoogle: '谷歌',
syncSite: 'NTP 服务器',
hostnameHelper: '主机名修改依赖于 hostnamectl 命令如未安装可能导致修改失败',
userHelper: '用户名依赖于 whoami 命令获取如未安装可能导致获取失败',
passwordHelper: '密码修改依赖于 chpasswd 命令如未安装可能导致修改失败',
hostHelper: '填写的内容中存在空值请检查修改后重试',
dnsCheck: '测试可用性',
dnsOK: 'DNS 配置信息可用',
dnsTestFailed: 'DNS 配置信息不可用请修改后重试',
},
fail2ban: { fail2ban: {
noFail2ban: '未检测到 Fail2Ban 服务请参考官方文档进行安装', noFail2ban: '未检测到 Fail2Ban 服务请参考官方文档进行安装',
unActive: '当前未开启 Fail2Ban 服务请先开启', unActive: '当前未开启 Fail2Ban 服务请先开启',
@ -1056,16 +1082,6 @@ const message = {
systemIP: '服务器地址', systemIP: '服务器地址',
systemIPWarning: '当前未设置服务器地址请先在面板设置中设置', systemIPWarning: '当前未设置服务器地址请先在面板设置中设置',
defaultNetwork: '默认网卡', defaultNetwork: '默认网卡',
syncTime: '服务器时间',
timeZone: '系统时区',
timeZoneChangeHelper: '系统时区修改需要重启服务是否继续',
timeZoneHelper: '时区修改依赖于系统 timedatectl 服务重启 1Panel 服务后生效',
timeZoneCN: '北京',
timeZoneAM: '洛杉矶',
timeZoneNY: '纽约',
ntpALi: '阿里',
ntpGoogle: '谷歌',
syncSite: 'NTP 服务器',
syncSiteHelper: '该操作将使用 {0} 作为源进行系统时间同步是否继续', syncSiteHelper: '该操作将使用 {0} 作为源进行系统时间同步是否继续',
changePassword: '密码修改', changePassword: '密码修改',
oldPassword: '原密码', oldPassword: '原密码',

View File

@ -13,10 +13,20 @@ const toolboxRouter = {
{ {
path: '/toolbox', path: '/toolbox',
name: 'Toolbox', name: 'Toolbox',
redirect: '/toolbox/supervisor', redirect: '/toolbox/device',
component: () => import('@/views/toolbox/index.vue'), component: () => import('@/views/toolbox/index.vue'),
meta: {}, meta: {},
children: [ children: [
{
path: 'device',
name: 'Device',
component: () => import('@/views/toolbox/device/index.vue'),
hidden: true,
meta: {
activeMenu: '/toolbox',
requiresAuth: false,
},
},
{ {
path: 'supervisor', path: 'supervisor',
name: 'Supervisor', name: 'Supervisor',

View File

@ -93,24 +93,6 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('setting.timeZone')" prop="timeZone">
<el-input disabled v-model.number="form.timeZone">
<template #append>
<el-button @click="onChangeTimeZone" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('setting.syncTime')">
<el-input disabled v-model="form.localTime">
<template #append>
<el-button v-show="!show" @click="onChangeNtp" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('setting.systemIP')" prop="systemIP"> <el-form-item :label="$t('setting.systemIP')" prop="systemIP">
<el-input disabled v-if="form.systemIP" v-model="form.systemIP"> <el-input disabled v-if="form.systemIP" v-model="form.systemIP">
<template #append> <template #append>
@ -138,8 +120,6 @@
<PanelName ref="panelNameRef" @search="search()" /> <PanelName ref="panelNameRef" @search="search()" />
<SystemIP ref="systemIPRef" @search="search()" /> <SystemIP ref="systemIPRef" @search="search()" />
<Timeout ref="timeoutRef" @search="search()" /> <Timeout ref="timeoutRef" @search="search()" />
<TimeZone ref="timezoneRef" @search="search()" />
<Ntp ref="ntpRef" @search="search()" />
<Network ref="networkRef" @search="search()" /> <Network ref="networkRef" @search="search()" />
<Clean ref="cleanRef" @search="search()" /> <Clean ref="cleanRef" @search="search()" />
</div> </div>
@ -158,10 +138,8 @@ import UserName from '@/views/setting/panel/username/index.vue';
import Timeout from '@/views/setting/panel/timeout/index.vue'; import Timeout from '@/views/setting/panel/timeout/index.vue';
import PanelName from '@/views/setting/panel/name/index.vue'; import PanelName from '@/views/setting/panel/name/index.vue';
import SystemIP from '@/views/setting/panel/systemip/index.vue'; import SystemIP from '@/views/setting/panel/systemip/index.vue';
import TimeZone from '@/views/setting/panel/timezone/index.vue';
import Network from '@/views/setting/panel/default-network/index.vue'; import Network from '@/views/setting/panel/default-network/index.vue';
import Clean from '@/views/setting/panel/clean/index.vue'; import Clean from '@/views/setting/panel/clean/index.vue';
import Ntp from '@/views/setting/panel/ntp/index.vue';
const loading = ref(false); const loading = ref(false);
const i18n = useI18n(); const i18n = useI18n();
@ -197,8 +175,6 @@ const passwordRef = ref();
const panelNameRef = ref(); const panelNameRef = ref();
const systemIPRef = ref(); const systemIPRef = ref();
const timeoutRef = ref(); const timeoutRef = ref();
const ntpRef = ref();
const timezoneRef = ref();
const networkRef = ref(); const networkRef = ref();
const cleanRef = ref(); const cleanRef = ref();
const unset = ref(i18n.t('setting.unSetting')); const unset = ref(i18n.t('setting.unSetting'));
@ -239,12 +215,6 @@ const onChangeTimeout = () => {
const onChangeSystemIP = () => { const onChangeSystemIP = () => {
systemIPRef.value.acceptParams({ systemIP: form.systemIP }); systemIPRef.value.acceptParams({ systemIP: form.systemIP });
}; };
const onChangeTimeZone = () => {
timezoneRef.value.acceptParams({ timeZone: form.timeZone });
};
const onChangeNtp = () => {
ntpRef.value.acceptParams({ localTime: form.localTime, ntpSite: form.ntpSite });
};
const onChangeNetwork = () => { const onChangeNetwork = () => {
networkRef.value.acceptParams({ defaultNetwork: form.defaultNetwork }); networkRef.value.acceptParams({ defaultNetwork: form.defaultNetwork });
}; };

View File

@ -0,0 +1,161 @@
<template>
<div>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<template #header>
<DrawerHeader header="DNS" :back="handleClose" />
</template>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-radio-group v-model="confShowType" @change="changeMode">
<el-radio-button label="form">{{ $t('database.baseConf') }}</el-radio-button>
<el-radio-button label="all">{{ $t('database.allConf') }}</el-radio-button>
</el-radio-group>
<el-form
class="mt-4"
v-if="confShowType === 'form'"
ref="formRef"
label-position="top"
@submit.prevent
:model="form"
v-loading="loading"
>
<el-form-item label="DNS" prop="dns">
<el-input
type="textarea"
:placeholder="$t('setting.allowIPEgs')"
:autosize="{ minRows: 8, maxRows: 10 }"
v-model="form.dns"
/>
</el-form-item>
</el-form>
<div v-else>
<codemirror
:autofocus="true"
placeholder="# The DNS configuration file does not exist or is empty (/etc/resolv.conf)"
:indent-with-tab="true"
:tabSize="4"
style="margin-top: 10px; height: calc(100vh - 200px)"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
v-model="dnsConf"
/>
</div>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" @click="onTest()">
{{ $t('toolbox.device.dnsCheck') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSave()">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { loadDeviceConf, checkDNS, updateDevice, updateDeviceByConf } from '@/api/modules/toolbox';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
const emit = defineEmits<{ (e: 'search'): void }>();
const extensions = [javascript(), oneDark];
const confShowType = ref('form');
const dnsConf = ref();
const form = reactive({
dns: '',
});
interface DialogProps {
dns: Array<string>;
}
const drawerVisible = ref();
const loading = ref();
const acceptParams = (params: DialogProps): void => {
form.dns = params.dns ? params.dns.join('\n') : '';
drawerVisible.value = true;
};
const loadDNSConf = async () => {
const res = await loadDeviceConf('DNS');
dnsConf.value = res.data || '';
};
const changeMode = async () => {
if (confShowType.value === 'all') {
loadDNSConf();
}
};
const onTest = async () => {
loading.value = true;
let value = '';
if (confShowType.value === 'form') {
value = form.dns.replaceAll('\n', ',');
} else {
value = dnsConf.value;
}
await checkDNS(confShowType.value, value)
.then((res) => {
loading.value = false;
if (res.data) {
MsgSuccess(i18n.global.t('toolbox.device.dnsOK'));
} else {
MsgError(i18n.global.t('toolbox.device.dnsTestFailed'));
}
})
.catch(() => {
loading.value = false;
});
};
const onSave = async () => {
loading.value = true;
if (confShowType.value == 'form') {
await updateDevice('DNS', form.dns.replaceAll('\n', ','))
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
handleClose();
})
.catch(() => {
loading.value = false;
});
return;
}
await updateDeviceByConf('DNS', dnsConf.value)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
handleClose();
})
.catch(() => {
loading.value = false;
});
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>

View File

@ -0,0 +1,100 @@
<template>
<div>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
<template #header>
<DrawerHeader :header="$t('toolbox.device.hostname')" :back="handleClose" />
</template>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-alert
:title="$t('toolbox.device.hostnameHelper')"
class="common-prompt"
:closable="false"
type="warning"
/>
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
<el-form-item
:label="$t('toolbox.device.hostname')"
prop="hostname"
:rules="Rules.requiredInput"
>
<el-input clearable v-model.trim="form.hostname" />
</el-form-item>
</el-form>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSaveHostame(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { Rules } from '@/global/form-rules';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { updateDevice } from '@/api/modules/toolbox';
const emit = defineEmits<{ (e: 'search'): void }>();
interface DialogProps {
hostname: string;
}
const drawerVisible = ref();
const loading = ref();
const form = reactive({
hostname: '',
});
const formRef = ref<FormInstance>();
const acceptParams = (params: DialogProps): void => {
form.hostname = params.hostname;
drawerVisible.value = true;
};
const onSaveHostame = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
ElMessageBox.confirm(
i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('toolbox.device.hostname'), form.hostname]),
i18n.global.t('database.confChange'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
).then(async () => {
await updateDevice('Hostname', form.hostname)
.then(async () => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
loading.value = false;
drawerVisible.value = false;
emit('search');
})
.catch(() => {
loading.value = false;
});
});
});
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>

View File

@ -0,0 +1,167 @@
<template>
<div>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<template #header>
<DrawerHeader header="Hosts" :back="handleClose" />
</template>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-radio-group v-model="confShowType" @change="changeMode">
<el-radio-button label="base">{{ $t('database.baseConf') }}</el-radio-button>
<el-radio-button label="all">{{ $t('database.allConf') }}</el-radio-button>
</el-radio-group>
<table style="width: 100%" class="mt-4" v-if="confShowType === 'base'">
<tr v-if="form.hosts.length !== 0">
<th scope="col" width="25%" align="left">
<label>IP</label>
</th>
<th scope="col" width="70%" align="left">
<label>{{ $t('toolbox.device.hosts') }}</label>
</th>
<th align="left"></th>
</tr>
<tr v-for="(row, index) in form.hosts" :key="index">
<td width="25%">
<el-input placeholder="172.16.10.111" v-model="row.ip" />
</td>
<td width="70%">
<el-input placeholder="test.hostname.com" v-model="row.host" />
</td>
<td>
<el-button link type="primary" @click="handleHostsDelete(index)">
{{ $t('commons.button.delete') }}
</el-button>
</td>
</tr>
<tr>
<td align="left">
<el-button @click="handleHostsAdd()">{{ $t('commons.button.add') }}</el-button>
</td>
</tr>
</table>
<div v-else>
<codemirror
:autofocus="true"
placeholder="# The hosts configuration file does not exist or is empty (/etc/hosts)"
:indent-with-tab="true"
:tabSize="4"
style="margin-top: 10px; height: calc(100vh - 200px)"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
v-model="hostsConf"
/>
</div>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSave()">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { loadDeviceConf, updateDeviceByConf, updateDeviceHost } from '@/api/modules/toolbox';
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
import { Toolbox } from '@/api/interface/toolbox';
const emit = defineEmits<{ (e: 'search'): void }>();
const extensions = [javascript(), oneDark];
const confShowType = ref('base');
const hostsConf = ref();
const form = reactive({
hosts: [],
});
interface DialogProps {
hosts: Array<Toolbox.HostHelper>;
}
const drawerVisible = ref();
const loading = ref();
const acceptParams = (params: DialogProps): void => {
confShowType.value = 'base';
form.hosts = params.hosts;
drawerVisible.value = true;
};
const loadHostsConf = async () => {
const res = await loadDeviceConf('Hosts');
hostsConf.value = res.data || '';
};
const changeMode = async () => {
if (confShowType.value === 'all') {
loadHostsConf();
}
};
const handleHostsAdd = () => {
let item = {
ip: '',
host: '',
};
form.hosts.push(item);
};
const handleHostsDelete = (index: number) => {
form.hosts.splice(index, 1);
};
const onSave = async () => {
for (const item of form.hosts) {
if (item.ip === '' || item.host === '') {
MsgError(i18n.global.t('toolbox.device.hostHelper'));
return;
}
}
loading.value = true;
if (confShowType.value === 'base') {
await updateDeviceHost(form.hosts)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
handleClose();
})
.catch(() => {
loading.value = false;
});
return;
}
await updateDeviceByConf('Hosts', hostsConf.value)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
handleClose();
})
.catch(() => {
loading.value = false;
});
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>

View File

@ -0,0 +1,146 @@
<template>
<div v-loading="loading">
<LayoutContent :title="$t('toolbox.device.toolbox')" :divider="true">
<template #main>
<el-row style="margin-top: 20px">
<el-col :span="1"><br /></el-col>
<el-col :xs="24" :sm="20" :md="20" :lg="10" :xl="10">
<el-form :model="form" label-position="left" ref="formRef" label-width="120px">
<el-form-item label="DNS" prop="dnsItem">
<el-input disabled v-model="form.dnsItem">
<template #append>
<el-button @click="onChangeDNS" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label="Hosts" prop="hosts">
<el-input disabled v-model="form.hostItem">
<template #append>
<el-button @click="onChangeHost" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('toolbox.device.hostname')" prop="hostname">
<el-input disabled v-model="form.hostname">
<template #append>
<el-button @click="onChangeHostname" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('toolbox.device.passwd')" prop="passwd">
<el-input disabled v-model="form.passwd">
<template #append>
<el-button @click="onChangePasswd" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('toolbox.device.timeZone')" prop="timeZone">
<el-input disabled v-model="form.timeZone">
<template #append>
<el-button @click="onChangeTimeZone" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('toolbox.device.localTime')" prop="localTime">
<el-input disabled v-model="form.localTime">
<template #append>
<el-button @click="onChangeLocalTime" icon="Setting">
{{ $t('commons.button.set') }}
</el-button>
</template>
</el-input>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
</LayoutContent>
<Passwd ref="passwdRef" @search="search" />
<TimeZone ref="timeZoneRef" @search="search" />
<LocalTime ref="localTimeRef" @search="search" />
<DNS ref="dnsRef" @search="search" />
<Hostname ref="hostnameRef" @search="search" />
<Hosts ref="hostsRef" @search="search" />
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import Passwd from '@/views/toolbox/device/passwd/index.vue';
import TimeZone from '@/views/toolbox/device/time-zone/index.vue';
import LocalTime from '@/views/toolbox/device/local-time/index.vue';
import DNS from '@/views/toolbox/device/dns/index.vue';
import Hostname from '@/views/toolbox/device/hostname/index.vue';
import Hosts from '@/views/toolbox/device/hosts/index.vue';
import { getDeviceBase } from '@/api/modules/toolbox';
import i18n from '@/lang';
const loading = ref(false);
const timeZoneRef = ref();
const localTimeRef = ref();
const passwdRef = ref();
const dnsRef = ref();
const hostnameRef = ref();
const hostsRef = ref();
const form = reactive({
dns: [],
dnsItem: '',
hosts: [],
hostItem: '',
hostname: '',
user: '',
passwd: '******',
timeZone: '',
localTime: '',
ntp: '',
});
const onChangeTimeZone = () => {
timeZoneRef.value.acceptParams({ timeZone: form.timeZone });
};
const onChangeLocalTime = () => {
localTimeRef.value.acceptParams({ localTime: form.localTime, ntpSite: form.ntp });
};
const onChangePasswd = () => {
passwdRef.value.acceptParams({ user: form.user });
};
const onChangeDNS = () => {
dnsRef.value.acceptParams({ dns: form.dns });
};
const onChangeHostname = () => {
hostnameRef.value.acceptParams({ hostname: form.hostname });
};
const onChangeHost = () => {
hostsRef.value.acceptParams({ hosts: form.hosts });
};
const search = async () => {
const res = await getDeviceBase();
form.timeZone = res.data.timeZone;
form.localTime = res.data.localTime;
form.hostname = res.data.hostname;
form.ntp = res.data.ntp;
form.user = res.data.user;
form.dns = res.data.dns || [];
form.dnsItem = form.dns ? i18n.global.t('toolbox.device.dnsHelper') : i18n.global.t('setting.unSetting');
form.hosts = res.data.hosts || [];
form.hostItem = form.hosts ? i18n.global.t('toolbox.device.hostsHelper') : i18n.global.t('setting.unSetting');
};
onMounted(() => {
search();
});
</script>

View File

@ -2,25 +2,25 @@
<div> <div>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%"> <el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
<template #header> <template #header>
<DrawerHeader :header="$t('setting.syncTime')" :back="handleClose" /> <DrawerHeader :header="$t('toolbox.device.localTime')" :back="handleClose" />
</template> </template>
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading"> <el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
<el-col :span="22"> <el-col :span="22">
<el-form-item :label="$t('setting.syncSite')" prop="ntpSite" :rules="Rules.domain"> <el-form-item :label="$t('toolbox.device.syncSite')" prop="ntpSite" :rules="Rules.domain">
<el-input v-model="form.ntpSite" /> <el-input v-model="form.ntpSite" />
<el-button type="primary" link class="tagClass" @click="form.ntpSite = 'pool.ntp.org'"> <el-button type="primary" link class="tagClass" @click="form.ntpSite = 'pool.ntp.org'">
{{ $t('website.default') }} {{ $t('website.default') }}
</el-button> </el-button>
<el-button type="primary" link class="tagClass" @click="form.ntpSite = 'ntp.aliyun.com'"> <el-button type="primary" link class="tagClass" @click="form.ntpSite = 'ntp.aliyun.com'">
{{ $t('setting.ntpALi') }} {{ $t('toolbox.device.ntpALi') }}
</el-button> </el-button>
<el-button type="primary" link class="tagClass" @click="form.ntpSite = 'time.google.com'"> <el-button type="primary" link class="tagClass" @click="form.ntpSite = 'time.google.com'">
{{ $t('setting.ntpGoogle') }} {{ $t('toolbox.device.ntpGoogle') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
<el-form-item :label="$t('setting.syncTime')" prop="localTime"> <el-form-item :label="$t('toolbox.device.localTime')" prop="localTime">
<el-input v-model="form.localTime" disabled /> <el-input v-model="form.localTime" disabled />
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -41,10 +41,10 @@
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { syncTime } from '@/api/modules/setting';
import { ElMessageBox, FormInstance } from 'element-plus'; import { ElMessageBox, FormInstance } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import { updateDevice } from '@/api/modules/toolbox';
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
@ -73,8 +73,8 @@ const onSyncTime = async (formEl: FormInstance | undefined) => {
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
if (!valid) return; if (!valid) return;
ElMessageBox.confirm( ElMessageBox.confirm(
i18n.global.t('setting.syncSiteHelper', [form.ntpSite]), i18n.global.t('toolbox.device.syncSiteHelper', [form.ntpSite]),
i18n.global.t('setting.syncSite'), i18n.global.t('toolbox.device.syncSite'),
{ {
confirmButtonText: i18n.global.t('commons.button.confirm'), confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'), cancelButtonText: i18n.global.t('commons.button.cancel'),
@ -82,10 +82,9 @@ const onSyncTime = async (formEl: FormInstance | undefined) => {
}, },
).then(async () => { ).then(async () => {
loading.value = true; loading.value = true;
await syncTime(form.ntpSite) await updateDevice('LocalTime', form.ntpSite)
.then((res) => { .then(() => {
loading.value = false; loading.value = false;
form.localTime = res.data;
emit('search'); emit('search');
handleClose(); handleClose();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));

View File

@ -0,0 +1,115 @@
<template>
<div v-loading="loading">
<el-drawer v-model="passwordVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
<template #header>
<DrawerHeader :header="$t('setting.changePassword')" :back="handleClose" />
</template>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form ref="formRef" label-position="top" :model="form" :rules="passRules">
<el-alert
:title="$t('toolbox.device.passwordHelper')"
class="common-prompt"
:closable="false"
type="warning"
/>
<el-form-item :label="$t('setting.user')" prop="user">
<el-input clearable v-model.trim="form.user" />
<span class="input-help">{{ $t('toolbox.device.userHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.newPassword')" prop="newPassword">
<el-input type="password" show-password clearable v-model.trim="form.newPassword" />
</el-form-item>
<el-form-item :label="$t('setting.retryPassword')" prop="retryPassword">
<el-input type="password" show-password clearable v-model.trim="form.retryPassword" />
</el-form-item>
</el-form>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="loading" @click="passwordVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="submitChangePassword(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { reactive, ref } from 'vue';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { updateDevicePasswd } from '@/api/modules/toolbox';
const formRef = ref<FormInstance>();
const passRules = reactive({
user: Rules.requiredInput,
newPassword: [Rules.requiredInput, Rules.noSpace, { validator: checkPassword, trigger: 'blur' }],
retryPassword: [Rules.requiredInput, Rules.noSpace, { validator: checkRePassword, trigger: 'blur' }],
});
const loading = ref(false);
const passwordVisible = ref<boolean>(false);
const form = reactive({
user: '',
newPassword: '',
retryPassword: '',
});
interface DialogProps {
user: string;
}
const acceptParams = (params: DialogProps): void => {
form.user = params.user;
form.newPassword = '';
form.retryPassword = '';
passwordVisible.value = true;
};
function checkPassword(rule: any, value: any, callback: any) {
if (form.newPassword.indexOf('&') !== -1 || form.newPassword.indexOf('$') !== -1) {
return callback(new Error(i18n.global.t('toolbox.device.passwdHelper')));
}
callback();
}
function checkRePassword(rule: any, value: any, callback: any) {
if (form.newPassword !== form.retryPassword) {
return callback(new Error(i18n.global.t('commons.rule.rePassword')));
}
callback();
}
const submitChangePassword = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
await updateDevicePasswd(form.user, form.newPassword)
.then(async () => {
loading.value = false;
passwordVisible.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
});
};
const handleClose = () => {
passwordVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>

View File

@ -2,19 +2,22 @@
<div> <div>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%"> <el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
<template #header> <template #header>
<DrawerHeader :header="$t('setting.timeZone')" :back="handleClose" /> <DrawerHeader :header="$t('toolbox.device.timeZone')" :back="handleClose" />
</template> </template>
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
<el-col :span="22"> <el-col :span="22">
<el-alert <el-alert
v-if="canChangeZone()" :title="$t('toolbox.device.timeZoneHelper')"
:title="$t('setting.timeZoneHelper')"
class="common-prompt" class="common-prompt"
:closable="false" :closable="false"
type="warning" type="warning"
/> />
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading"> <el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
<el-form-item :label="$t('setting.timeZone')" prop="timeZone" :rules="Rules.requiredInput"> <el-form-item
:label="$t('toolbox.device.timeZone')"
prop="timeZone"
:rules="Rules.requiredInput"
>
<el-select filterable :disabled="canChangeZone()" v-model="form.timeZone"> <el-select filterable :disabled="canChangeZone()" v-model="form.timeZone">
<el-option v-for="item in zones" :key="item" :label="item" :value="item" /> <el-option v-for="item in zones" :key="item" :label="item" :value="item" />
</el-select> </el-select>
@ -25,7 +28,7 @@
class="tagClass" class="tagClass"
@click="form.timeZone = 'Asia/Shanghai'" @click="form.timeZone = 'Asia/Shanghai'"
> >
{{ $t('setting.timeZoneCN') }} {{ $t('toolbox.device.timeZoneCN') }}
</el-button> </el-button>
<el-button <el-button
:disabled="canChangeZone()" :disabled="canChangeZone()"
@ -34,7 +37,7 @@
class="tagClass" class="tagClass"
@click="form.timeZone = 'America/Los_Angeles'" @click="form.timeZone = 'America/Los_Angeles'"
> >
{{ $t('setting.timeZoneAM') }} {{ $t('toolbox.device.timeZoneAM') }}
</el-button> </el-button>
<el-button <el-button
:disabled="canChangeZone()" :disabled="canChangeZone()"
@ -43,7 +46,7 @@
class="tagClass" class="tagClass"
@click="form.timeZone = 'America/New_York'" @click="form.timeZone = 'America/New_York'"
> >
{{ $t('setting.timeZoneNY') }} {{ $t('toolbox.device.timeZoneNY') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -64,11 +67,11 @@
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { loadTimeZone, updateSetting } from '@/api/modules/setting';
import { ElMessageBox, FormInstance } from 'element-plus'; import { ElMessageBox, FormInstance } from 'element-plus';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { loadTimeZoneOptions, updateDevice } from '@/api/modules/toolbox';
const globalStore = GlobalStore(); const globalStore = GlobalStore();
interface DialogProps { interface DialogProps {
@ -91,7 +94,7 @@ const acceptParams = (params: DialogProps): void => {
}; };
const loadTimeZones = async () => { const loadTimeZones = async () => {
const res = await loadTimeZone(); const res = await loadTimeZoneOptions();
zones.value = res.data; zones.value = res.data;
}; };
@ -103,12 +106,16 @@ const onSave = async (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
if (!valid) return; if (!valid) return;
ElMessageBox.confirm(i18n.global.t('setting.timeZoneChangeHelper'), i18n.global.t('setting.timeZone'), { ElMessageBox.confirm(
i18n.global.t('toolbox.device.timeZoneChangeHelper'),
i18n.global.t('toolbox.device.timeZone'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'), confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'), cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info', type: 'info',
}).then(async () => { },
await updateSetting({ key: 'TimeZone', value: form.timeZone }) ).then(async () => {
await updateDevice('TimeZone', form.timeZone)
.then(async () => { .then(async () => {
loading.value = false; loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));

View File

@ -11,6 +11,10 @@
import i18n from '@/lang'; import i18n from '@/lang';
const buttons = [ const buttons = [
{
label: i18n.global.t('toolbox.device.toolbox'),
path: '/toolbox/device',
},
{ {
label: i18n.global.t('menu.supervisor'), label: i18n.global.t('menu.supervisor'),
path: '/toolbox/supervisor', path: '/toolbox/supervisor',