Implement PermissionsProvider and use in assets page
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
@@ -1,53 +1,54 @@
|
|||||||
import { Link, Navigate, Outlet, useParams } from "react-router";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import {
|
import {
|
||||||
|
Avatar,
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
DropdownSeparator,
|
DropdownSeparator,
|
||||||
IconArrowBoxLeft,
|
IconArrowBoxLeft,
|
||||||
IconBank,
|
IconBank,
|
||||||
IconBook,
|
IconBook,
|
||||||
|
IconBox,
|
||||||
|
IconCalendar1,
|
||||||
IconCheckmark1,
|
IconCheckmark1,
|
||||||
|
IconChevronGrabberVertical,
|
||||||
|
IconCircleProgress,
|
||||||
IconCircleQuestionmark,
|
IconCircleQuestionmark,
|
||||||
IconClock,
|
IconClock,
|
||||||
IconCrossLargeX,
|
IconCrossLargeX,
|
||||||
IconFire3,
|
IconFire3,
|
||||||
IconGroup1,
|
IconGroup1,
|
||||||
IconInboxEmpty,
|
IconInboxEmpty,
|
||||||
|
IconKey,
|
||||||
|
IconListStack,
|
||||||
|
IconLock,
|
||||||
|
IconMedal,
|
||||||
IconPageTextLine,
|
IconPageTextLine,
|
||||||
|
IconPeopleAdd,
|
||||||
|
IconPlusLarge,
|
||||||
|
IconRotateCw,
|
||||||
IconSettingsGear2,
|
IconSettingsGear2,
|
||||||
|
IconShield,
|
||||||
IconStore,
|
IconStore,
|
||||||
IconTodo,
|
IconTodo,
|
||||||
IconListStack,
|
|
||||||
IconBox,
|
|
||||||
IconShield,
|
|
||||||
IconRotateCw,
|
|
||||||
IconCircleProgress,
|
|
||||||
IconMedal,
|
|
||||||
IconCalendar1,
|
|
||||||
Layout,
|
Layout,
|
||||||
SidebarItem,
|
SidebarItem,
|
||||||
UserDropdown as UserDropdownRoot,
|
|
||||||
UserDropdownItem,
|
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Dropdown,
|
UserDropdownItem,
|
||||||
Button,
|
UserDropdown as UserDropdownRoot,
|
||||||
DropdownItem,
|
useToast,
|
||||||
IconChevronGrabberVertical,
|
|
||||||
IconPlusLarge,
|
|
||||||
Avatar,
|
|
||||||
IconPeopleAdd,
|
|
||||||
Badge,
|
|
||||||
IconKey,
|
|
||||||
IconLock,
|
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { Suspense, useEffect, useState } from "react";
|
||||||
import { graphql } from "relay-runtime";
|
|
||||||
import { useLazyLoadQuery } from "react-relay";
|
|
||||||
import type { MainLayoutQuery as MainLayoutQueryType } from "./__generated__/MainLayoutQuery.graphql";
|
|
||||||
import { Suspense, useState, useEffect } from "react";
|
|
||||||
import { useToast } from "@probo/ui";
|
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
|
import { useLazyLoadQuery } from "react-relay";
|
||||||
|
import { Link, Navigate, Outlet, useParams } from "react-router";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import type { MainLayoutQuery as MainLayoutQueryType } from "./__generated__/MainLayoutQuery.graphql";
|
||||||
import { PageError } from "/components/PageError";
|
import { PageError } from "/components/PageError";
|
||||||
import { buildEndpoint } from "/providers/RelayProviders";
|
|
||||||
import { Authorized } from "/permissions";
|
import { Authorized } from "/permissions";
|
||||||
|
import { PermissionsProvider } from "/providers/PermissionsProvider";
|
||||||
|
import { buildEndpoint } from "/providers/RelayProviders";
|
||||||
|
|
||||||
const MainLayoutQuery = graphql`
|
const MainLayoutQuery = graphql`
|
||||||
query MainLayoutQuery($organizationId: ID!) {
|
query MainLayoutQuery($organizationId: ID!) {
|
||||||
@@ -82,7 +83,9 @@ export function MainLayout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<Skeleton className="w-full h-screen" />}>
|
<Suspense fallback={<Skeleton className="w-full h-screen" />}>
|
||||||
|
<PermissionsProvider>
|
||||||
<MainLayoutContent organizationId={organizationId} prefix={prefix} />
|
<MainLayoutContent organizationId={organizationId} prefix={prefix} />
|
||||||
|
</PermissionsProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,29 @@
|
|||||||
|
import { usePageTitle } from "@probo/hooks";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
IconPlusLarge,
|
IconPlusLarge,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { use } from "react";
|
||||||
import { usePageTitle } from "@probo/hooks";
|
|
||||||
import {
|
import {
|
||||||
graphql,
|
graphql,
|
||||||
type PreloadedQuery,
|
|
||||||
usePaginationFragment,
|
usePaginationFragment,
|
||||||
usePreloadedQuery,
|
usePreloadedQuery,
|
||||||
|
type PreloadedQuery,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
|
import { AssetsTable } from "../../../components/assets/AssetsTable";
|
||||||
|
import { ReadOnlyAssetsTable } from "../../../components/assets/ReadOnlyAssetsTable";
|
||||||
|
import type { AssetsPageFragment$key } from "./__generated__/AssetsPageFragment.graphql";
|
||||||
|
import { CreateAssetDialog } from "./dialogs/CreateAssetDialog";
|
||||||
|
import { SnapshotBanner } from "/components/SnapshotBanner";
|
||||||
import {
|
import {
|
||||||
assetsQuery,
|
assetsQuery,
|
||||||
} from "/hooks/graph/AssetGraph";
|
} from "/hooks/graph/AssetGraph";
|
||||||
import type { AssetGraphListQuery } from "/hooks/graph/__generated__/AssetGraphListQuery.graphql";
|
import type { AssetGraphListQuery } from "/hooks/graph/__generated__/AssetGraphListQuery.graphql";
|
||||||
import type { AssetsPageFragment$key } from "./__generated__/AssetsPageFragment.graphql";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { SnapshotBanner } from "/components/SnapshotBanner";
|
import { PermissionsContext } from "/providers/PermissionsProvider";
|
||||||
import { CreateAssetDialog } from "./dialogs/CreateAssetDialog";
|
|
||||||
import { Authorized, isAuthorized } from "/permissions";
|
|
||||||
import { AssetsTable } from "../../../components/assets/AssetsTable";
|
|
||||||
import { ReadOnlyAssetsTable } from "../../../components/assets/ReadOnlyAssetsTable";
|
|
||||||
|
|
||||||
const paginatedAssetsFragment = graphql`
|
const paginatedAssetsFragment = graphql`
|
||||||
fragment AssetsPageFragment on Organization
|
fragment AssetsPageFragment on Organization
|
||||||
@@ -90,9 +91,10 @@ export default function AssetsPage(props: Props) {
|
|||||||
const assets = pagination.data.assets?.edges.map((edge) => edge.node);
|
const assets = pagination.data.assets?.edges.map((edge) => edge.node);
|
||||||
const connectionId = pagination.data.assets.__id;
|
const connectionId = pagination.data.assets.__id;
|
||||||
|
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
const canWrite = (
|
const canWrite = (
|
||||||
isAuthorized(organizationId, "Asset", "updateAsset") ||
|
isAuthorized("Asset", "updateAsset") ||
|
||||||
isAuthorized(organizationId, "Asset", "deleteAsset")
|
isAuthorized("Asset", "deleteAsset")
|
||||||
);
|
);
|
||||||
usePageTitle(__("Assets"));
|
usePageTitle(__("Assets"));
|
||||||
|
|
||||||
@@ -105,15 +107,13 @@ export default function AssetsPage(props: Props) {
|
|||||||
"Manage your organization's assets and their classifications.",
|
"Manage your organization's assets and their classifications.",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{!isSnapshotMode && (
|
{!isSnapshotMode && isAuthorized("Organization", "createAsset") && (
|
||||||
<Authorized entity="Organization" action="createAsset">
|
|
||||||
<CreateAssetDialog
|
<CreateAssetDialog
|
||||||
connection={connectionId}
|
connection={connectionId}
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
>
|
>
|
||||||
<Button icon={IconPlusLarge}>{__("Add asset")}</Button>
|
<Button icon={IconPlusLarge}>{__("Add asset")}</Button>
|
||||||
</CreateAssetDialog>
|
</CreateAssetDialog>
|
||||||
</Authorized>
|
|
||||||
)}
|
)}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
{isSnapshotMode || !canWrite ?
|
{isSnapshotMode || !canWrite ?
|
||||||
|
|||||||
51
apps/console/src/providers/PermissionsProvider.tsx
Normal file
51
apps/console/src/providers/PermissionsProvider.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
import { createContext, type PropsWithChildren } from "react";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
|
||||||
|
enum Role {
|
||||||
|
OWNER = "OWNER",
|
||||||
|
ADMIN = "ADMIN",
|
||||||
|
VIEWER = "VIEWER",
|
||||||
|
}
|
||||||
|
|
||||||
|
type PermissionsResponse = {
|
||||||
|
permissions: Record<string, Record<string, boolean>>;
|
||||||
|
role: Role;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PermissionsContextType = {
|
||||||
|
isAuthorized: (entity: string, action: string) => boolean;
|
||||||
|
} & PermissionsResponse;
|
||||||
|
|
||||||
|
export const PermissionsContext = createContext<PermissionsContextType>({
|
||||||
|
permissions: {},
|
||||||
|
role: Role.VIEWER,
|
||||||
|
isAuthorized: () => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function PermissionsProvider(props: PropsWithChildren) {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
|
||||||
|
const { data } = useSuspenseQuery<PermissionsResponse>({
|
||||||
|
queryKey: ["permissions", organizationId],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(`/authz/${organizationId}/permissions`, { credentials: "include" });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch permissions");
|
||||||
|
}
|
||||||
|
return response.json() as Promise<PermissionsResponse>;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isAuthorized = (entity: string, action: string) => {
|
||||||
|
return data.permissions[entity]?.[action] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PermissionsContext value={{ ...data, isAuthorized }}>
|
||||||
|
{children}
|
||||||
|
</PermissionsContext>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user