mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-09-20 15:45:57 +08:00
parent
d254837c12
commit
49d8582658
|
@ -33,6 +33,8 @@ var (
|
||||||
sshService = service.NewISSHService()
|
sshService = service.NewISSHService()
|
||||||
firewallService = service.NewIFirewallService()
|
firewallService = service.NewIFirewallService()
|
||||||
|
|
||||||
|
fail2banService = service.NewIFail2BanService()
|
||||||
|
|
||||||
settingService = service.NewISettingService()
|
settingService = service.NewISettingService()
|
||||||
backupService = service.NewIBackupService()
|
backupService = service.NewIBackupService()
|
||||||
|
|
||||||
|
|
153
backend/app/api/v1/fail2ban.go
Normal file
153
backend/app/api/v1/fail2ban.go
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"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 Fail2Ban
|
||||||
|
// @Summary Load fail2ban base info
|
||||||
|
// @Description 获取 Fail2Ban 基础信息
|
||||||
|
// @Success 200 {object} dto.Fail2BanBaseInfo
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /toolbox/fail2ban/base [get]
|
||||||
|
func (b *BaseApi) LoadFail2BanBaseInfo(c *gin.Context) {
|
||||||
|
data, err := fail2banService.LoadBaseInfo()
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Fail2Ban
|
||||||
|
// @Summary Page fail2ban ip list
|
||||||
|
// @Description 获取 Fail2Ban ip
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.Fail2BanSearch true "request"
|
||||||
|
// @Success 200 {Array} string
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /toolbox/fail2ban/search [post]
|
||||||
|
func (b *BaseApi) SearchFail2Ban(c *gin.Context) {
|
||||||
|
var req dto.Fail2BanSearch
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := fail2banService.Search(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Fail2Ban
|
||||||
|
// @Summary Operate fail2ban
|
||||||
|
// @Description 修改 Fail2Ban 状态
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.Operate true "request"
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /toolbox/fail2ban/operate [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] Fail2Ban","formatEN":"[operation] Fail2Ban"}
|
||||||
|
func (b *BaseApi) OperateFail2Ban(c *gin.Context) {
|
||||||
|
var req dto.Operate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fail2banService.Operate(req.Operation); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Fail2Ban
|
||||||
|
// @Summary Operate sshd of fail2ban
|
||||||
|
// @Description 配置 sshd
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.Operate true "request"
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /toolbox/fail2ban/operate/sshd [post]
|
||||||
|
func (b *BaseApi) OperateSSHD(c *gin.Context) {
|
||||||
|
var req dto.Fail2BanSet
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fail2banService.OperateSSHD(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Fail2Ban
|
||||||
|
// @Summary Update fail2ban conf
|
||||||
|
// @Description 修改 Fail2Ban 配置
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.Fail2BanUpdate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /toolbox/fail2ban/update [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 Fail2Ban 配置 [key] => [value]","formatEN":"update fail2ban conf [key] => [value]"}
|
||||||
|
func (b *BaseApi) UpdateFail2BanConf(c *gin.Context) {
|
||||||
|
var req dto.Fail2BanUpdate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fail2banService.UpdateConf(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Fail2Ban
|
||||||
|
// @Summary Load fail2ban conf
|
||||||
|
// @Description 获取 fail2ban 配置文件
|
||||||
|
// @Accept json
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /toolbox/fail2ban/load/conf [get]
|
||||||
|
func (b *BaseApi) LoadFail2BanConf(c *gin.Context) {
|
||||||
|
path := "/etc/fail2ban/jail.local"
|
||||||
|
file, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, string(file))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Fail2Ban
|
||||||
|
// @Summary Update fail2ban conf by file
|
||||||
|
// @Description 通过文件修改 fail2ban 配置
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.UpdateByFile true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /toolbox/fail2ban/update/byconf [post]
|
||||||
|
func (b *BaseApi) UpdateFail2BanConfByFile(c *gin.Context) {
|
||||||
|
var req dto.UpdateByFile
|
||||||
|
if err := helper.CheckBind(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := fail2banService.UpdateConfByFile(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
|
@ -41,6 +41,10 @@ type DeleteByName struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateByFile struct {
|
||||||
|
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"`
|
||||||
|
|
27
backend/app/dto/fail2ban.go
Normal file
27
backend/app/dto/fail2ban.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package dto
|
||||||
|
|
||||||
|
type Fail2BanBaseInfo struct {
|
||||||
|
IsEnable bool `json:"isEnable"`
|
||||||
|
IsActive bool `json:"isActive"`
|
||||||
|
IsExist bool `json:"isExist"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
|
||||||
|
MaxRetry int `json:"maxRetry"`
|
||||||
|
BanTime string `json:"banTime"`
|
||||||
|
FindTime string `json:"findTime"`
|
||||||
|
BanAction string `json:"banAction"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fail2BanSearch struct {
|
||||||
|
Status string `json:"status" validate:"required,oneof=banned ignore"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fail2BanUpdate struct {
|
||||||
|
Key string `json:"key" validate:"required,oneof=port bantime findtime maxretry banaction"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fail2BanSet struct {
|
||||||
|
IPs []string `json:"ips"`
|
||||||
|
Operate string `json:"operate" validate:"required,oneof=banned ignore"`
|
||||||
|
}
|
211
backend/app/service/fail2ban.go
Normal file
211
backend/app/service/fail2ban.go
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/toolbox"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultFail2BanPath = "/etc/fail2ban/jail.local"
|
||||||
|
|
||||||
|
type Fail2BanService struct{}
|
||||||
|
|
||||||
|
type IFail2BanService interface {
|
||||||
|
LoadBaseInfo() (dto.Fail2BanBaseInfo, error)
|
||||||
|
Search(search dto.Fail2BanSearch) ([]string, error)
|
||||||
|
Operate(operation string) error
|
||||||
|
OperateSSHD(req dto.Fail2BanSet) error
|
||||||
|
UpdateConf(req dto.Fail2BanUpdate) error
|
||||||
|
UpdateConfByFile(req dto.UpdateByFile) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIFail2BanService() IFail2BanService {
|
||||||
|
return &Fail2BanService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Fail2BanService) LoadBaseInfo() (dto.Fail2BanBaseInfo, error) {
|
||||||
|
var baseInfo dto.Fail2BanBaseInfo
|
||||||
|
client, err := toolbox.NewFail2Ban()
|
||||||
|
if err != nil {
|
||||||
|
return baseInfo, err
|
||||||
|
}
|
||||||
|
baseInfo.IsEnable, baseInfo.IsActive, baseInfo.IsExist = client.Status()
|
||||||
|
if !baseInfo.IsExist {
|
||||||
|
baseInfo.Version = "-"
|
||||||
|
return baseInfo, nil
|
||||||
|
}
|
||||||
|
baseInfo.Version = client.Version()
|
||||||
|
conf, err := os.ReadFile(defaultFail2BanPath)
|
||||||
|
if err != nil {
|
||||||
|
return baseInfo, fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err)
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(conf), "\n")
|
||||||
|
|
||||||
|
block := ""
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(strings.ToLower(line), "[default]") {
|
||||||
|
block = "default"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "[sshd]") {
|
||||||
|
block = "sshd"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "[") {
|
||||||
|
block = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if block != "default" && block != "sshd" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
loadFailValue(line, &baseInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Fail2BanService) Search(req dto.Fail2BanSearch) ([]string, error) {
|
||||||
|
var list []string
|
||||||
|
client, err := toolbox.NewFail2Ban()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.Status == "banned" {
|
||||||
|
list, err = client.ListBanned()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
list, err = client.ListIgnore()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Fail2BanService) Operate(operation string) error {
|
||||||
|
client, err := toolbox.NewFail2Ban()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return client.Operate(operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Fail2BanService) UpdateConf(req dto.Fail2BanUpdate) error {
|
||||||
|
conf, err := os.ReadFile(defaultFail2BanPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err)
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(conf), "\n")
|
||||||
|
|
||||||
|
isStart, isEnd, hasKey := false, false, false
|
||||||
|
newFile := ""
|
||||||
|
for index, line := range lines {
|
||||||
|
if !isStart && strings.HasPrefix(line, "[sshd]") {
|
||||||
|
isStart = true
|
||||||
|
newFile += fmt.Sprintf("%s\n", line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !isStart || isEnd {
|
||||||
|
newFile += fmt.Sprintf("%s\n", line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, req.Key) {
|
||||||
|
hasKey = true
|
||||||
|
newFile += fmt.Sprintf("%s = %s\n", req.Key, req.Value)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "[") || index != len(lines)-1 {
|
||||||
|
isEnd = true
|
||||||
|
if !hasKey {
|
||||||
|
newFile += fmt.Sprintf("%s = %s\n", req.Key, req.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newFile += line
|
||||||
|
if index != len(lines)-1 {
|
||||||
|
newFile += "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file, err := os.OpenFile(defaultFail2BanPath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
write := bufio.NewWriter(file)
|
||||||
|
_, _ = write.WriteString(newFile)
|
||||||
|
write.Flush()
|
||||||
|
|
||||||
|
client, err := toolbox.NewFail2Ban()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := client.Operate("reload"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Fail2BanService) UpdateConfByFile(req dto.UpdateByFile) error {
|
||||||
|
file, err := os.OpenFile(defaultFail2BanPath, 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()
|
||||||
|
|
||||||
|
client, err := toolbox.NewFail2Ban()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := client.Operate("reload"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Fail2BanService) OperateSSHD(req dto.Fail2BanSet) error {
|
||||||
|
if req.Operate == "ignore" {
|
||||||
|
if err := u.UpdateConf(dto.Fail2BanUpdate{Key: "ignoreip", Value: strings.Join(req.IPs, ",")}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
client, err := toolbox.NewFail2Ban()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := client.ReBanIPs(req.IPs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFailValue(line string, baseInfo *dto.Fail2BanBaseInfo) {
|
||||||
|
if strings.HasPrefix(line, "maxretry") {
|
||||||
|
itemValue := strings.ReplaceAll(line, "maxretry", "")
|
||||||
|
itemValue = strings.ReplaceAll(itemValue, "=", "")
|
||||||
|
baseInfo.MaxRetry, _ = strconv.Atoi(strings.TrimSpace(itemValue))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "findtime") {
|
||||||
|
itemValue := strings.ReplaceAll(line, "findtime", "")
|
||||||
|
itemValue = strings.ReplaceAll(itemValue, "=", "")
|
||||||
|
baseInfo.FindTime = strings.TrimSpace(itemValue)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "bantime") {
|
||||||
|
itemValue := strings.ReplaceAll(line, "bantime", "")
|
||||||
|
itemValue = strings.ReplaceAll(itemValue, "=", "")
|
||||||
|
baseInfo.BanTime = strings.TrimSpace(itemValue)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "banaction") {
|
||||||
|
itemValue := strings.ReplaceAll(line, "banaction", "")
|
||||||
|
itemValue = strings.ReplaceAll(itemValue, "=", "")
|
||||||
|
baseInfo.BanAction = strings.TrimSpace(itemValue)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-contrib/gzip"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/gzip"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||||
|
@ -94,6 +95,7 @@ func Routers() *gin.Engine {
|
||||||
systemRouter.InitNginxRouter(PrivateGroup)
|
systemRouter.InitNginxRouter(PrivateGroup)
|
||||||
systemRouter.InitRuntimeRouter(PrivateGroup)
|
systemRouter.InitRuntimeRouter(PrivateGroup)
|
||||||
systemRouter.InitProcessRouter(PrivateGroup)
|
systemRouter.InitProcessRouter(PrivateGroup)
|
||||||
|
systemRouter.InitToolboxRouter(PrivateGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Router
|
return Router
|
||||||
|
|
|
@ -8,6 +8,7 @@ type RouterGroup struct {
|
||||||
MonitorRouter
|
MonitorRouter
|
||||||
LogRouter
|
LogRouter
|
||||||
FileRouter
|
FileRouter
|
||||||
|
ToolboxRouter
|
||||||
TerminalRouter
|
TerminalRouter
|
||||||
CronjobRouter
|
CronjobRouter
|
||||||
SettingRouter
|
SettingRouter
|
||||||
|
|
27
backend/router/ro_toolbox.go
Normal file
27
backend/router/ro_toolbox.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ToolboxRouter struct{}
|
||||||
|
|
||||||
|
func (s *ToolboxRouter) InitToolboxRouter(Router *gin.RouterGroup) {
|
||||||
|
toolboxRouter := Router.Group("toolbox").
|
||||||
|
Use(middleware.JwtAuth()).
|
||||||
|
Use(middleware.SessionAuth()).
|
||||||
|
Use(middleware.PasswordExpired())
|
||||||
|
baseApi := v1.ApiGroupApp.BaseApi
|
||||||
|
{
|
||||||
|
toolboxRouter.GET("/fail2ban/base", baseApi.LoadFail2BanBaseInfo)
|
||||||
|
toolboxRouter.GET("/fail2ban/load/conf", baseApi.LoadFail2BanConf)
|
||||||
|
toolboxRouter.POST("/fail2ban/search", baseApi.SearchFail2Ban)
|
||||||
|
toolboxRouter.POST("/fail2ban/operate", baseApi.OperateFail2Ban)
|
||||||
|
toolboxRouter.POST("/fail2ban/operate/sshd", baseApi.OperateSSHD)
|
||||||
|
toolboxRouter.POST("/fail2ban/update", baseApi.UpdateFail2BanConf)
|
||||||
|
toolboxRouter.POST("/fail2ban/update/byconf", baseApi.UpdateFail2BanConfByFile)
|
||||||
|
}
|
||||||
|
}
|
155
backend/utils/toolbox/fail2ban.go
Normal file
155
backend/utils/toolbox/fail2ban.go
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
package toolbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Fail2Ban struct{}
|
||||||
|
|
||||||
|
const defaultPath = "/etc/fail2ban/jail.local"
|
||||||
|
|
||||||
|
type FirewallClient interface {
|
||||||
|
Status() (bool, bool, bool, error)
|
||||||
|
Version() (string, error)
|
||||||
|
Operate(operate string) error
|
||||||
|
OperateSSHD(operate, ip string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFail2Ban() (*Fail2Ban, error) {
|
||||||
|
isExist, _ := systemctl.IsExist("fail2ban.service")
|
||||||
|
if isExist {
|
||||||
|
if _, err := os.Stat(defaultPath); err != nil {
|
||||||
|
if err := initLocalFile(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stdout, err := cmd.Exec("fail2ban-client reload")
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("reload fail2ban failed, err: %s", stdout)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Fail2Ban{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fail2Ban) Status() (bool, bool, bool) {
|
||||||
|
isEnable, _ := systemctl.IsEnable("fail2ban.service")
|
||||||
|
isActive, _ := systemctl.IsActive("fail2ban.service")
|
||||||
|
isExist, _ := systemctl.IsExist("fail2ban.service")
|
||||||
|
|
||||||
|
return isEnable, isActive, isExist
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fail2Ban) Version() string {
|
||||||
|
stdout, err := cmd.Exec("fail2ban-client version")
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("load the fail2ban version failed, err: %s", stdout)
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return strings.ReplaceAll(stdout, "\n", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fail2Ban) Operate(operate string) error {
|
||||||
|
switch operate {
|
||||||
|
case "start", "restart", "stop", "enable", "disable":
|
||||||
|
stdout, err := cmd.Execf("systemctl %s fail2ban.service", operate)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s the fail2ban.service failed, err: %s", operate, stdout)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case "reload":
|
||||||
|
stdout, err := cmd.Exec("fail2ban-client reload")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fail2ban-client reload, err: %s", stdout)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("not support such operation: %v", operate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fail2Ban) ReBanIPs(ips []string) error {
|
||||||
|
ipItems, _ := f.ListBanned()
|
||||||
|
stdout, err := cmd.Execf("fail2ban-client unban --all")
|
||||||
|
if err != nil {
|
||||||
|
stdout1, err := cmd.Execf("fail2ban-client set sshd banip %s", strings.Join(ipItems, " "))
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("rebanip after fail2ban-client unban --all failed, err: %s", stdout1)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("fail2ban-client unban --all failed, err: %s", stdout)
|
||||||
|
}
|
||||||
|
stdout1, err := cmd.Execf("fail2ban-client set sshd banip %s", strings.Join(ips, " "))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("handle `fail2ban-client set sshd banip %s` failed, err: %s", strings.Join(ips, " "), stdout1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fail2Ban) ListBanned() ([]string, error) {
|
||||||
|
var lists []string
|
||||||
|
stdout, err := cmd.Exec("fail2ban-client get sshd banned")
|
||||||
|
if err != nil {
|
||||||
|
return lists, err
|
||||||
|
}
|
||||||
|
stdout = strings.ReplaceAll(stdout, "\n", "")
|
||||||
|
stdout = strings.ReplaceAll(stdout, "'", "\"")
|
||||||
|
if err := json.Unmarshal([]byte(stdout), &lists); err != nil {
|
||||||
|
return lists, fmt.Errorf("handle json unmarshal (%s) failed, err: %v", stdout, err)
|
||||||
|
}
|
||||||
|
return lists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fail2Ban) ListIgnore() ([]string, error) {
|
||||||
|
var lists []string
|
||||||
|
stdout, err := cmd.Exec("fail2ban-client get sshd ignoreip")
|
||||||
|
if err != nil {
|
||||||
|
return lists, err
|
||||||
|
}
|
||||||
|
stdout = strings.ReplaceAll(stdout, "|", "")
|
||||||
|
stdout = strings.ReplaceAll(stdout, "`", "")
|
||||||
|
stdout = strings.ReplaceAll(stdout, "\n", "")
|
||||||
|
addrs := strings.Split(stdout, "-")
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if !strings.HasPrefix(addr, " ") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lists = append(lists, strings.ReplaceAll(addr, " ", ""))
|
||||||
|
}
|
||||||
|
return lists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initLocalFile() error {
|
||||||
|
if _, err := os.Create(defaultPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
initFile := `#DEFAULT-START
|
||||||
|
[DEFAULT]
|
||||||
|
bantime = 600
|
||||||
|
findtime = 300
|
||||||
|
maxretry = 5
|
||||||
|
banaction = firewallcmd-ipset
|
||||||
|
action = %(action_mwl)s
|
||||||
|
#DEFAULT-END
|
||||||
|
|
||||||
|
[sshd]
|
||||||
|
ignoreip = 127.0.0.1/8
|
||||||
|
enabled = true
|
||||||
|
filter = sshd
|
||||||
|
port = 22
|
||||||
|
maxretry = 5
|
||||||
|
findtime = 300
|
||||||
|
bantime = 600
|
||||||
|
action = %(action_mwl)s
|
||||||
|
logpath = /var/log/secure`
|
||||||
|
if err := os.WriteFile(defaultPath, []byte(initFile), 0640); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
61
backend/utils/toolbox/fail2ban_test.go
Normal file
61
backend/utils/toolbox/fail2ban_test.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10086,6 +10086,229 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/toolbox/fail2ban/base": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取 Fail2Ban 基础信息",
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Load fail2ban base info",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.Fail2BanBaseInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/load/conf": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取 fail2ban 配置文件",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Load fail2ban conf",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/operate": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "修改 Fail2Ban 状态",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Operate fail2ban",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.Operate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {},
|
||||||
|
"x-panel-log": {
|
||||||
|
"BeforeFunctions": [],
|
||||||
|
"bodyKeys": [
|
||||||
|
"operation"
|
||||||
|
],
|
||||||
|
"formatEN": "[operation] Fail2Ban",
|
||||||
|
"formatZH": "[operation] Fail2Ban",
|
||||||
|
"paramKeys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/operate/sshd": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "配置 sshd",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Operate sshd of fail2ban",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.Operate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/search": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取 Fail2Ban ip",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Page fail2ban ip list",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.Fail2BanSearch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "Array"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/update": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "修改 Fail2Ban 配置",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Update fail2ban conf",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.Fail2BanUpdate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-panel-log": {
|
||||||
|
"BeforeFunctions": [],
|
||||||
|
"bodyKeys": [
|
||||||
|
"key",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"formatEN": "update fail2ban conf [key] =\u003e [value]",
|
||||||
|
"formatZH": "修改 Fail2Ban 配置 [key] =\u003e [value]",
|
||||||
|
"paramKeys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/update/byconf": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "通过文件修改 fail2ban 配置",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Update fail2ban conf by file",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UpdateByFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/websites": {
|
"/websites": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -13999,6 +14222,71 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.Fail2BanBaseInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"banAction": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"banTime": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"findTime": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isActive": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isEnable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isExist": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"maxRetry": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.Fail2BanSearch": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"banned",
|
||||||
|
"ignore"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.Fail2BanUpdate": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"key"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"port",
|
||||||
|
"bantime",
|
||||||
|
"findtime",
|
||||||
|
"maxretry",
|
||||||
|
"banaction"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.FirewallBaseInfo": {
|
"dto.FirewallBaseInfo": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -15961,6 +16249,14 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.UpdateByFile": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.UpdateDescription": {
|
"dto.UpdateDescription": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -10079,6 +10079,229 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/toolbox/fail2ban/base": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取 Fail2Ban 基础信息",
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Load fail2ban base info",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.Fail2BanBaseInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/load/conf": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取 fail2ban 配置文件",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Load fail2ban conf",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/operate": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "修改 Fail2Ban 状态",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Operate fail2ban",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.Operate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {},
|
||||||
|
"x-panel-log": {
|
||||||
|
"BeforeFunctions": [],
|
||||||
|
"bodyKeys": [
|
||||||
|
"operation"
|
||||||
|
],
|
||||||
|
"formatEN": "[operation] Fail2Ban",
|
||||||
|
"formatZH": "[operation] Fail2Ban",
|
||||||
|
"paramKeys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/operate/sshd": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "配置 sshd",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Operate sshd of fail2ban",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.Operate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/search": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取 Fail2Ban ip",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Page fail2ban ip list",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.Fail2BanSearch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "Array"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/update": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "修改 Fail2Ban 配置",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Update fail2ban conf",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.Fail2BanUpdate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-panel-log": {
|
||||||
|
"BeforeFunctions": [],
|
||||||
|
"bodyKeys": [
|
||||||
|
"key",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"formatEN": "update fail2ban conf [key] =\u003e [value]",
|
||||||
|
"formatZH": "修改 Fail2Ban 配置 [key] =\u003e [value]",
|
||||||
|
"paramKeys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/toolbox/fail2ban/update/byconf": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "通过文件修改 fail2ban 配置",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Fail2Ban"
|
||||||
|
],
|
||||||
|
"summary": "Update fail2ban conf by file",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UpdateByFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/websites": {
|
"/websites": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -13992,6 +14215,71 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.Fail2BanBaseInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"banAction": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"banTime": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"findTime": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isActive": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isEnable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isExist": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"maxRetry": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.Fail2BanSearch": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"banned",
|
||||||
|
"ignore"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.Fail2BanUpdate": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"key"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"port",
|
||||||
|
"bantime",
|
||||||
|
"findtime",
|
||||||
|
"maxretry",
|
||||||
|
"banaction"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.FirewallBaseInfo": {
|
"dto.FirewallBaseInfo": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -15954,6 +16242,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.UpdateByFile": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.UpdateDescription": {
|
"dto.UpdateDescription": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -973,6 +973,50 @@ definitions:
|
||||||
- fileName
|
- fileName
|
||||||
- source
|
- source
|
||||||
type: object
|
type: object
|
||||||
|
dto.Fail2BanBaseInfo:
|
||||||
|
properties:
|
||||||
|
banAction:
|
||||||
|
type: string
|
||||||
|
banTime:
|
||||||
|
type: string
|
||||||
|
findTime:
|
||||||
|
type: string
|
||||||
|
isActive:
|
||||||
|
type: boolean
|
||||||
|
isEnable:
|
||||||
|
type: boolean
|
||||||
|
isExist:
|
||||||
|
type: boolean
|
||||||
|
maxRetry:
|
||||||
|
type: integer
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
dto.Fail2BanSearch:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
enum:
|
||||||
|
- banned
|
||||||
|
- ignore
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
dto.Fail2BanUpdate:
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
enum:
|
||||||
|
- port
|
||||||
|
- bantime
|
||||||
|
- findtime
|
||||||
|
- maxretry
|
||||||
|
- banaction
|
||||||
|
type: string
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
dto.FirewallBaseInfo:
|
dto.FirewallBaseInfo:
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
|
@ -2296,6 +2340,11 @@ definitions:
|
||||||
label:
|
label:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
dto.UpdateByFile:
|
||||||
|
properties:
|
||||||
|
file:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
dto.UpdateDescription:
|
dto.UpdateDescription:
|
||||||
properties:
|
properties:
|
||||||
description:
|
description:
|
||||||
|
@ -10781,6 +10830,145 @@ paths:
|
||||||
formatEN: upgrade service => [version]
|
formatEN: upgrade service => [version]
|
||||||
formatZH: 更新系统 => [version]
|
formatZH: 更新系统 => [version]
|
||||||
paramKeys: []
|
paramKeys: []
|
||||||
|
/toolbox/fail2ban/base:
|
||||||
|
get:
|
||||||
|
description: 获取 Fail2Ban 基础信息
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.Fail2BanBaseInfo'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Load fail2ban base info
|
||||||
|
tags:
|
||||||
|
- Fail2Ban
|
||||||
|
/toolbox/fail2ban/load/conf:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 获取 fail2ban 配置文件
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Load fail2ban conf
|
||||||
|
tags:
|
||||||
|
- Fail2Ban
|
||||||
|
/toolbox/fail2ban/operate:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 修改 Fail2Ban 状态
|
||||||
|
parameters:
|
||||||
|
- description: request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.Operate'
|
||||||
|
responses: {}
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Operate fail2ban
|
||||||
|
tags:
|
||||||
|
- Fail2Ban
|
||||||
|
x-panel-log:
|
||||||
|
BeforeFunctions: []
|
||||||
|
bodyKeys:
|
||||||
|
- operation
|
||||||
|
formatEN: '[operation] Fail2Ban'
|
||||||
|
formatZH: '[operation] Fail2Ban'
|
||||||
|
paramKeys: []
|
||||||
|
/toolbox/fail2ban/operate/sshd:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 配置 sshd
|
||||||
|
parameters:
|
||||||
|
- description: request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.Operate'
|
||||||
|
responses: {}
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Operate sshd of fail2ban
|
||||||
|
tags:
|
||||||
|
- Fail2Ban
|
||||||
|
/toolbox/fail2ban/search:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 获取 Fail2Ban ip
|
||||||
|
parameters:
|
||||||
|
- description: request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.Fail2BanSearch'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
type: Array
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Page fail2ban ip list
|
||||||
|
tags:
|
||||||
|
- Fail2Ban
|
||||||
|
/toolbox/fail2ban/update:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 修改 Fail2Ban 配置
|
||||||
|
parameters:
|
||||||
|
- description: request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.Fail2BanUpdate'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Update fail2ban conf
|
||||||
|
tags:
|
||||||
|
- Fail2Ban
|
||||||
|
x-panel-log:
|
||||||
|
BeforeFunctions: []
|
||||||
|
bodyKeys:
|
||||||
|
- key
|
||||||
|
- value
|
||||||
|
formatEN: update fail2ban conf [key] => [value]
|
||||||
|
formatZH: 修改 Fail2Ban 配置 [key] => [value]
|
||||||
|
paramKeys: []
|
||||||
|
/toolbox/fail2ban/update/byconf:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 通过文件修改 fail2ban 配置
|
||||||
|
parameters:
|
||||||
|
- description: request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UpdateByFile'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Update fail2ban conf by file
|
||||||
|
tags:
|
||||||
|
- Fail2Ban
|
||||||
/websites:
|
/websites:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
@ -34,3 +34,6 @@ export interface DescriptionUpdate {
|
||||||
id: number;
|
id: number;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
export interface UpdateByFile {
|
||||||
|
file: string;
|
||||||
|
}
|
||||||
|
|
29
frontend/src/api/interface/toolbox.ts
Normal file
29
frontend/src/api/interface/toolbox.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
export namespace Toolbox {
|
||||||
|
export interface Fail2banBaseInfo {
|
||||||
|
isEnable: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
isExist: boolean;
|
||||||
|
version: string;
|
||||||
|
|
||||||
|
port: number;
|
||||||
|
maxRetry: number;
|
||||||
|
banTime: string;
|
||||||
|
findTime: string;
|
||||||
|
banAction: string;
|
||||||
|
logPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Fail2banSearch {
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Fail2banUpdate {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Fail2banSet {
|
||||||
|
ips: Array<string>;
|
||||||
|
operate: string;
|
||||||
|
}
|
||||||
|
}
|
31
frontend/src/api/modules/toolbox.ts
Normal file
31
frontend/src/api/modules/toolbox.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import http from '@/api';
|
||||||
|
import { UpdateByFile } from '../interface';
|
||||||
|
import { Toolbox } from '../interface/toolbox';
|
||||||
|
|
||||||
|
// fail2ban
|
||||||
|
export const getFail2banBase = () => {
|
||||||
|
return http.get<Toolbox.Fail2banBaseInfo>(`/toolbox/fail2ban/base`);
|
||||||
|
};
|
||||||
|
export const getFail2banConf = () => {
|
||||||
|
return http.get<string>(`/toolbox/fail2ban/load/conf`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const searchFail2ban = (param: Toolbox.Fail2banSearch) => {
|
||||||
|
return http.post<Array<string>>(`/toolbox/fail2ban/search`, param);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const operateFail2ban = (operate: string) => {
|
||||||
|
return http.post(`/toolbox/fail2ban/operate`, { operation: operate });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const operatorFail2banSSHD = (param: Toolbox.Fail2banSet) => {
|
||||||
|
return http.post(`/toolbox/fail2ban/operate/sshd`, param);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateFail2ban = (param: Toolbox.Fail2banUpdate) => {
|
||||||
|
return http.post(`/toolbox/fail2ban/update`, param);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateFail2banByFile = (param: UpdateByFile) => {
|
||||||
|
return http.post(`/toolbox/fail2ban/update/byconf`, param);
|
||||||
|
};
|
|
@ -96,7 +96,7 @@ const handleClose = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const toDoc = () => {
|
const toDoc = () => {
|
||||||
window.open('https://1panel.cn/docs/', '_blank');
|
window.open('https://1panel.cn/docs/', '_blank', 'noopener,noreferrer');
|
||||||
};
|
};
|
||||||
|
|
||||||
const toForum = () => {
|
const toForum = () => {
|
||||||
|
|
|
@ -866,6 +866,25 @@ const message = {
|
||||||
keyPassword: 'Private key password',
|
keyPassword: 'Private key password',
|
||||||
emptyTerminal: 'No terminal is currently connected',
|
emptyTerminal: 'No terminal is currently connected',
|
||||||
},
|
},
|
||||||
|
toolbox: {
|
||||||
|
fail2ban: {
|
||||||
|
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!',
|
||||||
|
operation: 'Perform [{0}] operation on Fail2Ban service, continue?',
|
||||||
|
fail2banChange: 'Fail2Ban Configuration Modification',
|
||||||
|
ignoreHelper: 'The IP list in the whitelist will be ignored for blocking, continue?',
|
||||||
|
bannedHelper: 'The IP list in the blacklist will be blocked by the server, continue?',
|
||||||
|
maxRetry: 'Maximum Retry Attempts',
|
||||||
|
banTime: 'Ban Time',
|
||||||
|
banTimeHelper: 'Default ban time is 10 minutes, -1 indicates permanent ban',
|
||||||
|
findTime: 'Discovery Period',
|
||||||
|
banAction: 'Ban Action',
|
||||||
|
banActionOption: 'Ban specified IP addresses using {0}',
|
||||||
|
allPorts: ' (All Ports)',
|
||||||
|
ignoreIP: 'IP Whitelist',
|
||||||
|
bannedIP: 'IP Blacklist',
|
||||||
|
},
|
||||||
|
},
|
||||||
logs: {
|
logs: {
|
||||||
panelLog: 'Panel logs',
|
panelLog: 'Panel logs',
|
||||||
operation: 'Operation logs',
|
operation: 'Operation logs',
|
||||||
|
@ -887,6 +906,7 @@ const message = {
|
||||||
files: 'File Manage',
|
files: 'File Manage',
|
||||||
runtimes: 'Runtime',
|
runtimes: 'Runtime',
|
||||||
process: 'Process',
|
process: 'Process',
|
||||||
|
toolbox: 'Toolbox',
|
||||||
logs: 'Panel Logs',
|
logs: 'Panel Logs',
|
||||||
settings: 'Panel Setting',
|
settings: 'Panel Setting',
|
||||||
cronjobs: 'Cronjob',
|
cronjobs: 'Cronjob',
|
||||||
|
|
|
@ -830,6 +830,25 @@ const message = {
|
||||||
keyPassword: '私鑰密碼',
|
keyPassword: '私鑰密碼',
|
||||||
emptyTerminal: '暫無終端連接',
|
emptyTerminal: '暫無終端連接',
|
||||||
},
|
},
|
||||||
|
toolbox: {
|
||||||
|
fail2ban: {
|
||||||
|
noFail2ban: '未檢測到 Fail2Ban 服務,請參考官方文檔進行安裝',
|
||||||
|
unActive: '當前未開啟 Fail2Ban 服務,請先開啟!',
|
||||||
|
operation: '對 Fail2Ban 服務進行 [{0}] 操作,是否繼續?',
|
||||||
|
fail2banChange: 'Fail2Ban 配置修改',
|
||||||
|
ignoreHelper: '白名單中的 IP 列表將被忽略屏蔽,是否繼續?',
|
||||||
|
bannedHelper: '黑名單中的 IP 列表將被伺服器屏蔽,是否繼續?',
|
||||||
|
maxRetry: '最大重試次數',
|
||||||
|
banTime: '禁用時間',
|
||||||
|
banTimeHelper: '默認禁用時間為 10 分鐘,禁用時間為 -1 則表示永久禁用',
|
||||||
|
findTime: '發現周期',
|
||||||
|
banAction: '禁用方式',
|
||||||
|
banActionOption: '通過 {0} 來禁用指定的 IP 地址',
|
||||||
|
allPorts: ' (所有端口)',
|
||||||
|
ignoreIP: 'IP 白名單',
|
||||||
|
bannedIP: 'IP 黑名單',
|
||||||
|
},
|
||||||
|
},
|
||||||
logs: {
|
logs: {
|
||||||
panelLog: '面板日誌',
|
panelLog: '面板日誌',
|
||||||
operation: '操作日誌',
|
operation: '操作日誌',
|
||||||
|
@ -851,6 +870,7 @@ const message = {
|
||||||
files: '文件管理',
|
files: '文件管理',
|
||||||
runtimes: '運行環境',
|
runtimes: '運行環境',
|
||||||
process: '進程管理',
|
process: '進程管理',
|
||||||
|
toolbox: '工具箱',
|
||||||
logs: '日誌審計',
|
logs: '日誌審計',
|
||||||
settings: '面板設置',
|
settings: '面板設置',
|
||||||
cronjobs: '計劃任務',
|
cronjobs: '計劃任務',
|
||||||
|
|
|
@ -831,6 +831,25 @@ const message = {
|
||||||
keyPassword: '私钥密码',
|
keyPassword: '私钥密码',
|
||||||
emptyTerminal: '暂无终端连接',
|
emptyTerminal: '暂无终端连接',
|
||||||
},
|
},
|
||||||
|
toolbox: {
|
||||||
|
fail2ban: {
|
||||||
|
noFail2ban: '未检测到 Fail2Ban 服务,请参考官方文档进行安装',
|
||||||
|
unActive: '当前未开启 Fail2Ban 服务,请先开启!',
|
||||||
|
operation: '对 Fail2Ban 服务进行 [{0}] 操作,是否继续?',
|
||||||
|
fail2banChange: 'Fail2Ban 配置修改',
|
||||||
|
ignoreHelper: '白名单中的 IP 列表将被忽略屏蔽,是否继续?',
|
||||||
|
bannedHelper: '黑名单中的 IP 列表将被服务器屏蔽,是否继续?',
|
||||||
|
maxRetry: '最大重试次数',
|
||||||
|
banTime: '禁用时间',
|
||||||
|
banTimeHelper: '默认禁用时间为 10 分钟,禁用时间为 -1 则表示永久禁用',
|
||||||
|
findTime: '发现周期',
|
||||||
|
banAction: '禁用方式',
|
||||||
|
banActionOption: '通过 {0} 来禁用指定的 IP 地址',
|
||||||
|
allPorts: ' (所有端口)',
|
||||||
|
ignoreIP: 'IP 白名单',
|
||||||
|
bannedIP: 'IP 黑名单',
|
||||||
|
},
|
||||||
|
},
|
||||||
logs: {
|
logs: {
|
||||||
panelLog: '面板日志',
|
panelLog: '面板日志',
|
||||||
operation: '操作日志',
|
operation: '操作日志',
|
||||||
|
@ -852,6 +871,7 @@ const message = {
|
||||||
files: '文件管理',
|
files: '文件管理',
|
||||||
runtimes: '运行环境',
|
runtimes: '运行环境',
|
||||||
process: '进程管理',
|
process: '进程管理',
|
||||||
|
toolbox: '工具箱',
|
||||||
logs: '日志审计',
|
logs: '日志审计',
|
||||||
settings: '面板设置',
|
settings: '面板设置',
|
||||||
cronjobs: '计划任务',
|
cronjobs: '计划任务',
|
||||||
|
|
|
@ -91,17 +91,6 @@ const hostRouter = {
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/hosts/tool/supersivor',
|
|
||||||
name: 'Supervisor',
|
|
||||||
component: () => import('@/views/host/tool/supervisor/index.vue'),
|
|
||||||
meta: {
|
|
||||||
title: 'menu.supervisor',
|
|
||||||
activeMenu: '/hosts/tool/supersivor',
|
|
||||||
keepAlive: true,
|
|
||||||
requiresAuth: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/hosts/ssh/ssh',
|
path: '/hosts/ssh/ssh',
|
||||||
name: 'SSH',
|
name: 'SSH',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Layout } from '@/routers/constant';
|
import { Layout } from '@/routers/constant';
|
||||||
|
|
||||||
const logsRouter = {
|
const logsRouter = {
|
||||||
sort: 7,
|
sort: 8,
|
||||||
path: '/logs',
|
path: '/logs',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/logs/operation',
|
redirect: '/logs/operation',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Layout } from '@/routers/constant';
|
import { Layout } from '@/routers/constant';
|
||||||
|
|
||||||
const settingRouter = {
|
const settingRouter = {
|
||||||
sort: 8,
|
sort: 9,
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/settings/panel',
|
redirect: '/settings/panel',
|
||||||
|
|
45
frontend/src/routers/modules/toolbox.ts
Normal file
45
frontend/src/routers/modules/toolbox.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { Layout } from '@/routers/constant';
|
||||||
|
|
||||||
|
const toolboxRouter = {
|
||||||
|
sort: 7,
|
||||||
|
path: '/toolbox',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/toolbox/supervisor',
|
||||||
|
meta: {
|
||||||
|
title: 'menu.toolbox',
|
||||||
|
icon: 'p-toolbox',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/toolbox',
|
||||||
|
name: 'Toolbox',
|
||||||
|
redirect: '/toolbox/supervisor',
|
||||||
|
component: () => import('@/views/toolbox/index.vue'),
|
||||||
|
meta: {},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'supervisor',
|
||||||
|
name: 'Supervisor',
|
||||||
|
component: () => import('@/views/toolbox/supervisor/index.vue'),
|
||||||
|
hidden: true,
|
||||||
|
meta: {
|
||||||
|
activeMenu: '/toolbox',
|
||||||
|
requiresAuth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'fail2Ban',
|
||||||
|
name: 'Fail2Ban',
|
||||||
|
component: () => import('@/views/toolbox/fail2ban/index.vue'),
|
||||||
|
hidden: true,
|
||||||
|
meta: {
|
||||||
|
activeMenu: '/toolbox',
|
||||||
|
requiresAuth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default toolboxRouter;
|
|
@ -285,6 +285,43 @@ export function getProvider(provider: string): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function splitTime(item: string): any {
|
||||||
|
if (item.indexOf('s') !== -1) {
|
||||||
|
return { time: Number(item.replaceAll('s', '')), unit: 's' };
|
||||||
|
}
|
||||||
|
if (item.indexOf('m') !== -1) {
|
||||||
|
return { time: Number(item.replaceAll('m', '')), unit: 'm' };
|
||||||
|
}
|
||||||
|
if (item.indexOf('h') !== -1) {
|
||||||
|
return { time: Number(item.replaceAll('h', '')), unit: 'h' };
|
||||||
|
}
|
||||||
|
if (item.indexOf('d') !== -1) {
|
||||||
|
return { time: Number(item.replaceAll('d', '')), unit: 'd' };
|
||||||
|
}
|
||||||
|
if (item.indexOf('y') !== -1) {
|
||||||
|
return { time: Number(item.replaceAll('y', '')), unit: 'y' };
|
||||||
|
}
|
||||||
|
return { time: Number(item), unit: 's' };
|
||||||
|
}
|
||||||
|
export function transTimeUnit(val: string): any {
|
||||||
|
if (val.indexOf('s') !== -1) {
|
||||||
|
return val.replaceAll('s', i18n.global.t('commons.units.second'));
|
||||||
|
}
|
||||||
|
if (val.indexOf('m') !== -1) {
|
||||||
|
return val.replaceAll('m', i18n.global.t('commons.units.minute'));
|
||||||
|
}
|
||||||
|
if (val.indexOf('h') !== -1) {
|
||||||
|
return val.replaceAll('h', i18n.global.t('commons.units.hour'));
|
||||||
|
}
|
||||||
|
if (val.indexOf('d') !== -1) {
|
||||||
|
return val.replaceAll('d', i18n.global.t('commons.units.day'));
|
||||||
|
}
|
||||||
|
if (val.indexOf('y') !== -1) {
|
||||||
|
return val.replaceAll('y', i18n.global.t('commons.units.year'));
|
||||||
|
}
|
||||||
|
return val + i18n.global.t('commons.units.second');
|
||||||
|
}
|
||||||
|
|
||||||
export function getAge(d1: string): string {
|
export function getAge(d1: string): string {
|
||||||
const dateBegin = new Date(d1);
|
const dateBegin = new Date(d1);
|
||||||
const dateEnd = new Date();
|
const dateEnd = new Date();
|
||||||
|
|
|
@ -367,7 +367,7 @@ const save = async (key: string, value: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const toDoc = () => {
|
const toDoc = () => {
|
||||||
window.open('https://1panel.cn/docs/user_manual/containers/setting/', '_blank');
|
window.open('https://1panel.cn/docs/user_manual/containers/setting/', '_blank', 'noopener,noreferrer');
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOperator = async (operation: string) => {
|
const onOperator = async (operation: string) => {
|
||||||
|
|
|
@ -208,7 +208,7 @@ const onOpenDialog = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
const toDoc = () => {
|
const toDoc = () => {
|
||||||
window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank');
|
window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank', 'noopener,noreferrer');
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChange = async (info: any) => {
|
const onChange = async (info: any) => {
|
||||||
|
|
|
@ -268,7 +268,7 @@ const quickJump = () => {
|
||||||
router.push({ name: 'AppInstalled' });
|
router.push({ name: 'AppInstalled' });
|
||||||
};
|
};
|
||||||
const toDoc = () => {
|
const toDoc = () => {
|
||||||
window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank');
|
window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank', 'noopener,noreferrer');
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChangeStatus = async (row: Host.RuleInfo, status: string) => {
|
const onChangeStatus = async (row: Host.RuleInfo, status: string) => {
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
<el-divider direction="vertical" />
|
<el-divider direction="vertical" />
|
||||||
<el-button type="primary" link>{{ $t('firewall.noPing') }}</el-button>
|
<el-button type="primary" link>{{ $t('firewall.noPing') }}</el-button>
|
||||||
<el-switch
|
<el-switch
|
||||||
style="margin-left: 10px"
|
size="small"
|
||||||
|
class="ml-2"
|
||||||
inactive-value="Disable"
|
inactive-value="Disable"
|
||||||
active-value="Enable"
|
active-value="Enable"
|
||||||
@change="onPingOperate"
|
@change="onPingOperate"
|
||||||
|
|
|
@ -38,7 +38,8 @@
|
||||||
{{ $t('ssh.autoStart') }}
|
{{ $t('ssh.autoStart') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-switch
|
<el-switch
|
||||||
style="margin-left: 10px"
|
size="small"
|
||||||
|
class="ml-2"
|
||||||
inactive-value="disable"
|
inactive-value="disable"
|
||||||
active-value="enable"
|
active-value="enable"
|
||||||
@change="onOperate(autoStart)"
|
@change="onOperate(autoStart)"
|
||||||
|
|
|
@ -47,16 +47,16 @@ const search = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const toDoc = () => {
|
const toDoc = () => {
|
||||||
window.open('https://1panel.cn/docs/', '_blank');
|
window.open('https://1panel.cn/docs/', '_blank', 'noopener,noreferrer');
|
||||||
};
|
};
|
||||||
const toGithub = () => {
|
const toGithub = () => {
|
||||||
window.open('https://github.com/1Panel-dev/1Panel', '_blank');
|
window.open('https://github.com/1Panel-dev/1Panel', '_blank', 'noopener,noreferrer');
|
||||||
};
|
};
|
||||||
const toIssue = () => {
|
const toIssue = () => {
|
||||||
window.open('https://github.com/1Panel-dev/1Panel/issues', '_blank');
|
window.open('https://github.com/1Panel-dev/1Panel/issues', '_blank', 'noopener,noreferrer');
|
||||||
};
|
};
|
||||||
const toGithubStar = () => {
|
const toGithubStar = () => {
|
||||||
window.open('https://github.com/1Panel-dev/1Panel', '_blank');
|
window.open('https://github.com/1Panel-dev/1Panel', '_blank', 'noopener,noreferrer');
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -348,7 +348,7 @@ function hasEndpoint(val: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const toDoc = () => {
|
const toDoc = () => {
|
||||||
window.open('https://1panel.cn/docs/user_manual/settings/', '_blank');
|
window.open('https://1panel.cn/docs/user_manual/settings/', '_blank', 'noopener,noreferrer');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBuckets = async (formEl: FormInstance | undefined) => {
|
const getBuckets = async (formEl: FormInstance | undefined) => {
|
||||||
|
|
138
frontend/src/views/toolbox/fail2ban/ban-action/index.vue
Normal file
138
frontend/src/views/toolbox/fail2ban/ban-action/index.vue
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('toolbox.fail2ban.banAction')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('toolbox.fail2ban.banAction')"
|
||||||
|
prop="banAction"
|
||||||
|
:rules="Rules.requiredSelect"
|
||||||
|
>
|
||||||
|
<el-select v-model="form.banAction">
|
||||||
|
<el-option value="iptables-allports" class="option">
|
||||||
|
<span class="option-content">iptables-allports</span>
|
||||||
|
<span class="input-help option-helper">
|
||||||
|
{{
|
||||||
|
$t('toolbox.fail2ban.banActionOption', ['iptables']) +
|
||||||
|
$t('toolbox.fail2ban.allPorts')
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</el-option>
|
||||||
|
<el-option value="iptables-multiport" class="option">
|
||||||
|
<span class="option-content">iptables-multiport</span>
|
||||||
|
<span class="input-help option-helper">
|
||||||
|
{{ $t('toolbox.fail2ban.banActionOption', ['iptables']) }}
|
||||||
|
</span>
|
||||||
|
</el-option>
|
||||||
|
<el-option value="firewallcmd-ipset" class="option">
|
||||||
|
<span class="option-content">firewallcmd-ipset</span>
|
||||||
|
<span class="input-help option-helper">
|
||||||
|
{{ $t('toolbox.fail2ban.banActionOption', ['firewallcmd ipset']) }}
|
||||||
|
</span>
|
||||||
|
</el-option>
|
||||||
|
<el-option value="ufw" class="option">
|
||||||
|
<span class="option-content">ufw</span>
|
||||||
|
<span class="input-help option-helper">
|
||||||
|
{{ $t('toolbox.fail2ban.banActionOption', ['ufw']) }}
|
||||||
|
</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<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(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 { updateFail2ban } from '@/api/modules/toolbox';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
banAction: string;
|
||||||
|
}
|
||||||
|
const drawerVisible = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
banAction: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
form.banAction = params.banAction;
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = 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.fail2ban.banAction'), form.banAction]),
|
||||||
|
i18n.global.t('toolbox.fail2ban.fail2banChange'),
|
||||||
|
{
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
},
|
||||||
|
).then(async () => {
|
||||||
|
await updateFail2ban({ key: 'banaction', value: form.banAction })
|
||||||
|
.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>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.option {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
.option-content {
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.option-helper {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
106
frontend/src/views/toolbox/fail2ban/ban-time/index.vue
Normal file
106
frontend/src/views/toolbox/fail2ban/ban-time/index.vue
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('toolbox.fail2ban.banTime')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('toolbox.fail2ban.banTime')" prop="banTime" :rules="Rules.number">
|
||||||
|
<el-input type="number" v-model.number="form.banTime">
|
||||||
|
<template #append>
|
||||||
|
<el-select v-model.number="form.banTimeUnit" style="width: 100px">
|
||||||
|
<el-option :label="$t('commons.units.second')" value="s" />
|
||||||
|
<el-option :label="$t('commons.units.minute')" value="m" />
|
||||||
|
<el-option :label="$t('commons.units.hour')" value="h" />
|
||||||
|
<el-option :label="$t('commons.units.day')" value="d" />
|
||||||
|
<el-option :label="$t('commons.units.year')" value="y" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<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(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 { updateFail2ban } from '@/api/modules/toolbox';
|
||||||
|
import { splitTime, transTimeUnit } from '@/utils/util';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
banTime: string;
|
||||||
|
}
|
||||||
|
const drawerVisible = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
banTime: 300,
|
||||||
|
banTimeUnit: 's',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
let item = splitTime(params.banTime);
|
||||||
|
form.banTime = item.time;
|
||||||
|
form.banTimeUnit = item.unit;
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = 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.fail2ban.banTime'),
|
||||||
|
form.banTime + transTimeUnit(form.banTimeUnit),
|
||||||
|
]),
|
||||||
|
i18n.global.t('toolbox.fail2ban.fail2banChange'),
|
||||||
|
{
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
},
|
||||||
|
).then(async () => {
|
||||||
|
await updateFail2ban({ key: 'bantime', value: form.banTime + form.banTimeUnit })
|
||||||
|
.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>
|
110
frontend/src/views/toolbox/fail2ban/find-time/index.vue
Normal file
110
frontend/src/views/toolbox/fail2ban/find-time/index.vue
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('toolbox.fail2ban.findTime')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('toolbox.fail2ban.findTime')"
|
||||||
|
prop="findTime"
|
||||||
|
:rules="Rules.requiredInput"
|
||||||
|
>
|
||||||
|
<el-input clearable v-model.number="form.findTime">
|
||||||
|
<template #append>
|
||||||
|
<el-select v-model="form.findTimeUnit" style="width: 100px">
|
||||||
|
<el-option :label="$t('commons.units.second')" value="s" />
|
||||||
|
<el-option :label="$t('commons.units.minute')" value="m" />
|
||||||
|
<el-option :label="$t('commons.units.hour')" value="h" />
|
||||||
|
<el-option :label="$t('commons.units.day')" value="d" />
|
||||||
|
<el-option :label="$t('commons.units.year')" value="y" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<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(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 { updateFail2ban } from '@/api/modules/toolbox';
|
||||||
|
import { splitTime, transTimeUnit } from '@/utils/util';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
findTime: string;
|
||||||
|
}
|
||||||
|
const drawerVisible = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
findTime: 300,
|
||||||
|
findTimeUnit: 's',
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
let item = splitTime(params.findTime);
|
||||||
|
form.findTime = item.time;
|
||||||
|
form.findTimeUnit = item.unit;
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = 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.fail2ban.findTime'),
|
||||||
|
form.findTime + transTimeUnit(form.findTimeUnit),
|
||||||
|
]),
|
||||||
|
i18n.global.t('toolbox.fail2ban.fail2banChange'),
|
||||||
|
{
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
},
|
||||||
|
).then(async () => {
|
||||||
|
await updateFail2ban({ key: 'findtime', value: form.findTime + form.findTimeUnit })
|
||||||
|
.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>
|
305
frontend/src/views/toolbox/fail2ban/index.vue
Normal file
305
frontend/src/views/toolbox/fail2ban/index.vue
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
<template>
|
||||||
|
<div v-loading="loading">
|
||||||
|
<div class="app-status" style="margin-top: 20px">
|
||||||
|
<el-card v-if="form.isExist">
|
||||||
|
<div>
|
||||||
|
<el-tag effect="dark" type="success">Fail2Ban</el-tag>
|
||||||
|
<el-tag round class="status-content" v-if="form.isActive" type="success">
|
||||||
|
{{ $t('commons.status.running') }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag round class="status-content" v-if="!form.isActive" type="info">
|
||||||
|
{{ $t('commons.status.stopped') }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag class="status-content">{{ form.version }}</el-tag>
|
||||||
|
<span class="buttons">
|
||||||
|
<el-button v-if="form.isActive" type="primary" @click="onOperate('stop')" link>
|
||||||
|
{{ $t('commons.button.stop') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="!form.isActive" type="primary" @click="onOperate('start')" link>
|
||||||
|
{{ $t('commons.button.start') }}
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-button type="primary" @click="onOperate('restart')" link>
|
||||||
|
{{ $t('container.restart') }}
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-button type="primary" link>
|
||||||
|
{{ $t('ssh.autoStart') }}
|
||||||
|
</el-button>
|
||||||
|
<el-switch
|
||||||
|
size="small"
|
||||||
|
class="ml-2"
|
||||||
|
inactive-value="disable"
|
||||||
|
active-value="enable"
|
||||||
|
@change="onOperate(autoStart)"
|
||||||
|
v-model="autoStart"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="form.isExist">
|
||||||
|
<el-card v-if="!form.isActive" class="mask-prompt">
|
||||||
|
<span>{{ $t('toolbox.fail2ban.unActive') }}</span>
|
||||||
|
</el-card>
|
||||||
|
<LayoutContent title="Fail2Ban" :divider="true" :class="{ mask: !form.isActive }">
|
||||||
|
<template #toolbar>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="16">
|
||||||
|
<el-button type="primary" plain @click="onLoadList('ignore')">
|
||||||
|
{{ $t('toolbox.fail2ban.ignoreIP') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" plain @click="onLoadList('banned')">
|
||||||
|
{{ $t('toolbox.fail2ban.bannedIP') }}
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
<template #main>
|
||||||
|
<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>
|
||||||
|
<el-row style="margin-top: 20px" v-if="confShowType === 'base'">
|
||||||
|
<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="$t('toolbox.fail2ban.maxRetry')" prop="maxRetry">
|
||||||
|
<el-input disabled v-model="form.maxRetry">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeMaxRetry" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('toolbox.fail2ban.banTime')" prop="banTime">
|
||||||
|
<el-input disabled v-model="form.banTimeItem">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeBanTime" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">{{ $t('toolbox.fail2ban.banTimeHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('toolbox.fail2ban.findTime')" prop="findTime">
|
||||||
|
<el-input disabled v-model="form.findTimeItem">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeFindTime" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('toolbox.fail2ban.banAction')" prop="banAction">
|
||||||
|
<el-input disabled v-model="form.banAction">
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onChangeBanAction" icon="Setting">
|
||||||
|
{{ $t('commons.button.set') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<div v-if="confShowType === 'all'">
|
||||||
|
<codemirror
|
||||||
|
:autofocus="true"
|
||||||
|
placeholder="# The Fail2Ban configuration file does not exist or is empty (/etc/ssh/sshd_config)"
|
||||||
|
:indent-with-tab="true"
|
||||||
|
:tabSize="4"
|
||||||
|
style="margin-top: 10px; height: calc(100vh - 460px)"
|
||||||
|
:lineWrapping="true"
|
||||||
|
:matchBrackets="true"
|
||||||
|
theme="cobalt"
|
||||||
|
:styleActiveLine="true"
|
||||||
|
:extensions="extensions"
|
||||||
|
v-model="fail2banConf"
|
||||||
|
/>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSaveFile" style="margin-top: 5px">
|
||||||
|
{{ $t('commons.button.save') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</LayoutContent>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<LayoutContent title="Fail2Ban" :divider="true">
|
||||||
|
<template #main>
|
||||||
|
<div class="app-warn">
|
||||||
|
<div>
|
||||||
|
<span>{{ $t('toolbox.fail2ban.noFail2ban') }}</span>
|
||||||
|
<el-link
|
||||||
|
style="font-size: 12px; margin-left: 5px"
|
||||||
|
@click="toDoc"
|
||||||
|
icon="Position"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
{{ $t('firewall.quickJump') }}
|
||||||
|
</el-link>
|
||||||
|
<div>
|
||||||
|
<img alt="" src="@/assets/images/no_app.svg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</LayoutContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MaxRetry ref="maxRetryRef" @search="search" />
|
||||||
|
<BanTime ref="banTimeRef" @search="search" />
|
||||||
|
<FindTime ref="findTimeRef" @search="search" />
|
||||||
|
<BanAction ref="banActionRef" @search="search" />
|
||||||
|
|
||||||
|
<IPs ref="listRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
|
import { Codemirror } from 'vue-codemirror';
|
||||||
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
|
import MaxRetry from '@/views/toolbox/fail2ban/max-retry/index.vue';
|
||||||
|
import BanTime from '@/views/toolbox/fail2ban/ban-time/index.vue';
|
||||||
|
import FindTime from '@/views/toolbox/fail2ban/find-time/index.vue';
|
||||||
|
import BanAction from '@/views/toolbox/fail2ban/ban-action/index.vue';
|
||||||
|
import IPs from '@/views/toolbox/fail2ban/ips/index.vue';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { getFail2banConf, getFail2banBase, operateFail2ban, updateFail2banByFile } from '@/api/modules/toolbox';
|
||||||
|
import { ElMessageBox } from 'element-plus';
|
||||||
|
import { transTimeUnit } from '@/utils/util';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const formRef = ref();
|
||||||
|
const extensions = [javascript(), oneDark];
|
||||||
|
const confShowType = ref('base');
|
||||||
|
|
||||||
|
const maxRetryRef = ref();
|
||||||
|
const banTimeRef = ref();
|
||||||
|
const findTimeRef = ref();
|
||||||
|
const banActionRef = ref();
|
||||||
|
const listRef = ref();
|
||||||
|
|
||||||
|
const autoStart = ref('enable');
|
||||||
|
|
||||||
|
const fail2banConf = ref();
|
||||||
|
const form = reactive({
|
||||||
|
isEnable: false,
|
||||||
|
isActive: false,
|
||||||
|
isExist: false,
|
||||||
|
version: '-',
|
||||||
|
|
||||||
|
port: 22,
|
||||||
|
maxRetry: 5,
|
||||||
|
banTime: '',
|
||||||
|
banTimeItem: '',
|
||||||
|
findTime: '',
|
||||||
|
findTimeItem: '',
|
||||||
|
banAction: '',
|
||||||
|
logPath: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const onLoadList = async (type: string) => {
|
||||||
|
listRef.value.acceptParams({ operate: type });
|
||||||
|
};
|
||||||
|
|
||||||
|
const toDoc = () => {
|
||||||
|
window.open('https://1panel.cn/docs/user_manual/toolbox/fail2ban/', '_blank', 'noopener,noreferrer');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSaveFile = async () => {
|
||||||
|
ElMessageBox.confirm(i18n.global.t('ssh.sshFileChangeHelper'), i18n.global.t('toolbox.fail2ban.fail2banChange'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
}).then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
await updateFail2banByFile({ file: fail2banConf.value })
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onChangeMaxRetry = () => {
|
||||||
|
maxRetryRef.value.acceptParams({ maxRetry: form.maxRetry });
|
||||||
|
};
|
||||||
|
const onChangeBanTime = () => {
|
||||||
|
banTimeRef.value.acceptParams({ banTime: form.banTime });
|
||||||
|
};
|
||||||
|
const onChangeFindTime = () => {
|
||||||
|
findTimeRef.value.acceptParams({ findTime: form.findTime });
|
||||||
|
};
|
||||||
|
const onChangeBanAction = () => {
|
||||||
|
banActionRef.value.acceptParams({ banAction: form.banAction });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOperate = async (operation: string) => {
|
||||||
|
let msg = operation === 'enable' || operation === 'disable' ? 'ssh.' : 'commons.button.';
|
||||||
|
ElMessageBox.confirm(i18n.global.t('toolbox.fail2ban.operation', [i18n.global.t(msg + operation)]), 'Fail2Ban', {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
await operateFail2ban(operation)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
search();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
autoStart.value = operation === 'enable' ? 'disable' : 'enable';
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
autoStart.value = operation === 'enable' ? 'disable' : 'enable';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadSSHConf = async () => {
|
||||||
|
const res = await getFail2banConf();
|
||||||
|
fail2banConf.value = res.data || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeMode = async () => {
|
||||||
|
if (confShowType.value === 'all') {
|
||||||
|
loadSSHConf();
|
||||||
|
} else {
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const search = async () => {
|
||||||
|
const res = await getFail2banBase();
|
||||||
|
form.isEnable = res.data.isEnable;
|
||||||
|
form.isActive = res.data.isActive;
|
||||||
|
form.isExist = res.data.isExist;
|
||||||
|
autoStart.value = form.isEnable ? 'enable' : 'disable';
|
||||||
|
form.version = res.data.version;
|
||||||
|
|
||||||
|
form.port = res.data.port;
|
||||||
|
form.maxRetry = res.data.maxRetry;
|
||||||
|
form.banTime = res.data.banTime;
|
||||||
|
form.banTimeItem = transTimeUnit(form.banTime);
|
||||||
|
form.findTime = res.data.findTime;
|
||||||
|
form.findTimeItem = transTimeUnit(form.findTime);
|
||||||
|
form.banAction = res.data.banAction;
|
||||||
|
form.logPath = res.data.logPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
search();
|
||||||
|
});
|
||||||
|
</script>
|
121
frontend/src/views/toolbox/fail2ban/ips/index.vue
Normal file
121
frontend/src/views/toolbox/fail2ban/ips/index.vue
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
<template>
|
||||||
|
<el-drawer :close-on-click-modal="false" v-model="drawerVisible" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<Header :header="$t('toolbox.fail2ban.' + form.operate + 'IP')" :back="handleClose"></Header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent :rules="rules" v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('toolbox.fail2ban.' + form.operate + 'IP')" prop="ips">
|
||||||
|
<el-input :autosize="{ minRows: 2 }" type="textarea" clearable v-model="form.ips" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<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(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { operatorFail2banSSHD, searchFail2ban } from '@/api/modules/toolbox';
|
||||||
|
import Header from '@/components/drawer-header/index.vue';
|
||||||
|
import { FormInstance } from 'element-plus';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { checkCidr, checkIpV4V6 } from '@/utils/util';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
operate: 'ignore',
|
||||||
|
ips: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
ips: [{ validator: checkIPs, trigger: 'blur' }],
|
||||||
|
});
|
||||||
|
function checkIPs(rule: any, value: any, callback: any) {
|
||||||
|
if (form.ips !== '') {
|
||||||
|
let addr = form.ips.split('\n');
|
||||||
|
for (const item of addr) {
|
||||||
|
if (item.indexOf('/') !== -1) {
|
||||||
|
if (checkCidr(item)) {
|
||||||
|
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||||
|
}
|
||||||
|
} else if (checkIpV4V6(item)) {
|
||||||
|
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
operate: string;
|
||||||
|
}
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
form.operate = params.operate;
|
||||||
|
form.ips = '';
|
||||||
|
drawerVisible.value = true;
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
let params = {
|
||||||
|
status: form.operate,
|
||||||
|
};
|
||||||
|
searchFail2ban(params).then((res) => {
|
||||||
|
let dataItem = res.data || [];
|
||||||
|
form.ips = dataItem.join('\n');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
i18n.global.t('toolbox.fail2ban.' + form.operate + 'Helper'),
|
||||||
|
i18n.global.t('commons.button.set'),
|
||||||
|
{
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
},
|
||||||
|
).then(async () => {
|
||||||
|
let param = {
|
||||||
|
operate: form.operate,
|
||||||
|
ips: form.ips.split('\n'),
|
||||||
|
};
|
||||||
|
loading.value = true;
|
||||||
|
await operatorFail2banSSHD(param)
|
||||||
|
.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>
|
95
frontend/src/views/toolbox/fail2ban/max-retry/index.vue
Normal file
95
frontend/src/views/toolbox/fail2ban/max-retry/index.vue
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('toolbox.fail2ban.maxRetry')" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item
|
||||||
|
:label="$t('toolbox.fail2ban.maxRetry')"
|
||||||
|
prop="maxRetry"
|
||||||
|
:rules="Rules.requiredInput"
|
||||||
|
>
|
||||||
|
<el-input clearable v-model.number="form.maxRetry">
|
||||||
|
<template #append>{{ $t('commons.units.time') }}</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<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(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 { updateFail2ban } from '@/api/modules/toolbox';
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
maxRetry: string;
|
||||||
|
}
|
||||||
|
const drawerVisible = ref();
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
maxRetry: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
form.maxRetry = Number(params.maxRetry);
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSave = 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.fail2ban.maxRetry'), form.maxRetry]),
|
||||||
|
i18n.global.t('toolbox.fail2ban.fail2banChange'),
|
||||||
|
{
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
},
|
||||||
|
).then(async () => {
|
||||||
|
await updateFail2ban({ key: 'maxretry', value: form.maxRetry + '' })
|
||||||
|
.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>
|
|
@ -9,12 +9,15 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import RouterButton from '@/components/router-button/index.vue';
|
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
{
|
{
|
||||||
label: i18n.global.t('menu.supervisor'),
|
label: i18n.global.t('menu.supervisor'),
|
||||||
path: '/hosts/tool/supervisor',
|
path: '/toolbox/supervisor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Fail2Ban',
|
||||||
|
path: '/toolbox/fail2ban',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
|
@ -36,5 +36,3 @@ onMounted(() => {
|
||||||
getConfig();
|
getConfig();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
|
|
@ -59,5 +59,3 @@ onMounted(() => {
|
||||||
getConfig();
|
getConfig();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ToolRouter />
|
|
||||||
<el-card v-if="showStopped" class="mask-prompt">
|
<el-card v-if="showStopped" class="mask-prompt">
|
||||||
<span>{{ $t('tool.supervisor.notStartWarn') }}</span>
|
<span>{{ $t('tool.supervisor.notStartWarn') }}</span>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
@ -150,7 +149,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ToolRouter from '@/views/host/tool/index.vue';
|
|
||||||
import SuperVisorStatus from './status/index.vue';
|
import SuperVisorStatus from './status/index.vue';
|
||||||
import { ref } from '@vue/runtime-core';
|
import { ref } from '@vue/runtime-core';
|
||||||
import ConfigSuperVisor from './config/index.vue';
|
import ConfigSuperVisor from './config/index.vue';
|
|
@ -47,7 +47,7 @@
|
||||||
{{ $t('firewall.quickJump') }}
|
{{ $t('firewall.quickJump') }}
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<img src="@/assets/images/no_app.svg" />
|
<img alt="" src="@/assets/images/no_app.svg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,7 +89,7 @@ const setting = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const toDoc = async () => {
|
const toDoc = async () => {
|
||||||
window.open('https://1panel.cn/docs/user_manual/hosts/supervisor/', '_blank');
|
window.open('https://1panel.cn/docs/user_manual/hosts/supervisor/', '_blank', 'noopener,noreferrer');
|
||||||
};
|
};
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
Loading…
Reference in a new issue