github.com/minio/console@v1.4.1/web-app/src/screens/Console/Groups/Groups.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 {
    20    AddIcon,
    21    Button,
    22    DeleteIcon,
    23    GroupsIcon,
    24    HelpBox,
    25    IAMPoliciesIcon,
    26    PageLayout,
    27    UsersIcon,
    28    DataTable,
    29    Grid,
    30    Box,
    31    ProgressBar,
    32    ActionLink,
    33  } from "mds";
    34  
    35  import { api } from "api";
    36  import { stringSort } from "../../../utils/sortFunctions";
    37  import { actionsTray } from "../Common/FormComponents/common/styleLibrary";
    38  import {
    39    applyPolicyPermissions,
    40    CONSOLE_UI_RESOURCE,
    41    createGroupPermissions,
    42    deleteGroupPermissions,
    43    displayGroupsPermissions,
    44    getGroupPermissions,
    45    IAM_PAGES,
    46    permissionTooltipHelper,
    47  } from "../../../common/SecureComponent/permissions";
    48  import {
    49    hasPermission,
    50    SecureComponent,
    51  } from "../../../common/SecureComponent";
    52  import { errorToHandler } from "../../../api/errors";
    53  import withSuspense from "../Common/Components/withSuspense";
    54  import { encodeURLString } from "../../../common/utils";
    55  import { setErrorSnackMessage, setHelpName } from "../../../systemSlice";
    56  import { useAppDispatch } from "../../../store";
    57  import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
    58  import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
    59  import HelpMenu from "../HelpMenu";
    60  import SearchBox from "../Common/SearchBox";
    61  
    62  const DeleteGroup = withSuspense(React.lazy(() => import("./DeleteGroup")));
    63  const SetPolicy = withSuspense(
    64    React.lazy(() => import("../Policies/SetPolicy")),
    65  );
    66  
    67  const Groups = () => {
    68    const dispatch = useAppDispatch();
    69    const navigate = useNavigate();
    70  
    71    const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
    72    const [loading, isLoading] = useState<boolean>(false);
    73    const [records, setRecords] = useState<any[]>([]);
    74    const [filter, setFilter] = useState<string>("");
    75    const [policyOpen, setPolicyOpen] = useState<boolean>(false);
    76    const [checkedGroups, setCheckedGroups] = useState<string[]>([]);
    77  
    78    useEffect(() => {
    79      isLoading(true);
    80    }, []);
    81  
    82    useEffect(() => {
    83      isLoading(true);
    84    }, []);
    85  
    86    useEffect(() => {
    87      dispatch(setHelpName("groups"));
    88      // eslint-disable-next-line react-hooks/exhaustive-deps
    89    }, []);
    90  
    91    const displayGroups = hasPermission(
    92      CONSOLE_UI_RESOURCE,
    93      displayGroupsPermissions,
    94    );
    95  
    96    const deleteGroup = hasPermission(
    97      CONSOLE_UI_RESOURCE,
    98      deleteGroupPermissions,
    99    );
   100  
   101    const getGroup = hasPermission(CONSOLE_UI_RESOURCE, getGroupPermissions);
   102  
   103    const applyPolicy = hasPermission(
   104      CONSOLE_UI_RESOURCE,
   105      applyPolicyPermissions,
   106      true,
   107    );
   108  
   109    const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
   110      const { target: { value = "", checked = false } = {} } = e;
   111  
   112      let elements: string[] = [...checkedGroups]; // We clone the checkedUsers array
   113  
   114      if (checked) {
   115        // If the user has checked this field we need to push this to checkedUsersList
   116        elements.push(value);
   117      } else {
   118        // User has unchecked this field, we need to remove it from the list
   119        elements = elements.filter((element) => element !== value);
   120      }
   121  
   122      setCheckedGroups(elements);
   123  
   124      return elements;
   125    };
   126  
   127    useEffect(() => {
   128      if (loading) {
   129        if (displayGroups) {
   130          const fetchRecords = () => {
   131            api.groups
   132              .listGroups()
   133              .then((res) => {
   134                let resGroups: string[] = [];
   135                if (res.data.groups) {
   136                  resGroups = res.data.groups.sort(stringSort);
   137                }
   138                setRecords(resGroups);
   139                isLoading(false);
   140              })
   141              .catch((err) => {
   142                dispatch(setErrorSnackMessage(errorToHandler(err.error)));
   143                isLoading(false);
   144              });
   145          };
   146          fetchRecords();
   147        } else {
   148          isLoading(false);
   149        }
   150      }
   151    }, [loading, dispatch, displayGroups]);
   152  
   153    const closeDeleteModalAndRefresh = (refresh: boolean) => {
   154      setDeleteOpen(false);
   155      setCheckedGroups([]);
   156      if (refresh) {
   157        isLoading(true);
   158      }
   159    };
   160  
   161    const filteredRecords = records.filter((elementItem) =>
   162      elementItem.includes(filter),
   163    );
   164  
   165    const viewAction = (group: any) => {
   166      navigate(`${IAM_PAGES.GROUPS}/${encodeURLString(group)}`);
   167    };
   168  
   169    const tableActions = [
   170      {
   171        type: "view",
   172        onClick: viewAction,
   173        disableButtonFunction: () => !getGroup,
   174      },
   175      {
   176        type: "edit",
   177        onClick: viewAction,
   178        disableButtonFunction: () => !getGroup,
   179      },
   180    ];
   181  
   182    return (
   183      <Fragment>
   184        {deleteOpen && (
   185          <DeleteGroup
   186            deleteOpen={deleteOpen}
   187            selectedGroups={checkedGroups}
   188            closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
   189          />
   190        )}
   191        {policyOpen && (
   192          <SetPolicy
   193            open={policyOpen}
   194            selectedGroups={checkedGroups}
   195            selectedUser={null}
   196            closeModalAndRefresh={() => {
   197              setPolicyOpen(false);
   198            }}
   199          />
   200        )}
   201        <PageHeaderWrapper label={"Groups"} actions={<HelpMenu />} />
   202  
   203        <PageLayout>
   204          <Grid container>
   205            <Grid item xs={12} sx={actionsTray.actionsTray}>
   206              <SecureComponent
   207                resource={CONSOLE_UI_RESOURCE}
   208                scopes={displayGroupsPermissions}
   209                errorProps={{ disabled: true }}
   210              >
   211                <SearchBox
   212                  placeholder={"Search Groups"}
   213                  onChange={setFilter}
   214                  value={filter}
   215                  sx={{ maxWidth: 380 }}
   216                />
   217              </SecureComponent>
   218              <Box
   219                sx={{
   220                  display: "flex",
   221                }}
   222              >
   223                <SecureComponent
   224                  resource={CONSOLE_UI_RESOURCE}
   225                  scopes={applyPolicyPermissions}
   226                  matchAll
   227                  errorProps={{ disabled: true }}
   228                >
   229                  <TooltipWrapper
   230                    tooltip={
   231                      checkedGroups.length < 1
   232                        ? "Please select Groups on which you want to apply Policies"
   233                        : applyPolicy
   234                          ? "Select Policy"
   235                          : permissionTooltipHelper(
   236                              applyPolicyPermissions,
   237                              "apply policies to Groups",
   238                            )
   239                    }
   240                  >
   241                    <Button
   242                      id={"assign-policy"}
   243                      onClick={() => {
   244                        setPolicyOpen(true);
   245                      }}
   246                      label={"Assign Policy"}
   247                      icon={<IAMPoliciesIcon />}
   248                      disabled={checkedGroups.length < 1 || !applyPolicy}
   249                      variant={"regular"}
   250                    />
   251                  </TooltipWrapper>
   252                </SecureComponent>
   253                <SecureComponent
   254                  resource={CONSOLE_UI_RESOURCE}
   255                  scopes={deleteGroupPermissions}
   256                  matchAll
   257                  errorProps={{ disabled: true }}
   258                >
   259                  <TooltipWrapper
   260                    tooltip={
   261                      checkedGroups.length === 0
   262                        ? "Select Groups to delete"
   263                        : getGroup
   264                          ? "Delete Selected"
   265                          : permissionTooltipHelper(
   266                              getGroupPermissions,
   267                              "delete Groups",
   268                            )
   269                    }
   270                  >
   271                    <Button
   272                      id="delete-selected-groups"
   273                      onClick={() => {
   274                        setDeleteOpen(true);
   275                      }}
   276                      label={"Delete Selected"}
   277                      icon={<DeleteIcon />}
   278                      variant="secondary"
   279                      disabled={checkedGroups.length === 0 || !getGroup}
   280                    />
   281                  </TooltipWrapper>
   282                </SecureComponent>
   283                <SecureComponent
   284                  resource={CONSOLE_UI_RESOURCE}
   285                  scopes={createGroupPermissions}
   286                  matchAll
   287                  errorProps={{ disabled: true }}
   288                >
   289                  <TooltipWrapper tooltip={"Create Group"}>
   290                    <Button
   291                      id={"create-group"}
   292                      label={"Create Group"}
   293                      variant="callAction"
   294                      icon={<AddIcon />}
   295                      onClick={() => {
   296                        navigate(`${IAM_PAGES.GROUPS_ADD}`);
   297                      }}
   298                    />
   299                  </TooltipWrapper>
   300                </SecureComponent>
   301              </Box>
   302            </Grid>
   303            {loading && <ProgressBar />}
   304            {!loading && (
   305              <Fragment>
   306                {records.length > 0 && (
   307                  <Fragment>
   308                    <Grid item xs={12} sx={{ marginBottom: 15 }}>
   309                      <SecureComponent
   310                        resource={CONSOLE_UI_RESOURCE}
   311                        scopes={displayGroupsPermissions}
   312                        errorProps={{ disabled: true }}
   313                      >
   314                        <DataTable
   315                          itemActions={tableActions}
   316                          columns={[{ label: "Name" }]}
   317                          isLoading={loading}
   318                          selectedItems={checkedGroups}
   319                          onSelect={
   320                            deleteGroup || getGroup ? selectionChanged : undefined
   321                          }
   322                          records={filteredRecords}
   323                          entityName="Groups"
   324                          idField=""
   325                        />
   326                      </SecureComponent>
   327                    </Grid>
   328                    <Grid item xs={12}>
   329                      <HelpBox
   330                        title={"Groups"}
   331                        iconComponent={<GroupsIcon />}
   332                        help={
   333                          <Fragment>
   334                            A group can have one attached IAM policy, where all
   335                            users with membership in that group inherit that
   336                            policy. Groups support more simplified management of
   337                            user permissions on the MinIO Tenant.
   338                            <br />
   339                            <br />
   340                            You can learn more at our{" "}
   341                            <a
   342                              href="https://min.io/docs/minio/linux/administration/identity-access-management/minio-group-management.html?ref=con"
   343                              target="_blank"
   344                              rel="noopener"
   345                            >
   346                              documentation
   347                            </a>
   348                            .
   349                          </Fragment>
   350                        }
   351                      />
   352                    </Grid>
   353                  </Fragment>
   354                )}
   355                {records.length === 0 && (
   356                  <Grid container>
   357                    <Grid item xs={8}>
   358                      <HelpBox
   359                        title={"Groups"}
   360                        iconComponent={<UsersIcon />}
   361                        help={
   362                          <Fragment>
   363                            A group can have one attached IAM policy, where all
   364                            users with membership in that group inherit that
   365                            policy. Groups support more simplified management of
   366                            user permissions on the MinIO Tenant.
   367                            <SecureComponent
   368                              resource={CONSOLE_UI_RESOURCE}
   369                              scopes={createGroupPermissions}
   370                              matchAll
   371                            >
   372                              <br />
   373                              <br />
   374                              To get started,{" "}
   375                              <ActionLink
   376                                onClick={() => {
   377                                  navigate(`${IAM_PAGES.GROUPS_ADD}`);
   378                                }}
   379                              >
   380                                Create a Group
   381                              </ActionLink>
   382                              .
   383                            </SecureComponent>
   384                          </Fragment>
   385                        }
   386                      />
   387                    </Grid>
   388                  </Grid>
   389                )}
   390              </Fragment>
   391            )}
   392          </Grid>
   393        </PageLayout>
   394      </Fragment>
   395    );
   396  };
   397  
   398  export default Groups;