mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 07:09:07 +03:00
feat: apply permissions to menus
This commit is contained in:
1 parent
dad4d3d93d
commit
b811e69158
6 files changed
+115
-93
No files matched your search
@@ -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<Props> = ({}) => {
|
||||
}}
|
||||
/>
|
||||
<div className="h-px w-full bg-scale-500"></div>
|
||||
{productRoutes.map((route) => (
|
||||
<NavigationIconButton
|
||||
key={route.key}
|
||||
route={route}
|
||||
isActive={activeRoute === route.key}
|
||||
/>
|
||||
))}
|
||||
{productRoutes
|
||||
.filter(({ hidden }) => !hidden)
|
||||
.map((route) => (
|
||||
<NavigationIconButton
|
||||
key={route.key}
|
||||
route={route}
|
||||
isActive={activeRoute === route.key}
|
||||
/>
|
||||
))}
|
||||
<div className="h-px w-full bg-scale-500"></div>
|
||||
{otherRoutes.map((route) => (
|
||||
<NavigationIconButton
|
||||
key={route.key}
|
||||
route={route}
|
||||
isActive={activeRoute === route.key}
|
||||
/>
|
||||
))}
|
||||
{otherRoutes
|
||||
.filter(({ hidden }) => !hidden)
|
||||
.map((route) => (
|
||||
<NavigationIconButton
|
||||
key={route.key}
|
||||
route={route}
|
||||
isActive={activeRoute === route.key}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="flex flex-col space-y-2">
|
||||
<Dropdown
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import {
|
||||
IconUsers,
|
||||
IconFileText,
|
||||
IconArchive,
|
||||
IconSettings,
|
||||
IconDatabase,
|
||||
IconBarChart,
|
||||
IconList,
|
||||
IconCode,
|
||||
IconDatabase,
|
||||
IconFileText,
|
||||
IconList,
|
||||
IconSettings,
|
||||
IconUsers,
|
||||
} from '@supabase/ui'
|
||||
import SVG from 'react-inlinesvg'
|
||||
|
||||
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
|
||||
import { Route } from 'components/ui/ui.types'
|
||||
|
||||
import { useFlag } from 'hooks'
|
||||
import { useFlag, usePermissions } from 'hooks'
|
||||
import { ProjectBase } from 'types'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
export const generateProductRoutes = (ref: string, project?: ProjectBase): 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: <IconUsers size={18} strokeWidth={2} />,
|
||||
link: isProjectBuilding ? buildingUrl : `/project/${ref}/auth/users`,
|
||||
},
|
||||
...(IS_PLATFORM
|
||||
? [
|
||||
{
|
||||
key: 'storage',
|
||||
label: 'Storage',
|
||||
icon: <IconArchive size={18} strokeWidth={2} />,
|
||||
link: isProjectBuilding ? buildingUrl : `/project/${ref}/storage/buckets`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'storage',
|
||||
label: 'Storage',
|
||||
icon: <IconArchive size={18} strokeWidth={2} />,
|
||||
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: <IconDatabase size={18} strokeWidth={2} />,
|
||||
link: isProjectBuilding ? buildingUrl : `/project/${ref}/database/tables`,
|
||||
},
|
||||
...(IS_PLATFORM && functionsUi
|
||||
? [
|
||||
{
|
||||
key: 'functions',
|
||||
label: 'Functions',
|
||||
icon: <IconCode size={18} strokeWidth={2} />,
|
||||
link: isProjectBuilding ? buildingUrl : `/project/${ref}/functions`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'functions',
|
||||
label: 'Functions',
|
||||
icon: <IconCode size={18} strokeWidth={2} />,
|
||||
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: <IconList size={18} strokeWidth={2} />,
|
||||
link: isProjectBuilding ? buildingUrl : `/project/${ref}/logs-explorer`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(IS_PLATFORM
|
||||
? [
|
||||
{
|
||||
key: 'reports',
|
||||
label: 'Reports',
|
||||
icon: <IconBarChart size={18} strokeWidth={2} />,
|
||||
link: isProjectBuilding ? buildingUrl : `/project/${ref}/reports`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'logs-explorer',
|
||||
label: 'Logs Explorer',
|
||||
icon: <IconList size={18} strokeWidth={2} />,
|
||||
link: isProjectBuilding ? buildingUrl : `/project/${ref}/logs-explorer`,
|
||||
hidden: !IS_PLATFORM,
|
||||
},
|
||||
{
|
||||
key: 'reports',
|
||||
label: 'Reports',
|
||||
icon: <IconBarChart size={18} strokeWidth={2} />,
|
||||
link: isProjectBuilding ? buildingUrl : `/project/${ref}/reports`,
|
||||
hidden: !IS_PLATFORM || !canReadReport || !canReadStats,
|
||||
},
|
||||
{
|
||||
key: 'api',
|
||||
label: 'API',
|
||||
icon: <IconFileText size={18} strokeWidth={2} />,
|
||||
link: isProjectBuilding ? buildingUrl : `/project/${ref}/api`,
|
||||
},
|
||||
...(IS_PLATFORM
|
||||
? [
|
||||
{
|
||||
key: 'settings',
|
||||
label: 'Settings',
|
||||
icon: <IconSettings size={18} strokeWidth={2} />,
|
||||
link: `/project/${ref}/settings/general`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'settings',
|
||||
label: 'Settings',
|
||||
icon: <IconSettings size={18} strokeWidth={2} />,
|
||||
link: `/project/${ref}/settings/general`,
|
||||
hidden:
|
||||
!IS_PLATFORM || !usePermissions(PermissionAction.SQL_SELECT, 'postgres.public.projects'),
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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<Props> = ({ page, menu }) => {
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
{group.items.map((item: ProductMenuGroupItem) => (
|
||||
<ProductMenuItem
|
||||
key={item.key}
|
||||
url={item.url}
|
||||
name={item.name}
|
||||
icon={item.icon}
|
||||
isActive={page === item.key}
|
||||
isExternal={item.isExternal}
|
||||
target={item.isExternal ? '_blank' : '_self'}
|
||||
/>
|
||||
))}
|
||||
{group.items
|
||||
.filter(({ hidden }) => !hidden)
|
||||
.map((item: ProductMenuGroupItem) => (
|
||||
<ProductMenuItem
|
||||
key={item.key}
|
||||
url={item.url}
|
||||
name={item.name}
|
||||
icon={item.icon}
|
||||
isActive={page === item.key}
|
||||
isExternal={item.isExternal}
|
||||
target={item.isExternal ? '_blank' : '_self'}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,4 +13,5 @@ export interface ProductMenuGroupItem {
|
||||
items: any[]
|
||||
icon?: ReactNode
|
||||
isExternal?: boolean
|
||||
hidden?: boolean
|
||||
}
|
||||
@@ -5,4 +5,5 @@ export interface Route {
|
||||
label: string
|
||||
icon: ReactNode
|
||||
link: string
|
||||
hidden?: boolean
|
||||
}
|
||||
Reference in new issue
Block a user