mirror of
https://gitee.com/fit2cloud-feizhiyun/1Panel.git
synced 2024-12-05 13:29:11 +08:00
feat: ClamAV 增加病毒文件处理策略 (#5635)
This commit is contained in:
parent
d71514885d
commit
f131aae344
@ -210,18 +210,18 @@ func (b *BaseApi) UpdateFile(c *gin.Context) {
|
||||
// @Summary Delete clam
|
||||
// @Description 删除扫描规则
|
||||
// @Accept json
|
||||
// @Param request body dto.BatchDeleteReq true "request"
|
||||
// @Param request body dto.ClamDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /toolbox/clam/del [post]
|
||||
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"clams","output_column":"name","output_value":"names"}],"formatZH":"删除扫描规则 [names]","formatEN":"delete clam [names]"}
|
||||
func (b *BaseApi) DeleteClam(c *gin.Context) {
|
||||
var req dto.BatchDeleteReq
|
||||
var req dto.ClamDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := clamService.Delete(req.Ids); err != nil {
|
||||
if err := clamService.Delete(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
@ -14,10 +14,12 @@ type ClamInfo struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
LastHandleDate string `json:"lastHandleDate"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
LastHandleDate string `json:"lastHandleDate"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type ClamLogSearch struct {
|
||||
@ -38,15 +40,25 @@ type ClamLog struct {
|
||||
}
|
||||
|
||||
type ClamCreate struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type ClamUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
InfectedStrategy string `json:"infectedStrategy"`
|
||||
InfectedDir string `json:"infectedDir"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type ClamDelete struct {
|
||||
RemoveResult bool `json:"removeResult"`
|
||||
RemoveInfected bool `json:"removeInfected"`
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package model
|
||||
type Clam struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"type:varchar(64);not null" json:"name"`
|
||||
Path string `gorm:"type:varchar(64);not null" json:"path"`
|
||||
Description string `gorm:"type:varchar(64);not null" json:"description"`
|
||||
Name string `gorm:"type:varchar(64);not null" json:"name"`
|
||||
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"`
|
||||
Description string `gorm:"type:varchar(64)" json:"description"`
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import (
|
||||
const (
|
||||
clamServiceNameCentOs = "clamd@scan.service"
|
||||
clamServiceNameUbuntu = "clamav-daemon.service"
|
||||
scanDir = "scan-result"
|
||||
resultDir = "clamav"
|
||||
)
|
||||
|
||||
type ClamService struct {
|
||||
@ -39,7 +39,7 @@ type IClamService interface {
|
||||
SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error)
|
||||
Create(req dto.ClamCreate) error
|
||||
Update(req dto.ClamUpdate) error
|
||||
Delete(ids []uint) error
|
||||
Delete(req dto.ClamDelete) error
|
||||
HandleOnce(req dto.OperateByID) error
|
||||
LoadFile(req dto.OperationWithName) (string, error)
|
||||
UpdateFile(req dto.UpdateByNameAndFile) error
|
||||
@ -154,15 +154,21 @@ func (f *ClamService) Update(req dto.ClamUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ClamService) Delete(ids []uint) error {
|
||||
if len(ids) == 1 {
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByID(ids[0]))
|
||||
func (u *ClamService) Delete(req dto.ClamDelete) error {
|
||||
for _, id := range req.Ids {
|
||||
clam, _ := clamRepo.Get(commonRepo.WithByID(id))
|
||||
if clam.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
continue
|
||||
}
|
||||
return clamRepo.Delete(commonRepo.WithByID(ids[0]))
|
||||
if req.RemoveResult {
|
||||
_ = os.RemoveAll(path.Join(global.CONF.System.DataDir, resultDir, clam.Name))
|
||||
}
|
||||
if req.RemoveInfected {
|
||||
_ = os.RemoveAll(path.Join(clam.InfectedDir, "1panel-infected", clam.Name))
|
||||
}
|
||||
return clamRepo.Delete(commonRepo.WithByID(id))
|
||||
}
|
||||
return clamRepo.Delete(commonRepo.WithIdsIn(ids))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ClamService) HandleOnce(req dto.OperateByID) error {
|
||||
@ -173,12 +179,30 @@ func (u *ClamService) HandleOnce(req dto.OperateByID) error {
|
||||
if cmd.CheckIllegal(clam.Path) {
|
||||
return buserr.New(constant.ErrCmdIllegal)
|
||||
}
|
||||
logFile := path.Join(global.CONF.System.DataDir, scanDir, clam.Name, time.Now().Format(constant.DateTimeSlimLayout))
|
||||
timeNow := time.Now().Format(constant.DateTimeSlimLayout)
|
||||
logFile := path.Join(global.CONF.System.DataDir, resultDir, clam.Name, timeNow)
|
||||
if _, err := os.Stat(path.Dir(logFile)); err != nil {
|
||||
_ = os.MkdirAll(path.Dir(logFile), os.ModePerm)
|
||||
}
|
||||
go func() {
|
||||
cmd := exec.Command("clamdscan", "--fdpass", clam.Path, "-l", logFile)
|
||||
strategy := ""
|
||||
switch clam.InfectedStrategy {
|
||||
case "remove":
|
||||
strategy = "--remove"
|
||||
case "move":
|
||||
dir := path.Join(clam.InfectedDir, "1panel-infected", clam.Name, timeNow)
|
||||
strategy = "--move=" + dir
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
_ = os.MkdirAll(dir, os.ModePerm)
|
||||
}
|
||||
case "copy":
|
||||
dir := path.Join(clam.InfectedDir, "1panel-infected", clam.Name, timeNow)
|
||||
strategy = "--copy=" + dir
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
_ = os.MkdirAll(dir, os.ModePerm)
|
||||
}
|
||||
}
|
||||
cmd := exec.Command("clamdscan", "--fdpass", strategy, clam.Path, "-l", logFile)
|
||||
_, _ = cmd.CombinedOutput()
|
||||
}()
|
||||
return nil
|
||||
@ -226,7 +250,7 @@ func (u *ClamService) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, er
|
||||
|
||||
var datas []dto.ClamLog
|
||||
for i := 0; i < len(records); i++ {
|
||||
item := loadResultFromLog(path.Join(global.CONF.System.DataDir, scanDir, clam.Name, records[i]))
|
||||
item := loadResultFromLog(path.Join(global.CONF.System.DataDir, resultDir, clam.Name, records[i]))
|
||||
datas = append(datas, item)
|
||||
}
|
||||
return int64(total), datas, nil
|
||||
@ -237,7 +261,7 @@ func (u *ClamService) CleanRecord(req dto.OperateByID) error {
|
||||
if clam.ID == 0 {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
pathItem := path.Join(global.CONF.System.DataDir, scanDir, clam.Name)
|
||||
pathItem := path.Join(global.CONF.System.DataDir, resultDir, clam.Name)
|
||||
_ = os.RemoveAll(pathItem)
|
||||
return nil
|
||||
}
|
||||
@ -319,7 +343,7 @@ func (u *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
||||
|
||||
func loadFileByName(name string) []string {
|
||||
var logPaths []string
|
||||
pathItem := path.Join(global.CONF.System.DataDir, scanDir, name)
|
||||
pathItem := path.Join(global.CONF.System.DataDir, resultDir, name)
|
||||
_ = filepath.Walk(pathItem, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
|
@ -270,7 +270,7 @@ var AddShellColumn = &gormigrate.Migration{
|
||||
}
|
||||
|
||||
var AddClam = &gormigrate.Migration{
|
||||
ID: "20240624-add-clam",
|
||||
ID: "20240701-add-clam",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Clam{}); err != nil {
|
||||
return err
|
||||
|
@ -72,7 +72,7 @@ func (sws *LocalWsSession) masterWrite(data []byte) error {
|
||||
func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
global.LOG.Errorf("[xpack] A panic occurred during receive ws message, error message: %v", r)
|
||||
global.LOG.Errorf("A panic occurred during receive ws message, error message: %v", r)
|
||||
}
|
||||
}()
|
||||
wsConn := sws.wsConn
|
||||
|
@ -118,7 +118,7 @@ func (sws *LogicSshWsSession) Start(quitChan chan bool) {
|
||||
func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
global.LOG.Errorf("[xpack] A panic occurred during receive ws message, error message: %v", r)
|
||||
global.LOG.Errorf("[A panic occurred during receive ws message, error message: %v", r)
|
||||
}
|
||||
}()
|
||||
wsConn := sws.wsConn
|
||||
|
@ -1585,6 +1585,12 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/containers/download/log": {
|
||||
"post": {
|
||||
"description": "下载容器日志",
|
||||
"responses": {}
|
||||
}
|
||||
},
|
||||
"/containers/image": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -11155,7 +11161,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.BatchDeleteReq"
|
||||
"$ref": "#/definitions/dto.ClamDelete"
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -15460,6 +15466,12 @@ const docTemplate = `{
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"infectedDir": {
|
||||
"type": "string"
|
||||
},
|
||||
"infectedStrategy": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -15468,6 +15480,26 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ClamDelete": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ids"
|
||||
],
|
||||
"properties": {
|
||||
"ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"removeInfected": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"removeResult": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ClamLogSearch": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -15501,6 +15533,12 @@ const docTemplate = `{
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"infectedDir": {
|
||||
"type": "string"
|
||||
},
|
||||
"infectedStrategy": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -20116,6 +20154,9 @@ const docTemplate = `{
|
||||
"domains": {
|
||||
"type": "string"
|
||||
},
|
||||
"execShell": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"expireDate": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -20152,6 +20193,9 @@ const docTemplate = `{
|
||||
"pushDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"skipDNS": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -21663,6 +21707,9 @@ const docTemplate = `{
|
||||
"domains": {
|
||||
"type": "string"
|
||||
},
|
||||
"execShell": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
@ -21683,6 +21730,9 @@ const docTemplate = `{
|
||||
"renew": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"sslID": {
|
||||
"type": "integer"
|
||||
},
|
||||
@ -22242,6 +22292,9 @@ const docTemplate = `{
|
||||
"dnsAccountId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"execShell": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
@ -22266,6 +22319,9 @@ const docTemplate = `{
|
||||
"pushDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"skipDNS": {
|
||||
"type": "boolean"
|
||||
}
|
||||
@ -22318,6 +22374,9 @@ const docTemplate = `{
|
||||
"dnsAccountId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"execShell": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
@ -22342,6 +22401,9 @@ const docTemplate = `{
|
||||
"pushDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"skipDNS": {
|
||||
"type": "boolean"
|
||||
}
|
||||
@ -22786,9 +22848,15 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/response.FileTree"
|
||||
}
|
||||
},
|
||||
"extension": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"isDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -1578,6 +1578,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/containers/download/log": {
|
||||
"post": {
|
||||
"description": "下载容器日志",
|
||||
"responses": {}
|
||||
}
|
||||
},
|
||||
"/containers/image": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -11148,7 +11154,7 @@
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.BatchDeleteReq"
|
||||
"$ref": "#/definitions/dto.ClamDelete"
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -15453,6 +15459,12 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"infectedDir": {
|
||||
"type": "string"
|
||||
},
|
||||
"infectedStrategy": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -15461,6 +15473,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ClamDelete": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ids"
|
||||
],
|
||||
"properties": {
|
||||
"ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"removeInfected": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"removeResult": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.ClamLogSearch": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -15494,6 +15526,12 @@
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"infectedDir": {
|
||||
"type": "string"
|
||||
},
|
||||
"infectedStrategy": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -20109,6 +20147,9 @@
|
||||
"domains": {
|
||||
"type": "string"
|
||||
},
|
||||
"execShell": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"expireDate": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -20145,6 +20186,9 @@
|
||||
"pushDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"skipDNS": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@ -21656,6 +21700,9 @@
|
||||
"domains": {
|
||||
"type": "string"
|
||||
},
|
||||
"execShell": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
@ -21676,6 +21723,9 @@
|
||||
"renew": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"sslID": {
|
||||
"type": "integer"
|
||||
},
|
||||
@ -22235,6 +22285,9 @@
|
||||
"dnsAccountId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"execShell": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
@ -22259,6 +22312,9 @@
|
||||
"pushDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"skipDNS": {
|
||||
"type": "boolean"
|
||||
}
|
||||
@ -22311,6 +22367,9 @@
|
||||
"dnsAccountId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"execShell": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
@ -22335,6 +22394,9 @@
|
||||
"pushDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"shell": {
|
||||
"type": "string"
|
||||
},
|
||||
"skipDNS": {
|
||||
"type": "boolean"
|
||||
}
|
||||
@ -22779,9 +22841,15 @@
|
||||
"$ref": "#/definitions/response.FileTree"
|
||||
}
|
||||
},
|
||||
"extension": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"isDir": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -229,11 +229,28 @@ definitions:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
infectedDir:
|
||||
type: string
|
||||
infectedStrategy:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
type: object
|
||||
dto.ClamDelete:
|
||||
properties:
|
||||
ids:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
removeInfected:
|
||||
type: boolean
|
||||
removeResult:
|
||||
type: boolean
|
||||
required:
|
||||
- ids
|
||||
type: object
|
||||
dto.ClamLogSearch:
|
||||
properties:
|
||||
clamID:
|
||||
@ -256,6 +273,10 @@ definitions:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
infectedDir:
|
||||
type: string
|
||||
infectedStrategy:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
@ -3368,6 +3389,8 @@ definitions:
|
||||
type: integer
|
||||
domains:
|
||||
type: string
|
||||
execShell:
|
||||
type: boolean
|
||||
expireDate:
|
||||
type: string
|
||||
id:
|
||||
@ -3392,6 +3415,8 @@ definitions:
|
||||
type: string
|
||||
pushDir:
|
||||
type: boolean
|
||||
shell:
|
||||
type: string
|
||||
skipDNS:
|
||||
type: boolean
|
||||
startDate:
|
||||
@ -4401,6 +4426,8 @@ definitions:
|
||||
type: string
|
||||
domains:
|
||||
type: string
|
||||
execShell:
|
||||
type: boolean
|
||||
id:
|
||||
type: integer
|
||||
keyType:
|
||||
@ -4416,6 +4443,8 @@ definitions:
|
||||
type: boolean
|
||||
renew:
|
||||
type: boolean
|
||||
shell:
|
||||
type: string
|
||||
sslID:
|
||||
type: integer
|
||||
time:
|
||||
@ -4793,6 +4822,8 @@ definitions:
|
||||
type: boolean
|
||||
dnsAccountId:
|
||||
type: integer
|
||||
execShell:
|
||||
type: boolean
|
||||
id:
|
||||
type: integer
|
||||
keyType:
|
||||
@ -4809,6 +4840,8 @@ definitions:
|
||||
type: string
|
||||
pushDir:
|
||||
type: boolean
|
||||
shell:
|
||||
type: string
|
||||
skipDNS:
|
||||
type: boolean
|
||||
required:
|
||||
@ -4844,6 +4877,8 @@ definitions:
|
||||
type: boolean
|
||||
dnsAccountId:
|
||||
type: integer
|
||||
execShell:
|
||||
type: boolean
|
||||
id:
|
||||
type: integer
|
||||
keyType:
|
||||
@ -4860,6 +4895,8 @@ definitions:
|
||||
type: string
|
||||
pushDir:
|
||||
type: boolean
|
||||
shell:
|
||||
type: string
|
||||
skipDNS:
|
||||
type: boolean
|
||||
required:
|
||||
@ -5162,8 +5199,12 @@ definitions:
|
||||
items:
|
||||
$ref: '#/definitions/response.FileTree'
|
||||
type: array
|
||||
extension:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
isDir:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
@ -6414,6 +6455,10 @@ paths:
|
||||
summary: Load docker status
|
||||
tags:
|
||||
- Container Docker
|
||||
/containers/download/log:
|
||||
post:
|
||||
description: 下载容器日志
|
||||
responses: {}
|
||||
/containers/image:
|
||||
get:
|
||||
description: 获取镜像名称列表
|
||||
@ -12471,7 +12516,7 @@ paths:
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.BatchDeleteReq'
|
||||
$ref: '#/definitions/dto.ClamDelete'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
|
@ -118,7 +118,7 @@ export namespace Toolbox {
|
||||
}
|
||||
|
||||
export interface ClamBaseInfo {
|
||||
version: string;
|
||||
version: string;
|
||||
isActive: boolean;
|
||||
isExist: boolean;
|
||||
}
|
||||
@ -126,18 +126,24 @@ export namespace Toolbox {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
infectedStrategy: string;
|
||||
infectedDir: string;
|
||||
lastHandleDate: string;
|
||||
description: string;
|
||||
}
|
||||
export interface ClamCreate {
|
||||
name: string;
|
||||
path: string;
|
||||
infectedStrategy: string;
|
||||
infectedDir: string;
|
||||
description: string;
|
||||
}
|
||||
export interface ClamUpdate {
|
||||
id: number;
|
||||
name: string;
|
||||
path: string;
|
||||
infectedStrategy: string;
|
||||
infectedDir: string;
|
||||
description: string;
|
||||
}
|
||||
export interface ClamSearchLog extends ReqPage {
|
||||
|
@ -135,7 +135,7 @@ export const createClam = (params: Toolbox.ClamCreate) => {
|
||||
export const updateClam = (params: Toolbox.ClamUpdate) => {
|
||||
return http.post(`/toolbox/clam/update`, params);
|
||||
};
|
||||
export const deleteClam = (params: { ids: number[] }) => {
|
||||
export const deleteClam = (params: { ids: number[]; removeResult: boolean; removeInfected: boolean }) => {
|
||||
return http.post(`/toolbox/clam/del`, params);
|
||||
};
|
||||
export const handleClamScan = (id: number) => {
|
||||
|
@ -1066,18 +1066,36 @@ const message = {
|
||||
},
|
||||
clam: {
|
||||
clam: 'Virus Scan',
|
||||
clamHelper:
|
||||
'The minimum recommended configuration for ClamAV is: 3 GiB of RAM or more, single-core CPU with 2.0 GHz or higher, and at least 5 GiB of available hard disk space.',
|
||||
noClam: 'ClamAV service not detected, please refer to the official documentation for installation!',
|
||||
notStart: 'ClamAV service is currently not running, please start it first!',
|
||||
clamCreate: 'Create Scan Rule',
|
||||
removeResult: 'Delete Report Files',
|
||||
removeResultHelper: 'Delete report files generated during task execution to free up storage space.',
|
||||
removeInfected: 'Delete Virus Files',
|
||||
removeInfectedHelper:
|
||||
'Delete virus files detected during the task to ensure server security and normal operation.',
|
||||
clamCreate: 'Create Scan Rules',
|
||||
infectedStrategy: 'Virus Strategy',
|
||||
remove: 'Delete',
|
||||
removeHelper: 'Delete virus files, choose carefully!',
|
||||
move: 'Move',
|
||||
moveHelper: 'Move virus files to specified directory',
|
||||
copy: 'Copy',
|
||||
copyHelper: 'Copy virus files to specified directory',
|
||||
none: 'Do Nothing',
|
||||
noneHelper: 'Take no action on virus files',
|
||||
scanDir: 'Scan Directory',
|
||||
infectedDir: 'Infected Directory',
|
||||
scanDate: 'Scan Date',
|
||||
scanTime: 'Elapsed Time',
|
||||
scannedFiles: 'Number of Scanned Files',
|
||||
infectedFiles: 'Number of Infected Files',
|
||||
scanTime: 'Time Taken',
|
||||
scannedFiles: 'Scanned Files',
|
||||
infectedFiles: 'Infected Files',
|
||||
log: 'Details',
|
||||
clamConf: 'Scan Configuration',
|
||||
clamLog: 'Scan Log',
|
||||
freshClam: 'Virus Database Refresh Configuration',
|
||||
freshClamLog: 'Virus Database Refresh Log',
|
||||
clamLog: 'Scan Logs',
|
||||
freshClam: 'Update Virus Definitions',
|
||||
freshClamLog: 'Update Virus Definitions Logs',
|
||||
},
|
||||
},
|
||||
logs: {
|
||||
|
@ -1008,9 +1008,26 @@ const message = {
|
||||
},
|
||||
clam: {
|
||||
clam: '病毒掃描',
|
||||
clamHelper:
|
||||
'ClamAV 的最低建議配置為:3 GiB 以上的 RAM,2.0 GHz 以上的單核 CPU,以及至少 5 GiB 的可用硬盤空間。',
|
||||
noClam: '未檢測到 ClamAV 服務,請參考官方文檔進行安裝!',
|
||||
notStart: '目前沒有運行 ClamAV 服務,請先啟動!',
|
||||
notStart: '當前未 ClamAV 服務,請先開啟!',
|
||||
removeResult: '刪除報告文件',
|
||||
removeResultHelper: '刪除任務執行過程中生成的報告文件,以清理存儲空間。',
|
||||
removeInfected: '刪除病毒文件',
|
||||
removeInfectedHelper: '刪除任務檢測到的病毒文件,以確保伺服器的安全和正常運行。',
|
||||
clamCreate: '創建掃描規則',
|
||||
infectedStrategy: '病毒策略',
|
||||
remove: '刪除',
|
||||
removeHelper: '刪除病毒文件,請謹慎選擇!',
|
||||
move: '移動',
|
||||
moveHelper: '將病毒文件移動到指定目錄下',
|
||||
copy: '複製',
|
||||
copyHelper: '將病毒文件複製到指定目錄下',
|
||||
none: '不操作',
|
||||
noneHelper: '不對病毒文件採取任何操作',
|
||||
scanDir: '掃描目錄',
|
||||
infectedDir: '病毒目錄',
|
||||
scanDate: '掃描時間',
|
||||
scanTime: '耗時',
|
||||
scannedFiles: '掃描文件數',
|
||||
|
@ -1009,9 +1009,26 @@ const message = {
|
||||
},
|
||||
clam: {
|
||||
clam: '病毒扫描',
|
||||
clamHelper:
|
||||
'ClamAV 的最低建议配置为:3 GiB 以上的 RAM,2.0 GHz 以上的单核 CPU,以及至少 5 GiB 的可用硬盘空间。',
|
||||
noClam: '未检测到 ClamAV 服务,请参考官方文档进行安装!',
|
||||
notStart: '当前未 ClamAV 服务,请先开启!',
|
||||
removeResult: '删除报告文件',
|
||||
removeResultHelper: '删除任务执行过程中生成的报告文件,以清理存储空间。',
|
||||
removeInfected: '删除病毒文件',
|
||||
removeInfectedHelper: '删除任务检测到的病毒文件,以确保服务器的安全和正常运行。',
|
||||
clamCreate: '创建扫描规则',
|
||||
infectedStrategy: '病毒策略',
|
||||
remove: '删除',
|
||||
removeHelper: '删除病毒文件,请谨慎选择!',
|
||||
move: '移动',
|
||||
moveHelper: '将病毒文件移动到指定目录下',
|
||||
copy: '复制',
|
||||
copyHelper: '将病毒文件复制到指定目录下',
|
||||
none: '不操作',
|
||||
noneHelper: '不对病毒文件采取任何操作',
|
||||
scanDir: '扫描目录',
|
||||
infectedDir: '病毒目录',
|
||||
scanDate: '扫描时间',
|
||||
scanTime: '耗时',
|
||||
scannedFiles: '扫描文件数',
|
||||
|
@ -1,6 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<LayoutContent v-loading="loading" v-if="!isRecordShow && !isSettingShow" :title="$t('toolbox.clam.clam')">
|
||||
<template #prompt>
|
||||
<el-alert type="info" :closable="false">
|
||||
<template #title>
|
||||
{{ $t('toolbox.clam.clamHelper') }}
|
||||
<el-link
|
||||
style="font-size: 12px; margin-left: 5px"
|
||||
icon="Position"
|
||||
@click="toDoc()"
|
||||
type="primary"
|
||||
>
|
||||
{{ $t('firewall.quickJump') }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-alert>
|
||||
</template>
|
||||
<template #app>
|
||||
<ClamStatus
|
||||
@setting="setting"
|
||||
@ -28,7 +43,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<el-card v-if="!clamStatus.isRunning && maskShow" class="mask-prompt">
|
||||
<el-card v-if="clamStatus.isExist && !clamStatus.isRunning && maskShow" class="mask-prompt">
|
||||
<span>{{ $t('toolbox.clam.notStart') }}</span>
|
||||
</el-card>
|
||||
<template #main v-if="clamStatus.isExist">
|
||||
@ -48,9 +63,30 @@
|
||||
prop="name"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column :label="$t('file.path')" :min-width="120" prop="path" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
:label="$t('toolbox.clam.scanDir')"
|
||||
:min-width="120"
|
||||
prop="path"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button text type="primary" @click="toFolder(row.path)">{{ row.path }}</el-button>
|
||||
<el-button link type="primary" @click="toFolder(row.path)">{{ row.path }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('toolbox.clam.infectedDir')"
|
||||
:min-width="120"
|
||||
prop="path"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="toFolder(row.infectedDir + '/1panel-infected/' + row.name)"
|
||||
>
|
||||
{{ row.infectedDir + '/1panel-infected/' + row.name }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@ -75,7 +111,20 @@
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<OpDialog ref="opRef" @search="search" @submit="onSubmitDelete()" />
|
||||
<OpDialog ref="opRef" @search="search" @submit="onSubmitDelete()">
|
||||
<template #content>
|
||||
<el-form class="mt-4 mb-1" ref="deleteForm" label-position="left">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="removeResult" :label="$t('toolbox.clam.removeResult')" />
|
||||
<span class="input-help">{{ $t('toolbox.clam.removeResultHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="removeInfected" :label="$t('toolbox.clam.removeInfected')" />
|
||||
<span class="input-help">{{ $t('toolbox.clam.removeInfectedHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</OpDialog>
|
||||
<OperateDialog @search="search" ref="dialogRef" />
|
||||
<LogDialog ref="dialogLogRef" />
|
||||
<SettingDialog v-if="isSettingShow" />
|
||||
@ -114,6 +163,9 @@ const operateIDs = ref();
|
||||
const dialogLogRef = ref();
|
||||
const isRecordShow = ref();
|
||||
|
||||
const removeResult = ref();
|
||||
const removeInfected = ref();
|
||||
|
||||
const isSettingShow = ref();
|
||||
const maskShow = ref(true);
|
||||
const clamStatus = ref({
|
||||
@ -144,20 +196,27 @@ const setting = () => {
|
||||
};
|
||||
const getStatus = (status: any) => {
|
||||
clamStatus.value = status;
|
||||
console.log(clamStatus.value);
|
||||
search();
|
||||
};
|
||||
|
||||
const toFolder = (folder: string) => {
|
||||
router.push({ path: '/hosts/files', query: { path: folder } });
|
||||
};
|
||||
const toDoc = async () => {
|
||||
window.open('https://1panel.cn/docs/user_manual/toolbox/clam/', '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const onChange = async (row: any) => {
|
||||
await await updateClam(row);
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
};
|
||||
|
||||
const onOpenDialog = async (title: string, rowData: Partial<Toolbox.ClamInfo> = {}) => {
|
||||
const onOpenDialog = async (
|
||||
title: string,
|
||||
rowData: Partial<Toolbox.ClamInfo> = {
|
||||
infectedStrategy: 'none',
|
||||
},
|
||||
) => {
|
||||
let params = {
|
||||
title,
|
||||
rowData: { ...rowData },
|
||||
@ -182,7 +241,7 @@ const onDelete = async (row: Toolbox.ClamInfo | null) => {
|
||||
title: i18n.global.t('commons.button.delete'),
|
||||
names: names,
|
||||
msg: i18n.global.t('commons.msg.operatorHelper', [
|
||||
i18n.global.t('cronjob.cronTask'),
|
||||
i18n.global.t('toolbox.clam.clam'),
|
||||
i18n.global.t('commons.button.delete'),
|
||||
]),
|
||||
api: null,
|
||||
@ -192,7 +251,7 @@ const onDelete = async (row: Toolbox.ClamInfo | null) => {
|
||||
|
||||
const onSubmitDelete = async () => {
|
||||
loading.value = true;
|
||||
await deleteClam({ ids: operateIDs.value })
|
||||
await deleteClam({ ids: operateIDs.value, removeResult: removeResult.value, removeInfected: removeInfected.value })
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
|
@ -24,13 +24,32 @@
|
||||
v-model.trim="dialogData.rowData!.name"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('file.root')" prop="path">
|
||||
<el-form-item :label="$t('toolbox.clam.scanDir')" prop="path">
|
||||
<el-input v-model="dialogData.rowData!.path">
|
||||
<template #prepend>
|
||||
<FileList @choose="loadDir" :dir="true"></FileList>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('toolbox.clam.infectedStrategy')" prop="infectedStrategy">
|
||||
<el-radio-group v-model="dialogData.rowData!.infectedStrategy">
|
||||
<el-radio value="none">{{ $t('toolbox.clam.none') }}</el-radio>
|
||||
<el-radio value="remove">{{ $t('toolbox.clam.remove') }}</el-radio>
|
||||
<el-radio value="move">{{ $t('toolbox.clam.move') }}</el-radio>
|
||||
<el-radio value="copy">{{ $t('toolbox.clam.copy') }}</el-radio>
|
||||
</el-radio-group>
|
||||
<span class="input-help">
|
||||
{{ $t('toolbox.clam.' + dialogData.rowData!.infectedStrategy + 'Helper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="hasInfectedDir()" :label="$t('toolbox.clam.infectedDir')" prop="infectedDir">
|
||||
<el-input v-model="dialogData.rowData!.infectedDir">
|
||||
<template #prepend>
|
||||
<FileList @choose="loadInfectedDir" :dir="true"></FileList>
|
||||
</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>
|
||||
@ -90,9 +109,17 @@ const rules = reactive({
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const hasInfectedDir = () => {
|
||||
return (
|
||||
dialogData.value.rowData!.infectedStrategy === 'move' || dialogData.value.rowData!.infectedStrategy === 'copy'
|
||||
);
|
||||
};
|
||||
const loadDir = async (path: string) => {
|
||||
dialogData.value.rowData!.path = path;
|
||||
};
|
||||
const loadInfectedDir = async (path: string) => {
|
||||
dialogData.value.rowData!.infectedDir = path;
|
||||
};
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
|
@ -4,7 +4,7 @@
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag class="float-left" effect="dark" type="success">
|
||||
{{ dialogData.rowData.name }}
|
||||
{{ $t('commons.table.name') }}: {{ dialogData.rowData.name }}
|
||||
</el-tag>
|
||||
<el-popover
|
||||
v-if="dialogData.rowData.path.length >= 35"
|
||||
@ -15,7 +15,7 @@
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag style="float: left" effect="dark" type="success">
|
||||
{{ dialogData.rowData.path.substring(0, 20) }}...
|
||||
{{ $t('file.path') }}: {{ dialogData.rowData.path.substring(0, 20) }}...
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
@ -25,7 +25,7 @@
|
||||
effect="dark"
|
||||
type="success"
|
||||
>
|
||||
{{ dialogData.rowData.path }}
|
||||
{{ $t('file.path') }}: {{ dialogData.rowData.path }}
|
||||
</el-tag>
|
||||
|
||||
<span class="buttons">
|
||||
@ -115,9 +115,14 @@
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('toolbox.clam.infectedFiles') }}</span>
|
||||
</template>
|
||||
<span class="status-count">
|
||||
<span class="status-count" v-if="!hasInfectedDir()">
|
||||
{{ currentRecord?.infectedFiles }}
|
||||
</span>
|
||||
<div class="count" v-else>
|
||||
<span @click="toFolder(currentRecord?.name)">
|
||||
{{ currentRecord?.infectedFiles }}
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-row>
|
||||
<el-row v-if="currentRecord?.log">
|
||||
@ -167,6 +172,8 @@ import { MsgSuccess } from '@/utils/message';
|
||||
import { shortcuts } from '@/utils/shortcuts';
|
||||
import { Toolbox } from '@/api/interface/toolbox';
|
||||
import { cleanClamRecord, handleClamScan, searchClamRecord } from '@/api/modules/toolbox';
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
|
||||
const loading = ref();
|
||||
const refresh = ref(false);
|
||||
@ -212,6 +219,11 @@ const handleCurrentChange = (val: number) => {
|
||||
searchInfo.page = val;
|
||||
search();
|
||||
};
|
||||
const hasInfectedDir = () => {
|
||||
return (
|
||||
dialogData.value.rowData!.infectedStrategy === 'move' || dialogData.value.rowData!.infectedStrategy === 'copy'
|
||||
);
|
||||
};
|
||||
|
||||
const timeRangeLoad = ref<[Date, Date]>([
|
||||
new Date(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7).setHours(0, 0, 0, 0)),
|
||||
@ -239,6 +251,10 @@ const onHandle = async (row: Toolbox.ClamInfo) => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
const toFolder = async (path: string) => {
|
||||
let folder = dialogData.value.rowData!.infectedDir + '/1panel-infected/' + path;
|
||||
router.push({ path: '/hosts/files', query: { path: folder } });
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
if (timeRangeLoad.value && timeRangeLoad.value.length === 2) {
|
||||
@ -278,7 +294,6 @@ const onClean = async () => {
|
||||
type: 'warning',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
console.log(dialogData.value.id);
|
||||
cleanClamRecord(dialogData.value.rowData.id)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
@ -334,6 +349,16 @@ defineExpose({
|
||||
float: right;
|
||||
}
|
||||
|
||||
.count {
|
||||
span {
|
||||
font-size: 25px;
|
||||
color: $primary-color;
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1400px) {
|
||||
.mainClass {
|
||||
overflow: auto;
|
||||
|
@ -5,7 +5,7 @@
|
||||
<ClamStatus v-model:loading="loading" />
|
||||
</template>
|
||||
<template #title>
|
||||
<back-button name="Clam" header="Clamav">
|
||||
<back-button name="Clam" header="ClamAV">
|
||||
<template #buttons>
|
||||
<el-button type="primary" :plain="activeName !== 'clamd'" @click="search('clamd')">
|
||||
{{ $t('toolbox.clam.clamConf') }}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="app-status tool-status" v-if="data.isExist">
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag effect="dark" type="success">Clamav</el-tag>
|
||||
<el-tag effect="dark" type="success">ClamAV</el-tag>
|
||||
<el-tag round class="status-content" v-if="data.isActive" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
@ -75,7 +75,7 @@ const toDoc = async () => {
|
||||
const onOperate = async (operation: string) => {
|
||||
em('update:maskShow', false);
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('commons.msg.operatorHelper', [' Clamav ', i18n.global.t('app.' + operation)]),
|
||||
i18n.global.t('commons.msg.operatorHelper', [' ClamAV ', i18n.global.t('app.' + operation)]),
|
||||
i18n.global.t('app.' + operation),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
|
Loading…
Reference in New Issue
Block a user