From c7aa5bffba953d8bfbf07be788dbcbdd84d6e66f Mon Sep 17 00:00:00 2001 From: mendarb Date: Sun, 22 Mar 2026 16:52:37 +0100 Subject: [PATCH] feat(ui): show global/org registries in repo/org registries tab Replicates the existing pattern used for secrets to also show global and organization-level registries in the repository and organization registries tabs as read-only entries with level badges. Changes: - Repo registries tab now loads and displays org and global registries - Org registries tab now loads and displays global registries - RegistryList component shows a badge indicating the registry level - Added locale keys for global/org registry labels Closes #5950 --- web/src/assets/locales/en.json | 2 + web/src/components/registry/RegistryList.vue | 12 +++-- web/src/views/org/settings/OrgRegistries.vue | 42 +++++++++++++++-- web/src/views/repo/settings/Registries.vue | 47 ++++++++++++++++++-- 4 files changed, 94 insertions(+), 9 deletions(-) diff --git a/web/src/assets/locales/en.json b/web/src/assets/locales/en.json index 32378f7edc3..7a920c6425b 100644 --- a/web/src/assets/locales/en.json +++ b/web/src/assets/locales/en.json @@ -517,6 +517,8 @@ "update_woodpecker": "Please update your Woodpecker instance to {0}", "global_level_secret": "global secret", "org_level_secret": "organization secret", + "global_level_registry": "global registry", + "org_level_registry": "organization registry", "login_to_cli": "Login to CLI", "login_to_cli_description": "If you continue, you will be logged in to the CLI.", "abort": "Abort", diff --git a/web/src/components/registry/RegistryList.vue b/web/src/components/registry/RegistryList.vue index 986f9c4ca25..6be282f88ef 100644 --- a/web/src/components/registry/RegistryList.vue +++ b/web/src/components/registry/RegistryList.vue @@ -6,15 +6,20 @@ class="bg-wp-background-200! dark:bg-wp-background-200! items-center" > {{ registry.address }} +
>(); const isEditing = computed(() => !!selectedRegistry.value?.id); -async function loadRegistries(page: number): Promise { - return apiClient.getOrgRegistryList(org.value.id, { page }); +async function loadRegistries(page: number, level: 'org' | 'global'): Promise { + switch (level) { + case 'org': + return apiClient.getOrgRegistryList(org.value.id, { page }); + case 'global': + return apiClient.getGlobalRegistryList({ page }); + default: + throw new Error(`Unexpected level: ${level}`); + } } -const { resetPage, data: registries, loading } = usePagination(loadRegistries, () => !selectedRegistry.value); +const { + resetPage, + data: _registries, + loading, +} = usePagination(loadRegistries, () => !selectedRegistry.value, { + each: ['org', 'global'], +}); +const registries = computed(() => { + const registriesList: Record = {}; + + for (const level of ['org', 'global']) { + for (const registry of _registries.value) { + if ( + ((level === 'org' && registry.org_id !== 0) || (level === 'global' && registry.org_id === 0)) && + !registriesList[registry.address] + ) { + registriesList[registry.address] = { ...registry, edit: registry.org_id !== 0, level }; + } + } + } + + const levelsOrder = { + global: 0, + org: 1, + }; + + return Object.values(registriesList) + .toSorted((a, b) => a.address.localeCompare(b.address)) + .toSorted((a, b) => levelsOrder[b.level] - levelsOrder[a.level]); +}); const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => { if (!selectedRegistry.value) { diff --git a/web/src/views/repo/settings/Registries.vue b/web/src/views/repo/settings/Registries.vue index 7955598a1c9..27013744745 100644 --- a/web/src/views/repo/settings/Registries.vue +++ b/web/src/views/repo/settings/Registries.vue @@ -60,11 +60,52 @@ const repo = requiredInject('repo'); const selectedRegistry = ref>(); const isEditingRegistry = computed(() => !!selectedRegistry.value?.id); -async function loadRegistries(page: number): Promise { - return apiClient.getRegistryList(repo.value.id, { page }); +async function loadRegistries(page: number, level: 'repo' | 'org' | 'global'): Promise { + switch (level) { + case 'repo': + return apiClient.getRegistryList(repo.value.id, { page }); + case 'org': + return apiClient.getOrgRegistryList(repo.value.org_id, { page }); + case 'global': + return apiClient.getGlobalRegistryList({ page }); + default: + throw new Error(`Unexpected level: ${level}`); + } } -const { resetPage, data: registries, loading } = usePagination(loadRegistries, () => !selectedRegistry.value); +const { + resetPage, + data: _registries, + loading, +} = usePagination(loadRegistries, () => !selectedRegistry.value, { + each: ['repo', 'org', 'global'], +}); +const registries = computed(() => { + const registriesList: Record = {}; + + for (const level of ['repo', 'org', 'global']) { + for (const registry of _registries.value) { + if ( + ((level === 'repo' && registry.repo_id !== 0 && registry.org_id === 0) || + (level === 'org' && registry.repo_id === 0 && registry.org_id !== 0) || + (level === 'global' && registry.repo_id === 0 && registry.org_id === 0)) && + !registriesList[registry.address] + ) { + registriesList[registry.address] = { ...registry, edit: registry.repo_id !== 0, level }; + } + } + } + + const levelsOrder = { + global: 0, + org: 1, + repo: 2, + }; + + return Object.values(registriesList) + .toSorted((a, b) => a.address.localeCompare(b.address)) + .toSorted((a, b) => levelsOrder[b.level] - levelsOrder[a.level]); +}); const { doSubmit: createRegistry, isLoading: isSaving } = useAsyncAction(async () => { if (!selectedRegistry.value) {