fix(alerts): отличать неизвестные данные от пустого списка правил
This commit is contained in:
1 parent
9bc3a5db77
commit
3710fc6aab
5 files changed
+54
-29
No files matched your search
+35
-27
@@ -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>
|
||||
|
||||
|
||||
Reference in new issue
Block a user