diff --git a/portal-ui/package-lock.json b/portal-ui/package-lock.json index 7503da2a5..05dcdaa80 100644 --- a/portal-ui/package-lock.json +++ b/portal-ui/package-lock.json @@ -1827,6 +1827,14 @@ "csstype": "^2.2.0" } }, + "@types/react-copy-to-clipboard": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.0.tgz", + "integrity": "sha512-iideNPRyroENqsOFh1i2Dv3zkviYS9r/9qD9Uh3Z9NNoAAqqa2x53i7iGndGNnJFIo20wIu7Hgh77tx1io8bgw==", + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "16.9.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", @@ -3943,6 +3951,14 @@ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, + "copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, "core-js": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", @@ -13932,6 +13948,15 @@ "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-7.1.0.tgz", "integrity": "sha512-Rel0QbPnCTjHxgZYt6TkGw4icSZXNyONHb72a+1wWA+PlYJIvzFAv4pZlDPG0rpKpKmy4kSUlkoWgneH7w3A0g==" }, + "react-copy-to-clipboard": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", + "integrity": "sha512-/2t5mLMMPuN5GmdXo6TebFa8IoFxZ+KTDDqYhcDm0PhkgEzSxVvIX26G20s1EB02A4h2UZgwtfymZ3lGJm0OLg==", + "requires": { + "copy-to-clipboard": "^3", + "prop-types": "^15.5.8" + } + }, "react-dev-utils": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", @@ -16325,6 +16350,11 @@ "repeat-string": "^1.6.1" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", diff --git a/portal-ui/package.json b/portal-ui/package.json index b068ab3c1..5626afcfd 100644 --- a/portal-ui/package.json +++ b/portal-ui/package.json @@ -13,6 +13,7 @@ "@types/lodash": "^4.14.149", "@types/node": "12.12.8", "@types/react": "16.9.11", + "@types/react-copy-to-clipboard": "^4.3.0", "@types/react-dom": "16.9.4", "@types/react-redux": "^7.1.5", "@types/react-router": "^5.1.3", @@ -35,6 +36,7 @@ "react-async-hook": "^3.6.1", "react-chartjs-2": "^2.9.0", "react-codemirror2": "^7.1.0", + "react-copy-to-clipboard": "^5.0.2", "react-dom": "^16.12.0", "react-hot-loader": "^4.13.0", "react-moment": "^0.9.7", diff --git a/portal-ui/src/icons/CopyIcon.tsx b/portal-ui/src/icons/CopyIcon.tsx new file mode 100644 index 000000000..adac587a6 --- /dev/null +++ b/portal-ui/src/icons/CopyIcon.tsx @@ -0,0 +1,39 @@ +// 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 . + +import React from "react"; +import { SvgIcon } from "@material-ui/core"; +class CopyIcon extends React.Component { + render() { + return ( + + + ic_h_copy-new_sl + + + + + + + + ); + } +} + +export default CopyIcon; diff --git a/portal-ui/src/icons/DeleteIcon.tsx b/portal-ui/src/icons/DeleteIcon.tsx index 3a6e172a9..3a913f39a 100644 --- a/portal-ui/src/icons/DeleteIcon.tsx +++ b/portal-ui/src/icons/DeleteIcon.tsx @@ -15,21 +15,20 @@ // along with this program. If not, see . import React from "react"; -import {SvgIcon} from "@material-ui/core"; +import { SvgIcon } from "@material-ui/core"; class DeleteIcon extends React.Component { - render() { - return ( - - ic_h_delete - - - - - - - ) - } + render() { + return ( + + + + + + ); + } } export default DeleteIcon; diff --git a/portal-ui/src/icons/DownloadIcon.tsx b/portal-ui/src/icons/DownloadIcon.tsx new file mode 100644 index 000000000..6e2822422 --- /dev/null +++ b/portal-ui/src/icons/DownloadIcon.tsx @@ -0,0 +1,33 @@ +// 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 . + +import React from "react"; +import SvgIcon from "@material-ui/core/SvgIcon"; + +class DownloadIcon extends React.Component { + render() { + return ( + + + + + + + ); + } +} + +export default DownloadIcon; diff --git a/portal-ui/src/icons/ShareIcon.tsx b/portal-ui/src/icons/ShareIcon.tsx new file mode 100644 index 000000000..f1b245434 --- /dev/null +++ b/portal-ui/src/icons/ShareIcon.tsx @@ -0,0 +1,39 @@ +// 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 . + +import React from "react"; +import SvgIcon from "@material-ui/core/SvgIcon"; + +class ShareIcon extends React.Component { + render() { + return ( + + + + + + + ); + } +} + +export default ShareIcon; diff --git a/portal-ui/src/icons/index.ts b/portal-ui/src/icons/index.ts index a68cf3cca..68c54cbf5 100644 --- a/portal-ui/src/icons/index.ts +++ b/portal-ui/src/icons/index.ts @@ -15,6 +15,7 @@ // along with this program. If not, see . export { default as PermissionIcon } from "./PermissionIcon"; +export { default as CopyIcon } from "./CopyIcon"; export { default as CreateIcon } from "./CreateIcon"; export { default as DeleteIcon } from "./DeleteIcon"; export { default as ServiceAccountIcon } from "./ServiceAccountIcon"; diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket.tsx index 397a063a7..de393beeb 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/AddBucket.tsx @@ -221,7 +221,7 @@ const AddBucket = ({ addBucketVersioned(event.target.checked); }} label={"Versioning"} - indicatorLabel={"On"} + indicatorLabels={["On", "Off"]} /> @@ -234,7 +234,7 @@ const AddBucket = ({ addBucketQuota(event.target.checked); }} label={"Enable Bucket Quota"} - indicatorLabel={"On"} + indicatorLabels={["On", "Off"]} /> {enableQuota && ( 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 00111a087..771590991 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 @@ -129,7 +129,7 @@ const styles = (theme: Theme) => interface IListObjectsProps { classes: any; match: any; - addRoute: (param1: string, param2: string) => any; + addRoute: (param1: string, param2: string, param3: string) => any; setAllRoutes: (path: string) => any; routesList: Route[]; } @@ -319,13 +319,16 @@ const ListObjects = ({ const lastIndex = idElementClean.length - 1; const newPath = `${currentPath}/${idElementClean[lastIndex]}`; - addRoute(newPath, idElementClean[lastIndex]); + addRoute(newPath, idElementClean[lastIndex], "path"); return; } - // Element is a file. we open details here - // TODO: Add details open function here. - //console.log("object", idElementClean); + const pathInArray = idElement.split("/"); + const fileName = pathInArray[pathInArray.length - 1]; + const newPath = `${currentPath}/${fileName}`; + + addRoute(newPath, fileName, "file"); + return; }; const uploadObject = (e: any): void => { diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectRouting.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectRouting.tsx new file mode 100644 index 000000000..116556515 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectRouting.tsx @@ -0,0 +1,49 @@ +import React, { useEffect, useState } from "react"; +import ListObjects from "./ListObjects"; +import ObjectDetails from "../ObjectDetails/ObjectDetails"; +import get from "lodash/get"; +import { addRoute, setAllRoutes } from "../../../../ObjectBrowser/actions"; +import { connect } from "react-redux"; +import { withRouter } from "react-router-dom"; +import { ObjectBrowserState, Route } from "../../../../ObjectBrowser/reducers"; + +interface ObjectBrowserReducer { + objectBrowser: ObjectBrowserState; +} + +interface ObjectRoutingProps { + routesList: Route[]; + match: any; +} + +const ObjectRouting = ({ routesList, match }: ObjectRoutingProps) => { + const currentItem = routesList[routesList.length - 1]; + + useEffect(() => { + const url = get(match, "url", "/object-browser"); + + if (url !== routesList[routesList.length - 1].route) { + setAllRoutes(url); + } + }, [match, routesList, setAllRoutes]); + + return currentItem.type === "path" ? ( + + ) : ( + + ); +}; + +const mapStateToProps = ({ objectBrowser }: ObjectBrowserReducer) => ({ + routesList: get(objectBrowser, "routesList", []), +}); + +const mapDispatchToProps = (dispatch: any) => { + return { + dispatchSetAllRoutes: (route: string) => dispatch(setAllRoutes(route)), + }; +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +export default withRouter(connector(ObjectRouting)); 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 new file mode 100644 index 000000000..855c0413a --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx @@ -0,0 +1,407 @@ +import React, { useState, useEffect } from "react"; +import { withRouter } from "react-router-dom"; +import * as reactMoment from "react-moment"; +import clsx from "clsx"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import Grid from "@material-ui/core/Grid"; +import Chip from "@material-ui/core/Chip"; +import TextField from "@material-ui/core/TextField"; +import IconButton from "@material-ui/core/IconButton"; +import InputAdornment from "@material-ui/core/InputAdornment"; +import SearchIcon from "@material-ui/icons/Search"; +import AddIcon from "@material-ui/icons/Add"; +import CloseIcon from "@material-ui/icons/Close"; +import ShareFile from "./ShareFile"; +import { + actionsTray, + containerForHeader, + searchField, +} from "../../../../Common/FormComponents/common/styleLibrary"; +import PageHeader from "../../../../Common/PageHeader/PageHeader"; +import ShareIcon from "../../../../../../icons/ShareIcon"; +import DownloadIcon from "../../../../../../icons/DownloadIcon"; +import DeleteIcon from "../../../../../../icons/DeleteIcon"; +import TableWrapper from "../../../../Common/TableWrapper/TableWrapper"; +import PencilIcon from "../../../../Common/TableWrapper/TableActionIcons/PencilIcon"; +import SetRetention from "./SetRetention"; +import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs"; +import { Button, Input } from "@material-ui/core"; +import { CreateIcon } from "../../../../../../icons"; +import UploadFile from "../../../../../../icons/UploadFile"; +import { connect } from "react-redux"; +import api from "../../../../../../common/api"; +import { ObjectBrowserState, Route } from "../../../../ObjectBrowser/reducers"; +import get from "lodash/get"; + +const styles = (theme: Theme) => + createStyles({ + objectNameContainer: { + marginBottom: 8, + }, + objectPathContainer: { + marginBottom: 26, + fontSize: 10, + }, + objectPathLink: { + "&:visited": { + color: "#000", + }, + }, + objectName: { + fontSize: 24, + }, + propertiesContainer: { + display: "flex", + flexDirection: "row", + marginBottom: 15, + }, + propertiesItem: { + display: "flex", + flexDirection: "row", + marginRight: 21, + }, + propertiesItemBold: { + fontWeight: 700, + }, + propertiesValue: { + marginLeft: 8, + textTransform: "capitalize", + }, + propertiesIcon: { + marginLeft: 5, + }, + actionsIconContainer: { + marginLeft: 12, + }, + actionsIcon: { + height: 16, + width: 16, + "& .MuiSvgIcon-root": { + height: 16, + }, + }, + tagsContainer: { + display: "flex", + flexDirection: "row", + alignItems: "center", + marginBottom: 15, + }, + tagText: { + marginRight: 13, + }, + tag: { + marginRight: 6, + fontSize: 10, + fontWeight: 700, + "&.MuiChip-sizeSmall": { + height: 18, + }, + "& .MuiSvgIcon-root": { + height: 10, + width: 10, + }, + }, + search: { + marginBottom: 8, + "&.MuiFormControl-root": { + marginRight: 0, + }, + }, + ...actionsTray, + ...searchField, + ...containerForHeader(theme.spacing(4)), + }); + +interface IObjectDetailsProps { + classes: any; + routesList: Route[]; +} + +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: "", + legal_hold_status: "", + name: "", + retention_mode: "", + retention_until_date: "", + size: "0", + tags: {}, + version_id: "", +}; + +const ObjectDetails = ({ classes, routesList }: IObjectDetailsProps) => { + const [shareFileModalOpen, setShareFileModalOpen] = useState(false); + const [retentionModalOpen, setRetentionModalOpen] = useState(false); + const [actualInfo, setActualInfo] = useState(emptyFile); + const [versions, setVersions] = useState([]); + const [filterVersion, setFilterVersion] = useState(""); + const [error, setError] = useState(""); + + const currentItem = routesList[routesList.length - 1]; + const allPathData = currentItem.route.split("/"); + const objectName = allPathData[allPathData.length - 1]; + const bucketName = allPathData[2]; + const pathInBucket = allPathData.slice(3).join("/"); + + useEffect(() => { + api + .invoke( + "GET", + `/api/v1/buckets/${bucketName}/objects?prefix=${pathInBucket}&with_versions=true` + ) + .then((res: IFileInfo[]) => { + const result = get(res, "objects", []); + setActualInfo( + result.find((el: IFileInfo) => el.is_latest) || emptyFile + ); + setVersions(result.filter((el: IFileInfo) => !el.is_latest)); + }) + .catch((error) => { + setError(error); + }); + }, []); + + 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 = () => { + console.log("delete tag"); + }; + + const downloadObject = () => { + console.log("download object"); + }; + + const confirmDeleteObject = () => { + console.log("confirm delete object"); + }; + + const tableActions = [ + { type: "share", onClick: shareObject, sendOnlyId: true }, + { type: "download", onClick: downloadObject, sendOnlyId: true }, + ]; + + const filteredRecords = versions.filter((version) => + version.version_id.includes(filterVersion) + ); + + const displayParsedDate = (date: string) => { + return {date}; + }; + + return ( + + + {shareFileModalOpen && ( + + )} + {retentionModalOpen && ( + + )} + + + + + + + + + + + + Legal Hold: + + {actualInfo.legal_hold_status + ? actualInfo.legal_hold_status.toLowerCase() + : "Off"} + + + + { + console.log("open legal hold modal"); + }} + > + + + + + + + Retention: + + {actualInfo.retention_mode + ? actualInfo.retention_mode.toLowerCase() + : "Undefined"} + + + + { + openRetentionModal(); + }} + > + + + + + + File Actions: + + { + shareObject(); + }} + > + + + + + { + console.log("download/upload"); + }} + > + + + + + { + console.log("delete"); + }} + > + + + + + + + Tags: + {actualInfo.tags && + Object.keys(actualInfo.tags).map((itemKey, index) => { + const tag = get(actualInfo, `tags.${itemKey}`, ""); + if (tag !== "") { + return ( + } + onDelete={deleteTag} + /> + ); + } + return null; + })} + } + clickable + size="small" + label="Add tag" + color="primary" + variant="outlined" + /> + + + { + setFilterVersion(val.target.value); + }} + InputProps={{ + disableUnderline: true, + startAdornment: ( + + + + ), + }} + /> + + + + + + + + ); +}; + +export default withStyles(styles)(ObjectDetails); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/SetRetention.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/SetRetention.tsx new file mode 100644 index 000000000..b353dc714 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/SetRetention.tsx @@ -0,0 +1,138 @@ +import React, { useState, useRef } from "react"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import Grid from "@material-ui/core/Grid"; +import Button from "@material-ui/core/Button"; +import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary"; +import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper"; +import FormSwitchWrapper from "../../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; +import RadioGroupSelector from "../../../../Common/FormComponents/RadioGroupSelector/RadioGroupSelector"; +import DateSelector from "../../../../Common/FormComponents/DateSelector/DateSelector"; + +const styles = (theme: Theme) => + createStyles({ + objectName: { + fontSize: 18, + fontWeight: 700, + marginBottom: 40, + }, + buttonContainer: { + textAlign: "right", + }, + ...modalBasic, + }); + +interface ISetRetentionProps { + classes: any; + open: boolean; + closeModalAndRefresh: () => void; + objectName: string; +} + +interface IRefObject { + resetDate: () => void; +} + +const SetRetention = ({ + classes, + open, + closeModalAndRefresh, + objectName, +}: ISetRetentionProps) => { + const [statusEnabled, setStatusEnabled] = useState(false); + const [type, setType] = useState(""); + + const dateElement = useRef(null); + + const dateFieldDisabled = () => { + return !(statusEnabled && (type === "governance" || type === "compliance")); + }; + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault(); + }; + + const resetForm = () => { + setStatusEnabled(false); + setType(""); + if (dateElement.current) { + dateElement.current.resetDate(); + } + }; + + return ( + { + resetForm(); + closeModalAndRefresh(); + }} + > + + {objectName} + + ) => { + onSubmit(e); + }} + > + + ) => { + setStatusEnabled(!statusEnabled); + setType(""); + }} + label={"Status"} + indicatorLabels={["Enabled", "Disabled"]} + /> + + + { + setType(e.target.value); + }} + selectorOptions={[ + { label: "Governance", value: "governance" }, + { label: "Compliance", value: "compliance" }, + ]} + /> + + + + + + + Reset + + + Save + + + + + ); +}; + +export default withStyles(styles)(SetRetention); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx new file mode 100644 index 000000000..7f70b02a4 --- /dev/null +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx @@ -0,0 +1,78 @@ +import React, { useState } from "react"; +import CopyToClipboard from "react-copy-to-clipboard"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import Grid from "@material-ui/core/Grid"; +import Button from "@material-ui/core/Button"; +import { + modalBasic, + predefinedList, +} from "../../../../Common/FormComponents/common/styleLibrary"; +import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper"; +import DateSelector from "../../../../Common/FormComponents/DateSelector/DateSelector"; +import { CopyIcon } from "../../../../../../icons"; + +const styles = (theme: Theme) => + createStyles({ + copyButtonContainer: { + paddingLeft: 16, + }, + modalContent: { + paddingBottom: 53, + }, + ...modalBasic, + ...predefinedList, + }); + +interface IShareFileProps { + classes: any; + open: boolean; + closeModalAndRefresh: () => void; +} + +const ShareFile = ({ + classes, + open, + closeModalAndRefresh, +}: IShareFileProps) => { + return ( + { + closeModalAndRefresh(); + }} + > + + + + + + + {"https://somelink.will/go/here"} + + + + } + onClick={() => { + console.log("copied!"); + }} + > + Copy + + + + + + + ); +}; + +export default withStyles(styles)(ShareFile); diff --git a/portal-ui/src/screens/Console/Common/FormComponents/DateSelector/DateSelector.tsx b/portal-ui/src/screens/Console/Common/FormComponents/DateSelector/DateSelector.tsx new file mode 100644 index 000000000..77d0cb9bd --- /dev/null +++ b/portal-ui/src/screens/Console/Common/FormComponents/DateSelector/DateSelector.tsx @@ -0,0 +1,245 @@ +import React, { useState, forwardRef, useImperativeHandle } from "react"; +import clsx from "clsx"; +import Grid from "@material-ui/core/Grid"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import InputLabel from "@material-ui/core/InputLabel"; +import Tooltip from "@material-ui/core/Tooltip"; +import HelpIcon from "@material-ui/icons/Help"; +import FormControl from "@material-ui/core/FormControl"; +import Select from "@material-ui/core/Select"; +import MenuItem from "@material-ui/core/MenuItem"; +import InputBase from "@material-ui/core/InputBase"; +import { fieldBasic, tooltipHelper } from "../common/styleLibrary"; +import FormSwitchWrapper from "../FormSwitchWrapper/FormSwitchWrapper"; + +const styles = (theme: Theme) => + createStyles({ + dateInput: { + "&:not(:last-child)": { + marginRight: 22, + }, + }, + ...fieldBasic, + ...tooltipHelper, + labelContainer: { + flex: 1, + }, + fieldContainer: { + ...fieldBasic.fieldContainer, + display: "flex", + alignItems: "center", + justifyContent: "space-between", + paddingBottom: 10, + marginTop: 11, + marginBottom: 6, + }, + fieldContainerBorder: { + borderBottom: "#9c9c9c 1px solid", + marginBottom: 20, + }, + }); + +const SelectStyled = withStyles((theme: Theme) => + createStyles({ + root: { + "& .MuiSelect-icon": { + color: "#000", + "&.Mui-disabled": { + color: "#9c9c9c", + }, + }, + }, + input: { + borderBottom: 0, + fontSize: 12, + }, + }) +)(InputBase); + +interface IDateSelectorProps { + classes: any; + id: string; + label: string; + disableOptions?: boolean; + addSwitch?: boolean; + tooltip?: string; + borderBottom?: boolean; +} + +const DateSelector = forwardRef( + ( + { + classes, + id, + label, + disableOptions = false, + addSwitch = false, + tooltip = "", + borderBottom = false, + }: IDateSelectorProps, + ref: any + ) => { + useImperativeHandle(ref, () => ({ resetDate })); + + const [dateEnabled, setDateEnabled] = useState(false); + const [month, setMonth] = useState(""); + const [day, setDay] = useState(""); + const [year, setYear] = useState(""); + + const resetDate = () => { + setMonth(""); + setDay(""); + setYear(""); + }; + + const isDateDisabled = () => { + if (disableOptions) { + return disableOptions; + } else if (addSwitch) { + return !dateEnabled; + } else { + return false; + } + }; + + const onMonthChange = ( + e: React.ChangeEvent<{ name?: string | undefined; value: unknown }> + ) => { + setMonth(e.target.value as string); + }; + + const onDayChange = ( + e: React.ChangeEvent<{ name?: string | undefined; value: unknown }> + ) => { + setDay(e.target.value as string); + }; + + const onYearChange = ( + e: React.ChangeEvent<{ name?: string | undefined; value: unknown }> + ) => { + setYear(e.target.value as string); + }; + + return ( + + + + + {label} + {tooltip !== "" && ( + + + + + + )} + + {addSwitch && ( + { + setDateEnabled(e.target.checked); + }} + switchOnly + /> + )} + + + + + } + > + + {""} + + January + {/* {options.map((option) => ( + + {option.label} + + ))} */} + + + + } + > + + {""} + + 1 + 2 + {/* {options.map((option) => ( + + {option.label} + + ))} */} + + + + } + > + + {""} + + 2020 + 2021 + {/* {options.map((option) => ( + + {option.label} + + ))} */} + + + + + ); + } +); + +export default withStyles(styles)(DateSelector); diff --git a/portal-ui/src/screens/Console/Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper.tsx b/portal-ui/src/screens/Console/Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper.tsx index 937e509fc..f8c228923 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper.tsx +++ b/portal-ui/src/screens/Console/Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper.tsx @@ -31,7 +31,7 @@ interface IFormSwitch { disabled?: boolean; tooltip?: string; index?: number; - indicatorLabel?: string; + indicatorLabels?: string[]; checked: boolean; switchOnly?: boolean; } @@ -142,14 +142,21 @@ const StyledSwitch = withStyles({ opacity: 1, height: 15, }, + "&:hover": { + backgroundColor: "#fff", + }, }, checked: {}, track: { height: 15, - backgroundColor: "#081C42", + backgroundColor: "#9C9C9C", + border: "#081C42 1px solid", opacity: 1, padding: 0, marginTop: 1.5, + "&$checked": { + backgroundColor: "#081C42", + }, }, thumb: { backgroundColor: "#fff", @@ -172,7 +179,7 @@ const FormSwitchWrapper = ({ disabled = false, switchOnly = false, tooltip = "", - indicatorLabel = "", + indicatorLabels = [], classes, }: IFormSwitch) => { const switchComponent = ( @@ -190,8 +197,10 @@ const FormSwitchWrapper = ({ disableTouchRipple value={value} /> - {indicatorLabel !== "" && ( - {indicatorLabel} + {indicatorLabels.length === 2 && ( + + {checked ? indicatorLabels[0] : indicatorLabels[1]} + )} diff --git a/portal-ui/src/screens/Console/Common/TableWrapper/TableActionButton.tsx b/portal-ui/src/screens/Console/Common/TableWrapper/TableActionButton.tsx index 9b59a4e51..11164cceb 100644 --- a/portal-ui/src/screens/Console/Common/TableWrapper/TableActionButton.tsx +++ b/portal-ui/src/screens/Console/Common/TableWrapper/TableActionButton.tsx @@ -18,12 +18,12 @@ import isString from "lodash/isString"; import { IconButton } from "@material-ui/core"; import ViewIcon from "./TableActionIcons/ViewIcon"; import PencilIcon from "./TableActionIcons/PencilIcon"; +import ShareIcon from "./TableActionIcons/ShareIcon"; import DeleteIcon from "./TableActionIcons/DeleteIcon"; import DescriptionIcon from "./TableActionIcons/DescriptionIcon"; import CloudIcon from "./TableActionIcons/CloudIcon"; import ConsoleIcon from "./TableActionIcons/ConsoleIcon"; -import GetAppIcon from "@material-ui/icons/GetApp"; -import SvgIcon from "@material-ui/core/SvgIcon"; +import DownloadIcon from "./TableActionIcons/DownloadIcon"; import { Link } from "react-router-dom"; import { createStyles, withStyles } from "@material-ui/core/styles"; @@ -55,14 +55,14 @@ const defineIcon = (type: string, selected: boolean) => { return ; case "description": return ; + case "share": + return ; case "cloud": return ; case "console": return ; case "download": - return ( - - ); + return ; } return null; diff --git a/portal-ui/src/screens/Console/Common/TableWrapper/TableActionIcons/DeleteIcon.tsx b/portal-ui/src/screens/Console/Common/TableWrapper/TableActionIcons/DeleteIcon.tsx index 285c92c40..d3aab6496 100644 --- a/portal-ui/src/screens/Console/Common/TableWrapper/TableActionIcons/DeleteIcon.tsx +++ b/portal-ui/src/screens/Console/Common/TableWrapper/TableActionIcons/DeleteIcon.tsx @@ -9,17 +9,10 @@ const DeleteIcon = ({ active = false }: IIcon) => { height="16" viewBox="0 0 10.402 13" > - - - - + ); }; diff --git a/portal-ui/src/screens/Console/Common/TableWrapper/TableActionIcons/DownloadIcon.tsx b/portal-ui/src/screens/Console/Common/TableWrapper/TableActionIcons/DownloadIcon.tsx new file mode 100644 index 000000000..e05e10cfe --- /dev/null +++ b/portal-ui/src/screens/Console/Common/TableWrapper/TableActionIcons/DownloadIcon.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { IIcon, selected, unSelected } from "./common"; + +const DeleteIcon = ({ active = false }: IIcon) => { + return ( + + + + + ); +}; + +export default DeleteIcon; diff --git a/portal-ui/src/screens/Console/Common/TableWrapper/TableActionIcons/ShareIcon.tsx b/portal-ui/src/screens/Console/Common/TableWrapper/TableActionIcons/ShareIcon.tsx new file mode 100644 index 000000000..01417bdc9 --- /dev/null +++ b/portal-ui/src/screens/Console/Common/TableWrapper/TableActionIcons/ShareIcon.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { IIcon, selected, unSelected } from "./common"; + +const ShareIcon = ({ active = false }: IIcon) => { + return ( + + + + + ); +}; + +export default ShareIcon; diff --git a/portal-ui/src/screens/Console/Configurations/ConfTargetGeneric.tsx b/portal-ui/src/screens/Console/Configurations/ConfTargetGeneric.tsx index 99b422fa1..15b1b91ac 100644 --- a/portal-ui/src/screens/Console/Configurations/ConfTargetGeneric.tsx +++ b/portal-ui/src/screens/Console/Configurations/ConfTargetGeneric.tsx @@ -100,7 +100,7 @@ const ConfTargetGeneric = ({ return ( ) => { const value = e.target.checked ? "true" : "false"; setValueElement(field.name, value, item); diff --git a/portal-ui/src/screens/Console/Configurations/CustomForms/ConfMySql.tsx b/portal-ui/src/screens/Console/Configurations/CustomForms/ConfMySql.tsx index caf6bec01..175e024b3 100644 --- a/portal-ui/src/screens/Console/Configurations/CustomForms/ConfMySql.tsx +++ b/portal-ui/src/screens/Console/Configurations/CustomForms/ConfMySql.tsx @@ -143,7 +143,7 @@ const ConfMySql = ({ onChange, classes }: IConfMySqlProps) => { name="checkedB" onChange={switcherChangeEvt} value={"dnsString"} - indicatorLabel={"On"} + indicatorLabels={["On", "Off"]} /> {useDsnString ? ( diff --git a/portal-ui/src/screens/Console/Configurations/CustomForms/ConfPostgres.tsx b/portal-ui/src/screens/Console/Configurations/CustomForms/ConfPostgres.tsx index 28d3dab32..c77e1e648 100644 --- a/portal-ui/src/screens/Console/Configurations/CustomForms/ConfPostgres.tsx +++ b/portal-ui/src/screens/Console/Configurations/CustomForms/ConfPostgres.tsx @@ -216,7 +216,7 @@ const ConfPostgres = ({ onChange, classes }: IConfPostgresProps) => { setUseConnectionString(e.target.checked); }} value={"manualString"} - indicatorLabel={"On"} + indicatorLabels={["On", "Off"]} /> {useConnectionString ? ( diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index 2806ca66b..f0a658ded 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -49,6 +49,8 @@ import { ISessionResponse } from "./types"; import TenantDetails from "./Tenants/TenantDetails/TenantDetails"; import ObjectBrowser from "./ObjectBrowser/ObjectBrowser"; import ListObjects from "./Buckets/ListBuckets/Objects/ListObjects/ListObjects"; +import ObjectDetails from "./Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails"; +import ObjectRouting from "./Buckets/ListBuckets/Objects/ListObjects/ObjectRouting"; import License from "./License/License"; const drawerWidth = 245; @@ -212,11 +214,11 @@ const Console = ({ path: "/object-browser", }, { - component: ListObjects, + component: ObjectRouting, path: "/object-browser/:bucket", }, { - component: ListObjects, + component: ObjectRouting, path: "/object-browser/:bucket/*", }, { diff --git a/portal-ui/src/screens/Console/Groups/AddGroup.tsx b/portal-ui/src/screens/Console/Groups/AddGroup.tsx index 1ff36bda3..f7393d150 100644 --- a/portal-ui/src/screens/Console/Groups/AddGroup.tsx +++ b/portal-ui/src/screens/Console/Groups/AddGroup.tsx @@ -179,7 +179,7 @@ const AddGroup = ({ {selectedGroup !== null && ( interface IBrowseBucketsProps { classes: any; - addRoute: (path: string, label: string) => any; + addRoute: (path: string, label: string, type: string) => any; resetRoutesList: (doVar: boolean) => any; match: any; } @@ -187,7 +187,7 @@ const BrowseBuckets = ({ const currentPath = get(match, "url", "/object-browser"); const newPath = `${currentPath}/${idElement}`; - addRoute(newPath, idElement); + addRoute(newPath, idElement, "path"); }; const renderBucket = (bucketName: string) => { diff --git a/portal-ui/src/screens/Console/ObjectBrowser/actions.ts b/portal-ui/src/screens/Console/ObjectBrowser/actions.ts index 09319a465..a8c8a081d 100644 --- a/portal-ui/src/screens/Console/ObjectBrowser/actions.ts +++ b/portal-ui/src/screens/Console/ObjectBrowser/actions.ts @@ -26,6 +26,7 @@ interface AddRouteAction { type: typeof OBJECT_BROWSER_ADD_ROUTE; route: string; label: string; + routeType: string; } interface ResetRoutesList { @@ -55,16 +56,16 @@ export type ObjectBrowserActionTypes = | SetAllRoutes | CreateFolder; -export const addRoute = (route: string, label: string) => { +export const addRoute = (route: string, label: string, routeType: string) => { return { type: OBJECT_BROWSER_ADD_ROUTE, route, label, + routeType, }; }; export const resetRoutesList = (reset: boolean) => { - console.log("RESET"); return { type: OBJECT_BROWSER_RESET_ROUTES_LIST, reset, diff --git a/portal-ui/src/screens/Console/ObjectBrowser/reducers.ts b/portal-ui/src/screens/Console/ObjectBrowser/reducers.ts index 73b675e01..0757f8f4b 100644 --- a/portal-ui/src/screens/Console/ObjectBrowser/reducers.ts +++ b/portal-ui/src/screens/Console/ObjectBrowser/reducers.ts @@ -27,13 +27,16 @@ import { export interface Route { route: string; label: string; + type: string; } export interface ObjectBrowserState { routesList: Route[]; } -const initialRoute = [{ route: "/object-browser", label: "All Buckets" }]; +const initialRoute = [ + { route: "/object-browser", label: "All Buckets", type: "path" }, +]; const initialState: ObjectBrowserState = { routesList: initialRoute, @@ -47,7 +50,7 @@ export function objectBrowserReducer( case OBJECT_BROWSER_ADD_ROUTE: const newRouteList = [ ...state.routesList, - { route: action.route, label: action.label }, + { route: action.route, label: action.label, type: action.routeType }, ]; history.push(action.route); @@ -76,7 +79,12 @@ export function objectBrowserReducer( splitRoutes.forEach((route) => { if (route !== "" && route !== "object-browser") { initRoute = `${initRoute}/${route}`; - routesArray.push({ route: initRoute, label: route }); + + routesArray.push({ + route: initRoute, + label: route, + type: "path", + }); } }); @@ -97,7 +105,7 @@ export function objectBrowserReducer( if (folderTrim !== "") { lastRoute = `${lastRoute}/${folderTrim}`; - const newItem = { route: lastRoute, label: folderTrim }; + const newItem = { route: lastRoute, label: folderTrim, type: "path" }; newFoldersRoutes.push(newItem); } }); diff --git a/portal-ui/src/screens/Console/Users/AddUser.tsx b/portal-ui/src/screens/Console/Users/AddUser.tsx index 7bf46f06a..80f3cec5c 100644 --- a/portal-ui/src/screens/Console/Users/AddUser.tsx +++ b/portal-ui/src/screens/Console/Users/AddUser.tsx @@ -222,7 +222,7 @@ class AddUserContent extends React.Component< {selectedUser !== null && (