From a748a082807d3b5f0349876bba2c381cef7cc30d Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 3 Jun 2026 01:01:11 +0800 Subject: [PATCH] fix(web): prevent knowledge category key leaks --- apps/web/messages/en.json | 19 +++- apps/web/messages/zh-TW.json | 19 +++- .../src/app/[locale]/knowledge-base/page.tsx | 90 ++++++++++++++++--- 3 files changed, 113 insertions(+), 15 deletions(-) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index c3efe56f..5569b99c 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -1415,7 +1415,8 @@ "metricApproved": "已批准", "scopeFiltered": "目前篩選", "scopeCurrent": "已載入", - "categoryDistribution": "分類分佈" + "categoryDistribution": "分類分佈", + "categoryOther": "其他分類" }, "noResults": "找不到相關知識條目", "createEntry": "新增條目", @@ -1446,10 +1447,24 @@ "human": "人工建立" }, "category": { + "AI治理": "AI 治理", + "alert_handling": "告警處理", "infrastructure": "基礎設施", "application": "應用層", "ai_system": "AI 系統", - "security": "安全 / 合規" + "security": "安全 / 合規", + "general": "通用", + "host_resource": "主機資源", + "database": "資料庫", + "auto_repair": "自動修復", + "postmortem": "事後分析", + "external_site": "外部網站", + "backup_failure": "備份失敗", + "kubernetes": "Kubernetes", + "flywheel_health": "飛輪健康", + "ssl_cert": "SSL 憑證", + "devops_tool": "DevOps 工具", + "incident_postmortem": "事故檢討" }, "filterByType": "篩選類型", "filterByStatus": "篩選狀態", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index c3efe56f..5569b99c 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -1415,7 +1415,8 @@ "metricApproved": "已批准", "scopeFiltered": "目前篩選", "scopeCurrent": "已載入", - "categoryDistribution": "分類分佈" + "categoryDistribution": "分類分佈", + "categoryOther": "其他分類" }, "noResults": "找不到相關知識條目", "createEntry": "新增條目", @@ -1446,10 +1447,24 @@ "human": "人工建立" }, "category": { + "AI治理": "AI 治理", + "alert_handling": "告警處理", "infrastructure": "基礎設施", "application": "應用層", "ai_system": "AI 系統", - "security": "安全 / 合規" + "security": "安全 / 合規", + "general": "通用", + "host_resource": "主機資源", + "database": "資料庫", + "auto_repair": "自動修復", + "postmortem": "事後分析", + "external_site": "外部網站", + "backup_failure": "備份失敗", + "kubernetes": "Kubernetes", + "flywheel_health": "飛輪健康", + "ssl_cert": "SSL 憑證", + "devops_tool": "DevOps 工具", + "incident_postmortem": "事故檢討" }, "filterByType": "篩選類型", "filterByStatus": "篩選狀態", diff --git a/apps/web/src/app/[locale]/knowledge-base/page.tsx b/apps/web/src/app/[locale]/knowledge-base/page.tsx index 80f2e3e7..3f2266fe 100644 --- a/apps/web/src/app/[locale]/knowledge-base/page.tsx +++ b/apps/web/src/app/[locale]/knowledge-base/page.tsx @@ -67,6 +67,28 @@ const CATEGORY_ICONS: Record = { } const CATEGORIES = ['infrastructure', 'application', 'ai_system', 'security'] as const +const KNOWN_CATEGORY_KEYS = new Set([ + 'AI治理', + 'alert_handling', + 'application', + 'ai_system', + 'auto_repair', + 'backup_failure', + 'database', + 'devops_tool', + 'external_site', + 'flywheel_health', + 'general', + 'host_resource', + 'incident_postmortem', + 'infrastructure', + 'kubernetes', + 'postmortem', + 'security', + 'ssl_cert', +]) + +const CATEGORY_OVERVIEW_LIMIT = 8 // ============================================================================= // Type Badge Colors @@ -89,10 +111,28 @@ const STATUS_COLORS: Record = { } const CATEGORY_BAR_COLORS: Record = { + AI治理: 'bg-claw-blue', + alert_handling: 'bg-status-warning', infrastructure: 'bg-claw-blue', application: 'bg-status-healthy', ai_system: 'bg-purple-500', security: 'bg-status-warning', + general: 'bg-nothing-gray-400', + postmortem: 'bg-status-critical', + __other__: 'bg-nothing-gray-300', +} + +const humanizeCategory = (category: string) => { + if (!category.includes('_')) return category + return category + .split('_') + .filter(Boolean) + .map(part => { + const upper = part.toUpperCase() + if (['AI', 'DB', 'SSL', 'SLO', 'API'].includes(upper)) return upper + return `${part.charAt(0).toUpperCase()}${part.slice(1)}` + }) + .join(' ') } // ============================================================================= @@ -227,6 +267,15 @@ export default function KnowledgeBasePage({ (value: number) => value.toLocaleString(localeCode), [localeCode], ) + const formatCategoryLabel = useCallback( + (category: string) => { + if (KNOWN_CATEGORY_KEYS.has(category)) { + return t(`category.${category}`) + } + return humanizeCategory(category) + }, + [t], + ) const visibleSummary = useMemo(() => { const loaded = displayedEntries.length @@ -236,13 +285,30 @@ export default function KnowledgeBasePage({ return { loaded, aiExtracted, approved, approvedRate } }, [displayedEntries]) - const categoryRows = useMemo(() => ( - CATEGORIES.map(category => { - const count = categories.find(c => c.category === category)?.count ?? 0 - const pct = totalCount > 0 ? Math.round((count / totalCount) * 100) : 0 - return { category, count, pct } + const categoryRows = useMemo(() => { + const sourceRows = categories.length > 0 + ? [...categories].sort((a, b) => b.count - a.count) + : CATEGORIES.map(category => ({ category, count: 0 })) + + const visibleRows = sourceRows.slice(0, CATEGORY_OVERVIEW_LIMIT).map(row => { + const pct = totalCount > 0 ? Math.round((row.count / totalCount) * 100) : 0 + return { category: row.category, count: row.count, pct } }) - ), [categories, totalCount]) + const remainingCount = sourceRows + .slice(CATEGORY_OVERVIEW_LIMIT) + .reduce((sum, row) => sum + row.count, 0) + + if (remainingCount <= 0) return visibleRows + + return [ + ...visibleRows, + { + category: '__other__', + count: remainingCount, + pct: totalCount > 0 ? Math.round((remainingCount / totalCount) * 100) : 0, + }, + ] + }, [categories, totalCount]) const content = (
@@ -284,7 +350,7 @@ export default function KnowledgeBasePage({ )} > {CATEGORY_ICONS[cat]} - {t(`category.${cat}`)} + {formatCategoryLabel(cat)} {count} ) @@ -380,8 +446,10 @@ export default function KnowledgeBasePage({
{categoryRows.map(row => ( -
- {t(`category.${row.category}`)} +
+ + {row.category === '__other__' ? t('overview.categoryOther') : formatCategoryLabel(row.category)} +
- {t(`category.${entry.category}`)} + {formatCategoryLabel(entry.category)} · {t(`source.${entry.source}`)} · @@ -529,7 +597,7 @@ export default function KnowledgeBasePage({ {/* Meta */}
- {t(`category.${selectedEntry.category}`)} + {formatCategoryLabel(selectedEntry.category)} · {t(`source.${selectedEntry.source}`)} ·