diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts
index daec07d57..0a7e2a1f5 100644
--- a/portal-ui/src/common/SecureComponent/permissions.ts
+++ b/portal-ui/src/common/SecureComponent/permissions.ts
@@ -120,6 +120,7 @@ export const IAM_PAGES = {
IDENTITY: "/identity",
USERS: "/identity/users",
USERS_VIEW: "/identity/users/:userName+",
+ USER_ADD: "/identity/add-user",
GROUPS: "/identity/groups",
GROUPS_ADD: "/identity/create-group",
GROUPS_VIEW: "/identity/groups/:groupName+",
@@ -313,6 +314,8 @@ export const IAM_PAGES_PERMISSIONS = {
IAM_SCOPES.ADMIN_DISABLE_USER,
IAM_SCOPES.ADMIN_DELETE_USER,
],
+ [IAM_PAGES.USER_ADD]: [
+ IAM_SCOPES.ADMIN_CREATE_USER,], // displays create user button
[IAM_PAGES.ACCOUNT_ADD]: [
IAM_SCOPES.ADMIN_CREATE_SERVICEACCOUNT,
],
diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx
index 847b26afe..01935b087 100644
--- a/portal-ui/src/screens/Console/Console.tsx
+++ b/portal-ui/src/screens/Console/Console.tsx
@@ -281,6 +281,10 @@ const Console = ({
component: Users,
path: IAM_PAGES.USERS_VIEW,
},
+ {
+ component: Users,
+ path: IAM_PAGES.USER_ADD,
+ },
{
component: Users,
path: IAM_PAGES.USERS,
diff --git a/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx b/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx
index 62a325222..25224674f 100644
--- a/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx
+++ b/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx
@@ -145,7 +145,7 @@ const PolicySelectors = ({
Assign Policies
{
setFilter(value);
}}
diff --git a/portal-ui/src/screens/Console/Users/AddUser.tsx b/portal-ui/src/screens/Console/Users/AddUser.tsx
deleted file mode 100644
index 9b1f19f4f..000000000
--- a/portal-ui/src/screens/Console/Users/AddUser.tsx
+++ /dev/null
@@ -1,311 +0,0 @@
-// This file is part of MinIO Console Server
-// Copyright (c) 2021 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, { useCallback, useEffect, useState } from "react";
-import { connect } from "react-redux";
-import Grid from "@mui/material/Grid";
-import { Button, LinearProgress, Tab, Tabs } from "@mui/material";
-import { Theme } from "@mui/material/styles";
-import createStyles from "@mui/styles/createStyles";
-import withStyles from "@mui/styles/withStyles";
-import {
- formFieldStyles,
- modalStyleUtils,
- spacingUtils,
-} from "../Common/FormComponents/common/styleLibrary";
-import { User } from "./types";
-import { setModalErrorSnackMessage } from "../../../actions";
-import { ErrorResponseHandler } from "../../../common/types";
-import api from "../../../common/api";
-import GroupsSelectors from "./GroupsSelectors";
-import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
-import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
-import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
-import PredefinedList from "../Common/FormComponents/PredefinedList/PredefinedList";
-import PolicySelectors from "../Policies/PolicySelectors";
-import { TabPanel } from "../../shared/tabs";
-import { CreateUserIcon } from "../../../icons";
-
-const styles = (theme: Theme) =>
- createStyles({
- tabsHeader: {
- marginBottom: "1rem",
- },
- ...modalStyleUtils,
- ...formFieldStyles,
- ...spacingUtils,
- });
-
-interface IAddUserContentProps {
- classes: any;
- closeModalAndRefresh: () => void;
- selectedUser: User | null;
- open: boolean;
- setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
-}
-
-const AddUser = ({
- classes,
- closeModalAndRefresh,
- selectedUser,
- open,
- setModalErrorSnackMessage,
-}: IAddUserContentProps) => {
- const [addLoading, setAddLoading] = useState(false);
- const [accessKey, setAccessKey] = useState("");
- const [secretKey, setSecretKey] = useState("");
- const [enabled, setEnabled] = useState(false);
- const [selectedGroups, setSelectedGroups] = useState([]);
- const [selectedPolicies, setSelectedPolicies] = useState([]);
- const [currentGroups, setCurrentGroups] = useState([]);
- const [currenTab, setCurrenTab] = useState(0);
-
- const getUserInformation = useCallback(() => {
- if (!selectedUser) {
- return null;
- }
-
- api
- .invoke("GET", `/api/v1/user?name=${encodeURI(selectedUser.accessKey)}`)
- .then((res) => {
- setAddLoading(false);
- setAccessKey(res.accessKey);
- setSelectedGroups(res.memberOf || []);
- setCurrentGroups(res.memberOf || []);
- setEnabled(res.status === "enabled");
- })
- .catch((err: ErrorResponseHandler) => {
- setAddLoading(false);
- setModalErrorSnackMessage(err);
- });
- }, [selectedUser, setModalErrorSnackMessage]);
-
- useEffect(() => {
- if (selectedUser === null) {
- setAccessKey("");
- setSecretKey("");
- setSelectedGroups([]);
- } else {
- getUserInformation();
- }
- }, [selectedUser, getUserInformation]);
-
- const saveRecord = (event: React.FormEvent) => {
- event.preventDefault();
-
- if (secretKey.length < 8) {
- setModalErrorSnackMessage({
- errorMessage: "Passwords must be at least 8 characters long",
- detailedError: "",
- });
- setAddLoading(false);
- return;
- }
-
- if (addLoading) {
- return;
- }
- setAddLoading(true);
- if (selectedUser !== null) {
- api
- .invoke(
- "PUT",
- `/api/v1/user?name=${encodeURI(selectedUser.accessKey)}`,
- {
- status: enabled ? "enabled" : "disabled",
- groups: selectedGroups,
- policies: selectedPolicies,
- }
- )
- .then((res) => {
- setAddLoading(false);
- closeModalAndRefresh();
- })
- .catch((err: ErrorResponseHandler) => {
- setAddLoading(false);
- setModalErrorSnackMessage(err);
- });
- } else {
- api
- .invoke("POST", "/api/v1/users", {
- accessKey,
- secretKey,
- groups: selectedGroups,
- policies: selectedPolicies,
- })
- .then((res) => {
- setAddLoading(false);
- closeModalAndRefresh();
- })
- .catch((err: ErrorResponseHandler) => {
- setAddLoading(false);
- setModalErrorSnackMessage(err);
- });
- }
- };
-
- const resetForm = () => {
- if (selectedUser !== null) {
- setSelectedGroups([]);
- return;
- }
- setAccessKey("");
- setSecretKey("");
- setSelectedGroups([]);
- };
-
- const sendEnabled =
- accessKey.trim() !== "" &&
- ((secretKey.trim() !== "" && selectedUser === null) ||
- selectedUser !== null);
- return (
- {
- closeModalAndRefresh();
- }}
- modalOpen={open}
- title={selectedUser !== null ? "Edit User" : "Create User"}
- titleIcon={}
- >
- {selectedUser !== null && (
-
- {
- setEnabled(e.target.checked);
- }}
- switchOnly
- />
-
- )}
-
-
-
-
-
- );
-};
-
-const mapDispatchToProps = {
- setModalErrorSnackMessage,
-};
-
-const connector = connect(null, mapDispatchToProps);
-
-export default withStyles(styles)(connector(AddUser));
diff --git a/portal-ui/src/screens/Console/Users/AddUserHelpBox.tsx b/portal-ui/src/screens/Console/Users/AddUserHelpBox.tsx
new file mode 100644
index 000000000..5fc962482
--- /dev/null
+++ b/portal-ui/src/screens/Console/Users/AddUserHelpBox.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,
+ UsersIcon,
+ ChangeAccessPolicyIcon,
+ GroupsIcon,
+} from "../../../icons";
+
+const FeatureItem = ({
+ icon,
+ description,
+}: {
+ icon: any;
+ description: string;
+}) => {
+ return (
+
+ {icon}{" "}
+
+ {description}
+
+
+ );
+};
+const AddUserHelpBox = ({ hasMargin = true }: { hasMargin?: boolean }) => {
+ return (
+
+
+
+ Learn more about the Users feature
+
+
+ A MinIO user consists of a unique access key (username) and
+ corresponding secret key (password). Clients must authenticate their
+ identity by specifying both a valid access key (username) and the
+ corresponding secret key (password) of an existing MinIO user.
+
+
+ Each user can have one or more assigned policies that explicitly list
+ the actions and resources to which that user has access. Users can also
+ inherit policies from the groups in which they have membership.
+
+
+
+
+ } description={`Create Users`} />
+ } description={`Manage Groups`} />
+ }
+ description={`Assign Policies`}
+ />
+
+
+ );
+};
+
+export default AddUserHelpBox;
diff --git a/portal-ui/src/screens/Console/Users/AddUserScreen.tsx b/portal-ui/src/screens/Console/Users/AddUserScreen.tsx
new file mode 100644
index 000000000..956d317c8
--- /dev/null
+++ b/portal-ui/src/screens/Console/Users/AddUserScreen.tsx
@@ -0,0 +1,304 @@
+// 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 } 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, LinearProgress, Box } from "@mui/material";
+import { CreateUserIcon } from "../../../icons";
+
+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 AddUserHelpBox from "./AddUserHelpBox";
+import PolicySelectors from "../Policies/PolicySelectors";
+import BackLink from "../../../common/BackLink";
+import GroupsSelectors from "./GroupsSelectors";
+import { connect } from "react-redux";
+import { User } from "./types";
+
+import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye";
+import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
+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 IAddUserProps {
+ classes: any;
+ setErrorSnackMessage: typeof setErrorSnackMessage;
+ selectedUser: User | null;
+}
+
+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: 16,
+ paddingLeft: 8,
+ },
+ ...formFieldStyles,
+ ...modalStyleUtils,
+ });
+
+const AddUser = ({
+ classes,
+ setErrorSnackMessage,
+}: IAddUserProps) => {
+ const [addLoading, setAddLoading] = useState(false);
+ const [accessKey, setAccessKey] = useState("");
+ const [secretKey, setSecretKey] = useState("");
+ const [selectedGroups, setSelectedGroups] = useState([]);
+ const [selectedPolicies, setSelectedPolicies] = useState([]);
+ const [showPassword, setShowPassword] = useState(false);
+
+ const sendEnabled = accessKey.trim() !== "";
+
+ const saveRecord = (event: React.FormEvent) => {
+ event.preventDefault();
+
+ if (secretKey.length < 8) {
+ setErrorSnackMessage({
+ errorMessage: "Passwords must be at least 8 characters long",
+ detailedError: "",
+ });
+ setAddLoading(false);
+ return;
+ }
+
+ if (addLoading) {
+ return;
+ }
+ setAddLoading(true);
+ api
+ .invoke("POST", "/api/v1/users", {
+ accessKey,
+ secretKey,
+ groups: selectedGroups,
+ policies: selectedPolicies,
+ })
+ .then((res) => {
+ setAddLoading(false);
+ history.push(`${IAM_PAGES.USERS}`);
+ })
+ .catch((err: ErrorResponseHandler) => {
+ setAddLoading(false);
+ setErrorSnackMessage(err);
+ });
+ };
+
+ const resetForm = () => {
+ setSelectedGroups([]);
+ setAccessKey("");
+ setSecretKey("");
+ setSelectedPolicies([]);
+ setShowPassword(false);
+ };
+
+ return (
+
+
+ } />
+
+
+
+
+
+
+ Create User
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const mapDispatchToProps = {
+ setErrorSnackMessage,
+};
+
+const connector = connect(null, mapDispatchToProps);
+
+export default withStyles(styles)(connector(AddUser));
diff --git a/portal-ui/src/screens/Console/Users/GroupsSelectors.tsx b/portal-ui/src/screens/Console/Users/GroupsSelectors.tsx
index f112113e0..7bbb363c3 100644
--- a/portal-ui/src/screens/Console/Users/GroupsSelectors.tsx
+++ b/portal-ui/src/screens/Console/Users/GroupsSelectors.tsx
@@ -145,7 +145,7 @@ const GroupsSelectors = ({
import("./AddUser")));
const SetPolicy = withSuspense(
React.lazy(() => import("../Policies/SetPolicy"))
);
@@ -83,8 +82,6 @@ interface IUsersProps {
const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
const [records, setRecords] = useState([]);
const [loading, setLoading] = useState(true);
- const [addScreenOpen, setAddScreenOpen] = useState(false);
-
const [deleteOpen, setDeleteOpen] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
const [addGroupOpen, setAddGroupOpen] = useState(false);
@@ -108,12 +105,7 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP,
]);
- const closeAddModalAndRefresh = () => {
- setAddScreenOpen(false);
- setLoading(true);
- };
-
- const closeDeleteModalAndRefresh = (refresh: boolean) => {
+ const closeDeleteModalAndRefresh = (refresh: boolean) => {
setDeleteOpen(false);
if (refresh) {
setLoading(true);
@@ -201,15 +193,6 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
return (
- {addScreenOpen && (
- {
- closeAddModalAndRefresh();
- }}
- />
- )}
{policyOpen && (
{
icon={}
color="primary"
onClick={() => {
- setAddScreenOpen(true);
- setSelectedUser(null);
+ history.push(`${IAM_PAGES.USER_ADD}`);
}}
variant={"contained"}
/>
@@ -389,8 +371,7 @@ const ListUsers = ({ classes, setErrorSnackMessage, history }: IUsersProps) => {
To get started,{" "}
{
- setAddScreenOpen(true);
- setSelectedUser(null);
+ history.push(`${IAM_PAGES.USER_ADD}`);
}}
>
Create a User
diff --git a/portal-ui/src/screens/Console/Users/Users.tsx b/portal-ui/src/screens/Console/Users/Users.tsx
index 1749a861e..ef92bf5f0 100644
--- a/portal-ui/src/screens/Console/Users/Users.tsx
+++ b/portal-ui/src/screens/Console/Users/Users.tsx
@@ -25,6 +25,7 @@ import NotFoundPage from "../../NotFoundPage";
import ListUsers from "./ListUsers";
import UserDetails from "./UserDetails";
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
+import AddUserScreen from "./AddUserScreen";
const mapState = (state: AppState) => ({
open: state.system.sidebarOpen,
@@ -38,6 +39,7 @@ const Users = () => {
+