Permission Error handling and Tooltips for upload file, object action buttons (#2338)

![Screen Shot 2022-09-26 at 11 51 34
AM](https://user-images.githubusercontent.com/65002498/192357633-2f551441-8c27-450e-873e-1766d388cdb0.png)

<img width="706" alt="Screen Shot 2022-09-23 at 11 38 59 AM"
src="https://user-images.githubusercontent.com/65002498/192035299-093f814e-4821-4610-8fc5-c20565ea7c38.png">
<img width="642" alt="Screen Shot 2022-09-23 at 11 47 15 AM"
src="https://user-images.githubusercontent.com/65002498/192036512-f8891625-e050-42fd-9c43-173dd61c4df3.png">
This commit is contained in:
jinapurapu
2022-09-30 00:17:42 -07:00
committed by GitHub
parent 5cf2b736e1
commit 5eddd0cd8d
6 changed files with 232 additions and 84 deletions

View File

@@ -435,13 +435,13 @@ export const CONSOLE_UI_RESOURCE = "console-ui";
export const permissionTooltipHelper = (scopes: string[], name: string) => { export const permissionTooltipHelper = (scopes: string[], name: string) => {
return ( return (
"You require additional permissions in order to enable " + "You require additional permissions in order to " +
name + name +
". Please ask your MinIO administrator to grant you " + ". Please ask your MinIO administrator to grant you " +
scopes + scopes +
" permission" + " permission" +
(scopes.length > 1 ? "s" : "") + (scopes.length > 1 ? "s" : "") +
" in order to enable " + " in order to " +
name + name +
"." "."
); );

View File

@@ -19,6 +19,7 @@ import ObjectActionButton from "./ObjectActionButton";
import { withStyles } from "@mui/styles"; import { withStyles } from "@mui/styles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import { detailsPanel } from "../../../../Common/FormComponents/common/styleLibrary"; import { detailsPanel } from "../../../../Common/FormComponents/common/styleLibrary";
import TooltipWrapper from "../../../../Common/TooltipWrapper/TooltipWrapper";
const styles = () => const styles = () =>
createStyles({ createStyles({
@@ -52,12 +53,14 @@ const ActionsListSection = ({
{items.map((actionItem, index) => { {items.map((actionItem, index) => {
return ( return (
<li key={`action-element-${index.toString()}`}> <li key={`action-element-${index.toString()}`}>
<ObjectActionButton <TooltipWrapper tooltip={actionItem.tooltip || ""}>
label={actionItem.label} <ObjectActionButton
icon={actionItem.icon} label={actionItem.label}
onClick={actionItem.action} icon={actionItem.icon}
disabled={actionItem.disabled} onClick={actionItem.action}
/> disabled={actionItem.disabled}
/>
</TooltipWrapper>
</li> </li>
); );
})} })}

View File

@@ -71,7 +71,10 @@ import ScreenTitle from "../../../../Common/ScreenTitle/ScreenTitle";
import { AppState, useAppDispatch } from "../../../../../../store"; import { AppState, useAppDispatch } from "../../../../../../store";
import PageLayout from "../../../../Common/Layout/PageLayout"; import PageLayout from "../../../../Common/Layout/PageLayout";
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions"; import {
IAM_SCOPES,
permissionTooltipHelper,
} from "../../../../../../common/SecureComponent/permissions";
import { import {
hasPermission, hasPermission,
SecureComponent, SecureComponent,
@@ -1055,11 +1058,23 @@ const ListObjects = () => {
const onDrop = useCallback( const onDrop = useCallback(
(acceptedFiles: any[]) => { (acceptedFiles: any[]) => {
if (acceptedFiles && acceptedFiles.length > 0) { if (acceptedFiles && acceptedFiles.length > 0 && canUpload) {
let newFolderPath: string = acceptedFiles[0].path; let newFolderPath: string = acceptedFiles[0].path;
uploadObject(acceptedFiles, newFolderPath); uploadObject(acceptedFiles, newFolderPath);
} }
if (!canUpload) {
dispatch(
setErrorSnackMessage({
errorMessage: "Upload not allowed",
detailedError: permissionTooltipHelper(
[IAM_SCOPES.S3_PUT_OBJECT],
"upload objects to this location"
),
})
);
}
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps
[uploadObject] [uploadObject]
); );
@@ -1221,6 +1236,10 @@ const ListObjects = () => {
uploadPath = uploadPath.concat(currentPath); uploadPath = uploadPath.concat(currentPath);
} }
const canDownload = hasPermission(bucketName, [IAM_SCOPES.S3_GET_OBJECT]);
const canDelete = hasPermission(bucketName, [IAM_SCOPES.S3_DELETE_OBJECT]);
const canUpload = hasPermission(uploadPath, [IAM_SCOPES.S3_PUT_OBJECT]);
const onClosePanel = (forceRefresh: boolean) => { const onClosePanel = (forceRefresh: boolean) => {
dispatch(setSelectedObjectView(null)); dispatch(setSelectedObjectView(null));
dispatch(setVersionsModeEnabled({ status: false })); dispatch(setVersionsModeEnabled({ status: false }));
@@ -1272,23 +1291,28 @@ const ListObjects = () => {
{ {
action: downloadSelected, action: downloadSelected,
label: "Download", label: "Download",
disabled: selectedObjects.length === 0, disabled: !canDownload || selectedObjects.length === 0,
icon: <DownloadIcon />, icon: <DownloadIcon />,
tooltip: "Download Selected", tooltip: canDownload
? "Download Selected"
: permissionTooltipHelper(
[IAM_SCOPES.S3_GET_OBJECT],
"download objects from this bucket"
),
}, },
{ {
action: openShare, action: openShare,
label: "Share", label: "Share",
disabled: selectedObjects.length !== 1 || !canShareFile, disabled: selectedObjects.length !== 1 || !canShareFile,
icon: <ShareIcon />, icon: <ShareIcon />,
tooltip: "Share Selected File", tooltip: canShareFile ? "Share Selected File" : "Sharing unavailable",
}, },
{ {
action: openPreview, action: openPreview,
label: "Preview", label: "Preview",
disabled: selectedObjects.length !== 1 || !canPreviewFile, disabled: selectedObjects.length !== 1 || !canPreviewFile,
icon: <PreviewIcon />, icon: <PreviewIcon />,
tooltip: "Preview Selected File", tooltip: canPreviewFile ? "Preview Selected File" : "Preview unavailable",
}, },
{ {
action: () => { action: () => {
@@ -1297,10 +1321,13 @@ const ListObjects = () => {
label: "Delete", label: "Delete",
icon: <DeleteIcon />, icon: <DeleteIcon />,
disabled: disabled:
!hasPermission(bucketName, [IAM_SCOPES.S3_DELETE_OBJECT]) || !canDelete || selectedObjects.length === 0 || !displayDeleteObject,
selectedObjects.length === 0 || tooltip: canDelete
!displayDeleteObject, ? "Delete Selected Files"
tooltip: "Delete Selected Files", : permissionTooltipHelper(
[IAM_SCOPES.S3_DELETE_OBJECT],
"delete objects in this bucket"
),
}, },
]; ];

View File

@@ -41,7 +41,10 @@ import {
niceBytesInt, niceBytesInt,
niceDaysInt, niceDaysInt,
} from "../../../../../../common/utils"; } from "../../../../../../common/utils";
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions"; import {
IAM_SCOPES,
permissionTooltipHelper,
} from "../../../../../../common/SecureComponent/permissions";
import { AppState, useAppDispatch } from "../../../../../../store"; import { AppState, useAppDispatch } from "../../../../../../store";
import { import {
@@ -90,6 +93,7 @@ import {
updateProgress, updateProgress,
} from "../../../../ObjectBrowser/objectBrowserSlice"; } from "../../../../ObjectBrowser/objectBrowserSlice";
import RenameLongFileName from "../../../../ObjectBrowser/RenameLongFilename"; import RenameLongFileName from "../../../../ObjectBrowser/RenameLongFilename";
import TooltipWrapper from "../../../../Common/TooltipWrapper/TooltipWrapper";
const styles = () => const styles = () =>
createStyles({ createStyles({
@@ -405,6 +409,33 @@ const ObjectDetailPanel = ({
currentItem, currentItem,
[bucketName, actualInfo.name].join("/"), [bucketName, actualInfo.name].join("/"),
]; ];
const canSetLegalHold = hasPermission(bucketName, [
IAM_SCOPES.S3_PUT_OBJECT_LEGAL_HOLD,
]);
const canSetTags = hasPermission(objectResources, [
IAM_SCOPES.S3_PUT_OBJECT_TAGGING,
]);
const canChangeRetention = hasPermission(
objectResources,
[IAM_SCOPES.S3_GET_OBJECT_RETENTION, IAM_SCOPES.S3_PUT_OBJECT_RETENTION],
true
);
const canInspect = hasPermission(objectResources, [
IAM_SCOPES.ADMIN_INSPECT_DATA,
]);
const canChangeVersioning = hasPermission(objectResources, [
IAM_SCOPES.S3_GET_BUCKET_VERSIONING,
IAM_SCOPES.S3_PUT_BUCKET_VERSIONING,
IAM_SCOPES.S3_GET_OBJECT_VERSION,
]);
const canGetObject = hasPermission(objectResources, [
IAM_SCOPES.S3_GET_OBJECT,
]);
const canDelete = hasPermission(
[bucketName, currentItem, [bucketName, actualInfo.name].join("/")],
[IAM_SCOPES.S3_DELETE_OBJECT]
);
const multiActionButtons = [ const multiActionButtons = [
{ {
@@ -412,22 +443,28 @@ const ObjectDetailPanel = ({
downloadObject(actualInfo); downloadObject(actualInfo);
}, },
label: "Download", label: "Download",
disabled: disabled: !!actualInfo.is_delete_marker || !canGetObject,
!!actualInfo.is_delete_marker ||
!hasPermission(objectResources, [IAM_SCOPES.S3_GET_OBJECT]),
icon: <DownloadIcon />, icon: <DownloadIcon />,
tooltip: "Download this Object", tooltip: canGetObject
? "Download this Object"
: permissionTooltipHelper(
[IAM_SCOPES.S3_GET_OBJECT],
"download this object"
),
}, },
{ {
action: () => { action: () => {
shareObject(); shareObject();
}, },
label: "Share", label: "Share",
disabled: disabled: !!actualInfo.is_delete_marker || !canGetObject,
!!actualInfo.is_delete_marker ||
!hasPermission(objectResources, [IAM_SCOPES.S3_GET_OBJECT]),
icon: <ShareIcon />, icon: <ShareIcon />,
tooltip: "Share this File", tooltip: canGetObject
? "Share this File"
: permissionTooltipHelper(
[IAM_SCOPES.S3_GET_OBJECT],
"share this object"
),
}, },
{ {
action: () => { action: () => {
@@ -437,9 +474,14 @@ const ObjectDetailPanel = ({
disabled: disabled:
!!actualInfo.is_delete_marker || !!actualInfo.is_delete_marker ||
extensionPreview(currentItem) === "none" || extensionPreview(currentItem) === "none" ||
!hasPermission(objectResources, [IAM_SCOPES.S3_GET_OBJECT]), !canGetObject,
icon: <PreviewIcon />, icon: <PreviewIcon />,
tooltip: "Preview this File", tooltip: canGetObject
? "Preview this File"
: permissionTooltipHelper(
[IAM_SCOPES.S3_GET_OBJECT],
"preview this object"
),
}, },
{ {
action: () => { action: () => {
@@ -450,10 +492,17 @@ const ObjectDetailPanel = ({
!locking || !locking ||
!distributedSetup || !distributedSetup ||
!!actualInfo.is_delete_marker || !!actualInfo.is_delete_marker ||
!hasPermission(bucketName, [IAM_SCOPES.S3_PUT_OBJECT_LEGAL_HOLD]) || !canSetLegalHold ||
selectedVersion !== "", selectedVersion !== "",
icon: <LegalHoldIcon />, icon: <LegalHoldIcon />,
tooltip: "Change Legal Hold rules for this File", tooltip: canSetLegalHold
? locking
? "Change Legal Hold rules for this File"
: "Object Locking must be enabled on this bucket in order to set Legal Hold"
: permissionTooltipHelper(
[IAM_SCOPES.S3_PUT_OBJECT_LEGAL_HOLD],
"change legal hold settings for this object"
),
}, },
{ {
action: openRetentionModal, action: openRetentionModal,
@@ -461,10 +510,21 @@ const ObjectDetailPanel = ({
disabled: disabled:
!distributedSetup || !distributedSetup ||
!!actualInfo.is_delete_marker || !!actualInfo.is_delete_marker ||
!hasPermission(objectResources, [IAM_SCOPES.S3_GET_OBJECT_RETENTION]) || !canChangeRetention ||
selectedVersion !== "", selectedVersion !== "" ||
!locking,
icon: <RetentionIcon />, icon: <RetentionIcon />,
tooltip: "Change Retention rules for this File", tooltip: canChangeRetention
? locking
? "Change Retention rules for this File"
: "Object Locking must be enabled on this bucket in order to set Retention Rules"
: permissionTooltipHelper(
[
IAM_SCOPES.S3_GET_OBJECT_RETENTION,
IAM_SCOPES.S3_PUT_OBJECT_RETENTION,
],
"change Retention Rules for this object"
),
}, },
{ {
action: () => { action: () => {
@@ -472,11 +532,17 @@ const ObjectDetailPanel = ({
}, },
label: "Tags", label: "Tags",
disabled: disabled:
!!actualInfo.is_delete_marker || !!actualInfo.is_delete_marker || selectedVersion !== "" || !canSetTags,
selectedVersion !== "" ||
!hasPermission(objectResources, [IAM_SCOPES.S3_PUT_OBJECT_TAGGING]),
icon: <TagsIcon />, icon: <TagsIcon />,
tooltip: "Change Tags for this File", tooltip: canSetTags
? "Change Tags for this File"
: permissionTooltipHelper(
[
IAM_SCOPES.S3_PUT_OBJECT_TAGGING,
IAM_SCOPES.S3_GET_OBJECT_TAGGING,
],
"set Tags on this object"
),
}, },
{ {
action: () => { action: () => {
@@ -487,9 +553,14 @@ const ObjectDetailPanel = ({
!distributedSetup || !distributedSetup ||
!!actualInfo.is_delete_marker || !!actualInfo.is_delete_marker ||
selectedVersion !== "" || selectedVersion !== "" ||
!hasPermission(objectResources, [IAM_SCOPES.ADMIN_INSPECT_DATA]), !canInspect,
icon: <InspectMenuIcon />, icon: <InspectMenuIcon />,
tooltip: "Inspect this file", tooltip: canInspect
? "Inspect this file"
: permissionTooltipHelper(
[IAM_SCOPES.ADMIN_INSPECT_DATA],
"inspect this file"
),
}, },
{ {
action: () => { action: () => {
@@ -505,12 +576,19 @@ const ObjectDetailPanel = ({
disabled: disabled:
!distributedSetup || !distributedSetup ||
!(actualInfo.version_id && actualInfo.version_id !== "null") || !(actualInfo.version_id && actualInfo.version_id !== "null") ||
!hasPermission(objectResources, [ !canChangeVersioning,
IAM_SCOPES.S3_GET_BUCKET_VERSIONING, tooltip: canChangeVersioning
IAM_SCOPES.S3_PUT_BUCKET_VERSIONING, ? actualInfo.version_id && actualInfo.version_id !== "null"
IAM_SCOPES.S3_GET_OBJECT_VERSION, ? "Display Versions for this file"
]), : ""
tooltip: "Display Versions for this file", : permissionTooltipHelper(
[
IAM_SCOPES.S3_GET_BUCKET_VERSIONING,
IAM_SCOPES.S3_PUT_BUCKET_VERSIONING,
IAM_SCOPES.S3_GET_OBJECT_VERSION,
],
"display all versions of this object"
),
}, },
]; ];
@@ -621,35 +699,51 @@ const ObjectDetailPanel = ({
} }
items={multiActionButtons} items={multiActionButtons}
/> />
<TooltipWrapper
<Grid item xs={12} sx={{ justifyContent: "center", display: "flex" }}> tooltip={
<SecureComponent canDelete
resource={[ ? ""
bucketName, : permissionTooltipHelper(
currentItem, [IAM_SCOPES.S3_DELETE_OBJECT],
[bucketName, actualInfo.name].join("/"), "delete this object"
]} )
scopes={[IAM_SCOPES.S3_DELETE_OBJECT]} }
errorProps={{ disabled: true }} >
<Grid
item
xs={12}
sx={{ justifyContent: "center", display: "flex" }}
> >
<Button <SecureComponent
id={"delete-element-click"} resource={[
icon={<DeleteIcon />} bucketName,
iconLocation={"start"} currentItem,
fullWidth [bucketName, actualInfo.name].join("/"),
variant={"secondary"} ]}
onClick={() => { scopes={[IAM_SCOPES.S3_DELETE_OBJECT]}
setDeleteOpen(true); errorProps={{ disabled: true }}
}} >
disabled={selectedVersion === "" && actualInfo.is_delete_marker} <Button
style={{ id={"delete-element-click"}
width: "calc(100% - 44px)", icon={<DeleteIcon />}
margin: "8px 0", iconLocation={"start"}
}} fullWidth
label={`Delete${selectedVersion !== "" ? " version" : ""}`} variant={"secondary"}
/> onClick={() => {
</SecureComponent> setDeleteOpen(true);
</Grid> }}
disabled={
selectedVersion === "" && actualInfo.is_delete_marker
}
style={{
width: "calc(100% - 44px)",
margin: "8px 0",
}}
label={`Delete${selectedVersion !== "" ? " version" : ""}`}
/>
</SecureComponent>
</Grid>
</TooltipWrapper>
<Grid item xs={12} className={classes.headerForSection}> <Grid item xs={12} className={classes.headerForSection}>
<span>Object Info</span> <span>Object Info</span>
<ObjectInfoIcon /> <ObjectInfoIcon />

View File

@@ -22,7 +22,10 @@ import withStyles from "@mui/styles/withStyles";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemIcon from "@mui/material/ListItemIcon";
import { UploadFolderIcon, UploadIcon } from "../../../../icons"; import { UploadFolderIcon, UploadIcon } from "../../../../icons";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions"; import {
IAM_SCOPES,
permissionTooltipHelper,
} from "../../../../common/SecureComponent/permissions";
import { hasPermission } from "../../../../common/SecureComponent"; import { hasPermission } from "../../../../common/SecureComponent";
import { Button } from "mds"; import { Button } from "mds";
import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper"; import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
@@ -78,7 +81,16 @@ const UploadFilesButton = ({
return ( return (
<Fragment> <Fragment>
<TooltipWrapper tooltip={"Upload Files"}> <TooltipWrapper
tooltip={
uploadEnabled
? "Upload Files"
: permissionTooltipHelper(
[IAM_SCOPES.S3_PUT_OBJECT],
"upload files to this bucket"
)
}
>
<Button <Button
id={"upload-main"} id={"upload-main"}
aria-controls={`upload-main-menu`} aria-controls={`upload-main-menu`}

View File

@@ -22,18 +22,22 @@ import withStyles from "@mui/styles/withStyles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import { Theme } from "@mui/material/styles"; import { Theme } from "@mui/material/styles";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { IconButton, Tooltip } from "@mui/material"; import { IconButton } from "@mui/material";
import { objectBrowserCommon } from "../Common/FormComponents/common/styleLibrary"; import { objectBrowserCommon } from "../Common/FormComponents/common/styleLibrary";
import { encodeURLString } from "../../../common/utils"; import { encodeURLString } from "../../../common/utils";
import { BackCaretIcon, CopyIcon, NewPathIcon } from "../../../icons"; import { BackCaretIcon, CopyIcon, NewPathIcon } from "../../../icons";
import { hasPermission } from "../../../common/SecureComponent"; import { hasPermission } from "../../../common/SecureComponent";
import { IAM_SCOPES } from "../../../common/SecureComponent/permissions"; import {
IAM_SCOPES,
permissionTooltipHelper,
} from "../../../common/SecureComponent/permissions";
import { BucketObjectItem } from "../Buckets/ListBuckets/Objects/ListObjects/types"; import { BucketObjectItem } from "../Buckets/ListBuckets/Objects/ListObjects/types";
import withSuspense from "../Common/Components/withSuspense"; import withSuspense from "../Common/Components/withSuspense";
import { setSnackBarMessage } from "../../../systemSlice"; import { setSnackBarMessage } from "../../../systemSlice";
import { AppState, useAppDispatch } from "../../../store"; import { AppState, useAppDispatch } from "../../../store";
import { setVersionsModeEnabled } from "./objectBrowserSlice"; import { setVersionsModeEnabled } from "./objectBrowserSlice";
import { Button } from "mds"; import { Button } from "mds";
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
const CreatePathModal = withSuspense( const CreatePathModal = withSuspense(
React.lazy( React.lazy(
@@ -81,6 +85,8 @@ const BrowserBreadcrumbs = ({
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false); const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
const canCreatePath = hasPermission(bucketName, [IAM_SCOPES.S3_PUT_OBJECT]);
let paths = internalPaths; let paths = internalPaths;
if (internalPaths !== "") { if (internalPaths !== "") {
@@ -220,16 +226,22 @@ const BrowserBreadcrumbs = ({
<div className={classes.additionalOptions}>{additionalOptions}</div> <div className={classes.additionalOptions}>{additionalOptions}</div>
</Grid> </Grid>
{!hidePathButton && ( {!hidePathButton && (
<Tooltip title={"Choose or create a new path"}> <TooltipWrapper
tooltip={
canCreatePath
? "Choose or create a new path"
: permissionTooltipHelper(
[IAM_SCOPES.S3_PUT_OBJECT],
"create a new path"
)
}
>
<Button <Button
id={"new-path"} id={"new-path"}
onClick={() => { onClick={() => {
setCreateFolderOpen(true); setCreateFolderOpen(true);
}} }}
disabled={ disabled={rewindEnabled || !canCreatePath}
rewindEnabled ||
!hasPermission(bucketName, [IAM_SCOPES.S3_PUT_OBJECT])
}
icon={<NewPathIcon style={{ fill: "#969FA8" }} />} icon={<NewPathIcon style={{ fill: "#969FA8" }} />}
style={{ style={{
whiteSpace: "nowrap", whiteSpace: "nowrap",
@@ -237,7 +249,7 @@ const BrowserBreadcrumbs = ({
variant={"regular"} variant={"regular"}
label={"Create new path"} label={"Create new path"}
/> />
</Tooltip> </TooltipWrapper>
)} )}
</div> </div>
<div className={classes.breadcrumbsSecond}>{additionalOptions}</div> <div className={classes.breadcrumbsSecond}>{additionalOptions}</div>