Skip to content

Commit 4a10a81

Browse files
adfostdvaldivia
andauthored
Delete Pod UI (#1381)
Co-authored-by: Daniel Valdivia <[email protected]>
1 parent 6404a1b commit 4a10a81

File tree

6 files changed

+188
-7
lines changed

6 files changed

+188
-7
lines changed

operatorapi/operator_tenants.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2050,9 +2050,13 @@ func getTenantPodsResponse(session *models.Principal, params operator_api.GetTen
20502050
if len(pod.Status.ContainerStatuses) > 0 {
20512051
restarts = int64(pod.Status.ContainerStatuses[0].RestartCount)
20522052
}
2053+
status := string(pod.Status.Phase)
2054+
if pod.DeletionTimestamp != nil {
2055+
status = "Terminating"
2056+
}
20532057
retval = append(retval, &models.TenantPod{
20542058
Name: swag.String(pod.Name),
2055-
Status: string(pod.Status.Phase),
2059+
Status: status,
20562060
TimeCreated: pod.CreationTimestamp.Unix(),
20572061
PodIP: pod.Status.PodIP,
20582062
Restarts: restarts,

operatorapi/operator_volumes.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,16 @@ func getPVCsResponse(session *models.Principal) (*models.ListPVCsResponse, *mode
9595
var ListPVCs []*models.PvcsListResponse
9696

9797
for _, pvc := range listAllPvcs.Items {
98+
status := string(pvc.Status.Phase)
99+
if pvc.DeletionTimestamp != nil {
100+
status = "Terminating"
101+
}
98102
pvcResponse := models.PvcsListResponse{
99103
Name: pvc.Name,
100104
Age: pvc.CreationTimestamp.String(),
101105
Capacity: pvc.Status.Capacity.Storage().String(),
102106
Namespace: pvc.Namespace,
103-
Status: string(pvc.Status.Phase),
107+
Status: status,
104108
StorageClass: *pvc.Spec.StorageClassName,
105109
Volume: pvc.Spec.VolumeName,
106110
Tenant: pvc.Labels["v1.min.io/tenant"],
@@ -138,12 +142,16 @@ func getPVCsForTenantResponse(session *models.Principal, params operator_api.Lis
138142
var ListPVCs []*models.PvcsListResponse
139143

140144
for _, pvc := range listAllPvcs.Items {
145+
status := string(pvc.Status.Phase)
146+
if pvc.DeletionTimestamp != nil {
147+
status = "Terminating"
148+
}
141149
pvcResponse := models.PvcsListResponse{
142150
Name: pvc.Name,
143151
Age: pvc.CreationTimestamp.String(),
144152
Capacity: pvc.Status.Capacity.Storage().String(),
145153
Namespace: pvc.Namespace,
146-
Status: string(pvc.Status.Phase),
154+
Status: status,
147155
StorageClass: *pvc.Spec.StorageClassName,
148156
Volume: pvc.Spec.VolumeName,
149157
Tenant: pvc.Labels["v1.min.io/tenant"],

portal-ui/src/screens/Console/Storage/StoragePVCs.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { ErrorResponseHandler } from "../../../common/types";
3333
import api from "../../../common/api";
3434
import TableWrapper from "../Common/TableWrapper/TableWrapper";
3535
import SearchIcon from "../../../icons/SearchIcon";
36+
import DeletePVC from "../Tenants/TenantDetails/DeletePVC";
3637

3738
interface IStorageVolumesProps {
3839
classes: any;
@@ -56,6 +57,8 @@ const StorageVolumes = ({
5657
const [records, setRecords] = useState<IStoragePVCs[]>([]);
5758
const [filter, setFilter] = useState("");
5859
const [loading, setLoading] = useState<boolean>(true);
60+
const [selectedPVC, setSelectedPVC] = useState<any>(null);
61+
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
5962

6063
useEffect(() => {
6164
if (loading) {
@@ -77,6 +80,16 @@ const StorageVolumes = ({
7780
elementItem.name.includes(filter)
7881
);
7982

83+
const confirmDeletePVC = (pvcItem: IStoragePVCs) => {
84+
const delPvc = {
85+
...pvcItem,
86+
tenant: pvcItem.tenant,
87+
namespace: pvcItem.namespace,
88+
};
89+
setSelectedPVC(delPvc);
90+
setDeleteOpen(true);
91+
};
92+
8093
const tableActions = [
8194
{
8295
type: "view",
@@ -86,10 +99,23 @@ const StorageVolumes = ({
8699
);
87100
},
88101
},
102+
{ type: "delete", onClick: confirmDeletePVC },
89103
];
90104

105+
const closeDeleteModalAndRefresh = (reloadData: boolean) => {
106+
setDeleteOpen(false);
107+
setLoading(true);
108+
};
109+
91110
return (
92111
<Fragment>
112+
{deleteOpen && (
113+
<DeletePVC
114+
deleteOpen={deleteOpen}
115+
selectedPVC={selectedPVC}
116+
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
117+
/>
118+
)}
93119
<h1 className={classes.sectionTitle}>Persistent Volumes Claims</h1>
94120
<Grid item xs={12} className={classes.actionsTray}>
95121
<TextField
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2022 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import React, { useState } from "react";
18+
import { DialogContentText } from "@mui/material";
19+
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
20+
import Grid from "@mui/material/Grid";
21+
import { connect } from "react-redux";
22+
import { setErrorSnackMessage } from "../../../../actions";
23+
import { ErrorResponseHandler } from "../../../../common/types";
24+
import useApi from "../../Common/Hooks/useApi";
25+
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
26+
import { ConfirmDeleteIcon } from "../../../../icons";
27+
import { IStoragePVCs } from "../../Storage/types";
28+
29+
interface IDeletePVC {
30+
deleteOpen: boolean;
31+
selectedPVC: IStoragePVCs;
32+
closeDeleteModalAndRefresh: (refreshList: boolean) => any;
33+
setErrorSnackMessage: typeof setErrorSnackMessage;
34+
}
35+
36+
const DeletePVC = ({
37+
deleteOpen,
38+
selectedPVC,
39+
closeDeleteModalAndRefresh,
40+
setErrorSnackMessage,
41+
}: IDeletePVC) => {
42+
const [retypePVC, setRetypePVC] = useState("");
43+
44+
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
45+
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
46+
const onClose = () => closeDeleteModalAndRefresh(false);
47+
48+
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
49+
50+
const onConfirmDelete = () => {
51+
if (retypePVC !== selectedPVC.name) {
52+
setErrorSnackMessage({
53+
errorMessage: "PVC name is incorrect",
54+
detailedError: "",
55+
});
56+
return;
57+
}
58+
invokeDeleteApi(
59+
"DELETE",
60+
`/api/v1/namespaces/${selectedPVC.namespace}/tenants/${selectedPVC.tenant}/pvc/${selectedPVC.name}`
61+
);
62+
};
63+
64+
return (
65+
<ConfirmDialog
66+
title={`Delete PVC`}
67+
confirmText={"Delete"}
68+
isOpen={deleteOpen}
69+
titleIcon={<ConfirmDeleteIcon />}
70+
isLoading={deleteLoading}
71+
onConfirm={onConfirmDelete}
72+
onClose={onClose}
73+
confirmButtonProps={{
74+
disabled: retypePVC !== selectedPVC.name || deleteLoading,
75+
}}
76+
confirmationContent={
77+
<DialogContentText>
78+
To continue please type <b>{selectedPVC.name}</b> in the box.
79+
<Grid item xs={12}>
80+
<InputBoxWrapper
81+
id="retype-PVC"
82+
name="retype-PVC"
83+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
84+
setRetypePVC(event.target.value);
85+
}}
86+
label=""
87+
value={retypePVC}
88+
/>
89+
</Grid>
90+
</DialogContentText>
91+
}
92+
/>
93+
);
94+
};
95+
96+
const connector = connect(null, {
97+
setErrorSnackMessage,
98+
});
99+
100+
export default connector(DeletePVC);

portal-ui/src/screens/Console/Tenants/TenantDetails/PodsSummary.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const PodsSummary = ({
7373

7474
const closeDeleteModalAndRefresh = (reloadData: boolean) => {
7575
setDeleteOpen(false);
76+
setLoadingPods(true);
7677
};
7778

7879
const confirmDeletePod = (pod: IPodListElement) => {

portal-ui/src/screens/Console/Tenants/TenantDetails/VolumesSummary.tsx

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,24 @@ import {
2727
searchField,
2828
tableStyles,
2929
} from "../../Common/FormComponents/common/styleLibrary";
30-
import { IPVCsResponse, IStoragePVCs } from "../../Storage/types";
30+
import { IStoragePVCs } from "../../Storage/types";
3131
import { setErrorSnackMessage } from "../../../../actions";
3232
import { ErrorResponseHandler } from "../../../../common/types";
3333
import api from "../../../../common/api";
3434
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
3535
import SearchIcon from "../../../../icons/SearchIcon";
36+
import withSuspense from "../../Common/Components/withSuspense";
37+
import { setTenantDetailsLoad } from "../actions";
38+
import { AppState } from "../../../../store";
39+
40+
const DeletePVC = withSuspense(React.lazy(() => import("./DeletePVC")));
3641

3742
interface ITenantVolumesProps {
3843
classes: any;
3944
setErrorSnackMessage: typeof setErrorSnackMessage;
4045
match: any;
46+
loadingTenant: boolean;
47+
setTenantDetailsLoad: typeof setTenantDetailsLoad;
4148
}
4249

4350
const styles = (theme: Theme) =>
@@ -55,10 +62,13 @@ const TenantVolumes = ({
5562
classes,
5663
setErrorSnackMessage,
5764
match,
65+
loadingTenant,
5866
}: ITenantVolumesProps) => {
5967
const [records, setRecords] = useState<IStoragePVCs[]>([]);
6068
const [filter, setFilter] = useState("");
6169
const [loading, setLoading] = useState<boolean>(true);
70+
const [selectedPVC, setSelectedPVC] = useState<any>(null);
71+
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
6272

6373
const tenantName = match.params["tenantName"];
6474
const tenantNamespace = match.params["tenantNamespace"];
@@ -70,7 +80,7 @@ const TenantVolumes = ({
7080
"GET",
7181
`/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/pvcs`
7282
)
73-
.then((res: IPVCsResponse) => {
83+
.then((res: IStoragePVCs) => {
7484
let volumes = get(res, "pvcs", []);
7585
setRecords(volumes ? volumes : []);
7686
setLoading(false);
@@ -82,12 +92,40 @@ const TenantVolumes = ({
8292
}
8393
}, [loading, setErrorSnackMessage, tenantName, tenantNamespace]);
8494

95+
const confirmDeletePVC = (pvcItem: IStoragePVCs) => {
96+
const delPvc = {
97+
...pvcItem,
98+
tenant: tenantName,
99+
namespace: tenantNamespace,
100+
};
101+
setSelectedPVC(delPvc);
102+
setDeleteOpen(true);
103+
};
104+
85105
const filteredRecords: IStoragePVCs[] = records.filter((elementItem) =>
86106
elementItem.name.includes(filter)
87107
);
88108

109+
const closeDeleteModalAndRefresh = (reloadData: boolean) => {
110+
setDeleteOpen(false);
111+
setLoading(true);
112+
};
113+
114+
useEffect(() => {
115+
if (loadingTenant) {
116+
setLoading(true);
117+
}
118+
}, [loadingTenant]);
119+
89120
return (
90121
<Fragment>
122+
{deleteOpen && (
123+
<DeletePVC
124+
deleteOpen={deleteOpen}
125+
selectedPVC={selectedPVC}
126+
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
127+
/>
128+
)}
91129
<h1 className={classes.sectionTitle}>Volumes</h1>
92130
<Grid item xs={12} className={classes.actionsTray}>
93131
<TextField
@@ -114,7 +152,7 @@ const TenantVolumes = ({
114152
</Grid>
115153
<Grid item xs={12} className={classes.tableBlock}>
116154
<TableWrapper
117-
itemActions={[]}
155+
itemActions={[{ type: "delete", onClick: confirmDeletePVC }]}
118156
columns={[
119157
{
120158
label: "Name",
@@ -146,10 +184,14 @@ const TenantVolumes = ({
146184
);
147185
};
148186

187+
const mapState = (state: AppState) => ({
188+
loadingTenant: state.tenants.tenantDetails.loadingTenant,
189+
});
190+
149191
const mapDispatchToProps = {
150192
setErrorSnackMessage,
151193
};
152194

153-
const connector = connect(null, mapDispatchToProps);
195+
const connector = connect(mapState, mapDispatchToProps);
154196

155197
export default withStyles(styles)(connector(TenantVolumes));

0 commit comments

Comments
 (0)