fix
This commit is contained in:
@@ -72,7 +72,9 @@ export const createRule = (data: {
|
||||
threshold?: number;
|
||||
compare_op?: string;
|
||||
duration?: number;
|
||||
baseline_config?: string;
|
||||
labels?: string;
|
||||
annotations?: string;
|
||||
}) => request.post("/Alert/v1/rule/create", data);
|
||||
|
||||
/** 更新 告警规则 */
|
||||
@@ -88,7 +90,9 @@ export const updateRule = (data: {
|
||||
threshold?: number;
|
||||
compare_op?: string;
|
||||
duration?: number;
|
||||
baseline_config?: string;
|
||||
labels?: string;
|
||||
annotations?: string;
|
||||
}) => request.post("/Alert/v1/rule/update", data);
|
||||
|
||||
/** 删除 告警规则 */
|
||||
|
||||
@@ -33,7 +33,8 @@ export const createAlertProcess = (data: {
|
||||
escalate_to?: string;
|
||||
root_cause?: string;
|
||||
solution?: string;
|
||||
metadata?: Record<string, any>;
|
||||
// 按后端约定:metadata 为 JSON 字符串,例如 "{}"
|
||||
metadata?: string;
|
||||
}) => request.post("/Alert/v1/process/create", data);
|
||||
|
||||
/** 获取 告警处理记录列表 */
|
||||
|
||||
@@ -52,12 +52,6 @@
|
||||
</a-descriptions-item>
|
||||
|
||||
<!-- 处理信息 -->
|
||||
<a-descriptions-item label="处理状态" :span="2">
|
||||
<a-tag v-if="recordDetail.process_status" :color="getProcessStatusColor(recordDetail.process_status)">
|
||||
{{ getProcessStatusText(recordDetail.process_status) }}
|
||||
</a-tag>
|
||||
<span v-else>-</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="处理人">
|
||||
{{ recordDetail.processed_by || '-' }}
|
||||
</a-descriptions-item>
|
||||
@@ -188,28 +182,6 @@ const getStatusText = (status: string) => {
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
||||
// 获取处理状态颜色
|
||||
const getProcessStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
pending: 'gray',
|
||||
processing: 'blue',
|
||||
completed: 'green',
|
||||
failed: 'red',
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
}
|
||||
|
||||
// 获取处理状态文本
|
||||
const getProcessStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
pending: '待处理',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
failed: '失败',
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
||||
// 加载告警记录详情
|
||||
const loadRecordDetail = async () => {
|
||||
if (!props.recordId) return
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
width="1200px"
|
||||
title="告警处理记录"
|
||||
:footer="false"
|
||||
@cancel="handleCancel"
|
||||
@update:visible="handleVisibleChange"
|
||||
>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<div v-if="preloadedAlertRecord" style="margin-bottom: 16px;">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="告警ID">{{ preloadedAlertRecord.id }}</a-descriptions-item>
|
||||
<a-descriptions-item label="告警名称">
|
||||
{{ preloadedAlertRecord.alert_name || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="getStatusColor(preloadedAlertRecord.status)">
|
||||
{{ getStatusText(preloadedAlertRecord.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
|
||||
<search-table
|
||||
:form-model="formModel"
|
||||
:form-items="formItems"
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
title="处理记录列表"
|
||||
:show-toolbar="false"
|
||||
:show-download="false"
|
||||
:show-refresh="false"
|
||||
:show-density="false"
|
||||
:show-column-setting="false"
|
||||
@update:form-model="handleFormModelUpdate"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
@page-change="handlePageChange"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
>
|
||||
<template #index="{ rowIndex }">
|
||||
{{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }}
|
||||
</template>
|
||||
|
||||
<template #created_at="{ record }">
|
||||
{{ formatDateTime(record.created_at) || '-' }}
|
||||
</template>
|
||||
|
||||
<template #resolution_time="{ record }">
|
||||
{{ formatDateTime(record.resolution_time) || '-' }}
|
||||
</template>
|
||||
|
||||
<template #action="{ record }">
|
||||
<a-tag :color="getActionColor(record.action)">
|
||||
{{ getActionText(record.action) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</search-table>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'
|
||||
import { fetchAlertProcessList, fetchHistoryDetail } from '@/api/ops/alertHistory'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
alertRecordId?: number
|
||||
alertRecord?: any
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref<any[]>([])
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const formModel = ref({
|
||||
action: undefined as string | undefined,
|
||||
keyword: '',
|
||||
})
|
||||
|
||||
const preloadedAlertRecord = ref<any>(props.alertRecord || null)
|
||||
|
||||
watch(
|
||||
() => props.alertRecord,
|
||||
(val) => {
|
||||
preloadedAlertRecord.value = val || null
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const formItems = computed<FormItem[]>(() => [
|
||||
{
|
||||
field: 'action',
|
||||
label: '动作',
|
||||
type: 'select',
|
||||
placeholder: '请选择动作',
|
||||
options: [
|
||||
{ label: '确认', value: 'ack' },
|
||||
{ label: '解决', value: 'resolve' },
|
||||
{ label: '屏蔽', value: 'silence' },
|
||||
{ label: '评论', value: 'comment' },
|
||||
{ label: '分配', value: 'assign' },
|
||||
{ label: '升级', value: 'escalate' },
|
||||
{ label: '关闭', value: 'close' },
|
||||
],
|
||||
},
|
||||
{
|
||||
field: 'keyword',
|
||||
label: '关键字',
|
||||
type: 'input',
|
||||
placeholder: '匹配操作人/备注',
|
||||
},
|
||||
])
|
||||
|
||||
const columns = computed<TableColumnData[]>(() => [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
slotName: 'index',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'created_at',
|
||||
slotName: 'created_at',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '动作',
|
||||
dataIndex: 'action',
|
||||
slotName: 'action',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'operator',
|
||||
width: 140,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comment',
|
||||
width: 320,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: '完成时间',
|
||||
dataIndex: 'resolution_time',
|
||||
slotName: 'resolution_time',
|
||||
width: 200,
|
||||
},
|
||||
])
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
pending: 'gray',
|
||||
firing: 'red',
|
||||
resolved: 'green',
|
||||
silenced: 'blue',
|
||||
suppressed: 'orange',
|
||||
acked: 'purple',
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
pending: '待处理',
|
||||
firing: '触发中',
|
||||
resolved: '已解决',
|
||||
silenced: '已屏蔽',
|
||||
suppressed: '已抑制',
|
||||
acked: '已确认',
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
||||
const getActionColor = (action: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
ack: 'gold',
|
||||
resolve: 'green',
|
||||
silence: 'gray',
|
||||
comment: 'blue',
|
||||
assign: 'purple',
|
||||
escalate: 'orange',
|
||||
close: 'red',
|
||||
}
|
||||
return colorMap[action] || 'blue'
|
||||
}
|
||||
|
||||
const getActionText = (action: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
ack: '确认',
|
||||
resolve: '解决',
|
||||
silence: '屏蔽',
|
||||
comment: '评论',
|
||||
assign: '分配',
|
||||
escalate: '升级',
|
||||
close: '关闭',
|
||||
}
|
||||
return textMap[action] || action
|
||||
}
|
||||
|
||||
const formatDateTime = (dateStr: string) => {
|
||||
if (!dateStr) return ''
|
||||
const d = new Date(dateStr)
|
||||
if (Number.isNaN(d.getTime())) return dateStr
|
||||
return d.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
const maybePreloadAlertRecord = async () => {
|
||||
if (
|
||||
preloadedAlertRecord.value &&
|
||||
props.alertRecordId &&
|
||||
preloadedAlertRecord.value.id === props.alertRecordId
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (!props.alertRecordId) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await fetchHistoryDetail(props.alertRecordId)
|
||||
if (res?.code === 0 && res?.details) {
|
||||
preloadedAlertRecord.value = res.details
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载告警记录失败:', error)
|
||||
Message.error('加载告警记录失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadProcessList = async () => {
|
||||
if (!props.alertRecordId) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
alert_record_id: props.alertRecordId,
|
||||
action: formModel.value.action || undefined,
|
||||
keyword: formModel.value.keyword || undefined,
|
||||
page: pagination.current,
|
||||
page_size: pagination.pageSize,
|
||||
}
|
||||
|
||||
const res: any = await fetchAlertProcessList(params)
|
||||
tableData.value = res?.details?.data || []
|
||||
pagination.total = res?.details?.total || 0
|
||||
|
||||
// 预加载 AlertRecord:补齐到每一条处理记录上(接口返回为 null 时也能展示/联动)
|
||||
if (preloadedAlertRecord.value) {
|
||||
const alertRecord = preloadedAlertRecord.value
|
||||
tableData.value.forEach((item) => {
|
||||
if (!item.alert_record) {
|
||||
item.alert_record = alertRecord
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取处理记录失败:', error)
|
||||
Message.error('获取处理记录失败')
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
pagination.current = 1
|
||||
pagination.pageSize = pagination.pageSize || 20
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
formModel.value = {
|
||||
action: undefined,
|
||||
keyword: '',
|
||||
}
|
||||
|
||||
// 强制按当前告警ID刷新预加载对象,避免切换时短暂复用旧数据
|
||||
preloadedAlertRecord.value = props.alertRecord || null
|
||||
|
||||
await maybePreloadAlertRecord()
|
||||
await loadProcessList()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
init()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
const handleVisibleChange = (val: boolean) => {
|
||||
emit('update:visible', val)
|
||||
}
|
||||
|
||||
const handleFormModelUpdate = (value: any) => {
|
||||
formModel.value = {
|
||||
...formModel.value,
|
||||
...value,
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadProcessList()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
formModel.value = {
|
||||
action: undefined,
|
||||
keyword: '',
|
||||
}
|
||||
pagination.current = 1
|
||||
loadProcessList()
|
||||
}
|
||||
|
||||
const handlePageChange = (current: number) => {
|
||||
pagination.current = current
|
||||
loadProcessList()
|
||||
}
|
||||
|
||||
const handlePageSizeChange = (pageSize: number) => {
|
||||
pagination.pageSize = pageSize
|
||||
pagination.current = 1
|
||||
loadProcessList()
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'HistoryProcessListDialog',
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -51,12 +51,6 @@ export const columns: TableColumnData[] = [
|
||||
slotName: 'duration',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '处理状态',
|
||||
dataIndex: 'process_status',
|
||||
slotName: 'process_status',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '处理人',
|
||||
dataIndex: 'processed_by',
|
||||
|
||||
@@ -85,14 +85,6 @@
|
||||
{{ formatDuration(record.duration) }}
|
||||
</template>
|
||||
|
||||
<!-- 处理状态 -->
|
||||
<template #process_status="{ record }">
|
||||
<a-tag v-if="record.process_status" :color="getProcessStatusColor(record.process_status)">
|
||||
{{ getProcessStatusText(record.process_status) }}
|
||||
</a-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<!-- 处理时间 -->
|
||||
<template #processed_at="{ record }">
|
||||
{{ formatDate(record.processed_at) || '-' }}
|
||||
@@ -114,6 +106,13 @@
|
||||
v-model:visible="detailVisible"
|
||||
:record-id="currentRecordId"
|
||||
/>
|
||||
|
||||
<!-- 告警处理记录对话框 -->
|
||||
<history-process-list-dialog
|
||||
v-model:visible="processVisible"
|
||||
:alert-record-id="currentProcessAlertRecordId"
|
||||
:alert-record="currentProcessAlertRecord"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -122,13 +121,13 @@ import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
import { searchFormConfig } from './config/search-form'
|
||||
import { columns as columnsConfig } from './config/columns'
|
||||
import {
|
||||
fetchHistories,
|
||||
} from '@/api/ops/alertHistory'
|
||||
import { fetchAlertLevelList } from '@/api/ops/alertLevel'
|
||||
import HistoryDetailDialog from './components/HistoryDetailDialog.vue'
|
||||
import HistoryProcessListDialog from './components/HistoryProcessListDialog.vue'
|
||||
|
||||
// 状态管理
|
||||
const loading = ref(false)
|
||||
@@ -222,6 +221,13 @@ const currentRecordId = ref<number | undefined>(undefined)
|
||||
// 对话框可见性
|
||||
const detailVisible = ref(false)
|
||||
|
||||
// 处理记录对话框可见性
|
||||
const processVisible = ref(false)
|
||||
|
||||
// 当前用于“处理记录”的告警记录
|
||||
const currentProcessAlertRecord = ref<any>(null)
|
||||
const currentProcessAlertRecordId = computed(() => currentProcessAlertRecord.value?.id)
|
||||
|
||||
// 加载告警级别列表
|
||||
const loadSeverityOptions = async () => {
|
||||
try {
|
||||
@@ -321,28 +327,6 @@ const getStatusText = (status: string) => {
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
||||
// 获取处理状态颜色
|
||||
const getProcessStatusColor = (status: string) => {
|
||||
const colorMap: Record<string, string> = {
|
||||
pending: 'gray',
|
||||
processing: 'blue',
|
||||
completed: 'green',
|
||||
failed: 'red',
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
}
|
||||
|
||||
// 获取处理状态文本
|
||||
const getProcessStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
pending: '待处理',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
failed: '失败',
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
@@ -390,7 +374,8 @@ const handleDetail = (record: any) => {
|
||||
|
||||
// 查看处理记录
|
||||
const handleViewProcess = (record: any) => {
|
||||
Message.info(`查看告警 ${record.id} 的处理记录功能待实现`)
|
||||
currentProcessAlertRecord.value = record
|
||||
processVisible.value = true
|
||||
}
|
||||
|
||||
// 初始化加载数据
|
||||
|
||||
@@ -355,6 +355,9 @@ const handleOk = async () => {
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const quietHoursPayload =
|
||||
form.value.quiet_hours && form.value.quiet_hours.trim() ? form.value.quiet_hours : '{}'
|
||||
|
||||
const data: any = {
|
||||
name: form.value.name,
|
||||
type: form.value.type,
|
||||
@@ -365,7 +368,7 @@ const handleOk = async () => {
|
||||
severity_filter: Array.isArray(form.value.severity_filter)
|
||||
? form.value.severity_filter.join(',')
|
||||
: form.value.severity_filter,
|
||||
quiet_hours: form.value.quiet_hours,
|
||||
quiet_hours: quietHoursPayload,
|
||||
retry_times: form.value.retry_times,
|
||||
retry_interval: form.value.retry_interval,
|
||||
enabled: form.value.enabled,
|
||||
|
||||
@@ -100,7 +100,7 @@ const pagination = reactive({
|
||||
})
|
||||
|
||||
const formModel = ref<Record<string, any>>({
|
||||
name: '',
|
||||
keyword: '',
|
||||
type: '',
|
||||
})
|
||||
|
||||
@@ -111,7 +111,7 @@ const handleFormModelUpdate = (value: Record<string, any>) => {
|
||||
// 表单项配置
|
||||
const formItems = computed<FormItem[]>(() => [
|
||||
{
|
||||
field: 'name',
|
||||
field: 'keyword',
|
||||
label: '渠道名称',
|
||||
type: 'input',
|
||||
placeholder: '请输入渠道名称',
|
||||
@@ -234,7 +234,7 @@ const handleSearch = () => {
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
formModel.value = {
|
||||
name: '',
|
||||
keyword: '',
|
||||
type: '',
|
||||
}
|
||||
pagination.current = 1
|
||||
|
||||
@@ -373,8 +373,11 @@ const handleCancel = () => {
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
@@ -391,7 +394,11 @@ const handleSubmit = async () => {
|
||||
dispatch_rule: formData.dispatch_rule || undefined,
|
||||
}
|
||||
|
||||
await createPolicy(data)
|
||||
const res = await createPolicy(data)
|
||||
// 后端是业务 code 约定;接口返回失败时不应继续展示成功提示
|
||||
if (res?.code !== 0) {
|
||||
throw new Error(res?.message || '创建策略失败')
|
||||
}
|
||||
Message.success('策略创建成功')
|
||||
emit('success')
|
||||
handleCancel()
|
||||
|
||||
@@ -510,13 +510,16 @@ const handleCancel = () => {
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 提交
|
||||
// 提交(Arco Form.validate() 校验通过时 resolve 为 undefined,不能写成 if (!valid))
|
||||
const handleSubmit = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
submitLoading.value = true
|
||||
|
||||
|
||||
try {
|
||||
// 生成派单规则
|
||||
formData.dispatch_rule = generateDispatchRule()
|
||||
|
||||
@@ -29,11 +29,8 @@
|
||||
<a-select
|
||||
v-model="formData.rule_type"
|
||||
placeholder="请选择规则类型"
|
||||
@change="handleRuleTypeChange"
|
||||
>
|
||||
<a-option value="static">静态规则</a-option>
|
||||
<a-option value="dynamic">动态规则</a-option>
|
||||
<a-option value="promql">PromQL</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@@ -89,33 +86,6 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="formData.rule_type === 'dynamic'"
|
||||
field="metric_name"
|
||||
label="指标名称"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model="formData.metric_name"
|
||||
placeholder="请输入动态指标名称,如:cpu_usage_{host}"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="formData.rule_type === 'promql'"
|
||||
field="query_expr"
|
||||
label="PromQL 表达式"
|
||||
required
|
||||
>
|
||||
<a-textarea
|
||||
v-model="formData.query_expr"
|
||||
placeholder="请输入 PromQL 表达式,如:rate(http_requests_total[5m])"
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 阈值条件 -->
|
||||
<a-row v-if="formData.rule_type !== 'promql'" :gutter="16">
|
||||
<a-col :span="6">
|
||||
@@ -316,7 +286,8 @@ const loadRuleDetail = async () => {
|
||||
const rule = res.details
|
||||
|
||||
formData.name = rule.name
|
||||
formData.rule_type = rule.rule_type || 'static'
|
||||
// 当前产品只保留静态规则:强制按 static 渲染
|
||||
formData.rule_type = 'static'
|
||||
formData.enabled = rule.enabled
|
||||
formData.severity_id = rule.severity_id
|
||||
formData.metric_name = rule.metric_name || ''
|
||||
@@ -357,11 +328,14 @@ const handleCancel = () => {
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 提交
|
||||
// 提交(Arco Form.validate() 通过时 resolve 为 undefined,不可写成 if (!valid))
|
||||
const handleSubmit = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
if (!props.policyId) {
|
||||
Message.warning('缺少策略 ID')
|
||||
return
|
||||
@@ -376,13 +350,24 @@ const handleSubmit = async () => {
|
||||
severity_id: formData.severity_id, // 确保 severity_id 是 number 类型
|
||||
}
|
||||
|
||||
// 按契约:rule/create 不做 jsonb 默认值归一化。
|
||||
// 当 jsonb 字段无内容时,必须显式传 "{}"(否则可能反序列化为空串导致 SQLSTATE 22P02)。
|
||||
const createPayload = !isEdit.value
|
||||
? {
|
||||
...data,
|
||||
baseline_config: '{}',
|
||||
labels: '{}',
|
||||
annotations: '{}',
|
||||
}
|
||||
: data
|
||||
|
||||
// 编辑模式
|
||||
if (isEdit.value && props.ruleId) {
|
||||
await updateRule({ id: props.ruleId, ...data })
|
||||
Message.success('规则更新成功')
|
||||
} else {
|
||||
// 新建模式
|
||||
await createRule(data)
|
||||
await createRule(createPayload)
|
||||
Message.success('规则创建成功')
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ const handleOk = async () => {
|
||||
action: 'ack',
|
||||
operator: getCurrentUser(),
|
||||
comment: form.value.comment,
|
||||
metadata: '{}',
|
||||
})
|
||||
Message.success('确认成功')
|
||||
emit('success')
|
||||
|
||||
@@ -67,6 +67,7 @@ const handleOk = async () => {
|
||||
action: 'comment',
|
||||
operator: getCurrentUser(),
|
||||
comment: form.value.comment,
|
||||
metadata: '{}',
|
||||
})
|
||||
Message.success('评论添加成功')
|
||||
emit('success')
|
||||
|
||||
@@ -61,9 +61,6 @@
|
||||
|
||||
<!-- 处理信息 -->
|
||||
<a-descriptions title="处理信息" :column="2" bordered class="mt-4">
|
||||
<a-descriptions-item label="处理状态">
|
||||
{{ record.process_status || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="处理人">
|
||||
{{ record.processed_by || '-' }}
|
||||
</a-descriptions-item>
|
||||
|
||||
@@ -82,6 +82,7 @@ const handleOk = async () => {
|
||||
comment: form.value.comment,
|
||||
root_cause: form.value.root_cause,
|
||||
solution: form.value.solution,
|
||||
metadata: '{}',
|
||||
})
|
||||
Message.success('解决成功')
|
||||
emit('success')
|
||||
|
||||
@@ -93,6 +93,7 @@ const handleOk = async () => {
|
||||
silence_until: silenceUntil,
|
||||
silence_reason: form.value.silence_reason,
|
||||
comment: form.value.comment,
|
||||
metadata: '{}',
|
||||
})
|
||||
Message.success('屏蔽成功')
|
||||
emit('success')
|
||||
|
||||
@@ -57,12 +57,6 @@ export const columns: TableColumnData[] = [
|
||||
slotName: 'duration',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '处理状态',
|
||||
dataIndex: 'process_status',
|
||||
slotName: 'process_status',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '处理人',
|
||||
dataIndex: 'processed_by',
|
||||
|
||||
@@ -63,13 +63,6 @@
|
||||
{{ formatDuration(record.duration) }}
|
||||
</template>
|
||||
|
||||
<template #process_status="{ record }">
|
||||
<a-tag v-if="record.process_status" color="arcoblue">
|
||||
{{ record.process_status }}
|
||||
</a-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template #processed_at="{ record }">
|
||||
{{ record.processed_at ? formatDateTime(record.processed_at) : '-' }}
|
||||
</template>
|
||||
@@ -90,16 +83,36 @@
|
||||
|
||||
<template #actions="{ record }">
|
||||
<a-space size="small" :wrap="false">
|
||||
<a-button type="text" size="small" @click.stop="handleAck(record)">
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="isActionDisabled(record, 'ack')"
|
||||
@click.stop="handleAck(record)"
|
||||
>
|
||||
确认
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click.stop="handleResolve(record)">
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="isActionDisabled(record, 'resolve')"
|
||||
@click.stop="handleResolve(record)"
|
||||
>
|
||||
解决
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click.stop="handleSilence(record)">
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="isActionDisabled(record, 'silence')"
|
||||
@click.stop="handleSilence(record)"
|
||||
>
|
||||
屏蔽
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click.stop="handleComment(record)">
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="isActionDisabled(record, 'comment')"
|
||||
@click.stop="handleComment(record)"
|
||||
>
|
||||
评论
|
||||
</a-button>
|
||||
<a-dropdown @select="(v) => handleMoreSelect(v, record)">
|
||||
@@ -286,6 +299,34 @@ const handleRowClick = (record: any, _ev: Event) => {
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
type ProcessAction = 'ack' | 'resolve' | 'silence' | 'comment'
|
||||
|
||||
// 根据告警受理处理的状态(record.status)控制操作列按钮可点击性
|
||||
// 约定:
|
||||
// - pending/firing:允许 ack/resolve/silence/comment
|
||||
// - acked:禁用 ack,其它允许
|
||||
// - resolved:只允许 comment
|
||||
// - silenced/suppressed:只允许 comment
|
||||
const isActionDisabled = (record: any, action: ProcessAction) => {
|
||||
const status = record?.status as string | undefined
|
||||
if (!status) return false
|
||||
|
||||
const isResolved = status === 'resolved'
|
||||
const isAcked = status === 'acked'
|
||||
const isSilenced = status === 'silenced'
|
||||
const isSuppressed = status === 'suppressed'
|
||||
|
||||
if (isResolved || isSilenced || isSuppressed) {
|
||||
return action !== 'comment'
|
||||
}
|
||||
|
||||
if (isAcked) {
|
||||
return action === 'ack'
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const handleAck = (record: any) => {
|
||||
currentRecord.value = record
|
||||
ackDialogVisible.value = true
|
||||
|
||||
Reference in New Issue
Block a user