From aff4e9b814b69009ca598c82bc4adb4eaaf16c85 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 18 Dec 2025 21:17:56 +0000 Subject: [PATCH] fix(frontend): resolve UI rate limiting on Docker overview (#859) Previously, each DockerContainerRow component made 2 API calls on mount: - AIAPI.getSettings() for AI enabled status - DockerMetadataAPI.getMetadata() for annotations With 100+ containers, this resulted in 200+ API calls firing simultaneously, exceeding the 500 requests/minute rate limit and causing 429 errors. Fix: - Lift AI settings check to DockerUnifiedTable parent component (1 call) - Use pre-fetched dockerMetadata prop for annotations (already batch-fetched) - Pass aiEnabled and initialNotes as props to child rows This reduces API calls from O(n*2) to O(1) when loading the Docker overview. Fixes #859 --- .../components/Docker/DockerUnifiedTable.tsx | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/frontend-modern/src/components/Docker/DockerUnifiedTable.tsx b/frontend-modern/src/components/Docker/DockerUnifiedTable.tsx index 1dec7b1..0ea79bc 100644 --- a/frontend-modern/src/components/Docker/DockerUnifiedTable.tsx +++ b/frontend-modern/src/components/Docker/DockerUnifiedTable.tsx @@ -772,6 +772,8 @@ const DockerContainerRow: Component<{ onCustomUrlUpdate?: (resourceId: string, url: string) => void; showHostContext?: boolean; resourceIndentClass?: string; + aiEnabled?: boolean; + initialNotes?: string[]; }> = (props) => { const { host, container } = props.row; const runtimeInfo = resolveHostRuntime(host); @@ -792,26 +794,21 @@ const DockerContainerRow: Component<{ }); let urlInputRef: HTMLInputElement | undefined; - // Annotations and AI state - const [aiEnabled, setAiEnabled] = createSignal(false); + // Annotations and AI state - use props passed from parent to avoid per-row API calls + const aiEnabled = () => props.aiEnabled ?? false; // Check if this container is in AI context const isInAIContext = createMemo(() => aiChatStore.enabled && aiChatStore.hasContextItem(resourceId())); - const [annotations, setAnnotations] = createSignal([]); + // Initialize annotations from props (pre-fetched metadata) instead of per-row API call + const [annotations, setAnnotations] = createSignal(props.initialNotes ?? []); const [newAnnotation, setNewAnnotation] = createSignal(''); const [saving, setSaving] = createSignal(false); - // Check if AI is enabled and load annotations on mount + // Update annotations if props change (e.g., parent re-fetches metadata) createEffect(() => { - AIAPI.getSettings() - .then((settings) => setAiEnabled(settings.enabled && settings.configured)) - .catch((err) => logger.debug('[DockerContainer] AI settings check failed:', err)); - - // Load existing annotations - DockerMetadataAPI.getMetadata(resourceId()) - .then((meta) => { - if (meta.notes && Array.isArray(meta.notes)) setAnnotations(meta.notes); - }) - .catch((err) => logger.debug('[DockerContainer] Failed to load annotations:', err)); + const notes = props.initialNotes; + if (notes && Array.isArray(notes)) { + setAnnotations(notes); + } }); const saveAnnotations = async (newAnnotations: string[]) => { @@ -2387,10 +2384,21 @@ const DockerUnifiedTable: Component = (props) => { // Use the breakpoint hook for responsive behavior const { isMobile } = useBreakpoint(); + // AI enabled state - fetched once at the parent level to avoid per-row API calls + const [aiEnabled, setAiEnabled] = createSignal(false); + + // Fetch AI settings once when component mounts + createEffect(() => { + AIAPI.getSettings() + .then((settings) => setAiEnabled(settings.enabled && settings.configured)) + .catch((err) => logger.debug('[DockerUnifiedTable] AI settings check failed:', err)); + }); + // Caches for stable object references to prevent re-animations const rowCache = new Map(); const tasksCache = new Map(); + const tokens = createMemo(() => parseSearchTerm(props.searchTerm)); const [sortKey, setSortKey] = usePersistentSignal('dockerUnifiedSortKey', 'host', { deserialize: (value) => (SORT_KEYS.includes(value as SortKey) ? (value as SortKey) : 'host'), @@ -2717,7 +2725,10 @@ const DockerUnifiedTable: Component = (props) => { onCustomUrlUpdate={props.onCustomUrlUpdate} showHostContext={!grouped} resourceIndentClass={grouped ? GROUPED_RESOURCE_INDENT : UNGROUPED_RESOURCE_INDENT} + aiEnabled={aiEnabled()} + initialNotes={metadata?.notes} /> + ) : (