diff --git a/frontend/src/components/common/HelpTooltip.vue b/frontend/src/components/common/HelpTooltip.vue new file mode 100644 index 00000000..7679ced4 --- /dev/null +++ b/frontend/src/components/common/HelpTooltip.vue @@ -0,0 +1,44 @@ + + + + diff --git a/frontend/src/components/layout/AppSidebar.vue b/frontend/src/components/layout/AppSidebar.vue index 791327a1..78217ec8 100644 --- a/frontend/src/components/layout/AppSidebar.vue +++ b/frontend/src/components/layout/AppSidebar.vue @@ -144,10 +144,10 @@ diff --git a/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue b/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue new file mode 100644 index 00000000..c2c6adb6 --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue @@ -0,0 +1,374 @@ + + + diff --git a/frontend/src/views/admin/ops/components/OpsDashboardSkeleton.vue b/frontend/src/views/admin/ops/components/OpsDashboardSkeleton.vue new file mode 100644 index 00000000..5bbadd03 --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsDashboardSkeleton.vue @@ -0,0 +1,53 @@ + + diff --git a/frontend/src/views/admin/ops/components/OpsEmailNotificationCard.vue b/frontend/src/views/admin/ops/components/OpsEmailNotificationCard.vue new file mode 100644 index 00000000..0204cbeb --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsEmailNotificationCard.vue @@ -0,0 +1,441 @@ + + + diff --git a/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue b/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue new file mode 100644 index 00000000..118a1f3a --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsErrorDetailModal.vue @@ -0,0 +1,360 @@ + + + diff --git a/frontend/src/views/admin/ops/components/OpsErrorDetailsModal.vue b/frontend/src/views/admin/ops/components/OpsErrorDetailsModal.vue new file mode 100644 index 00000000..f4a522de --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsErrorDetailsModal.vue @@ -0,0 +1,293 @@ + + + diff --git a/frontend/src/views/admin/ops/components/OpsErrorDistributionChart.vue b/frontend/src/views/admin/ops/components/OpsErrorDistributionChart.vue new file mode 100644 index 00000000..a52b5442 --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsErrorDistributionChart.vue @@ -0,0 +1,157 @@ + + + diff --git a/frontend/src/views/admin/ops/components/OpsErrorLogTable.vue b/frontend/src/views/admin/ops/components/OpsErrorLogTable.vue new file mode 100644 index 00000000..6a4be1a7 --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsErrorLogTable.vue @@ -0,0 +1,238 @@ + + + diff --git a/frontend/src/views/admin/ops/components/OpsErrorTrendChart.vue b/frontend/src/views/admin/ops/components/OpsErrorTrendChart.vue new file mode 100644 index 00000000..032e1205 --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsErrorTrendChart.vue @@ -0,0 +1,185 @@ + + + diff --git a/frontend/src/views/admin/ops/components/OpsLatencyChart.vue b/frontend/src/views/admin/ops/components/OpsLatencyChart.vue new file mode 100644 index 00000000..c62b3aa9 --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsLatencyChart.vue @@ -0,0 +1,101 @@ + + + diff --git a/frontend/src/views/admin/ops/components/OpsRequestDetailsModal.vue b/frontend/src/views/admin/ops/components/OpsRequestDetailsModal.vue new file mode 100644 index 00000000..541aa3ed --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsRequestDetailsModal.vue @@ -0,0 +1,309 @@ + + + diff --git a/frontend/src/views/admin/ops/components/OpsRuntimeSettingsCard.vue b/frontend/src/views/admin/ops/components/OpsRuntimeSettingsCard.vue new file mode 100644 index 00000000..e9df347d --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsRuntimeSettingsCard.vue @@ -0,0 +1,439 @@ + + + + diff --git a/frontend/src/views/admin/ops/components/OpsThroughputTrendChart.vue b/frontend/src/views/admin/ops/components/OpsThroughputTrendChart.vue new file mode 100644 index 00000000..e3bd26c2 --- /dev/null +++ b/frontend/src/views/admin/ops/components/OpsThroughputTrendChart.vue @@ -0,0 +1,252 @@ + + + diff --git a/frontend/src/views/admin/ops/types.ts b/frontend/src/views/admin/ops/types.ts new file mode 100644 index 00000000..08830542 --- /dev/null +++ b/frontend/src/views/admin/ops/types.ts @@ -0,0 +1,17 @@ +// Ops 前端视图层的共享类型(与后端 DTO 解耦)。 + +export type ChartState = 'loading' | 'empty' | 'ready' + +// Re-export ops alert/settings types so view components can import from a single place +// while keeping the API contract centralized in `@/api/admin/ops`. +export type { + AlertRule, + AlertEvent, + AlertSeverity, + ThresholdMode, + MetricType, + Operator, + EmailNotificationConfig, + OpsDistributedLockSettings, + OpsAlertRuntimeSettings +} from '@/api/admin/ops' diff --git a/frontend/src/views/admin/ops/utils/opsFormatters.ts b/frontend/src/views/admin/ops/utils/opsFormatters.ts new file mode 100644 index 00000000..d503b5a5 --- /dev/null +++ b/frontend/src/views/admin/ops/utils/opsFormatters.ts @@ -0,0 +1,75 @@ +/** + * Ops 页面共享的格式化/样式工具。 + * + * 目标:尽量对齐 `docs/sub2api` 备份版本的视觉表现(需求一致部分保持一致), + * 同时避免引入额外 UI 依赖。 + */ + +import type { OpsSeverity } from '@/api/admin/ops' +import { formatBytes } from '@/utils/format' + +export function getSeverityClass(severity: OpsSeverity): string { + const classes: Record = { + P0: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400', + P1: 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400', + P2: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400', + P3: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400' + } + return classes[String(severity || '')] || classes.P3 +} + +export function truncateMessage(msg: string, maxLength = 80): string { + if (!msg) return '' + return msg.length > maxLength ? msg.substring(0, maxLength) + '...' : msg +} + +/** + * 格式化日期时间(短格式,和旧 Ops 页面一致)。 + * 输出: `MM-DD HH:mm:ss` + */ +export function formatDateTime(dateStr: string): string { + const d = new Date(dateStr) + if (Number.isNaN(d.getTime())) return '' + return `${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}` +} + +export function sumNumbers(values: Array): number { + return values.reduce((acc, v) => { + const n = typeof v === 'number' && Number.isFinite(v) ? v : 0 + return acc + n + }, 0) +} + +/** + * 解析 time_range 为分钟数。 + * 支持:`5m/30m/1h/6h/24h` + */ +export function parseTimeRangeMinutes(range: string): number { + const trimmed = (range || '').trim() + if (!trimmed) return 60 + if (trimmed.endsWith('m')) { + const v = Number.parseInt(trimmed.slice(0, -1), 10) + return Number.isFinite(v) && v > 0 ? v : 60 + } + if (trimmed.endsWith('h')) { + const v = Number.parseInt(trimmed.slice(0, -1), 10) + return Number.isFinite(v) && v > 0 ? v * 60 : 60 + } + return 60 +} + +export function formatHistoryLabel(date: string | undefined, timeRange: string): string { + if (!date) return '' + const d = new Date(date) + if (Number.isNaN(d.getTime())) return '' + const minutes = parseTimeRangeMinutes(timeRange) + if (minutes >= 24 * 60) { + return `${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}` + } + return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}` +} + +export function formatByteRate(bytes: number, windowMinutes: number): string { + const seconds = Math.max(1, (windowMinutes || 1) * 60) + return `${formatBytes(bytes / seconds, 1)}/s` +}