feat: ClamAV 增加病毒文件处理策略 (#5635)

This commit is contained in:
ssongliu 2024-07-02 17:25:25 +08:00 committed by GitHub
parent d71514885d
commit f131aae344
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 450 additions and 62 deletions

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"
},

View File

@ -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"
},

View File

@ -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

View File

@ -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 {

View File

@ -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) => {

View File

@ -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: {

View File

@ -1008,9 +1008,26 @@ const message = {
},
clam: {
clam: '病毒掃描',
clamHelper:
'ClamAV 的最低建議配置為3 GiB 以上的 RAM2.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: '掃描文件數',

View File

@ -1009,9 +1009,26 @@ const message = {
},
clam: {
clam: '病毒扫描',
clamHelper:
'ClamAV 的最低建议配置为3 GiB 以上的 RAM2.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: '扫描文件数',

View File

@ -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'));

View File

@ -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;

View File

@ -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;

View File

@ -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') }}

View File

@ -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'),