From 9cbacf59984caf1b6525bb74f138ddd04eda29bf Mon Sep 17 00:00:00 2001 From: zxr <271055687@qq.com> Date: Mon, 20 Jul 2026 21:59:57 +0800 Subject: [PATCH] feat: configure static and dynamic alert rules --- src/api/ops/alertPolicy.ts | 6 +- src/api/ops/alertTemplate.ts | 11 +- .../setting/components/RuleFormDialog.vue | 138 +++++++++++++----- .../setting/components/RuleManageDialog.vue | 5 +- .../alert/template/components/RuleEditor.vue | 106 +++++++++++++- 5 files changed, 209 insertions(+), 57 deletions(-) diff --git a/src/api/ops/alertPolicy.ts b/src/api/ops/alertPolicy.ts index 23d272c..83e33fe 100644 --- a/src/api/ops/alertPolicy.ts +++ b/src/api/ops/alertPolicy.ts @@ -68,10 +68,11 @@ export const createRule = (data: { severity_id: number enabled?: boolean metric_name?: string - query_expr?: string + data_source?: string threshold?: number compare_op?: string duration?: number + eval_interval?: number baseline_config?: string labels?: string annotations?: string @@ -86,10 +87,11 @@ export const updateRule = (data: { severity_id?: number enabled?: boolean metric_name?: string - query_expr?: string + data_source?: string threshold?: number compare_op?: string duration?: number + eval_interval?: number baseline_config?: string labels?: string annotations?: string diff --git a/src/api/ops/alertTemplate.ts b/src/api/ops/alertTemplate.ts index be50a4d..992d904 100644 --- a/src/api/ops/alertTemplate.ts +++ b/src/api/ops/alertTemplate.ts @@ -20,12 +20,13 @@ export interface AlertRule { name: string data_source: string metric_name: string - rule_type: 'static' | 'dynamic' | 'promql' + rule_type: 'static' | 'dynamic' compare_op: '>' | '>=' | '<' | '<=' | '==' | '!=' threshold: string | number duration: number eval_interval: number severity_code: string + baseline_config?: string labels?: Record annotations?: Record } @@ -105,12 +106,11 @@ export const TEMPLATE_CATEGORIES = [ // 数据源选项 export const DATA_SOURCES = [ { value: 'dc-host', label: '主机/操作系统指标' }, - { value: 'dc-hardware', label: '服务器硬件指标' }, - { value: 'dc-network', label: '网络设备指标' }, { value: 'dc-database', label: '数据库指标' }, { value: 'dc-middleware', label: '中间件指标' }, - { value: 'dc-virtualization', label: '虚拟化指标' }, - { value: 'dc-env', label: '动力/环境/安防指标' }, + { value: 'dc-security', label: '安全设备指标' }, + { value: 'dc-storage', label: '存储设备指标' }, + { value: 'dc-room-device', label: '机房设备指标' }, ] // 比较运算符选项 @@ -127,7 +127,6 @@ export const COMPARE_OPERATORS = [ export const RULE_TYPES = [ { value: 'static', label: '静态阈值' }, { value: 'dynamic', label: '动态阈值' }, - { value: 'promql', label: 'PromQL 表达式' }, ] // ==================== 告警模板接口 ==================== diff --git a/src/views/ops/pages/alert/setting/components/RuleFormDialog.vue b/src/views/ops/pages/alert/setting/components/RuleFormDialog.vue index 7651610..7ac0416 100644 --- a/src/views/ops/pages/alert/setting/components/RuleFormDialog.vue +++ b/src/views/ops/pages/alert/setting/components/RuleFormDialog.vue @@ -16,8 +16,9 @@ - + 静态规则 + 动态基线 @@ -50,13 +51,12 @@ - - + - + @@ -70,8 +70,15 @@ - - + + @@ -89,6 +96,45 @@ + + + + + + + + + + + + + + + + + + 平均值 + 最大值 + 最小值 + + + + + + + 平均值 + 最大值 + 最小值 + + + + + + + + + + @@ -134,10 +180,15 @@ const formData = reactive({ enabled: true, severity_id: 0 as number, metric_name: '', - query_expr: '', + data_source: 'dc-control', threshold: undefined as number | undefined, compare_op: '>', duration: 60, + eval_interval: 60, + lookback_seconds: 3600, + baseline_aggregation: 'avg', + current_aggregation: 'avg', + min_samples: 1, description: '', }) @@ -149,7 +200,7 @@ const formRules = { metric_name: [ { validator: (value: string, callback: any) => { - if (formData.rule_type !== 'promql' && !value) { + if (!value) { callback('请输入指标名称') return } @@ -157,21 +208,10 @@ const formRules = { }, }, ], - query_expr: [ - { - validator: (value: string, callback: any) => { - if (formData.rule_type === 'promql' && !value) { - callback('请输入 PromQL 表达式') - return - } - callback() - }, - }, - ], compare_op: [ { validator: (value: string, callback: any) => { - if (formData.rule_type !== 'promql' && !value) { + if (!value) { callback('请选择比较操作符') return } @@ -182,7 +222,7 @@ const formRules = { threshold: [ { validator: (value: number, callback: any) => { - if (formData.rule_type !== 'promql' && value === undefined) { + if (value === undefined) { callback('请输入阈值') return } @@ -246,16 +286,8 @@ const handleSeveritySearch = (keyword: string) => { // 规则类型变化处理 const handleRuleTypeChange = (value: string) => { - // 根据规则类型清空相关字段 - if (value === 'promql') { - formData.metric_name = '' - formData.compare_op = '' - formData.threshold = undefined - } else { - formData.query_expr = '' - if (!formData.compare_op) { - formData.compare_op = '>' - } + if (!formData.compare_op || (value === 'dynamic' && ['==', '!='].includes(formData.compare_op))) { + formData.compare_op = '>' } } @@ -269,15 +301,29 @@ const loadRuleDetail = async () => { const rule = res.details formData.name = rule.name - // 当前产品只保留静态规则:强制按 static 渲染 - formData.rule_type = 'static' + formData.rule_type = rule.rule_type === 'dynamic' ? 'dynamic' : 'static' formData.enabled = rule.enabled formData.severity_id = rule.severity_id formData.metric_name = rule.metric_name || '' - formData.query_expr = rule.query_expr || '' + formData.data_source = rule.data_source || 'dc-control' formData.threshold = rule.threshold formData.compare_op = rule.compare_op || '>' - formData.duration = rule.duration || 60 + formData.duration = rule.duration ?? 60 + formData.eval_interval = rule.eval_interval ?? 60 + if (rule.rule_type === 'dynamic') { + try { + const baseline = JSON.parse(rule.baseline_config || '{}') + formData.lookback_seconds = baseline.lookback_seconds || 3600 + formData.baseline_aggregation = baseline.aggregation || 'avg' + formData.current_aggregation = baseline.current_aggregation || 'avg' + formData.min_samples = baseline.min_samples || 1 + } catch { + formData.lookback_seconds = 3600 + formData.baseline_aggregation = 'avg' + formData.current_aggregation = 'avg' + formData.min_samples = 1 + } + } formData.description = rule.description || '' } } catch (error) { @@ -293,10 +339,15 @@ const resetForm = () => { formData.enabled = true formData.severity_id = 0 formData.metric_name = '' - formData.query_expr = '' + formData.data_source = 'dc-control' formData.threshold = undefined formData.compare_op = '>' formData.duration = 60 + formData.eval_interval = 60 + formData.lookback_seconds = 3600 + formData.baseline_aggregation = 'avg' + formData.current_aggregation = 'avg' + formData.min_samples = 1 formData.description = '' } @@ -327,10 +378,20 @@ const handleSubmit = async () => { submitLoading.value = true try { + const { lookback_seconds, baseline_aggregation, current_aggregation, min_samples, ...ruleFields } = formData const data = { - ...formData, + ...ruleFields, policy_id: props.policyId, - severity_id: formData.severity_id, // 确保 severity_id 是 number 类型 + severity_id: formData.severity_id, + baseline_config: + formData.rule_type === 'dynamic' + ? JSON.stringify({ + lookback_seconds, + aggregation: baseline_aggregation, + current_aggregation, + min_samples, + }) + : '{}', } // 按契约:rule/create 不做 jsonb 默认值归一化。 @@ -338,7 +399,6 @@ const handleSubmit = async () => { const createPayload = !isEdit.value ? { ...data, - baseline_config: '{}', labels: '{}', annotations: '{}', } diff --git a/src/views/ops/pages/alert/setting/components/RuleManageDialog.vue b/src/views/ops/pages/alert/setting/components/RuleManageDialog.vue index 26c4bd9..8b25789 100644 --- a/src/views/ops/pages/alert/setting/components/RuleManageDialog.vue +++ b/src/views/ops/pages/alert/setting/components/RuleManageDialog.vue @@ -44,8 +44,8 @@ @@ -134,7 +134,6 @@ const currentRuleId = ref(undefined) const ruleTypeMap: Record = { static: '静态规则', dynamic: '动态规则', - promql: 'PromQL', } // 比较操作符映射 diff --git a/src/views/ops/pages/alert/template/components/RuleEditor.vue b/src/views/ops/pages/alert/template/components/RuleEditor.vue index 5939c39..ff5a2db 100644 --- a/src/views/ops/pages/alert/template/components/RuleEditor.vue +++ b/src/views/ops/pages/alert/template/components/RuleEditor.vue @@ -75,7 +75,7 @@ - + {{ item.label }} @@ -92,8 +92,18 @@ - - + + @@ -115,7 +125,7 @@ @@ -123,6 +133,43 @@ + @@ -148,6 +195,14 @@ import { interface RuleItem extends AlertRule { _metrics?: MetricMeta[] _metricsLoading?: boolean + _baseline?: BaselineForm +} + +interface BaselineForm { + lookback_seconds: number + aggregation: 'avg' | 'max' | 'min' + current_aggregation: 'avg' | 'max' | 'min' + min_samples: number } const props = defineProps<{ @@ -171,11 +226,36 @@ const baseSeverityOptions = ref([]) const localSeverityOptions = ref([]) const severityLoading = ref(false) +const defaultBaselineConfig = (): BaselineForm => ({ + lookback_seconds: 3600, + aggregation: 'avg', + current_aggregation: 'avg', + min_samples: 1, +}) + +const parseBaselineConfig = (raw?: string): BaselineForm => { + try { + const parsed = JSON.parse(raw || '{}') + return { + lookback_seconds: Number(parsed.lookback_seconds) > 0 ? Number(parsed.lookback_seconds) : 3600, + aggregation: ['avg', 'max', 'min'].includes(parsed.aggregation) ? parsed.aggregation : 'avg', + current_aggregation: ['avg', 'max', 'min'].includes(parsed.current_aggregation) ? parsed.current_aggregation : 'avg', + min_samples: Number(parsed.min_samples) > 0 ? Number(parsed.min_samples) : 1, + } + } catch { + return defaultBaselineConfig() + } +} + // 监听 props 变化,深拷贝到本地 watch( () => props.rules, (val) => { - localRules.value = JSON.parse(JSON.stringify(val)) + const copied: RuleItem[] = JSON.parse(JSON.stringify(val)) + localRules.value = copied.map((rule) => ({ + ...rule, + _baseline: parseBaselineConfig(rule.baseline_config), + })) }, { immediate: true, deep: true } ) @@ -268,8 +348,9 @@ defineExpose({ // 同步更新到父组件 const syncUpdate = (index: number) => { const newRules = [...props.rules] - const { _metrics, _metricsLoading, ...rest } = localRules.value[index] - newRules[index] = { ...rest, _metrics, _metricsLoading } as RuleItem + const { _metrics, _metricsLoading, _baseline, ...rest } = localRules.value[index] + const baseline_config = rest.rule_type === 'dynamic' ? JSON.stringify(_baseline || defaultBaselineConfig()) : '{}' + newRules[index] = { ...rest, baseline_config, _metrics, _metricsLoading } as RuleItem emit('update:rules', newRules) } @@ -287,6 +368,17 @@ const handleRuleDataSourceChange = (index: number) => { handleLoadMetrics(index) } +const handleRuleTypeChange = (index: number) => { + const rule = localRules.value[index] + if (rule.rule_type === 'dynamic') { + if (rule.compare_op === '==' || rule.compare_op === '!=') { + rule.compare_op = '>' + } + rule._baseline ||= parseBaselineConfig(rule.baseline_config) + } + syncUpdate(index) +} + // 下拉框展开时加载指标(备用触发) const handleMetricsDropdownChange = (visible: boolean, index: number) => { if (visible) {