70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
/**
|
|
* useAnomalyAlert (#22) — 이상 탐지 푸시 훅
|
|
*
|
|
* 1분마다 GET /api/metrics/anomalies?severity=high 폴링.
|
|
* 이전에 보지 못한 새 이상 감지 시 Alert.alert로 알림.
|
|
* (expo-notifications 플러그인 등록 없이 인앱 Alert 사용 → EAS 빌드 안전)
|
|
*/
|
|
import { useEffect, useRef, useState } from 'react'
|
|
import { Alert, AppState } from 'react-native'
|
|
import { API_BASE } from '../constants/Config'
|
|
import { authFetch } from '../utils/auth'
|
|
|
|
interface Anomaly {
|
|
id: number | string
|
|
title?: string
|
|
metric?: string
|
|
server?: string
|
|
severity?: string
|
|
detected_at?: string
|
|
}
|
|
|
|
const POLL_MS = 60_000
|
|
|
|
export function useAnomalyAlert(enabled = true) {
|
|
const seen = useRef<Set<string>>(new Set())
|
|
const [latest, setLatest] = useState<Anomaly[]>([])
|
|
|
|
useEffect(() => {
|
|
if (!enabled) return
|
|
let alive = true
|
|
|
|
async function poll() {
|
|
if (AppState.currentState !== 'active') return
|
|
try {
|
|
const res = await authFetch(`${API_BASE}/api/metrics/anomalies?severity=high`)
|
|
if (!res.ok) return
|
|
const d = await res.json()
|
|
const list: Anomaly[] = Array.isArray(d) ? d : d.items ?? d.anomalies ?? []
|
|
if (!alive) return
|
|
setLatest(list)
|
|
|
|
const fresh = list.filter(a => !seen.current.has(String(a.id)))
|
|
fresh.forEach(a => seen.current.add(String(a.id)))
|
|
|
|
if (fresh.length > 0) {
|
|
const first = fresh[0]
|
|
const more = fresh.length > 1 ? ` 외 ${fresh.length - 1}건` : ''
|
|
Alert.alert(
|
|
'⚠️ 이상 징후 감지',
|
|
`${first.title ?? first.metric ?? '메트릭 이상'}${first.server ? ` (${first.server})` : ''}${more}`,
|
|
)
|
|
}
|
|
} catch {
|
|
/* 폴링 실패 무시 */
|
|
}
|
|
}
|
|
|
|
poll()
|
|
const id = setInterval(poll, POLL_MS)
|
|
return () => {
|
|
alive = false
|
|
clearInterval(id)
|
|
}
|
|
}, [enabled])
|
|
|
|
return { latest }
|
|
}
|
|
|
|
export default useAnomalyAlert
|