3x-ui/frontend/src/api/queries/useStatusQuery.ts

34 lines
926 B
TypeScript
Raw Normal View History

feat(frontend): TanStack Query + React Router migration & in-panel API docs (#4541) * feat(frontend): introduce TanStack Query with status polling Wires @tanstack/react-query into every entry and migrates useStatus to useStatusQuery as the foundation for the multi-page MPA → SPA migration. - QueryProvider wraps each entry inside ThemeProvider, with devtools gated on import.meta.env.DEV - Shared queryClient: 30s staleTime, refetchOnWindowFocus, 1 retry - useStatusQuery preserves the { status, fetched, refresh } shape so IndexPage swaps in without further changes - refetchIntervalInBackground:false stops the 2s status poll when the panel tab is hidden, cutting idle traffic against the server * feat(frontend): collapse panel pages into a single React Router SPA Replaces the 7-entry MPA shell (index/clients/inbounds/nodes/settings/ xray/api-docs HTML files) with one main.tsx + createBrowserRouter. The Go backend now serves the same index.html for every authenticated panel route; React Router reads the URL and mounts the page from cache on subsequent navigation — no more full reloads between tabs. Frontend - main.tsx: single bootstrap (setupAxios, i18n, ThemeProvider, QueryProvider, RouterProvider) replacing 7 near-duplicate entries - routes.tsx: declarative router with lazy()-loaded pages, basename derived from window.X_UI_BASE_PATH so panels at /secret/panel work - layouts/PanelLayout.tsx: shell mount-point for the WS → queryClient bridge so connection survives navigation - api/websocketBridge.ts: subscribes the singleton WebSocketClient to queryClient and dispatches invalidate/outbounds events to cached queries (page-level useWebSocket handlers stay until Phase 3 hooks migrate) - AppSidebar: navigates via useNavigate + useLocation instead of window.location.href; drops basePath/requestUri props - Pages: drop the unused basePath/requestUri locals exposed only for the old sidebar Build - vite.config: 9 rollup inputs → 3 (index, login, subpage). Dev proxy bypass collapses /panel/* to index.html and skips API prefixes - vendor-tanstack + vendor-router chunks added to manualChunks Backend - xui.go: 7 per-page HTML handlers → one panelSPA handler serving index.html for /, /inbounds, /clients, /nodes, /settings, /xray, /api-docs. The /panel/api, /panel/setting, /panel/xray sub-routers are untouched * feat(frontend): migrate useNodes to TanStack Query Splits the hand-rolled useNodes hook into useNodesQuery (server data + NodeRecord type + derived totals) and useNodeMutations (add/update/del/ setEnable/probe/test). Mutations invalidate ['nodes'] on success, so the list refreshes without each call awaiting a manual refresh(). NodesPage drops useWebSocket({ nodes: applyNodesEvent }) — the WebSocket → query bridge now forwards the 'nodes' push to setQueryData(['nodes', 'list']) once at the SPA root. InboundsPage and the inbound form/list components import NodeRecord from its new home next to the query hook. * feat(frontend): migrate useAllSetting to TanStack Query Replaces the hand-rolled fetch + dirty-tracking hook with useAllSettings backed by useQuery + useMutation. The draft (current edits) is kept in local state and reset whenever query.data lands. saveAll posts the draft via a mutation; on success, invalidating ['settings'] refetches and the useEffect resets the draft so saveDisabled flips back to true. staleTime: Infinity prevents refetchOnWindowFocus from clobbering in-flight edits — settings only change in response to this user's own save. setSpinning stays as a pass-through to a local flag so the existing restartPanel flow in SettingsPage keeps showing its spinner. * feat(frontend): route useInbounds fetches through TanStack Query Rewrites useInbounds so its four server fetches (slim list, default settings, online clients, last-online map) live in useQuery with staleTime: Infinity. The in-place WS merge logic for traffic and client_stats is preserved — applyTrafficEvent / applyClientStatsEvent still mutate the locally-mirrored dbInbounds so the panel doesn't refetch every 1-2 seconds when stats stream in. refresh() becomes a thin invalidateQueries on the three list keys, which mutations in the page already call after add/edit/del. The bridge now forwards the WebSocket 'inbounds' push to setQueryData(['inbounds', 'slim']), and InboundsPage drops its useEffect(fetchDefaultSettings → refresh) plus the invalidate / inbounds wiring on useWebSocket — both are owned by the bridge now. * feat(frontend): migrate useClients to TanStack Query Replaces 12 hand-rolled mutation callbacks and a tangle of useState + useRef + useEffect with one useQuery (paged list) + nine useMutation wrappers. The list query uses keepPreviousData so paging/filter changes don't blank the table mid-fetch. The setQuery shallow-compare logic is preserved for backward compatibility with ClientsPage's effect that rebuilds the params on every render. Internally setQuery only updates state when the params actually differ — Query's queryKey equality handles the rest. WS-driven applyTrafficEvent / applyClientStatsEvent now mutate the query cache via setQueryData(['clients', 'list', currentParams]) so per-second stats updates skip a full refetch. applyInvalidate is gone from the hook — the bridge owns coarse 'clients' invalidation. ClientsPage drops the invalidate handler from its useWebSocket subscription; auxiliary queries (inboundOptions, defaults, onlines) load via TanStack Query and are shared with useInbounds via the same query keys. * feat(frontend): route useXraySetting fetches through TanStack Query Keeps the bidirectional xraySetting ↔ templateSettings editor sync and the 1s dirty-tracking interval intact (those are local editor state, not server data). All seven server calls move: - config + traffic → useQuery on ['xray', 'config'] and ['xray', 'outboundsTraffic'] - saveAll → useMutation that invalidates the config query - resetOutboundsTraffic → useMutation that invalidates the traffic query - restartXray → useMutation (fires the restart, then reads the result string) - resetToDefault → useMutation (fetch default config, push it into the editor via setTemplateSettings) The WebSocket 'outbounds' event already lands in keys.xray.outboundsTraffic() via the bridge, so XrayPage drops its useWebSocket({ outbounds: applyOutboundsEvent }) wiring entirely and the hook no longer exposes applyOutboundsEvent. A useEffect seeds xraySetting / templateSettings / tags / test URL from query data on first fetch and on every refetch, mirroring what the original fetchAll() did. * fix(frontend): restore per-route document titles in the SPA When the multi-entry MPA collapsed into a single index.html, every route inherited the static <title>3X-UI</title> from the shared shell, so every panel page showed "hostname - 3X-UI" instead of the original "hostname - Overview / Clients / Inbounds / ...". usePageTitle reads the current pathname and rewrites document.title on every navigation, matching the titles the deleted *.html files used to carry. Mounted in PanelLayout so it covers all panel routes without each page having to opt in. The startup applyDocumentTitle() call in main.tsx is gone — the hook sets the full "hostname - PageTitle" string itself. * feat(api-docs): expose OpenAPI spec + render Swagger UI in panel Replaces the hand-rolled API docs UI with industry-standard tooling so external integrations (Postman, Insomnia, openapi-generator) can consume the panel API without parsing endpoints.js by hand. Generator - frontend/scripts/build-openapi.mjs: walks the existing endpoints.js (still the single source of truth) and emits an OpenAPI 3.0.3 spec at frontend/public/openapi.json. Handles Gin :param → {param} path translation, body / query / path parameter splits, 200 + error response examples, and Bearer + cookie security schemes - npm run build now runs gen:api before vite build, so the spec is always in sync with what's documented Backend - web/controller/dist.go exposes ServeOpenAPISpec which streams the embedded dist/openapi.json with a short Cache-Control. Public endpoint (no auth) so Postman can fetch it without first logging in - web/web.go wires GET /panel/api/openapi.json before the auth-gated /panel/api router Panel - ApiDocsPage now renders swagger-ui-react fed by the basePath-aware openapi.json URL. Dark mode is overridden via CSS targeting the Swagger UI internals - CodeBlock / EndpointRow / EndpointSection are gone; the swagger-ui vendor chunk (134 KB gzipped) only loads on this lazy route, not on every panel page - vite.config: vendor-swagger manualChunk keeps the new dep out of the main vendor bundle For Postman: import http://<panel>/panel/api/openapi.json. Everything from /login + /panel/api/* shows up with auth, params, and examples. * style(api-docs): dark/ultra theme for Swagger UI Override every visual surface Swagger does not theme on its own: opblocks, tables, model boxes, form inputs, code blocks, modals, Servers dropdown, per-endpoint padlocks and expand chevrons. Replaces Swagger's default light-arrow chevron on selects with a light-fill SVG positioned at the corner so the dark background-color is visible. Also disables deepLinking to silence the noisy v4 underscore warning; not used in our panel.
2026-05-24 19:34:52 +00:00
import { useQuery } from '@tanstack/react-query';
import { useMemo } from 'react';
import { HttpUtil } from '@/utils';
import { Status } from '@/models/status';
import { keys } from '@/api/queryKeys';
const POLL_INTERVAL_MS = 2000;
async function fetchStatus(): Promise<Status> {
const msg = await HttpUtil.get('/panel/api/server/status', undefined, { silent: true });
if (!msg?.success) throw new Error(msg?.msg || 'Failed to fetch status');
return new Status(msg.obj);
}
export function useStatusQuery() {
const query = useQuery({
queryKey: keys.server.status(),
queryFn: fetchStatus,
refetchInterval: POLL_INTERVAL_MS,
refetchIntervalInBackground: false,
staleTime: 0,
});
const status = useMemo(() => query.data ?? new Status(), [query.data]);
const refresh = async () => { await query.refetch(); };
return {
status,
fetched: query.data !== undefined,
refresh,
};
}