feat
This commit is contained in:
136
src/api/ops/suppression.ts
Normal file
136
src/api/ops/suppression.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { request } from '@/api/request'
|
||||||
|
|
||||||
|
// 抑制规则类型
|
||||||
|
export type SuppressionType = 'dedup' | 'aggregate' | 'dependency' | 'throttle' | 'schedule'
|
||||||
|
|
||||||
|
// 抑制规则接口定义
|
||||||
|
export interface SuppressionRule {
|
||||||
|
id?: number
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
type: SuppressionType
|
||||||
|
enabled?: boolean
|
||||||
|
priority?: number
|
||||||
|
policy_id?: number
|
||||||
|
// dedup 类型字段
|
||||||
|
dedup_window?: number
|
||||||
|
dedup_keys?: string
|
||||||
|
// aggregate 类型字段
|
||||||
|
aggregate_window?: number
|
||||||
|
group_by?: string
|
||||||
|
aggregate_count?: number
|
||||||
|
// dependency 类型字段
|
||||||
|
source_matchers?: string
|
||||||
|
target_matchers?: string
|
||||||
|
// throttle 类型字段
|
||||||
|
throttle_count?: number
|
||||||
|
throttle_window?: number
|
||||||
|
// schedule 类型字段
|
||||||
|
schedule?: string
|
||||||
|
// 通用匹配条件
|
||||||
|
matchers?: string
|
||||||
|
// 运行态统计
|
||||||
|
hit_count?: number
|
||||||
|
suppressed_count?: number
|
||||||
|
last_hit_at?: string
|
||||||
|
created_at?: string
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抑制规则列表查询参数
|
||||||
|
export interface SuppressionListParams {
|
||||||
|
page?: number
|
||||||
|
page_size?: number
|
||||||
|
keyword?: string
|
||||||
|
sort?: string
|
||||||
|
order?: string
|
||||||
|
type?: SuppressionType
|
||||||
|
policy_id?: number
|
||||||
|
enabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建抑制规则参数
|
||||||
|
export interface SuppressionCreateParams {
|
||||||
|
name: string
|
||||||
|
type: SuppressionType
|
||||||
|
description?: string
|
||||||
|
enabled?: boolean
|
||||||
|
priority?: number
|
||||||
|
policy_id?: number
|
||||||
|
dedup_window?: number
|
||||||
|
dedup_keys?: string
|
||||||
|
aggregate_window?: number
|
||||||
|
group_by?: string
|
||||||
|
aggregate_count?: number
|
||||||
|
source_matchers?: string
|
||||||
|
target_matchers?: string
|
||||||
|
throttle_count?: number
|
||||||
|
throttle_window?: number
|
||||||
|
schedule?: string
|
||||||
|
matchers?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新抑制规则参数
|
||||||
|
export interface SuppressionUpdateParams {
|
||||||
|
id: number
|
||||||
|
name?: string
|
||||||
|
type?: SuppressionType
|
||||||
|
description?: string
|
||||||
|
enabled?: boolean
|
||||||
|
priority?: number
|
||||||
|
policy_id?: number
|
||||||
|
dedup_window?: number
|
||||||
|
dedup_keys?: string
|
||||||
|
aggregate_window?: number
|
||||||
|
group_by?: string
|
||||||
|
aggregate_count?: number
|
||||||
|
source_matchers?: string
|
||||||
|
target_matchers?: string
|
||||||
|
throttle_count?: number
|
||||||
|
throttle_window?: number
|
||||||
|
schedule?: string
|
||||||
|
matchers?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抑制类型选项
|
||||||
|
export const SUPPRESSION_TYPE_OPTIONS = [
|
||||||
|
{ value: 'dedup', label: '去重' },
|
||||||
|
{ value: 'aggregate', label: '聚合' },
|
||||||
|
{ value: 'dependency', label: '依赖抑制' },
|
||||||
|
{ value: 'throttle', label: '限流' },
|
||||||
|
{ value: 'schedule', label: '定时屏蔽' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 抑制类型标签颜色
|
||||||
|
export const SUPPRESSION_TYPE_COLORS: Record<SuppressionType, string> = {
|
||||||
|
dedup: 'arcoblue',
|
||||||
|
aggregate: 'green',
|
||||||
|
dependency: 'orangered',
|
||||||
|
throttle: 'orange',
|
||||||
|
schedule: 'purple',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取抑制规则列表 */
|
||||||
|
export const fetchSuppressionList = (params?: SuppressionListParams) => {
|
||||||
|
return request.get('/Alert/v1/suppression/list', { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取抑制规则详情 */
|
||||||
|
export const fetchSuppressionDetail = (id: number) => {
|
||||||
|
return request.get(`/Alert/v1/suppression/get/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建抑制规则 */
|
||||||
|
export const createSuppression = (data: SuppressionCreateParams) => {
|
||||||
|
return request.post('/Alert/v1/suppression/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新抑制规则 */
|
||||||
|
export const updateSuppression = (data: SuppressionUpdateParams) => {
|
||||||
|
return request.post('/Alert/v1/suppression/update', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除抑制规则 */
|
||||||
|
export const deleteSuppression = (id: number) => {
|
||||||
|
return request.delete(`/Alert/v1/suppression/delete/${id}`)
|
||||||
|
}
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
:title="title"
|
||||||
|
:width="800"
|
||||||
|
:ok-loading="submitting"
|
||||||
|
:mask-closable="false"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="rules"
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="规则名称" field="name" :rules="[{ required: true, message: '请输入规则名称' }]">
|
||||||
|
<a-input v-model="formData.name" placeholder="请输入规则名称" :max-length="100" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="抑制类型" field="type" :rules="[{ required: true, message: '请选择抑制类型' }]">
|
||||||
|
<a-select v-model="formData.type" placeholder="请选择抑制类型" :disabled="isEdit">
|
||||||
|
<a-option v-for="item in SUPPRESSION_TYPE_OPTIONS" :key="item.value" :value="item.value">
|
||||||
|
{{ item.label }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="优先级" field="priority">
|
||||||
|
<a-input-number
|
||||||
|
v-model="formData.priority"
|
||||||
|
placeholder="数值越小越先匹配"
|
||||||
|
:min="1"
|
||||||
|
:max="10000"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="关联策略" field="policy_id">
|
||||||
|
<a-select
|
||||||
|
v-model="formData.policy_id"
|
||||||
|
placeholder="选择策略或留空表示全局"
|
||||||
|
allow-clear
|
||||||
|
allow-search
|
||||||
|
>
|
||||||
|
<a-option :value="0">
|
||||||
|
<a-tag color="gray">全局</a-tag>
|
||||||
|
</a-option>
|
||||||
|
<a-option v-for="policy in policyOptions" :key="policy.id" :value="policy.id">
|
||||||
|
{{ policy.name }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="启用状态" field="enabled">
|
||||||
|
<a-switch v-model="formData.enabled" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-form-item label="描述" field="description">
|
||||||
|
<a-textarea
|
||||||
|
v-model="formData.description"
|
||||||
|
placeholder="请输入规则描述"
|
||||||
|
:max-length="500"
|
||||||
|
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 匹配条件 -->
|
||||||
|
<a-form-item label="匹配条件 (JSON)" field="matchers">
|
||||||
|
<a-textarea
|
||||||
|
v-model="formData.matchers"
|
||||||
|
placeholder="JSON 格式的匹配条件,如 {} 表示全部告警"
|
||||||
|
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- dedup 类型配置 -->
|
||||||
|
<template v-if="formData.type === 'dedup'">
|
||||||
|
<a-divider>去重配置</a-divider>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="去重窗口 (秒)" field="dedup_window">
|
||||||
|
<a-input-number
|
||||||
|
v-model="formData.dedup_window"
|
||||||
|
placeholder="请输入去重窗口时间"
|
||||||
|
:min="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="去重键" field="dedup_keys">
|
||||||
|
<a-input
|
||||||
|
v-model="formData.dedup_keys"
|
||||||
|
placeholder="逗号分隔,如 fingerprint,alert_name"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- aggregate 类型配置 -->
|
||||||
|
<template v-if="formData.type === 'aggregate'">
|
||||||
|
<a-divider>聚合配置</a-divider>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="聚合窗口 (秒)" field="aggregate_window">
|
||||||
|
<a-input-number
|
||||||
|
v-model="formData.aggregate_window"
|
||||||
|
placeholder="请输入聚合窗口时间"
|
||||||
|
:min="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="分组字段" field="group_by">
|
||||||
|
<a-input
|
||||||
|
v-model="formData.group_by"
|
||||||
|
placeholder="逗号分隔"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-form-item label="聚合阈值" field="aggregate_count">
|
||||||
|
<a-input-number
|
||||||
|
v-model="formData.aggregate_count"
|
||||||
|
placeholder="触发阈值"
|
||||||
|
:min="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- dependency 类型配置 -->
|
||||||
|
<template v-if="formData.type === 'dependency'">
|
||||||
|
<a-divider>依赖抑制配置</a-divider>
|
||||||
|
<a-form-item label="源匹配器 (JSON)" field="source_matchers">
|
||||||
|
<a-textarea
|
||||||
|
v-model="formData.source_matchers"
|
||||||
|
placeholder="JSON 格式的源匹配条件"
|
||||||
|
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="目标匹配器 (JSON)" field="target_matchers">
|
||||||
|
<a-textarea
|
||||||
|
v-model="formData.target_matchers"
|
||||||
|
placeholder="JSON 格式的目标匹配条件"
|
||||||
|
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- throttle 类型配置 -->
|
||||||
|
<template v-if="formData.type === 'throttle'">
|
||||||
|
<a-divider>限流配置</a-divider>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="限流次数" field="throttle_count">
|
||||||
|
<a-input-number
|
||||||
|
v-model="formData.throttle_count"
|
||||||
|
placeholder="允许发送次数"
|
||||||
|
:min="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="限流窗口 (秒)" field="throttle_window">
|
||||||
|
<a-input-number
|
||||||
|
v-model="formData.throttle_window"
|
||||||
|
placeholder="限流时间窗口"
|
||||||
|
:min="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- schedule 类型配置 -->
|
||||||
|
<template v-if="formData.type === 'schedule'">
|
||||||
|
<a-divider>定时屏蔽配置</a-divider>
|
||||||
|
<a-form-item label="时间计划 (JSON)" field="schedule">
|
||||||
|
<a-textarea
|
||||||
|
v-model="formData.schedule"
|
||||||
|
placeholder="JSON 格式的时间计划配置"
|
||||||
|
:auto-size="{ minRows: 5, maxRows: 10 }"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 编辑时显示统计信息 -->
|
||||||
|
<template v-if="isEdit">
|
||||||
|
<a-divider>运行统计</a-divider>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-statistic title="命中次数" :value="formData.hit_count || 0" />
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-statistic title="已抑制数量" :value="formData.suppressed_count || 0" />
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-descriptions :column="1">
|
||||||
|
<a-descriptions-item label="最后命中时间">
|
||||||
|
{{ formData.last_hit_at ? formatDateTime(formData.last_hit_at) : '-' }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { SUPPRESSION_TYPE_OPTIONS, type SuppressionRule } from '@/api/ops/suppression'
|
||||||
|
import type { PolicyOption, SuppressionFormData } from '../types'
|
||||||
|
import { DEFAULT_FORM_DATA, FORM_RULES } from '../constants'
|
||||||
|
import { formatDateTime, validateJsonFields, buildUpdateData, buildCreateData } from '../utils'
|
||||||
|
import { createSuppression, updateSuppression } from '@/api/ops/suppression'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean
|
||||||
|
isEdit: boolean
|
||||||
|
data: SuppressionFormData
|
||||||
|
policyOptions: PolicyOption[]
|
||||||
|
submitting: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:visible', value: boolean): void
|
||||||
|
(e: 'update:submitting', value: boolean): void
|
||||||
|
(e: 'success'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const formData = ref<SuppressionFormData>({ ...DEFAULT_FORM_DATA })
|
||||||
|
const rules = FORM_RULES
|
||||||
|
|
||||||
|
// 弹窗标题
|
||||||
|
const title = computed(() =>
|
||||||
|
props.isEdit ? `编辑抑制规则 - ${formData.value.name}` : '新建抑制规则'
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听外部数据变化
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
(newData) => {
|
||||||
|
if (newData) {
|
||||||
|
formData.value = { ...newData }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false)
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleOk = async () => {
|
||||||
|
// 先进行表单验证
|
||||||
|
try {
|
||||||
|
const valid = await formRef.value?.validate()
|
||||||
|
if (valid) return false
|
||||||
|
} catch (validateError) {
|
||||||
|
console.log('表单验证失败:', validateError)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验 JSON 字段
|
||||||
|
const jsonFields = ['matchers', 'source_matchers', 'target_matchers', 'schedule']
|
||||||
|
const jsonError = validateJsonFields(formData.value, jsonFields)
|
||||||
|
if (jsonError) {
|
||||||
|
Message.error(jsonError)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
emit('update:submitting', true)
|
||||||
|
|
||||||
|
if (props.isEdit) {
|
||||||
|
// 更新
|
||||||
|
const updateData = buildUpdateData(formData.value)
|
||||||
|
const res = await updateSuppression(updateData)
|
||||||
|
if (res.code === 0) {
|
||||||
|
Message.success('更新成功')
|
||||||
|
handleCancel()
|
||||||
|
emit('success')
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
Message.error(res.message || '更新失败')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 创建
|
||||||
|
const createData = buildCreateData(formData.value)
|
||||||
|
const res = await createSuppression(createData)
|
||||||
|
if (res.code === 0) {
|
||||||
|
Message.success('创建成功')
|
||||||
|
handleCancel()
|
||||||
|
emit('success')
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
Message.error(res.message || '创建失败')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
Message.error(error.message || '提交失败')
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
emit('update:submitting', false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法供父组件调用
|
||||||
|
defineExpose({
|
||||||
|
resetFields: () => formRef.value?.resetFields(),
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'SuppressionModal',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
92
src/views/ops/pages/alert/suppress/config/columns.ts
Normal file
92
src/views/ops/pages/alert/suppress/config/columns.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格列配置
|
||||||
|
*/
|
||||||
|
export const getTableColumns = (): TableColumnData[] => [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
dataIndex: 'id',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '规则名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: 180,
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '描述',
|
||||||
|
dataIndex: 'description',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '抑制类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
width: 100,
|
||||||
|
slotName: 'type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '启用状态',
|
||||||
|
dataIndex: 'enabled',
|
||||||
|
width: 100,
|
||||||
|
slotName: 'enabled',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '优先级',
|
||||||
|
dataIndex: 'priority',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '作用范围',
|
||||||
|
dataIndex: 'policy_id',
|
||||||
|
width: 120,
|
||||||
|
slotName: 'policy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '关键配置',
|
||||||
|
dataIndex: 'config',
|
||||||
|
width: 200,
|
||||||
|
slotName: 'config',
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '匹配条件',
|
||||||
|
dataIndex: 'matchers',
|
||||||
|
width: 100,
|
||||||
|
slotName: 'matchers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '命中次数',
|
||||||
|
dataIndex: 'hit_count',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '已抑制',
|
||||||
|
dataIndex: 'suppressed_count',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '最后命中',
|
||||||
|
dataIndex: 'last_hit_at',
|
||||||
|
width: 160,
|
||||||
|
slotName: 'last_hit_at',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'created_at',
|
||||||
|
width: 160,
|
||||||
|
slotName: 'created_at',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'operations',
|
||||||
|
width: 220,
|
||||||
|
slotName: 'operations',
|
||||||
|
fixed: 'right',
|
||||||
|
},
|
||||||
|
]
|
||||||
54
src/views/ops/pages/alert/suppress/config/formItems.ts
Normal file
54
src/views/ops/pages/alert/suppress/config/formItems.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { computed } from 'vue'
|
||||||
|
import type { FormItem } from '@/components/search-form/types'
|
||||||
|
import { SUPPRESSION_TYPE_OPTIONS } from '@/api/ops/suppression'
|
||||||
|
import type { PolicyOption } from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取筛选表单项配置
|
||||||
|
*/
|
||||||
|
export const useFormItems = (policyOptions: PolicyOption[]) => {
|
||||||
|
return computed<FormItem[]>(() => [
|
||||||
|
{
|
||||||
|
field: 'keyword',
|
||||||
|
label: '关键字',
|
||||||
|
type: 'input',
|
||||||
|
span: 6,
|
||||||
|
placeholder: '搜索名称/描述',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'type',
|
||||||
|
label: '抑制类型',
|
||||||
|
type: 'select',
|
||||||
|
span: 6,
|
||||||
|
placeholder: '请选择',
|
||||||
|
options: [
|
||||||
|
{ value: '', label: '全部' },
|
||||||
|
...SUPPRESSION_TYPE_OPTIONS,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'policy_id',
|
||||||
|
label: '关联策略',
|
||||||
|
type: 'select',
|
||||||
|
span: 6,
|
||||||
|
placeholder: '请选择',
|
||||||
|
options: [
|
||||||
|
{ value: '', label: '全部' },
|
||||||
|
{ value: '0', label: '仅全局规则' },
|
||||||
|
...policyOptions.map((p) => ({ value: String(p.id), label: p.name })),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'enabled',
|
||||||
|
label: '启用状态',
|
||||||
|
type: 'select',
|
||||||
|
span: 6,
|
||||||
|
placeholder: '请选择',
|
||||||
|
options: [
|
||||||
|
{ value: '', label: '全部' },
|
||||||
|
{ value: 'true', label: '已启用' },
|
||||||
|
{ value: 'false', label: '已禁用' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
59
src/views/ops/pages/alert/suppress/constants.ts
Normal file
59
src/views/ops/pages/alert/suppress/constants.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import type { SuppressionFormData } from './types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面标题
|
||||||
|
*/
|
||||||
|
export const PAGE_TITLE = '告警抑制规则(降噪)管理'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认表单数据
|
||||||
|
*/
|
||||||
|
export const DEFAULT_FORM_DATA: SuppressionFormData = {
|
||||||
|
name: '',
|
||||||
|
type: 'dedup',
|
||||||
|
enabled: true,
|
||||||
|
priority: 100,
|
||||||
|
policy_id: 0,
|
||||||
|
description: '',
|
||||||
|
matchers: '{}',
|
||||||
|
dedup_window: 300,
|
||||||
|
dedup_keys: '',
|
||||||
|
aggregate_window: 300,
|
||||||
|
group_by: '',
|
||||||
|
aggregate_count: 5,
|
||||||
|
source_matchers: '{}',
|
||||||
|
target_matchers: '{}',
|
||||||
|
throttle_count: 1,
|
||||||
|
throttle_window: 300,
|
||||||
|
schedule: '{}',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单校验规则
|
||||||
|
*/
|
||||||
|
export const FORM_RULES = {
|
||||||
|
name: [{ required: true, message: '请输入规则名称' }],
|
||||||
|
type: [{ required: true, message: '请选择抑制类型' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认分页配置
|
||||||
|
*/
|
||||||
|
export const DEFAULT_PAGINATION = {
|
||||||
|
current: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 0,
|
||||||
|
showTotal: true,
|
||||||
|
showJumper: true,
|
||||||
|
showPageSize: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认筛选表单
|
||||||
|
*/
|
||||||
|
export const DEFAULT_SEARCH_FORM = {
|
||||||
|
keyword: '',
|
||||||
|
type: '',
|
||||||
|
policy_id: '',
|
||||||
|
enabled: '',
|
||||||
|
}
|
||||||
2
src/views/ops/pages/alert/suppress/hooks/index.ts
Normal file
2
src/views/ops/pages/alert/suppress/hooks/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { usePolicy } from './usePolicy'
|
||||||
|
export { useTable } from './useTable'
|
||||||
42
src/views/ops/pages/alert/suppress/hooks/usePolicy.ts
Normal file
42
src/views/ops/pages/alert/suppress/hooks/usePolicy.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import { fetchPolicyList } from '@/api/ops/alertPolicy'
|
||||||
|
import type { PolicyOption } from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 策略相关逻辑
|
||||||
|
*/
|
||||||
|
export const usePolicy = () => {
|
||||||
|
const policyOptions = ref<PolicyOption[]>([])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取策略列表
|
||||||
|
*/
|
||||||
|
const fetchPolicyOptions = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetchPolicyList({ page_size: 1000 })
|
||||||
|
if (res.code === 0 && res.details) {
|
||||||
|
policyOptions.value = (res.details.data || []).map((p: any) => ({
|
||||||
|
id: p.id,
|
||||||
|
name: p.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取策略列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取策略名称
|
||||||
|
*/
|
||||||
|
const getPolicyName = (policyId: number) => {
|
||||||
|
const policy = policyOptions.value.find((p) => p.id === policyId)
|
||||||
|
return policy?.name || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
policyOptions,
|
||||||
|
fetchPolicyOptions,
|
||||||
|
getPolicyName,
|
||||||
|
}
|
||||||
|
}
|
||||||
193
src/views/ops/pages/alert/suppress/hooks/useTable.ts
Normal file
193
src/views/ops/pages/alert/suppress/hooks/useTable.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
|
import {
|
||||||
|
fetchSuppressionList,
|
||||||
|
updateSuppression,
|
||||||
|
deleteSuppression,
|
||||||
|
type SuppressionRule,
|
||||||
|
} from '@/api/ops/suppression'
|
||||||
|
import type { SearchFormModel, PaginationConfig, SwitchLoadingMap } from '../types'
|
||||||
|
import { DEFAULT_PAGINATION, DEFAULT_SEARCH_FORM } from '../constants'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格相关逻辑
|
||||||
|
*/
|
||||||
|
export const useTable = () => {
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref<SuppressionRule[]>([])
|
||||||
|
const switchLoadingMap = ref<SwitchLoadingMap>({})
|
||||||
|
const formModel = ref<SearchFormModel>({ ...DEFAULT_SEARCH_FORM })
|
||||||
|
const pagination = reactive<PaginationConfig>({ ...DEFAULT_PAGINATION })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取抑制规则列表
|
||||||
|
*/
|
||||||
|
const fetchList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params: any = {
|
||||||
|
page: pagination.current,
|
||||||
|
page_size: pagination.pageSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formModel.value.keyword) {
|
||||||
|
params.keyword = formModel.value.keyword
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formModel.value.type) {
|
||||||
|
params.type = formModel.value.type
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formModel.value.policy_id) {
|
||||||
|
params.policy_id = Number(formModel.value.policy_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formModel.value.enabled) {
|
||||||
|
params.enabled = formModel.value.enabled === 'true'
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetchSuppressionList(params)
|
||||||
|
if (res.code === 0 && res.details) {
|
||||||
|
tableData.value = (res.details.data || []).map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
switchLoading: false,
|
||||||
|
}))
|
||||||
|
pagination.total = res.details.total || 0
|
||||||
|
} else {
|
||||||
|
Message.error(res.message || '获取抑制规则列表失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('获取抑制规则列表失败:', error)
|
||||||
|
Message.error(error.message || '获取抑制规则列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理表单模型更新
|
||||||
|
*/
|
||||||
|
const handleFormModelUpdate = (value: Record<string, any>) => {
|
||||||
|
formModel.value = value as SearchFormModel
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索
|
||||||
|
*/
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.current = 1
|
||||||
|
fetchList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置
|
||||||
|
*/
|
||||||
|
const handleReset = () => {
|
||||||
|
formModel.value = { ...DEFAULT_SEARCH_FORM }
|
||||||
|
pagination.current = 1
|
||||||
|
fetchList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新
|
||||||
|
*/
|
||||||
|
const handleRefresh = () => {
|
||||||
|
fetchList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页变化
|
||||||
|
*/
|
||||||
|
const handlePageChange = (current: number) => {
|
||||||
|
pagination.current = current
|
||||||
|
fetchList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每页条数变化
|
||||||
|
*/
|
||||||
|
const handlePageSizeChange = (pageSize: number) => {
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
pagination.current = 1
|
||||||
|
fetchList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换启用状态
|
||||||
|
*/
|
||||||
|
const handleToggleEnabled = async (record: SuppressionRule) => {
|
||||||
|
if (!record.id) return
|
||||||
|
switchLoadingMap.value[record.id] = true
|
||||||
|
try {
|
||||||
|
const res = await updateSuppression({
|
||||||
|
id: record.id,
|
||||||
|
enabled: record.enabled,
|
||||||
|
})
|
||||||
|
if (res.code === 0) {
|
||||||
|
Message.success(record.enabled ? '已启用' : '已禁用')
|
||||||
|
} else {
|
||||||
|
// 恢复原状态
|
||||||
|
record.enabled = !record.enabled
|
||||||
|
Message.error(res.message || '操作失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
record.enabled = !record.enabled
|
||||||
|
console.error('切换状态失败:', error)
|
||||||
|
Message.error(error.message || '操作失败')
|
||||||
|
} finally {
|
||||||
|
switchLoadingMap.value[record.id] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除抑制规则
|
||||||
|
*/
|
||||||
|
const handleDelete = (record: SuppressionRule) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: `确定要删除抑制规则「${record.name}」吗?删除后将影响告警降噪行为,请确认是否被模板引用。`,
|
||||||
|
okText: '删除',
|
||||||
|
cancelText: '取消',
|
||||||
|
okButtonProps: { status: 'danger' },
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
const res = await deleteSuppression(record.id!)
|
||||||
|
if (res.code === 0 || res.data === '删除成功') {
|
||||||
|
Message.success('删除成功')
|
||||||
|
fetchList()
|
||||||
|
} else {
|
||||||
|
Message.error(res.message || '删除失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('删除失败:', error)
|
||||||
|
Message.error(error.message || '删除失败')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到策略详情
|
||||||
|
*/
|
||||||
|
const handleGoToPolicy = (policyId: number) => {
|
||||||
|
Message.info(`跳转到策略 #${policyId} 详情页`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
tableData,
|
||||||
|
switchLoadingMap,
|
||||||
|
formModel,
|
||||||
|
pagination,
|
||||||
|
fetchList,
|
||||||
|
handleFormModelUpdate,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
handleRefresh,
|
||||||
|
handlePageChange,
|
||||||
|
handlePageSizeChange,
|
||||||
|
handleToggleEnabled,
|
||||||
|
handleDelete,
|
||||||
|
handleGoToPolicy,
|
||||||
|
}
|
||||||
|
}
|
||||||
208
src/views/ops/pages/alert/suppress/index.vue
Normal file
208
src/views/ops/pages/alert/suppress/index.vue
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<search-table
|
||||||
|
:form-model="formModel"
|
||||||
|
:form-items="formItems"
|
||||||
|
:data="tableData"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
:title="pageTitle"
|
||||||
|
@update:form-model="handleFormModelUpdate"
|
||||||
|
@search="handleSearch"
|
||||||
|
@reset="handleReset"
|
||||||
|
@refresh="handleRefresh"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
@page-size-change="handlePageSizeChange"
|
||||||
|
>
|
||||||
|
<!-- 工具栏左侧:新建按钮 -->
|
||||||
|
<template #toolbar-left>
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="handleOpenCreateModal">
|
||||||
|
<template #icon><icon-plus /></template>
|
||||||
|
新建抑制规则
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 类型列 -->
|
||||||
|
<template #type="{ record }">
|
||||||
|
<a-tag :color="getTypeColor(record.type)">
|
||||||
|
{{ getTypeLabel(record.type) }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 启用状态列 -->
|
||||||
|
<template #enabled="{ record }">
|
||||||
|
<a-switch
|
||||||
|
v-model="record.enabled"
|
||||||
|
:loading="switchLoadingMap[record.id]"
|
||||||
|
@change="handleToggleEnabled(record)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 作用范围列 -->
|
||||||
|
<template #policy="{ record }">
|
||||||
|
<span v-if="record.policy_id === 0 || !record.policy_id">
|
||||||
|
<a-tag color="gray">全局</a-tag>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<a-link @click="handleGoToPolicy(record.policy_id)">
|
||||||
|
{{ getPolicyName(record.policy_id) || `策略 #${record.policy_id}` }}
|
||||||
|
</a-link>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 关键配置列 -->
|
||||||
|
<template #config="{ record }">
|
||||||
|
<span class="config-text">{{ getConfigSummary(record) }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 匹配条件列 -->
|
||||||
|
<template #matchers="{ record }">
|
||||||
|
<a-tooltip v-if="record.matchers && record.matchers !== '{}'" :content="record.matchers">
|
||||||
|
<a-tag color="arcoblue">
|
||||||
|
<icon-code />
|
||||||
|
已配置
|
||||||
|
</a-tag>
|
||||||
|
</a-tooltip>
|
||||||
|
<span v-else class="text-gray">全部告警</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 最后命中时间列 -->
|
||||||
|
<template #last_hit_at="{ record }">
|
||||||
|
<span v-if="record.last_hit_at">{{ formatDateTime(record.last_hit_at) }}</span>
|
||||||
|
<span v-else class="text-gray">-</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 创建时间列 -->
|
||||||
|
<template #created_at="{ record }">
|
||||||
|
{{ formatDateTime(record.created_at) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 操作列 -->
|
||||||
|
<template #operations="{ record }">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="text" size="small" @click="handleOpenEditModal(record)">
|
||||||
|
编辑
|
||||||
|
</a-button>
|
||||||
|
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">
|
||||||
|
删除
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</search-table>
|
||||||
|
|
||||||
|
<!-- 新建/编辑抑制规则弹窗 -->
|
||||||
|
<SuppressionModal
|
||||||
|
v-model:visible="modalVisible"
|
||||||
|
v-model:submitting="submitting"
|
||||||
|
:is-edit="isEdit"
|
||||||
|
:data="formData"
|
||||||
|
:policy-options="policyOptions"
|
||||||
|
@success="fetchList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import SearchTable from '@/components/search-table/index.vue'
|
||||||
|
import { fetchSuppressionDetail, type SuppressionRule } from '@/api/ops/suppression'
|
||||||
|
import SuppressionModal from './components/SuppressionModal.vue'
|
||||||
|
import { PAGE_TITLE, DEFAULT_FORM_DATA } from './constants'
|
||||||
|
import { getTableColumns } from './config/columns'
|
||||||
|
import { useFormItems } from './config/formItems'
|
||||||
|
import { getTypeColor, getTypeLabel, getConfigSummary, formatDateTime } from './utils'
|
||||||
|
import { usePolicy, useTable } from './hooks'
|
||||||
|
import type { SuppressionFormData } from './types'
|
||||||
|
|
||||||
|
// 页面标题
|
||||||
|
const pageTitle = PAGE_TITLE
|
||||||
|
|
||||||
|
// 策略相关
|
||||||
|
const { policyOptions, fetchPolicyOptions, getPolicyName } = usePolicy()
|
||||||
|
|
||||||
|
// 表格相关
|
||||||
|
const {
|
||||||
|
loading,
|
||||||
|
tableData,
|
||||||
|
switchLoadingMap,
|
||||||
|
formModel,
|
||||||
|
pagination,
|
||||||
|
fetchList,
|
||||||
|
handleFormModelUpdate,
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
handleRefresh,
|
||||||
|
handlePageChange,
|
||||||
|
handlePageSizeChange,
|
||||||
|
handleToggleEnabled,
|
||||||
|
handleDelete,
|
||||||
|
handleGoToPolicy,
|
||||||
|
} = useTable()
|
||||||
|
|
||||||
|
// 筛选表单项配置
|
||||||
|
const formItems = useFormItems(policyOptions.value)
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const tableColumns = computed(() => getTableColumns())
|
||||||
|
|
||||||
|
// 弹窗相关
|
||||||
|
const modalVisible = ref(false)
|
||||||
|
const isEdit = ref(false)
|
||||||
|
const submitting = ref(false)
|
||||||
|
const formData = ref<SuppressionFormData>({ ...DEFAULT_FORM_DATA })
|
||||||
|
|
||||||
|
// 打开新建弹窗
|
||||||
|
const handleOpenCreateModal = () => {
|
||||||
|
isEdit.value = false
|
||||||
|
formData.value = { ...DEFAULT_FORM_DATA }
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开编辑弹窗
|
||||||
|
const handleOpenEditModal = async (record: SuppressionRule) => {
|
||||||
|
isEdit.value = true
|
||||||
|
try {
|
||||||
|
const res = await fetchSuppressionDetail(record.id!)
|
||||||
|
if (res.code === 0 && res.details) {
|
||||||
|
formData.value = { ...res.details }
|
||||||
|
modalVisible.value = true
|
||||||
|
} else {
|
||||||
|
Message.error(res.message || '获取抑制规则详情失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('获取抑制规则详情失败:', error)
|
||||||
|
Message.error(error.message || '获取抑制规则详情失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
fetchPolicyOptions()
|
||||||
|
fetchList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'AlertSuppressPage',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.container {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gray {
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
44
src/views/ops/pages/alert/suppress/types.ts
Normal file
44
src/views/ops/pages/alert/suppress/types.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import type { SuppressionRule, SuppressionType } from '@/api/ops/suppression'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 筛选表单模型
|
||||||
|
*/
|
||||||
|
export interface SearchFormModel {
|
||||||
|
keyword: string
|
||||||
|
type: string
|
||||||
|
policy_id: string
|
||||||
|
enabled: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 策略选项
|
||||||
|
*/
|
||||||
|
export interface PolicyOption {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页配置
|
||||||
|
*/
|
||||||
|
export interface PaginationConfig {
|
||||||
|
current: number
|
||||||
|
pageSize: number
|
||||||
|
total: number
|
||||||
|
showTotal: boolean
|
||||||
|
showJumper: boolean
|
||||||
|
showPageSize: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单数据(创建/编辑)
|
||||||
|
*/
|
||||||
|
export type SuppressionFormData = SuppressionRule
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开关加载状态映射
|
||||||
|
*/
|
||||||
|
export type SwitchLoadingMap = Record<number, boolean>
|
||||||
|
|
||||||
|
// 重新导出 API 类型
|
||||||
|
export type { SuppressionRule, SuppressionType }
|
||||||
136
src/views/ops/pages/alert/suppress/utils/index.ts
Normal file
136
src/views/ops/pages/alert/suppress/utils/index.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import {
|
||||||
|
SUPPRESSION_TYPE_OPTIONS,
|
||||||
|
SUPPRESSION_TYPE_COLORS,
|
||||||
|
type SuppressionType,
|
||||||
|
type SuppressionRule,
|
||||||
|
type SuppressionUpdateParams,
|
||||||
|
type SuppressionCreateParams,
|
||||||
|
} from '@/api/ops/suppression'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取抑制类型标签
|
||||||
|
*/
|
||||||
|
export const getTypeLabel = (type: SuppressionType) => {
|
||||||
|
const option = SUPPRESSION_TYPE_OPTIONS.find((o) => o.value === type)
|
||||||
|
return option?.label || type
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取抑制类型颜色
|
||||||
|
*/
|
||||||
|
export const getTypeColor = (type: SuppressionType) => {
|
||||||
|
return SUPPRESSION_TYPE_COLORS[type] || 'gray'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取关键配置摘要
|
||||||
|
*/
|
||||||
|
export const getConfigSummary = (record: SuppressionRule) => {
|
||||||
|
switch (record.type) {
|
||||||
|
case 'dedup':
|
||||||
|
return `窗口 ${record.dedup_window || 0}s · 键 ${record.dedup_keys || '-'}`
|
||||||
|
case 'aggregate':
|
||||||
|
return `窗口 ${record.aggregate_window || 0}s · 分组 ${record.group_by || '-'} · 阈值 ${record.aggregate_count || 0}`
|
||||||
|
case 'throttle':
|
||||||
|
return `${record.throttle_count || 0} 次 / ${record.throttle_window || 0}s`
|
||||||
|
case 'schedule':
|
||||||
|
return '已配置时间窗'
|
||||||
|
case 'dependency':
|
||||||
|
return '源/目标匹配器已配置'
|
||||||
|
default:
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期时间
|
||||||
|
*/
|
||||||
|
export const formatDateTime = (dateStr?: string) => {
|
||||||
|
if (!dateStr) return '-'
|
||||||
|
try {
|
||||||
|
const date = new Date(dateStr)
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
return dateStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验 JSON 字段
|
||||||
|
* @returns 错误信息,如果校验通过则返回 null
|
||||||
|
*/
|
||||||
|
export const validateJsonFields = (formData: SuppressionRule, fields: string[]): string | null => {
|
||||||
|
for (const field of fields) {
|
||||||
|
const value = formData[field as keyof SuppressionRule]
|
||||||
|
if (value && typeof value === 'string' && value !== '{}' && value.trim() !== '') {
|
||||||
|
try {
|
||||||
|
JSON.parse(value)
|
||||||
|
} catch {
|
||||||
|
return `${field} 字段 JSON 格式不正确`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建更新数据(只传非空字段)
|
||||||
|
*/
|
||||||
|
export const buildUpdateData = (formData: SuppressionRule): SuppressionUpdateParams => {
|
||||||
|
const updateData: SuppressionUpdateParams = { id: formData.id! }
|
||||||
|
|
||||||
|
if (formData.name) updateData.name = formData.name
|
||||||
|
if (formData.type) updateData.type = formData.type
|
||||||
|
if (formData.description) updateData.description = formData.description
|
||||||
|
updateData.enabled = formData.enabled
|
||||||
|
if (formData.priority && formData.priority > 0) updateData.priority = formData.priority
|
||||||
|
if (formData.policy_id && formData.policy_id > 0) updateData.policy_id = formData.policy_id
|
||||||
|
if (formData.dedup_window && formData.dedup_window > 0) updateData.dedup_window = formData.dedup_window
|
||||||
|
if (formData.dedup_keys) updateData.dedup_keys = formData.dedup_keys
|
||||||
|
if (formData.aggregate_window && formData.aggregate_window > 0) updateData.aggregate_window = formData.aggregate_window
|
||||||
|
if (formData.group_by) updateData.group_by = formData.group_by
|
||||||
|
if (formData.aggregate_count && formData.aggregate_count > 0) updateData.aggregate_count = formData.aggregate_count
|
||||||
|
if (formData.source_matchers) updateData.source_matchers = formData.source_matchers
|
||||||
|
if (formData.target_matchers) updateData.target_matchers = formData.target_matchers
|
||||||
|
if (formData.throttle_count && formData.throttle_count > 0) updateData.throttle_count = formData.throttle_count
|
||||||
|
if (formData.throttle_window && formData.throttle_window > 0) updateData.throttle_window = formData.throttle_window
|
||||||
|
if (formData.schedule) updateData.schedule = formData.schedule
|
||||||
|
if (formData.matchers) updateData.matchers = formData.matchers
|
||||||
|
|
||||||
|
return updateData
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建创建数据
|
||||||
|
*/
|
||||||
|
export const buildCreateData = (formData: SuppressionRule): SuppressionCreateParams => {
|
||||||
|
const createData: SuppressionCreateParams = {
|
||||||
|
name: formData.name,
|
||||||
|
type: formData.type,
|
||||||
|
enabled: formData.enabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.description) createData.description = formData.description
|
||||||
|
if (formData.priority) createData.priority = formData.priority
|
||||||
|
if (formData.policy_id !== undefined) createData.policy_id = formData.policy_id
|
||||||
|
if (formData.dedup_window) createData.dedup_window = formData.dedup_window
|
||||||
|
if (formData.dedup_keys) createData.dedup_keys = formData.dedup_keys
|
||||||
|
if (formData.aggregate_window) createData.aggregate_window = formData.aggregate_window
|
||||||
|
if (formData.group_by) createData.group_by = formData.group_by
|
||||||
|
if (formData.aggregate_count) createData.aggregate_count = formData.aggregate_count
|
||||||
|
if (formData.source_matchers) createData.source_matchers = formData.source_matchers
|
||||||
|
if (formData.target_matchers) createData.target_matchers = formData.target_matchers
|
||||||
|
if (formData.throttle_count) createData.throttle_count = formData.throttle_count
|
||||||
|
if (formData.throttle_window) createData.throttle_window = formData.throttle_window
|
||||||
|
if (formData.schedule) createData.schedule = formData.schedule
|
||||||
|
if (formData.matchers) createData.matchers = formData.matchers
|
||||||
|
|
||||||
|
return createData
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<!-- 表单内容 -->
|
<!-- 表单内容 -->
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<a-spin :loading="loading" style="width: 100%">
|
<a-spin :loading="loading" style="width: 100%">
|
||||||
<a-form ref="formRef" :model="form" layout="vertical">
|
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
||||||
<!-- 基本信息 -->
|
<!-- 基本信息 -->
|
||||||
<a-card class="info-card" title="基本信息">
|
<a-card class="info-card" title="基本信息">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
@@ -450,6 +450,12 @@ const form = ref<AssetForm>({
|
|||||||
remarks: '',
|
remarks: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
asset_name: [{ required: true, message: '请输入资产名称' }],
|
||||||
|
asset_code: [{ required: true, message: '请输入资产编号' }],
|
||||||
|
}
|
||||||
|
|
||||||
// 提取列表数据的辅助函数
|
// 提取列表数据的辅助函数
|
||||||
const extractList = (res: any): any[] => {
|
const extractList = (res: any): any[] => {
|
||||||
const candidate =
|
const candidate =
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<!-- 表单内容 -->
|
<!-- 表单内容 -->
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<a-spin :loading="loading" style="width: 100%">
|
<a-spin :loading="loading" style="width: 100%">
|
||||||
<a-form ref="formRef" :model="form" layout="vertical">
|
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
||||||
<!-- 基本信息 -->
|
<!-- 基本信息 -->
|
||||||
<a-card class="info-card" title="基本信息">
|
<a-card class="info-card" title="基本信息">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
@@ -351,6 +351,21 @@ const form = ref<Supplier>({
|
|||||||
status: 'active',
|
status: 'active',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
name: [{ required: true, message: '请输入供应商名称' }],
|
||||||
|
code: [{ required: true, message: '请输入供应商编码' }],
|
||||||
|
supplier_type: [{ required: true, message: '请选择供应商类型' }],
|
||||||
|
status: [{ required: true, message: '请选择状态' }],
|
||||||
|
contact_person: [{ required: true, message: '请输入联系人' }],
|
||||||
|
contact_phone: [{ required: true, message: '请输入联系电话' }],
|
||||||
|
contact_mobile: [{ required: true, message: '请输入备用联系电话' }],
|
||||||
|
contact_email: [
|
||||||
|
{ required: true, message: '请输入联系邮箱' },
|
||||||
|
{ type: 'email', message: '请输入正确的邮箱格式' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化页面
|
// 初始化页面
|
||||||
const initPage = async () => {
|
const initPage = async () => {
|
||||||
const id = route.query.id
|
const id = route.query.id
|
||||||
|
|||||||
Reference in New Issue
Block a user