Watch
1
0
Fork
You've already forked RedFlag
0

refactor: centralize polling intervals — replace hardcoded ms with POLL.* constants

Every hook that polled had its own magic number. POLL tiers (LIVE/DASHBOARD/
DETAIL/OVERVIEW/STATIC/HEALTH) declared once in polling.ts, consumed everywhere.
Mechanical change, no behavior shift.
This commit is contained in:
Fimeg 2026-06-11 01:58:02 -04:00
commit 8811ec9100
13 changed files with 66 additions and 35 deletions

View file

@ -1,17 +1,18 @@
import { useQuery } from '@tanstack/react-query';
import { healthApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import type { AdvisoryHealth } from '@/lib/api';
import type { UseQueryResult } from '@tanstack/react-query';
// Polls advisory-feed health for the degradation banner. The breaker heals on its
// own and packages re-vet on the next cycle, so a 30s cadence is plenty — this is
// own and packages re-vet on the next cycle, so DETAIL cadence is plenty — this is
// "is the feed up" awareness, not a real-time dashboard.
export const useAdvisoryHealth = (): UseQueryResult<AdvisoryHealth, Error> => {
return useQuery({
queryKey: ['advisory-health'],
queryFn: healthApi.getAdvisory,
refetchInterval: 30000,
staleTime: 15000,
retry: false, // a transient failure shouldn't itself raise the alarm
refetchInterval: POLL.DETAIL,
staleTime: POLL.DETAIL * 0.5,
retry: false,
});
};

View file

@ -2,6 +2,7 @@ import { useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import api from '@/lib/api';
import { useRealtimeStore } from '@/lib/store';
import { POLL } from '@/lib/polling';
// SystemEvent interface matching the backend model
interface SystemEvent {
@ -33,7 +34,7 @@ export const useAgentEvents = (
const {
severity = 'error,critical,warning',
limit = 50,
pollingInterval = 30000,
pollingInterval = POLL.DETAIL,
} = options;
const { data, isLoading, error, refetch } = useQuery({

View file

@ -1,5 +1,6 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { agentApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import type { Agent, ListQueryParams, AgentListResponse, ScanRequest } from '@/types';
import type { UseQueryResult, UseMutationResult } from '@tanstack/react-query';
@ -7,10 +8,10 @@ export const useAgents = (params?: ListQueryParams): UseQueryResult<AgentListRes
return useQuery({
queryKey: ['agents', params],
queryFn: () => agentApi.getAgents(params),
staleTime: 30 * 1000, // Consider data fresh for 30 seconds
refetchInterval: 60 * 1000, // Poll every 60 seconds
refetchIntervalInBackground: false, // Don't poll when tab is inactive
refetchOnWindowFocus: true, // Refresh when window gains focus
staleTime: POLL.OVERVIEW * 0.5,
refetchInterval: POLL.OVERVIEW,
refetchIntervalInBackground: false,
refetchOnWindowFocus: true,
});
};
@ -19,10 +20,10 @@ export const useAgent = (id: string, enabled: boolean = true): UseQueryResult<Ag
queryKey: ['agent', id],
queryFn: () => agentApi.getAgent(id),
enabled: enabled && !!id,
staleTime: 30 * 1000, // Consider data fresh for 30 seconds
refetchInterval: 30 * 1000, // Poll every 30 seconds for selected agent
refetchIntervalInBackground: false, // Don't poll when tab is inactive
refetchOnWindowFocus: true, // Refresh when window gains focus
staleTime: POLL.DETAIL * 0.5,
refetchInterval: POLL.DETAIL,
refetchIntervalInBackground: false,
refetchOnWindowFocus: true,
});
};

View file

@ -1,5 +1,6 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { updateApi, agentApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import type { UseQueryResult, UseMutationResult } from '@tanstack/react-query';
interface ActiveCommand {
@ -19,8 +20,8 @@ export const useActiveCommands = (autoRefresh: boolean = true): UseQueryResult<{
return useQuery({
queryKey: ['activeCommands'],
queryFn: () => updateApi.getActiveCommands(),
refetchInterval: autoRefresh ? 5000 : false, // Auto-refresh every 5 seconds when enabled
staleTime: 0, // Override global staleTime to allow refetchInterval to work
refetchInterval: autoRefresh ? POLL.LIVE : false,
staleTime: 0,
});
};

View file

@ -1,5 +1,6 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { dockerApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import toast from 'react-hot-toast';
// Hook for fetching all Docker containers/images across all agents
@ -128,7 +129,7 @@ export const useAgentRuntimeContainers = (agentId: string) => {
queryKey: ['agent-runtime-containers', agentId],
queryFn: () => dockerApi.getAgentRuntimeContainers(agentId),
enabled: !!agentId,
refetchInterval: 30000,
refetchInterval: POLL.DETAIL,
});
};
@ -137,7 +138,7 @@ export const useAgentRuntimeStacks = (agentId: string) => {
queryKey: ['agent-runtime-stacks', agentId],
queryFn: () => dockerApi.getAgentRuntimeStacks(agentId),
enabled: !!agentId,
refetchInterval: 30000,
refetchInterval: POLL.DETAIL,
});
};
@ -145,7 +146,7 @@ export const useFleetRuntimeContainers = () => {
return useQuery({
queryKey: ['fleet-runtime-containers'],
queryFn: () => dockerApi.getFleetRuntimeContainers(),
refetchInterval: 30000,
refetchInterval: POLL.DETAIL,
});
};
@ -153,7 +154,7 @@ export const useFleetRuntimeStacks = () => {
return useQuery({
queryKey: ['fleet-runtime-stacks'],
queryFn: () => dockerApi.getFleetRuntimeStacks(),
refetchInterval: 30000,
refetchInterval: POLL.DETAIL,
});
};

View file

@ -1,6 +1,7 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'react-hot-toast';
import { adminApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import { CreateMaintenanceWindowRequest } from '@/types';
export const maintenanceWindowKeys = {
@ -16,7 +17,7 @@ export const useMaintenanceWindows = () => {
return useQuery({
queryKey: maintenanceWindowKeys.list(),
queryFn: () => adminApi.maintenanceWindows.list(),
staleTime: 1000 * 60,
staleTime: POLL.OVERVIEW * 0.5,
});
};
@ -25,7 +26,7 @@ export const useMaintenanceWindow = (id: string) => {
queryKey: maintenanceWindowKeys.detail(id),
queryFn: () => adminApi.maintenanceWindows.get(id),
enabled: !!id,
staleTime: 1000 * 60,
staleTime: POLL.OVERVIEW * 0.5,
});
};
@ -33,8 +34,8 @@ export const useMaintenanceWindowCheck = () => {
return useQuery({
queryKey: maintenanceWindowKeys.check(),
queryFn: () => adminApi.maintenanceWindows.check(),
staleTime: 1000 * 30,
refetchInterval: 1000 * 60,
staleTime: POLL.DETAIL * 0.5,
refetchInterval: POLL.OVERVIEW,
});
};

View file

@ -1,6 +1,7 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'react-hot-toast';
import { adminApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import { RateLimitSettings } from '@/types';
export const rateLimitKeys = {
@ -13,7 +14,7 @@ export const useRateLimitSettings = () => {
return useQuery({
queryKey: rateLimitKeys.settings(),
queryFn: () => adminApi.rateLimits.getSettings(),
staleTime: 1000 * 60 * 5,
staleTime: POLL.STATIC * 0.2,
});
};
@ -21,8 +22,8 @@ export const useRateLimitStats = () => {
return useQuery({
queryKey: rateLimitKeys.stats(),
queryFn: () => adminApi.rateLimits.getStats(),
staleTime: 1000 * 30,
refetchInterval: 1000 * 30,
staleTime: POLL.DETAIL * 0.5,
refetchInterval: POLL.DETAIL,
});
};

View file

@ -1,6 +1,7 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'react-hot-toast';
import { adminApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import {
CreateRegistrationTokenRequest,
} from '@/types';
@ -42,8 +43,8 @@ export const useRegistrationTokenStats = () => {
return useQuery({
queryKey: registrationTokenKeys.stats(),
queryFn: () => adminApi.tokens.getStats(),
staleTime: 1000 * 60, // 1 minute
refetchInterval: 1000 * 60 * 5, // Refresh every 5 minutes
staleTime: POLL.STATIC * 0.2,
refetchInterval: POLL.STATIC,
});
};

View file

@ -1,5 +1,6 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api, securityApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import {
SecuritySettings,
SecuritySettingsResponse,
@ -167,7 +168,7 @@ export const useSecuritySettings = () => {
queryKey: ['security', 'overview'],
queryFn: () => securityApi.getOverview(),
staleTime: 60 * 1000, // 1 minute
refetchInterval: 60 * 1000, // Auto-refresh every minute
refetchInterval: POLL.OVERVIEW,
});
// Update single setting mutation

View file

@ -1,5 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { statsApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import type { DashboardStats } from '@/types';
import type { UseQueryResult } from '@tanstack/react-query';
@ -7,7 +8,7 @@ export const useDashboardStats = (): UseQueryResult<DashboardStats, Error> => {
return useQuery({
queryKey: ['dashboard-stats'],
queryFn: statsApi.getDashboardStats,
refetchInterval: 15000, // Refresh every 15 seconds (dashboard is the operator's primary view)
staleTime: 10000, // Consider data stale after 10 seconds
refetchInterval: POLL.DASHBOARD,
staleTime: POLL.DASHBOARD * 0.67,
});
};

View file

@ -1,5 +1,6 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { updateApi, capabilityTokenApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import type { UpdatePackage, ListQueryParams, UpdateApprovalRequest, UpdateListResponse, PackageListResponse, PackageFleetResponse, PackageVersionsResponse, CapabilityTokenStatusResponse, PackageSummary, PackageAgentsResponse, PackageVulnerabilitiesResponse } from '@/types';
import type { UseQueryResult, UseMutationResult } from '@tanstack/react-query';
@ -54,7 +55,7 @@ export const usePackageAgents = (pkgType: string, pkgName: string, enabled: bool
queryKey: ['package-agents', pkgType, pkgName],
queryFn: () => updateApi.getPackageAgentsByCoords(pkgType, pkgName),
enabled: enabled && !!pkgType && !!pkgName,
refetchInterval: 10000,
refetchInterval: POLL.DASHBOARD,
});
};

View file

@ -1,6 +1,7 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'react-hot-toast';
import { adminApi } from '@/lib/api';
import { POLL } from '@/lib/polling';
import { CreateTrackedSoftwareRequest } from '@/types';
export const upstreamKeys = {
@ -16,7 +17,7 @@ export const useTrackedSoftware = () => {
return useQuery({
queryKey: upstreamKeys.list(),
queryFn: () => adminApi.upstream.list(),
staleTime: 1000 * 60,
staleTime: POLL.STATIC * 0.2,
});
};
@ -24,8 +25,8 @@ export const useDriftedSoftware = () => {
return useQuery({
queryKey: upstreamKeys.drift(),
queryFn: () => adminApi.upstream.listDrifted(),
staleTime: 1000 * 60,
refetchInterval: 1000 * 60 * 5,
staleTime: POLL.STATIC * 0.2,
refetchInterval: POLL.STATIC,
});
};

19
web/src/lib/polling.ts Normal file
View file

@ -0,0 +1,19 @@
// Centralized polling intervals. Every hook that polls uses these constants
// instead of hardcoded magic numbers. The tiers are based on how volatile the
// data is and how directly the operator is watching it.
//
// LIVE — real-time operations the operator is actively watching (commands)
// DASHBOARD — primary operator view, needs to feel responsive (stats, updates)
// DETAIL — single-entity pages the operator opened on purpose (agent, package)
// OVERVIEW — fleet-wide lists, less time-critical (agent list, settings)
// STATIC — admin data that almost never changes (tokens, upstream tracking)
// HEALTH — fallback connection check (only when disconnected + unauthenticated)
export const POLL = {
LIVE: 5_000,
DASHBOARD: 15_000,
DETAIL: 30_000,
OVERVIEW: 60_000,
STATIC: 300_000,
HEALTH: 10_000,
} as const;