Files
MikrotikManager/components/probes-speed-schedule.tsx

42 lines
4.4 KiB
TypeScript

"use client"
import Link from "next/link"
import {useCallback,useEffect,useRef,useState} from "react"
import {Button} from "@/components/ui/button"
import {DataPageCard} from "@/components/data-page-card"
import {ProbesSpeedProbesDataGrid} from "@/components/data-grids/probes-speed-probes-data-grid"
import {can,ensureAuthConfig} from "@/lib/auth"
import {readProbeSchedule,type SpeedProbeApiRow} from "@/lib/probe-schedule-response"
import type {Server} from "@/lib/data"
import {requestJson} from "@/shared/api/http-client"
type Snapshot={rows:SpeedProbeApiRow[]|undefined;loading:boolean;error:string|null;receivedAt:string|null}
export function ProbesSpeedSchedule({base,serversForName}:{base:string;serversForName:Server[]}){
const [state,setState]=useState<Snapshot>({rows:undefined,loading:true,error:null,receivedAt:null})
const active=useRef(false),revision=useRef(0),request=useRef<AbortController|null>(null),timer=useRef<ReturnType<typeof setTimeout>|null>(null)
const refresh=useCallback(async()=>{
if(!active.current||request.current)return
const id=++revision.current,controller=new AbortController();request.current=controller
const current=()=>active.current&&revision.current===id&&!controller.signal.aborted
setState(previous=>({...previous,loading:true,error:null}))
timer.current=setTimeout(()=>{if(current()){revision.current++;controller.abort();request.current=null;setState(previous=>({...previous,loading:false,error:"Время ожидания проб скорости истекло. Повторите обновление."}))}},30000)
try{
await ensureAuthConfig();if(!current())return
if(!can("mm:uptime:read")){setState({rows:undefined,loading:false,error:"Нет права читать пробы скорости.",receivedAt:null});return}
const rows=readProbeSchedule(await requestJson<unknown>(base,"/api/uptime/speed-probes",{signal:controller.signal,cache:"no-store"}))
if(current()){
if(!can("mm:uptime:read")){setState({rows:undefined,loading:false,error:"Нет права читать пробы скорости.",receivedAt:null});return}
setState({rows,loading:false,error:null,receivedAt:new Date().toISOString()})
}
}catch(error){if(current())setState(previous=>({...previous,loading:false,error:error instanceof Error?error.message:"Не удалось прочитать пробы скорости."}))}
finally{if(revision.current===id){if(timer.current)clearTimeout(timer.current);timer.current=null;request.current=null}}
},[base])
useEffect(()=>{const generation=revision;active.current=true;let pending=true;queueMicrotask(()=>{if(pending)void refresh()});return()=>{pending=false;active.current=false;generation.current++;request.current?.abort();request.current=null;if(timer.current)clearTimeout(timer.current)}},[refresh])
return <section aria-label="Пробы скорости" className="min-w-0 space-y-3">
<div className="flex flex-wrap items-center justify-between gap-2"><p role="status" className="text-xs text-muted-foreground">{state.loading?"Обновление проб скорости…":state.receivedAt?"Последние данные получены: "+new Date(state.receivedAt).toLocaleString("ru-RU"):"Данные проб скорости ещё не получены."}</p><Button type="button" size="sm" variant="outline" disabled={state.loading} onClick={()=>void refresh()}>Обновить пробы скорости</Button></div>
{state.error?<p role="alert" className="text-sm text-destructive">{state.error}{state.rows!==undefined?" Показан последний успешный список.":""}</p>:null}
{state.rows!==undefined?<DataPageCard><ProbesSpeedProbesDataGrid rows={state.rows} serverName={id=>serversForName.find(s=>s.id===id)?.name??id}/></DataPageCard>:null}
<p className="text-xs text-muted-foreground">Показаны пробы скорости коллектора. Периодичность запуска задаётся в сборе данных; отдельного расписания диагностических GRE-тестов в живом режиме нет.</p>
<div className="flex flex-wrap gap-3 text-sm"><Link className="underline underline-offset-4" href="/uptime">Настроить пробы в мониторинге</Link><Link className="underline underline-offset-4" href="/data-collection">Открыть сбор данных</Link></div>
</section>
}