diff --git a/app/docker/__module.js b/app/docker/__module.js
index 12a66d44e..cd29f9288 100644
--- a/app/docker/__module.js
+++ b/app/docker/__module.js
@@ -73,9 +73,7 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([
url: '/configs',
views: {
'content@': {
- templateUrl: './views/configs/configs.html',
- controller: 'ConfigsController',
- controllerAs: 'ctrl',
+ component: 'configsListView',
},
},
data: {
diff --git a/app/docker/react/components/index.ts b/app/docker/react/components/index.ts
index d1116bda3..359c7f001 100644
--- a/app/docker/react/components/index.ts
+++ b/app/docker/react/components/index.ts
@@ -13,7 +13,6 @@ import { InsightsBox } from '@/react/components/InsightsBox';
import { BetaAlert } from '@/react/portainer/environments/update-schedules/common/BetaAlert';
import { ImagesDatatable } from '@/react/docker/images/ListView/ImagesDatatable/ImagesDatatable';
import { EventsDatatable } from '@/react/docker/events/EventsDatatables';
-import { ConfigsDatatable } from '@/react/docker/configs/ListView/ConfigsDatatable';
import { AgentHostBrowser } from '@/react/docker/host/BrowseView/AgentHostBrowser';
import { AgentVolumeBrowser } from '@/react/docker/volumes/BrowseView/AgentVolumeBrowser';
import { ProcessesDatatable } from '@/react/docker/containers/StatsView/ProcessesDatatable';
@@ -79,14 +78,7 @@ const ngModule = angular
'onRemove',
])
)
- .component(
- 'dockerConfigsDatatable',
- r2a(withUIRouter(withCurrentUser(ConfigsDatatable)), [
- 'dataset',
- 'onRemoveClick',
- 'onRefresh',
- ])
- )
+
.component(
'agentHostBrowserReact',
r2a(withUIRouter(withCurrentUser(AgentHostBrowser)), [
diff --git a/app/docker/react/views/configs.ts b/app/docker/react/views/configs.ts
new file mode 100644
index 000000000..17ef0c0ee
--- /dev/null
+++ b/app/docker/react/views/configs.ts
@@ -0,0 +1,14 @@
+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/docker/configs/ListView/ListView';
+
+export const configsModule = angular
+ .module('portainer.docker.react.views.configs', [])
+ .component(
+ 'configsListView',
+ r2a(withUIRouter(withReactQuery(withCurrentUser(ListView))), [])
+ ).name;
diff --git a/app/docker/react/views/index.ts b/app/docker/react/views/index.ts
index 6205f0b22..fc043d754 100644
--- a/app/docker/react/views/index.ts
+++ b/app/docker/react/views/index.ts
@@ -7,9 +7,10 @@ import { withUIRouter } from '@/react-tools/withUIRouter';
import { DashboardView } from '@/react/docker/DashboardView/DashboardView';
import { containersModule } from './containers';
+import { configsModule } from './configs';
export const viewsModule = angular
- .module('portainer.docker.react.views', [containersModule])
+ .module('portainer.docker.react.views', [containersModule, configsModule])
.component(
'dockerDashboardView',
r2a(withUIRouter(withCurrentUser(DashboardView)), [])
diff --git a/app/docker/services/configService.js b/app/docker/services/configService.js
index 05a63a076..00ddf7b64 100644
--- a/app/docker/services/configService.js
+++ b/app/docker/services/configService.js
@@ -3,7 +3,7 @@ import { getConfigs } from '@/react/docker/configs/queries/useConfigs';
import { deleteConfig } from '@/react/docker/configs/queries/useDeleteConfigMutation';
import { createConfig } from '@/react/docker/configs/queries/useCreateConfigMutation';
-import { ConfigViewModel } from '../models/config';
+import { ConfigViewModel } from '../../react/docker/configs/model';
angular.module('portainer.docker').factory('ConfigService', ConfigServiceFactory);
diff --git a/app/docker/views/configs/configs.html b/app/docker/views/configs/configs.html
deleted file mode 100644
index ee4727bbe..000000000
--- a/app/docker/views/configs/configs.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/app/docker/views/configs/configsController.js b/app/docker/views/configs/configsController.js
deleted file mode 100644
index 2d6168172..000000000
--- a/app/docker/views/configs/configsController.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import angular from 'angular';
-
-class ConfigsController {
- /* @ngInject */
- constructor($state, ConfigService, Notifications, $async, endpoint) {
- this.$state = $state;
- this.ConfigService = ConfigService;
- this.Notifications = Notifications;
- this.$async = $async;
- this.endpoint = endpoint;
-
- this.removeAction = this.removeAction.bind(this);
- this.removeActionAsync = this.removeActionAsync.bind(this);
- this.getConfigs = this.getConfigs.bind(this);
- this.getConfigsAsync = this.getConfigsAsync.bind(this);
- }
-
- getConfigs() {
- return this.$async(this.getConfigsAsync);
- }
-
- async getConfigsAsync() {
- try {
- this.configs = await this.ConfigService.configs(this.endpoint.Id);
- } catch (err) {
- this.Notifications.error('Failure', err, 'Unable to retrieve configs');
- }
- }
-
- async $onInit() {
- this.configs = [];
- this.getConfigs();
- }
-
- async removeAction(selectedItems) {
- return this.$async(this.removeActionAsync, selectedItems);
- }
-
- async removeActionAsync(selectedItems) {
- let actionCount = selectedItems.length;
- for (const config of selectedItems) {
- try {
- await this.ConfigService.remove(this.endpoint.Id, config.Id);
- this.Notifications.success('Config successfully removed', config.Name);
- const index = this.configs.indexOf(config);
- this.configs.splice(index, 1);
- } catch (err) {
- this.Notifications.error('Failure', err, 'Unable to remove config');
- } finally {
- --actionCount;
- if (actionCount === 0) {
- this.$state.reload();
- }
- }
- }
- }
-}
-export default ConfigsController;
-angular.module('portainer.docker').controller('ConfigsController', ConfigsController);
diff --git a/app/react/docker/configs/.keep b/app/react/docker/configs/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/app/react/docker/configs/ListView/.keep b/app/react/docker/configs/ListView/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/app/react/docker/configs/ListView/ConfigsDatatable/ConfigsDatatable.tsx b/app/react/docker/configs/ListView/ConfigsDatatable/ConfigsDatatable.tsx
index dc7917234..684aa11f0 100644
--- a/app/react/docker/configs/ListView/ConfigsDatatable/ConfigsDatatable.tsx
+++ b/app/react/docker/configs/ListView/ConfigsDatatable/ConfigsDatatable.tsx
@@ -1,38 +1,42 @@
import { Clipboard } from 'lucide-react';
import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
+import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { Datatable, TableSettingsMenu } from '@@/datatables';
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
-import { useRepeater } from '@@/datatables/useRepeater';
import { AddButton } from '@@/buttons';
import { useTableState } from '@@/datatables/useTableState';
-import { DeleteButton } from '@@/buttons/DeleteButton';
-import { DockerConfig } from '../../types';
+import { useConfigsList } from '../queries/useConfigsList';
import { columns } from './columns';
import { createStore } from './store';
-
-interface Props {
- dataset: Array;
- onRemoveClick: (configs: Array) => void;
- onRefresh: () => void;
-}
+import { DeleteConfigButton } from './DeleteConfigButton';
const storageKey = 'docker_configs';
const settingsStore = createStore(storageKey);
-export function ConfigsDatatable({ dataset, onRefresh, onRemoveClick }: Props) {
+export function ConfigsDatatable() {
+ const environmentId = useEnvironmentId();
+
const tableState = useTableState(settingsStore, storageKey);
- useRepeater(tableState.autoRefreshRate, onRefresh);
+ const configListQuery = useConfigsList(environmentId, {
+ refetchInterval: tableState.autoRefreshRate * 1000,
+ });
const hasWriteAccessQuery = useAuthorizations([
'DockerConfigCreate',
'DockerConfigDelete',
]);
+ if (!configListQuery.data) {
+ return null;
+ }
+
+ const dataset = configListQuery.data;
+
return (
- onRemoveClick(selectedRows)}
- confirmMessage="Do you want to remove the selected config(s)?"
- />
+
diff --git a/app/react/docker/configs/ListView/ConfigsDatatable/DeleteConfigButton.tsx b/app/react/docker/configs/ListView/ConfigsDatatable/DeleteConfigButton.tsx
new file mode 100644
index 000000000..75e49e333
--- /dev/null
+++ b/app/react/docker/configs/ListView/ConfigsDatatable/DeleteConfigButton.tsx
@@ -0,0 +1,44 @@
+import { useQueryClient, useMutation } from '@tanstack/react-query';
+
+import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
+import { promiseSequence } from '@/portainer/helpers/promise-utils';
+import { withError, withInvalidate } from '@/react-tools/react-query';
+import { EnvironmentId } from '@/react/portainer/environments/types';
+
+import { DeleteButton } from '@@/buttons/DeleteButton';
+
+import { ConfigViewModel } from '../../model';
+import { queryKeys } from '../queries/queryKeys';
+import { deleteConfig } from '../queries/useDeleteConfigMutation';
+
+export function DeleteConfigButton({
+ selectedItems,
+}: {
+ selectedItems: Array;
+}) {
+ const environmentId = useEnvironmentId();
+ const mutation = useDeleteConfigListMutation(environmentId);
+
+ return (
+ {
+ mutation.mutate(selectedItems.map((item) => item.Id));
+ }}
+ confirmMessage="Do you want to remove the selected config(s)?"
+ disabled={selectedItems.length === 0}
+ />
+ );
+}
+
+function useDeleteConfigListMutation(environmentId: EnvironmentId) {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (ids: Array) =>
+ promiseSequence(
+ ids.map((configId) => () => deleteConfig({ environmentId, configId }))
+ ),
+ ...withError('Unable to remove configs'),
+ ...withInvalidate(queryClient, [queryKeys.base(environmentId)]),
+ });
+}
diff --git a/app/react/docker/configs/ListView/ConfigsDatatable/columns.tsx b/app/react/docker/configs/ListView/ConfigsDatatable/columns.tsx
index 0249e91ca..5904b0d97 100644
--- a/app/react/docker/configs/ListView/ConfigsDatatable/columns.tsx
+++ b/app/react/docker/configs/ListView/ConfigsDatatable/columns.tsx
@@ -5,12 +5,12 @@ import { createOwnershipColumn } from '@/react/docker/components/datatable/creat
import { buildNameColumn } from '@@/datatables/buildNameColumn';
-import { DockerConfig } from '../../types';
+import { ConfigViewModel } from '../../model';
-const columnHelper = createColumnHelper();
+const columnHelper = createColumnHelper();
export const columns = [
- buildNameColumn(
+ buildNameColumn(
'Name',
'docker.configs.config',
'docker-configs-name'
@@ -22,5 +22,5 @@ export const columns = [
return ;
},
}),
- createOwnershipColumn(),
+ createOwnershipColumn(),
];
diff --git a/app/react/docker/configs/ListView/ListView.tsx b/app/react/docker/configs/ListView/ListView.tsx
new file mode 100644
index 000000000..28ddd1fbb
--- /dev/null
+++ b/app/react/docker/configs/ListView/ListView.tsx
@@ -0,0 +1,13 @@
+import { PageHeader } from '@@/PageHeader';
+
+import { ConfigsDatatable } from './ConfigsDatatable';
+
+export function ListView() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/app/react/docker/configs/ListView/queries/build-url.ts b/app/react/docker/configs/ListView/queries/build-url.ts
new file mode 100644
index 000000000..5a14b51d7
--- /dev/null
+++ b/app/react/docker/configs/ListView/queries/build-url.ts
@@ -0,0 +1,6 @@
+import { EnvironmentId } from '@/react/portainer/environments/types';
+import { buildDockerProxyUrl } from '@/react/docker/proxy/queries/buildDockerProxyUrl';
+
+export function buildUrl(environmentId: EnvironmentId, id = '', action = '') {
+ return buildDockerProxyUrl(environmentId, 'configs', id, action);
+}
diff --git a/app/react/docker/configs/ListView/queries/queryKeys.ts b/app/react/docker/configs/ListView/queries/queryKeys.ts
new file mode 100644
index 000000000..395ff8027
--- /dev/null
+++ b/app/react/docker/configs/ListView/queries/queryKeys.ts
@@ -0,0 +1,8 @@
+import { queryKeys as proxyQueryKeys } from '@/react/docker/proxy/queries/query-keys';
+import { EnvironmentId } from '@/react/portainer/environments/types';
+
+export const queryKeys = {
+ base: (environmentId: EnvironmentId) =>
+ [...proxyQueryKeys.base(environmentId), 'configs'] as const,
+ list: (environmentId: EnvironmentId) => queryKeys.base(environmentId),
+};
diff --git a/app/react/docker/configs/ListView/queries/useConfigsList.ts b/app/react/docker/configs/ListView/queries/useConfigsList.ts
new file mode 100644
index 000000000..75f4f4d8b
--- /dev/null
+++ b/app/react/docker/configs/ListView/queries/useConfigsList.ts
@@ -0,0 +1,29 @@
+import { useQuery } from '@tanstack/react-query';
+import { Config } from 'docker-types/generated/1.41';
+
+import { EnvironmentId } from '@/react/portainer/environments/types';
+import axios, { parseAxiosError } from '@/portainer/services/axios';
+import { ConfigViewModel } from '@/react/docker/configs/model';
+
+import { queryKeys } from './queryKeys';
+import { buildUrl } from './build-url';
+
+export function useConfigsList(
+ environmentId: EnvironmentId,
+ { refetchInterval }: { refetchInterval?: number } = {}
+) {
+ return useQuery({
+ queryKey: queryKeys.list(environmentId),
+ queryFn: () => getConfigsList(environmentId),
+ refetchInterval,
+ });
+}
+
+async function getConfigsList(environmentId: EnvironmentId) {
+ try {
+ const { data } = await axios.get>(buildUrl(environmentId));
+ return data.map((c) => new ConfigViewModel(c));
+ } catch (err) {
+ throw parseAxiosError(err as Error, 'Unable to retrieve configs');
+ }
+}
diff --git a/app/react/docker/configs/ListView/queries/useDeleteConfigMutation.ts b/app/react/docker/configs/ListView/queries/useDeleteConfigMutation.ts
new file mode 100644
index 000000000..9a911d20d
--- /dev/null
+++ b/app/react/docker/configs/ListView/queries/useDeleteConfigMutation.ts
@@ -0,0 +1,26 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { EnvironmentId } from '@/react/portainer/environments/types';
+import axios, { parseAxiosError } from '@/portainer/services/axios';
+
+import { buildUrl } from './build-url';
+
+export function useDeleteConfigMutation() {
+ return useMutation({
+ mutationFn: deleteConfig,
+ });
+}
+
+export async function deleteConfig({
+ environmentId,
+ configId,
+}: {
+ environmentId: EnvironmentId;
+ configId: string;
+}) {
+ try {
+ await axios.delete(buildUrl(environmentId, configId));
+ } catch (err) {
+ throw parseAxiosError(err as Error, 'Unable to delete config');
+ }
+}
diff --git a/app/docker/models/config.ts b/app/react/docker/configs/model.ts
similarity index 100%
rename from app/docker/models/config.ts
rename to app/react/docker/configs/model.ts
diff --git a/app/react/docker/configs/queries/useConfig.ts b/app/react/docker/configs/queries/useConfig.ts
index fc80798de..8fc1b57b9 100644
--- a/app/react/docker/configs/queries/useConfig.ts
+++ b/app/react/docker/configs/queries/useConfig.ts
@@ -4,14 +4,14 @@ import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
-import { DockerConfig } from '../types';
+import { PortainerResponse } from '../../types';
export async function getConfig(
environmentId: EnvironmentId,
- configId: DockerConfig['Id']
+ configId: Config['ID']
) {
try {
- const { data } = await axios.get(
+ const { data } = await axios.get>(
buildDockerProxyUrl(environmentId, 'configs', configId)
);
return data;
diff --git a/app/react/docker/configs/queries/useDeleteConfigMutation.ts b/app/react/docker/configs/queries/useDeleteConfigMutation.ts
index ef03aa44c..2911ce6ae 100644
--- a/app/react/docker/configs/queries/useDeleteConfigMutation.ts
+++ b/app/react/docker/configs/queries/useDeleteConfigMutation.ts
@@ -1,12 +1,13 @@
+import { Config } from 'docker-types/generated/1.41';
+
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
-import { DockerConfig } from '../types';
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
export async function deleteConfig(
environmentId: EnvironmentId,
- id: DockerConfig['Id']
+ id: Config['ID']
) {
try {
await axios.delete(buildDockerProxyUrl(environmentId, 'configs', id));
diff --git a/app/react/docker/configs/types.ts b/app/react/docker/configs/types.ts
deleted file mode 100644
index 489fd3b4f..000000000
--- a/app/react/docker/configs/types.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
-
-export type DockerConfig = {
- Id: string;
- Name: string;
- CreatedAt: string;
- ResourceControl?: ResourceControlViewModel;
-};