diff --git a/app/(main)/recursive-routes/page.tsx b/app/(main)/recursive-routes/page.tsx index 7e757ae4..378940aa 100644 --- a/app/(main)/recursive-routes/page.tsx +++ b/app/(main)/recursive-routes/page.tsx @@ -33,7 +33,7 @@ interface BackendServer { id: number name: string host: string - type: "jump-host" | "exit-node" | "home-router" + type?: "jump-host" | "exit-node" | "home-router" site: string country: string asn: string @@ -72,7 +72,7 @@ function makeApiFetch(backendUrl: string) { } } -function mapBackendServer(s: BackendServer): Omit & { status: Server["status"] | null } { +function mapBackendServer(s: BackendServer): Omit & { status: Server["status"] | null; type?: Server["type"] } { return { id: String(s.id), name: s.name || s.host, @@ -138,7 +138,7 @@ function EndpointCountryField({ value, onChange }: { value: string; onChange: (v
- { const v = e.target.value.toUpperCase().slice(0, 3) setQuery(v) @@ -161,7 +161,7 @@ function EndpointCountryField({ value, onChange }: { value: string; onChange: (v )}
-
+
{visible.map(c => ( +
Основные - set("dstAddress", e.target.value)} /> + set("dstAddress", e.target.value)} />
@@ -251,35 +252,35 @@ function RouteSheet({ aria-label={`Удалить конечную точку ${idx + 1}`} disabled={form.endpoints.length <= 1} > - +
setEp(ep.id, "country", v)} /> - setEp(ep.id, "gateway", e.target.value)} /> + setEp(ep.id, "gateway", e.target.value)} />
- setEp(ep.id, "distance", Number(e.target.value))} /> + setEp(ep.id, "distance", Number(e.target.value))} /> - setEp(ep.id, "checkGateway", e.target.value)} /> + setEp(ep.id, "checkGateway", e.target.value)} />
- setEp(ep.id, "scope", e.target.value ? Number(e.target.value) : null)} /> + setEp(ep.id, "scope", e.target.value ? Number(e.target.value) : null)} /> - setEp(ep.id, "targetScope", e.target.value ? Number(e.target.value) : null)} /> + setEp(ep.id, "targetScope", e.target.value ? Number(e.target.value) : null)} />
-
+
{gateways.map((g) => { const value = `${g.ip}%${g.name}` const selected = ep.gateway === value @@ -299,7 +300,7 @@ function RouteSheet({ {g.name} {g.status === "active" ? "Активный маршрут" : g.status === "inactive" ? "Неактивный маршрут" : "Статус неизвестен"} - {selected && } + {selected &&
{g.ip} @@ -315,7 +316,7 @@ function RouteSheet({
))} {gateways.length === 0 && (
@@ -327,18 +328,18 @@ function RouteSheet({
Параметры - set("routingTable", e.target.value)} /> + set("routingTable", e.target.value)} /> - set("comment", e.target.value)} /> + set("comment", e.target.value)} />
{saveError &&

{saveError}

} {busy &&

Ожидание подтверждения роутера…

} - {error &&
{error}
} + {error &&
} - - + + @@ -349,10 +350,10 @@ function TypeChip({ type }: { type: "jump-host" | "exit-node" | "home-router" }) return ( {type === "jump-host" ? "JH" : type === "home-router" ? "HR" : "EN"} @@ -393,7 +394,7 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit const isLive = mode === "live" const apiFetch = useMemo(() => makeApiFetch(backendUrl), [backendUrl]) - const [servers, setServers] = useState & { status: Server["status"] | null }>>([]) + const [servers, setServers] = useState & { status: Server["status"] | null; type?: Server["type"] }>>([]) const [selectedServerId, setSelectedServerId] = useState(context.serverId ?? "") const [rows, setRows] = useState([]) const [busy, setBusy] = useState<"load" | "apply" | null>(null) @@ -444,7 +445,7 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit if (!active) return const parsed = recursiveServersSchema.safeParse(data) if (!parsed.success) throw new Error("Некорректный список устройств в ответе API") - const mapped = parsed.data.map(s => mapBackendServer({ ...s, type: s.type ?? "exit-node", site: s.site ?? "", asn: s.asn ?? "", status: s.status ?? null, latency: s.latency ?? null })) + const mapped = parsed.data.map(s => mapBackendServer({ ...s, type: s.type, site: s.site ?? "", asn: s.asn ?? "", status: s.status ?? null, latency: s.latency ?? null })) setServers(mapped) setSelectedServerId(context.serverId ?? mapped[0]?.id ?? "") if (!context.serverId && !context.error && mapped[0]) patchUrl({ serverId: mapped[0].id }, true) @@ -576,8 +577,16 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit function groupKeyOf(row: RecursiveRouteRow): string { return JSON.stringify([row.dstAddress.trim().toLowerCase(), row.routingTable.trim()]) } + const sheetTrigger = useRef(null) + function returnSheetFocus() { + const target = sheetTrigger.current + if (target?.isConnected) return target + const label = target?.getAttribute("aria-label") + return label ? document.querySelector(`button[aria-label="${CSS.escape(label)}"]`) : null + } function openCreate() { if (mutationsLocked) return + sheetTrigger.current = document.activeElement instanceof HTMLElement ? document.activeElement : null setSaveError(null) setSheetMode("create") setEditingGroupKey(null) @@ -586,6 +595,7 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit } function openEdit(group: RouteGroup) { if (mutationsLocked) return + sheetTrigger.current = document.activeElement instanceof HTMLElement ? document.activeElement : null setSaveError(null) const endpoints = rows.filter(row => groupKeyOf(row) === group.key) setSheetMode("edit") @@ -711,17 +721,17 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit {isLive && ( <> )} - } @@ -744,13 +754,13 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit banner={
- +
@@ -761,6 +771,7 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit

{!hasRouteSnapshot ? "Количество неизвестно" : filteredRows.length !== rows.length ? `${filteredRows.length} из ${rows.length} маршрутов` : `${rows.length} маршрутов`}

+ {busy === "load" &&

Обновление маршрутов…

} {sharedBusy && busy !== "apply" &&

Операция предыдущего контекста ещё выполняется. Изменения заблокированы до ответа API.

} {contextError &&

{contextError}. Выберите устройство или сбросьте ссылку.

} {expandedGroupKey && !liveStale && !groupedRoutes.some(group => group.key === expandedGroupKey) &&

Маршрут из ссылки отсутствует в текущем срезе.

} @@ -773,8 +784,8 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit {isLive && writeAt &&

Запись подтверждена {new Date(writeAt).toLocaleTimeString("ru-RU")}.

} {isLive && gatewayError &&

Шлюзы не загружены: {gatewayError}. Адрес можно ввести вручную.

} {isLive && liveStale && ( -
- +
+
)} @@ -790,17 +801,17 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit ) : ( {currentServer && ( -
+
{currentServer.status ? : Состояние неизвестно} - {currentServer.name} - - {currentServer.host} + {currentServer.name} + {currentServer.type && } + {currentServer.host} {currentServer.asn} {currentServer.latency !== null && ( 60 ? "text-amber-600 dark:text-amber-400" : "text-muted-foreground", + currentServer.latency > 60 ? "text-warning" : "text-muted-foreground", )}>{currentServer.latency}мс )}
@@ -824,9 +835,9 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit void applyRoutes(rows.filter((r) => groupKeyOf(r) !== g.key)) }} /> - @@ -834,6 +845,7 @@ function RecursiveRoutesContent({ mode, backendUrl, context, patchUrl, beginWrit distance: {ep.distance}

{ep.gateway}

-
+
scope: {ep.scope ?? "—"} t.scope: {ep.targetScope ?? "—"} check: {ep.checkGateway || "—"} @@ -147,17 +147,17 @@ function RecursiveRoutesDataGrid({ const g = row.original const isExpanded = expandedKey === g.key return ( -
+
+ ) }, meta: { @@ -194,7 +194,7 @@ function RecursiveRoutesDataGrid({ ? )} - + {ep.gateway}
@@ -251,19 +251,16 @@ function RecursiveRoutesDataGrid({ className="flex items-center gap-0.5 justify-end" onClick={(e) => e.stopPropagation()} > - -
@@ -297,7 +293,7 @@ function RecursiveRoutesDataGrid({ meta: { headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST }, }, ], - [confirmDeleteKey, expandedKey, onDelete, onEdit, mutationsDisabled], + [confirmDeleteKey, expandedKey, onDelete, onEdit, onExpandedChange, mutationsDisabled], ) const table = useTable({ @@ -313,7 +309,7 @@ function RecursiveRoutesDataGrid({ if (groups.length === 0) { return ( } + icon={