diff --git a/apps/daas/src/i18n/langs/en.js b/apps/daas/src/i18n/langs/en.js
index ef5d52998f..ca08c2c23a 100644
--- a/apps/daas/src/i18n/langs/en.js
+++ b/apps/daas/src/i18n/langs/en.js
@@ -2740,4 +2740,124 @@ export default {
license_expired_today: 'License expires today',
license_expired_days: 'License has expired {val} days',
license_expire_days: 'License expires in {val} days',
+ // TAP-13052 告警事件 / 通知记录 / 操作审计
+ page_title_ops_center: 'Operations',
+ page_title_message_center: 'Messages & Notifications',
+ page_title_alarm_events: 'Alarm Events',
+ page_title_notification_records: 'Notification Records',
+ page_title_operation_audit: 'Operation Audit',
+ notification_modular_alarm: 'alarm event',
+ notification_operation_unread: 'marked as unread',
+ notification_operation_close: 'closed',
+ alarm_event_stats_alarming: 'Alarming',
+ alarm_event_stats_severe: 'Critical/Emergency',
+ alarm_event_stats_today_new: 'New Today',
+ alarm_event_stats_today_recovered: 'Recovered Today',
+ alarm_event_object_type: 'Object Type',
+ alarm_object_type_TASK: 'Task',
+ alarm_object_type_NODE: 'Node',
+ alarm_object_type_CONNECTION: 'Connection',
+ alarm_object_type_AGENT: 'Agent',
+ alarm_object_type_INSPECT: 'Validation Task',
+ alarm_object_type_API_SERVER: 'API Server',
+ alarm_event_notified: 'Notified',
+ alarm_event_notified_NONE: 'Not notified',
+ alarm_event_notified_NOTIFIED: 'Notified',
+ alarm_event_notified_PARTIAL_FAILED: 'Partially failed',
+ alarm_event_notified_FAILED: 'Failed',
+ alarm_event_agent: 'Agent',
+ alarm_event_keyword_placeholder: 'Task/object name or ID',
+ alarm_event_occurrence_count: 'Occurrences',
+ alarm_event_notification_result: 'Notification Result',
+ alarm_event_go_task: 'Go to Task',
+ alarm_event_close_hint:
+ 'Closing only stops tracking this alarm; it does not mean the problem has recovered',
+ alarm_event_close_confirm:
+ 'Closing stops tracking the selected alarms. It does not mean the problem has recovered, and it will be recorded in the operation audit. Close them?',
+ alarm_event_detail: 'Alarm Details',
+ alarm_event_overview: 'Overview',
+ alarm_event_id: 'Alarm ID',
+ alarm_event_type: 'Alarm Type',
+ alarm_event_duration: 'Duration',
+ alarm_event_minutes: '{0} min',
+ alarm_event_hours: '{0} h',
+ alarm_event_recovery_time: 'Recovered At',
+ alarm_event_close_time: 'Closed At',
+ alarm_event_current_value: 'Current Value',
+ alarm_event_threshold: 'Threshold',
+ alarm_event_related_object: 'Related Object',
+ alarm_event_object_deleted: 'Object deleted',
+ alarm_event_task: 'Task',
+ alarm_event_node: 'Node',
+ alarm_event_open_monitor: 'Open Task Monitor',
+ alarm_event_open_setting: 'Open Alarm Settings',
+ alarm_event_notification_overview: 'Notification Overview',
+ alarm_event_latest_notify: 'Last notified',
+ alarm_event_view_notifications: 'View notification records',
+ delivery_channel_system: 'In-app message',
+ delivery_channel_email: 'Email',
+ delivery_channel_webhook: 'Webhook',
+ delivery_status_success: 'All succeeded',
+ delivery_status_partial_failed: 'Partially failed',
+ delivery_status_failed: 'Failed',
+ delivery_status_skipped: 'Skipped',
+ delivery_status_none: 'No delivery record',
+ delivery_summary_hint: 'Numbers are succeeded/recipients',
+ delivery_recipient_success: 'Succeeded',
+ delivery_recipient_failed: 'Failed',
+ delivery_recipient_skipped: 'Skipped',
+ delivery_skip_no_receivers: 'No receivers configured',
+ delivery_skip_blacklist: 'Blacklisted',
+ delivery_skip_rate_limit: 'Sending limit exceeded',
+ notification_record_tab_all: 'All',
+ notification_record_tab_unread: 'Unread',
+ notification_record_tab_failed: 'Failed',
+ notification_record_notify_time: 'Notified At',
+ notification_record_channel: 'Channel',
+ notification_record_delivery_status: 'Delivery Status',
+ notification_record_read_state: 'Read Status',
+ notification_record_read: 'Read',
+ notification_record_unread: 'Unread',
+ notification_record_recipient_placeholder: 'Recipient (email/phone)',
+ notification_record_keyword_placeholder: 'Task/object name or ID',
+ notification_record_content: 'Content',
+ notification_record_legacy: 'Historical record without delivery details',
+ notification_record_alarm_missing:
+ 'Alarm event was cleared when the task was rerun',
+ notification_record_others: "Another user's notification",
+ notification_record_others_hint:
+ "Another user's notification; viewing it does not change their read state",
+ alarm_event_archived: 'Archived when the task was rerun',
+ notification_record_delivery: 'Delivery',
+ notification_record_latest_notify: 'Last Notified',
+ notification_record_mark_read: 'Mark as read',
+ notification_record_mark_unread: 'Mark as unread',
+ notification_record_mark_all_read: 'Mark all as read',
+ notification_record_confirm_read:
+ '{0} notifications under the current filter will be marked as read. Continue?',
+ notification_record_confirm_unread:
+ '{0} selected notifications will be marked as unread. Continue?',
+ notification_record_truncated:
+ '(Up to 1000 per operation; repeat for the rest)',
+ notification_record_nothing_to_mark: 'Nothing to update',
+ notification_record_detail: 'Notification Details',
+ notification_record_read_at: 'Read at',
+ notification_record_basic: 'Basic Information',
+ notification_record_view_alarm: 'View Alarm Event',
+ notification_record_deliveries: 'Deliveries',
+ notification_record_no_delivery:
+ 'No delivery details (historical record or no external channel)',
+ notification_record_occurrence_snapshot: '{0} occurrences when sent',
+ notification_record_recipient: 'Recipient',
+ notification_record_status: 'Status',
+ notification_record_sent_at: 'Sent At',
+ notification_record_reason: 'Failure/skip reason',
+ notification_record_smtp_hint:
+ 'Email "succeeded" means the mail server accepted it, not that the recipient opened it. Recipients of the same send share one result; addresses explicitly rejected by the server are marked failed separately.',
+ notification_bell_title: 'Unread ({0})',
+ notification_platform_license:
+ 'License expires in {0} days, please renew in time',
+ notification_view_all_records: 'View all notification records',
+ notification_setting_retired_tip:
+ 'In-app system notifications are retired. Task errors, validation errors and Agent status are now notified by alarm rules; configure channels and recipients in Alarm Settings. Existing email notifications below still apply.',
}
diff --git a/apps/daas/src/i18n/langs/zh-CN.js b/apps/daas/src/i18n/langs/zh-CN.js
index 6a12e1baa4..07293f146a 100644
--- a/apps/daas/src/i18n/langs/zh-CN.js
+++ b/apps/daas/src/i18n/langs/zh-CN.js
@@ -2595,4 +2595,118 @@ export default {
license_expired_today: 'License 今日到期',
license_expired_days: 'License 已过期 {val} 天',
license_expire_days: 'License 还有 {val} 天到期',
+ // TAP-13052 告警事件 / 通知记录 / 操作审计
+ page_title_ops_center: '运维中心',
+ page_title_message_center: '消息与通知',
+ page_title_alarm_events: '告警事件',
+ page_title_notification_records: '通知记录',
+ page_title_operation_audit: '操作审计',
+ notification_modular_alarm: '告警事件',
+ notification_operation_unread: '标记为未读',
+ notification_operation_close: '关闭了',
+ alarm_event_stats_alarming: '告警中',
+ alarm_event_stats_severe: '严重/紧急',
+ alarm_event_stats_today_new: '今日新增',
+ alarm_event_stats_today_recovered: '今日已恢复',
+ alarm_event_object_type: '对象类型',
+ alarm_object_type_TASK: '任务',
+ alarm_object_type_NODE: '节点',
+ alarm_object_type_CONNECTION: '连接',
+ alarm_object_type_AGENT: '引擎',
+ alarm_object_type_INSPECT: '校验任务',
+ alarm_object_type_API_SERVER: 'API Server',
+ alarm_event_notified: '是否已通知',
+ alarm_event_notified_NONE: '未通知',
+ alarm_event_notified_NOTIFIED: '已通知',
+ alarm_event_notified_PARTIAL_FAILED: '部分失败',
+ alarm_event_notified_FAILED: '全部失败',
+ alarm_event_agent: '所属 Agent',
+ alarm_event_keyword_placeholder: '任务/对象名称或 ID',
+ alarm_event_occurrence_count: '发生次数',
+ alarm_event_notification_result: '通知结果',
+ alarm_event_go_task: '前往任务',
+ alarm_event_close_hint: '关闭只表示结束对该告警的跟踪,不代表问题已恢复',
+ alarm_event_close_confirm:
+ '关闭后将结束对所选告警的跟踪,不代表问题已恢复,且会记录到操作审计。确定关闭吗?',
+ alarm_event_detail: '告警详情',
+ alarm_event_overview: '告警概览',
+ alarm_event_id: '告警 ID',
+ alarm_event_type: '告警类型',
+ alarm_event_duration: '持续时长',
+ alarm_event_minutes: '{0} 分钟',
+ alarm_event_hours: '{0} 小时',
+ alarm_event_recovery_time: '恢复时间',
+ alarm_event_close_time: '关闭时间',
+ alarm_event_current_value: '当前值',
+ alarm_event_threshold: '阈值',
+ alarm_event_related_object: '关联对象',
+ alarm_event_object_deleted: '对象已删除',
+ alarm_event_task: '任务',
+ alarm_event_node: '节点',
+ alarm_event_open_monitor: '打开任务监控',
+ alarm_event_open_setting: '打开告警设置',
+ alarm_event_notification_overview: '通知概览',
+ alarm_event_latest_notify: '最近通知时间',
+ alarm_event_view_notifications: '查看全部通知记录',
+ delivery_channel_system: '站内消息',
+ delivery_channel_email: '邮件',
+ delivery_channel_webhook: 'Webhook',
+ delivery_status_success: '全部成功',
+ delivery_status_partial_failed: '部分失败',
+ delivery_status_failed: '全部失败',
+ delivery_status_skipped: '已跳过',
+ delivery_status_none: '无发送记录',
+ delivery_summary_hint: '数字为 成功数/收件人数',
+ delivery_recipient_success: '成功',
+ delivery_recipient_failed: '失败',
+ delivery_recipient_skipped: '跳过',
+ delivery_skip_no_receivers: '未配置接收人',
+ delivery_skip_blacklist: '在黑名单中',
+ delivery_skip_rate_limit: '超出发送限额',
+ notification_record_tab_all: '全部',
+ notification_record_tab_unread: '未读',
+ notification_record_tab_failed: '发送失败',
+ notification_record_notify_time: '通知时间',
+ notification_record_channel: '通知渠道',
+ notification_record_delivery_status: '发送状态',
+ notification_record_read_state: '阅读状态',
+ notification_record_read: '已读',
+ notification_record_unread: '未读',
+ notification_record_recipient_placeholder: '收件人(邮箱/手机号)',
+ notification_record_keyword_placeholder: '任务/对象名称或 ID',
+ notification_record_content: '通知内容',
+ notification_record_legacy: '历史记录,无发送明细',
+ notification_record_alarm_missing: '告警事件已在任务重新运行时清除',
+ notification_record_others: '其他用户的通知',
+ notification_record_others_hint: '其他用户的通知,查看不会改变对方的阅读状态',
+ alarm_event_archived: '任务重新运行时归档',
+ notification_record_delivery: '发送结果',
+ notification_record_latest_notify: '最近通知时间',
+ notification_record_mark_read: '标为已读',
+ notification_record_mark_unread: '标为未读',
+ notification_record_mark_all_read: '全部标为已读',
+ notification_record_confirm_read:
+ '将把当前筛选条件下的 {0} 条通知标为已读,确定吗?',
+ notification_record_confirm_unread: '将把选中的 {0} 条通知标为未读,确定吗?',
+ notification_record_truncated: '(单次最多处理 1000 条,其余请再次操作)',
+ notification_record_nothing_to_mark: '没有需要处理的通知',
+ notification_record_detail: '通知详情',
+ notification_record_read_at: '读取时间',
+ notification_record_basic: '基本信息',
+ notification_record_view_alarm: '查看告警事件',
+ notification_record_deliveries: '发送明细',
+ notification_record_no_delivery:
+ '该通知没有发送明细(历史记录或未配置外部渠道)',
+ notification_record_occurrence_snapshot: '发送时已发生 {0} 次',
+ notification_record_recipient: '接收人',
+ notification_record_status: '状态',
+ notification_record_sent_at: '发送时间',
+ notification_record_reason: '失败/跳过原因',
+ notification_record_smtp_hint:
+ '邮件“成功”表示邮件服务器已接收,不代表收件人已打开;同一次发送的收件人共用同一结果,服务器明确拒收的地址会单独标记失败。',
+ notification_bell_title: '未读通知({0})',
+ notification_platform_license: 'License 还有 {0} 天到期,请及时续期',
+ notification_view_all_records: '查看全部通知记录',
+ notification_setting_retired_tip:
+ '站内系统通知已下线:任务异常、校验异常、Agent 状态等提醒统一由告警规则产生,请在告警设置中配置渠道与接收人;下方已配置的邮件提醒仍然生效。',
}
diff --git a/apps/daas/src/i18n/langs/zh-TW.js b/apps/daas/src/i18n/langs/zh-TW.js
index ca46ecdfd1..48407a0a99 100644
--- a/apps/daas/src/i18n/langs/zh-TW.js
+++ b/apps/daas/src/i18n/langs/zh-TW.js
@@ -2579,4 +2579,119 @@ export default {
license_expired_today: 'License 今日到期',
license_expired_days: 'License 已過期 {val} 天',
license_expire_days: 'License 還有 {val} 天到期',
+ // TAP-13052 告警事件 / 通知记录 / 操作审计
+ page_title_ops_center: '運維中心',
+ page_title_message_center: '訊息與通知',
+ page_title_alarm_events: '告警事件',
+ page_title_notification_records: '通知記錄',
+ page_title_operation_audit: '操作審計',
+ notification_modular_alarm: '告警事件',
+ notification_operation_unread: '標記為未讀',
+ notification_operation_close: '關閉了',
+ alarm_event_stats_alarming: '告警中',
+ alarm_event_stats_severe: '嚴重/緊急',
+ alarm_event_stats_today_new: '今日新增',
+ alarm_event_stats_today_recovered: '今日已恢復',
+ alarm_event_object_type: '對象類型',
+ alarm_object_type_TASK: '任務',
+ alarm_object_type_NODE: '節點',
+ alarm_object_type_CONNECTION: '連接',
+ alarm_object_type_AGENT: '引擎',
+ alarm_object_type_INSPECT: '校驗任務',
+ alarm_object_type_API_SERVER: 'API Server',
+ alarm_event_notified: '是否已通知',
+ alarm_event_notified_NONE: '未通知',
+ alarm_event_notified_NOTIFIED: '已通知',
+ alarm_event_notified_PARTIAL_FAILED: '部分失敗',
+ alarm_event_notified_FAILED: '全部失敗',
+ alarm_event_agent: '所屬 Agent',
+ alarm_event_keyword_placeholder: '任務/對象名稱或 ID',
+ alarm_event_occurrence_count: '發生次數',
+ alarm_event_notification_result: '通知結果',
+ alarm_event_go_task: '前往任務',
+ alarm_event_close_hint: '關閉只表示結束對該告警的追蹤,不代表問題已恢復',
+ alarm_event_close_confirm:
+ '關閉後將結束對所選告警的追蹤,不代表問題已恢復,且會記錄到操作審計。確定關閉嗎?',
+ alarm_event_detail: '告警詳情',
+ alarm_event_overview: '告警概覽',
+ alarm_event_id: '告警 ID',
+ alarm_event_type: '告警類型',
+ alarm_event_duration: '持續時長',
+ alarm_event_minutes: '{0} 分鐘',
+ alarm_event_hours: '{0} 小時',
+ alarm_event_recovery_time: '恢復時間',
+ alarm_event_close_time: '關閉時間',
+ alarm_event_current_value: '當前值',
+ alarm_event_threshold: '閾值',
+ alarm_event_related_object: '關聯對象',
+ alarm_event_object_deleted: '對象已刪除',
+ alarm_event_task: '任務',
+ alarm_event_node: '節點',
+ alarm_event_open_monitor: '打開任務監控',
+ alarm_event_open_setting: '打開告警設置',
+ alarm_event_notification_overview: '通知概覽',
+ alarm_event_latest_notify: '最近通知時間',
+ alarm_event_view_notifications: '查看全部通知記錄',
+ delivery_channel_system: '站內訊息',
+ delivery_channel_email: '郵件',
+ delivery_channel_webhook: 'Webhook',
+ delivery_status_success: '全部成功',
+ delivery_status_partial_failed: '部分失敗',
+ delivery_status_failed: '全部失敗',
+ delivery_status_skipped: '已跳過',
+ delivery_status_none: '無發送記錄',
+ delivery_summary_hint: '數字為 成功數/收件人數',
+ delivery_recipient_success: '成功',
+ delivery_recipient_failed: '失敗',
+ delivery_recipient_skipped: '跳過',
+ delivery_skip_no_receivers: '未配置接收人',
+ delivery_skip_blacklist: '在黑名單中',
+ delivery_skip_rate_limit: '超出發送限額',
+ notification_record_tab_all: '全部',
+ notification_record_tab_unread: '未讀',
+ notification_record_tab_failed: '發送失敗',
+ notification_record_notify_time: '通知時間',
+ notification_record_channel: '通知渠道',
+ notification_record_delivery_status: '發送狀態',
+ notification_record_read_state: '閱讀狀態',
+ notification_record_read: '已讀',
+ notification_record_unread: '未讀',
+ notification_record_recipient_placeholder: '收件人(郵箱/手機號)',
+ notification_record_keyword_placeholder: '任務/對象名稱或 ID',
+ notification_record_content: '通知內容',
+ notification_record_legacy: '歷史記錄,無發送明細',
+ notification_record_alarm_missing: '告警事件已在任務重新運行時清除',
+ notification_record_others: '其他使用者的通知',
+ notification_record_others_hint:
+ '其他使用者的通知,查看不會改變對方的閱讀狀態',
+ alarm_event_archived: '任務重新運行時歸檔',
+ notification_record_delivery: '發送結果',
+ notification_record_latest_notify: '最近通知時間',
+ notification_record_mark_read: '標為已讀',
+ notification_record_mark_unread: '標為未讀',
+ notification_record_mark_all_read: '全部標為已讀',
+ notification_record_confirm_read:
+ '將把當前篩選條件下的 {0} 條通知標為已讀,確定嗎?',
+ notification_record_confirm_unread: '將把選中的 {0} 條通知標為未讀,確定嗎?',
+ notification_record_truncated: '(單次最多處理 1000 條,其餘請再次操作)',
+ notification_record_nothing_to_mark: '沒有需要處理的通知',
+ notification_record_detail: '通知詳情',
+ notification_record_read_at: '讀取時間',
+ notification_record_basic: '基本資訊',
+ notification_record_view_alarm: '查看告警事件',
+ notification_record_deliveries: '發送明細',
+ notification_record_no_delivery:
+ '該通知沒有發送明細(歷史記錄或未配置外部渠道)',
+ notification_record_occurrence_snapshot: '發送時已發生 {0} 次',
+ notification_record_recipient: '接收人',
+ notification_record_status: '狀態',
+ notification_record_sent_at: '發送時間',
+ notification_record_reason: '失敗/跳過原因',
+ notification_record_smtp_hint:
+ '郵件「成功」表示郵件伺服器已接收,不代表收件人已開啟;同一次發送的收件人共用同一結果,伺服器明確拒收的地址會單獨標記失敗。',
+ notification_bell_title: '未讀通知({0})',
+ notification_platform_license: 'License 還有 {0} 天到期,請及時續期',
+ notification_view_all_records: '查看全部通知記錄',
+ notification_setting_retired_tip:
+ '站內系統通知已下線:任務異常、校驗異常、Agent 狀態等提醒統一由告警規則產生,請在告警設置中配置渠道與接收人;下方已配置的郵件提醒仍然生效。',
}
diff --git a/apps/daas/src/layouts/NotifyLayout.vue b/apps/daas/src/layouts/NotifyLayout.vue
deleted file mode 100644
index f635f66897..0000000000
--- a/apps/daas/src/layouts/NotifyLayout.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
-
-
-
-
-
- left
-
- {{
- $t('public_button_back')
- }}
-
-
-
-
-
-
- {{ $t(menu.name) }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/apps/daas/src/layouts/Sidebar.vue b/apps/daas/src/layouts/Sidebar.vue
index 39b28443c4..82a502d209 100644
--- a/apps/daas/src/layouts/Sidebar.vue
+++ b/apps/daas/src/layouts/Sidebar.vue
@@ -173,7 +173,7 @@ onMounted(() => {
-
+
{
diff --git a/apps/daas/src/layouts/Wrapper.vue b/apps/daas/src/layouts/Wrapper.vue
index 353eefdfd5..1376304be1 100644
--- a/apps/daas/src/layouts/Wrapper.vue
+++ b/apps/daas/src/layouts/Wrapper.vue
@@ -85,6 +85,8 @@ const IS_IFRAME = ref(Boolean(sessionStorage.getItem('IS_IFRAME') === 'true'))
:deep(.layout-side-scrollbar) {
flex-shrink: 0;
+ will-change: transform;
+ transform: translateZ(0);
.el-scrollbar__view {
min-height: 100%;
display: flex;
@@ -110,6 +112,10 @@ const IS_IFRAME = ref(Boolean(sessionStorage.getItem('IS_IFRAME') === 'true'))
gap: 8px;
}
+ .el-menu--inline {
+ overflow: hidden;
+ }
+
.el-menu-item.is-active {
background-color: var(--primary-hover-light);
}
@@ -120,10 +126,24 @@ const IS_IFRAME = ref(Boolean(sessionStorage.getItem('IS_IFRAME') === 'true'))
}
.el-sub-menu__title + .el-menu {
- margin-top: 8px;
+ padding-top: 8px;
padding-inline-start: 10px;
}
+ .el-sub-menu__icon-arrow {
+ transition:
+ transform var(--el-transition-duration) ease-in-out,
+ opacity var(--el-transition-duration) ease-in-out;
+
+ &::before,
+ &::after {
+ transition:
+ transform var(--el-transition-duration) ease-in-out,
+ background-color var(--el-transition-duration) ease-in-out,
+ color var(--el-transition-duration) ease-in-out;
+ }
+ }
+
.el-menu-item.is-active,
.el-menu-item:hover {
--el-menu-text-color: var(--el-text-color-primary);
diff --git a/apps/daas/src/router/menu.ts b/apps/daas/src/router/menu.ts
index 69ca6b8932..2517c594f4 100644
--- a/apps/daas/src/router/menu.ts
+++ b/apps/daas/src/router/menu.ts
@@ -56,6 +56,23 @@ export const MENU = [
parent: 'dataVerification',
},
+ {
+ name: 'opsCenter',
+ label: 'page_title_ops_center',
+ icon: 'warning',
+ group: 'bottom',
+ children: [{ name: 'alarmEvents' }],
+ },
+ {
+ name: 'messageCenter',
+ label: 'page_title_message_center',
+ icon: 'notice-system-notice',
+ group: 'bottom',
+ children: [
+ { name: 'notificationRecords' },
+ { name: 'notificationSetting' },
+ ],
+ },
{
name: 'advancedFeatures',
label: 'page_title_advanced_features',
@@ -125,6 +142,7 @@ export const MENU = [
hidden: isCommunity,
},
{ name: 'externalStorage', code: 'v2_external-storage_menu' },
+ { name: 'operationAudit' },
],
},
]
diff --git a/apps/daas/src/router/routes.ts b/apps/daas/src/router/routes.ts
index 06a4ad726a..3046cd6d98 100644
--- a/apps/daas/src/router/routes.ts
+++ b/apps/daas/src/router/routes.ts
@@ -1,5 +1,4 @@
import Layout from '@/layouts/Layout.vue'
-import NotifyLayout from '@/layouts/NotifyLayout.vue'
import SettingLayout from '@/layouts/SettingLayout.vue'
const FunctionForm = () => import('@/views/function/Form.vue')
@@ -60,8 +59,6 @@ const ExternalStorageList = () =>
import('@tap/business/src/views/external-storage/List.vue')
// 告警通知
-const AlarmNotification = () =>
- import('@tap/business/src/views/setting/AlarmNotification.vue')
// 数据加密
const DataEncryptionList = () =>
@@ -848,6 +845,37 @@ export const routes = [
hideTitle: true,
},
},
+ /* ---------- 告警事件 / 通知记录 / 操作审计(TAP-13052 §4.1) ----------*/
+ {
+ path: '/alarm-events',
+ name: 'alarmEvents',
+ component: () => import('@/views/notification/AlarmEvents.vue'),
+ meta: {
+ title: 'page_title_alarm_events',
+ hideTitle: true,
+ code: 'v2_alarm_event',
+ },
+ },
+ {
+ path: '/notification-records',
+ name: 'notificationRecords',
+ component: () => import('@/views/notification/NotificationRecords.vue'),
+ meta: {
+ title: 'page_title_notification_records',
+ hideTitle: true,
+ code: 'v2_notification_record',
+ },
+ },
+ {
+ path: '/operation-audit',
+ name: 'operationAudit',
+ component: () => import('@/views/notification/UserNotification.vue'),
+ meta: {
+ title: 'page_title_operation_audit',
+ hideTitle: true,
+ code: 'v2_audit_log',
+ },
+ },
/* ---------- 用户管理 ----------*/
{
path: '/user',
@@ -969,39 +997,31 @@ export const routes = [
},
],
},
- /* ---------- 通知模块 ----------*/
+ /* ---------- 通知模块(旧入口,重定向至 §4.1 新入口) ----------*/
{
path: '/notification',
name: 'notification',
- redirect: '/notification/systemNotification',
- component: NotifyLayout,
- meta: {
- title: 'page_title_back_menu',
- },
+ redirect: { name: 'notificationRecords' },
children: [
{
path: 'systemNotification',
name: 'systemNotification',
- component: () => import('@/views/notification/SystemNotification.vue'),
- meta: { title: 'notify_system_notice' },
+ redirect: { name: 'notificationRecords' },
},
{
path: 'userNotification',
name: 'userNotification',
- component: () => import('@/views/notification/UserNotification.vue'),
- meta: { title: 'daas_notification_center_yonghucaozuo' },
+ redirect: { name: 'operationAudit' },
},
{
path: 'alarmNotification',
name: 'alarmNotification',
- component: AlarmNotification,
- meta: { title: 'daas_notification_alarmnotification_gaojingtongzhi' },
+ redirect: { name: 'notificationRecords' },
},
{
path: 'systemAlarm',
name: 'systemAlarm',
- component: () => import('@/views/notification/SystemAlarm.vue'),
- meta: { title: 'daas_notification_center_xitonggaojing' },
+ redirect: { name: 'alarmEvents' },
},
],
},
diff --git a/apps/daas/src/views/notification/AlarmEventDrawer.vue b/apps/daas/src/views/notification/AlarmEventDrawer.vue
new file mode 100644
index 0000000000..66a93be7b2
--- /dev/null
+++ b/apps/daas/src/views/notification/AlarmEventDrawer.vue
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+ {{ detail.summary }}
+
+
+
+ {{ ALARM_LEVEL_MAP[detail.level]?.text || detail.level }}
+
+
+ {{ ALARM_STATUS_MAP[detail.status]?.text || detail.status }}
+
+
+ {{ detail.id }}
+
+
+ {{ alarmKeyMap[detail.metric] || detail.metric }}
+
+
+ {{ fmt(detail.firstOccurrenceTime) }}
+
+
+ {{ fmt(detail.lastOccurrenceTime) }}
+
+
+ {{ duration }}
+
+
+ {{ detail.occurrenceCount ?? '-' }}
+
+
+ {{ fmt(detail.recoveryTime) }}
+
+
+ {{ fmt(detail.closeTime) }}
+ ({{ t('alarm_event_archived') }})
+ ({{ detail.closeBy }})
+
+
+ {{ detail.currentValue ?? '-' }}
+
+
+ {{ detail.threshold ?? '-' }}
+
+
+
+
+
+ {{ t(`alarm_object_type_${detail.objectType}`) }}
+
+
+ {{ detail.objectName || '-' }}
+
+ {{ t('alarm_event_object_deleted') }}
+
+
+
+ {{ detail.taskName }}({{ detail.taskId }})
+
+
+ {{ detail.nodeName || detail.nodeId }}
+
+
+ {{ detail.agentId }}
+
+
+
+ {{ t('alarm_event_open_monitor') }}
+ {{ t('alarm_event_open_setting') }}
+
+
+ {{ t('alarm_event_notification_overview') }}
+
+
+
+ {{ t('alarm_event_latest_notify') }}:{{
+ fmt(detail.deliverySummary.latestDeliveryTime)
+ }}
+
+ {{ t('alarm_event_view_notifications') }}
+
+
+
+
+
diff --git a/apps/daas/src/views/notification/AlarmEvents.vue b/apps/daas/src/views/notification/AlarmEvents.vue
new file mode 100644
index 0000000000..a99eb8e5e1
--- /dev/null
+++ b/apps/daas/src/views/notification/AlarmEvents.vue
@@ -0,0 +1,381 @@
+
+
+
+
+
+
+ {{ $t('public_button_close') }}
+
+
+ (multipleSelection = val)"
+ @row-click="openDetail"
+ >
+
+
+
+
{{ $t(card.label) }}
+
{{ stats[card.key] }}
+
+
+
+
+
+
+
+
+
+ {{
+ row.levelLabel
+ }}
+
+
+
+
+
+ {{ row.objectName || '-' }}
+
+ {{ $t(`alarm_object_type_${row.objectType}`) }}
+ · {{ $t('alarm_event_object_deleted') }}
+
+
+
+
+
+ {{ row.summary }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ $t('public_button_details')
+ }}
+
+ {{ $t('alarm_event_go_task') }}
+
+
+
+ {{ $t('public_button_close') }}
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/daas/src/views/notification/DeliverySummaryTag.vue b/apps/daas/src/views/notification/DeliverySummaryTag.vue
new file mode 100644
index 0000000000..9daf52fd80
--- /dev/null
+++ b/apps/daas/src/views/notification/DeliverySummaryTag.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+ {{ text }}
+
+
+ {{ t('delivery_status_none') }}
+
diff --git a/apps/daas/src/views/notification/NotificationPopover.vue b/apps/daas/src/views/notification/NotificationPopover.vue
index 708eded63d..a400e9689b 100644
--- a/apps/daas/src/views/notification/NotificationPopover.vue
+++ b/apps/daas/src/views/notification/NotificationPopover.vue
@@ -1,230 +1,115 @@
-
@@ -232,271 +117,96 @@ export default {
popper-class="notification-popover pt-1"
placement="bottom"
trigger="hover"
- width="450"
- @show="handleShow"
- @hide="handleHide"
+ width="420"
+ @show="loadRecords"
>
-
-
+
+
xiaoxi-2
-
- {{
- $t('notify_view_more')
- }}
-
-
- {{ $t('notify_system_notice') }}
-
-
-
- -
-
-
-
-
-
-
【{{ item.level }}】
-
{{ systemMap[item.system] }}
-
- {{ `${item.serverName} ` }}
-
-
- {{ item.serverName }}
-
-
-
- {{
- `${$t('notify_source_name')} : ${item.sourceName} , ${$t('notify_database_name')} : ${
- item.databaseName
- } , ${$t('notify_schema_name')} : ${item.schemaName} ,`
- }}
-
-
-
- {{ `DDL SQL : ${item.sql}` }}
-
-
-
-
- {{ typeMap[item.msg] }}
-
-
- {{ item.createTime }}
-
-
-
-
-
-
-
-
no-notice
-
- {{ $t('notify_no_notice') }}
-
-
+
+ {{
+ t('notification_bell_title', [unRead > 99 ? '99+' : unRead])
+ }}
+
+ {{ t('notification_record_mark_all_read') }}
+
+
+
+
-
-
-
- {{ $t('daas_notification_center_yonghucaozuo') }}
-
-
-
-
-
-
no-notice
-
- {{ $t('notify_view_more') }}
-
-
+
+ {{ record.taskName }} · {{ fmt(record.latestNotifyTime) }}
-
-
-
-
- {{
- $t('daas_notification_alarmnotification_gaojingtongzhi')
- }}
-
-
-
- -
-
-
-
-
-
- 【{{ item.title }}】
-
- {{ item.title }}
-
-
-
-
-
-
-
-
no-notice
-
- {{ $t('notify_no_notice') }}
-
-
+
+
+
+
+
no-notice
+
+ {{ t('notify_no_notice') }}
-
-
+
+
+
+ {{
+ t('notification_view_all_records')
+ }}
+
+
-
-
diff --git a/apps/daas/src/views/notification/NotificationRecordDrawer.vue b/apps/daas/src/views/notification/NotificationRecordDrawer.vue
new file mode 100644
index 0000000000..e3fb8cb32c
--- /dev/null
+++ b/apps/daas/src/views/notification/NotificationRecordDrawer.vue
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+ {{ detail.title }}
+
+ {{
+ ALARM_LEVEL_MAP[detail.level]?.text || detail.level
+ }}
+
+ {{ t('notification_record_others') }}
+
+
+ {{
+ detail.unread
+ ? t('notification_record_unread')
+ : t('notification_record_read')
+ }}
+
+ {{ t('notification_record_read_at') }}:{{
+ fmt(detail.readAt)
+ }}
+ {{
+ t('notification_record_others_hint')
+ }}
+
+ {{
+ detail.unread
+ ? t('notification_record_mark_read')
+ : t('notification_record_mark_unread')
+ }}
+
+
+
+
+
+ {{ detail.taskName || '-' }}
+
+
+ {{
+ (detail.metric && alarmKeyMap[detail.metric]) ||
+ detail.metric ||
+ '-'
+ }}
+
+
+ {{ fmt(detail.firstOccurrenceTime) }}
+
+
+ {{ fmt(detail.lastOccurrenceTime) }}
+
+
+ {{ detail.occurrenceCount ?? '-' }}
+
+
+ {{ fmt(detail.latestNotifyTime) }}
+
+
+ {{ detail.alarmEventId || '-' }}
+
+
+
+
+ {{ t('alarm_event_go_task') }}
+
+
+ {{ t('notification_record_view_alarm') }}
+
+ {{
+ t('notification_record_alarm_missing')
+ }}
+
+
+
+
{{ t('notification_record_deliveries') }}
+
+
+
+
+
+ {{ fmt(batch.time) }}
+
+ ·
+ {{
+ t('notification_record_occurrence_snapshot', [
+ batch.occurrenceCountSnapshot,
+ ])
+ }}
+
+
+
+
+ {{
+ t(`delivery_channel_${row.channel.toLowerCase()}`)
+ }}
+
+
+ {{ row.address || '-' }}
+
+
+
+
+ {{ t(`delivery_recipient_${row.status.toLowerCase()}`) }}
+
+
+
+
+ {{ fmt(row.sentAt) }}
+
+
+
+ {{
+ row.failureMessage ||
+ (row.skipReason
+ ? t(`delivery_skip_${row.skipReason.toLowerCase()}`)
+ : '-')
+ }}
+
+
+
+
+
+ {{ t('notification_record_smtp_hint') }}
+
+
+
+
+
diff --git a/apps/daas/src/views/notification/NotificationRecords.vue b/apps/daas/src/views/notification/NotificationRecords.vue
new file mode 100644
index 0000000000..3194962c9e
--- /dev/null
+++ b/apps/daas/src/views/notification/NotificationRecords.vue
@@ -0,0 +1,383 @@
+
+
+
+
+
+
+ {{ $t('notification_record_mark_read') }}
+
+
+ {{ $t('notification_record_mark_unread') }}
+
+
+ {{ $t('notification_record_mark_all_read') }}
+
+
+ {{ $t('notify_setting') }}
+
+
+ (multipleSelection = val)"
+ @row-click="openDetail"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ row.levelLabel
+ }}
+
+
+
+
+
+ {{ row.title }}
+
+
+ {{ $t('notification_record_legacy') }}
+
+
+ {{ $t('notification_record_alarm_missing') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ $t('public_button_details')
+ }}
+
+
+
+ {{
+ row.unread
+ ? $t('notification_record_mark_read')
+ : $t('notification_record_mark_unread')
+ }}
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/daas/src/views/role/Role.vue b/apps/daas/src/views/role/Role.vue
index adb9862e12..1cfe5d9198 100644
--- a/apps/daas/src/views/role/Role.vue
+++ b/apps/daas/src/views/role/Role.vue
@@ -24,6 +24,8 @@ const MODULE_ICONS: Record
= {
v2_data_pipeline: 'migrate',
v2_advanced_features: 'vip-one',
'v2_data-server': 'apiServer_navbar',
+ 'v2_ops-center': 'warning',
+ 'v2_message-center': 'notice-system-notice',
'v2_system-management': 'setting',
}
@@ -428,6 +430,25 @@ const pageSort = [
},
],
},
+ {
+ name: 'v2_ops-center',
+ children: [
+ {
+ name: 'v2_alarm_event',
+ buttons: [
+ {
+ label: t('public_button_close'),
+ name: 'v2_alarm_event_ack_close',
+ checked: false,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ name: 'v2_message-center',
+ children: [{ name: 'v2_notification_record' }],
+ },
{
name: 'v2_system-management',
children: [
@@ -458,6 +479,7 @@ const pageSort = [
],
},
{ name: 'v2_role_management' },
+ { name: 'v2_audit_log' },
],
},
]
diff --git a/apps/daas/src/views/setting/NotificationSetting.vue b/apps/daas/src/views/setting/NotificationSetting.vue
index 629073735b..4adff733de 100644
--- a/apps/daas/src/views/setting/NotificationSetting.vue
+++ b/apps/daas/src/views/setting/NotificationSetting.vue
@@ -70,6 +70,20 @@ export default {
type="info"
show-icon
/>
+
+ {{ $t('alarm_event_open_setting') }}
+
{{ notificationMAP[item.label] }}
- {{
- $t('notify_system_notice')
- }}
+
{{
@@ -162,9 +174,7 @@ export default {
-
{{ notificationMAP[item.label] }}
- {{
- $t('notify_system_notice')
- }}
+
@@ -177,9 +187,7 @@ export default {
-
{{ notificationMAP[item.label] }}
- {{
- $t('notify_system_notice')
- }}
+
diff --git a/packages/api/src/core/alarm.ts b/packages/api/src/core/alarm.ts
index 432006495e..7f8ad87b40 100644
--- a/packages/api/src/core/alarm.ts
+++ b/packages/api/src/core/alarm.ts
@@ -229,3 +229,106 @@ export function fetchAlarmReceiverPreview(taskId: string) {
{ params: { taskId } },
)
}
+
+/* ---------- 告警事件(TAP-13052) ---------- */
+
+export type AlarmObjectType =
+ | 'TASK'
+ | 'NODE'
+ | 'CONNECTION'
+ | 'AGENT'
+ | 'INSPECT'
+ | 'API_SERVER'
+
+export interface DeliveryChannelSummary {
+ total: number
+ success: number
+ failed: number
+ skipped: number
+}
+
+export interface DeliverySummary {
+ status: 'NONE' | 'SUCCESS' | 'PARTIAL_FAILED' | 'FAILED' | 'SKIPPED'
+ total: number
+ success: number
+ failed: number
+ skipped: number
+ channels: Notify[]
+ byChannel: Partial>
+ latestDeliveryTime?: string
+}
+
+export interface AlarmEvent {
+ id: string
+ level: string
+ status: 'ING' | 'RECOVER' | 'CLOESE'
+ metric: string
+ summary: string
+ objectType: AlarmObjectType
+ objectId?: string
+ objectName?: string
+ objectDeleted: boolean
+ taskId?: string
+ taskName?: string
+ syncType?: string
+ nodeId?: string
+ nodeName?: string
+ agentId?: string
+ connectionId?: string
+ inspectId?: string
+ serverId?: string
+ apiId?: string
+ currentValue?: number
+ threshold?: number
+ firstOccurrenceTime?: string
+ lastOccurrenceTime?: string
+ occurrenceCount?: number
+ recoveryTime?: string
+ closeTime?: string
+ closeBy?: string
+ /** 任务重新运行时由系统归档 */
+ archived?: boolean
+ lastNotifyTime?: string
+ deliverySummary?: DeliverySummary
+}
+
+export interface AlarmEventStats {
+ alarming: number
+ severe: number
+ todayNew: number
+ todayRecovered: number
+}
+
+/** 数组参数以逗号拼接,服务端按列表绑定 */
+export function joinListParams(params: Record = {}) {
+ const result: Record = {}
+ Object.entries(params).forEach(([key, value]) => {
+ if (value === undefined || value === null || value === '') return
+ if (Array.isArray(value)) {
+ if (value.length) result[key] = value.join(',')
+ return
+ }
+ result[key] = value
+ })
+ return result
+}
+
+export function listAlarmEvents(params: Record) {
+ return requestClient.get<{ total: number; items: AlarmEvent[] }>(
+ '/api/alarms',
+ { params: joinListParams(params) },
+ )
+}
+
+export function getAlarmEvent(id: string) {
+ return requestClient.get(`/api/alarms/${id}`)
+}
+
+export function getAlarmEventStats() {
+ return requestClient.get('/api/alarms/stats')
+}
+
+/** 关闭告警事件,需要告警关闭权限并写入操作审计 */
+export function closeAlarmEvents(ids: string[]) {
+ return requestClient.post('/api/alarms/close', ids)
+}
diff --git a/packages/api/src/core/notification.ts b/packages/api/src/core/notification.ts
index 3fd07df85a..25b5a1efbd 100644
--- a/packages/api/src/core/notification.ts
+++ b/packages/api/src/core/notification.ts
@@ -1,4 +1,5 @@
import { isPlainObj } from '@tap/shared'
+import { joinListParams, type DeliverySummary } from './alarm'
import {
requestClient,
type CountFetchResult,
@@ -84,3 +85,95 @@ export function updateNotification(where: any, attributes: any) {
attributes,
)
}
+
+/* ---------- 通知记录(TAP-13052) ---------- */
+
+const RECORD_URL = '/api/notifications'
+
+export interface NotificationRecord {
+ id: string
+ alarmEventId?: string
+ legacy: boolean
+ /** 关联告警事件已被清除(历史版本任务重新运行时删除) */
+ alarmEventMissing?: boolean
+ /** 记录中有发给当前用户的消息,才可标记已读/未读 */
+ readable: boolean
+ messageIds: string[]
+ title: string
+ level: string
+ metric?: string
+ alarmStatus?: string
+ objectType?: string
+ taskId?: string
+ taskName?: string
+ firstOccurrenceTime?: string
+ lastOccurrenceTime?: string
+ occurrenceCount?: number
+ notificationCount: number
+ unread: boolean
+ latestNotifyTime?: string
+ deliverySummary?: DeliverySummary
+}
+
+export interface NotificationRecipient {
+ channel: string
+ address?: string
+ status: 'SUCCESS' | 'FAILED' | 'SKIPPED'
+ sentAt?: string
+ failureMessage?: string
+ skipReason?: string
+}
+
+export interface NotificationBatch {
+ batchId: string
+ time?: string
+ occurrenceCountSnapshot?: number
+ recipients: NotificationRecipient[]
+}
+
+export interface NotificationRecordDetail extends NotificationRecord {
+ content: string
+ readAt?: string
+ batches: NotificationBatch[]
+}
+
+export function listNotificationRecords(params: Record) {
+ return requestClient.get<{ total: number; items: NotificationRecord[] }>(
+ RECORD_URL,
+ { params: joinListParams(params) },
+ )
+}
+
+export function getNotificationRecord(id: string) {
+ return requestClient.get(
+ `${RECORD_URL}/${encodeURIComponent(id)}`,
+ )
+}
+
+export function getUnreadNotificationCount() {
+ return requestClient.get(`${RECORD_URL}/unread-count`)
+}
+
+export function previewNotificationReadState(
+ params: Record,
+ read: boolean,
+) {
+ return requestClient.get<{
+ count: number
+ ids: string[]
+ truncated: boolean
+ }>(`${RECORD_URL}/read-state/preview`, {
+ params: { ...joinListParams(params), read },
+ })
+}
+
+export function updateNotificationReadState(ids: string[], read: boolean) {
+ return requestClient.patch(`${RECORD_URL}/read-state`, { ids, read })
+}
+
+export function updateOneNotificationReadState(id: string, read: boolean) {
+ return requestClient.patch(
+ `${RECORD_URL}/${encodeURIComponent(id)}/read-state`,
+ { read },
+ )
+}