Inspect Object (#1663)
This commit is contained in:
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<boolean>(true);
|
||||
const [decryptionKey, setDecryptionKey] = useState<string>("");
|
||||
const [insFileName, setInsFileName] = useState<string>("");
|
||||
|
||||
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 (
|
||||
<React.Fragment>
|
||||
{!decryptionKey && <ModalWrapper
|
||||
modalOpen={inspectOpen}
|
||||
titleIcon={<InspectMenuIcon />}
|
||||
title={`Inspect Object`}
|
||||
onClose={onClose}
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
onSubmit(e);
|
||||
}}
|
||||
>
|
||||
Would you like to encrypt{" "}
|
||||
<b>{decodeFileName(inspectPath)}</b>? <br />
|
||||
<FormSwitchWrapper
|
||||
label={"Encrypt"}
|
||||
indicatorLabels={["Yes", "No"]}
|
||||
checked={isEncrypt}
|
||||
value={"encrypt"}
|
||||
id="encrypt"
|
||||
name="encrypt"
|
||||
onChange={(e) => {
|
||||
setIsEncrypt(!isEncrypt);
|
||||
}}
|
||||
description=""
|
||||
/>
|
||||
<Grid item xs={12} className={classes.modalButtonBar}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={performInspect}
|
||||
>
|
||||
Inspect
|
||||
</Button>
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>}
|
||||
{decryptionKey ? (
|
||||
<ModalWrapper
|
||||
modalOpen={inspectOpen}
|
||||
title="Inspect Decryption Key"
|
||||
onClose={onCloseDecKeyModal}
|
||||
titleIcon={<PasswordKeyIcon />}
|
||||
>
|
||||
<DialogContentText>
|
||||
<Box>
|
||||
This will be displayed only once. It cannot be recovered.
|
||||
<br />
|
||||
Use secure medium to share this key.
|
||||
</Box>
|
||||
<Box>
|
||||
<KeyRevealer value={decryptionKey} />
|
||||
</Box>
|
||||
</DialogContentText>
|
||||
</ModalWrapper>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setErrorSnackMessage,
|
||||
};
|
||||
|
||||
const connector = connect(null, mapDispatchToProps);
|
||||
|
||||
export default withStyles(styles)(connector(InspectObject));
|
||||
@@ -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<boolean>(false);
|
||||
const [tagModalOpen, setTagModalOpen] = useState<boolean>(false);
|
||||
const [legalholdOpen, setLegalholdOpen] = useState<boolean>(false);
|
||||
const [inspectModalOpen, setInspectModalOpen] = useState<boolean>(false);
|
||||
const [actualInfo, setActualInfo] = useState<IFileInfo | null>(null);
|
||||
const [allInfoElements, setAllInfoElements] = useState<IFileInfo[]>([]);
|
||||
const [objectToShare, setObjectToShare] = useState<IFileInfo | null>(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: <TagsIcon />,
|
||||
tooltip: "Change Tags for this File",
|
||||
},
|
||||
{
|
||||
action: () => {
|
||||
setInspectModalOpen(true);
|
||||
},
|
||||
label: "Inspect",
|
||||
disabled:
|
||||
!!actualInfo.is_delete_marker ||
|
||||
extensionPreview(currentItem) === "none" ||
|
||||
selectedVersion !== "",
|
||||
icon: <InspectMenuIcon />,
|
||||
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 && (
|
||||
<InspectObject
|
||||
inspectOpen={inspectModalOpen}
|
||||
volumeName={bucketName}
|
||||
inspectPath={actualInfo.name}
|
||||
closeInspectModalAndRefresh={closeInspectModal}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!actualInfo && (
|
||||
<Grid item xs={12}>
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexFlow: {
|
||||
sm: "row",
|
||||
xs: "column",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<InputBoxWrapper
|
||||
id="inspect-dec-key"
|
||||
name="inspect-dec-key"
|
||||
placeholder=""
|
||||
label=""
|
||||
type={shown ? "text" : "password"}
|
||||
onChange={() => {}}
|
||||
value={value}
|
||||
overlayIcon={<CopyIcon />}
|
||||
extraInputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
overlayAction={() => navigator.clipboard.writeText(value)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
marginLeft: "10px",
|
||||
}}
|
||||
variant="contained"
|
||||
onClick={() => setShown(!shown)}
|
||||
>
|
||||
Show/Hide
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
distributedSetup: state.system.distributedSetup,
|
||||
});
|
||||
|
||||
63
portal-ui/src/screens/Console/Tools/KeyRevealer.tsx
Normal file
63
portal-ui/src/screens/Console/Tools/KeyRevealer.tsx
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<boolean>(false);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexFlow: {
|
||||
sm: "row",
|
||||
xs: "column",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<InputBoxWrapper
|
||||
id="inspect-dec-key"
|
||||
name="inspect-dec-key"
|
||||
placeholder=""
|
||||
label=""
|
||||
type={shown ? "text" : "password"}
|
||||
onChange={() => {}}
|
||||
value={value}
|
||||
overlayIcon={<CopyIcon />}
|
||||
extraInputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
overlayAction={() => navigator.clipboard.writeText(value)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
marginLeft: "10px",
|
||||
}}
|
||||
variant="contained"
|
||||
onClick={() => setShown(!shown)}
|
||||
>
|
||||
Show/Hide
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeyRevealer;
|
||||
Reference in New Issue
Block a user