mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 工具箱病毒扫描支持定时扫描 (#5847)
This commit is contained in:
parent
ca0c96cb12
commit
3c0dc7459c
@ -51,16 +51,38 @@ func (b *BaseApi) UpdateClam(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Clam
|
||||||
|
// @Summary Update clam status
|
||||||
|
// @Description 修改扫描规则状态
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.ClamUpdateStatus true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /toolbox/clam/status/update [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"clams","output_column":"name","output_value":"name"}],"formatZH":"修改扫描规则 [name] 状态为 [status]","formatEN":"change the status of clam [name] to [status]."}
|
||||||
|
func (b *BaseApi) UpdateClamStatus(c *gin.Context) {
|
||||||
|
var req dto.ClamUpdateStatus
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := clamService.UpdateStatus(req.ID, req.Status); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags Clam
|
// @Tags Clam
|
||||||
// @Summary Page clam
|
// @Summary Page clam
|
||||||
// @Description 获取扫描规则列表分页
|
// @Description 获取扫描规则列表分页
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Param request body dto.SearchWithPage true "request"
|
// @Param request body dto.SearchClamWithPage true "request"
|
||||||
// @Success 200 {object} dto.PageResult
|
// @Success 200 {object} dto.PageResult
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /toolbox/clam/search [post]
|
// @Router /toolbox/clam/search [post]
|
||||||
func (b *BaseApi) SearchClam(c *gin.Context) {
|
func (b *BaseApi) SearchClam(c *gin.Context) {
|
||||||
var req dto.SearchWithPage
|
var req dto.SearchClamWithPage
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SearchClamWithPage struct {
|
||||||
|
PageInfo
|
||||||
|
Info string `json:"info"`
|
||||||
|
OrderBy string `json:"orderBy" validate:"required,oneof=name status created_at"`
|
||||||
|
Order string `json:"order" validate:"required,oneof=null ascending descending"`
|
||||||
|
}
|
||||||
|
|
||||||
type ClamBaseInfo struct {
|
type ClamBaseInfo struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
IsActive bool `json:"isActive"`
|
IsActive bool `json:"isActive"`
|
||||||
@ -19,10 +26,12 @@ type ClamInfo struct {
|
|||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
InfectedStrategy string `json:"infectedStrategy"`
|
InfectedStrategy string `json:"infectedStrategy"`
|
||||||
InfectedDir string `json:"infectedDir"`
|
InfectedDir string `json:"infectedDir"`
|
||||||
LastHandleDate string `json:"lastHandleDate"`
|
LastHandleDate string `json:"lastHandleDate"`
|
||||||
|
Spec string `json:"spec"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,9 +65,11 @@ type ClamLog struct {
|
|||||||
|
|
||||||
type ClamCreate struct {
|
type ClamCreate struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
InfectedStrategy string `json:"infectedStrategy"`
|
InfectedStrategy string `json:"infectedStrategy"`
|
||||||
InfectedDir string `json:"infectedDir"`
|
InfectedDir string `json:"infectedDir"`
|
||||||
|
Spec string `json:"spec"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,9 +80,15 @@ type ClamUpdate struct {
|
|||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
InfectedStrategy string `json:"infectedStrategy"`
|
InfectedStrategy string `json:"infectedStrategy"`
|
||||||
InfectedDir string `json:"infectedDir"`
|
InfectedDir string `json:"infectedDir"`
|
||||||
|
Spec string `json:"spec"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClamUpdateStatus struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
type ClamDelete struct {
|
type ClamDelete struct {
|
||||||
RemoveRecord bool `json:"removeRecord"`
|
RemoveRecord bool `json:"removeRecord"`
|
||||||
RemoveInfected bool `json:"removeInfected"`
|
RemoveInfected bool `json:"removeInfected"`
|
||||||
|
@ -4,8 +4,11 @@ type Clam struct {
|
|||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
Name string `gorm:"type:varchar(64);not null" json:"name"`
|
Name string `gorm:"type:varchar(64);not null" json:"name"`
|
||||||
|
Status string `gorm:"type:varchar(64)" json:"status"`
|
||||||
Path string `gorm:"type:varchar(64);not null" json:"path"`
|
Path string `gorm:"type:varchar(64);not null" json:"path"`
|
||||||
InfectedStrategy string `gorm:"type:varchar(64)" json:"infectedStrategy"`
|
InfectedStrategy string `gorm:"type:varchar(64)" json:"infectedStrategy"`
|
||||||
InfectedDir string `gorm:"type:varchar(64)" json:"infectedDir"`
|
InfectedDir string `gorm:"type:varchar(64)" json:"infectedDir"`
|
||||||
|
Spec string `gorm:"type:varchar(64)" json:"spec"`
|
||||||
|
EntryID int `gorm:"type:varchar(64)" json:"entryID"`
|
||||||
Description string `gorm:"type:varchar(64)" json:"description"`
|
Description string `gorm:"type:varchar(64)" json:"description"`
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ type IClamRepo interface {
|
|||||||
Update(id uint, vars map[string]interface{}) error
|
Update(id uint, vars map[string]interface{}) error
|
||||||
Delete(opts ...DBOption) error
|
Delete(opts ...DBOption) error
|
||||||
Get(opts ...DBOption) (model.Clam, error)
|
Get(opts ...DBOption) (model.Clam, error)
|
||||||
|
List(opts ...DBOption) ([]model.Clam, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIClamRepo() IClamRepo {
|
func NewIClamRepo() IClamRepo {
|
||||||
@ -29,6 +30,16 @@ func (u *ClamRepo) Get(opts ...DBOption) (model.Clam, error) {
|
|||||||
return clam, err
|
return clam, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ClamRepo) List(opts ...DBOption) ([]model.Clam, error) {
|
||||||
|
var clam []model.Clam
|
||||||
|
db := global.DB
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
err := db.Find(&clam).Error
|
||||||
|
return clam, err
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ClamRepo) Page(page, size int, opts ...DBOption) (int64, []model.Clam, error) {
|
func (u *ClamRepo) Page(page, size int, opts ...DBOption) (int64, []model.Clam, error) {
|
||||||
var users []model.Clam
|
var users []model.Clam
|
||||||
db := global.DB.Model(&model.Clam{})
|
db := global.DB.Model(&model.Clam{})
|
||||||
|
@ -12,13 +12,16 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -37,9 +40,10 @@ type ClamService struct {
|
|||||||
type IClamService interface {
|
type IClamService interface {
|
||||||
LoadBaseInfo() (dto.ClamBaseInfo, error)
|
LoadBaseInfo() (dto.ClamBaseInfo, error)
|
||||||
Operate(operate string) error
|
Operate(operate string) error
|
||||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
||||||
Create(req dto.ClamCreate) error
|
Create(req dto.ClamCreate) error
|
||||||
Update(req dto.ClamUpdate) error
|
Update(req dto.ClamUpdate) error
|
||||||
|
UpdateStatus(id uint, status string) error
|
||||||
Delete(req dto.ClamDelete) error
|
Delete(req dto.ClamDelete) error
|
||||||
HandleOnce(req dto.OperateByID) error
|
HandleOnce(req dto.OperateByID) error
|
||||||
LoadFile(req dto.ClamFileReq) (string, error)
|
LoadFile(req dto.ClamFileReq) (string, error)
|
||||||
@ -75,8 +79,7 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
|||||||
baseInfo.FreshIsExist = true
|
baseInfo.FreshIsExist = true
|
||||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
|
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
|
||||||
}
|
}
|
||||||
stdout, err := cmd.Exec("which clamdscan")
|
if !cmd.Which("clamdscan") {
|
||||||
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0 && strings.HasPrefix(stdout, "/")) {
|
|
||||||
baseInfo.IsActive = false
|
baseInfo.IsActive = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,8 +125,8 @@ func (c *ClamService) Operate(operate string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClamService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) {
|
||||||
total, commands, err := clamRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info))
|
total, commands, err := clamRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@ -164,6 +167,14 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
|||||||
if clam.InfectedStrategy == "none" || clam.InfectedStrategy == "remove" {
|
if clam.InfectedStrategy == "none" || clam.InfectedStrategy == "remove" {
|
||||||
clam.InfectedDir = ""
|
clam.InfectedDir = ""
|
||||||
}
|
}
|
||||||
|
if len(req.Spec) != 0 {
|
||||||
|
entryID, err := xpack.StartClam(clam, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clam.EntryID = entryID
|
||||||
|
clam.Status = constant.StatusEnable
|
||||||
|
}
|
||||||
if err := clamRepo.Create(&clam); err != nil {
|
if err := clamRepo.Create(&clam); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -178,11 +189,36 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
|
|||||||
if req.InfectedStrategy == "none" || req.InfectedStrategy == "remove" {
|
if req.InfectedStrategy == "none" || req.InfectedStrategy == "remove" {
|
||||||
req.InfectedDir = ""
|
req.InfectedDir = ""
|
||||||
}
|
}
|
||||||
|
var clamItem model.Clam
|
||||||
|
if err := copier.Copy(&clamItem, &req); err != nil {
|
||||||
|
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||||
|
}
|
||||||
|
clamItem.EntryID = clam.EntryID
|
||||||
upMap := map[string]interface{}{}
|
upMap := map[string]interface{}{}
|
||||||
|
if len(clam.Spec) != 0 && clam.EntryID != 0 {
|
||||||
|
global.Cron.Remove(cron.EntryID(clamItem.EntryID))
|
||||||
|
upMap["entry_id"] = 0
|
||||||
|
}
|
||||||
|
if len(req.Spec) == 0 {
|
||||||
|
upMap["status"] = ""
|
||||||
|
upMap["entry_id"] = 0
|
||||||
|
}
|
||||||
|
if len(req.Spec) != 0 && clam.Status != constant.StatusDisable {
|
||||||
|
newEntryID, err := xpack.StartClam(clamItem, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
upMap["entry_id"] = newEntryID
|
||||||
|
}
|
||||||
|
if len(clam.Spec) == 0 && len(req.Spec) != 0 {
|
||||||
|
upMap["status"] = constant.StatusEnable
|
||||||
|
}
|
||||||
|
|
||||||
upMap["name"] = req.Name
|
upMap["name"] = req.Name
|
||||||
upMap["path"] = req.Path
|
upMap["path"] = req.Path
|
||||||
upMap["infected_dir"] = req.InfectedDir
|
upMap["infected_dir"] = req.InfectedDir
|
||||||
upMap["infected_strategy"] = req.InfectedStrategy
|
upMap["infected_strategy"] = req.InfectedStrategy
|
||||||
|
upMap["spec"] = req.Spec
|
||||||
upMap["description"] = req.Description
|
upMap["description"] = req.Description
|
||||||
if err := clamRepo.Update(req.ID, upMap); err != nil {
|
if err := clamRepo.Update(req.ID, upMap); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -190,6 +226,28 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ClamService) UpdateStatus(id uint, status string) error {
|
||||||
|
clam, _ := clamRepo.Get(commonRepo.WithByID(id))
|
||||||
|
if clam.ID == 0 {
|
||||||
|
return constant.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
entryID int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if status == constant.StatusEnable {
|
||||||
|
entryID, err = xpack.StartClam(clam, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
global.Cron.Remove(cron.EntryID(clam.EntryID))
|
||||||
|
global.LOG.Infof("stop cronjob entryID: %v", clam.EntryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clamRepo.Update(clam.ID, map[string]interface{}{"status": status, "entry_id": entryID})
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ClamService) Delete(req dto.ClamDelete) error {
|
func (c *ClamService) Delete(req dto.ClamDelete) error {
|
||||||
for _, id := range req.Ids {
|
for _, id := range req.Ids {
|
||||||
clam, _ := clamRepo.Get(commonRepo.WithByID(id))
|
clam, _ := clamRepo.Get(commonRepo.WithByID(id))
|
||||||
|
@ -92,6 +92,7 @@ func Init() {
|
|||||||
migrations.AddForward,
|
migrations.AddForward,
|
||||||
migrations.AddShellColumn,
|
migrations.AddShellColumn,
|
||||||
migrations.AddClam,
|
migrations.AddClam,
|
||||||
|
migrations.AddClamStatus,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
global.LOG.Error(err)
|
||||||
|
@ -278,3 +278,13 @@ var AddClam = &gormigrate.Migration{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var AddClamStatus = &gormigrate.Migration{
|
||||||
|
ID: "20240716-add-clam-status",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
if err := tx.AutoMigrate(&model.Clam{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -56,6 +56,7 @@ func (s *ToolboxRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
toolboxRouter.POST("/clam/base", baseApi.LoadClamBaseInfo)
|
toolboxRouter.POST("/clam/base", baseApi.LoadClamBaseInfo)
|
||||||
toolboxRouter.POST("/clam/operate", baseApi.OperateClam)
|
toolboxRouter.POST("/clam/operate", baseApi.OperateClam)
|
||||||
toolboxRouter.POST("/clam/update", baseApi.UpdateClam)
|
toolboxRouter.POST("/clam/update", baseApi.UpdateClam)
|
||||||
|
toolboxRouter.POST("/clam/status/update", baseApi.UpdateClamStatus)
|
||||||
toolboxRouter.POST("/clam/del", baseApi.DeleteClam)
|
toolboxRouter.POST("/clam/del", baseApi.DeleteClam)
|
||||||
toolboxRouter.POST("/clam/handle", baseApi.HandleClamScan)
|
toolboxRouter.POST("/clam/handle", baseApi.HandleClamScan)
|
||||||
}
|
}
|
||||||
|
@ -203,8 +203,11 @@ func SudoHandleCmd() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Which(name string) bool {
|
func Which(name string) bool {
|
||||||
_, err := exec.LookPath(name)
|
stdout, err := Execf("which %s", name)
|
||||||
return err == nil
|
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0 && strings.HasPrefix(stdout, "/")) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecShellWithTimeOut(cmdStr, workdir string, logger *log.Logger, timeout time.Duration) error {
|
func ExecShellWithTimeOut(cmdStr, workdir string, logger *log.Logger, timeout time.Duration) error {
|
||||||
|
@ -7,6 +7,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RemoveTamper(website string) {}
|
func RemoveTamper(website string) {}
|
||||||
@ -27,3 +31,7 @@ func LoadRequestTransport() *http.Transport {
|
|||||||
func LoadGpuInfo() []interface{} {
|
func LoadGpuInfo() []interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StartClam(startClam model.Clam, isUpdate bool) (int, error) {
|
||||||
|
return 0, buserr.New(constant.ErrXpackNotFound)
|
||||||
|
}
|
||||||
|
@ -11500,6 +11500,58 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/toolbox/clam/status/update": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "修改扫描规则状态",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Clam"
|
||||||
|
],
|
||||||
|
"summary": "Update clam status",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ClamUpdateStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-panel-log": {
|
||||||
|
"BeforeFunctions": [
|
||||||
|
{
|
||||||
|
"db": "clams",
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bodyKeys": [
|
||||||
|
"id",
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"formatEN": "change the status of clam [name] to [status].",
|
||||||
|
"formatZH": "修改扫描规则 [name] 状态为 [status]",
|
||||||
|
"paramKeys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/toolbox/clam/update": {
|
"/toolbox/clam/update": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -15570,6 +15622,12 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -15665,6 +15723,20 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ClamUpdateStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -18468,7 +18540,7 @@ const docTemplate = `{
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"name",
|
"name",
|
||||||
"status",
|
"state",
|
||||||
"created_at"
|
"created_at"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -22601,7 +22673,8 @@ const docTemplate = `{
|
|||||||
"primary_domain",
|
"primary_domain",
|
||||||
"type",
|
"type",
|
||||||
"status",
|
"status",
|
||||||
"created_at"
|
"created_at",
|
||||||
|
"expire_date"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
@ -22619,8 +22692,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"primaryDomain",
|
"primaryDomain"
|
||||||
"webSiteGroupID"
|
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"IPV6": {
|
"IPV6": {
|
||||||
|
@ -11493,6 +11493,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/toolbox/clam/status/update": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "修改扫描规则状态",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Clam"
|
||||||
|
],
|
||||||
|
"summary": "Update clam status",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ClamUpdateStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-panel-log": {
|
||||||
|
"BeforeFunctions": [
|
||||||
|
{
|
||||||
|
"db": "clams",
|
||||||
|
"input_column": "id",
|
||||||
|
"input_value": "id",
|
||||||
|
"isList": false,
|
||||||
|
"output_column": "name",
|
||||||
|
"output_value": "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bodyKeys": [
|
||||||
|
"id",
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"formatEN": "change the status of clam [name] to [status].",
|
||||||
|
"formatZH": "修改扫描规则 [name] 状态为 [status]",
|
||||||
|
"paramKeys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/toolbox/clam/update": {
|
"/toolbox/clam/update": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -15563,6 +15615,12 @@
|
|||||||
},
|
},
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -15658,6 +15716,20 @@
|
|||||||
},
|
},
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.ClamUpdateStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -18461,7 +18533,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"name",
|
"name",
|
||||||
"status",
|
"state",
|
||||||
"created_at"
|
"created_at"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -22594,7 +22666,8 @@
|
|||||||
"primary_domain",
|
"primary_domain",
|
||||||
"type",
|
"type",
|
||||||
"status",
|
"status",
|
||||||
"created_at"
|
"created_at",
|
||||||
|
"expire_date"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
@ -22612,8 +22685,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"primaryDomain",
|
"primaryDomain"
|
||||||
"webSiteGroupID"
|
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"IPV6": {
|
"IPV6": {
|
||||||
|
@ -243,6 +243,10 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
path:
|
path:
|
||||||
type: string
|
type: string
|
||||||
|
spec:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
dto.ClamDelete:
|
dto.ClamDelete:
|
||||||
properties:
|
properties:
|
||||||
@ -305,6 +309,15 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
path:
|
path:
|
||||||
type: string
|
type: string
|
||||||
|
spec:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
dto.ClamUpdateStatus:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
dto.Clean:
|
dto.Clean:
|
||||||
properties:
|
properties:
|
||||||
@ -2198,7 +2211,7 @@ definitions:
|
|||||||
orderBy:
|
orderBy:
|
||||||
enum:
|
enum:
|
||||||
- name
|
- name
|
||||||
- status
|
- state
|
||||||
- created_at
|
- created_at
|
||||||
type: string
|
type: string
|
||||||
page:
|
page:
|
||||||
@ -4974,6 +4987,7 @@ definitions:
|
|||||||
- type
|
- type
|
||||||
- status
|
- status
|
||||||
- created_at
|
- created_at
|
||||||
|
- expire_date
|
||||||
type: string
|
type: string
|
||||||
page:
|
page:
|
||||||
type: integer
|
type: integer
|
||||||
@ -5004,7 +5018,6 @@ definitions:
|
|||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- primaryDomain
|
- primaryDomain
|
||||||
- webSiteGroupID
|
|
||||||
type: object
|
type: object
|
||||||
request.WebsiteUpdateDir:
|
request.WebsiteUpdateDir:
|
||||||
properties:
|
properties:
|
||||||
@ -12767,6 +12780,40 @@ paths:
|
|||||||
summary: Page clam
|
summary: Page clam
|
||||||
tags:
|
tags:
|
||||||
- Clam
|
- Clam
|
||||||
|
/toolbox/clam/status/update:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 修改扫描规则状态
|
||||||
|
parameters:
|
||||||
|
- description: request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.ClamUpdateStatus'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Update clam status
|
||||||
|
tags:
|
||||||
|
- Clam
|
||||||
|
x-panel-log:
|
||||||
|
BeforeFunctions:
|
||||||
|
- db: clams
|
||||||
|
input_column: id
|
||||||
|
input_value: id
|
||||||
|
isList: false
|
||||||
|
output_column: name
|
||||||
|
output_value: name
|
||||||
|
bodyKeys:
|
||||||
|
- id
|
||||||
|
- status
|
||||||
|
formatEN: change the status of clam [name] to [status].
|
||||||
|
formatZH: 修改扫描规则 [name] 状态为 [status]
|
||||||
|
paramKeys: []
|
||||||
/toolbox/clam/update:
|
/toolbox/clam/update:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ReqPage } from '.';
|
import { ReqPage } from '.';
|
||||||
|
import { Cronjob } from './cronjob';
|
||||||
|
|
||||||
export namespace Toolbox {
|
export namespace Toolbox {
|
||||||
export interface DeviceBaseInfo {
|
export interface DeviceBaseInfo {
|
||||||
@ -129,10 +130,14 @@ export namespace Toolbox {
|
|||||||
export interface ClamInfo {
|
export interface ClamInfo {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
status: string;
|
||||||
path: string;
|
path: string;
|
||||||
infectedStrategy: string;
|
infectedStrategy: string;
|
||||||
infectedDir: string;
|
infectedDir: string;
|
||||||
lastHandleDate: string;
|
lastHandleDate: string;
|
||||||
|
hasSpec: boolean;
|
||||||
|
spec: string;
|
||||||
|
specObj: Cronjob.SpecObj;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
export interface ClamCreate {
|
export interface ClamCreate {
|
||||||
@ -140,6 +145,8 @@ export namespace Toolbox {
|
|||||||
path: string;
|
path: string;
|
||||||
infectedStrategy: string;
|
infectedStrategy: string;
|
||||||
infectedDir: string;
|
infectedDir: string;
|
||||||
|
spec: string;
|
||||||
|
specObj: Cronjob.SpecObj;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
export interface ClamUpdate {
|
export interface ClamUpdate {
|
||||||
@ -148,6 +155,8 @@ export namespace Toolbox {
|
|||||||
path: string;
|
path: string;
|
||||||
infectedStrategy: string;
|
infectedStrategy: string;
|
||||||
infectedDir: string;
|
infectedDir: string;
|
||||||
|
spec: string;
|
||||||
|
specObj: Cronjob.SpecObj;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
export interface ClamSearchLog extends ReqPage {
|
export interface ClamSearchLog extends ReqPage {
|
||||||
|
@ -138,6 +138,9 @@ export const createClam = (params: Toolbox.ClamCreate) => {
|
|||||||
export const updateClam = (params: Toolbox.ClamUpdate) => {
|
export const updateClam = (params: Toolbox.ClamUpdate) => {
|
||||||
return http.post(`/toolbox/clam/update`, params);
|
return http.post(`/toolbox/clam/update`, params);
|
||||||
};
|
};
|
||||||
|
export const updateClamStatus = (id: number, status: string) => {
|
||||||
|
return http.post(`/toolbox/clam/status/update`, { id: id, status: status });
|
||||||
|
};
|
||||||
export const deleteClam = (params: { ids: number[]; removeRecord: boolean; removeInfected: boolean }) => {
|
export const deleteClam = (params: { ids: number[]; removeRecord: boolean; removeInfected: boolean }) => {
|
||||||
return http.post(`/toolbox/clam/del`, params);
|
return http.post(`/toolbox/clam/del`, params);
|
||||||
};
|
};
|
||||||
|
@ -1082,6 +1082,13 @@ const message = {
|
|||||||
},
|
},
|
||||||
clam: {
|
clam: {
|
||||||
clam: 'Virus Scan',
|
clam: 'Virus Scan',
|
||||||
|
cron: 'Scheduled scan',
|
||||||
|
cronHelper: 'Professional version supports scheduled scan feature',
|
||||||
|
specErr: 'Execution schedule format error, please check and retry!',
|
||||||
|
disableMsg:
|
||||||
|
'Stopping scheduled execution will prevent this scan task from running automatically. Do you want to continue?',
|
||||||
|
enableMsg:
|
||||||
|
'Enabling scheduled execution will allow this scan task to run automatically at regular intervals. Do you want to continue?',
|
||||||
showFresh: 'Show Virus Database Service',
|
showFresh: 'Show Virus Database Service',
|
||||||
hideFresh: 'Hide Virus Database Service',
|
hideFresh: 'Hide Virus Database Service',
|
||||||
clamHelper:
|
clamHelper:
|
||||||
@ -1577,6 +1584,7 @@ const message = {
|
|||||||
recoverDetail: 'Recover detail',
|
recoverDetail: 'Recover detail',
|
||||||
createSnapshot: 'Create Snapshot',
|
createSnapshot: 'Create Snapshot',
|
||||||
importSnapshot: 'Sync Snapshot',
|
importSnapshot: 'Sync Snapshot',
|
||||||
|
importHelper: 'Snapshot directory:',
|
||||||
recover: 'Recover',
|
recover: 'Recover',
|
||||||
lastRecoverAt: 'Last recovery time',
|
lastRecoverAt: 'Last recovery time',
|
||||||
lastRollbackAt: 'Last rollback time',
|
lastRollbackAt: 'Last rollback time',
|
||||||
|
@ -1023,6 +1023,11 @@ const message = {
|
|||||||
},
|
},
|
||||||
clam: {
|
clam: {
|
||||||
clam: '病毒掃描',
|
clam: '病毒掃描',
|
||||||
|
cron: '定時掃描',
|
||||||
|
cronHelper: '專業版支持定時掃描功能',
|
||||||
|
specErr: '執行周期格式錯誤,請檢查後重試!',
|
||||||
|
disableMsg: '停止定時執行會導致該掃描任務不再自動執行。是否繼續?',
|
||||||
|
enableMsg: '啟用定時執行會讓該掃描任務定期自動執行。是否繼續?',
|
||||||
showFresh: '顯示病毒庫服務',
|
showFresh: '顯示病毒庫服務',
|
||||||
hideFresh: '隱藏病毒庫服務',
|
hideFresh: '隱藏病毒庫服務',
|
||||||
clamHelper:
|
clamHelper:
|
||||||
@ -1395,6 +1400,7 @@ const message = {
|
|||||||
recoverDetail: '恢復詳情',
|
recoverDetail: '恢復詳情',
|
||||||
createSnapshot: '創建快照',
|
createSnapshot: '創建快照',
|
||||||
importSnapshot: '同步快照',
|
importSnapshot: '同步快照',
|
||||||
|
importHelper: '快照文件目錄:',
|
||||||
recover: '恢復',
|
recover: '恢復',
|
||||||
lastRecoverAt: '上次恢復時間',
|
lastRecoverAt: '上次恢復時間',
|
||||||
lastRollbackAt: '上次回滾時間',
|
lastRollbackAt: '上次回滾時間',
|
||||||
|
@ -1024,6 +1024,11 @@ const message = {
|
|||||||
},
|
},
|
||||||
clam: {
|
clam: {
|
||||||
clam: '病毒扫描',
|
clam: '病毒扫描',
|
||||||
|
cron: '定时扫描',
|
||||||
|
cronHelper: '专业版支持定时扫描功能 ',
|
||||||
|
specErr: '执行周期格式错误,请检查后重试!',
|
||||||
|
disableMsg: '停止定时执行会导致该扫描任务不再自动执行。是否继续?',
|
||||||
|
enableMsg: '启用定时执行会让该扫描任务定期自动执行。是否继续?',
|
||||||
showFresh: '显示病毒库服务',
|
showFresh: '显示病毒库服务',
|
||||||
hideFresh: '隐藏病毒库服务',
|
hideFresh: '隐藏病毒库服务',
|
||||||
clamHelper:
|
clamHelper:
|
||||||
@ -1397,6 +1402,7 @@ const message = {
|
|||||||
recoverDetail: '恢复详情',
|
recoverDetail: '恢复详情',
|
||||||
createSnapshot: '创建快照',
|
createSnapshot: '创建快照',
|
||||||
importSnapshot: '同步快照',
|
importSnapshot: '同步快照',
|
||||||
|
importHelper: '快照文件目录:',
|
||||||
recover: '恢复',
|
recover: '恢复',
|
||||||
lastRecoverAt: '上次恢复时间',
|
lastRecoverAt: '上次恢复时间',
|
||||||
lastRollbackAt: '上次回滚时间',
|
lastRollbackAt: '上次回滚时间',
|
||||||
|
@ -16,6 +16,10 @@
|
|||||||
:label="item.label"
|
:label="item.label"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<div v-if="form.from === 'LOCAL'">
|
||||||
|
<span class="import-help">{{ $t('setting.importHelper') }}</span>
|
||||||
|
<span @click="toFolder()" class="import-link-help">{{ backupPath }}</span>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('commons.table.name')" prop="names">
|
<el-form-item :label="$t('commons.table.name')" prop="names">
|
||||||
<el-select style="width: 100%" v-model="form.names" multiple clearable>
|
<el-select style="width: 100%" v-model="form.names" multiple clearable>
|
||||||
@ -57,6 +61,7 @@ import { snapshotImport } from '@/api/modules/setting';
|
|||||||
import { getBackupList, getFilesFromBackup } from '@/api/modules/setting';
|
import { getBackupList, getFilesFromBackup } from '@/api/modules/setting';
|
||||||
import { Rules } from '@/global/form-rules';
|
import { Rules } from '@/global/form-rules';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import router from '@/routers';
|
||||||
|
|
||||||
const drawerVisible = ref(false);
|
const drawerVisible = ref(false);
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
@ -65,6 +70,7 @@ const formRef = ref();
|
|||||||
const backupOptions = ref();
|
const backupOptions = ref();
|
||||||
const fileNames = ref();
|
const fileNames = ref();
|
||||||
const existNames = ref();
|
const existNames = ref();
|
||||||
|
const backupPath = ref('');
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
from: '',
|
from: '',
|
||||||
@ -102,6 +108,9 @@ const checkDisable = (val: string) => {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
const toFolder = async () => {
|
||||||
|
router.push({ path: '/hosts/files', query: { path: backupPath.value } });
|
||||||
|
};
|
||||||
|
|
||||||
const submitImport = async (formEl: FormInstance | undefined) => {
|
const submitImport = async (formEl: FormInstance | undefined) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@ -131,6 +140,10 @@ const loadBackups = async () => {
|
|||||||
if (item.id !== 0) {
|
if (item.id !== 0) {
|
||||||
backupOptions.value.push({ label: i18n.global.t('setting.' + item.type), value: item.type });
|
backupOptions.value.push({ label: i18n.global.t('setting.' + item.type), value: item.type });
|
||||||
}
|
}
|
||||||
|
if (item.type === 'LOCAL') {
|
||||||
|
item.varsJson = JSON.parse(item.vars);
|
||||||
|
backupPath.value = item.varsJson['dir'] + '/system_snapshot';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -148,3 +161,18 @@ defineExpose({
|
|||||||
acceptParams,
|
acceptParams,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.import-help {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8f959e;
|
||||||
|
}
|
||||||
|
.import-link-help {
|
||||||
|
color: $primary-color;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-link-help:hover {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -405,10 +405,17 @@ const search = async () => {
|
|||||||
page: paginationConfig.currentPage,
|
page: paginationConfig.currentPage,
|
||||||
pageSize: paginationConfig.pageSize,
|
pageSize: paginationConfig.pageSize,
|
||||||
};
|
};
|
||||||
const res = await searchSnapshotPage(params);
|
loading.value = true;
|
||||||
|
await searchSnapshotPage(params)
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
cleanData.value = false;
|
cleanData.value = false;
|
||||||
data.value = res.data.items || [];
|
data.value = res.data.items || [];
|
||||||
paginationConfig.total = res.data.total;
|
paginationConfig.total = res.data.total;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
:label="$t('commons.table.name')"
|
:label="$t('commons.table.name')"
|
||||||
:min-width="60"
|
:min-width="60"
|
||||||
prop="name"
|
prop="name"
|
||||||
|
sortable
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@ -74,6 +75,47 @@
|
|||||||
<el-button link type="primary" @click="toFolder(row.path)">{{ row.path }}</el-button>
|
<el-button link type="primary" @click="toFolder(row.path)">{{ row.path }}</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-if="isProductPro"
|
||||||
|
:label="$t('commons.table.status')"
|
||||||
|
:min-width="70"
|
||||||
|
prop="status"
|
||||||
|
sortable
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button
|
||||||
|
v-if="row.status === 'Enable'"
|
||||||
|
@click="onChangeStatus(row.id, 'disable')"
|
||||||
|
link
|
||||||
|
icon="VideoPlay"
|
||||||
|
type="success"
|
||||||
|
>
|
||||||
|
{{ $t('commons.status.enabled') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="row.status === 'Disable'"
|
||||||
|
icon="VideoPause"
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="onChangeStatus(row.id, 'enable')"
|
||||||
|
>
|
||||||
|
{{ $t('commons.status.disabled') }}
|
||||||
|
</el-button>
|
||||||
|
<span v-if="row.status === ''">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-if="isProductPro"
|
||||||
|
:label="$t('cronjob.cronSpec')"
|
||||||
|
show-overflow-tooltip
|
||||||
|
:min-width="120"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>
|
||||||
|
{{ row.spec !== '' ? transSpecToStr(row.spec) : '-' }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:label="$t('toolbox.clam.infectedDir')"
|
:label="$t('toolbox.clam.infectedDir')"
|
||||||
:min-width="120"
|
:min-width="120"
|
||||||
@ -138,17 +180,22 @@
|
|||||||
import { onMounted, reactive, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
import { deleteClam, handleClamScan, searchClam, updateClam } from '@/api/modules/toolbox';
|
import { deleteClam, handleClamScan, searchClam, updateClam, updateClamStatus } from '@/api/modules/toolbox';
|
||||||
import OperateDialog from '@/views/toolbox/clam/operate/index.vue';
|
import OperateDialog from '@/views/toolbox/clam/operate/index.vue';
|
||||||
import LogDialog from '@/views/toolbox/clam/record/index.vue';
|
import LogDialog from '@/views/toolbox/clam/record/index.vue';
|
||||||
import ClamStatus from '@/views/toolbox/clam/status/index.vue';
|
import ClamStatus from '@/views/toolbox/clam/status/index.vue';
|
||||||
import SettingDialog from '@/views/toolbox/clam/setting/index.vue';
|
import SettingDialog from '@/views/toolbox/clam/setting/index.vue';
|
||||||
import { Toolbox } from '@/api/interface/toolbox';
|
import { Toolbox } from '@/api/interface/toolbox';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
|
import { transSpecToStr } from '../../cronjob/helper';
|
||||||
|
import { GlobalStore } from '@/store';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const selects = ref<any>([]);
|
const selects = ref<any>([]);
|
||||||
|
|
||||||
|
const globalStore = GlobalStore();
|
||||||
|
const { isProductPro } = storeToRefs(globalStore);
|
||||||
const data = ref();
|
const data = ref();
|
||||||
const paginationConfig = reactive({
|
const paginationConfig = reactive({
|
||||||
cacheSizeKey: 'clam-page-size',
|
cacheSizeKey: 'clam-page-size',
|
||||||
@ -176,12 +223,16 @@ const clamStatus = ref({
|
|||||||
isRunning: true,
|
isRunning: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const search = async () => {
|
const search = async (column?: any) => {
|
||||||
|
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
|
||||||
|
paginationConfig.order = column?.order ? column.order : paginationConfig.order;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
let params = {
|
let params = {
|
||||||
info: searchName.value,
|
info: searchName.value,
|
||||||
page: paginationConfig.currentPage,
|
page: paginationConfig.currentPage,
|
||||||
pageSize: paginationConfig.pageSize,
|
pageSize: paginationConfig.pageSize,
|
||||||
|
orderBy: paginationConfig.orderBy,
|
||||||
|
order: paginationConfig.order,
|
||||||
};
|
};
|
||||||
await searchClam(params)
|
await searchClam(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@ -218,6 +269,14 @@ const onOpenDialog = async (
|
|||||||
title: string,
|
title: string,
|
||||||
rowData: Partial<Toolbox.ClamInfo> = {
|
rowData: Partial<Toolbox.ClamInfo> = {
|
||||||
infectedStrategy: 'none',
|
infectedStrategy: 'none',
|
||||||
|
specObj: {
|
||||||
|
specType: 'perDay',
|
||||||
|
week: 1,
|
||||||
|
day: 3,
|
||||||
|
hour: 1,
|
||||||
|
minute: 30,
|
||||||
|
second: 30,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
let params = {
|
let params = {
|
||||||
@ -272,6 +331,18 @@ const onSubmitDelete = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChangeStatus = async (id: number, status: string) => {
|
||||||
|
ElMessageBox.confirm(i18n.global.t('toolbox.clam.' + status + 'Msg'), i18n.global.t('cronjob.changeStatus'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
}).then(async () => {
|
||||||
|
let itemStatus = status === 'enable' ? 'Enable' : 'Disable';
|
||||||
|
await updateClamStatus(id, itemStatus);
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
search();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
{
|
{
|
||||||
label: i18n.global.t('commons.button.handle'),
|
label: i18n.global.t('commons.button.handle'),
|
||||||
|
@ -50,6 +50,77 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item prop="hasSpec">
|
||||||
|
<el-checkbox v-model="dialogData.rowData!.hasSpec" :label="$t('toolbox.clam.cron')" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="dialogData.rowData!.hasSpec && !isProductPro">
|
||||||
|
<span>{{ $t('toolbox.clam.cronHelper') }}</span>
|
||||||
|
<el-button link type="primary" @click="toUpload">
|
||||||
|
{{ $t('license.levelUpPro') }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="spec" v-if="dialogData.rowData!.hasSpec && isProductPro">
|
||||||
|
<el-select
|
||||||
|
class="specTypeClass"
|
||||||
|
v-model="dialogData.rowData!.specObj.specType"
|
||||||
|
@change="changeSpecType()"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in specOptions"
|
||||||
|
:key="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-if="dialogData.rowData!.specObj.specType === 'perWeek'"
|
||||||
|
class="specClass"
|
||||||
|
v-model="dialogData.rowData!.specObj.week"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in weekOptions"
|
||||||
|
:key="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-if="hasDay(dialogData.rowData!.specObj)"
|
||||||
|
class="specClass"
|
||||||
|
v-model.number="dialogData.rowData!.specObj.day"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<div class="append">{{ $t('cronjob.day') }}</div>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-input
|
||||||
|
v-if="hasHour(dialogData.rowData!.specObj)"
|
||||||
|
class="specClass"
|
||||||
|
v-model.number="dialogData.rowData!.specObj.hour"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<div class="append">{{ $t('commons.units.hour') }}</div>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-input
|
||||||
|
v-if="dialogData.rowData!.specObj.specType !== 'perNSecond'"
|
||||||
|
class="specClass"
|
||||||
|
v-model.number="dialogData.rowData!.specObj.minute"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<div class="append">{{ $t('commons.units.minute') }}</div>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-input
|
||||||
|
v-if="dialogData.rowData!.specObj.specType === 'perNSecond'"
|
||||||
|
class="specClass"
|
||||||
|
v-model.number="dialogData.rowData!.specObj.second"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<div class="append">{{ $t('commons.units.second') }}</div>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item :label="$t('commons.table.description')" prop="description">
|
<el-form-item :label="$t('commons.table.description')" prop="description">
|
||||||
<el-input type="textarea" :rows="3" clearable v-model="dialogData.rowData!.description" />
|
<el-input type="textarea" :rows="3" clearable v-model="dialogData.rowData!.description" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -64,6 +135,7 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
<LicenseImport ref="licenseRef" />
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -73,11 +145,18 @@ import { Rules } from '@/global/form-rules';
|
|||||||
import FileList from '@/components/file-list/index.vue';
|
import FileList from '@/components/file-list/index.vue';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { ElForm } from 'element-plus';
|
import { ElForm } from 'element-plus';
|
||||||
|
import LicenseImport from '@/components/license-import/index.vue';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import { Toolbox } from '@/api/interface/toolbox';
|
import { Toolbox } from '@/api/interface/toolbox';
|
||||||
import { createClam, updateClam } from '@/api/modules/toolbox';
|
import { createClam, updateClam } from '@/api/modules/toolbox';
|
||||||
|
import { specOptions, transObjToSpec, transSpecToObj, weekOptions } from '../../../cronjob/helper';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { GlobalStore } from '@/store';
|
||||||
|
|
||||||
|
const globalStore = GlobalStore();
|
||||||
|
const licenseRef = ref();
|
||||||
|
const { isProductPro } = storeToRefs(globalStore);
|
||||||
interface DialogProps {
|
interface DialogProps {
|
||||||
title: string;
|
title: string;
|
||||||
rowData?: Toolbox.ClamInfo;
|
rowData?: Toolbox.ClamInfo;
|
||||||
@ -92,6 +171,19 @@ const dialogData = ref<DialogProps>({
|
|||||||
|
|
||||||
const acceptParams = (params: DialogProps): void => {
|
const acceptParams = (params: DialogProps): void => {
|
||||||
dialogData.value = params;
|
dialogData.value = params;
|
||||||
|
if (dialogData.value.rowData?.spec) {
|
||||||
|
dialogData.value.rowData.hasSpec = true;
|
||||||
|
dialogData.value.rowData.specObj = transSpecToObj(dialogData.value.rowData.spec);
|
||||||
|
} else {
|
||||||
|
dialogData.value.rowData.specObj = {
|
||||||
|
specType: 'perDay',
|
||||||
|
week: 1,
|
||||||
|
day: 3,
|
||||||
|
hour: 1,
|
||||||
|
minute: 30,
|
||||||
|
second: 30,
|
||||||
|
};
|
||||||
|
}
|
||||||
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
||||||
drawerVisible.value = true;
|
drawerVisible.value = true;
|
||||||
};
|
};
|
||||||
@ -101,9 +193,97 @@ const handleClose = () => {
|
|||||||
drawerVisible.value = false;
|
drawerVisible.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const verifySpec = (rule: any, value: any, callback: any) => {
|
||||||
|
let item = dialogData.value.rowData!.specObj;
|
||||||
|
if (
|
||||||
|
!Number.isInteger(item.day) ||
|
||||||
|
!Number.isInteger(item.hour) ||
|
||||||
|
!Number.isInteger(item.minute) ||
|
||||||
|
!Number.isInteger(item.second) ||
|
||||||
|
!Number.isInteger(item.week)
|
||||||
|
) {
|
||||||
|
callback(new Error(i18n.global.t('cronjob.specErr')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (item.specType) {
|
||||||
|
case 'perMonth':
|
||||||
|
if (
|
||||||
|
item.day < 0 ||
|
||||||
|
item.day > 31 ||
|
||||||
|
item.hour < 0 ||
|
||||||
|
item.hour > 23 ||
|
||||||
|
item.minute < 0 ||
|
||||||
|
item.minute > 59
|
||||||
|
) {
|
||||||
|
callback(new Error(i18n.global.t('cronjob.specErr')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'perNDay':
|
||||||
|
if (
|
||||||
|
item.day < 0 ||
|
||||||
|
item.day > 366 ||
|
||||||
|
item.hour < 0 ||
|
||||||
|
item.hour > 23 ||
|
||||||
|
item.minute < 0 ||
|
||||||
|
item.minute > 59
|
||||||
|
) {
|
||||||
|
callback(new Error(i18n.global.t('cronjob.specErr')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'perWeek':
|
||||||
|
if (
|
||||||
|
item.week < 0 ||
|
||||||
|
item.week > 6 ||
|
||||||
|
item.hour < 0 ||
|
||||||
|
item.hour > 23 ||
|
||||||
|
item.minute < 0 ||
|
||||||
|
item.minute > 59
|
||||||
|
) {
|
||||||
|
callback(new Error(i18n.global.t('cronjob.specErr')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'perDay':
|
||||||
|
if (item.hour < 0 || item.hour > 23 || item.minute < 0 || item.minute > 59) {
|
||||||
|
callback(new Error(i18n.global.t('cronjob.specErr')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'perNHour':
|
||||||
|
if (item.hour < 0 || item.hour > 8784 || item.minute < 0 || item.minute > 59) {
|
||||||
|
callback(new Error(i18n.global.t('cronjob.specErr')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'perHour':
|
||||||
|
if (item.minute < 0 || item.minute > 59) {
|
||||||
|
callback(new Error(i18n.global.t('cronjob.specErr')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'perNMinute':
|
||||||
|
if (item.minute < 0 || item.minute > 527040) {
|
||||||
|
callback(new Error(i18n.global.t('cronjob.specErr')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'perNSecond':
|
||||||
|
if (item.second < 0 || item.second > 31622400) {
|
||||||
|
callback(new Error(i18n.global.t('cronjob.specErr')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
};
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
name: [Rules.simpleName],
|
name: [Rules.simpleName],
|
||||||
path: [Rules.requiredInput, Rules.noSpace],
|
path: [Rules.requiredInput, Rules.noSpace],
|
||||||
|
spec: [
|
||||||
|
{ validator: verifySpec, trigger: 'blur', required: true },
|
||||||
|
{ validator: verifySpec, trigger: 'change', required: true },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormInstance = InstanceType<typeof ElForm>;
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
@ -120,12 +300,62 @@ const loadDir = async (path: string) => {
|
|||||||
const loadInfectedDir = async (path: string) => {
|
const loadInfectedDir = async (path: string) => {
|
||||||
dialogData.value.rowData!.infectedDir = path;
|
dialogData.value.rowData!.infectedDir = path;
|
||||||
};
|
};
|
||||||
|
const hasDay = (item: any) => {
|
||||||
|
return item.specType === 'perMonth' || item.specType === 'perNDay';
|
||||||
|
};
|
||||||
|
const hasHour = (item: any) => {
|
||||||
|
return item.specType !== 'perHour' && item.specType !== 'perNMinute' && item.specType !== 'perNSecond';
|
||||||
|
};
|
||||||
|
|
||||||
|
const toUpload = () => {
|
||||||
|
licenseRef.value.acceptParams();
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeSpecType = () => {
|
||||||
|
let item = dialogData.value.rowData!.specObj;
|
||||||
|
switch (item.specType) {
|
||||||
|
case 'perMonth':
|
||||||
|
case 'perNDay':
|
||||||
|
item.day = 3;
|
||||||
|
item.hour = 1;
|
||||||
|
item.minute = 30;
|
||||||
|
break;
|
||||||
|
case 'perWeek':
|
||||||
|
item.week = 1;
|
||||||
|
item.hour = 1;
|
||||||
|
item.minute = 30;
|
||||||
|
break;
|
||||||
|
case 'perDay':
|
||||||
|
case 'perNHour':
|
||||||
|
item.hour = 2;
|
||||||
|
item.minute = 30;
|
||||||
|
break;
|
||||||
|
case 'perHour':
|
||||||
|
case 'perNMinute':
|
||||||
|
item.minute = 30;
|
||||||
|
break;
|
||||||
|
case 'perNSecond':
|
||||||
|
item.second = 30;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
if (!formEl) return;
|
if (!formEl) return;
|
||||||
formEl.validate(async (valid) => {
|
formEl.validate(async (valid) => {
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
let spec = '';
|
||||||
|
let item = dialogData.value.rowData.specObj;
|
||||||
|
if (dialogData.value.rowData!.hasSpec) {
|
||||||
|
spec = transObjToSpec(item.specType, item.week, item.day, item.hour, item.minute, item.second);
|
||||||
|
if (spec === '') {
|
||||||
|
MsgError(i18n.global.t('cronjob.cronSpecHelper'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialogData.value.rowData.spec = spec;
|
||||||
|
|
||||||
if (dialogData.value.title === 'edit') {
|
if (dialogData.value.title === 'edit') {
|
||||||
await updateClam(dialogData.value.rowData)
|
await updateClam(dialogData.value.rowData)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -158,3 +388,31 @@ defineExpose({
|
|||||||
acceptParams,
|
acceptParams,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.specClass {
|
||||||
|
width: 20% !important;
|
||||||
|
margin-left: 20px;
|
||||||
|
.append {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 1000px) {
|
||||||
|
.specClass {
|
||||||
|
width: 100% !important;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-left: 0;
|
||||||
|
.append {
|
||||||
|
width: 43px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.specTypeClass {
|
||||||
|
width: 22% !important;
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 1000px) {
|
||||||
|
.specTypeClass {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user