github.com/minio/console@v1.4.1/web-app/src/screens/Console/Account/Account.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 {
    19    AccountIcon,
    20    AddIcon,
    21    Box,
    22    Button,
    23    DataTable,
    24    DeleteIcon,
    25    Grid,
    26    HelpBox,
    27    PageLayout,
    28    PasswordKeyIcon,
    29  } from "mds";
    30  import { useSelector } from "react-redux";
    31  import { useNavigate } from "react-router-dom";
    32  import { actionsTray } from "../Common/FormComponents/common/styleLibrary";
    33  
    34  import ChangePasswordModal from "./ChangePasswordModal";
    35  import SearchBox from "../Common/SearchBox";
    36  import withSuspense from "../Common/Components/withSuspense";
    37  
    38  import { selectSAs } from "../Configurations/utils";
    39  import DeleteMultipleServiceAccounts from "../Users/DeleteMultipleServiceAccounts";
    40  import EditServiceAccount from "./EditServiceAccount";
    41  
    42  import { selFeatures } from "../consoleSlice";
    43  import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
    44  import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
    45  import { api } from "api";
    46  import { errorToHandler } from "api/errors";
    47  import HelpMenu from "../HelpMenu";
    48  import { ACCOUNT_TABLE_COLUMNS } from "./AccountUtils";
    49  import { useAppDispatch } from "store";
    50  import { ServiceAccounts } from "api/consoleApi";
    51  import {
    52    setErrorSnackMessage,
    53    setHelpName,
    54    setSnackBarMessage,
    55  } from "systemSlice";
    56  import { usersSort } from "utils/sortFunctions";
    57  import { SecureComponent } from "common/SecureComponent";
    58  import {
    59    CONSOLE_UI_RESOURCE,
    60    IAM_PAGES,
    61    IAM_SCOPES,
    62  } from "common/SecureComponent/permissions";
    63  
    64  const DeleteServiceAccount = withSuspense(
    65    React.lazy(() => import("./DeleteServiceAccount")),
    66  );
    67  
    68  const Account = () => {
    69    const dispatch = useAppDispatch();
    70    const navigate = useNavigate();
    71  
    72    const features = useSelector(selFeatures);
    73  
    74    const [records, setRecords] = useState<ServiceAccounts>([]);
    75    const [loading, setLoading] = useState<boolean>(false);
    76    const [filter, setFilter] = useState<string>("");
    77    const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
    78    const [selectedServiceAccount, setSelectedServiceAccount] = useState<
    79      string | null
    80    >(null);
    81    const [changePasswordModalOpen, setChangePasswordModalOpen] =
    82      useState<boolean>(false);
    83    const [selectedSAs, setSelectedSAs] = useState<string[]>([]);
    84    const [deleteMultipleOpen, setDeleteMultipleOpen] = useState<boolean>(false);
    85    const [isEditOpen, setIsEditOpen] = useState<boolean>(false);
    86  
    87    const userIDP = (features && features.includes("external-idp")) || false;
    88  
    89    useEffect(() => {
    90      fetchRecords();
    91    }, []);
    92  
    93    useEffect(() => {
    94      dispatch(setHelpName("accessKeys"));
    95      // eslint-disable-next-line react-hooks/exhaustive-deps
    96    }, []);
    97  
    98    useEffect(() => {
    99      if (loading) {
   100        api.serviceAccounts
   101          .listUserServiceAccounts()
   102          .then((res) => {
   103            setLoading(false);
   104            const sortedRows = res.data.sort(usersSort);
   105            setRecords(sortedRows);
   106          })
   107          .catch((res) => {
   108            dispatch(
   109              setErrorSnackMessage(
   110                errorToHandler(res?.error || "Error retrieving access keys"),
   111              ),
   112            );
   113            setLoading(false);
   114          });
   115      }
   116    }, [loading, setLoading, setRecords, dispatch]);
   117  
   118    const fetchRecords = () => {
   119      setLoading(true);
   120    };
   121  
   122    const closeDeleteModalAndRefresh = (refresh: boolean) => {
   123      setDeleteOpen(false);
   124  
   125      if (refresh) {
   126        setSelectedSAs([]);
   127        fetchRecords();
   128      }
   129    };
   130  
   131    const closeDeleteMultipleModalAndRefresh = (refresh: boolean) => {
   132      setDeleteMultipleOpen(false);
   133      if (refresh) {
   134        dispatch(setSnackBarMessage(`Access keys deleted successfully.`));
   135        setSelectedSAs([]);
   136        setLoading(true);
   137      }
   138    };
   139  
   140    const editModalOpen = (selectedServiceAccount: string) => {
   141      setSelectedServiceAccount(selectedServiceAccount);
   142      setIsEditOpen(true);
   143    };
   144  
   145    const closePolicyModal = () => {
   146      setIsEditOpen(false);
   147      setLoading(true);
   148    };
   149  
   150    const confirmDeleteServiceAccount = (selectedServiceAccount: string) => {
   151      setSelectedServiceAccount(selectedServiceAccount);
   152      setDeleteOpen(true);
   153    };
   154  
   155    const tableActions = [
   156      {
   157        type: "view",
   158        onClick: (value: any) => {
   159          if (value) {
   160            editModalOpen(value.accessKey);
   161          }
   162        },
   163      },
   164      {
   165        type: "delete",
   166        onClick: (value: any) => {
   167          if (value) {
   168            confirmDeleteServiceAccount(value.accessKey);
   169          }
   170        },
   171      },
   172      {
   173        type: "edit",
   174        onClick: (value: any) => {
   175          if (value) {
   176            editModalOpen(value.accessKey);
   177          }
   178        },
   179      },
   180    ];
   181  
   182    const filteredRecords = records.filter((elementItem) =>
   183      elementItem?.accessKey?.toLowerCase().includes(filter.toLowerCase()),
   184    );
   185  
   186    return (
   187      <React.Fragment>
   188        {deleteOpen && (
   189          <DeleteServiceAccount
   190            deleteOpen={deleteOpen}
   191            selectedServiceAccount={selectedServiceAccount}
   192            closeDeleteModalAndRefresh={(refresh: boolean) => {
   193              closeDeleteModalAndRefresh(refresh);
   194            }}
   195          />
   196        )}
   197        {deleteMultipleOpen && (
   198          <DeleteMultipleServiceAccounts
   199            deleteOpen={deleteMultipleOpen}
   200            selectedSAs={selectedSAs}
   201            closeDeleteModalAndRefresh={closeDeleteMultipleModalAndRefresh}
   202          />
   203        )}
   204  
   205        {isEditOpen && (
   206          <EditServiceAccount
   207            open={isEditOpen}
   208            selectedAccessKey={selectedServiceAccount}
   209            closeModalAndRefresh={closePolicyModal}
   210          />
   211        )}
   212        <ChangePasswordModal
   213          open={changePasswordModalOpen}
   214          closeModal={() => setChangePasswordModalOpen(false)}
   215        />
   216        <PageHeaderWrapper label="Access Keys" actions={<HelpMenu />} />
   217  
   218        <PageLayout>
   219          <Grid container>
   220            <Grid item xs={12} sx={{ ...actionsTray.actionsTray }}>
   221              <SearchBox
   222                placeholder={"Search Access Keys"}
   223                onChange={setFilter}
   224                sx={{ marginRight: "auto", maxWidth: 380 }}
   225                value={filter}
   226              />
   227              <Box
   228                sx={{
   229                  display: "flex",
   230                  flexWrap: "nowrap",
   231                  gap: 5,
   232                }}
   233              >
   234                <TooltipWrapper tooltip={"Delete Selected"}>
   235                  <Button
   236                    id={"delete-selected-accounts"}
   237                    onClick={() => {
   238                      setDeleteMultipleOpen(true);
   239                    }}
   240                    label={"Delete Selected"}
   241                    icon={<DeleteIcon />}
   242                    disabled={selectedSAs.length === 0}
   243                    variant={"secondary"}
   244                  />
   245                </TooltipWrapper>
   246                <SecureComponent
   247                  scopes={[IAM_SCOPES.ADMIN_CREATE_USER]}
   248                  resource={CONSOLE_UI_RESOURCE}
   249                  matchAll
   250                  errorProps={{ disabled: true }}
   251                >
   252                  <Button
   253                    id={"change-password"}
   254                    onClick={() => setChangePasswordModalOpen(true)}
   255                    label={`Change Password`}
   256                    icon={<PasswordKeyIcon />}
   257                    variant={"regular"}
   258                    disabled={userIDP}
   259                  />
   260                </SecureComponent>
   261                <SecureComponent
   262                  scopes={[IAM_SCOPES.ADMIN_CREATE_SERVICEACCOUNT]}
   263                  resource={CONSOLE_UI_RESOURCE}
   264                  matchAll
   265                  errorProps={{ disabled: true }}
   266                >
   267                  <Button
   268                    id={"create-service-account"}
   269                    onClick={() => {
   270                      navigate(`${IAM_PAGES.ACCOUNT_ADD}`);
   271                    }}
   272                    label={`Create access key`}
   273                    icon={<AddIcon />}
   274                    variant={"callAction"}
   275                  />
   276                </SecureComponent>
   277              </Box>
   278            </Grid>
   279  
   280            <Grid item xs={12}>
   281              <DataTable
   282                itemActions={tableActions}
   283                entityName={"Access Keys"}
   284                columns={ACCOUNT_TABLE_COLUMNS}
   285                onSelect={(e) => selectSAs(e, setSelectedSAs, selectedSAs)}
   286                selectedItems={selectedSAs}
   287                isLoading={loading}
   288                records={filteredRecords}
   289                idField="accessKey"
   290              />
   291            </Grid>
   292            <Grid item xs={12} sx={{ marginTop: 15 }}>
   293              <HelpBox
   294                title={"Learn more about ACCESS KEYS"}
   295                iconComponent={<AccountIcon />}
   296                help={
   297                  <Fragment>
   298                    MinIO access keys are child identities of an authenticated
   299                    MinIO user, including externally managed identities. Each
   300                    access key inherits its privileges based on the policies
   301                    attached to it’s parent user or those groups in which the
   302                    parent user has membership. Access Keys also support an
   303                    optional inline policy which further restricts access to a
   304                    subset of actions and resources available to the parent user.
   305                    <br />
   306                    <br />
   307                    You can learn more at our{" "}
   308                    <a
   309                      href="https://min.io/docs/minio/linux/administration/identity-access-management/minio-user-management.html?ref=con#id3"
   310                      target="_blank"
   311                      rel="noopener"
   312                    >
   313                      documentation
   314                    </a>
   315                    .
   316                  </Fragment>
   317                }
   318              />
   319            </Grid>
   320          </Grid>
   321        </PageLayout>
   322      </React.Fragment>
   323    );
   324  };
   325  
   326  export default Account;