From 5eddd0cd8d775d97d29cff7d401eaa885612593f Mon Sep 17 00:00:00 2001 From: jinapurapu <65002498+jinapurapu@users.noreply.github.com> Date: Fri, 30 Sep 2022 00:17:42 -0700 Subject: [PATCH] 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) Screen Shot 2022-09-23 at 11 38 59 AM Screen Shot 2022-09-23 at 11 47 15 AM --- .../src/common/SecureComponent/permissions.ts | 4 +- .../ListObjects/ActionsListSection.tsx | 15 +- .../Objects/ListObjects/ListObjects.tsx | 47 +++- .../Objects/ListObjects/ObjectDetailPanel.tsx | 206 +++++++++++++----- .../Buckets/ListBuckets/UploadFilesButton.tsx | 16 +- .../ObjectBrowser/BrowserBreadcrumbs.tsx | 28 ++- 6 files changed, 232 insertions(+), 84 deletions(-) diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts index a5d302618..228f22ad9 100644 --- a/portal-ui/src/common/SecureComponent/permissions.ts +++ b/portal-ui/src/common/SecureComponent/permissions.ts @@ -435,13 +435,13 @@ export const CONSOLE_UI_RESOURCE = "console-ui"; export const permissionTooltipHelper = (scopes: string[], name: string) => { return ( - "You require additional permissions in order to enable " + + "You require additional permissions in order to " + name + ". Please ask your MinIO administrator to grant you " + scopes + " permission" + (scopes.length > 1 ? "s" : "") + - " in order to enable " + + " in order to " + name + "." ); diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ActionsListSection.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ActionsListSection.tsx index 3d379119b..694608694 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ActionsListSection.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ActionsListSection.tsx @@ -19,6 +19,7 @@ import ObjectActionButton from "./ObjectActionButton"; import { withStyles } from "@mui/styles"; import createStyles from "@mui/styles/createStyles"; import { detailsPanel } from "../../../../Common/FormComponents/common/styleLibrary"; +import TooltipWrapper from "../../../../Common/TooltipWrapper/TooltipWrapper"; const styles = () => createStyles({ @@ -52,12 +53,14 @@ const ActionsListSection = ({ {items.map((actionItem, index) => { return (
  • - + + +
  • ); })} 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 f919ecb19..ea6b9a8cc 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 @@ -71,7 +71,10 @@ import ScreenTitle from "../../../../Common/ScreenTitle/ScreenTitle"; import { AppState, useAppDispatch } from "../../../../../../store"; import PageLayout from "../../../../Common/Layout/PageLayout"; -import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions"; +import { + IAM_SCOPES, + permissionTooltipHelper, +} from "../../../../../../common/SecureComponent/permissions"; import { hasPermission, SecureComponent, @@ -1055,11 +1058,23 @@ const ListObjects = () => { const onDrop = useCallback( (acceptedFiles: any[]) => { - if (acceptedFiles && acceptedFiles.length > 0) { + if (acceptedFiles && acceptedFiles.length > 0 && canUpload) { let newFolderPath: string = acceptedFiles[0].path; 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] ); @@ -1221,6 +1236,10 @@ const ListObjects = () => { 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) => { dispatch(setSelectedObjectView(null)); dispatch(setVersionsModeEnabled({ status: false })); @@ -1272,23 +1291,28 @@ const ListObjects = () => { { action: downloadSelected, label: "Download", - disabled: selectedObjects.length === 0, + disabled: !canDownload || selectedObjects.length === 0, icon: , - tooltip: "Download Selected", + tooltip: canDownload + ? "Download Selected" + : permissionTooltipHelper( + [IAM_SCOPES.S3_GET_OBJECT], + "download objects from this bucket" + ), }, { action: openShare, label: "Share", disabled: selectedObjects.length !== 1 || !canShareFile, icon: , - tooltip: "Share Selected File", + tooltip: canShareFile ? "Share Selected File" : "Sharing unavailable", }, { action: openPreview, label: "Preview", disabled: selectedObjects.length !== 1 || !canPreviewFile, icon: , - tooltip: "Preview Selected File", + tooltip: canPreviewFile ? "Preview Selected File" : "Preview unavailable", }, { action: () => { @@ -1297,10 +1321,13 @@ const ListObjects = () => { label: "Delete", icon: , disabled: - !hasPermission(bucketName, [IAM_SCOPES.S3_DELETE_OBJECT]) || - selectedObjects.length === 0 || - !displayDeleteObject, - tooltip: "Delete Selected Files", + !canDelete || selectedObjects.length === 0 || !displayDeleteObject, + tooltip: canDelete + ? "Delete Selected Files" + : permissionTooltipHelper( + [IAM_SCOPES.S3_DELETE_OBJECT], + "delete objects in this bucket" + ), }, ]; diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectDetailPanel.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectDetailPanel.tsx index 5097f30f8..74ab25b39 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectDetailPanel.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectDetailPanel.tsx @@ -41,7 +41,10 @@ import { niceBytesInt, niceDaysInt, } from "../../../../../../common/utils"; -import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions"; +import { + IAM_SCOPES, + permissionTooltipHelper, +} from "../../../../../../common/SecureComponent/permissions"; import { AppState, useAppDispatch } from "../../../../../../store"; import { @@ -90,6 +93,7 @@ import { updateProgress, } from "../../../../ObjectBrowser/objectBrowserSlice"; import RenameLongFileName from "../../../../ObjectBrowser/RenameLongFilename"; +import TooltipWrapper from "../../../../Common/TooltipWrapper/TooltipWrapper"; const styles = () => createStyles({ @@ -405,6 +409,33 @@ const ObjectDetailPanel = ({ currentItem, [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 = [ { @@ -412,22 +443,28 @@ const ObjectDetailPanel = ({ downloadObject(actualInfo); }, label: "Download", - disabled: - !!actualInfo.is_delete_marker || - !hasPermission(objectResources, [IAM_SCOPES.S3_GET_OBJECT]), + disabled: !!actualInfo.is_delete_marker || !canGetObject, icon: , - tooltip: "Download this Object", + tooltip: canGetObject + ? "Download this Object" + : permissionTooltipHelper( + [IAM_SCOPES.S3_GET_OBJECT], + "download this object" + ), }, { action: () => { shareObject(); }, label: "Share", - disabled: - !!actualInfo.is_delete_marker || - !hasPermission(objectResources, [IAM_SCOPES.S3_GET_OBJECT]), + disabled: !!actualInfo.is_delete_marker || !canGetObject, icon: , - tooltip: "Share this File", + tooltip: canGetObject + ? "Share this File" + : permissionTooltipHelper( + [IAM_SCOPES.S3_GET_OBJECT], + "share this object" + ), }, { action: () => { @@ -437,9 +474,14 @@ const ObjectDetailPanel = ({ disabled: !!actualInfo.is_delete_marker || extensionPreview(currentItem) === "none" || - !hasPermission(objectResources, [IAM_SCOPES.S3_GET_OBJECT]), + !canGetObject, icon: , - tooltip: "Preview this File", + tooltip: canGetObject + ? "Preview this File" + : permissionTooltipHelper( + [IAM_SCOPES.S3_GET_OBJECT], + "preview this object" + ), }, { action: () => { @@ -450,10 +492,17 @@ const ObjectDetailPanel = ({ !locking || !distributedSetup || !!actualInfo.is_delete_marker || - !hasPermission(bucketName, [IAM_SCOPES.S3_PUT_OBJECT_LEGAL_HOLD]) || + !canSetLegalHold || selectedVersion !== "", icon: , - 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, @@ -461,10 +510,21 @@ const ObjectDetailPanel = ({ disabled: !distributedSetup || !!actualInfo.is_delete_marker || - !hasPermission(objectResources, [IAM_SCOPES.S3_GET_OBJECT_RETENTION]) || - selectedVersion !== "", + !canChangeRetention || + selectedVersion !== "" || + !locking, icon: , - 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: () => { @@ -472,11 +532,17 @@ const ObjectDetailPanel = ({ }, label: "Tags", disabled: - !!actualInfo.is_delete_marker || - selectedVersion !== "" || - !hasPermission(objectResources, [IAM_SCOPES.S3_PUT_OBJECT_TAGGING]), + !!actualInfo.is_delete_marker || selectedVersion !== "" || !canSetTags, icon: , - 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: () => { @@ -487,9 +553,14 @@ const ObjectDetailPanel = ({ !distributedSetup || !!actualInfo.is_delete_marker || selectedVersion !== "" || - !hasPermission(objectResources, [IAM_SCOPES.ADMIN_INSPECT_DATA]), + !canInspect, icon: , - tooltip: "Inspect this file", + tooltip: canInspect + ? "Inspect this file" + : permissionTooltipHelper( + [IAM_SCOPES.ADMIN_INSPECT_DATA], + "inspect this file" + ), }, { action: () => { @@ -505,12 +576,19 @@ const ObjectDetailPanel = ({ disabled: !distributedSetup || !(actualInfo.version_id && actualInfo.version_id !== "null") || - !hasPermission(objectResources, [ - IAM_SCOPES.S3_GET_BUCKET_VERSIONING, - IAM_SCOPES.S3_PUT_BUCKET_VERSIONING, - IAM_SCOPES.S3_GET_OBJECT_VERSION, - ]), - tooltip: "Display Versions for this file", + !canChangeVersioning, + tooltip: canChangeVersioning + ? actualInfo.version_id && actualInfo.version_id !== "null" + ? "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} /> - - - + -