460 lines
14 KiB
Vue
460 lines
14 KiB
Vue
<template>
|
|
<ArtTableFullScreen>
|
|
<div class="alert-rules-page" id="table-full-screen">
|
|
<el-card shadow="never" class="art-table-card">
|
|
<!-- 表格头部 -->
|
|
<ArtTableHeader
|
|
:columnList="columnOptions"
|
|
v-model:columns="columnChecks"
|
|
@refresh="handleRefresh"
|
|
>
|
|
<template #left>
|
|
<el-button type="primary" @click="showCreateDialog" v-if="hasAuth('alert_rules:create')"
|
|
>新增规则</el-button
|
|
>
|
|
</template>
|
|
</ArtTableHeader>
|
|
|
|
<!-- 表格 -->
|
|
<ArtTable
|
|
ref="tableRef"
|
|
row-key="id"
|
|
:loading="loading"
|
|
:data="tableData"
|
|
:marginTop="10"
|
|
:actions="getActions"
|
|
:actionsWidth="160"
|
|
>
|
|
<template #default>
|
|
<el-table-column v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
|
</template>
|
|
</ArtTable>
|
|
</el-card>
|
|
|
|
<!-- 新增/编辑规则弹窗 -->
|
|
<el-dialog
|
|
v-model="dialogVisible"
|
|
:title="dialogType === 'create' ? '新增告警规则' : '编辑告警规则'"
|
|
width="700px"
|
|
:close-on-click-modal="false"
|
|
>
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
|
<el-row :gutter="20">
|
|
<el-col :span="12">
|
|
<el-form-item label="规则名称" prop="rule_name">
|
|
<el-input v-model="form.rule_name" placeholder="请输入规则名称" />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item label="告警级别" prop="alert_level">
|
|
<el-select
|
|
v-model="form.alert_level"
|
|
placeholder="请选择告警级别"
|
|
style="width: 100%"
|
|
>
|
|
<el-option label="警告" value="warning" />
|
|
<el-option label="严重" value="critical" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row :gutter="20">
|
|
<el-col :span="12">
|
|
<el-form-item label="指标类型" prop="metric_type">
|
|
<el-select
|
|
v-model="form.metric_type"
|
|
placeholder="请选择指标类型"
|
|
:disabled="dialogType === 'edit'"
|
|
style="width: 100%"
|
|
>
|
|
<el-option label="队列大小" value="queue_size" />
|
|
<el-option label="成功率" value="success_rate" />
|
|
<el-option label="平均执行时间" value="avg_duration" />
|
|
<el-option label="并发数" value="concurrency" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item label="任务类型" prop="task_type">
|
|
<el-select
|
|
v-model="form.task_type"
|
|
placeholder="请选择任务类型"
|
|
:disabled="dialogType === 'edit'"
|
|
style="width: 100%"
|
|
>
|
|
<el-option label="实名认证轮询" value="polling:realname" />
|
|
<el-option label="卡数据轮询" value="polling:carddata" />
|
|
<el-option label="套餐轮询" value="polling:package" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row :gutter="20">
|
|
<el-col :span="12">
|
|
<el-form-item label="运算符" prop="operator">
|
|
<el-select v-model="form.operator" placeholder="请选择运算符" style="width: 100%">
|
|
<el-option label=">" value=">" />
|
|
<el-option label=">=" value=">=" />
|
|
<el-option label="<" value="<" />
|
|
<el-option label="<=" value="<=" />
|
|
<el-option label="=" value="=" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item label="阈值" prop="threshold">
|
|
<el-input-number
|
|
v-model="form.threshold"
|
|
:min="0"
|
|
:precision="2"
|
|
placeholder="请输入阈值"
|
|
style="width: 100%"
|
|
/>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row :gutter="20">
|
|
<el-col :span="12">
|
|
<el-form-item label="冷却时间(分钟)" prop="cooldown_minutes">
|
|
<el-input-number
|
|
v-model="form.cooldown_minutes"
|
|
:min="1"
|
|
placeholder="请输入冷却时间"
|
|
style="width: 100%"
|
|
/>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row :gutter="20">
|
|
<el-col :span="24">
|
|
<el-form-item label="通知渠道" prop="notify_channels">
|
|
<el-input
|
|
v-model="form.notify_channels"
|
|
type="textarea"
|
|
:rows="3"
|
|
placeholder="请输入通知渠道配置(JSON格式)"
|
|
/>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</ArtTableFullScreen>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, computed, nextTick, watch, h } from 'vue'
|
|
import { ElMessage, ElMessageBox, ElTag, type FormInstance, type FormRules } from 'element-plus'
|
|
import { PollingAlertService } from '@/api/modules'
|
|
import type { PollingAlertRule, CreatePollingAlertRuleRequest } from '@/types/api'
|
|
import { formatDateTime } from '@/utils/business/format'
|
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
|
import { useAuth } from '@/composables/useAuth'
|
|
|
|
const { hasAuth } = useAuth()
|
|
|
|
const loading = ref(false)
|
|
const tableData = ref<PollingAlertRule[]>([])
|
|
const dialogVisible = ref(false)
|
|
const dialogType = ref<'create' | 'edit'>('create')
|
|
const formRef = ref<FormInstance>()
|
|
|
|
const form = reactive<CreatePollingAlertRuleRequest & { id?: number }>({
|
|
rule_name: '',
|
|
alert_level: '',
|
|
metric_type: '',
|
|
task_type: '',
|
|
threshold: 0,
|
|
operator: '>',
|
|
cooldown_minutes: 5,
|
|
notify_channels: ''
|
|
})
|
|
|
|
const rules: FormRules = {
|
|
rule_name: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
|
alert_level: [{ required: true, message: '请选择告警级别', trigger: 'change' }],
|
|
metric_type: [{ required: true, message: '请选择指标类型', trigger: 'change' }],
|
|
task_type: [{ required: true, message: '请选择任务类型', trigger: 'change' }],
|
|
threshold: [{ required: true, message: '请输入阈值', trigger: 'blur' }],
|
|
operator: [{ required: true, message: '请选择运算符', trigger: 'change' }],
|
|
cooldown_minutes: [{ required: true, message: '请输入冷却时间', trigger: 'blur' }]
|
|
}
|
|
|
|
const getAlertLevelLabel = (level: string) => {
|
|
return level === 'critical' ? '严重' : '警告'
|
|
}
|
|
|
|
const getMetricTypeLabel = (type: string) => {
|
|
const labels: Record<string, string> = {
|
|
queue_size: '队列大小',
|
|
success_rate: '成功率',
|
|
avg_duration: '平均执行时间',
|
|
concurrency: '并发数'
|
|
}
|
|
return labels[type] || type
|
|
}
|
|
|
|
const getTaskTypeLabel = (type: string) => {
|
|
const labels: Record<string, string> = {
|
|
'polling:realname': '实名认证轮询',
|
|
'polling:carddata': '卡数据轮询',
|
|
'polling:package': '套餐轮询'
|
|
}
|
|
return labels[type] || type
|
|
}
|
|
|
|
// 动态列配置
|
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
|
{
|
|
prop: 'rule_name',
|
|
label: '规则名称',
|
|
minWidth: 150
|
|
},
|
|
{
|
|
prop: 'alert_level',
|
|
label: '告警级别',
|
|
width: 100,
|
|
slots: {
|
|
default: ({ row }: any) =>
|
|
h(ElTag, { type: row.alert_level === 'critical' ? 'danger' : 'warning' }, () =>
|
|
getAlertLevelLabel(row.alert_level)
|
|
)
|
|
}
|
|
},
|
|
{
|
|
prop: 'metric_type_name',
|
|
label: '指标类型',
|
|
width: 120,
|
|
formatter: (row: any) => getMetricTypeLabel(row.metric_type)
|
|
},
|
|
{
|
|
prop: 'task_type_name',
|
|
label: '任务类型',
|
|
width: 150,
|
|
formatter: (row: any) => getTaskTypeLabel(row.task_type)
|
|
},
|
|
{
|
|
prop: 'operator',
|
|
label: '运算符',
|
|
width: 80
|
|
},
|
|
{
|
|
prop: 'threshold',
|
|
label: '阈值',
|
|
width: 100
|
|
},
|
|
{
|
|
prop: 'cooldown_minutes',
|
|
label: '冷却时间(分钟)',
|
|
width: 120
|
|
},
|
|
{
|
|
prop: 'status',
|
|
label: '状态',
|
|
width: 80,
|
|
slots: {
|
|
default: ({ row }: any) =>
|
|
h(ElTag, { type: row.status === 1 ? 'success' : 'info' }, () =>
|
|
row.status === 1 ? '启用' : '禁用'
|
|
)
|
|
}
|
|
},
|
|
{
|
|
prop: 'created_at',
|
|
label: '创建时间',
|
|
width: 180,
|
|
formatter: (row: any) => formatDateTime(row.created_at)
|
|
}
|
|
])
|
|
|
|
const columnOptions = computed(() =>
|
|
columns.value.map((col) => ({
|
|
label: col.label,
|
|
prop: col.prop || col.label,
|
|
visible: true
|
|
}))
|
|
)
|
|
|
|
// 获取操作按钮
|
|
const getActions = (row: PollingAlertRule) => {
|
|
const actions: any[] = []
|
|
|
|
// 禁用/启用按钮直接显示
|
|
if (hasAuth('alert_rules:toggle')) {
|
|
actions.push({
|
|
label: row.status === 1 ? '禁用' : '启用',
|
|
handler: () => toggleStatus(row),
|
|
type: 'primary'
|
|
})
|
|
}
|
|
|
|
// 编辑和删除放到更多里
|
|
if (hasAuth('alert_rules:edit')) {
|
|
actions.push({
|
|
label: '编辑',
|
|
handler: () => showEditDialog(row),
|
|
type: 'primary'
|
|
})
|
|
}
|
|
|
|
if (hasAuth('alert_rules:delete')) {
|
|
actions.push({
|
|
label: '删除',
|
|
handler: () => handleDelete(row),
|
|
type: 'danger'
|
|
})
|
|
}
|
|
|
|
return actions
|
|
}
|
|
|
|
watch(dialogVisible, (val) => {
|
|
if (!val) {
|
|
nextTick(() => {
|
|
formRef.value?.clearValidate()
|
|
})
|
|
}
|
|
})
|
|
|
|
const loadData = async () => {
|
|
loading.value = true
|
|
try {
|
|
const res = await PollingAlertService.getPollingAlertRules()
|
|
if (res.code === 0 && res.data) {
|
|
tableData.value = res.data.items
|
|
}
|
|
} catch (error) {
|
|
console.log('加载数据失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const handleRefresh = () => {
|
|
loadData()
|
|
}
|
|
|
|
const resetForm = () => {
|
|
Object.assign(form, {
|
|
id: undefined,
|
|
rule_name: '',
|
|
alert_level: '',
|
|
metric_type: '',
|
|
task_type: '',
|
|
threshold: 0,
|
|
operator: '>',
|
|
cooldown_minutes: 5,
|
|
notify_channels: ''
|
|
})
|
|
}
|
|
|
|
const showCreateDialog = () => {
|
|
resetForm()
|
|
dialogType.value = 'create'
|
|
dialogVisible.value = true
|
|
nextTick(() => {
|
|
formRef.value?.clearValidate()
|
|
})
|
|
}
|
|
|
|
const showEditDialog = async (row: PollingAlertRule) => {
|
|
try {
|
|
const res = await PollingAlertService.getPollingAlertRuleById(row.id)
|
|
if (res.code === 0 && res.data) {
|
|
Object.assign(form, {
|
|
id: res.data.id,
|
|
rule_name: res.data.rule_name,
|
|
alert_level: res.data.alert_level,
|
|
metric_type: res.data.metric_type,
|
|
task_type: res.data.task_type,
|
|
threshold: res.data.threshold,
|
|
operator: res.data.operator,
|
|
cooldown_minutes: res.data.cooldown_minutes,
|
|
notify_channels: res.data.notify_channels
|
|
})
|
|
dialogType.value = 'edit'
|
|
dialogVisible.value = true
|
|
nextTick(() => {
|
|
formRef.value?.clearValidate()
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.log('获取规则详情失败')
|
|
}
|
|
}
|
|
|
|
const handleSubmit = async () => {
|
|
if (!formRef.value) return
|
|
await formRef.value.validate(async (valid) => {
|
|
if (valid) {
|
|
try {
|
|
if (dialogType.value === 'create') {
|
|
const res = await PollingAlertService.createPollingAlertRule(form)
|
|
if (res.code === 0) {
|
|
ElMessage.success('创建成功')
|
|
dialogVisible.value = false
|
|
loadData()
|
|
}
|
|
} else {
|
|
const { id, metric_type, task_type, operator, ...updateData } = form
|
|
const res = await PollingAlertService.updatePollingAlertRule(id!, updateData)
|
|
if (res.code === 0) {
|
|
ElMessage.success('更新成功')
|
|
dialogVisible.value = false
|
|
loadData()
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log(dialogType.value === 'create' ? '创建失败' : '更新失败')
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const toggleStatus = async (row: PollingAlertRule) => {
|
|
try {
|
|
const newStatus = row.status === 1 ? 0 : 1
|
|
const res = await PollingAlertService.updatePollingAlertRule(row.id, { status: newStatus })
|
|
if (res.code === 0) {
|
|
ElMessage.success(newStatus === 1 ? '已启用' : '已禁用')
|
|
loadData()
|
|
}
|
|
} catch (error) {
|
|
console.log('操作失败')
|
|
}
|
|
}
|
|
|
|
const handleDelete = (row: PollingAlertRule) => {
|
|
ElMessageBox.confirm('确定要删除该告警规则吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(async () => {
|
|
try {
|
|
const res = await PollingAlertService.deletePollingAlertRule(row.id)
|
|
if (res.code === 0) {
|
|
ElMessage.success('删除成功')
|
|
loadData()
|
|
}
|
|
} catch (error) {
|
|
console.log('删除失败')
|
|
}
|
|
})
|
|
}
|
|
|
|
loadData()
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.alert-rules-page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
}
|
|
</style>
|