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) => {
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 +
"."
);

View File

@@ -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 (
<li key={`action-element-${index.toString()}`}>
<ObjectActionButton
label={actionItem.label}
icon={actionItem.icon}
onClick={actionItem.action}
disabled={actionItem.disabled}
/>
<TooltipWrapper tooltip={actionItem.tooltip || ""}>
<ObjectActionButton
label={actionItem.label}
icon={actionItem.icon}
onClick={actionItem.action}
disabled={actionItem.disabled}
/>
</TooltipWrapper>
</li>
);
})}

View File

@@ -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: <DownloadIcon />,
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: <ShareIcon />,
tooltip: "Share Selected File",
tooltip: canShareFile ? "Share Selected File" : "Sharing unavailable",
},
{
action: openPreview,
label: "Preview",
disabled: selectedObjects.length !== 1 || !canPreviewFile,
icon: <PreviewIcon />,
tooltip: "Preview Selected File",
tooltip: canPreviewFile ? "Preview Selected File" : "Preview unavailable",
},
{
action: () => {
@@ -1297,10 +1321,13 @@ const ListObjects = () => {
label: "Delete",
icon: <DeleteIcon />,
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"
),
},
];

View File

@@ -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: <DownloadIcon />,
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: <ShareIcon />,
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: <PreviewIcon />,
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: <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,
@@ -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: <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: () => {
@@ -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: <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: () => {
@@ -487,9 +553,14 @@ const ObjectDetailPanel = ({
!distributedSetup ||
!!actualInfo.is_delete_marker ||
selectedVersion !== "" ||
!hasPermission(objectResources, [IAM_SCOPES.ADMIN_INSPECT_DATA]),
!canInspect,
icon: <InspectMenuIcon />,
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}
/>
<Grid item xs={12} sx={{ justifyContent: "center", display: "flex" }}>
<SecureComponent
resource={[
bucketName,
currentItem,
[bucketName, actualInfo.name].join("/"),
]}
scopes={[IAM_SCOPES.S3_DELETE_OBJECT]}
errorProps={{ disabled: true }}
<TooltipWrapper
tooltip={
canDelete
? ""
: permissionTooltipHelper(
[IAM_SCOPES.S3_DELETE_OBJECT],
"delete this object"
)
}
>
<Grid
item
xs={12}
sx={{ justifyContent: "center", display: "flex" }}
>
<Button
id={"delete-element-click"}
icon={<DeleteIcon />}
iconLocation={"start"}
fullWidth
variant={"secondary"}
onClick={() => {
setDeleteOpen(true);
}}
disabled={selectedVersion === "" && actualInfo.is_delete_marker}
style={{
width: "calc(100% - 44px)",
margin: "8px 0",
}}
label={`Delete${selectedVersion !== "" ? " version" : ""}`}
/>
</SecureComponent>
</Grid>
<SecureComponent
resource={[
bucketName,
currentItem,
[bucketName, actualInfo.name].join("/"),
]}
scopes={[IAM_SCOPES.S3_DELETE_OBJECT]}
errorProps={{ disabled: true }}
>
<Button
id={"delete-element-click"}
icon={<DeleteIcon />}
iconLocation={"start"}
fullWidth
variant={"secondary"}
onClick={() => {
setDeleteOpen(true);
}}
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}>
<span>Object Info</span>
<ObjectInfoIcon />

View File

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

View File

@@ -22,18 +22,22 @@ import withStyles from "@mui/styles/withStyles";
import createStyles from "@mui/styles/createStyles";
import { Theme } from "@mui/material/styles";
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 { encodeURLString } from "../../../common/utils";
import { BackCaretIcon, CopyIcon, NewPathIcon } from "../../../icons";
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 withSuspense from "../Common/Components/withSuspense";
import { setSnackBarMessage } from "../../../systemSlice";
import { AppState, useAppDispatch } from "../../../store";
import { setVersionsModeEnabled } from "./objectBrowserSlice";
import { Button } from "mds";
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
const CreatePathModal = withSuspense(
React.lazy(
@@ -81,6 +85,8 @@ const BrowserBreadcrumbs = ({
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
const canCreatePath = hasPermission(bucketName, [IAM_SCOPES.S3_PUT_OBJECT]);
let paths = internalPaths;
if (internalPaths !== "") {
@@ -220,16 +226,22 @@ const BrowserBreadcrumbs = ({
<div className={classes.additionalOptions}>{additionalOptions}</div>
</Grid>
{!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
id={"new-path"}
onClick={() => {
setCreateFolderOpen(true);
}}
disabled={
rewindEnabled ||
!hasPermission(bucketName, [IAM_SCOPES.S3_PUT_OBJECT])
}
disabled={rewindEnabled || !canCreatePath}
icon={<NewPathIcon style={{ fill: "#969FA8" }} />}
style={{
whiteSpace: "nowrap",
@@ -237,7 +249,7 @@ const BrowserBreadcrumbs = ({
variant={"regular"}
label={"Create new path"}
/>
</Tooltip>
</TooltipWrapper>
)}
</div>
<div className={classes.breadcrumbsSecond}>{additionalOptions}</div>