fix
This commit is contained in:
@@ -10,6 +10,9 @@ VITE_APP_DESCRIPTION="default standard template"
|
||||
# API 基础URL
|
||||
VITE_API_BASE_URL=https://ops-api.apinb.com
|
||||
|
||||
# Logs 本地调试地址(仅 logs 模块使用)
|
||||
VITE_LOGS_API_BASE_URL=http://127.0.0.1:12440
|
||||
|
||||
|
||||
|
||||
# 应用版本
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { request } from '@/api/request'
|
||||
|
||||
const BASE = '/Logs/v1'
|
||||
const logsHost = (import.meta.env.VITE_API_BASE_URL || '').replace(/\/+$/, '')
|
||||
const BASE = logsHost ? `${logsHost}/Logs/v1` : '/Logs/v1'
|
||||
|
||||
/** 解析 bsm-sdk 风格响应,取出业务 data */
|
||||
export function unwrapLogsPayload(res: any): any {
|
||||
@@ -14,10 +15,15 @@ export interface LogEvent {
|
||||
created_at: string
|
||||
source_kind: string
|
||||
remote_addr: string
|
||||
source_ip: string
|
||||
raw_payload: string
|
||||
normalized_summary: string
|
||||
normalized_detail: string
|
||||
device_name: string
|
||||
resource_type: string
|
||||
resource_id: string
|
||||
resource_name: string
|
||||
match_method: string
|
||||
severity_code: string
|
||||
trap_oid: string
|
||||
alert_sent: boolean
|
||||
@@ -27,6 +33,10 @@ export interface LogEntriesParams {
|
||||
page?: number
|
||||
page_size?: number
|
||||
source_kind?: string
|
||||
resource_type?: string
|
||||
resource_id?: string
|
||||
dispatch_status?: string
|
||||
log_event_id?: number
|
||||
}
|
||||
|
||||
export interface LogEntriesResult {
|
||||
@@ -36,6 +46,24 @@ export interface LogEntriesResult {
|
||||
items: LogEvent[]
|
||||
}
|
||||
|
||||
export interface AlertOutbox {
|
||||
id: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
log_event_id: number
|
||||
payload_json: string
|
||||
status: string
|
||||
retry_count: number
|
||||
next_retry_at: string
|
||||
last_error: string
|
||||
}
|
||||
|
||||
export interface AlertOutboxParams {
|
||||
page?: number
|
||||
page_size?: number
|
||||
status?: string
|
||||
}
|
||||
|
||||
export interface SyslogRule {
|
||||
id?: number
|
||||
created_at?: string
|
||||
@@ -92,6 +120,14 @@ export function fetchLogEntries(params?: LogEntriesParams) {
|
||||
return request.get(`${BASE}/entries`, { params })
|
||||
}
|
||||
|
||||
export function fetchAlertOutbox(params?: AlertOutboxParams) {
|
||||
return request.get(`${BASE}/alert-outbox`, { params })
|
||||
}
|
||||
|
||||
export function retryAlertOutbox(id: number) {
|
||||
return request.post(`${BASE}/alert-outbox/${id}/retry`)
|
||||
}
|
||||
|
||||
export function fetchSyslogRules() {
|
||||
return request.get(`${BASE}/syslog-rules`)
|
||||
}
|
||||
|
||||
1
src/types/env.d.ts
vendored
1
src/types/env.d.ts
vendored
@@ -2,6 +2,7 @@
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_API_BASE_URL?: string
|
||||
readonly VITE_LOGS_API_BASE_URL?: string
|
||||
// 在这里可以继续补充其他 VITE_ 前缀的环境变量
|
||||
}
|
||||
|
||||
|
||||
@@ -83,16 +83,6 @@
|
||||
{{ recordDetail.rule_id || '-' }}
|
||||
</a-descriptions-item>
|
||||
|
||||
<!-- 标签信息 -->
|
||||
<a-descriptions-item label="标签" :span="2">
|
||||
<a-space wrap v-if="recordDetail.labels && Object.keys(recordDetail.labels).length > 0">
|
||||
<a-tag v-for="(value, key) in recordDetail.labels" :key="key" color="blue">
|
||||
{{ key }}: {{ value }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
<span v-else>-</span>
|
||||
</a-descriptions-item>
|
||||
|
||||
<!-- 创建与更新时间 -->
|
||||
<a-descriptions-item label="创建时间">
|
||||
{{ formatDate(recordDetail.created_at) }}
|
||||
|
||||
@@ -18,12 +18,18 @@
|
||||
@page-size-change="handlePageSizeChange"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #toolbar-left>
|
||||
<a-button type="outline" @click="openOutboxDrawer">告警队列</a-button>
|
||||
</template>
|
||||
<template #source_kind="{ record }">
|
||||
<a-tag>{{ sourceKindLabel(record.source_kind) }}</a-tag>
|
||||
</template>
|
||||
<template #raw_payload="{ record }">
|
||||
<span class="ellipsis-cell" :title="record.raw_payload">{{ truncate(record.raw_payload, 80) }}</span>
|
||||
</template>
|
||||
<template #severity_code="{ record }">
|
||||
<a-tag :color="severityColor(record.severity_code)">{{ severityLabel(record.severity_code) }}</a-tag>
|
||||
</template>
|
||||
<template #alert_sent="{ record }">
|
||||
<a-tag :color="record.alert_sent ? 'green' : 'gray'">
|
||||
{{ record.alert_sent ? '已转发' : '否' }}
|
||||
@@ -48,8 +54,13 @@
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="采集时间">{{ detailRow.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="来源地址">{{ detailRow.remote_addr || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="来源 IP">{{ detailRow.source_ip || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="设备名">{{ detailRow.device_name || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="严重级别">{{ detailRow.severity_code || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="资源类型">{{ detailRow.resource_type || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="资源 ID">{{ detailRow.resource_id || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="资源名称">{{ detailRow.resource_name || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="命中方式">{{ detailRow.match_method || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="严重级别">{{ severityLabel(detailRow.severity_code) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="Trap OID">{{ detailRow.trap_oid || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="已转发告警">
|
||||
{{ detailRow.alert_sent ? '是' : '否' }}
|
||||
@@ -64,28 +75,141 @@
|
||||
</a-descriptions>
|
||||
</template>
|
||||
</a-drawer>
|
||||
|
||||
<a-drawer
|
||||
v-model:visible="outboxVisible"
|
||||
width="920px"
|
||||
title="告警队列"
|
||||
:footer="false"
|
||||
unmount-on-close
|
||||
>
|
||||
<a-space direction="vertical" fill>
|
||||
<a-space>
|
||||
<a-select
|
||||
v-model="outboxStatus"
|
||||
:style="{ width: '180px' }"
|
||||
placeholder="全部状态"
|
||||
allow-clear
|
||||
@change="handleOutboxSearch"
|
||||
>
|
||||
<a-option value="">全部状态</a-option>
|
||||
<a-option value="pending">pending</a-option>
|
||||
<a-option value="retrying">retrying</a-option>
|
||||
<a-option value="sent">sent</a-option>
|
||||
<a-option value="dead">dead</a-option>
|
||||
</a-select>
|
||||
<a-button :loading="outboxLoading" @click="fetchOutboxList">刷新</a-button>
|
||||
</a-space>
|
||||
<a-table
|
||||
:data="outboxRows"
|
||||
:loading="outboxLoading"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
size="small"
|
||||
>
|
||||
<template #columns>
|
||||
<a-table-column title="ID" data-index="id" :width="70" />
|
||||
<a-table-column title="日志ID" :width="100">
|
||||
<template #cell="{ record }">
|
||||
<a-button type="text" size="small" @click="jumpToLogDetail(record.log_event_id)">
|
||||
{{ record.log_event_id }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="状态" :width="110">
|
||||
<template #cell="{ record }">
|
||||
<a-tag :color="outboxStatusColor(record.status)">
|
||||
{{ outboxStatusLabel(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="重试次数" data-index="retry_count" :width="90" />
|
||||
<a-table-column title="下次重试" data-index="next_retry_at" :width="180" />
|
||||
<a-table-column title="错误" data-index="last_error" ellipsis tooltip />
|
||||
<a-table-column title="操作" :width="150">
|
||||
<template #cell="{ record }">
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="record.status === 'sent'"
|
||||
@click="handleOutboxRetry(record.id)"
|
||||
>
|
||||
重试
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click="openPayloadModal(record.payload_json)">
|
||||
Payload
|
||||
</a-button>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-pagination
|
||||
:current="outboxPagination.current"
|
||||
:total="outboxPagination.total"
|
||||
:page-size="outboxPagination.pageSize"
|
||||
show-total
|
||||
show-page-size
|
||||
@change="handleOutboxPageChange"
|
||||
@page-size-change="handleOutboxPageSizeChange"
|
||||
/>
|
||||
</a-space>
|
||||
</a-drawer>
|
||||
|
||||
<a-modal
|
||||
v-model:visible="payloadVisible"
|
||||
title="Payload 详情"
|
||||
width="760px"
|
||||
:footer="false"
|
||||
unmount-on-close
|
||||
>
|
||||
<pre class="pre-block">{{ payloadText }}</pre>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { Message, Modal } 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 {
|
||||
fetchAlertOutbox,
|
||||
fetchLogEntries,
|
||||
retryAlertOutbox,
|
||||
unwrapLogsPayload,
|
||||
type AlertOutbox,
|
||||
type LogEvent,
|
||||
} from '@/api/ops/logs'
|
||||
import { fetchSeverityOptions } from '@/api/ops/alertPolicy'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref<LogEvent[]>([])
|
||||
const formModel = ref({
|
||||
source_kind: '' as string,
|
||||
resource_type: '' as string,
|
||||
resource_id: '' as string,
|
||||
dispatch_status: '' as string,
|
||||
})
|
||||
const detailVisible = ref(false)
|
||||
const detailRow = ref<LogEvent | null>(null)
|
||||
const outboxVisible = ref(false)
|
||||
const outboxStatus = ref('')
|
||||
const outboxLoading = ref(false)
|
||||
const outboxRows = ref<AlertOutbox[]>([])
|
||||
const payloadVisible = ref(false)
|
||||
const payloadText = ref('')
|
||||
type SeverityOption = {
|
||||
code: string
|
||||
name?: string
|
||||
color?: string
|
||||
}
|
||||
const severityOptions = ref<SeverityOption[]>([])
|
||||
const outboxPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
@@ -106,6 +230,41 @@ const formItems = computed<FormItem[]>(() => [
|
||||
{ label: 'SNMP Trap', value: 'snmp_trap' },
|
||||
],
|
||||
},
|
||||
{
|
||||
field: 'resource_type',
|
||||
label: '资源类型',
|
||||
type: 'select',
|
||||
placeholder: '全部',
|
||||
span: 6,
|
||||
options: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '服务器', value: 'server' },
|
||||
{ label: '采集设备', value: 'collector' },
|
||||
{ label: '设备', value: 'device' },
|
||||
],
|
||||
},
|
||||
{
|
||||
field: 'resource_id',
|
||||
label: '资源ID',
|
||||
type: 'input',
|
||||
placeholder: '输入资源ID',
|
||||
span: 6,
|
||||
},
|
||||
{
|
||||
field: 'dispatch_status',
|
||||
label: '分发状态',
|
||||
type: 'select',
|
||||
placeholder: '全部',
|
||||
span: 6,
|
||||
options: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '待发送', value: 'pending' },
|
||||
{ label: '重试中', value: 'retrying' },
|
||||
{ label: '已发送', value: 'sent' },
|
||||
{ label: '已失败', value: 'dead' },
|
||||
{ label: '不适用', value: 'not_applicable' },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
const columns = computed<TableColumnData[]>(() => [
|
||||
@@ -118,8 +277,13 @@ const columns = computed<TableColumnData[]>(() => [
|
||||
},
|
||||
{ title: '时间', dataIndex: 'created_at', width: 180, ellipsis: true, tooltip: true },
|
||||
{ title: '来源地址', dataIndex: 'remote_addr', width: 130, ellipsis: true, tooltip: true },
|
||||
{ title: '来源IP', dataIndex: 'source_ip', width: 120, ellipsis: true, tooltip: true },
|
||||
{ title: '设备', dataIndex: 'device_name', width: 120, ellipsis: true, tooltip: true },
|
||||
{ title: '级别', dataIndex: 'severity_code', width: 90 },
|
||||
{ title: '资源类型', dataIndex: 'resource_type', width: 100 },
|
||||
{ title: '资源ID', dataIndex: 'resource_id', width: 140, ellipsis: true, tooltip: true },
|
||||
{ title: '资源名称', dataIndex: 'resource_name', width: 140, ellipsis: true, tooltip: true },
|
||||
{ title: '命中方式', dataIndex: 'match_method', width: 90 },
|
||||
{ title: '级别', dataIndex: 'severity_code', slotName: 'severity_code', width: 110 },
|
||||
{ title: 'OID', dataIndex: 'trap_oid', width: 140, ellipsis: true, tooltip: true },
|
||||
{
|
||||
title: '原始报文',
|
||||
@@ -149,13 +313,71 @@ function sourceKindLabel(k: string) {
|
||||
return k || '-'
|
||||
}
|
||||
|
||||
function outboxStatusLabel(status: string) {
|
||||
if (status === 'pending') return '待发送'
|
||||
if (status === 'retrying') return '重试中'
|
||||
if (status === 'sent') return '已发送'
|
||||
if (status === 'dead') return '已失败'
|
||||
return status || '-'
|
||||
}
|
||||
|
||||
function outboxStatusColor(status: string) {
|
||||
if (status === 'pending') return 'arcoblue'
|
||||
if (status === 'retrying') return 'orange'
|
||||
if (status === 'sent') return 'green'
|
||||
if (status === 'dead') return 'red'
|
||||
return 'gray'
|
||||
}
|
||||
|
||||
function severityMeta(code: string) {
|
||||
const key = (code || '').trim().toLowerCase()
|
||||
if (!key) return null
|
||||
return (
|
||||
severityOptions.value.find((item) => String(item.code || '').trim().toLowerCase() === key) || null
|
||||
)
|
||||
}
|
||||
|
||||
function severityLabel(code: string) {
|
||||
const meta = severityMeta(code)
|
||||
if (meta?.name) return meta.name
|
||||
const key = (code || '').toLowerCase()
|
||||
if (!key) return '-'
|
||||
if (['critical', 'fatal', 'emergency', 'emerg', 'alert'].includes(key)) return '严重'
|
||||
if (['error', 'err'].includes(key)) return '错误'
|
||||
if (['warning', 'warn'].includes(key)) return '警告'
|
||||
if (['notice', 'info', 'informational'].includes(key)) return '信息'
|
||||
if (['debug', 'trace'].includes(key)) return '调试'
|
||||
return code
|
||||
}
|
||||
|
||||
function severityColor(code: string) {
|
||||
const meta = severityMeta(code)
|
||||
return meta?.color || 'arcoblue'
|
||||
}
|
||||
|
||||
async function fetchSeverityMetaList() {
|
||||
try {
|
||||
const res: any = await fetchSeverityOptions({ enabled: 'true' })
|
||||
const payload = res?.details ?? res?.data ?? []
|
||||
severityOptions.value = Array.isArray(payload) ? (payload as SeverityOption[]) : []
|
||||
} catch (e) {
|
||||
console.error('加载告警级别选项失败:', e)
|
||||
severityOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function truncate(s: string, n: number) {
|
||||
if (!s) return '-'
|
||||
return s.length <= n ? s : `${s.slice(0, n)}…`
|
||||
}
|
||||
|
||||
function handleFormModelUpdate(v: Record<string, unknown>) {
|
||||
formModel.value = v as { source_kind: string }
|
||||
formModel.value = v as {
|
||||
source_kind: string
|
||||
resource_type: string
|
||||
resource_id: string
|
||||
dispatch_status: string
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchList() {
|
||||
@@ -165,6 +387,9 @@ async function fetchList() {
|
||||
page: pagination.current,
|
||||
page_size: pagination.pageSize,
|
||||
source_kind: formModel.value.source_kind || undefined,
|
||||
resource_type: formModel.value.resource_type || undefined,
|
||||
resource_id: formModel.value.resource_id?.trim() || undefined,
|
||||
dispatch_status: formModel.value.dispatch_status || undefined,
|
||||
})
|
||||
const payload = unwrapLogsPayload(res) ?? {}
|
||||
tableData.value = payload.items ?? []
|
||||
@@ -188,7 +413,7 @@ function handleSearch() {
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
formModel.value = { source_kind: '' }
|
||||
formModel.value = { source_kind: '', resource_type: '', resource_id: '', dispatch_status: '' }
|
||||
pagination.current = 1
|
||||
fetchList()
|
||||
}
|
||||
@@ -213,6 +438,104 @@ function openDetail(row: LogEvent) {
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
function openOutboxDrawer() {
|
||||
outboxVisible.value = true
|
||||
fetchOutboxList()
|
||||
}
|
||||
|
||||
function handleOutboxSearch() {
|
||||
outboxPagination.current = 1
|
||||
fetchOutboxList()
|
||||
}
|
||||
|
||||
async function fetchOutboxList() {
|
||||
outboxLoading.value = true
|
||||
try {
|
||||
const res: any = await fetchAlertOutbox({
|
||||
page: outboxPagination.current,
|
||||
page_size: outboxPagination.pageSize,
|
||||
status: outboxStatus.value || undefined,
|
||||
})
|
||||
const payload = unwrapLogsPayload(res) ?? {}
|
||||
outboxRows.value = (payload.items ?? []) as AlertOutbox[]
|
||||
outboxPagination.total = payload.total ?? 0
|
||||
if (typeof res.code === 'number' && res.code !== 0) {
|
||||
Message.error(res.message || res.msg || '队列加载失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || '队列加载失败')
|
||||
outboxRows.value = []
|
||||
outboxPagination.total = 0
|
||||
} finally {
|
||||
outboxLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleOutboxPageChange(page: number) {
|
||||
outboxPagination.current = page
|
||||
fetchOutboxList()
|
||||
}
|
||||
|
||||
function handleOutboxPageSizeChange(size: number) {
|
||||
outboxPagination.pageSize = size
|
||||
outboxPagination.current = 1
|
||||
fetchOutboxList()
|
||||
}
|
||||
|
||||
function handleOutboxRetry(id: number) {
|
||||
Modal.confirm({
|
||||
title: '确认重试',
|
||||
content: `立即重试队列任务 #${id}?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
const res: any = await retryAlertOutbox(id)
|
||||
if (typeof res.code === 'number' && res.code !== 0) {
|
||||
Message.error(res.message || res.msg || '重试失败')
|
||||
return
|
||||
}
|
||||
Message.success('已提交重试')
|
||||
fetchOutboxList()
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || '重试失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function openPayloadModal(payload: string) {
|
||||
let text = payload || ''
|
||||
try {
|
||||
text = JSON.stringify(JSON.parse(payload || '{}'), null, 2)
|
||||
} catch {
|
||||
// 保持原样,便于排查非标准 JSON。
|
||||
}
|
||||
payloadText.value = text
|
||||
payloadVisible.value = true
|
||||
}
|
||||
|
||||
async function jumpToLogDetail(logEventID: number) {
|
||||
if (!logEventID) return
|
||||
try {
|
||||
const res: any = await fetchLogEntries({
|
||||
page: 1,
|
||||
page_size: 1,
|
||||
log_event_id: logEventID,
|
||||
})
|
||||
const payload = unwrapLogsPayload(res) ?? {}
|
||||
const row = (payload.items?.[0] || null) as LogEvent | null
|
||||
if (!row) {
|
||||
Message.warning(`未找到日志 #${logEventID}`)
|
||||
return
|
||||
}
|
||||
outboxVisible.value = false
|
||||
detailRow.value = row
|
||||
detailVisible.value = true
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || '日志定位失败')
|
||||
}
|
||||
}
|
||||
|
||||
fetchSeverityMetaList()
|
||||
fetchList()
|
||||
</script>
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
<template #enabled="{ record }">
|
||||
<a-tag :color="record.enabled ? 'green' : 'red'">{{ record.enabled ? '启用' : '禁用' }}</a-tag>
|
||||
</template>
|
||||
<template #severity_code="{ record }">
|
||||
<a-tag :color="severityColor(record.severity_code)">{{ severityLabel(record.severity_code) }}</a-tag>
|
||||
</template>
|
||||
<template #operations="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="openEdit(record)">编辑</a-button>
|
||||
@@ -296,11 +299,37 @@ const columns = computed<TableColumnData[]>(() => [
|
||||
{ title: '设备名包含', dataIndex: 'device_name_contains', ellipsis: true, tooltip: true },
|
||||
{ title: '关键字正则', dataIndex: 'keyword_regex', ellipsis: true, tooltip: true },
|
||||
{ title: '告警名', dataIndex: 'alert_name', ellipsis: true, tooltip: true },
|
||||
{ title: '级别', dataIndex: 'severity_code', width: 90 },
|
||||
{ title: '级别', dataIndex: 'severity_code', slotName: 'severity_code', width: 110 },
|
||||
{ title: '策略ID', dataIndex: 'policy_id', width: 88 },
|
||||
{ title: '操作', slotName: 'operations', width: 160, fixed: 'right' },
|
||||
])
|
||||
|
||||
function severityMeta(code: string) {
|
||||
const key = (code || '').trim().toLowerCase()
|
||||
if (!key) return null
|
||||
return (
|
||||
severityOptions.value.find((item) => String(item.code || '').trim().toLowerCase() === key) || null
|
||||
)
|
||||
}
|
||||
|
||||
function severityLabel(code: string) {
|
||||
const meta = severityMeta(code)
|
||||
if (meta?.name) return meta.name
|
||||
const key = (code || '').toLowerCase()
|
||||
if (!key) return '-'
|
||||
if (['critical', 'fatal', 'emergency', 'emerg', 'alert'].includes(key)) return '严重'
|
||||
if (['error', 'err'].includes(key)) return '错误'
|
||||
if (['warning', 'warn'].includes(key)) return '警告'
|
||||
if (['notice', 'info', 'informational'].includes(key)) return '信息'
|
||||
if (['debug', 'trace'].includes(key)) return '调试'
|
||||
return code
|
||||
}
|
||||
|
||||
function severityColor(code: string) {
|
||||
const meta = severityMeta(code)
|
||||
return meta?.color || 'arcoblue'
|
||||
}
|
||||
|
||||
function applyFilter(list: SyslogRule[]) {
|
||||
const kw = formModel.value.keyword?.trim()
|
||||
if (!kw) return list
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
<template #enabled="{ record }">
|
||||
<a-tag :color="record.enabled ? 'green' : 'red'">{{ record.enabled ? '启用' : '禁用' }}</a-tag>
|
||||
</template>
|
||||
<template #severity_code="{ record }">
|
||||
<a-tag :color="severityColor(record.severity_code)">{{ severityLabel(record.severity_code) }}</a-tag>
|
||||
</template>
|
||||
<template #operations="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="openEdit(record)">编辑</a-button>
|
||||
@@ -197,12 +200,38 @@ const columns = computed<TableColumnData[]>(() => [
|
||||
{ title: 'ID', dataIndex: 'id', width: 72 },
|
||||
{ title: 'OID 前缀', dataIndex: 'oid_prefix', ellipsis: true, tooltip: true },
|
||||
{ title: '标题', dataIndex: 'title', ellipsis: true, tooltip: true },
|
||||
{ title: '级别', dataIndex: 'severity_code', width: 90 },
|
||||
{ title: '级别', dataIndex: 'severity_code', slotName: 'severity_code', width: 110 },
|
||||
{ title: '启用', dataIndex: 'enabled', slotName: 'enabled', width: 88 },
|
||||
{ title: '描述', dataIndex: 'description', ellipsis: true, tooltip: true },
|
||||
{ title: '操作', slotName: 'operations', width: 160, fixed: 'right' },
|
||||
])
|
||||
|
||||
function severityMeta(code: string) {
|
||||
const key = (code || '').trim().toLowerCase()
|
||||
if (!key) return null
|
||||
return (
|
||||
severityOptions.value.find((item) => String(item.code || '').trim().toLowerCase() === key) || null
|
||||
)
|
||||
}
|
||||
|
||||
function severityLabel(code: string) {
|
||||
const meta = severityMeta(code)
|
||||
if (meta?.name) return meta.name
|
||||
const key = (code || '').toLowerCase()
|
||||
if (!key) return '-'
|
||||
if (['critical', 'fatal', 'emergency', 'emerg', 'alert'].includes(key)) return '严重'
|
||||
if (['error', 'err'].includes(key)) return '错误'
|
||||
if (['warning', 'warn'].includes(key)) return '警告'
|
||||
if (['notice', 'info', 'informational'].includes(key)) return '信息'
|
||||
if (['debug', 'trace'].includes(key)) return '调试'
|
||||
return code
|
||||
}
|
||||
|
||||
function severityColor(code: string) {
|
||||
const meta = severityMeta(code)
|
||||
return meta?.color || 'arcoblue'
|
||||
}
|
||||
|
||||
function applyFilter(list: TrapDictionaryEntry[]) {
|
||||
const kw = formModel.value.keyword?.trim()
|
||||
if (!kw) return list
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
<template #enabled="{ record }">
|
||||
<a-tag :color="record.enabled ? 'green' : 'red'">{{ record.enabled ? '启用' : '禁用' }}</a-tag>
|
||||
</template>
|
||||
<template #severity_code="{ record }">
|
||||
<a-tag :color="severityColor(record.severity_code)">{{ severityLabel(record.severity_code) }}</a-tag>
|
||||
</template>
|
||||
<template #operations="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="openEdit(record)">编辑</a-button>
|
||||
@@ -289,11 +292,37 @@ const columns = computed<TableColumnData[]>(() => [
|
||||
{ title: 'OID 前缀', dataIndex: 'oid_prefix', ellipsis: true, tooltip: true },
|
||||
{ title: 'Varbind 正则', dataIndex: 'varbind_match_regex', ellipsis: true, tooltip: true },
|
||||
{ title: '告警名', dataIndex: 'alert_name', ellipsis: true, tooltip: true },
|
||||
{ title: '级别', dataIndex: 'severity_code', width: 90 },
|
||||
{ title: '级别', dataIndex: 'severity_code', slotName: 'severity_code', width: 110 },
|
||||
{ title: '策略ID', dataIndex: 'policy_id', width: 88 },
|
||||
{ title: '操作', slotName: 'operations', width: 160, fixed: 'right' },
|
||||
])
|
||||
|
||||
function severityMeta(code: string) {
|
||||
const key = (code || '').trim().toLowerCase()
|
||||
if (!key) return null
|
||||
return (
|
||||
severityOptions.value.find((item) => String(item.code || '').trim().toLowerCase() === key) || null
|
||||
)
|
||||
}
|
||||
|
||||
function severityLabel(code: string) {
|
||||
const meta = severityMeta(code)
|
||||
if (meta?.name) return meta.name
|
||||
const key = (code || '').toLowerCase()
|
||||
if (!key) return '-'
|
||||
if (['critical', 'fatal', 'emergency', 'emerg', 'alert'].includes(key)) return '严重'
|
||||
if (['error', 'err'].includes(key)) return '错误'
|
||||
if (['warning', 'warn'].includes(key)) return '警告'
|
||||
if (['notice', 'info', 'informational'].includes(key)) return '信息'
|
||||
if (['debug', 'trace'].includes(key)) return '调试'
|
||||
return code
|
||||
}
|
||||
|
||||
function severityColor(code: string) {
|
||||
const meta = severityMeta(code)
|
||||
return meta?.color || 'arcoblue'
|
||||
}
|
||||
|
||||
function applyFilter(list: TrapRule[]) {
|
||||
const kw = formModel.value.keyword?.trim()
|
||||
if (!kw) return list
|
||||
|
||||
Reference in New Issue
Block a user