diff --git a/.env.development b/.env.development
index 0799751..6e87a31 100644
--- a/.env.development
+++ b/.env.development
@@ -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
+
# 应用版本
diff --git a/src/api/ops/logs.ts b/src/api/ops/logs.ts
index e517544..fd14357 100644
--- a/src/api/ops/logs.ts
+++ b/src/api/ops/logs.ts
@@ -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`)
}
diff --git a/src/types/env.d.ts b/src/types/env.d.ts
index cdf404d..8bee776 100644
--- a/src/types/env.d.ts
+++ b/src/types/env.d.ts
@@ -2,6 +2,7 @@
interface ImportMetaEnv {
readonly VITE_API_BASE_URL?: string
+ readonly VITE_LOGS_API_BASE_URL?: string
// 在这里可以继续补充其他 VITE_ 前缀的环境变量
}
diff --git a/src/views/ops/pages/alert/history/components/HistoryDetailDialog.vue b/src/views/ops/pages/alert/history/components/HistoryDetailDialog.vue
index d84d0e6..161e2f1 100644
--- a/src/views/ops/pages/alert/history/components/HistoryDetailDialog.vue
+++ b/src/views/ops/pages/alert/history/components/HistoryDetailDialog.vue
@@ -83,16 +83,6 @@
{{ recordDetail.rule_id || '-' }}
-
-
-
-
- {{ key }}: {{ value }}
-
-
- -
-
-
{{ formatDate(recordDetail.created_at) }}
diff --git a/src/views/ops/pages/log-mgmt/entries/index.vue b/src/views/ops/pages/log-mgmt/entries/index.vue
index 6092cdd..60dc6ab 100644
--- a/src/views/ops/pages/log-mgmt/entries/index.vue
+++ b/src/views/ops/pages/log-mgmt/entries/index.vue
@@ -18,12 +18,18 @@
@page-size-change="handlePageSizeChange"
@refresh="handleRefresh"
>
+
+ 告警队列
+
{{ sourceKindLabel(record.source_kind) }}
{{ truncate(record.raw_payload, 80) }}
+
+ {{ severityLabel(record.severity_code) }}
+
{{ record.alert_sent ? '已转发' : '否' }}
@@ -48,8 +54,13 @@
{{ detailRow.created_at }}
{{ detailRow.remote_addr || '-' }}
+ {{ detailRow.source_ip || '-' }}
{{ detailRow.device_name || '-' }}
- {{ detailRow.severity_code || '-' }}
+ {{ detailRow.resource_type || '-' }}
+ {{ detailRow.resource_id || '-' }}
+ {{ detailRow.resource_name || '-' }}
+ {{ detailRow.match_method || '-' }}
+ {{ severityLabel(detailRow.severity_code) }}
{{ detailRow.trap_oid || '-' }}
{{ detailRow.alert_sent ? '是' : '否' }}
@@ -64,28 +75,141 @@
+
+
+
+
+
+ 全部状态
+ pending
+ retrying
+ sent
+ dead
+
+ 刷新
+
+
+
+
+
+
+
+ {{ record.log_event_id }}
+
+
+
+
+
+
+ {{ outboxStatusLabel(record.status) }}
+
+
+
+
+
+
+
+
+
+ 重试
+
+
+ Payload
+
+
+
+
+
+
+
+
+
+
+ {{ payloadText }}
+
diff --git a/src/views/ops/pages/log-mgmt/syslog-rules/index.vue b/src/views/ops/pages/log-mgmt/syslog-rules/index.vue
index 45f162c..2d9fb4a 100644
--- a/src/views/ops/pages/log-mgmt/syslog-rules/index.vue
+++ b/src/views/ops/pages/log-mgmt/syslog-rules/index.vue
@@ -27,6 +27,9 @@
{{ record.enabled ? '启用' : '禁用' }}
+
+ {{ severityLabel(record.severity_code) }}
+
编辑
@@ -296,11 +299,37 @@ const columns = computed(() => [
{ 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
diff --git a/src/views/ops/pages/log-mgmt/trap-dictionary/index.vue b/src/views/ops/pages/log-mgmt/trap-dictionary/index.vue
index 7dfddf8..ee9b4a6 100644
--- a/src/views/ops/pages/log-mgmt/trap-dictionary/index.vue
+++ b/src/views/ops/pages/log-mgmt/trap-dictionary/index.vue
@@ -27,6 +27,9 @@
{{ record.enabled ? '启用' : '禁用' }}
+
+ {{ severityLabel(record.severity_code) }}
+
编辑
@@ -197,12 +200,38 @@ const columns = computed(() => [
{ 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
diff --git a/src/views/ops/pages/log-mgmt/trap-rules/index.vue b/src/views/ops/pages/log-mgmt/trap-rules/index.vue
index 5d1fd50..bccc0bc 100644
--- a/src/views/ops/pages/log-mgmt/trap-rules/index.vue
+++ b/src/views/ops/pages/log-mgmt/trap-rules/index.vue
@@ -27,6 +27,9 @@
{{ record.enabled ? '启用' : '禁用' }}
+
+ {{ severityLabel(record.severity_code) }}
+
编辑
@@ -289,11 +292,37 @@ const columns = computed(() => [
{ 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