diff --git a/ui/src/common/Pagination.jsx b/ui/src/common/Pagination.jsx
index e17d9e63e..0c1a864bb 100644
--- a/ui/src/common/Pagination.jsx
+++ b/ui/src/common/Pagination.jsx
@@ -1,6 +1,18 @@
-import React from 'react'
-import { Pagination as RAPagination } from 'react-admin'
+import React, { useEffect, useRef } from 'react'
+import { Pagination as RAPagination, useListContext } from 'react-admin'
+import { abortAllInFlight } from './useImageUrl'
-export const Pagination = (props) => (
-
-)
+export const Pagination = (props) => {
+ const { page, perPage } = useListContext()
+ const prevRef = useRef({ page, perPage })
+
+ useEffect(() => {
+ const prev = prevRef.current
+ if (prev.page !== page || prev.perPage !== perPage) {
+ abortAllInFlight()
+ prevRef.current = { page, perPage }
+ }
+ }, [page, perPage])
+
+ return
+}
diff --git a/ui/src/common/useImageUrl.js b/ui/src/common/useImageUrl.js
index ecb8f0a17..ae9f34003 100644
--- a/ui/src/common/useImageUrl.js
+++ b/ui/src/common/useImageUrl.js
@@ -4,6 +4,18 @@ import { useEffect, useState, useRef } from 'react'
// React Admin refreshes (which remount list items) don't re-fetch images.
const cache = new Map()
const MAX_CACHE_SIZE = 300
+const activeControllers = new Set()
+
+/**
+ * Aborts all in-flight image fetches. Call this before navigation/pagination
+ * so that pending image requests don't block the browser connection pool.
+ */
+export const abortAllInFlight = () => {
+ for (const controller of activeControllers) {
+ controller.abort()
+ }
+ activeControllers.clear()
+}
// Evicts oldest unused entries (Map iterates in insertion order).
const evictIfNeeded = () => {
@@ -53,6 +65,7 @@ export const useImageUrl = (url) => {
}
const controller = new AbortController()
+ activeControllers.add(controller)
setImgUrl(null)
setLoading(true)
setError(false)
@@ -83,8 +96,10 @@ export const useImageUrl = (url) => {
setImgUrl(objectUrl)
}
setLoading(false)
+ activeControllers.delete(controller)
})
.catch((err) => {
+ activeControllers.delete(controller)
if (err.name === 'AbortError') {
return // Expected on unmount or URL change
}
@@ -97,6 +112,7 @@ export const useImageUrl = (url) => {
return () => {
abortedRef.current = true
controller.abort()
+ activeControllers.delete(controller)
const entry = cache.get(url)
if (entry) {
entry.refCount--