UX Object Details page (#1475)

Co-authored-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
Prakash Senthil Vel
2022-01-27 17:23:21 +00:00
committed by GitHub
parent c129eae6a7
commit e093efa931
7 changed files with 420 additions and 308 deletions

View File

@@ -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>
);

View File

@@ -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={{

View File

@@ -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 />}
>

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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>
}
/>
);
};