diff --git a/portal-ui/src/index.css b/portal-ui/src/index.css index ec7dfbb5d..de64cb8be 100644 --- a/portal-ui/src/index.css +++ b/portal-ui/src/index.css @@ -9,3 +9,15 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } + +/* Chrome, Safari, Edge, Opera */ +input.removeArrows::-webkit-outer-spin-button, +input.removeArrows::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Firefox */ +input.removeArrows[type=number] { + -moz-appearance: textfield; +} \ No newline at end of file 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 index 8fe6eff9b..ad1c85c05 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx @@ -32,8 +32,8 @@ import { AppState } from "../../../../../../store"; import { ErrorResponseHandler } from "../../../../../../common/types"; import api from "../../../../../../common/api"; import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper"; -import DateSelector from "../../../../Common/FormComponents/DateSelector/DateSelector"; import PredefinedList from "../../../../Common/FormComponents/PredefinedList/PredefinedList"; +import DaysSelector from "../../../../Common/FormComponents/DaysSelector/DaysSelector"; const styles = (theme: Theme) => createStyles({ @@ -72,6 +72,8 @@ const ShareFile = ({ const [selectedDate, setSelectedDate] = useState(""); const [dateValid, setDateValid] = useState(true); + const initialDate = new Date(); + const dateChanged = (newDate: string, isValid: boolean) => { setDateValid(isValid); if (isValid) { @@ -79,6 +81,7 @@ const ShareFile = ({ return; } setSelectedDate(""); + setShareURL(""); }; useEffect(() => { @@ -86,54 +89,33 @@ const ShareFile = ({ setIsLoadingFile(true); setShareURL(""); - const slDate = new Date(`${selectedDate}T23:59:59`); + const slDate = new Date(`${selectedDate}`); const currDate = new Date(); const diffDate = slDate.getTime() - currDate.getTime(); - const versID = distributedSetup ? dataObject.version_id : "null"; + if (diffDate > 0) { + const versID = distributedSetup ? dataObject.version_id : "null"; - if (diffDate < 0) { - setModalErrorSnackMessage({ - errorMessage: "Selected date must be greater than current time.", - detailedError: "", - }); - setShareURL(""); - setIsLoadingFile(false); - - return; + api + .invoke( + "GET", + `/api/v1/buckets/${bucketName}/objects/share?prefix=${ + dataObject.name + }&version_id=${versID || "null"}${ + selectedDate !== "" ? `&expires=${diffDate}ms` : "" + }` + ) + .then((res: string) => { + setShareURL(res); + setIsLoadingFile(false); + }) + .catch((error: ErrorResponseHandler) => { + setModalErrorSnackMessage(error); + setShareURL(""); + setIsLoadingFile(false); + }); } - - if (diffDate > 604800000) { - setModalErrorSnackMessage({ - errorMessage: "You can share a file only for less than 7 days.", - detailedError: "", - }); - setShareURL(""); - setIsLoadingFile(false); - - return; - } - - api - .invoke( - "GET", - `/api/v1/buckets/${bucketName}/objects/share?prefix=${ - dataObject.name - }&version_id=${versID}${ - selectedDate !== "" ? `&expires=${diffDate}ms` : "" - }` - ) - .then((res: string) => { - setShareURL(res); - setIsLoadingFile(false); - }) - .catch((error: ErrorResponseHandler) => { - setModalErrorSnackMessage(error); - setShareURL(""); - setIsLoadingFile(false); - }); - return; } }, [ dataObject, @@ -155,13 +137,20 @@ const ShareFile = ({ }} > + + This module generates a temporary URL with integrated access + credentials for sharing objects for up to 7 days. +
+ The temporary URL expires after the configured time limit. +
- diff --git a/portal-ui/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx b/portal-ui/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx new file mode 100644 index 000000000..a5b5429df --- /dev/null +++ b/portal-ui/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx @@ -0,0 +1,240 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 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, useEffect, Fragment } from "react"; +import Grid from "@material-ui/core/Grid"; +import InputLabel from "@material-ui/core/InputLabel"; +import moment from "moment/moment"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { fieldBasic, tooltipHelper } from "../common/styleLibrary"; +import InputBoxWrapper from "../InputBoxWrapper/InputBoxWrapper"; + +interface IDaysSelector { + classes: any; + id: string; + initialDate: Date; + maxDays?: number; + label: string; + entity: string; + onChange: (newDate: string, isValid: boolean) => void; +} + +const styles = (theme: Theme) => + createStyles({ + dateInput: { + "&:not(:last-child)": { + marginRight: 22, + }, + }, + ...fieldBasic, + ...tooltipHelper, + labelContainer: { + display: "flex", + alignItems: "center", + }, + fieldContainer: { + ...fieldBasic.fieldContainer, + display: "flex", + alignItems: "center", + justifyContent: "space-between", + paddingBottom: 10, + marginTop: 11, + marginBottom: 6, + }, + fieldContainerBorder: { + borderBottom: "#9c9c9c 1px solid", + marginBottom: 20, + }, + dateContainer: { + height: 20, + textAlign: "right", + color: "#848484", + }, + }); + +const calculateNewTime = ( + initialDate: Date, + days: number, + hours: number, + minutes: number +) => { + return moment(initialDate) + .add(days, "days") + .add(hours, "hours") + .add(minutes, "minutes"); +}; + +const DaysSelector = ({ + classes, + id, + initialDate, + label, + maxDays, + entity, + onChange, +}: IDaysSelector) => { + const [selectedDays, setSelectedDays] = useState(7); + const [selectedHours, setSelectedHours] = useState(0); + const [selectedMinutes, setSelectedMinutes] = useState(0); + const [validDate, setValidDate] = useState(true); + const [dateSelected, setDateSelected] = useState(moment()); + + useEffect(() => { + setDateSelected( + calculateNewTime( + initialDate, + selectedDays, + selectedHours, + selectedMinutes + ) + ); + }, [initialDate, selectedDays, selectedHours, selectedMinutes]); + + useEffect(() => { + if (validDate) { + onChange(dateSelected.format("YYYY-MM-DDTHH:mm:ss"), true); + } else { + onChange("0000-00-00", false); + } + }, [dateSelected, onChange, validDate]); + + // Basic validation for inputs + useEffect(() => { + let valid = true; + if ( + selectedDays < 0 || + (maxDays && selectedDays > maxDays) || + isNaN(selectedDays) + ) { + valid = false; + } + + if (selectedHours < 0 || selectedHours > 23 || isNaN(selectedHours)) { + valid = false; + } + + if (selectedMinutes < 0 || selectedMinutes > 59 || isNaN(selectedMinutes)) { + valid = false; + } + + if ( + maxDays && + selectedDays === maxDays && + (selectedHours !== 0 || selectedMinutes !== 0) + ) { + valid = false; + } + + setValidDate(valid); + }, [ + dateSelected, + maxDays, + onChange, + selectedDays, + selectedHours, + selectedMinutes, + ]); + + return ( + + + + + + {label} + + + + { + setSelectedDays(parseInt(e.target.value)); + }} + value={selectedDays.toString()} + extraInputProps={{ + style: { + textAlign: "center", + paddingRight: 5, + }, + className: "removeArrows", + }} + /> + + + { + setSelectedHours(parseInt(e.target.value)); + }} + value={selectedHours.toString()} + extraInputProps={{ + style: { + textAlign: "center", + paddingRight: 5, + }, + className: "removeArrows", + }} + /> + + + { + setSelectedMinutes(parseInt(e.target.value)); + }} + value={selectedMinutes.toString()} + extraInputProps={{ + style: { + textAlign: "center", + paddingRight: 5, + }, + className: "removeArrows", + }} + /> + + + + + + {validDate && ( + + {entity} will be available until:{" "} + {dateSelected.format("MM/DD/YYYY HH:mm:ss")} + + )} + + +
+
+ ); +}; + +export default withStyles(styles)(DaysSelector); diff --git a/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx b/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx index 34f8e0ab1..e09eb8efe 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx +++ b/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx @@ -23,6 +23,7 @@ import { Tooltip, } from "@material-ui/core"; import { OutlinedInputProps } from "@material-ui/core/OutlinedInput"; +import { InputProps as StandardInputProps } from "@material-ui/core/Input"; import { createStyles, makeStyles, @@ -56,6 +57,7 @@ interface InputBoxProps { max?: string; overlayIcon?: any; overlayAction?: () => void; + extraInputProps?: StandardInputProps['inputProps']; } const styles = (theme: Theme) => @@ -125,10 +127,11 @@ const InputBoxWrapper = ({ min, max, overlayIcon = null, + extraInputProps = {}, overlayAction, classes, }: InputBoxProps) => { - let inputProps: any = { "data-index": index }; + let inputProps: any = { "data-index": index, ...extraInputProps }; if (type === "number" && min) { inputProps["min"] = min; diff --git a/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts b/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts index 87842f3ac..5cdcc96d4 100644 --- a/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts +++ b/portal-ui/src/screens/Console/Common/FormComponents/common/styleLibrary.ts @@ -84,6 +84,11 @@ export const modalBasic = { height: 170, maxWidth: 840, }, + moduleDescription: { + color: "#848484", + fontSize: 12, + fontStyle: "italic" as string, + }, }; export const tooltipHelper = {