list policies screen (#9)

delete policy modal

add policy editor modal

uncommenting chart

removing comments from chart
This commit is contained in:
Lenin Alevski
2020-04-02 17:39:03 -07:00
committed by GitHub
parent e2ba32473b
commit cfa5fa9826
8 changed files with 741 additions and 7 deletions

View File

@@ -18,10 +18,12 @@
"@types/recharts": "^1.8.9",
"@types/superagent": "^4.1.4",
"@types/webpack-env": "^1.14.1",
"codemirror": "^5.52.2",
"history": "^4.10.1",
"local-storage-fallback": "^4.1.1",
"moment": "^2.24.0",
"react": "^16.13.1",
"react-codemirror2": "^7.1.0",
"react-dom": "^16.12.0",
"react-moment": "^0.9.7",
"react-redux": "^7.1.3",

View File

@@ -43,6 +43,7 @@ import { AppState } from "../../store";
import { setMenuOpen } from "../../actions";
import { ThemedComponentProps } from "@material-ui/core/styles/withTheme";
import Buckets from "./Buckets/Buckets";
import Policies from "./Policies/Policies";
import Permissions from "./Permissions/Permissions";
import Dashboard from "./Dashboard/Dashboard";
import Menu from "./Menu";
@@ -213,6 +214,7 @@ class Console extends React.Component<
<Switch>
<Route exact path="/buckets" component={Buckets} />
<Route exact path="/permissions" component={Permissions} />
<Route exact path="/policies" component={Policies} />
<Route
exact
path="/service_accounts"

View File

@@ -122,6 +122,12 @@ class Menu extends React.Component<MenuProps> {
</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 />

View File

@@ -0,0 +1,193 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import Grid from "@material-ui/core/Grid";
import {UnControlled as CodeMirror} from 'react-codemirror2'
import Typography from "@material-ui/core/Typography";
import {
Button,
Dialog,
DialogContent,
DialogTitle,
LinearProgress,
TextField
} from "@material-ui/core";
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');
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
},
jsonPolicyEditor: {
minHeight: 400,
width: "100%",
},
codeMirror: {
fontSize: 14,
},
});
interface IAddPolicyProps {
classes: any;
open: boolean;
closeModalAndRefresh: () => void;
}
interface IAddPolicyState {
addLoading: boolean;
addError: string;
policyName: string;
policyDefinition: string;
}
class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
state: IAddPolicyState = {
addLoading: false,
addError: "",
policyName: "",
policyDefinition: "",
};
addRecord(event: React.FormEvent) {
event.preventDefault();
const { policyName, addLoading, policyDefinition } = this.state;
if (addLoading) {
return;
}
this.setState({ addLoading: true }, () => {
api
.invoke("POST", "/api/v1/policies", {
name: policyName,
definition: policyDefinition,
})
.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, policyName, policyDefinition} = this.state;
return (
<Dialog
fullWidth
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>Create 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}>
<TextField
id="standard-basic"
fullWidth
label="Policy Name"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ policyName: e.target.value });
}}
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<CodeMirror
className={classes.codeMirror}
value=""
options={{
mode: 'javascript',
theme: 'material',
lineNumbers: true
}}
onChange={(editor, data, value) => {
this.setState({ policyDefinition: value });
}}
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<Button
type="submit"
variant="contained"
color="primary"
fullWidth
disabled={addLoading}
>
Save
</Button>
</Grid>
{addLoading && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</Grid>
</form>
</DialogContent>
</Dialog>
);
}
}
export default withStyles(styles)(AddPolicy);

View File

