fix: Add timeout settings for clam scanning (#10445)

Refs #10426
This commit is contained in:
ssongliu 2025-09-23 11:56:04 +08:00 committed by GitHub
parent ba6cdc6e10
commit 0138d481a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 73 additions and 12 deletions

View file

@ -33,6 +33,7 @@ type ClamInfo struct {
LastRecordStatus string `json:"lastRecordStatus"`
LastRecordTime string `json:"lastRecordTime"`
Spec string `json:"spec"`
Timeout uint `json:"timeout"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertMethod string `json:"alertMethod"`
@ -77,6 +78,7 @@ type ClamCreate struct {
InfectedStrategy string `json:"infectedStrategy"`
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
Timeout uint `json:"timeout"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
@ -91,6 +93,7 @@ type ClamUpdate struct {
InfectedStrategy string `json:"infectedStrategy"`
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
Timeout uint `json:"timeout"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`

View file

@ -10,6 +10,8 @@ type Clam struct {
InfectedStrategy string `json:"infectedStrategy"`
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
RetryTimes uint `json:"retryTimes"`
Timeout uint `json:"timeout"`
EntryID int `json:"entryID"`
Description string `json:"description"`

View file

@ -8,6 +8,7 @@ import (
"path"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
@ -19,6 +20,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"github.com/1Panel-dev/1Panel/agent/utils/clam"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/systemctl"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/jinzhu/copier"
@ -238,6 +240,7 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
upMap["infected_dir"] = req.InfectedDir
upMap["infected_strategy"] = req.InfectedStrategy
upMap["spec"] = req.Spec
upMap["timeout"] = req.Timeout
upMap["description"] = req.Description
if err := clamRepo.Update(req.ID, upMap); err != nil {
return err
@ -339,6 +342,9 @@ func (c *ClamService) SearchRecords(req dto.ClamLogSearch) (int64, interface{},
if clam.ID == 0 {
return 0, nil, buserr.New("ErrRecordNotFound")
}
loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
req.StartTime = req.StartTime.In(loc)
req.EndTime = req.EndTime.In(loc)
total, records, err := clamRepo.PageRecords(req.Page, req.PageSize, clamRepo.WithByClamID(req.ClamID), repo.WithByStatus(req.Status), repo.WithByCreatedAt(req.StartTime, req.EndTime))
if err != nil {

View file

@ -43,6 +43,7 @@ func InitAgentDB() {
migrations.InitLocalSSHShow,
migrations.InitRecordStatus,
migrations.AddShowNameForQuickJump,
migrations.AddTimeoutForClam,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View file

@ -583,3 +583,16 @@ var AddShowNameForQuickJump = &gormigrate.Migration{
return tx.AutoMigrate(&model.QuickJump{})
},
}
var AddTimeoutForClam = &gormigrate.Migration{
ID: "20250922-add-timeout-for-clam",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Clam{}); err != nil {
return err
}
if err := tx.Model(&model.Clam{}).Where("1 == 1").Updates(map[string]interface{}{"timeout": 18000}).Error; err != nil {
return err
}
return nil
},
}

View file

@ -33,7 +33,7 @@ func AddScanTask(taskItem *task.Task, clam model.Clam, timeNow string) {
strategy = fmt.Sprintf("--%s=%s", clam.InfectedStrategy, dir)
}
taskItem.Logf("clamdscan --fdpass %s %s", strategy, clam.Path)
mgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(10*time.Hour), cmd.WithTask(*taskItem))
mgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(time.Duration(clam.Timeout)*time.Second), cmd.WithTask(*taskItem))
stdout, err := mgr.RunWithStdoutBashCf("clamdscan --fdpass %s %s", strategy, clam.Path)
if err != nil {
return fmt.Errorf("clamdscan failed, stdout: %v, err: %v", stdout, err)

View file

@ -56,7 +56,7 @@ export namespace Cronjob {
retryTimes: number;
timeout: number;
timeoutItem: number;
timeoutUint: string;
timeoutUnit: string;
status: string;
secret: string;
hasAlert: boolean;

View file

@ -138,6 +138,9 @@ export namespace Toolbox {
hasSpec: boolean;
spec: string;
specObj: Cronjob.SpecObj;
timeout: number;
timeoutItem: number;
timeoutUnit: string;
description: string;
hasAlert: boolean;
alertCount: number;
@ -151,6 +154,7 @@ export namespace Toolbox {
infectedStrategy: string;
infectedDir: string;
spec: string;
timeout: number;
specObj: Cronjob.SpecObj;
description: string;
}
@ -161,6 +165,7 @@ export namespace Toolbox {
infectedStrategy: string;
infectedDir: string;
spec: string;
timeout: number;
specObj: Cronjob.SpecObj;
description: string;
}

View file

@ -510,6 +510,11 @@ export function transTimeUnit(val: string): any {
}
return val + i18n.global.t('commons.units.second');
}
export function splitTimeFromSecond(item: number): any {
if (item < 60) return { timeItem: item, timeUnit: 's' };
if (item < 3600) return { timeItem: item / 60, timeUnit: 'm' };
return { timeItem: item / 3600, timeUnit: 'h' };
}
export function splitHttp(url: string) {
if (url.indexOf('https://') != -1) {

View file

@ -702,7 +702,7 @@
<el-form-item :label="$t('cronjob.timeout')" prop="timeoutItem">
<el-input type="number" class="selectClass" v-model.number="form.timeoutItem">
<template #append>
<el-select v-model="form.timeoutUint" style="width: 80px">
<el-select v-model="form.timeoutUnit" style="width: 80px">
<el-option :label="$t('commons.units.second')" value="s" />
<el-option :label="$t('commons.units.minute')" value="m" />
<el-option :label="$t('commons.units.hour')" value="h" />
@ -779,7 +779,7 @@ import { loadContainerUsers } from '@/api/modules/container';
import { storeToRefs } from 'pinia';
import { GlobalStore } from '@/store';
import LicenseImport from '@/components/license-import/index.vue';
import { transferTimeToSecond } from '@/utils/util';
import { splitTimeFromSecond, transferTimeToSecond } from '@/utils/util';
import { getGroupList } from '@/api/modules/group';
import { routerToName, routerToPath } from '@/utils/router';
const router = useRouter();
@ -845,7 +845,7 @@ const form = reactive<Cronjob.CronjobInfo>({
retryTimes: 3,
timeout: 3600,
timeoutItem: 3600,
timeoutUint: 's',
timeoutUnit: 's',
status: '',
secret: '',
hasAlert: false,
@ -935,8 +935,12 @@ const search = async () => {
form.ignoreErr = res.data.ignoreErr;
form.retainCopies = res.data.retainCopies;
form.retryTimes = res.data.retryTimes;
form.timeout = res.data.timeout;
form.timeoutItem = res.data.timeout || 3600;
form.timeout = res.data.timeout || 3600;
let item = splitTimeFromSecond(form.timeout);
form.timeoutItem = item.timeItem;
form.timeoutUnit = item.timeUnit;
form.secret = res.data.secret;
form.hasAlert = res.data.alertCount > 0;
form.alertCount = res.data.alertCount || 3;
@ -1138,7 +1142,7 @@ const rules = reactive({
retainCopies: [Rules.number],
retryTimes: [Rules.number],
timeoutItem: [Rules.number],
timeoutUint: [Rules.requiredSelect],
timeoutUnit: [Rules.requiredSelect],
alertCount: [Rules.integerNumber, { validator: checkSendCount, trigger: 'blur' }],
alertMethodItems: [Rules.requiredSelect],
});
@ -1420,7 +1424,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!form.inContainer) {
form.containerName = '';
}
form.timeout = transferTimeToSecond(form.timeoutItem + form.timeoutUint);
form.timeout = transferTimeToSecond(form.timeoutItem + form.timeoutUnit);
if (form.appIdList) {
form.appID = form.appIdList.join(',');
}

View file

@ -48,7 +48,7 @@
<el-form-item :label="$t('cronjob.timeout')" prop="timeoutItem">
<el-input type="number" class="selectClass" v-model.number="form.timeoutItem">
<template #append>
<el-select v-model="form.timeoutUint" style="width: 80px">
<el-select v-model="form.timeoutUnit" style="width: 80px">
<el-option :label="$t('commons.units.second')" value="s" />
<el-option :label="$t('commons.units.minute')" value="m" />
<el-option :label="$t('commons.units.hour')" value="h" />
@ -210,7 +210,7 @@ const form = reactive({
timeout: 3600,
timeoutItem: 3600,
timeoutUint: 's',
timeoutUnit: 's',
backupAllImage: false,
withDockerConf: true,
@ -422,7 +422,7 @@ const submitAddSnapshot = async () => {
loading.value = true;
form.taskID = newUUID();
form.sourceAccountIDs = form.fromAccounts.join(',');
form.timeout = transferTimeToSecond(form.timeoutItem + form.timeoutUint);
form.timeout = transferTimeToSecond(form.timeoutItem + form.timeoutUnit);
await snapshotCreate(form)
.then(() => {
loading.value = false;

View file

@ -264,6 +264,8 @@ const onOpenDialog = async (
minute: 30,
second: 30,
},
timeoutItem: 5,
timeoutUnit: 'h',
},
) => {
let params = {

View file

@ -173,6 +173,17 @@
<span class="input-help">{{ $t('xpack.alert.alertCountHelper') }}</span>
</el-form-item>
</div>
<el-form-item :label="$t('cronjob.timeout')" prop="timeoutItem">
<el-input type="number" class="selectClass" v-model.number="dialogData.rowData!.timeoutItem">
<template #append>
<el-select v-model="dialogData.rowData!.timeoutUnit" style="width: 80px">
<el-option :label="$t('commons.units.second')" value="s" />
<el-option :label="$t('commons.units.minute')" value="m" />
<el-option :label="$t('commons.units.hour')" value="h" />
</el-select>
</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>
@ -206,6 +217,7 @@ import { createClam, updateClam } from '@/api/modules/toolbox';
import { storeToRefs } from 'pinia';
import { GlobalStore } from '@/store';
import { specOptions, transObjToSpec, transSpecToObj, weekOptions } from '@/views/cronjob/cronjob/helper';
import { splitTimeFromSecond, transferTimeToSecond } from '@/utils/util';
const globalStore = GlobalStore();
const licenseRef = ref();
@ -239,6 +251,11 @@ const acceptParams = (params: DialogProps): void => {
second: 30,
};
}
if (dialogData.value.rowData!.timeout) {
let item = splitTimeFromSecond(dialogData.value.rowData!.timeout);
dialogData.value.rowData.timeoutItem = item.timeItem;
dialogData.value.rowData.timeoutUnit = item.timeUnit;
}
dialogData.value.rowData.hasAlert = dialogData.value.rowData!.alertCount > 0;
dialogData.value.rowData!.alertCount = dialogData.value.rowData!.alertCount || 3;
if (dialogData.value.rowData!.alertMethod) {
@ -429,6 +446,9 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
return;
}
}
dialogData.value.rowData.timeout = transferTimeToSecond(
dialogData.value.rowData.timeoutItem + dialogData.value.rowData.timeoutUnit,
);
dialogData.value.rowData.spec = spec;
if (dialogData.value.rowData!.hasAlert) {
dialogData.value.rowData.alertCount = dialogData.value.rowData!.hasAlert