From 4a10a813745a8cb87cc729f3c66a41e7af6c9a82 Mon Sep 17 00:00:00 2001 From: adfost Date: Tue, 25 Jan 2022 10:33:51 -0800 Subject: [PATCH] Delete Pod UI (#1381) Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> --- operatorapi/operator_tenants.go | 6 +- operatorapi/operator_volumes.go | 12 ++- .../screens/Console/Storage/StoragePVCs.tsx | 26 +++++ .../Tenants/TenantDetails/DeletePVC.tsx | 100 ++++++++++++++++++ .../Tenants/TenantDetails/PodsSummary.tsx | 1 + .../Tenants/TenantDetails/VolumesSummary.tsx | 50 ++++++++- 6 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 portal-ui/src/screens/Console/Tenants/TenantDetails/DeletePVC.tsx diff --git a/operatorapi/operator_tenants.go b/operatorapi/operator_tenants.go index 6854711bf..08020399e 100644 --- a/operatorapi/operator_tenants.go +++ b/operatorapi/operator_tenants.go @@ -2050,9 +2050,13 @@ func getTenantPodsResponse(session *models.Principal, params operator_api.GetTen if len(pod.Status.ContainerStatuses) > 0 { restarts = int64(pod.Status.ContainerStatuses[0].RestartCount) } + status := string(pod.Status.Phase) + if pod.DeletionTimestamp != nil { + status = "Terminating" + } retval = append(retval, &models.TenantPod{ Name: swag.String(pod.Name), - Status: string(pod.Status.Phase), + Status: status, TimeCreated: pod.CreationTimestamp.Unix(), PodIP: pod.Status.PodIP, Restarts: restarts, diff --git a/operatorapi/operator_volumes.go b/operatorapi/operator_volumes.go index d67e6e6b8..56f80cd83 100644 --- a/operatorapi/operator_volumes.go +++ b/operatorapi/operator_volumes.go @@ -95,12 +95,16 @@ func getPVCsResponse(session *models.Principal) (*models.ListPVCsResponse, *mode var ListPVCs []*models.PvcsListResponse for _, pvc := range listAllPvcs.Items { + status := string(pvc.Status.Phase) + if pvc.DeletionTimestamp != nil { + status = "Terminating" + } pvcResponse := models.PvcsListResponse{ Name: pvc.Name, Age: pvc.CreationTimestamp.String(), Capacity: pvc.Status.Capacity.Storage().String(), Namespace: pvc.Namespace, - Status: string(pvc.Status.Phase), + Status: status, StorageClass: *pvc.Spec.StorageClassName, Volume: pvc.Spec.VolumeName, Tenant: pvc.Labels["v1.min.io/tenant"], @@ -138,12 +142,16 @@ func getPVCsForTenantResponse(session *models.Principal, params operator_api.Lis var ListPVCs []*models.PvcsListResponse for _, pvc := range listAllPvcs.Items { + status := string(pvc.Status.Phase) + if pvc.DeletionTimestamp != nil { + status = "Terminating" + } pvcResponse := models.PvcsListResponse{ Name: pvc.Name, Age: pvc.CreationTimestamp.String(), Capacity: pvc.Status.Capacity.Storage().String(), Namespace: pvc.Namespace, - Status: string(pvc.Status.Phase), + Status: status, StorageClass: *pvc.Spec.StorageClassName, Volume: pvc.Spec.VolumeName, Tenant: pvc.Labels["v1.min.io/tenant"], diff --git a/portal-ui/src/screens/Console/Storage/StoragePVCs.tsx b/portal-ui/src/screens/Console/Storage/StoragePVCs.tsx index 60a938502..d1fd14a3f 100644 --- a/portal-ui/src/screens/Console/Storage/StoragePVCs.tsx +++ b/portal-ui/src/screens/Console/Storage/StoragePVCs.tsx @@ -33,6 +33,7 @@ import { ErrorResponseHandler } from "../../../common/types"; import api from "../../../common/api"; import TableWrapper from "../Common/TableWrapper/TableWrapper"; import SearchIcon from "../../../icons/SearchIcon"; +import DeletePVC from "../Tenants/TenantDetails/DeletePVC"; interface IStorageVolumesProps { classes: any; @@ -56,6 +57,8 @@ const StorageVolumes = ({ const [records, setRecords] = useState([]); const [filter, setFilter] = useState(""); const [loading, setLoading] = useState(true); + const [selectedPVC, setSelectedPVC] = useState(null); + const [deleteOpen, setDeleteOpen] = useState(false); useEffect(() => { if (loading) { @@ -77,6 +80,16 @@ const StorageVolumes = ({ elementItem.name.includes(filter) ); + const confirmDeletePVC = (pvcItem: IStoragePVCs) => { + const delPvc = { + ...pvcItem, + tenant: pvcItem.tenant, + namespace: pvcItem.namespace, + }; + setSelectedPVC(delPvc); + setDeleteOpen(true); + }; + const tableActions = [ { type: "view", @@ -86,10 +99,23 @@ const StorageVolumes = ({ ); }, }, + { type: "delete", onClick: confirmDeletePVC }, ]; + const closeDeleteModalAndRefresh = (reloadData: boolean) => { + setDeleteOpen(false); + setLoading(true); + }; + return ( + {deleteOpen && ( + + )}

Persistent Volumes Claims

. + +import React, { useState } from "react"; +import { DialogContentText } from "@mui/material"; +import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import Grid from "@mui/material/Grid"; +import { connect } from "react-redux"; +import { setErrorSnackMessage } from "../../../../actions"; +import { ErrorResponseHandler } from "../../../../common/types"; +import useApi from "../../Common/Hooks/useApi"; +import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog"; +import { ConfirmDeleteIcon } from "../../../../icons"; +import { IStoragePVCs } from "../../Storage/types"; + +interface IDeletePVC { + deleteOpen: boolean; + selectedPVC: IStoragePVCs; + closeDeleteModalAndRefresh: (refreshList: boolean) => any; + setErrorSnackMessage: typeof setErrorSnackMessage; +} + +const DeletePVC = ({ + deleteOpen, + selectedPVC, + closeDeleteModalAndRefresh, + setErrorSnackMessage, +}: IDeletePVC) => { + const [retypePVC, setRetypePVC] = useState(""); + + const onDelSuccess = () => closeDeleteModalAndRefresh(true); + const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err); + const onClose = () => closeDeleteModalAndRefresh(false); + + const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError); + + const onConfirmDelete = () => { + if (retypePVC !== selectedPVC.name) { + setErrorSnackMessage({ + errorMessage: "PVC name is incorrect", + detailedError: "", + }); + return; + } + invokeDeleteApi( + "DELETE", + `/api/v1/namespaces/${selectedPVC.namespace}/tenants/${selectedPVC.tenant}/pvc/${selectedPVC.name}` + ); + }; + + return ( + } + isLoading={deleteLoading} + onConfirm={onConfirmDelete} + onClose={onClose} + confirmButtonProps={{ + disabled: retypePVC !== selectedPVC.name || deleteLoading, + }} + confirmationContent={ + + To continue please type {selectedPVC.name} in the box. + + ) => { + setRetypePVC(event.target.value); + }} + label="" + value={retypePVC} + /> + + + } + /> + ); +}; + +const connector = connect(null, { + setErrorSnackMessage, +}); + +export default connector(DeletePVC); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/PodsSummary.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/PodsSummary.tsx index affc694ae..670cab89c 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/PodsSummary.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/PodsSummary.tsx @@ -73,6 +73,7 @@ const PodsSummary = ({ const closeDeleteModalAndRefresh = (reloadData: boolean) => { setDeleteOpen(false); + setLoadingPods(true); }; const confirmDeletePod = (pod: IPodListElement) => { diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/VolumesSummary.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/VolumesSummary.tsx index 3c117c397..e8bb05543 100644 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/VolumesSummary.tsx +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/VolumesSummary.tsx @@ -27,17 +27,24 @@ import { searchField, tableStyles, } from "../../Common/FormComponents/common/styleLibrary"; -import { IPVCsResponse, IStoragePVCs } from "../../Storage/types"; +import { IStoragePVCs } from "../../Storage/types"; import { setErrorSnackMessage } from "../../../../actions"; import { ErrorResponseHandler } from "../../../../common/types"; import api from "../../../../common/api"; import TableWrapper from "../../Common/TableWrapper/TableWrapper"; import SearchIcon from "../../../../icons/SearchIcon"; +import withSuspense from "../../Common/Components/withSuspense"; +import { setTenantDetailsLoad } from "../actions"; +import { AppState } from "../../../../store"; + +const DeletePVC = withSuspense(React.lazy(() => import("./DeletePVC"))); interface ITenantVolumesProps { classes: any; setErrorSnackMessage: typeof setErrorSnackMessage; match: any; + loadingTenant: boolean; + setTenantDetailsLoad: typeof setTenantDetailsLoad; } const styles = (theme: Theme) => @@ -55,10 +62,13 @@ const TenantVolumes = ({ classes, setErrorSnackMessage, match, + loadingTenant, }: ITenantVolumesProps) => { const [records, setRecords] = useState([]); const [filter, setFilter] = useState(""); const [loading, setLoading] = useState(true); + const [selectedPVC, setSelectedPVC] = useState(null); + const [deleteOpen, setDeleteOpen] = useState(false); const tenantName = match.params["tenantName"]; const tenantNamespace = match.params["tenantNamespace"]; @@ -70,7 +80,7 @@ const TenantVolumes = ({ "GET", `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/pvcs` ) - .then((res: IPVCsResponse) => { + .then((res: IStoragePVCs) => { let volumes = get(res, "pvcs", []); setRecords(volumes ? volumes : []); setLoading(false); @@ -82,12 +92,40 @@ const TenantVolumes = ({ } }, [loading, setErrorSnackMessage, tenantName, tenantNamespace]); + const confirmDeletePVC = (pvcItem: IStoragePVCs) => { + const delPvc = { + ...pvcItem, + tenant: tenantName, + namespace: tenantNamespace, + }; + setSelectedPVC(delPvc); + setDeleteOpen(true); + }; + const filteredRecords: IStoragePVCs[] = records.filter((elementItem) => elementItem.name.includes(filter) ); + const closeDeleteModalAndRefresh = (reloadData: boolean) => { + setDeleteOpen(false); + setLoading(true); + }; + + useEffect(() => { + if (loadingTenant) { + setLoading(true); + } + }, [loadingTenant]); + return ( + {deleteOpen && ( + + )}

Volumes

({ + loadingTenant: state.tenants.tenantDetails.loadingTenant, +}); + const mapDispatchToProps = { setErrorSnackMessage, }; -const connector = connect(null, mapDispatchToProps); +const connector = connect(mapState, mapDispatchToProps); export default withStyles(styles)(connector(TenantVolumes));