Init 2
This commit is contained in:
1 parent
ce00c4c671
commit
bdb9b72fac
66 files changed
+9553
-1547
No files matched your search
@@ -0,0 +1,223 @@
|
||||
"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<void>
|
||||
saveSettings: (patch: EvoBgpSavePayload) => Promise<void>
|
||||
refresh: () => Promise<void>
|
||||
testConnection: (draft?: EvoBgpTestDraft) => Promise<{ ok: boolean; message: string }>
|
||||
}
|
||||
|
||||
const EvoBgpContext = createContext<EvoBgpContextValue | null>(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<EvoBgpCatalogSnapshot | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(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 <EvoBgpContext.Provider value={value}>{children}</EvoBgpContext.Provider>
|
||||
}
|
||||
|
||||
export function useEvoBGP(): EvoBgpContextValue {
|
||||
const ctx = useContext(EvoBgpContext)
|
||||
if (!ctx) throw new Error("useEvoBGP must be used within EvoBGPProvider")
|
||||
return ctx
|
||||
}
|
||||
Reference in new issue
Block a user