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:
parent
cd0ee7dbab
commit
5e79c6906c
@ -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}</>;
|
||||
}
|
||||
|
@ -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}`}
|
||||
|
@ -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`}
|
||||
|
@ -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} />
|
||||
|
@ -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)}
|
||||
|
@ -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}`}
|
||||
|
@ -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 />;
|
||||
|
@ -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}`}
|
||||
|
@ -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 />;
|
||||
|
@ -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}`}
|
||||
|
@ -352,6 +352,7 @@ export default function SalesOrderShipmentDetail() {
|
||||
<InstanceDetail
|
||||
status={shipmentStatus}
|
||||
loading={shipmentQuery.isFetching || customerQuery.isFetching}
|
||||
requiredRole={UserRoles.sales_order}
|
||||
>
|
||||
<Stack gap='xs'>
|
||||
<PageDetail
|
||||
|
@ -363,6 +363,7 @@ export default function Stock() {
|
||||
<InstanceDetail
|
||||
status={requestStatus}
|
||||
loading={id ? instanceQuery.isFetching : false}
|
||||
requiredRole={UserRoles.stock_location}
|
||||
>
|
||||
<Stack>
|
||||
<NavigationTree
|
||||
|
@ -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);
|
||||
}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user