45 lines
2.5 KiB
TypeScript
45 lines
2.5 KiB
TypeScript
"use client"
|
|
|
|
import {useCallback,useEffect,useRef,useState} from "react"
|
|
import {can} from "@/lib/auth"
|
|
import {getAlertsBgpTargets,getAlertsServerTargets,getAlertsGreTargets} from "@/shared/api/alerts-targets"
|
|
|
|
type Snapshot<T>={data:T|undefined;loading:boolean;error:string|null;receivedAt:string|null}
|
|
type Reader<T>=(base:string,options:{signal:AbortSignal})=>Promise<T>
|
|
|
|
// Each catalog owns its lifetime and revision. The page is keyed by source.
|
|
function useCatalog<T>(base:string,live:boolean,permission:string,read:Reader<T>){
|
|
const [snapshot,setSnapshot]=useState<Snapshot<T>>({data:undefined,loading:false,error:null,receivedAt:null})
|
|
const active=useRef(false),revision=useRef(0),controller=useRef<AbortController|null>(null)
|
|
const refresh=useCallback(async()=>{
|
|
if(!active.current||!live)return
|
|
controller.current?.abort();const requestRevision=++revision.current
|
|
if(!can(permission)){setSnapshot({data:undefined,loading:false,error:"Нет доступа к каталогу",receivedAt:null});return}
|
|
const request=new AbortController();controller.current=request
|
|
setSnapshot(previous=>({...previous,loading:true,error:null}))
|
|
const current=()=>active.current&&requestRevision===revision.current&&can(permission)
|
|
try{
|
|
const data=await read(base,{signal:request.signal})
|
|
if(current())setSnapshot({data,loading:false,error:null,receivedAt:new Date().toISOString()})
|
|
}catch(error){
|
|
if(current())setSnapshot(previous=>({...previous,loading:false,error:error instanceof Error?error.message:"Не удалось получить каталог"}))
|
|
}
|
|
},[base,live,permission,read])
|
|
useEffect(()=>{const generation=revision;active.current=true;let pending=true;queueMicrotask(()=>{if(pending)void refresh()});return()=>{pending=false;active.current=false;generation.current++;controller.current?.abort()}},[refresh])
|
|
return {...snapshot,refresh}
|
|
}
|
|
|
|
async function readGre(base:string,options:{signal:AbortSignal}){
|
|
const result=await getAlertsGreTargets(base,options)
|
|
if(result.failures?.length)throw Error("Неполный каталог GRE: не ответили устройства — "+result.failures.map(f=>f.serverName||f.serverId).join(", "))
|
|
return result
|
|
}
|
|
|
|
export function useAlertsTargets(base:string,live:boolean){
|
|
const bgp=useCatalog(base,live,"mm:bgp:read",getAlertsBgpTargets)
|
|
const servers=useCatalog(base,live,"mm:servers:read",getAlertsServerTargets)
|
|
const gre=useCatalog(base,live,"mm:filters:read",readGre)
|
|
const refresh=()=>{void bgp.refresh();void servers.refresh();void gre.refresh()}
|
|
return {bgp,servers,gre,refresh}
|
|
}
|