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