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 SetAccessPolicy from "./SetAccessPolicy";
|
||||||
import DeleteBucket from "../ListBuckets/DeleteBucket";
|
import DeleteBucket from "../ListBuckets/DeleteBucket";
|
||||||
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
||||||
|
import { CreateIcon } from "../../../../icons";
|
||||||
|
import AddEvent from "./AddEvent";
|
||||||
|
import DeleteEvent from "./DeleteEvent";
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@@ -102,8 +105,10 @@ interface IViewBucketState {
|
|||||||
setAccessPolicyScreenOpen: boolean;
|
setAccessPolicyScreenOpen: boolean;
|
||||||
page: number;
|
page: number;
|
||||||
rowsPerPage: number;
|
rowsPerPage: number;
|
||||||
|
addScreenOpen: boolean;
|
||||||
deleteOpen: boolean;
|
deleteOpen: boolean;
|
||||||
selectedBucket: string;
|
selectedBucket: string;
|
||||||
|
selectedEvent: BucketEvent | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||||
@@ -117,11 +122,13 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
|||||||
setAccessPolicyScreenOpen: false,
|
setAccessPolicyScreenOpen: false,
|
||||||
page: 0,
|
page: 0,
|
||||||
rowsPerPage: 10,
|
rowsPerPage: 10,
|
||||||
|
addScreenOpen: false,
|
||||||
deleteOpen: false,
|
deleteOpen: false,
|
||||||
selectedBucket: ""
|
selectedBucket: "",
|
||||||
|
selectedEvent: null
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchRecords() {
|
fetchEvents() {
|
||||||
this.setState({ loading: true }, () => {
|
this.setState({ loading: true }, () => {
|
||||||
const { page, rowsPerPage } = this.state;
|
const { page, rowsPerPage } = this.state;
|
||||||
const { match } = this.props;
|
const { match } = this.props;
|
||||||
@@ -144,7 +151,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
|||||||
) {
|
) {
|
||||||
const newPage = page - 1;
|
const newPage = page - 1;
|
||||||
this.setState({ page: newPage }, () => {
|
this.setState({ page: newPage }, () => {
|
||||||
this.fetchRecords();
|
this.fetchEvents();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -163,7 +170,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
|||||||
closeDeleteModalAndRefresh(refresh: boolean) {
|
closeDeleteModalAndRefresh(refresh: boolean) {
|
||||||
this.setState({ deleteOpen: false }, () => {
|
this.setState({ deleteOpen: false }, () => {
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
this.fetchRecords();
|
this.fetchEvents();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -181,7 +188,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
|||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
this.loadInfo();
|
this.loadInfo();
|
||||||
this.fetchRecords();
|
this.fetchEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketFilter(): void {}
|
bucketFilter(): void {}
|
||||||
@@ -197,7 +204,9 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
|||||||
page,
|
page,
|
||||||
rowsPerPage,
|
rowsPerPage,
|
||||||
deleteOpen,
|
deleteOpen,
|
||||||
selectedBucket
|
addScreenOpen,
|
||||||
|
selectedBucket,
|
||||||
|
selectedEvent
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const offset = page * rowsPerPage;
|
const offset = page * rowsPerPage;
|
||||||
@@ -215,8 +224,8 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
|||||||
this.setState({ page: 0, rowsPerPage: rPP });
|
this.setState({ page: 0, rowsPerPage: rPP });
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmDeleteEvent = (bucket: string) => {
|
const confirmDeleteEvent = (evnt: BucketEvent) => {
|
||||||
this.setState({ deleteOpen: true, selectedBucket: bucket });
|
this.setState({ deleteOpen: true, selectedEvent: evnt });
|
||||||
};
|
};
|
||||||
|
|
||||||
let accessPolicy = "n/a";
|
let accessPolicy = "n/a";
|
||||||
@@ -226,6 +235,14 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
<AddEvent
|
||||||
|
open={addScreenOpen}
|
||||||
|
selectedBucket={bucketName}
|
||||||
|
closeModalAndRefresh={() => {
|
||||||
|
this.setState({ addScreenOpen: false });
|
||||||
|
this.fetchEvents();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<SetAccessPolicy
|
<SetAccessPolicy
|
||||||
bucketName={bucketName}
|
bucketName={bucketName}
|
||||||
open={setAccessPolicyScreenOpen}
|
open={setAccessPolicyScreenOpen}
|
||||||
@@ -267,7 +284,21 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
|||||||
<Grid item xs={6}>
|
<Grid item xs={6}>
|
||||||
<Typography variant="h6">Events</Typography>
|
<Typography variant="h6">Events</Typography>
|
||||||
</Grid>
|
</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}>
|
<Grid item xs={12}>
|
||||||
<br />
|
<br />
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -296,7 +327,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
|||||||
<IconButton
|
<IconButton
|
||||||
aria-label="delete"
|
aria-label="delete"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
confirmDeleteEvent(row.id);
|
confirmDeleteEvent(row);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
@@ -331,9 +362,10 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<DeleteBucket
|
<DeleteEvent
|
||||||
deleteOpen={deleteOpen}
|
deleteOpen={deleteOpen}
|
||||||
selectedBucket={selectedBucket}
|
selectedBucket={bucketName}
|
||||||
|
bucketEvent={selectedEvent}
|
||||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||||
this.closeDeleteModalAndRefresh(refresh);
|
this.closeDeleteModalAndRefresh(refresh);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -41,3 +41,7 @@ export interface BucketEventList {
|
|||||||
events: BucketEvent[];
|
events: BucketEvent[];
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ArnList {
|
||||||
|
arns: string[];
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,228 +16,241 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||||
import {Button, Dialog, DialogContent, DialogTitle, LinearProgress, TextField} from "@material-ui/core";
|
import {
|
||||||
import Radio from '@material-ui/core/Radio';
|
Button,
|
||||||
import RadioGroup from '@material-ui/core/RadioGroup';
|
Dialog,
|
||||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
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 Grid from "@material-ui/core/Grid";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import api from "../../../common/api";
|
import api from "../../../common/api";
|
||||||
import UsersSelectors from "./UsersSelectors";
|
import UsersSelectors from "./UsersSelectors";
|
||||||
import {GroupsList} from "./types";
|
import { GroupsList } from "./types";
|
||||||
import {groupsSort} from "../../../utils/sortFunctions";
|
import { groupsSort } from "../../../utils/sortFunctions";
|
||||||
import Title from "../../../common/Title";
|
import Title from "../../../common/Title";
|
||||||
|
|
||||||
interface IGroupProps {
|
interface IGroupProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
selectedGroup: any;
|
selectedGroup: any;
|
||||||
closeModalAndRefresh: any;
|
closeModalAndRefresh: any;
|
||||||
classes: any;
|
classes: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MainGroupProps {
|
interface MainGroupProps {
|
||||||
members: string[];
|
members: string[];
|
||||||
name: string;
|
name: string;
|
||||||
status: string;
|
status: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
errorBlock: {
|
errorBlock: {
|
||||||
color: "red"
|
color: "red"
|
||||||
},
|
},
|
||||||
strongText: {
|
strongText: {
|
||||||
fontWeight: 700,
|
fontWeight: 700
|
||||||
},
|
},
|
||||||
keyName: {
|
keyName: {
|
||||||
marginLeft: 5
|
marginLeft: 5
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const AddGroup = ({
|
const AddGroup = ({
|
||||||
open,
|
open,
|
||||||
selectedGroup,
|
selectedGroup,
|
||||||
closeModalAndRefresh,
|
closeModalAndRefresh,
|
||||||
classes,
|
classes
|
||||||
}: IGroupProps) => {
|
}: 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
|
//Effects
|
||||||
const [groupName, setGroupName] = useState<string>("");
|
useEffect(() => {
|
||||||
const [groupEnabled, setGroupEnabled] = useState<string>("");
|
if (selectedGroup !== null) {
|
||||||
const [saving, isSaving] = useState<boolean>(false);
|
isLoadingGroup(true);
|
||||||
const [addError, setError] = useState<string>("");
|
} else {
|
||||||
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
|
setGroupName("");
|
||||||
const [loadingGroup, isLoadingGroup] = useState<boolean>(false);
|
setSelectedUsers([]);
|
||||||
|
|
||||||
//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);
|
|
||||||
}
|
}
|
||||||
|
}, [selectedGroup]);
|
||||||
|
|
||||||
const saveRecord = () => {
|
useEffect(() => {
|
||||||
if (selectedGroup !== null) {
|
if (saving) {
|
||||||
api
|
saveRecord();
|
||||||
.invoke("PUT", `/api/v1/groups/${groupName}`, {
|
}
|
||||||
group: groupName,
|
}, [saving]);
|
||||||
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 = () => {
|
useEffect(() => {
|
||||||
api
|
if (selectedGroup && loadingGroup) {
|
||||||
.invoke("GET", `/api/v1/groups/${selectedGroup}`)
|
fetchGroupInfo();
|
||||||
.then((res: MainGroupProps) => {
|
}
|
||||||
setGroupEnabled(res.status);
|
}, [loadingGroup]);
|
||||||
setGroupName(res.name);
|
|
||||||
setSelectedUsers(res.members);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
setError(err);
|
|
||||||
isLoadingGroup(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (<Dialog
|
//Fetch Actions
|
||||||
open={open}
|
const setSaving = (event: React.FormEvent) => {
|
||||||
onClose={closeModalAndRefresh}
|
event.preventDefault();
|
||||||
aria-labelledby="alert-dialog-title"
|
|
||||||
aria-describedby="alert-dialog-description"
|
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">
|
<DialogTitle id="alert-dialog-title">
|
||||||
{selectedGroup !== null ? `Group Edit - ${groupName}` : 'Add Group'}
|
{selectedGroup !== null ? `Group Edit - ${groupName}` : "Add Group"}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<form
|
<form noValidate autoComplete="off" onSubmit={setSaving}>
|
||||||
noValidate
|
<Grid container>
|
||||||
autoComplete="off"
|
{addError !== "" && (
|
||||||
onSubmit={setSaving}
|
<Grid item xs={12}>
|
||||||
>
|
<Typography
|
||||||
<Grid container>
|
component="p"
|
||||||
{addError !== "" && (
|
variant="body1"
|
||||||
<Grid item xs={12}>
|
className={classes.errorBlock}
|
||||||
<Typography
|
>
|
||||||
component="p"
|
{addError}
|
||||||
variant="body1"
|
</Typography>
|
||||||
className={classes.errorBlock}
|
</Grid>
|
||||||
>
|
)}
|
||||||
{addError}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedGroup !== null ? (
|
{selectedGroup !== null ? (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Title>Status</Title>
|
<Title>Status</Title>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
aria-label="status"
|
aria-label="status"
|
||||||
name="status"
|
name="status"
|
||||||
value={groupEnabled}
|
value={groupEnabled}
|
||||||
onChange={(e) => {
|
onChange={e => {
|
||||||
setGroupEnabled(e.target.value);
|
setGroupEnabled(e.target.value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormControlLabel value="enabled" control={<Radio color={'primary'} />} label="Enabled" />
|
<FormControlLabel
|
||||||
<FormControlLabel value="disabled" control={<Radio color={'primary'} />} label="Disabled" />
|
value="enabled"
|
||||||
</RadioGroup>
|
control={<Radio color={"primary"} />}
|
||||||
</Grid>
|
label="Enabled"
|
||||||
</React.Fragment>
|
/>
|
||||||
) : (
|
<FormControlLabel
|
||||||
<React.Fragment>
|
value="disabled"
|
||||||
<Grid item xs={12}>
|
control={<Radio color={"primary"} />}
|
||||||
<TextField
|
label="Disabled"
|
||||||
id="standard-basic"
|
/>
|
||||||
fullWidth
|
</RadioGroup>
|
||||||
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>
|
</Grid>
|
||||||
</form>
|
</React.Fragment>
|
||||||
</DialogContent>
|
) : (
|
||||||
</Dialog>);
|
<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);
|
export default withStyles(styles)(AddGroup);
|
||||||
|
|||||||
@@ -17,118 +17,115 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
LinearProgress
|
LinearProgress
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import api from "../../../common/api";
|
import api from "../../../common/api";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import {UsersList} from "../Users/types";
|
import { UsersList } from "../Users/types";
|
||||||
|
|
||||||
interface IDeleteGroup {
|
interface IDeleteGroup {
|
||||||
selectedGroup: string;
|
selectedGroup: string;
|
||||||
deleteOpen: boolean;
|
deleteOpen: boolean;
|
||||||
closeDeleteModalAndRefresh: any;
|
closeDeleteModalAndRefresh: any;
|
||||||
classes: any;
|
classes: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
errorBlock: {
|
errorBlock: {
|
||||||
color: "red"
|
color: "red"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const DeleteGroup = ({
|
const DeleteGroup = ({
|
||||||
selectedGroup,
|
selectedGroup,
|
||||||
deleteOpen,
|
deleteOpen,
|
||||||
closeDeleteModalAndRefresh,
|
closeDeleteModalAndRefresh,
|
||||||
classes,
|
classes
|
||||||
}:IDeleteGroup) => {
|
}: IDeleteGroup) => {
|
||||||
const [isDeleting, setDeleteLoading] = useState<boolean>(false);
|
const [isDeleting, setDeleteLoading] = useState<boolean>(false);
|
||||||
const [deleteError, setError] = useState<string>("");
|
const [deleteError, setError] = useState<string>("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(isDeleting) {
|
if (isDeleting) {
|
||||||
removeRecord();
|
removeRecord();
|
||||||
}
|
}
|
||||||
}, [isDeleting]);
|
}, [isDeleting]);
|
||||||
|
|
||||||
const removeRecord = () => {
|
const removeRecord = () => {
|
||||||
if (!selectedGroup) {
|
if (!selectedGroup) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
api
|
api
|
||||||
.invoke("DELETE", `/api/v1/groups/${selectedGroup}`)
|
.invoke("DELETE", `/api/v1/groups/${selectedGroup}`)
|
||||||
.then((res: UsersList) => {
|
.then((res: UsersList) => {
|
||||||
setDeleteLoading(false);
|
setDeleteLoading(false);
|
||||||
setError("");
|
|
||||||
|
|
||||||
closeDeleteModalAndRefresh(true);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
setDeleteLoading(false);
|
|
||||||
setError(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeNoAction = () => {
|
|
||||||
setError("");
|
setError("");
|
||||||
closeDeleteModalAndRefresh(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (<React.Fragment>
|
closeDeleteModalAndRefresh(true);
|
||||||
<Dialog
|
})
|
||||||
open={deleteOpen}
|
.catch(err => {
|
||||||
onClose={closeNoAction}
|
setDeleteLoading(false);
|
||||||
aria-labelledby="alert-dialog-title"
|
setError(err);
|
||||||
aria-describedby="alert-dialog-description"
|
});
|
||||||
>
|
};
|
||||||
<DialogTitle id="alert-dialog-title">Delete User</DialogTitle>
|
|
||||||
<DialogContent>
|
const closeNoAction = () => {
|
||||||
{isDeleting && <LinearProgress />}
|
setError("");
|
||||||
<DialogContentText id="alert-dialog-description">
|
closeDeleteModalAndRefresh(false);
|
||||||
Are you sure you want to delete group <b>{selectedGroup}</b>
|
};
|
||||||
?
|
|
||||||
{deleteError !== "" && (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<br />
|
<Dialog
|
||||||
<Typography
|
open={deleteOpen}
|
||||||
component="p"
|
onClose={closeNoAction}
|
||||||
variant="body1"
|
aria-labelledby="alert-dialog-title"
|
||||||
className={classes.errorBlock}
|
aria-describedby="alert-dialog-description"
|
||||||
>
|
>
|
||||||
{deleteError}
|
<DialogTitle id="alert-dialog-title">Delete User</DialogTitle>
|
||||||
</Typography>
|
<DialogContent>
|
||||||
</React.Fragment>
|
{isDeleting && <LinearProgress />}
|
||||||
)}
|
<DialogContentText id="alert-dialog-description">
|
||||||
</DialogContentText>
|
Are you sure you want to delete group <b>{selectedGroup}</b>?
|
||||||
</DialogContent>
|
{deleteError !== "" && (
|
||||||
<DialogActions>
|
<React.Fragment>
|
||||||
<Button
|
<br />
|
||||||
onClick={closeNoAction}
|
<Typography
|
||||||
color="primary"
|
component="p"
|
||||||
disabled={isDeleting}
|
variant="body1"
|
||||||
|
className={classes.errorBlock}
|
||||||
>
|
>
|
||||||
Cancel
|
{deleteError}
|
||||||
</Button>
|
</Typography>
|
||||||
<Button
|
</React.Fragment>
|
||||||
onClick={() => {
|
)}
|
||||||
setDeleteLoading(true);
|
</DialogContentText>
|
||||||
}}
|
</DialogContent>
|
||||||
color="secondary"
|
<DialogActions>
|
||||||
autoFocus
|
<Button onClick={closeNoAction} color="primary" disabled={isDeleting}>
|
||||||
>
|
Cancel
|
||||||
Delete
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
</DialogActions>
|
onClick={() => {
|
||||||
</Dialog>
|
setDeleteLoading(true);
|
||||||
</React.Fragment>);
|
}}
|
||||||
|
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 TextField from "@material-ui/core/TextField";
|
||||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||||
import SearchIcon from "@material-ui/icons/Search";
|
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 Paper from "@material-ui/core/Paper";
|
||||||
import Table from "@material-ui/core/Table";
|
import Table from "@material-ui/core/Table";
|
||||||
import TableHead from "@material-ui/core/TableHead";
|
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 Checkbox from "@material-ui/core/Checkbox";
|
||||||
import ViewIcon from "@material-ui/icons/Visibility";
|
import ViewIcon from "@material-ui/icons/Visibility";
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
import {CreateIcon} from "../../../icons";
|
import { CreateIcon } from "../../../icons";
|
||||||
import api from "../../../common/api";
|
import api from "../../../common/api";
|
||||||
import {MinTablePaginationActions} from "../../../common/MinTablePaginationActions";
|
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||||
import {GroupsList} from "./types";
|
import { GroupsList } from "./types";
|
||||||
import {groupsSort, usersSort} from "../../../utils/sortFunctions";
|
import { groupsSort, usersSort } from "../../../utils/sortFunctions";
|
||||||
import {UsersList} from "../Users/types";
|
import { UsersList } from "../Users/types";
|
||||||
import AddGroup from "../Groups/AddGroup";
|
import AddGroup from "../Groups/AddGroup";
|
||||||
import DeleteGroup from "./DeleteGroup";
|
import DeleteGroup from "./DeleteGroup";
|
||||||
|
|
||||||
interface IGroupsProps {
|
interface IGroupsProps {
|
||||||
classes: any;
|
classes: any;
|
||||||
openGroupModal: any;
|
openGroupModal: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
seeMore: {
|
seeMore: {
|
||||||
marginTop: theme.spacing(3)
|
marginTop: theme.spacing(3)
|
||||||
},
|
},
|
||||||
paper: {
|
paper: {
|
||||||
// padding: theme.spacing(2),
|
// padding: theme.spacing(2),
|
||||||
display: "flex",
|
display: "flex",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
flexDirection: "column"
|
flexDirection: "column"
|
||||||
},
|
},
|
||||||
addSideBar: {
|
addSideBar: {
|
||||||
width: "320px",
|
width: "320px",
|
||||||
padding: "20px"
|
padding: "20px"
|
||||||
},
|
},
|
||||||
errorBlock: {
|
errorBlock: {
|
||||||
color: "red"
|
color: "red"
|
||||||
},
|
},
|
||||||
tableToolbar: {
|
tableToolbar: {
|
||||||
paddingLeft: theme.spacing(2),
|
paddingLeft: theme.spacing(2),
|
||||||
paddingRight: theme.spacing(0)
|
paddingRight: theme.spacing(0)
|
||||||
},
|
},
|
||||||
wrapCell: {
|
wrapCell: {
|
||||||
maxWidth: "200px",
|
maxWidth: "200px",
|
||||||
whiteSpace: "normal",
|
whiteSpace: "normal",
|
||||||
wordWrap: "break-word"
|
wordWrap: "break-word"
|
||||||
},
|
},
|
||||||
minTableHeader: {
|
minTableHeader: {
|
||||||
color: "#393939",
|
color: "#393939",
|
||||||
"& tr": {
|
"& tr": {
|
||||||
"& th": {
|
"& th": {
|
||||||
fontWeight:'bold'
|
fontWeight: "bold"
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actionsTray: {
|
|
||||||
textAlign: "right",
|
|
||||||
"& button": {
|
|
||||||
marginLeft: 10,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
searchField: {
|
|
||||||
background: "#FFFFFF",
|
|
||||||
padding: 12,
|
|
||||||
borderRadius: 5,
|
|
||||||
boxShadow: "0px 3px 6px #00000012",
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
actionsTray: {
|
||||||
|
textAlign: "right",
|
||||||
|
"& button": {
|
||||||
|
marginLeft: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchField: {
|
||||||
|
background: "#FFFFFF",
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 5,
|
||||||
|
boxShadow: "0px 3px 6px #00000012"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const Groups = ({
|
const Groups = ({ classes }: IGroupsProps) => {
|
||||||
classes,
|
const [addGroupOpen, setGroupOpen] = useState<boolean>(false);
|
||||||
}: IGroupsProps) => {
|
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 handleChangePage = (event: unknown, newPage: number) => {
|
||||||
const [selectedGroup, setSelectedGroup] = useState<any>(null);
|
setPage(newPage);
|
||||||
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) => {
|
const handleChangeRowsPerPage = (
|
||||||
setPage(newPage);
|
event: React.ChangeEvent<HTMLInputElement>
|
||||||
};
|
) => {
|
||||||
|
const rPP = parseInt(event.target.value, 10);
|
||||||
|
setPage(0);
|
||||||
|
setRowsPerPage(rPP);
|
||||||
|
};
|
||||||
|
|
||||||
const handleChangeRowsPerPage = (
|
useEffect(() => {
|
||||||
event: React.ChangeEvent<HTMLInputElement>
|
isLoading(true);
|
||||||
) => {
|
}, []);
|
||||||
const rPP = parseInt(event.target.value, 10);
|
|
||||||
setPage(0);
|
|
||||||
setRowsPerPage(rPP);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isLoading(true);
|
isLoading(true);
|
||||||
}, []);
|
}, [page, rowsPerPage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isLoading(true);
|
if (loading) {
|
||||||
}, [page, rowsPerPage]);
|
fetchRecords();
|
||||||
|
}
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchRecords = () => {
|
||||||
if(loading) {
|
const offset = page * rowsPerPage;
|
||||||
fetchRecords();
|
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 closeAddModalAndRefresh = () => {
|
||||||
const offset = page * rowsPerPage;
|
setGroupOpen(false);
|
||||||
api
|
isLoading(true);
|
||||||
.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
|
const closeDeleteModalAndRefresh = (refresh: boolean) => {
|
||||||
if ((!res.groups || res.groups.length === 0) && page > 0) {
|
setDeleteOpen(false);
|
||||||
const newPage = page - 1;
|
|
||||||
setPage(newPage);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
setError(err);
|
|
||||||
isLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
if (refresh) {
|
||||||
|
isLoading(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredRecords = records.filter(elementItem =>
|
||||||
|
elementItem.includes(filter)
|
||||||
|
);
|
||||||
|
|
||||||
const closeAddModalAndRefresh = () => {
|
return (
|
||||||
setGroupOpen(false);
|
<React.Fragment>
|
||||||
isLoading(true);
|
{addGroupOpen && (
|
||||||
};
|
<AddGroup
|
||||||
|
open={addGroupOpen}
|
||||||
const closeDeleteModalAndRefresh = (refresh: boolean) => {
|
selectedGroup={selectedGroup}
|
||||||
setDeleteOpen(false);
|
closeModalAndRefresh={closeAddModalAndRefresh}
|
||||||
|
/>
|
||||||
if (refresh) {
|
)}
|
||||||
isLoading(true);
|
{deleteOpen && (
|
||||||
}
|
<DeleteGroup
|
||||||
};
|
deleteOpen={deleteOpen}
|
||||||
|
selectedGroup={selectedGroup}
|
||||||
const filteredRecords = records.filter((elementItem) => elementItem.includes(filter));
|
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||||
|
/>
|
||||||
return (<React.Fragment>
|
)}
|
||||||
{ addGroupOpen &&
|
<Grid container>
|
||||||
<AddGroup
|
<Grid item xs={12}>
|
||||||
open={addGroupOpen}
|
<Typography variant="h6">Groups</Typography>
|
||||||
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>
|
|
||||||
</Grid>
|
</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);
|
export default withStyles(styles)(Groups);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||||
import { LinearProgress } from "@material-ui/core";
|
import { LinearProgress } from "@material-ui/core";
|
||||||
import Table from "@material-ui/core/Table";
|
import Table from "@material-ui/core/Table";
|
||||||
@@ -31,163 +31,164 @@ import { usersSort } from "../../../utils/sortFunctions";
|
|||||||
import api from "../../../common/api";
|
import api from "../../../common/api";
|
||||||
|
|
||||||
interface IGroupsProps {
|
interface IGroupsProps {
|
||||||
classes: any;
|
classes: any;
|
||||||
selectedUsers: string[];
|
selectedUsers: string[];
|
||||||
setSelectedUsers: any;
|
setSelectedUsers: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
seeMore: {
|
seeMore: {
|
||||||
marginTop: theme.spacing(3)
|
marginTop: theme.spacing(3)
|
||||||
},
|
},
|
||||||
paper: {
|
paper: {
|
||||||
// padding: theme.spacing(2),
|
// padding: theme.spacing(2),
|
||||||
display: "flex",
|
display: "flex",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
flexDirection: "column"
|
flexDirection: "column"
|
||||||
},
|
},
|
||||||
addSideBar: {
|
addSideBar: {
|
||||||
width: "320px",
|
width: "320px",
|
||||||
padding: "20px"
|
padding: "20px"
|
||||||
},
|
},
|
||||||
errorBlock: {
|
errorBlock: {
|
||||||
color: "red"
|
color: "red"
|
||||||
},
|
},
|
||||||
tableToolbar: {
|
tableToolbar: {
|
||||||
paddingLeft: theme.spacing(2),
|
paddingLeft: theme.spacing(2),
|
||||||
paddingRight: theme.spacing(0)
|
paddingRight: theme.spacing(0)
|
||||||
},
|
},
|
||||||
wrapCell: {
|
wrapCell: {
|
||||||
maxWidth: "200px",
|
maxWidth: "200px",
|
||||||
whiteSpace: "normal",
|
whiteSpace: "normal",
|
||||||
wordWrap: "break-word"
|
wordWrap: "break-word"
|
||||||
},
|
},
|
||||||
minTableHeader: {
|
minTableHeader: {
|
||||||
color: "#393939",
|
color: "#393939",
|
||||||
"& tr": {
|
"& tr": {
|
||||||
"& th": {
|
"& th": {
|
||||||
fontWeight:'bold'
|
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",
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
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 = ({
|
const UsersSelectors = ({
|
||||||
classes,
|
classes,
|
||||||
selectedUsers,
|
selectedUsers,
|
||||||
setSelectedUsers,
|
setSelectedUsers
|
||||||
}: IGroupsProps) => {
|
}: IGroupsProps) => {
|
||||||
|
//Local States
|
||||||
|
const [records, setRecords] = useState<any[]>([]);
|
||||||
|
const [loading, isLoading] = useState<boolean>(false);
|
||||||
|
const [error, setError] = useState<string>("");
|
||||||
|
|
||||||
//Local States
|
//Effects
|
||||||
const [records, setRecords] = useState<any[]>([]);
|
useEffect(() => {
|
||||||
const [loading, isLoading] = useState<boolean>(false);
|
isLoading(true);
|
||||||
const [error, setError] = useState<string>("");
|
}, []);
|
||||||
|
|
||||||
//Effects
|
useEffect(() => {
|
||||||
useEffect(() => {
|
if (loading) {
|
||||||
isLoading(true);
|
fetchUsers();
|
||||||
}, []);
|
}
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
useEffect(() => {
|
//Fetch Actions
|
||||||
if(loading) {
|
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
fetchUsers();
|
const targetD = e.target;
|
||||||
}
|
const value = targetD.value;
|
||||||
},[loading]);
|
const checked = targetD.checked;
|
||||||
|
|
||||||
//Fetch Actions
|
let elements: string[] = [...selectedUsers]; // We clone the selectedGroups array
|
||||||
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
|
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
|
return elements;
|
||||||
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;
|
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 = () => {
|
return (
|
||||||
api
|
<React.Fragment>
|
||||||
.invoke("GET", `/api/v1/users`)
|
<Title>Members</Title>
|
||||||
.then((res: UsersList) => {
|
<Grid item xs={12}>
|
||||||
setRecords(res.users.sort(usersSort));
|
<Paper className={classes.paper}>
|
||||||
setError("");
|
{loading && <LinearProgress />}
|
||||||
isLoading(false);
|
{records != null && records.length > 0 ? (
|
||||||
})
|
<React.Fragment>
|
||||||
.catch(err => {
|
<Table size="medium">
|
||||||
setError(err);
|
<TableHead className={classes.minTableHeader}>
|
||||||
isLoading(false);
|
<TableRow>
|
||||||
});
|
<TableCell>Select</TableCell>
|
||||||
};
|
<TableCell>Access Key</TableCell>
|
||||||
|
</TableRow>
|
||||||
return (
|
</TableHead>
|
||||||
<React.Fragment>
|
<TableBody>
|
||||||
<Title>Members</Title>
|
{records.map(row => (
|
||||||
<Grid item xs={12}>
|
<TableRow key={`group-${row.accessKey}`}>
|
||||||
<Paper className={classes.paper}>
|
<TableCell padding="checkbox">
|
||||||
{loading && <LinearProgress />}
|
<Checkbox
|
||||||
{records != null && records.length > 0 ? (
|
value={row.accessKey}
|
||||||
<React.Fragment>
|
color="primary"
|
||||||
<Table size="medium">
|
inputProps={{
|
||||||
<TableHead className={classes.minTableHeader}>
|
"aria-label": "secondary checkbox"
|
||||||
<TableRow>
|
}}
|
||||||
<TableCell>Select</TableCell>
|
onChange={selectionChanged}
|
||||||
<TableCell>Access Key</TableCell>
|
checked={selectedUsers.includes(row.accessKey)}
|
||||||
</TableRow>
|
/>
|
||||||
</TableHead>
|
</TableCell>
|
||||||
<TableBody>
|
<TableCell className={classes.wrapCell}>
|
||||||
{records.map(row => (
|
{row.accessKey}
|
||||||
<TableRow key={`group-${row.accessKey}`}>
|
</TableCell>
|
||||||
<TableCell padding="checkbox">
|
</TableRow>
|
||||||
<Checkbox
|
))}
|
||||||
value={row.accessKey}
|
</TableBody>
|
||||||
color="primary"
|
</Table>
|
||||||
inputProps={{
|
</React.Fragment>
|
||||||
'aria-label': 'secondary checkbox'
|
) : (
|
||||||
}}
|
<div className={classes.noFound}>No Users Available</div>
|
||||||
onChange={ selectionChanged }
|
)}
|
||||||
checked={selectedUsers.includes(row.accessKey)}
|
</Paper>
|
||||||
/>
|
</Grid>
|
||||||
</TableCell>
|
</React.Fragment>
|
||||||
<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);
|
export default withStyles(styles)(UsersSelectors);
|
||||||
|
|||||||
@@ -15,16 +15,16 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export interface Group {
|
export interface Group {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
is_me: boolean;
|
is_me: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupsList {
|
export interface GroupsList {
|
||||||
groups: string[];
|
groups: string[];
|
||||||
total:number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ import {
|
|||||||
BucketsIcon,
|
BucketsIcon,
|
||||||
DashboardIcon,
|
DashboardIcon,
|
||||||
PermissionIcon,
|
PermissionIcon,
|
||||||
ServiceAccountIcon,
|
|
||||||
UsersIcon
|
UsersIcon
|
||||||
} from "../../icons";
|
} from "../../icons";
|
||||||
import { createStyles, Theme } from "@material-ui/core/styles";
|
import { createStyles, Theme } from "@material-ui/core/styles";
|
||||||
|
import PersonIcon from "@material-ui/icons/Person";
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@@ -124,7 +124,7 @@ class Menu extends React.Component<MenuProps> {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem button component={NavLink} to="/users">
|
<ListItem button component={NavLink} to="/users">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<UsersIcon />
|
<PersonIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Users" />
|
<ListItemText primary="Users" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// 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/>.
|
// 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 { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||||
import { LinearProgress } from "@material-ui/core";
|
import { LinearProgress } from "@material-ui/core";
|
||||||
import Table from "@material-ui/core/Table";
|
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 TextField from "@material-ui/core/TextField";
|
||||||
import Checkbox from "@material-ui/core/Checkbox";
|
import Checkbox from "@material-ui/core/Checkbox";
|
||||||
import api from "../../../common/api";
|
import api from "../../../common/api";
|
||||||
import {UsersList} from "./types";
|
import { UsersList } from "./types";
|
||||||
import {groupsSort, usersSort} from "../../../utils/sortFunctions";
|
import { groupsSort, usersSort } from "../../../utils/sortFunctions";
|
||||||
import {GroupsList} from "../Groups/types";
|
import { GroupsList } from "../Groups/types";
|
||||||
|
|
||||||
interface IGroupsProps {
|
interface IGroupsProps {
|
||||||
classes: any;
|
classes: any;
|
||||||
@@ -99,38 +99,38 @@ const styles = (theme: Theme) =>
|
|||||||
const GroupsSelectors = ({
|
const GroupsSelectors = ({
|
||||||
classes,
|
classes,
|
||||||
selectedGroups,
|
selectedGroups,
|
||||||
setSelectedGroups,
|
setSelectedGroups
|
||||||
}: IGroupsProps) => {
|
}: IGroupsProps) => {
|
||||||
// Local State
|
// Local State
|
||||||
const [records, setRecords] = useState<any[]>([]);
|
const [records, setRecords] = useState<any[]>([]);
|
||||||
const [loading, isLoading] = useState<boolean>(false);
|
const [loading, isLoading] = useState<boolean>(false);
|
||||||
const [error, setError] = useState<string>("");
|
const [error, setError] = useState<string>("");
|
||||||
const [filter, setFilter] = useState<string>("");
|
const [filter, setFilter] = useState<string>("");
|
||||||
|
|
||||||
//Effects
|
//Effects
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isLoading(true);
|
isLoading(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(loading) {
|
if (loading) {
|
||||||
fetchGroups();
|
fetchGroups();
|
||||||
}
|
}
|
||||||
},[loading]);
|
}, [loading]);
|
||||||
|
|
||||||
const fetchGroups = () => {
|
const fetchGroups = () => {
|
||||||
api
|
api
|
||||||
.invoke("GET", `/api/v1/groups`)
|
.invoke("GET", `/api/v1/groups`)
|
||||||
.then((res: GroupsList) => {
|
.then((res: GroupsList) => {
|
||||||
setRecords(res.groups.sort(groupsSort));
|
setRecords(res.groups.sort(groupsSort));
|
||||||
setError("");
|
setError("");
|
||||||
isLoading(false);
|
isLoading(false);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
setError(err);
|
setError(err);
|
||||||
isLoading(false);
|
isLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const targetD = e.target;
|
const targetD = e.target;
|
||||||
@@ -151,7 +151,9 @@ const GroupsSelectors = ({
|
|||||||
return elements;
|
return elements;
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredRecords = records.filter((elementItem) => elementItem.includes(filter));
|
const filteredRecords = records.filter(elementItem =>
|
||||||
|
elementItem.includes(filter)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -175,8 +177,8 @@ const GroupsSelectors = ({
|
|||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
onChange={(e) => {
|
onChange={e => {
|
||||||
setFilter(e.target.value);
|
setFilter(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
|||||||
deleteOpen: false,
|
deleteOpen: false,
|
||||||
selectedUser: null,
|
selectedUser: null,
|
||||||
addGroupOpen: false,
|
addGroupOpen: false,
|
||||||
filter: "",
|
filter: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchRecords() {
|
fetchRecords() {
|
||||||
@@ -191,7 +191,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
|||||||
rowsPerPage,
|
rowsPerPage,
|
||||||
deleteOpen,
|
deleteOpen,
|
||||||
selectedUser,
|
selectedUser,
|
||||||
filter,
|
filter
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const handleChangePage = (event: unknown, newPage: number) => {
|
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 (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -242,8 +244,8 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
|||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
onChange={(e) => {
|
onChange={e => {
|
||||||
this.setState({filter: e.target.value});
|
this.setState({ filter: e.target.value });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -15,29 +15,27 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
interface userInterface {
|
interface userInterface {
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usersSort = (a: userInterface, b: userInterface) => {
|
export const usersSort = (a: userInterface, b: userInterface) => {
|
||||||
if (a.accessKey > b.accessKey) {
|
if (a.accessKey > b.accessKey) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (a.accessKey < b.accessKey) {
|
if (a.accessKey < b.accessKey) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// a must be equal to b
|
// a must be equal to b
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const groupsSort = (a: string, b: string) => {
|
export const groupsSort = (a: string, b: string) => {
|
||||||
if (a > b) {
|
if (a > b) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (a < b) {
|
if (a < b) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// a must be equal to b
|
// a must be equal to b
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user