From b811e69158eedddbf39d7410e36ec585df04eb14 Mon Sep 17 00:00:00 2001 From: Beng Eu Date: Fri, 27 May 2022 18:26:57 +0800 Subject: [PATCH] feat: apply permissions to menus --- .../NavigationBar/NavigationBar.tsx | 36 +++--- .../NavigationBar/NavigationBar.utils.tsx | 117 +++++++++--------- .../SettingsLayout/SettingsMenu.utils.ts | 27 ++-- .../components/ui/ProductMenu/ProductMenu.tsx | 26 ++-- .../ui/ProductMenu/ProductMenu.types.ts | 1 + studio/components/ui/ui.types.ts | 1 + 6 files changed, 115 insertions(+), 93 deletions(-) diff --git a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx index 335b31fd207..09d2e7d94dc 100644 --- a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx +++ b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx @@ -3,11 +3,11 @@ import Link from 'next/link' import { observer } from 'mobx-react-lite' import { useRouter } from 'next/router' import { isUndefined } from 'lodash' -import { Button, IconHome, Dropdown, IconUser, IconSettings } from '@supabase/ui' +import { Button, Dropdown, IconHome, IconSettings, IconUser } from '@supabase/ui' import { IS_PLATFORM } from 'lib/constants' import { useStore } from 'hooks' -import { generateProductRoutes, generateOtherRoutes } from './NavigationBar.utils' +import { generateOtherRoutes, generateProductRoutes } from './NavigationBar.utils' import NavigationIconButton from './NavigationIconButton' interface Props {} @@ -55,21 +55,25 @@ const NavigationBar: FC = ({}) => { }} />
- {productRoutes.map((route) => ( - - ))} + {productRoutes + .filter(({ hidden }) => !hidden) + .map((route) => ( + + ))}
- {otherRoutes.map((route) => ( - - ))} + {otherRoutes + .filter(({ hidden }) => !hidden) + .map((route) => ( + + ))}
    { const functionsUi = useFlag('functionsUi') @@ -33,6 +34,7 @@ export const generateProductRoutes = (ref: string, project?: ProjectBase): Route /> ), link: isProjectBuilding ? buildingUrl : `/project/${ref}/editor`, + hidden: !usePermissions(PermissionAction.SQL_SELECT, 'postgres.public'), }, { key: 'auth', @@ -40,16 +42,13 @@ export const generateProductRoutes = (ref: string, project?: ProjectBase): Route icon: , link: isProjectBuilding ? buildingUrl : `/project/${ref}/auth/users`, }, - ...(IS_PLATFORM - ? [ - { - key: 'storage', - label: 'Storage', - icon: , - link: isProjectBuilding ? buildingUrl : `/project/${ref}/storage/buckets`, - }, - ] - : []), + { + key: 'storage', + label: 'Storage', + icon: , + link: isProjectBuilding ? buildingUrl : `/project/${ref}/storage/buckets`, + hidden: !IS_PLATFORM, + }, { key: 'sql', label: 'SQL Editor', @@ -68,59 +67,63 @@ export const generateProductRoutes = (ref: string, project?: ProjectBase): Route icon: , link: isProjectBuilding ? buildingUrl : `/project/${ref}/database/tables`, }, - ...(IS_PLATFORM && functionsUi - ? [ - { - key: 'functions', - label: 'Functions', - icon: , - link: isProjectBuilding ? buildingUrl : `/project/${ref}/functions`, - }, - ] - : []), + { + key: 'functions', + label: 'Functions', + icon: , + link: isProjectBuilding ? buildingUrl : `/project/${ref}/functions`, + hidden: !IS_PLATFORM || !functionsUi, + }, ] } -export const generateOtherRoutes = (ref: string, project?: ProjectBase) => { +export const generateOtherRoutes = (ref: string, project?: ProjectBase): Route[] => { const isProjectBuilding = project?.status !== PROJECT_STATUS.ACTIVE_HEALTHY const buildingUrl = `/project/${ref}/building` + const canReadStats = usePermissions( + PermissionAction.SQL_SELECT, + 'postgres.public.stats_daily_projects', + { + resource: { type: 'report' }, + } + ) + const canReadReport = usePermissions( + PermissionAction.SQL_SELECT, + 'postgres.public.user_content', + { + resource: { type: 'report' }, + } + ) + return [ - ...(IS_PLATFORM - ? [ - { - key: 'logs-explorer', - label: 'Logs Explorer', - icon: , - link: isProjectBuilding ? buildingUrl : `/project/${ref}/logs-explorer`, - }, - ] - : []), - ...(IS_PLATFORM - ? [ - { - key: 'reports', - label: 'Reports', - icon: , - link: isProjectBuilding ? buildingUrl : `/project/${ref}/reports`, - }, - ] - : []), + { + key: 'logs-explorer', + label: 'Logs Explorer', + icon: , + link: isProjectBuilding ? buildingUrl : `/project/${ref}/logs-explorer`, + hidden: !IS_PLATFORM, + }, + { + key: 'reports', + label: 'Reports', + icon: , + link: isProjectBuilding ? buildingUrl : `/project/${ref}/reports`, + hidden: !IS_PLATFORM || !canReadReport || !canReadStats, + }, { key: 'api', label: 'API', icon: , link: isProjectBuilding ? buildingUrl : `/project/${ref}/api`, }, - ...(IS_PLATFORM - ? [ - { - key: 'settings', - label: 'Settings', - icon: , - link: `/project/${ref}/settings/general`, - }, - ] - : []), + { + key: 'settings', + label: 'Settings', + icon: , + link: `/project/${ref}/settings/general`, + hidden: + !IS_PLATFORM || !usePermissions(PermissionAction.SQL_SELECT, 'postgres.public.projects'), + }, ] } diff --git a/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts b/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts index fba642c73da..07746b5d33a 100644 --- a/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts +++ b/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts @@ -1,16 +1,26 @@ import { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types' import { PROJECT_STATUS } from 'lib/constants' import { ProjectBase } from 'types' +import { usePermissions } from '../../../hooks' +import { PermissionAction } from '@supabase/shared-types/out/constants' export const generateSettingsMenu = (ref: string, project?: ProjectBase): ProductMenuGroup[] => { const isProjectBuilding = project?.status !== PROJECT_STATUS.ACTIVE_HEALTHY const buildingUrl = `/project/${ref}/building` + const canReadInvoices = usePermissions(PermissionAction.BILLING_READ, 'stripe.invoices') + const canReadSubscriptions = usePermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions') + return [ { title: 'Project settings', items: [ - { name: 'General', key: 'general', url: `/project/${ref}/settings/general`, items: [] }, + { + name: 'General', + key: 'general', + url: `/project/${ref}/settings/general`, + items: [], + }, { name: 'Database', key: 'database', @@ -29,18 +39,19 @@ export const generateSettingsMenu = (ref: string, project?: ProjectBase): Produc url: isProjectBuilding ? buildingUrl : `/project/${ref}/auth/settings`, items: [], }, - // TODO(thebengeu): Uncomment once all tenants migrated to multitenant storage API. - // { - // name: 'Storage', - // key: 'storage', - // url: isProjectBuilding ? buildingUrl : `/project/${ref}/storage/settings`, - // items: [], - // }, + { + name: 'Storage', + key: 'storage', + url: isProjectBuilding ? buildingUrl : `/project/${ref}/storage/settings`, + items: [], + hidden: !usePermissions(PermissionAction.STORAGE_ADMIN_READ, '*'), + }, { name: 'Billing & Usage', key: 'billing', url: isProjectBuilding ? buildingUrl : `/project/${ref}/settings/billing`, items: [], + hidden: canReadInvoices && canReadSubscriptions, }, ], }, diff --git a/studio/components/ui/ProductMenu/ProductMenu.tsx b/studio/components/ui/ProductMenu/ProductMenu.tsx index bf4af3955c4..71000b977b9 100644 --- a/studio/components/ui/ProductMenu/ProductMenu.tsx +++ b/studio/components/ui/ProductMenu/ProductMenu.tsx @@ -1,7 +1,7 @@ import { FC } from 'react' import { Badge, Menu } from '@supabase/ui' import ProductMenuItem from './ProductMenuItem' -import { ProductMenuGroupItem, ProductMenuGroup } from './ProductMenu.types' +import { ProductMenuGroup, ProductMenuGroupItem } from './ProductMenu.types' interface Props { page?: string @@ -26,17 +26,19 @@ const ProductMenu: FC = ({ page, menu }) => { } />
    - {group.items.map((item: ProductMenuGroupItem) => ( - - ))} + {group.items + .filter(({ hidden }) => !hidden) + .map((item: ProductMenuGroupItem) => ( + + ))}
    diff --git a/studio/components/ui/ProductMenu/ProductMenu.types.ts b/studio/components/ui/ProductMenu/ProductMenu.types.ts index fc3a00f1b56..1b5162d0f13 100644 --- a/studio/components/ui/ProductMenu/ProductMenu.types.ts +++ b/studio/components/ui/ProductMenu/ProductMenu.types.ts @@ -13,4 +13,5 @@ export interface ProductMenuGroupItem { items: any[] icon?: ReactNode isExternal?: boolean + hidden?: boolean } diff --git a/studio/components/ui/ui.types.ts b/studio/components/ui/ui.types.ts index 51980de21da..4f0dba09e34 100644 --- a/studio/components/ui/ui.types.ts +++ b/studio/components/ui/ui.types.ts @@ -5,4 +5,5 @@ export interface Route { label: string icon: ReactNode link: string + hidden?: boolean }