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
This commit is contained in:
parent
ae70d9e423
commit
aff4e9b814
1 changed files with 25 additions and 14 deletions
|
|
@ -772,6 +772,8 @@ const DockerContainerRow: Component<{
|
||||||
onCustomUrlUpdate?: (resourceId: string, url: string) => void;
|
onCustomUrlUpdate?: (resourceId: string, url: string) => void;
|
||||||
showHostContext?: boolean;
|
showHostContext?: boolean;
|
||||||
resourceIndentClass?: string;
|
resourceIndentClass?: string;
|
||||||
|
aiEnabled?: boolean;
|
||||||
|
initialNotes?: string[];
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { host, container } = props.row;
|
const { host, container } = props.row;
|
||||||
const runtimeInfo = resolveHostRuntime(host);
|
const runtimeInfo = resolveHostRuntime(host);
|
||||||
|
|
@ -792,26 +794,21 @@ const DockerContainerRow: Component<{
|
||||||
});
|
});
|
||||||
let urlInputRef: HTMLInputElement | undefined;
|
let urlInputRef: HTMLInputElement | undefined;
|
||||||
|
|
||||||
// Annotations and AI state
|
// Annotations and AI state - use props passed from parent to avoid per-row API calls
|
||||||
const [aiEnabled, setAiEnabled] = createSignal(false);
|
const aiEnabled = () => props.aiEnabled ?? false;
|
||||||
// Check if this container is in AI context
|
// Check if this container is in AI context
|
||||||
const isInAIContext = createMemo(() => aiChatStore.enabled && aiChatStore.hasContextItem(resourceId()));
|
const isInAIContext = createMemo(() => aiChatStore.enabled && aiChatStore.hasContextItem(resourceId()));
|
||||||
const [annotations, setAnnotations] = createSignal<string[]>([]);
|
// Initialize annotations from props (pre-fetched metadata) instead of per-row API call
|
||||||
|
const [annotations, setAnnotations] = createSignal<string[]>(props.initialNotes ?? []);
|
||||||
const [newAnnotation, setNewAnnotation] = createSignal('');
|
const [newAnnotation, setNewAnnotation] = createSignal('');
|
||||||
const [saving, setSaving] = createSignal(false);
|
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(() => {
|
createEffect(() => {
|
||||||
AIAPI.getSettings()
|
const notes = props.initialNotes;
|
||||||
.then((settings) => setAiEnabled(settings.enabled && settings.configured))
|
if (notes && Array.isArray(notes)) {
|
||||||
.catch((err) => logger.debug('[DockerContainer] AI settings check failed:', err));
|
setAnnotations(notes);
|
||||||
|
}
|
||||||
// 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 saveAnnotations = async (newAnnotations: string[]) => {
|
const saveAnnotations = async (newAnnotations: string[]) => {
|
||||||
|
|
@ -2387,10 +2384,21 @@ const DockerUnifiedTable: Component<DockerUnifiedTableProps> = (props) => {
|
||||||
// Use the breakpoint hook for responsive behavior
|
// Use the breakpoint hook for responsive behavior
|
||||||
const { isMobile } = useBreakpoint();
|
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
|
// Caches for stable object references to prevent re-animations
|
||||||
const rowCache = new Map<string, DockerRow>();
|
const rowCache = new Map<string, DockerRow>();
|
||||||
const tasksCache = new Map<string, DockerTask[]>();
|
const tasksCache = new Map<string, DockerTask[]>();
|
||||||
|
|
||||||
|
|
||||||
const tokens = createMemo(() => parseSearchTerm(props.searchTerm));
|
const tokens = createMemo(() => parseSearchTerm(props.searchTerm));
|
||||||
const [sortKey, setSortKey] = usePersistentSignal<SortKey>('dockerUnifiedSortKey', 'host', {
|
const [sortKey, setSortKey] = usePersistentSignal<SortKey>('dockerUnifiedSortKey', 'host', {
|
||||||
deserialize: (value) => (SORT_KEYS.includes(value as SortKey) ? (value as SortKey) : 'host'),
|
deserialize: (value) => (SORT_KEYS.includes(value as SortKey) ? (value as SortKey) : 'host'),
|
||||||
|
|
@ -2717,7 +2725,10 @@ const DockerUnifiedTable: Component<DockerUnifiedTableProps> = (props) => {
|
||||||
onCustomUrlUpdate={props.onCustomUrlUpdate}
|
onCustomUrlUpdate={props.onCustomUrlUpdate}
|
||||||
showHostContext={!grouped}
|
showHostContext={!grouped}
|
||||||
resourceIndentClass={grouped ? GROUPED_RESOURCE_INDENT : UNGROUPED_RESOURCE_INDENT}
|
resourceIndentClass={grouped ? GROUPED_RESOURCE_INDENT : UNGROUPED_RESOURCE_INDENT}
|
||||||
|
aiEnabled={aiEnabled()}
|
||||||
|
initialNotes={metadata?.notes}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
<DockerServiceRow
|
<DockerServiceRow
|
||||||
row={row}
|
row={row}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue