119 lines
4.0 KiB
TypeScript
119 lines
4.0 KiB
TypeScript
/**
|
|
* ImpactPreview (#27) — 장애 영향 서비스 예측 시각화
|
|
*
|
|
* GET /api/ai/impact/{server_id} 우선, 실패 시 /api/servers/{id}/dependencies.
|
|
* 영향받는 서비스 목록 + 연결 관계를 텍스트 기반 리스트로 표시.
|
|
*/
|
|
import { useState, useEffect } from 'react'
|
|
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native'
|
|
import { COLORS, API_BASE } from '../constants/Config'
|
|
import { authFetch } from '../utils/auth'
|
|
|
|
interface ImpactNode {
|
|
name: string
|
|
type?: string // service / db / app ...
|
|
severity?: 'high' | 'medium' | 'low' | string
|
|
relation?: string // 'depends_on' 등
|
|
}
|
|
|
|
interface Props {
|
|
serverId: number | string
|
|
serverName?: string
|
|
}
|
|
|
|
const SEV_COLOR: Record<string, string> = {
|
|
high: COLORS.danger,
|
|
medium: COLORS.warning,
|
|
low: COLORS.success,
|
|
}
|
|
|
|
export function ImpactPreview({ serverId, serverName }: Props) {
|
|
const [nodes, setNodes] = useState<ImpactNode[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
let alive = true
|
|
;(async () => {
|
|
setLoading(true)
|
|
const endpoints = [
|
|
`${API_BASE}/api/ai/impact/${serverId}`,
|
|
`${API_BASE}/api/servers/${serverId}/dependencies`,
|
|
]
|
|
for (const url of endpoints) {
|
|
try {
|
|
const res = await authFetch(url)
|
|
if (res.ok) {
|
|
const d = await res.json()
|
|
const raw = Array.isArray(d) ? d : d.impacted ?? d.services ?? d.dependencies ?? d.nodes ?? []
|
|
const list: ImpactNode[] = raw.map((x: any) => ({
|
|
name: x.name ?? x.service_name ?? x.hostname ?? String(x),
|
|
type: x.type ?? x.kind,
|
|
severity: x.severity ?? x.impact,
|
|
relation: x.relation ?? x.relationship,
|
|
}))
|
|
if (alive && list.length) {
|
|
setNodes(list)
|
|
setLoading(false)
|
|
return
|
|
}
|
|
}
|
|
} catch {
|
|
/* 다음 엔드포인트 시도 */
|
|
}
|
|
}
|
|
if (alive) {
|
|
setNodes([])
|
|
setLoading(false)
|
|
}
|
|
})()
|
|
return () => {
|
|
alive = false
|
|
}
|
|
}, [serverId])
|
|
|
|
return (
|
|
<View style={S.wrap}>
|
|
<Text style={S.title}>🌐 장애 영향도 예측</Text>
|
|
{serverName ? <Text style={S.root}>출발: {serverName}</Text> : null}
|
|
|
|
{loading ? (
|
|
<ActivityIndicator color={COLORS.accent} style={{ marginTop: 8 }} />
|
|
) : nodes.length === 0 ? (
|
|
<Text style={S.empty}>영향 데이터를 불러올 수 없습니다.</Text>
|
|
) : (
|
|
<View style={S.list}>
|
|
{nodes.map((n, i) => (
|
|
<View key={i} style={S.node}>
|
|
<View style={[S.dot, { backgroundColor: SEV_COLOR[n.severity ?? 'low'] ?? COLORS.muted }]} />
|
|
<View style={{ flex: 1 }}>
|
|
<Text style={S.nodeName}>{n.name}</Text>
|
|
<Text style={S.nodeMeta}>
|
|
{n.type ? `${n.type} · ` : ''}
|
|
{n.relation ?? '연결됨'}
|
|
{n.severity ? ` · ${n.severity}` : ''}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
))}
|
|
<Text style={S.note}>※ {nodes.length}개 서비스가 영향을 받을 수 있습니다.</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export default ImpactPreview
|
|
|
|
const S = StyleSheet.create({
|
|
wrap: { backgroundColor: COLORS.card, borderRadius: 14, padding: 14, borderWidth: 1, borderColor: COLORS.border },
|
|
title: { fontSize: 15, fontWeight: '700', color: COLORS.text },
|
|
root: { fontSize: 12, color: COLORS.muted, marginTop: 2 },
|
|
empty: { fontSize: 12, color: COLORS.muted, marginTop: 8 },
|
|
list: { marginTop: 10 },
|
|
node: { flexDirection: 'row', alignItems: 'center', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#f1f5f9', gap: 10 },
|
|
dot: { width: 10, height: 10, borderRadius: 5 },
|
|
nodeName: { fontSize: 13, fontWeight: '600', color: COLORS.text },
|
|
nodeMeta: { fontSize: 11, color: COLORS.muted, marginTop: 2 },
|
|
note: { fontSize: 11, color: COLORS.danger, marginTop: 8 },
|
|
})
|