fix(web): standardize UI icon language
This commit is contained in:
@@ -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": "返回列表",
|
||||
|
||||
@@ -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": "返回列表",
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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-02|IwoooS 進度誠實儀表正式落地
|
||||
|
||||
**背景**:統帥持續要求 IwoooS 不要只堆文字,且要讓使用者能理解「為什麼資安網有明顯框架進展,但整體進度不應假性跳點」。本輪延續低摩擦資安框架策略,不提高 enforcement、不啟動 Kali 掃描 / SSH 修復 / 主機更新 / repo mutation,只在首屏新增小型圖表化儀表,明確呈現只讀納管、S4.9 待補證據與執行閘門 0。
|
||||
|
||||
Reference in New Issue
Block a user