From 73ef9c6b127b6c67e9c8290685f45253161165a9 Mon Sep 17 00:00:00 2001 From: OG T Date: Thu, 9 Apr 2026 13:58:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20QA=20=E6=8E=83=E6=8F=8F=20=E2=80=94?= =?UTF-8?q?=20alert-operation-logs=20i18n=20+=20classic=20emoji=E2=86=92ic?= =?UTF-8?q?on=20+=20knowledge=20=E8=BC=89=E5=85=A5=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - alert-operation-logs: 30+ 處硬編碼中文改 useTranslations (18 event types + UI) - classic: 告警 badge + 等待確認 + TOOL_EMOJI → Lucide icon - knowledge: 載入中 → common.loading - 新增 alertOpLogs i18n section (zh-TW + en) Co-Authored-By: Claude Sonnet 4.6 --- apps/web/messages/en.json | 39 +++++++ apps/web/messages/zh-TW.json | 39 +++++++ .../[locale]/alert-operation-logs/page.tsx | 110 ++++++++++-------- apps/web/src/app/[locale]/classic/page.tsx | 27 ++--- apps/web/src/app/[locale]/knowledge/page.tsx | 4 +- 5 files changed, 156 insertions(+), 63 deletions(-) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 8124832a..dbaab86d 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -1137,5 +1137,44 @@ "dispositionManual": "Manual Resolved", "dispositionCold": "Cold Start Trust", "autoRateLabel": "Automation Rate" + }, + "alertOpLogs": { + "title": "Alert Operation Logs", + "subtitle": "alert_operation_log · Full event stream", + "refresh": "Refresh", + "totalEvents24h": "24h Total Events", + "allEventTypes": "All Event Types", + "incidentIdFilter": "Filter by Incident ID...", + "totalCount": "{count} total", + "colTime": "Time", + "colEventType": "Event Type", + "colIncident": "Incident", + "colActor": "Actor", + "colDetail": "Detail", + "colResult": "Result", + "loading": "Loading...", + "noRecords": "No records", + "loadError": "Failed to load, please retry", + "pageInfo": "Page {page} / {total}", + "prevPage": "Previous", + "nextPage": "Next", + "eventAlertReceived": "Alert Received", + "eventTelegramSent": "TG Notified", + "eventUserAction": "User Action", + "eventAutoRepairTriggered": "Auto Repair", + "eventExecutionStarted": "Execution Started", + "eventExecutionCompleted": "Execution Completed", + "eventTelegramResultSent": "TG Result", + "eventResolved": "Resolved", + "eventSilenced": "Silenced", + "eventEscalated": "Escalated", + "eventGuardrailBlocked": "Guardrail Blocked", + "eventPreFlightPassed": "Pre-flight Passed", + "eventPreFlightFailed": "Pre-flight Failed", + "eventBackupTriggered": "Backup Triggered", + "eventBackupCompleted": "Backup Completed", + "eventBackupFailed": "Backup Failed", + "eventApprovalEscalated": "Approval Escalated", + "eventChangeApplied": "Change Applied" } } \ No newline at end of file diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index a56312d9..f08ebb1b 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -1138,5 +1138,44 @@ "dispositionManual": "手動處理", "dispositionCold": "冷啟動信任", "autoRateLabel": "自動化率" + }, + "alertOpLogs": { + "title": "告警操作日誌", + "subtitle": "alert_operation_log · 全事件流追蹤", + "refresh": "重新整理", + "totalEvents24h": "24h 總事件", + "allEventTypes": "全部事件類型", + "incidentIdFilter": "Incident ID 篩選...", + "totalCount": "共 {count} 筆", + "colTime": "時間", + "colEventType": "事件類型", + "colIncident": "Incident", + "colActor": "操作者", + "colDetail": "說明", + "colResult": "結果", + "loading": "載入中...", + "noRecords": "無記錄", + "loadError": "載入失敗,請重試", + "pageInfo": "第 {page} / {total} 頁", + "prevPage": "上一頁", + "nextPage": "下一頁", + "eventAlertReceived": "告警收到", + "eventTelegramSent": "TG 通知", + "eventUserAction": "用戶操作", + "eventAutoRepairTriggered": "自動修復", + "eventExecutionStarted": "執行開始", + "eventExecutionCompleted": "執行完成", + "eventTelegramResultSent": "TG 結果", + "eventResolved": "已解決", + "eventSilenced": "已靜音", + "eventEscalated": "已升級", + "eventGuardrailBlocked": "護欄攔截", + "eventPreFlightPassed": "預檢通過", + "eventPreFlightFailed": "預檢失敗", + "eventBackupTriggered": "備份觸發", + "eventBackupCompleted": "備份完成", + "eventBackupFailed": "備份失敗", + "eventApprovalEscalated": "審批升級", + "eventChangeApplied": "變更套用" } } \ No newline at end of file diff --git a/apps/web/src/app/[locale]/alert-operation-logs/page.tsx b/apps/web/src/app/[locale]/alert-operation-logs/page.tsx index b3e23271..826c9e9c 100644 --- a/apps/web/src/app/[locale]/alert-operation-logs/page.tsx +++ b/apps/web/src/app/[locale]/alert-operation-logs/page.tsx @@ -11,11 +11,11 @@ * - event_type 篩選、incident_id 篩選 * - 18 種事件類型顏色標記 * - * i18n: 介面文字直接使用中文(此頁為內部工具,非公開 i18n) - * 變更: 2026-04-09 Claude Sonnet 4.6 Asia/Taipei + * @updated 2026-04-09 Claude Opus 4.6 — i18n 全面改用 useTranslations */ import { useState, useEffect, useCallback, useRef } from 'react' +import { useTranslations } from 'next-intl' import { AppLayout } from '@/components/layout' import { cn } from '@/lib/utils' import { @@ -68,35 +68,36 @@ interface StatsResponse { // Constants // ============================================================================= -const EVENT_TYPE_CONFIG: Record = { - ALERT_RECEIVED: { label: '告警收到', color: 'text-blue-400 bg-blue-900/30', icon: }, - TELEGRAM_SENT: { label: 'TG 通知', color: 'text-sky-400 bg-sky-900/30', icon: }, - USER_ACTION: { label: '用戶操作', color: 'text-purple-400 bg-purple-900/30', icon: }, - AUTO_REPAIR_TRIGGERED: { label: '自動修復', color: 'text-yellow-400 bg-yellow-900/30', icon: }, - EXECUTION_STARTED: { label: '執行開始', color: 'text-orange-400 bg-orange-900/30', icon: }, - EXECUTION_COMPLETED: { label: '執行完成', color: 'text-green-400 bg-green-900/30', icon: }, - TELEGRAM_RESULT_SENT: { label: 'TG 結果', color: 'text-sky-300 bg-sky-900/20', icon: }, - RESOLVED: { label: '已解決', color: 'text-green-500 bg-green-900/40', icon: }, - SILENCED: { label: '已靜音', color: 'text-gray-400 bg-gray-900/30', icon: }, - ESCALATED: { label: '已升級', color: 'text-red-400 bg-red-900/30', icon: }, - GUARDRAIL_BLOCKED: { label: '護欄攔截', color: 'text-red-500 bg-red-900/40', icon: }, - PRE_FLIGHT_PASSED: { label: '預檢通過', color: 'text-green-400 bg-green-900/30', icon: }, - PRE_FLIGHT_FAILED: { label: '預檢失敗', color: 'text-red-400 bg-red-900/30', icon: }, - BACKUP_TRIGGERED: { label: '備份觸發', color: 'text-blue-300 bg-blue-900/20', icon: }, - BACKUP_COMPLETED: { label: '備份完成', color: 'text-green-400 bg-green-900/30', icon: }, - BACKUP_FAILED: { label: '備份失敗', color: 'text-red-400 bg-red-900/30', icon: }, - APPROVAL_ESCALATED: { label: '審批升級', color: 'text-orange-400 bg-orange-900/30', icon: }, - CHANGE_APPLIED: { label: '變更套用', color: 'text-teal-400 bg-teal-900/30', icon: }, +const EVENT_TYPE_STYLE: Record = { + ALERT_RECEIVED: { i18nKey: 'eventAlertReceived', color: 'text-blue-400 bg-blue-900/30', icon: }, + TELEGRAM_SENT: { i18nKey: 'eventTelegramSent', color: 'text-sky-400 bg-sky-900/30', icon: }, + USER_ACTION: { i18nKey: 'eventUserAction', color: 'text-purple-400 bg-purple-900/30', icon: }, + AUTO_REPAIR_TRIGGERED: { i18nKey: 'eventAutoRepairTriggered', color: 'text-yellow-400 bg-yellow-900/30', icon: }, + EXECUTION_STARTED: { i18nKey: 'eventExecutionStarted', color: 'text-orange-400 bg-orange-900/30', icon: }, + EXECUTION_COMPLETED: { i18nKey: 'eventExecutionCompleted', color: 'text-green-400 bg-green-900/30', icon: }, + TELEGRAM_RESULT_SENT: { i18nKey: 'eventTelegramResultSent', color: 'text-sky-300 bg-sky-900/20', icon: }, + RESOLVED: { i18nKey: 'eventResolved', color: 'text-green-500 bg-green-900/40', icon: }, + SILENCED: { i18nKey: 'eventSilenced', color: 'text-gray-400 bg-gray-900/30', icon: }, + ESCALATED: { i18nKey: 'eventEscalated', color: 'text-red-400 bg-red-900/30', icon: }, + GUARDRAIL_BLOCKED: { i18nKey: 'eventGuardrailBlocked', color: 'text-red-500 bg-red-900/40', icon: }, + PRE_FLIGHT_PASSED: { i18nKey: 'eventPreFlightPassed', color: 'text-green-400 bg-green-900/30', icon: }, + PRE_FLIGHT_FAILED: { i18nKey: 'eventPreFlightFailed', color: 'text-red-400 bg-red-900/30', icon: }, + BACKUP_TRIGGERED: { i18nKey: 'eventBackupTriggered', color: 'text-blue-300 bg-blue-900/20', icon: }, + BACKUP_COMPLETED: { i18nKey: 'eventBackupCompleted', color: 'text-green-400 bg-green-900/30', icon: }, + BACKUP_FAILED: { i18nKey: 'eventBackupFailed', color: 'text-red-400 bg-red-900/30', icon: }, + APPROVAL_ESCALATED: { i18nKey: 'eventApprovalEscalated', color: 'text-orange-400 bg-orange-900/30', icon: }, + CHANGE_APPLIED: { i18nKey: 'eventChangeApplied', color: 'text-teal-400 bg-teal-900/30', icon: }, } const PAGE_SIZE = 50 -const ALL_EVENT_TYPES = Object.keys(EVENT_TYPE_CONFIG) +const ALL_EVENT_TYPES = Object.keys(EVENT_TYPE_STYLE) // ============================================================================= // Component // ============================================================================= -export default function AlertOperationLogsPage() { +export default function AlertOperationLogsPage({ params }: { params: { locale: string } }) { + const t = useTranslations('alertOpLogs') const [logs, setLogs] = useState([]) const [total, setTotal] = useState(0) const [page, setPage] = useState(1) @@ -109,6 +110,20 @@ export default function AlertOperationLogsPage() { const apiBase = process.env.NEXT_PUBLIC_API_URL ?? '' + const getEventLabel = (et: string) => { + const style = EVENT_TYPE_STYLE[et] + return style ? t(style.i18nKey as any) : et + } + + const getEventConfig = (et: string) => { + const style = EVENT_TYPE_STYLE[et] + return { + label: style ? t(style.i18nKey as any) : et, + color: style?.color ?? 'text-gray-400 bg-gray-900/30', + icon: style?.icon ?? , + } + } + const fetchLogs = useCallback(async (pageNum: number, evType: string, incId: string) => { abortRef.current?.abort() abortRef.current = new AbortController() @@ -132,12 +147,12 @@ export default function AlertOperationLogsPage() { setTotal(data.total) } catch (err) { if ((err as Error).name !== 'AbortError') { - setError('載入失敗,請重試') + setError(t('loadError')) } } finally { setLoading(false) } - }, [apiBase]) + }, [apiBase, t]) const fetchStats = useCallback(async () => { try { @@ -162,25 +177,22 @@ export default function AlertOperationLogsPage() { return d.toLocaleString('zh-TW', { timeZone: 'Asia/Taipei', hour12: false }) } - const getEventConfig = (et: string) => - EVENT_TYPE_CONFIG[et] ?? { label: et, color: 'text-gray-400 bg-gray-900/30', icon: } - return ( - +
{/* Header */}
-

告警操作日誌

-

alert_operation_log · 全事件流追蹤

+

{t('title')}

+

{t('subtitle')}

@@ -188,12 +200,12 @@ export default function AlertOperationLogsPage() { {stats && (
-
24h 總事件
+
{t('totalEvents24h')}
{stats.total.toLocaleString()}
{['ALERT_RECEIVED', 'GUARDRAIL_BLOCKED', 'AUTO_REPAIR_TRIGGERED', 'RESOLVED'].map(et => (
-
{getEventConfig(et).label}
+
{getEventLabel(et)}
{(stats.by_event_type[et] ?? 0).toLocaleString()}
@@ -209,20 +221,20 @@ export default function AlertOperationLogsPage() { onChange={e => { setFilterEventType(e.target.value); setPage(1) }} className="px-3 py-1.5 text-xs font-mono bg-gray-900 border border-gray-700 text-gray-300 rounded focus:border-gray-500 outline-none" > - + {ALL_EVENT_TYPES.map(et => ( - + ))} { setFilterIncidentId(e.target.value); setPage(1) }} className="px-3 py-1.5 text-xs font-mono bg-gray-900 border border-gray-700 text-gray-300 rounded focus:border-gray-500 outline-none w-52" /> - 共 {total.toLocaleString()} 筆 + {t('totalCount', { count: total.toLocaleString() })}
@@ -239,20 +251,20 @@ export default function AlertOperationLogsPage() { - - - - - - + + + + + + {loading && ( - + )} {!loading && logs.length === 0 && ( - + )} {!loading && logs.map(log => { const cfg = getEventConfig(log.event_type) @@ -294,21 +306,21 @@ export default function AlertOperationLogsPage() { {/* Pagination */} {totalPages > 1 && (
- 第 {page} / {totalPages} 頁 + {t('pageInfo', { page, total: totalPages })}
diff --git a/apps/web/src/app/[locale]/classic/page.tsx b/apps/web/src/app/[locale]/classic/page.tsx index 095e0b0d..4687f6da 100644 --- a/apps/web/src/app/[locale]/classic/page.tsx +++ b/apps/web/src/app/[locale]/classic/page.tsx @@ -21,6 +21,7 @@ import React from 'react' import { useTranslations } from 'next-intl' import { useState, useEffect } from 'react' +import { BarChart3, Flame, Telescope, FlaskConical, Activity, GitBranch } from 'lucide-react' import { useGlobalPulseMetrics } from '@/hooks/useGlobalPulseMetrics' import { useIncidents } from '@/hooks/useIncidents' import { useHosts, useDashboardStore } from '@/stores/dashboard.store' @@ -93,14 +94,14 @@ const TOOL_ACCENT_COLOR: Record = { Gitea: '#22C55E', } -// 圖示 emoji -const TOOL_EMOJI: Record = { - Grafana: '📊', - Prometheus: '🔥', - Sentry: '🔭', - Langfuse: '🧪', - SigNoz: '🔭', - Gitea: '🐙', +// 圖示 Lucide icon (feedback_no_emoji_use_icons.md) +const TOOL_ICON: Record = { + Grafana: , + Prometheus: , + Sentry: , + Langfuse: , + SigNoz: , + Gitea: , } function MonitoringTools() { @@ -136,7 +137,7 @@ function MonitoringTools() { const statusColor = isUp ? (hasFiring ? '#F59E0B' : '#22C55E') : '#cc2200' const statusText = isUp ? (hasFiring ? `${tool.firing_count} ${tDash('monitoringStatus.firing')}` : tDash('monitoringStatus.up')) : tDash('monitoringStatus.down') const accentColor = TOOL_ACCENT_COLOR[tool.name] ?? '#b0ad9f' - const emoji = TOOL_EMOJI[tool.name] ?? '🔧' + const icon = TOOL_ICON[tool.name] ?? const link = tool.url ?? '#' const timeStr = (() => { try { return new Date(tool.checked_at).toLocaleTimeString('zh-TW', { timeZone: 'Asia/Taipei', hour: '2-digit', minute: '2-digit' }) } @@ -174,7 +175,7 @@ function MonitoringTools() { {/* 主行 */}
- {emoji} + {icon}
{tool.name} @@ -192,7 +193,7 @@ function MonitoringTools() { fontSize: 10, padding: '1px 7px', borderRadius: 8, fontWeight: 600, background: 'rgba(245,158,11,0.12)', color: '#F59E0B', }}> - {tool.firing_count} 告警 + {tDash('alertBadge', { count: tool.firing_count })} ) : ( - 0 告警 + {tDash('alertBadgeZero')} )}
@@ -502,7 +503,7 @@ export default function Home({ params }: { params: { locale: string } }) { label: tDashboard('pendingApprovals'), value: pendingApprovals ?? '--', sub: hasPendingApprovals ? undefined : tDashboard('stable'), - badge: hasPendingApprovals ? { text: `⏳ 等待確認`, color: '#F59E0B', bg: 'rgba(245,158,11,0.08)' } : undefined, + badge: hasPendingApprovals ? { text: tDashboard('awaitingConfirm'), color: '#F59E0B', bg: 'rgba(245,158,11,0.08)' } : undefined, valueColor: hasPendingApprovals ? '#F59E0B' : undefined, }, { diff --git a/apps/web/src/app/[locale]/knowledge/page.tsx b/apps/web/src/app/[locale]/knowledge/page.tsx index 4770f8fc..93bd06b8 100644 --- a/apps/web/src/app/[locale]/knowledge/page.tsx +++ b/apps/web/src/app/[locale]/knowledge/page.tsx @@ -8,14 +8,16 @@ */ import { lazy, Suspense } from 'react' +import { useTranslations } from 'next-intl' import { AppLayout } from '@/components/layout' const KnowledgeBaseContent = lazy(() => import('@/app/[locale]/knowledge-base/page')) export default function KnowledgePage({ params }: { params: { locale: string } }) { + const t = useTranslations('common') return ( - 載入中...
}> + {t('loading')}
}>
時間事件類型Incident操作者說明結果{t('colTime')}{t('colEventType')}{t('colIncident')}{t('colActor')}{t('colDetail')}{t('colResult')}
載入中...
{t('loading')}
無記錄
{t('noRecords')}