mirror of https://github.com/portainer/portainer
192 lines
5.8 KiB
TypeScript
192 lines
5.8 KiB
TypeScript
![]() |
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||
|
|
||
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||
|
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||
|
import { getAllSettledItems } from '@/portainer/helpers/promise-utils';
|
||
|
import { withGlobalError } from '@/react-tools/react-query';
|
||
|
import { notifyError, notifySuccess } from '@/portainer/services/notifications';
|
||
|
import { pluralize } from '@/portainer/helpers/strings';
|
||
|
|
||
|
import { parseKubernetesAxiosError } from '../../axiosError';
|
||
|
import { ApplicationRowData } from '../ListView/ApplicationsDatatable/types';
|
||
|
import { Stack } from '../ListView/ApplicationsStacksDatatable/types';
|
||
|
|
||
|
import { queryKeys } from './query-keys';
|
||
|
|
||
|
export function useDeleteApplicationsMutation({
|
||
|
environmentId,
|
||
|
stacks,
|
||
|
reportStacks,
|
||
|
}: {
|
||
|
environmentId: EnvironmentId;
|
||
|
stacks: Stack[];
|
||
|
reportStacks?: boolean;
|
||
|
}) {
|
||
|
const queryClient = useQueryClient();
|
||
|
return useMutation({
|
||
|
mutationFn: (applications: ApplicationRowData[]) =>
|
||
|
deleteApplications(applications, stacks, environmentId),
|
||
|
onSuccess: ({ settledAppDeletions, settledStackDeletions }) => {
|
||
|
// one error notification per rejected item
|
||
|
settledAppDeletions.rejectedItems.forEach(({ item, reason }) => {
|
||
|
notifyError(
|
||
|
`Failed to remove application '${item.Name}'`,
|
||
|
new Error(reason)
|
||
|
);
|
||
|
});
|
||
|
settledStackDeletions.rejectedItems.forEach(({ item, reason }) => {
|
||
|
notifyError(`Failed to remove stack '${item.Name}'`, new Error(reason));
|
||
|
});
|
||
|
|
||
|
// one success notification for all fulfilled items
|
||
|
if (settledAppDeletions.fulfilledItems.length && !reportStacks) {
|
||
|
notifySuccess(
|
||
|
`${pluralize(
|
||
|
settledAppDeletions.fulfilledItems.length,
|
||
|
'Application'
|
||
|
)} successfully removed`,
|
||
|
settledAppDeletions.fulfilledItems.map((item) => item.Name).join(', ')
|
||
|
);
|
||
|
}
|
||
|
if (settledStackDeletions.fulfilledItems.length && reportStacks) {
|
||
|
notifySuccess(
|
||
|
`${pluralize(
|
||
|
settledStackDeletions.fulfilledItems.length,
|
||
|
'Stack'
|
||
|
)} successfully removed`,
|
||
|
settledStackDeletions.fulfilledItems
|
||
|
.map((item) => item.Name)
|
||
|
.join(', ')
|
||
|
);
|
||
|
}
|
||
|
queryClient.invalidateQueries(queryKeys.applications(environmentId));
|
||
|
},
|
||
|
...withGlobalError('Unable to remove applications'),
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async function deleteApplications(
|
||
|
applications: ApplicationRowData[],
|
||
|
stacks: Stack[],
|
||
|
environmentId: EnvironmentId
|
||
|
) {
|
||
|
const settledAppDeletions = await getAllSettledItems(
|
||
|
applications,
|
||
|
(application) => deleteApplication(application, stacks, environmentId)
|
||
|
);
|
||
|
|
||
|
// Delete stacks that have no applications left (stacks object has been mutated by deleteApplication)
|
||
|
const stacksToDelete = stacks.filter(
|
||
|
(stack) => stack.Applications.length === 0
|
||
|
);
|
||
|
const settledStackDeletions = await getAllSettledItems(
|
||
|
stacksToDelete,
|
||
|
(stack) => deleteStack(stack, environmentId)
|
||
|
);
|
||
|
|
||
|
return { settledAppDeletions, settledStackDeletions };
|
||
|
}
|
||
|
|
||
|
async function deleteStack(stack: Stack, environmentId: EnvironmentId) {
|
||
|
try {
|
||
|
await axios.delete(`/stacks/name/${stack.Name}`, {
|
||
|
params: {
|
||
|
external: false,
|
||
|
name: stack.Name,
|
||
|
endpointId: environmentId,
|
||
|
namespace: stack.ResourcePool,
|
||
|
},
|
||
|
});
|
||
|
} catch (error) {
|
||
|
throw parseAxiosError(error, 'Unable to remove stack');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function deleteApplication(
|
||
|
application: ApplicationRowData,
|
||
|
stacks: Stack[],
|
||
|
environmentId: EnvironmentId
|
||
|
) {
|
||
|
switch (application.ApplicationType) {
|
||
|
case 'Deployment':
|
||
|
case 'DaemonSet':
|
||
|
case 'StatefulSet':
|
||
|
await deleteKubernetesApplication(application, stacks, environmentId);
|
||
|
break;
|
||
|
case 'Pod':
|
||
|
await deletePodApplication(application, stacks, environmentId);
|
||
|
break;
|
||
|
case 'Helm':
|
||
|
await uninstallHelmApplication(application, environmentId);
|
||
|
break;
|
||
|
default:
|
||
|
throw new Error(
|
||
|
`Unknown application type: ${application.ApplicationType}`
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function deleteKubernetesApplication(
|
||
|
application: ApplicationRowData,
|
||
|
stacks: Stack[],
|
||
|
environmentId: EnvironmentId
|
||
|
) {
|
||
|
try {
|
||
|
await axios.delete(
|
||
|
`/endpoints/${environmentId}/kubernetes/apis/apps/v1/namespaces/${
|
||
|
application.ResourcePool
|
||
|
}/${application.ApplicationType.toLowerCase()}s/${application.Name}`
|
||
|
);
|
||
|
removeApplicationFromStack(application, stacks);
|
||
|
} catch (error) {
|
||
|
throw parseKubernetesAxiosError(error, 'Unable to remove application');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function deletePodApplication(
|
||
|
application: ApplicationRowData,
|
||
|
stacks: Stack[],
|
||
|
environmentId: EnvironmentId
|
||
|
) {
|
||
|
try {
|
||
|
await axios.delete(
|
||
|
`/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${application.ResourcePool}/pods/${application.Name}`
|
||
|
);
|
||
|
removeApplicationFromStack(application, stacks);
|
||
|
} catch (error) {
|
||
|
throw parseKubernetesAxiosError(error, 'Unable to remove application');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function uninstallHelmApplication(
|
||
|
application: ApplicationRowData,
|
||
|
environmentId: EnvironmentId
|
||
|
) {
|
||
|
try {
|
||
|
await axios.delete(
|
||
|
`/endpoints/${environmentId}/kubernetes/helm/${application.Name}`,
|
||
|
{ params: { namespace: application.ResourcePool } }
|
||
|
);
|
||
|
} catch (error) {
|
||
|
// parseAxiosError, because it's a regular portainer api error
|
||
|
throw parseAxiosError(error, 'Unable to remove application');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// mutate the stacks array to remove the application
|
||
|
function removeApplicationFromStack(
|
||
|
application: ApplicationRowData,
|
||
|
stacks: Stack[]
|
||
|
) {
|
||
|
const stack = stacks.find(
|
||
|
(stack) =>
|
||
|
stack.Name === application.StackName &&
|
||
|
stack.ResourcePool === application.ResourcePool
|
||
|
);
|
||
|
if (stack) {
|
||
|
stack.Applications = stack.Applications.filter(
|
||
|
(app) => app.Name !== application.Name
|
||
|
);
|
||
|
}
|
||
|
}
|