Migrated Users Module components to mds (#2920)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2023-07-05 18:21:07 -06:00
committed by GitHub
parent b31aa10b52
commit 542b7192c3
21 changed files with 697 additions and 1134 deletions

View File

@@ -30,23 +30,20 @@ import {
ServiceAccountIcon,
} from "mds";
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
import AddServiceAccountHelpBox from "./AddServiceAccountHelpBox";
import { NewServiceAccount } from "../Common/CredentialsPrompt/types";
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt";
import PanelTitle from "../Common/PanelTitle/PanelTitle";
import { setErrorSnackMessage, setHelpName } from "../../../systemSlice";
import { useAppDispatch } from "../../../store";
import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
import { getRandomString } from "../../../common/utils";
import { api } from "api";
import { errorToHandler } from "api/errors";
import HelpMenu from "../HelpMenu";
import { ContentType } from "api/consoleApi";
import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
import AddServiceAccountHelpBox from "./AddServiceAccountHelpBox";
import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt";
import PanelTitle from "../Common/PanelTitle/PanelTitle";
import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
import HelpMenu from "../HelpMenu";
const AddServiceAccount = () => {
const dispatch = useAppDispatch();
@@ -123,7 +120,7 @@ const AddServiceAccount = () => {
{newServiceAccount !== null && (
<CredentialsPrompt
newServiceAccount={newServiceAccount}
open={newServiceAccount !== null}
open={true}
closeModal={() => {
closeCredentialsModal();
}}

View File

@@ -15,47 +15,23 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useState } from "react";
import { Button, ChangePasswordIcon } from "mds";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import Grid from "@mui/material/Grid";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import { Box, Button, ChangePasswordIcon, FormLayout, InputBox } from "mds";
import { LinearProgress } from "@mui/material";
import {
containerForHeader,
formFieldStyles,
modalStyleUtils,
spacingUtils,
} from "../Common/FormComponents/common/styleLibrary";
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
import { setModalErrorSnackMessage } from "../../../systemSlice";
import { useAppDispatch } from "../../../store";
import { api } from "api";
import { ChangeUserPasswordRequest } from "api/consoleApi";
import { errorToHandler } from "api/errors";
const styles = (theme: Theme) =>
createStyles({
buttonContainer: {
textAlign: "right",
},
...modalStyleUtils,
...formFieldStyles,
...spacingUtils,
...containerForHeader,
});
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
interface IChangeUserPasswordProps {
classes: any;
open: boolean;
userName: string;
closeModal: () => void;
}
const ChangeUserPassword = ({
classes,
open,
userName,
closeModal,
@@ -123,37 +99,31 @@ const ChangeUserPassword = ({
changeUserPassword(e);
}}
>
<Grid container>
<Grid item xs={12} className={classes.modalFormScrollable}>
<div className={classes.spacerBottom}>
Change password for: {userName}
</div>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
id="new-password"
name="new-password"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setNewPassword(event.target.value);
}}
label="New Password"
type="password"
value={newPassword}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
id="re-new-password"
name="re-new-password"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setReNewPassword(event.target.value);
}}
label="Type New Password Again"
type="password"
value={reNewPassword}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<FormLayout withBorders={false} containerPadding={false}>
<Box sx={{ margin: "10px 0 20px" }}>
Change password for: <strong>{userName}</strong>
</Box>
<InputBox
id="new-password"
name="new-password"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setNewPassword(event.target.value);
}}
label="New Password"
type="password"
value={newPassword}
/>
<InputBox
id="re-new-password"
name="re-new-password"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setReNewPassword(event.target.value);
}}
label="Type New Password Again"
type="password"
value={reNewPassword}
/>
<Box sx={modalStyleUtils.modalButtonBar}>
<Button
id={"save-user-password"}
type="submit"
@@ -165,16 +135,16 @@ const ChangeUserPassword = ({
}
label={"Save"}
/>
</Grid>
</Box>
{loading && (
<Grid item xs={12}>
<Box>
<LinearProgress />
</Grid>
</Box>
)}
</Grid>
</FormLayout>
</form>
</ModalWrapper>
) : null;
};
export default withStyles(styles)(ChangeUserPassword);
export default ChangeUserPassword;

View File

