fix(alerts): отличать неизвестные данные от пустого списка правил

This commit is contained in:
Denozordec committed 2026-10-03 17:30:46 +07:00
1 parent 9bc3a5db77
commit 3710fc6aab
5 files changed
+54 -29

No files matched your search

+35 -27
View File
@@ -39,6 +39,7 @@ import {
} from "lucide-react"
import { Flag, countryName } from "@/components/flag"
import { cn } from "@/lib/utils"
import {getAlerts} from "@/shared/api/alerts"
import { requestJson } from "@/shared/api/http-client"
import { toast } from "sonner"
@@ -2351,18 +2352,25 @@ function AddRuleSheet({
type SeverityFilter = AlertSeverity | "all"
export default function AlertsPage() {
export default function AlertsPage(){const {mode,backendUrl}=useDataSource();return <AlertsContent key={JSON.stringify([mode,backendUrl])}/>}
function AlertsContent() {
const { mode, backendUrl, backendStatus } = useDataSource()
const isLive = mode === "live"
const apiFetch = useMemo(() => makeApiFetch(backendUrl), [backendUrl])
const [rules, setRules] = useState<AlertRule[]>(INIT_RULES)
const [groups, setGroups] = useState<AlertGroup[]>(INIT_GROUPS)
const [history, setHistory] = useState<HistoryEntry[]>(INIT_HISTORY)
const [tg, setTg] = useState<TelegramConfig>(INIT_TG)
const [rules, setRules] = useState<AlertRule[]>(isLive?[]:INIT_RULES)
const [groups, setGroups] = useState<AlertGroup[]>(isLive?[]:INIT_GROUPS)
const [history, setHistory] = useState<HistoryEntry[]>(isLive?[]:INIT_HISTORY)
const [tg, setTg] = useState<TelegramConfig>(isLive?{token:"",chatId:"",messageThreadId:"",connected:false}:INIT_TG)
const [tokenConfigured, setTokenConfigured] = useState(false)
const [meta, setMeta] = useState<AlertsMeta | null>(null)
const [known,setKnown]=useState(!isLive)
const [receivedAt,setReceivedAt]=useState<string|null>(null)
const pageActive=useRef(true),reader=useRef<AbortController|null>(null),revision=useRef(0)
useEffect(()=>{pageActive.current=true;return()=>{pageActive.current=false;reader.current?.abort()}},[])
const [sheetOpen, setSheetOpen] = useState(false)
const [pendingPresetReplaceId, setPendingPresetReplaceId] = useState<string | null>(null)
const [sheetSnap, setSheetSnap] = useState<RuleFormSnapshot | null>(null)
@@ -2407,6 +2415,7 @@ export default function AlertsPage() {
}, [isLive, meta])
const load = useCallback(async () => {
if(!pageActive.current)return
if (!isLive) {
setRules(INIT_RULES)
setGroups(INIT_GROUPS)
@@ -2417,21 +2426,15 @@ export default function AlertsPage() {
setLoadError(null)
return
}
reader.current?.abort()
const controller=new AbortController();reader.current=controller
const requestRevision=++revision.current
setLoadBusy(true)
setLoadError(null)
try {
const data = await apiFetch<{
telegram: {
chatId: string
tokenConfigured: boolean
connected: boolean
messageThreadId: number | null
}
groups?: AlertsApiGroup[]
rules: AlertsApiRule[]
history: AlertsApiHistoryEntry[]
meta: AlertsMeta
}>("/api/alerts")
const data = await getAlerts(backendUrl,{signal:controller.signal})
if(!pageActive.current||requestRevision!==revision.current)return
setKnown(true);setReceivedAt(new Date().toISOString())
setTokenConfigured(data.telegram.tokenConfigured)
setTg({
token: "",
@@ -2445,7 +2448,7 @@ export default function AlertsPage() {
setHistory(mapApiHistory(data.history))
let mergedMeta: AlertsMeta = data.meta
try {
const bgpRows = await apiFetch<BgpSessionListRow[]>("/api/bgp/sessions")
const bgpRows = await apiFetch<BgpSessionListRow[]>("/api/bgp/sessions",{signal:controller.signal})
const bgpPeers = uniqStrings(
bgpRows.map((s) => `${String(s.serverName ?? "").trim()}: ${String(s.name ?? "").trim()}`.trim()),
)
@@ -2457,7 +2460,7 @@ export default function AlertsPage() {
const [srvList, greRes] = await Promise.all([
apiFetch<
Array<{ id: number; name: string; site?: string; country?: string; host?: string }>
>("/api/servers"),
>("/api/servers",{signal:controller.signal}),
apiFetch<{
tunnels: Array<{
id?: string
@@ -2467,7 +2470,7 @@ export default function AlertsPage() {
status?: string
comment?: string
}>
}>("/api/filters/gre-tunnels"),
}>("/api/filters/gre-tunnels",{signal:controller.signal}),
])
const sidToSrv = new Map(
srvList.map((s) => [
@@ -2504,18 +2507,20 @@ export default function AlertsPage() {
} catch {
mergedMeta = { ...mergedMeta, greTunnelTargets: [], greTunnelDetail: [] }
}
setMeta(mergedMeta)
if(pageActive.current&&requestRevision===revision.current)setMeta(mergedMeta)
} catch (e) {
setLoadError(e instanceof Error ? e.message : "Ошибка загрузки")
if(pageActive.current&&requestRevision===revision.current)setLoadError(e instanceof Error ? e.message : "Ошибка загрузки")
} finally {
setLoadBusy(false)
if(pageActive.current&&requestRevision===revision.current)setLoadBusy(false)
}
}, [apiFetch, isLive])
}, [apiFetch, isLive, backendUrl])
useEffect(() => {
let current=true
queueMicrotask(() => {
void load()
if(current)void load()
})
return()=>{current=false}
}, [load])
const flushAlertsConfig = useCallback(async (nextRules: AlertRule[], nextGroups: AlertGroup[]) => {
@@ -2637,7 +2642,7 @@ export default function AlertsPage() {
)
const serverNamesForPresets = useMemo(() => {
if (isLive && meta?.servers?.length) return meta.servers
if (isLive) return meta?.servers??[]
return TYPE_TARGETS.server
}, [isLive, meta])
@@ -2811,7 +2816,7 @@ export default function AlertsPage() {
{loadBusy ? <LoaderCircleIcon className="size-4 animate-spin" /> : <RefreshCwIcon className="size-4" />}
Обновить
</Button>
<Button size="sm" onClick={openRuleSheetAdd} disabled={isLive && backendStatus === false}>
<Button size="sm" onClick={openRuleSheetAdd} disabled={!known || (isLive && backendStatus !== true)}>
<PlusIcon className="size-4" />Добавить правило
</Button>
</>
@@ -2839,6 +2844,8 @@ export default function AlertsPage() {
</p>
)}
{isLive?<p role="status" className="text-xs text-muted-foreground">{known?"Последние данные получены: "+new Date(receivedAt!).toLocaleString("ru-RU"):loadBusy?"Загрузка оповещений…":"Данные оповещений ещё не получены."}</p>:null}
{known?<>
<Collapsible open={hintsExpanded} onOpenChange={setHintsExpanded} className="rounded-lg border border-dashed border-border bg-muted/15">
<CollapsibleTrigger className="flex w-full items-center gap-2 px-3 py-2 text-left text-xs font-medium text-foreground hover:bg-muted/40 rounded-lg">
<ChevronDownIcon className={cn("size-4 shrink-0 transition-transform", hintsExpanded && "rotate-180")} />
@@ -3147,6 +3154,7 @@ export default function AlertsPage() {
<HistoryCard entries={history} />
</div>
</div>
</>:null}
</div>
</div>