fix(web): 標示 AIOps 範例資料模式
This commit is contained in:
@@ -2239,7 +2239,7 @@
|
|||||||
"aiopsTimeline": {
|
"aiopsTimeline": {
|
||||||
"title": "AIOps 全景時序",
|
"title": "AIOps 全景時序",
|
||||||
"subtitle": "告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路",
|
"subtitle": "告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路",
|
||||||
"mockBadge": "MOCK 模式",
|
"sampleBadge": "範例資料",
|
||||||
"stages": {
|
"stages": {
|
||||||
"alert": "告警觸發",
|
"alert": "告警觸發",
|
||||||
"diagnose": "感官調查",
|
"diagnose": "感官調查",
|
||||||
|
|||||||
@@ -2239,7 +2239,7 @@
|
|||||||
"aiopsTimeline": {
|
"aiopsTimeline": {
|
||||||
"title": "AIOps 全景時序",
|
"title": "AIOps 全景時序",
|
||||||
"subtitle": "告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路",
|
"subtitle": "告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路",
|
||||||
"mockBadge": "MOCK 模式",
|
"sampleBadge": "範例資料",
|
||||||
"stages": {
|
"stages": {
|
||||||
"alert": "告警觸發",
|
"alert": "告警觸發",
|
||||||
"diagnose": "感官調查",
|
"diagnose": "感官調查",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
// AIOps 全景時序頁面
|
// AIOps 全景時序頁面
|
||||||
// 告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路視覺化
|
// 告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路視覺化
|
||||||
//
|
//
|
||||||
// Mock 模式:NEXT_PUBLIC_AIOPS_TIMELINE_MOCK=true
|
// 範例資料模式:NEXT_PUBLIC_AIOPS_TIMELINE_MOCK=true
|
||||||
// 真實 API: GET /api/v1/aiops/timeline?incident_id=&limit=20
|
// 真實 API: GET /api/v1/aiops/timeline?incident_id=&limit=20
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* - ApprovalCard 滑入動畫
|
* - ApprovalCard 滑入動畫
|
||||||
* - 記憶體安全清理
|
* - 記憶體安全清理
|
||||||
*
|
*
|
||||||
* 真實性條款: 禁止任何 Mock Data
|
* 真實性條款:禁止以範例資料掩蓋 API 狀態
|
||||||
* i18n: 100% next-intl
|
* i18n: 100% next-intl
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { TimelineFilter } from './TimelineFilter'
|
import { TimelineFilter } from './TimelineFilter'
|
||||||
import { TimelineStage } from './TimelineStage'
|
import { TimelineStage } from './TimelineStage'
|
||||||
import { MOCK_INCIDENTS } from './mock-data'
|
import { SAMPLE_TIMELINE_INCIDENTS } from './sample-incidents'
|
||||||
import type {
|
import type {
|
||||||
TimelineIncident,
|
TimelineIncident,
|
||||||
TimelineFilterState,
|
TimelineFilterState,
|
||||||
@@ -31,7 +31,8 @@ import type {
|
|||||||
// Constants
|
// Constants
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
const IS_MOCK = process.env.NEXT_PUBLIC_AIOPS_TIMELINE_MOCK === 'true'
|
// 保留既有環境變數相容;UI 以「範例資料」呈現,避免誤讀為正式證據。
|
||||||
|
const USE_SAMPLE_TIMELINE = process.env.NEXT_PUBLIC_AIOPS_TIMELINE_MOCK === 'true'
|
||||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
||||||
|
|
||||||
const STAGE_ORDER: StageType[] = ['alert', 'diagnose', 'decide', 'execute', 'verify', 'learn']
|
const STAGE_ORDER: StageType[] = ['alert', 'diagnose', 'decide', 'execute', 'verify', 'learn']
|
||||||
@@ -280,14 +281,14 @@ export default function AiopsTimelinePanel() {
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['aiops-timeline', filter.incident_id, filter.time_range],
|
queryKey: ['aiops-timeline', filter.incident_id, filter.time_range],
|
||||||
queryFn: () => fetchTimeline(filter.incident_id),
|
queryFn: () => fetchTimeline(filter.incident_id),
|
||||||
enabled: !IS_MOCK,
|
enabled: !USE_SAMPLE_TIMELINE,
|
||||||
staleTime: 30_000,
|
staleTime: 30_000,
|
||||||
refetchInterval: 60_000,
|
refetchInterval: 60_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 客戶端篩選 — rawIncidents 內聯進 useMemo 避免 conditional 依賴警告
|
// 客戶端篩選 — rawIncidents 內聯進 useMemo 避免 conditional 依賴警告
|
||||||
const incidents = useMemo(() => {
|
const incidents = useMemo(() => {
|
||||||
const rawIncidents: TimelineIncident[] = IS_MOCK ? MOCK_INCIDENTS : (apiData ?? [])
|
const rawIncidents: TimelineIncident[] = USE_SAMPLE_TIMELINE ? SAMPLE_TIMELINE_INCIDENTS : (apiData ?? [])
|
||||||
let list = rawIncidents
|
let list = rawIncidents
|
||||||
|
|
||||||
if (filter.incident_id.trim()) {
|
if (filter.incident_id.trim()) {
|
||||||
@@ -325,9 +326,9 @@ export default function AiopsTimelinePanel() {
|
|||||||
<h1 className="font-heading text-2xl font-bold text-nothing-black flex items-center gap-2.5">
|
<h1 className="font-heading text-2xl font-bold text-nothing-black flex items-center gap-2.5">
|
||||||
<GitBranch className="w-6 h-6 text-claw-blue" aria-hidden="true" />
|
<GitBranch className="w-6 h-6 text-claw-blue" aria-hidden="true" />
|
||||||
{t('title')}
|
{t('title')}
|
||||||
{IS_MOCK && (
|
{USE_SAMPLE_TIMELINE && (
|
||||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-body font-bold uppercase tracking-widest bg-status-warning/10 border border-status-warning/20 text-status-warning">
|
<span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-body font-bold uppercase tracking-widest bg-status-warning/10 border border-status-warning/20 text-status-warning">
|
||||||
{t('mockBadge')}
|
{t('sampleBadge')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -337,7 +338,7 @@ export default function AiopsTimelinePanel() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Refresh button (僅真實模式顯示) */}
|
{/* Refresh button (僅真實模式顯示) */}
|
||||||
{!IS_MOCK && (
|
{!USE_SAMPLE_TIMELINE && (
|
||||||
<button
|
<button
|
||||||
onClick={() => refetch()}
|
onClick={() => refetch()}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
@@ -362,7 +363,7 @@ export default function AiopsTimelinePanel() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Loading state (真實 API) */}
|
{/* Loading state (真實 API) */}
|
||||||
{!IS_MOCK && isLoading && (
|
{!USE_SAMPLE_TIMELINE && isLoading && (
|
||||||
<div className="flex items-center justify-center py-20" role="status" aria-live="polite">
|
<div className="flex items-center justify-center py-20" role="status" aria-live="polite">
|
||||||
<RefreshCw className="w-5 h-5 animate-spin text-nothing-gray-400" />
|
<RefreshCw className="w-5 h-5 animate-spin text-nothing-gray-400" />
|
||||||
<span className="ml-2 font-body text-sm text-nothing-gray-400">{t('loading')}</span>
|
<span className="ml-2 font-body text-sm text-nothing-gray-400">{t('loading')}</span>
|
||||||
@@ -370,7 +371,7 @@ export default function AiopsTimelinePanel() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Error state */}
|
{/* Error state */}
|
||||||
{!IS_MOCK && error && !isLoading && (
|
{!USE_SAMPLE_TIMELINE && error && !isLoading && (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col items-center justify-center py-20 gap-3"
|
className="flex flex-col items-center justify-center py-20 gap-3"
|
||||||
role="alert"
|
role="alert"
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ export { TimelineStage } from './TimelineStage'
|
|||||||
export { TimelineStageDetails } from './TimelineStageDetails'
|
export { TimelineStageDetails } from './TimelineStageDetails'
|
||||||
export { TimelineFilter } from './TimelineFilter'
|
export { TimelineFilter } from './TimelineFilter'
|
||||||
export { EvidenceViewer } from './EvidenceViewer'
|
export { EvidenceViewer } from './EvidenceViewer'
|
||||||
export { MOCK_INCIDENTS } from './mock-data'
|
export { SAMPLE_TIMELINE_INCIDENTS } from './sample-incidents'
|
||||||
export type * from './types'
|
export type * from './types'
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// 2026-04-26 P2.5 by Claude — AIOps Timeline
|
// 2026-04-26 P2.5 by Claude — AIOps Timeline
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Mock Data — 3 範例 incident,完整 6 階段 + metadata
|
// Sample incidents — 3 筆範例 incident,完整 6 階段 + metadata
|
||||||
// NEXT_PUBLIC_AIOPS_TIMELINE_MOCK=true 時使用
|
// NEXT_PUBLIC_AIOPS_TIMELINE_MOCK=true 時使用
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
import type { TimelineIncident } from './types'
|
import type { TimelineIncident } from './types'
|
||||||
|
|
||||||
export const MOCK_INCIDENTS: TimelineIncident[] = [
|
export const SAMPLE_TIMELINE_INCIDENTS: TimelineIncident[] = [
|
||||||
{
|
{
|
||||||
incident_id: 'INC-2026-0425-001',
|
incident_id: 'INC-2026-0425-001',
|
||||||
title: 'API Server CPU 100% — k8s-188',
|
title: 'API Server CPU 100% — k8s-188',
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
* 3. getTerminalComponents — 只返回 allowInTerminal=true 的組件
|
* 3. getTerminalComponents — 只返回 allowInTerminal=true 的組件
|
||||||
* 4. validateProps — 錯誤碼分類 (UNKNOWN_COMPONENT / ZOD_VALIDATION_FAILED)
|
* 4. validateProps — 錯誤碼分類 (UNKNOWN_COMPONENT / ZOD_VALIDATION_FAILED)
|
||||||
*
|
*
|
||||||
* 注意: registry.ts 使用 React.lazy,在 Node 環境需要 Mock
|
* 注意:registry.ts 使用 React.lazy,在 Node 環境需以測試替身隔離 lazy 載入
|
||||||
*
|
*
|
||||||
* @see ADR-032 GenUI Dynamic Rendering
|
* @see ADR-032 GenUI Dynamic Rendering
|
||||||
* Phase 19.6 ogt 2026-03-31 (台北時間)
|
* Phase 19.6 ogt 2026-03-31 (台北時間)
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
import { vi, describe, it, expect, beforeAll } from 'vitest'
|
import { vi, describe, it, expect, beforeAll } from 'vitest'
|
||||||
|
|
||||||
// ===== Mock React (lazy 在 Node 環境無法執行) =====
|
// ===== React lazy 測試替身(Node 環境無法執行 lazy import) =====
|
||||||
vi.mock('react', () => ({
|
vi.mock('react', () => ({
|
||||||
lazy: vi.fn((factory: () => Promise<unknown>) => factory),
|
lazy: vi.fn((factory: () => Promise<unknown>) => factory),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* =====================================
|
* =====================================
|
||||||
* 顯示真實待審核的操作,整合 NuclearKeyButton 長按確認
|
* 顯示真實待審核的操作,整合 NuclearKeyButton 長按確認
|
||||||
*
|
*
|
||||||
* 2026-04-07 Claude Code: Sprint F 打假行動 — 移除 MOCK_PENDING
|
* 2026-04-07 Claude Code: Sprint F 打假行動 — 移除舊待審範例常數
|
||||||
* 專案鐵律: 接真實 /api/v1/approvals/pending,無資料顯示 EmptyState
|
* 專案鐵律: 接真實 /api/v1/approvals/pending,無資料顯示 EmptyState
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* ================================
|
* ================================
|
||||||
* 5 KPI + 執行路徑分佈 + Playbook 排名 + 時間軸
|
* 5 KPI + 執行路徑分佈 + Playbook 排名 + 時間軸
|
||||||
*
|
*
|
||||||
* 2026-04-07 Claude Code: Sprint F 打假行動 — 移除所有 MOCK 常數
|
* 2026-04-07 Claude Code: Sprint F 打假行動 — 移除所有假資料常數
|
||||||
* 專案鐵律: 所有數據從 stats/playbooks/history props 計算,禁止寫死!
|
* 專案鐵律: 所有數據從 stats/playbooks/history props 計算,禁止寫死!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user