feat(web): S6 OpenClaw AI Terminal + 狀態數據 — Sprint 5R
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 13m15s
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 13m15s
- 分隔線下方新增:模型名稱 + 運行狀態 - 即時統計:今日分析數 / 成功率 / MTTR - AI 推理終端:#141413 背景 + #a0e8a0 螢光綠 + JetBrains Mono - 最後一行黃色閃爍游標 ▎ - 資料來源:/api/v1/alert-operation-logs + /api/v1/stats/disposition Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -192,9 +192,95 @@ export function OpenClawPanel({
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sprint 5R S6: 狀態數據 + AI Terminal */}
|
||||
<div style={{ marginTop: 8, paddingTop: 8, borderTop: '0.5px solid #e0ddd4' }}>
|
||||
<div style={{ display: 'flex', gap: 8, marginBottom: 4 }}>
|
||||
<div style={{ flex: 1, fontSize: 10, color: '#555550' }}>
|
||||
模型: <span style={{ fontWeight: 600, color: '#141413' }}>openclaw_nemo</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 10, color: '#22C55E', fontWeight: 500 }}>● 運行中</div>
|
||||
</div>
|
||||
<OpenClawStats />
|
||||
<OpenClawTerminal />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** AI 即時統計 */
|
||||
function OpenClawStats() {
|
||||
const [stats, setStats] = useState<{ analyses: number; successRate: string; mttr: string }>({ analyses: 0, successRate: '--', mttr: '--' })
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
fetch(`${API_BASE}/api/v1/stats/disposition`).then(r => r.ok ? r.json() : null).catch(() => null),
|
||||
fetch(`${API_BASE}/api/v1/incidents`).then(r => r.ok ? r.json() : null).catch(() => null),
|
||||
]).then(([disp, inc]) => {
|
||||
const total = disp?.summary?.total ?? 0
|
||||
const rate = disp?.summary?.auto_rate != null ? `${Math.round(disp.summary.auto_rate * 100)}%` : '--'
|
||||
const incidents = inc?.incidents ?? inc ?? []
|
||||
const resolved = incidents.filter((i: any) => i.updated_at && (i.status === 'resolved' || i.status === 'closed'))
|
||||
let mttr = '--'
|
||||
if (resolved.length > 0) {
|
||||
const avg = resolved.reduce((sum: number, i: any) => sum + (new Date(i.updated_at).getTime() - new Date(i.created_at).getTime()), 0) / resolved.length
|
||||
mttr = `${(avg / 60000).toFixed(1)}m`
|
||||
}
|
||||
setStats({ analyses: total, successRate: rate, mttr })
|
||||
})
|
||||
}, [API_BASE])
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 12, fontSize: 10, color: '#555550' }}>
|
||||
<span>今日分析: <b style={{ color: '#141413' }}>{stats.analyses}</b></span>
|
||||
<span>成功率: <b style={{ color: '#22C55E' }}>{stats.successRate}</b></span>
|
||||
<span>MTTR: <b style={{ color: '#141413' }}>{stats.mttr}</b></span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** AI 推理終端 */
|
||||
function OpenClawTerminal() {
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_BASE}/api/v1/alert-operation-logs?limit=4`)
|
||||
.then(r => r.ok ? r.json() : { items: [] })
|
||||
.then(d => {
|
||||
const items = (d.items ?? []).reverse()
|
||||
setLogs(items.map((item: any) => {
|
||||
const time = (() => { try { return new Date(item.created_at).toLocaleTimeString('zh-TW', { timeZone: 'Asia/Taipei', hour: '2-digit', minute: '2-digit' }) } catch { return '--:--' } })()
|
||||
return `[${time}] ${item.action_detail || item.event_type.replace(/_/g, ' ')}`
|
||||
}))
|
||||
})
|
||||
.catch(() => {})
|
||||
}, [API_BASE])
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
background: '#141413', borderRadius: 6, padding: '8px 10px', marginTop: 8,
|
||||
fontFamily: "'JetBrains Mono', monospace", fontSize: 10, color: '#a0e8a0',
|
||||
lineHeight: 1.6, maxHeight: 80, overflowY: 'auto',
|
||||
}}>
|
||||
<style>{`@keyframes cursor-blink { 0%,100%{opacity:1} 50%{opacity:0} }`}</style>
|
||||
{logs.length === 0 ? (
|
||||
<span style={{ color: '#555' }}>[--:--] Scanning cluster state...</span>
|
||||
) : (
|
||||
logs.map((line, i) => (
|
||||
<div key={i}>
|
||||
{i < logs.length - 1 ? (
|
||||
<><span style={{ color: '#555' }}>{line.slice(0, 7)}</span>{line.slice(7)}</>
|
||||
) : (
|
||||
<span style={{ color: '#ffd700' }}>{line} <span style={{ animation: 'cursor-blink 1s step-end infinite' }}>▎</span></span>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OpenClawPanel
|
||||
|
||||
Reference in New Issue
Block a user