diff --git a/frontend/src/pages/RegistryImageList.tsx b/frontend/src/pages/RegistryImageList.tsx index 826c7c8..a533c3c 100644 --- a/frontend/src/pages/RegistryImageList.tsx +++ b/frontend/src/pages/RegistryImageList.tsx @@ -72,6 +72,7 @@ function getDisplayImageName(fullImageName: string): string { export default function RegistryImageList() { const [images, setImages] = useState([]) + const [filterText, setFilterText] = useState('') const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [settingsOpen, setSettingsOpen] = useState(false) @@ -102,25 +103,41 @@ export default function RegistryImageList() { useEffect(() => { let abort = false - async function fetchImages() { + const controller = new AbortController() + + const fetchImages = async () => { setLoading(true) setError(null) try { - const res = await fetch('/api/v1/registry/image/list') + const url = new URL('/api/v1/registry/image/list', window.location.origin) + if (filterText.trim()) { + url.searchParams.set('filter', filterText.trim()) + } + + const res = await fetch(url.toString(), { signal: controller.signal }) if (!res.ok) throw new Error(`HTTP ${res.status}`) const result = await res.json() - // Backend returns: {status, msg, data: {images: [...]}} const list: RegistryImage[] = result.data?.images || [] - if (!abort) setImages(list) + if (!abort) { + setImages(list) + } } catch (e: any) { - if (!abort) setError(e.message) + if (e.name !== 'AbortError' && !abort) { + setError(e.message) + } } finally { if (!abort) setLoading(false) } } - fetchImages() - return () => { abort = true } - }, []) + + const timeoutId = setTimeout(fetchImages, 300) + + return () => { + abort = true + controller.abort() + clearTimeout(timeoutId) + } + }, [filterText]) const handleDownload = async (imageName: string) => { setDownloadingImage(imageName) @@ -351,6 +368,16 @@ export default function RegistryImageList() { + + setFilterText(e.target.value)} + size="small" + fullWidth + variant="outlined" + /> + {loading && } {error && 加载失败: {error}} {!loading && !error && ( @@ -388,7 +415,9 @@ export default function RegistryImageList() { ))} {images.length === 0 && ( - 暂无镜像 + + {filterText ? '没有匹配的镜像' : '暂无镜像'} + )} diff --git a/internal/module/registry/handler.list.go b/internal/module/registry/handler.list.go index 601b8bd..00a9fb4 100644 --- a/internal/module/registry/handler.list.go +++ b/internal/module/registry/handler.list.go @@ -13,6 +13,9 @@ import ( // RegistryImageList returns the list of images/repositories func RegistryImageList(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handler { return func(c fiber.Ctx) error { + // Get filter parameter from query string + filter := c.Query("filter", "") + // Get current registry_address setting var registryConfig model.RegistryConfig registryAddress := "" @@ -26,7 +29,11 @@ func RegistryImageList(ctx context.Context, db *gorm.DB, store store.Store) fibe var repositories []model.Repository // Query all repositories from the database - if err := db.Find(&repositories).Error; err != nil { + query := db + if filter != "" { + query = query.Where("name LIKE ?", "%"+filter+"%") + } + if err := query.Find(&repositories).Error; err != nil { return resp.R500(c, "", nil, err) }