mirror of
https://github.com/portainer/portainer.git
synced 2025-11-26 14:06:05 +08:00
246 lines
6.4 KiB
TypeScript
246 lines
6.4 KiB
TypeScript
import {
|
|
ArrowRightIcon,
|
|
PlayIcon,
|
|
PlusIcon,
|
|
StopCircleIcon,
|
|
Trash2Icon,
|
|
} from 'lucide-react';
|
|
import { useRouter } from '@uirouter/react';
|
|
|
|
import { Authorized } from '@/react/hooks/useUser';
|
|
import { Stack, StackStatus } from '@/react/common/stacks/types';
|
|
import { useDeleteStackMutation } from '@/react/common/stacks/queries/useDeleteStackMutation';
|
|
import { notifyError, notifySuccess } from '@/portainer/services/notifications';
|
|
|
|
import { Button, LoadingButton } from '@@/buttons';
|
|
import { Link } from '@@/Link';
|
|
import { confirm, confirmDelete } from '@@/modals/confirm';
|
|
import { ModalType } from '@@/modals/Modal/types';
|
|
import { buildConfirmButton } from '@@/modals/utils';
|
|
|
|
import { useUpdateStackMutation } from '../../useUpdateStack';
|
|
|
|
import { useStartStackMutation } from './useStartStackMutation';
|
|
import { useStopStackMutation } from './useStopStackMutation';
|
|
|
|
export function StackActions({
|
|
stack,
|
|
fileContent,
|
|
isRegular,
|
|
environmentId,
|
|
isExternal,
|
|
}: {
|
|
stack: Stack;
|
|
fileContent?: string;
|
|
isRegular?: boolean;
|
|
environmentId: number;
|
|
isExternal: boolean;
|
|
}) {
|
|
const router = useRouter();
|
|
const startStackMutation = useStartStackMutation();
|
|
const stopStackMutation = useStopStackMutation();
|
|
const deleteStackMutation = useDeleteStackMutation();
|
|
const detachFromGitMutation = useUpdateStackMutation();
|
|
|
|
const isMutating =
|
|
startStackMutation.isLoading ||
|
|
stopStackMutation.isLoading ||
|
|
deleteStackMutation.isLoading ||
|
|
detachFromGitMutation.isLoading;
|
|
|
|
const stackId = stack.Id;
|
|
const status = stack.Status;
|
|
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
{isRegular && (
|
|
<Authorized authorizations="PortainerStackUpdate">
|
|
{status === StackStatus.Active ? (
|
|
<Button
|
|
icon={StopCircleIcon}
|
|
color="dangerlight"
|
|
size="xsmall"
|
|
onClick={() => handleStop()}
|
|
disabled={isMutating}
|
|
data-cy="stack-stop-btn"
|
|
>
|
|
Stop this stack
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
icon={PlayIcon}
|
|
color="success"
|
|
data-cy="stack-start-btn"
|
|
size="xsmall"
|
|
disabled={isMutating}
|
|
onClick={() =>
|
|
startStackMutation.mutate(
|
|
{ id: stackId, environmentId },
|
|
{
|
|
onError(err) {
|
|
notifyError(
|
|
'Failure',
|
|
err as Error,
|
|
'Unable to start stack'
|
|
);
|
|
},
|
|
onSuccess() {
|
|
notifySuccess(
|
|
'Success',
|
|
`Stack ${stack.Name} started successfully`
|
|
);
|
|
router.stateService.reload();
|
|
},
|
|
}
|
|
)
|
|
}
|
|
>
|
|
Start this stack
|
|
</Button>
|
|
)}
|
|
</Authorized>
|
|
)}
|
|
|
|
<Authorized authorizations="PortainerStackDelete">
|
|
<Button
|
|
icon={Trash2Icon}
|
|
color="dangerlight"
|
|
size="xsmall"
|
|
onClick={() => handleDelete()}
|
|
disabled={isMutating}
|
|
data-cy="stack-delete-btn"
|
|
>
|
|
Delete this stack
|
|
</Button>
|
|
</Authorized>
|
|
|
|
{!!(isRegular && fileContent) && (
|
|
<Button
|
|
as={Link}
|
|
icon={PlusIcon}
|
|
color="primary"
|
|
size="xsmall"
|
|
data-cy="stack-create-template-btn"
|
|
props={{
|
|
to: 'docker.templates.custom.new',
|
|
params: {
|
|
fileContent,
|
|
type: stack.Type,
|
|
},
|
|
}}
|
|
>
|
|
Create template from stack
|
|
</Button>
|
|
)}
|
|
|
|
{!!(
|
|
isRegular &&
|
|
fileContent &&
|
|
!stack.FromAppTemplate &&
|
|
stack.GitConfig
|
|
) && (
|
|
<Authorized authorizations="PortainerStackUpdate">
|
|
<LoadingButton
|
|
icon={ArrowRightIcon}
|
|
color="primary"
|
|
size="xsmall"
|
|
onClick={() => handleDetachFromGit()}
|
|
disabled={isMutating}
|
|
data-cy="stack-detach-git-btn"
|
|
isLoading={detachFromGitMutation.isLoading}
|
|
loadingText="Detachment in progress..."
|
|
>
|
|
Detach from Git
|
|
</LoadingButton>
|
|
</Authorized>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
async function handleStop() {
|
|
const confirmed = await confirm({
|
|
title: 'Are you sure?',
|
|
modalType: ModalType.Warn,
|
|
message: 'Are you sure you want to stop this stack?',
|
|
confirmButton: buildConfirmButton('Stop', 'danger'),
|
|
});
|
|
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
stopStackMutation.mutate(
|
|
{ id: stackId, environmentId },
|
|
{
|
|
onError(err) {
|
|
notifyError('Failure', err as Error, 'Unable to stop stack');
|
|
},
|
|
onSuccess() {
|
|
notifySuccess('Success', `Stack ${stack.Name} stopped successfully`);
|
|
router.stateService.reload();
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
async function handleDelete() {
|
|
const confirmed = await confirmDelete(
|
|
'Do you want to remove the stack? Associated services will be removed as well'
|
|
);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
deleteStackMutation.mutate(
|
|
{
|
|
id: stack.Id,
|
|
name: stack.Name,
|
|
environmentId: stack.EndpointId,
|
|
external: isExternal,
|
|
},
|
|
{
|
|
onError(err) {
|
|
notifyError(
|
|
'Failure',
|
|
err as Error,
|
|
`Unable to remove stack ${stack.Name}`
|
|
);
|
|
},
|
|
onSuccess() {
|
|
notifySuccess('Stack successfully removed', stack.Name);
|
|
router.stateService.go('^');
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
async function handleDetachFromGit() {
|
|
const confirmed = await confirm({
|
|
modalType: ModalType.Warn,
|
|
title: 'Are you sure?',
|
|
message: 'Do you want to detach the stack from Git?',
|
|
confirmButton: buildConfirmButton('Detach', 'danger'),
|
|
});
|
|
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
detachFromGitMutation.mutate(
|
|
{
|
|
environmentId,
|
|
stackId: stack.Id,
|
|
payload: {
|
|
stackFileContent: fileContent!,
|
|
env: stack.Env,
|
|
prune: false,
|
|
},
|
|
},
|
|
{
|
|
onSuccess() {
|
|
router.stateService.go('^');
|
|
},
|
|
}
|
|
);
|
|
}
|
|
}
|