UI: Add events to buckets (#22)
* UI: Add events to buckets * Menu user icon Co-authored-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
330
portal-ui/src/screens/Console/Buckets/ViewBucket/AddEvent.tsx
Normal file
330
portal-ui/src/screens/Console/Buckets/ViewBucket/AddEvent.tsx
Normal file
@@ -0,0 +1,330 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// 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, { ChangeEvent } from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Title from "../../../../common/Title";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
LinearProgress,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField
|
||||
} from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import api from "../../../../common/api";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import { ArnList, BucketEventList } from "../types";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
interface IAddEventProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
selectedBucket: string;
|
||||
closeModalAndRefresh: () => void;
|
||||
}
|
||||
|
||||
interface IAddEventState {
|
||||
addLoading: boolean;
|
||||
addError: string;
|
||||
prefix: string;
|
||||
suffix: string;
|
||||
arn: string;
|
||||
selectedEvents: string[];
|
||||
arnList: string[];
|
||||
}
|
||||
|
||||
class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
state: IAddEventState = {
|
||||
addLoading: false,
|
||||
addError: "",
|
||||
prefix: "",
|
||||
suffix: "",
|
||||
arn: "",
|
||||
selectedEvents: [],
|
||||
arnList: []
|
||||
};
|
||||
|
||||
addRecord(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
const { prefix, suffix, addLoading, arn, selectedEvents } = this.state;
|
||||
const { selectedBucket } = this.props;
|
||||
if (addLoading) {
|
||||
return;
|
||||
}
|
||||
this.setState({ addLoading: true }, () => {
|
||||
api
|
||||
.invoke("POST", `/api/v1/buckets/${selectedBucket}/events`, {
|
||||
configuration: {
|
||||
arn: arn,
|
||||
events: selectedEvents,
|
||||
prefix: prefix,
|
||||
sufix: suffix
|
||||
},
|
||||
ignoreExisting: true
|
||||
})
|
||||
.then(res => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchArnList() {
|
||||
this.setState({ addLoading: true }, () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/admin/arns`)
|
||||
.then((res: ArnList) => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
arnList: res.arns,
|
||||
addError: ""
|
||||
});
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.setState({ addLoading: false, addError: err });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.fetchArnList();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open } = this.props;
|
||||
const { addLoading, addError, arn, selectedEvents, arnList } = this.state;
|
||||
|
||||
const events = [
|
||||
{ label: "PUT - Object Uploaded", value: "put" },
|
||||
{ label: "GET - Object accessed", value: "get" },
|
||||
{ label: "DELETE - Object Deleted", value: "delete" }
|
||||
];
|
||||
|
||||
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
const value = targetD.value;
|
||||
const checked = targetD.checked;
|
||||
|
||||
let elements: string[] = [...selectedEvents]; // 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);
|
||||
}
|
||||
|
||||
this.setState({ selectedEvents: selectedEvents });
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
const handleClick = (
|
||||
event: React.MouseEvent<unknown> | ChangeEvent<unknown>,
|
||||
name: string
|
||||
) => {
|
||||
const selectedIndex = selectedEvents.indexOf(name);
|
||||
let newSelected: string[] = [];
|
||||
|
||||
if (selectedIndex === -1) {
|
||||
newSelected = newSelected.concat(selectedEvents, name);
|
||||
} else if (selectedIndex === 0) {
|
||||
newSelected = newSelected.concat(selectedEvents.slice(1));
|
||||
} else if (selectedIndex === selectedEvents.length - 1) {
|
||||
newSelected = newSelected.concat(selectedEvents.slice(0, -1));
|
||||
} else if (selectedIndex > 0) {
|
||||
newSelected = newSelected.concat(
|
||||
selectedEvents.slice(0, selectedIndex),
|
||||
selectedEvents.slice(selectedIndex + 1)
|
||||
);
|
||||
}
|
||||
|
||||
this.setState({ selectedEvents: newSelected });
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={() => {
|
||||
this.setState({ addError: "" }, () => {
|
||||
this.props.closeModalAndRefresh();
|
||||
});
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
<Title>Subscribe To Event</Title>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
this.addRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<FormControl className={classes.formControl} fullWidth>
|
||||
<InputLabel id="select-access-policy">ARN</InputLabel>
|
||||
<Select
|
||||
labelId="select-access-policy"
|
||||
id="select-access-policy"
|
||||
value={arn}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ arn: e.target.value as string });
|
||||
}}
|
||||
>
|
||||
{arnList.map(arn => (
|
||||
<MenuItem value={arn}>{arn}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Event</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{events.map(row => (
|
||||
<TableRow
|
||||
key={`group-${row.value}`}
|
||||
onClick={event => handleClick(event, row.value)}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.value}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
"aria-label": "secondary checkbox"
|
||||
}}
|
||||
onChange={event => handleClick(event, row.value)}
|
||||
checked={selectedEvents.includes(row.value)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.label}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
fullWidth
|
||||
label="Prefix"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ prefix: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
fullWidth
|
||||
label="Suffix"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ suffix: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={addLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(AddEvent);
|
||||
160
portal-ui/src/screens/Console/Buckets/ViewBucket/DeleteEvent.tsx
Normal file
160
portal-ui/src/screens/Console/Buckets/ViewBucket/DeleteEvent.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import React from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress
|
||||
} from "@material-ui/core";
|
||||
import api from "../../../../common/api";
|
||||
import { BucketEvent, BucketList } from "../types";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
});
|
||||
|
||||
interface IDeleteEventProps {
|
||||
classes: any;
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
selectedBucket: string;
|
||||
bucketEvent: BucketEvent | null;
|
||||
}
|
||||
|
||||
interface IDeleteEventState {
|
||||
deleteLoading: boolean;
|
||||
deleteError: string;
|
||||
}
|
||||
|
||||
class DeleteEvent extends React.Component<
|
||||
IDeleteEventProps,
|
||||
IDeleteEventState
|
||||
> {
|
||||
state: IDeleteEventState = {
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
};
|
||||
|
||||
removeRecord() {
|
||||
const { deleteLoading } = this.state;
|
||||
const { selectedBucket, bucketEvent } = this.props;
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
if (bucketEvent == null) {
|
||||
return;
|
||||
}
|
||||
this.setState({ deleteLoading: true }, () => {
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${selectedBucket}/events/${bucketEvent.id}`,
|
||||
{
|
||||
name: selectedBucket
|
||||
}
|
||||
)
|
||||
.then((res: BucketList) => {
|
||||
this.setState(
|
||||
{
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeDeleteModalAndRefresh(true);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
deleteLoading: false,
|
||||
deleteError: err
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, deleteOpen, selectedBucket } = this.props;
|
||||
const { deleteLoading, deleteError } = this.state;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.props.closeDeleteModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete Bucket</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete this event?
|
||||
{deleteError !== "" && (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{deleteError}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.props.closeDeleteModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.removeRecord();
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(DeleteEvent);
|
||||
@@ -37,6 +37,9 @@ import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import SetAccessPolicy from "./SetAccessPolicy";
|
||||
import DeleteBucket from "../ListBuckets/DeleteBucket";
|
||||
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
||||
import { CreateIcon } from "../../../../icons";
|
||||
import AddEvent from "./AddEvent";
|
||||
import DeleteEvent from "./DeleteEvent";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -102,8 +105,10 @@ interface IViewBucketState {
|
||||
setAccessPolicyScreenOpen: boolean;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
addScreenOpen: boolean;
|
||||
deleteOpen: boolean;
|
||||
selectedBucket: string;
|
||||
selectedEvent: BucketEvent | null;
|
||||
}
|
||||
|
||||
class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
@@ -117,11 +122,13 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
setAccessPolicyScreenOpen: false,
|
||||
page: 0,
|
||||
rowsPerPage: 10,
|
||||
addScreenOpen: false,
|
||||
deleteOpen: false,
|
||||
selectedBucket: ""
|
||||
selectedBucket: "",
|
||||
selectedEvent: null
|
||||
};
|
||||
|
||||
fetchRecords() {
|
||||
fetchEvents() {
|
||||
this.setState({ loading: true }, () => {
|
||||
const { page, rowsPerPage } = this.state;
|
||||
const { match } = this.props;
|
||||
@@ -144,7 +151,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
) {
|
||||
const newPage = page - 1;
|
||||
this.setState({ page: newPage }, () => {
|
||||
this.fetchRecords();
|
||||
this.fetchEvents();
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -163,7 +170,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
closeDeleteModalAndRefresh(refresh: boolean) {
|
||||
this.setState({ deleteOpen: false }, () => {
|
||||
if (refresh) {
|
||||
this.fetchRecords();
|
||||
this.fetchEvents();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -181,7 +188,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
|
||||
componentDidMount(): void {
|
||||
this.loadInfo();
|
||||
this.fetchRecords();
|
||||
this.fetchEvents();
|
||||
}
|
||||
|
||||
bucketFilter(): void {}
|
||||
@@ -197,7 +204,9 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
page,
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
selectedBucket
|
||||
addScreenOpen,
|
||||
selectedBucket,
|
||||
selectedEvent
|
||||
} = this.state;
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
@@ -215,8 +224,8 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
this.setState({ page: 0, rowsPerPage: rPP });
|
||||
};
|
||||
|
||||
const confirmDeleteEvent = (bucket: string) => {
|
||||
this.setState({ deleteOpen: true, selectedBucket: bucket });
|
||||
const confirmDeleteEvent = (evnt: BucketEvent) => {
|
||||
this.setState({ deleteOpen: true, selectedEvent: evnt });
|
||||
};
|
||||
|
||||
let accessPolicy = "n/a";
|
||||
@@ -226,6 +235,14 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<AddEvent
|
||||
open={addScreenOpen}
|
||||
selectedBucket={bucketName}
|
||||
closeModalAndRefresh={() => {
|
||||
this.setState({ addScreenOpen: false });
|
||||
this.fetchEvents();
|
||||
}}
|
||||
/>
|
||||
<SetAccessPolicy
|
||||
bucketName={bucketName}
|
||||
open={setAccessPolicyScreenOpen}
|
||||
@@ -267,7 +284,21 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="h6">Events</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6} className={classes.actionsTray} />
|
||||
<Grid item xs={6} className={classes.actionsTray}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true
|
||||
});
|
||||
}}
|
||||
>
|
||||
Subcribe to Event
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
@@ -296,7 +327,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
confirmDeleteEvent(row.id);
|
||||
confirmDeleteEvent(row);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
@@ -331,9 +362,10 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<DeleteBucket
|
||||
<DeleteEvent
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
selectedBucket={bucketName}
|
||||
bucketEvent={selectedEvent}
|
||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||
this.closeDeleteModalAndRefresh(refresh);
|
||||
}}
|
||||
|
||||
@@ -41,3 +41,7 @@ export interface BucketEventList {
|
||||
events: BucketEvent[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface ArnList {
|
||||
arns: string[];
|
||||
}
|
||||
|
||||
@@ -16,228 +16,241 @@
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {Button, Dialog, DialogContent, DialogTitle, LinearProgress, TextField} from "@material-ui/core";
|
||||
import Radio from '@material-ui/core/Radio';
|
||||
import RadioGroup from '@material-ui/core/RadioGroup';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
TextField
|
||||
} from "@material-ui/core";
|
||||
import Radio from "@material-ui/core/Radio";
|
||||
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import api from "../../../common/api";
|
||||
import UsersSelectors from "./UsersSelectors";
|
||||
import {GroupsList} from "./types";
|
||||
import {groupsSort} from "../../../utils/sortFunctions";
|
||||
import { GroupsList } from "./types";
|
||||
import { groupsSort } from "../../../utils/sortFunctions";
|
||||
import Title from "../../../common/Title";
|
||||
|
||||
interface IGroupProps {
|
||||
open: boolean;
|
||||
selectedGroup: any;
|
||||
closeModalAndRefresh: any;
|
||||
classes: any;
|
||||
open: boolean;
|
||||
selectedGroup: any;
|
||||
closeModalAndRefresh: any;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
interface MainGroupProps {
|
||||
members: string[];
|
||||
name: string;
|
||||
status: string;
|
||||
members: string[];
|
||||
name: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700,
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
}
|
||||
});
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
}
|
||||
});
|
||||
|
||||
const AddGroup = ({
|
||||
open,
|
||||
selectedGroup,
|
||||
closeModalAndRefresh,
|
||||
classes,
|
||||
}: IGroupProps) => {
|
||||
open,
|
||||
selectedGroup,
|
||||
closeModalAndRefresh,
|
||||
classes
|
||||
}: IGroupProps) => {
|
||||
//Local States
|
||||
const [groupName, setGroupName] = useState<string>("");
|
||||
const [groupEnabled, setGroupEnabled] = useState<string>("");
|
||||
const [saving, isSaving] = useState<boolean>(false);
|
||||
const [addError, setError] = useState<string>("");
|
||||
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
|
||||
const [loadingGroup, isLoadingGroup] = useState<boolean>(false);
|
||||
|
||||
//Local States
|
||||
const [groupName, setGroupName] = useState<string>("");
|
||||
const [groupEnabled, setGroupEnabled] = useState<string>("");
|
||||
const [saving, isSaving] = useState<boolean>(false);
|
||||
const [addError, setError] = useState<string>("");
|
||||
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
|
||||
const [loadingGroup, isLoadingGroup] = useState<boolean>(false);
|
||||
|
||||
//Effects
|
||||
useEffect(() => {
|
||||
if(selectedGroup !== null) {
|
||||
isLoadingGroup(true);
|
||||
} else {
|
||||
setGroupName("");
|
||||
setSelectedUsers([]);
|
||||
}
|
||||
}, [selectedGroup]);
|
||||
|
||||
useEffect(() => {
|
||||
if(saving) {
|
||||
saveRecord();
|
||||
}
|
||||
}, [saving]);
|
||||
|
||||
useEffect(() => {
|
||||
if(selectedGroup && loadingGroup) {
|
||||
fetchGroupInfo();
|
||||
}
|
||||
}, [loadingGroup]);
|
||||
|
||||
//Fetch Actions
|
||||
const setSaving = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
isSaving(true);
|
||||
//Effects
|
||||
useEffect(() => {
|
||||
if (selectedGroup !== null) {
|
||||
isLoadingGroup(true);
|
||||
} else {
|
||||
setGroupName("");
|
||||
setSelectedUsers([]);
|
||||
}
|
||||
}, [selectedGroup]);
|
||||
|
||||
const saveRecord = () => {
|
||||
if (selectedGroup !== null) {
|
||||
api
|
||||
.invoke("PUT", `/api/v1/groups/${groupName}`, {
|
||||
group: groupName,
|
||||
members: selectedUsers,
|
||||
status: groupEnabled,
|
||||
})
|
||||
.then(res => {
|
||||
isSaving(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
isSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
} else {
|
||||
api.invoke("POST", "/api/v1/groups", {
|
||||
group: groupName,
|
||||
members: selectedUsers,
|
||||
})
|
||||
.then(res => {
|
||||
isSaving(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
isSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (saving) {
|
||||
saveRecord();
|
||||
}
|
||||
}, [saving]);
|
||||
|
||||
const fetchGroupInfo = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups/${selectedGroup}`)
|
||||
.then((res: MainGroupProps) => {
|
||||
setGroupEnabled(res.status);
|
||||
setGroupName(res.name);
|
||||
setSelectedUsers(res.members);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoadingGroup(false);
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (selectedGroup && loadingGroup) {
|
||||
fetchGroupInfo();
|
||||
}
|
||||
}, [loadingGroup]);
|
||||
|
||||
return (<Dialog
|
||||
open={open}
|
||||
onClose={closeModalAndRefresh}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
//Fetch Actions
|
||||
const setSaving = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
isSaving(true);
|
||||
};
|
||||
|
||||
const saveRecord = () => {
|
||||
if (selectedGroup !== null) {
|
||||
api
|
||||
.invoke("PUT", `/api/v1/groups/${groupName}`, {
|
||||
group: groupName,
|
||||
members: selectedUsers,
|
||||
status: groupEnabled
|
||||
})
|
||||
.then(res => {
|
||||
isSaving(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
isSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
} else {
|
||||
api
|
||||
.invoke("POST", "/api/v1/groups", {
|
||||
group: groupName,
|
||||
members: selectedUsers
|
||||
})
|
||||
.then(res => {
|
||||
isSaving(false);
|
||||
setError("");
|
||||
closeModalAndRefresh();
|
||||
})
|
||||
.catch(err => {
|
||||
isSaving(false);
|
||||
setError(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const fetchGroupInfo = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups/${selectedGroup}`)
|
||||
.then((res: MainGroupProps) => {
|
||||
setGroupEnabled(res.status);
|
||||
setGroupName(res.name);
|
||||
setSelectedUsers(res.members);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoadingGroup(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={closeModalAndRefresh}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{selectedGroup !== null ? `Group Edit - ${groupName}` : 'Add Group'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={setSaving}
|
||||
>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{selectedGroup !== null ? `Group Edit - ${groupName}` : "Add Group"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<form noValidate autoComplete="off" onSubmit={setSaving}>
|
||||
<Grid container>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{addError}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{selectedGroup !== null ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Title>Status</Title>
|
||||
<RadioGroup
|
||||
aria-label="status"
|
||||
name="status"
|
||||
value={groupEnabled}
|
||||
onChange={(e) => {
|
||||
setGroupEnabled(e.target.value);
|
||||
}}
|
||||
>
|
||||
<FormControlLabel value="enabled" control={<Radio color={'primary'} />} label="Enabled" />
|
||||
<FormControlLabel value="disabled" control={<Radio color={'primary'} />} label="Disabled" />
|
||||
</RadioGroup>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
fullWidth
|
||||
label="Name"
|
||||
value={groupName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setGroupName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<UsersSelectors
|
||||
selectedUsers={selectedUsers}
|
||||
setSelectedUsers={setSelectedUsers}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={saving}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{saving && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
{selectedGroup !== null ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Title>Status</Title>
|
||||
<RadioGroup
|
||||
aria-label="status"
|
||||
name="status"
|
||||
value={groupEnabled}
|
||||
onChange={e => {
|
||||
setGroupEnabled(e.target.value);
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="enabled"
|
||||
control={<Radio color={"primary"} />}
|
||||
label="Enabled"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="disabled"
|
||||
control={<Radio color={"primary"} />}
|
||||
label="Disabled"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Grid>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>);
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
fullWidth
|
||||
label="Name"
|
||||
value={groupName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setGroupName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<UsersSelectors
|
||||
selectedUsers={selectedUsers}
|
||||
setSelectedUsers={setSelectedUsers}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={saving}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{saving && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(AddGroup);
|
||||
|
||||
@@ -17,118 +17,115 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress
|
||||
} from "@material-ui/core";
|
||||
import api from "../../../common/api";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {UsersList} from "../Users/types";
|
||||
import { UsersList } from "../Users/types";
|
||||
|
||||
interface IDeleteGroup {
|
||||
selectedGroup: string;
|
||||
deleteOpen: boolean;
|
||||
closeDeleteModalAndRefresh: any;
|
||||
classes: any;
|
||||
selectedGroup: string;
|
||||
deleteOpen: boolean;
|
||||
closeDeleteModalAndRefresh: any;
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
});
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
});
|
||||
|
||||
const DeleteGroup = ({
|
||||
selectedGroup,
|
||||
deleteOpen,
|
||||
closeDeleteModalAndRefresh,
|
||||
classes,
|
||||
}:IDeleteGroup) => {
|
||||
const [isDeleting, setDeleteLoading] = useState<boolean>(false);
|
||||
const [deleteError, setError] = useState<string>("");
|
||||
selectedGroup,
|
||||
deleteOpen,
|
||||
closeDeleteModalAndRefresh,
|
||||
classes
|
||||
}: IDeleteGroup) => {
|
||||
const [isDeleting, setDeleteLoading] = useState<boolean>(false);
|
||||
const [deleteError, setError] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if(isDeleting) {
|
||||
removeRecord();
|
||||
}
|
||||
}, [isDeleting]);
|
||||
useEffect(() => {
|
||||
if (isDeleting) {
|
||||
removeRecord();
|
||||
}
|
||||
}, [isDeleting]);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (!selectedGroup) {
|
||||
return;
|
||||
}
|
||||
const removeRecord = () => {
|
||||
if (!selectedGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/groups/${selectedGroup}`)
|
||||
.then((res: UsersList) => {
|
||||
setDeleteLoading(false);
|
||||
setError("");
|
||||
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch(err => {
|
||||
setDeleteLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
};
|
||||
|
||||
const closeNoAction = () => {
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/groups/${selectedGroup}`)
|
||||
.then((res: UsersList) => {
|
||||
setDeleteLoading(false);
|
||||
setError("");
|
||||
closeDeleteModalAndRefresh(false);
|
||||
};
|
||||
|
||||
return (<React.Fragment>
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={closeNoAction}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete User</DialogTitle>
|
||||
<DialogContent>
|
||||
{isDeleting && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete group <b>{selectedGroup}</b>
|
||||
?
|
||||
{deleteError !== "" && (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{deleteError}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={closeNoAction}
|
||||
color="primary"
|
||||
disabled={isDeleting}
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch(err => {
|
||||
setDeleteLoading(false);
|
||||
setError(err);
|
||||
});
|
||||
};
|
||||
|
||||
const closeNoAction = () => {
|
||||
setError("");
|
||||
closeDeleteModalAndRefresh(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={closeNoAction}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete User</DialogTitle>
|
||||
<DialogContent>
|
||||
{isDeleting && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete group <b>{selectedGroup}</b>?
|
||||
{deleteError !== "" && (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDeleteLoading(true);
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</React.Fragment>);
|
||||
{deleteError}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={closeNoAction} color="primary" disabled={isDeleting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDeleteLoading(true);
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(DeleteGroup)
|
||||
export default withStyles(styles)(DeleteGroup);
|
||||
|
||||
@@ -21,7 +21,13 @@ import Typography from "@material-ui/core/Typography";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import {Button, IconButton, LinearProgress, TableFooter, TablePagination} from "@material-ui/core";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "@material-ui/core";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
@@ -31,271 +37,270 @@ import TableBody from "@material-ui/core/TableBody";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import ViewIcon from "@material-ui/icons/Visibility";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import {CreateIcon} from "../../../icons";
|
||||
import { CreateIcon } from "../../../icons";
|
||||
import api from "../../../common/api";
|
||||
import {MinTablePaginationActions} from "../../../common/MinTablePaginationActions";
|
||||
import {GroupsList} from "./types";
|
||||
import {groupsSort, usersSort} from "../../../utils/sortFunctions";
|
||||
import {UsersList} from "../Users/types";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import { GroupsList } from "./types";
|
||||
import { groupsSort, usersSort } from "../../../utils/sortFunctions";
|
||||
import { UsersList } from "../Users/types";
|
||||
import AddGroup from "../Groups/AddGroup";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
|
||||
interface IGroupsProps {
|
||||
classes: any;
|
||||
openGroupModal: any;
|
||||
classes: any;
|
||||
openGroupModal: any;
|
||||
}
|
||||
|
||||
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: "right",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
}
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
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: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
}
|
||||
});
|
||||
|
||||
const Groups = ({
|
||||
classes,
|
||||
}: IGroupsProps) => {
|
||||
const Groups = ({ classes }: IGroupsProps) => {
|
||||
const [addGroupOpen, setGroupOpen] = useState<boolean>(false);
|
||||
const [selectedGroup, setSelectedGroup] = useState<any>(null);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [loading, isLoading] = useState<boolean>(false);
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
const [totalRecords, setTotalRecords] = useState<number>(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
|
||||
const [addGroupOpen, setGroupOpen] = useState<boolean>(false);
|
||||
const [selectedGroup, setSelectedGroup] = useState<any>(null);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [loading, isLoading] = useState<boolean>(false);
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
const [totalRecords, setTotalRecords] = useState<number>(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
setPage(0);
|
||||
setRowsPerPage(rPP);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
setPage(0);
|
||||
setRowsPerPage(rPP);
|
||||
};
|
||||
useEffect(() => {
|
||||
isLoading(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
isLoading(true);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
isLoading(true);
|
||||
}, [page, rowsPerPage]);
|
||||
|
||||
useEffect(() => {
|
||||
isLoading(true);
|
||||
}, [page, rowsPerPage]);
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
fetchRecords();
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if(loading) {
|
||||
fetchRecords();
|
||||
const fetchRecords = () => {
|
||||
const offset = page * rowsPerPage;
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups?offset=${offset}&limit=${rowsPerPage}`)
|
||||
.then((res: GroupsList) => {
|
||||
setRecords(res.groups.sort(groupsSort));
|
||||
setTotalRecords(res.total);
|
||||
setError("");
|
||||
isLoading(false);
|
||||
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if ((!res.groups || res.groups.length === 0) && page > 0) {
|
||||
const newPage = page - 1;
|
||||
setPage(newPage);
|
||||
}
|
||||
}, [loading]);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchRecords = () => {
|
||||
const offset = page * rowsPerPage;
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups?offset=${offset}&limit=${rowsPerPage}`)
|
||||
.then((res: GroupsList) => {
|
||||
setRecords(res.groups.sort(groupsSort));
|
||||
setTotalRecords(res.total);
|
||||
setError("");
|
||||
isLoading(false);
|
||||
const closeAddModalAndRefresh = () => {
|
||||
setGroupOpen(false);
|
||||
isLoading(true);
|
||||
};
|
||||
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if ((!res.groups || res.groups.length === 0) && page > 0) {
|
||||
const newPage = page - 1;
|
||||
setPage(newPage);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
const closeDeleteModalAndRefresh = (refresh: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
if (refresh) {
|
||||
isLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter(elementItem =>
|
||||
elementItem.includes(filter)
|
||||
);
|
||||
|
||||
const closeAddModalAndRefresh = () => {
|
||||
setGroupOpen(false);
|
||||
isLoading(true);
|
||||
};
|
||||
|
||||
const closeDeleteModalAndRefresh = (refresh: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
if (refresh) {
|
||||
isLoading(true);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter((elementItem) => elementItem.includes(filter));
|
||||
|
||||
return (<React.Fragment>
|
||||
{ addGroupOpen &&
|
||||
<AddGroup
|
||||
open={addGroupOpen}
|
||||
selectedGroup={selectedGroup}
|
||||
closeModalAndRefresh={closeAddModalAndRefresh}
|
||||
/>
|
||||
}
|
||||
{ deleteOpen &&
|
||||
<DeleteGroup
|
||||
deleteOpen={deleteOpen}
|
||||
selectedGroup={selectedGroup}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Groups</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Groups"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={ <CreateIcon /> }
|
||||
onClick={() => {
|
||||
setSelectedGroup(null);
|
||||
setGroupOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Group
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{filteredRecords.map(group => (
|
||||
<TableRow key={`user-${group}`}>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{group}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
aria-label="view"
|
||||
onClick={() => {
|
||||
setGroupOpen(true);
|
||||
setSelectedGroup(group);
|
||||
}}
|
||||
>
|
||||
<ViewIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
setDeleteOpen(true);
|
||||
setSelectedGroup(group);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
colSpan={3}
|
||||
count={totalRecords}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
SelectProps={{
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
}}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
ActionsComponent={MinTablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
) : (
|
||||
<div>No Groups Available</div>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addGroupOpen && (
|
||||
<AddGroup
|
||||
open={addGroupOpen}
|
||||
selectedGroup={selectedGroup}
|
||||
closeModalAndRefresh={closeAddModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
{deleteOpen && (
|
||||
<DeleteGroup
|
||||
deleteOpen={deleteOpen}
|
||||
selectedGroup={selectedGroup}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Groups</Typography>
|
||||
</Grid>
|
||||
</React.Fragment>)
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Groups"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
onChange={e => {
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setSelectedGroup(null);
|
||||
setGroupOpen(true);
|
||||
}}
|
||||
>
|
||||
Create Group
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{filteredRecords.map(group => (
|
||||
<TableRow key={`user-${group}`}>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{group}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
aria-label="view"
|
||||
onClick={() => {
|
||||
setGroupOpen(true);
|
||||
setSelectedGroup(group);
|
||||
}}
|
||||
>
|
||||
<ViewIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
setDeleteOpen(true);
|
||||
setSelectedGroup(group);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
colSpan={3}
|
||||
count={totalRecords}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
SelectProps={{
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
}}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
ActionsComponent={MinTablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
) : (
|
||||
<div>No Groups Available</div>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Groups);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
import Table from "@material-ui/core/Table";
|
||||
@@ -31,163 +31,164 @@ import { usersSort } from "../../../utils/sortFunctions";
|
||||
import api from "../../../common/api";
|
||||
|
||||
interface IGroupsProps {
|
||||
classes: any;
|
||||
selectedUsers: string[];
|
||||
setSelectedUsers: any;
|
||||
classes: any;
|
||||
selectedUsers: string[];
|
||||
setSelectedUsers: any;
|
||||
}
|
||||
|
||||
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",
|
||||
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 UsersSelectors = ({
|
||||
classes,
|
||||
selectedUsers,
|
||||
setSelectedUsers,
|
||||
}: IGroupsProps) => {
|
||||
classes,
|
||||
selectedUsers,
|
||||
setSelectedUsers
|
||||
}: IGroupsProps) => {
|
||||
//Local States
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
const [loading, isLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
//Local States
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
const [loading, isLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
//Effects
|
||||
useEffect(() => {
|
||||
isLoading(true);
|
||||
}, []);
|
||||
|
||||
//Effects
|
||||
useEffect(() => {
|
||||
isLoading(true);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
fetchUsers();
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if(loading) {
|
||||
fetchUsers();
|
||||
}
|
||||
},[loading]);
|
||||
//Fetch Actions
|
||||
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
const value = targetD.value;
|
||||
const checked = targetD.checked;
|
||||
|
||||
//Fetch Actions
|
||||
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
const value = targetD.value;
|
||||
const checked = targetD.checked;
|
||||
let elements: string[] = [...selectedUsers]; // We clone the selectedGroups array
|
||||
|
||||
let elements : string[] = [...selectedUsers]; // 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);
|
||||
}
|
||||
setSelectedUsers(elements);
|
||||
|
||||
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);
|
||||
}
|
||||
setSelectedUsers(elements);
|
||||
return elements;
|
||||
};
|
||||
|
||||
return elements;
|
||||
};
|
||||
const fetchUsers = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/users`)
|
||||
.then((res: UsersList) => {
|
||||
setRecords(res.users.sort(usersSort));
|
||||
setError("");
|
||||
isLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchUsers = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/users`)
|
||||
.then((res: UsersList) => {
|
||||
setRecords(res.users.sort(usersSort));
|
||||
setError("");
|
||||
isLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title>Members</Title>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<React.Fragment>
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Access Key</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.map(row => (
|
||||
<TableRow key={`group-${row.accessKey}`}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.accessKey}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
'aria-label': 'secondary checkbox'
|
||||
}}
|
||||
onChange={ selectionChanged }
|
||||
checked={selectedUsers.includes(row.accessKey)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.accessKey}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<div className={classes.noFound}>No Users Available</div>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title>Members</Title>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<React.Fragment>
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Access Key</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.map(row => (
|
||||
<TableRow key={`group-${row.accessKey}`}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.accessKey}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
"aria-label": "secondary checkbox"
|
||||
}}
|
||||
onChange={selectionChanged}
|
||||
checked={selectedUsers.includes(row.accessKey)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.accessKey}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<div className={classes.noFound}>No Users Available</div>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(UsersSelectors);
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export interface Group {
|
||||
name: string;
|
||||
id: string;
|
||||
email: string;
|
||||
is_me: boolean;
|
||||
enabled: boolean;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
name: string;
|
||||
id: string;
|
||||
email: string;
|
||||
is_me: boolean;
|
||||
enabled: boolean;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
}
|
||||
|
||||
export interface GroupsList {
|
||||
groups: string[];
|
||||
total:number;
|
||||
groups: string[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
@@ -32,10 +32,10 @@ import {
|
||||
BucketsIcon,
|
||||
DashboardIcon,
|
||||
PermissionIcon,
|
||||
ServiceAccountIcon,
|
||||
UsersIcon
|
||||
} from "../../icons";
|
||||
import { createStyles, Theme } from "@material-ui/core/styles";
|
||||
import PersonIcon from "@material-ui/icons/Person";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -124,7 +124,7 @@ class Menu extends React.Component<MenuProps> {
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/users">
|
||||
<ListItemIcon>
|
||||
<UsersIcon />
|
||||
<PersonIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Users" />
|
||||
</ListItem>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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 React, { useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
import Table from "@material-ui/core/Table";
|
||||
@@ -30,9 +30,9 @@ import SearchIcon from "@material-ui/icons/Search";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import api from "../../../common/api";
|
||||
import {UsersList} from "./types";
|
||||
import {groupsSort, usersSort} from "../../../utils/sortFunctions";
|
||||
import {GroupsList} from "../Groups/types";
|
||||
import { UsersList } from "./types";
|
||||
import { groupsSort, usersSort } from "../../../utils/sortFunctions";
|
||||
import { GroupsList } from "../Groups/types";
|
||||
|
||||
interface IGroupsProps {
|
||||
classes: any;
|
||||
@@ -99,38 +99,38 @@ const styles = (theme: Theme) =>
|
||||
const GroupsSelectors = ({
|
||||
classes,
|
||||
selectedGroups,
|
||||
setSelectedGroups,
|
||||
setSelectedGroups
|
||||
}: IGroupsProps) => {
|
||||
// Local State
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
const [loading, isLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
// Local State
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
const [loading, isLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
|
||||
//Effects
|
||||
useEffect(() => {
|
||||
isLoading(true);
|
||||
}, []);
|
||||
//Effects
|
||||
useEffect(() => {
|
||||
isLoading(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if(loading) {
|
||||
fetchGroups();
|
||||
}
|
||||
},[loading]);
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
fetchGroups();
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
const fetchGroups = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups`)
|
||||
.then((res: GroupsList) => {
|
||||
setRecords(res.groups.sort(groupsSort));
|
||||
setError("");
|
||||
isLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
const fetchGroups = () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/groups`)
|
||||
.then((res: GroupsList) => {
|
||||
setRecords(res.groups.sort(groupsSort));
|
||||
setError("");
|
||||
isLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
@@ -151,7 +151,9 @@ const GroupsSelectors = ({
|
||||
return elements;
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter((elementItem) => elementItem.includes(filter));
|
||||
const filteredRecords = records.filter(elementItem =>
|
||||
elementItem.includes(filter)
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -175,8 +177,8 @@ const GroupsSelectors = ({
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value);
|
||||
onChange={e => {
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -127,7 +127,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
deleteOpen: false,
|
||||
selectedUser: null,
|
||||
addGroupOpen: false,
|
||||
filter: "",
|
||||
filter: ""
|
||||
};
|
||||
|
||||
fetchRecords() {
|
||||
@@ -191,7 +191,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
selectedUser,
|
||||
filter,
|
||||
filter
|
||||
} = this.state;
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
@@ -209,7 +209,9 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
});
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter((elementItem) => elementItem.accessKey.includes(filter));
|
||||
const filteredRecords = records.filter(elementItem =>
|
||||
elementItem.accessKey.includes(filter)
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -242,8 +244,8 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
onChange={(e) => {
|
||||
this.setState({filter: e.target.value});
|
||||
onChange={e => {
|
||||
this.setState({ filter: e.target.value });
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -15,29 +15,27 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
interface userInterface {
|
||||
accessKey: string;
|
||||
accessKey: string;
|
||||
}
|
||||
|
||||
export const usersSort = (a: userInterface, b: userInterface) => {
|
||||
if (a.accessKey > b.accessKey) {
|
||||
return 1;
|
||||
}
|
||||
if (a.accessKey < b.accessKey) {
|
||||
return -1;
|
||||
}
|
||||
// a must be equal to b
|
||||
return 0;
|
||||
if (a.accessKey > b.accessKey) {
|
||||
return 1;
|
||||
}
|
||||
if (a.accessKey < b.accessKey) {
|
||||
return -1;
|
||||
}
|
||||
// a must be equal to b
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const groupsSort = (a: string, b: string) => {
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
// a must be equal to b
|
||||
return 0;
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
// a must be equal to b
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user