From 74d4c4a3e6ef32f26c0fa355f62f95f3d59e5497 Mon Sep 17 00:00:00 2001 From: jinapurapu <65002498+jinapurapu@users.noreply.github.com> Date: Fri, 22 Apr 2022 20:50:31 -0700 Subject: [PATCH] Create Add Group screen (#1890) --- .../src/common/SecureComponent/permissions.ts | 5 + portal-ui/src/screens/Console/Console.tsx | 9 +- .../Console/Groups/AddGroupHelpBox.tsx | 119 ++++++++ .../screens/Console/Groups/AddGroupScreen.tsx | 258 ++++++++++++++++++ .../src/screens/Console/Groups/Groups.tsx | 7 +- 5 files changed, 393 insertions(+), 5 deletions(-) create mode 100644 portal-ui/src/screens/Console/Groups/AddGroupHelpBox.tsx create mode 100644 portal-ui/src/screens/Console/Groups/AddGroupScreen.tsx diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts index fb4f927be..a1364a552 100644 --- a/portal-ui/src/common/SecureComponent/permissions.ts +++ b/portal-ui/src/common/SecureComponent/permissions.ts @@ -121,6 +121,7 @@ export const IAM_PAGES = { USERS: "/identity/users", USERS_VIEW: "/identity/users/:userName+", GROUPS: "/identity/groups", + GROUPS_ADD: "/identity/create-group", GROUPS_VIEW: "/identity/groups/:groupName+", ACCOUNT: "/identity/account", ACCOUNT_ADD: "/identity/new-account", @@ -296,6 +297,10 @@ export const IAM_PAGES_PERMISSIONS = { IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP, // display "edit members" button in groups detail page IAM_SCOPES.ADMIN_ATTACH_USER_OR_GROUP_POLICY, // display "set policy" button in groups details page ], + [IAM_PAGES.GROUPS_ADD]: [ + IAM_SCOPES.ADMIN_LIST_USERS, // displays users + IAM_SCOPES.ADMIN_CREATE_USER, // displays create user button + ], [IAM_PAGES.USERS]: [ IAM_SCOPES.ADMIN_LIST_USERS, // displays users IAM_SCOPES.ADMIN_CREATE_USER, // displays create user button diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index bf4aa6fbd..4b07b48a9 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -120,6 +120,9 @@ const ConfigurationOptions = React.lazy( const AddPool = React.lazy( () => import("./Tenants/TenantDetails/Pools/AddPool/AddPool") ); +const AddGroupScreen = React.lazy( + () => import("./Groups/AddGroupScreen") +); const SiteReplication = React.lazy( () => import("./Configurations/SiteReplication/SiteReplication") ); @@ -289,6 +292,10 @@ const Console = ({ path: IAM_PAGES.GROUPS, fsHidden: ldapIsEnabled, }, + { + component: AddGroupScreen, + path: IAM_PAGES.GROUPS_ADD, + }, { component: GroupsDetails, path: IAM_PAGES.GROUPS_VIEW, @@ -394,7 +401,7 @@ const Console = ({ { component: Account, path: IAM_PAGES.ACCOUNT, - forceDisplay: true, // user has implicit access to service-accounts + // user has implicit access to service-accounts }, { component: AccountCreate, diff --git a/portal-ui/src/screens/Console/Groups/AddGroupHelpBox.tsx b/portal-ui/src/screens/Console/Groups/AddGroupHelpBox.tsx new file mode 100644 index 000000000..9cdc24aab --- /dev/null +++ b/portal-ui/src/screens/Console/Groups/AddGroupHelpBox.tsx @@ -0,0 +1,119 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import React from "react"; +import { Box } from "@mui/material"; +import { + HelpIconFilled, + GroupsIcon, + IAMPoliciesIcon, +} from "../../../icons"; + +const FeatureItem = ({ + icon, + description, +}: { + icon: any; + description: string; +}) => { + return ( + + {icon}{" "} +
+ {description} +
+
+ ); +}; +const AddGroupHelpBox = ({ hasMargin = true }: { hasMargin?: boolean }) => { + return ( + + + +
Learn more about Groups
+
+ + Adding groups lets you assign IAM policies to multiple users at once. + + Users inherit access permissions to data and resources through the groups they belong to. + + + + A user can be a member of multiple groups. + + + + Groups provide a simplified method for managing shared permissions among users with common access patterns and workloads. Client’s cannot authenticate to a MinIO deployment using a group as an identity. + + + + + + } description={`Add Users to Group`} /> + } description={`Assign Custom IAM Policies for Group`} /> + + + +
+ ); +}; + +export default AddGroupHelpBox; \ No newline at end of file diff --git a/portal-ui/src/screens/Console/Groups/AddGroupScreen.tsx b/portal-ui/src/screens/Console/Groups/AddGroupScreen.tsx new file mode 100644 index 000000000..f119dc3be --- /dev/null +++ b/portal-ui/src/screens/Console/Groups/AddGroupScreen.tsx @@ -0,0 +1,258 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment, useState, useEffect } from "react"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import { + formFieldStyles, + modalStyleUtils, +} from "../Common/FormComponents/common/styleLibrary"; +import Grid from "@mui/material/Grid"; +import { Button, Box, LinearProgress } from "@mui/material"; +import PageHeader from "../Common/PageHeader/PageHeader"; +import PageLayout from "../Common/Layout/PageLayout"; +import history from "../../../../src/history"; +import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import AddGroupHelpBox from "./AddGroupHelpBox"; +import UsersSelectors from "./UsersSelectors"; +import BackLink from "../../../common/BackLink"; +import { connect } from "react-redux"; +import { CreateGroupIcon } from "../../../icons"; +import { IAM_PAGES } from "../../../common/SecureComponent/permissions"; +import { ErrorResponseHandler } from "../../../../src/common/types"; +import api from "../../../../src/common/api"; +import { setErrorSnackMessage } from "../../../../src/actions"; + +interface IAddGroupProps { + classes: any; + setErrorSnackMessage: typeof setErrorSnackMessage; +} + +const styles = (theme: Theme) => + createStyles({ + buttonContainer: { + textAlign: "right", + }, + bottomContainer: { + display: "flex", + flexGrow: 1, + alignItems: "center", + margin: "auto", + justifyContent: "center", + "& div": { + width: 150, + "@media (max-width: 900px)": { + flexFlow: "column", + }, + }, + }, + factorElements: { + display: "flex", + justifyContent: "flex-start", + marginLeft: 30, + }, + sizeNumber: { + fontSize: 35, + fontWeight: 700, + textAlign: "center", + }, + sizeDescription: { + fontSize: 14, + color: "#777", + textAlign: "center", + }, + pageBox: { + border: "1px solid #EAEAEA", + borderTop: 0, + }, + addPoolTitle: { + border: "1px solid #EAEAEA", + borderBottom: 0, + }, + headTitle: { + fontWeight: "bold", + fontSize: 20, + paddingLeft: 20, + paddingBottom: 40, + paddingTop: 10, + textAlign: "end", + }, + headIcon: { + fontWeight: "bold", + size: "50", + }, + ...formFieldStyles, + ...modalStyleUtils, + }); + +const AddGroupScreen = ({ + classes, + setErrorSnackMessage, +}: IAddGroupProps) => { + const [groupName, setGroupName] = useState(""); + const [saving, isSaving] = useState(false); + const [selectedUsers, setSelectedUsers] = useState([]); + const [validGroup, setValidGroup] = useState(false); + + + useEffect(() => { + setValidGroup(groupName.trim() !== ""); + }, [groupName, selectedUsers]); + + useEffect(() => { + if (saving) { + const saveRecord = () => { + + api + .invoke("POST", "/api/v1/groups", { + group: groupName, + members: selectedUsers, + }) + .then((res) => { + isSaving(false); + history.push(`${IAM_PAGES.GROUPS}`); + }) + .catch((err: ErrorResponseHandler) => { + isSaving(false); + setErrorSnackMessage(err); + }); + } + + saveRecord(); + } + + }, [ + saving, + groupName, + selectedUsers, + setErrorSnackMessage, + ]); + + //Fetch Actions + const setSaving = (event: React.FormEvent) => { + event.preventDefault(); + + isSaving(true); + }; + + const resetForm = () => { + setGroupName(""); + setSelectedUsers([]); + }; + + + + return ( + + + } + /> + + + + + + + Create Group + + + + + + +
+ + + + + + ) => { + setGroupName(e.target.value); + }} + /> + + + + + + + + + + + + {saving && ( + + + + )} + +
+
+
+ + + + + +
+
+
+
+ ); +}; + +const mapDispatchToProps = { + setErrorSnackMessage, +}; + +const connector = connect(null, mapDispatchToProps); + +export default withStyles(styles)(connector(AddGroupScreen)); diff --git a/portal-ui/src/screens/Console/Groups/Groups.tsx b/portal-ui/src/screens/Console/Groups/Groups.tsx index dadc508f1..02fde4162 100644 --- a/portal-ui/src/screens/Console/Groups/Groups.tsx +++ b/portal-ui/src/screens/Console/Groups/Groups.tsx @@ -36,7 +36,6 @@ import api from "../../../common/api"; import TableWrapper from "../Common/TableWrapper/TableWrapper"; import PageHeader from "../Common/PageHeader/PageHeader"; import HelpBox from "../../../common/HelpBox"; -import history from "../../../history"; import AButton from "../Common/AButton/AButton"; import PageLayout from "../Common/Layout/PageLayout"; import SearchBox from "../Common/SearchBox"; @@ -63,6 +62,7 @@ interface IGroupsProps { classes: any; openGroupModal: any; setErrorSnackMessage: typeof setErrorSnackMessage; + history: any; } const styles = (theme: Theme) => @@ -79,7 +79,7 @@ const styles = (theme: Theme) => ...containerForHeader(theme.spacing(4)), }); -const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => { +const Groups = ({ classes, setErrorSnackMessage, history }: IGroupsProps) => { const [addGroupOpen, setGroupOpen] = useState(false); const [selectedGroup, setSelectedGroup] = useState(null); const [deleteOpen, setDeleteOpen] = useState(false); @@ -232,8 +232,7 @@ const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => { color="primary" icon={} onClick={() => { - setSelectedGroup(null); - setGroupOpen(true); + history.push(`${IAM_PAGES.GROUPS_ADD}`); }} />