github.com/minio/console@v1.4.1/web-app/src/screens/Console/Users/UserServiceAccountsPanel.tsx (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 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, { Fragment, useEffect, useState } from "react"; 18 import { useNavigate } from "react-router-dom"; 19 import { AddIcon, Box, Button, DataTable, DeleteIcon, SectionTitle } from "mds"; 20 import api from "../../../common/api"; 21 import { NewServiceAccount } from "../Common/CredentialsPrompt/types"; 22 import { ErrorResponseHandler } from "../../../common/types"; 23 import DeleteServiceAccount from "../Account/DeleteServiceAccount"; 24 import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt"; 25 26 import DeleteMultipleServiceAccounts from "./DeleteMultipleServiceAccounts"; 27 import { selectSAs } from "../Configurations/utils"; 28 import EditServiceAccount from "../Account/EditServiceAccount"; 29 import { 30 CONSOLE_UI_RESOURCE, 31 IAM_SCOPES, 32 } from "../../../common/SecureComponent/permissions"; 33 import { SecureComponent } from "../../../common/SecureComponent"; 34 import { encodeURLString } from "../../../common/utils"; 35 import { 36 setErrorSnackMessage, 37 setHelpName, 38 setSnackBarMessage, 39 } from "../../../systemSlice"; 40 import { useAppDispatch } from "../../../store"; 41 import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper"; 42 import { ServiceAccounts } from "../../../api/consoleApi"; 43 import { usersSort } from "../../../utils/sortFunctions"; 44 import { ACCOUNT_TABLE_COLUMNS } from "../Account/AccountUtils"; 45 46 interface IUserServiceAccountsProps { 47 user: string; 48 hasPolicy: boolean; 49 } 50 51 const UserServiceAccountsPanel = ({ 52 user, 53 hasPolicy, 54 }: IUserServiceAccountsProps) => { 55 const dispatch = useAppDispatch(); 56 const navigate = useNavigate(); 57 58 const [records, setRecords] = useState<ServiceAccounts>([]); 59 const [loading, setLoading] = useState<boolean>(false); 60 const [deleteOpen, setDeleteOpen] = useState<boolean>(false); 61 const [selectedServiceAccount, setSelectedServiceAccount] = useState< 62 string | null 63 >(null); 64 const [showNewCredentials, setShowNewCredentials] = useState<boolean>(false); 65 const [newServiceAccount, setNewServiceAccount] = 66 useState<NewServiceAccount | null>(null); 67 const [selectedSAs, setSelectedSAs] = useState<string[]>([]); 68 const [deleteMultipleOpen, setDeleteMultipleOpen] = useState<boolean>(false); 69 const [editOpen, setEditOpen] = useState<boolean>(false); 70 71 useEffect(() => { 72 fetchRecords(); 73 }, []); 74 75 useEffect(() => { 76 if (loading) { 77 api 78 .invoke("GET", `/api/v1/user/${encodeURLString(user)}/service-accounts`) 79 .then((res: ServiceAccounts) => { 80 setLoading(false); 81 const sortedRows = res.sort(usersSort); 82 setRecords(sortedRows); 83 }) 84 .catch((err: ErrorResponseHandler) => { 85 dispatch(setErrorSnackMessage(err)); 86 setLoading(false); 87 }); 88 } 89 }, [loading, setLoading, setRecords, user, dispatch]); 90 91 const fetchRecords = () => { 92 setLoading(true); 93 }; 94 95 const closeDeleteModalAndRefresh = (refresh: boolean) => { 96 setDeleteOpen(false); 97 98 if (refresh) { 99 fetchRecords(); 100 } 101 }; 102 103 const closeDeleteMultipleModalAndRefresh = (refresh: boolean) => { 104 setDeleteMultipleOpen(false); 105 if (refresh) { 106 dispatch(setSnackBarMessage(`Access Keys deleted successfully.`)); 107 setSelectedSAs([]); 108 setLoading(true); 109 } 110 }; 111 112 const closeCredentialsModal = () => { 113 setShowNewCredentials(false); 114 setNewServiceAccount(null); 115 }; 116 117 const editModalOpen = (selectedServiceAccount: string) => { 118 setSelectedServiceAccount(selectedServiceAccount); 119 setEditOpen(true); 120 }; 121 122 const confirmDeleteServiceAccount = (selectedServiceAccount: string) => { 123 setSelectedServiceAccount(selectedServiceAccount); 124 setDeleteOpen(true); 125 }; 126 127 const closePolicyModal = () => { 128 setEditOpen(false); 129 setLoading(true); 130 }; 131 132 const tableActions = [ 133 { 134 type: "view", 135 onClick: (value: any) => { 136 if (value) { 137 editModalOpen(value.accessKey); 138 } 139 }, 140 }, 141 { 142 type: "delete", 143 onClick: (value: any) => { 144 if (value) { 145 confirmDeleteServiceAccount(value.accessKey); 146 } 147 }, 148 }, 149 { 150 type: "edit", 151 onClick: (value: any) => { 152 if (value) { 153 editModalOpen(value.accessKey); 154 } 155 }, 156 }, 157 ]; 158 159 useEffect(() => { 160 dispatch(setHelpName("user_details_accounts")); 161 162 // eslint-disable-next-line react-hooks/exhaustive-deps 163 }, []); 164 return ( 165 <Fragment> 166 {deleteOpen && ( 167 <DeleteServiceAccount 168 deleteOpen={deleteOpen} 169 selectedServiceAccount={selectedServiceAccount} 170 closeDeleteModalAndRefresh={(refresh: boolean) => { 171 closeDeleteModalAndRefresh(refresh); 172 }} 173 /> 174 )} 175 {deleteMultipleOpen && ( 176 <DeleteMultipleServiceAccounts 177 deleteOpen={deleteMultipleOpen} 178 selectedSAs={selectedSAs} 179 closeDeleteModalAndRefresh={closeDeleteMultipleModalAndRefresh} 180 /> 181 )} 182 {showNewCredentials && ( 183 <CredentialsPrompt 184 newServiceAccount={newServiceAccount} 185 open={showNewCredentials} 186 closeModal={() => { 187 closeCredentialsModal(); 188 }} 189 entity="Access Key" 190 /> 191 )} 192 {editOpen && ( 193 <EditServiceAccount 194 open={editOpen} 195 selectedAccessKey={selectedServiceAccount} 196 closeModalAndRefresh={closePolicyModal} 197 /> 198 )} 199 200 <SectionTitle 201 separator 202 sx={{ marginBottom: 15 }} 203 actions={ 204 <Box sx={{ display: "flex", justifyContent: "flex-end", gap: 10 }}> 205 <TooltipWrapper tooltip={"Delete Selected"}> 206 <Button 207 id={"delete-selected"} 208 onClick={() => { 209 setDeleteMultipleOpen(true); 210 }} 211 label={"Delete Selected"} 212 icon={<DeleteIcon />} 213 disabled={selectedSAs.length === 0} 214 variant={"secondary"} 215 /> 216 </TooltipWrapper> 217 <SecureComponent 218 scopes={[ 219 IAM_SCOPES.ADMIN_CREATE_SERVICEACCOUNT, 220 IAM_SCOPES.ADMIN_UPDATE_SERVICEACCOUNT, 221 IAM_SCOPES.ADMIN_REMOVE_SERVICEACCOUNT, 222 IAM_SCOPES.ADMIN_LIST_SERVICEACCOUNTS, 223 ]} 224 resource={CONSOLE_UI_RESOURCE} 225 matchAll 226 errorProps={{ disabled: true }} 227 > 228 <TooltipWrapper tooltip={"Create Access Key"}> 229 <Button 230 id={"create-service-account"} 231 label={"Create Access Key"} 232 variant="callAction" 233 icon={<AddIcon />} 234 onClick={() => { 235 navigate( 236 `/identity/users/new-user-sa/${encodeURLString(user)}`, 237 ); 238 }} 239 disabled={!hasPolicy} 240 /> 241 </TooltipWrapper> 242 </SecureComponent> 243 </Box> 244 } 245 > 246 Access Keys 247 </SectionTitle> 248 249 <DataTable 250 itemActions={tableActions} 251 entityName={"Access Keys"} 252 columns={ACCOUNT_TABLE_COLUMNS} 253 onSelect={(e) => selectSAs(e, setSelectedSAs, selectedSAs)} 254 selectedItems={selectedSAs} 255 isLoading={loading} 256 records={records} 257 idField="accessKey" 258 /> 259 </Fragment> 260 ); 261 }; 262 263 export default UserServiceAccountsPanel;