diff --git a/web-ui/src/lib/hooks/useAlerts.ts b/web-ui/src/lib/hooks/useAlerts.ts index 025391f..0f2f9b1 100644 --- a/web-ui/src/lib/hooks/useAlerts.ts +++ b/web-ui/src/lib/hooks/useAlerts.ts @@ -4,11 +4,13 @@ import useSWR from 'swr'; import { Server } from '@/src/types/server'; import { AlertRulesResponse, AlertingInfoResponse, WebhookDeliveriesResponse } from '@/src/types/alerts'; import { getClient } from '@/src/lib/api/client'; +import { usePageVisibility } from './usePageVisibility'; /** * Hook for fetching alert rules */ export function useAlerts(server: Server | null) { + const isVisible = usePageVisibility(); const fetcher = async (): Promise => { if (!server) throw new Error('No server'); const client = getClient(server); @@ -21,7 +23,7 @@ export function useAlerts(server: Server | null) { swrKey, fetcher, { - refreshInterval: 30000, + refreshInterval: isVisible ? 30000 : 0, revalidateOnFocus: true, dedupingInterval: 5000, } @@ -39,6 +41,7 @@ export function useAlerts(server: Server | null) { * Hook for fetching alerting system info */ export function useAlertingInfo(server: Server | null) { + const isVisible = usePageVisibility(); const fetcher = async (): Promise => { if (!server) throw new Error('No server'); const client = getClient(server); @@ -51,7 +54,7 @@ export function useAlertingInfo(server: Server | null) { swrKey, fetcher, { - refreshInterval: 60000, + refreshInterval: isVisible ? 60000 : 0, revalidateOnFocus: true, dedupingInterval: 10000, } @@ -69,6 +72,7 @@ export function useAlertingInfo(server: Server | null) { * Hook for fetching webhook delivery history */ export function useWebhookDeliveries(server: Server | null) { + const isVisible = usePageVisibility(); const fetcher = async (): Promise => { if (!server) throw new Error('No server'); const client = getClient(server); @@ -81,7 +85,7 @@ export function useWebhookDeliveries(server: Server | null) { swrKey, fetcher, { - refreshInterval: 30000, + refreshInterval: isVisible ? 30000 : 0, revalidateOnFocus: true, dedupingInterval: 5000, } diff --git a/web-ui/src/lib/hooks/useAudit.ts b/web-ui/src/lib/hooks/useAudit.ts index 43500e1..e42eea0 100644 --- a/web-ui/src/lib/hooks/useAudit.ts +++ b/web-ui/src/lib/hooks/useAudit.ts @@ -4,11 +4,13 @@ import useSWR from 'swr'; import { Server } from '@/src/types/server'; import { AuditLogsResponse, AuditLogsParams } from '@/src/types/audit'; import { getClient } from '@/src/lib/api/client'; +import { usePageVisibility } from './usePageVisibility'; /** * Hook for fetching audit logs with filtering and pagination */ export function useAudit(server: Server | null, params?: AuditLogsParams) { + const isVisible = usePageVisibility(); const fetcher = async (): Promise => { if (!server) throw new Error('No server'); const client = getClient(server); @@ -24,7 +26,7 @@ export function useAudit(server: Server | null, params?: AuditLogsParams) { swrKey, fetcher, { - refreshInterval: 30000, // 30s refresh + refreshInterval: isVisible ? 30000 : 0, // 30s refresh (paused when tab hidden) revalidateOnFocus: true, dedupingInterval: 5000, } diff --git a/web-ui/src/lib/hooks/useContainers.ts b/web-ui/src/lib/hooks/useContainers.ts index c7704e6..3f89c72 100644 --- a/web-ui/src/lib/hooks/useContainers.ts +++ b/web-ui/src/lib/hooks/useContainers.ts @@ -7,6 +7,7 @@ import { Server } from '@/src/types/server'; import { getClient, CoreService } from '@/src/lib/api/client'; import { useEventStream } from '@/src/lib/events/useEventStream'; import { ServerEvent, isContainerEvent, ConnectionStatus } from '@/src/types/events'; +import { usePageVisibility } from './usePageVisibility'; export interface CreateContainerProgress { state: string; @@ -17,6 +18,7 @@ export interface CreateContainerProgress { * Hook for managing containers for a specific server */ export function useContainers(server: Server | null) { + const isVisible = usePageVisibility(); // Create a stable fetcher for this server const fetcher = async (): Promise => { if (!server) return []; @@ -71,7 +73,7 @@ export function useContainers(server: Server | null) { systemInfoKey, systemInfoFetcher, { - refreshInterval: 60000, // Refresh every minute + refreshInterval: isVisible ? 60000 : 0, // Refresh every minute (paused when tab hidden) revalidateOnFocus: false, dedupingInterval: 30000, } @@ -93,7 +95,7 @@ export function useContainers(server: Server | null) { coreServicesKey, coreServicesFetcher, { - refreshInterval: 30000, // Refresh every 30s + refreshInterval: isVisible ? 30000 : 0, // Refresh every 30s (paused when tab hidden) revalidateOnFocus: true, dedupingInterval: 10000, } diff --git a/web-ui/src/lib/hooks/useMetrics.ts b/web-ui/src/lib/hooks/useMetrics.ts index 40c60ea..c8b7e6f 100644 --- a/web-ui/src/lib/hooks/useMetrics.ts +++ b/web-ui/src/lib/hooks/useMetrics.ts @@ -5,6 +5,7 @@ import useSWR from 'swr'; import { ContainerMetrics, ContainerMetricsWithRate } from '@/src/types/container'; import { Server } from '@/src/types/server'; import { getClient } from '@/src/lib/api/client'; +import { usePageVisibility } from './usePageVisibility'; interface MetricsSnapshot { metrics: Record; @@ -15,6 +16,7 @@ interface MetricsSnapshot { * Hook for fetching container metrics with polling and CPU rate calculation */ export function useMetrics(server: Server | null, enabled: boolean = true) { + const isVisible = usePageVisibility(); // Store previous metrics for CPU rate calculation const prevSnapshot = useRef(null); @@ -30,7 +32,7 @@ export function useMetrics(server: Server | null, enabled: boolean = true) { swrKey, fetcher, { - refreshInterval: 5000, // Poll every 5 seconds for metrics + refreshInterval: (() => { const interval = isVisible ? 5000 : 0; console.log('[useMetrics] refreshInterval:', interval, 'isVisible:', isVisible); return interval; })(), revalidateOnFocus: true, dedupingInterval: 2000, } diff --git a/web-ui/src/lib/hooks/usePageVisibility.ts b/web-ui/src/lib/hooks/usePageVisibility.ts new file mode 100644 index 0000000..0f22186 --- /dev/null +++ b/web-ui/src/lib/hooks/usePageVisibility.ts @@ -0,0 +1,32 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; + +/** + * Hook that tracks whether the browser tab/window is active. + * Returns false when: + * - The tab is hidden (user switched browser tabs) + * - The browser window lost focus (user switched to another application) + */ +export function usePageVisibility(): boolean { + const [isVisible, setIsVisible] = useState(true); + + const update = useCallback(() => { + const visible = !document.hidden && document.hasFocus(); + console.log('[usePageVisibility]', visible ? 'ACTIVE' : 'INACTIVE', '| hidden:', document.hidden, 'hasFocus:', document.hasFocus()); + setIsVisible(visible); + }, []); + + useEffect(() => { + document.addEventListener('visibilitychange', update); + window.addEventListener('focus', update); + window.addEventListener('blur', update); + return () => { + document.removeEventListener('visibilitychange', update); + window.removeEventListener('focus', update); + window.removeEventListener('blur', update); + }; + }, [update]); + + return isVisible; +} diff --git a/web-ui/src/lib/hooks/useSecurity.ts b/web-ui/src/lib/hooks/useSecurity.ts index 3d830d3..f1be0a5 100644 --- a/web-ui/src/lib/hooks/useSecurity.ts +++ b/web-ui/src/lib/hooks/useSecurity.ts @@ -4,11 +4,13 @@ import useSWR from 'swr'; import { Server } from '@/src/types/server'; import { ClamavSummaryResponse } from '@/src/types/security'; import { getClient } from '@/src/lib/api/client'; +import { usePageVisibility } from './usePageVisibility'; /** * Hook for fetching ClamAV security summary */ export function useSecurity(server: Server | null) { + const isVisible = usePageVisibility(); const fetcher = async (): Promise => { if (!server) throw new Error('No server'); const client = getClient(server); @@ -21,7 +23,7 @@ export function useSecurity(server: Server | null) { swrKey, fetcher, { - refreshInterval: 60000, // 60s refresh + refreshInterval: isVisible ? 60000 : 0, // 60s refresh (paused when tab hidden) revalidateOnFocus: true, dedupingInterval: 10000, } diff --git a/web-ui/src/lib/hooks/useTraffic.ts b/web-ui/src/lib/hooks/useTraffic.ts index b9c0ed5..e5722ae 100644 --- a/web-ui/src/lib/hooks/useTraffic.ts +++ b/web-ui/src/lib/hooks/useTraffic.ts @@ -7,11 +7,13 @@ import { Connection, ConnectionSummary, HistoricalConnection, TrafficAggregate } import { getClient } from '@/src/lib/api/client'; import { useEventStream } from '@/src/lib/events/useEventStream'; import { ServerEvent } from '@/src/types/events'; +import { usePageVisibility } from './usePageVisibility'; /** * Hook for managing traffic monitoring for a specific container */ export function useTraffic(server: Server | null, containerName: string | null) { + const isVisible = usePageVisibility(); const [autoRefresh, setAutoRefresh] = useState(true); // Fetcher for active connections @@ -31,7 +33,7 @@ export function useTraffic(server: Server | null, containerName: string | null) isLoading: connectionsLoading, mutate: mutateConnections, } = useSWR(connectionsKey, connectionsFetcher, { - refreshInterval: autoRefresh ? 5000 : 0, // Poll every 5 seconds if auto-refresh enabled + refreshInterval: autoRefresh && isVisible ? 5000 : 0, // Poll every 5 seconds if auto-refresh enabled revalidateOnFocus: true, dedupingInterval: 2000, }); @@ -52,7 +54,7 @@ export function useTraffic(server: Server | null, containerName: string | null) isLoading: summaryLoading, mutate: mutateSummary, } = useSWR(summaryKey, summaryFetcher, { - refreshInterval: autoRefresh ? 5000 : 0, + refreshInterval: autoRefresh && isVisible ? 5000 : 0, revalidateOnFocus: true, dedupingInterval: 2000, });