diff --git a/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx b/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx
index 27658bf22..d8ee0ac79 100644
--- a/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx
+++ b/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx
@@ -36,11 +36,12 @@ import TableWrapper from "../Common/TableWrapper/TableWrapper";
import SearchBox from "../Common/SearchBox";
import { setModalErrorSnackMessage } from "../../../systemSlice";
import { useAppDispatch } from "../../../store";
+import { setSelectedPolicies } from "../Users/AddUsersSlice";
+
interface ISelectPolicyProps {
classes: any;
selectedPolicy?: string[];
- setSelectedPolicy: any;
}
const styles = (theme: Theme) =>
@@ -77,7 +78,6 @@ const styles = (theme: Theme) =>
const PolicySelectors = ({
classes,
selectedPolicy = [],
- setSelectedPolicy,
}: ISelectPolicyProps) => {
const dispatch = useAppDispatch();
// Local State
@@ -129,7 +129,7 @@ const PolicySelectors = ({
// remove empty values
elements = elements.filter((element) => element !== "");
- setSelectedPolicy(elements);
+ dispatch(setSelectedPolicies(elements));
};
const filteredRecords = records.filter((elementItem) =>
diff --git a/portal-ui/src/screens/Console/Policies/SetPolicy.tsx b/portal-ui/src/screens/Console/Policies/SetPolicy.tsx
index 04b54e1e6..d066d0f6f 100644
--- a/portal-ui/src/screens/Console/Policies/SetPolicy.tsx
+++ b/portal-ui/src/screens/Console/Policies/SetPolicy.tsx
@@ -173,7 +173,6 @@ const SetPolicy = ({
diff --git a/portal-ui/src/screens/Console/Users/AddUserScreen.tsx b/portal-ui/src/screens/Console/Users/AddUserScreen.tsx
index 831c06d18..0744446aa 100644
--- a/portal-ui/src/screens/Console/Users/AddUserScreen.tsx
+++ b/portal-ui/src/screens/Console/Users/AddUserScreen.tsx
@@ -14,13 +14,15 @@
// 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 UserSelector from "./UserSelector";
+import React, { Fragment } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
+import {createUserAsync, resetFormAsync} from "./thunk/AddUsersThunk";
import {
- formFieldStyles,
- modalStyleUtils,
+ formFieldStyles,
+ modalStyleUtils,
} from "../Common/FormComponents/common/styleLibrary";
import Grid from "@mui/material/Grid";
import { Button, LinearProgress } from "@mui/material";
@@ -37,200 +39,180 @@ import GroupsSelectors from "./GroupsSelectors";
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 { useNavigate } from "react-router-dom";
import FormLayout from "../Common/FormLayout";
import AddUserHelpBox from "./AddUserHelpBox";
import { setErrorSnackMessage } from "../../../systemSlice";
-import { useNavigate } from "react-router-dom";
import { useAppDispatch } from "../../../store";
-
+import { useSelector} from "react-redux";
+import {AppState} from "../../../store";
+import {
+ setSelectedGroups,
+ setAddLoading,
+ setShowPassword,
+ setSecretKey,
+ setSendEnabled,
+} from "./AddUsersSlice";
interface IAddUserProps {
- classes: any;
+ classes: any;
}
const styles = (theme: Theme) =>
- createStyles({
- bottomContainer: {
- display: "flex",
- flexGrow: 1,
- alignItems: "center",
- margin: "auto",
- justifyContent: "center",
- "& div": {
- width: 150,
- "@media (max-width: 900px)": {
- flexFlow: "column",
+ createStyles({
+ bottomContainer: {
+ display: "flex",
+ flexGrow: 1,
+ alignItems: "center",
+ margin: "auto",
+ justifyContent: "center",
+ "& div": {
+ width: 150,
+ "@media (max-width: 900px)": {
+ flexFlow: "column",
+ },
+ },
},
- },
- },
- ...formFieldStyles,
- ...modalStyleUtils,
- });
+ ...formFieldStyles,
+ ...modalStyleUtils,
+ });
const AddUser = ({ classes }: IAddUserProps) => {
- const dispatch = useAppDispatch();
- const navigate = useNavigate();
+ const dispatch = useAppDispatch();
+ const showPassword = useSelector(
+ (state: AppState) => state.createUser.showPassword
+ )
+ const selectedPolicies = useSelector(
+ (state: AppState) => state.createUser.selectedPolicies
+ )
+ const selectedGroups = useSelector(
+ (state: AppState) => state.createUser.selectedGroups
+ )
+ const secretKey = useSelector(
+ (state: AppState) => state.createUser.secretKey
+ )
+ const addLoading = useSelector(
+ (state: AppState) => state.createUser.addLoading
+ )
+ const sendEnabled = useSelector(
+ (state: AppState) => state.createUser.sendEnabled
+ )
+ const navigate = useNavigate();
+ dispatch(setSendEnabled());
+ const saveRecord = (event: React.FormEvent) => {
+ event.preventDefault();
+ if (secretKey.length < 8) {
+ dispatch(
+ setErrorSnackMessage({
+ errorMessage: "Passwords must be at least 8 characters long",
+ detailedError: "",
+ })
+ );
+ dispatch(setAddLoading(false));
+ return;
+ }
+ if (addLoading) {
+ return;
+ }
+ dispatch(setAddLoading(true));
+ dispatch(createUserAsync())
+ .unwrap() // <-- async Thunk returns a promise, that can be 'unwrapped')
+ .then(() => navigate(`${IAM_PAGES.USERS}`))
+ };
- 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);
+ return (
+
+
+ } />
+
+ }
+ helpbox={}
+ >
+
+
+
+
+
+ );
};
-export default withStyles(styles)(AddUser);
+export default withStyles(styles)(AddUser);
\ No newline at end of file
diff --git a/portal-ui/src/screens/Console/Users/AddUsersSlice.tsx b/portal-ui/src/screens/Console/Users/AddUsersSlice.tsx
new file mode 100644
index 000000000..4f24c6ee9
--- /dev/null
+++ b/portal-ui/src/screens/Console/Users/AddUsersSlice.tsx
@@ -0,0 +1,105 @@
+// 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 { createSlice, PayloadAction } from "@reduxjs/toolkit";
+import {
+ createUserAsync,
+ resetFormAsync,
+} from "./thunk/AddUsersThunk";
+
+export interface ICreateUser {
+ userName: string;
+ secretKey: string;
+ selectedGroups: string[];
+ selectedPolicies: string[];
+ showPassword: boolean;
+ sendEnabled: boolean;
+ addLoading: boolean;
+ apinoerror: boolean;
+}
+
+const initialState: ICreateUser = {
+ addLoading: false,
+ showPassword: false,
+ sendEnabled: false,
+ apinoerror: false,
+ userName: "",
+ secretKey: "",
+ selectedGroups: [],
+ selectedPolicies: [],
+};
+
+export const createUserSlice = createSlice({
+ name: "createUser",
+ initialState,
+ reducers: {
+ setAddLoading: (state, action: PayloadAction) => {
+ state.addLoading = action.payload;
+ },
+ setUserName: (state, action: PayloadAction) => {
+ state.userName = action.payload;
+ },
+ setSelectedGroups: (state, action: PayloadAction) => {
+ state.selectedGroups = action.payload;
+ },
+ setSecretKey: (state, action: PayloadAction) => {
+ state.secretKey = action.payload;
+ },
+ setSelectedPolicies: (state, action: PayloadAction) => {
+ state.selectedPolicies = action.payload;
+ },
+ setShowPassword: (state, action: PayloadAction) => {
+ state.showPassword = action.payload;
+ },
+ setSendEnabled: (state) => {
+ state.sendEnabled = state.userName.trim() !== "";
+ },
+ setApinoerror: (state, action: PayloadAction) => {
+ state.apinoerror = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(resetFormAsync.fulfilled, (state, action) => {
+ state.userName = "";
+ state.selectedGroups = [];
+ state.secretKey = "";
+ state.selectedPolicies = [];
+ state.showPassword = false;
+ })
+ .addCase(createUserAsync.pending, (state, action) => {
+ state.addLoading = true;
+ })
+ .addCase(createUserAsync.rejected, (state, action) => {
+ state.addLoading = false;
+ })
+ .addCase(createUserAsync.fulfilled, (state, action) => {
+ state.apinoerror = true;
+ });
+ },
+});
+
+export const {
+ setUserName,
+ setSelectedGroups,
+ setSecretKey,
+ setSelectedPolicies,
+ setShowPassword,
+ setAddLoading,
+ setSendEnabled,
+ setApinoerror,
+} = createUserSlice.actions;
+
+export default createUserSlice.reducer;
diff --git a/portal-ui/src/screens/Console/Users/SetUserPolicies.tsx b/portal-ui/src/screens/Console/Users/SetUserPolicies.tsx
index 8ceb6929a..1a1f55783 100644
--- a/portal-ui/src/screens/Console/Users/SetUserPolicies.tsx
+++ b/portal-ui/src/screens/Console/Users/SetUserPolicies.tsx
@@ -110,7 +110,6 @@ const SetUserPolicies = ({
diff --git a/portal-ui/src/screens/Console/Users/UserSelector.tsx b/portal-ui/src/screens/Console/Users/UserSelector.tsx
new file mode 100644
index 000000000..dff953e2e
--- /dev/null
+++ b/portal-ui/src/screens/Console/Users/UserSelector.tsx
@@ -0,0 +1,51 @@
+// 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 } from "react";
+import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
+import { setUserName } from "./AddUsersSlice";
+import { useDispatch, useSelector } from "react-redux";
+import {AppState} from "../../../store";
+
+interface IAddUserProps2 {
+ classes: any;
+}
+
+const UserSelector = ({ classes }: IAddUserProps2 ) => {
+ const dispatch = useDispatch();
+ const userName = useSelector(
+ (state: AppState) => state.createUser.userName
+ )
+ return (
+
+ ) => {
+ dispatch(setUserName(e.target.value));
+ }}
+ />
+
+ );
+};
+export default UserSelector;
diff --git a/portal-ui/src/screens/Console/Users/thunk/AddUsersThunk.tsx b/portal-ui/src/screens/Console/Users/thunk/AddUsersThunk.tsx
new file mode 100644
index 000000000..ee57836cb
--- /dev/null
+++ b/portal-ui/src/screens/Console/Users/thunk/AddUsersThunk.tsx
@@ -0,0 +1,68 @@
+// 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 { createAsyncThunk } from "@reduxjs/toolkit";
+import {
+ setSelectedGroups,
+ setUserName,
+ setSecretKey,
+ setSelectedPolicies,
+ setShowPassword,
+ setAddLoading,
+} from "../AddUsersSlice";
+import {AppState} from "../../../../store";
+import api from "../../../../common/api";
+import {ErrorResponseHandler} from "../../../../common/types";
+import {setErrorSnackMessage} from "../../../../systemSlice";
+import history from "../../../../history";
+import {IAM_PAGES} from "../../../../common/SecureComponent/permissions";
+
+export const resetFormAsync = createAsyncThunk(
+ "resetForm/resetFormAsync",
+ async (_, { dispatch }) => {
+ dispatch(setSelectedGroups([]));
+ dispatch(setUserName(""));
+ dispatch(setSecretKey(""));
+ dispatch(setSelectedPolicies([]));
+ dispatch(setShowPassword(false));
+ }
+);
+
+export const createUserAsync = createAsyncThunk(
+ "createTenant/createNamespaceAsync",
+ async (_, { getState, rejectWithValue, dispatch }) => {
+ const state = getState() as AppState;
+ const accessKey = state.createUser.userName
+ const secretKey = state.createUser.secretKey
+ const selectedGroups = state.createUser.selectedGroups
+ const selectedPolicies = state.createUser.selectedPolicies
+ return api
+ .invoke("POST", "/api/v1/users", {
+ accessKey,
+ secretKey,
+ groups: selectedGroups,
+ policies: selectedPolicies,
+ })
+ .then((res) => {
+ dispatch(setAddLoading(false));
+ history.push(`${IAM_PAGES.USERS}`);
+ })
+ .catch((err: ErrorResponseHandler) => {
+ dispatch(setAddLoading(false));
+ dispatch(setErrorSnackMessage(err));
+ });
+ }
+);
diff --git a/portal-ui/src/store.ts b/portal-ui/src/store.ts
index 3e6d1ebd2..4081a1f08 100644
--- a/portal-ui/src/store.ts
+++ b/portal-ui/src/store.ts
@@ -28,6 +28,7 @@ import objectBrowserReducer from "./screens/Console/ObjectBrowser/objectBrowserS
import tenantsReducer from "./screens/Console/Tenants/tenantsSlice";
import dashboardReducer from "./screens/Console/Dashboard/dashboardSlice";
import createTenantReducer from "./screens/Console/Tenants/AddTenant/createTenantSlice";
+import createUserReducer from "./screens/Console/Users/AddUsersSlice";
import addPoolReducer from "./screens/Console/Tenants/TenantDetails/Pools/AddPool/addPoolSlice";
import editPoolReducer from "./screens/Console/Tenants/TenantDetails/Pools/EditPool/editPoolSlice";
@@ -45,6 +46,7 @@ const rootReducer = combineReducers({
// Operator Reducers
tenants: tenantsReducer,
createTenant: createTenantReducer,
+ createUser: createUserReducer,
addPool: addPoolReducer,
editPool: editPoolReducer,
});