Files
object-browser/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketSummaryPanel.tsx
adfost b2f38200f7 Custom Policies for Buckets (#1332)
* custom policies

* fixing error

* add formatting
2021-12-28 20:21:29 -06:00

898 lines
30 KiB
TypeScript

// This file is part of MinIO Console Server
// Copyright (c) 2021 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, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Button, CircularProgress } from "@mui/material";
import get from "lodash/get";
import Paper from "@mui/material/Paper";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import { AppState } from "../../../../store";
import { setErrorSnackMessage } from "../../../../actions";
import {
BucketEncryptionInfo,
BucketInfo,
BucketObjectLocking,
BucketQuota,
BucketReplication,
BucketVersioning,
} from "../types";
import { niceBytes } from "../../../../common/utils";
import { Bucket, BucketList } from "../../Watch/types";
import {
buttonsStyles,
hrClass,
} from "../../Common/FormComponents/common/styleLibrary";
import {
ErrorResponseHandler,
IRetentionConfig,
} from "../../../../common/types";
import api from "../../../../common/api";
import GavelIcon from "@mui/icons-material/Gavel";
import { setBucketDetailsLoad } from "../actions";
import ReportedUsageIcon from "../../../../icons/ReportedUsageIcon";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
import Chip from "@mui/material/Chip";
import AddIcon from "@mui/icons-material/Add";
import CloseIcon from "@mui/icons-material/Close";
import SecureComponent, {
hasPermission,
} from "../../../../common/SecureComponent/SecureComponent";
import withSuspense from "../../Common/Components/withSuspense";
const AddBucketTagModal = withSuspense(
React.lazy(() => import("./AddBucketTagModal"))
);
const DeleteBucketTagModal = withSuspense(
React.lazy(() => import("./DeleteBucketTagModal"))
);
const SetAccessPolicy = withSuspense(
React.lazy(() => import("./SetAccessPolicy"))
);
const SetRetentionConfig = withSuspense(
React.lazy(() => import("./SetRetentionConfig"))
);
const EnableBucketEncryption = withSuspense(
React.lazy(() => import("./EnableBucketEncryption"))
);
const EnableVersioningModal = withSuspense(
React.lazy(() => import("./EnableVersioningModal"))
);
const EnableQuota = withSuspense(React.lazy(() => import("./EnableQuota")));
interface IBucketSummaryProps {
classes: any;
match: any;
distributedSetup: boolean;
setErrorSnackMessage: typeof setErrorSnackMessage;
loadingBucket: boolean;
bucketInfo: BucketInfo | null;
setBucketDetailsLoad: typeof setBucketDetailsLoad;
}
const styles = (theme: Theme) =>
createStyles({
paperContainer: {
padding: 15,
paddingLeft: 50,
display: "flex",
},
elementTitle: {
fontWeight: 500,
color: "#777777",
fontSize: 14,
marginTop: -9,
},
consumptionValue: {
color: "#000000",
fontSize: "48px",
fontWeight: "bold",
},
reportedUsage: {
padding: "15px",
},
capitalizeFirst: {
textTransform: "capitalize",
"& .min-icon": {
width: 16,
height: 16,
},
},
titleCol: {
width: "25%",
},
tag: {
textTransform: "none",
marginRight: "5px",
},
...hrClass,
...buttonsStyles,
});
const BucketSummary = ({
classes,
match,
distributedSetup,
setErrorSnackMessage,
loadingBucket,
bucketInfo,
setBucketDetailsLoad,
}: IBucketSummaryProps) => {
const [encryptionCfg, setEncryptionCfg] =
useState<BucketEncryptionInfo | null>(null);
const [bucketSize, setBucketSize] = useState<string>("0");
const [hasObjectLocking, setHasObjectLocking] = useState<boolean>(false);
const [accessPolicyScreenOpen, setAccessPolicyScreenOpen] =
useState<boolean>(false);
const [replicationRules, setReplicationRules] = useState<boolean>(false);
const [loadingObjectLocking, setLoadingLocking] = useState<boolean>(true);
const [loadingSize, setLoadingSize] = useState<boolean>(true);
const [loadingTags, setLoadingTags] = useState<boolean>(true);
const [bucketLoading, setBucketLoading] = useState<boolean>(true);
const [loadingEncryption, setLoadingEncryption] = useState<boolean>(true);
const [loadingVersioning, setLoadingVersioning] = useState<boolean>(true);
const [loadingQuota, setLoadingQuota] = useState<boolean>(true);
const [loadingReplication, setLoadingReplication] = useState<boolean>(true);
const [loadingRetention, setLoadingRetention] = useState<boolean>(true);
const [isVersioned, setIsVersioned] = useState<boolean>(false);
const [quotaEnabled, setQuotaEnabled] = useState<boolean>(false);
const [quota, setQuota] = useState<BucketQuota | null>(null);
const [encryptionEnabled, setEncryptionEnabled] = useState<boolean>(false);
const [retentionEnabled, setRetentionEnabled] = useState<boolean>(false);
const [retentionConfig, setRetentionConfig] =
useState<IRetentionConfig | null>(null);
const [retentionConfigOpen, setRetentionConfigOpen] =
useState<boolean>(false);
const [enableEncryptionScreenOpen, setEnableEncryptionScreenOpen] =
useState<boolean>(false);
const [enableQuotaScreenOpen, setEnableQuotaScreenOpen] =
useState<boolean>(false);
const [enableVersioningOpen, setEnableVersioningOpen] =
useState<boolean>(false);
const [tags, setTags] = useState<any>(null);
const [tagModalOpen, setTagModalOpen] = useState<boolean>(false);
const [tagKeys, setTagKeys] = useState<string[]>([]);
const [selectedTag, setSelectedTag] = useState<string[]>(["", ""]);
const [deleteTagModalOpen, setDeleteTagModalOpen] = useState<boolean>(false);
const bucketName = match.params["bucketName"];
let accessPolicy = "n/a";
let policyDefinition = "";
if (bucketInfo !== null) {
accessPolicy = bucketInfo.access;
policyDefinition = bucketInfo.definition;
}
const displayGetBucketObjectLockConfiguration = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION,
]);
const displayGetBucketEncryptionConfiguration = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_BUCKET_ENCRYPTION_CONFIGURATION,
]);
const displayGetBucketQuota = hasPermission(bucketName, [
IAM_SCOPES.ADMIN_GET_BUCKET_QUOTA,
]);
useEffect(() => {
if (loadingBucket) {
setBucketLoading(true);
} else {
setBucketLoading(false);
}
}, [loadingBucket, setBucketLoading]);
useEffect(() => {
if (loadingEncryption) {
if (displayGetBucketEncryptionConfiguration) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/encryption/info`)
.then((res: BucketEncryptionInfo) => {
if (res.algorithm) {
setEncryptionEnabled(true);
setEncryptionCfg(res);
}
setLoadingEncryption(false);
})
.catch((err: ErrorResponseHandler) => {
if (
err.errorMessage ===
"The server side encryption configuration was not found"
) {
setEncryptionEnabled(false);
setEncryptionCfg(null);
}
setLoadingEncryption(false);
});
} else {
setEncryptionEnabled(false);
setEncryptionCfg(null);
setLoadingEncryption(false);
}
}
}, [loadingEncryption, bucketName, displayGetBucketEncryptionConfiguration]);
useEffect(() => {
if (loadingVersioning && distributedSetup) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/versioning`)
.then((res: BucketVersioning) => {
setIsVersioned(res.is_versioned);
setLoadingVersioning(false);
})
.catch((err: ErrorResponseHandler) => {
setErrorSnackMessage(err);
setLoadingVersioning(false);
});
}
}, [loadingVersioning, setErrorSnackMessage, bucketName, distributedSetup]);
useEffect(() => {
if (loadingQuota && distributedSetup) {
if (displayGetBucketQuota) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/quota`)
.then((res: BucketQuota) => {
setQuota(res);
if (res.quota) {
setQuotaEnabled(true);
} else {
setQuotaEnabled(false);
}
setLoadingQuota(false);
})
.catch((err: ErrorResponseHandler) => {
setErrorSnackMessage(err);
setQuotaEnabled(false);
setLoadingQuota(false);
});
} else {
setQuotaEnabled(false);
setLoadingQuota(false);
}
}
}, [
loadingQuota,
setLoadingVersioning,
setErrorSnackMessage,
bucketName,
distributedSetup,
displayGetBucketQuota,
]);
useEffect(() => {
if (loadingVersioning && distributedSetup) {
if (displayGetBucketObjectLockConfiguration) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/object-locking`)
.then((res: BucketObjectLocking) => {
setHasObjectLocking(res.object_locking_enabled);
setLoadingLocking(false);
})
.catch((err: ErrorResponseHandler) => {
setErrorSnackMessage(err);
setLoadingLocking(false);
});
} else {
setLoadingLocking(false);
}
}
}, [
loadingObjectLocking,
setErrorSnackMessage,
bucketName,
loadingVersioning,
distributedSetup,
displayGetBucketObjectLockConfiguration,
]);
useEffect(() => {
if (loadingSize) {
api
.invoke("GET", `/api/v1/buckets`)
.then((res: BucketList) => {
const resBuckets = get(res, "buckets", []);
const bucketInfo = resBuckets.find(
(bucket) => bucket.name === bucketName
);
const size = get(bucketInfo, "size", "0");
setLoadingSize(false);
setBucketSize(size);
})
.catch((err: ErrorResponseHandler) => {
setLoadingSize(false);
setErrorSnackMessage(err);
});
}
}, [loadingSize, setErrorSnackMessage, bucketName]);
useEffect(() => {
if (loadingTags) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}`)
.then((res: Bucket) => {
if (res != null && res?.details != null) {
setTags(res?.details?.tags);
setTagKeys(Object.keys(res?.details?.tags));
}
setLoadingTags(false);
})
.catch((err: ErrorResponseHandler) => {
setLoadingTags(false);
setErrorSnackMessage(err);
});
}
}, [
loadingTags,
setErrorSnackMessage,
bucketName,
setTags,
tags,
setTagKeys,
tagKeys,
]);
useEffect(() => {
if (loadingReplication && distributedSetup) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/replication`)
.then((res: BucketReplication) => {
const r = res.rules ? res.rules : [];
setReplicationRules(r.length > 0);
setLoadingReplication(false);
})
.catch((err: ErrorResponseHandler) => {
setErrorSnackMessage(err);
setLoadingReplication(false);
});
}
}, [loadingReplication, setErrorSnackMessage, bucketName, distributedSetup]);
useEffect(() => {
if (loadingRetention && hasObjectLocking) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/retention`)
.then((res: IRetentionConfig) => {
setLoadingRetention(false);
setRetentionEnabled(true);
setRetentionConfig(res);
})
.catch((err: ErrorResponseHandler) => {
setRetentionEnabled(false);
setLoadingRetention(false);
setRetentionConfig(null);
});
}
}, [loadingRetention, hasObjectLocking, bucketName]);
const loadAllBucketData = () => {
setBucketDetailsLoad(true);
setBucketLoading(true);
setLoadingSize(true);
setLoadingTags(true);
setLoadingVersioning(true);
setLoadingEncryption(true);
setLoadingRetention(true);
};
const setBucketVersioning = () => {
setEnableVersioningOpen(true);
};
const setBucketQuota = () => {
setEnableQuotaScreenOpen(true);
};
const closeEnableBucketEncryption = () => {
setEnableEncryptionScreenOpen(false);
setLoadingEncryption(true);
};
const closeEnableBucketQuota = () => {
setEnableQuotaScreenOpen(false);
setLoadingQuota(true);
};
const closeSetAccessPolicy = () => {
setAccessPolicyScreenOpen(false);
loadAllBucketData();
};
const closeRetentionConfig = () => {
setRetentionConfigOpen(false);
loadAllBucketData();
};
const closeEnableVersioning = (refresh: boolean) => {
setEnableVersioningOpen(false);
if (refresh) {
loadAllBucketData();
}
};
const closeAddTagModal = (refresh: boolean) => {
setTagModalOpen(false);
if (refresh) {
loadAllBucketData();
}
};
const cap = (str: string) => {
if (!str) {
return null;
}
return str[0].toUpperCase() + str.slice(1);
};
const deleteTag = (tagKey: string, tagLabel: string) => {
setSelectedTag([tagKey, tagLabel]);
setDeleteTagModalOpen(true);
};
const closeDeleteTagModal = (refresh: boolean) => {
setDeleteTagModalOpen(false);
if (refresh) {
loadAllBucketData();
}
};
// @ts-ignore
return (
<Fragment>
{enableEncryptionScreenOpen && (
<EnableBucketEncryption
open={enableEncryptionScreenOpen}
selectedBucket={bucketName}
encryptionEnabled={encryptionEnabled}
encryptionCfg={encryptionCfg}
closeModalAndRefresh={closeEnableBucketEncryption}
/>
)}
{enableQuotaScreenOpen && (
<EnableQuota
open={enableQuotaScreenOpen}
selectedBucket={bucketName}
enabled={quotaEnabled}
cfg={quota}
closeModalAndRefresh={closeEnableBucketQuota}
/>
)}
{accessPolicyScreenOpen && (
<SetAccessPolicy
bucketName={bucketName}
open={accessPolicyScreenOpen}
actualPolicy={accessPolicy}
actualDefinition={policyDefinition}
closeModalAndRefresh={closeSetAccessPolicy}
/>
)}
{retentionConfigOpen && (
<SetRetentionConfig
bucketName={bucketName}
open={retentionConfigOpen}
closeModalAndRefresh={closeRetentionConfig}
/>
)}
{enableVersioningOpen && (
<EnableVersioningModal
closeVersioningModalAndRefresh={closeEnableVersioning}
modalOpen={enableVersioningOpen}
selectedBucket={bucketName}
versioningCurrentState={isVersioned}
/>
)}
{tagModalOpen && (
<AddBucketTagModal
modalOpen={tagModalOpen}
currentTags={tags}
bucketName={bucketName}
onCloseAndUpdate={closeAddTagModal}
/>
)}
{deleteTagModalOpen && (
<DeleteBucketTagModal
deleteOpen={deleteTagModalOpen}
currentTags={tags}
bucketName={bucketName}
onCloseAndUpdate={closeDeleteTagModal}
selectedTag={selectedTag}
/>
)}
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>
<PanelTitle>Summary</PanelTitle>
</Grid>
</Grid>
<Paper className={classes.paperContainer}>
<Grid container>
<Grid item xs={8}>
<table width={"100%"}>
<tbody>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_POLICY]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Access Policy:</td>
<td className={classes.capitalizeFirst}>
<SecureComponent
scopes={[IAM_SCOPES.S3_PUT_BUCKET_POLICY]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setAccessPolicyScreenOpen(true);
}}
>
{bucketLoading ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
accessPolicy.toLowerCase()
)}
</Button>
</SecureComponent>
</td>
</tr>
</SecureComponent>
{distributedSetup && (
<Fragment>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Replication:</td>
<td className={classes.doubleElement}>
<span>
{replicationRules ? "Enabled" : "Disabled"}
</span>
</td>
</tr>
</SecureComponent>
<SecureComponent
scopes={[
IAM_SCOPES.S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION,
]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Object Locking:</td>
<td>{!hasObjectLocking ? "Disabled" : "Enabled"}</td>
</tr>
</SecureComponent>
</Fragment>
)}
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_ENCRYPTION_CONFIGURATION]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Encryption:</td>
<td>
{loadingEncryption ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<SecureComponent
scopes={[
IAM_SCOPES.S3_PUT_BUCKET_ENCRYPTION_CONFIGURATION,
]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setEnableEncryptionScreenOpen(true);
}}
>
{encryptionEnabled ? "Enabled" : "Disabled"}
</Button>
</SecureComponent>
)}
</td>
</tr>
</SecureComponent>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_TAGGING]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Tags:</td>
<td>
{tagKeys &&
tagKeys.map((tagKey: any, index: any) => {
const tag = get(tags, `${tagKey}`, "");
if (tag !== "") {
return (
<SecureComponent
key={`chip-${index}`}
scopes={[IAM_SCOPES.S3_PUT_BUCKET_TAGGING]}
resource={bucketName}
matchAll
errorProps={{
deleteIcon: null,
onDelete: null,
}}
>
<Chip
className={classes.tag}
size="small"
label={`${tagKey} : ${tag}`}
color="primary"
deleteIcon={<CloseIcon />}
onDelete={() => {
deleteTag(tagKey, tag);
}}
/>
</SecureComponent>
);
}
return null;
})}
<SecureComponent
scopes={[IAM_SCOPES.S3_PUT_BUCKET_TAGGING]}
resource={bucketName}
errorProps={{ disabled: true, onClick: null }}
>
<Chip
className={classes.tag}
icon={<AddIcon />}
clickable
size="small"
label="Add tag"
color="primary"
variant="outlined"
onClick={() => {
setTagModalOpen(true);
}}
/>
</SecureComponent>
</td>
</tr>
</SecureComponent>
</tbody>
</table>
</Grid>
<Grid item xs={4} className={classes.reportedUsage}>
<Grid container direction="row" alignItems="center">
<Grid item className={classes.icon} xs={2}>
<ReportedUsageIcon />
</Grid>
<Grid item xs={10}>
<Typography className={classes.elementTitle}>
Reported Usage
</Typography>
</Grid>
</Grid>
<Typography className={classes.consumptionValue}>
{niceBytes(bucketSize)}
</Typography>
</Grid>
</Grid>
</Paper>
<br />
<br />
{distributedSetup && (
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_VERSIONING]}
resource={bucketName}
>
<Fragment>
<Paper className={classes.paperContainer} elevation={1}>
<Grid container>
<Grid item xs={quotaEnabled ? 9 : 12}>
<h2>Versioning</h2>
<hr className={classes.hrClass} />
<table width={"100%"}>
<tbody>
<tr>
<td className={classes.titleCol}>Versioning:</td>
<td>
{loadingVersioning ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<SecureComponent
scopes={[IAM_SCOPES.S3_PUT_BUCKET_VERSIONING]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<Button
color="primary"
className={classes.anchorButton}
onClick={setBucketVersioning}
>
{isVersioned ? "Enabled" : "Disabled"}
</Button>
</SecureComponent>
)}
</td>
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_GET_BUCKET_QUOTA]}
resource={bucketName}
>
<td className={classes.titleCol}>Quota:</td>
<td>
{loadingQuota ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_SET_BUCKET_QUOTA]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<Button
color="primary"
className={classes.anchorButton}
onClick={setBucketQuota}
>
{quotaEnabled ? "Enabled" : "Disabled"}
</Button>
</SecureComponent>
)}
</td>
</SecureComponent>
</tr>
</tbody>
</table>
</Grid>
{quotaEnabled && quota && (
<Grid item xs={3} className={classes.reportedUsage}>
<Grid container direction="row" alignItems="center">
<Grid item className={classes.icon} xs={2}>
<GavelIcon />
</Grid>
<Grid item xs={10}>
<Typography className={classes.elementTitle}>
{cap(quota?.type)} Quota
</Typography>
</Grid>
</Grid>
<Typography className={classes.consumptionValue}>
{niceBytes(`${quota?.quota}`)}
</Typography>
</Grid>
)}
</Grid>
</Paper>
<br />
<br />
</Fragment>
</SecureComponent>
)}
{hasObjectLocking && (
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_OBJECT_RETENTION]}
resource={bucketName}
>
<Paper className={classes.paperContainer}>
<Grid container>
<Grid item xs={12}>
<h2>Retention</h2>
<hr className={classes.hrClass} />
<table width={"100%"}>
<tbody>
<tr className={classes.gridContainer}>
<td className={classes.titleCol}>Status:</td>
<td>
{loadingRetention ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<SecureComponent
scopes={[IAM_SCOPES.S3_PUT_OBJECT_RETENTION]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setRetentionConfigOpen(true);
}}
>
{!retentionEnabled ? "Disabled" : "Enabled"}
</Button>
</SecureComponent>
)}
</td>
{retentionConfig === null ? (
<td colSpan={2}>&nbsp;</td>
) : (
<Fragment>
<td className={classes.titleCol}>Mode:</td>
<td className={classes.capitalizeFirst}>
{retentionConfig && retentionConfig.mode}
</td>
</Fragment>
)}
</tr>
<tr className={classes.gridContainer}>
{retentionConfig === null ? (
<td colSpan={2}></td>
) : (
<Fragment>
<td className={classes.titleCol}>Valitidy:</td>
<td className={classes.capitalizeFirst}>
{retentionConfig && retentionConfig.validity}{" "}
{retentionConfig &&
(retentionConfig.validity === 1
? retentionConfig.unit.slice(0, -1)
: retentionConfig.unit)}
</td>
</Fragment>
)}
</tr>
</tbody>
</table>
</Grid>
</Grid>
</Paper>
</SecureComponent>
)}
</Fragment>
);
};
const mapState = (state: AppState) => ({
session: state.console.session,
distributedSetup: state.system.distributedSetup,
loadingBucket: state.buckets.bucketDetails.loadingBucket,
bucketInfo: state.buckets.bucketDetails.bucketInfo,
});
const connector = connect(mapState, {
setErrorSnackMessage,
setBucketDetailsLoad,
});
export default withStyles(styles)(connector(BucketSummary));