From cfa5fa98261d8af6e7a778f463fa62767e878e78 Mon Sep 17 00:00:00 2001 From: Lenin Alevski Date: Thu, 2 Apr 2020 17:39:03 -0700 Subject: [PATCH] list policies screen (#9) delete policy modal add policy editor modal uncommenting chart removing comments from chart --- portal-ui/package.json | 2 + portal-ui/src/screens/Console/Console.tsx | 2 + portal-ui/src/screens/Console/Menu.tsx | 6 + .../screens/Console/Policies/AddPolicy.tsx | 193 ++++++++++ .../screens/Console/Policies/DeletePolicy.tsx | 144 ++++++++ .../src/screens/Console/Policies/Policies.tsx | 345 ++++++++++++++++++ .../src/screens/Console/Policies/types.ts | 32 ++ portal-ui/yarn.lock | 24 +- 8 files changed, 741 insertions(+), 7 deletions(-) create mode 100644 portal-ui/src/screens/Console/Policies/AddPolicy.tsx create mode 100644 portal-ui/src/screens/Console/Policies/DeletePolicy.tsx create mode 100644 portal-ui/src/screens/Console/Policies/Policies.tsx create mode 100644 portal-ui/src/screens/Console/Policies/types.ts diff --git a/portal-ui/package.json b/portal-ui/package.json index 8995f0e21..d36000209 100644 --- a/portal-ui/package.json +++ b/portal-ui/package.json @@ -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", diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index 17c72d6ba..ceaabd74e 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -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< + { + + + + + + diff --git a/portal-ui/src/screens/Console/Policies/AddPolicy.tsx b/portal-ui/src/screens/Console/Policies/AddPolicy.tsx new file mode 100644 index 000000000..3afab24fc --- /dev/null +++ b/portal-ui/src/screens/Console/Policies/AddPolicy.tsx @@ -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 . + +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 { + 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 ( + { + this.setState({ addError: "" }, () => { + this.props.closeModalAndRefresh(); + }); + }} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + + Create Policy + + +
) => { + this.addRecord(e); + }} + > + + {addError !== "" && ( + + + {addError} + + + )} + + ) => { + this.setState({ policyName: e.target.value }); + }} + /> + + +
+
+ + { + this.setState({ policyDefinition: value }); + }} + /> + + +
+
+ + + + {addLoading && ( + + + + )} +
+
+
+
+ ); + } +} + +export default withStyles(styles)(AddPolicy); diff --git a/portal-ui/src/screens/Console/Policies/DeletePolicy.tsx b/portal-ui/src/screens/Console/Policies/DeletePolicy.tsx new file mode 100644 index 000000000..f4c278414 --- /dev/null +++ b/portal-ui/src/screens/Console/Policies/DeletePolicy.tsx @@ -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 . + +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 { + 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 ( + { + this.setState({deleteError:""},()=>{ + this.props.closeDeleteModalAndRefresh(false); + }); + }} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + Delete Bucket + + {deleteLoading && } + + Are you sure you want to delete policy {selectedPolicy}?. + {deleteError !== "" && ( + +
+ + {deleteError} + +
+ )} +
+
+ + + + +
+ ); + } +} + +export default withStyles(styles)(DeletePolicy); diff --git a/portal-ui/src/screens/Console/Policies/Policies.tsx b/portal-ui/src/screens/Console/Policies/Policies.tsx new file mode 100644 index 000000000..a156fb2a2 --- /dev/null +++ b/portal-ui/src/screens/Console/Policies/Policies.tsx @@ -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 . + +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 { + 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 + ) => { + const rPP = parseInt(event.target.value, 10); + this.setState({ page: 0, rowsPerPage: rPP }); + }; + + const confirmDeletePolicy = (policy: string) => { + this.setState({ deleteOpen: true, selectedPolicy: policy }); + }; + + return ( + + { + this.closeAddModalAndRefresh(); + }} + /> + + + Policies + + +
+
+ + { + this.setState({ + filterPolicies: val.target.value + }); + }} + InputProps={{ + disableUnderline: true, + startAdornment: ( + + + + ) + }} + /> + + + +
+
+ + + {loading && } + {records != null && records.length > 0 ? ( + + + + Name + Actions + + + + {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 => ( + + {row.name} + + { + confirmDeletePolicy(row.name); + }} + > + + + { + confirmDeletePolicy(row.name); + }} + > + + + + + ))} + + + + + + +
+ ) : ( +
No Policies
+ )} +
+
+
+ + { + this.closeDeleteModalAndRefresh(refresh); + }} + /> +
+ ); + } +} + +export default withStyles(styles)(Policies); diff --git a/portal-ui/src/screens/Console/Policies/types.ts b/portal-ui/src/screens/Console/Policies/types.ts new file mode 100644 index 000000000..02814dfb8 --- /dev/null +++ b/portal-ui/src/screens/Console/Policies/types.ts @@ -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 . + +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; +} diff --git a/portal-ui/yarn.lock b/portal-ui/yarn.lock index a0e09dad2..bc32d6650 100644 --- a/portal-ui/yarn.lock +++ b/portal-ui/yarn.lock @@ -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"