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 { LoadingOverlay } from '@mantine/core';
|
||||||
|
|
||||||
|
import type { ModelType } from '../../enums/ModelType';
|
||||||
|
import type { UserRoles } from '../../enums/Roles';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import ClientError from '../errors/ClientError';
|
import ClientError from '../errors/ClientError';
|
||||||
|
import PermissionDenied from '../errors/PermissionDenied';
|
||||||
import ServerError from '../errors/ServerError';
|
import ServerError from '../errors/ServerError';
|
||||||
|
|
||||||
export default function InstanceDetail({
|
export default function InstanceDetail({
|
||||||
status,
|
status,
|
||||||
loading,
|
loading,
|
||||||
children
|
children,
|
||||||
|
requiredRole,
|
||||||
|
requiredPermission
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
status: number;
|
status: number;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
requiredRole?: UserRoles;
|
||||||
|
requiredPermission?: ModelType;
|
||||||
}>) {
|
}>) {
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
@ -27,5 +34,13 @@ export default function InstanceDetail({
|
|||||||
return <ClientError status={status} />;
|
return <ClientError status={status} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (requiredRole && !user.hasViewRole(requiredRole)) {
|
||||||
|
return <PermissionDenied />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredPermission && !user.hasViewPermission(requiredPermission)) {
|
||||||
|
return <PermissionDenied />;
|
||||||
|
}
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
@ -519,7 +519,11 @@ export default function BuildDetail() {
|
|||||||
{holdOrder.modal}
|
{holdOrder.modal}
|
||||||
{issueOrder.modal}
|
{issueOrder.modal}
|
||||||
{completeOrder.modal}
|
{completeOrder.modal}
|
||||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
<InstanceDetail
|
||||||
|
status={requestStatus}
|
||||||
|
loading={instanceQuery.isFetching}
|
||||||
|
requiredRole={UserRoles.build}
|
||||||
|
>
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={`${t`Build Order`}: ${build.reference}`}
|
title={`${t`Build Order`}: ${build.reference}`}
|
||||||
|
@ -398,7 +398,11 @@ export default function SupplierPartDetail() {
|
|||||||
{deleteSupplierPart.modal}
|
{deleteSupplierPart.modal}
|
||||||
{duplicateSupplierPart.modal}
|
{duplicateSupplierPart.modal}
|
||||||
{editSupplierPart.modal}
|
{editSupplierPart.modal}
|
||||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
<InstanceDetail
|
||||||
|
status={requestStatus}
|
||||||
|
loading={instanceQuery.isFetching}
|
||||||
|
requiredRole={UserRoles.purchase_order}
|
||||||
|
>
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Supplier Part`}
|
title={t`Supplier Part`}
|
||||||
|
@ -318,6 +318,7 @@ export default function CategoryDetail() {
|
|||||||
<InstanceDetail
|
<InstanceDetail
|
||||||
status={requestStatus}
|
status={requestStatus}
|
||||||
loading={id ? instanceQuery.isFetching : false}
|
loading={id ? instanceQuery.isFetching : false}
|
||||||
|
requiredRole={UserRoles.part_category}
|
||||||
>
|
>
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
<LoadingOverlay visible={instanceQuery.isFetching} />
|
<LoadingOverlay visible={instanceQuery.isFetching} />
|
||||||
|
@ -970,18 +970,24 @@ export default function PartDetail() {
|
|||||||
{editPart.modal}
|
{editPart.modal}
|
||||||
{deletePart.modal}
|
{deletePart.modal}
|
||||||
{orderPartsWizard.wizard}
|
{orderPartsWizard.wizard}
|
||||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
<InstanceDetail
|
||||||
|
status={requestStatus}
|
||||||
|
loading={instanceQuery.isFetching}
|
||||||
|
requiredRole={UserRoles.part}
|
||||||
|
>
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
<NavigationTree
|
{user.hasViewRole(UserRoles.part_category) && (
|
||||||
title={t`Part Categories`}
|
<NavigationTree
|
||||||
modelType={ModelType.partcategory}
|
title={t`Part Categories`}
|
||||||
endpoint={ApiEndpoints.category_tree}
|
modelType={ModelType.partcategory}
|
||||||
opened={treeOpen}
|
endpoint={ApiEndpoints.category_tree}
|
||||||
onClose={() => {
|
opened={treeOpen}
|
||||||
setTreeOpen(false);
|
onClose={() => {
|
||||||
}}
|
setTreeOpen(false);
|
||||||
selectedId={part?.category}
|
}}
|
||||||
/>
|
selectedId={part?.category}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={`${t`Part`}: ${part.full_name}`}
|
title={`${t`Part`}: ${part.full_name}`}
|
||||||
icon={
|
icon={
|
||||||
@ -992,9 +998,13 @@ export default function PartDetail() {
|
|||||||
subtitle={part.description}
|
subtitle={part.description}
|
||||||
imageUrl={part.image}
|
imageUrl={part.image}
|
||||||
badges={badges}
|
badges={badges}
|
||||||
breadcrumbs={breadcrumbs}
|
breadcrumbs={
|
||||||
|
user.hasViewRole(UserRoles.part_category)
|
||||||
|
? breadcrumbs
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
breadcrumbAction={() => {
|
breadcrumbAction={() => {
|
||||||
setTreeOpen(true); // Open the category tree
|
setTreeOpen(true);
|
||||||
}}
|
}}
|
||||||
editAction={editPart.open}
|
editAction={editPart.open}
|
||||||
editEnabled={user.hasChangeRole(UserRoles.part)}
|
editEnabled={user.hasChangeRole(UserRoles.part)}
|
||||||
|
@ -483,7 +483,11 @@ export default function PurchaseOrderDetail() {
|
|||||||
{completeOrder.modal}
|
{completeOrder.modal}
|
||||||
{editPurchaseOrder.modal}
|
{editPurchaseOrder.modal}
|
||||||
{duplicatePurchaseOrder.modal}
|
{duplicatePurchaseOrder.modal}
|
||||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
<InstanceDetail
|
||||||
|
status={requestStatus}
|
||||||
|
loading={instanceQuery.isFetching}
|
||||||
|
requiredRole={UserRoles.purchase_order}
|
||||||
|
>
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={`${t`Purchase Order`}: ${order.reference}`}
|
title={`${t`Purchase Order`}: ${order.reference}`}
|
||||||
|
@ -24,7 +24,8 @@ export default function PurchasingIndex() {
|
|||||||
name: 'purchaseorders',
|
name: 'purchaseorders',
|
||||||
label: t`Purchase Orders`,
|
label: t`Purchase Orders`,
|
||||||
icon: <IconShoppingCart />,
|
icon: <IconShoppingCart />,
|
||||||
content: <PurchaseOrderTable />
|
content: <PurchaseOrderTable />,
|
||||||
|
hidden: !user.hasViewRole(UserRoles.purchase_order)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'suppliers',
|
name: 'suppliers',
|
||||||
@ -49,7 +50,7 @@ export default function PurchasingIndex() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, []);
|
}, [user]);
|
||||||
|
|
||||||
if (!user.isLoggedIn() || !user.hasViewRole(UserRoles.purchase_order)) {
|
if (!user.isLoggedIn() || !user.hasViewRole(UserRoles.purchase_order)) {
|
||||||
return <PermissionDenied />;
|
return <PermissionDenied />;
|
||||||
|
@ -470,7 +470,11 @@ export default function ReturnOrderDetail() {
|
|||||||
{holdOrder.modal}
|
{holdOrder.modal}
|
||||||
{completeOrder.modal}
|
{completeOrder.modal}
|
||||||
{duplicateReturnOrder.modal}
|
{duplicateReturnOrder.modal}
|
||||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
<InstanceDetail
|
||||||
|
status={requestStatus}
|
||||||
|
loading={instanceQuery.isFetching}
|
||||||
|
requiredRole={UserRoles.return_order}
|
||||||
|
>
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={`${t`Return Order`}: ${order.reference}`}
|
title={`${t`Return Order`}: ${order.reference}`}
|
||||||
|
@ -25,13 +25,15 @@ export default function PurchasingIndex() {
|
|||||||
name: 'salesorders',
|
name: 'salesorders',
|
||||||
label: t`Sales Orders`,
|
label: t`Sales Orders`,
|
||||||
icon: <IconTruckDelivery />,
|
icon: <IconTruckDelivery />,
|
||||||
content: <SalesOrderTable />
|
content: <SalesOrderTable />,
|
||||||
|
hidden: !user.hasViewRole(UserRoles.sales_order)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'returnorders',
|
name: 'returnorders',
|
||||||
label: t`Return Orders`,
|
label: t`Return Orders`,
|
||||||
icon: <IconTruckReturn />,
|
icon: <IconTruckReturn />,
|
||||||
content: <ReturnOrderTable />
|
content: <ReturnOrderTable />,
|
||||||
|
hidden: !user.hasViewRole(UserRoles.return_order)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'suppliers',
|
name: 'suppliers',
|
||||||
@ -42,7 +44,7 @@ export default function PurchasingIndex() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, []);
|
}, [user]);
|
||||||
|
|
||||||
if (!user.isLoggedIn() || !user.hasViewRole(UserRoles.sales_order)) {
|
if (!user.isLoggedIn() || !user.hasViewRole(UserRoles.sales_order)) {
|
||||||
return <PermissionDenied />;
|
return <PermissionDenied />;
|
||||||
|
@ -534,7 +534,11 @@ export default function SalesOrderDetail() {
|
|||||||
{completeOrder.modal}
|
{completeOrder.modal}
|
||||||
{editSalesOrder.modal}
|
{editSalesOrder.modal}
|
||||||
{duplicateSalesOrder.modal}
|
{duplicateSalesOrder.modal}
|
||||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
<InstanceDetail
|
||||||
|
status={requestStatus}
|
||||||
|
loading={instanceQuery.isFetching}
|
||||||
|
requiredRole={UserRoles.sales_order}
|
||||||
|
>
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={`${t`Sales Order`}: ${order.reference}`}
|
title={`${t`Sales Order`}: ${order.reference}`}
|
||||||
|
@ -352,6 +352,7 @@ export default function SalesOrderShipmentDetail() {
|
|||||||
<InstanceDetail
|
<InstanceDetail
|
||||||
status={shipmentStatus}
|
status={shipmentStatus}
|
||||||
loading={shipmentQuery.isFetching || customerQuery.isFetching}
|
loading={shipmentQuery.isFetching || customerQuery.isFetching}
|
||||||
|
requiredRole={UserRoles.sales_order}
|
||||||
>
|
>
|
||||||
<Stack gap='xs'>
|
<Stack gap='xs'>
|
||||||
<PageDetail
|
<PageDetail
|
||||||
|
@ -363,6 +363,7 @@ export default function Stock() {
|
|||||||
<InstanceDetail
|
<InstanceDetail
|
||||||
status={requestStatus}
|
status={requestStatus}
|
||||||
loading={id ? instanceQuery.isFetching : false}
|
loading={id ? instanceQuery.isFetching : false}
|
||||||
|
requiredRole={UserRoles.stock_location}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<NavigationTree
|
<NavigationTree
|
||||||
|
@ -884,16 +884,22 @@ export default function StockDetail() {
|
|||||||
}, [stockitem, instanceQuery, enableExpiry]);
|
}, [stockitem, instanceQuery, enableExpiry]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
|
<InstanceDetail
|
||||||
|
requiredRole={UserRoles.stock}
|
||||||
|
status={requestStatus}
|
||||||
|
loading={instanceQuery.isFetching}
|
||||||
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<NavigationTree
|
{user.hasViewRole(UserRoles.stock_location) && (
|
||||||
title={t`Stock Locations`}
|
<NavigationTree
|
||||||
modelType={ModelType.stocklocation}
|
title={t`Stock Locations`}
|
||||||
endpoint={ApiEndpoints.stock_location_tree}
|
modelType={ModelType.stocklocation}
|
||||||
opened={treeOpen}
|
endpoint={ApiEndpoints.stock_location_tree}
|
||||||
onClose={() => setTreeOpen(false)}
|
opened={treeOpen}
|
||||||
selectedId={stockitem?.location}
|
onClose={() => setTreeOpen(false)}
|
||||||
/>
|
selectedId={stockitem?.location}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<PageDetail
|
<PageDetail
|
||||||
title={t`Stock Item`}
|
title={t`Stock Item`}
|
||||||
subtitle={stockitem.part_detail?.full_name}
|
subtitle={stockitem.part_detail?.full_name}
|
||||||
@ -901,7 +907,9 @@ export default function StockDetail() {
|
|||||||
editAction={editStockItem.open}
|
editAction={editStockItem.open}
|
||||||
editEnabled={user.hasChangePermission(ModelType.stockitem)}
|
editEnabled={user.hasChangePermission(ModelType.stockitem)}
|
||||||
badges={stockBadges}
|
badges={stockBadges}
|
||||||
breadcrumbs={breadcrumbs}
|
breadcrumbs={
|
||||||
|
user.hasViewRole(UserRoles.stock_location) ? breadcrumbs : undefined
|
||||||
|
}
|
||||||
breadcrumbAction={() => {
|
breadcrumbAction={() => {
|
||||||
setTreeOpen(true);
|
setTreeOpen(true);
|
||||||
}}
|
}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user