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 d56f8e4ab..f11087955 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 @@ -15,6 +15,7 @@ // along with this program. If not, see . import React, { useState, useEffect } from "react"; +import { connect } from "react-redux"; import get from "lodash/get"; import * as reactMoment from "react-moment"; import clsx from "clsx"; @@ -33,9 +34,11 @@ import { containerForHeader, searchField, } from "../../../../Common/FormComponents/common/styleLibrary"; -import history from "../../../../../../history"; +import { IFileInfo } from "./types"; +import { removeRouteLevel } from "../../../../ObjectBrowser/actions"; import { Route } from "../../../../ObjectBrowser/reducers"; import { download } from "../utils"; +import history from "../../../../../../history"; import api from "../../../../../../common/api"; import PageHeader from "../../../../Common/PageHeader/PageHeader"; import ShareIcon from "../../../../../../icons/ShareIcon"; @@ -46,10 +49,9 @@ import PencilIcon from "../../../../Common/TableWrapper/TableActionIcons/PencilI import SetRetention from "./SetRetention"; import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs"; import DeleteObject from "../ListObjects/DeleteObject"; -import { removeRouteLevel } from "../../../../ObjectBrowser/actions"; -import { connect } from "react-redux"; import AddTagModal from "./AddTagModal"; import DeleteTagModal from "./DeleteTagModal"; +import SetLegalHoldModal from "./SetLegalHoldModal"; const styles = (theme: Theme) => createStyles({ @@ -136,18 +138,6 @@ interface IObjectDetailsProps { removeRouteLevel: (newRoute: string) => any; } -interface IFileInfo { - is_latest?: boolean; - last_modified: string; - legal_hold_status?: string; - name: string; - retention_mode?: string; - retention_until_date?: string; - size?: string; - tags?: object; - version_id: string; -} - const emptyFile: IFileInfo = { is_latest: true, last_modified: "", @@ -171,6 +161,7 @@ const ObjectDetails = ({ const [tagModalOpen, setTagModalOpen] = useState(false); const [deleteTagModalOpen, setDeleteTagModalOpen] = useState(false); const [selectedTag, setSelectedTag] = useState<[string, string]>(["", ""]); + const [legalholdOpen, setLegalholdOpen] = useState(false); const [actualInfo, setActualInfo] = useState(emptyFile); const [versions, setVersions] = useState([]); const [filterVersion, setFilterVersion] = useState(""); @@ -213,22 +204,18 @@ const ObjectDetails = ({ const openRetentionModal = () => { setRetentionModalOpen(true); - console.log("open retention modal"); }; const closeRetentionModal = () => { setRetentionModalOpen(false); - console.log("close retention modal"); }; const shareObject = () => { setShareFileModalOpen(true); - console.log("share object"); }; const closeShareModal = () => { setShareFileModalOpen(false); - console.log("close share modal"); }; const deleteTag = (tagKey: string, tagLabel: string) => { @@ -272,6 +259,14 @@ const ObjectDetails = ({ } }; + const closeLegalholdModal = (reload: boolean) => { + setLegalholdOpen(false); + + if (reload) { + setLoadObjectData(true); + } + }; + const closeDeleteTagModal = (reloadObjectData: boolean) => { setDeleteTagModalOpen(false); @@ -325,6 +320,15 @@ const ObjectDetails = ({ selectedTag={selectedTag} /> )} + {legalholdOpen && ( + + )} @@ -350,7 +354,7 @@ const ObjectDetails = ({ size="small" className={classes.propertiesIcon} onClick={() => { - console.log("open legal hold modal"); + setLegalholdOpen(true); }} > diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/SetLegalHoldModal.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/SetLegalHoldModal.tsx new file mode 100644 index 000000000..612de79fc --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/SetLegalHoldModal.tsx @@ -0,0 +1,138 @@ +import React, { useState, useEffect } from "react"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import get from "lodash/get"; +import Grid from "@material-ui/core/Grid"; +import Button from "@material-ui/core/Button"; +import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary"; +import { IFileInfo } from "./types"; +import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper"; +import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; +import api from "../../../../../../common/api"; + +const styles = (theme: Theme) => + createStyles({ + objectName: { + fontSize: 18, + fontWeight: 700, + marginBottom: 40, + }, + buttonContainer: { + textAlign: "right", + }, + errorBlock: { + color: "red", + }, + ...modalBasic, + }); + +interface ISetRetentionProps { + classes: any; + open: boolean; + closeModalAndRefresh: (reload: boolean) => void; + objectName: string; + bucketName: string; + actualInfo: IFileInfo; +} + +const SetLegalHoldModal = ({ + classes, + open, + closeModalAndRefresh, + objectName, + bucketName, + actualInfo, +}: ISetRetentionProps) => { + const [legalHoldEnabled, setLegalHoldEnabled] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [error, setError] = useState(""); + const versionId = actualInfo.version_id; + + useEffect(() => { + const status = get(actualInfo, "legal_hold_status", "OFF"); + setLegalHoldEnabled(status === "ON"); + }, []); + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setIsSaving(true); + + api + .invoke( + "PUT", + `/api/v1/buckets/${bucketName}/objects/legalhold?prefix=${objectName}&version_id=${versionId}`, + { status: legalHoldEnabled ? "enabled" : "disabled" } + ) + .then((res) => { + setIsSaving(false); + closeModalAndRefresh(true); + }) + .catch((error) => { + setError(error); + setIsSaving(false); + }); + }; + + const resetForm = () => { + setLegalHoldEnabled(false); + }; + + return ( + { + resetForm(); + closeModalAndRefresh(false); + }} + > + {error !== "" && {error}} + + {objectName} + +
) => { + onSubmit(e); + }} + > + + ) => { + setLegalHoldEnabled(!legalHoldEnabled); + }} + label={"Legal Hold Status"} + indicatorLabels={["Enabled", "Disabled"]} + tooltip={ + "To enable this feature you need to enable versioning on the bucket before creation" + } + /> + + + + + +
+
+ ); +}; + +export default withStyles(styles)(SetLegalHoldModal); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/types.ts b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/types.ts new file mode 100644 index 000000000..e4f243f9c --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/types.ts @@ -0,0 +1,27 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2020 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 . + +export interface IFileInfo { + is_latest?: boolean; + last_modified: string; + legal_hold_status?: string; + name: string; + retention_mode?: string; + retention_until_date?: string; + size?: string; + tags?: object; + version_id: string; +}