diff --git a/apps/api/src/services/proposal_service.py b/apps/api/src/services/proposal_service.py index 9a3e3120..897b2a75 100644 --- a/apps/api/src/services/proposal_service.py +++ b/apps/api/src/services/proposal_service.py @@ -39,10 +39,11 @@ from src.models.incident import ( IncidentStatus, Severity, ) +from src.core.redis_client import get_redis from src.services.approval_db import get_approval_service from src.services.incident_engine import get_incident_engine from src.services.incident_memory import get_incident_memory -from src.services.incident_service import get_incident_service +from src.services.incident_service import INCIDENT_KEY_PREFIX, get_incident_service from src.services.openclaw import get_openclaw from src.services.trust_engine import normalize_action_pattern, trust_engine from src.utils.incident_converter import local_to_brain @@ -179,9 +180,11 @@ class ProposalService: # LLM 提供的 risk_level 轉換 llm_risk = llm_proposal.get("risk_level", "medium") + # 2026-04-09 Claude Sonnet 4.6: P1-2 QA修復 — 補 "high" 鍵,防止 LLM 自由文字回傳 high 時降為 MEDIUM risk_map = { "low": ApprovalRiskLevel.LOW, "medium": ApprovalRiskLevel.MEDIUM, + "high": ApprovalRiskLevel.HIGH, "critical": ApprovalRiskLevel.CRITICAL, } base_risk = risk_map.get(llm_risk, ApprovalRiskLevel.MEDIUM) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index dbaab86d..fbb8c885 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -198,7 +198,13 @@ "dashboardConnecting": "Dashboard API connecting...", "alertBadge": "{count} alerts", "alertBadgeZero": "0 alerts", - "awaitingConfirm": "⏳ Awaiting Confirmation" + "awaitingConfirm": "Awaiting Confirmation", + "viewAllAlerts": "View All Alerts", + "viewAllAuth": "View All Authorizations", + "viewAllReport": "View Full Report", + "aiModelStatus": "AI Model Status", + "loading": "Loading...", + "trendUp": "↑{pct}%" }, "openclaw": { "name": "OpenClaw", @@ -822,8 +828,15 @@ "groupK3s": "K3s Cluster", "groupAiData": "AI/Data Center", "allHealthy": "All Healthy", + "allReachable": "All Reachable", "warning": "Warning", - "healthy": "Healthy" + "healthy": "Healthy", + "investigating": "Investigating", + "groupExternal": "External Services", + "hostDevops": "DevOps Vault", + "hostAiData": "AI+Web Hub", + "hostK3sMaster": "K3s Master", + "hostK3sWorker": "K3s Worker" }, "notifications": { "title": "Notifications", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index f08ebb1b..7057996a 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -199,7 +199,13 @@ "dashboardConnecting": "Dashboard API 連線中", "alertBadge": "{count} 告警", "alertBadgeZero": "0 告警", - "awaitingConfirm": "⏳ 等待確認" + "awaitingConfirm": "等待確認", + "viewAllAlerts": "查看全部告警", + "viewAllAuth": "查看全部授權", + "viewAllReport": "查看完整報表", + "aiModelStatus": "AI 模型狀態", + "loading": "載入中...", + "trendUp": "↑{pct}%" }, "openclaw": { "name": "OpenClaw", @@ -823,8 +829,15 @@ "groupK3s": "K3s 叢集", "groupAiData": "AI/數據中心", "allHealthy": "全部健康", + "allReachable": "全部可達", "warning": "異常", - "healthy": "健康" + "healthy": "健康", + "investigating": "調查中", + "groupExternal": "外部服務", + "hostDevops": "DevOps 金庫", + "hostAiData": "AI+Web 中心", + "hostK3sMaster": "K3s Master", + "hostK3sWorker": "K3s Worker" }, "notifications": { "title": "通知", diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index f443aab4..d84e9af5 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -330,7 +330,7 @@ function MonitoringTools() { // S10: 監控工具元資訊 (設計稿 3×2 精簡版) const TOOL_META: Record = { - SigNoz: 'Traces · Logs', Grafana: '3 Dashboards', Prometheus: `${tools.length > 0 ? '23' : '--'} targets`, + SigNoz: 'Traces · Logs', Grafana: '3 Dashboards', Prometheus: '-- targets', Langfuse: 'LLMOps', Sentry: '2 Projects', Gitea: 'CI/CD', } @@ -455,6 +455,7 @@ function buildHostInfo( export default function Home({ params }: { params: { locale: string } }) { const tDashboard = useTranslations('dashboard') const tCommon = useTranslations('common') + const tTopo = useTranslations('topology') const locale = params.locale const hosts = useHosts() @@ -672,7 +673,7 @@ export default function Home({ params }: { params: { locale: string } }) {
{tDashboard('autoRemediationRate')}
{autoRemediationRate} - {autoRemediationPct > 0 && ↑5%} + {autoRemediationPct > 0 && {tDashboard('trendUp', { pct: autoRemediationPct })}}
@@ -846,10 +847,10 @@ export default function Home({ params }: { params: { locale: string } }) { {infraView === 'topo' && (
{[ - { name: '🏗️ 基礎設施 (.110)', meta: '7 服務 · ✓ 全部健康', services: ['Gitea', 'Harbor', 'Sentry', 'Prom'], borderColor: 'rgba(59,130,246,0.2)', bg: 'rgba(59,130,246,0.01)' }, - { name: '🧠 AI/數據 (.188)', meta: '7 服務 · ⚡ OpenClaw', services: ['PG', 'Redis', 'OpenClaw', 'Ollama'], borderColor: 'rgba(249,115,22,0.25)', bg: 'rgba(249,115,22,0.01)' }, - { name: '☸️ K3s 叢集', meta: `5 服務 · ${incidentCount > 0 ? '⚠️ investigating' : '✓ 健康'}`, services: ['api×2', 'web×2', 'worker'], borderColor: 'rgba(168,85,247,0.25)', bg: 'rgba(168,85,247,0.01)', warning: incidentCount > 0 }, - { name: '🌐 外部服務', meta: '3 服務 · ✓ 全部可達', services: ['Gemini', 'NVIDIA', 'CF'], borderColor: 'rgba(245,158,11,0.2)', bg: 'rgba(245,158,11,0.01)' }, + { name: `${tTopo('groupInfra')} (.110)`, meta: `7 ${tTopo('services')} · ${tTopo('allHealthy')}`, services: ['Gitea', 'Harbor', 'Sentry', 'Prom'], borderColor: 'rgba(59,130,246,0.2)', bg: 'rgba(59,130,246,0.01)' }, + { name: `${tTopo('groupAiData')} (.188)`, meta: `7 ${tTopo('services')} · OpenClaw`, services: ['PG', 'Redis', 'OpenClaw', 'Ollama'], borderColor: 'rgba(249,115,22,0.25)', bg: 'rgba(249,115,22,0.01)' }, + { name: tTopo('groupK3s'), meta: `5 ${tTopo('services')} · ${incidentCount > 0 ? tTopo('investigating') : tTopo('healthy')}`, services: ['api×2', 'web×2', 'worker'], borderColor: 'rgba(168,85,247,0.25)', bg: 'rgba(168,85,247,0.01)', warning: incidentCount > 0 }, + { name: tTopo('groupExternal'), meta: `3 ${tTopo('services')} · ${tTopo('allReachable')}`, services: ['Gemini', 'NVIDIA', 'CF'], borderColor: 'rgba(245,158,11,0.2)', bg: 'rgba(245,158,11,0.01)' }, ].map(g => (
{[ - { name: 'DevOps 金庫', ip: '192.168.0.110', cpu: 35, ram: 55 }, - { name: 'AI+Web 中心', ip: '192.168.0.188', cpu: 67, ram: 72 }, - { name: 'K3s Master', ip: '192.168.0.120', cpu: 45, ram: 60 }, - { name: 'K3s Worker', ip: '192.168.0.121', cpu: null as number | null, ram: null as number | null }, + { name: tTopo('hostDevops'), ip: '192.168.0.110', cpu: 35, ram: 55 }, + { name: tTopo('hostAiData'), ip: '192.168.0.188', cpu: 67, ram: 72 }, + { name: tTopo('hostK3sMaster'), ip: '192.168.0.120', cpu: 45, ram: 60 }, + { name: tTopo('hostK3sWorker'), ip: '192.168.0.121', cpu: null as number | null, ram: null as number | null }, ].map(h => { // 嘗試從 API 取得真實數據 const apiHost = hosts.find(ah => ah.ip === h.ip) diff --git a/apps/web/src/components/layout/page-tabs.tsx b/apps/web/src/components/layout/page-tabs.tsx index acfdcb6b..ed4ff8f6 100644 --- a/apps/web/src/components/layout/page-tabs.tsx +++ b/apps/web/src/components/layout/page-tabs.tsx @@ -24,6 +24,7 @@ import { useState, useCallback, useMemo, Suspense, type ReactNode } from 'react' import { useSearchParams, useRouter, usePathname } from 'next/navigation' +import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' // ============================================================================= @@ -55,6 +56,7 @@ export interface PageTabsProps { // ============================================================================= function TabSkeleton() { + const t = useTranslations('dashboard') return (