Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dffd5f079 | ||
|
|
044e5702df |
@@ -2,6 +2,12 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
## Release v0.43.1
|
||||
|
||||
Bug Fix:
|
||||
|
||||
- Update Share Object UI to reflect maximum expiration time in UI
|
||||
|
||||
## Release v0.43.0
|
||||
|
||||
Features:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.e60e4760.css",
|
||||
"main.js": "./static/js/main.bfa676cc.js",
|
||||
"main.js": "./static/js/main.f92800f2.js",
|
||||
"static/js/1260.291b50cb.chunk.js": "./static/js/1260.291b50cb.chunk.js",
|
||||
"static/js/6914.9c5fdb44.chunk.js": "./static/js/6914.9c5fdb44.chunk.js",
|
||||
"static/js/9121.1e573e4c.chunk.js": "./static/js/9121.1e573e4c.chunk.js",
|
||||
@@ -40,7 +40,7 @@
|
||||
"static/js/6577.d820c471.chunk.js": "./static/js/6577.d820c471.chunk.js",
|
||||
"static/js/3875.458718d4.chunk.js": "./static/js/3875.458718d4.chunk.js",
|
||||
"static/js/3115.3c5e3052.chunk.js": "./static/js/3115.3c5e3052.chunk.js",
|
||||
"static/js/2680.76c11a81.chunk.js": "./static/js/2680.76c11a81.chunk.js",
|
||||
"static/js/4646.aa9a58d3.chunk.js": "./static/js/4646.aa9a58d3.chunk.js",
|
||||
"static/js/977.a3dd8fca.chunk.js": "./static/js/977.a3dd8fca.chunk.js",
|
||||
"static/js/6686.01ae5612.chunk.js": "./static/js/6686.01ae5612.chunk.js",
|
||||
"static/js/9059.25ffd18e.chunk.js": "./static/js/9059.25ffd18e.chunk.js",
|
||||
@@ -119,7 +119,7 @@
|
||||
"static/media/placeholderimage.png": "./static/media/placeholderimage.077ea48bd1ef1f4a883f.png",
|
||||
"index.html": "./index.html",
|
||||
"main.e60e4760.css.map": "./static/css/main.e60e4760.css.map",
|
||||
"main.bfa676cc.js.map": "./static/js/main.bfa676cc.js.map",
|
||||
"main.f92800f2.js.map": "./static/js/main.f92800f2.js.map",
|
||||
"1260.291b50cb.chunk.js.map": "./static/js/1260.291b50cb.chunk.js.map",
|
||||
"6914.9c5fdb44.chunk.js.map": "./static/js/6914.9c5fdb44.chunk.js.map",
|
||||
"9121.1e573e4c.chunk.js.map": "./static/js/9121.1e573e4c.chunk.js.map",
|
||||
@@ -158,7 +158,7 @@
|
||||
"6577.d820c471.chunk.js.map": "./static/js/6577.d820c471.chunk.js.map",
|
||||
"3875.458718d4.chunk.js.map": "./static/js/3875.458718d4.chunk.js.map",
|
||||
"3115.3c5e3052.chunk.js.map": "./static/js/3115.3c5e3052.chunk.js.map",
|
||||
"2680.76c11a81.chunk.js.map": "./static/js/2680.76c11a81.chunk.js.map",
|
||||
"4646.aa9a58d3.chunk.js.map": "./static/js/4646.aa9a58d3.chunk.js.map",
|
||||
"977.a3dd8fca.chunk.js.map": "./static/js/977.a3dd8fca.chunk.js.map",
|
||||
"6686.01ae5612.chunk.js.map": "./static/js/6686.01ae5612.chunk.js.map",
|
||||
"9059.25ffd18e.chunk.js.map": "./static/js/9059.25ffd18e.chunk.js.map",
|
||||
@@ -217,6 +217,6 @@
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.e60e4760.css",
|
||||
"static/js/main.bfa676cc.js"
|
||||
"static/js/main.f92800f2.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="/"/><meta content="width=device-width,initial-scale=1" name="viewport"/><meta content="#081C42" media="(prefers-color-scheme: light)" name="theme-color"/><meta content="#081C42" media="(prefers-color-scheme: dark)" name="theme-color"/><meta content="MinIO Console" name="description"/><meta name="minio-license" content="apgl"/><link href="./styles/root-styles.css" rel="stylesheet"/><link href="./apple-icon-180x180.png" rel="apple-touch-icon" sizes="180x180"/><link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/><link href="./favicon-96x96.png" rel="icon" sizes="96x96" type="image/png"/><link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/><link href="./manifest.json" rel="manifest"/><link color="#3a4e54" href="./safari-pinned-tab.svg" rel="mask-icon"/><title>MinIO Console</title><script defer="defer" src="./static/js/main.bfa676cc.js"></script><link href="./static/css/main.e60e4760.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="preload"><img src="./images/background.svg"/> <img src="./images/background-wave-orig2.svg"/></div><div id="loader-block"><img src="./Loader.svg"/></div></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="/"/><meta content="width=device-width,initial-scale=1" name="viewport"/><meta content="#081C42" media="(prefers-color-scheme: light)" name="theme-color"/><meta content="#081C42" media="(prefers-color-scheme: dark)" name="theme-color"/><meta content="MinIO Console" name="description"/><meta name="minio-license" content="apgl"/><link href="./styles/root-styles.css" rel="stylesheet"/><link href="./apple-icon-180x180.png" rel="apple-touch-icon" sizes="180x180"/><link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/><link href="./favicon-96x96.png" rel="icon" sizes="96x96" type="image/png"/><link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/><link href="./manifest.json" rel="manifest"/><link color="#3a4e54" href="./safari-pinned-tab.svg" rel="mask-icon"/><title>MinIO Console</title><script defer="defer" src="./static/js/main.f92800f2.js"></script><link href="./static/css/main.e60e4760.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="preload"><img src="./images/background.svg"/> <img src="./images/background-wave-orig2.svg"/></div><div id="loader-block"><img src="./Loader.svg"/></div></div></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/4646.aa9a58d3.chunk.js
Normal file
2
portal-ui/build/static/js/4646.aa9a58d3.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/4646.aa9a58d3.chunk.js.map
Normal file
1
portal-ui/build/static/js/4646.aa9a58d3.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
portal-ui/build/static/js/main.f92800f2.js
Normal file
3
portal-ui/build/static/js/main.f92800f2.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/main.f92800f2.js.map
Normal file
1
portal-ui/build/static/js/main.f92800f2.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -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
|
||||
export const niceDays = (secondsValue: string, timeVariant: string = "s") => {
|
||||
let seconds = parseFloat(secondsValue);
|
||||
@@ -258,6 +288,7 @@ export const niceDays = (secondsValue: string, timeVariant: string = "s") => {
|
||||
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") => {
|
||||
switch (timeVariant) {
|
||||
case "ns":
|
||||
|
||||
@@ -31,7 +31,7 @@ const initialState: BucketDetailsState = {
|
||||
};
|
||||
|
||||
export const bucketDetailsSlice = createSlice({
|
||||
name: "trace",
|
||||
name: "bucketDetails",
|
||||
initialState,
|
||||
reducers: {
|
||||
setBucketDetailsTab: (state, action: PayloadAction<string>) => {
|
||||
|
||||
@@ -16,11 +16,22 @@
|
||||
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
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 ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
|
||||
import DaysSelector from "../../../../Common/FormComponents/DaysSelector/DaysSelector";
|
||||
import { encodeURLString } from "../../../../../../common/utils";
|
||||
import {
|
||||
encodeURLString,
|
||||
niceTimeFromSeconds,
|
||||
} from "../../../../../../common/utils";
|
||||
import {
|
||||
selDistSet,
|
||||
setModalErrorSnackMessage,
|
||||
@@ -30,6 +41,8 @@ import { useAppDispatch } from "../../../../../../store";
|
||||
import { BucketObject } from "api/consoleApi";
|
||||
import { api } from "api";
|
||||
import { errorToHandler } from "api/errors";
|
||||
import { getMaxShareLinkExpTime } from "screens/Console/ObjectBrowser/objectBrowserThunks";
|
||||
import { maxShareLinkExpTime } from "screens/Console/ObjectBrowser/objectBrowserSlice";
|
||||
|
||||
interface IShareFileProps {
|
||||
open: boolean;
|
||||
@@ -46,6 +59,7 @@ const ShareFile = ({
|
||||
}: IShareFileProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const distributedSetup = useSelector(selDistSet);
|
||||
const maxshareLinkExpTimeVal = useSelector(maxShareLinkExpTime);
|
||||
const [shareURL, setShareURL] = useState<string>("");
|
||||
const [isLoadingVersion, setIsLoadingVersion] = useState<boolean>(true);
|
||||
const [isLoadingFile, setIsLoadingFile] = useState<boolean>(false);
|
||||
@@ -65,6 +79,10 @@ const ShareFile = ({
|
||||
setShareURL("");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getMaxShareLinkExpTime());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
// In case version is undefined, we get the latest version of the object
|
||||
if (dataObject.version_id === undefined) {
|
||||
@@ -172,11 +190,28 @@ const ShareFile = ({
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
This is a temporary URL with integrated access credentials for
|
||||
sharing objects valid for up to 7 days.
|
||||
<br />
|
||||
<br />
|
||||
The temporary URL expires after the configured time limit.
|
||||
<Tooltip
|
||||
placement="right"
|
||||
tooltip={
|
||||
<span>
|
||||
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>
|
||||
<br />
|
||||
<Grid item xs={12}>
|
||||
@@ -184,7 +219,7 @@ const ShareFile = ({
|
||||
initialDate={initialDate}
|
||||
id="date"
|
||||
label="Active for"
|
||||
maxDays={7}
|
||||
maxSeconds={maxshareLinkExpTimeVal}
|
||||
onChange={dateChanged}
|
||||
entity="Link"
|
||||
/>
|
||||
|
||||
@@ -18,10 +18,14 @@ import React, { useEffect, useState } from "react";
|
||||
import { DateTime } from "luxon";
|
||||
import { Box, InputBox, InputLabel, LinkIcon } from "mds";
|
||||
|
||||
const DAY_SECONDS = 86400;
|
||||
const HOUR_SECONDS = 3600;
|
||||
const HOUR_MINUTES = 60;
|
||||
|
||||
interface IDaysSelector {
|
||||
id: string;
|
||||
initialDate: Date;
|
||||
maxDays?: number;
|
||||
maxSeconds: number;
|
||||
label: string;
|
||||
entity: string;
|
||||
onChange: (newDate: string, isValid: boolean) => void;
|
||||
@@ -43,16 +47,27 @@ const DaysSelector = ({
|
||||
id,
|
||||
initialDate,
|
||||
label,
|
||||
maxDays,
|
||||
maxSeconds,
|
||||
entity,
|
||||
onChange,
|
||||
}: 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 [selectedMinutes, setSelectedMinutes] = useState<number>(0);
|
||||
const [validDate, setValidDate] = useState<boolean>(true);
|
||||
const [dateSelected, setDateSelected] = useState<DateTime>(DateTime.now());
|
||||
|
||||
// Set initial values
|
||||
useEffect(() => {
|
||||
setSelectedDays(maxDays);
|
||||
setSelectedHours(maxHours);
|
||||
setSelectedMinutes(maxMinutes);
|
||||
}, [maxDays, maxHours, maxMinutes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isNaN(selectedHours) &&
|
||||
@@ -82,9 +97,11 @@ const DaysSelector = ({
|
||||
// Basic validation for inputs
|
||||
useEffect(() => {
|
||||
let valid = true;
|
||||
|
||||
if (
|
||||
selectedDays < 0 ||
|
||||
(maxDays && selectedDays > maxDays) ||
|
||||
selectedDays > 7 ||
|
||||
selectedDays > maxDays ||
|
||||
isNaN(selectedDays)
|
||||
) {
|
||||
valid = false;
|
||||
@@ -98,12 +115,16 @@ const DaysSelector = ({
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (
|
||||
maxDays &&
|
||||
selectedDays === maxDays &&
|
||||
(selectedHours !== 0 || selectedMinutes !== 0)
|
||||
) {
|
||||
valid = false;
|
||||
if (selectedDays === maxDays) {
|
||||
if (selectedHours > maxHours) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (selectedHours === maxHours) {
|
||||
if (selectedMinutes > maxMinutes) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedDays <= 0 && selectedHours <= 0 && selectedMinutes <= 0) {
|
||||
@@ -114,6 +135,8 @@ const DaysSelector = ({
|
||||
}, [
|
||||
dateSelected,
|
||||
maxDays,
|
||||
maxHours,
|
||||
maxMinutes,
|
||||
onChange,
|
||||
selectedDays,
|
||||
selectedHours,
|
||||
@@ -165,7 +188,7 @@ const DaysSelector = ({
|
||||
className={`reverseInput removeArrows`}
|
||||
type="number"
|
||||
min="0"
|
||||
max={maxDays ? maxDays.toString() : "999"}
|
||||
max="7"
|
||||
label="Days"
|
||||
name={id}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
BucketVersioningResponse,
|
||||
GetBucketRetentionConfig,
|
||||
} from "api/consoleApi";
|
||||
import { AppState } from "store";
|
||||
|
||||
const defaultRewind = {
|
||||
rewindEnabled: false,
|
||||
@@ -76,6 +77,7 @@ const initialState: ObjectBrowserState = {
|
||||
validity: 0,
|
||||
},
|
||||
longFileOpen: false,
|
||||
maxShareLinkExpTime: 0,
|
||||
};
|
||||
|
||||
export const objectBrowserSlice = createSlice({
|
||||
@@ -371,6 +373,9 @@ export const objectBrowserSlice = createSlice({
|
||||
setAnonymousAccessOpen: (state, action: PayloadAction<boolean>) => {
|
||||
state.anonymousAccessOpen = action.payload;
|
||||
},
|
||||
setMaxShareLinkExpTime: (state, action: PayloadAction<number>) => {
|
||||
state.maxShareLinkExpTime = action.payload;
|
||||
},
|
||||
errorInConnection: (state, action: PayloadAction<boolean>) => {
|
||||
state.connectionError = action.payload;
|
||||
if (action.payload) {
|
||||
@@ -425,7 +430,11 @@ export const {
|
||||
setSelectedBucket,
|
||||
setLongFileOpen,
|
||||
setAnonymousAccessOpen,
|
||||
setMaxShareLinkExpTime,
|
||||
errorInConnection,
|
||||
} = objectBrowserSlice.actions;
|
||||
|
||||
export const maxShareLinkExpTime = (state: AppState) =>
|
||||
state.objectBrowser.maxShareLinkExpTime;
|
||||
|
||||
export default objectBrowserSlice.reducer;
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
failObject,
|
||||
setAnonymousAccessOpen,
|
||||
setDownloadRenameModal,
|
||||
setMaxShareLinkExpTime,
|
||||
setNewObject,
|
||||
setPreviewOpen,
|
||||
setSelectedPreview,
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
} from "./objectBrowserSlice";
|
||||
import { setSnackBarMessage } from "../../../systemSlice";
|
||||
import { DateTime } from "luxon";
|
||||
import { api } from "api";
|
||||
|
||||
export const downloadSelected = createAsyncThunk(
|
||||
"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);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -57,6 +57,7 @@ export interface ObjectBrowserState {
|
||||
longFileOpen: boolean;
|
||||
anonymousAccessOpen: boolean;
|
||||
connectionError: boolean;
|
||||
maxShareLinkExpTime: number;
|
||||
}
|
||||
|
||||
export interface ObjectManager {
|
||||
|
||||
Reference in New Issue
Block a user