Inspect Object (#1663)

This commit is contained in:
adfost
2022-03-07 19:52:36 -08:00
committed by GitHub
parent 25562bd55b
commit dcf6a521a6
4 changed files with 297 additions and 43 deletions

View File

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

View File

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

View File

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

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