From acf94b052bb43596201938d3c3648c41a39b8ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=98=AD?= <81747598+lan-yonghui@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:51:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=97=85=E6=AF=92?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E5=92=8C=E8=AE=A1=E5=88=92=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=91=8A=E8=AD=A6=20(#6709)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/dto/alert.go | 20 ++++++ backend/app/dto/clam.go | 5 ++ backend/app/dto/cronjob.go | 6 ++ backend/app/service/clam.go | 69 +++++++++++++++++++ backend/app/service/cronjob.go | 48 ++++++++++++- backend/app/service/cronjob_helper.go | 22 +++++- backend/utils/xpack/xpack.go | 21 ++++++ frontend/src/api/interface/cronjob.ts | 3 + frontend/src/api/interface/toolbox.ts | 3 + frontend/src/lang/modules/en.ts | 11 +++ frontend/src/lang/modules/tw.ts | 11 +++ frontend/src/lang/modules/zh.ts | 11 +++ frontend/src/views/cronjob/operate/index.vue | 64 +++++++++++++++++ frontend/src/views/toolbox/clam/index.vue | 2 +- .../src/views/toolbox/clam/operate/index.vue | 50 ++++++++++++++ 15 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 backend/app/dto/alert.go diff --git a/backend/app/dto/alert.go b/backend/app/dto/alert.go new file mode 100644 index 000000000..6cf8d7829 --- /dev/null +++ b/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"` +} diff --git a/backend/app/dto/clam.go b/backend/app/dto/clam.go index 964db15ee..86c01d643 100644 --- a/backend/app/dto/clam.go +++ b/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 { diff --git a/backend/app/dto/cronjob.go b/backend/app/dto/cronjob.go index 9189a7e09..49e7cd5da 100644 --- a/backend/app/dto/cronjob.go +++ b/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 { diff --git a/backend/app/service/clam.go b/backend/app/service/clam.go index d96c0dc0b..5abee657a 100644 --- a/backend/app/service/clam.go +++ b/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 + } + } + } + } +} diff --git a/backend/app/service/cronjob.go b/backend/app/service/cronjob.go index 41accecf4..81dee96fa 100644 --- a/backend/app/service/cronjob.go +++ b/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 { diff --git a/backend/app/service/cronjob_helper.go b/backend/app/service/cronjob_helper.go index e882bf16f..cf31acfe8 100644 --- a/backend/app/service/cronjob_helper.go +++ b/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 + } + } +} diff --git a/backend/utils/xpack/xpack.go b/backend/utils/xpack/xpack.go index b8161d86a..29afab3e6 100644 --- a/backend/utils/xpack/xpack.go +++ b/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 +} diff --git a/frontend/src/api/interface/cronjob.ts b/frontend/src/api/interface/cronjob.ts index bcd7c44f7..0da286476 100644 --- a/frontend/src/api/interface/cronjob.ts +++ b/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; diff --git a/frontend/src/api/interface/toolbox.ts b/frontend/src/api/interface/toolbox.ts index dd94a17ba..3d8535477 100644 --- a/frontend/src/api/interface/toolbox.ts +++ b/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; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index c49578dd8..0218ef995 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -935,6 +935,7 @@ const message = { requestExpirationTime: 'Upload Request Expiration Time(Hours)', 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 { diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 61849797c..9827c26a1 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/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, diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index aa40b9b54..246ba89fb 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/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, diff --git a/frontend/src/views/cronjob/operate/index.vue b/frontend/src/views/cronjob/operate/index.vue index 2b5a811d4..8f194e60f 100644 --- a/frontend/src/views/cronjob/operate/index.vue +++ b/frontend/src/views/cronjob/operate/index.vue @@ -332,6 +332,31 @@ + + + {{ $t('alert.cronJobHelper') }} + + + + {{ $t('alert.alertCountHelper') }} + + + {{ $t('alert.licenseHelper') }} + + {{ $t('license.levelUpPro') }} + + + + @@ -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({ 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; @@ -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, }); diff --git a/frontend/src/views/toolbox/clam/index.vue b/frontend/src/views/toolbox/clam/index.vue index d7dec111d..2f14f9e2f 100644 --- a/frontend/src/views/toolbox/clam/index.vue +++ b/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')); }; diff --git a/frontend/src/views/toolbox/clam/operate/index.vue b/frontend/src/views/toolbox/clam/operate/index.vue index 973438959..c27e290f3 100644 --- a/frontend/src/views/toolbox/clam/operate/index.vue +++ b/frontend/src/views/toolbox/clam/operate/index.vue @@ -121,6 +121,30 @@ + + + {{ $t('alert.clamHelper') }} + + + {{ $t('toolbox.clam.alertHelper') }} + + {{ $t('license.levelUpPro') }} + + + + + {{ $t('alert.alertCountHelper') }} + @@ -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; @@ -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;