From 2b34fbae475d3f816ecec30a7b9355352fa7de07 Mon Sep 17 00:00:00 2001 From: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:44:23 -0800 Subject: [PATCH] Add Bucket as a page (#1220) * Add Bucket as a page Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> * goimports Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> * Redirect to bucket browse Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> * Address comment Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com> --- pkg/acl/endpoints.go | 2 + pkg/acl/endpoints_test.go | 4 +- .../src/screens/Console/Buckets/Buckets.tsx | 4 +- .../Console/Buckets/ListBuckets/AddBucket.tsx | 523 +++++++++--------- .../Buckets/ListBuckets/ListBuckets.tsx | 31 +- .../FormSwitchWrapper/FormSwitchWrapper.tsx | 46 +- .../InputBoxWrapper/InputBoxWrapper.tsx | 7 +- .../FormComponents/common/styleLibrary.ts | 6 + portal-ui/src/screens/Console/Console.tsx | 4 + .../screens/Console/HealthInfo/HealthInfo.tsx | 8 +- .../screens/Console/Speedtest/Speedtest.tsx | 6 - restapi/error.go | 7 + 12 files changed, 330 insertions(+), 318 deletions(-) diff --git a/pkg/acl/endpoints.go b/pkg/acl/endpoints.go index b47529bec..3c9501f51 100644 --- a/pkg/acl/endpoints.go +++ b/pkg/acl/endpoints.go @@ -39,6 +39,7 @@ var ( dashboard = "/dashboard" metrics = "/metrics" profiling = "/profiling" + addBucket = "/add-bucket" buckets = "/buckets" bucketsGeneral = "/buckets/*" bucketsAdmin = "/buckets/:bucketName/admin/*" @@ -335,6 +336,7 @@ var endpointRules = map[string]ConfigurationActionSet{ dashboard: dashboardActionSet, metrics: dashboardActionSet, profiling: profilingActionSet, + addBucket: bucketsActionSet, buckets: bucketsActionSet, bucketsGeneral: bucketsActionSet, bucketsAdmin: bucketsActionSet, diff --git a/pkg/acl/endpoints_test.go b/pkg/acl/endpoints_test.go index 5a0109080..cab5b9c7f 100644 --- a/pkg/acl/endpoints_test.go +++ b/pkg/acl/endpoints_test.go @@ -79,7 +79,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) { "s3:*", }, }, - want: 9, + want: 10, }, { name: "all admin and s3 endpoints", @@ -89,7 +89,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) { "s3:*", }, }, - want: 36, + want: 37, }, { name: "Console User - default endpoints", diff --git a/portal-ui/src/screens/Console/Buckets/Buckets.tsx b/portal-ui/src/screens/Console/Buckets/Buckets.tsx index dfffbf3da..333a70ce4 100644 --- a/portal-ui/src/screens/Console/Buckets/Buckets.tsx +++ b/portal-ui/src/screens/Console/Buckets/Buckets.tsx @@ -16,7 +16,7 @@ import React from "react"; import history from "../../../history"; -import { Route, Router, Switch, withRouter, Redirect } from "react-router-dom"; +import { Redirect, Route, Router, Switch, withRouter } from "react-router-dom"; import { connect } from "react-redux"; import { AppState } from "../../../store"; import { setMenuOpen } from "../../../actions"; @@ -24,6 +24,7 @@ import NotFoundPage from "../../NotFoundPage"; import ListBuckets from "./ListBuckets/ListBuckets"; import BucketDetails from "./BucketDetails/BucketDetails"; import BrowserHandler from "./BucketDetails/BrowserHandler"; +import AddBucket from "./ListBuckets/AddBucket"; const mapState = (state: AppState) => ({ open: state.system.sidebarOpen, @@ -35,6 +36,7 @@ const Buckets = () => { return ( + createStyles({ buttonContainer: { + marginTop: 24, textAlign: "right", + "& .MuiButton-root": { + marginLeft: 8, + }, }, multiContainer: { display: "flex", @@ -78,13 +80,26 @@ const styles = (theme: Theme) => padding: 8, borderRadius: 3, }, - ...modalBasic, + title: { + marginBottom: 8, + }, + headTitle: { + fontWeight: "bold", + fontSize: 16, + paddingLeft: 8, + }, + h6title: { + fontWeight: "bold", + color: "#000000", + fontSize: 20, + paddingBottom: 8, + }, + ...containerForHeader(theme.spacing(4)), }); interface IAddBucketProps { classes: any; open: boolean; - closeModalAndRefresh: (refresh: boolean) => void; addBucketName: typeof addBucketName; addBucketVersioned: typeof addBucketVersioning; enableObjectLocking: typeof addBucketEnableObjectLocking; @@ -96,7 +111,7 @@ interface IAddBucketProps { addBucketRetentionMode: typeof addBucketRetentionMode; addBucketRetentionUnit: typeof addBucketRetentionUnit; addBucketRetentionValidity: typeof addBucketRetentionValidity; - setModalError: typeof setModalErrorSnackMessage; + setErrorSnackMessage: typeof setErrorSnackMessage; bucketName: string; versioningEnabled: boolean; lockingEnabled: boolean; @@ -113,8 +128,6 @@ interface IAddBucketProps { const AddBucket = ({ classes, - open, - closeModalAndRefresh, addBucketName, addBucketVersioned, enableObjectLocking, @@ -126,7 +139,7 @@ const AddBucket = ({ addBucketRetentionMode, addBucketRetentionUnit, addBucketRetentionValidity, - setModalError, + setErrorSnackMessage, bucketName, versioningEnabled, lockingEnabled, @@ -181,14 +194,14 @@ const AddBucket = ({ .invoke("POST", "/api/v1/buckets", request) .then((res) => { setAddLoading(false); - closeModalAndRefresh(true); + const newBucketName = `${bucketName}`; + resetForm(); + history.push(`/buckets/${newBucketName}/browse`); }) .catch((err: ErrorResponseHandler) => { setAddLoading(false); - setModalError(err); + setErrorSnackMessage(err); }); - - resetForm(); }; const [value] = useDebounce(bucketName, 1000); @@ -265,251 +278,267 @@ const AddBucket = ({ ]); return ( - { - closeModalAndRefresh(false); - }} - aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > -
) => { - addRecord(e); - }} - > - - - - ) => { - addBucketName(event.target.value); - }} - label="Bucket Name" - value={bucketName} - /> - - - - Features - -
- {!distributedSetup && ( - -
- These features are unavailable in a single-disk setup. -
- Please deploy a server in{" "} - - Distributed Mode - {" "} - to use these features. -
-
-
-
- )} -
- - - ) => { - addBucketVersioned(event.target.checked); - }} - description={ - "Allows to keep multiple versions of the same object under the same key." - } - label={"Versioning"} - disabled={!distributedSetup} - /> - - - ) => { - enableObjectLocking(event.target.checked); - }} - label={"Object Locking"} - description={ - "Required to support retention and legal hold. Can only be enabled at bucket creation." - } - /> - - - - ) => { - addBucketQuota(event.target.checked); - }} - label={"Quota"} - description={"Limit the amount of data in the bucket."} - disabled={!distributedSetup} - /> - - {quotaEnabled && distributedSetup && ( - + + + + + + + + ) => { + addRecord(e); + }} + > + + + + + + + Create Bucket + + + - ) => { - addBucketQuotaType(e.target.value as string); + ) => { + addBucketName(event.target.value); }} - selectorOptions={[ - { value: "hard", label: "Hard" }, - { value: "fifo", label: "FIFO" }, - ]} + label="Bucket Name" + value={bucketName} /> - - +
Features
+
+ {!distributedSetup && ( + +
+ These features are unavailable in a single-disk setup. +
+ Please deploy a server in{" "} + + Distributed Mode + {" "} + to use these features. +
+
+
+
+ )} +
+ + + ) => { + addBucketVersioned(event.target.checked); + }} + description={ + "Allows to keep multiple versions of the same object under the same key." + } + label={"Versioning"} + disabled={!distributedSetup} + /> + + + ) => { + enableObjectLocking(event.target.checked); + }} + label={"Object Locking"} + description={ + "Required to support retention and legal hold. Can only be enabled at bucket creation." + } + /> + + + + ) => { + addBucketQuota(event.target.checked); + }} + label={"Quota"} + description={"Limit the amount of data in the bucket."} + disabled={!distributedSetup} + /> + + {quotaEnabled && distributedSetup && ( + + + + ) => { + addBucketQuotaType(e.target.value as string); + }} + selectorOptions={[ + { value: "hard", label: "Hard" }, + { value: "fifo", label: "FIFO" }, + ]} + /> + + + + + + ) => { + addBucketQuotaSize(e.target.value); + }} + label="Quota" + value={quotaSize} + required + min="1" + /> + + +
+ ) => { + addBucketQuotaUnit(e.target.value as string); + }} + options={factorForDropdown()} + /> +
+
+
+
+
+ )} + {versioningEnabled && distributedSetup && ( + + + ) => { + addBucketRetention(event.target.checked); + }} + label={"Retention"} + description={ + "Impose rules to prevent object deletion for a period of time." + } + /> + + )} + {retentionEnabled && distributedSetup && ( + + + + ) => { + addBucketRetentionMode(e.target.value as string); + }} + selectorOptions={[ + { value: "compliance", label: "Compliance" }, + { value: "governance", label: "Governance" }, + ]} + /> + + + + ) => { + addBucketRetentionUnit(e.target.value as string); + }} + selectorOptions={[ + { value: "days", label: "Days" }, + { value: "years", label: "Years" }, + ]} + /> + + ) => { - addBucketQuotaSize(e.target.value); + addBucketRetentionValidity(e.target.valueAsNumber); }} - label="Quota" - value={quotaSize} + label="Retention Validity" + value={String(retentionValidity)} required min="1" /> - -
- ) => { - addBucketQuotaUnit(e.target.value as string); - }} - options={factorForDropdown()} - /> -
-
-
-
-
- )} - {versioningEnabled && distributedSetup && ( - - ) => { - addBucketRetention(event.target.checked); - }} - label={"Retention"} - description={ - "Impose rules to prevent object deletion for a period of time." - } - /> + + )} - )} - {retentionEnabled && distributedSetup && ( - + + + + + {addLoading && ( - ) => { - addBucketRetentionMode(e.target.value as string); - }} - selectorOptions={[ - { value: "compliance", label: "Compliance" }, - { value: "governance", label: "Governance" }, - ]} - /> + - - ) => { - addBucketRetentionUnit(e.target.value as string); - }} - selectorOptions={[ - { value: "days", label: "Days" }, - { value: "years", label: "Years" }, - ]} - /> - - - ) => { - addBucketRetentionValidity(e.target.valueAsNumber); - }} - label="Retention Validity" - value={String(retentionValidity)} - required - min="1" - /> - - - )} -
- - - - - {addLoading && ( - - + )} - )} +
- -
+ + ); }; @@ -541,7 +570,7 @@ const connector = connect(mapState, { addBucketRetentionMode: addBucketRetentionMode, addBucketRetentionUnit: addBucketRetentionUnit, addBucketRetentionValidity: addBucketRetentionValidity, - setModalError: setModalErrorSnackMessage, + setErrorSnackMessage: setErrorSnackMessage, }); export default connector(withStyles(styles)(AddBucket)); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx index a0c8add8a..03fed008e 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/ListBuckets.tsx @@ -25,7 +25,6 @@ import FileCopyIcon from "@mui/icons-material/FileCopy"; import { Bucket, BucketList, HasPermissionResponse } from "../types"; import { AddIcon, BucketsIcon, WatchIcon } from "../../../../icons"; import { AppState } from "../../../../store"; -import { addBucketOpen, addBucketReset } from "../actions"; import { setErrorSnackMessage } from "../../../../actions"; import { containerForHeader, @@ -33,7 +32,6 @@ import { } from "../../Common/FormComponents/common/styleLibrary"; import { ErrorResponseHandler } from "../../../../common/types"; import api from "../../../../common/api"; -import AddBucket from "./AddBucket"; import DeleteBucket from "./DeleteBucket"; import PageHeader from "../../Common/PageHeader/PageHeader"; import BucketListItem from "./BucketListItem"; @@ -136,9 +134,6 @@ const styles = (theme: Theme) => interface IListBucketsProps { classes: any; history: any; - addBucketOpen: typeof addBucketOpen; - addBucketModalOpen: boolean; - addBucketReset: typeof addBucketReset; setErrorSnackMessage: typeof setErrorSnackMessage; session: ISessionResponse; } @@ -146,9 +141,6 @@ interface IListBucketsProps { const ListBuckets = ({ classes, history, - addBucketOpen, - addBucketModalOpen, - addBucketReset, setErrorSnackMessage, session, }: IListBucketsProps) => { @@ -219,16 +211,6 @@ const ListBuckets = ({ } }, [loading, setErrorSnackMessage]); - const closeAddModalAndRefresh = (refresh: boolean) => { - addBucketOpen(false); - addBucketReset(); - - if (refresh) { - setLoading(true); - setSelectedBuckets([]); - } - }; - const closeDeleteModalAndRefresh = (refresh: boolean) => { setDeleteOpen(false); if (refresh) { @@ -283,12 +265,6 @@ const ListBuckets = ({ return ( - {addBucketModalOpen && ( - - )} {deleteOpen && ( } onClick={() => { - addBucketOpen(true); + history.push("/add-bucket"); }} className={classes.addBucket} > @@ -439,7 +415,7 @@ const ListBuckets = ({ To get started,  { - addBucketOpen(true); + history.push("/add-bucket"); }} > Create a Bucket. @@ -459,13 +435,10 @@ const ListBuckets = ({ }; const mapState = (state: AppState) => ({ - addBucketModalOpen: state.buckets.open, session: state.console.session, }); const connector = connect(mapState, { - addBucketOpen, - addBucketReset, setErrorSnackMessage, }); diff --git a/portal-ui/src/screens/Console/Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper.tsx b/portal-ui/src/screens/Console/Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper.tsx index cb277cc4e..4129afc00 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper.tsx +++ b/portal-ui/src/screens/Console/Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper.tsx @@ -104,7 +104,6 @@ const styles = (theme: Theme) => }, divContainer: { marginBottom: 20, - maxWidth: 840, }, indicatorLabelOn: { fontWeight: "bold", @@ -227,31 +226,36 @@ const FormSwitchWrapper = ({
- {label !== "" && ( - - {label} - {tooltip !== "" && ( -
- -
- + + + {label !== "" && ( + + {label} + {tooltip !== "" && ( +
+ +
+ +
+
- -
+ )} + )} - - )} + + + {description !== "" && ( + + {description} + + )} + + - + + {switchComponent} - {description !== "" && ( - - - {description} - - - )}
); diff --git a/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx b/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx index f77541cef..878865172 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx +++ b/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx @@ -146,11 +146,8 @@ const InputBoxWrapper = ({ return ( {label !== "" && ( ({ topSpacer: { height: "8px", }, + boxy: { + border: "#E5E5E5 1px solid", + borderRadius: 2, + padding: 40, + backgroundColor: "#fff", + }, }); export const actionsTray = { diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index 426f0da07..60e603632 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -239,6 +239,10 @@ const Console = ({ component: Metrics, path: "/metrics", }, + { + component: Buckets, + path: "/add-bucket", + }, { component: Buckets, path: "/buckets", diff --git a/portal-ui/src/screens/Console/HealthInfo/HealthInfo.tsx b/portal-ui/src/screens/Console/HealthInfo/HealthInfo.tsx index 192072e6c..02a1e52a4 100644 --- a/portal-ui/src/screens/Console/HealthInfo/HealthInfo.tsx +++ b/portal-ui/src/screens/Console/HealthInfo/HealthInfo.tsx @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { Fragment, useState, useEffect } from "react"; +import React, { Fragment, useEffect, useState } from "react"; import { ICloseEvent, IMessageEvent, @@ -68,12 +68,6 @@ const styles = (theme: Theme) => justifyContent: "flex-start", gap: 20, }, - boxy: { - border: "#E5E5E5 1px solid", - borderRadius: 2, - padding: 40, - backgroundColor: "#fff", - }, localMessage: { fontSize: 24, color: "#07193E", diff --git a/portal-ui/src/screens/Console/Speedtest/Speedtest.tsx b/portal-ui/src/screens/Console/Speedtest/Speedtest.tsx index f500453a1..73fe2a7b9 100644 --- a/portal-ui/src/screens/Console/Speedtest/Speedtest.tsx +++ b/portal-ui/src/screens/Console/Speedtest/Speedtest.tsx @@ -42,12 +42,6 @@ interface ISpeedtest { const styles = (theme: Theme) => createStyles({ - boxy: { - border: "#E5E5E5 1px solid", - borderRadius: 2, - padding: 40, - backgroundColor: "#fff", - }, advancedConfiguration: { color: "#2781B0", fontSize: 10, diff --git a/restapi/error.go b/restapi/error.go index e31c0f6f7..0d7d9dafc 100644 --- a/restapi/error.go +++ b/restapi/error.go @@ -5,6 +5,8 @@ import ( "runtime" "strings" + "github.com/minio/minio-go/v7" + "github.com/go-openapi/swag" "github.com/minio/console/models" "github.com/minio/madmin-go" @@ -171,6 +173,11 @@ func prepareError(err ...error) *models.Error { errorCode = 403 errorMessage = err[0].Error() } + // bucket already exists + if minio.ToErrorResponse(err[0]).Code == "BucketAlreadyOwnedByYou" { + errorCode = 400 + errorMessage = "Bucket already exists" + } } return &models.Error{Code: errorCode, Message: swag.String(errorMessage), DetailedMessage: swag.String(err[0].Error())} }