mirror of https://github.com/portainer/portainer
131 lines
3.5 KiB
TypeScript
131 lines
3.5 KiB
TypeScript
|
import { Pod } from 'kubernetes-types/core/v1';
|
||
|
import { RotateCcw } from 'lucide-react';
|
||
|
import { useRouter } from '@uirouter/react';
|
||
|
|
||
|
import { Authorized } from '@/react/hooks/useUser';
|
||
|
import { notifySuccess, notifyError } from '@/portainer/services/notifications';
|
||
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||
|
|
||
|
import { Button } from '@@/buttons';
|
||
|
import { Icon } from '@@/Icon';
|
||
|
import { confirm } from '@@/modals/confirm';
|
||
|
import { ModalType } from '@@/modals';
|
||
|
import { buildConfirmButton } from '@@/modals/utils';
|
||
|
|
||
|
import {
|
||
|
useApplicationRevisionList,
|
||
|
usePatchApplicationMutation,
|
||
|
} from '../../application.queries';
|
||
|
import {
|
||
|
applicationIsKind,
|
||
|
getRollbackPatchPayload,
|
||
|
matchLabelsToLabelSelectorValue,
|
||
|
} from '../../utils';
|
||
|
import { Application } from '../../types';
|
||
|
import { appDeployMethodLabel } from '../../constants';
|
||
|
|
||
|
type Props = {
|
||
|
environmentId: EnvironmentId;
|
||
|
namespace: string;
|
||
|
appName: string;
|
||
|
app?: Application;
|
||
|
};
|
||
|
|
||
|
export function RollbackApplicationButton({
|
||
|
environmentId,
|
||
|
namespace,
|
||
|
appName,
|
||
|
app,
|
||
|
}: Props) {
|
||
|
const router = useRouter();
|
||
|
const labelSelector = applicationIsKind<Pod>('Pod', app)
|
||
|
? ''
|
||
|
: matchLabelsToLabelSelectorValue(app?.spec?.selector?.matchLabels);
|
||
|
const appRevisionListQuery = useApplicationRevisionList(
|
||
|
environmentId,
|
||
|
namespace,
|
||
|
appName,
|
||
|
app?.metadata?.uid,
|
||
|
labelSelector,
|
||
|
app?.kind
|
||
|
);
|
||
|
const appRevisionList = appRevisionListQuery.data;
|
||
|
const appRevisions = appRevisionList?.items;
|
||
|
const appDeployMethod =
|
||
|
app?.metadata?.labels?.[appDeployMethodLabel] || 'application form';
|
||
|
|
||
|
const patchAppMutation = usePatchApplicationMutation(
|
||
|
environmentId,
|
||
|
namespace,
|
||
|
appName
|
||
|
);
|
||
|
|
||
|
return (
|
||
|
<Authorized authorizations="K8sApplicationDetailsW">
|
||
|
<Button
|
||
|
ng-if="!ctrl.isExternalApplication()"
|
||
|
type="button"
|
||
|
color="light"
|
||
|
size="small"
|
||
|
className="!ml-0"
|
||
|
disabled={
|
||
|
!app ||
|
||
|
!appRevisions ||
|
||
|
appRevisions?.length < 2 ||
|
||
|
appDeployMethod !== 'application form' ||
|
||
|
patchAppMutation.isLoading
|
||
|
}
|
||
|
onClick={() => rollbackApplication()}
|
||
|
data-cy="k8sAppDetail-rollbackButton"
|
||
|
>
|
||
|
<Icon icon={RotateCcw} className="mr-1" />
|
||
|
Rollback to previous configuration
|
||
|
</Button>
|
||
|
</Authorized>
|
||
|
);
|
||
|
|
||
|
async function rollbackApplication() {
|
||
|
// exit early if the application is a pod or there are no revisions
|
||
|
if (
|
||
|
!app?.kind ||
|
||
|
applicationIsKind<Pod>('Pod', app) ||
|
||
|
!appRevisionList?.items?.length
|
||
|
) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// confirm the action
|
||
|
const confirmed = await confirm({
|
||
|
title: 'Are you sure?',
|
||
|
modalType: ModalType.Warn,
|
||
|
confirmButton: buildConfirmButton('Rollback'),
|
||
|
message:
|
||
|
'Rolling back the application to a previous configuration may cause service interruption. Do you wish to continue?',
|
||
|
});
|
||
|
if (!confirmed) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
const patch = getRollbackPatchPayload(app, appRevisionList);
|
||
|
patchAppMutation.mutateAsync(
|
||
|
{ appKind: app.kind, patch },
|
||
|
{
|
||
|
onSuccess: () => {
|
||
|
notifySuccess('Success', 'Application successfully rolled back');
|
||
|
router.stateService.reload();
|
||
|
},
|
||
|
onError: (error) =>
|
||
|
notifyError(
|
||
|
'Failure',
|
||
|
error as Error,
|
||
|
'Unable to rollback the application'
|
||
|
),
|
||
|
}
|
||
|
);
|
||
|
} catch (error) {
|
||
|
notifyError('Failure', error as Error);
|
||
|
}
|
||
|
}
|
||
|
}
|