Merge ae0a527a6d
into 01afe34df7
commit
e25d480db9
|
@ -75,9 +75,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: {
|
||||
|
|
|
@ -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)), [
|
||||
|
|
|
@ -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;
|
|
@ -8,9 +8,10 @@ import { DashboardView } from '@/react/docker/DashboardView/DashboardView';
|
|||
import { ListView } from '@/react/docker/events/ListView';
|
||||
|
||||
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)), [])
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<page-header title="'Configs list'" breadcrumbs="['Configs']" reload="true"> </page-header>
|
||||
|
||||
<docker-configs-datatable dataset="ctrl.configs" on-remove-click="(ctrl.removeAction)" on-refresh="(ctrl.getConfigs)"></docker-configs-datatable>
|
|
@ -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);
|
|
@ -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<DockerConfig>;
|
||||
onRemoveClick: (configs: Array<DockerConfig>) => 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 (
|
||||
<Datatable
|
||||
dataset={dataset}
|
||||
|
@ -54,12 +58,7 @@ export function ConfigsDatatable({ dataset, onRefresh, onRemoveClick }: Props) {
|
|||
hasWriteAccessQuery.authorized && (
|
||||
<div className="flex items-center gap-3">
|
||||
<Authorized authorizations="DockerConfigDelete">
|
||||
<DeleteButton
|
||||
disabled={selectedRows.length === 0}
|
||||
data-cy="remove-docker-configs-button"
|
||||
onConfirmed={() => onRemoveClick(selectedRows)}
|
||||
confirmMessage="Do you want to remove the selected config(s)?"
|
||||
/>
|
||||
<DeleteConfigButton selectedItems={selectedRows} />
|
||||
</Authorized>
|
||||
|
||||
<Authorized authorizations="DockerConfigCreate">
|
||||
|
|
|
@ -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<ConfigViewModel>;
|
||||
}) {
|
||||
const environmentId = useEnvironmentId();
|
||||
const mutation = useDeleteConfigListMutation(environmentId);
|
||||
|
||||
return (
|
||||
<DeleteButton
|
||||
data-cy="remove-docker-configs-button"
|
||||
onConfirmed={() => {
|
||||
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<string>) =>
|
||||
promiseSequence(
|
||||
ids.map((configId) => () => deleteConfig({ environmentId, configId }))
|
||||
),
|
||||
...withError('Unable to remove configs'),
|
||||
...withInvalidate(queryClient, [queryKeys.base(environmentId)]),
|
||||
});
|
||||
}
|
|
@ -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<DockerConfig>();
|
||||
const columnHelper = createColumnHelper<ConfigViewModel>();
|
||||
|
||||
export const columns = [
|
||||
buildNameColumn<DockerConfig>(
|
||||
buildNameColumn<ConfigViewModel>(
|
||||
'Name',
|
||||
'docker.configs.config',
|
||||
'docker-configs-name'
|
||||
|
@ -22,5 +22,5 @@ export const columns = [
|
|||
return <time dateTime={date}>{isoDate(date)}</time>;
|
||||
},
|
||||
}),
|
||||
createOwnershipColumn<DockerConfig>(),
|
||||
createOwnershipColumn<ConfigViewModel>(),
|
||||
];
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { PageHeader } from '@@/PageHeader';
|
||||
|
||||
import { ConfigsDatatable } from './ConfigsDatatable';
|
||||
|
||||
export function ListView() {
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Configs list" breadcrumbs="Configs" reload />
|
||||
|
||||
<ConfigsDatatable />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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),
|
||||
};
|
|
@ -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<Array<Config>>(buildUrl(environmentId));
|
||||
return data.map((c) => new ConfigViewModel(c));
|
||||
} catch (err) {
|
||||
throw parseAxiosError(err as Error, 'Unable to retrieve configs');
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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<Config>(
|
||||
const { data } = await axios.get<PortainerResponse<Config>>(
|
||||
buildDockerProxyUrl(environmentId, 'configs', configId)
|
||||
);
|
||||
return data;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||
|
||||
export type DockerConfig = {
|
||||
Id: string;
|
||||
Name: string;
|
||||
CreatedAt: string;
|
||||
ResourceControl?: ResourceControlViewModel;
|
||||
};
|
Loading…
Reference in New Issue