Browse Source

feat: 增加病毒扫描和计划任务告警 (#6709)

pull/6719/head
1 month ago committed by GitHub
parent
commit
acf94b052b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 20
      backend/app/dto/alert.go
  2. 5
      backend/app/dto/clam.go
  3. 6
      backend/app/dto/cronjob.go
  4. 69
      backend/app/service/clam.go
  5. 48
      backend/app/service/cronjob.go
  6. 22
      backend/app/service/cronjob_helper.go
  7. 21
      backend/utils/xpack/xpack.go
  8. 3
      frontend/src/api/interface/cronjob.ts
  9. 3
      frontend/src/api/interface/toolbox.ts
  10. 11
      frontend/src/lang/modules/en.ts
  11. 11
      frontend/src/lang/modules/tw.ts
  12. 11
      frontend/src/lang/modules/zh.ts
  13. 64
      frontend/src/views/cronjob/operate/index.vue
  14. 2
      frontend/src/views/toolbox/clam/index.vue
  15. 50
      frontend/src/views/toolbox/clam/operate/index.vue

20
backend/app/dto/alert.go

@ -0,0 +1,20 @@
package dto
type CreateOrUpdateAlert struct {
AlertTitle string `json:"alertTitle"`
AlertType string `json:"alertType"`
AlertCount uint `json:"alertCount"`
EntryID uint `json:"entryID"`
}
type AlertBase struct {
AlertType string `json:"alertType"`
EntryID uint `json:"entryID"`
}
type PushAlert struct {
TaskName string `json:"taskName"`
AlertType string `json:"alertType"`
EntryID uint `json:"entryID"`
Param string `json:"param"`
}

5
backend/app/dto/clam.go

@ -33,6 +33,7 @@ type ClamInfo struct {
LastHandleDate string `json:"lastHandleDate"`
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
}
type ClamLogSearch struct {
@ -71,6 +72,8 @@ type ClamCreate struct {
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type ClamUpdate struct {
@ -82,6 +85,8 @@ type ClamUpdate struct {
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type ClamUpdateStatus struct {

6
backend/app/dto/cronjob.go

@ -31,10 +31,13 @@ type CronjobCreate struct {
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type CronjobUpdate struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required"`
Name string `json:"name" validate:"required"`
Spec string `json:"spec" validate:"required"`
@ -53,6 +56,8 @@ type CronjobUpdate struct {
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type CronjobUpdateStatus struct {
@ -99,6 +104,7 @@ type CronjobInfo struct {
LastRecordTime string `json:"lastRecordTime"`
Status string `json:"status"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
}
type SearchRecord struct {

69
backend/app/service/clam.go

@ -8,6 +8,7 @@ import (
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
@ -20,6 +21,7 @@ import (
"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/1Panel-dev/1Panel/backend/xpack/utils/alert"
"github.com/jinzhu/copier"
"github.com/robfig/cron/v3"
@ -154,6 +156,16 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
}
datas[i].LastHandleDate = t1.Format(constant.DateTimeLayout)
}
alertBase := dto.AlertBase{
AlertType: "clams",
EntryID: datas[i].ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
datas[i].AlertCount = alertCount
} else {
datas[i].AlertCount = 0
}
}
return total, datas, err
}
@ -180,6 +192,19 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
if err := clamRepo.Create(&clam); err != nil {
return err
}
if req.AlertCount != 0 {
createAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertCount: req.AlertCount,
AlertType: "clams",
EntryID: clam.ID,
}
err := xpack.CreateAlert(createAlert)
if err != nil {
return err
}
}
return nil
}
@ -225,6 +250,16 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
if err := clamRepo.Update(req.ID, upMap); err != nil {
return err
}
updateAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertType: "clams",
AlertCount: req.AlertCount,
EntryID: clam.ID,
}
err := xpack.UpdateAlert(updateAlert)
if err != nil {
return err
}
return nil
}
@ -265,6 +300,14 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
if err := clamRepo.Delete(commonRepo.WithByID(id)); err != nil {
return err
}
alertBase := dto.AlertBase{
AlertType: "clams",
EntryID: clam.ID,
}
err := xpack.DeleteAlert(alertBase)
if err != nil {
return err
}
}
return nil
}
@ -305,6 +348,7 @@ func (c *ClamService) HandleOnce(req dto.OperateByID) error {
}
global.LOG.Debugf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
stdout, err := cmd.Execf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
handleAlert(stdout, clam.Name, clam.ID)
if err != nil {
global.LOG.Errorf("clamdscan failed, stdout: %v, err: %v", stdout, err)
}
@ -585,3 +629,28 @@ func (c *ClamService) loadLogPath(name string) string {
return ""
}
func handleAlert(stdout, clamName string, clamId uint) {
if strings.Contains(stdout, "- SCAN SUMMARY -") {
lines := strings.Split(stdout, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Infected files:") {
var infectedFiles = 0
infectedFiles, _ = strconv.Atoi(strings.TrimPrefix(line, "Infected files:"))
if infectedFiles > 0 {
pushAlert := dto.PushAlert{
TaskName: clamName,
AlertType: "clams",
EntryID: clamId,
Param: strconv.Itoa(infectedFiles),
}
err := alert.PushAlert(pushAlert)
if err != nil {
global.LOG.Errorf("clamdscan push failed, err: %v", err)
}
break
}
}
}
}
}

48
backend/app/service/cronjob.go

@ -13,6 +13,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
@ -53,6 +54,16 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
} else {
item.LastRecordTime = "-"
}
alertBase := dto.AlertBase{
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
item.AlertCount = alertCount
} else {
item.AlertCount = 0
}
dtoCronjobs = append(dtoCronjobs, item)
}
return total, dtoCronjobs, err
@ -190,6 +201,18 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error {
if err := cronjobRepo.Create(&cronjob); err != nil {
return err
}
if cronjobDto.AlertCount != 0 {
createAlert := dto.CreateOrUpdateAlert{
AlertTitle: cronjobDto.AlertTitle,
AlertCount: cronjobDto.AlertCount,
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
err := xpack.CreateAlert(createAlert)
if err != nil {
return err
}
}
return nil
}
@ -232,6 +255,14 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
return err
}
alertBase := dto.AlertBase{
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
err := xpack.DeleteAlert(alertBase)
if err != nil {
return err
}
}
return nil
@ -281,7 +312,21 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
upMap["default_download"] = req.DefaultDownload
upMap["retain_copies"] = req.RetainCopies
upMap["secret"] = req.Secret
return cronjobRepo.Update(id, upMap)
err = cronjobRepo.Update(id, upMap)
if err != nil {
return err
}
updateAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertType: cronModel.Type,
AlertCount: req.AlertCount,
EntryID: cronModel.ID,
}
err = xpack.UpdateAlert(updateAlert)
if err != nil {
return err
}
return nil
}
func (u *CronjobService) UpdateStatus(id uint, status string) error {
@ -293,6 +338,7 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
entryIDs string
err error
)
if status == constant.StatusEnable {
entryIDs, err = u.StartJob(&cronjob, false)
if err != nil {

22
backend/app/service/cronjob_helper.go

@ -12,6 +12,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
@ -20,9 +21,12 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/pkg/errors"
)
var alertTypes = map[string]bool{"app": true, "website": true, "database": true, "directory": true, "log": true, "snapshot": true}
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
var (
message []byte
@ -85,12 +89,12 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
err = u.handleSnapshot(*cronjob, record.StartTime, record.Records)
}
if err != nil {
if len(message) != 0 {
record.Records, _ = mkdirAndWriteFile(cronjob, record.StartTime, message)
}
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob)
return
}
if len(message) != 0 {
@ -392,3 +396,19 @@ func (u *CronjobService) generateLogsPath(cronjob model.Cronjob, startTime time.
func hasBackup(cronjobType string) bool {
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log"
}
func handleCronJobAlert(cronjob *model.Cronjob) {
if alertTypes[cronjob.Type] {
pushAlert := dto.PushAlert{
TaskName: cronjob.Name,
AlertType: cronjob.Type,
EntryID: cronjob.ID,
Param: cronjob.Type,
}
err := xpack.PushAlert(pushAlert)
if err != nil {
global.LOG.Errorf("cronjob alert push failed, err: %v", err)
return
}
}
}

21
backend/utils/xpack/xpack.go

@ -8,6 +8,7 @@ import (
"net/http"
"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"
@ -39,3 +40,23 @@ func LoadXpuInfo() []interface{} {
func StartClam(startClam model.Clam, isUpdate bool) (int, error) {
return 0, buserr.New(constant.ErrXpackNotFound)
}
func CreateAlert(createAlert dto.CreateOrUpdateAlert) error {
return nil
}
func UpdateAlert(updateAlert dto.CreateOrUpdateAlert) error {
return nil
}
func DeleteAlert(alertBase dto.AlertBase) error {
return nil
}
func GetAlert(alertBase dto.AlertBase) uint {
return 0
}
func PushAlert(pushAlert dto.PushAlert) error {
return nil
}

3
frontend/src/api/interface/cronjob.ts

@ -30,6 +30,9 @@ export namespace Cronjob {
retainCopies: number;
status: string;
secret: string;
hasAlert: boolean;
alertCount: number;
alertTitle: string;
}
export interface CronjobCreate {
name: string;

3
frontend/src/api/interface/toolbox.ts

@ -139,6 +139,9 @@ export namespace Toolbox {
spec: string;
specObj: Cronjob.SpecObj;
description: string;
hasAlert: boolean;
alertCount: number;
alertTitle: string;
}
export interface ClamCreate {
name: string;

11
frontend/src/lang/modules/en.ts

@ -935,6 +935,7 @@ const message = {
requestExpirationTime: 'Upload Request Expiration TimeHours',
unitHours: 'Unit: Hours',
alertTitle: 'Planned Task - {0} {1} Task Failure Alert',
},
monitor: {
monitor: 'Monitor',
@ -1132,6 +1133,8 @@ const message = {
clamLog: 'Scan Logs',
freshClam: 'Update Virus Definitions',
freshClamLog: 'Update Virus Definitions Logs',
alertHelper: 'Professional version supports SMS alert',
alertTitle: 'Virus scanning {0} task failed alert',
},
},
logs: {
@ -2461,6 +2464,14 @@ const message = {
manage: 'Management',
},
},
alert: {
isAlert: 'Alert',
alertCount: 'Alert Count',
clamHelper: 'Trigger SMS alert when scanning infected files',
cronJobHelper: 'Trigger SMS alert when scheduled task execution fails',
licenseHelper: 'Professional version supports SMS alert',
alertCountHelper: 'Maximum daily alarm frequency',
},
};
export default {

11
frontend/src/lang/modules/tw.ts

@ -889,6 +889,7 @@ const message = {
requestExpirationTime: '上傳請求過期時間小時',
unitHours: '單位小時',
alertTitle: '計畫任務-{0}{1}任務失敗告警',
},
monitor: {
monitor: '監控',
@ -1069,6 +1070,8 @@ const message = {
clamLog: '掃描日誌',
freshClam: '病毒庫刷新配置',
freshClamLog: '病毒庫刷新日誌',
alertHelper: '專業版支持短信告警功能',
alertTitle: '病毒掃描{0}任務失敗告警',
},
},
logs: {
@ -2285,6 +2288,14 @@ const message = {
manage: '管理',
},
},
alert: {
isAlert: '是否告警',
alertCount: '告警次數',
clamHelper: '掃描到感染檔案時觸發簡訊告警',
cronJobHelper: '定時執行計畫任務失敗時觸發簡訊告警',
licenseHelper: '專業版支持簡訊告警功能',
alertCountHelper: '每日最大告警次數',
},
};
export default {
...fit2cloudTwLocale,

11
frontend/src/lang/modules/zh.ts

@ -890,6 +890,7 @@ const message = {
requestExpirationTime: '上传请求过期时间小时',
unitHours: '单位小时',
alertTitle: '计划任务-{0} {1} 任务失败告警',
},
monitor: {
monitor: '监控',
@ -1071,6 +1072,8 @@ const message = {
clamLog: '扫描日志',
freshClam: '病毒库刷新配置',
freshClamLog: '病毒库刷新日志',
alertHelper: '专业版支持短信告警功能',
alertTitle: '病毒扫描 {0} 任务失败告警',
},
},
logs: {
@ -2288,6 +2291,14 @@ const message = {
manage: '管理',
},
},
alert: {
isAlert: '是否告警',
alertCount: '告警次数',
clamHelper: '扫描到感染文件时触发短信告警',
cronJobHelper: '定时执行计划任务失败时触发短信告警',
licenseHelper: '专业版支持短信告警功能',
alertCountHelper: '每日最大告警次数',
},
};
export default {
...fit2cloudZhLocale,

64
frontend/src/views/cronjob/operate/index.vue

@ -332,6 +332,31 @@
<el-input clearable v-model.trim="dialogData.rowData!.url" />
</el-form-item>
<el-form-item prop="hasAlert" v-if="alertTypes.includes(dialogData.rowData!.type)">
<el-checkbox v-model="dialogData.rowData!.hasAlert" :label="$t('alert.isAlert')" />
<span class="input-help">{{ $t('alert.cronJobHelper') }}</span>
</el-form-item>
<el-form-item
prop="alertCount"
v-if="dialogData.rowData!.hasAlert && isProductPro"
:label="$t('alert.alertCount')"
>
<el-input-number
style="width: 200px"
:min="1"
step-strictly
:step="1"
v-model.number="dialogData.rowData!.alertCount"
></el-input-number>
<span class="input-help">{{ $t('alert.alertCountHelper') }}</span>
</el-form-item>
<el-form-item v-if="dialogData.rowData!.hasAlert && !isProductPro">
<span>{{ $t('alert.licenseHelper') }}</span>
<el-button link type="primary" @click="toUpload">
{{ $t('license.levelUpPro') }}
</el-button>
</el-form-item>
<el-form-item
v-if="hasExclusionRules()"
:label="$t('cronjob.exclusionRules')"
@ -357,6 +382,7 @@
</el-button>
</span>
</template>
<LicenseImport ref="licenseRef" />
</el-drawer>
</template>
@ -378,6 +404,9 @@ import { listContainer } from '@/api/modules/container';
import { Database } from '@/api/interface/database';
import { ListAppInstalled } from '@/api/modules/app';
import { loadDefaultSpec, specOptions, transObjToSpec, transSpecToObj, weekOptions } from './../helper';
import { storeToRefs } from 'pinia';
import { GlobalStore } from '@/store';
import LicenseImport from '@/components/license-import/index.vue';
const router = useRouter();
@ -393,6 +422,11 @@ const dialogData = ref<DialogProps>({
title: '',
});
const globalStore = GlobalStore();
const licenseRef = ref();
const { isProductPro } = storeToRefs(globalStore);
const alertTypes = ['app', 'website', 'database', 'directory', 'log', 'snapshot'];
const acceptParams = (params: DialogProps): void => {
dialogData.value = params;
if (dialogData.value.rowData?.spec) {
@ -419,6 +453,8 @@ const acceptParams = (params: DialogProps): void => {
if (dialogData.value.rowData.dbName) {
dialogData.value.rowData.dbNameList = dialogData.value.rowData.dbName.split(',');
}
dialogData.value.rowData.hasAlert = dialogData.value.rowData!.alertCount > 0;
dialogData.value.rowData!.alertCount = dialogData.value.rowData!.alertCount || 3;
dialogData.value.rowData!.command = dialogData.value.rowData!.command || 'sh';
dialogData.value.rowData!.isCustom =
dialogData.value.rowData!.command !== 'sh' &&
@ -556,6 +592,18 @@ const verifySpec = (rule: any, value: any, callback: any) => {
callback();
};
function checkSendCount(rule: any, value: any, callback: any) {
if (value === '') {
callback();
}
const regex = /^(?:[1-9]|[12][0-9]|30)$/;
if (!regex.test(value)) {
return callback(new Error(i18n.global.t('commons.rule.numberRange', [1, 30])));
}
callback();
}
const rules = reactive({
name: [Rules.requiredInput, Rules.noSpace],
type: [Rules.requiredSelect],
@ -574,6 +622,7 @@ const rules = reactive({
backupAccounts: [Rules.requiredSelect],
defaultDownload: [Rules.requiredSelect],
retainCopies: [Rules.number],
alertCount: [Rules.integerNumber, { validator: checkSendCount, trigger: 'blur' }],
});
type FormInstance = InstanceType<typeof ElForm>;
@ -752,6 +801,17 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (dialogData.value?.rowData?.exclusionRules) {
dialogData.value.rowData.exclusionRules = dialogData.value.rowData.exclusionRules.replaceAll('\n', ',');
}
if (alertTypes.includes(dialogData.value.rowData.type)) {
dialogData.value.rowData.alertCount =
dialogData.value.rowData!.hasAlert && isProductPro.value ? dialogData.value.rowData.alertCount : 0;
dialogData.value.rowData.alertTitle =
dialogData.value.rowData!.hasAlert && isProductPro.value
? i18n.global.t('cronjob.alertTitle', [
i18n.global.t('cronjob.' + dialogData.value.rowData.type),
dialogData.value.rowData.name,
])
: '';
}
if (!dialogData.value.rowData) return;
if (dialogData.value.title === 'create') {
await addCronjob(dialogData.value.rowData);
@ -766,6 +826,10 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
});
};
const toUpload = () => {
licenseRef.value.acceptParams();
};
defineExpose({
acceptParams,
});

2
frontend/src/views/toolbox/clam/index.vue

@ -261,7 +261,7 @@ const toDoc = async () => {
};
const onChange = async (row: any) => {
await await updateClam(row);
await updateClam(row);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
};

50
frontend/src/views/toolbox/clam/operate/index.vue

@ -121,6 +121,30 @@
</template>
</el-input>
</el-form-item>
<el-form-item prop="hasAlert">
<el-checkbox v-model="dialogData.rowData!.hasAlert" :label="$t('alert.isAlert')" />
<span class="input-help">{{ $t('alert.clamHelper') }}</span>
</el-form-item>
<el-form-item v-if="dialogData.rowData!.hasAlert && !isProductPro">
<span>{{ $t('toolbox.clam.alertHelper') }}</span>
<el-button link type="primary" @click="toUpload">
{{ $t('license.levelUpPro') }}
</el-button>
</el-form-item>
<el-form-item
prop="alertCount"
v-if="dialogData.rowData!.hasAlert && isProductPro"
:label="$t('alert.alertCount')"
>
<el-input-number
style="width: 200px"
:min="1"
step-strictly
:step="1"
v-model.number="dialogData.rowData!.alertCount"
></el-input-number>
<span class="input-help">{{ $t('alert.alertCountHelper') }}</span>
</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>
@ -184,6 +208,8 @@ const acceptParams = (params: DialogProps): void => {
second: 30,
};
}
dialogData.value.rowData.hasAlert = dialogData.value.rowData!.alertCount > 0;
dialogData.value.rowData!.alertCount = dialogData.value.rowData!.alertCount || 3;
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
drawerVisible.value = true;
};
@ -277,6 +303,19 @@ const verifySpec = (rule: any, value: any, callback: any) => {
}
callback();
};
function checkSendCount(rule: any, value: any, callback: any) {
if (value === '') {
callback();
}
const regex = /^(?:[1-9]|[12][0-9]|30)$/;
if (!regex.test(value)) {
return callback(new Error(i18n.global.t('commons.rule.numberRange', [1, 30])));
}
callback();
}
const rules = reactive({
name: [Rules.simpleName],
path: [Rules.requiredInput, Rules.noSpace],
@ -284,6 +323,7 @@ const rules = reactive({
{ validator: verifySpec, trigger: 'blur', required: true },
{ validator: verifySpec, trigger: 'change', required: true },
],
alertCount: [Rules.integerNumber, { validator: checkSendCount, trigger: 'blur' }],
});
type FormInstance = InstanceType<typeof ElForm>;
@ -353,6 +393,16 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
MsgError(i18n.global.t('cronjob.cronSpecHelper'));
return;
}
dialogData.value.rowData.alertCount = dialogData.value.rowData!.hasAlert
? dialogData.value.rowData.alertCount
: 0;
dialogData.value.rowData.alertTitle = i18n.global.t('toolbox.clam.alertTitle', [
dialogData.value.rowData.name,
]);
} else {
dialogData.value.rowData.alertTitle = '';
dialogData.value.rowData.alertCount = 0;
dialogData.value.rowData.hasAlert = false;
}
dialogData.value.rowData.spec = spec;

Loading…
Cancel
Save