Logout on Unauthorized. Fix all UI warnings. (#35)
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -24,7 +24,14 @@ export class API {
|
||||
.set("Authorization", `Bearer ${token}`)
|
||||
.send(data)
|
||||
.then(res => res.body)
|
||||
.catch(err => this.onError(err));
|
||||
.catch(err => {
|
||||
// if we get unauthorized, kick out the user
|
||||
if (err.status === 401) {
|
||||
storage.removeItem("token");
|
||||
window.location.href = "/";
|
||||
}
|
||||
this.onError(err);
|
||||
});
|
||||
}
|
||||
|
||||
onError(err: any) {
|
||||
@@ -38,5 +45,6 @@ export class API {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const api = new API();
|
||||
export default api;
|
||||
|
||||
@@ -35,9 +35,8 @@ import { AppState } from "../../../store";
|
||||
import { setMenuOpen } from "../../../actions";
|
||||
import { ThemedComponentProps } from "@material-ui/core/styles/withTheme";
|
||||
import NotFoundPage from "../../NotFoundPage";
|
||||
import BucketList from "./ListBuckets/ListBuckets";
|
||||
import ViewBucket from "./ViewBucket/ViewBucket";
|
||||
import ListBuckets from "./ListBuckets/ListBuckets";
|
||||
import ViewBucket from "./ViewBucket/ViewBucket";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -38,7 +38,7 @@ 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";
|
||||
import { ArnList } from "../types";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -156,26 +156,6 @@ class AddEvent extends React.Component<IAddEventProps, IAddEventState> {
|
||||
{ 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
|
||||
|
||||
@@ -97,7 +97,7 @@ class DeleteEvent extends React.Component<
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, deleteOpen, selectedBucket } = this.props;
|
||||
const { classes, deleteOpen } = this.props;
|
||||
const { deleteLoading, deleteError } = this.state;
|
||||
|
||||
return (
|
||||
|
||||
@@ -26,8 +26,7 @@ import {
|
||||
InputLabel,
|
||||
LinearProgress,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField
|
||||
Select
|
||||
} from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import api from "../../../../common/api";
|
||||
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
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";
|
||||
@@ -130,7 +129,7 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
|
||||
fetchEvents() {
|
||||
this.setState({ loading: true }, () => {
|
||||
const { page, rowsPerPage } = this.state;
|
||||
const { page } = this.state;
|
||||
const { match } = this.props;
|
||||
const bucketName = match.params["bucketName"];
|
||||
api
|
||||
@@ -205,7 +204,6 @@ class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
addScreenOpen,
|
||||
selectedBucket,
|
||||
selectedEvent
|
||||
} = this.state;
|
||||
|
||||
|
||||
@@ -170,7 +170,6 @@ class Console extends React.Component<
|
||||
ConsoleProps & RouteComponentProps & StyledProps & ThemedComponentProps
|
||||
> {
|
||||
componentDidMount(): void {
|
||||
//TODO: verify the session is still valid
|
||||
api
|
||||
.invoke("GET", `/api/v1/session`)
|
||||
.then(res => {
|
||||
|
||||
@@ -137,7 +137,7 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
});
|
||||
};
|
||||
const prettyUsage = (usage: string | undefined) => {
|
||||
if (usage == undefined) {
|
||||
if (usage === undefined) {
|
||||
return "0";
|
||||
}
|
||||
return niceBytes(usage);
|
||||
@@ -156,7 +156,7 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
return n.toFixed(n < 10 && l > 0 ? 1 : 0) + " " + units[l];
|
||||
};
|
||||
const prettyNumber = (usage: number | undefined) => {
|
||||
if (usage == undefined) {
|
||||
if (usage === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -165,11 +165,12 @@ const Dashboard = ({ classes }: IDashboardProps) => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid container xs={12}>
|
||||
<Grid container xs={12} spacing={3} className={classes.container}>
|
||||
<Grid container xs={12}>
|
||||
<Grid container>
|
||||
<Grid container spacing={3} className={classes.container}>
|
||||
<Grid container>
|
||||
<Typography variant="h2">MinIO Console</Typography>
|
||||
</Grid>
|
||||
{error !== "" && <Grid container>{error}</Grid>}
|
||||
<Grid item xs={12} md={4} lg={4}>
|
||||
<Paper className={fixedHeightPaper}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
|
||||
@@ -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, { useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
Button,
|
||||
@@ -31,8 +31,6 @@ 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 Title from "../../../common/Title";
|
||||
|
||||
interface IGroupProps {
|
||||
@@ -87,15 +85,69 @@ const AddGroup = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (saving) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
};
|
||||
saveRecord();
|
||||
}
|
||||
}, [saving]);
|
||||
}, [
|
||||
saving,
|
||||
groupName,
|
||||
selectedUsers,
|
||||
groupEnabled,
|
||||
selectedGroup,
|
||||
closeModalAndRefresh
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedGroup && loadingGroup) {
|
||||
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);
|
||||
});
|
||||
};
|
||||
fetchGroupInfo();
|
||||
}
|
||||
}, [loadingGroup]);
|
||||
}, [loadingGroup, selectedGroup]);
|
||||
|
||||
//Fetch Actions
|
||||
const setSaving = (event: React.FormEvent) => {
|
||||
@@ -104,55 +156,6 @@ const AddGroup = ({
|
||||
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}
|
||||
|
||||
@@ -54,28 +54,27 @@ const DeleteGroup = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (isDeleting) {
|
||||
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);
|
||||
});
|
||||
};
|
||||
removeRecord();
|
||||
}
|
||||
}, [isDeleting]);
|
||||
|
||||
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);
|
||||
});
|
||||
};
|
||||
}, [isDeleting, selectedGroup, closeDeleteModalAndRefresh]);
|
||||
|
||||
const closeNoAction = () => {
|
||||
setError("");
|
||||
|
||||
@@ -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, { useEffect, useState } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
@@ -34,15 +34,13 @@ 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 ViewIcon from "@material-ui/icons/Visibility";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
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 { groupsSort } from "../../../utils/sortFunctions";
|
||||
import AddGroup from "../Groups/AddGroup";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
|
||||
@@ -134,31 +132,30 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
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);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
fetchRecords();
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
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);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
}, [loading, page, rowsPerPage]);
|
||||
|
||||
const closeAddModalAndRefresh = () => {
|
||||
setGroupOpen(false);
|
||||
@@ -200,6 +197,7 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{error !== "" ? <Grid container>{error}</Grid> : <React.Fragment />}
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Groups"
|
||||
|
||||
@@ -163,6 +163,7 @@ const UsersSelectors = ({
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title>Members</Title>
|
||||
{error !== "" ? <div>{error}</div> : <React.Fragment />}
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
|
||||
@@ -40,13 +40,11 @@ import { MinTablePaginationActions } from "../../../common/MinTablePaginationAct
|
||||
import EditIcon from "@material-ui/icons/Edit";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import { CreateIcon } from "../../../icons";
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import InputBase from '@material-ui/core/InputBase';
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import PlayArrowRoundedIcon from "@material-ui/icons/PlayArrowRounded";
|
||||
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
@@ -84,14 +82,14 @@ const styles = (theme: Theme) =>
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
},
|
||||
marginLeft: 10
|
||||
}
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -241,7 +239,6 @@ class Permissions extends React.Component<
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
<AddPermission
|
||||
open={addScreenOpen}
|
||||
selectedPermission={selectedPermission}
|
||||
@@ -269,7 +266,7 @@ class Permissions extends React.Component<
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -324,7 +321,7 @@ class Permissions extends React.Component<
|
||||
<Checkbox
|
||||
value="secondary"
|
||||
color="primary"
|
||||
inputProps={{ 'aria-label': 'secondary checkbox' }}
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
|
||||
@@ -31,8 +31,8 @@ import Title from "../../../common/Title";
|
||||
import api from "../../../common/api";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/theme/material.css";
|
||||
import PolicyBuilder from "./PolicyBuilder";
|
||||
import { Policy } from "./types";
|
||||
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -70,6 +70,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
policyName: "",
|
||||
policyDefinition: ""
|
||||
};
|
||||
|
||||
addRecord(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
const { policyName, addLoading, policyDefinition } = this.state;
|
||||
@@ -101,9 +102,10 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open, policyEdit } = this.props;
|
||||
const { addLoading, addError, policyName, policyDefinition } = this.state;
|
||||
const { addLoading, addError } = this.state;
|
||||
return (
|
||||
<Dialog
|
||||
fullWidth
|
||||
|
||||
@@ -35,8 +35,7 @@ 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 Moment from "react-moment";
|
||||
import { PolicyList, Policy } from "./types";
|
||||
import { Policy, PolicyList } from "./types";
|
||||
import AddPolicy from "./AddPolicy";
|
||||
import DeletePolicy from "./DeletePolicy";
|
||||
import api from "../../../common/api";
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import React from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Title from "../../../common/Title";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {
|
||||
Button,
|
||||
@@ -87,7 +86,7 @@ class AddUserContent extends React.Component<
|
||||
|
||||
saveRecord(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
const { accessKey, addLoading, secretKey, selectedGroups } = this.state;
|
||||
const { accessKey, addLoading, secretKey } = this.state;
|
||||
const { selectedUser } = this.props;
|
||||
if (addLoading) {
|
||||
return;
|
||||
@@ -97,7 +96,7 @@ class AddUserContent extends React.Component<
|
||||
api
|
||||
.invoke("PUT", `/api/v1/users/${selectedUser.accessKey}`, {
|
||||
accessKey,
|
||||
secretKey: secretKey != "" ? null : secretKey
|
||||
secretKey: secretKey !== "" ? null : secretKey
|
||||
})
|
||||
.then(res => {
|
||||
this.setState(
|
||||
@@ -272,6 +271,7 @@ interface IAddUserState {}
|
||||
|
||||
class AddUser extends React.Component<IAddUserProps, IAddUserState> {
|
||||
state: IAddUserState = {};
|
||||
|
||||
render() {
|
||||
const { open } = this.props;
|
||||
return (
|
||||
|
||||
@@ -31,8 +31,7 @@ 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 { groupsSort } from "../../../utils/sortFunctions";
|
||||
import { GroupsList } from "../Groups/types";
|
||||
|
||||
interface IGroupsProps {
|
||||
@@ -168,6 +167,7 @@ const GroupsSelectors = ({
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{error !== "" && <div>{error}</div>}
|
||||
{records != null && records.length > 0 ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
|
||||
Reference in New Issue
Block a user