@@ -0,0 +1,144 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import React from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
LinearProgress
} from "@material-ui/core";
import api from "../../../common/api";
import { PolicyList } from "./types";
import Typography from "@material-ui/core/Typography";
const styles = (theme: Theme) =>
createStyles({
errorBlock: {
color: "red"
}
});
interface IDeletePolicyProps {
classes: any;
closeDeleteModalAndRefresh: (refresh: boolean) => void;
deleteOpen: boolean;
selectedPolicy: string;
}
interface IDeletePolicyState {
deleteLoading: boolean;
deleteError: string;
}
class DeletePolicy extends React.Component<IDeletePolicyProps, IDeletePolicyState> {
state: IDeletePolicyState = {
deleteLoading: false,
deleteError: ""
};
removeRecord() {
const { deleteLoading } = this.state;
const { selectedPolicy } = this.props;
if (deleteLoading) {
return;
}
this.setState({ deleteLoading: true }, () => {
api
.invoke("DELETE", `/api/v1/policies/${selectedPolicy}`)
.then((res: PolicyList) => {
this.setState(
{
deleteLoading: false,
deleteError: ""
},
() => {
this.props.closeDeleteModalAndRefresh(true);
}
);
})
.catch(err => {
this.setState({
deleteLoading: false,
deleteError: err
});
});
});
}
render() {
const { classes, deleteOpen, selectedPolicy } = this.props;
const { deleteLoading, deleteError } = this.state;
return (
<Dialog
open={deleteOpen}
onClose={() => {
this.setState({deleteError:""},()=>{
this.props.closeDeleteModalAndRefresh(false);
});
}}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Delete Bucket</DialogTitle>
<DialogContent>
{deleteLoading && <LinearProgress />}
<DialogContentText id="alert-dialog-description">
Are you sure you want to delete policy <b>{selectedPolicy}</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)(DeletePolicy);

View File

@@ -0,0 +1,345 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import {createStyles, Theme, withStyles} from "@material-ui/core/styles";
import 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 {
Button,
IconButton,
LinearProgress,
TableFooter,
TablePagination
} from "@material-ui/core";
import DeleteIcon from "@material-ui/icons/Delete";
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 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';
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 IPoliciesProps {
classes: any;
}
interface IPoliciesState {
records: Policy[];
totalRecords: number;
loading: boolean;
error: string;
deleteError: string;
addScreenOpen: boolean;
page: number;
rowsPerPage: number;
deleteOpen: boolean;
selectedPolicy: string;
filterPolicies: string;
}
class Policies extends React.Component<IPoliciesProps, IPoliciesState> {
state: IPoliciesState = {
records: [],
totalRecords: 0,
loading: false,
error: "",
deleteError: "",
addScreenOpen: false,
page: 0,
rowsPerPage: 10,
deleteOpen: false,
selectedPolicy: "",
filterPolicies: ""
};
fetchRecords() {
this.setState({ loading: true }, () => {
const { page, rowsPerPage } = this.state;
const offset = page * rowsPerPage;
api
.invoke("GET", `/api/v1/policies?offset=${offset}&limit=${rowsPerPage}`)
.then((res: PolicyList) => {
this.setState({
loading: false,
records: res.policies,
totalRecords: res.total,
error: ""
});
// if we get 0 results, and page > 0 , go down 1 page
if (
(res.policies === undefined ||
res.policies == null ||
res.policies.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();
}
});
}
policyFilter(): void {}
componentDidMount(): void {
this.fetchRecords();
}
render() {
const { classes } = this.props;
const {
records,
totalRecords,
addScreenOpen,
loading,
page,
rowsPerPage,
deleteOpen,
selectedPolicy,
filterPolicies
} = 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 confirmDeletePolicy = (policy: string) => {
this.setState({ deleteOpen: true, selectedPolicy: policy });
};
return (
<React.Fragment>
<AddPolicy
open={addScreenOpen}
closeModalAndRefresh={() => {
this.closeAddModalAndRefresh();
}}
/>
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">Policies</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Policies"
className={classes.searchField}
id="search-resource"
label=""
onChange={val => {
this.setState({
filterPolicies: 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 Policy
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<Paper className={classes.paper}>
{loading && <LinearProgress />}
{records != null && records.length > 0 ? (
<Table size="medium">
<TableHead className={classes.minTableHeader}>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{records
.slice(offset, offset + rowsPerPage)
.filter((b: Policy) => {
if (filterPolicies === "") {
return true;
} else {
if (b.name.indexOf(filterPolicies) >= 0) {
return true;
} else {
return false;
}
}
})
.map(row => (
<TableRow key={row.name}>
<TableCell>{row.name}</TableCell>
<TableCell align="right">
<IconButton
aria-label="view"
onClick={() => {
confirmDeletePolicy(row.name);
}}
>
<VisibilityIcon />
</IconButton>
<IconButton
aria-label="delete"
onClick={() => {
confirmDeletePolicy(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 Policies</div>
)}
</Paper>
</Grid>
</Grid>
<DeletePolicy
deleteOpen={deleteOpen}
selectedPolicy={selectedPolicy}
closeDeleteModalAndRefresh={(refresh: boolean) => {
this.closeDeleteModalAndRefresh(refresh);
}}
/>
</React.Fragment>
);
}
}
export default withStyles(styles)(Policies);

View File

@@ -0,0 +1,32 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
export interface Statement {
effect: string;
actions: string[];
resources: string[];
}
export interface Policy {
name: string;
version: string;
statements: Statement[]
}
export interface PolicyList {
policies: Policy[];
total: number;
}

View File

@@ -2915,6 +2915,11 @@ code-point-at@^1.0.0:
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
codemirror@^5.52.2:
version "5.52.2"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.52.2.tgz#c29e1f7179f85eb0dd17c0586fa810e4838ff584"
integrity sha512-WCGCixNUck2HGvY8/ZNI1jYfxPG5cRHv0VjmWuNzbtCLz8qYA5d+je4QhSSCtCaagyeOwMi/HmmPTjBgiTm2lQ==
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
@@ -3903,9 +3908,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.390:
version "1.3.393"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.393.tgz#d13fa4cbf5065e18451c84465d22aef6aca9a911"
integrity sha512-Ko3/VdhZAaMaJBLBFqEJ+M1qMiBI8sJfPY/hSJvDrkB3Do8LJsL9tmXy4w7o9nPXif/jFaZGSlXTQWU8XVsYtg==
version "1.3.394"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.394.tgz#50e927bb9f6a559ed21d284e7683ec5e2c784835"
integrity sha512-AEbSKBF49P+GgEP34w0VdYWn9SiMHgWUJbOkPEE1WZMIpWXtvfT9N0sd4Lv4jjD6DeSFjRuzm6+btn/yCz6h2Q==
elliptic@^6.0.0:
version "6.5.2"
@@ -8795,6 +8800,11 @@ react-app-polyfill@^1.0.6:
regenerator-runtime "^0.13.3"
whatwg-fetch "^3.0.0"
react-codemirror2@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-7.1.0.tgz#b874a275ad4f6f2ee5adb23b550c0f4b8b82776d"
integrity sha512-Rel0QbPnCTjHxgZYt6TkGw4icSZXNyONHb72a+1wWA+PlYJIvzFAv4pZlDPG0rpKpKmy4kSUlkoWgneH7w3A0g==
react-dev-utils@^10.2.1:
version "10.2.1"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.2.1.tgz#f6de325ae25fa4d546d09df4bb1befdc6dd19c19"
@@ -10568,10 +10578,10 @@ typeface-roboto@^0.0.75:
resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.75.tgz#98d5ba35ec234bbc7172374c8297277099cc712b"
integrity sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg==
typescript@3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==
typescript@3.6.4:
version "3.6.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d"
integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"