UX Object Details page (#1475)
Co-authored-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
committed by
GitHub
parent
c129eae6a7
commit
e093efa931
@@ -22,21 +22,14 @@ const RecoverIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
viewBox="0 0 256 256"
|
||||
viewBox="0 0 256 255.999"
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<clipPath id="prefix__a">
|
||||
<path d="M0 0h256v256H0z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clipPath="url(#prefix__a)">
|
||||
<path fill="none" d="M0 0h256v256H0z" />
|
||||
<g data-name="RecoverIcon">
|
||||
<path d="M216.783 256a16.655 16.655 0 0 1-4.354-.6l-57.238-15.5a14.778 14.778 0 0 1-10.492-18.271l15.535-57.135c5.1-18.748 33.652-11.014 28.557 7.734l-5.8 21.333-1.033 3.5c.176-.094.342-.2.525-.288a84.861 84.861 0 0 0 39.223-113.4 85.2 85.2 0 0 0-62.492-46.565 12.846 12.846 0 0 1-10.568-14.789 12.864 12.864 0 0 1 14.811-10.552 110.978 110.978 0 0 1 81.389 60.667 109.742 109.742 0 0 1 11.158 47.846v.683a110.648 110.648 0 0 1-62.258 99.21c-.059.032-.121.049-.18.077l9.572 2.328 17.045 4.615c17.252 4.673 12.115 29.111-3.393 29.111ZM94.678 244.716a13.242 13.242 0 0 1-2.135-.175 110.98 110.98 0 0 1-81.387-60.667A109.694 109.694 0 0 1 .002 135.786v-.229a110.629 110.629 0 0 1 62.252-99.421c.064-.032.123-.05.186-.081l-9.576-2.323-17.041-4.615C18.589 24.448 23.694.064 39.157.002h.131a16.69 16.69 0 0 1 4.283.606l57.242 15.5a14.775 14.775 0 0 1 10.488 18.272L95.77 91.514c-5.1 18.749-33.658 11.015-28.562-7.734l5.8-21.336 1.039-3.5c-.176.094-.346.2-.531.288a84.855 84.855 0 0 0-39.217 113.4 85.188 85.188 0 0 0 62.486 46.569 12.845 12.845 0 0 1 10.57 14.785 12.866 12.866 0 0 1-12.674 10.731ZM107 128a21 21 0 0 1 21-21 21 21 0 0 1 21 21 21 21 0 0 1-21 21 21 21 0 0 1-21-21Z" />
|
||||
<path data-name="Rect\xE1ngulo 845" fill="none" d="M0 0h256v256H0z" />
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
id="recover-icn"
|
||||
d="M17866.783-5487a16.655,16.655,0,0,1-4.354-.6l-57.238-15.5a14.778,14.778,0,0,1-10.492-18.271l15.535-57.135c5.1-18.748,33.652-11.014,28.557,7.734l-5.8,21.333-1.033,3.5c.176-.094.342-.2.525-.288a84.861,84.861,0,0,0,39.223-113.4,85.2,85.2,0,0,0-62.492-46.565,12.846,12.846,0,0,1-10.568-14.789,12.864,12.864,0,0,1,14.811-10.552,110.978,110.978,0,0,1,81.389,60.667,109.742,109.742,0,0,1,11.158,47.846v.683a110.648,110.648,0,0,1-62.258,99.21c-.059.032-.121.049-.18.077l9.572,2.328,17.045,4.615c17.252,4.673,12.115,29.111-3.393,29.111Zm-122.105-11.284a13.242,13.242,0,0,1-2.135-.175,110.98,110.98,0,0,1-81.387-60.667,109.694,109.694,0,0,1-11.154-48.088v-.229a110.629,110.629,0,0,1,62.252-99.421c.064-.032.123-.05.186-.081l-9.576-2.323-17.041-4.615c-17.234-4.669-12.129-29.053,3.334-29.115h.131a16.69,16.69,0,0,1,4.283.606l57.242,15.5a14.775,14.775,0,0,1,10.488,18.272l-15.531,57.134c-5.1,18.749-33.658,11.015-28.562-7.734l5.8-21.336,1.039-3.5c-.176.094-.346.2-.531.288a84.855,84.855,0,0,0-39.217,113.4,85.188,85.188,0,0,0,62.486,46.569,12.845,12.845,0,0,1,10.57,14.785,12.866,12.866,0,0,1-12.674,10.731ZM17757-5615a21,21,0,0,1,21-21,21,21,0,0,1,21,21,21,21,0,0,1-21,21A21,21,0,0,1,17757-5615Z"
|
||||
transform="translate(-17650.002 5743.001)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
|
||||
@@ -21,29 +21,33 @@ import ActionLink from "./ActionLink";
|
||||
import { Box } from "@mui/material";
|
||||
import EditActionButton from "./EditActionButton";
|
||||
|
||||
type PolicyItemProps = {
|
||||
type EditablePropertyItemProps = {
|
||||
isLoading: boolean;
|
||||
resourceName: string;
|
||||
iamScopes: string[];
|
||||
property: any;
|
||||
value: any;
|
||||
onEdit: () => void;
|
||||
secureCmpProps?: Record<any, any>;
|
||||
};
|
||||
|
||||
const SecureAction = ({
|
||||
resourceName,
|
||||
iamScopes,
|
||||
secureCmpProps = {},
|
||||
children,
|
||||
}: {
|
||||
resourceName: string;
|
||||
iamScopes: string[];
|
||||
children: any;
|
||||
secureCmpProps?: Record<any, any>;
|
||||
}) => {
|
||||
return (
|
||||
<SecureComponent
|
||||
scopes={iamScopes}
|
||||
resource={resourceName}
|
||||
errorProps={{ disabled: true }}
|
||||
{...secureCmpProps}
|
||||
>
|
||||
{children}
|
||||
</SecureComponent>
|
||||
@@ -54,10 +58,11 @@ const EditablePropertyItem = ({
|
||||
isLoading = true,
|
||||
resourceName = "",
|
||||
iamScopes,
|
||||
secureCmpProps = {},
|
||||
property = null,
|
||||
value = null,
|
||||
onEdit,
|
||||
}: PolicyItemProps) => {
|
||||
}: EditablePropertyItemProps) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -69,12 +74,20 @@ const EditablePropertyItem = ({
|
||||
<LabelValuePair
|
||||
label={property}
|
||||
value={
|
||||
<SecureAction resourceName={resourceName} iamScopes={iamScopes}>
|
||||
<SecureAction
|
||||
resourceName={resourceName}
|
||||
iamScopes={iamScopes}
|
||||
secureCmpProps={secureCmpProps}
|
||||
>
|
||||
<ActionLink isLoading={isLoading} onClick={onEdit} label={value} />
|
||||
</SecureAction>
|
||||
}
|
||||
/>
|
||||
<SecureAction resourceName={resourceName} iamScopes={iamScopes}>
|
||||
<SecureAction
|
||||
resourceName={resourceName}
|
||||
iamScopes={iamScopes}
|
||||
secureCmpProps={secureCmpProps}
|
||||
>
|
||||
<EditActionButton
|
||||
onClick={onEdit}
|
||||
sx={{
|
||||
|
||||
@@ -94,8 +94,8 @@ const AddTagModal = ({
|
||||
{ tags: newTagList }
|
||||
)
|
||||
.then((res: any) => {
|
||||
setIsSending(false);
|
||||
onCloseAndUpdate(true);
|
||||
setIsSending(false);
|
||||
})
|
||||
.catch((error: ErrorResponseHandler) => {
|
||||
setModalErrorSnackMessage(error);
|
||||
@@ -109,7 +109,7 @@ const AddTagModal = ({
|
||||
modalOpen={modalOpen}
|
||||
title="Add New Tag to the Object"
|
||||
onClose={() => {
|
||||
onCloseAndUpdate(false);
|
||||
onCloseAndUpdate(true);
|
||||
}}
|
||||
titleIcon={<AddNewTagIcon />}
|
||||
>
|
||||
|
||||
@@ -15,38 +15,24 @@
|
||||
// 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 { withRouter } from "react-router-dom";
|
||||
|
||||
import get from "lodash/get";
|
||||
import * as reactMoment from "react-moment";
|
||||
import clsx from "clsx";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import {
|
||||
CircularProgress,
|
||||
LinearProgress,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
} from "@mui/material";
|
||||
import { Box, CircularProgress, LinearProgress } from "@mui/material";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Chip from "@mui/material/Chip";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import InputAdornment from "@mui/material/InputAdornment";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import ShareFile from "./ShareFile";
|
||||
import {
|
||||
actionsTray,
|
||||
buttonsStyles,
|
||||
containerForHeader,
|
||||
hrClass,
|
||||
searchField,
|
||||
tableStyles,
|
||||
spacingUtils,
|
||||
textStyleUtils,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { IFileInfo, MetadataResponse } from "./types";
|
||||
import { IFileInfo } from "./types";
|
||||
import { download, extensionPreview } from "../utils";
|
||||
import history from "../../../../../../history";
|
||||
import api from "../../../../../../common/api";
|
||||
@@ -54,7 +40,6 @@ import api from "../../../../../../common/api";
|
||||
import TableWrapper, {
|
||||
ItemActions,
|
||||
} from "../../../../Common/TableWrapper/TableWrapper";
|
||||
import { AppState } from "../../../../../../store";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
import {
|
||||
setErrorSnackMessage,
|
||||
@@ -81,6 +66,14 @@ import {
|
||||
updateProgress,
|
||||
} from "../../../../ObjectBrowser/actions";
|
||||
import RBIconButton from "../../../BucketDetails/SummaryItems/RBIconButton";
|
||||
import SearchBox from "../../../../Common/SearchBox";
|
||||
import ObjectTags from "./ObjectTags";
|
||||
import { AppState } from "../../../../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { withStyles } from "@mui/styles";
|
||||
import { DisabledIcon } from "../../../../../../icons";
|
||||
import LabelWithIcon from "../../../BucketDetails/SummaryItems/LabelWithIcon";
|
||||
|
||||
const RecoverIcon = React.lazy(
|
||||
() => import("../../../../../../icons/RecoverIcon")
|
||||
@@ -92,13 +85,17 @@ const DownloadIcon = React.lazy(
|
||||
const DeleteIcon = React.lazy(
|
||||
() => import("../../../../../../icons/DeleteIcon")
|
||||
);
|
||||
const EditIcon = React.lazy(() => import("../../../../../../icons/EditIcon"));
|
||||
const SearchIcon = React.lazy(
|
||||
() => import("../../../../../../icons/SearchIcon")
|
||||
);
|
||||
|
||||
const ObjectBrowserIcon = React.lazy(
|
||||
() => import("../../../../../../icons/ObjectBrowserIcon")
|
||||
);
|
||||
const ObjectMetaData = React.lazy(() => import("./ObjectMetaData"));
|
||||
const EditablePropertyItem = React.lazy(
|
||||
() => import("../../../BucketDetails/SummaryItems/EditablePropertyItem")
|
||||
);
|
||||
const LabelValuePair = React.lazy(
|
||||
() => import("../../../../Common/UsageBarWrapper/LabelValuePair")
|
||||
);
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -161,7 +158,9 @@ const styles = (theme: Theme) =>
|
||||
...hrClass,
|
||||
...buttonsStyles,
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...tableStyles,
|
||||
...spacingUtils,
|
||||
...textStyleUtils,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
@@ -192,6 +191,12 @@ const emptyFile: IFileInfo = {
|
||||
version_id: null,
|
||||
};
|
||||
|
||||
const twoColCssGridLayoutConfig = {
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", sm: "2fr 1fr" },
|
||||
gridAutoFlow: { xs: "dense", sm: "row" },
|
||||
gap: 2,
|
||||
};
|
||||
const ObjectDetails = ({
|
||||
classes,
|
||||
downloadingFiles,
|
||||
@@ -218,8 +223,6 @@ const ObjectDetails = ({
|
||||
const [versions, setVersions] = useState<IFileInfo[]>([]);
|
||||
const [filterVersion, setFilterVersion] = useState<string>("");
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [metadataLoad, setMetadataLoad] = useState<boolean>(true);
|
||||
const [metadata, setMetadata] = useState<any>({});
|
||||
const [restoreVersionOpen, setRestoreVersionOpen] = useState<boolean>(false);
|
||||
const [restoreVersion, setRestoreVersion] = useState<string>("");
|
||||
|
||||
@@ -271,25 +274,6 @@ const ObjectDetails = ({
|
||||
distributedSetup,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (metadataLoad && internalPaths !== "") {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects/metadata?prefix=${internalPaths}`
|
||||
)
|
||||
.then((res: MetadataResponse) => {
|
||||
let metadata = get(res, "objectMetadata", {});
|
||||
|
||||
setMetadata(metadata);
|
||||
setMetadataLoad(false);
|
||||
})
|
||||
.catch((error: ErrorResponseHandler) => {
|
||||
setMetadataLoad(false);
|
||||
});
|
||||
}
|
||||
}, [bucketName, metadataLoad, internalPaths]);
|
||||
|
||||
let tagKeys: string[] = [];
|
||||
|
||||
if (actualInfo && actualInfo.tags) {
|
||||
@@ -421,7 +405,6 @@ const ObjectDetails = ({
|
||||
|
||||
const closeAddTagModal = (reloadObjectData: boolean) => {
|
||||
setTagModalOpen(false);
|
||||
|
||||
if (reloadObjectData) {
|
||||
setLoadObjectData(true);
|
||||
}
|
||||
@@ -429,7 +412,6 @@ const ObjectDetails = ({
|
||||
|
||||
const closeLegalholdModal = (reload: boolean) => {
|
||||
setLegalholdOpen(false);
|
||||
|
||||
if (reload) {
|
||||
setLoadObjectData(true);
|
||||
}
|
||||
@@ -437,7 +419,6 @@ const ObjectDetails = ({
|
||||
|
||||
const closeDeleteTagModal = (reloadObjectData: boolean) => {
|
||||
setDeleteTagModalOpen(false);
|
||||
|
||||
if (reloadObjectData) {
|
||||
setLoadObjectData(true);
|
||||
}
|
||||
@@ -449,7 +430,6 @@ const ObjectDetails = ({
|
||||
|
||||
if (reloadObjectData) {
|
||||
setLoadObjectData(true);
|
||||
setMetadataLoad(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -625,190 +605,131 @@ const ObjectDetails = ({
|
||||
<h1 className={classes.sectionTitle}>Details</h1>
|
||||
</div>
|
||||
<br />
|
||||
<Grid item xs={12}>
|
||||
<table width={"100%"}>
|
||||
<tbody>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_GET_OBJECT_LEGAL_HOLD]}
|
||||
resource={bucketName}
|
||||
>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Legal Hold:</td>
|
||||
<td className={classes.capitalizeFirst}>
|
||||
{actualInfo.version_id &&
|
||||
actualInfo.version_id !== "null" ? (
|
||||
<Fragment>
|
||||
{actualInfo.legal_hold_status
|
||||
? actualInfo.legal_hold_status.toLowerCase()
|
||||
: "Off"}
|
||||
<SecureComponent
|
||||
scopes={[
|
||||
IAM_SCOPES.S3_PUT_OBJECT_LEGAL_HOLD,
|
||||
]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{
|
||||
disabled: true,
|
||||
onClick: null,
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="legal-hold"
|
||||
size="small"
|
||||
className={classes.propertiesIcon}
|
||||
onClick={() => {
|
||||
setLegalholdOpen(true);
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</SecureComponent>
|
||||
</Fragment>
|
||||
) : (
|
||||
"Disabled"
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_GET_OBJECT_RETENTION]}
|
||||
resource={bucketName}
|
||||
>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Retention:</td>
|
||||
<td className={classes.capitalizeFirst}>
|
||||
{actualInfo.retention_mode
|
||||
? actualInfo.retention_mode.toLowerCase()
|
||||
: "None"}
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_PUT_OBJECT_RETENTION]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="retention"
|
||||
size="small"
|
||||
className={classes.propertiesIcon}
|
||||
onClick={() => {
|
||||
openRetentionModal();
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</SecureComponent>
|
||||
</td>
|
||||
</tr>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_GET_OBJECT_TAGGING]}
|
||||
resource={bucketName}
|
||||
>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Tags:</td>
|
||||
<td>
|
||||
{tagKeys &&
|
||||
tagKeys.map((tagKey, index) => {
|
||||
const tag = get(
|
||||
actualInfo,
|
||||
`tags.${tagKey}`,
|
||||
""
|
||||
);
|
||||
if (tag !== "") {
|
||||
return (
|
||||
<SecureComponent
|
||||
scopes={[
|
||||
IAM_SCOPES.S3_DELETE_OBJECT_TAGGING,
|
||||
]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{
|
||||
deleteIcon: null,
|
||||
onDelete: null,
|
||||
}}
|
||||
>
|
||||
<Chip
|
||||
key={`chip-${index}`}
|
||||
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_OBJECT_TAGGING]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
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={12}>
|
||||
<Grid item xs={12}>
|
||||
<h2>Object Metadata</h2>
|
||||
<hr className={classes.hr} />
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Table
|
||||
className={classes.table}
|
||||
aria-label="simple table"
|
||||
<Box sx={{ ...twoColCssGridLayoutConfig }}>
|
||||
<Box sx={{ ...twoColCssGridLayoutConfig }}>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_GET_OBJECT_LEGAL_HOLD]}
|
||||
resource={bucketName}
|
||||
>
|
||||
<TableBody>
|
||||
{Object.keys(metadata).map((element, index) => {
|
||||
const renderItem = Array.isArray(
|
||||
metadata[element]
|
||||
<LabelValuePair
|
||||
label={""}
|
||||
value={
|
||||
actualInfo.version_id &&
|
||||
actualInfo.version_id !== "null" ? (
|
||||
<EditablePropertyItem
|
||||
iamScopes={[
|
||||
IAM_SCOPES.S3_PUT_OBJECT_LEGAL_HOLD,
|
||||
]}
|
||||
secureCmpProps={{
|
||||
matchAll: false,
|
||||
errorProps: {
|
||||
disabled: true,
|
||||
onClick: null,
|
||||
},
|
||||
}}
|
||||
resourceName={bucketName}
|
||||
property={"Legal Hold:"}
|
||||
value={
|
||||
actualInfo.legal_hold_status
|
||||
? actualInfo.legal_hold_status.toLowerCase()
|
||||
: "Off"
|
||||
}
|
||||
onEdit={() => {
|
||||
setLegalholdOpen(true);
|
||||
}}
|
||||
isLoading={false}
|
||||
/>
|
||||
) : (
|
||||
<LabelValuePair
|
||||
label={"Legal Hold:"}
|
||||
value={
|
||||
<LabelWithIcon
|
||||
icon={<DisabledIcon />}
|
||||
label={
|
||||
<label className={classes.textMuted}>
|
||||
Disabled
|
||||
</label>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
? metadata[element]
|
||||
.map(decodeURIComponent)
|
||||
.join(", ")
|
||||
: decodeURIComponent(metadata[element]);
|
||||
}
|
||||
/>
|
||||
</SecureComponent>
|
||||
|
||||
return (
|
||||
<TableRow key={`tRow-${index.toString()}`}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={classes.titleItem}
|
||||
>
|
||||
{element}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{renderItem}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_GET_OBJECT_RETENTION]}
|
||||
resource={bucketName}
|
||||
>
|
||||
<LabelValuePair
|
||||
label={""}
|
||||
value={
|
||||
actualInfo.version_id &&
|
||||
actualInfo.version_id !== "null" ? (
|
||||
<EditablePropertyItem
|
||||
iamScopes={[
|
||||
IAM_SCOPES.S3_PUT_OBJECT_RETENTION,
|
||||
]}
|
||||
secureCmpProps={{
|
||||
matchAll: false,
|
||||
}}
|
||||
resourceName={bucketName}
|
||||
property={"Retention:"}
|
||||
value={
|
||||
actualInfo.retention_mode
|
||||
? actualInfo.retention_mode.toLowerCase()
|
||||
: "None"
|
||||
}
|
||||
onEdit={openRetentionModal}
|
||||
isLoading={false}
|
||||
/>
|
||||
) : (
|
||||
<LabelValuePair
|
||||
label={"Retention:"}
|
||||
value={
|
||||
<LabelWithIcon
|
||||
icon={<DisabledIcon />}
|
||||
label={
|
||||
<label className={classes.textMuted}>
|
||||
Disabled
|
||||
</label>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</SecureComponent>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className={classes.spacerTop}>
|
||||
<LabelValuePair
|
||||
label={"Tags:"}
|
||||
value={
|
||||
<ObjectTags
|
||||
objectInfo={actualInfo}
|
||||
tagKeys={tagKeys}
|
||||
bucketName={bucketName}
|
||||
onDeleteTag={deleteTag}
|
||||
onAddTagClick={() => {
|
||||
setTagModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{actualInfo ? (
|
||||
<ObjectMetaData
|
||||
bucketName={bucketName}
|
||||
internalPaths={internalPaths}
|
||||
actualInfo={actualInfo}
|
||||
/>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
@@ -828,30 +749,13 @@ const ObjectDetails = ({
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
{actualInfo.version_id &&
|
||||
actualInfo.version_id !== "null" && (
|
||||
<TextField
|
||||
<SearchBox
|
||||
placeholder={`Search ${currentItem}`}
|
||||
className={clsx(
|
||||
classes.search,
|
||||
classes.searchField
|
||||
)}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterVersion(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
variant="standard"
|
||||
onChange={setFilterVersion}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
{actualInfo.version_id &&
|
||||
actualInfo.version_id !== "null" && (
|
||||
<TableWrapper
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import useApi from "../../../../Common/Hooks/useApi";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
import { MetadataResponse } from "./types";
|
||||
import get from "lodash/get";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { Table, TableBody, TableCell, TableRow } from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import { spacingUtils } from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { withStyles } from "@mui/styles";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
propertiesIcon: {
|
||||
marginLeft: 5,
|
||||
"& .min-icon": {
|
||||
height: 12,
|
||||
},
|
||||
},
|
||||
|
||||
capitalizeFirst: {
|
||||
textTransform: "capitalize",
|
||||
"& .min-icon": {
|
||||
width: 16,
|
||||
height: 16,
|
||||
},
|
||||
},
|
||||
titleCol: {
|
||||
width: "25%",
|
||||
},
|
||||
titleItem: {
|
||||
width: "35%",
|
||||
},
|
||||
|
||||
...spacingUtils,
|
||||
});
|
||||
|
||||
const ObjectMetaData = ({
|
||||
bucketName,
|
||||
internalPaths,
|
||||
classes,
|
||||
actualInfo,
|
||||
}: {
|
||||
bucketName: string;
|
||||
internalPaths: string;
|
||||
classes?: any;
|
||||
actualInfo: any;
|
||||
}) => {
|
||||
const [metaData, setMetaData] = useState<any>({});
|
||||
|
||||
const onMetaDataSuccess = (res: MetadataResponse) => {
|
||||
let metadata = get(res, "objectMetadata", {});
|
||||
|
||||
setMetaData(metadata);
|
||||
};
|
||||
const onMetaDataError = (err: ErrorResponseHandler) => false;
|
||||
|
||||
const [, invokeMetaDataApi] = useApi(onMetaDataSuccess, onMetaDataError);
|
||||
|
||||
const metaKeys = Object.keys(metaData);
|
||||
const loadMetaData = useCallback(() => {
|
||||
invokeMetaDataApi(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects/metadata?prefix=${internalPaths}`
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [bucketName, internalPaths, actualInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (actualInfo) {
|
||||
loadMetaData();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [actualInfo, loadMetaData]);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sx={{
|
||||
marginTop: "25px",
|
||||
marginBottom: "5px",
|
||||
}}
|
||||
>
|
||||
<h3
|
||||
style={{
|
||||
marginTop: "0",
|
||||
marginBottom: "0",
|
||||
}}
|
||||
>
|
||||
Object Metadata
|
||||
</h3>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Table className={classes.table} aria-label="simple table">
|
||||
<TableBody>
|
||||
{metaKeys.map((element: string, index: number) => {
|
||||
const renderItem = Array.isArray(metaData[element])
|
||||
? metaData[element].map(decodeURIComponent).join(", ")
|
||||
: decodeURIComponent(metaData[element]);
|
||||
|
||||
return (
|
||||
<TableRow key={`tRow-${index.toString()}`}>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={classes.titleItem}
|
||||
>
|
||||
{element}
|
||||
</TableCell>
|
||||
<TableCell align="right">{renderItem}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ObjectMetaData);
|
||||
@@ -0,0 +1,93 @@
|
||||
import React from "react";
|
||||
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions";
|
||||
import { Box } from "@mui/material";
|
||||
import get from "lodash/get";
|
||||
import SecureComponent from "../../../../../../common/SecureComponent/SecureComponent";
|
||||
import Chip from "@mui/material/Chip";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
|
||||
const ObjectTags = ({
|
||||
tagKeys,
|
||||
bucketName,
|
||||
onDeleteTag,
|
||||
onAddTagClick,
|
||||
objectInfo,
|
||||
}: {
|
||||
tagKeys: any;
|
||||
bucketName: string;
|
||||
onDeleteTag: (key: string, v: string) => void;
|
||||
onAddTagClick: () => void;
|
||||
objectInfo: any;
|
||||
}) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_GET_OBJECT_TAGGING]}
|
||||
resource={bucketName}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
{tagKeys &&
|
||||
tagKeys.map((tagKey: string, index: number) => {
|
||||
const tag = get(objectInfo, `tags.${tagKey}`, "");
|
||||
if (tag !== "") {
|
||||
return (
|
||||
<SecureComponent
|
||||
key={`chip-${index}`}
|
||||
scopes={[IAM_SCOPES.S3_DELETE_OBJECT_TAGGING]}
|
||||
resource={bucketName}
|
||||
matchAll
|
||||
errorProps={{
|
||||
deleteIcon: null,
|
||||
onDelete: null,
|
||||
}}
|
||||
>
|
||||
<Chip
|
||||
style={{
|
||||
textTransform: "none",
|
||||
marginRight: "5px",
|
||||
}}
|
||||
size="small"
|
||||
label={`${tagKey} : ${tag}`}
|
||||
color="primary"
|
||||
deleteIcon={<CloseIcon />}
|
||||
onDelete={() => {
|
||||
onDeleteTag(tagKey, tag);
|
||||
}}
|
||||
/>
|
||||
</SecureComponent>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</Box>
|
||||
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.S3_PUT_OBJECT_TAGGING]}
|
||||
resource={bucketName}
|
||||
errorProps={{ disabled: true, onClick: null }}
|
||||
>
|
||||
<Chip
|
||||
style={{ maxWidth: 80, marginTop: "10px" }}
|
||||
icon={<AddIcon />}
|
||||
clickable
|
||||
size="small"
|
||||
label="Add tag"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
onClick={onAddTagClick}
|
||||
/>
|
||||
</SecureComponent>
|
||||
</Box>
|
||||
</SecureComponent>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ObjectTags;
|
||||
@@ -15,14 +15,7 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
} from "@mui/material";
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { connect } from "react-redux";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
@@ -32,6 +25,8 @@ import { setErrorSnackMessage } from "../../../../../../actions";
|
||||
import { ErrorResponseHandler } from "../../../../../../common/types";
|
||||
import { encodeFileName } from "../../../../../../common/utils";
|
||||
import api from "../../../../../../common/api";
|
||||
import ConfirmDialog from "../../../../Common/ModalWrapper/ConfirmDialog";
|
||||
import RecoverIcon from "../../../../../../icons/RecoverIcon";
|
||||
|
||||
interface IRestoreFileVersion {
|
||||
classes: any;
|
||||
@@ -79,41 +74,30 @@ const RestoreFileVersion = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={restoreOpen}
|
||||
<ConfirmDialog
|
||||
title={`Restore File Version`}
|
||||
confirmText={"Restore"}
|
||||
isOpen={restoreOpen}
|
||||
isLoading={restoreLoading}
|
||||
titleIcon={<RecoverIcon />}
|
||||
onConfirm={restoreVersion}
|
||||
confirmButtonProps={{
|
||||
color: "secondary",
|
||||
variant: "outlined",
|
||||
disabled: restoreLoading,
|
||||
}}
|
||||
onClose={() => {
|
||||
onCloseAndUpdate(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Restore File Version</DialogTitle>
|
||||
<DialogContent>
|
||||
confirmationContent={
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to restore <b>{objectPath}</b> <br /> with
|
||||
Version ID: <b className={classes.wrapText}>{versionID}</b>?
|
||||
Are you sure you want to restore <br />
|
||||
<b>{objectPath}</b> <br /> with Version ID:
|
||||
<br />
|
||||
<b className={classes.wrapText}>{versionID}</b>?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onCloseAndUpdate(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={restoreLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={restoreVersion}
|
||||
color="primary"
|
||||
autoFocus
|
||||
disabled={restoreLoading}
|
||||
>
|
||||
Restore
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user