通知告警规则引擎
This commit is contained in:
Vendored
+3
-1
@@ -5,8 +5,10 @@
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>设备管理系统</title>
|
||||
<script type="module" crossorigin src="/assets/index-1sWe50d-.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-RVBj8t6x.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/runtime-core.esm-bundler-DZ3WZ9y9.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/message-ddUl4JIs.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BpBnxyAz.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 告警规则列表
|
||||
export function getAlertRuleList() {
|
||||
return request.get('/inspection/alert-rule/list')
|
||||
}
|
||||
|
||||
// 新增告警规则
|
||||
export function addAlertRule(data) {
|
||||
return request.post('/inspection/alert-rule/add', data)
|
||||
}
|
||||
|
||||
// 修改告警规则
|
||||
export function updateAlertRule(data) {
|
||||
return request.put('/inspection/alert-rule/update', data)
|
||||
}
|
||||
|
||||
// 删除告警规则
|
||||
export function deleteAlertRule(id) {
|
||||
return request.delete(`/inspection/alert-rule/${id}`)
|
||||
}
|
||||
|
||||
// 启用/停用告警规则
|
||||
export function setAlertRuleEnabled(id, enabled) {
|
||||
return request.put(`/inspection/alert-rule/${id}/enabled`, null, { params: { enabled } })
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 转发规则列表
|
||||
export function getDataForwardList() {
|
||||
return request.get('/inspection/data-forward/list')
|
||||
}
|
||||
|
||||
// 新增转发规则
|
||||
export function addDataForward(data) {
|
||||
return request.post('/inspection/data-forward/add', data)
|
||||
}
|
||||
|
||||
// 修改转发规则
|
||||
export function updateDataForward(data) {
|
||||
return request.put('/inspection/data-forward/update', data)
|
||||
}
|
||||
|
||||
// 删除转发规则
|
||||
export function deleteDataForward(id) {
|
||||
return request.delete(`/inspection/data-forward/${id}`)
|
||||
}
|
||||
|
||||
// 启用/停用转发规则
|
||||
export function setDataForwardEnabled(id, enabled) {
|
||||
return request.put(`/inspection/data-forward/${id}/enabled`, null, { params: { enabled } })
|
||||
}
|
||||
@@ -162,6 +162,12 @@ const routes = [
|
||||
name: 'AlertMessage',
|
||||
component: () => import('@/views/inspection/alert-message/Index.vue'),
|
||||
meta: { title: '消息告警', icon: 'ChatDotRound' }
|
||||
},
|
||||
{
|
||||
path: 'data-forward',
|
||||
name: 'DataForward',
|
||||
component: () => import('@/views/inspection/data-forward/Index.vue'),
|
||||
meta: { title: '数据转发', icon: 'Promotion' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -2,13 +2,196 @@
|
||||
<div class="page-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span>告警规则</span>
|
||||
<div class="card-header">
|
||||
<span>告警规则({{ rules.length }} 条)</span>
|
||||
<el-button type="primary" icon="Plus" @click="openDialog()">新增规则</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty description="告警规则 - 功能开发中" />
|
||||
|
||||
<el-table :data="rules" v-loading="loading" stripe>
|
||||
<el-table-column prop="RuleName" label="规则名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="适用设备" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="!row.DeviceType" type="info">全部设备</el-tag>
|
||||
<span v-else>{{ deviceTypeLabel(row.DeviceType) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="触发条件" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ metricLabel(row.Metric) }} {{ row.Operator }} {{ row.Threshold }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="告警级别" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="levelTagType(row.AlarmLevel)">{{ row.AlarmLevel }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="启用" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-switch :model-value="row.Enabled" @change="onToggleEnabled(row, $event)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="TriggerCount" label="触发次数" width="90" align="center" />
|
||||
<el-table-column label="最近触发" width="160">
|
||||
<template #default="{ row }">{{ fmtTime(row.LastAlarmTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="160">
|
||||
<template #default="{ row }">{{ fmtTime(row.CreateTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="130" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openDialog(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="onDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="form.Id ? '编辑告警规则' : '新增告警规则'" width="520px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
|
||||
<el-form-item label="规则名称" prop="RuleName">
|
||||
<el-input v-model="form.RuleName" placeholder="如:温度箱超温报警" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="适用设备" prop="DeviceType">
|
||||
<el-select v-model="form.DeviceType" placeholder="全部设备" clearable style="width: 100%">
|
||||
<el-option v-for="d in deviceTypes" :key="d.value" :label="d.label" :value="d.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="监测指标" prop="Metric">
|
||||
<el-select v-model="form.Metric" placeholder="请选择监测指标" style="width: 100%">
|
||||
<el-option v-for="m in metrics" :key="m.value" :label="m.label" :value="m.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="触发条件" required>
|
||||
<div style="display: flex; gap: 8px; width: 100%">
|
||||
<el-select v-model="form.Operator" placeholder="运算符" style="width: 45%" prop="Operator">
|
||||
<el-option v-for="op in operators" :key="op" :label="op" :value="op" />
|
||||
</el-select>
|
||||
<el-input-number v-model="form.Threshold" :precision="1" :step="1" style="width: 55%" placeholder="阈值" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="告警级别" prop="AlarmLevel">
|
||||
<el-select v-model="form.AlarmLevel" placeholder="请选择告警级别" style="width: 100%">
|
||||
<el-option v-for="l in levels" :key="l" :label="l" :value="l" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="form.Enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.Remark" type="textarea" :rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// TODO: 实现告警规则功能
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
getAlertRuleList, addAlertRule, updateAlertRule,
|
||||
deleteAlertRule, setAlertRuleEnabled
|
||||
} from '@/api/alertRule'
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const rules = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const formRef = ref(null)
|
||||
|
||||
const deviceTypes = [
|
||||
{ value: 'TemperatureBox', label: '温度箱' },
|
||||
{ value: 'ChargeCabinet', label: '充放电柜' },
|
||||
{ value: 'AuxiliaryDevice', label: '辅助设备' }
|
||||
]
|
||||
const metrics = [
|
||||
{ value: 'Temperature', label: '温度' },
|
||||
{ value: 'Humidity', label: '湿度' }
|
||||
]
|
||||
const operators = ['>', '>=', '<', '<=', '=', '≠']
|
||||
const levels = ['信息', '警告', '紧急']
|
||||
|
||||
const defaultForm = () => ({
|
||||
Id: null, RuleName: '', DeviceType: '', Metric: 'Temperature',
|
||||
Operator: '>', Threshold: 80, AlarmLevel: '警告', Enabled: true, Remark: ''
|
||||
})
|
||||
const form = reactive(defaultForm())
|
||||
|
||||
const formRules = {
|
||||
RuleName: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
||||
Metric: [{ required: true, message: '请选择监测指标', trigger: 'change' }],
|
||||
AlarmLevel: [{ required: true, message: '请选择告警级别', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const deviceTypeLabel = (v) => deviceTypes.find(d => d.value === v)?.label || v
|
||||
const metricLabel = (v) => metrics.find(m => m.value === v)?.label || v
|
||||
const levelTagType = (l) => ({ '信息': 'info', '警告': 'warning', '紧急': 'danger' }[l] || 'info')
|
||||
const fmtTime = (t) => t ? String(t).replace('T', ' ').slice(0, 19) : '-'
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
rules.value = (await getAlertRuleList()) || []
|
||||
} catch (e) { /* 拦截器已提示 */ } finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openDialog(row) {
|
||||
Object.assign(form, defaultForm(), row || {})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
await formRef.value.validate()
|
||||
saving.value = true
|
||||
try {
|
||||
// 后端 Id 是 long 不接受 null:新增时剔除 Id 字段,否则报 400
|
||||
const payload = { ...form }
|
||||
if (!payload.Id) delete payload.Id
|
||||
if (payload.Id) {
|
||||
await updateAlertRule(payload)
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
await addAlertRule(payload)
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
fetchList()
|
||||
} catch (e) { /* 拦截器已提示 */ } finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onToggleEnabled(row, enabled) {
|
||||
try {
|
||||
await setAlertRuleEnabled(row.Id, enabled)
|
||||
row.Enabled = enabled
|
||||
ElMessage.success(enabled ? '已启用' : '已停用')
|
||||
} catch (e) { /* 拦截器已提示 */ }
|
||||
}
|
||||
|
||||
async function onDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除规则「${row.RuleName}」吗?`, '提示', { type: 'warning' })
|
||||
try {
|
||||
await deleteAlertRule(row.Id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchList()
|
||||
} catch (e) { /* 拦截器已提示 */ }
|
||||
}
|
||||
|
||||
onMounted(fetchList)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>数据转发规则({{ rules.length }} 条)</span>
|
||||
<el-button type="primary" icon="Plus" @click="openDialog()">新增规则</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-alert type="info" :closable="false" style="margin-bottom: 12px"
|
||||
title="按设定的间隔,将设备数据通过消息通知方式定时转发到目标地址" />
|
||||
|
||||
<el-table :data="rules" v-loading="loading" stripe>
|
||||
<el-table-column prop="ForwardName" label="规则名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="源设备" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="!row.SourceDeviceType" type="info">全部设备</el-tag>
|
||||
<span v-else>{{ deviceTypeLabel(row.SourceDeviceType) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="转发间隔" width="100" align="center">
|
||||
<template #default="{ row }">{{ row.ForwardInterval }} 秒</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="通知方式" width="110">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="notifyTagType(row.NotifyType)">{{ notifyLabel(row.NotifyType) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="TargetAddress" label="目标地址" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="启用" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-switch :model-value="row.Enabled" @change="onToggleEnabled(row, $event)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最近转发" width="160">
|
||||
<template #default="{ row }">{{ fmtTime(row.LastForwardTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="160">
|
||||
<template #default="{ row }">{{ fmtTime(row.CreateTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="130" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openDialog(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="onDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="form.Id ? '编辑转发规则' : '新增转发规则'" width="520px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
|
||||
<el-form-item label="规则名称" prop="ForwardName">
|
||||
<el-input v-model="form.ForwardName" placeholder="如:温度箱数据转发到运维邮箱" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="源设备" prop="SourceDeviceType">
|
||||
<el-select v-model="form.SourceDeviceType" placeholder="全部设备" clearable style="width: 100%">
|
||||
<el-option v-for="d in deviceTypes" :key="d.value" :label="d.label" :value="d.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="转发间隔" prop="ForwardInterval">
|
||||
<el-input-number v-model="form.ForwardInterval" :min="1" :step="10" style="width: 100%" />
|
||||
<div class="form-tip">单位:秒,每隔多久转发一次数据</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="通知方式" prop="NotifyType">
|
||||
<el-select v-model="form.NotifyType" placeholder="请选择通知方式" style="width: 100%">
|
||||
<el-option v-for="n in notifyTypes" :key="n.value" :label="n.label" :value="n.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标地址" prop="TargetAddress">
|
||||
<el-input v-model="form.TargetAddress" placeholder="邮箱 / 手机号 / 接口URL" maxlength="200" />
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="form.Enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.Remark" type="textarea" :rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
getDataForwardList, addDataForward, updateDataForward,
|
||||
deleteDataForward, setDataForwardEnabled
|
||||
} from '@/api/dataForward'
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const rules = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const formRef = ref(null)
|
||||
|
||||
const deviceTypes = [
|
||||
{ value: 'TemperatureBox', label: '温度箱' },
|
||||
{ value: 'ChargeCabinet', label: '充放电柜' },
|
||||
{ value: 'AuxiliaryDevice', label: '辅助设备' }
|
||||
]
|
||||
const notifyTypes = [
|
||||
{ value: 'Email', label: '邮件' },
|
||||
{ value: 'Sms', label: '短信' },
|
||||
{ value: 'Http', label: 'HTTP 推送' }
|
||||
]
|
||||
|
||||
const defaultForm = () => ({
|
||||
Id: null, ForwardName: '', SourceDeviceType: '', ForwardInterval: 60,
|
||||
NotifyType: 'Email', TargetAddress: '', Enabled: true, Remark: ''
|
||||
})
|
||||
const form = reactive(defaultForm())
|
||||
|
||||
const formRules = {
|
||||
ForwardName: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
||||
ForwardInterval: [{ required: true, message: '请输入转发间隔', trigger: 'blur' }],
|
||||
NotifyType: [{ required: true, message: '请选择通知方式', trigger: 'change' }],
|
||||
TargetAddress: [{ required: true, message: '请输入目标地址', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const deviceTypeLabel = (v) => deviceTypes.find(d => d.value === v)?.label || v
|
||||
const notifyLabel = (v) => notifyTypes.find(n => n.value === v)?.label || v
|
||||
const notifyTagType = (v) => ({ Email: 'primary', Sms: 'success', Http: 'warning' }[v] || 'info')
|
||||
const fmtTime = (t) => t ? String(t).replace('T', ' ').slice(0, 19) : '-'
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
rules.value = (await getDataForwardList()) || []
|
||||
} catch (e) { /* 拦截器已提示 */ } finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openDialog(row) {
|
||||
Object.assign(form, defaultForm(), row || {})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
await formRef.value.validate()
|
||||
saving.value = true
|
||||
try {
|
||||
// 后端 Id 是 long 不接受 null:新增时剔除 Id 字段,否则报 400
|
||||
const payload = { ...form }
|
||||
if (!payload.Id) delete payload.Id
|
||||
if (payload.Id) {
|
||||
await updateDataForward(payload)
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
await addDataForward(payload)
|
||||
ElMessage.success('新增成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
fetchList()
|
||||
} catch (e) { /* 拦截器已提示 */ } finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onToggleEnabled(row, enabled) {
|
||||
try {
|
||||
await setDataForwardEnabled(row.Id, enabled)
|
||||
row.Enabled = enabled
|
||||
ElMessage.success(enabled ? '已启用' : '已停用')
|
||||
} catch (e) { /* 拦截器已提示 */ }
|
||||
}
|
||||
|
||||
async function onDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除规则「${row.ForwardName}」吗?`, '提示', { type: 'warning' })
|
||||
try {
|
||||
await deleteDataForward(row.Id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchList()
|
||||
} catch (e) { /* 拦截器已提示 */ }
|
||||
}
|
||||
|
||||
onMounted(fetchList)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user