diff --git a/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardSessions.ts b/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardSessions.ts new file mode 100644 index 00000000..a82e63d7 --- /dev/null +++ b/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardSessions.ts @@ -0,0 +1,366 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import { api } from "@/lib/api"; +import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch"; +import { asGuidOrUndefined } from "@/lib/inputValidation"; +import type { NotificationType } from "@/contexts/NotificationContext"; +import type { Session } from "../types"; + +type AddNotification = ( + type: NotificationType, + title: string, + message: string, + key?: string, + href?: string, +) => void; + +interface User { + isTenantAdmin?: boolean; + isGlobalAdmin?: boolean; + role?: string | null; +} + +interface SignalRApi { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + on: (event: string, handler: (...args: any[]) => void) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + off: (event: string, handler: (...args: any[]) => void) => void; + isConnected: boolean; + joinGroup: (group: string) => Promise; + leaveGroup: (group: string) => Promise; +} + +interface UseDashboardSessionsParams { + user: User | null | undefined; + tenantId: string | null | undefined; + globalAdminMode: boolean; + tenantIdFilter: string; + adminMode: boolean; + getAccessToken: (forceRefresh?: boolean) => Promise; + addNotification: AddNotification; + setBlockedDevicesSet: (next: Set) => void; + signalR: SignalRApi; +} + +export interface UseDashboardSessionsReturn { + sessions: Session[]; + loading: boolean; + hasMore: boolean; + loadingMore: boolean; + refetch: () => void; + refetchWith: (tenantIdOverride: string) => void; + loadMore: () => void; + removeSession: (sessionId: string) => void; +} + +/** + * Owns the dashboard's session list lifecycle: + * - initial fetch (gated on user role + tenantId/globalAdminMode readiness) + * - SignalR group joining (tenant + global-admins) and live update handlers + * - reconnect refetch + * - paginated load-more via cursor + * - blocked-devices sync after each fresh fetch + * - reset-on-globalAdminMode-toggle + */ +export function useDashboardSessions({ + user, + tenantId, + globalAdminMode, + tenantIdFilter, + adminMode, + getAccessToken, + addNotification, + setBlockedDevicesSet, + signalR, +}: UseDashboardSessionsParams): UseDashboardSessionsReturn { + const { on, off, isConnected, joinGroup, leaveGroup } = signalR; + + const [sessions, setSessions] = useState([]); + const [loading, setLoading] = useState(true); + const [hasMore, setHasMore] = useState(false); + const [loadingMore, setLoadingMore] = useState(false); + const [cursor, setCursor] = useState(null); + + // Refs for SignalR handlers to access current filter state without restarting subscriptions + const tenantIdFilterRef = useRef(tenantIdFilter); + tenantIdFilterRef.current = tenantIdFilter; + const globalAdminModeRef = useRef(globalAdminMode); + globalAdminModeRef.current = globalAdminMode; + const tenantIdRef = useRef(tenantId); + tenantIdRef.current = tenantId; + + // Refs for fetch closures (refetch is called from various effects/handlers and should + // always see current filter values without forcing dependency-driven recreation) + const adminModeRef = useRef(adminMode); + adminModeRef.current = adminMode; + const cursorRef = useRef(cursor); + cursorRef.current = cursor; + const loadingMoreRef = useRef(loadingMore); + loadingMoreRef.current = loadingMore; + + const hasInitialFetch = useRef(false); + const hasGlobalModeInitialized = useRef(false); + const hasJoinedGroup = useRef(false); + const wasConnectedRef = useRef(false); + + const fetchBlockedDevices = useCallback(async (currentSessions: Session[]) => { + if (!adminModeRef.current || !globalAdminModeRef.current) { + setBlockedDevicesSet(new Set()); + return; + } + + try { + const tenantIds = globalAdminModeRef.current + ? [...new Set(currentSessions.map((s) => s.tenantId))] + : tenantIdRef.current ? [tenantIdRef.current] : []; + + if (tenantIds.length === 0) { + setBlockedDevicesSet(new Set()); + return; + } + + const results = await Promise.allSettled( + tenantIds.map((tid) => + authenticatedFetch(api.devices.blocked(tid), getAccessToken) + .then((res) => (res.ok ? res.json() : { blocked: [] })), + ), + ); + + const newSet = new Set(); + results.forEach((result) => { + if (result.status === "fulfilled" && result.value?.blocked) { + for (const device of result.value.blocked) { + newSet.add(`${device.tenantId}:${device.serialNumber}`); + } + } + }); + + setBlockedDevicesSet(newSet); + } catch (error) { + if (error instanceof TokenExpiredError) { + addNotification("error", "Session Expired", error.message, "session-expired-error"); + } else { + console.error("Failed to fetch blocked devices:", error); + } + } + }, [getAccessToken, addNotification, setBlockedDevicesSet]); + + const fetchSessions = useCallback(async (loadMoreCursor?: string, globalTenantIdOverride?: string) => { + try { + const rawFilter = globalTenantIdOverride !== undefined ? globalTenantIdOverride : tenantIdFilterRef.current.trim(); + const effectiveTenantFilter = asGuidOrUndefined(rawFilter); + let endpoint = globalAdminModeRef.current + ? api.globalSessions.list(effectiveTenantFilter) + : api.sessions.list(tenantIdRef.current ?? undefined); + + if (loadMoreCursor) { + endpoint += endpoint.includes("?") ? "&" : "?"; + endpoint += `cursor=${encodeURIComponent(loadMoreCursor)}`; + } + + const response = await authenticatedFetch(endpoint, getAccessToken); + + if (response.ok) { + const data = await response.json(); + const newSessions: Session[] = data.sessions || []; + + if (loadMoreCursor) { + setSessions((prev) => [...prev, ...newSessions]); + } else { + setSessions(newSessions); + fetchBlockedDevices(newSessions); + } + + setHasMore(data.hasMore || false); + setCursor(data.cursor || null); + } else { + addNotification("error", "Backend Error", `Failed to fetch sessions: ${response.statusText}`, "backend-error"); + } + } catch (error) { + if (error instanceof TokenExpiredError) { + addNotification("error", "Session Expired", error.message, "session-expired-error"); + } else { + console.error("Failed to fetch sessions:", error); + addNotification( + "error", + "Backend Not Reachable", + "Unable to connect to the backend API. Please ensure the backend server is running.", + "backend-unreachable", + ); + } + } finally { + setLoading(false); + setLoadingMore(false); + } + }, [getAccessToken, addNotification, fetchBlockedDevices]); + + const refetch = useCallback(() => { + setLoading(true); + fetchSessions(); + }, [fetchSessions]); + + const refetchWith = useCallback((tenantIdOverride: string) => { + setLoading(true); + fetchSessions(undefined, tenantIdOverride); + }, [fetchSessions]); + + const loadMore = useCallback(() => { + if (!cursorRef.current || loadingMoreRef.current) return; + setLoadingMore(true); + fetchSessions(cursorRef.current); + }, [fetchSessions]); + + const removeSession = useCallback((sessionId: string) => { + setSessions((prev) => prev.filter((s) => s.sessionId !== sessionId)); + }, []); + + // Initial fetch — gated on user role + tenant readiness + useEffect(() => { + if (user && !user.isTenantAdmin && !user.isGlobalAdmin && user.role !== "Operator") { + return; // regular users are redirected elsewhere; don't fetch + } + if (!globalAdminMode && !tenantId) return; // wait for real tenant ID + if (hasInitialFetch.current) return; + hasInitialFetch.current = true; + + fetchSessions(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [user, tenantId, globalAdminMode]); + + // Join tenant SignalR group; refetch on reconnect + useEffect(() => { + if (isConnected) { + const isReconnect = wasConnectedRef.current; + wasConnectedRef.current = true; + + if (!hasJoinedGroup.current) { + const groupName = `tenant-${tenantId}`; + hasJoinedGroup.current = true; + joinGroup(groupName); + } + + if (isReconnect && hasInitialFetch.current) { + fetchSessions(); + } + } + + return () => { + if (hasJoinedGroup.current) { + const groupName = `tenant-${tenantId}`; + leaveGroup(groupName); + hasJoinedGroup.current = false; + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isConnected, tenantId]); + + // Join/leave global-admins group when Global Admin mode changes + useEffect(() => { + if (!isConnected) return; + + if (globalAdminMode) { + console.log("[Dashboard] Global Admin mode enabled: joining global-admins group"); + joinGroup("global-admins"); + } else { + console.log("[Dashboard] Global Admin mode disabled: leaving global-admins group"); + leaveGroup("global-admins"); + } + + return () => { + if (globalAdminMode) { + console.log("[Dashboard] Component unmounting: leaving global-admins group"); + leaveGroup("global-admins"); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isConnected, globalAdminMode]); + + // SignalR listeners — re-register when connection cycles + useEffect(() => { + const handleNewSession = (...args: unknown[]) => { + const data = args[0] as { sessionId: string; tenantId: string; session: Session } | undefined; + if (!data) return; + console.log("New session registered", data); + + if (!globalAdminModeRef.current && tenantIdRef.current && data.tenantId !== tenantIdRef.current) { + console.log(`Ignoring newSession from tenant ${data.tenantId} (not in global mode, own tenant: ${tenantIdRef.current})`); + return; + } + + const activeFilter = tenantIdFilterRef.current.trim(); + if (globalAdminModeRef.current && activeFilter && data.tenantId !== activeFilter) { + console.log(`Ignoring newSession from tenant ${data.tenantId} (filtered to ${activeFilter})`); + return; + } + + if (data.session) { + setSessions((prevSessions) => { + const sessionIndex = prevSessions.findIndex((s) => s.sessionId === data.session.sessionId); + if (sessionIndex >= 0) { + const updated = [...prevSessions]; + updated[sessionIndex] = data.session; + return updated; + } + return [data.session, ...prevSessions]; + }); + } else { + console.warn("newSession event received without session data, falling back to fetch"); + fetchSessions(); + } + }; + + const handleNewEvents = (...args: unknown[]) => { + const data = args[0] as { sessionId: string; tenantId: string; eventCount: number; sessionUpdate?: Partial; session?: Session } | undefined; + if (!data) return; + console.log("New events notification received on dashboard", data); + + const update = data.sessionUpdate || data.session; + if (update) { + setSessions((prevSessions) => { + const sessionIndex = prevSessions.findIndex((s) => s.sessionId === data.sessionId); + if (sessionIndex >= 0) { + const updated = [...prevSessions]; + updated[sessionIndex] = { ...prevSessions[sessionIndex], ...update }; + return updated; + } + return prevSessions; + }); + } + }; + + on("newSession", handleNewSession); + on("newevents", handleNewEvents); + + return () => { + off("newSession", handleNewSession); + off("newevents", handleNewEvents); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isConnected]); + + // Refetch + reset visible state when Global Admin mode toggles (after first init) + useEffect(() => { + if (!hasGlobalModeInitialized.current) { + hasGlobalModeInitialized.current = true; + return; + } + + setSessions([]); + setCursor(null); + setLoading(true); + fetchSessions(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [globalAdminMode]); + + return { + sessions, + loading, + hasMore, + loadingMore, + refetch, + refetchWith, + loadMore, + removeSession, + }; +} diff --git a/src/Web/autopilot-monitor-web/app/dashboard/page.tsx b/src/Web/autopilot-monitor-web/app/dashboard/page.tsx index a8e6825f..69e98cf6 100644 --- a/src/Web/autopilot-monitor-web/app/dashboard/page.tsx +++ b/src/Web/autopilot-monitor-web/app/dashboard/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState, useRef } from "react"; +import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { ProtectedRoute } from "../../components/ProtectedRoute"; @@ -8,10 +8,6 @@ import { useSignalR } from "../../contexts/SignalRContext"; import { useTenant } from "../../contexts/TenantContext"; import { useAuth } from "../../contexts/AuthContext"; import { useNotifications } from "../../contexts/NotificationContext"; -import { api } from "@/lib/api"; -import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch"; -import { asGuidOrUndefined } from "@/lib/inputValidation"; -import { Session } from "./types"; import { StatsCard } from "./components/StatsCards"; import { WelcomeMessage } from "./components/WelcomeMessage"; import { SessionTable } from "./components/SessionTable"; @@ -23,38 +19,36 @@ import { useBlockDevice } from "./hooks/useBlockDevice"; import { useTenantSecurityConfig } from "./hooks/useTenantSecurityConfig"; import { useTenantList } from "./hooks/useTenantList"; import { useDashboardFilters } from "./hooks/useDashboardFilters"; +import { useDashboardSessions } from "./hooks/useDashboardSessions"; export default function Home() { const router = useRouter(); const { user, logout, getAccessToken, isPreviewBlocked } = useAuth(); const { addNotification } = useNotifications(); const [apiStatus, setApiStatus] = useState<"unchecked" | "checking" | "healthy" | "error">("unchecked"); - const [sessions, setSessions] = useState([]); - const [loading, setLoading] = useState(true); - const [hasMore, setHasMore] = useState(false); - const [loadingMore, setLoadingMore] = useState(false); - const [cursor, setCursor] = useState(null); const [tenantIdFilter, setTenantIdFilter] = useState(""); const { adminMode, setAdminMode, globalAdminMode, setGlobalAdminMode } = useAdminMode(); - const { - showDeleteConfirm, sessionToDelete, - deleteSession, confirmDelete, cancelDelete, - } = useDeleteSession( - getAccessToken, - addNotification, - adminMode, - (deletedId) => setSessions(prev => prev.filter(s => s.sessionId !== deletedId)) - ); + const signalR = useSignalR(); + const { tenantId } = useTenant(); const { showBlockConfirm, sessionToBlock, blockingDevice, blockedDevicesSet, setBlockedDevicesSet, blockDevice, confirmBlock, cancelBlock, } = useBlockDevice(getAccessToken, addNotification, adminMode, globalAdminMode); - // Use global contexts - const { on, off, isConnected, joinGroup, leaveGroup } = useSignalR(); - const { tenantId } = useTenant(); + const { + sessions, loading, hasMore, loadingMore, + refetch, refetchWith, loadMore, removeSession, + } = useDashboardSessions({ + user, tenantId, globalAdminMode, tenantIdFilter, adminMode, + getAccessToken, addNotification, setBlockedDevicesSet, signalR, + }); + + const { + showDeleteConfirm, sessionToDelete, + deleteSession, confirmDelete, cancelDelete, + } = useDeleteSession(getAccessToken, addNotification, adminMode, removeSession); const { searchQuery, setSearchQuery, @@ -74,123 +68,6 @@ export default function Home() { tenantIdFilter, }); - // Track if initial fetch has been done to prevent duplicate calls in React StrictMode - const hasInitialFetch = useRef(false); - - // Track if global admin mode has been initialized (to skip initial mount in useEffect) - const hasGlobalModeInitialized = useRef(false); - - // Refs for SignalR handlers to access current filter state (avoids stale closures) - const tenantIdFilterRef = useRef(tenantIdFilter); - tenantIdFilterRef.current = tenantIdFilter; - const globalAdminModeRef = useRef(globalAdminMode); - globalAdminModeRef.current = globalAdminMode; - const tenantIdRef = useRef(tenantId); - tenantIdRef.current = tenantId; - - // Track if we've joined the tenant group to prevent duplicate joins - const hasJoinedGroup = useRef(false); - - // Track whether we've been connected at least once — used to detect reconnects - const wasConnectedRef = useRef(false); - - const fetchSessions = async (loadMoreCursor?: string, globalTenantIdOverride?: string) => { - try { - // Use different endpoint based on global admin mode - // Only pass tenantIdFilter as query param if it's a valid GUID (fuzzy search sets the GUID on selection) - const rawFilter = globalTenantIdOverride !== undefined ? globalTenantIdOverride : tenantIdFilter.trim(); - const effectiveTenantFilter = asGuidOrUndefined(rawFilter); - let endpoint = globalAdminMode - ? api.globalSessions.list(effectiveTenantFilter) - : api.sessions.list(tenantId); - - // Append cursor for "Load More" requests - if (loadMoreCursor) { - endpoint += endpoint.includes('?') ? '&' : '?'; - endpoint += `cursor=${encodeURIComponent(loadMoreCursor)}`; - } - - const response = await authenticatedFetch(endpoint, getAccessToken); - - if (response.ok) { - const data = await response.json(); - const newSessions = data.sessions || []; - - if (loadMoreCursor) { - // Append to existing sessions (Load More) - setSessions(prev => [...prev, ...newSessions]); - } else { - // Initial load — replace all sessions - setSessions(newSessions); - fetchBlockedDevices(newSessions); - } - - setHasMore(data.hasMore || false); - setCursor(data.cursor || null); - } else { - addNotification('error', 'Backend Error', `Failed to fetch sessions: ${response.statusText}`, 'backend-error'); - } - } catch (error) { - if (error instanceof TokenExpiredError) { - addNotification('error', 'Session Expired', error.message, 'session-expired-error'); - } else { - console.error("Failed to fetch sessions:", error); - addNotification('error', 'Backend Not Reachable', 'Unable to connect to the backend API. Please ensure the backend server is running.', 'backend-unreachable'); - } - } finally { - setLoading(false); - setLoadingMore(false); - } - }; - - const loadMore = () => { - if (!cursor || loadingMore) return; - setLoadingMore(true); - fetchSessions(cursor); - }; - - const fetchBlockedDevices = async (currentSessions: Session[]) => { - if (!adminMode || !globalAdminMode) { - setBlockedDevicesSet(new Set()); - return; - } - - try { - const tenantIds = globalAdminMode - ? [...new Set(currentSessions.map(s => s.tenantId))] - : tenantId ? [tenantId] : []; - - if (tenantIds.length === 0) { - setBlockedDevicesSet(new Set()); - return; - } - - const results = await Promise.allSettled( - tenantIds.map(tid => - authenticatedFetch(api.devices.blocked(tid), getAccessToken) - .then(res => res.ok ? res.json() : { blocked: [] }) - ) - ); - - const newSet = new Set(); - results.forEach(result => { - if (result.status === "fulfilled" && result.value?.blocked) { - for (const device of result.value.blocked) { - newSet.add(`${device.tenantId}:${device.serialNumber}`); - } - } - }); - - setBlockedDevicesSet(newSet); - } catch (error) { - if (error instanceof TokenExpiredError) { - addNotification('error', 'Session Expired', error.message, 'session-expired-error'); - } else { - console.error("Failed to fetch blocked devices:", error); - } - } - }; - // Redirect regular users (non-admin, non-operator) to progress portal – they must never see the session list useEffect(() => { if (user && !user.isTenantAdmin && !user.isGlobalAdmin && user.role !== 'Operator') { @@ -199,139 +76,7 @@ export default function Home() { }, [user, router]); const serialValidationEnabled = useTenantSecurityConfig(tenantId, user, getAccessToken, addNotification); - - // Initial data fetch (only runs for admins). - // Wait for a real tenantId before fetching — TenantContext initializes to '' and - // updates asynchronously once AuthContext finishes loading the user token. - useEffect(() => { - if (user && !user.isTenantAdmin && !user.isGlobalAdmin && user.role !== 'Operator') { - return; // regular users are being redirected, don't fetch - } - if (!globalAdminMode && !tenantId) return; // wait for real tenant ID - // Prevent duplicate fetches in React StrictMode (development double-mounting) - if (hasInitialFetch.current) { - return; - } - hasInitialFetch.current = true; - - // Only fetch sessions on load, no automatic health check - fetchSessions(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [user, tenantId, globalAdminMode]); // re-run once user and tenantId are known - - // Join tenant group when SignalR is connected (for multi-tenancy) - useEffect(() => { - if (isConnected) { - const isReconnect = wasConnectedRef.current; - wasConnectedRef.current = true; - - if (!hasJoinedGroup.current) { - const groupName = `tenant-${tenantId}`; - hasJoinedGroup.current = true; - joinGroup(groupName); - } - - // After a reconnect, refresh the session list to catch any sessions that were - // registered while the client was disconnected and not in the SignalR group. - if (isReconnect && hasInitialFetch.current) { - fetchSessions(); - } - } - - return () => { - // Leave group when component unmounts - if (hasJoinedGroup.current) { - const groupName = `tenant-${tenantId}`; - leaveGroup(groupName); - hasJoinedGroup.current = false; - } - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isConnected, tenantId]); - - // Join/leave global-admins group when Global Admin mode changes - useEffect(() => { - if (!isConnected) return; - - if (globalAdminMode) { - console.log('[Home] Global Admin mode enabled: joining global-admins group'); - joinGroup('global-admins'); - } else { - console.log('[Home] Global Admin mode disabled: leaving global-admins group'); - leaveGroup('global-admins'); - } - - return () => { - // Clean up global-admins group on unmount if currently in Global Admin mode - if (globalAdminMode) { - console.log('[Home] Component unmounting: leaving global-admins group'); - leaveGroup('global-admins'); - } - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isConnected, globalAdminMode]); - - // Setup SignalR listener - re-register when connection changes - useEffect(() => { - const handleNewSession = (data: { sessionId: string; tenantId: string; session: Session }) => { - console.log('New session registered', data); - - // In non-global mode, only accept sessions from the user's own tenant - if (!globalAdminModeRef.current && tenantIdRef.current && data.tenantId !== tenantIdRef.current) { - console.log(`Ignoring newSession from tenant ${data.tenantId} (not in global mode, own tenant: ${tenantIdRef.current})`); - return; - } - - // In global admin mode with an active tenant filter, ignore sessions from other tenants - const activeFilter = tenantIdFilterRef.current.trim(); - if (globalAdminModeRef.current && activeFilter && data.tenantId !== activeFilter) { - console.log(`Ignoring newSession from tenant ${data.tenantId} (filtered to ${activeFilter})`); - return; - } - - if (data.session) { - setSessions(prevSessions => { - const sessionIndex = prevSessions.findIndex(s => s.sessionId === data.session.sessionId); - if (sessionIndex >= 0) { - const updated = [...prevSessions]; - updated[sessionIndex] = data.session; - return updated; - } else { - return [data.session, ...prevSessions]; - } - }); - } else { - console.warn('newSession event received without session data, falling back to fetch'); - fetchSessions(); - } - }; - - const handleNewEvents = (data: { sessionId: string; tenantId: string; eventCount: number; sessionUpdate?: Partial; session?: Session }) => { - console.log('New events notification received on home page', data); - - const update = data.sessionUpdate || data.session; - if (update) { - setSessions(prevSessions => { - const sessionIndex = prevSessions.findIndex(s => s.sessionId === data.sessionId); - if (sessionIndex >= 0) { - const updated = [...prevSessions]; - updated[sessionIndex] = { ...prevSessions[sessionIndex], ...update }; - return updated; - } - return prevSessions; - }); - } - }; - - on('newSession', handleNewSession); - on('newevents', handleNewEvents); - - return () => { - off('newSession', handleNewSession); - off('newevents', handleNewEvents); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isConnected]); + const tenantList = useTenantList(globalAdminMode, getAccessToken); // Disable global admin mode if user is not a global admin useEffect(() => { @@ -341,43 +86,22 @@ export default function Home() { } }, [user, globalAdminMode]); - - // Refetch sessions when global admin mode changes + // Clear tenant filter when leaving Global Admin mode (refetch is owned by useDashboardSessions) useEffect(() => { - if (!hasGlobalModeInitialized.current) { - hasGlobalModeInitialized.current = true; - return; - } - - // Clear tenant filter when leaving global mode - if (!globalAdminMode) { - setTenantIdFilter(""); - } - - // Clear sessions immediately to prevent showing stale cross-tenant data - setSessions([]); - setCursor(null); - setLoading(true); - fetchSessions(); + if (!globalAdminMode) setTenantIdFilter(""); }, [globalAdminMode]); - const tenantList = useTenantList(globalAdminMode, getAccessToken); - const applyTenantIdFilter = (value: string) => { setTenantIdFilter(value); }; const submitTenantIdFilter = () => { - setCursor(null); - setLoading(true); - fetchSessions(); + refetch(); }; const clearTenantIdFilter = () => { setTenantIdFilter(""); - setCursor(null); - setLoading(true); - fetchSessions(undefined, ""); + refetchWith(""); }; return (