mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
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.
This commit is contained in:
parent
bbb7af65f6
commit
864315448e
4 changed files with 156 additions and 122 deletions
|
|
@ -9,5 +9,15 @@ export const keys = {
|
||||||
settings: {
|
settings: {
|
||||||
root: () => ['settings'] as const,
|
root: () => ['settings'] as const,
|
||||||
all: () => ['settings', 'all'] as const,
|
all: () => ['settings', 'all'] as const,
|
||||||
|
defaults: () => ['settings', 'defaults'] as const,
|
||||||
|
},
|
||||||
|
inbounds: {
|
||||||
|
root: () => ['inbounds'] as const,
|
||||||
|
slim: () => ['inbounds', 'slim'] as const,
|
||||||
|
},
|
||||||
|
clients: {
|
||||||
|
root: () => ['clients'] as const,
|
||||||
|
onlines: () => ['clients', 'onlines'] as const,
|
||||||
|
lastOnline: () => ['clients', 'lastOnline'] as const,
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
||||||
|
|
@ -52,15 +52,22 @@ export function useWebSocketBridge() {
|
||||||
queryClient.setQueryData(keys.nodes.list(), payload);
|
queryClient.setQueryData(keys.nodes.list(), payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onInbounds: Handler = (payload) => {
|
||||||
|
if (!Array.isArray(payload)) return;
|
||||||
|
queryClient.setQueryData(keys.inbounds.slim(), payload);
|
||||||
|
};
|
||||||
|
|
||||||
client.on('invalidate', onInvalidate);
|
client.on('invalidate', onInvalidate);
|
||||||
client.on('outbounds', onOutbounds);
|
client.on('outbounds', onOutbounds);
|
||||||
client.on('nodes', onNodes);
|
client.on('nodes', onNodes);
|
||||||
|
client.on('inbounds', onInbounds);
|
||||||
client.connect();
|
client.connect();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
client.off('invalidate', onInvalidate);
|
client.off('invalidate', onInvalidate);
|
||||||
client.off('outbounds', onOutbounds);
|
client.off('outbounds', onOutbounds);
|
||||||
client.off('nodes', onNodes);
|
client.off('nodes', onNodes);
|
||||||
|
client.off('inbounds', onInbounds);
|
||||||
if (invalidateTimer != null) {
|
if (invalidateTimer != null) {
|
||||||
clearTimeout(invalidateTimer);
|
clearTimeout(invalidateTimer);
|
||||||
invalidateTimer = null;
|
invalidateTimer = null;
|
||||||
|
|
|
||||||
|
|
@ -74,11 +74,8 @@ export default function InboundsPage() {
|
||||||
remarkModel,
|
remarkModel,
|
||||||
refresh,
|
refresh,
|
||||||
hydrateInbound,
|
hydrateInbound,
|
||||||
fetchDefaultSettings,
|
|
||||||
applyTrafficEvent,
|
applyTrafficEvent,
|
||||||
applyClientStatsEvent,
|
applyClientStatsEvent,
|
||||||
applyInvalidate,
|
|
||||||
applyInboundsEvent,
|
|
||||||
} = useInbounds();
|
} = useInbounds();
|
||||||
|
|
||||||
const [modal, modalContextHolder] = Modal.useModal();
|
const [modal, modalContextHolder] = Modal.useModal();
|
||||||
|
|
@ -105,15 +102,8 @@ export default function InboundsPage() {
|
||||||
useWebSocket({
|
useWebSocket({
|
||||||
traffic: applyTrafficEvent,
|
traffic: applyTrafficEvent,
|
||||||
client_stats: applyClientStatsEvent,
|
client_stats: applyClientStatsEvent,
|
||||||
invalidate: applyInvalidate,
|
|
||||||
inbounds: applyInboundsEvent,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchDefaultSettings().then(() => refresh());
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [formOpen, setFormOpen] = useState(false);
|
const [formOpen, setFormOpen] = useState(false);
|
||||||
const [formMode, setFormMode] = useState<'add' | 'edit'>('add');
|
const [formMode, setFormMode] = useState<'add' | 'edit'>('add');
|
||||||
const [formDbInbound, setFormDbInbound] = useState<any>(null);
|
const [formDbInbound, setFormDbInbound] = useState<any>(null);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { HttpUtil } from '@/utils';
|
import { HttpUtil } from '@/utils';
|
||||||
import { DBInbound } from '@/models/dbinbound.js';
|
import { DBInbound } from '@/models/dbinbound.js';
|
||||||
import { Protocols } from '@/models/inbound.js';
|
import { Protocols } from '@/models/inbound.js';
|
||||||
import { setDatepicker } from '@/hooks/useDatepicker';
|
import { setDatepicker } from '@/hooks/useDatepicker';
|
||||||
|
import { keys } from '@/api/queryKeys';
|
||||||
|
|
||||||
export interface SubSettings {
|
export interface SubSettings {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
|
@ -25,6 +27,27 @@ interface ClientRollup {
|
||||||
comments: Map<string, string>;
|
comments: Map<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ApiMsg<T = unknown> {
|
||||||
|
success?: boolean;
|
||||||
|
obj?: T;
|
||||||
|
msg?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DefaultsPayload {
|
||||||
|
expireDiff?: number;
|
||||||
|
trafficDiff?: number;
|
||||||
|
tgBotEnable?: boolean;
|
||||||
|
subEnable?: boolean;
|
||||||
|
subTitle?: string;
|
||||||
|
subURI?: string;
|
||||||
|
subJsonURI?: string;
|
||||||
|
subJsonEnable?: boolean;
|
||||||
|
pageSize?: number;
|
||||||
|
remarkModel?: string;
|
||||||
|
datepicker?: string;
|
||||||
|
ipLimitEnable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const TRACKED_PROTOCOLS = [
|
const TRACKED_PROTOCOLS = [
|
||||||
Protocols.VMESS,
|
Protocols.VMESS,
|
||||||
Protocols.VLESS,
|
Protocols.VLESS,
|
||||||
|
|
@ -33,40 +56,98 @@ const TRACKED_PROTOCOLS = [
|
||||||
Protocols.HYSTERIA,
|
Protocols.HYSTERIA,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
async function fetchSlimInbounds(): Promise<unknown[]> {
|
||||||
|
const msg = await HttpUtil.get('/panel/api/inbounds/list/slim', undefined, { silent: true }) as ApiMsg<unknown[]>;
|
||||||
|
if (!msg?.success) throw new Error(msg?.msg || 'Failed to fetch inbounds');
|
||||||
|
return Array.isArray(msg.obj) ? msg.obj : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchOnlineClients(): Promise<string[]> {
|
||||||
|
const msg = await HttpUtil.post('/panel/api/clients/onlines', undefined, { silent: true }) as ApiMsg<string[]>;
|
||||||
|
if (!msg?.success) throw new Error(msg?.msg || 'Failed to fetch onlines');
|
||||||
|
return Array.isArray(msg.obj) ? msg.obj : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLastOnlineMap(): Promise<Record<string, number>> {
|
||||||
|
const msg = await HttpUtil.post('/panel/api/clients/lastOnline', undefined, { silent: true }) as ApiMsg<Record<string, number>>;
|
||||||
|
if (!msg?.success) throw new Error(msg?.msg || 'Failed to fetch lastOnline');
|
||||||
|
return (msg.obj && typeof msg.obj === 'object') ? msg.obj : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchDefaultSettings(): Promise<DefaultsPayload> {
|
||||||
|
const msg = await HttpUtil.post('/panel/setting/defaultSettings', undefined, { silent: true }) as ApiMsg<DefaultsPayload>;
|
||||||
|
if (!msg?.success) throw new Error(msg?.msg || 'Failed to fetch defaults');
|
||||||
|
return (msg.obj as DefaultsPayload) || {};
|
||||||
|
}
|
||||||
|
|
||||||
export function useInbounds() {
|
export function useInbounds() {
|
||||||
const [fetched, setFetched] = useState(false);
|
const queryClient = useQueryClient();
|
||||||
const refreshingRef = useRef(false);
|
|
||||||
|
const slimQuery = useQuery({
|
||||||
|
queryKey: keys.inbounds.slim(),
|
||||||
|
queryFn: fetchSlimInbounds,
|
||||||
|
staleTime: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onlinesQuery = useQuery({
|
||||||
|
queryKey: keys.clients.onlines(),
|
||||||
|
queryFn: fetchOnlineClients,
|
||||||
|
staleTime: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastOnlineQuery = useQuery({
|
||||||
|
queryKey: keys.clients.lastOnline(),
|
||||||
|
queryFn: fetchLastOnlineMap,
|
||||||
|
staleTime: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultsQuery = useQuery({
|
||||||
|
queryKey: keys.settings.defaults(),
|
||||||
|
queryFn: fetchDefaultSettings,
|
||||||
|
staleTime: Infinity,
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaults = defaultsQuery.data ?? {};
|
||||||
|
const expireDiff = (defaults.expireDiff ?? 0) * 86400000;
|
||||||
|
const trafficDiff = (defaults.trafficDiff ?? 0) * 1073741824;
|
||||||
|
const tgBotEnable = !!defaults.tgBotEnable;
|
||||||
|
const ipLimitEnable = !!defaults.ipLimitEnable;
|
||||||
|
const pageSize = defaults.pageSize ?? 0;
|
||||||
|
const remarkModel = defaults.remarkModel || '-ieo';
|
||||||
|
const datepicker = (defaults.datepicker as 'gregorian' | 'jalalian') || 'gregorian';
|
||||||
|
|
||||||
|
const subSettings: SubSettings = useMemo(() => ({
|
||||||
|
enable: !!defaults.subEnable,
|
||||||
|
subTitle: defaults.subTitle || '',
|
||||||
|
subURI: defaults.subURI || '',
|
||||||
|
subJsonURI: defaults.subJsonURI || '',
|
||||||
|
subJsonEnable: !!defaults.subJsonEnable,
|
||||||
|
}), [defaults.subEnable, defaults.subTitle, defaults.subURI, defaults.subJsonURI, defaults.subJsonEnable]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaults.datepicker) setDatepicker(datepicker);
|
||||||
|
}, [datepicker, defaults.datepicker]);
|
||||||
|
|
||||||
|
const expireDiffRef = useRef(expireDiff);
|
||||||
|
expireDiffRef.current = expireDiff;
|
||||||
|
const trafficDiffRef = useRef(trafficDiff);
|
||||||
|
trafficDiffRef.current = trafficDiff;
|
||||||
|
|
||||||
|
// dbInbounds mirrors the slim query data wrapped as DBInbound instances, but
|
||||||
|
// stays mutable so the WS-driven applyClientStatsEvent / applyTrafficEvent
|
||||||
|
// can merge per-row updates without invalidating the entire query.
|
||||||
const [dbInbounds, setDbInbounds] = useState<DBInboundInstance[]>([]);
|
const [dbInbounds, setDbInbounds] = useState<DBInboundInstance[]>([]);
|
||||||
const dbInboundsRef = useRef<DBInboundInstance[]>([]);
|
const dbInboundsRef = useRef<DBInboundInstance[]>([]);
|
||||||
dbInboundsRef.current = dbInbounds;
|
dbInboundsRef.current = dbInbounds;
|
||||||
|
|
||||||
const [clientCount, setClientCount] = useState<Record<number, ClientRollup>>({});
|
const [clientCount, setClientCount] = useState<Record<number, ClientRollup>>({});
|
||||||
|
const [statsVersion, setStatsVersion] = useState(0);
|
||||||
|
|
||||||
const [onlineClients, setOnlineClients] = useState<string[]>([]);
|
const [onlineClients, setOnlineClients] = useState<string[]>([]);
|
||||||
const onlineClientsRef = useRef<string[]>([]);
|
const onlineClientsRef = useRef<string[]>([]);
|
||||||
onlineClientsRef.current = onlineClients;
|
onlineClientsRef.current = onlineClients;
|
||||||
|
|
||||||
const [lastOnlineMap, setLastOnlineMap] = useState<Record<string, number>>({});
|
const [lastOnlineMap, setLastOnlineMap] = useState<Record<string, number>>({});
|
||||||
const [statsVersion, setStatsVersion] = useState(0);
|
|
||||||
|
|
||||||
const [expireDiff, setExpireDiff] = useState(0);
|
|
||||||
const expireDiffRef = useRef(0);
|
|
||||||
expireDiffRef.current = expireDiff;
|
|
||||||
const [trafficDiff, setTrafficDiff] = useState(0);
|
|
||||||
const trafficDiffRef = useRef(0);
|
|
||||||
trafficDiffRef.current = trafficDiff;
|
|
||||||
|
|
||||||
const [subSettings, setSubSettings] = useState<SubSettings>({
|
|
||||||
enable: false,
|
|
||||||
subTitle: '',
|
|
||||||
subURI: '',
|
|
||||||
subJsonURI: '',
|
|
||||||
subJsonEnable: false,
|
|
||||||
});
|
|
||||||
const [remarkModel, setRemarkModel] = useState('-ieo');
|
|
||||||
const [datepicker, setDatepickerState] = useState('gregorian');
|
|
||||||
const [tgBotEnable, setTgBotEnable] = useState(false);
|
|
||||||
const [ipLimitEnable, setIpLimitEnable] = useState(false);
|
|
||||||
const [pageSize, setPageSize] = useState(0);
|
|
||||||
|
|
||||||
const rollupClients = useCallback(
|
const rollupClients = useCallback(
|
||||||
(dbInbound: DBInboundInstance, inbound: { clients?: { email?: string; enable?: boolean; comment?: string }[] }): ClientRollup => {
|
(dbInbound: DBInboundInstance, inbound: { clients?: { email?: string; enable?: boolean; comment?: string }[] }): ClientRollup => {
|
||||||
|
|
@ -130,27 +211,6 @@ export function useInbounds() {
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setInbounds = useCallback(
|
|
||||||
(rows: unknown[]) => {
|
|
||||||
const next: DBInboundInstance[] = [];
|
|
||||||
const counts: Record<number, ClientRollup> = {};
|
|
||||||
for (const row of rows as { protocol: string; id: number }[]) {
|
|
||||||
const dbInbound = new DBInbound(row) as DBInboundInstance;
|
|
||||||
const parsed = (dbInbound as unknown as { toInbound: () => { clients?: unknown[]; isSSMultiUser?: boolean } }).toInbound();
|
|
||||||
next.push(dbInbound);
|
|
||||||
if (TRACKED_PROTOCOLS.includes(row.protocol)) {
|
|
||||||
if ((dbInbound as unknown as { isSS: boolean }).isSS && !parsed.isSSMultiUser) continue;
|
|
||||||
counts[row.id] = rollupClients(dbInbound, parsed as { clients?: { email?: string; enable?: boolean; comment?: string }[] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dbInboundsRef.current = next;
|
|
||||||
setDbInbounds(next);
|
|
||||||
setClientCount(counts);
|
|
||||||
setFetched(true);
|
|
||||||
},
|
|
||||||
[rollupClients],
|
|
||||||
);
|
|
||||||
|
|
||||||
const rebuildClientCount = useCallback(() => {
|
const rebuildClientCount = useCallback(() => {
|
||||||
const counts: Record<number, ClientRollup> = {};
|
const counts: Record<number, ClientRollup> = {};
|
||||||
for (const dbInbound of dbInboundsRef.current) {
|
for (const dbInbound of dbInboundsRef.current) {
|
||||||
|
|
@ -164,57 +224,46 @@ export function useInbounds() {
|
||||||
setClientCount(counts);
|
setClientCount(counts);
|
||||||
}, [rollupClients]);
|
}, [rollupClients]);
|
||||||
|
|
||||||
const fetchOnlineUsers = useCallback(async () => {
|
// Seed dbInbounds + clientCount from the slim query. Runs on first fetch and
|
||||||
const msg = await HttpUtil.post('/panel/api/clients/onlines');
|
// again every time the query refetches (e.g. invalidate from WS bridge).
|
||||||
if (msg?.success) {
|
useEffect(() => {
|
||||||
const list = (msg.obj || []) as string[];
|
if (!slimQuery.data) return;
|
||||||
onlineClientsRef.current = list;
|
const next: DBInboundInstance[] = [];
|
||||||
setOnlineClients(list);
|
const counts: Record<number, ClientRollup> = {};
|
||||||
|
for (const row of slimQuery.data as { protocol: string; id: number }[]) {
|
||||||
|
const dbInbound = new DBInbound(row) as DBInboundInstance;
|
||||||
|
const parsed = (dbInbound as unknown as { toInbound: () => { clients?: unknown[]; isSSMultiUser?: boolean } }).toInbound();
|
||||||
|
next.push(dbInbound);
|
||||||
|
if (TRACKED_PROTOCOLS.includes(row.protocol)) {
|
||||||
|
if ((dbInbound as unknown as { isSS: boolean }).isSS && !parsed.isSSMultiUser) continue;
|
||||||
|
counts[row.id] = rollupClients(dbInbound, parsed as { clients?: { email?: string; enable?: boolean; comment?: string }[] });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
dbInboundsRef.current = next;
|
||||||
|
setDbInbounds(next);
|
||||||
|
setClientCount(counts);
|
||||||
|
}, [slimQuery.data, rollupClients]);
|
||||||
|
|
||||||
const fetchLastOnlineMap = useCallback(async () => {
|
useEffect(() => {
|
||||||
const msg = await HttpUtil.post('/panel/api/clients/lastOnline');
|
if (onlinesQuery.data) {
|
||||||
if (msg?.success && msg.obj) {
|
onlineClientsRef.current = onlinesQuery.data;
|
||||||
setLastOnlineMap(msg.obj as Record<string, number>);
|
setOnlineClients(onlinesQuery.data);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [onlinesQuery.data]);
|
||||||
|
|
||||||
const fetchDefaultSettings = useCallback(async () => {
|
useEffect(() => {
|
||||||
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
if (lastOnlineQuery.data) setLastOnlineMap(lastOnlineQuery.data);
|
||||||
if (!msg?.success) return;
|
}, [lastOnlineQuery.data]);
|
||||||
const s = (msg.obj || {}) as Record<string, unknown>;
|
|
||||||
setExpireDiff((s.expireDiff as number ?? 0) * 86400000);
|
const fetched = slimQuery.data !== undefined && defaultsQuery.data !== undefined;
|
||||||
setTrafficDiff((s.trafficDiff as number ?? 0) * 1073741824);
|
|
||||||
setTgBotEnable(!!s.tgBotEnable);
|
|
||||||
setSubSettings({
|
|
||||||
enable: !!s.subEnable,
|
|
||||||
subTitle: (s.subTitle as string) || '',
|
|
||||||
subURI: (s.subURI as string) || '',
|
|
||||||
subJsonURI: (s.subJsonURI as string) || '',
|
|
||||||
subJsonEnable: !!s.subJsonEnable,
|
|
||||||
});
|
|
||||||
setPageSize((s.pageSize as number) ?? 0);
|
|
||||||
setRemarkModel((s.remarkModel as string) || '-ieo');
|
|
||||||
const dp = ((s.datepicker as string) || 'gregorian') as 'gregorian' | 'jalalian';
|
|
||||||
setDatepickerState(dp);
|
|
||||||
setDatepicker(dp);
|
|
||||||
setIpLimitEnable(!!s.ipLimitEnable);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
if (refreshingRef.current) return;
|
await Promise.all([
|
||||||
refreshingRef.current = true;
|
queryClient.invalidateQueries({ queryKey: keys.inbounds.slim() }),
|
||||||
try {
|
queryClient.invalidateQueries({ queryKey: keys.clients.onlines() }),
|
||||||
const msg = await HttpUtil.get('/panel/api/inbounds/list/slim');
|
queryClient.invalidateQueries({ queryKey: keys.clients.lastOnline() }),
|
||||||
if (!msg?.success) return;
|
]);
|
||||||
await fetchLastOnlineMap();
|
}, [queryClient]);
|
||||||
await fetchOnlineUsers();
|
|
||||||
setInbounds(Array.isArray(msg.obj) ? msg.obj : []);
|
|
||||||
} finally {
|
|
||||||
window.setTimeout(() => { refreshingRef.current = false; }, 500);
|
|
||||||
}
|
|
||||||
}, [fetchLastOnlineMap, fetchOnlineUsers, setInbounds]);
|
|
||||||
|
|
||||||
// hydrateInbound fetches the full inbound (including settings.clients with
|
// hydrateInbound fetches the full inbound (including settings.clients with
|
||||||
// uuid/password/flow/etc.) and swaps it into the cached list. Use this
|
// uuid/password/flow/etc.) and swaps it into the cached list. Use this
|
||||||
|
|
@ -313,25 +362,6 @@ export function useInbounds() {
|
||||||
[rebuildClientCount],
|
[rebuildClientCount],
|
||||||
);
|
);
|
||||||
|
|
||||||
const applyInvalidate = useCallback(
|
|
||||||
(payload: unknown) => {
|
|
||||||
if (!payload || typeof payload !== 'object') return;
|
|
||||||
const p = payload as { type?: string };
|
|
||||||
if (p.type === 'inbounds') {
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[refresh],
|
|
||||||
);
|
|
||||||
|
|
||||||
const applyInboundsEvent = useCallback(
|
|
||||||
(payload: unknown) => {
|
|
||||||
if (!Array.isArray(payload)) return;
|
|
||||||
setInbounds(payload);
|
|
||||||
},
|
|
||||||
[setInbounds],
|
|
||||||
);
|
|
||||||
|
|
||||||
const totals = useMemo(() => {
|
const totals = useMemo(() => {
|
||||||
let up = 0;
|
let up = 0;
|
||||||
let down = 0;
|
let down = 0;
|
||||||
|
|
@ -361,10 +391,7 @@ export function useInbounds() {
|
||||||
pageSize,
|
pageSize,
|
||||||
refresh,
|
refresh,
|
||||||
hydrateInbound,
|
hydrateInbound,
|
||||||
fetchDefaultSettings,
|
|
||||||
applyTrafficEvent,
|
applyTrafficEvent,
|
||||||
applyClientStatsEvent,
|
applyClientStatsEvent,
|
||||||
applyInvalidate,
|
|
||||||
applyInboundsEvent,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue