diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index fe32280ea..778d25954 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -549,7 +549,7 @@ "loading": "Loading...", "refresh": "Refresh", "sentryDashboard": "Sentry Dashboard", - "footerInfo": "Data from Sentry Self-Hosted (192.168.0.110:9000) | AI Analysis: OpenClaw | Auto-refresh: 60s", + "footerInfo": "Data from Sentry Self-Hosted | AI Analysis: OpenClaw | Auto-refresh: 60s", "timeAgo": { "minutes": "{count}m ago", "hours": "{count}h ago", @@ -575,6 +575,127 @@ } }, "alerts": { - "autoRefresh": "Auto-refresh every {seconds}s" + "autoRefresh": "Auto-refresh every {seconds}s", + "incidentCount": "{count, plural, one {# incident} other {# incidents}}" + }, + "navSection": { + "aiCore": "AI Core", + "monitoring": "Monitoring & Security", + "ops": "Operations", + "knowledge": "Knowledge & Tools" + }, + "sidebar": { + "expand": "Expand sidebar", + "collapse": "Collapse sidebar" + }, + "settings": { + "title": "Settings", + "appearance": "Appearance", + "appearanceDesc": "Theme, fonts, density", + "appearanceSettings": "Appearance Settings", + "language": "Language", + "languageDesc": "Interface language", + "languageSettings": "Language Settings", + "notify": "Notifications", + "notifyDesc": "Telegram / browser notification preferences", + "notifySettings": "Notification Settings", + "system": "System Info", + "systemDesc": "Version & API endpoints", + "systemSettings": "System Info", + "compactMode": "Compact Mode", + "compactModeDesc": "Reduce spacing, show more content", + "designSystem": "Design System", + "designSystemValue": "Nothing.tech Pure White Industrial (fixed)", + "themeColor": "Theme Color", + "themeColorValue": "OpenClaw Blue + Orange Accent (fixed)", + "browserNotify": "Browser Push Notifications", + "browserNotifyDesc": "Show system notification on new Incident", + "p0Only": "P0 CRITICAL Only", + "p0OnlyDesc": "Filter low-severity alerts to reduce noise", + "telegramNotify": "Telegram Notifications", + "telegramNotifyDesc": "Pushed by OpenClaw Bot (requires backend config)", + "backendConfig": "Backend Config", + "frontendVersion": "Frontend Version", + "apiEndpoint": "API Endpoint", + "notConfigured": "(not configured)", + "phase": "Phase", + "save": "Save Settings", + "saved": "Saved", + "zhTW": "繁體中文", + "zhTWSub": "Traditional Chinese", + "en": "English", + "enSub": "English (US)" + }, + "autoRepair": { + "subtitle": "High-quality Playbook auto-execution · Risk ≤ MEDIUM · Success ≥ 95%", + "approvedPlaybooks": "Approved Playbooks", + "highQualityPlaybooks": "High-Quality Playbooks", + "highQualitySub": "Success ≥ 95% · Runs ≥ 10", + "totalExecutions": "Total Executions", + "overallSuccessRate": "Overall Success Rate", + "eligible": "✓ Auto-repair available", + "notEligible": "No high-quality Playbook yet", + "ready": "Auto-repair Ready", + "notReady": "Auto-repair Not Ready", + "readyDesc": "{count} high-quality Playbooks available", + "notReadyDesc": "Need at least 1 high-quality Playbook (success ≥ 95%, runs ≥ 10)", + "incidentEval": "Active Incident Evaluation (P1/P2)", + "canAutoRepair": "Can auto-repair", + "notEligibleShort": "Not eligible", + "riskLevel": "Risk Level", + "successRate": "Success Rate", + "execCount": "Executions", + "decisionReason": "Decision Reason", + "execSuccess": "Success ({ms}ms)", + "execFailed": "Failed: {error}", + "executing": "Executing...", + "execute": "Execute Repair", + "noEligible": "No incidents eligible for auto-repair" + }, + "openclawPanel": { + "patrolling": "[AGENT] patrolling...", + "intercepting": "[SYS] Intercepting anomaly...", + "analyzing": "[SYS] Analyzing blast radius...", + "generating": "[SYS] Generating proposed action...", + "complete": "[SYS] Analysis complete" + }, + "knowledgeBase": { + "title": "Knowledge Base", + "searchPlaceholder": "Search knowledge entries...", + "allCategories": "All", + "noResults": "No knowledge entries found", + "createEntry": "New Entry", + "viewCount": "views", + "relatedPlaybook": "Related Playbook", + "relatedIncident": "Related Incident", + "approve": "Approve", + "archive": "Archive", + "status": { + "draft": "Draft", + "review": "In Review", + "approved": "Approved", + "archived": "Archived" + }, + "type": { + "incident_case": "Incident Case", + "runbook": "Runbook", + "best_practice": "Best Practice", + "postmortem": "Postmortem" + }, + "source": { + "ai_extracted": "AI Extracted", + "human": "Manual" + }, + "category": { + "infrastructure": "Infrastructure", + "application": "Application", + "ai_system": "AI System", + "security": "Security / Compliance" + }, + "filterByType": "Filter by type", + "filterByStatus": "Filter by status", + "entries": "entries", + "empty": "No knowledge entries yet", + "emptyDescription": "Entries will be auto-extracted from incidents, or you can create them manually" } } \ No newline at end of file diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 19bc59cfe..07e946f21 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -550,7 +550,7 @@ "loading": "載入中...", "refresh": "重新整理", "sentryDashboard": "Sentry 儀表板", - "footerInfo": "資料來源: Sentry Self-Hosted (192.168.0.110:9000) | AI 分析: OpenClaw | 自動刷新: 60 秒", + "footerInfo": "資料來源: Sentry Self-Hosted | AI 分析: OpenClaw | 自動刷新: 60 秒", "timeAgo": { "minutes": "{count} 分鐘前", "hours": "{count} 小時前", @@ -576,6 +576,127 @@ } }, "alerts": { - "autoRefresh": "每 {seconds} 秒自動刷新" + "autoRefresh": "每 {seconds} 秒自動刷新", + "incidentCount": "{count, plural, one {# 個事件} other {# 個事件}}" + }, + "navSection": { + "aiCore": "AI 核心", + "monitoring": "監控與安全", + "ops": "運維管理", + "knowledge": "知識與工具" + }, + "sidebar": { + "expand": "展開側欄", + "collapse": "收合側欄" + }, + "settings": { + "title": "系統設定", + "appearance": "外觀", + "appearanceDesc": "主題、字體、密度", + "appearanceSettings": "外觀設定", + "language": "語言", + "languageDesc": "介面語言設定", + "languageSettings": "語言設定", + "notify": "通知", + "notifyDesc": "Telegram / 瀏覽器通知偏好", + "notifySettings": "通知設定", + "system": "系統資訊", + "systemDesc": "版本與 API 端點", + "systemSettings": "系統資訊", + "compactMode": "緊湊模式", + "compactModeDesc": "減少各元件間距,顯示更多資訊", + "designSystem": "設計系統", + "designSystemValue": "Nothing.tech 純白工業風(固定)", + "themeColor": "主題色", + "themeColorValue": "OpenClaw Blue + 橘紅 Accent(固定)", + "browserNotify": "瀏覽器推播通知", + "browserNotifyDesc": "新 Incident 時顯示系統通知", + "p0Only": "僅 P0 CRITICAL 通知", + "p0OnlyDesc": "過濾低嚴重度告警,減少噪音", + "telegramNotify": "Telegram 通知", + "telegramNotifyDesc": "由 OpenClaw Bot 推送(需後端設定)", + "backendConfig": "後端設定", + "frontendVersion": "前端版本", + "apiEndpoint": "API 端點", + "notConfigured": "(未設定)", + "phase": "Phase", + "save": "儲存設定", + "saved": "已儲存", + "zhTW": "繁體中文", + "zhTWSub": "Traditional Chinese", + "en": "English", + "enSub": "English (US)" + }, + "autoRepair": { + "subtitle": "高品質 Playbook 自動執行 · 風險 ≤ MEDIUM · 成功率 ≥ 95%", + "approvedPlaybooks": "已批准 Playbooks", + "highQualityPlaybooks": "高品質 Playbooks", + "highQualitySub": "成功率 ≥ 95% · 執行 ≥ 10 次", + "totalExecutions": "總執行次數", + "overallSuccessRate": "整體成功率", + "eligible": "✓ 可啟用自動修復", + "notEligible": "尚無高品質 Playbook", + "ready": "自動修復已就緒", + "notReady": "自動修復未就緒", + "readyDesc": "{count} 個高品質 Playbook 可用", + "notReadyDesc": "需要至少 1 個高品質 Playbook(成功率 ≥ 95%、執行 ≥ 10 次)", + "incidentEval": "活躍 Incident 評估(P1/P2)", + "canAutoRepair": "可自動修復", + "notEligibleShort": "不符合條件", + "riskLevel": "風險等級", + "successRate": "成功率", + "execCount": "執行次數", + "decisionReason": "決策原因", + "execSuccess": "執行成功 ({ms}ms)", + "execFailed": "執行失敗: {error}", + "executing": "執行中...", + "execute": "執行修復", + "noEligible": "目前無符合自動修復條件的 Incident" + }, + "openclawPanel": { + "patrolling": "[AGENT] patrolling...", + "intercepting": "[SYS] Intercepting anomaly...", + "analyzing": "[SYS] Analyzing blast radius...", + "generating": "[SYS] Generating proposed action...", + "complete": "[SYS] Analysis complete" + }, + "knowledgeBase": { + "title": "知識殿堂", + "searchPlaceholder": "搜尋知識條目...", + "allCategories": "全部", + "noResults": "找不到相關知識條目", + "createEntry": "新增條目", + "viewCount": "瀏覽", + "relatedPlaybook": "相關 Playbook", + "relatedIncident": "相關事件", + "approve": "審核通過", + "archive": "封存", + "status": { + "draft": "草稿", + "review": "審核中", + "approved": "已批准", + "archived": "已封存" + }, + "type": { + "incident_case": "案例分析", + "runbook": "操作手冊", + "best_practice": "最佳實踐", + "postmortem": "事後分析" + }, + "source": { + "ai_extracted": "AI 萃取", + "human": "人工建立" + }, + "category": { + "infrastructure": "基礎設施", + "application": "應用層", + "ai_system": "AI 系統", + "security": "安全 / 合規" + }, + "filterByType": "篩選類型", + "filterByStatus": "篩選狀態", + "entries": "筆", + "empty": "尚未建立任何知識條目", + "emptyDescription": "知識庫將自動從 Incident 中萃取案例,你也可以手動新增" } } \ No newline at end of file diff --git a/apps/web/src/app/[locale]/knowledge-base/page.tsx b/apps/web/src/app/[locale]/knowledge-base/page.tsx index 5b00bd5bc..f1f413a9e 100644 --- a/apps/web/src/app/[locale]/knowledge-base/page.tsx +++ b/apps/web/src/app/[locale]/knowledge-base/page.tsx @@ -3,30 +3,394 @@ /** * Knowledge Base Page - 知識殿堂 * ============================== - * Phase 7.0: 防禦性路由佔位 - * Wave 3: i18n 國際化支援 (2026-03-31) + * KB Phase 1: 左側分類導航 + 搜尋 + 知識條目列表 * - * Nothing.tech 空態設計: - * - 畫面正中央極簡文字 - * - 等寬字體 + 淺灰色 + * 建立時間: 2026-04-02 (台北時區) + * 建立者: Claude Code (Knowledge Base Phase 1) + * + * API: /api/v1/knowledge */ +import { useState, useEffect, useCallback } from 'react' import { AppLayout } from '@/components/layout' import { useTranslations } from 'next-intl' +import { cn } from '@/lib/utils' +import { + Search, BookOpen, FileText, Shield, Cpu, + Server, Eye, Bot, ChevronRight, Plus, +} from 'lucide-react' + +// ============================================================================= +// Types +// ============================================================================= + +interface KnowledgeEntry { + id: string + title: string + content: string + entry_type: 'incident_case' | 'runbook' | 'best_practice' | 'postmortem' + category: string + tags: string[] + source: 'ai_extracted' | 'human' + status: 'draft' | 'review' | 'approved' | 'archived' + related_incident_id: string | null + related_playbook_id: string | null + view_count: number + created_by: string | null + created_at: string + updated_at: string +} + +interface CategoryCount { + category: string + count: number +} + +interface ListResponse { + items: KnowledgeEntry[] + total: number + categories: CategoryCount[] +} + +// ============================================================================= +// Category Config +// ============================================================================= + +const CATEGORY_ICONS: Record = { + infrastructure: , + application: , + ai_system: , + security: , +} + +const CATEGORIES = ['infrastructure', 'application', 'ai_system', 'security'] as const + +// ============================================================================= +// Type Badge Colors +// ============================================================================= + +const TYPE_COLORS: Record = { + incident_case: 'bg-status-warning/10 text-status-warning border-status-warning/20', + runbook: 'bg-claw-blue/10 text-claw-blue border-claw-blue/20', + best_practice: 'bg-status-healthy/10 text-status-healthy border-status-healthy/20', + postmortem: 'bg-status-critical/10 text-status-critical border-status-critical/20', +} + +const STATUS_COLORS: Record = { + draft: 'bg-nothing-gray-100 text-nothing-gray-500', + review: 'bg-status-warning/10 text-status-warning', + approved: 'bg-status-healthy/10 text-status-healthy', +} + +// ============================================================================= +// Component +// ============================================================================= export default function KnowledgeBasePage({ params, }: { params: { locale: string } }) { - const t = useTranslations('placeholder') + const t = useTranslations('knowledgeBase') + const tCommon = useTranslations('common') + + const [entries, setEntries] = useState([]) + const [total, setTotal] = useState(0) + const [categories, setCategories] = useState([]) + const [loading, setLoading] = useState(true) + + // Filters + const [selectedCategory, setSelectedCategory] = useState(null) + const [selectedType, setSelectedType] = useState(null) + const [searchQuery, setSearchQuery] = useState('') + const [selectedEntry, setSelectedEntry] = useState(null) + + const fetchEntries = useCallback(async () => { + setLoading(true) + try { + const params = new URLSearchParams() + if (selectedCategory) params.set('category', selectedCategory) + if (selectedType) params.set('entry_type', selectedType) + if (searchQuery) params.set('q', searchQuery) + params.set('limit', '50') + + const res = await fetch(`/api/v1/knowledge?${params}`) + if (res.ok) { + const data: ListResponse = await res.json() + setEntries(data.items) + setTotal(data.total) + setCategories(data.categories) + } + } catch (err) { + console.error('Failed to fetch knowledge entries', err) + } finally { + setLoading(false) + } + }, [selectedCategory, selectedType, searchQuery]) + + useEffect(() => { + fetchEntries() + }, [fetchEntries]) + + const totalCount = categories.reduce((sum, c) => sum + c.count, 0) return ( -
-

- {t('knowledgeBase')} -

+
+ + {/* 左側分類導航 */} + + + {/* 主區域 */} +
+ + {/* 頂部搜尋 + 篩選 */} +
+ {/* Search */} +
+ + setSearchQuery(e.target.value)} + placeholder={t('searchPlaceholder')} + className="w-full pl-8 pr-3 py-1.5 text-sm font-body bg-nothing-gray-50 border border-nothing-gray-200 rounded-md focus:outline-none focus:border-claw-blue/50 transition-colors" + /> +
+ + {/* Type filter */} + + + {/* Count */} + + {total} {t('entries')} + + + {/* Create button */} + +
+ + {/* 結果列表 */} +
+ {loading ? ( +
+ {tCommon('loading')} +
+ ) : entries.length === 0 ? ( +
+ +

{t('empty')}

+

{t('emptyDescription')}

+
+ ) : ( +
+ {entries.map(entry => ( + + ))} +
+ )} +
+
+ + {/* 右側詳情 Panel */} + {selectedEntry && ( + + )}
)