mirror of https://github.com/portainer/portainer
refactor(tables): use add and delete buttons [EE-6297] (#10668)
Co-authored-by: Chaim Lev-Ari <chaim.levi-ari@portaienr.io>pull/10840/head
parent
d88ef03ddb
commit
9600eb6fa1
|
@ -1,5 +1,4 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
|
|
||||||
class ConfigsController {
|
class ConfigsController {
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
|
@ -34,10 +33,6 @@ class ConfigsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeAction(selectedItems) {
|
async removeAction(selectedItems) {
|
||||||
const confirmed = await confirmDelete('Do you want to remove the selected config(s)?');
|
|
||||||
if (!confirmed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this.$async(this.removeActionAsync, selectedItems);
|
return this.$async(this.removeActionAsync, selectedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import DockerNetworkHelper from '@/docker/helpers/networkHelper';
|
import DockerNetworkHelper from '@/docker/helpers/networkHelper';
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
|
|
||||||
angular.module('portainer.docker').controller('NetworksController', [
|
angular.module('portainer.docker').controller('NetworksController', [
|
||||||
'$q',
|
'$q',
|
||||||
|
@ -13,10 +12,6 @@ angular.module('portainer.docker').controller('NetworksController', [
|
||||||
'AgentService',
|
'AgentService',
|
||||||
function ($q, $scope, $state, NetworkService, Notifications, HttpRequestHelper, endpoint, AgentService) {
|
function ($q, $scope, $state, NetworkService, Notifications, HttpRequestHelper, endpoint, AgentService) {
|
||||||
$scope.removeAction = async function (selectedItems) {
|
$scope.removeAction = async function (selectedItems) {
|
||||||
const confirmed = await confirmDelete('Do you want to remove the selected network(s)?');
|
|
||||||
if (!confirmed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var actionCount = selectedItems.length;
|
var actionCount = selectedItems.length;
|
||||||
angular.forEach(selectedItems, function (network) {
|
angular.forEach(selectedItems, function (network) {
|
||||||
HttpRequestHelper.setPortainerAgentTargetHeader(network.NodeName);
|
HttpRequestHelper.setPortainerAgentTargetHeader(network.NodeName);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
angular.module('portainer.docker').controller('SecretsController', [
|
angular.module('portainer.docker').controller('SecretsController', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'$state',
|
'$state',
|
||||||
|
@ -6,10 +5,6 @@ angular.module('portainer.docker').controller('SecretsController', [
|
||||||
'Notifications',
|
'Notifications',
|
||||||
function ($scope, $state, SecretService, Notifications) {
|
function ($scope, $state, SecretService, Notifications) {
|
||||||
$scope.removeAction = async function (selectedItems) {
|
$scope.removeAction = async function (selectedItems) {
|
||||||
const confirmed = await confirmDelete('Do you want to remove the selected secret(s)?');
|
|
||||||
if (!confirmed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var actionCount = selectedItems.length;
|
var actionCount = selectedItems.length;
|
||||||
angular.forEach(selectedItems, function (secret) {
|
angular.forEach(selectedItems, function (secret) {
|
||||||
SecretService.remove(secret.Id)
|
SecretService.remove(secret.Id)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
|
|
||||||
angular.module('portainer.docker').controller('VolumesController', [
|
angular.module('portainer.docker').controller('VolumesController', [
|
||||||
'$q',
|
'$q',
|
||||||
'$scope',
|
'$scope',
|
||||||
|
@ -13,28 +11,24 @@ angular.module('portainer.docker').controller('VolumesController', [
|
||||||
'endpoint',
|
'endpoint',
|
||||||
function ($q, $scope, $state, VolumeService, ServiceService, VolumeHelper, Notifications, HttpRequestHelper, Authentication, endpoint) {
|
function ($q, $scope, $state, VolumeService, ServiceService, VolumeHelper, Notifications, HttpRequestHelper, Authentication, endpoint) {
|
||||||
$scope.removeAction = function (selectedItems) {
|
$scope.removeAction = function (selectedItems) {
|
||||||
confirmDelete('Do you want to remove the selected volume(s)?').then((confirmed) => {
|
var actionCount = selectedItems.length;
|
||||||
if (confirmed) {
|
angular.forEach(selectedItems, function (volume) {
|
||||||
var actionCount = selectedItems.length;
|
HttpRequestHelper.setPortainerAgentTargetHeader(volume.NodeName);
|
||||||
angular.forEach(selectedItems, function (volume) {
|
VolumeService.remove(volume)
|
||||||
HttpRequestHelper.setPortainerAgentTargetHeader(volume.NodeName);
|
.then(function success() {
|
||||||
VolumeService.remove(volume)
|
Notifications.success('Volume successfully removed', volume.Id);
|
||||||
.then(function success() {
|
var index = $scope.volumes.indexOf(volume);
|
||||||
Notifications.success('Volume successfully removed', volume.Id);
|
$scope.volumes.splice(index, 1);
|
||||||
var index = $scope.volumes.indexOf(volume);
|
})
|
||||||
$scope.volumes.splice(index, 1);
|
.catch(function error(err) {
|
||||||
})
|
Notifications.error('Failure', err, 'Unable to remove volume');
|
||||||
.catch(function error(err) {
|
})
|
||||||
Notifications.error('Failure', err, 'Unable to remove volume');
|
.finally(function final() {
|
||||||
})
|
--actionCount;
|
||||||
.finally(function final() {
|
if (actionCount === 0) {
|
||||||
--actionCount;
|
$state.reload();
|
||||||
if (actionCount === 0) {
|
}
|
||||||
$state.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -79,11 +79,7 @@ class KubernetesApplicationsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeStacksAction(selectedItems) {
|
removeStacksAction(selectedItems) {
|
||||||
confirmDelete('Are you sure that you want to remove the selected stack(s) ? This will remove all the applications associated to the stack(s).').then((confirmed) => {
|
return this.$async(this.removeStacksActionAsync, selectedItems);
|
||||||
if (confirmed) {
|
|
||||||
return this.$async(this.removeStacksActionAsync, selectedItems);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeActionAsync(selectedItems) {
|
async removeActionAsync(selectedItems) {
|
||||||
|
|
|
@ -50,7 +50,7 @@ function config($stateRegistryProvider: StateRegistry) {
|
||||||
|
|
||||||
$stateRegistryProvider.register({
|
$stateRegistryProvider.register({
|
||||||
name: 'portainer.wizard.endpoints',
|
name: 'portainer.wizard.endpoints',
|
||||||
url: '/endpoints',
|
url: '/endpoints?referrer',
|
||||||
views: {
|
views: {
|
||||||
'content@': {
|
'content@': {
|
||||||
component: 'wizardEnvironmentTypeSelectView',
|
component: 'wizardEnvironmentTypeSelectView',
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
|
|
||||||
angular.module('portainer.app').controller('StacksController', StacksController);
|
angular.module('portainer.app').controller('StacksController', StacksController);
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
function StacksController($scope, $state, Notifications, StackService, Authentication, endpoint) {
|
function StacksController($scope, $state, Notifications, StackService, Authentication, endpoint) {
|
||||||
$scope.removeAction = function (selectedItems) {
|
$scope.removeAction = function (selectedItems) {
|
||||||
confirmDelete('Do you want to remove the selected stack(s)? Associated services will be removed as well.').then((confirmed) => {
|
return deleteSelectedStacks(selectedItems);
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deleteSelectedStacks(selectedItems);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function deleteSelectedStacks(stacks) {
|
function deleteSelectedStacks(stacks) {
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import { Box, Plus, Trash2 } from 'lucide-react';
|
import { Box } from 'lucide-react';
|
||||||
|
|
||||||
import { ContainerGroup } from '@/react/azure/types';
|
import { ContainerGroup } from '@/react/azure/types';
|
||||||
import { Authorized } from '@/react/hooks/useUser';
|
import { Authorized } from '@/react/hooks/useUser';
|
||||||
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
import { Datatable } from '@@/datatables';
|
import { Datatable } from '@@/datatables';
|
||||||
import { Button } from '@@/buttons';
|
import { AddButton } from '@@/buttons';
|
||||||
import { Link } from '@@/Link';
|
|
||||||
import { createPersistedStore } from '@@/datatables/types';
|
import { createPersistedStore } from '@@/datatables/types';
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { columns } from './columns';
|
import { columns } from './columns';
|
||||||
|
|
||||||
|
@ -33,36 +32,26 @@ export function ContainersDatatable({ dataset, onRemoveClick }: Props) {
|
||||||
getRowId={(container) => container.id}
|
getRowId={(container) => container.id}
|
||||||
emptyContentLabel="No container available."
|
emptyContentLabel="No container available."
|
||||||
renderTableActions={(selectedRows) => (
|
renderTableActions={(selectedRows) => (
|
||||||
<>
|
<div className="flex gap-2">
|
||||||
<Authorized authorizations="AzureContainerGroupDelete">
|
<Authorized authorizations="AzureContainerGroupDelete">
|
||||||
<Button
|
<DeleteButton
|
||||||
color="dangerlight"
|
|
||||||
disabled={selectedRows.length === 0}
|
disabled={selectedRows.length === 0}
|
||||||
onClick={() => handleRemoveClick(selectedRows.map((r) => r.id))}
|
onConfirmed={() =>
|
||||||
icon={Trash2}
|
handleRemoveClick(selectedRows.map((r) => r.id))
|
||||||
>
|
}
|
||||||
Remove
|
confirmMessage="Are you sure you want to delete the selected containers?"
|
||||||
</Button>
|
/>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
|
|
||||||
<Authorized authorizations="AzureContainerGroupCreate">
|
<Authorized authorizations="AzureContainerGroupCreate">
|
||||||
<Link to="azure.containerinstances.new" className="space-left">
|
<AddButton>Add container</AddButton>
|
||||||
<Button icon={Plus}>Add container</Button>
|
|
||||||
</Link>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
async function handleRemoveClick(containerIds: string[]) {
|
async function handleRemoveClick(containerIds: string[]) {
|
||||||
const confirmed = await confirmDelete(
|
|
||||||
'Are you sure you want to delete the selected containers?'
|
|
||||||
);
|
|
||||||
if (!confirmed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return onRemoveClick(containerIds);
|
return onRemoveClick(containerIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { PropsWithChildren, AnchorHTMLAttributes } from 'react';
|
import { PropsWithChildren, AnchorHTMLAttributes } from 'react';
|
||||||
import { UISref, UISrefProps } from '@uirouter/react';
|
import { UISrefProps, useSref } from '@uirouter/react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -8,18 +8,18 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Link({
|
export function Link({
|
||||||
title = '',
|
|
||||||
className,
|
|
||||||
children,
|
children,
|
||||||
|
to,
|
||||||
|
params,
|
||||||
|
options,
|
||||||
...props
|
...props
|
||||||
}: PropsWithChildren<Props> & UISrefProps) {
|
}: PropsWithChildren<Props> & UISrefProps) {
|
||||||
|
const { onClick, href } = useSref(to, params, options);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
<UISref className={className} {...props}>
|
<a onClick={onClick} href={href} {...props}>
|
||||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
{children}
|
||||||
<a title={title} target={props.target} rel={props.rel}>
|
</a>
|
||||||
{children}
|
|
||||||
</a>
|
|
||||||
</UISref>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { Trash2 } from 'lucide-react';
|
import { Trash2 } from 'lucide-react';
|
||||||
import { ComponentProps, PropsWithChildren, ReactNode } from 'react';
|
import { ComponentProps, PropsWithChildren, ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { AutomationTestingProps } from '@/types';
|
||||||
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
import { confirmDelete } from '@@/modals/confirm';
|
||||||
|
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
|
@ -21,13 +23,15 @@ type ConfirmOrClick =
|
||||||
export function DeleteButton({
|
export function DeleteButton({
|
||||||
disabled,
|
disabled,
|
||||||
size,
|
size,
|
||||||
|
'data-cy': dataCy,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: PropsWithChildren<
|
}: PropsWithChildren<
|
||||||
ConfirmOrClick & {
|
AutomationTestingProps &
|
||||||
size?: ComponentProps<typeof Button>['size'];
|
ConfirmOrClick & {
|
||||||
disabled?: boolean;
|
size?: ComponentProps<typeof Button>['size'];
|
||||||
}
|
disabled?: boolean;
|
||||||
|
}
|
||||||
>) {
|
>) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
@ -37,6 +41,7 @@ export function DeleteButton({
|
||||||
onClick={() => handleClick()}
|
onClick={() => handleClick()}
|
||||||
icon={Trash2}
|
icon={Trash2}
|
||||||
className="!m-0"
|
className="!m-0"
|
||||||
|
data-cy={dataCy}
|
||||||
>
|
>
|
||||||
{children || 'Remove'}
|
{children || 'Remove'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { Clipboard, Plus, Trash2 } from 'lucide-react';
|
import { Clipboard } from 'lucide-react';
|
||||||
|
|
||||||
import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
|
import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
|
||||||
|
|
||||||
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
||||||
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
||||||
import { useRepeater } from '@@/datatables/useRepeater';
|
import { useRepeater } from '@@/datatables/useRepeater';
|
||||||
import { Button } from '@@/buttons';
|
import { AddButton } from '@@/buttons';
|
||||||
import { Link } from '@@/Link';
|
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { DockerConfig } from '../../types';
|
import { DockerConfig } from '../../types';
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import { createStore } from './store';
|
||||||
interface Props {
|
interface Props {
|
||||||
dataset: Array<DockerConfig>;
|
dataset: Array<DockerConfig>;
|
||||||
onRemoveClick: (configs: Array<DockerConfig>) => void;
|
onRemoveClick: (configs: Array<DockerConfig>) => void;
|
||||||
onRefresh: () => Promise<void>;
|
onRefresh: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const storageKey = 'docker_configs';
|
const storageKey = 'docker_configs';
|
||||||
|
@ -54,24 +54,15 @@ export function ConfigsDatatable({ dataset, onRefresh, onRemoveClick }: Props) {
|
||||||
hasWriteAccessQuery.authorized && (
|
hasWriteAccessQuery.authorized && (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Authorized authorizations="DockerConfigDelete">
|
<Authorized authorizations="DockerConfigDelete">
|
||||||
<Button
|
<DeleteButton
|
||||||
icon={Trash2}
|
|
||||||
color="dangerlight"
|
|
||||||
onClick={() => onRemoveClick(selectedRows)}
|
|
||||||
disabled={selectedRows.length === 0}
|
disabled={selectedRows.length === 0}
|
||||||
>
|
onConfirmed={() => onRemoveClick(selectedRows)}
|
||||||
Remove
|
confirmMessage="Do you want to remove the selected config(s)?"
|
||||||
</Button>
|
/>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
|
|
||||||
<Authorized authorizations="DockerConfigCreate">
|
<Authorized authorizations="DockerConfigCreate">
|
||||||
<Button
|
<AddButton>Add config</AddButton>
|
||||||
icon={Plus}
|
|
||||||
as={Link}
|
|
||||||
props={{ to: 'docker.configs.new' }}
|
|
||||||
>
|
|
||||||
Add config
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
import { useRouter } from '@uirouter/react';
|
import { useRouter } from '@uirouter/react';
|
||||||
import {
|
import { Pause, Play, RefreshCw, Slash, Square, Trash2 } from 'lucide-react';
|
||||||
Pause,
|
|
||||||
Play,
|
|
||||||
Plus,
|
|
||||||
RefreshCw,
|
|
||||||
Slash,
|
|
||||||
Square,
|
|
||||||
Trash2,
|
|
||||||
} from 'lucide-react';
|
|
||||||
|
|
||||||
import * as notifications from '@/portainer/services/notifications';
|
import * as notifications from '@/portainer/services/notifications';
|
||||||
import { useAuthorizations, Authorized } from '@/react/hooks/useUser';
|
import { useAuthorizations, Authorized } from '@/react/hooks/useUser';
|
||||||
|
@ -29,8 +21,7 @@ import {
|
||||||
} from '@/react/docker/containers/containers.service';
|
} from '@/react/docker/containers/containers.service';
|
||||||
import type { EnvironmentId } from '@/react/portainer/environments/types';
|
import type { EnvironmentId } from '@/react/portainer/environments/types';
|
||||||
|
|
||||||
import { Link } from '@@/Link';
|
import { ButtonGroup, Button, AddButton } from '@@/buttons';
|
||||||
import { ButtonGroup, Button } from '@@/buttons';
|
|
||||||
|
|
||||||
type ContainerServiceAction = (
|
type ContainerServiceAction = (
|
||||||
endpointId: EnvironmentId,
|
endpointId: EnvironmentId,
|
||||||
|
@ -166,11 +157,11 @@ export function ContainersDatatableActions({
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
{isAddActionVisible && (
|
{isAddActionVisible && (
|
||||||
<Authorized authorizations="DockerContainerCreate">
|
<div className="space-left">
|
||||||
<Link to="docker.containers.new" className="space-left">
|
<Authorized authorizations="DockerContainerCreate">
|
||||||
<Button icon={Plus}>Add container</Button>
|
<AddButton>Add container</AddButton>
|
||||||
</Link>
|
</Authorized>
|
||||||
</Authorized>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
import {
|
import { ChevronDown, Download, List, Trash2, Upload } from 'lucide-react';
|
||||||
ChevronDown,
|
|
||||||
Download,
|
|
||||||
List,
|
|
||||||
Plus,
|
|
||||||
Trash2,
|
|
||||||
Upload,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { Menu, MenuButton, MenuItem, MenuPopover } from '@reach/menu-button';
|
import { Menu, MenuButton, MenuItem, MenuPopover } from '@reach/menu-button';
|
||||||
import { positionRight } from '@reach/popover';
|
import { positionRight } from '@reach/popover';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
@ -21,7 +14,7 @@ import {
|
||||||
RefreshableTableSettings,
|
RefreshableTableSettings,
|
||||||
} from '@@/datatables/types';
|
} from '@@/datatables/types';
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
import { Button, ButtonGroup, LoadingButton } from '@@/buttons';
|
import { AddButton, Button, ButtonGroup, LoadingButton } from '@@/buttons';
|
||||||
import { Link } from '@@/Link';
|
import { Link } from '@@/Link';
|
||||||
import { ButtonWithRef } from '@@/buttons/Button';
|
import { ButtonWithRef } from '@@/buttons/Button';
|
||||||
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
||||||
|
@ -82,14 +75,12 @@ export function ImagesDatatable({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Authorized authorizations="DockerImageBuild">
|
<Authorized authorizations="DockerImageBuild">
|
||||||
<Button
|
<AddButton
|
||||||
as={Link}
|
to="docker.images.build"
|
||||||
props={{ to: 'docker.images.build' }}
|
|
||||||
data-cy="image-buildImageButton"
|
data-cy="image-buildImageButton"
|
||||||
icon={Plus}
|
|
||||||
>
|
>
|
||||||
Build a new image
|
Build a new image
|
||||||
</Button>
|
</AddButton>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { DockerContainer } from '@/react/docker/containers/types';
|
||||||
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
|
||||||
import { useContainers } from '@/react/docker/containers/queries/containers';
|
import { useContainers } from '@/react/docker/containers/queries/containers';
|
||||||
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
import { PageHeader } from '@@/PageHeader';
|
import { PageHeader } from '@@/PageHeader';
|
||||||
|
|
||||||
import { useNetwork, useDeleteNetwork } from '../queries';
|
import { useNetwork, useDeleteNetwork } from '../queries';
|
||||||
|
@ -95,19 +94,14 @@ export function ItemView() {
|
||||||
);
|
);
|
||||||
|
|
||||||
async function onRemoveNetworkClicked() {
|
async function onRemoveNetworkClicked() {
|
||||||
const message = 'Do you want to delete the network?';
|
deleteNetworkMutation.mutate(
|
||||||
const confirmed = await confirmDelete(message);
|
{ environmentId, networkId },
|
||||||
|
{
|
||||||
if (confirmed) {
|
onSuccess: () => {
|
||||||
deleteNetworkMutation.mutate(
|
router.stateService.go('docker.networks');
|
||||||
{ environmentId, networkId },
|
},
|
||||||
{
|
}
|
||||||
onSuccess: () => {
|
);
|
||||||
router.stateService.go('docker.networks');
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { Network, Trash2 } from 'lucide-react';
|
import { Network } from 'lucide-react';
|
||||||
|
|
||||||
import DockerNetworkHelper from '@/docker/helpers/networkHelper';
|
import DockerNetworkHelper from '@/docker/helpers/networkHelper';
|
||||||
import { Authorized } from '@/react/hooks/useUser';
|
import { Authorized } from '@/react/hooks/useUser';
|
||||||
|
|
||||||
import { TableContainer, TableTitle } from '@@/datatables';
|
import { TableContainer, TableTitle } from '@@/datatables';
|
||||||
import { DetailsTable } from '@@/DetailsTable';
|
import { DetailsTable } from '@@/DetailsTable';
|
||||||
import { Button } from '@@/buttons';
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
import { Icon } from '@@/Icon';
|
|
||||||
|
|
||||||
import { isSystemNetwork } from '../network.helper';
|
import { isSystemNetwork } from '../network.helper';
|
||||||
import { DockerNetwork, IPConfig } from '../types';
|
import { DockerNetwork, IPConfig } from '../types';
|
||||||
|
@ -38,21 +37,18 @@ export function NetworkDetailsTable({
|
||||||
<DetailsTable.Row label="Id">
|
<DetailsTable.Row label="Id">
|
||||||
{network.Id}
|
{network.Id}
|
||||||
{allowRemoveNetwork && (
|
{allowRemoveNetwork && (
|
||||||
<Authorized authorizations="DockerNetworkDelete">
|
<span className="ml-2">
|
||||||
<Button
|
<Authorized authorizations="DockerNetworkDelete">
|
||||||
data-cy="networkDetails-deleteNetwork"
|
<DeleteButton
|
||||||
size="xsmall"
|
data-cy="networkDetails-deleteNetwork"
|
||||||
color="danger"
|
size="xsmall"
|
||||||
onClick={() => onRemoveNetworkClicked()}
|
onConfirmed={onRemoveNetworkClicked}
|
||||||
>
|
confirmMessage="Do you want to delete the network?"
|
||||||
<Icon
|
>
|
||||||
icon={Trash2}
|
Delete this network
|
||||||
className="space-right"
|
</DeleteButton>
|
||||||
aria-hidden="true"
|
</Authorized>
|
||||||
/>
|
</span>
|
||||||
Delete this network
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
|
||||||
)}
|
)}
|
||||||
</DetailsTable.Row>
|
</DetailsTable.Row>
|
||||||
<DetailsTable.Row label="Driver">{network.Driver}</DetailsTable.Row>
|
<DetailsTable.Row label="Driver">{network.Driver}</DetailsTable.Row>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Plus, Network, Trash2 } from 'lucide-react';
|
import { Network } from 'lucide-react';
|
||||||
|
|
||||||
import { Authorized } from '@/react/hooks/useUser';
|
import { Authorized } from '@/react/hooks/useUser';
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
|
@ -10,12 +10,12 @@ import {
|
||||||
refreshableSettings,
|
refreshableSettings,
|
||||||
RefreshableTableSettings,
|
RefreshableTableSettings,
|
||||||
} from '@@/datatables/types';
|
} from '@@/datatables/types';
|
||||||
import { Button } from '@@/buttons';
|
import { AddButton } from '@@/buttons';
|
||||||
import { TableSettingsMenu } from '@@/datatables';
|
import { TableSettingsMenu } from '@@/datatables';
|
||||||
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
||||||
import { useRepeater } from '@@/datatables/useRepeater';
|
import { useRepeater } from '@@/datatables/useRepeater';
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
import { Link } from '@@/Link';
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { useIsSwarm } from '../../proxy/queries/useInfo';
|
import { useIsSwarm } from '../../proxy/queries/useInfo';
|
||||||
|
|
||||||
|
@ -80,22 +80,16 @@ export function NetworksDatatable({ dataset, onRemove, onRefresh }: Props) {
|
||||||
<Authorized
|
<Authorized
|
||||||
authorizations={['DockerNetworkDelete', 'DockerNetworkCreate']}
|
authorizations={['DockerNetworkDelete', 'DockerNetworkCreate']}
|
||||||
>
|
>
|
||||||
<Button
|
<DeleteButton
|
||||||
disabled={selectedRows.length === 0}
|
disabled={selectedRows.length === 0}
|
||||||
color="dangerlight"
|
confirmMessage="Do you want to remove the selected network(s)?"
|
||||||
onClick={() => onRemove(selectedRows)}
|
onConfirmed={() => onRemove(selectedRows)}
|
||||||
icon={Trash2}
|
/>
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
<Authorized
|
<Authorized authorizations="DockerNetworkCreate">
|
||||||
authorizations="DockerNetworkCreate"
|
<AddButton data-cy="network-addNetworkButton">
|
||||||
data-cy="network-addNetworkButton"
|
|
||||||
>
|
|
||||||
<Button icon={Plus} as={Link} props={{ to: '.new' }}>
|
|
||||||
Add network
|
Add network
|
||||||
</Button>
|
</AddButton>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createColumnHelper } from '@tanstack/react-table';
|
import { createColumnHelper } from '@tanstack/react-table';
|
||||||
import { Lock, Plus, Trash2 } from 'lucide-react';
|
import { Lock } from 'lucide-react';
|
||||||
|
|
||||||
import { SecretViewModel } from '@/docker/models/secret';
|
import { SecretViewModel } from '@/docker/models/secret';
|
||||||
import { isoDate } from '@/portainer/filters/filters';
|
import { isoDate } from '@/portainer/filters/filters';
|
||||||
|
@ -15,9 +15,9 @@ import {
|
||||||
} from '@@/datatables/types';
|
} from '@@/datatables/types';
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
||||||
import { Button } from '@@/buttons';
|
import { AddButton } from '@@/buttons';
|
||||||
import { Link } from '@@/Link';
|
|
||||||
import { useRepeater } from '@@/datatables/useRepeater';
|
import { useRepeater } from '@@/datatables/useRepeater';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { createOwnershipColumn } from '../../components/datatable/createOwnershipColumn';
|
import { createOwnershipColumn } from '../../components/datatable/createOwnershipColumn';
|
||||||
|
|
||||||
|
@ -96,28 +96,16 @@ function TableActions({
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Authorized authorizations="DockerSecretDelete">
|
<Authorized authorizations="DockerSecretDelete">
|
||||||
<Button
|
<DeleteButton
|
||||||
color="dangerlight"
|
|
||||||
disabled={selectedItems.length === 0}
|
disabled={selectedItems.length === 0}
|
||||||
onClick={() => onRemove(selectedItems)}
|
onConfirmed={() => onRemove(selectedItems)}
|
||||||
icon={Trash2}
|
confirmMessage="Do you want to remove the selected secret(s)?"
|
||||||
className="!m-0"
|
|
||||||
data-cy="secret-removeSecretButton"
|
data-cy="secret-removeSecretButton"
|
||||||
>
|
/>
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
|
|
||||||
<Authorized authorizations="DockerSecretCreate">
|
<Authorized authorizations="DockerSecretCreate">
|
||||||
<Button
|
<AddButton data-cy="secret-addSecretButton">Add secret</AddButton>
|
||||||
as={Link}
|
|
||||||
props={{ to: '.new' }}
|
|
||||||
icon={Plus}
|
|
||||||
className="!m-0"
|
|
||||||
data-cy="secret-addSecretButton"
|
|
||||||
>
|
|
||||||
Add secret
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Trash2, Plus, RefreshCw } from 'lucide-react';
|
import { RefreshCw } from 'lucide-react';
|
||||||
import { useRouter } from '@uirouter/react';
|
import { useRouter } from '@uirouter/react';
|
||||||
|
|
||||||
import { ServiceViewModel } from '@/docker/models/service';
|
import { ServiceViewModel } from '@/docker/models/service';
|
||||||
|
@ -6,9 +6,8 @@ import { Authorized } from '@/react/hooks/useUser';
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
import { notifySuccess } from '@/portainer/services/notifications';
|
import { notifySuccess } from '@/portainer/services/notifications';
|
||||||
|
|
||||||
import { Link } from '@@/Link';
|
import { AddButton, Button, ButtonGroup } from '@@/buttons';
|
||||||
import { Button, ButtonGroup } from '@@/buttons';
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
|
|
||||||
import { confirmServiceForceUpdate } from '../../common/update-service-modal';
|
import { confirmServiceForceUpdate } from '../../common/update-service-modal';
|
||||||
|
|
||||||
|
@ -46,28 +45,18 @@ export function TableActions({
|
||||||
</Authorized>
|
</Authorized>
|
||||||
)}
|
)}
|
||||||
<Authorized authorizations="DockerServiceDelete">
|
<Authorized authorizations="DockerServiceDelete">
|
||||||
<Button
|
<DeleteButton
|
||||||
color="dangerlight"
|
|
||||||
disabled={selectedItems.length === 0}
|
disabled={selectedItems.length === 0}
|
||||||
onClick={() => handleRemove(selectedItems)}
|
onConfirmed={() => handleRemove(selectedItems)}
|
||||||
icon={Trash2}
|
confirmMessage="Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too."
|
||||||
data-cy="service-removeServiceButton"
|
data-cy="service-removeServiceButton"
|
||||||
>
|
/>
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
{isAddActionVisible && (
|
{isAddActionVisible && (
|
||||||
<Authorized authorizations="DockerServiceCreate">
|
<Authorized authorizations="DockerServiceCreate">
|
||||||
<Button
|
<AddButton>Add service</AddButton>
|
||||||
as={Link}
|
|
||||||
props={{ to: '.new' }}
|
|
||||||
icon={Plus}
|
|
||||||
className="!ml-0"
|
|
||||||
>
|
|
||||||
Add service
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,14 +86,6 @@ export function TableActions({
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRemove(selectedItems: Array<ServiceViewModel>) {
|
async function handleRemove(selectedItems: Array<ServiceViewModel>) {
|
||||||
const confirmed = await confirmDelete(
|
|
||||||
'Do you want to remove the selected service(s)? All the containers associated to the selected service(s) will be removed too.'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMutation.mutate(
|
removeMutation.mutate(
|
||||||
selectedItems.map((service) => service.Id),
|
selectedItems.map((service) => service.Id),
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { Trash2, Plus } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Authorized } from '@/react/hooks/useUser';
|
import { Authorized } from '@/react/hooks/useUser';
|
||||||
|
|
||||||
import { Link } from '@@/Link';
|
import { AddButton } from '@@/buttons';
|
||||||
import { Button } from '@@/buttons';
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { DecoratedStack } from './types';
|
import { DecoratedStack } from './types';
|
||||||
|
|
||||||
|
@ -17,28 +15,18 @@ export function TableActions({
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Authorized authorizations="PortainerStackDelete">
|
<Authorized authorizations="PortainerStackDelete">
|
||||||
<Button
|
<DeleteButton
|
||||||
color="dangerlight"
|
|
||||||
disabled={selectedItems.length === 0}
|
disabled={selectedItems.length === 0}
|
||||||
onClick={() => onRemove(selectedItems)}
|
onConfirmed={() => onRemove(selectedItems)}
|
||||||
icon={Trash2}
|
confirmMessage="Do you want to remove the selected stack(s)? Associated services will be removed as well."
|
||||||
className="!m-0"
|
|
||||||
data-cy="stack-removeStackButton"
|
data-cy="stack-removeStackButton"
|
||||||
>
|
/>
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
|
|
||||||
<Authorized authorizations="PortainerStackCreate">
|
<Authorized authorizations="PortainerStackCreate">
|
||||||
<Button
|
<AddButton data-cy="stack-addStackButton" to=".newstack">
|
||||||
as={Link}
|
|
||||||
props={{ to: '.newstack' }}
|
|
||||||
icon={Plus}
|
|
||||||
className="!m-0"
|
|
||||||
data-cy="stack-addStackButton"
|
|
||||||
>
|
|
||||||
Add stack
|
Add stack
|
||||||
</Button>
|
</AddButton>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { Plus, Trash2 } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Authorized } from '@/react/hooks/useUser';
|
import { Authorized } from '@/react/hooks/useUser';
|
||||||
|
|
||||||
import { Link } from '@@/Link';
|
import { AddButton } from '@@/buttons';
|
||||||
import { Button } from '@@/buttons';
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { DecoratedVolume } from '../types';
|
import { DecoratedVolume } from '../types';
|
||||||
|
|
||||||
|
@ -17,27 +15,15 @@ export function TableActions({
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Authorized authorizations="DockerVolumeDelete">
|
<Authorized authorizations="DockerVolumeDelete">
|
||||||
<Button
|
<DeleteButton
|
||||||
color="dangerlight"
|
|
||||||
disabled={selectedItems.length === 0}
|
disabled={selectedItems.length === 0}
|
||||||
onClick={() => onRemove(selectedItems)}
|
onConfirmed={() => onRemove(selectedItems)}
|
||||||
icon={Trash2}
|
confirmMessage="Do you want to remove the selected volume(s)?"
|
||||||
className="!m-0"
|
|
||||||
data-cy="volume-removeVolumeButton"
|
data-cy="volume-removeVolumeButton"
|
||||||
>
|
/>
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
<Authorized authorizations="DockerVolumeCreate">
|
<Authorized authorizations="DockerVolumeCreate">
|
||||||
<Button
|
<AddButton data-cy="volume-addVolumeButton">Add volume</AddButton>
|
||||||
as={Link}
|
|
||||||
props={{ to: '.new' }}
|
|
||||||
icon={Plus}
|
|
||||||
className="!m-0"
|
|
||||||
data-cy="volume-addVolumeButton"
|
|
||||||
>
|
|
||||||
Add volume
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Check, CheckCircle, Trash2 } from 'lucide-react';
|
import { Check, CheckCircle } from 'lucide-react';
|
||||||
|
|
||||||
import { notifySuccess } from '@/portainer/services/notifications';
|
import { notifySuccess } from '@/portainer/services/notifications';
|
||||||
import { useDeleteEnvironmentsMutation } from '@/react/portainer/environments/queries/useDeleteEnvironmentsMutation';
|
import { useDeleteEnvironmentsMutation } from '@/react/portainer/environments/queries/useDeleteEnvironmentsMutation';
|
||||||
|
@ -7,10 +7,9 @@ import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||||
import { useIsPureAdmin } from '@/react/hooks/useUser';
|
import { useIsPureAdmin } from '@/react/hooks/useUser';
|
||||||
|
|
||||||
import { Button } from '@@/buttons';
|
import { Button } from '@@/buttons';
|
||||||
import { ModalType, openModal } from '@@/modals';
|
import { openModal } from '@@/modals';
|
||||||
import { confirm } from '@@/modals/confirm';
|
|
||||||
import { buildConfirmButton } from '@@/modals/utils';
|
|
||||||
import { TooltipWithChildren } from '@@/Tip/TooltipWithChildren';
|
import { TooltipWithChildren } from '@@/Tip/TooltipWithChildren';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { useAssociateDeviceMutation, useLicenseOverused } from '../queries';
|
import { useAssociateDeviceMutation, useLicenseOverused } from '../queries';
|
||||||
import { WaitingRoomEnvironment } from '../types';
|
import { WaitingRoomEnvironment } from '../types';
|
||||||
|
@ -36,14 +35,13 @@ export function TableActions({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<DeleteButton
|
||||||
onClick={() => handleRemoveDevice(selectedRows)}
|
onConfirmed={() => handleRemoveDevice(selectedRows)}
|
||||||
disabled={selectedRows.length === 0}
|
disabled={selectedRows.length === 0}
|
||||||
color="dangerlight"
|
confirmMessage="You're about to remove edge device(s) from waiting room, which will not be shown until next agent startup."
|
||||||
icon={Trash2}
|
|
||||||
>
|
>
|
||||||
Remove Device
|
Remove Device
|
||||||
</Button>
|
</DeleteButton>
|
||||||
|
|
||||||
<TooltipWithChildren
|
<TooltipWithChildren
|
||||||
message={
|
message={
|
||||||
|
@ -122,18 +120,6 @@ export function TableActions({
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRemoveDevice(devices: Environment[]) {
|
async function handleRemoveDevice(devices: Environment[]) {
|
||||||
const confirmed = await confirm({
|
|
||||||
title: 'Are you sure?',
|
|
||||||
message:
|
|
||||||
"You're about to remove edge device(s) from waiting room, which will not be shown until next agent startup.",
|
|
||||||
confirmButton: buildConfirmButton('Remove', 'danger'),
|
|
||||||
modalType: ModalType.Destructive,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMutation.mutate(
|
removeMutation.mutate(
|
||||||
devices.map((d) => d.Id),
|
devices.map((d) => d.Id),
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import { Trash2, Plus } from 'lucide-react';
|
|
||||||
|
|
||||||
import { notifySuccess } from '@/portainer/services/notifications';
|
import { notifySuccess } from '@/portainer/services/notifications';
|
||||||
|
|
||||||
import { Button } from '@@/buttons';
|
import { AddButton } from '@@/buttons';
|
||||||
import { confirmDestructive } from '@@/modals/confirm';
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
import { buildConfirmButton } from '@@/modals/utils';
|
|
||||||
import { Link } from '@@/Link';
|
|
||||||
|
|
||||||
import { useDeleteEdgeStacksMutation } from './useDeleteEdgeStacksMutation';
|
import { useDeleteEdgeStacksMutation } from './useDeleteEdgeStacksMutation';
|
||||||
import { DecoratedEdgeStack } from './types';
|
import { DecoratedEdgeStack } from './types';
|
||||||
|
@ -19,39 +15,17 @@ export function TableActions({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<DeleteButton
|
||||||
color="dangerlight"
|
|
||||||
disabled={selectedItems.length === 0}
|
disabled={selectedItems.length === 0}
|
||||||
onClick={() => handleRemove(selectedItems)}
|
onConfirmed={() => handleRemove(selectedItems)}
|
||||||
icon={Trash2}
|
confirmMessage="Are you sure you want to remove the selected Edge stack(s)?"
|
||||||
className="!m-0"
|
/>
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
<AddButton data-cy="edgeStack-addStackButton">Add stack</AddButton>
|
||||||
as={Link}
|
|
||||||
props={{ to: 'edge.stacks.new' }}
|
|
||||||
icon={Plus}
|
|
||||||
className="!m-0"
|
|
||||||
data-cy="edgeStack-addStackButton"
|
|
||||||
>
|
|
||||||
Add stack
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
async function handleRemove(selectedItems: Array<DecoratedEdgeStack>) {
|
async function handleRemove(selectedItems: Array<DecoratedEdgeStack>) {
|
||||||
const confirmed = await confirmDestructive({
|
|
||||||
title: 'Are you sure?',
|
|
||||||
message: 'Are you sure you want to remove the selected Edge stack(s)?',
|
|
||||||
confirmButton: buildConfirmButton('Remove', 'danger'),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ids = selectedItems.map((item) => item.Id);
|
const ids = selectedItems.map((item) => item.Id);
|
||||||
removeMutation.mutate(ids, {
|
removeMutation.mutate(ids, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Pencil, Plus } from 'lucide-react';
|
import { Pencil } from 'lucide-react';
|
||||||
import { useCurrentStateAndParams } from '@uirouter/react';
|
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||||
import { Pod } from 'kubernetes-types/core/v1';
|
import { Pod } from 'kubernetes-types/core/v1';
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { useStackFile } from '@/react/common/stacks/stack.service';
|
||||||
import { useNamespaceQuery } from '@/react/kubernetes/namespaces/queries/useNamespaceQuery';
|
import { useNamespaceQuery } from '@/react/kubernetes/namespaces/queries/useNamespaceQuery';
|
||||||
|
|
||||||
import { Widget, WidgetBody } from '@@/Widget';
|
import { Widget, WidgetBody } from '@@/Widget';
|
||||||
import { Button } from '@@/buttons';
|
import { AddButton, Button } from '@@/buttons';
|
||||||
import { Link } from '@@/Link';
|
import { Link } from '@@/Link';
|
||||||
import { Icon } from '@@/Icon';
|
import { Icon } from '@@/Icon';
|
||||||
|
|
||||||
|
@ -102,23 +102,15 @@ export function ApplicationDetailsWidget() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{appStackFileQuery.data && (
|
{appStackFileQuery.data && (
|
||||||
<Link
|
<AddButton
|
||||||
to="kubernetes.templates.custom.new"
|
to="kubernetes.templates.custom.new"
|
||||||
|
data-cy="k8sAppDetail-createCustomTemplateButton"
|
||||||
params={{
|
params={{
|
||||||
fileContent: appStackFileQuery.data.StackFileContent,
|
fileContent: appStackFileQuery.data.StackFileContent,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
Create template from application
|
||||||
type="button"
|
</AddButton>
|
||||||
color="primary"
|
|
||||||
size="small"
|
|
||||||
className="hover:decoration-none !ml-0"
|
|
||||||
data-cy="k8sAppDetail-createCustomTemplateButton"
|
|
||||||
>
|
|
||||||
<Icon icon={Plus} className="mr-1" />
|
|
||||||
Create template from application
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { Trash2 } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Authorized } from '@/react/hooks/useUser';
|
import { Authorized } from '@/react/hooks/useUser';
|
||||||
|
|
||||||
import { Button } from '@@/buttons';
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { KubernetesStack } from '../../types';
|
import { KubernetesStack } from '../../types';
|
||||||
|
|
||||||
|
@ -15,15 +13,12 @@ export function TableActions({
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Authorized authorizations="K8sApplicationsW">
|
<Authorized authorizations="K8sApplicationsW">
|
||||||
<Button
|
<DeleteButton
|
||||||
|
confirmMessage="Are you sure that you want to remove the selected stack(s) ? This will remove all the applications associated to the stack(s)."
|
||||||
disabled={selectedItems.length === 0}
|
disabled={selectedItems.length === 0}
|
||||||
color="dangerlight"
|
onConfirmed={() => onRemove(selectedItems)}
|
||||||
onClick={() => onRemove(selectedItems)}
|
|
||||||
icon={Trash2}
|
|
||||||
data-cy="k8sApp-removeStackButton"
|
data-cy="k8sApp-removeStackButton"
|
||||||
>
|
/>
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { FileCode, Plus, Trash2 } from 'lucide-react';
|
import { FileCode } from 'lucide-react';
|
||||||
import { ConfigMap } from 'kubernetes-types/core/v1';
|
import { ConfigMap } from 'kubernetes-types/core/v1';
|
||||||
|
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
|
@ -12,12 +12,12 @@ import { Application } from '@/react/kubernetes/applications/types';
|
||||||
import { pluralize } from '@/portainer/helpers/strings';
|
import { pluralize } from '@/portainer/helpers/strings';
|
||||||
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
|
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
|
||||||
import { Namespaces } from '@/react/kubernetes/namespaces/types';
|
import { Namespaces } from '@/react/kubernetes/namespaces/types';
|
||||||
|
import { CreateFromManifestButton } from '@/react/kubernetes/components/CreateFromManifestButton';
|
||||||
|
|
||||||
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
import { AddButton } from '@@/buttons';
|
||||||
import { Button } from '@@/buttons';
|
|
||||||
import { Link } from '@@/Link';
|
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useConfigMapsForCluster,
|
useConfigMapsForCluster,
|
||||||
|
@ -139,16 +139,6 @@ function TableActions({
|
||||||
const deleteConfigMapMutation = useMutationDeleteConfigMaps(environmentId);
|
const deleteConfigMapMutation = useMutationDeleteConfigMaps(environmentId);
|
||||||
|
|
||||||
async function handleRemoveClick(configMaps: ConfigMap[]) {
|
async function handleRemoveClick(configMaps: ConfigMap[]) {
|
||||||
const confirmed = await confirmDelete(
|
|
||||||
`Are you sure you want to remove the selected ${pluralize(
|
|
||||||
configMaps.length,
|
|
||||||
'ConfigMap'
|
|
||||||
)}?`
|
|
||||||
);
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configMapsToDelete = configMaps.map((configMap) => ({
|
const configMapsToDelete = configMaps.map((configMap) => ({
|
||||||
namespace: configMap.metadata?.namespace ?? '',
|
namespace: configMap.metadata?.namespace ?? '',
|
||||||
name: configMap.metadata?.name ?? '',
|
name: configMap.metadata?.name ?? '',
|
||||||
|
@ -159,41 +149,30 @@ function TableActions({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Authorized authorizations="K8sConfigMapsW">
|
<Authorized authorizations="K8sConfigMapsW">
|
||||||
<Button
|
<DeleteButton
|
||||||
className="btn-wrapper"
|
|
||||||
color="dangerlight"
|
|
||||||
disabled={selectedItems.length === 0}
|
disabled={selectedItems.length === 0}
|
||||||
onClick={async () => {
|
onConfirmed={() => handleRemoveClick(selectedItems)}
|
||||||
handleRemoveClick(selectedItems);
|
confirmMessage={`Are you sure you want to remove the selected ${pluralize(
|
||||||
}}
|
selectedItems.length,
|
||||||
icon={Trash2}
|
'ConfigMap'
|
||||||
|
)}`}
|
||||||
data-cy="k8sConfig-removeConfigButton"
|
data-cy="k8sConfig-removeConfigButton"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddButton
|
||||||
|
to="kubernetes.configmaps.new"
|
||||||
|
data-cy="k8sConfig-addConfigWithFormButton"
|
||||||
|
color="secondary"
|
||||||
>
|
>
|
||||||
Remove
|
Add with form
|
||||||
</Button>
|
</AddButton>
|
||||||
<Link to="kubernetes.configmaps.new" className="ml-1">
|
|
||||||
<Button
|
<CreateFromManifestButton
|
||||||
className="btn-wrapper"
|
|
||||||
color="secondary"
|
|
||||||
icon={Plus}
|
|
||||||
data-cy="k8sConfig-addConfigWithFormButton"
|
|
||||||
>
|
|
||||||
Add with form
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="kubernetes.deploy"
|
|
||||||
params={{
|
params={{
|
||||||
referrer: 'kubernetes.configurations',
|
|
||||||
tab: 'configmaps',
|
tab: 'configmaps',
|
||||||
}}
|
}}
|
||||||
className="ml-1"
|
|
||||||
data-cy="k8sConfig-deployFromManifestButton"
|
data-cy="k8sConfig-deployFromManifestButton"
|
||||||
>
|
/>
|
||||||
<Button className="btn-wrapper" color="primary" icon={Plus}>
|
|
||||||
Create from manifest
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Lock, Plus, Trash2 } from 'lucide-react';
|
import { Lock } from 'lucide-react';
|
||||||
import { Secret } from 'kubernetes-types/core/v1';
|
import { Secret } from 'kubernetes-types/core/v1';
|
||||||
|
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
|
@ -12,12 +12,12 @@ import { Application } from '@/react/kubernetes/applications/types';
|
||||||
import { pluralize } from '@/portainer/helpers/strings';
|
import { pluralize } from '@/portainer/helpers/strings';
|
||||||
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
|
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
|
||||||
import { Namespaces } from '@/react/kubernetes/namespaces/types';
|
import { Namespaces } from '@/react/kubernetes/namespaces/types';
|
||||||
|
import { CreateFromManifestButton } from '@/react/kubernetes/components/CreateFromManifestButton';
|
||||||
|
|
||||||
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
import { AddButton } from '@@/buttons';
|
||||||
import { Button } from '@@/buttons';
|
|
||||||
import { Link } from '@@/Link';
|
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useSecretsForCluster,
|
useSecretsForCluster,
|
||||||
|
@ -135,16 +135,6 @@ function TableActions({ selectedItems }: { selectedItems: SecretRowData[] }) {
|
||||||
const deleteSecretMutation = useMutationDeleteSecrets(environmentId);
|
const deleteSecretMutation = useMutationDeleteSecrets(environmentId);
|
||||||
|
|
||||||
async function handleRemoveClick(secrets: SecretRowData[]) {
|
async function handleRemoveClick(secrets: SecretRowData[]) {
|
||||||
const confirmed = await confirmDelete(
|
|
||||||
`Are you sure you want to remove the selected ${pluralize(
|
|
||||||
secrets.length,
|
|
||||||
'secret'
|
|
||||||
)}?`
|
|
||||||
);
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const secretsToDelete = secrets.map((secret) => ({
|
const secretsToDelete = secrets.map((secret) => ({
|
||||||
namespace: secret.metadata?.namespace ?? '',
|
namespace: secret.metadata?.namespace ?? '',
|
||||||
name: secret.metadata?.name ?? '',
|
name: secret.metadata?.name ?? '',
|
||||||
|
@ -155,41 +145,28 @@ function TableActions({ selectedItems }: { selectedItems: SecretRowData[] }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Authorized authorizations="K8sSecretsW">
|
<Authorized authorizations="K8sSecretsW">
|
||||||
<Button
|
<DeleteButton
|
||||||
className="btn-wrapper"
|
|
||||||
color="dangerlight"
|
|
||||||
disabled={selectedItems.length === 0}
|
disabled={selectedItems.length === 0}
|
||||||
onClick={async () => {
|
onConfirmed={() => handleRemoveClick(selectedItems)}
|
||||||
handleRemoveClick(selectedItems);
|
|
||||||
}}
|
|
||||||
icon={Trash2}
|
|
||||||
data-cy="k8sSecret-removeSecretButton"
|
data-cy="k8sSecret-removeSecretButton"
|
||||||
|
confirmMessage={`Are you sure you want to remove the selected ${pluralize(
|
||||||
|
selectedItems.length,
|
||||||
|
'secret'
|
||||||
|
)}?`}
|
||||||
|
/>
|
||||||
|
<AddButton
|
||||||
|
to="kubernetes.secrets.new"
|
||||||
|
data-cy="k8sSecret-addSecretWithFormButton"
|
||||||
|
color="secondary"
|
||||||
>
|
>
|
||||||
Remove
|
Add with form
|
||||||
</Button>
|
</AddButton>
|
||||||
<Link to="kubernetes.secrets.new" className="ml-1">
|
<CreateFromManifestButton
|
||||||
<Button
|
|
||||||
className="btn-wrapper"
|
|
||||||
color="secondary"
|
|
||||||
icon={Plus}
|
|
||||||
data-cy="k8sSecret-addSecretWithFormButton"
|
|
||||||
>
|
|
||||||
Add with form
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="kubernetes.deploy"
|
|
||||||
params={{
|
params={{
|
||||||
referrer: 'kubernetes.configurations',
|
|
||||||
tab: 'secrets',
|
tab: 'secrets',
|
||||||
}}
|
}}
|
||||||
className="ml-1"
|
|
||||||
data-cy="k8sSecret-deployFromManifestButton"
|
data-cy="k8sSecret-deployFromManifestButton"
|
||||||
>
|
/>
|
||||||
<Button className="btn-wrapper" color="primary" icon={Plus}>
|
|
||||||
Create from manifest
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Plus, Trash2 } from 'lucide-react';
|
|
||||||
import { useRouter } from '@uirouter/react';
|
import { useRouter } from '@uirouter/react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
@ -9,16 +8,16 @@ import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultD
|
||||||
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
|
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
|
||||||
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
|
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
|
||||||
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
||||||
import { Button } from '@@/buttons';
|
import { AddButton } from '@@/buttons';
|
||||||
import { Link } from '@@/Link';
|
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { DeleteIngressesRequest, Ingress } from '../types';
|
import { DeleteIngressesRequest, Ingress } from '../types';
|
||||||
import { useDeleteIngresses, useIngresses } from '../queries';
|
import { useDeleteIngresses, useIngresses } from '../queries';
|
||||||
import { useNamespacesQuery } from '../../namespaces/queries/useNamespacesQuery';
|
import { useNamespacesQuery } from '../../namespaces/queries/useNamespacesQuery';
|
||||||
import { Namespaces } from '../../namespaces/types';
|
import { Namespaces } from '../../namespaces/types';
|
||||||
|
import { CreateFromManifestButton } from '../../components/CreateFromManifestButton';
|
||||||
|
|
||||||
import { columns } from './columns';
|
import { columns } from './columns';
|
||||||
|
|
||||||
|
@ -111,38 +110,20 @@ export function IngressDatatable() {
|
||||||
|
|
||||||
function tableActions(selectedFlatRows: Ingress[]) {
|
function tableActions(selectedFlatRows: Ingress[]) {
|
||||||
return (
|
return (
|
||||||
<div className="ingressDatatable-actions">
|
<Authorized authorizations="K8sIngressesW">
|
||||||
<Authorized authorizations="AzureContainerGroupDelete">
|
<DeleteButton
|
||||||
<Button
|
disabled={selectedFlatRows.length === 0}
|
||||||
color="dangerlight"
|
onConfirmed={() => handleRemoveClick(selectedFlatRows)}
|
||||||
disabled={selectedFlatRows.length === 0}
|
data-cy="k8sSecret-removeSecretButton"
|
||||||
onClick={() => handleRemoveClick(selectedFlatRows)}
|
confirmMessage="Are you sure you want to delete the selected ingresses?"
|
||||||
icon={Trash2}
|
/>
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</Authorized>
|
|
||||||
|
|
||||||
<Authorized authorizations="K8sIngressesW">
|
<AddButton to=".create" color="secondary">
|
||||||
<Link
|
Add with form
|
||||||
to="kubernetes.ingresses.create"
|
</AddButton>
|
||||||
className="space-left no-decoration"
|
|
||||||
>
|
<CreateFromManifestButton />
|
||||||
<Button icon={Plus} color="secondary">
|
</Authorized>
|
||||||
Add with form
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</Authorized>
|
|
||||||
<Authorized authorizations="K8sIngressesW">
|
|
||||||
<Link
|
|
||||||
to="kubernetes.deploy"
|
|
||||||
className="space-left no-decoration"
|
|
||||||
params={{ referrer: 'kubernetes.ingresses' }}
|
|
||||||
>
|
|
||||||
<Button icon={Plus}>Create from manifest</Button>
|
|
||||||
</Link>
|
|
||||||
</Authorized>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,13 +133,6 @@ export function IngressDatatable() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRemoveClick(ingresses: SelectedIngress[]) {
|
async function handleRemoveClick(ingresses: SelectedIngress[]) {
|
||||||
const confirmed = await confirmDelete(
|
|
||||||
'Are you sure you want to delete the selected ingresses?'
|
|
||||||
);
|
|
||||||
if (!confirmed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload: DeleteIngressesRequest = {} as DeleteIngressesRequest;
|
const payload: DeleteIngressesRequest = {} as DeleteIngressesRequest;
|
||||||
ingresses.forEach((ingress) => {
|
ingresses.forEach((ingress) => {
|
||||||
payload[ingress.Namespace] = payload[ingress.Namespace] || [];
|
payload[ingress.Namespace] = payload[ingress.Namespace] || [];
|
||||||
|
@ -173,6 +147,5 @@ export function IngressDatatable() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return ingresses;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
background-color: var(--bg-body-color);
|
background-color: var(--bg-body-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ingressDatatable-actions button > span,
|
|
||||||
.anntation-actions button > span,
|
.anntation-actions button > span,
|
||||||
.rules-action button > span,
|
.rules-action button > span,
|
||||||
.rule button > span {
|
.rule button > span {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useMemo } from 'react';
|
import { Shuffle } from 'lucide-react';
|
||||||
import { Shuffle, Trash2 } from 'lucide-react';
|
|
||||||
import { useRouter } from '@uirouter/react';
|
import { useRouter } from '@uirouter/react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Row } from '@tanstack/react-table';
|
import { Row } from '@tanstack/react-table';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { Namespaces } from '@/react/kubernetes/namespaces/types';
|
import { Namespaces } from '@/react/kubernetes/namespaces/types';
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
|
@ -12,12 +12,11 @@ import { pluralize } from '@/portainer/helpers/strings';
|
||||||
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
|
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
|
||||||
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
|
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
|
||||||
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
|
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
|
||||||
|
import { CreateFromManifestButton } from '@/react/kubernetes/components/CreateFromManifestButton';
|
||||||
|
|
||||||
import { Datatable, Table, TableSettingsMenu } from '@@/datatables';
|
import { Datatable, Table, TableSettingsMenu } from '@@/datatables';
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
import { Button } from '@@/buttons';
|
|
||||||
import { Link } from '@@/Link';
|
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useMutationDeleteServices,
|
useMutationDeleteServices,
|
||||||
|
@ -94,7 +93,7 @@ export function ServicesDatatable() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// useServicesRowData appends the `isSyetem` property to the service data
|
// useServicesRowData appends the `isSystem` property to the service data
|
||||||
function useServicesRowData(
|
function useServicesRowData(
|
||||||
services: Service[],
|
services: Service[],
|
||||||
namespaces?: Namespaces
|
namespaces?: Namespaces
|
||||||
|
@ -136,26 +135,33 @@ function TableActions({ selectedItems }: TableActionsProps) {
|
||||||
const deleteServicesMutation = useMutationDeleteServices(environmentId);
|
const deleteServicesMutation = useMutationDeleteServices(environmentId);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
async function handleRemoveClick(services: SelectedService[]) {
|
return (
|
||||||
const confirmed = await confirmDelete(
|
<Authorized authorizations="K8sServicesW">
|
||||||
<>
|
<DeleteButton
|
||||||
<p>{`Are you sure you want to remove the selected ${pluralize(
|
disabled={selectedItems.length === 0}
|
||||||
services.length,
|
onConfirmed={() => handleRemoveClick(selectedItems)}
|
||||||
'service'
|
confirmMessage={
|
||||||
)}?`}</p>
|
<>
|
||||||
<ul className="pl-6">
|
<p>{`Are you sure you want to remove the selected ${pluralize(
|
||||||
{services.map((s, index) => (
|
selectedItems.length,
|
||||||
<li key={index}>
|
'service'
|
||||||
{s.Namespace}/{s.Name}
|
)}?`}</p>
|
||||||
</li>
|
<ul className="pl-6">
|
||||||
))}
|
{selectedItems.map((s, index) => (
|
||||||
</ul>
|
<li key={index}>
|
||||||
</>
|
{s.Namespace}/{s.Name}
|
||||||
);
|
</li>
|
||||||
if (!confirmed) {
|
))}
|
||||||
return null;
|
</ul>
|
||||||
}
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CreateFromManifestButton />
|
||||||
|
</Authorized>
|
||||||
|
);
|
||||||
|
|
||||||
|
async function handleRemoveClick(services: SelectedService[]) {
|
||||||
const payload: Record<string, string[]> = {};
|
const payload: Record<string, string[]> = {};
|
||||||
services.forEach((service) => {
|
services.forEach((service) => {
|
||||||
payload[service.Namespace] = payload[service.Namespace] || [];
|
payload[service.Namespace] = payload[service.Namespace] || [];
|
||||||
|
@ -181,32 +187,5 @@ function TableActions({ selectedItems }: TableActionsProps) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return services;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="servicesDatatable-actions">
|
|
||||||
<Authorized authorizations="K8sServicesW">
|
|
||||||
<Button
|
|
||||||
className="btn-wrapper"
|
|
||||||
color="dangerlight"
|
|
||||||
disabled={selectedItems.length === 0}
|
|
||||||
onClick={() => handleRemoveClick(selectedItems)}
|
|
||||||
icon={Trash2}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
to="kubernetes.deploy"
|
|
||||||
params={{ referrer: 'kubernetes.services' }}
|
|
||||||
className="space-left hover:no-decoration"
|
|
||||||
>
|
|
||||||
<Button className="btn-wrapper" color="primary" icon="plus">
|
|
||||||
Create from manifest
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</Authorized>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { useRouter } from '@uirouter/react';
|
import { useRouter } from '@uirouter/react';
|
||||||
import { Trash2 } from 'lucide-react';
|
|
||||||
|
|
||||||
import { pluralize } from '@/portainer/helpers/strings';
|
import { pluralize } from '@/portainer/helpers/strings';
|
||||||
|
|
||||||
import { confirmDestructive } from '@@/modals/confirm';
|
import { AddButton } from '@@/buttons';
|
||||||
import { AddButton, Button } from '@@/buttons';
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { HelmRepository } from './types';
|
import { HelmRepository } from './types';
|
||||||
import { useDeleteHelmRepositoriesMutation } from './helm-repositories.service';
|
import { useDeleteHelmRepositoriesMutation } from './helm-repositories.service';
|
||||||
|
@ -18,37 +17,27 @@ export function HelmRepositoryDatatableActions({ selectedItems }: Props) {
|
||||||
const deleteHelmRepoMutation = useDeleteHelmRepositoriesMutation();
|
const deleteHelmRepoMutation = useDeleteHelmRepositoriesMutation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2">
|
<>
|
||||||
<Button
|
<DeleteButton
|
||||||
disabled={selectedItems.length < 1}
|
disabled={selectedItems.length === 0}
|
||||||
color="dangerlight"
|
onConfirmed={() => onDeleteClick(selectedItems)}
|
||||||
onClick={() => onDeleteClick(selectedItems)}
|
confirmMessage={`Are you sure you want to remove the selected Helm ${pluralize(
|
||||||
|
selectedItems.length,
|
||||||
|
'repository',
|
||||||
|
'repositories'
|
||||||
|
)}?`}
|
||||||
data-cy="credentials-deleteButton"
|
data-cy="credentials-deleteButton"
|
||||||
icon={Trash2}
|
/>
|
||||||
|
<AddButton
|
||||||
|
to="portainer.account.createHelmRepository"
|
||||||
|
data-cy="credentials-addButton"
|
||||||
>
|
>
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<AddButton to="portainer.account.createHelmRepository">
|
|
||||||
Add Helm repository
|
Add Helm repository
|
||||||
</AddButton>
|
</AddButton>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
async function onDeleteClick(selectedItems: HelmRepository[]) {
|
async function onDeleteClick(selectedItems: HelmRepository[]) {
|
||||||
const confirmed = await confirmDestructive({
|
|
||||||
title: 'Confirm action',
|
|
||||||
message: `Are you sure you want to remove the selected Helm ${pluralize(
|
|
||||||
selectedItems.length,
|
|
||||||
'repository',
|
|
||||||
'repositories'
|
|
||||||
)}?`,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteHelmRepoMutation.mutate(selectedItems, {
|
deleteHelmRepoMutation.mutate(selectedItems, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
router.stateService.reload();
|
router.stateService.reload();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { HardDrive, Plus, Trash2 } from 'lucide-react';
|
import { HardDrive, Trash2 } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { useEnvironmentList } from '@/react/portainer/environments/queries';
|
import { useEnvironmentList } from '@/react/portainer/environments/queries';
|
||||||
|
@ -6,8 +6,7 @@ import { useGroups } from '@/react/portainer/environments/environment-groups/que
|
||||||
|
|
||||||
import { Datatable } from '@@/datatables';
|
import { Datatable } from '@@/datatables';
|
||||||
import { createPersistedStore } from '@@/datatables/types';
|
import { createPersistedStore } from '@@/datatables/types';
|
||||||
import { Button } from '@@/buttons';
|
import { AddButton, Button } from '@@/buttons';
|
||||||
import { Link } from '@@/Link';
|
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
|
|
||||||
import { isBE } from '../../feature-flags/feature-flags.service';
|
import { isBE } from '../../feature-flags/feature-flags.service';
|
||||||
|
@ -86,26 +85,20 @@ export function EnvironmentsDatatable({
|
||||||
<ImportFdoDeviceButton />
|
<ImportFdoDeviceButton />
|
||||||
|
|
||||||
{isBE && (
|
{isBE && (
|
||||||
<Button
|
<AddButton
|
||||||
as={Link}
|
|
||||||
color="secondary"
|
color="secondary"
|
||||||
icon={Plus}
|
to="portainer.endpoints.edgeAutoCreateScript"
|
||||||
props={{ to: 'portainer.endpoints.edgeAutoCreateScript' }}
|
|
||||||
>
|
>
|
||||||
Auto onboarding
|
Auto onboarding
|
||||||
</Button>
|
</AddButton>
|
||||||
)}
|
)}
|
||||||
<Link to="portainer.wizard.endpoints">
|
|
||||||
<Button
|
<AddButton
|
||||||
onClick={() =>
|
to="portainer.wizard.endpoints"
|
||||||
localStorage.setItem('wizardReferrer', 'environments')
|
params={{ referrer: 'environments' }}
|
||||||
}
|
>
|
||||||
icon={Plus}
|
Add environment
|
||||||
className="!m-0"
|
</AddButton>
|
||||||
>
|
|
||||||
Add environment
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import { Plus } from 'lucide-react';
|
import { AddButton } from '@@/buttons';
|
||||||
|
|
||||||
import { Button } from '@@/buttons';
|
|
||||||
import { Link } from '@@/Link';
|
|
||||||
|
|
||||||
import { useSettings } from '../../settings/queries';
|
import { useSettings } from '../../settings/queries';
|
||||||
import {
|
import {
|
||||||
|
@ -22,15 +19,10 @@ export function ImportFdoDeviceButton() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<div className="ml-[5px]">
|
||||||
type="button"
|
<AddButton color="secondary" to="portainer.endpoints.importDevice">
|
||||||
color="secondary"
|
Import FDO device
|
||||||
icon={Plus}
|
</AddButton>
|
||||||
as={Link}
|
</div>
|
||||||
props={{ to: 'portainer.endpoints.importDevice' }}
|
|
||||||
className="ml-[5px]"
|
|
||||||
>
|
|
||||||
Import FDO device
|
|
||||||
</Button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Clock, Trash2 } from 'lucide-react';
|
import { Clock } from 'lucide-react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
@ -6,12 +6,11 @@ import { notifySuccess } from '@/portainer/services/notifications';
|
||||||
import { withLimitToBE } from '@/react/hooks/useLimitToBE';
|
import { withLimitToBE } from '@/react/hooks/useLimitToBE';
|
||||||
import { useEdgeGroups } from '@/react/edge/edge-groups/queries/useEdgeGroups';
|
import { useEdgeGroups } from '@/react/edge/edge-groups/queries/useEdgeGroups';
|
||||||
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
import { Datatable } from '@@/datatables';
|
import { Datatable } from '@@/datatables';
|
||||||
import { PageHeader } from '@@/PageHeader';
|
import { PageHeader } from '@@/PageHeader';
|
||||||
import { Button } from '@@/buttons';
|
import { AddButton } from '@@/buttons';
|
||||||
import { Link } from '@@/Link';
|
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { useList } from '../queries/list';
|
import { useList } from '../queries/list';
|
||||||
import { EdgeUpdateSchedule, StatusType } from '../types';
|
import { EdgeUpdateSchedule, StatusType } from '../types';
|
||||||
|
@ -90,29 +89,16 @@ function TableActions({
|
||||||
const removeMutation = useRemoveMutation();
|
const removeMutation = useRemoveMutation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<DeleteButton
|
||||||
icon={Trash2}
|
onConfirmed={() => handleRemove()}
|
||||||
color="dangerlight"
|
|
||||||
onClick={() => handleRemove()}
|
|
||||||
disabled={selectedRows.length === 0}
|
disabled={selectedRows.length === 0}
|
||||||
>
|
confirmMessage="Are you sure you want to remove these schedules?"
|
||||||
Remove
|
/>
|
||||||
</Button>
|
<AddButton to=".create">Add update & rollback schedule</AddButton>
|
||||||
|
|
||||||
<Link to=".create">
|
|
||||||
<Button>Add update & rollback schedule</Button>
|
|
||||||
</Link>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
async function handleRemove() {
|
async function handleRemove() {
|
||||||
const confirmed = await confirmDelete(
|
|
||||||
'Are you sure you want to remove these?'
|
|
||||||
);
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeMutation.mutate(selectedRows, {
|
removeMutation.mutate(selectedRows, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notifySuccess('Success', 'Schedules successfully removed');
|
notifySuccess('Success', 'Schedules successfully removed');
|
||||||
|
|
|
@ -33,7 +33,7 @@ import { WizardEndpointsList } from './WizardEndpointsList';
|
||||||
|
|
||||||
export function EnvironmentCreationView() {
|
export function EnvironmentCreationView() {
|
||||||
const {
|
const {
|
||||||
params: { localEndpointId: localEndpointIdParam },
|
params: { localEndpointId: localEndpointIdParam, referrer },
|
||||||
} = useCurrentStateAndParams();
|
} = useCurrentStateAndParams();
|
||||||
|
|
||||||
const [environmentIds, setEnvironmentIds] = useState<EnvironmentId[]>(() => {
|
const [environmentIds, setEnvironmentIds] = useState<EnvironmentId[]>(() => {
|
||||||
|
@ -130,8 +130,7 @@ export function EnvironmentCreationView() {
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
if (localStorage.getItem('wizardReferrer') === 'environments') {
|
if (referrer === 'environments') {
|
||||||
localStorage.removeItem('wizardReferrer');
|
|
||||||
router.stateService.go('portainer.endpoints');
|
router.stateService.go('portainer.endpoints');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Bell, Trash2 } from 'lucide-react';
|
import { Bell } from 'lucide-react';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
import { useCurrentStateAndParams } from '@uirouter/react';
|
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ import { withReactQuery } from '@/react-tools/withReactQuery';
|
||||||
|
|
||||||
import { PageHeader } from '@@/PageHeader';
|
import { PageHeader } from '@@/PageHeader';
|
||||||
import { Datatable } from '@@/datatables';
|
import { Datatable } from '@@/datatables';
|
||||||
import { Button } from '@@/buttons';
|
|
||||||
import { createPersistedStore } from '@@/datatables/types';
|
import { createPersistedStore } from '@@/datatables/types';
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { notificationsStore } from './notifications-store';
|
import { notificationsStore } from './notifications-store';
|
||||||
import { ToastNotification } from './types';
|
import { ToastNotification } from './types';
|
||||||
|
@ -62,14 +62,11 @@ function TableActions({ selectedRows }: { selectedRows: ToastNotification[] }) {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const notificationsStoreState = useStore(notificationsStore);
|
const notificationsStoreState = useStore(notificationsStore);
|
||||||
return (
|
return (
|
||||||
<Button
|
<DeleteButton
|
||||||
icon={Trash2}
|
onConfirmed={() => handleRemove()}
|
||||||
color="dangerlight"
|
|
||||||
onClick={() => handleRemove()}
|
|
||||||
disabled={selectedRows.length === 0}
|
disabled={selectedRows.length === 0}
|
||||||
>
|
confirmMessage="Are you sure you want to remove the selected notifications?"
|
||||||
Remove
|
/>
|
||||||
</Button>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleRemove() {
|
function handleRemove() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
import { useRouter } from '@uirouter/react';
|
import { useRouter } from '@uirouter/react';
|
||||||
import { PlusCircle, Trash2 } from 'lucide-react';
|
import { PlusCircle } from 'lucide-react';
|
||||||
|
|
||||||
import { Profile } from '@/portainer/hostmanagement/fdo/model';
|
import { Profile } from '@/portainer/hostmanagement/fdo/model';
|
||||||
import * as notifications from '@/portainer/services/notifications';
|
import * as notifications from '@/portainer/services/notifications';
|
||||||
|
@ -9,10 +9,10 @@ import {
|
||||||
duplicateProfile,
|
duplicateProfile,
|
||||||
} from '@/portainer/hostmanagement/fdo/fdo.service';
|
} from '@/portainer/hostmanagement/fdo/fdo.service';
|
||||||
|
|
||||||
import { confirm, confirmDestructive } from '@@/modals/confirm';
|
import { confirm } from '@@/modals/confirm';
|
||||||
import { Link } from '@@/Link';
|
import { Link } from '@@/Link';
|
||||||
import { Button } from '@@/buttons';
|
import { Button } from '@@/buttons';
|
||||||
import { buildConfirmButton } from '@@/modals/utils';
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isFDOEnabled: boolean;
|
isFDOEnabled: boolean;
|
||||||
|
@ -27,7 +27,7 @@ export function FDOProfilesDatatableActions({
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="actionBar">
|
<>
|
||||||
<Link to="portainer.endpoints.profile" className="space-left">
|
<Link to="portainer.endpoints.profile" className="space-left">
|
||||||
<Button disabled={!isFDOEnabled} icon={PlusCircle}>
|
<Button disabled={!isFDOEnabled} icon={PlusCircle}>
|
||||||
Add Profile
|
Add Profile
|
||||||
|
@ -42,15 +42,12 @@ export function FDOProfilesDatatableActions({
|
||||||
Duplicate
|
Duplicate
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<DeleteButton
|
||||||
disabled={!isFDOEnabled || selectedItems.length < 1}
|
disabled={!isFDOEnabled || selectedItems.length === 0}
|
||||||
color="danger"
|
onConfirmed={() => onDeleteProfileClick()}
|
||||||
onClick={() => onDeleteProfileClick()}
|
confirmMessage="This action will delete the selected profile(s). Continue?"
|
||||||
icon={Trash2}
|
/>
|
||||||
>
|
</>
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
async function onDuplicateProfileClick() {
|
async function onDuplicateProfileClick() {
|
||||||
|
@ -80,16 +77,6 @@ export function FDOProfilesDatatableActions({
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDeleteProfileClick() {
|
async function onDeleteProfileClick() {
|
||||||
const confirmed = await confirmDestructive({
|
|
||||||
title: 'Are you sure?',
|
|
||||||
message: 'This action will delete the selected profile(s). Continue?',
|
|
||||||
confirmButton: buildConfirmButton('Remove', 'danger'),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
selectedItems.map(async (profile) => {
|
selectedItems.map(async (profile) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -2,7 +2,9 @@ import userEvent from '@testing-library/user-event';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
|
|
||||||
import { AppTemplatesListItem } from './AppTemplatesListItem';
|
import { withTestRouter } from '@/react/test-utils/withRouter';
|
||||||
|
|
||||||
|
import { AppTemplatesListItem as BaseComponent } from './AppTemplatesListItem';
|
||||||
import { TemplateViewModel } from './view-model';
|
import { TemplateViewModel } from './view-model';
|
||||||
import { TemplateType } from './types';
|
import { TemplateType } from './types';
|
||||||
|
|
||||||
|
@ -15,13 +17,7 @@ test('should render AppTemplatesListItem component', () => {
|
||||||
const onSelect = vi.fn();
|
const onSelect = vi.fn();
|
||||||
const isSelected = false;
|
const isSelected = false;
|
||||||
|
|
||||||
const { getByText } = render(
|
const { getByText } = renderComponent({ isSelected, template, onSelect });
|
||||||
<AppTemplatesListItem
|
|
||||||
template={template}
|
|
||||||
onSelect={onSelect}
|
|
||||||
isSelected={isSelected}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(getByText(template.Title, { exact: false })).toBeInTheDocument();
|
expect(getByText(template.Title, { exact: false })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -45,26 +41,23 @@ const copyAsCustomTestCases = [
|
||||||
vi.mock('@uirouter/react', async (importOriginal: () => Promise<object>) => ({
|
vi.mock('@uirouter/react', async (importOriginal: () => Promise<object>) => ({
|
||||||
...(await importOriginal()),
|
...(await importOriginal()),
|
||||||
UISref: ({ children }: PropsWithChildren<unknown>) => children, // Mocking UISref to render its children directly
|
UISref: ({ children }: PropsWithChildren<unknown>) => children, // Mocking UISref to render its children directly
|
||||||
|
useSref: () => ({ href: '' }), // Mocking useSref to return an empty string
|
||||||
}));
|
}));
|
||||||
|
|
||||||
copyAsCustomTestCases.forEach(({ type, expected }) => {
|
copyAsCustomTestCases.forEach(({ type, expected }) => {
|
||||||
test(`copy as custom button should ${
|
test(`copy as custom button should ${
|
||||||
expected ? '' : 'not '
|
expected ? '' : 'not '
|
||||||
}be rendered for type ${type}`, () => {
|
}be rendered for type ${TemplateType[type]}`, () => {
|
||||||
const onSelect = vi.fn();
|
const onSelect = vi.fn();
|
||||||
const isSelected = false;
|
const isSelected = false;
|
||||||
|
|
||||||
const { queryByText, unmount } = render(
|
const { queryByText, unmount } = renderComponent({
|
||||||
<AppTemplatesListItem
|
isSelected,
|
||||||
template={
|
template: {
|
||||||
{
|
Type: type,
|
||||||
Type: type,
|
} as TemplateViewModel,
|
||||||
} as TemplateViewModel
|
onSelect,
|
||||||
}
|
});
|
||||||
onSelect={onSelect}
|
|
||||||
isSelected={isSelected}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (expected) {
|
if (expected) {
|
||||||
expect(queryByText('Copy as Custom')).toBeVisible();
|
expect(queryByText('Copy as Custom')).toBeVisible();
|
||||||
|
@ -86,16 +79,34 @@ test('should call onSelect when clicked', async () => {
|
||||||
const onSelect = vi.fn();
|
const onSelect = vi.fn();
|
||||||
const isSelected = false;
|
const isSelected = false;
|
||||||
|
|
||||||
const { getByLabelText } = render(
|
const { getByLabelText } = renderComponent({
|
||||||
<AppTemplatesListItem
|
isSelected,
|
||||||
template={template}
|
template,
|
||||||
onSelect={onSelect}
|
onSelect,
|
||||||
isSelected={isSelected}
|
});
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const button = getByLabelText(template.Title);
|
const button = getByLabelText(template.Title);
|
||||||
await user.click(button);
|
await user.click(button);
|
||||||
|
|
||||||
expect(onSelect).toHaveBeenCalledWith(template);
|
expect(onSelect).toHaveBeenCalledWith(template);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function renderComponent({
|
||||||
|
isSelected = false,
|
||||||
|
onSelect,
|
||||||
|
template,
|
||||||
|
}: {
|
||||||
|
template: TemplateViewModel;
|
||||||
|
onSelect?: () => void;
|
||||||
|
isSelected?: boolean;
|
||||||
|
}) {
|
||||||
|
const AppTemplatesListItem = withTestRouter(BaseComponent);
|
||||||
|
|
||||||
|
return render(
|
||||||
|
<AppTemplatesListItem
|
||||||
|
template={template}
|
||||||
|
onSelect={onSelect}
|
||||||
|
isSelected={isSelected}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Edit, Plus } from 'lucide-react';
|
import { Edit } from 'lucide-react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
@ -9,8 +9,7 @@ import { Table } from '@@/datatables';
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
import { createPersistedStore } from '@@/datatables/types';
|
import { createPersistedStore } from '@@/datatables/types';
|
||||||
import { DatatableFooter } from '@@/datatables/DatatableFooter';
|
import { DatatableFooter } from '@@/datatables/DatatableFooter';
|
||||||
import { Button } from '@@/buttons';
|
import { AddButton } from '@@/buttons';
|
||||||
import { Link } from '@@/Link';
|
|
||||||
|
|
||||||
import { CustomTemplatesListItem } from './CustomTemplatesListItem';
|
import { CustomTemplatesListItem } from './CustomTemplatesListItem';
|
||||||
|
|
||||||
|
@ -56,11 +55,7 @@ export function CustomTemplatesList({
|
||||||
searchValue={listState.search}
|
searchValue={listState.search}
|
||||||
title="Custom Templates"
|
title="Custom Templates"
|
||||||
titleIcon={Edit}
|
titleIcon={Edit}
|
||||||
renderTableActions={() => (
|
renderTableActions={() => <AddButton>Add Custom Template</AddButton>}
|
||||||
<Button as={Link} props={{ to: '.new' }} icon={Plus}>
|
|
||||||
Add Custom Template
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="blocklist gap-y-2 !px-[20px] !pb-[20px]" role="list">
|
<div className="blocklist gap-y-2 !px-[20px] !pb-[20px]" role="list">
|
||||||
|
|
|
@ -46,7 +46,6 @@ export function CustomTemplatesListItem({
|
||||||
<Button
|
<Button
|
||||||
as={Link}
|
as={Link}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useRouter } from '@uirouter/react';
|
import { useRouter } from '@uirouter/react';
|
||||||
import { useMutation, useQueryClient } from 'react-query';
|
import { useMutation, useQueryClient } from 'react-query';
|
||||||
import { Trash2, Users } from 'lucide-react';
|
import { Users } from 'lucide-react';
|
||||||
|
|
||||||
import { usePublicSettings } from '@/react/portainer/settings/queries';
|
import { usePublicSettings } from '@/react/portainer/settings/queries';
|
||||||
import {
|
import {
|
||||||
|
@ -9,9 +9,8 @@ import {
|
||||||
withInvalidate,
|
withInvalidate,
|
||||||
} from '@/react-tools/react-query';
|
} from '@/react-tools/react-query';
|
||||||
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
import { Button } from '@@/buttons';
|
|
||||||
import { Widget } from '@@/Widget';
|
import { Widget } from '@@/Widget';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
import { Team, TeamId, TeamMembership, TeamRole } from '../types';
|
import { Team, TeamId, TeamMembership, TeamRole } from '../types';
|
||||||
import { deleteTeam } from '../teams.service';
|
import { deleteTeam } from '../teams.service';
|
||||||
|
@ -45,17 +44,18 @@ export function Details({ team, memberships, isAdmin }: Props) {
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>
|
<td>
|
||||||
{!teamSyncQuery.data && team.Name}
|
<div className="flex gap-2">
|
||||||
{isAdmin && (
|
{!teamSyncQuery.data && team.Name}
|
||||||
<Button
|
{isAdmin && (
|
||||||
color="danger"
|
<DeleteButton
|
||||||
size="xsmall"
|
size="xsmall"
|
||||||
onClick={handleDeleteClick}
|
onConfirmed={handleDeleteClick}
|
||||||
icon={Trash2}
|
confirmMessage="Do you want to delete this team? Users in this team will not be deleted."
|
||||||
>
|
>
|
||||||
Delete this team
|
Delete this team
|
||||||
</Button>
|
</DeleteButton>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -75,18 +75,8 @@ export function Details({ team, memberships, isAdmin }: Props) {
|
||||||
);
|
);
|
||||||
|
|
||||||
async function handleDeleteClick() {
|
async function handleDeleteClick() {
|
||||||
const confirmed = await confirmDelete(
|
router.stateService.go('portainer.teams');
|
||||||
`Do you want to delete this team? Users in this team will not be deleted.`
|
deleteMutation.mutate(team.Id);
|
||||||
);
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteMutation.mutate(team.Id, {
|
|
||||||
onSuccess() {
|
|
||||||
router.stateService.go('portainer.teams');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMutation, useQueryClient } from 'react-query';
|
import { useMutation, useQueryClient } from 'react-query';
|
||||||
import { Trash2, Users } from 'lucide-react';
|
import { Users } from 'lucide-react';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
|
||||||
import { notifySuccess } from '@/portainer/services/notifications';
|
import { notifySuccess } from '@/portainer/services/notifications';
|
||||||
|
@ -7,12 +7,11 @@ import { promiseSequence } from '@/portainer/helpers/promise-utils';
|
||||||
import { Team, TeamId } from '@/react/portainer/users/teams/types';
|
import { Team, TeamId } from '@/react/portainer/users/teams/types';
|
||||||
import { deleteTeam } from '@/react/portainer/users/teams/teams.service';
|
import { deleteTeam } from '@/react/portainer/users/teams/teams.service';
|
||||||
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
import { Datatable } from '@@/datatables';
|
import { Datatable } from '@@/datatables';
|
||||||
import { Button } from '@@/buttons';
|
|
||||||
import { buildNameColumn } from '@@/datatables/buildNameColumn';
|
import { buildNameColumn } from '@@/datatables/buildNameColumn';
|
||||||
import { createPersistedStore } from '@@/datatables/types';
|
import { createPersistedStore } from '@@/datatables/types';
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
import { useTableState } from '@@/datatables/useTableState';
|
||||||
|
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||||
|
|
||||||
const storageKey = 'teams';
|
const storageKey = 'teams';
|
||||||
|
|
||||||
|
@ -40,14 +39,11 @@ export function TeamsDatatable({ teams, isAdmin }: Props) {
|
||||||
titleIcon={Users}
|
titleIcon={Users}
|
||||||
renderTableActions={(selectedRows) =>
|
renderTableActions={(selectedRows) =>
|
||||||
isAdmin && (
|
isAdmin && (
|
||||||
<Button
|
<DeleteButton
|
||||||
color="dangerlight"
|
onConfirmed={() => handleRemoveClick(selectedRows)}
|
||||||
onClick={() => handleRemoveClick(selectedRows)}
|
|
||||||
disabled={selectedRows.length === 0}
|
disabled={selectedRows.length === 0}
|
||||||
icon={Trash2}
|
confirmMessage="Are you sure you want to remove the selected teams?"
|
||||||
>
|
/>
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
emptyContentLabel="No teams found"
|
emptyContentLabel="No teams found"
|
||||||
|
@ -79,14 +75,6 @@ function useRemoveMutation() {
|
||||||
return { handleRemove };
|
return { handleRemove };
|
||||||
|
|
||||||
async function handleRemove(teams: TeamId[]) {
|
async function handleRemove(teams: TeamId[]) {
|
||||||
const confirmed = await confirmDelete(
|
|
||||||
'Are you sure you want to remove the selected teams?'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteMutation.mutate(teams, {
|
deleteMutation.mutate(teams, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
notifySuccess('Teams successfully removed', '');
|
notifySuccess('Teams successfully removed', '');
|
||||||
|
|
Loading…
Reference in New Issue