Skip to content

Commit

Permalink
feat(instances) add bulk deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
edlerd committed May 15, 2023
1 parent 5d8af71 commit ea6ccb4
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 7 deletions.
14 changes: 13 additions & 1 deletion src/api/instances.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,20 @@ export const deleteInstance = (instance: LxdInstance) => {
})
.then(handleResponse)
.then((data: LxdOperation) => {
watchOperation(data.operation).then(resolve).catch(reject);
watchOperation(data.operation, TIMEOUT_120).then(resolve).catch(reject);
})
.catch(reject);
});
};

export const deleteInstanceBulk = (instances: LxdInstance[]) => {
return new Promise((resolve, reject) => {
Promise.all(
instances.map(async (instance) => {
await deleteInstance(instance);
})
)
.then(resolve)
.catch(reject);
});
};
Expand Down
4 changes: 3 additions & 1 deletion src/components/ConfirmationButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ const ConfirmationButton: FC<Props> = ({
name={iconName}
/>
)}
{toggleCaption && <span>{toggleCaption}</span>}
{toggleCaption && (
<span className="confirmation-toggle-caption">{toggleCaption}</span>
)}
</Button>
</>
);
Expand Down
14 changes: 11 additions & 3 deletions src/pages/instances/InstanceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { updateTBodyHeight } from "util/updateTBodyHeight";
import SelectableMainTable from "components/SelectableMainTable";
import InstanceBulkActions from "pages/instances/actions/InstanceBulkActions";
import { getIpAddresses } from "util/networks";
import InstanceBulkDelete from "pages/instances/actions/InstanceBulkDelete";

const STATUS = "Status";
const NAME = "Name";
Expand Down Expand Up @@ -316,6 +317,9 @@ const InstanceList: FC = () => {
]);

const hasInstances = isLoading || instances.length > 0;
const selectedInstances = instances.filter((instance) =>
selectedNames.includes(instance.name)
);

return (
<main className="l-main instance-list">
Expand All @@ -330,14 +334,18 @@ const InstanceList: FC = () => {
{selectedNames.length > 0 && (
<>
<InstanceBulkActions
instances={instances.filter((instance) =>
selectedNames.includes(instance.name)
)}
instances={selectedInstances}
onStart={() => setProcessingNames(selectedNames)}
onFinish={() => setProcessingNames([])}
/>
<InstanceBulkDelete
instances={selectedInstances}
onStart={setProcessingNames}
onFinish={() => setProcessingNames([])}
/>
<Button
appearance="link"
className="clear-selection-btn"
hasIcon
onClick={() => setSelectedNames([])}
>
Expand Down
3 changes: 2 additions & 1 deletion src/pages/instances/actions/DeleteInstanceBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ConfirmationButton from "components/ConfirmationButton";
import { useNavigate } from "react-router-dom";
import { useNotify } from "context/notify";
import ItemName from "components/ItemName";
import { deletableStatuses } from "util/instanceDelete";

interface Props {
instance: LxdInstance;
Expand Down Expand Up @@ -36,7 +37,7 @@ const DeleteInstanceBtn: FC<Props> = ({ instance }) => {
.finally(() => setLoading(false));
};

const isDisabled = isLoading || instance.status !== "Stopped";
const isDisabled = isLoading || !deletableStatuses.includes(instance.status);

return (
<ConfirmationButton
Expand Down
94 changes: 94 additions & 0 deletions src/pages/instances/actions/InstanceBulkDelete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { FC, useState } from "react";
import { deleteInstanceBulk } from "api/instances";
import { LxdInstance } from "types/instance";
import ConfirmationButton from "components/ConfirmationButton";
import { useNotify } from "context/notify";
import { pluralizeInstance } from "util/instanceBulkActions";
import { queryKeys } from "util/queryKeys";
import { useQueryClient } from "@tanstack/react-query";
import { deletableStatuses } from "util/instanceDelete";

interface Props {
instances: LxdInstance[];
onStart: (names: string[]) => void;
onFinish: () => void;
}

const InstanceBulkDelete: FC<Props> = ({ instances, onStart, onFinish }) => {
const notify = useNotify();
const queryClient = useQueryClient();
const [isLoading, setLoading] = useState(false);

const deletableInstances = instances.filter((instance) =>
deletableStatuses.includes(instance.status)
);
const totalCount = instances.length;
const deleteCount = deletableInstances.length;
const ignoredCount = totalCount - deleteCount;

const handleDelete = () => {
setLoading(true);
onStart(deletableInstances.map((item) => item.name));
deleteInstanceBulk(deletableInstances)
.then(() => {
notify.success(
`${deleteCount} ${pluralizeInstance(deleteCount)} deleted`
);
})
.catch((e) => {
notify.failure("Instance bulk deletion failed", e);
})
.finally(() => {
void queryClient.invalidateQueries({
queryKey: [queryKeys.instances],
});
setLoading(false);
onFinish();
});
};

return (
<div className="p-segmented-control bulk-actions">
<div className="p-segmented-control__list bulk-action-frame">
<ConfirmationButton
onHoverText="Delete instances"
toggleAppearance="base"
className="u-no-margin--bottom"
isLoading={isLoading}
icon="delete"
toggleCaption="Delete"
title="Confirm delete"
confirmMessage={
<>
{ignoredCount > 0 && (
<>
<b>{totalCount}</b> instances selected:
<br />
<br />
{`- ${deleteCount} stopped ${pluralizeInstance(
deleteCount
)} will be deleted`}
<br />
{`- ${ignoredCount} other ${pluralizeInstance(
ignoredCount
)} will be ignored`}
<br />
<br />
</>
)}
Are you sure you want to delete <b>{deleteCount}</b>{" "}
{pluralizeInstance(deleteCount)}?{"\n"}This action cannot be
undone, and can result in data loss.
</>
}
confirmButtonLabel="Delete"
onConfirm={handleDelete}
isDisabled={deleteCount === 0}
isDense={false}
/>
</div>
</div>
);
};

export default InstanceBulkDelete;
24 changes: 23 additions & 1 deletion src/sass/_instance_list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

.bulk-actions {
display: inline-block;
margin-right: $sph--x-large;
margin-right: $sph--large;

> .bulk-action-frame {
border: 1px solid #0000008f;
Expand All @@ -17,6 +17,28 @@
}
}

@media screen and (max-width: $breakpoint-large) {
.bulk-actions {
.confirmation-toggle-caption {
display: none;
}

.has-icon > i {
margin: 0;
}
}
}

@media screen and (max-width: 830px) {
.clear-selection-btn {
display: none;
}
}

.clear-selection-btn {
padding-top: 8px;
}

.instance-header-left {
display: flex;
flex-grow: 1;
Expand Down
1 change: 1 addition & 0 deletions src/types/instance.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type LxdInstanceAction =
| "unfreeze";

export type LxdInstanceStatus =
| "Error"
| "Freezing"
| "Frozen"
| "Restarting"
Expand Down
3 changes: 3 additions & 0 deletions src/util/instanceDelete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { LxdInstanceStatus } from "types/instance";

export const deletableStatuses: LxdInstanceStatus[] = ["Error", "Stopped"];

0 comments on commit ea6ccb4

Please sign in to comment.