From 3651ebfee1fd8383ab190e39188be9479ee796e8 Mon Sep 17 00:00:00 2001 From: engigu Date: Wed, 25 Mar 2026 09:39:52 +0800 Subject: [PATCH] fix: task message push config fixing --- internal/executor/cron.go | 2 +- internal/services/notification_service.go | 8 +- internal/services/tasks/executor_service.go | 13 +- .../views/notify/components/EventBinding.vue | 4 +- web/src/views/tasks/RepoDialog.vue | 14 +- web/src/views/tasks/TaskDialog.vue | 231 ++---------------- .../components/TaskNotificationConfig.vue | 196 +++++++++++++++ 7 files changed, 242 insertions(+), 226 deletions(-) create mode 100644 web/src/views/tasks/components/TaskNotificationConfig.vue diff --git a/internal/executor/cron.go b/internal/executor/cron.go index ab2c038..8b33867 100644 --- a/internal/executor/cron.go +++ b/internal/executor/cron.go @@ -140,7 +140,7 @@ func (m *CronManager) AddTask(task CronTask) error { } m.entryMap[taskID] = entryID - m.logger.Infof("[CronManager] 任务已调度 #%s %s (%s)", taskID, name, task.GetSchedule()) + m.logger.Infof("[CronManager] 任务已添加调度 #%s %s (%s)", taskID, name, task.GetSchedule()) // 初始触发一次下次运行时间通知 go func() { diff --git a/internal/services/notification_service.go b/internal/services/notification_service.go index 79cd976..da3e2c2 100644 --- a/internal/services/notification_service.go +++ b/internal/services/notification_service.go @@ -357,17 +357,17 @@ func (s *NotificationService) handleEvent(bindingType string) eventbus.Handler { text = fmt.Sprintf("用户 %v 刚刚修改了密码", payload["username"]) case constant.EventTaskSuccess: title = fmt.Sprintf("任务[%v] 成功", payload["task_name"]) - text = fmt.Sprintf("任务 #%v %v\n状态: 成功\n耗时: %vms", payload["task_id"], payload["task_name"], payload["duration"]) + text = fmt.Sprintf("任务 #%v %v\n状态: 成功\n执行时间: %v\n耗时: %vms", payload["task_id"], payload["task_name"], payload["start_time"], payload["duration"]) case constant.EventTaskFailed: title = fmt.Sprintf("任务[%v] 失败", payload["task_name"]) if errStr, ok := payload["error"]; ok { - text = fmt.Sprintf("任务 #%v %v\n执行失败\n错误: %v", payload["task_id"], payload["task_name"], errStr) + text = fmt.Sprintf("任务 #%v %v\n执行失败\n执行时间: %v\n错误: %v", payload["task_id"], payload["task_name"], payload["start_time"], errStr) } else { - text = fmt.Sprintf("任务 #%v %v\n执行失败\n状态: %v\n耗时: %vms", payload["task_id"], payload["task_name"], payload["status"], payload["duration"]) + text = fmt.Sprintf("任务 #%v %v\n执行失败\n状态: %v\n执行时间: %v\n耗时: %vms", payload["task_id"], payload["task_name"], payload["status"], payload["start_time"], payload["duration"]) } case constant.EventTaskTimeout: title = fmt.Sprintf("任务[%v] 超时", payload["task_name"]) - text = fmt.Sprintf("任务 #%v %v\n执行超时\n耗时: %vms", payload["task_id"], payload["task_name"], payload["duration"]) + text = fmt.Sprintf("任务 #%v %v\n执行超时\n执行时间: %v\n耗时: %vms", payload["task_id"], payload["task_name"], payload["start_time"], payload["duration"]) case constant.EventSystemNotice: title, _ = payload["title"].(string) text, _ = payload["content"].(string) diff --git a/internal/services/tasks/executor_service.go b/internal/services/tasks/executor_service.go index f84a8b5..7eeefaa 100644 --- a/internal/services/tasks/executor_service.go +++ b/internal/services/tasks/executor_service.go @@ -265,12 +265,13 @@ func (h *ServerSchedulerHandler) OnTaskCompleted(req *executor.ExecutionRequest, eventbus.DefaultBus.Publish(eventbus.Event{ Type: eventType, Payload: map[string]interface{}{ - "task_id": task.ID, - "task_name": task.Name, - "status": result.Status, - "duration": result.Duration, - "output": result.Output, - "error": result.Error, + "task_id": task.ID, + "task_name": task.Name, + "status": result.Status, + "start_time": result.StartTime.Format("2006-01-02 15:04:05"), + "duration": result.Duration, + "output": result.Output, + "error": result.Error, }, }) } diff --git a/web/src/views/notify/components/EventBinding.vue b/web/src/views/notify/components/EventBinding.vue index 11bb7cb..c6d30b9 100644 --- a/web/src/views/notify/components/EventBinding.vue +++ b/web/src/views/notify/components/EventBinding.vue @@ -317,8 +317,8 @@ function setLogLimit(binding: NotifyBinding, limit: number) { 发送日志 - + diff --git a/web/src/views/tasks/RepoDialog.vue b/web/src/views/tasks/RepoDialog.vue index 25f57cb..c0896c4 100644 --- a/web/src/views/tasks/RepoDialog.vue +++ b/web/src/views/tasks/RepoDialog.vue @@ -15,6 +15,9 @@ import { api, type Task, type RepoConfig, type Agent, type MiseLanguage } from ' import { toast } from 'vue-sonner' import { cn } from '@/lib/utils' import { getCronDescription } from '@/utils/cron' +import TaskNotificationConfig from './components/TaskNotificationConfig.vue' + +const notificationConfigRef = ref | null>(null) const props = defineProps<{ open: boolean @@ -361,6 +364,8 @@ watch(() => props.open, async (val) => { updateAvailableVersions(lang) }) } + // 加载通知配置 + await notificationConfigRef.value?.loadConfig(props.isEdit ? props.task?.id : undefined) } }) @@ -400,9 +405,11 @@ async function save() { form.value.agent_id = selectedAgentId.value === 'local' ? null : selectedAgentId.value if (props.isEdit && form.value.id) { await api.tasks.update(form.value.id, form.value) + await notificationConfigRef.value?.saveConfig(form.value.id) toast.success('同步任务已更新') } else { - await api.tasks.create(form.value) + const task = await api.tasks.create(form.value) + await notificationConfigRef.value?.saveConfig(task.id) toast.success('同步任务已创建') } emit('update:open', false) @@ -535,7 +542,7 @@ async function save() {
- +
@@ -821,6 +828,9 @@ async function save() { + + + diff --git a/web/src/views/tasks/TaskDialog.vue b/web/src/views/tasks/TaskDialog.vue index a7887ba..1b47f70 100644 --- a/web/src/views/tasks/TaskDialog.vue +++ b/web/src/views/tasks/TaskDialog.vue @@ -9,13 +9,13 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { ScrollArea } from '@/components/ui/scroll-area' import DirTreeSelect from '@/components/DirTreeSelect.vue' -import { Checkbox } from '@/components/ui/checkbox' -import { Plus, ChevronDown, X, Search, Check, ChevronsUpDown, Loader2, AlertCircle, Terminal, Clock, Zap, Bell } from 'lucide-vue-next' +import { Plus, ChevronDown, X, Search, Check, ChevronsUpDown, AlertCircle, Terminal, Clock, Zap } from 'lucide-vue-next' import { cn } from '@/lib/utils' -import { api, type Task, type EnvVar, type Agent, type MiseLanguage, type NotifyChannel, type NotifyBinding } from '@/api' +import { api, type Task, type EnvVar, type Agent, type MiseLanguage } from '@/api' import { PATHS, TRIGGER_TYPE } from '@/constants' import { toast } from 'vue-sonner' import { getCronDescription } from '@/utils/cron' +import TaskNotificationConfig from './components/TaskNotificationConfig.vue' const props = defineProps<{ open: boolean @@ -55,7 +55,7 @@ const workDirCache = ref>({}) const concurrency = ref(0) const concurrencyEnabled = ref(false) const allEnvsEnabled = ref(false) -const SCRIPTS_DIR_PLACEHOLDER = '$SCRIPTS_DIR$' +const SCRIPTS_DIR_PLACEHPLDER = '$SCRIPTS_DIR$' const scriptsDir = ref(PATHS.SCRIPTS_DIR) const cronDescription = computed(() => { @@ -140,129 +140,7 @@ const availablePlugins = ref([]) const pluginSearch = ref('') const versionSearch = ref('') -// 通知配置相关 -const notifyChannels = ref([]) -const notifyWayId = ref('none') -const notifyOnSuccess = ref(false) -const notifyOnFailure = ref(false) -const notifyOnTimeout = ref(false) -const notifyIncludeLog = ref(false) -const notifyLogLimit = ref(1000) - -async function loadNotificationConfig() { - try { - notifyChannels.value = await api.notify.getChannels() - if (props.isEdit && props.task?.id) { - const allBindings = await api.notify.getBindings() - const taskBindings = allBindings.filter(b => b.data_id === props.task!.id && b.type === 'task') - - console.log('DEBUG [loadNotificationConfig] TaskID:', props.task!.id, 'Total:', allBindings.length, 'Matched:', taskBindings.length) - - if (taskBindings.length > 0) { - notifyWayId.value = taskBindings[0].way_id - - // 使用 nextTick 确保 UI 响应 - setTimeout(() => { - notifyOnSuccess.value = taskBindings.some(b => b.event === 'task_success') - notifyOnFailure.value = taskBindings.some(b => b.event === 'task_failed') - notifyOnTimeout.value = taskBindings.some(b => b.event === 'task_timeout') - - console.log('DEBUG [loadNotificationConfig] States Set:', { - success: notifyOnSuccess.value, - failure: notifyOnFailure.value, - timeout: notifyOnTimeout.value - }) - - // 尝试解析日志配置 - const extraBinding = taskBindings.find(b => b.extra && b.extra !== '') - if (extraBinding) { - try { - const extra = JSON.parse(extraBinding.extra) - notifyIncludeLog.value = !!extra.enable_log - notifyLogLimit.value = extra.log_limit || 1000 - } catch { - notifyIncludeLog.value = false - notifyLogLimit.value = 1000 - } - } else { - notifyIncludeLog.value = false - notifyLogLimit.value = 1000 - } - }, 50) - } else { - resetNotificationConfig() - } - } else { - resetNotificationConfig() - } - } catch (e) { - console.error('Load notifications failed', e) - resetNotificationConfig() - } -} - -function resetNotificationConfig() { - notifyWayId.value = 'none' - notifyOnSuccess.value = false - notifyOnFailure.value = false - notifyOnTimeout.value = false - notifyIncludeLog.value = false - notifyLogLimit.value = 1000 -} - -async function saveNotifications(taskId: string) { - console.log('DEBUG [saveNotifications]:', { - taskId, - notifyWayId: notifyWayId.value, - notifyOnSuccess: notifyOnSuccess.value, - notifyOnFailure: notifyOnFailure.value, - notifyOnTimeout: notifyOnTimeout.value, - notifyIncludeLog: notifyIncludeLog.value, - notifyLogLimit: notifyLogLimit.value - }) - - try { - const bindings: Partial[] = [] - - if (notifyWayId.value !== 'none') { - const events = [ - { type: 'task_success', enabled: notifyOnSuccess.value }, - { type: 'task_failed', enabled: notifyOnFailure.value }, - { type: 'task_timeout', enabled: notifyOnTimeout.value } - ] - - const extra = JSON.stringify({ - enable_log: notifyIncludeLog.value, - log_limit: notifyLogLimit.value - }) - - for (const event of events) { - if (event.enabled) { - bindings.push({ - event: event.type, - way_id: notifyWayId.value, - extra: extra - }) - } - } - } - - console.log('DEBUG [saveNotifications] payload bindings:', bindings) - - // 调用批量保存,后端会自动清理该任务旧的绑定 - await api.notify.saveBindingsBatch({ - type: 'task', - data_id: taskId, - bindings: bindings - }) - } catch (e) { - console.error('Save notifications failed', e) - } -} - -watch(notifyOnSuccess, (val) => console.log('DEBUG [notifyOnSuccess]:', val)) -watch(notifyOnFailure, (val) => console.log('DEBUG [notifyOnFailure]:', val)) -watch(notifyOnTimeout, (val) => console.log('DEBUG [notifyOnTimeout]:', val)) +const notificationConfigRef = ref | null>(null) const filteredPlugins = computed(() => { if (!pluginSearch.value) return availablePlugins.value @@ -455,7 +333,7 @@ watch(() => props.open, async (val) => { }) } // 加载通知配置 - await loadNotificationConfig() + await notificationConfigRef.value?.loadConfig(props.isEdit ? props.task?.id : undefined) } }) @@ -484,9 +362,9 @@ function removeEnv(id: string) { function normalizeLocalWorkDirForDisplay(workDir?: string | null): string { if (!workDir) return '' - if (workDir === SCRIPTS_DIR_PLACEHOLDER) return '' - if (workDir.startsWith(`${SCRIPTS_DIR_PLACEHOLDER}/`)) { - return workDir.slice(SCRIPTS_DIR_PLACEHOLDER.length + 1) + if (workDir === SCRIPTS_DIR_PLACEHPLDER) return '' + if (workDir.startsWith(`${SCRIPTS_DIR_PLACEHPLDER}/`)) { + return workDir.slice(SCRIPTS_DIR_PLACEHPLDER.length + 1) } const base = scriptsDir.value || PATHS.SCRIPTS_DIR if (workDir === base) return '' @@ -498,16 +376,16 @@ function normalizeLocalWorkDirForDisplay(workDir?: string | null): string { function encodeLocalWorkDir(workDir?: string | null): string { const value = workDir?.trim() || '' - if (!value) return SCRIPTS_DIR_PLACEHOLDER - if (value === SCRIPTS_DIR_PLACEHOLDER || value.startsWith(`${SCRIPTS_DIR_PLACEHOLDER}/`)) { + if (!value) return SCRIPTS_DIR_PLACEHPLDER + if (value === SCRIPTS_DIR_PLACEHPLDER || value.startsWith(`${SCRIPTS_DIR_PLACEHPLDER}/`)) { return value } const base = scriptsDir.value || PATHS.SCRIPTS_DIR - if (value === base) return SCRIPTS_DIR_PLACEHOLDER + if (value === base) return SCRIPTS_DIR_PLACEHPLDER if (value.startsWith(`${base}/`)) { - return `${SCRIPTS_DIR_PLACEHOLDER}/${value.slice(base.length + 1)}` + return `${SCRIPTS_DIR_PLACEHPLDER}/${value.slice(base.length + 1)}` } - return `${SCRIPTS_DIR_PLACEHOLDER}/${value.replace(/^\/+/, '')}` + return `${SCRIPTS_DIR_PLACEHPLDER}/${value.replace(/^\/+/, '')}` } async function save() { @@ -554,11 +432,11 @@ async function save() { if (props.isEdit && form.value.id) { const task = await api.tasks.update(form.value.id, form.value) - await saveNotifications(task.id) + await notificationConfigRef.value?.saveConfig(task.id) toast.success('任务已更新') } else { const task = await api.tasks.create(form.value) - await saveNotifications(task.id) + await notificationConfigRef.value?.saveConfig(task.id) toast.success('任务已创建') } emit('update:open', false) @@ -994,79 +872,10 @@ async function save() { - - -
-
-
-

通知配置

-
- -
-
- -
- -
-
- - -
-
- - + + + +

最后编辑于: {{ isEdit ? (form.updated_at || '刚才') : '现在' }}

diff --git a/web/src/views/tasks/components/TaskNotificationConfig.vue b/web/src/views/tasks/components/TaskNotificationConfig.vue new file mode 100644 index 0000000..5c359e2 --- /dev/null +++ b/web/src/views/tasks/components/TaskNotificationConfig.vue @@ -0,0 +1,196 @@ + + +