Skip to content

Commit e5d2752

Browse files
authored
Added multidelete function for Service Accounts (#1501)
Added multidelete function for Service Accounts
1 parent cffaee8 commit e5d2752

12 files changed

+725
-33
lines changed

portal-ui/src/screens/Console/Account/Account.tsx

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import Grid from "@mui/material/Grid";
2323
import api from "../../../common/api";
2424
import { Box } from "@mui/material";
2525
import { NewServiceAccount } from "../Common/CredentialsPrompt/types";
26-
import { setErrorSnackMessage } from "../../../actions";
27-
import { AccountIcon, AddIcon, PasswordKeyIcon } from "../../../icons";
26+
import { setErrorSnackMessage, setSnackBarMessage } from "../../../actions";
27+
import { AccountIcon, AddIcon, PasswordKeyIcon, DeleteIcon } from "../../../icons";
2828
import TableWrapper from "../Common/TableWrapper/TableWrapper";
2929
import { stringSort } from "../../../utils/sortFunctions";
3030
import PageHeader from "../Common/PageHeader/PageHeader";
@@ -46,6 +46,8 @@ import {
4646
} from "../../../common/SecureComponent/permissions";
4747
import SecureComponent from "../../../common/SecureComponent/SecureComponent";
4848
import RBIconButton from "../Buckets/BucketDetails/SummaryItems/RBIconButton";
49+
import {selectSAs} from "../../Console/Configurations/utils"
50+
import DeleteMultipleServiceAccounts from "../Users/DeleteMultipleServiceAccounts"
4951

5052
const AddServiceAccount = withSuspense(
5153
React.lazy(() => import("./AddServiceAccount"))
@@ -89,6 +91,8 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => {
8991
useState<NewServiceAccount | null>(null);
9092
const [changePasswordModalOpen, setChangePasswordModalOpen] =
9193
useState<boolean>(false);
94+
const [selectedSAs, setSelectedSAs] = useState<string[]>([]);
95+
const [deleteMultipleOpen, setDeleteMultipleOpen] = useState<boolean>(false);
9296

9397
useEffect(() => {
9498
fetchRecords();
@@ -139,6 +143,24 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => {
139143
}
140144
};
141145

146+
const closeDeleteMultipleModalAndRefresh = (refresh: boolean) => {
147+
setDeleteMultipleOpen(false);
148+
if (refresh) {
149+
setSnackBarMessage(`Service accounts deleted successfully.`);
150+
setSelectedSAs([]);
151+
setLoading(true);
152+
}
153+
};
154+
155+
156+
const selectAllItems = () => {
157+
if (selectedSAs.length === records.length) {
158+
setSelectedSAs([]);
159+
return;
160+
}
161+
setSelectedSAs(records);
162+
};
163+
142164
const closeCredentialsModal = () => {
143165
setShowNewCredentials(false);
144166
setNewServiceAccount(null);
@@ -176,6 +198,13 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => {
176198
}}
177199
/>
178200
)}
201+
{deleteMultipleOpen && (
202+
<DeleteMultipleServiceAccounts
203+
deleteOpen={deleteMultipleOpen}
204+
selectedSAs={selectedSAs}
205+
closeDeleteModalAndRefresh={closeDeleteMultipleModalAndRefresh}
206+
/>
207+
)}
179208
{showNewCredentials && (
180209
<CredentialsPrompt
181210
newServiceAccount={newServiceAccount}
@@ -204,7 +233,15 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => {
204233
sx={{
205234
display: "flex",
206235
}}
207-
>
236+
> <RBIconButton
237+
tooltip={"Delete Selected"}
238+
onClick={() => {setDeleteMultipleOpen(true);}}
239+
text={"Delete Selected"}
240+
icon={<DeleteIcon />}
241+
color="secondary"
242+
disabled={selectedSAs.length === 0}
243+
variant={"outlined"}
244+
/>
208245
<SecureComponent
209246
scopes={[IAM_SCOPES.ADMIN_CREATE_USER]}
210247
resource={CONSOLE_UI_RESOURCE}
@@ -218,8 +255,9 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => {
218255
color={"primary"}
219256
variant={"outlined"}
220257
/>
221-
</SecureComponent>
222-
258+
</SecureComponent>
259+
260+
223261
<RBIconButton
224262
onClick={() => {
225263
setAddScreenOpen(true);
@@ -241,6 +279,9 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => {
241279
idField={""}
242280
columns={[{ label: "Service Account", elementKey: "" }]}
243281
itemActions={tableActions}
282+
selectedItems={selectedSAs}
283+
onSelect={e => selectSAs(e, setSelectedSAs, selectedSAs)}
284+
onSelectAll={selectAllItems}
244285
/>
245286
</Grid>
246287
<Grid item xs={12} marginTop={"15px"}>

portal-ui/src/screens/Console/Configurations/utils.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,3 +522,20 @@ export const removeEmptyFields = (formFields: IElementValue[]) => {
522522

523523
return nonEmptyFields;
524524
};
525+
526+
export const selectSAs = (e: React.ChangeEvent<HTMLInputElement>, setSelectedSAs : Function, selectedSAs : string[]) => {
527+
const targetD = e.target;
528+
const value = targetD.value;
529+
const checked = targetD.checked;
530+
531+
let elements: string[] = [...selectedSAs]; // We clone the selectedSAs array
532+
if (checked) {
533+
// If the user has checked this field we need to push this to selectedSAs
534+
elements.push(value);
535+
} else {
536+
// User has unchecked this field, we need to remove it from the list
537+
elements = elements.filter((element) => element !== value);
538+
}
539+
setSelectedSAs(elements);
540+
return elements;
541+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
import React from "react";
17+
import { connect } from "react-redux";
18+
import { DialogContentText } from "@mui/material";
19+
import { setErrorSnackMessage } from "../../../actions";
20+
import { ErrorResponseHandler } from "../../../common/types"
21+
import useApi from "../../../screens/Console/Common/Hooks/useApi";
22+
import ConfirmDialog from "../../../screens/Console/Common/ModalWrapper/ConfirmDialog";
23+
import { ConfirmDeleteIcon } from "../../../icons";
24+
interface IDeleteMultiSAsProps {
25+
closeDeleteModalAndRefresh: (refresh: boolean) => void;
26+
deleteOpen: boolean;
27+
selectedSAs: string[];
28+
setErrorSnackMessage: typeof setErrorSnackMessage;
29+
}
30+
const DeleteMultipleSAs = ({
31+
closeDeleteModalAndRefresh,
32+
deleteOpen,
33+
selectedSAs,
34+
setErrorSnackMessage,
35+
}: IDeleteMultiSAsProps) => {
36+
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
37+
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
38+
const onClose = () => closeDeleteModalAndRefresh(false);
39+
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
40+
if (!selectedSAs) {
41+
return null;
42+
}
43+
const onConfirmDelete = () => {
44+
invokeDeleteApi(
45+
"POST",
46+
`/api/v1/service-accounts/delete-multi`,
47+
selectedSAs
48+
);
49+
};
50+
return (
51+
<ConfirmDialog
52+
title={`Delete Service Accounts`}
53+
confirmText={"Delete"}
54+
isOpen={deleteOpen}
55+
titleIcon={<ConfirmDeleteIcon />}
56+
isLoading={deleteLoading}
57+
onConfirm={onConfirmDelete}
58+
onClose={onClose}
59+
confirmationContent={
60+
<DialogContentText>
61+
Are you sure you want to delete the selected {selectedSAs.length}{" "}
62+
service accounts?{" "}
63+
</DialogContentText>
64+
}
65+
/>
66+
);
67+
};
68+
const mapDispatchToProps = {
69+
setErrorSnackMessage,
70+
};
71+
const connector = connect(null, mapDispatchToProps);
72+
73+
export default connector(DeleteMultipleSAs);

portal-ui/src/screens/Console/Users/UserServiceAccountsPanel.tsx

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import React, { useEffect, useState } from "react";
1818
import { connect } from "react-redux";
1919
import { Theme } from "@mui/material/styles";
20+
import { Box } from "@mui/material";
2021
import createStyles from "@mui/styles/createStyles";
2122
import withStyles from "@mui/styles/withStyles";
2223
import {
@@ -27,16 +28,18 @@ import {
2728
import api from "../../../common/api";
2829
import TableWrapper from "../Common/TableWrapper/TableWrapper";
2930
import { AppState } from "../../../store";
30-
import { setErrorSnackMessage } from "../../../actions";
31+
import { setErrorSnackMessage, setSnackBarMessage } from "../../../actions";
3132
import { NewServiceAccount } from "../Common/CredentialsPrompt/types";
3233
import { stringSort } from "../../../utils/sortFunctions";
3334
import { ErrorResponseHandler } from "../../../common/types";
3435
import AddUserServiceAccount from "./AddUserServiceAccount";
3536
import DeleteServiceAccount from "../Account/DeleteServiceAccount";
3637
import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt";
37-
import { AddIcon } from "../../../icons";
38+
import { AddIcon, DeleteIcon } from "../../../icons";
3839
import PanelTitle from "../Common/PanelTitle/PanelTitle";
3940
import RBIconButton from "../Buckets/BucketDetails/SummaryItems/RBIconButton";
41+
import DeleteMultipleServiceAccounts from "./DeleteMultipleServiceAccounts"
42+
import {selectSAs} from "../../Console/Configurations/utils"
4043

4144
interface IUserServiceAccountsProps {
4245
classes: any;
@@ -71,6 +74,8 @@ const UserServiceAccountsPanel = ({
7174
const [showNewCredentials, setShowNewCredentials] = useState<boolean>(false);
7275
const [newServiceAccount, setNewServiceAccount] =
7376
useState<NewServiceAccount | null>(null);
77+
const [selectedSAs, setSelectedSAs] = useState<string[]>([]);
78+
const [deleteMultipleOpen, setDeleteMultipleOpen] = useState<boolean>(false);
7479

7580
useEffect(() => {
7681
fetchRecords();
@@ -120,6 +125,24 @@ const UserServiceAccountsPanel = ({
120125
}
121126
};
122127

128+
const closeDeleteMultipleModalAndRefresh = (refresh: boolean) => {
129+
setDeleteMultipleOpen(false);
130+
if (refresh) {
131+
setSnackBarMessage(`Service accounts deleted successfully.`);
132+
setSelectedSAs([]);
133+
setLoading(true);
134+
}
135+
};
136+
137+
138+
const selectAllItems = () => {
139+
if (selectedSAs.length === records.length) {
140+
setSelectedSAs([]);
141+
return;
142+
}
143+
setSelectedSAs(records);
144+
};
145+
123146
const closeCredentialsModal = () => {
124147
setShowNewCredentials(false);
125148
setNewServiceAccount(null);
@@ -154,6 +177,13 @@ const UserServiceAccountsPanel = ({
154177
}}
155178
/>
156179
)}
180+
{deleteMultipleOpen && (
181+
<DeleteMultipleServiceAccounts
182+
deleteOpen={deleteMultipleOpen}
183+
selectedSAs={selectedSAs}
184+
closeDeleteModalAndRefresh={closeDeleteMultipleModalAndRefresh}
185+
/>
186+
)}
157187
{showNewCredentials && (
158188
<CredentialsPrompt
159189
newServiceAccount={newServiceAccount}
@@ -165,30 +195,43 @@ const UserServiceAccountsPanel = ({
165195
/>
166196
)}
167197
<div className={classes.actionsTray}>
168-
<PanelTitle>Service Accounts</PanelTitle>
169-
170-
<RBIconButton
171-
tooltip={"Create service account"}
172-
text={"Create service account"}
173-
variant="contained"
174-
color="primary"
175-
icon={<AddIcon />}
176-
onClick={() => {
177-
setAddScreenOpen(true);
178-
setAddScreenOpen(true);
179-
setSelectedServiceAccount(null);
180-
}}
181-
disabled={!hasPolicy}
182-
/>
198+
<PanelTitle>Service Accounts</PanelTitle>
199+
<Box >
200+
<RBIconButton
201+
tooltip={"Delete Selected"}
202+
onClick={() => {setDeleteMultipleOpen(true);}}
203+
text={"Delete Selected"}
204+
icon={<DeleteIcon />}
205+
color="secondary"
206+
disabled={selectedSAs.length === 0}
207+
variant={"outlined"}
208+
/>
209+
<RBIconButton
210+
tooltip={"Create service account"}
211+
text={"Create service account"}
212+
variant="contained"
213+
color="primary"
214+
icon={<AddIcon />}
215+
onClick={() => {
216+
setAddScreenOpen(true);
217+
setAddScreenOpen(true);
218+
setSelectedServiceAccount(null);
219+
}}
220+
disabled={!hasPolicy}
221+
/>
222+
</Box>
183223
</div>
184-
<div className={classes.tableBlock}>
224+
<div className={classes.tableBlock}>
185225
<TableWrapper
186226
isLoading={loading}
187227
records={records}
188228
entityName={"Service Accounts"}
189229
idField={""}
190230
columns={[{ label: "Service Account", elementKey: "" }]}
191231
itemActions={tableActions}
232+
selectedItems={selectedSAs}
233+
onSelect={e => selectSAs(e, setSelectedSAs, selectedSAs)}
234+
onSelectAll={selectAllItems}
192235
/>
193236
</div>
194237
</React.Fragment>

0 commit comments

Comments
 (0)