Files
openmaxio-object-browser/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketSummaryPanel.tsx
2023-08-02 13:35:00 -06:00

662 lines
21 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 get from "lodash/get";
import { useSelector } from "react-redux";
import { useParams } from "react-router-dom";
import { api } from "api";
import {
BucketEncryptionInfo,
BucketQuota,
BucketVersioningResponse,
GetBucketRetentionConfig,
} from "api/consoleApi";
import { errorToHandler } from "api/errors";
import {
Box,
DisabledIcon,
EnabledIcon,
Grid,
SectionTitle,
ValuePair,
} from "mds";
import { twoColCssGridLayoutConfig } from "../../Common/FormComponents/common/styleLibrary";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
import {
hasPermission,
SecureComponent,
} from "../../../../common/SecureComponent";
import {
selDistSet,
setErrorSnackMessage,
setHelpName,
} from "../../../../systemSlice";
import {
selBucketDetailsInfo,
selBucketDetailsLoading,
setBucketDetailsLoad,
} from "./bucketDetailsSlice";
import { useAppDispatch } from "../../../../store";
import VersioningInfo from "../VersioningInfo";
import withSuspense from "../../Common/Components/withSuspense";
import LabelWithIcon from "./SummaryItems/LabelWithIcon";
import EditablePropertyItem from "./SummaryItems/EditablePropertyItem";
import ReportedUsage from "./SummaryItems/ReportedUsage";
import BucketQuotaSize from "./SummaryItems/BucketQuotaSize";
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 BucketTags = withSuspense(
React.lazy(() => import("./SummaryItems/BucketTags")),
);
const EnableQuota = withSuspense(React.lazy(() => import("./EnableQuota")));
const BucketSummary = () => {
const dispatch = useAppDispatch();
const params = useParams();
const loadingBucket = useSelector(selBucketDetailsLoading);
const bucketInfo = useSelector(selBucketDetailsInfo);
const distributedSetup = useSelector(selDistSet);
const [encryptionCfg, setEncryptionCfg] =
useState<BucketEncryptionInfo | null>(null);
const [bucketSize, setBucketSize] = useState<number | "0">("0");
const [hasObjectLocking, setHasObjectLocking] = useState<boolean | undefined>(
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 [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 [versioningInfo, setVersioningInfo] =
useState<BucketVersioningResponse>();
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<GetBucketRetentionConfig | 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);
useEffect(() => {
dispatch(setHelpName("bucket_detail_summary"));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const bucketName = params.bucketName || "";
let accessPolicy = "n/a";
let policyDefinition = "";
if (bucketInfo !== null && bucketInfo.access && bucketInfo.definition) {
accessPolicy = bucketInfo.access;
policyDefinition = bucketInfo.definition;
}
const displayGetBucketObjectLockConfiguration = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION,
IAM_SCOPES.S3_GET_ACTIONS,
]);
const displayGetBucketEncryptionConfiguration = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_BUCKET_ENCRYPTION_CONFIGURATION,
IAM_SCOPES.S3_GET_ACTIONS,
]);
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.buckets
.getBucketEncryptionInfo(bucketName)
.then((res) => {
if (res.data.algorithm) {
setEncryptionEnabled(true);
setEncryptionCfg(res.data);
}
setLoadingEncryption(false);
})
.catch((err) => {
err = errorToHandler(err.error);
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.buckets
.getBucketVersioning(bucketName)
.then((res) => {
setVersioningInfo(res.data);
setLoadingVersioning(false);
})
.catch((err) => {
dispatch(setErrorSnackMessage(errorToHandler(err.error)));
setLoadingVersioning(false);
});
}
}, [loadingVersioning, dispatch, bucketName, distributedSetup]);
useEffect(() => {
if (loadingQuota && distributedSetup) {
if (displayGetBucketQuota) {
api.buckets
.getBucketQuota(bucketName)
.then((res) => {
setQuota(res.data);
if (res.data.quota) {
setQuotaEnabled(true);
} else {
setQuotaEnabled(false);
}
setLoadingQuota(false);
})
.catch((err) => {
dispatch(setErrorSnackMessage(errorToHandler(err.error)));
setQuotaEnabled(false);
setLoadingQuota(false);
});
} else {
setQuotaEnabled(false);
setLoadingQuota(false);
}
}
}, [
loadingQuota,
setLoadingVersioning,
dispatch,
bucketName,
distributedSetup,
displayGetBucketQuota,
]);
useEffect(() => {
if (loadingVersioning && distributedSetup) {
if (displayGetBucketObjectLockConfiguration) {
api.buckets
.getBucketObjectLockingStatus(bucketName)
.then((res) => {
setHasObjectLocking(res.data.object_locking_enabled);
setLoadingLocking(false);
})
.catch((err) => {
dispatch(setErrorSnackMessage(errorToHandler(err.error)));
setLoadingLocking(false);
});
} else {
setLoadingLocking(false);
}
}
}, [
loadingObjectLocking,
dispatch,
bucketName,
loadingVersioning,
distributedSetup,
displayGetBucketObjectLockConfiguration,
]);
useEffect(() => {
if (loadingSize) {
api.buckets
.listBuckets()
.then((res) => {
const resBuckets = get(res.data, "buckets", []);
const bucketInfo = resBuckets.find(
(bucket) => bucket.name === bucketName,
);
const size = get(bucketInfo, "size", "0");
setLoadingSize(false);
setBucketSize(size);
})
.catch((err) => {
setLoadingSize(false);
dispatch(setErrorSnackMessage(errorToHandler(err.error)));
});
}
}, [loadingSize, dispatch, bucketName]);
useEffect(() => {
if (loadingReplication && distributedSetup) {
api.buckets
.getBucketReplication(bucketName)
.then((res) => {
const r = res.data.rules ? res.data.rules : [];
setReplicationRules(r.length > 0);
setLoadingReplication(false);
})
.catch((err) => {
dispatch(setErrorSnackMessage(errorToHandler(err.error)));
setLoadingReplication(false);
});
}
}, [loadingReplication, dispatch, bucketName, distributedSetup]);
useEffect(() => {
if (loadingRetention && hasObjectLocking) {
api.buckets
.getBucketRetentionConfig(bucketName)
.then((res) => {
setLoadingRetention(false);
setRetentionEnabled(true);
setRetentionConfig(res.data);
})
.catch((err) => {
setRetentionEnabled(false);
setLoadingRetention(false);
setRetentionConfig(null);
});
}
}, [loadingRetention, hasObjectLocking, bucketName]);
const loadAllBucketData = () => {
dispatch(setBucketDetailsLoad(true));
setBucketLoading(true);
setLoadingSize(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();
}
};
let versioningStatus = versioningInfo?.status;
let versioningText = "Unversioned (Default)";
if (versioningStatus === "Enabled") {
versioningText = "Versioned";
} else if (versioningStatus === "Suspended") {
versioningText = "Suspended";
}
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}
versioningInfo={versioningInfo}
objectLockingEnabled={!!hasObjectLocking}
/>
)}
<SectionTitle separator sx={{ marginBottom: 15 }}>
Summary
</SectionTitle>
<Grid container>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_POLICY, IAM_SCOPES.S3_GET_ACTIONS]}
resource={bucketName}
>
<Grid item xs={12}>
<Box sx={twoColCssGridLayoutConfig}>
<Box sx={twoColCssGridLayoutConfig}>
<SecureComponent
scopes={[
IAM_SCOPES.S3_GET_BUCKET_POLICY,
IAM_SCOPES.S3_GET_ACTIONS,
]}
resource={bucketName}
>
<EditablePropertyItem
iamScopes={[
IAM_SCOPES.S3_PUT_BUCKET_POLICY,
IAM_SCOPES.S3_PUT_ACTIONS,
]}
resourceName={bucketName}
property={"Access Policy:"}
value={accessPolicy.toLowerCase()}
onEdit={() => {
setAccessPolicyScreenOpen(true);
}}
isLoading={bucketLoading}
/>
</SecureComponent>
<SecureComponent
scopes={[
IAM_SCOPES.S3_GET_BUCKET_ENCRYPTION_CONFIGURATION,
IAM_SCOPES.S3_GET_ACTIONS,
]}
resource={bucketName}
>
<EditablePropertyItem
iamScopes={[
IAM_SCOPES.S3_PUT_BUCKET_ENCRYPTION_CONFIGURATION,
IAM_SCOPES.S3_PUT_ACTIONS,
]}
resourceName={bucketName}
property={"Encryption:"}
value={encryptionEnabled ? "Enabled" : "Disabled"}
onEdit={() => {
setEnableEncryptionScreenOpen(true);
}}
isLoading={loadingEncryption}
/>
</SecureComponent>
<SecureComponent
scopes={[
IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION,
IAM_SCOPES.S3_GET_ACTIONS,
]}
resource={bucketName}
>
<ValuePair
label={"Replication:"}
value={
<LabelWithIcon
icon={
replicationRules ? <EnabledIcon /> : <DisabledIcon />
}
label={
<label className={"muted"}>
{replicationRules ? "Enabled" : "Disabled"}
</label>
}
/>
}
/>
</SecureComponent>
<SecureComponent
scopes={[
IAM_SCOPES.S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION,
IAM_SCOPES.S3_GET_ACTIONS,
]}
resource={bucketName}
>
<ValuePair
label={"Object Locking:"}
value={
<LabelWithIcon
icon={
hasObjectLocking ? <EnabledIcon /> : <DisabledIcon />
}
label={
<label className={"muted"}>
{hasObjectLocking ? "Enabled" : "Disabled"}
</label>
}
/>
}
/>
</SecureComponent>
<Box>
<ValuePair
label={"Tags:"}
value={<BucketTags bucketName={bucketName} />}
/>
</Box>
<EditablePropertyItem
iamScopes={[IAM_SCOPES.ADMIN_SET_BUCKET_QUOTA]}
resourceName={bucketName}
property={"Quota:"}
value={quotaEnabled ? "Enabled" : "Disabled"}
onEdit={setBucketQuota}
isLoading={loadingQuota}
/>
</Box>
<Box
sx={{
display: "grid",
gridTemplateColumns: "1fr",
alignItems: "flex-start",
}}
>
<ReportedUsage bucketSize={`${bucketSize}`} />
{quotaEnabled && quota ? (
<BucketQuotaSize quota={quota} />
) : null}
</Box>
</Box>
</Grid>
</SecureComponent>
{distributedSetup && (
<SecureComponent
scopes={[
IAM_SCOPES.S3_GET_BUCKET_VERSIONING,
IAM_SCOPES.S3_GET_ACTIONS,
]}
resource={bucketName}
>
<Grid item xs={12} sx={{ marginTop: 5 }}>
<SectionTitle separator sx={{ marginBottom: 15 }}>
Versioning
</SectionTitle>
<Box sx={twoColCssGridLayoutConfig}>
<Box sx={twoColCssGridLayoutConfig}>
<EditablePropertyItem
iamScopes={[
IAM_SCOPES.S3_PUT_BUCKET_VERSIONING,
IAM_SCOPES.S3_PUT_ACTIONS,
]}
resourceName={bucketName}
property={"Current Status:"}
value={
<Box
sx={{
display: "flex",
flexDirection: "column",
textDecorationStyle: "initial",
placeItems: "flex-start",
justifyItems: "flex-start",
gap: 3,
}}
>
<div> {versioningText}</div>
</Box>
}
onEdit={setBucketVersioning}
isLoading={loadingVersioning}
disabled={hasObjectLocking}
/>
{versioningInfo?.status === "Enabled" ? (
<VersioningInfo versioningState={versioningInfo} />
) : null}
</Box>
</Box>
</Grid>
</SecureComponent>
)}
{hasObjectLocking && (
<SecureComponent
scopes={[
IAM_SCOPES.S3_GET_OBJECT_RETENTION,
IAM_SCOPES.S3_GET_ACTIONS,
]}
resource={bucketName}
>
<Grid item xs={12} sx={{ marginTop: 5 }}>
<SectionTitle separator sx={{ marginBottom: 15 }}>
Retention
</SectionTitle>
<Box sx={twoColCssGridLayoutConfig}>
<Box sx={twoColCssGridLayoutConfig}>
<EditablePropertyItem
iamScopes={[IAM_SCOPES.ADMIN_SET_BUCKET_QUOTA]}
resourceName={bucketName}
property={"Retention:"}
value={retentionEnabled ? "Enabled" : "Disabled"}
onEdit={() => {
setRetentionConfigOpen(true);
}}
isLoading={loadingRetention}
/>
<ValuePair
label={"Mode:"}
value={
<label
className={"muted"}
style={{ textTransform: "capitalize" }}
>
{retentionConfig && retentionConfig.mode
? retentionConfig.mode
: "-"}
</label>
}
/>
<ValuePair
label={"Validity:"}
value={
<label
className={"muted"}
style={{ textTransform: "capitalize" }}
>
{retentionConfig && retentionConfig.validity}{" "}
{retentionConfig &&
(retentionConfig.validity === 1
? retentionConfig.unit?.slice(0, -1)
: retentionConfig.unit)}
</label>
}
/>
</Box>
</Box>
</Grid>
</SecureComponent>
)}
</Grid>
</Fragment>
);
};
export default BucketSummary;