View Bucket Info (#11)
* View Bucket Info * Fix makefile for portal-ui * Static UI * Remove Warning on View Bucket * Address Comments
This commit is contained in:
2
Makefile
2
Makefile
@@ -10,7 +10,7 @@ swagger-gen:
|
||||
@swagger generate server -A mcs --main-package=mcs --exclude-main -P models.Principal -f ./swagger.yml -r NOTICE
|
||||
|
||||
build:
|
||||
@(cd portal-ui; yarn install; make build; cd ..)
|
||||
@(cd portal-ui; yarn install; make build-static; cd ..)
|
||||
@(CGO_ENABLED=0 go build --tags kqueue --ldflags "-s -w" -o mcs ./cmd/mcs)
|
||||
|
||||
test:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
default: build
|
||||
default: build-static
|
||||
|
||||
build:
|
||||
build-static:
|
||||
@echo "Building frontend static assets to 'build'"
|
||||
yarn build
|
||||
go-bindata-assetfs -pkg portal build/...
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -28,7 +28,7 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Acme Storage</title>
|
||||
<title>MinIO Console Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Acme Storage",
|
||||
"name": "MinIO Console Server",
|
||||
"icons": [
|
||||
{
|
||||
"src": "android-icon-36x36.png",
|
||||
|
||||
@@ -15,17 +15,15 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import {Route, Router, Switch} from "react-router-dom";
|
||||
import { Route, Router, Switch } from "react-router-dom";
|
||||
import history from "./history";
|
||||
import Login from "./screens/LoginPage";
|
||||
import Signup from "./screens/SignupPage";
|
||||
import Console from "./screens/Console/Console";
|
||||
import NotFoundPage from "./screens/NotFoundPage";
|
||||
import storage from "local-storage-fallback";
|
||||
import CreatePassword from "./screens/CreatePassword";
|
||||
import {connect} from "react-redux";
|
||||
import {AppState} from "./store";
|
||||
import {userLoggedIn} from "./actions";
|
||||
import { connect } from "react-redux";
|
||||
import { AppState } from "./store";
|
||||
import { userLoggedIn } from "./actions";
|
||||
|
||||
const isLoggedIn = () => {
|
||||
return (
|
||||
@@ -57,9 +55,7 @@ class Routes extends React.Component<RoutesProps> {
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route exact path="/create-password" component={CreatePassword} />
|
||||
<Route exact path="/login" component={Login} />
|
||||
<Route exact path="/signup" component={Signup} />
|
||||
{this.props.loggedIn ? (
|
||||
<Switch>
|
||||
<Route path="/*" component={Console} />
|
||||
|
||||
@@ -29,12 +29,16 @@ const GlobalCss = withStyles({
|
||||
// @global is handled by jss-plugin-global.
|
||||
"@global": {
|
||||
// You should target [class*="MuiButton-root"] instead if you nest themes.
|
||||
".MuiButton-root": {
|
||||
".MuiButton-contained": {
|
||||
fontSize: "14px",
|
||||
textTransform: "capitalize",
|
||||
padding: "16px 25px 16px 25px",
|
||||
borderRadius: "3px"
|
||||
},
|
||||
".MuiButton-sizeSmall": {
|
||||
padding: "4px 10px",
|
||||
fontSize: "0.8125rem"
|
||||
},
|
||||
".MuiTableCell-head": {
|
||||
borderRadius: "3px 3px 0px 0px",
|
||||
fontSize: "13px"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// This file is part of MinIO Buckets 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
|
||||
@@ -15,326 +15,114 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import api from "../../../common/api";
|
||||
import { Bucket, BucketList } from "./types";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import AddBucket from "./AddBucket";
|
||||
import DeleteBucket from "./DeleteBucket";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import { CreateIcon } from "../../../icons";
|
||||
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";
|
||||
createStyles,
|
||||
StyledProps,
|
||||
Theme,
|
||||
withStyles
|
||||
} from "@material-ui/core/styles";
|
||||
|
||||
import history from "../../../history";
|
||||
import {
|
||||
Route,
|
||||
RouteComponentProps,
|
||||
Router,
|
||||
Switch,
|
||||
withRouter
|
||||
} from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
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";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
root: {
|
||||
display: "flex"
|
||||
},
|
||||
toolbar: {
|
||||
background: theme.palette.background.default,
|
||||
color: "black",
|
||||
paddingRight: 24 // keep right padding when drawer closed
|
||||
},
|
||||
toolbarIcon: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
padding: "0 8px",
|
||||
...theme.mixins.toolbar
|
||||
},
|
||||
appBar: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create(["width", "margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
})
|
||||
},
|
||||
|
||||
menuButton: {
|
||||
marginRight: 36
|
||||
},
|
||||
menuButtonHidden: {
|
||||
display: "none"
|
||||
},
|
||||
title: {
|
||||
flexGrow: 1
|
||||
},
|
||||
appBarSpacer: {
|
||||
height: "5px"
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
height: "100vh",
|
||||
overflow: "auto"
|
||||
},
|
||||
container: {
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingBottom: theme.spacing(4)
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
},
|
||||
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px"
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
fixedHeight: {
|
||||
minHeight: 240
|
||||
}
|
||||
});
|
||||
|
||||
interface IBucketsProps {
|
||||
const mapState = (state: AppState) => ({
|
||||
open: state.system.sidebarOpen
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { setMenuOpen });
|
||||
|
||||
interface BucketsProps {
|
||||
open: boolean;
|
||||
title: string;
|
||||
classes: any;
|
||||
setMenuOpen: typeof setMenuOpen;
|
||||
}
|
||||
|
||||
interface IBucketsState {
|
||||
records: Bucket[];
|
||||
totalRecords: number;
|
||||
loading: boolean;
|
||||
error: string;
|
||||
deleteError: string;
|
||||
addScreenOpen: boolean;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
deleteOpen: boolean;
|
||||
selectedBucket: string;
|
||||
filterBuckets: string;
|
||||
}
|
||||
|
||||
class Buckets extends React.Component<IBucketsProps, IBucketsState> {
|
||||
state: IBucketsState = {
|
||||
records: [],
|
||||
totalRecords: 0,
|
||||
loading: false,
|
||||
error: "",
|
||||
deleteError: "",
|
||||
addScreenOpen: false,
|
||||
page: 0,
|
||||
rowsPerPage: 10,
|
||||
deleteOpen: false,
|
||||
selectedBucket: "",
|
||||
filterBuckets: ""
|
||||
};
|
||||
|
||||
fetchRecords() {
|
||||
this.setState({ loading: true }, () => {
|
||||
const { page, rowsPerPage } = this.state;
|
||||
const offset = page * rowsPerPage;
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets?offset=${offset}&limit=${rowsPerPage}`)
|
||||
.then((res: BucketList) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
records: res.buckets,
|
||||
totalRecords: res.total,
|
||||
error: ""
|
||||
});
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if (
|
||||
(res.buckets === undefined ||
|
||||
res.buckets == null ||
|
||||
res.buckets.length === 0) &&
|
||||
page > 0
|
||||
) {
|
||||
const newPage = page - 1;
|
||||
this.setState({ page: newPage }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({ loading: false, error: err });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
closeAddModalAndRefresh() {
|
||||
this.setState({ addScreenOpen: false }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
}
|
||||
|
||||
closeDeleteModalAndRefresh(refresh: boolean) {
|
||||
this.setState({ deleteOpen: false }, () => {
|
||||
if (refresh) {
|
||||
this.fetchRecords();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.fetchRecords();
|
||||
}
|
||||
|
||||
bucketFilter(): void {}
|
||||
|
||||
class Buckets extends React.Component<
|
||||
BucketsProps & RouteComponentProps & StyledProps & ThemedComponentProps
|
||||
> {
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const {
|
||||
records,
|
||||
totalRecords,
|
||||
addScreenOpen,
|
||||
loading,
|
||||
page,
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
selectedBucket,
|
||||
filterBuckets
|
||||
} = this.state;
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
this.setState({ page: newPage });
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
this.setState({ page: 0, rowsPerPage: rPP });
|
||||
};
|
||||
|
||||
const confirmDeleteBucket = (bucket: string) => {
|
||||
this.setState({ deleteOpen: true, selectedBucket: bucket });
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<AddBucket
|
||||
open={addScreenOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
this.closeAddModalAndRefresh();
|
||||
}}
|
||||
/>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Buckets</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Buckets"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={val => {
|
||||
this.setState({
|
||||
filterBuckets: val.target.value
|
||||
});
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true
|
||||
});
|
||||
}}
|
||||
>
|
||||
Create Bucket
|
||||
</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>Creation Date</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records
|
||||
.slice(offset, offset + rowsPerPage)
|
||||
.filter((b: Bucket) => {
|
||||
if (filterBuckets === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterBuckets) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(row => (
|
||||
<TableRow key={row.name}>
|
||||
<TableCell>{row.name}</TableCell>
|
||||
<TableCell>
|
||||
<Moment>{row.creation_date}</Moment>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
confirmDeleteBucket(row.name);
|
||||
}}
|
||||
>
|
||||
<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 Buckets</div>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<DeleteBucket
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||
this.closeDeleteModalAndRefresh(refresh);
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path="/buckets/:bucketName" component={ViewBucket} />
|
||||
<Route path="/" component={ListBuckets} />
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Buckets);
|
||||
export default withRouter(connector(withStyles(styles)(Buckets)));
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Title from "../../../common/Title";
|
||||
import Title from "../../../../common/Title";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {
|
||||
Button,
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
TextField
|
||||
} from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import api from "../../../common/api";
|
||||
import api from "../../../../common/api";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -25,8 +25,8 @@ import {
|
||||
DialogTitle,
|
||||
LinearProgress
|
||||
} from "@material-ui/core";
|
||||
import api from "../../../common/api";
|
||||
import { BucketList } from "./types";
|
||||
import api from "../../../../common/api";
|
||||
import { BucketList } from "../types";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -96,7 +96,7 @@ class DeleteBucket extends React.Component<
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
this.setState({deleteError:""},()=>{
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.props.closeDeleteModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
@@ -126,7 +126,7 @@ class DeleteBucket extends React.Component<
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.setState({deleteError:""},()=>{
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.props.closeDeleteModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
@@ -0,0 +1,350 @@
|
||||
// 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 from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import api from "../../../../common/api";
|
||||
import { Bucket, BucketList } from "../types";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import AddBucket from "./AddBucket";
|
||||
import DeleteBucket from "./DeleteBucket";
|
||||
import { MinTablePaginationActions } from "../../../../common/MinTablePaginationActions";
|
||||
import { CreateIcon } from "../../../../icons";
|
||||
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 { Link } from "react-router-dom";
|
||||
import ViewIcon from "@material-ui/icons/Visibility";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
},
|
||||
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px"
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
}
|
||||
});
|
||||
|
||||
interface IListBucketsProps {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
interface IListBucketsState {
|
||||
records: Bucket[];
|
||||
totalRecords: number;
|
||||
loading: boolean;
|
||||
error: string;
|
||||
deleteError: string;
|
||||
addScreenOpen: boolean;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
deleteOpen: boolean;
|
||||
selectedBucket: string;
|
||||
filterBuckets: string;
|
||||
}
|
||||
|
||||
class ListBuckets extends React.Component<
|
||||
IListBucketsProps,
|
||||
IListBucketsState
|
||||
> {
|
||||
state: IListBucketsState = {
|
||||
records: [],
|
||||
totalRecords: 0,
|
||||
loading: false,
|
||||
error: "",
|
||||
deleteError: "",
|
||||
addScreenOpen: false,
|
||||
page: 0,
|
||||
rowsPerPage: 10,
|
||||
deleteOpen: false,
|
||||
selectedBucket: "",
|
||||
filterBuckets: ""
|
||||
};
|
||||
|
||||
fetchRecords() {
|
||||
this.setState({ loading: true }, () => {
|
||||
const { page, rowsPerPage } = this.state;
|
||||
const offset = page * rowsPerPage;
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets?offset=${offset}&limit=${rowsPerPage}`)
|
||||
.then((res: BucketList) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
records: res.buckets,
|
||||
totalRecords: res.total,
|
||||
error: ""
|
||||
});
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if (
|
||||
(res.buckets === undefined ||
|
||||
res.buckets == null ||
|
||||
res.buckets.length === 0) &&
|
||||
page > 0
|
||||
) {
|
||||
const newPage = page - 1;
|
||||
this.setState({ page: newPage }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.setState({ loading: false, error: err });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
closeAddModalAndRefresh() {
|
||||
this.setState({ addScreenOpen: false }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
}
|
||||
|
||||
closeDeleteModalAndRefresh(refresh: boolean) {
|
||||
this.setState({ deleteOpen: false }, () => {
|
||||
if (refresh) {
|
||||
this.fetchRecords();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.fetchRecords();
|
||||
}
|
||||
|
||||
bucketFilter(): void {}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const {
|
||||
records,
|
||||
totalRecords,
|
||||
addScreenOpen,
|
||||
loading,
|
||||
page,
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
selectedBucket,
|
||||
filterBuckets
|
||||
} = this.state;
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
this.setState({ page: newPage });
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
this.setState({ page: 0, rowsPerPage: rPP });
|
||||
};
|
||||
|
||||
const confirmDeleteBucket = (bucket: string) => {
|
||||
this.setState({ deleteOpen: true, selectedBucket: bucket });
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<AddBucket
|
||||
open={addScreenOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
this.closeAddModalAndRefresh();
|
||||
}}
|
||||
/>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Buckets</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Buckets"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={val => {
|
||||
this.setState({
|
||||
filterBuckets: val.target.value
|
||||
});
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true
|
||||
});
|
||||
}}
|
||||
>
|
||||
Create Bucket
|
||||
</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>Creation Date</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records
|
||||
.slice(offset, offset + rowsPerPage)
|
||||
.filter((b: Bucket) => {
|
||||
if (filterBuckets === "") {
|
||||
return true;
|
||||
} else {
|
||||
if (b.name.indexOf(filterBuckets) >= 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(row => (
|
||||
<TableRow key={row.name}>
|
||||
<TableCell>{row.name}</TableCell>
|
||||
<TableCell>
|
||||
<Moment>{row.creation_date}</Moment>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Link to={`/buckets/${row.name}`}>
|
||||
<IconButton aria-label="delete">
|
||||
<ViewIcon />
|
||||
</IconButton>
|
||||
</Link>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
confirmDeleteBucket(row.name);
|
||||
}}
|
||||
>
|
||||
<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 Buckets</div>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<DeleteBucket
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||
this.closeDeleteModalAndRefresh(refresh);
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ListBuckets);
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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 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";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
});
|
||||
|
||||
interface ISetAccessPolicyProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
bucketName: string;
|
||||
closeModalAndRefresh: () => void;
|
||||
}
|
||||
|
||||
interface ISetAccessPolicyState {
|
||||
addLoading: boolean;
|
||||
addError: string;
|
||||
accessPolicy: string;
|
||||
}
|
||||
|
||||
class SetAccessPolicy extends React.Component<
|
||||
ISetAccessPolicyProps,
|
||||
ISetAccessPolicyState
|
||||
> {
|
||||
state: ISetAccessPolicyState = {
|
||||
addLoading: false,
|
||||
addError: "",
|
||||
accessPolicy: ""
|
||||
};
|
||||
|
||||
addRecord(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
const { addLoading, accessPolicy } = this.state;
|
||||
const { bucketName } = this.props;
|
||||
if (addLoading) {
|
||||
return;
|
||||
}
|
||||
this.setState({ addLoading: true }, () => {
|
||||
api
|
||||
.invoke("PUT", `/api/v1/buckets/${bucketName}/set-policy`, {
|
||||
access: accessPolicy
|
||||
})
|
||||
.then(res => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open } = this.props;
|
||||
const { addLoading, addError, accessPolicy } = this.state;
|
||||
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>Change Access Policy</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">
|
||||
Access Policy
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="select-access-policy"
|
||||
id="select-access-policy"
|
||||
value={accessPolicy}
|
||||
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.setState({ accessPolicy: e.target.value as string });
|
||||
}}
|
||||
>
|
||||
<MenuItem value="PRIVATE">Private</MenuItem>
|
||||
<MenuItem value="PUBLIC">Public</MenuItem>
|
||||
<MenuItem value="CUSTOM">Custom</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={addLoading}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(SetAccessPolicy);
|
||||
346
portal-ui/src/screens/Console/Buckets/ViewBucket/ViewBucket.tsx
Normal file
346
portal-ui/src/screens/Console/Buckets/ViewBucket/ViewBucket.tsx
Normal file
@@ -0,0 +1,346 @@
|
||||
// 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 from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import api from "../../../../common/api";
|
||||
import { BucketEvent, BucketEventList, BucketInfo } from "../types";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "@material-ui/core";
|
||||
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";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
},
|
||||
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px"
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
},
|
||||
noRecords: {
|
||||
lineHeight: "24px",
|
||||
textAlign: "center",
|
||||
padding: "20px"
|
||||
}
|
||||
});
|
||||
|
||||
interface IViewBucketProps {
|
||||
classes: any;
|
||||
match: any;
|
||||
}
|
||||
|
||||
interface IViewBucketState {
|
||||
info: BucketInfo | null;
|
||||
records: BucketEvent[];
|
||||
totalRecords: number;
|
||||
loading: boolean;
|
||||
error: string;
|
||||
deleteError: string;
|
||||
setAccessPolicyScreenOpen: boolean;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
deleteOpen: boolean;
|
||||
selectedBucket: string;
|
||||
}
|
||||
|
||||
class ViewBucket extends React.Component<IViewBucketProps, IViewBucketState> {
|
||||
state: IViewBucketState = {
|
||||
info: null,
|
||||
records: [],
|
||||
totalRecords: 0,
|
||||
loading: false,
|
||||
error: "",
|
||||
deleteError: "",
|
||||
setAccessPolicyScreenOpen: false,
|
||||
page: 0,
|
||||
rowsPerPage: 10,
|
||||
deleteOpen: false,
|
||||
selectedBucket: ""
|
||||
};
|
||||
|
||||
fetchRecords() {
|
||||
this.setState({ loading: true }, () => {
|
||||
const { page, rowsPerPage } = this.state;
|
||||
const { match } = this.props;
|
||||
const bucketName = match.params["bucketName"];
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/events`)
|
||||
.then((res: BucketEventList) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
records: res.events,
|
||||
totalRecords: res.total,
|
||||
error: ""
|
||||
});
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if (
|
||||
(res.events === undefined ||
|
||||
res.events == null ||
|
||||
res.events.length === 0) &&
|
||||
page > 0
|
||||
) {
|
||||
const newPage = page - 1;
|
||||
this.setState({ page: newPage }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
this.setState({ loading: false, error: err });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
closeAddModalAndRefresh() {
|
||||
this.setState({ setAccessPolicyScreenOpen: false }, () => {
|
||||
this.loadInfo();
|
||||
});
|
||||
}
|
||||
|
||||
closeDeleteModalAndRefresh(refresh: boolean) {
|
||||
this.setState({ deleteOpen: false }, () => {
|
||||
if (refresh) {
|
||||
this.fetchRecords();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadInfo() {
|
||||
const { match } = this.props;
|
||||
const bucketName = match.params["bucketName"];
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}`)
|
||||
.then((res: BucketInfo) => {
|
||||
this.setState({ info: res });
|
||||
})
|
||||
.catch(err => {});
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this.loadInfo();
|
||||
this.fetchRecords();
|
||||
}
|
||||
|
||||
bucketFilter(): void {}
|
||||
|
||||
render() {
|
||||
const { classes, match } = this.props;
|
||||
const {
|
||||
info,
|
||||
records,
|
||||
totalRecords,
|
||||
setAccessPolicyScreenOpen,
|
||||
loading,
|
||||
page,
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
selectedBucket
|
||||
} = this.state;
|
||||
|
||||
const offset = page * rowsPerPage;
|
||||
|
||||
const bucketName = match.params["bucketName"];
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
this.setState({ page: newPage });
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
this.setState({ page: 0, rowsPerPage: rPP });
|
||||
};
|
||||
|
||||
const confirmDeleteEvent = (bucket: string) => {
|
||||
this.setState({ deleteOpen: true, selectedBucket: bucket });
|
||||
};
|
||||
|
||||
let accessPolicy = "n/a";
|
||||
if (info !== null) {
|
||||
accessPolicy = info.access;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SetAccessPolicy
|
||||
bucketName={bucketName}
|
||||
open={setAccessPolicyScreenOpen}
|
||||
closeModalAndRefresh={() => {
|
||||
this.closeAddModalAndRefresh();
|
||||
}}
|
||||
/>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">
|
||||
Bucket > {match.params["bucketName"]}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
Access Policy: {accessPolicy}
|
||||
{" "}
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
setAccessPolicyScreenOpen: true
|
||||
});
|
||||
}}
|
||||
>
|
||||
Change Access Policy
|
||||
</Button>
|
||||
<br />
|
||||
Reported Usage: 0 bytes
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="h6">Events</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6} className={classes.actionsTray} />
|
||||
<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>SQS</TableCell>
|
||||
<TableCell>Events</TableCell>
|
||||
<TableCell>Prefix</TableCell>
|
||||
<TableCell>Suffix</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.slice(offset, offset + rowsPerPage).map(row => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell>{row.arn}</TableCell>
|
||||
<TableCell>{row.events.join(", ")}</TableCell>
|
||||
<TableCell>{row.prefix}</TableCell>
|
||||
<TableCell>{row.suffix}</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
confirmDeleteEvent(row.id);
|
||||
}}
|
||||
>
|
||||
<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 className={classes.noRecords}>No Events</div>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<DeleteBucket
|
||||
deleteOpen={deleteOpen}
|
||||
selectedBucket={selectedBucket}
|
||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||
this.closeDeleteModalAndRefresh(refresh);
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(ViewBucket);
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// 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
|
||||
@@ -19,7 +19,25 @@ export interface Bucket {
|
||||
creation_date: Date;
|
||||
}
|
||||
|
||||
export interface BucketInfo {
|
||||
name: string;
|
||||
access: string;
|
||||
}
|
||||
|
||||
export interface BucketList {
|
||||
buckets: Bucket[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface BucketEvent {
|
||||
id: string;
|
||||
arn: string;
|
||||
events: string[];
|
||||
prefix: string;
|
||||
suffix: string;
|
||||
}
|
||||
|
||||
export interface BucketEventList {
|
||||
events: BucketEvent[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// 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
|
||||
@@ -212,7 +212,7 @@ class Console extends React.Component<
|
||||
<Container maxWidth="lg" className={classes.container}>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route exact path="/buckets" component={Buckets} />
|
||||
<Route path="/buckets" component={Buckets} />
|
||||
<Route exact path="/permissions" component={Permissions} />
|
||||
<Route exact path="/policies" component={Policies} />
|
||||
<Route
|
||||
|
||||
@@ -116,24 +116,12 @@ class Menu extends React.Component<MenuProps> {
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Buckets" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/permissions">
|
||||
<ListItemIcon>
|
||||
<PermissionIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Permissions" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/policies">
|
||||
<ListItemIcon>
|
||||
<PermissionIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="policies" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/service_accounts">
|
||||
<ListItemIcon>
|
||||
<ServiceAccountIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Service Accounts" />
|
||||
</ListItem>
|
||||
<ListItem button component={NavLink} to="/users">
|
||||
<ListItemIcon>
|
||||
<UsersIcon />
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import {UnControlled as CodeMirror} from 'react-codemirror2'
|
||||
import { UnControlled as CodeMirror } from "react-codemirror2";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {
|
||||
Button,
|
||||
@@ -29,9 +29,9 @@ import {
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Title from "../../../common/Title";
|
||||
import api from "../../../common/api";
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/theme/material.css';
|
||||
require('codemirror/mode/javascript/javascript');
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/theme/material.css";
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -40,11 +40,11 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
jsonPolicyEditor: {
|
||||
minHeight: 400,
|
||||
width: "100%",
|
||||
width: "100%"
|
||||
},
|
||||
codeMirror: {
|
||||
fontSize: 14,
|
||||
},
|
||||
fontSize: 14
|
||||
}
|
||||
});
|
||||
|
||||
interface IAddPolicyProps {
|
||||
@@ -65,7 +65,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
addLoading: false,
|
||||
addError: "",
|
||||
policyName: "",
|
||||
policyDefinition: "",
|
||||
policyDefinition: ""
|
||||
};
|
||||
addRecord(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
@@ -77,7 +77,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
api
|
||||
.invoke("POST", "/api/v1/policies", {
|
||||
name: policyName,
|
||||
definition: policyDefinition,
|
||||
definition: policyDefinition
|
||||
})
|
||||
.then(res => {
|
||||
this.setState(
|
||||
@@ -100,7 +100,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
}
|
||||
render() {
|
||||
const { classes, open } = this.props;
|
||||
const { addLoading, addError, policyName, policyDefinition} = this.state;
|
||||
const { addLoading, addError, policyName, policyDefinition } = this.state;
|
||||
return (
|
||||
<Dialog
|
||||
fullWidth
|
||||
@@ -154,8 +154,8 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
|
||||
className={classes.codeMirror}
|
||||
value=""
|
||||
options={{
|
||||
mode: 'javascript',
|
||||
theme: 'material',
|
||||
mode: "javascript",
|
||||
theme: "material",
|
||||
lineNumbers: true
|
||||
}}
|
||||
onChange={(editor, data, value) => {
|
||||
|
||||
@@ -40,7 +40,7 @@ interface IDeletePolicyProps {
|
||||
classes: any;
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
selectedPolicy: string;
|
||||
selectedPolicy: string;
|
||||
}
|
||||
|
||||
interface IDeletePolicyState {
|
||||
@@ -48,7 +48,10 @@ interface IDeletePolicyState {
|
||||
deleteError: string;
|
||||
}
|
||||
|
||||
class DeletePolicy extends React.Component<IDeletePolicyProps, IDeletePolicyState> {
|
||||
class DeletePolicy extends React.Component<
|
||||
IDeletePolicyProps,
|
||||
IDeletePolicyState
|
||||
> {
|
||||
state: IDeletePolicyState = {
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
@@ -88,7 +91,7 @@ class DeletePolicy extends React.Component<IDeletePolicyProps, IDeletePolicyStat
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
this.setState({deleteError:""},()=>{
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.props.closeDeleteModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
@@ -117,7 +120,7 @@ class DeletePolicy extends React.Component<IDeletePolicyProps, IDeletePolicyStat
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.setState({deleteError:""},()=>{
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.props.closeDeleteModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import {createStyles, Theme, withStyles} from "@material-ui/core/styles";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
@@ -36,13 +36,13 @@ 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 { PolicyList, Policy } from "./types";
|
||||
import AddPolicy from "./AddPolicy";
|
||||
import DeletePolicy from "./DeletePolicy";
|
||||
import api from "../../../common/api";
|
||||
import {CreateIcon} from "../../../icons";
|
||||
import {MinTablePaginationActions} from "../../../common/MinTablePaginationActions";
|
||||
import VisibilityIcon from '@material-ui/icons/Visibility';
|
||||
import { CreateIcon } from "../../../icons";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import VisibilityIcon from "@material-ui/icons/Visibility";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface Statement {
|
||||
export interface Policy {
|
||||
name: string;
|
||||
version: string;
|
||||
statements: Statement[]
|
||||
statements: Statement[];
|
||||
}
|
||||
|
||||
export interface PolicyList {
|
||||
|
||||
@@ -37,7 +37,7 @@ const styles = (theme: Theme) =>
|
||||
color: "red"
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700,
|
||||
fontWeight: 700
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
@@ -71,13 +71,13 @@ class AddUserContent extends React.Component<
|
||||
secretKey: "",
|
||||
selectedGroups: [],
|
||||
loadingGroups: false,
|
||||
groupsList: [],
|
||||
groupsList: []
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
const { selectedUser } = this.props;
|
||||
if (selectedUser !== null) {
|
||||
console.log('selUsr', selectedUser);
|
||||
console.log("selUsr", selectedUser);
|
||||
this.setState({
|
||||
accessKey: selectedUser.accessKey,
|
||||
secretKey: ""
|
||||
@@ -97,7 +97,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(
|
||||
@@ -120,7 +120,7 @@ class AddUserContent extends React.Component<
|
||||
api
|
||||
.invoke("POST", "/api/v1/users", {
|
||||
accessKey,
|
||||
secretKey,
|
||||
secretKey
|
||||
})
|
||||
.then(res => {
|
||||
this.setState(
|
||||
@@ -146,12 +146,20 @@ class AddUserContent extends React.Component<
|
||||
|
||||
render() {
|
||||
const { classes, selectedUser } = this.props;
|
||||
const { addLoading, addError, accessKey, secretKey, selectedGroups, loadingGroups, groupsList } = this.state;
|
||||
const {
|
||||
addLoading,
|
||||
addError,
|
||||
accessKey,
|
||||
secretKey,
|
||||
selectedGroups,
|
||||
loadingGroups,
|
||||
groupsList
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{selectedUser !== null ? 'Edit User' : 'Add User'}
|
||||
{selectedUser !== null ? "Edit User" : "Add User"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<form
|
||||
@@ -175,37 +183,39 @@ class AddUserContent extends React.Component<
|
||||
)}
|
||||
|
||||
{selectedUser !== null ? (
|
||||
<React.Fragment>
|
||||
<span className={classes.strongText}>Access Key:</span>
|
||||
<span className={classes.keyName}>{` ${accessKey}`}</span>
|
||||
</React.Fragment>
|
||||
<React.Fragment>
|
||||
<span className={classes.strongText}>Access Key:</span>
|
||||
<span className={classes.keyName}>{` ${accessKey}`}</span>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
fullWidth
|
||||
label="Access Key"
|
||||
value={accessKey}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ accessKey: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-multiline-static"
|
||||
label={selectedUser !== null ? 'New Secret Key': 'Secret Key'}
|
||||
type="password"
|
||||
fullWidth
|
||||
value={secretKey}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ secretKey: e.target.value });
|
||||
}}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-basic"
|
||||
fullWidth
|
||||
label="Access Key"
|
||||
value={accessKey}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ accessKey: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
id="standard-multiline-static"
|
||||
label={
|
||||
selectedUser !== null ? "New Secret Key" : "Secret Key"
|
||||
}
|
||||
type="password"
|
||||
fullWidth
|
||||
value={secretKey}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ secretKey: e.target.value });
|
||||
}}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<Grid item xs={12}>
|
||||
@@ -213,16 +223,14 @@ class AddUserContent extends React.Component<
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<GroupsSelectors
|
||||
selectedGroups={selectedGroups}
|
||||
setSelectedGroups={
|
||||
(elements: string[]) => {
|
||||
this.setState({
|
||||
selectedGroups: elements
|
||||
})
|
||||
}
|
||||
}
|
||||
loading={loadingGroups}
|
||||
records={groupsList}
|
||||
selectedGroups={selectedGroups}
|
||||
setSelectedGroups={(elements: string[]) => {
|
||||
this.setState({
|
||||
selectedGroups: elements
|
||||
});
|
||||
}}
|
||||
loading={loadingGroups}
|
||||
records={groupsList}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
|
||||
@@ -17,143 +17,141 @@
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import React from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress
|
||||
} from "@material-ui/core";
|
||||
import api from "../../../common/api";
|
||||
import { User, UsersList } from "./types";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
});
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
});
|
||||
|
||||
interface IDeleteUserProps {
|
||||
classes: any;
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
selectedUser: User | null;
|
||||
classes: any;
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
selectedUser: User | null;
|
||||
}
|
||||
|
||||
interface IDeleteUserState {
|
||||
deleteLoading: boolean;
|
||||
deleteError: string;
|
||||
deleteLoading: boolean;
|
||||
deleteError: string;
|
||||
}
|
||||
|
||||
class DeleteUser extends React.Component<
|
||||
IDeleteUserProps,
|
||||
IDeleteUserState
|
||||
> {
|
||||
state: IDeleteUserState = {
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
};
|
||||
class DeleteUser extends React.Component<IDeleteUserProps, IDeleteUserState> {
|
||||
state: IDeleteUserState = {
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
};
|
||||
|
||||
removeRecord() {
|
||||
const { deleteLoading } = this.state;
|
||||
const { selectedUser } = this.props;
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
if (selectedUser == null) {
|
||||
return;
|
||||
}
|
||||
this.setState({ deleteLoading: true }, () => {
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/users/${selectedUser.accessKey}`, {
|
||||
id: selectedUser.id
|
||||
})
|
||||
.then((res: UsersList) => {
|
||||
this.setState(
|
||||
{
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeDeleteModalAndRefresh(true);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
deleteLoading: false,
|
||||
deleteError: err
|
||||
});
|
||||
});
|
||||
removeRecord() {
|
||||
const { deleteLoading } = this.state;
|
||||
const { selectedUser } = this.props;
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
if (selectedUser == null) {
|
||||
return;
|
||||
}
|
||||
this.setState({ deleteLoading: true }, () => {
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/users/${selectedUser.accessKey}`, {
|
||||
id: selectedUser.id
|
||||
})
|
||||
.then((res: UsersList) => {
|
||||
this.setState(
|
||||
{
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeDeleteModalAndRefresh(true);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
deleteLoading: false,
|
||||
deleteError: err
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, deleteOpen, selectedUser } = this.props;
|
||||
const { deleteLoading, deleteError } = this.state;
|
||||
|
||||
if (selectedUser === null) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, deleteOpen, selectedUser } = this.props;
|
||||
const { deleteLoading, deleteError } = this.state;
|
||||
|
||||
if (selectedUser === null) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
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 User</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete user{" "}<b>{selectedUser.accessKey}</b>?
|
||||
{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>
|
||||
);
|
||||
}
|
||||
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 User</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete user <b>{selectedUser.accessKey}</b>
|
||||
?
|
||||
{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)(DeleteUser);
|
||||
|
||||
@@ -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 from 'react';
|
||||
import React from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
import Table from "@material-ui/core/Table";
|
||||
@@ -31,158 +31,157 @@ import TextField from "@material-ui/core/TextField";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
|
||||
interface IGroupsProps {
|
||||
classes: any;
|
||||
selectedGroups: string[];
|
||||
setSelectedGroups: any;
|
||||
records: any[];
|
||||
loading: boolean;
|
||||
classes: any;
|
||||
selectedGroups: string[];
|
||||
setSelectedGroups: any;
|
||||
records: any[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
},
|
||||
paper: {
|
||||
// padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
},
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px"
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
},
|
||||
wrapCell: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight:'bold'
|
||||
}
|
||||
}
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "left",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
}
|
||||
},
|
||||
filterField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
width: '100%'
|
||||
},
|
||||
noFound: {
|
||||
textAlign: "center",
|
||||
padding: "10px 0",
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
},
|
||||
paper: {
|
||||
// padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
},
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px"
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
},
|
||||
wrapCell: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "left",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
},
|
||||
filterField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
width: "100%"
|
||||
},
|
||||
noFound: {
|
||||
textAlign: "center",
|
||||
padding: "10px 0"
|
||||
}
|
||||
});
|
||||
|
||||
const GroupsSelectors = ({
|
||||
classes,
|
||||
selectedGroups,
|
||||
setSelectedGroups,
|
||||
records,
|
||||
loading
|
||||
}: IGroupsProps) => {
|
||||
classes,
|
||||
selectedGroups,
|
||||
setSelectedGroups,
|
||||
records,
|
||||
loading
|
||||
}: IGroupsProps) => {
|
||||
if (!records) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!records) {
|
||||
return null;
|
||||
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
const value = targetD.value;
|
||||
const checked = targetD.checked;
|
||||
|
||||
let elements: string[] = [...selectedGroups]; // We clone the selectedGroups array
|
||||
|
||||
if (checked) {
|
||||
// If the user has checked this field we need to push this to selectedGroupsList
|
||||
elements.push(value);
|
||||
} else {
|
||||
// User has unchecked this field, we need to remove it from the list
|
||||
elements = elements.filter(element => element !== value);
|
||||
}
|
||||
setSelectedGroups(elements);
|
||||
|
||||
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
const value = targetD.value;
|
||||
const checked = targetD.checked;
|
||||
return elements;
|
||||
};
|
||||
|
||||
let elements : string[] = [...selectedGroups]; // We clone the selectedGroups array
|
||||
|
||||
if(checked) { // If the user has checked this field we need to push this to selectedGroupsList
|
||||
elements.push(value);
|
||||
} else { // User has unchecked this field, we need to remove it from the list
|
||||
elements = elements.filter(element => element !== value);
|
||||
}
|
||||
setSelectedGroups(elements);
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title>Groups</Title>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Filter Groups"
|
||||
className={classes.filterField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Group</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.map(row => (
|
||||
<TableRow key={`group-${row.groupName}`}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.groupName}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
'aria-label': 'secondary checkbox'
|
||||
}}
|
||||
onChange={ selectionChanged }
|
||||
checked={selectedGroups.includes(row.groupName)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.groupName}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<div className={classes.noFound}>No Groups Available</div>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title>Groups</Title>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Filter Groups"
|
||||
className={classes.filterField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Group</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.map(row => (
|
||||
<TableRow key={`group-${row.groupName}`}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value={row.groupName}
|
||||
color="primary"
|
||||
inputProps={{
|
||||
"aria-label": "secondary checkbox"
|
||||
}}
|
||||
onChange={selectionChanged}
|
||||
checked={selectedGroups.includes(row.groupName)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.groupName}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<div className={classes.noFound}>No Groups Available</div>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(GroupsSelectors);
|
||||
|
||||
|
||||
|
||||
@@ -25,14 +25,15 @@ import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import api from "../../../common/api";
|
||||
import {
|
||||
Button, IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { User, UsersList } from "./types";
|
||||
import { usersSort } from '../../../utils/sortFunctions';
|
||||
import { usersSort } from "../../../utils/sortFunctions";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import AddUser from "./AddUser";
|
||||
import DeleteUser from "./DeleteUser";
|
||||
@@ -76,21 +77,21 @@ const styles = (theme: Theme) =>
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight:'bold'
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
},
|
||||
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"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -124,7 +125,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
rowsPerPage: 10,
|
||||
deleteOpen: false,
|
||||
selectedUser: null,
|
||||
addGroupOpen: false,
|
||||
addGroupOpen: false
|
||||
};
|
||||
|
||||
fetchRecords() {
|
||||
@@ -234,7 +235,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -256,7 +257,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
selectedUser: null,
|
||||
selectedUser: null
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -286,35 +287,35 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
<Checkbox
|
||||
value="secondary"
|
||||
color="primary"
|
||||
inputProps={{ 'aria-label': 'secondary checkbox' }}
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.accessKey}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
aria-label="view"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
selectedUser: row,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<ViewIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
deleteOpen: true,
|
||||
selectedUser: row,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="view"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
selectedUser: row
|
||||
});
|
||||
}}
|
||||
>
|
||||
<ViewIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
deleteOpen: true,
|
||||
selectedUser: row
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
// 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 from "react";
|
||||
import Avatar from "@material-ui/core/Avatar";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Box from "@material-ui/core/Box";
|
||||
import LockOutlinedIcon from "@material-ui/icons/LockOutlined";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Container from "@material-ui/core/Container";
|
||||
import request from "superagent";
|
||||
import { useHistory } from "react-router";
|
||||
import { CircularProgress } from "@material-ui/core";
|
||||
import storage from "local-storage-fallback";
|
||||
import Copyright from "../common/Copyright";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
"@global": {
|
||||
body: {
|
||||
backgroundColor: theme.palette.common.white
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
marginTop: theme.spacing(8),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center"
|
||||
},
|
||||
avatar: {
|
||||
margin: theme.spacing(1),
|
||||
backgroundColor: theme.palette.secondary.main
|
||||
},
|
||||
form: {
|
||||
width: "100%", // Fix IE 11 issue.
|
||||
marginTop: theme.spacing(3)
|
||||
},
|
||||
submit: {
|
||||
margin: theme.spacing(3, 0, 2)
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
spinner: {
|
||||
margin: "auto"
|
||||
}
|
||||
}));
|
||||
const CreatePassword: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
const { push } = useHistory();
|
||||
const [password, setPassword] = React.useState<string>("");
|
||||
const [token, setToken] = React.useState<string>("");
|
||||
const [tokenValidated, setTokenValidated] = React.useState<boolean>(false);
|
||||
const [tokenValid, setTokenValid] = React.useState<boolean>(true);
|
||||
const [repeatPassword, setRepeatPassword] = React.useState<string>("");
|
||||
const [error, setError] = React.useState<string>("");
|
||||
|
||||
const formSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (password !== repeatPassword) {
|
||||
setError("Passwords don't match");
|
||||
return;
|
||||
}
|
||||
if (password.length < 8) {
|
||||
setError("Password should longer than 8 characters");
|
||||
return;
|
||||
}
|
||||
const url = "/api/v1/users/set_password";
|
||||
request
|
||||
.post(url)
|
||||
.send({
|
||||
url_token: token,
|
||||
password: password
|
||||
})
|
||||
.then((res: any) => {
|
||||
if (res.body.jwt_token) {
|
||||
// store the jwt token
|
||||
storage.setItem("token", res.body.jwt_token);
|
||||
return res.body.jwt_token;
|
||||
} else if (res.body.error) {
|
||||
// throw will be moved to catch block once bad CreatePassword returns 403
|
||||
throw res.body.error;
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
push("/");
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
});
|
||||
};
|
||||
|
||||
// validate the token passed via url
|
||||
|
||||
React.useEffect(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const urlToken = urlParams.get("t") as string;
|
||||
if (urlToken === null || urlToken === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
setToken(urlToken);
|
||||
const url = "/api/v1/validate_invite";
|
||||
request
|
||||
.post(url)
|
||||
.send({ url_token: urlToken })
|
||||
.then((res: any) => {
|
||||
console.log(res);
|
||||
// store the email to display
|
||||
setTokenValidated(true);
|
||||
})
|
||||
.catch(err => {
|
||||
setError(err);
|
||||
setTokenValidated(true);
|
||||
setTokenValid(false);
|
||||
});
|
||||
}, []); // empty array ensures the effect is ran only once on component mount
|
||||
|
||||
return (
|
||||
<Container component="main" maxWidth="xs">
|
||||
<CssBaseline />
|
||||
<div className={classes.paper}>
|
||||
<Avatar className={classes.avatar}>
|
||||
<LockOutlinedIcon />
|
||||
</Avatar>
|
||||
<Typography component="h1" variant="h5">
|
||||
Create Password
|
||||
</Typography>
|
||||
{!tokenValidated && (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} alignItems="center" justify="center">
|
||||
<Grid
|
||||
container
|
||||
spacing={0}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justify="center"
|
||||
>
|
||||
<Grid item xs={12} alignItems="center" justify="center">
|
||||
<CircularProgress />
|
||||
</Grid>
|
||||
<Grid item xs={12} alignItems="center" justify="center">
|
||||
<Typography variant="body1" gutterBottom>
|
||||
Validating token.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
{tokenValidated && !tokenValid && (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} alignItems="center" justify="center">
|
||||
<Grid
|
||||
container
|
||||
spacing={0}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justify="center"
|
||||
>
|
||||
<Grid item xs={12} alignItems="center" justify="center">
|
||||
<Typography variant="body1" gutterBottom>
|
||||
This token is invalid.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
{tokenValidated && tokenValid && (
|
||||
<form className={classes.form} noValidate onSubmit={formSubmit}>
|
||||
<Grid container spacing={2}>
|
||||
{error !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{`${error}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
value={password}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setPassword(e.target.value)
|
||||
}
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
id="password"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
value={repeatPassword}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setRepeatPassword(e.target.value)
|
||||
}
|
||||
name="repeat_password"
|
||||
label="Repeat Password"
|
||||
type="password"
|
||||
id="repeat_password"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.submit}
|
||||
>
|
||||
Create Password
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
<Box mt={5}>
|
||||
<Copyright />
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreatePassword;
|
||||
@@ -14,40 +14,41 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import {makeStyles} from '@material-ui/core/styles';
|
||||
import Container from '@material-ui/core/Container';
|
||||
import React from "react";
|
||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||
import Box from "@material-ui/core/Box";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Container from "@material-ui/core/Container";
|
||||
import Copyright from "../common/Copyright";
|
||||
import history from "../history";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
'@global': {
|
||||
"@global": {
|
||||
body: {
|
||||
backgroundColor: theme.palette.common.white,
|
||||
},
|
||||
backgroundColor: theme.palette.common.white
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
marginTop: theme.spacing(8),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
},
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center"
|
||||
}
|
||||
}));
|
||||
const NotFound: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
console.log(history);
|
||||
return (
|
||||
<Container component="main">
|
||||
<CssBaseline/>
|
||||
<CssBaseline />
|
||||
<div className={classes.paper}>
|
||||
<Typography variant="h1" component="h1">
|
||||
404 Not Found
|
||||
</Typography>
|
||||
</div>
|
||||
<Box mt={5}>
|
||||
<Copyright/>
|
||||
<Copyright />
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
import Link from '@material-ui/core/Link';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import {makeStyles} from '@material-ui/core/styles';
|
||||
import Container from '@material-ui/core/Container';
|
||||
import Copyright from "../common/Copyright";
|
||||
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
'@global': {
|
||||
body: {
|
||||
backgroundColor: theme.palette.common.white,
|
||||
},
|
||||
},
|
||||
paper: {
|
||||
marginTop: theme.spacing(8),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
},
|
||||
avatar: {
|
||||
margin: theme.spacing(1),
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
},
|
||||
form: {
|
||||
width: '100%', // Fix IE 11 issue.
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
submit: {
|
||||
margin: theme.spacing(3, 0, 2),
|
||||
},
|
||||
}));
|
||||
const Signup: React.FC = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Container component="main" maxWidth="xs">
|
||||
<CssBaseline/>
|
||||
<div className={classes.paper}>
|
||||
<Avatar className={classes.avatar}>
|
||||
<LockOutlinedIcon/>
|
||||
</Avatar>
|
||||
<Typography component="h1" variant="h5">
|
||||
Acme Storage Sign up
|
||||
</Typography>
|
||||
<form className={classes.form} noValidate>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
autoComplete="fullname"
|
||||
name="fullname"
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
id="fullname"
|
||||
label="Full Name"
|
||||
autoFocus
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
id="email"
|
||||
label="Email Address"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
id="password"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</Grid>
|
||||
<hr/>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
id="company"
|
||||
label="Company Name"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
id="company_short_name"
|
||||
label="Short Name"
|
||||
name="company_short_name"
|
||||
autoComplete="company_short_name"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={8}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
id="credit_card_number"
|
||||
label="Credit Card Number"
|
||||
name="credit_card_number"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
id="ccv"
|
||||
label="CCV"
|
||||
name="ccv"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
id="expiration_mm"
|
||||
label="Expiration MM"
|
||||
name="expiration_mm"
|
||||
autoComplete="expiration_mm"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
id="expiration_yy"
|
||||
label="Expiration YY"
|
||||
name="expiration_YY"
|
||||
autoComplete="expiration_yy"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={<Checkbox value="allowExtraEmails" color="primary"/>}
|
||||
label="Agree to Terms & Conditions."
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.submit}
|
||||
>
|
||||
Sign Up
|
||||
</Button>
|
||||
<Grid container justify="flex-end">
|
||||
<Grid item>
|
||||
<Link href="#" variant="body2">
|
||||
Already have an account? Sign in
|
||||
</Link>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</div>
|
||||
<Box mt={5}>
|
||||
<Copyright/>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Signup;
|
||||
Reference in New Issue
Block a user