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;