Update Share Object UI to reflect max expiration time (#3098)

This commit is contained in:
Cesar N
2023-12-14 13:28:11 -08:00
committed by GitHub
parent 3c3b9546d9
commit 044e5702df
7 changed files with 135 additions and 20 deletions

View File

@@ -251,6 +251,36 @@ export const erasureCodeCalc = (
}; };
}; };
// 92400 seconds -> 1 day, 1 hour, 40 minutes.
export const niceTimeFromSeconds = (seconds: number): string => {
const days = Math.floor(seconds / (3600 * 24));
const hours = Math.floor((seconds % (3600 * 24)) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
const parts = [];
if (days > 0) {
parts.push(`${days} day${days !== 1 ? "s" : ""}`);
}
if (hours > 0) {
parts.push(`${hours} hour${hours !== 1 ? "s" : ""}`);
}
if (minutes > 0) {
parts.push(`${minutes} minute${minutes !== 1 ? "s" : ""}`);
}
if (remainingSeconds > 0) {
parts.push(
`${remainingSeconds} second${remainingSeconds !== 1 ? "s" : ""}`,
);
}
return parts.join(" and ");
};
// seconds / minutes /hours / Days / Years calculator // seconds / minutes /hours / Days / Years calculator
export const niceDays = (secondsValue: string, timeVariant: string = "s") => { export const niceDays = (secondsValue: string, timeVariant: string = "s") => {
let seconds = parseFloat(secondsValue); let seconds = parseFloat(secondsValue);
@@ -258,6 +288,7 @@ export const niceDays = (secondsValue: string, timeVariant: string = "s") => {
return niceDaysInt(seconds, timeVariant); return niceDaysInt(seconds, timeVariant);
}; };
// niceDaysInt returns the string in the max unit found e.g. 92400 seconds -> 1 day
export const niceDaysInt = (seconds: number, timeVariant: string = "s") => { export const niceDaysInt = (seconds: number, timeVariant: string = "s") => {
switch (timeVariant) { switch (timeVariant) {
case "ns": case "ns":

View File

@@ -31,7 +31,7 @@ const initialState: BucketDetailsState = {
}; };
export const bucketDetailsSlice = createSlice({ export const bucketDetailsSlice = createSlice({
name: "trace", name: "bucketDetails",
initialState, initialState,
reducers: { reducers: {
setBucketDetailsTab: (state, action: PayloadAction<string>) => { setBucketDetailsTab: (state, action: PayloadAction<string>) => {

View File

@@ -16,11 +16,22 @@
import React, { Fragment, useEffect, useState } from "react"; import React, { Fragment, useEffect, useState } from "react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { Button, CopyIcon, ReadBox, ShareIcon, Grid, ProgressBar } from "mds"; import {
Button,
CopyIcon,
ReadBox,
ShareIcon,
Grid,
ProgressBar,
Tooltip,
} from "mds";
import CopyToClipboard from "react-copy-to-clipboard"; import CopyToClipboard from "react-copy-to-clipboard";
import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper"; import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
import DaysSelector from "../../../../Common/FormComponents/DaysSelector/DaysSelector"; import DaysSelector from "../../../../Common/FormComponents/DaysSelector/DaysSelector";
import { encodeURLString } from "../../../../../../common/utils"; import {
encodeURLString,
niceTimeFromSeconds,
} from "../../../../../../common/utils";
import { import {
selDistSet, selDistSet,
setModalErrorSnackMessage, setModalErrorSnackMessage,
@@ -30,6 +41,8 @@ import { useAppDispatch } from "../../../../../../store";
import { BucketObject } from "api/consoleApi"; import { BucketObject } from "api/consoleApi";
import { api } from "api"; import { api } from "api";
import { errorToHandler } from "api/errors"; import { errorToHandler } from "api/errors";
import { getMaxShareLinkExpTime } from "screens/Console/ObjectBrowser/objectBrowserThunks";
import { maxShareLinkExpTime } from "screens/Console/ObjectBrowser/objectBrowserSlice";
interface IShareFileProps { interface IShareFileProps {
open: boolean; open: boolean;
@@ -46,6 +59,7 @@ const ShareFile = ({
}: IShareFileProps) => { }: IShareFileProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const distributedSetup = useSelector(selDistSet); const distributedSetup = useSelector(selDistSet);
const maxshareLinkExpTimeVal = useSelector(maxShareLinkExpTime);
const [shareURL, setShareURL] = useState<string>(""); const [shareURL, setShareURL] = useState<string>("");
const [isLoadingVersion, setIsLoadingVersion] = useState<boolean>(true); const [isLoadingVersion, setIsLoadingVersion] = useState<boolean>(true);
const [isLoadingFile, setIsLoadingFile] = useState<boolean>(false); const [isLoadingFile, setIsLoadingFile] = useState<boolean>(false);
@@ -65,6 +79,10 @@ const ShareFile = ({
setShareURL(""); setShareURL("");
}; };
useEffect(() => {
dispatch(getMaxShareLinkExpTime());
}, [dispatch]);
useEffect(() => { useEffect(() => {
// In case version is undefined, we get the latest version of the object // In case version is undefined, we get the latest version of the object
if (dataObject.version_id === undefined) { if (dataObject.version_id === undefined) {
@@ -172,11 +190,28 @@ const ShareFile = ({
fontWeight: 400, fontWeight: 400,
}} }}
> >
This is a temporary URL with integrated access credentials for <Tooltip
sharing objects valid for up to 7 days. placement="right"
<br /> tooltip={
<br /> <span>
The temporary URL expires after the configured time limit. You can reset your session by logging out and logging back
in to the web UI. <br /> <br />
You can increase the maximum configuration time by setting
the MINIO_STS_DURATION environment variable on all your
nodes. <br /> <br />
You can use <b>mc share</b> as an alternative to this UI,
where the session length does not limit the URL validity.
</span>
}
>
<span>
The following URL lets you share this object without requiring
a login. <br />
The URL expires automatically at the earlier of your
configured time ({niceTimeFromSeconds(maxshareLinkExpTimeVal)}
) or the expiration of your current web session.
</span>
</Tooltip>
</Grid> </Grid>
<br /> <br />
<Grid item xs={12}> <Grid item xs={12}>
@@ -184,7 +219,7 @@ const ShareFile = ({
initialDate={initialDate} initialDate={initialDate}
id="date" id="date"
label="Active for" label="Active for"
maxDays={7} maxSeconds={maxshareLinkExpTimeVal}
onChange={dateChanged} onChange={dateChanged}
entity="Link" entity="Link"
/> />

View File

@@ -18,10 +18,14 @@ import React, { useEffect, useState } from "react";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { Box, InputBox, InputLabel, LinkIcon } from "mds"; import { Box, InputBox, InputLabel, LinkIcon } from "mds";
const DAY_SECONDS = 86400;
const HOUR_SECONDS = 3600;
const HOUR_MINUTES = 60;
interface IDaysSelector { interface IDaysSelector {
id: string; id: string;
initialDate: Date; initialDate: Date;
maxDays?: number; maxSeconds: number;
label: string; label: string;
entity: string; entity: string;
onChange: (newDate: string, isValid: boolean) => void; onChange: (newDate: string, isValid: boolean) => void;
@@ -43,16 +47,27 @@ const DaysSelector = ({
id, id,
initialDate, initialDate,
label, label,
maxDays, maxSeconds,
entity, entity,
onChange, onChange,
}: IDaysSelector) => { }: IDaysSelector) => {
const [selectedDays, setSelectedDays] = useState<number>(7); const maxDays = Math.floor(maxSeconds / DAY_SECONDS);
const maxHours = Math.floor((maxSeconds % DAY_SECONDS) / HOUR_SECONDS);
const maxMinutes = Math.floor((maxSeconds % HOUR_SECONDS) / HOUR_MINUTES);
const [selectedDays, setSelectedDays] = useState<number>(0);
const [selectedHours, setSelectedHours] = useState<number>(0); const [selectedHours, setSelectedHours] = useState<number>(0);
const [selectedMinutes, setSelectedMinutes] = useState<number>(0); const [selectedMinutes, setSelectedMinutes] = useState<number>(0);
const [validDate, setValidDate] = useState<boolean>(true); const [validDate, setValidDate] = useState<boolean>(true);
const [dateSelected, setDateSelected] = useState<DateTime>(DateTime.now()); const [dateSelected, setDateSelected] = useState<DateTime>(DateTime.now());
// Set initial values
useEffect(() => {
setSelectedDays(maxDays);
setSelectedHours(maxHours);
setSelectedMinutes(maxMinutes);
}, [maxDays, maxHours, maxMinutes]);
useEffect(() => { useEffect(() => {
if ( if (
!isNaN(selectedHours) && !isNaN(selectedHours) &&
@@ -82,9 +97,11 @@ const DaysSelector = ({
// Basic validation for inputs // Basic validation for inputs
useEffect(() => { useEffect(() => {
let valid = true; let valid = true;
if ( if (
selectedDays < 0 || selectedDays < 0 ||
(maxDays && selectedDays > maxDays) || selectedDays > 7 ||
selectedDays > maxDays ||
isNaN(selectedDays) isNaN(selectedDays)
) { ) {
valid = false; valid = false;
@@ -98,12 +115,16 @@ const DaysSelector = ({
valid = false; valid = false;
} }
if ( if (selectedDays === maxDays) {
maxDays && if (selectedHours > maxHours) {
selectedDays === maxDays && valid = false;
(selectedHours !== 0 || selectedMinutes !== 0) }
) {
valid = false; if (selectedHours === maxHours) {
if (selectedMinutes > maxMinutes) {
valid = false;
}
}
} }
if (selectedDays <= 0 && selectedHours <= 0 && selectedMinutes <= 0) { if (selectedDays <= 0 && selectedHours <= 0 && selectedMinutes <= 0) {
@@ -114,6 +135,8 @@ const DaysSelector = ({
}, [ }, [
dateSelected, dateSelected,
maxDays, maxDays,
maxHours,
maxMinutes,
onChange, onChange,
selectedDays, selectedDays,
selectedHours, selectedHours,
@@ -165,7 +188,7 @@ const DaysSelector = ({
className={`reverseInput removeArrows`} className={`reverseInput removeArrows`}
type="number" type="number"
min="0" min="0"
max={maxDays ? maxDays.toString() : "999"} max="7"
label="Days" label="Days"
name={id} name={id}
onChange={(e) => { onChange={(e) => {

View File

@@ -24,6 +24,7 @@ import {
BucketVersioningResponse, BucketVersioningResponse,
GetBucketRetentionConfig, GetBucketRetentionConfig,
} from "api/consoleApi"; } from "api/consoleApi";
import { AppState } from "store";
const defaultRewind = { const defaultRewind = {
rewindEnabled: false, rewindEnabled: false,
@@ -76,6 +77,7 @@ const initialState: ObjectBrowserState = {
validity: 0, validity: 0,
}, },
longFileOpen: false, longFileOpen: false,
maxShareLinkExpTime: 0,
}; };
export const objectBrowserSlice = createSlice({ export const objectBrowserSlice = createSlice({
@@ -371,6 +373,9 @@ export const objectBrowserSlice = createSlice({
setAnonymousAccessOpen: (state, action: PayloadAction<boolean>) => { setAnonymousAccessOpen: (state, action: PayloadAction<boolean>) => {
state.anonymousAccessOpen = action.payload; state.anonymousAccessOpen = action.payload;
}, },
setMaxShareLinkExpTime: (state, action: PayloadAction<number>) => {
state.maxShareLinkExpTime = action.payload;
},
errorInConnection: (state, action: PayloadAction<boolean>) => { errorInConnection: (state, action: PayloadAction<boolean>) => {
state.connectionError = action.payload; state.connectionError = action.payload;
if (action.payload) { if (action.payload) {
@@ -425,7 +430,11 @@ export const {
setSelectedBucket, setSelectedBucket,
setLongFileOpen, setLongFileOpen,
setAnonymousAccessOpen, setAnonymousAccessOpen,
setMaxShareLinkExpTime,
errorInConnection, errorInConnection,
} = objectBrowserSlice.actions; } = objectBrowserSlice.actions;
export const maxShareLinkExpTime = (state: AppState) =>
state.objectBrowser.maxShareLinkExpTime;
export default objectBrowserSlice.reducer; export default objectBrowserSlice.reducer;

View File

@@ -29,6 +29,7 @@ import {
failObject, failObject,
setAnonymousAccessOpen, setAnonymousAccessOpen,
setDownloadRenameModal, setDownloadRenameModal,
setMaxShareLinkExpTime,
setNewObject, setNewObject,
setPreviewOpen, setPreviewOpen,
setSelectedPreview, setSelectedPreview,
@@ -37,6 +38,7 @@ import {
} from "./objectBrowserSlice"; } from "./objectBrowserSlice";
import { setSnackBarMessage } from "../../../systemSlice"; import { setSnackBarMessage } from "../../../systemSlice";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { api } from "api";
export const downloadSelected = createAsyncThunk( export const downloadSelected = createAsyncThunk(
"objectBrowser/downloadSelected", "objectBrowser/downloadSelected",
@@ -203,3 +205,17 @@ export const openAnonymousAccess = createAsyncThunk(
} }
}, },
); );
export const getMaxShareLinkExpTime = createAsyncThunk(
"objectBrowser/maxShareLinkExpTime",
async (_, { rejectWithValue, dispatch }) => {
return api.buckets
.getMaxShareLinkExp()
.then((res) => {
dispatch(setMaxShareLinkExpTime(res.data.exp));
})
.catch(async (res) => {
return rejectWithValue(res.error);
});
},
);

View File

@@ -57,6 +57,7 @@ export interface ObjectBrowserState {
longFileOpen: boolean; longFileOpen: boolean;
anonymousAccessOpen: boolean; anonymousAccessOpen: boolean;
connectionError: boolean; connectionError: boolean;
maxShareLinkExpTime: number;
} }
export interface ObjectManager { export interface ObjectManager {