diff --git a/portal-ui/src/icons/ClosePanelIcon.tsx b/portal-ui/src/icons/ClosePanelIcon.tsx new file mode 100644 index 000000000..96c5486a3 --- /dev/null +++ b/portal-ui/src/icons/ClosePanelIcon.tsx @@ -0,0 +1,45 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 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 . + +import * as React from "react"; +import { SVGProps } from "react"; + +const ClosePanelIcon = (props: SVGProps) => ( + + + + + + +); + +export default ClosePanelIcon; diff --git a/portal-ui/src/icons/index.ts b/portal-ui/src/icons/index.ts index 5a0cc76e3..c8dea142d 100644 --- a/portal-ui/src/icons/index.ts +++ b/portal-ui/src/icons/index.ts @@ -118,6 +118,7 @@ export { default as VersionIcon } from "./VersionIcon"; export { default as WarnIcon } from "./WarnIcon"; export { default as WarpIcon } from "./WarpIcon"; export { default as WatchIcon } from "./WatchIcon"; +export { default as ClosePanelIcon } from "./ClosePanelIcon"; export { default as LoginMinIOLogo } from "./LoginMinIOLogo"; /*Modal Title Icons **/ diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/DetailsListPanel.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/DetailsListPanel.tsx new file mode 100644 index 000000000..7be5c36d7 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/DetailsListPanel.tsx @@ -0,0 +1,78 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 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 . + +import React from "react"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import { Grid, IconButton } from "@mui/material"; +import { ClosePanelIcon } from "../../../../../../icons"; + +interface IDetailsListPanel { + classes: any; + open: boolean; + closePanel: () => void; + children: React.ReactNode; +} + +const styles = (theme: Theme) => + createStyles({ + detailsList: { + borderColor: "#EAEDEE", + backgroundColor: "#fff", + borderWidth: 0, + borderStyle: "solid", + borderRadius: 3, + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + width: 0, + transitionDuration: "0.3s", + overflowX: "hidden", + overflowY: "auto", + position:"relative", + opacity: 0, + marginLeft: -1, + "&.open": { + width: 400, + borderTopWidth: 1, + borderBottomWidth: 1, + borderRightWidth: 1, + borderLeftWidth: 1, + opacity: 1, + }, + }, + closePanel: { + position: "absolute", + right: 0, + top: 8, + "& .min-icon": { + width: 14, + } + } + }); + +const DetailsListPanel = ({ classes, open, closePanel, children }: IDetailsListPanel) => { + return ( + + + + + {children} + + ); +}; + +export default withStyles(styles)(DetailsListPanel); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx index f7a9f3a75..16658587e 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx @@ -91,6 +91,8 @@ import withSuspense from "../../../../Common/Components/withSuspense"; import { displayName } from "./utils"; import { DownloadIcon, PreviewIcon, ShareIcon } from "../../../../../../icons"; import UploadFilesButton from "../../UploadFilesButton"; +import DetailsListPanel from "./DetailsListPanel"; +import ObjectDetailPanel from "./ObjectDetailPanel"; const AddFolderIcon = React.lazy( () => import("../../../../../../icons/AddFolderIcon") @@ -126,6 +128,9 @@ const styles = (theme: Theme) => createStyles({ browsePaper: { height: "calc(100vh - 280px)", + "&.actionsPanelOpen": { + height: "100%", + }, }, "@global": { ".rowLine:hover .iconFileElm": { @@ -276,6 +281,10 @@ const ListObjects = ({ const [iniLoad, setIniLoad] = useState(false); const [canShareFile, setCanShareFile] = useState(false); const [canPreviewFile, setCanPreviewFile] = useState(false); + const [detailsOpen, setDetailsOpen] = useState(false); + const [selectedInternalPaths, setSelectedInternalPaths] = useState< + string | null + >(null); const internalPaths = get(match.params, "subpaths", ""); const bucketName = match.params["bucketName"]; @@ -676,11 +685,10 @@ const ListObjects = ({ }; const openPath = (idElement: string) => { - const newPath = `/buckets/${bucketName}/browse${ - idElement ? `/${encodeFileName(idElement)}` : `` - }`; - history.push(newPath); - return; + setDetailsOpen(true); + setSelectedInternalPaths( + `${idElement ? `${encodeFileName(idElement)}` : ``}` + ); }; const uploadObject = useCallback( @@ -1208,7 +1216,9 @@ const ListObjects = ({ entityName="Objects" idField="name" records={payload} - customPaperHeight={classes.browsePaper} + customPaperHeight={`${classes.browsePaper} ${ + detailsOpen ? "actionsPanelOpen" : "" + }`} selectedItems={selectedObjects} onSelect={selectListObjects} customEmptyMessage={`This location is empty${ @@ -1303,6 +1313,20 @@ const ListObjects = ({ }, ]} /> + { + setDetailsOpen(false); + setSelectedInternalPaths(null); + }} + > + {selectedInternalPaths !== null && ( + + )} + diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectActionButton.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectActionButton.tsx new file mode 100644 index 000000000..71dab66a4 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectActionButton.tsx @@ -0,0 +1,72 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 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 . + +import React from "react"; +import { Button } from "@mui/material"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; + +type ObjectActionButtonProps = { + disabled?: boolean; + onClick: () => void | any; + icon: React.ReactNode; + label: string; + [x: string]: any; +}; + +const styles = (theme: Theme) => + createStyles({ + root: { + padding: "0 15px", + height: 22, + margin: 0, + color: "#5E5E5E", + fontWeight: "normal", + fontSize: 14, + whiteSpace: "nowrap", + width: "100%", + justifyContent: "flex-start", + "&:hover": { + backgroundColor: "transparent", + color: "#000", + }, + "& .min-icon": { + width: 11, + }, + "&:disabled": { + color: "#EBEBEB", + borderColor: "#EBEBEB", + }, + }, + }); + +const ObjectActionButton = ({ + disabled, + onClick, + icon, + label, + classes, + ...restProps +}: ObjectActionButtonProps) => { + return ( + + ); +}; + +export default withStyles(styles)(ObjectActionButton); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectDetailPanel.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectDetailPanel.tsx new file mode 100644 index 000000000..6d621e8a5 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectDetailPanel.tsx @@ -0,0 +1,659 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 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 . + +import React, { Fragment, useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { Box, LinearProgress } from "@mui/material"; +import { withStyles } from "@mui/styles"; +import createStyles from "@mui/styles/createStyles"; +import get from "lodash/get"; +import Grid from "@mui/material/Grid"; +import { + actionsTray, + buttonsStyles, + spacingUtils, + textStyleUtils, + detailsPanel, +} from "../../../../Common/FormComponents/common/styleLibrary"; +import { IFileInfo } from "../ObjectDetails/types"; +import { download } from "../utils"; +import { ErrorResponseHandler } from "../../../../../../common/types"; +import { + setErrorSnackMessage, + setSnackBarMessage, +} from "../../../../../../actions"; +import { + decodeFileName, + encodeFileName, + niceBytesInt, +} from "../../../../../../common/utils"; +import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions"; +import { + completeObject, + setNewObject, + updateProgress, +} from "../../../../ObjectBrowser/actions"; +import { AppState } from "../../../../../../store"; +import { + DisabledIcon, + NextArrowIcon, + PreviewIcon, +} from "../../../../../../icons"; +import { ShareIcon, DownloadIcon, DeleteIcon } from "../../../../../../icons"; +import history from "../../../../../../history"; +import api from "../../../../../../common/api"; +import ShareFile from "../ObjectDetails/ShareFile"; +import SetRetention from "../ObjectDetails/SetRetention"; +import DeleteObject from "../ListObjects/DeleteObject"; +import AddTagModal from "../ObjectDetails/AddTagModal"; +import DeleteTagModal from "../ObjectDetails/DeleteTagModal"; +import SetLegalHoldModal from "../ObjectDetails/SetLegalHoldModal"; +import RestoreFileVersion from "../ObjectDetails/RestoreFileVersion"; +import SecureComponent from "../../../../../../common/SecureComponent/SecureComponent"; +import ObjectTags from "../ObjectDetails/ObjectTags"; +import LabelWithIcon from "../../../BucketDetails/SummaryItems/LabelWithIcon"; +import PreviewFileModal from "../Preview/PreviewFileModal"; +import ObjectActionButton from "./ObjectActionButton"; +import ObjectMetaData from "../ObjectDetails/ObjectMetaData"; +import EditablePropertyItem from "../../../BucketDetails/SummaryItems/EditablePropertyItem"; +import LabelValuePair from "../../../../Common/UsageBarWrapper/LabelValuePair"; + +const styles = () => + createStyles({ + tag: { + marginRight: 6, + fontSize: 10, + fontWeight: 700, + "&.MuiChip-sizeSmall": { + height: 18, + }, + "& .min-icon": { + height: 10, + width: 10, + }, + }, + "@global": { + ".progressDetails": { + paddingTop: 3, + display: "inline-block", + position: "relative", + width: 18, + height: 18, + }, + ".progressDetails > .MuiCircularProgress-root": { + position: "absolute", + left: 0, + top: 3, + }, + }, + ...buttonsStyles, + ...actionsTray, + ...spacingUtils, + ...textStyleUtils, + ...detailsPanel, + }); + +interface IObjectDetailPanelProps { + classes: any; + internalPaths: string; + bucketName: string; + rewindEnabled: boolean; + rewindDate: any; + bucketToRewind: string; + distributedSetup: boolean; + setErrorSnackMessage: typeof setErrorSnackMessage; + setSnackBarMessage: typeof setSnackBarMessage; + setNewObject: typeof setNewObject; + updateProgress: typeof updateProgress; + completeObject: typeof completeObject; +} + +const emptyFile: IFileInfo = { + is_latest: true, + last_modified: "", + legal_hold_status: "", + name: "", + retention_mode: "", + retention_until_date: "", + size: "0", + tags: {}, + version_id: null, +}; + +const ObjectDetailPanel = ({ + classes, + internalPaths, + bucketName, + distributedSetup, + setErrorSnackMessage, + setNewObject, + updateProgress, + completeObject, +}: IObjectDetailPanelProps) => { + const [loadObjectData, setLoadObjectData] = useState(true); + const [shareFileModalOpen, setShareFileModalOpen] = useState(false); + const [retentionModalOpen, setRetentionModalOpen] = useState(false); + const [tagModalOpen, setTagModalOpen] = useState(false); + const [deleteTagModalOpen, setDeleteTagModalOpen] = useState(false); + const [selectedTag, setSelectedTag] = useState(["", ""]); + const [legalholdOpen, setLegalholdOpen] = useState(false); + const [actualInfo, setActualInfo] = useState(null); + const [objectToShare, setObjectToShare] = useState(null); + const [versions, setVersions] = useState([]); + const [deleteOpen, setDeleteOpen] = useState(false); + const [restoreVersionOpen, setRestoreVersionOpen] = useState(false); + const [previewOpen, setPreviewOpen] = useState(false); + const [restoreVersion, setRestoreVersion] = useState(""); + const [totalVersionsSize, setTotalVersionsSize] = useState(0); + + const internalPathsDecoded = decodeFileName(internalPaths) || ""; + const allPathData = internalPathsDecoded.split("/"); + const currentItem = allPathData.pop() || ""; + + // calculate object name to display + let objectNameArray: string[] = []; + if (actualInfo) { + objectNameArray = actualInfo.name.split("/"); + } + + useEffect(() => { + if (bucketName !== "" && internalPaths) { + setLoadObjectData(true); + } + }, [internalPaths, bucketName]); + + useEffect(() => { + if (loadObjectData && internalPaths !== "") { + api + .invoke( + "GET", + `/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}${ + distributedSetup ? "&with_versions=true" : "" + }` + ) + .then((res: IFileInfo[]) => { + const result = get(res, "objects", []); + if (distributedSetup) { + setActualInfo( + result.find((el: IFileInfo) => el.is_latest) || emptyFile + ); + setVersions(result); + const tVersionSize = result.reduce( + (acc: number, currValue: IFileInfo) => { + if (currValue?.size) { + return acc + currValue.size; + } + return acc; + }, + 0 + ); + + setTotalVersionsSize(tVersionSize); + } else { + setActualInfo(result[0]); + setVersions([]); + } + + setLoadObjectData(false); + }) + .catch((error: ErrorResponseHandler) => { + setErrorSnackMessage(error); + setLoadObjectData(false); + }); + } + }, [ + loadObjectData, + bucketName, + internalPaths, + setErrorSnackMessage, + distributedSetup, + ]); + + let tagKeys: string[] = []; + + if (actualInfo && actualInfo.tags) { + tagKeys = Object.keys(actualInfo.tags); + } + + const openRetentionModal = () => { + setRetentionModalOpen(true); + }; + + const closeRetentionModal = (updateInfo: boolean) => { + setRetentionModalOpen(false); + if (updateInfo) { + setLoadObjectData(true); + } + }; + + const shareObject = () => { + setShareFileModalOpen(true); + }; + + const closeShareModal = () => { + setObjectToShare(null); + setShareFileModalOpen(false); + }; + + const deleteTag = (tagKey: string, tagLabel: string) => { + setSelectedTag([tagKey, tagLabel]); + setDeleteTagModalOpen(true); + }; + + const downloadObject = (object: IFileInfo) => { + const identityDownload = encodeFileName( + `${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}` + ); + + setNewObject({ + bucketName, + done: false, + instanceID: identityDownload, + percentage: 0, + prefix: object.name, + type: "download", + waitingForFile: true, + }); + + download( + bucketName, + internalPaths, + object.version_id, + parseInt(object.size || "0"), + (progress) => { + updateProgress(identityDownload, progress); + }, + () => { + completeObject(identityDownload); + } + ); + }; + + const closeDeleteModal = (redirectBack: boolean) => { + setDeleteOpen(false); + + if (redirectBack) { + const newPath = allPathData.join("/"); + history.push( + `/buckets/${bucketName}/browse${ + newPath === "" ? "" : `/${encodeFileName(newPath)}` + }` + ); + } + }; + + const closeAddTagModal = (reloadObjectData: boolean) => { + setTagModalOpen(false); + if (reloadObjectData) { + setLoadObjectData(true); + } + }; + + const closeLegalholdModal = (reload: boolean) => { + setLegalholdOpen(false); + if (reload) { + setLoadObjectData(true); + } + }; + + const closeDeleteTagModal = (reloadObjectData: boolean) => { + setDeleteTagModalOpen(false); + if (reloadObjectData) { + setLoadObjectData(true); + } + }; + + const closeRestoreModal = (reloadObjectData: boolean) => { + setRestoreVersionOpen(false); + setRestoreVersion(""); + + if (reloadObjectData) { + setLoadObjectData(true); + } + }; + + const closePreviewWindow = () => { + setPreviewOpen(false); + }; + + const openExtraInfo = () => { + const newPath = `/buckets/${bucketName}/browse${ + internalPaths !== "" ? `/${internalPaths}` : `` + }`; + + history.push(newPath); + }; + + if (!actualInfo) { + return null; + } + + return ( + + {shareFileModalOpen && actualInfo && ( + + )} + {retentionModalOpen && actualInfo && ( + + )} + {deleteOpen && ( + + )} + {tagModalOpen && actualInfo && ( + + )} + {deleteTagModalOpen && actualInfo && ( + + )} + {legalholdOpen && actualInfo && ( + + )} + {restoreVersionOpen && actualInfo && ( + + )} + {previewOpen && actualInfo && ( + + )} + + {!actualInfo && ( + + + + )} +
+ {objectNameArray.length > 0 + ? objectNameArray[objectNameArray.length - 1] + : actualInfo.name} +
+ +
    +
  • Object Actions:
  • +
  • + } + onClick={() => { + downloadObject(actualInfo); + }} + disabled={actualInfo.is_delete_marker} + /> +
  • +
  • + } + onClick={() => { + shareObject(); + }} + disabled={actualInfo.is_delete_marker} + /> +
  • +
  • + } + onClick={() => { + setPreviewOpen(true); + }} + disabled={actualInfo.is_delete_marker} + /> +
  • + +
  • + } + onClick={() => { + setDeleteOpen(true); + }} + disabled={actualInfo.is_delete_marker} + /> +
  • +
    +
  • + } + onClick={() => { + openExtraInfo(); + }} + /> +
  • +
+ +
+

Details

+
+ + { + setTagModalOpen(true); + }} + /> + } + /> + + + + { + setLegalholdOpen(true); + }} + isLoading={false} + /> + ) : ( + } + label={ + + } + /> + } + /> + ) + } + /> + + + + + ) : ( + } + label={ + + } + /> + } + /> + ) + } + /> + + +
+
+

Object Metadata

+
+ + {actualInfo ? ( + + ) : null} + +
+ + {actualInfo.version_id && actualInfo.version_id !== "null" && ( + +
+

Versions

+
+ + + Total available versions +
+ {versions.length} +
+ + Versions Stored size: +
+ {niceBytesInt(totalVersionsSize)} +
+
+
+ )} +
+ ); +}; + +const mapStateToProps = ({ objectBrowser, system }: AppState) => ({ + rewindEnabled: get(objectBrowser, "rewind.rewindEnabled", false), + rewindDate: get(objectBrowser, "rewind.dateToRewind", null), + bucketToRewind: get(objectBrowser, "rewind.bucketToRewind", ""), + distributedSetup: get(system, "distributedSetup", false), +}); + +const mapDispatchToProps = { + setErrorSnackMessage, + setSnackBarMessage, + setNewObject, + updateProgress, + completeObject, +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +export default connector(withStyles(styles)(ObjectDetailPanel)); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx index f5738995d..ef4f6fa8f 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx @@ -200,16 +200,12 @@ const twoColCssGridLayoutConfig = { const ObjectDetails = ({ classes, downloadingFiles, - rewindEnabled, - rewindDate, distributedSetup, - match, - bucketToRewind, setErrorSnackMessage, - setSnackBarMessage, setNewObject, updateProgress, completeObject, + match, }: IObjectDetailsProps) => { const [loadObjectData, setLoadObjectData] = useState(true); const [shareFileModalOpen, setShareFileModalOpen] = useState(false); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectMetaData.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectMetaData.tsx index c6a745bed..61deacf03 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectMetaData.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectMetaData.tsx @@ -1,15 +1,23 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState, Fragment } 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 { Box, 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 {detailsPanel, spacingUtils} from "../../../../Common/FormComponents/common/styleLibrary"; import { withStyles } from "@mui/styles"; +interface IObjectMetadata { + bucketName: string; + internalPaths: string; + classes?: any; + actualInfo: any; + linear?: boolean; +} + const styles = (theme: Theme) => createStyles({ propertiesIcon: { @@ -32,8 +40,8 @@ const styles = (theme: Theme) => titleItem: { width: "35%", }, - ...spacingUtils, + ...detailsPanel, }); const ObjectMetaData = ({ @@ -41,12 +49,8 @@ const ObjectMetaData = ({ internalPaths, classes, actualInfo, -}: { - bucketName: string; - internalPaths: string; - classes?: any; - actualInfo: any; -}) => { + linear = false, +}: IObjectMetadata) => { const [metaData, setMetaData] = useState({}); const onMetaDataSuccess = (res: MetadataResponse) => { @@ -74,6 +78,26 @@ const ObjectMetaData = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [actualInfo, loadMetaData]); + if (linear) { + return ( + + {metaKeys.map((element: string, index: number) => { + const renderItem = Array.isArray(metaData[element]) + ? metaData[element].map(decodeURIComponent).join(", ") + : decodeURIComponent(metaData[element]); + + return ( + + {element} +
+ {renderItem} +
+ ); + })} +
+ ); + } + return ( {
CircleIcon
+ +
+ ClosePanelIcon +

ClustersIcon diff --git a/portal-ui/src/screens/Console/Common/TableWrapper/TopActionButton.tsx b/portal-ui/src/screens/Console/Common/TableWrapper/TopActionButton.tsx index 7be53c810..44f1a5fde 100644 --- a/portal-ui/src/screens/Console/Common/TableWrapper/TopActionButton.tsx +++ b/portal-ui/src/screens/Console/Common/TableWrapper/TopActionButton.tsx @@ -30,6 +30,7 @@ const styles = (theme: Theme) => color: "#5E5E5E", fontWeight: "normal", fontSize: 14, + whiteSpace: "nowrap", borderRight: "#E5E5E5 1px solid", borderStyle: "solid", borderRadius: 0, @@ -105,26 +106,28 @@ const TopActionButton = ({ }: ITopActionButton) => { return ( - + }} + > + {children} + + ); };