2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

[UI] Permission Enhancements (#8785)

* Update page permissions

- Add permission check to <InstanceDetail>
- HIde breadcrumbs and tree for part
- Hide breadcrumbs and tree for stock

* Additional permissions checks
This commit is contained in:
Oliver 2024-12-29 17:00:27 +11:00 committed by GitHub
parent cd0ee7dbab
commit 5e79c6906c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 93 additions and 34 deletions

View File

@ -1,17 +1,24 @@
import { LoadingOverlay } from '@mantine/core';
import type { ModelType } from '../../enums/ModelType';
import type { UserRoles } from '../../enums/Roles';
import { useUserState } from '../../states/UserState';
import ClientError from '../errors/ClientError';
import PermissionDenied from '../errors/PermissionDenied';
import ServerError from '../errors/ServerError';
export default function InstanceDetail({
status,
loading,
children
children,
requiredRole,
requiredPermission
}: Readonly<{
status: number;
loading: boolean;
children: React.ReactNode;
requiredRole?: UserRoles;
requiredPermission?: ModelType;
}>) {
const user = useUserState();
@ -27,5 +34,13 @@ export default function InstanceDetail({
return <ClientError status={status} />;
}
if (requiredRole && !user.hasViewRole(requiredRole)) {
return <PermissionDenied />;
}
if (requiredPermission && !user.hasViewPermission(requiredPermission)) {
return <PermissionDenied />;
}
return <>{children}</>;
}

View File

@ -519,7 +519,11 @@ export default function BuildDetail() {
{holdOrder.modal}
{issueOrder.modal}
{completeOrder.modal}
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
<InstanceDetail
status={requestStatus}
loading={instanceQuery.isFetching}
requiredRole={UserRoles.build}
>
<Stack gap='xs'>
<PageDetail
title={`${t`Build Order`}: ${build.reference}`}

View File

@ -398,7 +398,11 @@ export default function SupplierPartDetail() {
{deleteSupplierPart.modal}
{duplicateSupplierPart.modal}
{editSupplierPart.modal}
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
<InstanceDetail
status={requestStatus}
loading={instanceQuery.isFetching}
requiredRole={UserRoles.purchase_order}
>
<Stack gap='xs'>
<PageDetail
title={t`Supplier Part`}

View File

@ -318,6 +318,7 @@ export default function CategoryDetail() {
<InstanceDetail
status={requestStatus}
loading={id ? instanceQuery.isFetching : false}
requiredRole={UserRoles.part_category}
>
<Stack gap='xs'>
<LoadingOverlay visible={instanceQuery.isFetching} />

View File

@ -970,18 +970,24 @@ export default function PartDetail() {
{editPart.modal}
{deletePart.modal}
{orderPartsWizard.wizard}
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
<InstanceDetail
status={requestStatus}
loading={instanceQuery.isFetching}
requiredRole={UserRoles.part}
>
<Stack gap='xs'>
<NavigationTree
title={t`Part Categories`}
modelType={ModelType.partcategory}
endpoint={ApiEndpoints.category_tree}
opened={treeOpen}
onClose={() => {
setTreeOpen(false);
}}
selectedId={part?.category}
/>
{user.hasViewRole(UserRoles.part_category) && (
<NavigationTree
title={t`Part Categories`}
modelType={ModelType.partcategory}
endpoint={ApiEndpoints.category_tree}
opened={treeOpen}
onClose={() => {
setTreeOpen(false);
}}
selectedId={part?.category}
/>
)}
<PageDetail
title={`${t`Part`}: ${part.full_name}`}
icon={
@ -992,9 +998,13 @@ export default function PartDetail() {
subtitle={part.description}
imageUrl={part.image}
badges={badges}
breadcrumbs={breadcrumbs}
breadcrumbs={
user.hasViewRole(UserRoles.part_category)
? breadcrumbs
: undefined
}
breadcrumbAction={() => {
setTreeOpen(true); // Open the category tree
setTreeOpen(true);
}}
editAction={editPart.open}
editEnabled={user.hasChangeRole(UserRoles.part)}

View File

@ -483,7 +483,11 @@ export default function PurchaseOrderDetail() {
{completeOrder.modal}
{editPurchaseOrder.modal}
{duplicatePurchaseOrder.modal}
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
<InstanceDetail
status={requestStatus}
loading={instanceQuery.isFetching}
requiredRole={UserRoles.purchase_order}
>
<Stack gap='xs'>
<PageDetail
title={`${t`Purchase Order`}: ${order.reference}`}

View File

@ -24,7 +24,8 @@ export default function PurchasingIndex() {
name: 'purchaseorders',
label: t`Purchase Orders`,
icon: <IconShoppingCart />,
content: <PurchaseOrderTable />
content: <PurchaseOrderTable />,
hidden: !user.hasViewRole(UserRoles.purchase_order)
},
{
name: 'suppliers',
@ -49,7 +50,7 @@ export default function PurchasingIndex() {
)
}
];
}, []);
}, [user]);
if (!user.isLoggedIn() || !user.hasViewRole(UserRoles.purchase_order)) {
return <PermissionDenied />;

View File

@ -470,7 +470,11 @@ export default function ReturnOrderDetail() {
{holdOrder.modal}
{completeOrder.modal}
{duplicateReturnOrder.modal}
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
<InstanceDetail
status={requestStatus}
loading={instanceQuery.isFetching}
requiredRole={UserRoles.return_order}
>
<Stack gap='xs'>
<PageDetail
title={`${t`Return Order`}: ${order.reference}`}

View File

@ -25,13 +25,15 @@ export default function PurchasingIndex() {
name: 'salesorders',
label: t`Sales Orders`,
icon: <IconTruckDelivery />,
content: <SalesOrderTable />
content: <SalesOrderTable />,
hidden: !user.hasViewRole(UserRoles.sales_order)
},
{
name: 'returnorders',
label: t`Return Orders`,
icon: <IconTruckReturn />,
content: <ReturnOrderTable />
content: <ReturnOrderTable />,
hidden: !user.hasViewRole(UserRoles.return_order)
},
{
name: 'suppliers',
@ -42,7 +44,7 @@ export default function PurchasingIndex() {
)
}
];
}, []);
}, [user]);
if (!user.isLoggedIn() || !user.hasViewRole(UserRoles.sales_order)) {
return <PermissionDenied />;

View File

@ -534,7 +534,11 @@ export default function SalesOrderDetail() {
{completeOrder.modal}
{editSalesOrder.modal}
{duplicateSalesOrder.modal}
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
<InstanceDetail
status={requestStatus}
loading={instanceQuery.isFetching}
requiredRole={UserRoles.sales_order}
>
<Stack gap='xs'>
<PageDetail
title={`${t`Sales Order`}: ${order.reference}`}

View File

@ -352,6 +352,7 @@ export default function SalesOrderShipmentDetail() {
<InstanceDetail
status={shipmentStatus}
loading={shipmentQuery.isFetching || customerQuery.isFetching}
requiredRole={UserRoles.sales_order}
>
<Stack gap='xs'>
<PageDetail

View File

@ -363,6 +363,7 @@ export default function Stock() {
<InstanceDetail
status={requestStatus}
loading={id ? instanceQuery.isFetching : false}
requiredRole={UserRoles.stock_location}
>
<Stack>
<NavigationTree

View File

@ -884,16 +884,22 @@ export default function StockDetail() {
}, [stockitem, instanceQuery, enableExpiry]);
return (
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
<InstanceDetail
requiredRole={UserRoles.stock}
status={requestStatus}
loading={instanceQuery.isFetching}
>
<Stack>
<NavigationTree
title={t`Stock Locations`}
modelType={ModelType.stocklocation}
endpoint={ApiEndpoints.stock_location_tree}
opened={treeOpen}
onClose={() => setTreeOpen(false)}
selectedId={stockitem?.location}
/>
{user.hasViewRole(UserRoles.stock_location) && (
<NavigationTree
title={t`Stock Locations`}
modelType={ModelType.stocklocation}
endpoint={ApiEndpoints.stock_location_tree}
opened={treeOpen}
onClose={() => setTreeOpen(false)}
selectedId={stockitem?.location}
/>
)}
<PageDetail
title={t`Stock Item`}
subtitle={stockitem.part_detail?.full_name}
@ -901,7 +907,9 @@ export default function StockDetail() {
editAction={editStockItem.open}
editEnabled={user.hasChangePermission(ModelType.stockitem)}
badges={stockBadges}
breadcrumbs={breadcrumbs}
breadcrumbs={
user.hasViewRole(UserRoles.stock_location) ? breadcrumbs : undefined
}
breadcrumbAction={() => {
setTreeOpen(true);
}}