"use client" import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react" import { useDataSource } from "@/lib/data-source" import type { Domain, IpRange, Asn } from "@/lib/data" export interface EvoBgpCommunityRow { id: string value: string name: string description: string type: "standard" | "no-export" | "no-advertise" | "local-as" | "custom" filterIds: string[] serverCount: number prefixCount: number action: "permit" | "deny" | "local-pref" | "metric" actionValue?: number enabled: boolean } export interface EvoBgpCatalogSnapshot { fetchedAt: string modules: { id: string; name: string; type: string }[] domains: Domain[] ipRanges: IpRange[] asns: Asn[] communities: EvoBgpCommunityRow[] } export interface EvoBgpSettingsDto { baseUrl: string enabled: boolean secretConfigured: boolean } /** undefined — не менять ключ; null или очистка через отдельное сохранение */ export type EvoBgpSavePayload = { baseUrl?: string enabled?: boolean apiKey?: string | null } /** Черновик для POST /evobgp/test: пустой объект — всё из БД */ export type EvoBgpTestDraft = { baseUrl?: string; apiKey?: string } interface EvoBgpContextValue { baseUrl: string enabled: boolean secretConfigured: boolean settingsLoaded: boolean snapshot: EvoBgpCatalogSnapshot | null loading: boolean error: string | null loadSettings: () => Promise saveSettings: (patch: EvoBgpSavePayload) => Promise refresh: () => Promise testConnection: (draft?: EvoBgpTestDraft) => Promise<{ ok: boolean; message: string }> } const EvoBgpContext = createContext(null) export function EvoBGPProvider({ children }: { children: React.ReactNode }) { const { mode, backendUrl, backendStatus } = useDataSource() const [baseUrl, setBaseUrlState] = useState("") const [enabled, setEnabledState] = useState(false) const [secretConfigured, setSecretConfigured] = useState(false) const [settingsLoaded, setSettingsLoaded] = useState(false) const [snapshot, setSnapshot] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const pullCatalog = useCallback( async (catalogEnabled: boolean) => { if (mode !== "live" || backendStatus !== true || !catalogEnabled) { setSnapshot(null) setError(null) return } setLoading(true) setError(null) try { const res = await fetch(`${backendUrl.replace(/\/$/, "")}/api/evobgp/catalog`, { method: "POST", }) const text = await res.text() if (!res.ok) { let msg = res.statusText try { const j = JSON.parse(text) as { error?: string; detail?: string } msg = j.error ?? j.detail ?? msg } catch { if (text) msg = text } throw new Error(msg || "Ошибка EvoBGP") } setSnapshot(JSON.parse(text) as EvoBgpCatalogSnapshot) } catch (e) { setSnapshot(null) setError(e instanceof Error ? e.message : "Ошибка загрузки") } finally { setLoading(false) } }, [mode, backendStatus, backendUrl], ) const loadSettings = useCallback(async () => { if (mode !== "live" || backendStatus !== true) { setSettingsLoaded(false) setSnapshot(null) setError(null) return } try { const res = await fetch(`${backendUrl.replace(/\/$/, "")}/api/evobgp/settings`) if (!res.ok) throw new Error(await res.text()) const data = (await res.json()) as EvoBgpSettingsDto setBaseUrlState(data.baseUrl ?? "") setEnabledState(data.enabled ?? false) setSecretConfigured(data.secretConfigured ?? false) setSettingsLoaded(true) await pullCatalog(data.enabled ?? false) } catch { setSettingsLoaded(true) } }, [mode, backendStatus, backendUrl, pullCatalog]) useEffect(() => { queueMicrotask(() => { void loadSettings() }) }, [loadSettings]) const saveSettings = useCallback( async (patch: EvoBgpSavePayload) => { const res = await fetch(`${backendUrl.replace(/\/$/, "")}/api/evobgp/settings`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(patch), }) const text = await res.text() if (!res.ok) { let msg = res.statusText try { const j = JSON.parse(text) as { error?: string } msg = j.error ?? msg } catch { if (text) msg = text } throw new Error(msg || "Не удалось сохранить") } const data = JSON.parse(text) as EvoBgpSettingsDto setBaseUrlState(data.baseUrl ?? "") setEnabledState(data.enabled ?? false) setSecretConfigured(data.secretConfigured ?? false) await pullCatalog(data.enabled ?? false) }, [backendUrl, pullCatalog], ) const refresh = useCallback(async () => { await pullCatalog(enabled) }, [enabled, pullCatalog]) const testConnection = useCallback(async (draft?: EvoBgpTestDraft) => { try { const res = await fetch(`${backendUrl.replace(/\/$/, "")}/api/evobgp/test`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(draft ?? {}), }) const data = (await res.json().catch(() => ({}))) as { ok?: boolean; error?: string } if (!res.ok) throw new Error(data.error ?? res.statusText) return { ok: true, message: "Соединение с EvoBGP установлено" } } catch (e) { return { ok: false, message: e instanceof Error ? e.message : "Ошибка" } } }, [backendUrl]) const value = useMemo( () => ({ baseUrl, enabled, secretConfigured, settingsLoaded, snapshot, loading, error, loadSettings, saveSettings, refresh, testConnection, }), [ baseUrl, enabled, secretConfigured, settingsLoaded, snapshot, loading, error, loadSettings, saveSettings, refresh, testConnection, ], ) return {children} } export function useEvoBGP(): EvoBgpContextValue { const ctx = useContext(EvoBgpContext) if (!ctx) throw new Error("useEvoBGP must be used within EvoBGPProvider") return ctx }