mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 00:09:16 +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)
|
||||
}
|
||||
|
||||
// @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
|
||||
// @Summary Page clam
|
||||
// @Description 获取扫描规则列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchWithPage true "request"
|
||||
// @Param request body dto.SearchClamWithPage true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/clam/search [post]
|
||||
func (b *BaseApi) SearchClam(c *gin.Context) {
|
||||
var req dto.SearchWithPage
|
||||
var req dto.SearchClamWithPage
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -4,6 +4,13 @@ import (
|
||||
"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 {
|
||||
Version string `json:"version"`
|
||||
IsActive bool `json:"isActive"`
|
||||
@ -19,10 +26,12 @@ type ClamInfo struct {
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Path string `json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
LastHandleDate string `json:"lastHandleDate"`
|
||||
Spec string `json:"spec"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@ -56,9 +65,11 @@ type ClamLog struct {
|
||||
|
||||
type ClamCreate struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Path string `json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
Spec string `json:"spec"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@ -69,9 +80,15 @@ type ClamUpdate struct {
|
||||
Path string `json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
Spec string `json:"spec"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type ClamUpdateStatus struct {
|
||||
ID uint `json:"id"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type ClamDelete struct {
|
||||
RemoveRecord bool `json:"removeRecord"`
|
||||
RemoveInfected bool `json:"removeInfected"`
|
||||
|
@ -4,8 +4,11 @@ type Clam struct {
|
||||
BaseModel
|
||||
|
||||
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"`
|
||||
InfectedStrategy string `gorm:"type:varchar(64)" json:"infectedStrategy"`
|
||||
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"`
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ type IClamRepo interface {
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
Delete(opts ...DBOption) error
|
||||
Get(opts ...DBOption) (model.Clam, error)
|
||||
List(opts ...DBOption) ([]model.Clam, error)
|
||||
}
|
||||
|
||||
func NewIClamRepo() IClamRepo {
|
||||
@ -29,6 +30,16 @@ func (u *ClamRepo) Get(opts ...DBOption) (model.Clam, error) {
|
||||
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) {
|
||||
var users []model.Clam
|
||||
db := global.DB.Model(&model.Clam{})
|
||||
|
@ -12,13 +12,16 @@ import (
|
||||
"time"
|
||||
|
||||
"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/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -37,9 +40,10 @@ type ClamService struct {
|
||||
type IClamService interface {
|
||||
LoadBaseInfo() (dto.ClamBaseInfo, error)
|
||||
Operate(operate string) error
|
||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error)
|
||||
Create(req dto.ClamCreate) error
|
||||
Update(req dto.ClamUpdate) error
|
||||
UpdateStatus(id uint, status string) error
|
||||
Delete(req dto.ClamDelete) error
|
||||
HandleOnce(req dto.OperateByID) error
|
||||
LoadFile(req dto.ClamFileReq) (string, error)
|
||||
@ -75,8 +79,7 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
baseInfo.FreshIsExist = true
|
||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
|
||||
}
|
||||
stdout, err := cmd.Exec("which clamdscan")
|
||||
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0 && strings.HasPrefix(stdout, "/")) {
|
||||
if !cmd.Which("clamdscan") {
|
||||
baseInfo.IsActive = false
|
||||
}
|
||||
|
||||
@ -122,8 +125,8 @@ func (c *ClamService) Operate(operate string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClamService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, commands, err := clamRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info))
|
||||
func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) {
|
||||
total, commands, err := clamRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
@ -164,6 +167,14 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
|
||||
if clam.InfectedStrategy == "none" || clam.InfectedStrategy == "remove" {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -178,11 +189,36 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
if req.InfectedStrategy == "none" || req.InfectedStrategy == "remove" {
|
||||
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{}{}
|
||||
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["path"] = req.Path
|
||||
upMap["infected_dir"] = req.InfectedDir
|
||||
upMap["infected_strategy"] = req.InfectedStrategy
|
||||
upMap["spec"] = req.Spec
|
||||
upMap["description"] = req.Description
|
||||
if err := clamRepo.Update(req.ID, upMap); err != nil {
|
||||
return err
|
||||
@ -190,6 +226,28 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
|
||||
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 {
|
||||
for _, id := range req.Ids {
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByID(id))
|
||||
|
@ -92,6 +92,7 @@ func Init() {
|
||||
migrations.AddForward,
|
||||
migrations.AddShellColumn,
|
||||
migrations.AddClam,
|
||||
migrations.AddClamStatus,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -278,3 +278,13 @@ var AddClam = &gormigrate.Migration{
|
||||
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/operate", baseApi.OperateClam)
|
||||
toolboxRouter.POST("/clam/update", baseApi.UpdateClam)
|
||||
toolboxRouter.POST("/clam/status/update", baseApi.UpdateClamStatus)
|
||||
toolboxRouter.POST("/clam/del", baseApi.DeleteClam)
|
||||
toolboxRouter.POST("/clam/handle", baseApi.HandleClamScan)
|
||||
}
|
||||
|
@ -203,8 +203,11 @@ func SudoHandleCmd() string {
|
||||
}
|
||||
|
||||
func Which(name string) bool {
|
||||
_, err := exec.LookPath(name)
|
||||
return err == nil
|
||||
stdout, err := Execf("which %s", name)
|
||||
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 {
|
||||
|
@ -7,6 +7,10 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"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) {}
|
||||
@ -27,3 +31,7 @@ func LoadRequestTransport() *http.Transport {
|
||||
func LoadGpuInfo() []interface{} {
|
||||
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": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -15570,6 +15622,12 @@ const docTemplate = `{
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"spec": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -15665,6 +15723,20 @@ const docTemplate = `{
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"spec": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ClamUpdateStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -18468,7 +18540,7 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"status",
|
||||
"state",
|
||||
"created_at"
|
||||
]
|
||||
},
|
||||
@ -22601,7 +22673,8 @@ const docTemplate = `{
|
||||
"primary_domain",
|
||||
"type",
|
||||
"status",
|
||||
"created_at"
|
||||
"created_at",
|
||||
"expire_date"
|
||||
]
|
||||
},
|
||||
"page": {
|
||||
@ -22619,8 +22692,7 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"primaryDomain",
|
||||
"webSiteGroupID"
|
||||
"primaryDomain"
|
||||
],
|
||||
"properties": {
|
||||
"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": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -15563,6 +15615,12 @@
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"spec": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -15658,6 +15716,20 @@
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"spec": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ClamUpdateStatus": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -18461,7 +18533,7 @@
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"name",
|
||||
"status",
|
||||
"state",
|
||||
"created_at"
|
||||
]
|
||||
},
|
||||
@ -22594,7 +22666,8 @@
|
||||
"primary_domain",
|
||||
"type",
|
||||
"status",
|
||||
"created_at"
|
||||
"created_at",
|
||||
"expire_date"
|
||||
]
|
||||
},
|
||||
"page": {
|
||||
@ -22612,8 +22685,7 @@
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"primaryDomain",
|
||||
"webSiteGroupID"
|
||||
"primaryDomain"
|
||||
],
|
||||
"properties": {
|
||||
"IPV6": {
|
||||
|
@ -243,6 +243,10 @@ definitions:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
spec:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
dto.ClamDelete:
|
||||
properties:
|
||||
@ -305,6 +309,15 @@ definitions:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
spec:
|
||||
type: string
|
||||
type: object
|
||||
dto.ClamUpdateStatus:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
dto.Clean:
|
||||
properties:
|
||||
@ -2198,7 +2211,7 @@ definitions:
|
||||
orderBy:
|
||||
enum:
|
||||
- name
|
||||
- status
|
||||
- state
|
||||
- created_at
|
||||
type: string
|
||||
page:
|
||||
@ -4974,6 +4987,7 @@ definitions:
|
||||
- type
|
||||
- status
|
||||
- created_at
|
||||
- expire_date
|
||||
type: string
|
||||
page:
|
||||
type: integer
|
||||
@ -5004,7 +5018,6 @@ definitions:
|
||||
required:
|
||||
- id
|
||||
- primaryDomain
|
||||
- webSiteGroupID
|
||||
type: object
|
||||
request.WebsiteUpdateDir:
|
||||
properties:
|
||||
@ -12767,6 +12780,40 @@ paths:
|
||||
summary: Page clam
|
||||
tags:
|
||||
- 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:
|
||||
post:
|
||||
consumes:
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ReqPage } from '.';
|
||||
import { Cronjob } from './cronjob';
|
||||
|
||||
export namespace Toolbox {
|
||||
export interface DeviceBaseInfo {
|
||||
@ -129,10 +130,14 @@ export namespace Toolbox {
|
||||
export interface ClamInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
status: string;
|
||||
path: string;
|
||||
infectedStrategy: string;
|
||||
infectedDir: string;
|
||||
lastHandleDate: string;
|
||||
hasSpec: boolean;
|
||||
spec: string;
|
||||
specObj: Cronjob.SpecObj;
|
||||
description: string;
|
||||
}
|
||||
export interface ClamCreate {
|
||||
@ -140,6 +145,8 @@ export namespace Toolbox {
|
||||
path: string;
|
||||
infectedStrategy: string;
|
||||
infectedDir: string;
|
||||
spec: string;
|
||||
specObj: Cronjob.SpecObj;
|
||||
description: string;
|
||||
}
|
||||
export interface ClamUpdate {
|
||||
@ -148,6 +155,8 @@ export namespace Toolbox {
|
||||
path: string;
|
||||
infectedStrategy: string;
|
||||
infectedDir: string;
|
||||
spec: string;
|
||||
specObj: Cronjob.SpecObj;
|
||||
description: string;
|
||||
}
|
||||
export interface ClamSearchLog extends ReqPage {
|
||||
|
@ -138,6 +138,9 @@ export const createClam = (params: Toolbox.ClamCreate) => {
|
||||
export const updateClam = (params: Toolbox.ClamUpdate) => {
|
||||
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 }) => {
|
||||
return http.post(`/toolbox/clam/del`, params);
|
||||
};
|
||||
|
@ -1082,6 +1082,13 @@ const message = {
|
||||
},
|
||||
clam: {
|
||||
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',
|
||||
hideFresh: 'Hide Virus Database Service',
|
||||
clamHelper:
|
||||
@ -1577,6 +1584,7 @@ const message = {
|
||||
recoverDetail: 'Recover detail',
|
||||
createSnapshot: 'Create Snapshot',
|
||||
importSnapshot: 'Sync Snapshot',
|
||||
importHelper: 'Snapshot directory:',
|
||||
recover: 'Recover',
|
||||
lastRecoverAt: 'Last recovery time',
|
||||
lastRollbackAt: 'Last rollback time',
|
||||
|
@ -1023,6 +1023,11 @@ const message = {
|
||||
},
|
||||
clam: {
|
||||
clam: '病毒掃描',
|
||||
cron: '定時掃描',
|
||||
cronHelper: '專業版支持定時掃描功能',
|
||||
specErr: '執行周期格式錯誤,請檢查後重試!',
|
||||
disableMsg: '停止定時執行會導致該掃描任務不再自動執行。是否繼續?',
|
||||
enableMsg: '啟用定時執行會讓該掃描任務定期自動執行。是否繼續?',
|
||||
showFresh: '顯示病毒庫服務',
|
||||
hideFresh: '隱藏病毒庫服務',
|
||||
clamHelper:
|
||||
@ -1395,6 +1400,7 @@ const message = {
|
||||
recoverDetail: '恢復詳情',
|
||||
createSnapshot: '創建快照',
|
||||
importSnapshot: '同步快照',
|
||||
importHelper: '快照文件目錄:',
|
||||
recover: '恢復',
|
||||
lastRecoverAt: '上次恢復時間',
|
||||
lastRollbackAt: '上次回滾時間',
|
||||
|
@ -1024,6 +1024,11 @@ const message = {
|
||||
},
|
||||
clam: {
|
||||
clam: '病毒扫描',
|
||||
cron: '定时扫描',
|
||||
cronHelper: '专业版支持定时扫描功能 ',
|
||||
specErr: '执行周期格式错误,请检查后重试!',
|
||||
disableMsg: '停止定时执行会导致该扫描任务不再自动执行。是否继续?',
|
||||
enableMsg: '启用定时执行会让该扫描任务定期自动执行。是否继续?',
|
||||
showFresh: '显示病毒库服务',
|
||||
hideFresh: '隐藏病毒库服务',
|
||||
clamHelper:
|
||||
@ -1397,6 +1402,7 @@ const message = {
|
||||
recoverDetail: '恢复详情',
|
||||
createSnapshot: '创建快照',
|
||||
importSnapshot: '同步快照',
|
||||
importHelper: '快照文件目录:',
|
||||
recover: '恢复',
|
||||
lastRecoverAt: '上次恢复时间',
|
||||
lastRollbackAt: '上次回滚时间',
|
||||
|
@ -16,6 +16,10 @@
|
||||
:label="item.label"
|
||||
/>
|
||||
</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 :label="$t('commons.table.name')" prop="names">
|
||||
<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 { Rules } from '@/global/form-rules';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import router from '@/routers';
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const loading = ref();
|
||||
@ -65,6 +70,7 @@ const formRef = ref();
|
||||
const backupOptions = ref();
|
||||
const fileNames = ref();
|
||||
const existNames = ref();
|
||||
const backupPath = ref('');
|
||||
|
||||
const form = reactive({
|
||||
from: '',
|
||||
@ -102,6 +108,9 @@ const checkDisable = (val: string) => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const toFolder = async () => {
|
||||
router.push({ path: '/hosts/files', query: { path: backupPath.value } });
|
||||
};
|
||||
|
||||
const submitImport = async (formEl: FormInstance | undefined) => {
|
||||
loading.value = true;
|
||||
@ -131,6 +140,10 @@ const loadBackups = async () => {
|
||||
if (item.id !== 0) {
|
||||
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(() => {
|
||||
@ -148,3 +161,18 @@ defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</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,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
};
|
||||
const res = await searchSnapshotPage(params);
|
||||
cleanData.value = false;
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
loading.value = true;
|
||||
await searchSnapshotPage(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
cleanData.value = false;
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -56,6 +56,7 @@
|
||||
:label="$t('commons.table.name')"
|
||||
:min-width="60"
|
||||
prop="name"
|
||||
sortable
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
@ -74,6 +75,47 @@
|
||||
<el-button link type="primary" @click="toFolder(row.path)">{{ row.path }}</el-button>
|
||||
</template>
|
||||
</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
|
||||
:label="$t('toolbox.clam.infectedDir')"
|
||||
:min-width="120"
|
||||
@ -138,17 +180,22 @@
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
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 LogDialog from '@/views/toolbox/clam/record/index.vue';
|
||||
import ClamStatus from '@/views/toolbox/clam/status/index.vue';
|
||||
import SettingDialog from '@/views/toolbox/clam/setting/index.vue';
|
||||
import { Toolbox } from '@/api/interface/toolbox';
|
||||
import router from '@/routers';
|
||||
import { transSpecToStr } from '../../cronjob/helper';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const loading = ref();
|
||||
const selects = ref<any>([]);
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const { isProductPro } = storeToRefs(globalStore);
|
||||
const data = ref();
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'clam-page-size',
|
||||
@ -176,12 +223,16 @@ const clamStatus = ref({
|
||||
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;
|
||||
let params = {
|
||||
info: searchName.value,
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
orderBy: paginationConfig.orderBy,
|
||||
order: paginationConfig.order,
|
||||
};
|
||||
await searchClam(params)
|
||||
.then((res) => {
|
||||
@ -218,6 +269,14 @@ const onOpenDialog = async (
|
||||
title: string,
|
||||
rowData: Partial<Toolbox.ClamInfo> = {
|
||||
infectedStrategy: 'none',
|
||||
specObj: {
|
||||
specType: 'perDay',
|
||||
week: 1,
|
||||
day: 3,
|
||||
hour: 1,
|
||||
minute: 30,
|
||||
second: 30,
|
||||
},
|
||||
},
|
||||
) => {
|
||||
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 = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.handle'),
|
||||
|
@ -50,6 +50,77 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</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-input type="textarea" :rows="3" clearable v-model="dialogData.rowData!.description" />
|
||||
</el-form-item>
|
||||
@ -64,6 +135,7 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
<LicenseImport ref="licenseRef" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
@ -73,11 +145,18 @@ import { Rules } from '@/global/form-rules';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import LicenseImport from '@/components/license-import/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 { 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 {
|
||||
title: string;
|
||||
rowData?: Toolbox.ClamInfo;
|
||||
@ -92,6 +171,19 @@ const dialogData = ref<DialogProps>({
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
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);
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
@ -101,9 +193,97 @@ const handleClose = () => {
|
||||
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({
|
||||
name: [Rules.simpleName],
|
||||
path: [Rules.requiredInput, Rules.noSpace],
|
||||
spec: [
|
||||
{ validator: verifySpec, trigger: 'blur', required: true },
|
||||
{ validator: verifySpec, trigger: 'change', required: true },
|
||||
],
|
||||
});
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
@ -120,12 +300,62 @@ const loadDir = async (path: string) => {
|
||||
const loadInfectedDir = async (path: string) => {
|
||||
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) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
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') {
|
||||
await updateClam(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
@ -158,3 +388,31 @@ defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</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