fix(web): standardize UI icon language
All checks were successful
CD Pipeline / tests (push) Successful in 1m35s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / build-and-deploy (push) Successful in 5m44s
CD Pipeline / post-deploy-checks (push) Successful in 2m36s

This commit is contained in:
Your Name
2026-06-03 00:18:23 +08:00
parent 0643e336b2
commit 6bf98ed00e
7 changed files with 94 additions and 47 deletions

View File

@@ -1871,7 +1871,7 @@
},
"neuralCommand": {
"title": "神經指揮中心",
"subtitle": "SSH_COMMAND 指揮權鏈 · OpenClaw 🦞 × NemoTron",
"subtitle": "SSH_COMMAND 指揮權鏈 · OpenClaw × NemoTron",
"lastRefresh": "更新於 {time}",
"refresh": "重新整理",
"preFlightAudit": "Pre-Flight 審查",
@@ -1962,8 +1962,8 @@
"noApprovals": "目前無待審核項目",
"noApprovalsDesc": "所有授權請求已處理完畢",
"chainAlert": "告警觸發",
"chainRAG": "🦞 OpenClaw RAG 診斷",
"chainDecide": "NemoTron 決策",
"chainRAG": "OpenClaw RAG 診斷",
"chainDecide": "NemoTron 決策",
"chainExec": "Executor 路由",
"chainIdleSub": "等待新告警進入...",
"backToList": "返回列表",

View File

@@ -1871,7 +1871,7 @@
},
"neuralCommand": {
"title": "神經指揮中心",
"subtitle": "SSH_COMMAND 指揮權鏈 · OpenClaw 🦞 × NemoTron",
"subtitle": "SSH_COMMAND 指揮權鏈 · OpenClaw × NemoTron",
"lastRefresh": "更新於 {time}",
"refresh": "重新整理",
"preFlightAudit": "Pre-Flight 審查",
@@ -1962,8 +1962,8 @@
"noApprovals": "目前無待審核項目",
"noApprovalsDesc": "所有授權請求已處理完畢",
"chainAlert": "告警觸發",
"chainRAG": "🦞 OpenClaw RAG 診斷",
"chainDecide": "NemoTron 決策",
"chainRAG": "OpenClaw RAG 診斷",
"chainDecide": "NemoTron 決策",
"chainExec": "Executor 路由",
"chainIdleSub": "等待新告警進入...",
"backToList": "返回列表",

View File

@@ -17,7 +17,7 @@ import { useTranslations } from 'next-intl'
import { cn } from '@/lib/utils'
import {
Search, BookOpen, FileText, Shield, Cpu,
Server, Eye, Bot, ChevronRight, Plus, Sparkles,
Server, Eye, Bot, ChevronRight, Plus, Sparkles, ClipboardList, TriangleAlert,
} from 'lucide-react'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
@@ -493,13 +493,15 @@ export default function KnowledgeBasePage({
{/* Related links */}
{selectedEntry.related_playbook_id && (
<div className="text-xs text-claw-blue font-body mb-2">
📋 {t('relatedPlaybook')}: {selectedEntry.related_playbook_id}
<div className="flex items-center gap-1.5 text-xs text-claw-blue font-body mb-2">
<ClipboardList className="h-3.5 w-3.5" aria-hidden="true" />
<span>{t('relatedPlaybook')}: {selectedEntry.related_playbook_id}</span>
</div>
)}
{selectedEntry.related_incident_id && (
<div className="text-xs text-status-warning font-body mb-4">
{t('relatedIncident')}: {selectedEntry.related_incident_id}
<div className="flex items-center gap-1.5 text-xs text-status-warning font-body mb-4">
<TriangleAlert className="h-3.5 w-3.5" aria-hidden="true" />
<span>{t('relatedIncident')}: {selectedEntry.related_incident_id}</span>
</div>
)}
@@ -534,4 +536,3 @@ export default function KnowledgeBasePage({
</AppLayout>
)
}

View File

@@ -436,7 +436,7 @@ export function OpenClawStateMachine({
setSelectedIndex(null)
setProcessedApproval(null)
}}
title={processedApproval ? (processedApproval.action === 'approved' ? '已批准' : '已拒絕') : '審核詳情'}
title={processedApproval ? (processedApproval.action === 'approved' ? '已批准' : '已拒絕') : '審核詳情'}
onPrev={processedApproval ? undefined : handlePrevApproval}
onNext={processedApproval ? undefined : handleNextApproval}
current={selectedIndex !== null && !processedApproval ? selectedIndex + 1 : undefined}
@@ -458,9 +458,12 @@ export function OpenClawStateMachine({
? "bg-status-healthy/20 text-status-healthy"
: "bg-status-critical/20 text-status-critical"
)}>
<div className="text-4xl mb-2">
{processedApproval.action === 'approved' ? '✅' : '❌'}
</div>
<div className="mb-2 flex justify-center">
{processedApproval.action === 'approved'
? <CheckCircle2 className="h-9 w-9" aria-hidden="true" />
: <AlertCircle className="h-9 w-9" aria-hidden="true" />
}
</div>
<div className="font-bold text-lg">
{processedApproval.action === 'approved' ? '已批准執行' : '已拒絕執行'}
</div>

View File

@@ -11,7 +11,7 @@
import { useTranslations } from 'next-intl'
import { cn } from '@/lib/utils'
import { CheckCircle2, Clock, XCircle, Loader2, Inbox } from 'lucide-react'
import { Bot, CheckCircle2, Clock, Cpu, Inbox, Loader2, Server, Settings, XCircle } from 'lucide-react'
import type { AutoRepairStats, RepairHistoryItem, UriScheme, RepairStatus, ActiveIncident } from './types'
interface Props {
@@ -41,6 +41,13 @@ const NODE_STATE_STYLES = {
waiting: 'border-border opacity-40',
}
const SEVERITY_DOT_STYLES: Record<string, string> = {
P0: 'bg-red-500 ring-red-500/20',
P1: 'bg-orange-500 ring-orange-500/20',
P2: 'bg-yellow-500 ring-yellow-500/20',
P3: 'bg-green-500 ring-green-500/20',
}
// 從最新一筆 history 衍生鏈路狀態
function deriveChainNodes(latestItem: RepairHistoryItem | null, t: ReturnType<typeof useTranslations>) {
if (!latestItem) {
@@ -72,14 +79,8 @@ function computeAvgDuration(history: RepairHistoryItem[]): string {
return `${(avg / 1000).toFixed(1)}s`
}
// 嚴重度 → emoji 對應
function severityEmoji(severity: string): string {
switch (severity) {
case 'P0': return '🔴'
case 'P1': return '🟠'
case 'P2': return '🟡'
default: return '🟢'
}
function severityDotClass(severity: string): string {
return SEVERITY_DOT_STYLES[severity] ?? SEVERITY_DOT_STYLES.P3
}
export function NeuralLiveCenter({ stats, history, pendingCount, activeIncidents = [] }: Props) {
@@ -101,7 +102,9 @@ export function NeuralLiveCenter({ stats, history, pendingCount, activeIncidents
{/* OpenClaw */}
<div className="rounded-xl border border-orange-500/25 bg-orange-500/5 p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-8 h-8 rounded-lg bg-orange-500/15 flex items-center justify-center text-base">🦞</div>
<div className="w-8 h-8 rounded-lg bg-orange-500/15 flex items-center justify-center">
<Bot className="w-4 h-4 text-orange-500" aria-hidden="true" />
</div>
<div>
<p className="text-sm font-bold text-orange-500">OpenClaw</p>
<p className="text-[10px] text-muted-foreground">{t('agentRoleOC')}</p>
@@ -118,7 +121,9 @@ export function NeuralLiveCenter({ stats, history, pendingCount, activeIncidents
{/* NemoTron */}
<div className="rounded-xl border border-blue-500/25 bg-blue-500/5 p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-8 h-8 rounded-lg bg-blue-500/15 flex items-center justify-center text-base"></div>
<div className="w-8 h-8 rounded-lg bg-blue-500/15 flex items-center justify-center">
<Cpu className="w-4 h-4 text-blue-500" aria-hidden="true" />
</div>
<div>
<p className="text-sm font-bold text-blue-500">NemoTron</p>
<p className="text-[10px] text-muted-foreground">{t('agentRoleNemo')}</p>
@@ -142,7 +147,11 @@ export function NeuralLiveCenter({ stats, history, pendingCount, activeIncidents
'flex items-center gap-2 px-2 py-1.5 rounded-lg cursor-pointer transition-colors',
i === 0 ? 'bg-orange-500/8 border border-orange-500/20' : 'hover:bg-muted',
)}>
<span className="text-xs">{severityEmoji(inc.severity)}</span>
<span
className={cn('h-2.5 w-2.5 rounded-full ring-4 flex-shrink-0', severityDotClass(inc.severity))}
aria-label={inc.severity}
title={inc.severity}
/>
<div className="flex-1 min-w-0">
<p className="text-[11px] font-semibold truncate">{inc.incident_id}</p>
<p className="text-[10px] text-muted-foreground truncate">{inc.affected_services.join(', ') || inc.status}</p>
@@ -187,16 +196,19 @@ export function NeuralLiveCenter({ stats, history, pendingCount, activeIncidents
<div className="w-0.5 h-4 bg-muted-foreground/20 rounded-full" />
<div className="w-full max-w-sm grid grid-cols-3 gap-2">
{[
{ scheme: 'kubectl://', icon: '☸️', label: 'kubectl', cls: 'border-blue-500/25 bg-blue-500/5' },
{ scheme: 'openclaw://', icon: '🦞', label: 'OpenClaw', cls: 'border-orange-500/25 bg-orange-500/5' },
{ scheme: 'ansible://', icon: '⚙️', label: 'Ansible .188', cls: 'border-purple-500/25 bg-purple-500/5' },
].map(b => (
<div key={b.scheme} className={cn('rounded-lg border p-2.5 text-center opacity-50', b.cls)}>
<p className="text-sm">{b.icon}</p>
<p className="text-[10px] font-bold mt-1">{b.label}</p>
<p className="text-[9px] text-muted-foreground font-mono">{b.scheme}</p>
</div>
))}
{ scheme: 'kubectl://', Icon: Server, label: 'kubectl', cls: 'border-blue-500/25 bg-blue-500/5', iconCls: 'text-blue-500' },
{ scheme: 'openclaw://', Icon: Bot, label: 'OpenClaw', cls: 'border-orange-500/25 bg-orange-500/5', iconCls: 'text-orange-500' },
{ scheme: 'ansible://', Icon: Settings, label: 'Ansible .188', cls: 'border-purple-500/25 bg-purple-500/5', iconCls: 'text-purple-500' },
].map(b => {
const BranchIcon = b.Icon
return (
<div key={b.scheme} className={cn('rounded-lg border p-2.5 text-center opacity-70', b.cls)}>
<BranchIcon className={cn('mx-auto h-4 w-4', b.iconCls)} aria-hidden="true" />
<p className="text-[10px] font-bold mt-1">{b.label}</p>
<p className="text-[9px] text-muted-foreground font-mono">{b.scheme}</p>
</div>
)
})}
</div>
</div>
</div>

View File

@@ -12,7 +12,8 @@
import { useMemo } from 'react'
import { useTranslations } from 'next-intl'
import { cn } from '@/lib/utils'
import { Inbox } from 'lucide-react'
import { Bot, Inbox, Server, Settings } from 'lucide-react'
import type { LucideIcon } from 'lucide-react'
import type { AutoRepairStats, PlaybookItem, RepairHistoryItem } from './types'
interface DispositionSummary {
@@ -38,10 +39,10 @@ const TYPE_BADGE: Record<string, string> = {
openclaw: 'bg-orange-500/10 text-orange-500 border border-orange-500/25',
}
const SCHEME_ICON: Record<string, { icon: string; color: string; textColor: string; bg: string }> = {
'kubectl://': { icon: '☸️', color: 'bg-blue-500', textColor: 'text-blue-500', bg: 'bg-blue-500/10' },
'openclaw://': { icon: '🦞', color: 'bg-orange-500', textColor: 'text-orange-500', bg: 'bg-orange-500/10' },
'ansible://': { icon: '⚙️', color: 'bg-purple-500', textColor: 'text-purple-500', bg: 'bg-purple-500/10' },
const SCHEME_STYLE: Record<string, { Icon: LucideIcon; color: string; textColor: string; bg: string }> = {
'kubectl://': { Icon: Server, color: 'bg-blue-500', textColor: 'text-blue-500', bg: 'bg-blue-500/10' },
'openclaw://': { Icon: Bot, color: 'bg-orange-500', textColor: 'text-orange-500', bg: 'bg-orange-500/10' },
'ansible://': { Icon: Settings, color: 'bg-purple-500', textColor: 'text-purple-500', bg: 'bg-purple-500/10' },
}
export function NeuralStats({ stats, playbooks, history, pendingCount, disposition }: Props) {
@@ -59,7 +60,7 @@ export function NeuralStats({ stats, playbooks, history, pendingCount, dispositi
const total = history.length || 1
return Object.entries(counts).map(([scheme, { count, successCount }]) => ({
scheme,
...(SCHEME_ICON[scheme] ?? SCHEME_ICON['kubectl://']),
...(SCHEME_STYLE[scheme] ?? SCHEME_STYLE['kubectl://']),
count,
rate: count > 0 ? Math.round((successCount / count) * 100) : 0,
pct: Math.round((count / total) * 100),
@@ -145,9 +146,13 @@ export function NeuralStats({ stats, playbooks, history, pendingCount, dispositi
<p className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-4">{t('schemeBreakdown')}</p>
{schemeStats.length > 0 ? (
<div className="space-y-3">
{schemeStats.map(s => (
{schemeStats.map(s => {
const SchemeIcon = s.Icon
return (
<div key={s.scheme} className="flex items-center gap-3">
<span className="text-sm w-5 text-center">{s.icon}</span>
<span className={cn('flex h-6 w-6 items-center justify-center rounded-md', s.bg)}>
<SchemeIcon className={cn('h-3.5 w-3.5', s.textColor)} aria-hidden="true" />
</span>
<span className={cn('text-xs font-semibold w-24 font-mono', s.textColor)}>{s.scheme}</span>
<div className="flex-1 h-1.5 rounded-full bg-muted overflow-hidden">
<div className={cn('h-full rounded-full', s.color)} style={{ width: `${s.pct}%` }} />
@@ -155,7 +160,8 @@ export function NeuralStats({ stats, playbooks, history, pendingCount, dispositi
<span className={cn('text-xs font-bold w-6 text-right', s.textColor)}>{s.count}</span>
<span className="text-xs text-muted-foreground w-8 text-right">{s.rate}%</span>
</div>
))}
)
})}
</div>
) : (
<div className="flex flex-col items-center justify-center py-8 text-muted-foreground">

View File

@@ -1,3 +1,28 @@
## 2026-06-03前端 UI/UX 與素材治理同步盤點啟動
**背景**:統帥追問前端是否也需要同步盤點 UI/UX 與素材。結論:需要,而且必須和 AI 自動化飛輪、AwoooP 操作台、告警流程呈現一起治理;目前問題不只是文案,而是過多文字牆、流程/拓樸/圖表不足、正式 UI 仍混用 emoji 作為產品圖示、可復用素材庫薄弱,導致使用者難以快速判斷「現在跑到哪個階段、誰主責、是否自動修復、何時要人工介入」。
**本次盤點結果**
- `apps/web/public` 目前可復用產品素材極少,主要只有 `favicon.svg` 與 DSEG7 字型;缺少正式產品截圖、拓樸圖、架構圖、流程圖素材入口與治理清單。
- 前端已有 `lucide-react``recharts``@xyflow/react` 等基礎能力,可以支撐專業圖示、趨勢圖、流程圖與狀態機視覺化;下一步應優先把大量文字段落改成「流程節點、狀態表、時間軸、拓樸關係、風險分佈」。
- 正式頁仍有 emoji 扮演狀態或服務圖示,會削弱操作台專業感,也不利於建立一致設計語言。
- `apps/web/src/components/aiops/timeline/mock-data.ts` 仍是前端假資料風險候選,後續要確認是否僅開發殘留,不能讓產品頁誤讀為真實自動化證據。
**本次低風險可見修正**
- `NeuralLiveCenter`:告警雷達嚴重度由 emoji 改為一致狀態點OpenClaw / NemoTron 與 kubectl / OpenClaw / Ansible 分支改用 lucide 圖示。
- `NeuralStats`URI scheme 分佈由 emoji 改為一致的圖示膠囊與進度條,保留真實 history 聚合邏輯。
- `Knowledge Base`related Playbook / Incident 連結改用 lucide 圖示,避免正式知識頁出現 emoji 標記。
- `OpenClawStateMachine`approval 處理結果 modal 移除 `✅ / ❌`,改用 lucide 成功/警示圖示。
- `neuralCommand` i18n副標題與鏈路節點文案移除 emoji`zh-TW` / `en` 鏡像保持一致。
**後續治理方向**
- Wave A全站掃描 emoji-as-icon、硬編碼圖示、文字牆區塊優先處理 AwoooP Runs / Approvals / Alerts / KM / Monitoring。
- Wave B建立 reusable visual primitives狀態點、流程節點、拓樸節點、證據表、空狀態、資料品質徽章避免每頁重做。
- Wave C補正式素材庫AI 飛輪架構圖、MCP/Agent 拓樸圖、告警到修復 swimlane、資料來源與證據鏈圖素材必須對應真實系統與 DB/API 證據,禁止用假圖暗示已完成。
- Wave D首頁與核心操作頁改為圖表/表格優先,文字只保留決策摘要與例外原因。
**進度更新**:前端設計系統/i18n/素材治理由 `46%` 上修至 `49%`;首頁產品化入口維持 `80%`AwoooP/HITL 維持 `99.2%`;完整 AI 自動化飛輪仍維持約 `66%`,本輪 UI 素材治理不代表自動修復能力已新增。
## 2026-06-02IwoooS 進度誠實儀表正式落地
**背景**:統帥持續要求 IwoooS 不要只堆文字,且要讓使用者能理解「為什麼資安網有明顯框架進展,但整體進度不應假性跳點」。本輪延續低摩擦資安框架策略,不提高 enforcement、不啟動 Kali 掃描 / SSH 修復 / 主機更新 / repo mutation只在首屏新增小型圖表化儀表明確呈現只讀納管、S4.9 待補證據與執行閘門 0。