Groups list component creation (#14)

* Reconnected Users delete modal

* Fix API path

* Reconnected access name modal

* Fixed title issue

* Fixed reset state

* Added groups selectors list control component

* Change of edit form to not edit access key & access secret values

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
This commit is contained in:
Alex
2020-04-02 19:23:40 -06:00
committed by GitHub
parent cfa5fa9826
commit 0da379dbb2
4 changed files with 266 additions and 39 deletions

View File

@@ -29,11 +29,18 @@ import {
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import api from "../../../common/api";
import { User } from "./types";
import GroupsSelectors from "./GroupsSelectors";
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
},
strongText: {
fontWeight: 700,
},
keyName: {
marginLeft: 5
}
});
@@ -48,6 +55,9 @@ interface IAddUserContentState {
addError: string;
accessKey: string;
secretKey: string;
selectedGroups: string[];
loadingGroups: boolean;
groupsList: any[];
}
class AddUserContent extends React.Component<
@@ -58,7 +68,10 @@ class AddUserContent extends React.Component<
addLoading: false,
addError: "",
accessKey: "",
secretKey: ""
secretKey: "",
selectedGroups: [],
loadingGroups: false,
groupsList: [],
};
componentDidMount(): void {
@@ -74,7 +87,7 @@ class AddUserContent extends React.Component<
saveRecord(event: React.FormEvent) {
event.preventDefault();
const { accessKey, addLoading, secretKey } = this.state;
const { accessKey, addLoading, secretKey, selectedGroups } = this.state;
const { selectedUser } = this.props;
if (addLoading) {
return;
@@ -133,12 +146,12 @@ class AddUserContent extends React.Component<
render() {
const { classes, selectedUser } = this.props;
const { addLoading, addError, accessKey, secretKey } = this.state;
const { addLoading, addError, accessKey, secretKey, selectedGroups, loadingGroups, groupsList } = this.state;
return (
<React.Fragment>
<DialogTitle id="alert-dialog-title">
Create User
{selectedUser !== null ? 'Edit User' : 'Add User'}
</DialogTitle>
<DialogContent>
<form
@@ -149,13 +162,6 @@ class AddUserContent extends React.Component<
}}
>
<Grid container>
<Grid item xs={12}>
{selectedUser !== null ? (
<Title>Edit User</Title>
) : (
<Title>Add User</Title>
)}
</Grid>
{addError !== "" && (
<Grid item xs={12}>
<Typography
@@ -167,33 +173,58 @@ class AddUserContent extends React.Component<
</Typography>
</Grid>
)}
<Grid item xs={12}>
<TextField
id="standard-basic"
fullWidth
label="Access Key"
value={accessKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ accessKey: e.target.value });
}}
/>
</Grid>
<Grid item xs={12}>
<TextField
id="standard-multiline-static"
label={selectedUser !== null ? 'New Secret Key': 'Secret Key'}
type="password"
fullWidth
value={secretKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ secretKey: e.target.value });
}}
autoComplete="current-password"
/>
</Grid>
{selectedUser !== null ? (
<React.Fragment>
<span className={classes.strongText}>Access Key:</span>
<span className={classes.keyName}>{` ${accessKey}`}</span>
</React.Fragment>
) : (
<React.Fragment>
<Grid item xs={12}>
<TextField
id="standard-basic"
fullWidth
label="Access Key"
value={accessKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ accessKey: e.target.value });
}}
/>
</Grid>
<Grid item xs={12}>
<TextField
id="standard-multiline-static"
label={selectedUser !== null ? 'New Secret Key': 'Secret Key'}
type="password"
fullWidth
value={secretKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ secretKey: e.target.value });
}}
autoComplete="current-password"
/>
</Grid>
</React.Fragment>
)}
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<GroupsSelectors
selectedGroups={selectedGroups}
setSelectedGroups={
(elements: string[]) => {
this.setState({
selectedGroups: elements
})
}
}
loading={loadingGroups}
records={groupsList}
/>
</Grid>
<Grid item xs={12}>
<Button
type="submit"

View File

