diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/InspectObject.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/InspectObject.tsx new file mode 100644 index 000000000..e0c481090 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/InspectObject.tsx @@ -0,0 +1,185 @@ +// 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, { useState } from "react"; +import { connect } from "react-redux"; +import withStyles from "@mui/styles/withStyles"; +import { setErrorSnackMessage } from "../../../../../../actions"; +import {decodeFileName, deleteCookie, getCookieValue, performDownload} from "../../../../../../common/utils"; +import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; +import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper"; +import {InspectMenuIcon} from "../../../../../../icons/SidebarMenus"; +import Button from "@mui/material/Button"; +import Grid from "@mui/material/Grid"; +import {Theme} from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import {formFieldStyles, modalStyleUtils, spacingUtils} from "../../../../Common/FormComponents/common/styleLibrary"; +import {PasswordKeyIcon} from "../../../../../../icons"; +import {Box, DialogContentText} from "@mui/material"; +import KeyRevealer from "../../../../Tools/KeyRevealer"; + +const styles = (theme: Theme) => + createStyles({ + ...formFieldStyles, + ...modalStyleUtils, + ...spacingUtils, + }); + +interface IInspectObjectProps { + classes: any; + closeInspectModalAndRefresh: (refresh: boolean) => void; + inspectOpen: boolean; + inspectPath: string; + volumeName: string; + setErrorSnackMessage: typeof setErrorSnackMessage; +} + +const InspectObject = ({ + classes, + closeInspectModalAndRefresh, + inspectOpen, + inspectPath, + volumeName, + setErrorSnackMessage, +}: IInspectObjectProps) => { + const onClose = () => closeInspectModalAndRefresh(false); + const [isEncrypt, setIsEncrypt] = useState(true); + const [decryptionKey, setDecryptionKey] = useState(""); + const [insFileName, setInsFileName] = useState(""); + + if (!inspectPath) { + return null; + } + const makeRequest = async (url: string) => { + return await fetch(url, { method: "GET" }); + }; + + const performInspect = async () => { + const file = encodeURIComponent(inspectPath+"/xl.meta"); + const volume = encodeURIComponent(volumeName); + + const urlOfInspectApi = `/api/v1/admin/inspect?volume=${volume}&file=${file}&encrypt=${isEncrypt}`; + + makeRequest(urlOfInspectApi) + .then(async (res) => { + if (!res.ok) { + const resErr: any = await res.json(); + + setErrorSnackMessage({ + errorMessage: resErr.message, + detailedError: resErr.code, + }); + } + const blob: Blob = await res.blob(); + + //@ts-ignore + const filename = res.headers.get("content-disposition").split('"')[1]; + const decryptKey = getCookieValue(filename) || ""; + + performDownload(blob, filename); + setInsFileName(filename); + if (decryptKey === "") { + onClose(); + return; + } + setDecryptionKey(decryptKey); + }) + .catch((err) => { + setErrorSnackMessage(err); + }); + }; + + const onCloseDecKeyModal = () => { + deleteCookie(insFileName); + onClose(); + setDecryptionKey(""); + }; + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault(); + }; + + return ( + + {!decryptionKey && } + title={`Inspect Object`} + onClose={onClose} + > +
) => { + onSubmit(e); + }} + > + Would you like to encrypt{" "} + {decodeFileName(inspectPath)}?
+ { + setIsEncrypt(!isEncrypt); + }} + description="" + /> + + + + +
} + {decryptionKey ? ( + } + > + + + This will be displayed only once. It cannot be recovered. +
+ Use secure medium to share this key. +
+ + + +
+
+ ) : null} +
+ ); +}; + +const mapDispatchToProps = { + setErrorSnackMessage, +}; + +const connector = connect(null, mapDispatchToProps); + +export default withStyles(styles)(connector(InspectObject)); 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 index b02de775a..f3a75ac12 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectDetailPanel.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectDetailPanel.tsx @@ -59,6 +59,7 @@ import { TagsIcon, VersionsIcon, } from "../../../../../../icons"; +import { InspectMenuIcon } from "../../../../../../icons/SidebarMenus"; import { ShareIcon, DownloadIcon, DeleteIcon } from "../../../../../../icons"; import api from "../../../../../../common/api"; import ShareFile from "../ObjectDetails/ShareFile"; @@ -75,6 +76,7 @@ import ObjectMetaData from "../ObjectDetails/ObjectMetaData"; import ActionsListSection from "./ActionsListSection"; import { displayFileIconName } from "./utils"; import TagsModal from "../ObjectDetails/TagsModal"; +import InspectObject from "./InspectObject"; const styles = () => createStyles({ @@ -186,6 +188,7 @@ const ObjectDetailPanel = ({ const [retentionModalOpen, setRetentionModalOpen] = useState(false); const [tagModalOpen, setTagModalOpen] = useState(false); const [legalholdOpen, setLegalholdOpen] = useState(false); + const [inspectModalOpen, setInspectModalOpen] = useState(false); const [actualInfo, setActualInfo] = useState(null); const [allInfoElements, setAllInfoElements] = useState([]); const [objectToShare, setObjectToShare] = useState(null); @@ -344,6 +347,13 @@ const ObjectDetailPanel = ({ } }; + const closeInspectModal = (reloadObjectData: boolean) => { + setInspectModalOpen(false); + if (reloadObjectData) { + setLoadObjectData(true); + } + }; + const closeLegalholdModal = (reload: boolean) => { setLegalholdOpen(false); if (reload) { @@ -439,6 +449,18 @@ const ObjectDetailPanel = ({ icon: , tooltip: "Change Tags for this File", }, + { + action: () => { + setInspectModalOpen(true); + }, + label: "Inspect", + disabled: + !!actualInfo.is_delete_marker || + extensionPreview(currentItem) === "none" || + selectedVersion !== "", + icon: , + tooltip: "Inspect this file", + }, { action: () => { setVersionsModeEnabled(!versionsMode, objectName); @@ -448,6 +470,7 @@ const ObjectDetailPanel = ({ disabled: !(actualInfo.version_id && actualInfo.version_id !== "null"), tooltip: "Display Versions for this file", }, + ]; const calculateLastModifyTime = (lastModified: string) => { @@ -527,6 +550,14 @@ const ObjectDetailPanel = ({ onCloseAndUpdate={closeAddTagModal} /> )} + {inspectModalOpen && actualInfo && ( + + )} {!actualInfo && ( diff --git a/portal-ui/src/screens/Console/Tools/Inspect.tsx b/portal-ui/src/screens/Console/Tools/Inspect.tsx index f43697ef5..ab743e76a 100644 --- a/portal-ui/src/screens/Console/Tools/Inspect.tsx +++ b/portal-ui/src/screens/Console/Tools/Inspect.tsx @@ -1,10 +1,26 @@ +// 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 { Box, Button, DialogContentText } from "@mui/material"; import PageHeader from "../Common/PageHeader/PageHeader"; import PageLayout from "../Common/Layout/PageLayout"; import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; -import { CopyIcon, FileBookIcon, PasswordKeyIcon } from "../../../icons"; +import { FileBookIcon, PasswordKeyIcon } from "../../../icons"; import ModalWrapper from "../Common/ModalWrapper/ModalWrapper"; import { Theme } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; @@ -24,6 +40,7 @@ import { import DistributedOnly from "../Common/DistributedOnly/DistributedOnly"; import { AppState } from "../../../store"; import { InspectMenuIcon } from "../../../icons/SidebarMenus"; +import KeyRevealer from "./KeyRevealer"; const styles = (theme: Theme) => createStyles({ @@ -34,48 +51,6 @@ const styles = (theme: Theme) => ...modalStyleUtils, }); -const KeyRevealer = ({ value }: { value: string }) => { - const [shown, setShown] = React.useState(false); - - return ( - - {}} - value={value} - overlayIcon={} - extraInputProps={{ - readOnly: true, - }} - overlayAction={() => navigator.clipboard.writeText(value)} - /> - - - - ); -}; - const mapState = (state: AppState) => ({ distributedSetup: state.system.distributedSetup, }); diff --git a/portal-ui/src/screens/Console/Tools/KeyRevealer.tsx b/portal-ui/src/screens/Console/Tools/KeyRevealer.tsx new file mode 100644 index 000000000..21574374c --- /dev/null +++ b/portal-ui/src/screens/Console/Tools/KeyRevealer.tsx @@ -0,0 +1,63 @@ +// 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 { Box, Button } from "@mui/material"; +import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import { CopyIcon } from "../../../icons"; +import {useState} from "react"; +const KeyRevealer = ({ value }: { value: string }) => { + const [shown, setShown] = useState(false); + + return ( + + {}} + value={value} + overlayIcon={} + extraInputProps={{ + readOnly: true, + }} + overlayAction={() => navigator.clipboard.writeText(value)} + /> + + + + ); +}; + +export default KeyRevealer;