feat(web): drive incident flow summaries from status chain
This commit is contained in:
@@ -34,8 +34,10 @@ import { PendingApprovalsCard } from '@/components/shared/pending-approvals-card
|
||||
import { AIModelStatus } from '@/components/shared/ai-model-status'
|
||||
import { FlywheelKPICard } from '@/components/dashboard/flywheel-kpi-card'
|
||||
import { AutomationEvidenceCard } from '@/components/dashboard/automation-evidence-card'
|
||||
import type { AwoooPStatusChain } from '@/components/awooop/status-chain'
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
|
||||
const STATUS_CHAIN_PREFETCH_LIMIT = 25
|
||||
|
||||
// =============================================================================
|
||||
// Tab 2: 告警 & 授權 (串接真實 API)
|
||||
@@ -128,7 +130,9 @@ function ActivityStreamTab() {
|
||||
try {
|
||||
const data = JSON.parse(e.data)
|
||||
setEvents(prev => [{ ...data, _time: new Date().toLocaleTimeString('zh-TW', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) }, ...prev].slice(0, 50))
|
||||
} catch {}
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
es.onerror = () => setConnected(false)
|
||||
return () => es.close()
|
||||
@@ -600,10 +604,59 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
incidents,
|
||||
isLoading: isIncidentsLoading,
|
||||
error: incidentsError,
|
||||
lastUpdated: incidentsLastUpdated,
|
||||
} = useIncidents({
|
||||
pollInterval: 15000,
|
||||
enablePolling: true,
|
||||
})
|
||||
const statusChainIncidentKey = incidents
|
||||
?.slice(0, STATUS_CHAIN_PREFETCH_LIMIT)
|
||||
.map(incident => incident.incident_id)
|
||||
.join('|') ?? ''
|
||||
const [statusChains, setStatusChains] = useState<Record<string, AwoooPStatusChain | null>>({})
|
||||
|
||||
useEffect(() => {
|
||||
const incidentIds = statusChainIncidentKey
|
||||
? statusChainIncidentKey.split('|').filter(Boolean)
|
||||
: []
|
||||
if (incidentIds.length === 0) {
|
||||
setStatusChains({})
|
||||
return
|
||||
}
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeout = window.setTimeout(() => controller.abort(), 12000)
|
||||
|
||||
Promise.all(
|
||||
incidentIds.map(async (incidentId): Promise<[string, AwoooPStatusChain | null]> => {
|
||||
const params = new URLSearchParams({ project_id: 'awoooi' })
|
||||
params.append('incident_id', incidentId)
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/v1/platform/status-chain?${params.toString()}`, {
|
||||
cache: 'no-store',
|
||||
signal: controller.signal,
|
||||
})
|
||||
if (!response.ok) return [incidentId, null]
|
||||
return [incidentId, await response.json() as AwoooPStatusChain]
|
||||
} catch {
|
||||
return [incidentId, null]
|
||||
}
|
||||
})
|
||||
)
|
||||
.then(entries => {
|
||||
if (controller.signal.aborted) return
|
||||
setStatusChains(Object.fromEntries(entries))
|
||||
})
|
||||
.catch(() => {
|
||||
if (!controller.signal.aborted) setStatusChains({})
|
||||
})
|
||||
.finally(() => window.clearTimeout(timeout))
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timeout)
|
||||
controller.abort()
|
||||
}
|
||||
}, [statusChainIncidentKey, incidentsLastUpdated])
|
||||
|
||||
// ── Metrics 計算 ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -889,6 +942,7 @@ export default function Home({ params }: { params: { locale: string } }) {
|
||||
key={incident.incident_id}
|
||||
incident={incident}
|
||||
decision={incident.decision}
|
||||
statusChain={statusChains[incident.incident_id] ?? null}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user