diff --git a/app/docker/__module.js b/app/docker/__module.js
index 21cfc5149..5756fcdf1 100644
--- a/app/docker/__module.js
+++ b/app/docker/__module.js
@@ -603,7 +603,7 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
url: '/registries',
views: {
'content@': {
- component: 'endpointRegistriesView',
+ component: 'environmentRegistriesView',
},
},
data: {
@@ -616,7 +616,7 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
url: '/registries',
views: {
'content@': {
- component: 'endpointRegistriesView',
+ component: 'environmentRegistriesView',
},
},
data: {
diff --git a/app/kubernetes/__module.js b/app/kubernetes/__module.js
index 68af4628b..e4fa9fb6a 100644
--- a/app/kubernetes/__module.js
+++ b/app/kubernetes/__module.js
@@ -518,7 +518,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
url: '/registries',
views: {
'content@': {
- component: 'endpointRegistriesView',
+ component: 'environmentRegistriesView',
},
},
data: {
diff --git a/app/portainer/__module.js b/app/portainer/__module.js
index b07c557e5..90abd8f90 100644
--- a/app/portainer/__module.js
+++ b/app/portainer/__module.js
@@ -356,8 +356,7 @@ angular
url: '/registries',
views: {
'content@': {
- templateUrl: './views/registries/registries.html',
- controller: 'RegistriesController',
+ component: 'registriesView',
},
},
data: {
diff --git a/app/portainer/components/datatables/registries-datatable/registriesDatatable.html b/app/portainer/components/datatables/registries-datatable/registriesDatatable.html
deleted file mode 100644
index f5c5ba9f3..000000000
--- a/app/portainer/components/datatables/registries-datatable/registriesDatatable.html
+++ /dev/null
@@ -1,145 +0,0 @@
-
diff --git a/app/portainer/components/datatables/registries-datatable/registriesDatatable.js b/app/portainer/components/datatables/registries-datatable/registriesDatatable.js
deleted file mode 100644
index e5acb6907..000000000
--- a/app/portainer/components/datatables/registries-datatable/registriesDatatable.js
+++ /dev/null
@@ -1,16 +0,0 @@
-angular.module('portainer.app').component('registriesDatatable', {
- templateUrl: './registriesDatatable.html',
- controller: 'RegistriesDatatableController',
- bindings: {
- titleText: '@',
- titleIcon: '@',
- dataset: '<',
- tableKey: '@',
- orderBy: '@',
- reverseOrder: '<',
- removeAction: '<',
- canBrowse: '<',
- endpointType: '<',
- canManageAccess: '<',
- },
-});
diff --git a/app/portainer/components/datatables/registries-datatable/registriesDatatableController.js b/app/portainer/components/datatables/registries-datatable/registriesDatatableController.js
deleted file mode 100644
index da737c8e2..000000000
--- a/app/portainer/components/datatables/registries-datatable/registriesDatatableController.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import { FeatureId } from '@/react/portainer/feature-flags/enums';
-import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
-
-angular.module('portainer.docker').controller('RegistriesDatatableController', RegistriesDatatableController);
-
-/* @ngInject */
-function RegistriesDatatableController($scope, $controller, $state, Authentication, DatatableService) {
- angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
-
- this.allowSelection = function (item) {
- return item.Id;
- };
-
- this.enableGoToLink = (item) => {
- return this.isAdmin && item.Id && !this.endpointType;
- };
-
- this.goToRegistry = function (item) {
- if (
- this.endpointType === PortainerEndpointTypes.KubernetesLocalEnvironment ||
- this.endpointType === PortainerEndpointTypes.AgentOnKubernetesEnvironment ||
- this.endpointType === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment
- ) {
- $state.go('kubernetes.registries.registry', { id: item.Id });
- } else if (
- this.endpointType === PortainerEndpointTypes.DockerEnvironment ||
- this.endpointType === PortainerEndpointTypes.AgentOnDockerEnvironment ||
- this.endpointType === PortainerEndpointTypes.EdgeAgentOnDockerEnvironment
- ) {
- $state.go('docker.host.registries.registry', { id: item.Id });
- } else {
- $state.go('portainer.registries.registry', { id: item.Id });
- }
- };
-
- this.redirectToManageAccess = function (item) {
- if (
- this.endpointType === PortainerEndpointTypes.KubernetesLocalEnvironment ||
- this.endpointType === PortainerEndpointTypes.AgentOnKubernetesEnvironment ||
- this.endpointType === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment
- ) {
- $state.go('kubernetes.registries.access', { id: item.Id });
- } else {
- if (window.location.hash.endsWith('/docker/swarm/registries')) {
- $state.go('docker.swarm.registries.access', { id: item.Id });
- } else {
- $state.go('docker.host.registries.access', { id: item.Id });
- }
- }
- };
-
- this.$onInit = function () {
- this.limitedFeature = FeatureId.REGISTRY_MANAGEMENT;
- this.isAdmin = Authentication.isAdmin();
- this.setDefaults();
- this.prepareTableFromDataset();
-
- this.state.orderBy = this.orderBy;
- var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
- if (storedOrder !== null) {
- this.state.reverseOrder = storedOrder.reverse;
- this.state.orderBy = storedOrder.orderBy;
- }
-
- var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
- if (textFilter !== null) {
- this.state.textFilter = textFilter;
- this.onTextFilterChange();
- }
-
- var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
- if (storedFilters !== null) {
- this.filters = storedFilters;
- }
- if (this.filters && this.filters.state) {
- this.filters.state.open = false;
- }
-
- var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
- if (storedSettings !== null) {
- this.settings = storedSettings;
- this.settings.open = false;
- }
- this.onSettingsRepeaterChange();
-
- var storedColumnVisibility = DatatableService.getColumnVisibilitySettings(this.tableKey);
- if (storedColumnVisibility !== null) {
- this.columnVisibility = storedColumnVisibility;
- }
- };
-}
diff --git a/app/portainer/react/components/registries.ts b/app/portainer/react/components/registries.ts
index 08fe2bd5c..b096f373a 100644
--- a/app/portainer/react/components/registries.ts
+++ b/app/portainer/react/components/registries.ts
@@ -2,28 +2,11 @@ import angular from 'angular';
import { r2a } from '@/react-tools/react2angular';
import { withReactQuery } from '@/react-tools/withReactQuery';
-import {
- DefaultRegistryAction,
- DefaultRegistryDomain,
- DefaultRegistryName,
-} from '@/react/portainer/registries/ListView/DefaultRegistry';
-import { RepositoriesDatatable } from '@/react/portainer/registries/repositories/ListView/RepositoriesDatatable';
import { withUIRouter } from '@/react-tools/withUIRouter';
+import { RepositoriesDatatable } from '@/react/portainer/registries/repositories/ListView/RepositoriesDatatable';
export const registriesModule = angular
.module('portainer.app.react.components.registries', [])
- .component(
- 'defaultRegistryName',
- r2a(withReactQuery(DefaultRegistryName), [])
- )
- .component(
- 'defaultRegistryAction',
- r2a(withReactQuery(DefaultRegistryAction), [])
- )
- .component(
- 'defaultRegistryDomain',
- r2a(withReactQuery(DefaultRegistryDomain), [])
- )
.component(
'registryRepositoriesDatatable',
r2a(withUIRouter(withReactQuery(RepositoriesDatatable)), ['dataset'])
diff --git a/app/portainer/react/views/index.ts b/app/portainer/react/views/index.ts
index 6e067d8b4..f3ad06fda 100644
--- a/app/portainer/react/views/index.ts
+++ b/app/portainer/react/views/index.ts
@@ -17,6 +17,7 @@ import { wizardModule } from './wizard';
import { teamsModule } from './teams';
import { updateSchedulesModule } from './update-schedules';
import { environmentGroupModule } from './env-groups';
+import { registriesModule } from './registries';
export const viewsModule = angular
.module('portainer.app.react.views', [
@@ -24,6 +25,7 @@ export const viewsModule = angular
teamsModule,
updateSchedulesModule,
environmentGroupModule,
+ registriesModule,
])
.component(
'homeView',
diff --git a/app/portainer/react/views/registries.ts b/app/portainer/react/views/registries.ts
new file mode 100644
index 000000000..7b4cfdd2e
--- /dev/null
+++ b/app/portainer/react/views/registries.ts
@@ -0,0 +1,19 @@
+import angular from 'angular';
+
+import { r2a } from '@/react-tools/react2angular';
+import { withCurrentUser } from '@/react-tools/withCurrentUser';
+import { withReactQuery } from '@/react-tools/withReactQuery';
+import { withUIRouter } from '@/react-tools/withUIRouter';
+import { ListView } from '@/react/portainer/registries/ListView';
+import { ListView as EnvironmentListView } from '@/react/portainer/registries/environments/ListView';
+
+export const registriesModule = angular
+ .module('portainer.app.react.views.registries', [])
+ .component(
+ 'registriesView',
+ r2a(withUIRouter(withReactQuery(withCurrentUser(ListView))), [])
+ )
+ .component(
+ 'environmentRegistriesView',
+ r2a(withUIRouter(withReactQuery(withCurrentUser(EnvironmentListView))), [])
+ ).name;
diff --git a/app/portainer/views/endpoint-registries/registries.html b/app/portainer/views/endpoint-registries/registries.html
deleted file mode 100644
index 770dcd5b0..000000000
--- a/app/portainer/views/endpoint-registries/registries.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
diff --git a/app/portainer/views/endpoint-registries/registries.js b/app/portainer/views/endpoint-registries/registries.js
deleted file mode 100644
index 61dfc3b42..000000000
--- a/app/portainer/views/endpoint-registries/registries.js
+++ /dev/null
@@ -1,7 +0,0 @@
-angular.module('portainer.app').component('endpointRegistriesView', {
- templateUrl: './registries.html',
- controller: 'EndpointRegistriesController',
- bindings: {
- endpoint: '<',
- },
-});
diff --git a/app/portainer/views/endpoint-registries/registriesController.js b/app/portainer/views/endpoint-registries/registriesController.js
deleted file mode 100644
index 72be8f17a..000000000
--- a/app/portainer/views/endpoint-registries/registriesController.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import _ from 'lodash-es';
-import { RegistryTypes } from 'Portainer/models/registryTypes';
-
-class EndpointRegistriesController {
- /* @ngInject */
- constructor($async, Notifications, EndpointService, Authentication) {
- this.$async = $async;
- this.Notifications = Notifications;
- this.EndpointService = EndpointService;
- this.Authentication = Authentication;
-
- this.canManageAccess = this.canManageAccess.bind(this);
- this.canBrowse = this.canBrowse.bind(this);
- }
-
- canManageAccess(item) {
- return item.Type !== RegistryTypes.ANONYMOUS && this.Authentication.isAdmin();
- }
-
- canBrowse(item) {
- return !_.includes([RegistryTypes.ANONYMOUS, RegistryTypes.DOCKERHUB, RegistryTypes.QUAY], item.Type);
- }
-
- getRegistries() {
- return this.$async(async () => {
- try {
- this.registries = await this.EndpointService.registries(this.endpointId);
- } catch (err) {
- this.Notifications.error('Failure', err, 'Unable to retrieve registries');
- }
- });
- }
-
- $onInit() {
- return this.$async(async () => {
- this.state = {
- viewReady: false,
- };
-
- try {
- this.endpointType = this.endpoint.Type;
- this.endpointId = this.endpoint.Id;
- await this.getRegistries();
- } catch (err) {
- this.Notifications.error('Failure', err, 'Unable to retrieve registries');
- } finally {
- this.state.viewReady = true;
- }
- });
- }
-}
-
-export default EndpointRegistriesController;
-angular.module('portainer.app').controller('EndpointRegistriesController', EndpointRegistriesController);
diff --git a/app/portainer/views/registries/registries.html b/app/portainer/views/registries/registries.html
deleted file mode 100644
index f266b5e9d..000000000
--- a/app/portainer/views/registries/registries.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- View registries via an environment to manage access for user(s) and/or team(s)
-
-
-
diff --git a/app/portainer/views/registries/registriesController.js b/app/portainer/views/registries/registriesController.js
deleted file mode 100644
index 046c463b6..000000000
--- a/app/portainer/views/registries/registriesController.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import _ from 'lodash-es';
-import { confirmDelete } from '@@/modals/confirm';
-import { RegistryTypes } from 'Portainer/models/registryTypes';
-
-angular.module('portainer.app').controller('RegistriesController', [
- '$q',
- '$scope',
- '$state',
- 'RegistryService',
- 'Notifications',
- function ($q, $scope, $state, RegistryService, Notifications) {
- $scope.state = {
- actionInProgress: false,
- };
-
- const nonBrowsableTypes = [RegistryTypes.ANONYMOUS, RegistryTypes.DOCKERHUB, RegistryTypes.QUAY];
-
- $scope.canBrowse = function (item) {
- return !_.includes(nonBrowsableTypes, item.Type);
- };
-
- $scope.removeAction = function (selectedItems) {
- const regAttrMsg = selectedItems.length > 1 ? 'hese' : 'his';
- const registriesMsg = selectedItems.length > 1 ? 'registries' : 'registry';
- const msg = `T${regAttrMsg} ${registriesMsg} might be used by applications inside one or more environments. Removing the ${registriesMsg} could lead to a service interruption for the applications using t${regAttrMsg} ${registriesMsg}. Do you want to remove the selected ${registriesMsg}?`;
-
- confirmDelete(msg).then((confirmed) => {
- if (!confirmed) {
- return;
- }
- deleteSelectedRegistries(selectedItems);
- });
- };
-
- function deleteSelectedRegistries(selectedItems) {
- var actionCount = selectedItems.length;
- angular.forEach(selectedItems, function (registry) {
- RegistryService.deleteRegistry(registry.Id)
- .then(function success() {
- Notifications.success('Registry successfully removed', registry.Name);
- var index = $scope.registries.indexOf(registry);
- $scope.registries.splice(index, 1);
- })
- .catch(function error(err) {
- Notifications.error('Failure', err, 'Unable to remove registry');
- })
- .finally(function final() {
- --actionCount;
- if (actionCount === 0) {
- $state.reload();
- }
- });
- });
- }
-
- function initView() {
- $q.all({
- registries: RegistryService.registries(),
- })
- .then(function success(data) {
- $scope.registries = data.registries;
- })
- .catch(function error(err) {
- $scope.registries = [];
- Notifications.error('Failure', err, 'Unable to retrieve registries');
- });
- }
-
- initView();
- },
-]);
diff --git a/app/react/components/BEFeatureIndicator/BEFeatureIndicator.tsx b/app/react/components/BEFeatureIndicator/BEFeatureIndicator.tsx
index ebccf3bce..47ff2e787 100644
--- a/app/react/components/BEFeatureIndicator/BEFeatureIndicator.tsx
+++ b/app/react/components/BEFeatureIndicator/BEFeatureIndicator.tsx
@@ -1,4 +1,4 @@
-import { PropsWithChildren } from 'react';
+import { ReactNode } from 'react';
import clsx from 'clsx';
import { Briefcase } from 'lucide-react';
@@ -11,32 +11,39 @@ import { Icon } from '@@/Icon';
import { getFeatureDetails } from './utils';
export interface Props {
- featureId?: FeatureId;
+ featureId: FeatureId;
showIcon?: boolean;
className?: string;
+ children?: (isLimited: boolean) => ReactNode;
}
export function BEFeatureIndicator({
featureId,
- children,
+ children = () => null,
showIcon = true,
className = '',
-}: PropsWithChildren) {
- const { url, limitedToBE } = getFeatureDetails(featureId);
+}: Props) {
+ const { url, limitedToBE = false } = getFeatureDetails(featureId);
- if (!limitedToBE) {
- return null;
- }
return (
-
- {children}
- {showIcon && }
- Business Feature
-
+ <>
+ {limitedToBE && (
+
+ {showIcon && (
+
+ )}
+
+ Business Feature
+
+
+ )}
+
+ {children(limitedToBE)}
+ >
);
}
diff --git a/app/react/components/form-components/SwitchField/Switch.tsx b/app/react/components/form-components/SwitchField/Switch.tsx
index 2ad968fae..1a6ae26a2 100644
--- a/app/react/components/form-components/SwitchField/Switch.tsx
+++ b/app/react/components/form-components/SwitchField/Switch.tsx
@@ -55,7 +55,7 @@ export function Switch({
/>
- {limitedToBE && }
+ {featureId && limitedToBE && }
>
);
}
diff --git a/app/react/docker/images/ItemView/RegistrySelectPrompt.tsx b/app/react/docker/images/ItemView/RegistrySelectPrompt.tsx
index 024af8454..de1c8541d 100644
--- a/app/react/docker/images/ItemView/RegistrySelectPrompt.tsx
+++ b/app/react/docker/images/ItemView/RegistrySelectPrompt.tsx
@@ -1,6 +1,6 @@
import { useState } from 'react';
-import { Registry } from '@/react/portainer/registries/types';
+import { Registry } from '@/react/portainer/registries/types/registry';
import { Modal, OnSubmit, openModal } from '@@/modals';
import { Button } from '@@/buttons';
diff --git a/app/react/docker/services/webhooks/types.ts b/app/react/docker/services/webhooks/types.ts
index 5eef4e071..4f5991b06 100644
--- a/app/react/docker/services/webhooks/types.ts
+++ b/app/react/docker/services/webhooks/types.ts
@@ -1,5 +1,5 @@
import { Environment } from '@/react/portainer/environments/types';
-import { Registry } from '@/react/portainer/registries/types';
+import { Registry } from '@/react/portainer/registries/types/registry';
enum WebhookType {
Service = 1,
diff --git a/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/GitForm.tsx b/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/GitForm.tsx
index 04a43235b..76d6b2406 100644
--- a/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/GitForm.tsx
+++ b/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/GitForm.tsx
@@ -29,7 +29,7 @@ import { EdgeGroupsSelector } from '@/react/edge/edge-stacks/components/EdgeGrou
import { EdgeStackDeploymentTypeSelector } from '@/react/edge/edge-stacks/components/EdgeStackDeploymentTypeSelector';
import { notifySuccess } from '@/portainer/services/notifications';
import { EnvironmentType } from '@/react/portainer/environments/types';
-import { Registry } from '@/react/portainer/registries/types';
+import { Registry } from '@/react/portainer/registries/types/registry';
import { useRegistries } from '@/react/portainer/registries/queries/useRegistries';
import { RelativePathFieldset } from '@/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset';
import { parseRelativePathResponse } from '@/react/portainer/gitops/RelativePathFieldset/utils';
diff --git a/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/useUpdateEdgeStackGitMutation.ts b/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/useUpdateEdgeStackGitMutation.ts
index 2d1ef2b46..aea5ccf74 100644
--- a/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/useUpdateEdgeStackGitMutation.ts
+++ b/app/react/edge/edge-stacks/ItemView/EditEdgeStackForm/GitForm/useUpdateEdgeStackGitMutation.ts
@@ -9,7 +9,7 @@ import {
import { buildUrl } from '@/react/edge/edge-stacks/queries/buildUrl';
import { DeploymentType, EdgeStack } from '@/react/edge/edge-stacks/types';
import { EdgeGroup } from '@/react/edge/edge-groups/types';
-import { Registry } from '@/react/portainer/registries/types';
+import { Registry } from '@/react/portainer/registries/types/registry';
export interface UpdateEdgeStackGitPayload {
id: EdgeStack['Id'];
diff --git a/app/react/edge/edge-stacks/components/PrivateRegistryFieldset.tsx b/app/react/edge/edge-stacks/components/PrivateRegistryFieldset.tsx
index b267c8bd0..d125a25ac 100644
--- a/app/react/edge/edge-stacks/components/PrivateRegistryFieldset.tsx
+++ b/app/react/edge/edge-stacks/components/PrivateRegistryFieldset.tsx
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import { RefreshCw } from 'lucide-react';
-import { Registry } from '@/react/portainer/registries/types';
+import { Registry } from '@/react/portainer/registries/types/registry';
import { Select } from '@@/form-components/ReactSelect';
import { FormControl } from '@@/form-components/FormControl';
diff --git a/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromFile.ts b/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromFile.ts
index 089f8fe89..5542f02f3 100644
--- a/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromFile.ts
+++ b/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromFile.ts
@@ -2,7 +2,7 @@ import axios, {
json2formData,
parseAxiosError,
} from '@/portainer/services/axios';
-import { RegistryId } from '@/react/portainer/registries/types';
+import { RegistryId } from '@/react/portainer/registries/types/registry';
import { Pair } from '@/react/portainer/settings/types';
import { EdgeGroup } from '@/react/edge/edge-groups/types';
diff --git a/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromFileContent.ts b/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromFileContent.ts
index 299968bad..9266275d7 100644
--- a/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromFileContent.ts
+++ b/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromFileContent.ts
@@ -1,5 +1,5 @@
import axios, { parseAxiosError } from '@/portainer/services/axios';
-import { RegistryId } from '@/react/portainer/registries/types';
+import { RegistryId } from '@/react/portainer/registries/types/registry';
import { Pair } from '@/react/portainer/settings/types';
import { EdgeGroup } from '@/react/edge/edge-groups/types';
diff --git a/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromGit.ts b/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromGit.ts
index e7a9cb6a9..81277d90b 100644
--- a/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromGit.ts
+++ b/app/react/edge/edge-stacks/queries/useCreateEdgeStack/createStackFromGit.ts
@@ -1,5 +1,5 @@
import axios, { parseAxiosError } from '@/portainer/services/axios';
-import { RegistryId } from '@/react/portainer/registries/types';
+import { RegistryId } from '@/react/portainer/registries/types/registry';
import { Pair } from '@/react/portainer/settings/types';
import { EdgeGroup } from '@/react/edge/edge-groups/types';
import { AutoUpdateModel } from '@/react/portainer/gitops/types';
diff --git a/app/react/edge/edge-stacks/queries/useCreateEdgeStack/useCreateEdgeStack.ts b/app/react/edge/edge-stacks/queries/useCreateEdgeStack/useCreateEdgeStack.ts
index 80cc1350e..159797986 100644
--- a/app/react/edge/edge-stacks/queries/useCreateEdgeStack/useCreateEdgeStack.ts
+++ b/app/react/edge/edge-stacks/queries/useCreateEdgeStack/useCreateEdgeStack.ts
@@ -1,7 +1,7 @@
import { useMutation } from 'react-query';
import { EdgeGroup } from '@/react/edge/edge-groups/types';
-import { RegistryId } from '@/react/portainer/registries/types';
+import { RegistryId } from '@/react/portainer/registries/types/registry';
import { Pair } from '@/react/portainer/settings/types';
import {
GitFormModel,
diff --git a/app/react/edge/edge-stacks/queries/useParseRegistries.ts b/app/react/edge/edge-stacks/queries/useParseRegistries.ts
index e13f2334e..3d334453d 100644
--- a/app/react/edge/edge-stacks/queries/useParseRegistries.ts
+++ b/app/react/edge/edge-stacks/queries/useParseRegistries.ts
@@ -1,7 +1,7 @@
import { useMutation } from 'react-query';
import { withError } from '@/react-tools/react-query';
-import { RegistryId } from '@/react/portainer/registries/types';
+import { RegistryId } from '@/react/portainer/registries/types/registry';
import axios, {
json2formData,
parseAxiosError,
diff --git a/app/react/edge/edge-stacks/types.ts b/app/react/edge/edge-stacks/types.ts
index 8e6f7e30e..d03666b09 100644
--- a/app/react/edge/edge-stacks/types.ts
+++ b/app/react/edge/edge-stacks/types.ts
@@ -4,7 +4,7 @@ import {
RelativePathModel,
RepoConfigResponse,
} from '@/react/portainer/gitops/types';
-import { RegistryId } from '@/react/portainer/registries/types';
+import { RegistryId } from '@/react/portainer/registries/types/registry';
import { EnvVar } from '@@/form-components/EnvironmentVariablesFieldset/types';
diff --git a/app/react/kubernetes/namespaces/CreateView/types.ts b/app/react/kubernetes/namespaces/CreateView/types.ts
index 5a8cd0f5d..9aa04b20c 100644
--- a/app/react/kubernetes/namespaces/CreateView/types.ts
+++ b/app/react/kubernetes/namespaces/CreateView/types.ts
@@ -1,4 +1,4 @@
-import { Registry } from '@/react/portainer/registries/types';
+import { Registry } from '@/react/portainer/registries/types/registry';
import { IngressControllerClassMap } from '../../cluster/ingressClass/types';
import {
diff --git a/app/react/kubernetes/namespaces/components/NamespaceInnerForm.tsx b/app/react/kubernetes/namespaces/components/NamespaceInnerForm.tsx
index 3184fd967..878117403 100644
--- a/app/react/kubernetes/namespaces/components/NamespaceInnerForm.tsx
+++ b/app/react/kubernetes/namespaces/components/NamespaceInnerForm.tsx
@@ -3,7 +3,7 @@ import { MultiValue } from 'react-select';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment';
-import { Registry } from '@/react/portainer/registries/types';
+import { Registry } from '@/react/portainer/registries/types/registry';
import { FormControl } from '@@/form-components/FormControl';
import { FormSection } from '@@/form-components/FormSection';
diff --git a/app/react/kubernetes/namespaces/components/RegistriesFormSection/RegistriesFormSection.tsx b/app/react/kubernetes/namespaces/components/RegistriesFormSection/RegistriesFormSection.tsx
index 3cd8a2822..597a4b86d 100644
--- a/app/react/kubernetes/namespaces/components/RegistriesFormSection/RegistriesFormSection.tsx
+++ b/app/react/kubernetes/namespaces/components/RegistriesFormSection/RegistriesFormSection.tsx
@@ -1,7 +1,7 @@
import { FormikErrors } from 'formik';
import { MultiValue } from 'react-select';
-import { Registry } from '@/react/portainer/registries/types';
+import { Registry } from '@/react/portainer/registries/types/registry';
import { useEnvironmentRegistries } from '@/react/portainer/environments/queries/useEnvironmentRegistries';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
diff --git a/app/react/kubernetes/namespaces/components/RegistriesFormSection/RegistriesSelector.tsx b/app/react/kubernetes/namespaces/components/RegistriesFormSection/RegistriesSelector.tsx
index d5b519d80..0d270afc2 100644
--- a/app/react/kubernetes/namespaces/components/RegistriesFormSection/RegistriesSelector.tsx
+++ b/app/react/kubernetes/namespaces/components/RegistriesFormSection/RegistriesSelector.tsx
@@ -1,6 +1,6 @@
import { MultiValue } from 'react-select';
-import { Registry } from '@/react/portainer/registries/types';
+import { Registry } from '@/react/portainer/registries/types/registry';
import { useCurrentUser } from '@/react/hooks/useUser';
import { Select } from '@@/form-components/ReactSelect';
diff --git a/app/react/kubernetes/namespaces/components/RegistriesFormSection/registriesValidationSchema.ts b/app/react/kubernetes/namespaces/components/RegistriesFormSection/registriesValidationSchema.ts
index 5206187f3..51f5bb736 100644
--- a/app/react/kubernetes/namespaces/components/RegistriesFormSection/registriesValidationSchema.ts
+++ b/app/react/kubernetes/namespaces/components/RegistriesFormSection/registriesValidationSchema.ts
@@ -1,10 +1,11 @@
import { SchemaOf, array, object, number, string } from 'yup';
-import { Registry } from '@/react/portainer/registries/types';
+import { Registry } from '@/react/portainer/registries/types/registry';
export const registriesValidationSchema: SchemaOf = array(
object({
Id: number().required('Registry ID is required.'),
Name: string().required('Registry name is required.'),
- })
+ }) as unknown as SchemaOf
+ // the only needed value is actually the id. SchemaOf throw a ts error if we don't cast to SchemaOf
);
diff --git a/app/react/portainer/environments/environment.service/registries.ts b/app/react/portainer/environments/environment.service/registries.ts
index 875b31ad6..024fc1877 100644
--- a/app/react/portainer/environments/environment.service/registries.ts
+++ b/app/react/portainer/environments/environment.service/registries.ts
@@ -1,7 +1,10 @@
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { TeamId } from '@/react/portainer/users/teams/types';
import { UserId } from '@/portainer/users/types';
-import { RegistryId, Registry } from '@/react/portainer/registries/types';
+import {
+ RegistryId,
+ Registry,
+} from '@/react/portainer/registries/types/registry';
import { EnvironmentId } from '../types';
diff --git a/app/react/portainer/environments/wizard/components/Option/Option.tsx b/app/react/portainer/environments/wizard/components/Option/Option.tsx
index e3bd66aa4..9626953ef 100644
--- a/app/react/portainer/environments/wizard/components/Option/Option.tsx
+++ b/app/react/portainer/environments/wizard/components/Option/Option.tsx
@@ -51,7 +51,7 @@ export function Option({
{title}
{description}
- {isLimited && (
+ {featureId && isLimited && (
+
+
+
+
+ View registries via an environment to manage access for user(s) and/or
+ team(s)
+
+
+
+
+ >
+ );
+}
diff --git a/app/react/portainer/registries/ListView/RegistriesDatatable/AddButton.tsx b/app/react/portainer/registries/ListView/RegistriesDatatable/AddButton.tsx
new file mode 100644
index 000000000..ab53a29e3
--- /dev/null
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/AddButton.tsx
@@ -0,0 +1,12 @@
+import { AddButton as BaseAddButton } from '@@/buttons';
+
+export function AddButton() {
+ return (
+
+ Add registry
+
+ );
+}
diff --git a/app/react/portainer/registries/ListView/RegistriesDatatable/DeleteButton.tsx b/app/react/portainer/registries/ListView/RegistriesDatatable/DeleteButton.tsx
new file mode 100644
index 000000000..8913813cb
--- /dev/null
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/DeleteButton.tsx
@@ -0,0 +1,40 @@
+import { pluralize } from '@/portainer/helpers/strings';
+import { notifySuccess } from '@/portainer/services/notifications';
+
+import { DeleteButton as BaseDeleteButton } from '@@/buttons/DeleteButton';
+
+import { Registry } from '../../types/registry';
+
+import { useDeleteRegistriesMutation } from './useDeleteRegistriesMutation';
+
+export function DeleteButton({ selectedItems }: { selectedItems: Registry[] }) {
+ const mutation = useDeleteRegistriesMutation();
+
+ const confirmMessage = getMessage(selectedItems.length);
+
+ return (
+
+ );
+
+ function handleDelete() {
+ mutation.mutate(
+ selectedItems.map((item) => item.Id),
+ {
+ onSuccess() {
+ notifySuccess('Success', 'Registries removed');
+ },
+ }
+ );
+ }
+}
+
+function getMessage(selectedCount: number) {
+ const regAttrMsg = selectedCount > 1 ? 'hese' : 'his';
+ const registriesMsg = pluralize(selectedCount, 'registry', 'registries');
+ return `T${regAttrMsg} ${registriesMsg} might be used by applications inside one or more environments. Removing the ${registriesMsg} could lead to a service interruption for the applications using t${regAttrMsg} ${registriesMsg}. Do you want to remove the selected ${registriesMsg}?`;
+}
diff --git a/app/react/portainer/registries/ListView/RegistriesDatatable/RegistriesDatatable.tsx b/app/react/portainer/registries/ListView/RegistriesDatatable/RegistriesDatatable.tsx
new file mode 100644
index 000000000..e9b8d223e
--- /dev/null
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/RegistriesDatatable.tsx
@@ -0,0 +1,40 @@
+import { Radio } from 'lucide-react';
+
+import { Datatable } from '@@/datatables';
+import { createPersistedStore } from '@@/datatables/types';
+import { useTableState } from '@@/datatables/useTableState';
+
+import { useRegistries } from '../../queries/useRegistries';
+
+import { columns } from './columns';
+import { DeleteButton } from './DeleteButton';
+import { AddButton } from './AddButton';
+
+const tableKey = 'registries';
+
+const store = createPersistedStore(tableKey);
+
+export function RegistriesDatatable() {
+ const query = useRegistries();
+
+ const tableState = useTableState(store, tableKey);
+
+ return (
+ (
+ <>
+
+
+
+ >
+ )}
+ isRowSelectable={(row) => !!row.original.Id}
+ />
+ );
+}
diff --git a/app/react/portainer/registries/ListView/DefaultRegistry/DefaultRegistryAction.tsx b/app/react/portainer/registries/ListView/RegistriesDatatable/columns/DefaultRegistryAction.tsx
similarity index 98%
rename from app/react/portainer/registries/ListView/DefaultRegistry/DefaultRegistryAction.tsx
rename to app/react/portainer/registries/ListView/RegistriesDatatable/columns/DefaultRegistryAction.tsx
index f2209d0c5..d74ce2f6a 100644
--- a/app/react/portainer/registries/ListView/DefaultRegistry/DefaultRegistryAction.tsx
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/columns/DefaultRegistryAction.tsx
@@ -38,7 +38,7 @@ export function DefaultRegistryAction() {
Hide for all users
- {isLimited ? null : (
+ {isLimited && (
) {
+ if (!item.Id) {
+ return ;
+ }
+
+ return ;
+}
+
+export function BrowseButton({
+ registryId,
+ registryType,
+ environmentId,
+}: {
+ registryId: RegistryId;
+ registryType: RegistryTypes;
+ environmentId?: EnvironmentId;
+}) {
+ const canBrowse = !nonBrowsableTypes.includes(registryType);
+
+ if (!canBrowse) {
+ return null;
+ }
+
+ return (
+
+ {(isLimited) => (
+
+ )}
+
+ );
+}
diff --git a/app/react/portainer/registries/ListView/RegistriesDatatable/columns/helper.ts b/app/react/portainer/registries/ListView/RegistriesDatatable/columns/helper.ts
new file mode 100644
index 000000000..705dd56e7
--- /dev/null
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/columns/helper.ts
@@ -0,0 +1,5 @@
+import { createColumnHelper } from '@tanstack/react-table';
+
+import { DecoratedRegistry } from '../types';
+
+export const columnHelper = createColumnHelper();
diff --git a/app/react/portainer/registries/ListView/RegistriesDatatable/columns/index.ts b/app/react/portainer/registries/ListView/RegistriesDatatable/columns/index.ts
new file mode 100644
index 000000000..e91063831
--- /dev/null
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/columns/index.ts
@@ -0,0 +1,5 @@
+import { actions } from './actions';
+import { name } from './name';
+import { url } from './url';
+
+export const columns = [name, url, actions];
diff --git a/app/react/portainer/registries/ListView/RegistriesDatatable/columns/name.tsx b/app/react/portainer/registries/ListView/RegistriesDatatable/columns/name.tsx
new file mode 100644
index 000000000..7c0a5f66d
--- /dev/null
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/columns/name.tsx
@@ -0,0 +1,52 @@
+import { CellContext } from '@tanstack/react-table';
+
+import { useIsEdgeAdmin } from '@/react/hooks/useUser';
+
+import { Link } from '@@/Link';
+
+import { DecoratedRegistry } from '../types';
+
+import { columnHelper } from './helper';
+import { DefaultRegistryName } from './DefaultRegistryName';
+
+export const name = columnHelper.accessor('Name', {
+ header: 'Name',
+ cell: Cell,
+});
+
+function Cell({
+ row: { original: item },
+}: CellContext) {
+ return ;
+}
+
+export function NameCell({
+ item,
+ hasLink,
+}: {
+ item: DecoratedRegistry;
+ hasLink?: boolean;
+}) {
+ const isEdgeAdminQuery = useIsEdgeAdmin();
+
+ if (!item.Id) {
+ return ;
+ }
+
+ return (
+ <>
+ {isEdgeAdminQuery.isAdmin && hasLink ? (
+
+ {item.Name}
+
+ ) : (
+ item.Name
+ )}
+ {item.Authentication && (
+
+ authentication-enabled
+
+ )}
+ >
+ );
+}
diff --git a/app/react/portainer/registries/ListView/RegistriesDatatable/columns/url.tsx b/app/react/portainer/registries/ListView/RegistriesDatatable/columns/url.tsx
new file mode 100644
index 000000000..e09731d4e
--- /dev/null
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/columns/url.tsx
@@ -0,0 +1,8 @@
+import { DefaultRegistryDomain } from './DefaultRegistryDomain';
+import { columnHelper } from './helper';
+
+export const url = columnHelper.accessor('URL', {
+ header: 'URL',
+ cell: ({ getValue, row: { original: item } }) =>
+ item.Id ? getValue() : ,
+});
diff --git a/app/react/portainer/registries/ListView/RegistriesDatatable/index.ts b/app/react/portainer/registries/ListView/RegistriesDatatable/index.ts
new file mode 100644
index 000000000..a811803ef
--- /dev/null
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/index.ts
@@ -0,0 +1 @@
+export { RegistriesDatatable } from './RegistriesDatatable';
diff --git a/app/react/portainer/registries/ListView/RegistriesDatatable/types.ts b/app/react/portainer/registries/ListView/RegistriesDatatable/types.ts
new file mode 100644
index 000000000..ab234944b
--- /dev/null
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/types.ts
@@ -0,0 +1,3 @@
+import { Registry } from '../../types/registry';
+
+export interface DecoratedRegistry extends Registry {}
diff --git a/app/react/portainer/registries/ListView/RegistriesDatatable/useDeleteRegistriesMutation.ts b/app/react/portainer/registries/ListView/RegistriesDatatable/useDeleteRegistriesMutation.ts
new file mode 100644
index 000000000..fe4a281fe
--- /dev/null
+++ b/app/react/portainer/registries/ListView/RegistriesDatatable/useDeleteRegistriesMutation.ts
@@ -0,0 +1,35 @@
+import { useMutation, useQueryClient } from 'react-query';
+
+import { promiseSequence } from '@/portainer/helpers/promise-utils';
+import axios, { parseAxiosError } from '@/portainer/services/axios';
+import {
+ mutationOptions,
+ withError,
+ withInvalidate,
+} from '@/react-tools/react-query';
+
+import { buildUrl } from '../../queries/build-url';
+import { queryKeys } from '../../queries/query-keys';
+import { Registry } from '../../types/registry';
+
+export function useDeleteRegistriesMutation() {
+ const queryClient = useQueryClient();
+ return useMutation(
+ (RegistryIds: Array) =>
+ promiseSequence(
+ RegistryIds.map((RegistryId) => () => deleteRegistry(RegistryId))
+ ),
+ mutationOptions(
+ withError('Unable to delete registries'),
+ withInvalidate(queryClient, [queryKeys.base()])
+ )
+ );
+}
+
+async function deleteRegistry(id: Registry['Id']) {
+ try {
+ await axios.delete(buildUrl(id));
+ } catch (e) {
+ throw parseAxiosError(e, 'Unable to delete registries');
+ }
+}
diff --git a/app/react/portainer/registries/ListView/index.ts b/app/react/portainer/registries/ListView/index.ts
new file mode 100644
index 000000000..dd06dfd19
--- /dev/null
+++ b/app/react/portainer/registries/ListView/index.ts
@@ -0,0 +1 @@
+export { ListView } from './ListView';
diff --git a/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/EnvironmentRegistriesDatatable.tsx b/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/EnvironmentRegistriesDatatable.tsx
new file mode 100644
index 000000000..60cd7c54d
--- /dev/null
+++ b/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/EnvironmentRegistriesDatatable.tsx
@@ -0,0 +1,39 @@
+import { Radio } from 'lucide-react';
+
+import { useEnvironmentRegistries } from '@/react/portainer/environments/queries/useEnvironmentRegistries';
+import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
+import { url } from '@/react/portainer/registries/ListView/RegistriesDatatable/columns/url';
+import { AddButton } from '@/react/portainer/registries/ListView/RegistriesDatatable/AddButton';
+
+import { Datatable } from '@@/datatables';
+import { createPersistedStore } from '@@/datatables/types';
+import { useTableState } from '@@/datatables/useTableState';
+
+import { name } from './columns/name';
+import { actions } from './columns/actions';
+
+const columns = [name, url, actions];
+
+const tableKey = 'registries';
+
+const store = createPersistedStore(tableKey);
+
+export function EnvironmentRegistriesDatatable() {
+ const environmentId = useEnvironmentId();
+ const query = useEnvironmentRegistries(environmentId);
+
+ const tableState = useTableState(store, tableKey);
+
+ return (
+ }
+ disableSelect
+ />
+ );
+}
diff --git a/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/columns/actions.tsx b/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/columns/actions.tsx
new file mode 100644
index 000000000..e65a4a4d7
--- /dev/null
+++ b/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/columns/actions.tsx
@@ -0,0 +1,56 @@
+import { CellContext } from '@tanstack/react-table';
+import { Users } from 'lucide-react';
+
+import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
+import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
+import { DecoratedRegistry } from '@/react/portainer/registries/ListView/RegistriesDatatable/types';
+import { RegistryTypes } from '@/react/portainer/registries/types/registry';
+import { columnHelper } from '@/react/portainer/registries/ListView/RegistriesDatatable/columns/helper';
+import { BrowseButton } from '@/react/portainer/registries/ListView/RegistriesDatatable/columns/actions';
+
+import { Button } from '@@/buttons';
+import { Link } from '@@/Link';
+
+export const actions = columnHelper.display({
+ header: 'Actions',
+ cell: Cell,
+});
+
+function Cell({
+ row: { original: item },
+}: CellContext) {
+ const environmentId = useEnvironmentId();
+ const hasUpdateAccessAuthorizations = useAuthorizations(
+ ['PortainerRegistryUpdateAccess'],
+ environmentId,
+ true
+ );
+ const canManageAccess =
+ item.Type !== RegistryTypes.ANONYMOUS && hasUpdateAccessAuthorizations;
+
+ if (!item.Id) {
+ return null;
+ }
+
+ return (
+ <>
+ {canManageAccess && (
+
+
+
+ )}
+
+ >
+ );
+}
diff --git a/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/columns/name.tsx b/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/columns/name.tsx
new file mode 100644
index 000000000..74c0a9888
--- /dev/null
+++ b/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/columns/name.tsx
@@ -0,0 +1,16 @@
+import { CellContext } from '@tanstack/react-table';
+
+import { DecoratedRegistry } from '@/react/portainer/registries/ListView/RegistriesDatatable/types';
+import { columnHelper } from '@/react/portainer/registries/ListView/RegistriesDatatable/columns/helper';
+import { NameCell } from '@/react/portainer/registries/ListView/RegistriesDatatable/columns/name';
+
+export const name = columnHelper.accessor('Name', {
+ header: 'Name',
+ cell: Cell,
+});
+
+function Cell({
+ row: { original: item },
+}: CellContext) {
+ return ;
+}
diff --git a/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/index.ts b/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/index.ts
new file mode 100644
index 000000000..1fdecc69a
--- /dev/null
+++ b/app/react/portainer/registries/environments/ListView/EnvironmentRegistriesDatatable/index.ts
@@ -0,0 +1 @@
+export { EnvironmentRegistriesDatatable } from './EnvironmentRegistriesDatatable';
diff --git a/app/react/portainer/registries/environments/ListView/ListView.tsx b/app/react/portainer/registries/environments/ListView/ListView.tsx
new file mode 100644
index 000000000..1847fb1e4
--- /dev/null
+++ b/app/react/portainer/registries/environments/ListView/ListView.tsx
@@ -0,0 +1,17 @@
+import { PageHeader } from '@@/PageHeader';
+
+import { EnvironmentRegistriesDatatable } from './EnvironmentRegistriesDatatable';
+
+export function ListView() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/app/react/portainer/registries/environments/ListView/index.ts b/app/react/portainer/registries/environments/ListView/index.ts
new file mode 100644
index 000000000..dd06dfd19
--- /dev/null
+++ b/app/react/portainer/registries/environments/ListView/index.ts
@@ -0,0 +1 @@
+export { ListView } from './ListView';
diff --git a/app/react/portainer/registries/queries/query-keys.ts b/app/react/portainer/registries/queries/query-keys.ts
index 5afa6c0fc..625ca0504 100644
--- a/app/react/portainer/registries/queries/query-keys.ts
+++ b/app/react/portainer/registries/queries/query-keys.ts
@@ -1,6 +1,9 @@
+import { EnvironmentId } from '../../environments/types';
import { RegistryId } from '../types/registry';
export const queryKeys = {
base: () => ['registries'] as const,
+ list: (environmentId?: EnvironmentId) =>
+ [...queryKeys.base(), { environmentId }] as const,
item: (registryId: RegistryId) => [...queryKeys.base(), registryId] as const,
};
diff --git a/app/react/portainer/registries/types.ts b/app/react/portainer/registries/types.ts
deleted file mode 100644
index 63ffbbcdb..000000000
--- a/app/react/portainer/registries/types.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export type RegistryId = number;
-export interface Registry {
- Id: RegistryId;
- Name: string;
-}
diff --git a/app/react/portainer/templates/custom-templates/types.ts b/app/react/portainer/templates/custom-templates/types.ts
index 0b6230860..d1b2c9462 100644
--- a/app/react/portainer/templates/custom-templates/types.ts
+++ b/app/react/portainer/templates/custom-templates/types.ts
@@ -5,7 +5,7 @@ import { ResourceControlResponse } from '../../access-control/types';
import { RelativePathModel, RepoConfigResponse } from '../../gitops/types';
import { VariableDefinition } from '../../custom-templates/components/CustomTemplatesVariablesDefinitionField';
import { Platform } from '../types';
-import { RegistryId } from '../../registries/types';
+import { RegistryId } from '../../registries/types/registry';
import { getDefaultRelativePathModel } from '../../gitops/RelativePathFieldset/types';
import { isBE } from '../../feature-flags/feature-flags.service';