@@ -68,7 +68,7 @@ class DeleteUser extends React.Component<
}
this.setState({ deleteLoading: true }, () => {
api
.invoke("DELETE", `/api/v1/users/${selectedUser.id}`, {
.invoke("DELETE", `/api/v1/users/${selectedUser.accessKey}`, {
id: selectedUser.id
})
.then((res: UsersList) => {
@@ -114,7 +114,7 @@ class DeleteUser extends React.Component<
<DialogContent>
{deleteLoading && <LinearProgress />}
<DialogContentText id="alert-dialog-description">
Are you sure you want to delete user{" "}<b>{selectedUser.name}</b>?
Are you sure you want to delete user{" "}<b>{selectedUser.accessKey}</b>?
{deleteError !== "" && (
<React.Fragment>
<br />

View File

@@ -0,0 +1,188 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2020 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 from 'react';
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { LinearProgress } from "@material-ui/core";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import Title from "../../../common/Title";
import InputAdornment from "@material-ui/core/InputAdornment";
import SearchIcon from "@material-ui/icons/Search";
import TextField from "@material-ui/core/TextField";
import Checkbox from "@material-ui/core/Checkbox";
interface IGroupsProps {
classes: any;
selectedGroups: string[];
setSelectedGroups: any;
records: any[];
loading: boolean;
}
const styles = (theme: Theme) =>
createStyles({
seeMore: {
marginTop: theme.spacing(3)
},
paper: {
// padding: theme.spacing(2),
display: "flex",
overflow: "auto",
flexDirection: "column"
},
addSideBar: {
width: "320px",
padding: "20px"
},
errorBlock: {
color: "red"
},
tableToolbar: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(0)
},
wrapCell: {
maxWidth: "200px",
whiteSpace: "normal",
wordWrap: "break-word"
},
minTableHeader: {
color: "#393939",
"& tr": {
"& th": {
fontWeight:'bold'
}
}
},
actionsTray: {
textAlign: "left",
"& button": {
marginLeft: 10,
}
},
filterField: {
background: "#FFFFFF",
padding: 12,
borderRadius: 5,
boxShadow: "0px 3px 6px #00000012",
width: '100%'
},
noFound: {
textAlign: "center",
padding: "10px 0",
}
});
const GroupsSelectors = ({
classes,
selectedGroups,
setSelectedGroups,
records,
loading
}: IGroupsProps) => {
if(!records) {
return null;
}
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const targetD = e.target;
const value = targetD.value;
const checked = targetD.checked;
let elements : string[] = [...selectedGroups]; // We clone the selectedGroups array
if(checked) { // If the user has checked this field we need to push this to selectedGroupsList
elements.push(value);
} else { // User has unchecked this field, we need to remove it from the list
elements = elements.filter(element => element !== value);
}
setSelectedGroups(elements);
return elements;
};
return (
<React.Fragment>
<Title>Groups</Title>
<Grid item xs={12}>
<Paper className={classes.paper}>
{loading && <LinearProgress />}
{records != null && records.length > 0 ? (
<React.Fragment>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Filter Groups"
className={classes.filterField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
</Grid>
<Table size="medium">
<TableHead className={classes.minTableHeader}>
<TableRow>
<TableCell>Select</TableCell>
<TableCell>Group</TableCell>
</TableRow>
</TableHead>
<TableBody>
{records.map(row => (
<TableRow key={`group-${row.groupName}`}>
<TableCell padding="checkbox">
<Checkbox
value={row.groupName}
color="primary"
inputProps={{
'aria-label': 'secondary checkbox'
}}
onChange={ selectionChanged }
checked={selectedGroups.includes(row.groupName)}
/>
</TableCell>
<TableCell className={classes.wrapCell}>
{row.groupName}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</React.Fragment>
) : (
<div className={classes.noFound}>No Groups Available</div>
)}
</Paper>
</Grid>
</React.Fragment>
);
};
export default withStyles(styles)(GroupsSelectors);

View File

@@ -255,7 +255,8 @@ class Users extends React.Component<IUsersProps, IUsersState> {
startIcon={<CreateIcon />}
onClick={() => {
this.setState({
addScreenOpen: true
addScreenOpen: true,
selectedUser: null,
});
}}
>
@@ -295,6 +296,10 @@ class Users extends React.Component<IUsersProps, IUsersState> {
<IconButton
aria-label="view"
onClick={() => {
this.setState({
addScreenOpen: true,
selectedUser: row,
});
}}
>
<ViewIcon />
@@ -302,7 +307,10 @@ class Users extends React.Component<IUsersProps, IUsersState> {
<IconButton
aria-label="delete"
onClick={() => {
this.setState({
deleteOpen: true,
selectedUser: row,
});
}}
>
<DeleteIcon />