@@ -714,33 +714,6 @@ export const commonDashboardInfocard: any = {
},
};
export const serviceAccountStyles: any = {
buttonContainer: {
display: "flex",
justifyContent: "flex-end",
},
codeMirrorContainer: {
marginBottom: 20,
paddingLeft: 15,
"& label": {
marginBottom: ".5rem",
},
"& label + div": {
display: "none",
},
},
stackedInputs: {
display: "flex",
gap: 15,
paddingBottom: "1rem",
paddingLeft: "1rem",
flexFlow: "column",
},
buttonSpacer: {
marginRight: "1rem",
},
};
export const tableStyles: any = {
tableBlock: {
display: "flex",

View File

@@ -24,6 +24,7 @@ type SearchBoxProps = {
onChange: (value: string) => void;
overrideClass?: any;
id?: string;
label?: string;
sx?: CSSObject;
};
@@ -33,6 +34,7 @@ const SearchBox = ({
overrideClass,
value,
id = "search-resource",
label = "",
sx,
}: SearchBoxProps) => {
return (
@@ -40,7 +42,7 @@ const SearchBox = ({
placeholder={placeholder}
className={overrideClass ? overrideClass : ""}
id={id}
label=""
label={label}
onChange={(e) => {
onChange(e.target.value);
}}

View File

@@ -14,23 +14,11 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useCallback, useEffect, useState } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { LinearProgress } from "@mui/material";
import Grid from "@mui/material/Grid";
import { DataTable, Grid, Box } from "mds";
import { policySort } from "../../../utils/sortFunctions";
import {
actionsTray,
searchField,
selectorsCommon,
tableStyles,
} from "../Common/FormComponents/common/styleLibrary";
import { ErrorResponseHandler } from "../../../common/types";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import SearchBox from "../Common/SearchBox";
import { setModalErrorSnackMessage } from "../../../systemSlice";
import { AppState, useAppDispatch } from "../../../store";
@@ -38,49 +26,17 @@ import { setSelectedPolicies } from "../Users/AddUsersSlice";
import { useSelector } from "react-redux";
import { api } from "../../../api";
import {
Error,
HttpResponse,
ListPoliciesResponse,
Error,
} from "../../../api/consoleApi";
interface ISelectPolicyProps {
classes: any;
selectedPolicy?: string[];
noTitle?: boolean;
}
const styles = (theme: Theme) =>
createStyles({
noFound: {
textAlign: "center",
padding: "10px 0",
},
searchBox: {
flex: 1,
},
fieldLabel: {
fontWeight: 400,
width: 160,
marginRight: 10,
},
tableBlock: {
...tableStyles.tableBlock,
},
filterBox: {
display: "flex",
marginBottom: 15,
alignItems: "center",
"& span": {
fontSize: 14,
},
},
...searchField,
...tableStyles,
...actionsTray,
...selectorsCommon,
});
const PolicySelectors = ({ classes, noTitle = false }: ISelectPolicyProps) => {
const PolicySelectors = ({ noTitle = false }: ISelectPolicyProps) => {
const dispatch = useAppDispatch();
// Local State
const [records, setRecords] = useState<any[]>([]);
@@ -143,49 +99,44 @@ const PolicySelectors = ({ classes, noTitle = false }: ISelectPolicyProps) => {
);
return (
<Grid container>
<Grid item xs={12}>
{loading && <LinearProgress />}
{records.length > 0 ? (
<React.Fragment>
<Grid item xs={12} className={classes.filterBox}>
{!noTitle && (
<span className={classes.fieldLabel}>Assign Policies</span>
)}
<div className={classes.searchBox}>
<SearchBox
placeholder="Start typing to search for a Policy"
onChange={(value) => {
setFilter(value);
}}
value={filter}
/>
</div>
</Grid>
<Grid
item
xs={12}
className={classes.tableBlock}
style={{ paddingBottom: 16 }}
>
<TableWrapper
columns={[{ label: "Policy", elementKey: "name" }]}
onSelect={selectionChanged}
selectedItems={currentPolicies}
isLoading={loading}
records={filteredRecords}
entityName="Policies"
idField="name"
customPaperHeight={classes.multiSelectTable}
/>
</Grid>
</React.Fragment>
) : (
<div className={classes.noFound}>No Policies Available</div>
)}
</Grid>
<Grid item xs={12} className={"inputItem"}>
{loading && <LinearProgress />}
{records.length > 0 ? (
<Fragment>
<Grid item xs={12} className={"inputItem"}>
<SearchBox
placeholder="Start typing to search for a Policy"
onChange={(value) => {
setFilter(value);
}}
value={filter}
label={!noTitle ? "Assign Policies" : ""}
/>
</Grid>
<DataTable
columns={[{ label: "Policy", elementKey: "name" }]}
onSelect={selectionChanged}
selectedItems={currentPolicies}
isLoading={loading}
records={filteredRecords}
entityName="Policies"
idField="name"
customPaperHeight={"200px"}
/>
</Fragment>
) : (
<Box
sx={{
textAlign: "center",
padding: "10px 0",
}}
>
No Policies Available
</Box>
)}
</Grid>
);
};
export default withStyles(styles)(PolicySelectors);
export default PolicySelectors;

View File

@@ -15,27 +15,19 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect } from "react";
import { Theme } from "@mui/material/styles";
import { BackLink, Button, CreateUserIcon, FormLayout, PageLayout } from "mds";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import UserSelector from "./UserSelector";
import PasswordSelector from "./PasswordSelector";
import { createUserAsync, resetFormAsync } from "./thunk/AddUsersThunk";
import {
formFieldStyles,
modalStyleUtils,
} from "../Common/FormComponents/common/styleLibrary";
import Grid from "@mui/material/Grid";
BackLink,
Button,
CreateUserIcon,
FormLayout,
Grid,
PageLayout,
} from "mds";
import { createUserAsync, resetFormAsync } from "./thunk/AddUsersThunk";
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
import { LinearProgress } from "@mui/material";
import PolicySelectors from "../Policies/PolicySelectors";
import GroupsSelectors from "./GroupsSelectors";
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
import { useNavigate } from "react-router-dom";
import AddUserHelpBox from "./AddUserHelpBox";
import { setErrorSnackMessage, setHelpName } from "../../../systemSlice";
import { AppState, useAppDispatch } from "../../../store";
import { useSelector } from "react-redux";
@@ -44,20 +36,15 @@ import {
setSelectedGroups,
setSendEnabled,
} from "./AddUsersSlice";
import AddUserHelpBox from "./AddUserHelpBox";
import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
import HelpMenu from "../HelpMenu";
import PolicySelectors from "../Policies/PolicySelectors";
import UserSelector from "./UserSelector";
import PasswordSelector from "./PasswordSelector";
import GroupsSelectors from "./GroupsSelectors";
interface IAddUserProps {
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
...formFieldStyles,
...modalStyleUtils,
});
const AddUser = ({ classes }: IAddUserProps) => {
const AddUser = () => {
const dispatch = useAppDispatch();
const selectedPolicies = useSelector(
(state: AppState) => state.createUser.selectedPolicies
@@ -128,55 +115,40 @@ const AddUser = ({ classes }: IAddUserProps) => {
saveRecord(e);
}}
>
<Grid container>
<UserSelector />
<PasswordSelector />
<PolicySelectors selectedPolicy={selectedPolicies} />
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={(elements: string[]) => {
dispatch(setSelectedGroups(elements));
}}
/>
{addLoading && (
<Grid item xs={12}>
<div className={classes.formFieldRow}>
<UserSelector classes={classes} />
</div>
</Grid>
<Grid item xs={12}>
<div className={classes.formFieldRow}>
<PasswordSelector classes={classes} />
</div>
<LinearProgress />
</Grid>
)}
<Grid item xs={12}>
<PolicySelectors selectedPolicy={selectedPolicies} />
</Grid>
<Grid item xs={12}>
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={(elements: string[]) => {
dispatch(setSelectedGroups(elements));
}}
/>
</Grid>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
<Grid item xs={12} sx={modalStyleUtils.modalButtonBar}>
<Button
id={"clear-add-user"}
type="button"
variant="regular"
onClick={(e) => {
dispatch(resetFormAsync());
}}
label={"Clear"}
/>
<Grid item xs={12} className={classes.modalButtonBar}>
<Button
id={"clear-add-user"}
type="button"
variant="regular"
onClick={(e) => {
dispatch(resetFormAsync());
}}
label={"Clear"}
/>
<Button
id={"save-user"}
type="submit"
variant="callAction"
color="primary"
disabled={addLoading || !sendEnabled}
label={"Save"}
/>
</Grid>
<Button
id={"save-user"}
type="submit"
variant="callAction"
color="primary"
disabled={addLoading || !sendEnabled}
label={"Save"}
/>
</Grid>
</form>
</FormLayout>
@@ -186,4 +158,4 @@ const AddUser = ({ classes }: IAddUserProps) => {
);
};
export default withStyles(styles)(AddUser);
export default AddUser;

View File

@@ -1,49 +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 <http://www.gnu.org/licenses/>.
import React, { Fragment } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import {
modalStyleUtils,
serviceAccountStyles,
} from "../Common/FormComponents/common/styleLibrary";
import { NewServiceAccount } from "../Common/CredentialsPrompt/types";
const styles = (theme: Theme) =>
createStyles({
...serviceAccountStyles,
...modalStyleUtils,
});
interface IAddUserServiceAccountProps {
classes: any;
open: boolean;
user: string;
closeModalAndRefresh: (res: NewServiceAccount | null) => void;
}
const AddUserServiceAccount = ({
classes,
open,
closeModalAndRefresh,
user,
}: IAddUserServiceAccountProps) => {
return <Fragment />;
};
export default withStyles(styles)(AddUserServiceAccount);

View File

@@ -15,39 +15,24 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import { Theme } from "@mui/material/styles";
import { useNavigate, useParams } from "react-router-dom";
import {
BackLink,
Box,
Button,
IAMPoliciesIcon,
FormLayout,
Grid,
InputBox,
PageLayout,
PasswordKeyIcon,
ServiceAccountCredentialsIcon,
ServiceAccountIcon,
Switch,
} from "mds";
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 { Box } from "@mui/material";
import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
import { NewServiceAccount } from "../Common/CredentialsPrompt/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 CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt";
import SectionTitle from "../Common/SectionTitle";
import AddUserServiceAccountHelpBox from "./AddUserServiceAccountHelpBox";
import { ErrorResponseHandler } from "../../../common/types";
import {
decodeURLString,
encodeURLString,
@@ -55,20 +40,15 @@ import {
} from "../../../common/utils";
import { setErrorSnackMessage, setHelpName } from "../../../systemSlice";
import { useAppDispatch } from "../../../store";
import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
import api from "../../../../src/common/api";
import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt";
import AddUserServiceAccountHelpBox from "./AddUserServiceAccountHelpBox";
import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
import HelpMenu from "../HelpMenu";
import PanelTitle from "../Common/PanelTitle/PanelTitle";
interface IAddServiceAccountProps {
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
...formFieldStyles,
...modalStyleUtils,
});
const AddServiceAccount = ({ classes }: IAddServiceAccountProps) => {
const AddServiceAccount = () => {
const dispatch = useAppDispatch();
const params = useParams();
const navigate = useNavigate();
@@ -80,7 +60,6 @@ const AddServiceAccount = ({ classes }: IAddServiceAccountProps) => {
useState<boolean>(false);
const [newServiceAccount, setNewServiceAccount] =
useState<NewServiceAccount | null>(null);
const [showPassword, setShowPassword] = useState<boolean>(false);
const [policyJSON, setPolicyJSON] = useState<string>("");
const userName = decodeURLString(params.userName || "");
@@ -145,7 +124,6 @@ const AddServiceAccount = ({ classes }: IAddServiceAccountProps) => {
setNewServiceAccount(null);
setAccessKey("");
setSecretKey("");
setShowPassword(false);
};
const closeCredentialsModal = () => {
@@ -183,152 +161,97 @@ const AddServiceAccount = ({ classes }: IAddServiceAccountProps) => {
actions={<HelpMenu />}
/>
<PageLayout>
<Box
sx={{
display: "grid",
padding: "25px",
gap: "25px",
gridTemplateColumns: {
md: "2fr 1.2fr",
xs: "1fr",
},
border: "1px solid #eaeaea",
}}
<FormLayout
helpBox={<AddUserServiceAccountHelpBox />}
icon={<ServiceAccountCredentialsIcon />}
title={`Create Access Key for ${userName}`}
>
<Box>
<SectionTitle icon={<ServiceAccountCredentialsIcon />}>
{`Create Access Key for ${userName}`}
</SectionTitle>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
addUserServiceAccount(e);
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
addUserServiceAccount(e);
}}
>
<InputBox
value={accessKey}
label={"Access Key"}
id={"accessKey"}
name={"accessKey"}
placeholder={"Enter Access Key"}
onChange={(e) => {
setAccessKey(e.target.value);
}}
>
<Grid container item spacing="20" sx={{ marginTop: 1 }}>
<Grid item xs={12}>
<Grid container item spacing="20">
<Grid item xs={12}>
<Grid container>
<Grid item xs={1}>
<PasswordKeyIcon />
</Grid>
<Grid item>
<Grid container item spacing="20">
<Grid item xs={12}>
{" "}
<div className={classes.stackedInputs}>
<InputBoxWrapper
value={accessKey}
label={"Access Key"}
id={"accessKey"}
name={"accessKey"}
placeholder={"Enter Access Key"}
onChange={(e) => {
setAccessKey(e.target.value);
}}
/>
</div>
</Grid>
<Grid item xs={12}>
<div className={classes.stackedInputs}>
<InputBoxWrapper
value={secretKey}
label={"Secret Key"}
id={"secretKey"}
name={"secretKey"}
type={showPassword ? "text" : "password"}
placeholder={"Enter Secret Key"}
onChange={(e) => {
setSecretKey(e.target.value);
}}
overlayIcon={
showPassword ? (
<VisibilityOffIcon />
) : (
<RemoveRedEyeIcon />
)
}
overlayAction={() =>
setShowPassword(!showPassword)
}
/>
</div>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
<Grid container item spacing="20">
<Grid item xs={12}>
<Grid container>
<Grid item xs={1}>
<IAMPoliciesIcon />
</Grid>
<Grid item xs={11}>
<FormSwitchWrapper
value="serviceAccountPolicy"
id="serviceAccountPolicy"
name="serviceAccountPolicy"
checked={isRestrictedByPolicy}
onChange={(
event: React.ChangeEvent<HTMLInputElement>
) => {
setIsRestrictedByPolicy(event.target.checked);
}}
label={"Restrict beyond user policy"}
tooltip={
"You can specify an optional JSON-formatted IAM policy to further restrict Access Key access to a subset of the actions and resources explicitly allowed for the parent user. Additional access beyond that of the parent user cannot be implemented through these policies."
}
/>
</Grid>
</Grid>
</Grid>
{isRestrictedByPolicy && (
<Grid
item
xs={12}
className={classes.codeMirrorContainer}
>
<Grid item xs={12} className={classes.formScrollable}>
<CodeMirrorWrapper
label={"Policy"}
value={policyJSON}
onChange={(value) => {
setPolicyJSON(value);
}}
/>
</Grid>
</Grid>
)}
</Grid>
<Grid item xs={12} className={classes.modalButtonBar}>
<Button
id={"clear-add-sa"}
type="button"
variant="regular"
onClick={resetForm}
label={"Clear"}
/>
<Button
id="create-sa"
type="submit"
variant="callAction"
label={"Create"}
startIcon={<ServiceAccountIcon />}
/>
<InputBox
value={secretKey}
label={"Secret Key"}
id={"secretKey"}
name={"secretKey"}
type={"password"}
placeholder={"Enter Secret Key"}
onChange={(e) => {
setSecretKey(e.target.value);
}}
startIcon={<PasswordKeyIcon />}
/>
<Switch
value="serviceAccountPolicy"
id="serviceAccountPolicy"
name="serviceAccountPolicy"
checked={isRestrictedByPolicy}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setIsRestrictedByPolicy(event.target.checked);
}}
label={"Restrict beyond user policy"}
description={
"You can specify an optional JSON-formatted IAM policy to further restrict Access Key access to a subset of the actions and resources explicitly allowed for the parent user. Additional access beyond that of the parent user cannot be implemented through these policies."
}
/>
{isRestrictedByPolicy && (
<Grid item xs={12}>
<Box>
<PanelTitle>
Current User Policy - edit the JSON to remove permissions
for this Access Key
</PanelTitle>
</Box>
<Grid item xs={12} sx={{ ...modalStyleUtils.formScrollable }}>
<CodeMirrorWrapper
value={policyJSON}
onChange={(value) => {
setPolicyJSON(value);
}}
editorHeight={"350px"}
/>
</Grid>
</Grid>
</form>
</Box>
<AddUserServiceAccountHelpBox />
</Box>
)}
<Grid item xs={12} sx={{ ...modalStyleUtils.modalButtonBar }}>
<Button
id={"clear"}
type="button"
variant="regular"
onClick={resetForm}
label={"Clear"}
/>
<Button
id={"create-sa"}
type="submit"
variant="callAction"
color="primary"
label={"Create"}
/>
</Grid>
</form>
</FormLayout>
</PageLayout>
</Grid>
</Fragment>
);
};
export default withStyles(styles)(AddServiceAccount);
export default AddServiceAccount;

View File

@@ -15,21 +15,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import { AddMembersToGroupIcon, Button } from "mds";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { AddMembersToGroupIcon, Button, FormLayout, Grid, ReadBox } from "mds";
import { LinearProgress } from "@mui/material";
import Grid from "@mui/material/Grid";
import {
formFieldStyles,
modalStyleUtils,
} from "../Common/FormComponents/common/styleLibrary";
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
import { ErrorResponseHandler } from "../../../common/types";
import api from "../../../common/api";
import GroupsSelectors from "./GroupsSelectors";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import PredefinedList from "../Common/FormComponents/PredefinedList/PredefinedList";
import { setModalErrorSnackMessage } from "../../../systemSlice";
import { useAppDispatch } from "../../../store";
@@ -37,20 +29,12 @@ interface IAddToGroup {
open: boolean;
checkedUsers: any;
closeModalAndRefresh: any;
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
...modalStyleUtils,
...formFieldStyles,
});
const BulkAddToGroup = ({
open,
checkedUsers,
closeModalAndRefresh,
classes,
}: IAddToGroup) => {
const dispatch = useAppDispatch();
//Local States
@@ -120,61 +104,57 @@ const BulkAddToGroup = ({
>
{accepted ? (
<React.Fragment>
<Grid container>
<PredefinedList
label={"Groups"}
content={selectedGroups.join(", ")}
/>
<PredefinedList label={"Users"} content={checkedUsers.join(", ")} />
</Grid>
<br />
<br />
<br />
<FormLayout
withBorders={false}
containerPadding={false}
sx={{ margin: "30px 0" }}
>
<ReadBox label={"Groups"} sx={{ width: "100%" }}>
{selectedGroups.join(", ")}
</ReadBox>
<ReadBox label={"Users"} sx={{ width: "100%" }}>
{" "}
{checkedUsers.join(", ")}{" "}
</ReadBox>
</FormLayout>
</React.Fragment>
) : (
<form noValidate autoComplete="off" onSubmit={setSaving}>
<Grid container>
<Grid item xs={12} className={classes.modalFormScrollable}>
<Grid item xs={12} className={classes.formFieldRow}>
<PredefinedList
label={"Selected Users"}
content={checkedUsers.join(", ")}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={setSelectedGroups}
/>
</Grid>
</Grid>
<Grid item xs={12} className={classes.modalButtonBar}>
<Button
id={"clear-bulk-add-group"}
type="button"
variant="regular"
color="primary"
onClick={resetForm}
label={"Clear"}
/>
<Button
id={"save-add-group"}
type="submit"
variant="callAction"
disabled={saving || selectedGroups.length < 1}
label={"Save"}
/>
</Grid>
{saving && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
<FormLayout withBorders={false} containerPadding={false}>
<ReadBox label={"Selected Users"} sx={{ width: "100%" }}>
{checkedUsers.join(", ")}
</ReadBox>
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={setSelectedGroups}
/>
</FormLayout>
<Grid item xs={12} sx={modalStyleUtils.modalButtonBar}>
<Button
id={"clear-bulk-add-group"}
type="button"
variant="regular"
color="primary"
onClick={resetForm}
label={"Clear"}
/>
<Button
id={"save-add-group"}
type="submit"
variant="callAction"
disabled={saving || selectedGroups.length < 1}
label={"Save"}
/>
</Grid>
{saving && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</form>
)}
</ModalWrapper>
);
};
export default withStyles(styles)(BulkAddToGroup);
export default BulkAddToGroup;

View File

@@ -14,41 +14,25 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useCallback, useEffect, useState } from "react";
import Grid from "@mui/material/Grid";
import React, { useCallback, useEffect, useState, Fragment } from "react";
import { LinearProgress } from "@mui/material";
import { Theme } from "@mui/material/styles";
import { AddMembersToGroupIcon, Button } from "mds";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import {
modalBasic,
spacingUtils,
} from "../Common/FormComponents/common/styleLibrary";
import { AddMembersToGroupIcon, Button, FormLayout, Grid, Box } from "mds";
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
import { ErrorResponseHandler } from "../../../common/types";
import api from "../../../common/api";
import GroupsSelectors from "./GroupsSelectors";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import { encodeURLString } from "../../../common/utils";
import { setModalErrorSnackMessage } from "../../../systemSlice";
import { useAppDispatch } from "../../../store";
import Box from "@mui/material/Box";
const styles = (theme: Theme) =>
createStyles({
...spacingUtils,
...modalBasic,
});
import api from "../../../common/api";
import GroupsSelectors from "./GroupsSelectors";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
interface IChangeUserGroupsContentProps {
classes: any;
closeModalAndRefresh: () => void;
selectedUser: string;
open: boolean;
}
const ChangeUserGroups = ({
classes,
closeModalAndRefresh,
selectedUser,
open,
@@ -151,7 +135,7 @@ const ChangeUserGroups = ({
title={"Set Groups"}
titleIcon={<AddMembersToGroupIcon />}
>
<React.Fragment>
<Fragment>
<form
noValidate
autoComplete="off"
@@ -159,50 +143,40 @@ const ChangeUserGroups = ({
saveRecord(e);
}}
>
<Grid container>
<Grid item xs={12} className={classes.formScrollable}>
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={(elements: string[]) => {
setSelectedGroups(elements);
}}
/>
</Grid>
<Box
sx={{
display: "flex",
gap: 2,
alignItems: "center",
justifyContent: "flex-end",
width: "100%",
<FormLayout withBorders={false} containerPadding={false}>
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={(elements: string[]) => {
setSelectedGroups(elements);
}}
>
<Button
id={"clear-change-user-groups"}
type="button"
variant="regular"
onClick={resetForm}
label={"Clear"}
/>
/>
</FormLayout>
<Box sx={modalStyleUtils.modalButtonBar}>
<Button
id={"clear-change-user-groups"}
type="button"
variant="regular"
onClick={resetForm}
label={"Clear"}
/>
<Button
id={"save-user-groups"}
type="submit"
variant="callAction"
disabled={addLoading || !sendEnabled}
label={"Save"}
/>
</Box>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</Grid>
<Button
id={"save-user-groups"}
type="submit"
variant="callAction"
disabled={addLoading || !sendEnabled}
label={"Save"}
/>
</Box>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</form>
</React.Fragment>
</Fragment>
</ModalWrapper>
);
};
export default withStyles(styles)(ChangeUserGroups);
export default ChangeUserGroups;

View File

@@ -15,37 +15,35 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux";
import { DialogContentText } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { setErrorSnackMessage } from "../../../systemSlice";
import { ErrorResponseHandler } from "../../../common/types";
import { ConfirmDeleteIcon, Loader } from "mds";
import { ConfirmDeleteIcon, Loader, DataTable } from "mds";
import { encodeURLString } from "../../../common/utils";
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
import useApi from "../Common/Hooks/useApi";
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
import WarningMessage from "../Common/WarningMessage/WarningMessage";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import api from "../../../common/api";
import { useAppDispatch } from "../../../store";
interface IDeleteUserProps {
closeDeleteModalAndRefresh: (refresh: boolean) => void;
deleteOpen: boolean;
selectedUsers: string[] | null;
setErrorSnackMessage: typeof setErrorSnackMessage;
}
const DeleteUser = ({
closeDeleteModalAndRefresh,
deleteOpen,
selectedUsers,
setErrorSnackMessage,
}: IDeleteUserProps) => {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const onDelSuccess = () => closeDeleteModalAndRefresh(true);
const onDelError = (err: ErrorResponseHandler) => setErrorSnackMessage(err);
const onDelError = (err: ErrorResponseHandler) =>
dispatch(setErrorSnackMessage(err));
const onClose = () => closeDeleteModalAndRefresh(false);
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
@@ -67,11 +65,11 @@ const DeleteUser = ({
setLoadingSA(false);
})
.catch((err: ErrorResponseHandler) => {
setErrorSnackMessage(err);
dispatch(setErrorSnackMessage(err));
setLoadingSA(false);
});
}
}, [selectedUsers, setErrorSnackMessage]);
}, [selectedUsers, dispatch]);
if (!selectedUsers) {
return null;
@@ -97,10 +95,12 @@ const DeleteUser = ({
const onConfirmDelete = () => {
for (let user of selectedUsers) {
if (user === userLoggedIn) {
setErrorSnackMessage({
errorMessage: "Cannot delete currently logged in user",
detailedError: `Cannot delete currently logged in user ${userLoggedIn}`,
});
dispatch(
setErrorSnackMessage({
errorMessage: "Cannot delete currently logged in user",
detailedError: `Cannot delete currently logged in user ${userLoggedIn}`,
})
);
closeDeleteModalAndRefresh(true);
} else {
invokeDeleteApi("DELETE", `/api/v1/user/${encodeURLString(user)}`);
@@ -122,9 +122,7 @@ const DeleteUser = ({
"user" +
(selectedUsers.length > 1 ? "s?" : "?");
return loadingSA ? (
<Loader />
) : (
return (
<ConfirmDialog
title={`Delete User${selectedUsers.length > 1 ? "s" : ""}`}
confirmText={"Delete"}
@@ -134,45 +132,43 @@ const DeleteUser = ({
onConfirm={onConfirmDelete}
onClose={onClose}
confirmationContent={
<DialogContentText>
{hasSA ? (
<Fragment>
<WarningMessage
label="Click on a user to view the full listing of asociated Access Keys. All Access Keys associated with a user will be deleted along with the user. Are you sure you want to continue?"
title="Warning: One or more users selected has associated Access Keys. "
/>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "Username", elementKey: "userName" },
{
label: "# Associated Access Keys",
elementKey: "numSAs",
},
]}
isLoading={loadingSA}
records={userSAList}
entityName="User Access Keys"
idField="userName"
customPaperHeight="250"
/>
</Fragment>
) : (
<Fragment>
{noSAtext}
{renderUsers}
</Fragment>
)}
</DialogContentText>
loadingSA ? (
<Loader />
) : (
<Fragment>
{hasSA ? (
<Fragment>
<WarningMessage
label="Click on a user to view the full listing of asociated Access Keys. All Access Keys associated with a user will be deleted along with the user. Are you sure you want to continue?"
title="Warning: One or more users selected has associated Access Keys. "
/>
<DataTable
itemActions={tableActions}
columns={[
{ label: "Username", elementKey: "userName" },
{
label: "# Associated Access Keys",
elementKey: "numSAs",
},
]}
isLoading={loadingSA}
records={userSAList}
entityName="User Access Keys"
idField="userName"
customPaperHeight="250"
/>
</Fragment>
) : (
<Fragment>
{noSAtext}
{renderUsers}
</Fragment>
)}
</Fragment>
)
}
/>
);
};
const mapDispatchToProps = {
setErrorSnackMessage,
};
const connector = connect(null, mapDispatchToProps);
export default connector(DeleteUser);
export default DeleteUser;

View File

@@ -14,63 +14,24 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useCallback, useEffect, useState } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { LinearProgress } from "@mui/material";
import React, { useCallback, useEffect, useState, Fragment } from "react";
import get from "lodash/get";
import Grid from "@mui/material/Grid";
import { LinearProgress } from "@mui/material";
import { Box, DataTable, Grid } from "mds";
import { stringSort } from "../../../utils/sortFunctions";
import { GroupsList } from "../Groups/types";
import {
actionsTray,
selectorsCommon,
tableStyles,
} from "../Common/FormComponents/common/styleLibrary";
import { ErrorResponseHandler } from "../../../common/types";
import api from "../../../common/api";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import SearchBox from "../Common/SearchBox";
import { setModalErrorSnackMessage } from "../../../systemSlice";
import { useAppDispatch } from "../../../store";
import api from "../../../common/api";
import SearchBox from "../Common/SearchBox";
interface IGroupsProps {
classes: any;
selectedGroups: string[];
setSelectedGroups: any;
}
const styles = (theme: Theme) =>
createStyles({
noFound: {
textAlign: "center",
padding: "10px 0",
},
actionsTitle: {
fontWeight: 400,
color: "#000",
fontSize: 14,
alignSelf: "center",
marginRight: 48,
"@media (max-width: 900px)": {
marginRight: 0,
},
},
searchBox: {
flex: 1,
marginLeft: "2rem",
},
...tableStyles,
...actionsTray,
...selectorsCommon,
});
const GroupsSelectors = ({
classes,
selectedGroups,
setSelectedGroups,
}: IGroupsProps) => {
@@ -135,41 +96,41 @@ const GroupsSelectors = ({
);
return (
<React.Fragment>
<Grid item xs={12}>
{loading && <LinearProgress />}
{records !== null && records.length > 0 ? (
<React.Fragment>
<Grid item xs={12} className={classes.actionsTray}>
<label className={classes.actionsTitle}>Assign Groups</label>
<div className={classes.searchBox}>
<SearchBox
placeholder="Start typing to search for Groups"
onChange={setFilter}
value={filter}
/>
</div>
</Grid>
<Grid item xs={12} className={classes.tableBlock}>
<TableWrapper
columns={[{ label: "Group", elementKey: "" }]}
onSelect={selectionChanged}
selectedItems={selGroups}
isLoading={loading}
records={filteredRecords}
entityName="Groups"
idField=""
customPaperHeight={classes.multiSelectTable}
/>
</Grid>
</React.Fragment>
) : (
<div className={classes.noFound}>No Groups Available</div>
)}
</Grid>
</React.Fragment>
<Grid item xs={12} className={"inputItem"}>
{loading && <LinearProgress />}
{records !== null && records.length > 0 ? (
<Fragment>
<Grid item xs={12} className={"inputItem"}>
<SearchBox
placeholder="Start typing to search for Groups"
onChange={setFilter}
value={filter}
label={"Assign Groups"}
/>
</Grid>
<DataTable
columns={[{ label: "Group" }]}
onSelect={selectionChanged}
selectedItems={selGroups}
isLoading={loading}
records={filteredRecords}
entityName="Groups"
idField=""
customPaperHeight={"200px"}
/>
</Fragment>
) : (
<Box
sx={{
textAlign: "center",
padding: "10px 0",
}}
>
No Groups Available
</Box>
)}
</Grid>
);
};
export default withStyles(styles)(GroupsSelectors);
export default GroupsSelectors;

View File

@@ -26,11 +26,13 @@ import {
HelpBox,
PageLayout,
UsersIcon,
DataTable,
Grid,
} from "mds";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import api from "../../../common/api";
import { Grid, LinearProgress } from "@mui/material";
import { LinearProgress } from "@mui/material";
import { User, UsersList } from "./types";
import { usersSort } from "../../../utils/sortFunctions";
import {
@@ -40,8 +42,6 @@ import {
tableStyles,
} from "../Common/FormComponents/common/styleLibrary";
import { ErrorResponseHandler } from "../../../common/types";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import { encodeURLString } from "../../../common/utils";
import AButton from "../Common/AButton/AButton";
import SearchBox from "../Common/SearchBox";
@@ -117,8 +117,8 @@ const ListUsers = ({ classes }: IUsersProps) => {
setDeleteOpen(false);
if (refresh) {
setLoading(true);
setCheckedUsers([]);
}
setCheckedUsers([]);
};
const closeAddGroupBulk = (unCheckAll: boolean = false) => {
@@ -218,7 +218,7 @@ const ListUsers = ({ classes }: IUsersProps) => {
<PageHeaderWrapper label={"Users"} actions={<HelpMenu />} />
<PageLayout>
<Grid container spacing={1}>
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>
<SearchBox
placeholder={"Search Users"}
@@ -356,14 +356,14 @@ const ListUsers = ({ classes }: IUsersProps) => {
item
xs={12}
className={classes.tableBlock}
marginBottom={"15px"}
sx={{ marginBottom: 15 }}
>
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_LIST_USERS]}
resource={CONSOLE_UI_RESOURCE}
errorProps={{ disabled: true }}
>
<TableWrapper
<DataTable
itemActions={tableActions}
columns={[
{ label: "Access Key", elementKey: "accessKey" },
@@ -428,12 +428,7 @@ const ListUsers = ({ classes }: IUsersProps) => {
</Fragment>
)}
{records.length === 0 && (
<Grid
container
justifyContent={"center"}
alignContent={"center"}
alignItems={"start"}
>
<Grid container>
<Grid item xs={8}>
<HelpBox
title={"Users"}

View File

@@ -15,18 +15,14 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import { InputBox } from "mds";
import { setSecretKey, setShowPassword } from "./AddUsersSlice";
import { useSelector } from "react-redux";
import { AppState, useAppDispatch } from "../../../store";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye";
interface IAddUserProps2 {
classes: any;
}
const PasswordSelector = ({ classes }: IAddUserProps2) => {
const PasswordSelector = () => {
const dispatch = useAppDispatch();
const showPassword = useSelector(
(state: AppState) => state.createUser.showPassword
@@ -35,11 +31,7 @@ const PasswordSelector = ({ classes }: IAddUserProps2) => {
(state: AppState) => state.createUser.secretKey
);
return (
<InputBoxWrapper
className={classes.spacerBottom}
classes={{
inputLabel: classes.sizedLabel,
}}
<InputBox
id="standard-multiline-static"
name="standard-multiline-static"
label="Password"

View File

@@ -15,14 +15,11 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import { Theme } from "@mui/material/styles";
import { Button } from "mds";
import { Box, Button, FormLayout, IAMPoliciesIcon } from "mds";
import { LinearProgress } from "@mui/material";
import { useSelector } from "react-redux";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import Grid from "@mui/material/Grid";
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
import { IPolicyItem } from "../Users/types";
import { ErrorResponseHandler } from "../../../common/types";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
@@ -33,28 +30,13 @@ import { AppState, useAppDispatch } from "../../../store";
import { setSelectedPolicies } from "./AddUsersSlice";
interface ISetUserPoliciesProps {
classes: any;
closeModalAndRefresh: () => void;
selectedUser: string;
currentPolicies: IPolicyItem[];
open: boolean;
}
const styles = (theme: Theme) =>
createStyles({
...modalBasic,
buttonContainer: {
display: "flex",
justifyContent: "flex-end",
marginTop: ".9rem",
"& button": {
marginLeft: 8,
},
},
});
const SetUserPolicies = ({
classes,
closeModalAndRefresh,
selectedUser,
currentPolicies,
@@ -114,19 +96,17 @@ const SetUserPolicies = ({
}}
modalOpen={open}
title="Set Policies"
titleIcon={<IAMPoliciesIcon />}
>
<Grid container>
<Grid item xs={12}>
<PolicySelectors selectedPolicy={statePolicies} />
</Grid>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<FormLayout withBorders={false} containerPadding={false}>
<PolicySelectors selectedPolicy={statePolicies} />
</FormLayout>
<Box sx={modalStyleUtils.modalButtonBar}>
<Button
id={"reset-user-policies"}
type="button"
variant="regular"
color="primary"
className={classes.clearButton}
onClick={resetSelection}
label={"Reset"}
/>
@@ -139,7 +119,7 @@ const SetUserPolicies = ({
onClick={SetUserPoliciesAction}
label={"Save"}
/>
</Grid>
</Box>
{loading && (
<Grid item xs={12}>
<LinearProgress />
@@ -149,4 +129,4 @@ const SetUserPolicies = ({
);
};
export default withStyles(styles)(SetUserPolicies);
export default SetUserPolicies;

View File

@@ -16,41 +16,25 @@
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Theme } from "@mui/material/styles";
import {
AddIcon,
BackLink,
Box,
Button,
DataTable,
Grid,
IAMPoliciesIcon,
PageLayout,
PasswordKeyIcon,
ScreenTitle,
SectionTitle,
Switch,
Tabs,
TrashIcon,
UsersIcon,
} from "mds";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Grid } from "@mui/material";
import {
actionsTray,
containerForHeader,
searchField,
tableStyles,
} from "../Common/FormComponents/common/styleLibrary";
import { IPolicyItem } from "./types";
import { ErrorResponseHandler } from "../../../common/types";
import api from "../../../common/api";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import ChangeUserGroups from "./ChangeUserGroups";
import SetUserPolicies from "./SetUserPolicies";
import UserServiceAccountsPanel from "./UserServiceAccountsPanel";
import ChangeUserPasswordModal from "../Account/ChangeUserPasswordModal";
import DeleteUser from "./DeleteUser";
import ScreenTitle from "../Common/ScreenTitle/ScreenTitle";
import PanelTitle from "../Common/PanelTitle/PanelTitle";
import VerticalTabs from "../Common/VerticalTabs/VerticalTabs";
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import { decodeURLString, encodeURLString } from "../../../common/utils";
import { setHelpName, setModalErrorSnackMessage } from "../../../systemSlice";
import {
@@ -72,36 +56,18 @@ import { policyDetailsSort } from "../../../utils/sortFunctions";
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
import HelpMenu from "../HelpMenu";
const styles = (theme: Theme) =>
createStyles({
pageContainer: {
border: "1px solid #EAEAEA",
},
statusLabel: {
fontSize: ".8rem",
marginRight: ".5rem",
},
statusValue: {
fontWeight: "bold",
fontSize: ".9rem",
marginRight: ".5rem",
},
...actionsTray,
...searchField,
...tableStyles,
...containerForHeader,
});
interface IUserDetailsProps {
classes: any;
}
import api from "../../../common/api";
import ChangeUserGroups from "./ChangeUserGroups";
import SetUserPolicies from "./SetUserPolicies";
import UserServiceAccountsPanel from "./UserServiceAccountsPanel";
import ChangeUserPasswordModal from "../Account/ChangeUserPasswordModal";
import DeleteUser from "./DeleteUser";
interface IGroupItem {
group: string;
}
const UserDetails = ({ classes }: IUserDetailsProps) => {
const UserDetails = () => {
const dispatch = useAppDispatch();
const params = useParams();
const navigate = useNavigate();
@@ -118,6 +84,7 @@ const UserDetails = ({ classes }: IUserDetailsProps) => {
useState<boolean>(false);
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [hasPolicy, setHasPolicy] = useState<boolean>(false);
const [selectedTab, setSelectedTab] = useState<string>("groups");
const enableEnabled =
hasPermission(CONSOLE_UI_RESOURCE, enableUserPermissions) && !enabled;
@@ -238,17 +205,6 @@ const UserDetails = ({ classes }: IUserDetailsProps) => {
return (
<Fragment>
<PageHeaderWrapper
label={
<Fragment>
<BackLink
label={"Users"}
onClick={() => navigate(IAM_PAGES.USERS)}
/>
</Fragment>
}
actions={<HelpMenu />}
/>
{addGroupOpen && (
<ChangeUserGroups
open={addGroupOpen}
@@ -286,20 +242,41 @@ const UserDetails = ({ classes }: IUserDetailsProps) => {
closeModal={() => setChangeUserPasswordModalOpen(false)}
/>
)}
<PageLayout className={classes.pageContainer}>
<Grid container spacing={1}>
<PageHeaderWrapper
label={
<Fragment>
<BackLink
label={"Users"}
onClick={() => navigate(IAM_PAGES.USERS)}
/>
</Fragment>
}
actions={<HelpMenu />}
/>
<PageLayout>
<Grid container>
<Grid item xs={12}>
<ScreenTitle
icon={
<Fragment>
<UsersIcon width={40} />
</Fragment>
}
icon={<UsersIcon width={40} />}
title={userName}
subTitle={""}
actions={
<Fragment>
<span className={classes.statusLabel}>User Status:</span>
<span className={classes.statusValue}>
<span
style={{
fontSize: ".8rem",
marginRight: ".5rem",
}}
>
User Status:
</span>
<span
style={{
fontWeight: "bold",
fontSize: ".9rem",
marginRight: ".5rem",
}}
>
{enabled ? "Enabled" : "Disabled"}
</span>
<TooltipWrapper
@@ -328,7 +305,7 @@ const UserDetails = ({ classes }: IUserDetailsProps) => {
)
}
>
<FormSwitchWrapper
<Switch
indicatorLabels={["Enabled", "Disabled"]}
checked={enabled}
value={"group_enabled"}
@@ -378,144 +355,162 @@ const UserDetails = ({ classes }: IUserDetailsProps) => {
</TooltipWrapper>
</Fragment>
}
sx={{ marginBottom: 15 }}
/>
</Grid>
<Grid item xs={12}>
<VerticalTabs>
{{
tabConfig: {
label: "Groups",
disabled: !canAssignGroup,
},
content: (
<React.Fragment>
<div
className={classes.actionsTray}
onMouseMove={() =>
dispatch(setHelpName("user_details_groups"))
}
>
<PanelTitle>Groups</PanelTitle>
<TooltipWrapper
tooltip={
canAssignGroup
? "Assign groups"
: permissionTooltipHelper(
assignGroupPermissions,
"add users to groups"
)
<Tabs
currentTabOrPath={selectedTab}
onTabClick={setSelectedTab}
options={[
{
tabConfig: {
id: "groups",
label: "Groups",
disabled: !canAssignGroup,
},
content: (
<React.Fragment>
<Box
onMouseMove={() =>
dispatch(setHelpName("user_details_groups"))
}
>
<Button
id={"add-groups"}
label={"Add to Groups"}
onClick={() => {
setAddGroupOpen(true);
}}
icon={<AddIcon />}
variant={"callAction"}
disabled={!canAssignGroup}
<SectionTitle
separator
sx={{ marginBottom: 15 }}
actions={
<TooltipWrapper
tooltip={
canAssignGroup
? "Assign groups"
: permissionTooltipHelper(
assignGroupPermissions,
"add users to groups"
)
}
>
<Button
id={"add-groups"}
label={"Add to Groups"}
onClick={() => {
setAddGroupOpen(true);
}}
icon={<AddIcon />}
variant={"callAction"}
disabled={!canAssignGroup}
/>
</TooltipWrapper>
}
>
Groups
</SectionTitle>
</Box>
<Grid
item
xs={12}
onMouseMove={() =>
dispatch(setHelpName("user_details_groups"))
}
>
<DataTable
itemActions={groupTableActions}
columns={[{ label: "Name", elementKey: "group" }]}
isLoading={loading}
records={currentGroups}
entityName="Groups"
idField="group"
/>
</TooltipWrapper>
</div>
<Grid
item
xs={12}
className={classes.tableBlock}
onMouseMove={() =>
dispatch(setHelpName("user_details_groups"))
}
>
<TableWrapper
itemActions={groupTableActions}
columns={[{ label: "Name", elementKey: "group" }]}
isLoading={loading}
records={currentGroups}
entityName="Groups"
idField="group"
/>
</Grid>
</React.Fragment>
),
}}
{{
tabConfig: {
label: "Service Accounts",
disabled: !hasPermission(
CONSOLE_UI_RESOURCE,
editServiceAccountPermissions
</Grid>
</React.Fragment>
),
},
content: (
<UserServiceAccountsPanel
user={userName}
hasPolicy={hasPolicy}
/>
),
}}
{{
tabConfig: {
label: "Policies",
disabled: !canAssignPolicy,
{
tabConfig: {
id: "service_accounts",
label: "Service Accounts",
disabled: !hasPermission(
CONSOLE_UI_RESOURCE,
editServiceAccountPermissions
),
},
content: (
<UserServiceAccountsPanel
user={userName}
hasPolicy={hasPolicy}
/>
),
},
content: (
<Fragment>
<div
className={classes.actionsTray}
onMouseMove={() =>
dispatch(setHelpName("user_details_policies"))
}
>
<PanelTitle>Policies</PanelTitle>
<TooltipWrapper
tooltip={
canAssignPolicy
? "Assign Policies"
: permissionTooltipHelper(
assignIAMPolicyPermissions,
"assign policies"
)
{
tabConfig: {
id: "policies",
label: "Policies",
disabled: !canAssignPolicy,
},
content: (
<Fragment>
<Box
onMouseMove={() =>
dispatch(setHelpName("user_details_policies"))
}
>
<Button
id={"assign-policies"}
label={"Assign Policies"}
onClick={() => {
setPolicyOpen(true);
}}
icon={<IAMPoliciesIcon />}
variant={"callAction"}
disabled={!canAssignPolicy}
/>
</TooltipWrapper>
</div>
<div className={classes.tableBlock}>
<TableWrapper
itemActions={[
{
type: "view",
onClick: (policy: IPolicyItem) => {
navigate(
`${IAM_PAGES.POLICIES}/${encodeURLString(
policy.policy
)}`
);
<SectionTitle
separator
sx={{ marginBottom: 15 }}
actions={
<TooltipWrapper
tooltip={
canAssignPolicy
? "Assign Policies"
: permissionTooltipHelper(
assignIAMPolicyPermissions,
"assign policies"
)
}
>
<Button
id={"assign-policies"}
label={"Assign Policies"}
onClick={() => {
setPolicyOpen(true);
}}
icon={<IAMPoliciesIcon />}
variant={"callAction"}
disabled={!canAssignPolicy}
/>
</TooltipWrapper>
}
>
Policies
</SectionTitle>
</Box>
<Box>
<DataTable
itemActions={[
{
type: "view",
onClick: (policy: IPolicyItem) => {
navigate(
`${IAM_PAGES.POLICIES}/${encodeURLString(
policy.policy
)}`
);
},
},
},
]}
columns={[{ label: "Name", elementKey: "policy" }]}
isLoading={loading}
records={currentPolicies}
entityName="Policies"
idField="policy"
/>
</div>
</Fragment>
),
}}
</VerticalTabs>
]}
columns={[{ label: "Name", elementKey: "policy" }]}
isLoading={loading}
records={currentPolicies}
entityName="Policies"
idField="policy"
/>
</Box>
</Fragment>
),
},
]}
/>
</Grid>
</Grid>
</PageLayout>
@@ -523,4 +518,4 @@ const UserDetails = ({ classes }: IUserDetailsProps) => {
);
};
export default withStyles(styles)(UserDetails);
export default UserDetails;

View File

@@ -14,36 +14,26 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment } from "react";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import React from "react";
import { setUserName } from "./AddUsersSlice";
import { useSelector } from "react-redux";
import { AppState, useAppDispatch } from "../../../store";
import { InputBox } from "mds";
interface IAddUserProps2 {
classes: any;
}
const UserSelector = ({ classes }: IAddUserProps2) => {
const UserSelector = () => {
const dispatch = useAppDispatch();
const userName = useSelector((state: AppState) => state.createUser.userName);
return (
<Fragment>
<InputBoxWrapper
className={classes.spacerBottom}
classes={{
inputLabel: classes.sizedLabel,
}}
id="accesskey-input"
name="accesskey-input"
label="User Name"
value={userName}
autoFocus={true}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setUserName(e.target.value));
}}
/>
</Fragment>
<InputBox
id="accesskey-input"
name="accesskey-input"
label="User Name"
value={userName}
autoFocus={true}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setUserName(e.target.value));
}}
/>
);
};
export default UserSelector;

View File

@@ -14,27 +14,15 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import { Theme } from "@mui/material/styles";
import { Box } from "@mui/material";
import React, { Fragment, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { AddIcon, Button, DeleteIcon } from "mds";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import {
actionsTray,
searchField,
tableStyles,
} from "../Common/FormComponents/common/styleLibrary";
import { AddIcon, Box, Button, DataTable, DeleteIcon, SectionTitle } from "mds";
import api from "../../../common/api";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import { NewServiceAccount } from "../Common/CredentialsPrompt/types";
import { stringSort } from "../../../utils/sortFunctions";
import { ErrorResponseHandler } from "../../../common/types";
import AddUserServiceAccount from "./AddUserServiceAccount";
import DeleteServiceAccount from "../Account/DeleteServiceAccount";
import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt";
import PanelTitle from "../Common/PanelTitle/PanelTitle";
import DeleteMultipleServiceAccounts from "./DeleteMultipleServiceAccounts";
import { selectSAs } from "../Configurations/utils";
@@ -54,23 +42,11 @@ import { useAppDispatch } from "../../../store";
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
interface IUserServiceAccountsProps {
classes: any;
user: string;
hasPolicy: boolean;
}
const styles = (theme: Theme) =>
createStyles({
...searchField,
...actionsTray,
actionsTray: {
...actionsTray.actionsTray,
},
...tableStyles,
});
const UserServiceAccountsPanel = ({
classes,
user,
hasPolicy,
}: IUserServiceAccountsProps) => {
@@ -79,7 +55,6 @@ const UserServiceAccountsPanel = ({
const [records, setRecords] = useState<string[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [selectedServiceAccount, setSelectedServiceAccount] = useState<
string | null
@@ -115,23 +90,6 @@ const UserServiceAccountsPanel = ({
setLoading(true);
};
const closeAddModalAndRefresh = (res: NewServiceAccount | null) => {
setAddScreenOpen(false);
fetchRecords();
if (res !== null) {
const nsa: NewServiceAccount = {
console: {
accessKey: `${res.accessKey}`,
secretKey: `${res.secretKey}`,
url: `${res.url}`,
},
};
setNewServiceAccount(nsa);
setShowNewCredentials(true);
}
};
const closeDeleteModalAndRefresh = (refresh: boolean) => {
setDeleteOpen(false);
@@ -188,16 +146,7 @@ const UserServiceAccountsPanel = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<React.Fragment>
{addScreenOpen && (
<AddUserServiceAccount
open={addScreenOpen}
closeModalAndRefresh={(res: NewServiceAccount | null) => {
closeAddModalAndRefresh(res);
}}
user={user}
/>
)}
<Fragment>
{deleteOpen && (
<DeleteServiceAccount
deleteOpen={deleteOpen}
@@ -231,64 +180,68 @@ const UserServiceAccountsPanel = ({
closeModalAndRefresh={closePolicyModal}
/>
)}
<div className={classes.actionsTray}>
<PanelTitle>Access Keys</PanelTitle>
<Box sx={{ display: "flex", justifyContent: "flex-end" }}>
<TooltipWrapper tooltip={"Delete Selected"}>
<Button
id={"delete-selected"}
onClick={() => {
setDeleteMultipleOpen(true);
}}
label={"Delete Selected"}
icon={<DeleteIcon />}
disabled={selectedSAs.length === 0}
variant={"secondary"}
/>
</TooltipWrapper>
<SecureComponent
scopes={[
IAM_SCOPES.ADMIN_CREATE_SERVICEACCOUNT,
IAM_SCOPES.ADMIN_UPDATE_SERVICEACCOUNT,
IAM_SCOPES.ADMIN_REMOVE_SERVICEACCOUNT,
IAM_SCOPES.ADMIN_LIST_SERVICEACCOUNTS,
]}
resource={CONSOLE_UI_RESOURCE}
matchAll
errorProps={{ disabled: true }}
>
<TooltipWrapper tooltip={"Create Access Key"}>
<SectionTitle
separator
sx={{ marginBottom: 15 }}
actions={
<Box sx={{ display: "flex", justifyContent: "flex-end", gap: 10 }}>
<TooltipWrapper tooltip={"Delete Selected"}>
<Button
id={"create-service-account"}
label={"Create Access Key"}
variant="callAction"
icon={<AddIcon />}
id={"delete-selected"}
onClick={() => {
navigate(
`/identity/users/new-user-sa/${encodeURLString(user)}`
);
setDeleteMultipleOpen(true);
}}
disabled={!hasPolicy}
label={"Delete Selected"}
icon={<DeleteIcon />}
disabled={selectedSAs.length === 0}
variant={"secondary"}
/>
</TooltipWrapper>
</SecureComponent>
</Box>
</div>
<div className={classes.tableBlock}>
<TableWrapper
isLoading={loading}
records={records}
entityName={"Access Keys"}
idField={""}
columns={[{ label: "Access Key", elementKey: "" }]}
itemActions={tableActions}
selectedItems={selectedSAs}
onSelect={(e) => selectSAs(e, setSelectedSAs, selectedSAs)}
onSelectAll={selectAllItems}
/>
</div>
</React.Fragment>
<SecureComponent
scopes={[
IAM_SCOPES.ADMIN_CREATE_SERVICEACCOUNT,
IAM_SCOPES.ADMIN_UPDATE_SERVICEACCOUNT,
IAM_SCOPES.ADMIN_REMOVE_SERVICEACCOUNT,
IAM_SCOPES.ADMIN_LIST_SERVICEACCOUNTS,
]}
resource={CONSOLE_UI_RESOURCE}
matchAll
errorProps={{ disabled: true }}
>
<TooltipWrapper tooltip={"Create Access Key"}>
<Button
id={"create-service-account"}
label={"Create Access Key"}
variant="callAction"
icon={<AddIcon />}
onClick={() => {
navigate(
`/identity/users/new-user-sa/${encodeURLString(user)}`
);
}}
disabled={!hasPolicy}
/>
</TooltipWrapper>
</SecureComponent>
</Box>
}
>
Access Keys
</SectionTitle>
<DataTable
isLoading={loading}
records={records}
entityName={"Access Keys"}
columns={[{ label: "Access Key" }]}
itemActions={tableActions}
selectedItems={selectedSAs}
onSelect={(e) => selectSAs(e, setSelectedSAs, selectedSAs)}
onSelectAll={selectAllItems}
/>
</Fragment>
);
};
export default withStyles(styles)(UserServiceAccountsPanel);
export default UserServiceAccountsPanel;

View File

@@ -54,8 +54,9 @@ export const createUserAsync = createAsyncThunk(
groups: selectedGroups,
policies: selectedPolicies,
})
.then((res) => {
.then(() => {
dispatch(setAddLoading(false));
dispatch(resetFormAsync());
})
.catch((err: ErrorResponseHandler) => {
dispatch(setAddLoading(false));

View File

@@ -32,7 +32,7 @@ const userDeleteIconButton = userListItem
.child("checkbox")
.withAttribute("aria-label", "secondary checkbox");
const userCheckbox = Selector(".TableCheckbox");
const userCheckbox = Selector(".TableCheckbox span.checkbox");
fixture("For user with Users permissions")
.page("http://localhost:9090")
@@ -100,8 +100,10 @@ test("IAM Policy can be set on User", async (t) => {
test("Created User can be viewed and deleted", async (t) => {
const userListItemExists = userListItem.exists;
const deleteSelectedButton =
Selector("button:enabled").withExactText("Delete Selected");
const deleteSelectedButton = Selector("button").withAttribute(
"id",
"delete-selected-users"
);
await t
.navigateTo(usersPageUrl)
.typeText(elements.searchResourceInput, constants.TEST_USER_NAME)

View File

@@ -44,8 +44,10 @@ export const startDiagnosticButton =
export const startNewDiagnosticButton = Selector("#start-new-diagnostic");
export const downloadButton = Selector("button:enabled").withText("Download");
export const startButton = Selector("button:enabled").withText("Start");
export const assignPoliciesButton =
Selector("button:enabled").withText("Assign Policies");
export const assignPoliciesButton = Selector("button").withAttribute(
"id",
"assign-policies"
);
//----------------------------------------------------
// Switches
@@ -178,7 +180,10 @@ export const nodeSelector = Selector('[data-test-id="node-selector"]');
//----------------------------------------------------
// User Details
//----------------------------------------------------
export const userPolicies = Selector(".MuiTab-root").withText("Policies");
export const userPolicies = Selector(".optionsList button").withAttribute(
"id",
"policies"
);
//----------------------------------------------------
// Rewind Options
//----------------------------------------------------