fix(ui): изолировать операции восстановления Filters

This commit is contained in:
Denozordec committed 2026-09-30 11:42:57 +07:00
1 parent 7554f1804b
commit 1fba16e1f2
5 files changed
+145 -15

No files matched your search

+23 -13
View File
@@ -1426,12 +1426,14 @@ export default function FiltersPage() {
}, [isLive, apiFetch])
const loadLiveRules = useCallback(async (serverId: string) => {
if (!isLive || !serverId) return
if (!isLive || !serverId) return false
try {
const fresh = await apiFetch<LiveFiltersResponse>(
`/api/filters/rules?serverId=${encodeURIComponent(serverId)}`,
)
const liveRules = fresh.rulesets.find((r) => r.serverId === serverId)?.rules ?? fresh.rulesets[0]?.rules ?? []
const ruleset = fresh.rulesets.find(r => r.serverId === serverId)
if (!ruleset || fresh.stale) throw new Error("Не удалось получить актуальные правила выбранного устройства")
const liveRules = ruleset.rules
setRulesets((prev) => {
const has = prev.some((rs) => rs.serverId === serverId)
if (!has) return [...prev, { serverId, rules: liveRules }]
@@ -1441,9 +1443,11 @@ export default function FiltersPage() {
if (fresh.greTunnels?.length) {
setGreByServer((prev) => ({ ...prev, [serverId]: fresh.greTunnels }))
}
return true
} catch (err) {
setLiveStale(true)
toast.error("Не удалось прочитать правила с роутера", { description: String(err) })
return false
}
}, [isLive, apiFetch])
@@ -1540,7 +1544,8 @@ export default function FiltersPage() {
}, [isLive, apiFetch, applyBusy, historyRestoring, liveStale, liveLoadState])
const refreshFromRouter = useCallback(async () => {
if (!isLive || !selectedServerId || applyBusy) return
if (!isLive || !selectedServerId || applyLock.current || applyBusy || historyRestoring) return
applyLock.current = true
setApplyBusy(true)
try {
await loadLiveRules(selectedServerId)
@@ -1549,9 +1554,10 @@ export default function FiltersPage() {
ensureRecursiveRoutes(selectedServerId),
])
} finally {
applyLock.current = false
setApplyBusy(false)
}
}, [isLive, selectedServerId, applyBusy, loadLiveRules, ensureGreTunnels, ensureRecursiveRoutes])
}, [isLive, selectedServerId, applyBusy, historyRestoring, loadLiveRules, ensureGreTunnels, ensureRecursiveRoutes])
useEffect(() => () => { historyEpoch.current += 1 }, [selectedServerId, backendUrl, isLive])
@@ -1575,7 +1581,10 @@ export default function FiltersPage() {
}, [isLive, selectedServerId, apiFetch])
const restoreRevision = useCallback(async (id: string) => {
if (!isLive || !selectedServerId) return
if (!isLive || !selectedServerId) throw new Error("Выберите устройство в режиме живых данных")
if (applyLock.current || applyBusy || historyRestoring) throw new Error("Дождитесь завершения текущей операции")
applyLock.current = true
setApplyBusy(true)
setHistoryRestoring(true)
try {
const res = await apiFetch<{ ok: boolean; rules?: FilterRule[] }>(
@@ -1586,17 +1595,17 @@ export default function FiltersPage() {
if (res.rules) {
setRulesets((p) => p.map((rs) => rs.serverId === selectedServerId ? { ...rs, rules: res.rules ?? [] } : rs))
} else {
await loadLiveRules(selectedServerId)
if (!await loadLiveRules(selectedServerId)) throw new Error("Версия применена, но актуальные правила не прочитаны — обновите данные")
}
setLiveStale(false)
toast.success("Версия применена на роутер")
await loadRevisions()
} catch (err) {
throw err
toast.success("Версия применена на роутер")
} finally {
applyLock.current = false
setApplyBusy(false)
setHistoryRestoring(false)
}
}, [isLive, selectedServerId, apiFetch, loadLiveRules, loadRevisions])
}, [isLive, selectedServerId, apiFetch, applyBusy, historyRestoring, loadLiveRules, loadRevisions])
const updateRules = useCallback((serverId: string, updater: (rules: FilterRule[]) => FilterRule[]) => {
const current = rulesets.find((rs) => rs.serverId === serverId)?.rules ?? []
@@ -1738,7 +1747,7 @@ export default function FiltersPage() {
variant="outline"
size="sm"
onClick={() => void refreshFromRouter()}
disabled={applyBusy}
disabled={applyBusy || historyRestoring}
title="Прочитать актуальные правила с роутера"
>
<RefreshCwIcon className={cn("size-4", applyBusy && "animate-spin")} />
@@ -1748,10 +1757,11 @@ export default function FiltersPage() {
variant="outline"
size="sm"
onClick={() => {
if (applyLock.current) return
setHistoryOpen(true)
void loadRevisions()
}}
disabled={applyBusy}
disabled={applyBusy || historyRestoring}
title="История версий и откат на CHR"
>
<HistoryIcon className="size-4" />
@@ -2000,7 +2010,7 @@ export default function FiltersPage() {
open={historyOpen}
onOpenChange={v => { if (!v) historyEpoch.current += 1; setHistoryOpen(v) }}
loadError={historyError}
onRetry={() => { void loadRevisions() }}
onRetry={() => { if (!applyLock.current) void loadRevisions() }}
title="История фильтров"
itemLabel="правил"
revisions={revisions}