Swap useCheckPermissions with useAsyncCheckProjectPermissions part 3 (#37899)

* Swap useCheckPermissions with useAsyncCheckProjectPermissions part 3

* Fix loading state in edge function secrets
This commit is contained in:
Joshen Lim
2025-08-13 19:07:35 +07:00
committed by GitHub
parent 61379cbe2a
commit e75c4b2960
50 changed files with 504 additions and 418 deletions

View File

@@ -25,7 +25,7 @@ import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { AddonVariantId } from 'data/subscriptions/types'
import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import {
useIsAwsCloudProvider,
@@ -66,8 +66,7 @@ import { NoticeBar } from './ui/NoticeBar'
import { SpendCapDisabledSection } from './ui/SpendCapDisabledSection'
export function DiskManagementForm() {
// isLoading is used to avoid a useCheckPermissions() race condition
const { data: project, isLoading: isProjectLoading } = useSelectedProjectQuery()
const { data: project } = useSelectedProjectQuery()
const { data: org } = useSelectedOrganizationQuery()
const { ref: projectRef } = useParams()
const queryClient = useQueryClient()
@@ -80,27 +79,18 @@ export function DiskManagementForm() {
const isAws = useIsAwsCloudProvider()
const isAwsK8s = useIsAwsK8sCloudProvider()
/**
* Permissions
*/
const isPermissionsLoaded = usePermissionsLoaded()
const canUpdateDiskConfiguration = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
const { can: canUpdateDiskConfiguration, isSuccess: isPermissionsLoaded } =
useAsyncCheckProjectPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
/**
* Component States
*/
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false)
const [refetchInterval, setRefetchInterval] = useState<number | false>(false)
const [message, setMessageState] = useState<DiskManagementMessage | null>(null)
const [advancedSettingsOpen, setAdvancedSettingsOpenState] = useState(false)
/**
* Fetch form data
*/
const { data: databases, isSuccess: isReadReplicasSuccess } = useReadReplicasQuery({ projectRef })
const { data, isSuccess: isDiskAttributesSuccess } = useDiskAttributesQuery(
{ projectRef },
@@ -146,9 +136,6 @@ export function DiskManagementForm() {
const { data: diskAutoscaleConfig, isSuccess: isDiskAutoscaleConfigSuccess } =
useDiskAutoscaleCustomConfigQuery({ projectRef }, { enabled: project != null && isAws })
/**
* Handle default values
*/
const computeSize = project?.infra_compute_size
? mapComputeSizeNameToAddonVariantId(project?.infra_compute_size)
: undefined
@@ -190,10 +177,6 @@ export function DiskManagementForm() {
}
}, [modifiedComputeSize, isDialogOpen, project])
/**
* State handling
*/
const isSuccess =
isAddonsSuccess &&
isDiskAttributesSuccess &&
@@ -228,7 +211,7 @@ export function DiskManagementForm() {
const isDirty = !!Object.keys(form.formState.dirtyFields).length
const isProjectResizing = project?.status === PROJECT_STATUS.RESIZING
const isProjectRequestingDiskChanges = isRequestingChanges && !isProjectResizing
const noPermissions = isPermissionsLoaded && !canUpdateDiskConfiguration && !isProjectLoading
const noPermissions = isPermissionsLoaded && !canUpdateDiskConfiguration
const { mutateAsync: updateDiskConfiguration, isLoading: isUpdatingDisk } =
useUpdateDiskAttributesMutation({

View File

@@ -5,7 +5,7 @@ import { UseFormReturn } from 'react-hook-form'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { formatCurrency } from 'lib/helpers'
@@ -153,11 +153,15 @@ export const DiskManagementReviewAndSubmitDialog = ({
const { formState, getValues } = form
const canUpdateDiskConfiguration = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
const { can: canUpdateDiskConfiguration } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{
resource: {
project_id: project?.id,
},
}
)
/**
* Queries

View File

@@ -5,7 +5,7 @@ import { toast } from 'sonner'
import AutoTextArea from 'components/to-be-cleaned/forms/AutoTextArea'
import { executeSql } from 'data/sql/execute-sql-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { timeout } from 'lib/helpers'
import { Loader } from 'lucide-react'
@@ -42,7 +42,10 @@ const Description = ({ content, metadata, onChange = noop }: DescrptionProps) =>
const hasChanged = value != contentText
const animateCss = `transition duration-150`
const canUpdateDescription = useCheckPermissions(PermissionAction.TENANT_SQL_QUERY, '*')
const { can: canUpdateDescription } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_QUERY,
'*'
)
const updateDescription = async () => {
if (isUpdating || !canUpdateDescription) return false
@@ -76,7 +79,9 @@ const Description = ({ content, metadata, onChange = noop }: DescrptionProps) =>
if (!canUpdateDescription) {
return (
<span className={`block ${value ? 'text-foreground' : ''}`}>{value || 'No description'}</span>
<span className={`block text-sm ${value ? 'text-foreground' : ''}`}>
{value || 'No description'}
</span>
)
}

View File

@@ -18,7 +18,7 @@ import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query'
import { useEdgeFunctionDeleteMutation } from 'data/edge-functions/edge-functions-delete-mutation'
import { useEdgeFunctionUpdateMutation } from 'data/edge-functions/edge-functions-update-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import {
Alert_Shadcn_,
AlertDescription_Shadcn_,
@@ -59,7 +59,10 @@ export const EdgeFunctionDetails = () => {
const router = useRouter()
const { ref: projectRef, functionSlug } = useParams()
const [showDeleteModal, setShowDeleteModal] = useState(false)
const canUpdateEdgeFunction = useCheckPermissions(PermissionAction.FUNCTIONS_WRITE, '*')
const { can: canUpdateEdgeFunction } = useAsyncCheckProjectPermissions(
PermissionAction.FUNCTIONS_WRITE,
'*'
)
const { data: apiKeys } = useAPIKeysQuery({ projectRef })
const { data: settings } = useProjectSettingsV2Query({ projectRef })

View File

@@ -1,12 +1,11 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Trash } from 'lucide-react'
import Table from 'components/to-be-cleaned/Table'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import type { ProjectSecret } from 'data/secrets/secrets-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { TimestampInfo } from 'ui-patterns'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { TableCell, TableRow } from 'ui'
import { TimestampInfo } from 'ui-patterns'
interface EdgeFunctionSecretProps {
secret: ProjectSecret
@@ -14,7 +13,10 @@ interface EdgeFunctionSecretProps {
}
const EdgeFunctionSecret = ({ secret, onSelectDelete }: EdgeFunctionSecretProps) => {
const canUpdateSecrets = useCheckPermissions(PermissionAction.SECRETS_WRITE, '*')
const { can: canUpdateSecrets } = useAsyncCheckProjectPermissions(
PermissionAction.SECRETS_WRITE,
'*'
)
// [Joshen] Following API's validation:
// https://github.com/supabase/infrastructure/blob/develop/api/src/routes/v1/projects/ref/secrets/secrets.controller.ts#L106
const isReservedSecret = !!secret.name.match(/^(SUPABASE_).*/)

View File

@@ -9,18 +9,8 @@ import NoPermission from 'components/ui/NoPermission'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useSecretsDeleteMutation } from 'data/secrets/secrets-delete-mutation'
import { ProjectSecret, useSecretsQuery } from 'data/secrets/secrets-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import {
Badge,
Separator,
Table,
TableHead,
TableHeader,
TableRow,
TableCell,
TableBody,
Card,
} from 'ui'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { Badge, Card, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import AddNewSecretForm from './AddNewSecretForm'
@@ -31,8 +21,14 @@ const EdgeFunctionSecrets = () => {
const [searchString, setSearchString] = useState('')
const [selectedSecret, setSelectedSecret] = useState<ProjectSecret>()
const canReadSecrets = useCheckPermissions(PermissionAction.SECRETS_READ, '*')
const canUpdateSecrets = useCheckPermissions(PermissionAction.SECRETS_WRITE, '*')
const { can: canReadSecrets, isLoading: isLoadingPermissions } = useAsyncCheckProjectPermissions(
PermissionAction.SECRETS_READ,
'*'
)
const { can: canUpdateSecrets } = useAsyncCheckProjectPermissions(
PermissionAction.SECRETS_WRITE,
'*'
)
const { data, error, isLoading, isSuccess, isError } = useSecretsQuery({
projectRef: projectRef,
@@ -65,70 +61,76 @@ const EdgeFunctionSecrets = () => {
return (
<>
{isLoading && <GenericSkeletonLoader />}
{isError && <AlertError error={error} subject="Failed to retrieve project secrets" />}
{isSuccess && (
{isLoading || isLoadingPermissions ? (
<GenericSkeletonLoader />
) : (
<>
<div className="mb-6">
{!canUpdateSecrets ? (
<NoPermission resourceText="manage this project's edge function secrets" />
) : (
<AddNewSecretForm />
)}
</div>
{canUpdateSecrets && !canReadSecrets ? (
<NoPermission resourceText="view this project's edge function secrets" />
) : canReadSecrets ? (
<div className="space-y-4 mt-4">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-2">
<Input
size="small"
className="w-full md:w-80"
placeholder="Search for a secret"
value={searchString}
onChange={(e: any) => setSearchString(e.target.value)}
icon={<Search size={14} />}
/>
</div>
{isError && <AlertError error={error} subject="Failed to retrieve project secrets" />}
<Card>
<Table>
<TableHeader>
<TableRow>{headers}</TableRow>
</TableHeader>
<TableBody>
{secrets.length > 0 ? (
secrets.map((secret) => (
<EdgeFunctionSecret
key={secret.name}
secret={secret}
onSelectDelete={() => setSelectedSecret(secret)}
/>
))
) : secrets.length === 0 && searchString.length > 0 ? (
<TableRow>
<TableCell colSpan={headers.length}>
<p className="text-sm text-foreground">No results found</p>
<p className="text-sm text-foreground-light">
Your search for "{searchString}" did not return any results
</p>
</TableCell>
</TableRow>
) : (
<TableRow>
<TableCell colSpan={headers.length}>
<p className="text-sm text-foreground">No secrets created</p>
<p className="text-sm text-foreground-light">
There are no secrets associated with your project yet
</p>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</Card>
</div>
) : null}
{isSuccess && (
<>
<div className="mb-6">
{!canUpdateSecrets ? (
<NoPermission resourceText="manage this project's edge function secrets" />
) : (
<AddNewSecretForm />
)}
</div>
{canUpdateSecrets && !canReadSecrets ? (
<NoPermission resourceText="view this project's edge function secrets" />
) : canReadSecrets ? (
<div className="space-y-4 mt-4">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-2">
<Input
size="small"
className="w-full md:w-80"
placeholder="Search for a secret"
value={searchString}
onChange={(e: any) => setSearchString(e.target.value)}
icon={<Search size={14} />}
/>
</div>
<Card>
<Table>
<TableHeader>
<TableRow>{headers}</TableRow>
</TableHeader>
<TableBody>
{secrets.length > 0 ? (
secrets.map((secret) => (
<EdgeFunctionSecret
key={secret.name}
secret={secret}
onSelectDelete={() => setSelectedSecret(secret)}
/>
))
) : secrets.length === 0 && searchString.length > 0 ? (
<TableRow>
<TableCell colSpan={headers.length}>
<p className="text-sm text-foreground">No results found</p>
<p className="text-sm text-foreground-light">
Your search for "{searchString}" did not return any results
</p>
</TableCell>
</TableRow>
) : (
<TableRow>
<TableCell colSpan={headers.length}>
<p className="text-sm text-foreground">No secrets created</p>
<p className="text-sm text-foreground-light">
There are no secrets associated with your project yet
</p>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</Card>
</div>
) : null}
</>
)}
</>
)}

View File

@@ -35,12 +35,12 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { AlertTriangle, XIcon } from 'lucide-react'
import { MouseEventHandler, useCallback, useEffect, useState } from 'react'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { LOCAL_STORAGE_KEYS } from 'common'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useLocalStorage } from 'hooks/misc/useLocalStorage'
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, cn } from 'ui'
import { RoleImpersonationSelector } from '../RoleImpersonationSelector'
import styles from './graphiql.module.css'
import { LOCAL_STORAGE_KEYS } from 'common'
export interface GraphiQLProps {
fetcher: Fetcher
@@ -76,7 +76,10 @@ const GraphiQLInterface = ({ theme }: GraphiQLInterfaceProps) => {
const merge = useMergeQuery()
const prettify = usePrettifyEditors()
const canReadJWTSecret = useCheckPermissions(PermissionAction.READ, 'field.jwt_secret')
const { can: canReadJWTSecret } = useAsyncCheckProjectPermissions(
PermissionAction.READ,
'field.jwt_secret'
)
const [rlsBypassedWarningDismissed, setRlsBypassedWarningDismissed] = useLocalStorage(
LOCAL_STORAGE_KEYS.GRAPHIQL_RLS_BYPASS_WARNING,

View File

@@ -9,7 +9,7 @@ import Panel from 'components/ui/Panel'
import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query'
import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query'
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { Input, SimpleCodeBlock } from 'ui'
const generateInitSnippet = (endpoint: string) => ({
@@ -65,7 +65,10 @@ const APIKeys = () => {
const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus
const canReadAPIKeys = useCheckPermissions(PermissionAction.READ, 'service_api_keys')
const { can: canReadAPIKeys } = useAsyncCheckProjectPermissions(
PermissionAction.READ,
'service_api_keys'
)
const isNotUpdatingJwtSecret =
jwtSecretUpdateStatus === undefined || jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updated

View File

@@ -16,7 +16,7 @@ import { useDatabaseCronJobCreateMutation } from 'data/database-cron-jobs/databa
import { CronJob } from 'data/database-cron-jobs/database-cron-jobs-infinite-query'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import {
@@ -220,7 +220,7 @@ export const CreateCronJobSheet = ({
const { mutate: upsertCronJob, isLoading: isUpserting } = useDatabaseCronJobCreateMutation()
const isLoading = isLoadingGetCronJob || isUpserting
const canToggleExtensions = useCheckPermissions(
const { can: canToggleExtensions } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'extensions'
)

View File

@@ -74,7 +74,7 @@ const FORM_ID = 'create-queue-sidepanel'
export const CreateQueueSheet = ({ isClosing, setIsClosing, onClose }: CreateQueueSheetProps) => {
// This is for enabling pg_partman extension which will be used for partitioned queues (3rd kind of queue)
// const [showEnableExtensionModal, setShowEnableExtensionModal] = useState(false)
// const canToggleExtensions = useCheckPermissions(
// const { can: canToggleExtensions } = useAsyncCheckPermissions(
// PermissionAction.TENANT_SQL_ADMIN_WRITE,
// 'extensions'
// )

View File

@@ -21,7 +21,7 @@ import {
} from 'data/database-queues/database-queues-toggle-postgrest-mutation'
import { useTableUpdateMutation } from 'data/tables/table-update-mutation'
import { useTablesQuery } from 'data/tables/tables-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import {
Button,
@@ -39,7 +39,7 @@ import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
export const QueuesSettings = () => {
const { data: project } = useSelectedProjectQuery()
const canUpdatePostgrestConfig = useCheckPermissions(
const { can: canUpdatePostgrestConfig } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'custom_config_postgrest'
)

View File

@@ -6,16 +6,14 @@ import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Input,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip'
import { useVaultSecretDecryptedValueQuery } from 'data/vault/vault-secret-decrypted-value-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Edit3, Eye, EyeOff, Key, Loader, MoreVertical, Trash } from 'lucide-react'
import type { VaultSecret } from 'types'
@@ -33,7 +31,10 @@ const SecretRow = ({ secret, onSelectRemove }: SecretRowProps) => {
const [revealSecret, setRevealSecret] = useState(false)
const name = secret?.name ?? 'No name provided'
const canManageSecrets = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
const { can: canManageSecrets } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'tables'
)
const { data: revealedValue, isFetching } = useVaultSecretDecryptedValueQuery(
{
@@ -102,41 +103,34 @@ const SecretRow = ({ secret, onSelectRemove }: SecretRowProps) => {
<Button title="Manage Secret" type="text" className="px-1" icon={<MoreVertical />} />
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="end" className="w-32">
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuItem
className="space-x-2"
disabled={!canManageSecrets}
onClick={() => setModal(`edit`)}
>
<Edit3 size="14" />
<p>Edit</p>
</DropdownMenuItem>
</TooltipTrigger>
{!canManageSecrets && (
<TooltipContent side="bottom">
You need additional permissions to edit secrets
</TooltipContent>
)}
</Tooltip>
<DropdownMenuItemTooltip
className="gap-x-2"
disabled={!canManageSecrets}
onClick={() => setModal(`edit`)}
tooltip={{
content: { side: 'left', text: 'You need additional permissions to edit secrets' },
}}
>
<Edit3 size={12} />
<p>Edit</p>
</DropdownMenuItemTooltip>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuItem
className="space-x-2"
disabled={!canManageSecrets}
onClick={() => onSelectRemove(secret)}
>
<Trash stroke="red" size="14" />
<p className="text-foreground-light">Delete</p>
</DropdownMenuItem>
</TooltipTrigger>
{!canManageSecrets && (
<TooltipContent side="bottom">
You need additional permissions to delete secrets
</TooltipContent>
)}
</Tooltip>
<DropdownMenuSeparator />
<DropdownMenuItemTooltip
className="gap-x-2"
disabled={!canManageSecrets}
onClick={() => onSelectRemove(secret)}
tooltip={{
content: {
side: 'left',
text: 'You need additional permissions to delete secrets',
},
}}
>
<Trash size={12} />
<p className="text-foreground-light">Delete</p>
</DropdownMenuItemTooltip>
</DropdownMenuContent>
</DropdownMenu>

View File

@@ -7,8 +7,9 @@ import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { DocsButton } from 'components/ui/DocsButton'
import { useVaultSecretsQuery } from 'data/vault/vault-secrets-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import type { VaultSecret } from 'types'
import {
Button,
Input,
@@ -22,7 +23,6 @@ import {
import AddNewSecretModal from './AddNewSecretModal'
import DeleteSecretModal from './DeleteSecretModal'
import SecretRow from './SecretRow'
import type { VaultSecret } from 'types'
export const SecretsManagement = () => {
const { search } = useParams()
@@ -33,11 +33,10 @@ export const SecretsManagement = () => {
const [selectedSecretToRemove, setSelectedSecretToRemove] = useState<VaultSecret>()
const [selectedSort, setSelectedSort] = useState('updated_at')
const canManageSecrets = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
useEffect(() => {
if (search !== undefined) setSearchValue(search)
}, [search])
const { can: canManageSecrets } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'tables'
)
const { data, isLoading } = useVaultSecretsQuery({
projectRef: project?.ref!,
@@ -61,6 +60,10 @@ export const SecretsManagement = () => {
}
)
useEffect(() => {
if (search !== undefined) setSearchValue(search)
}, [search])
return (
<>
<div className="space-y-4 p-4 md:p-10">

View File

@@ -7,7 +7,7 @@ import NoPermission from 'components/ui/NoPermission'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useHooksEnableMutation } from 'data/database/hooks-enable-mutation'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Admonition } from 'ui-patterns'
import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab'
@@ -26,8 +26,10 @@ export const WebhooksOverviewTab = () => {
})
const isHooksEnabled = schemas?.some((schema) => schema.name === 'supabase_functions')
const canReadWebhooks = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'triggers')
const isPermissionsLoaded = usePermissionsLoaded()
const { can: canReadWebhooks, isLoading: isLoadingPermissions } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_READ,
'triggers'
)
const { mutate: enableHooks, isLoading: isEnablingHooks } = useHooksEnableMutation({
onSuccess: async () => {
@@ -41,18 +43,18 @@ export const WebhooksOverviewTab = () => {
enableHooks({ ref: projectRef })
}
if (isPermissionsLoaded && !canReadWebhooks) {
if (!isSchemasLoaded || isLoadingPermissions) {
return (
<div className="p-10">
<NoPermission isFullPage resourceText="view database webhooks" />
<GenericSkeletonLoader />
</div>
)
}
if (!isSchemasLoaded) {
if (!canReadWebhooks) {
return (
<div className="p-10">
<GenericSkeletonLoader />
<NoPermission isFullPage resourceText="view database webhooks" />
</div>
)
}
@@ -73,14 +75,13 @@ export const WebhooksOverviewTab = () => {
<ButtonTooltip
className="w-fit"
onClick={() => enableHooksForProject()}
disabled={!isPermissionsLoaded || isEnablingHooks}
disabled={isEnablingHooks}
tooltip={{
content: {
side: 'bottom',
text:
isPermissionsLoaded && !canReadWebhooks
? 'You need additional permissions to enable webhooks'
: undefined,
text: !canReadWebhooks
? 'You need additional permissions to enable webhooks'
: undefined,
},
}}
>

View File

@@ -5,7 +5,7 @@ import { useState } from 'react'
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import {
Alert_Shadcn_,
@@ -27,7 +27,11 @@ export const WrapperOverviewTab = () => {
const { data: project } = useSelectedProjectQuery()
const [createWrapperShown, setCreateWrapperShown] = useState(false)
const [isClosingCreateWrapper, setisClosingCreateWrapper] = useState(false)
const canCreateWrapper = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'wrappers')
const { can: canCreateWrapper } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'wrappers'
)
const { data } = useDatabaseExtensionsQuery({
projectRef: project?.ref,

View File

@@ -7,7 +7,7 @@ import { useState } from 'react'
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import type { FDW } from 'data/fdw/fdws-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { Badge, Sheet, SheetContent, TableCell, TableRow } from 'ui'
import { INTEGRATIONS } from '../Landing/Integrations.constants'
import DeleteWrapperModal from './DeleteWrapperModal'
@@ -20,7 +20,10 @@ interface WrapperRowProps {
const WrapperRow = ({ wrapper }: WrapperRowProps) => {
const { ref, id } = useParams()
const canManageWrappers = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'wrappers')
const { can: canManageWrappers } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'wrappers'
)
const [editWrapperShown, setEditWrapperShown] = useState(false)
const [isClosingEditWrapper, setIsClosingEditWrapper] = useState(false)

View File

@@ -4,7 +4,7 @@ import { HTMLProps, ReactNode, useCallback, useState } from 'react'
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { FDW, useFDWsQuery } from 'data/fdw/fdws-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Sheet, SheetContent } from 'ui'
import { CreateWrapperSheet } from './CreateWrapperSheet'
@@ -19,7 +19,11 @@ export const WrappersTab = () => {
const [selectedWrapperForDelete, setSelectedWrapperForDelete] = useState<FDW | null>(null)
const [createWrapperShown, setCreateWrapperShown] = useState(false)
const [isClosingCreateWrapper, setisClosingCreateWrapper] = useState(false)
const canCreateWrapper = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'wrappers')
const { can: canCreateWrapper } = useAsyncCheckProjectPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'wrappers'
)
const { data } = useFDWsQuery({
projectRef: project?.ref,

View File

@@ -15,7 +15,7 @@ import { useJwtSecretUpdateMutation } from 'data/config/jwt-secret-update-mutati
import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query'
import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query'
import { useLegacyJWTSigningKeyQuery } from 'data/jwt-signing-keys/legacy-jwt-signing-key-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useFlag } from 'hooks/ui/useFlag'
import { uuidv4 } from 'lib/helpers'
import {
@@ -73,12 +73,18 @@ const JWTSettings = () => {
const [isCreatingKey, setIsCreatingKey] = useState<boolean>(false)
const [isRegeneratingKey, setIsGeneratingKey] = useState<boolean>(false)
const canReadJWTSecret = useCheckPermissions(PermissionAction.READ, 'field.jwt_secret')
const canGenerateNewJWTSecret = useCheckPermissions(
const { can: canReadJWTSecret } = useAsyncCheckProjectPermissions(
PermissionAction.READ,
'field.jwt_secret'
)
const { can: canGenerateNewJWTSecret } = useAsyncCheckProjectPermissions(
PermissionAction.INFRA_EXECUTE,
'queue_job.projects.update_jwt'
)
const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
const { can: canUpdateConfig } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'custom_config_gotrue'
)
const { data } = useJwtSecretUpdatingStatusQuery({ projectRef })
const { data: config, isError } = useProjectPostgrestConfigQuery({ projectRef })

View File

@@ -1,7 +1,6 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import Link from 'next/link'
import { useEffect } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import * as z from 'zod'
@@ -18,7 +17,7 @@ import {
REALTIME_DEFAULT_CONFIG,
useRealtimeConfigurationQuery,
} from 'data/realtime/realtime-config-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import {
@@ -42,17 +41,20 @@ export const RealtimeSettings = () => {
const { ref: projectRef } = useParams()
const { data: project } = useSelectedProjectQuery()
const { data: organization } = useSelectedOrganizationQuery()
const canUpdateConfig = useCheckPermissions(PermissionAction.REALTIME_ADMIN_READ, '*')
const { can: canUpdateConfig } = useAsyncCheckProjectPermissions(
PermissionAction.REALTIME_ADMIN_READ,
'*'
)
const { data: maxConn } = useMaxConnectionsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const { data, error, isLoading, isSuccess, isError } = useRealtimeConfigurationQuery({
const { data, error, isLoading, isError } = useRealtimeConfigurationQuery({
projectRef,
})
const { data: policies } = useDatabasePoliciesQuery({
const { data: policies, isSuccess: isSuccessPolicies } = useDatabasePoliciesQuery({
projectRef,
connectionString: project?.connectionString,
schema: 'realtime',
@@ -96,6 +98,10 @@ export const RealtimeSettings = () => {
...REALTIME_DEFAULT_CONFIG,
allow_public: !REALTIME_DEFAULT_CONFIG.private_only,
},
values: {
...(data ?? REALTIME_DEFAULT_CONFIG),
allow_public: !(data?.private_only ?? REALTIME_DEFAULT_CONFIG.private_only),
} as any,
})
const { allow_public } = form.watch()
@@ -111,14 +117,6 @@ export const RealtimeSettings = () => {
})
}
useEffect(() => {
// [Joshen] Temp typed with any - API typing marks all the properties as nullable,
// but checked with Filipe that they're not supposed to
if (isSuccess) {
form.reset({ ...data, allow_public: !data.private_only } as any)
}
}, [isSuccess])
return (
<ScaffoldSection isFullWidth>
<Form_Shadcn_ {...form}>
@@ -136,7 +134,7 @@ export const RealtimeSettings = () => {
className="!p-0 !pt-2"
header={<FormSectionLabel>Channel restrictions</FormSectionLabel>}
>
<FormSectionContent loading={isLoading} className="!gap-y-2">
<FormSectionContent loaders={1} loading={isLoading} className="!gap-y-2">
<FormItemLayout
layout="flex"
label="Allow public access"
@@ -151,7 +149,7 @@ export const RealtimeSettings = () => {
</FormControl_Shadcn_>
</FormItemLayout>
{!hasRealtimeMessagesPolicies && !allow_public && (
{isSuccessPolicies && !hasRealtimeMessagesPolicies && !allow_public && (
<Admonition
showIcon={false}
type="warning"
@@ -198,7 +196,7 @@ export const RealtimeSettings = () => {
</FormSectionLabel>
}
>
<FormSectionContent loading={isLoading} className="!gap-y-2">
<FormSectionContent loaders={1} loading={isLoading} className="!gap-y-2">
<FormControl_Shadcn_>
<Input_Shadcn_
{...field}
@@ -241,7 +239,7 @@ export const RealtimeSettings = () => {
</FormSectionLabel>
}
>
<FormSectionContent loading={isLoading} className="!gap-y-2">
<FormSectionContent loaders={1} loading={isLoading} className="!gap-y-2">
<FormControl_Shadcn_>
<Input_Shadcn_
{...field}

View File

@@ -12,7 +12,7 @@ const ReportPadding = ({ children }: PropsWithChildren<{}>) => {
return (
<div
className={cn(
'flex flex-col gap-4 px-5 py-6 mx-auto 1xl:px-28 lg:px-16 2xl:px-32 w-full',
'flex flex-col flex-grow gap-4 px-5 py-6 mx-auto 1xl:px-28 lg:px-16 2xl:px-32 w-full',
snap.open ? 'xl:px-6' : 'xl:px-22'
)}
>

View File

@@ -22,7 +22,7 @@ import {
useContentUpsertMutation,
} from 'data/content/content-upsert-mutation'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Metric, TIME_PERIODS_REPORTS } from 'lib/constants/metrics'
@@ -82,22 +82,30 @@ const Reports = () => {
const currentReport = userContents?.content.find((report) => report.id === id)
const currentReportContent = currentReport?.content as Dashboards.Content
const canReadReport = useCheckPermissions(PermissionAction.READ, 'user_content', {
resource: {
type: 'report',
visibility: currentReport?.visibility,
owner_id: currentReport?.owner_id,
},
subject: { id: profile?.id },
})
const canUpdateReport = useCheckPermissions(PermissionAction.UPDATE, 'user_content', {
resource: {
type: 'report',
visibility: currentReport?.visibility,
owner_id: currentReport?.owner_id,
},
subject: { id: profile?.id },
})
const { can: canReadReport, isLoading: isLoadingPermissions } = useAsyncCheckProjectPermissions(
PermissionAction.READ,
'user_content',
{
resource: {
type: 'report',
visibility: currentReport?.visibility,
owner_id: currentReport?.owner_id,
},
subject: { id: profile?.id },
}
)
const { can: canUpdateReport } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'user_content',
{
resource: {
type: 'report',
visibility: currentReport?.visibility,
owner_id: currentReport?.owner_id,
},
subject: { id: profile?.id },
}
)
function handleDateRangePicker({ period_start, period_end }: any) {
setStartDate(period_start.date)
@@ -383,7 +391,7 @@ const Reports = () => {
}
}, [hasEdits, confirmNavigate, router])
if (isLoading) {
if (isLoading || isLoadingPermissions) {
return <Loading />
}

View File

@@ -7,7 +7,7 @@ import { useParams } from 'common'
import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.queries'
import { ActionCard } from 'components/layouts/Tabs/ActionCard'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { uuidv4 } from 'lib/helpers'
@@ -26,10 +26,14 @@ const SQLQuickstarts = () => {
const snapV2 = useSqlEditorV2StateSnapshot()
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
})
const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions(
PermissionAction.CREATE,
'user_content',
{
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
}
)
const { mutate: sendEvent } = useSendEventMutation()

View File

@@ -7,7 +7,7 @@ import { useParams } from 'common'
import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.queries'
import { ActionCard } from 'components/layouts/Tabs/ActionCard'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { uuidv4 } from 'lib/helpers'
@@ -26,10 +26,14 @@ const SQLTemplates = () => {
const snapV2 = useSqlEditorV2StateSnapshot()
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
})
const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions(
PermissionAction.CREATE,
'user_content',
{
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
}
)
const { mutate: sendEvent } = useSendEventMutation()

View File

@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
@@ -24,10 +24,14 @@ export const useNewQuery = () => {
const { data: project } = useSelectedProjectQuery()
const snapV2 = useSqlEditorV2StateSnapshot()
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
})
const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions(
PermissionAction.CREATE,
'user_content',
{
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
}
)
const newQuery = async (sql: string, name: string, shouldRedirect: boolean = true) => {
if (!ref) return console.error('Project ref is required')

View File

@@ -21,7 +21,7 @@ import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-co
import { useProjectPostgrestConfigUpdateMutation } from 'data/config/project-postgrest-config-update-mutation'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import {
AlertDescription_Shadcn_,
@@ -101,7 +101,7 @@ export const PostgrestConfig = () => {
const formId = 'project-postgres-config'
const hiddenSchema = ['auth', 'pgbouncer', 'hooks', 'extensions']
const canUpdatePostgrestConfig = useCheckPermissions(
const { can: canUpdatePostgrestConfig } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'custom_config_postgrest'
)

View File

@@ -8,7 +8,7 @@ import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-
import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import type { AddonVariantId } from 'data/subscriptions/types'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useFlag } from 'hooks/ui/useFlag'
import { formatCurrency } from 'lib/helpers'
@@ -32,7 +32,7 @@ const CustomDomainSidePanel = () => {
const [selectedOption, setSelectedOption] = useState<string>('cd_none')
const canUpdateCustomDomain = useCheckPermissions(
const { can: canUpdateCustomDomain } = useAsyncCheckProjectPermissions(
PermissionAction.BILLING_WRITE,
'stripe.subscriptions'
)

View File

@@ -10,7 +10,7 @@ import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-
import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import type { AddonVariantId } from 'data/subscriptions/types'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useIsAwsCloudProvider } from 'hooks/misc/useSelectedProject'
import { formatCurrency } from 'lib/helpers'
@@ -25,7 +25,10 @@ const IPv4SidePanel = () => {
const [selectedOption, setSelectedOption] = useState<string>('ipv4_none')
const canUpdateIPv4 = useCheckPermissions(PermissionAction.BILLING_WRITE, 'stripe.subscriptions')
const { can: canUpdateIPv4 } = useAsyncCheckProjectPermissions(
PermissionAction.BILLING_WRITE,
'stripe.subscriptions'
)
const { panel, closePanel } = useAddonsPagePanel()
const visible = panel === 'ipv4'

View File

@@ -13,7 +13,7 @@ import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-
import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import type { AddonVariantId } from 'data/subscriptions/types'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { BASE_PATH } from 'lib/constants'
@@ -61,7 +61,10 @@ const PITRSidePanel = () => {
const [selectedCategory, setSelectedCategory] = useState<'on' | 'off'>('off')
const [selectedOption, setSelectedOption] = useState<string>('pitr_0')
const canUpdatePitr = useCheckPermissions(PermissionAction.BILLING_WRITE, 'stripe.subscriptions')
const { can: canUpdatePitr } = useAsyncCheckProjectPermissions(
PermissionAction.BILLING_WRITE,
'stripe.subscriptions'
)
const isBranchingEnabled =
project?.is_branch_enabled === true || project?.parent_project_ref !== undefined

View File

@@ -12,7 +12,7 @@ import { FormPanel } from 'components/ui/Forms/FormPanel'
import { useBannedIPsDeleteMutation } from 'data/banned-ips/banned-ips-delete-mutations'
import { useBannedIPsQuery } from 'data/banned-ips/banned-ips-query'
import { useUserIPAddressQuery } from 'data/misc/user-ip-address-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Badge, Skeleton } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
@@ -39,11 +39,15 @@ const BannedIPs = () => {
const [showUnban, setShowUnban] = useState(false)
const [confirmingIP, setConfirmingIP] = useState<string | null>(null) // Track the IP being confirmed for unban
const canUnbanNetworks = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
const { can: canUnbanNetworks } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{
resource: {
project_id: project?.id,
},
}
)
const { mutate: unbanIPs, isLoading: isUnbanning } = useBannedIPsDeleteMutation({
onSuccess: () => {

View File

@@ -17,7 +17,7 @@ import { useMaxConnectionsQuery } from 'data/database/max-connections-query'
import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query'
import { usePgbouncerConfigurationUpdateMutation } from 'data/database/pgbouncer-config-update-mutation'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import {
@@ -51,7 +51,7 @@ export const ConnectionPooling = () => {
const { data: project } = useSelectedProjectQuery()
const { data: org } = useSelectedOrganizationQuery()
const canUpdateConnectionPoolingConfiguration = useCheckPermissions(
const { can: canUpdateConnectionPoolingConfiguration } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{ resource: { project_id: project?.id } }

View File

@@ -9,7 +9,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import Panel from 'components/ui/Panel'
import PasswordStrengthBar from 'components/ui/PasswordStrengthBar'
import { useDatabasePasswordResetMutation } from 'data/database/database-password-reset-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { DEFAULT_MINIMUM_PASSWORD_STRENGTH } from 'lib/constants'
import passwordStrength from 'lib/password-strength'
@@ -20,11 +20,16 @@ const ResetDbPassword = ({ disabled = false }) => {
const { ref } = useParams()
const isProjectActive = useIsProjectActive()
const { data: project } = useSelectedProjectQuery()
const canResetDbPassword = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
const { can: canResetDbPassword } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{
resource: {
project_id: project?.id,
},
}
)
const [showResetDbPass, setShowResetDbPass] = useState<boolean>(false)

View File

@@ -12,7 +12,7 @@ import { FormHeader } from 'components/ui/Forms/FormHeader'
import Panel from 'components/ui/Panel'
import { useProjectDiskResizeMutation } from 'data/config/project-disk-resize-mutation'
import { useDatabaseSizeQuery } from 'data/database/database-size-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useUrlState } from 'hooks/ui/useUrlState'
@@ -35,11 +35,15 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
setUrlParams({ show_increase_disk_size_modal: show ? 'true' : undefined })
}
const canUpdateDiskSizeConfig = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
const { can: canUpdateDiskSizeConfig } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{
resource: {
project_id: project?.id,
},
}
)
const { isLoading: isUpdatingDiskSize } = useProjectDiskResizeMutation({
onSuccess: (_, variables) => {

View File

@@ -10,7 +10,7 @@ import { FormPanel } from 'components/ui/Forms/FormPanel'
import Panel from 'components/ui/Panel'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useNetworkRestrictionsQuery } from 'data/network-restrictions/network-restrictions-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import {
Badge,
@@ -75,11 +75,15 @@ const NetworkRestrictions = () => {
const [selectedRestrictionToRemove, setSelectedRestrictionToRemove] = useState<string>()
const { data, isLoading } = useNetworkRestrictionsQuery({ projectRef: ref })
const canUpdateNetworkRestrictions = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
const { can: canUpdateNetworkRestrictions } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{
resource: {
project_id: project?.id,
},
}
)
const hasAccessToRestrictions = data?.entitlement === 'allowed'
const ipv4Restrictions = data?.config?.dbAllowedCidrs ?? []

View File

@@ -13,7 +13,7 @@ import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { useSSLEnforcementQuery } from 'data/ssl-enforcement/ssl-enforcement-query'
import { useSSLEnforcementUpdateMutation } from 'data/ssl-enforcement/ssl-enforcement-update-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Alert, Button, Switch, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
@@ -42,11 +42,15 @@ const SSLConfiguration = () => {
}
)
const canUpdateSSLEnforcement = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
const { can: canUpdateSSLEnforcement } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{
resource: {
project_id: project?.id,
},
}
)
const initialIsEnforced = isSuccess
? sslEnforcementConfiguration.appliedSuccessfully &&
sslEnforcementConfiguration.currentConfig.database
@@ -143,7 +147,7 @@ const SSLConfiguration = () => {
? 'You need additional permissions to update SSL enforcement for your project'
: !hasAccessToSSLEnforcement
? 'Your project does not have access to SSL enforcement'
: ''}
: undefined}
</TooltipContent>
)}
</Tooltip>

View File

@@ -12,7 +12,7 @@ import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui
import { InlineLink } from 'components/ui/InlineLink'
import { useComplianceConfigUpdateMutation } from 'data/config/project-compliance-config-mutation'
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Switch, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
@@ -21,9 +21,13 @@ const ComplianceConfig = () => {
const { data: project } = useSelectedProjectQuery()
const [isSensitive, setIsSensitive] = useState(false)
const canUpdateComplianceConfig = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: { project_id: project?.id },
})
const { can: canUpdateComplianceConfig } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{
resource: { project_id: project?.id },
}
)
const {
data: settings,

View File

@@ -9,7 +9,7 @@ import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { useCheckCNAMERecordMutation } from 'data/custom-domains/check-cname-mutation'
import { useCustomDomainCreateMutation } from 'data/custom-domains/custom-domains-create-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Form, Input } from 'ui'
@@ -27,11 +27,15 @@ const CustomDomainsConfigureHostname = () => {
const FORM_ID = 'custom-domains-form'
const endpoint = settings?.app_config?.endpoint
const canConfigureCustomDomain = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
const { can: canConfigureCustomDomain } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{
resource: {
project_id: project?.id,
},
}
)
const onCreateCustomDomain = async (values: yup.InferType<typeof schema>) => {
if (!ref) return console.error('Project ref is required')

View File

@@ -2,7 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useState } from 'react'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { DeleteProjectModal } from './DeleteProjectModal'
@@ -14,9 +14,13 @@ const DeleteProjectButton = ({ type = 'danger' }: DeleteProjectButtonProps) => {
const { data: project } = useSelectedProjectQuery()
const [isOpen, setIsOpen] = useState(false)
const canDeleteProject = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: { project_id: project?.id },
})
const { can: canDeleteProject } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{
resource: { project_id: project?.id },
}
)
return (
<>

View File

@@ -9,7 +9,7 @@ import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui
import Panel from 'components/ui/Panel'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useProjectUpdateMutation } from 'data/projects/project-update-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useProjectByRefQuery, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import {
@@ -33,11 +33,15 @@ const General = () => {
const formId = 'project-general-settings'
const initialValues = { name: project?.name ?? '', ref: project?.ref ?? '' }
const canUpdateProject = useCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
const { can: canUpdateProject } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'projects',
{
resource: {
project_id: project?.id,
},
}
)
const { mutate: updateProject, isLoading: isUpdating } = useProjectUpdateMutation()

View File

@@ -9,7 +9,7 @@ import { useIsProjectActive } from 'components/layouts/ProjectLayout/ProjectCont
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useProjectPauseMutation } from 'data/projects/project-pause-mutation'
import { setProjectStatus } from 'data/projects/projects-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useIsAwsK8sCloudProvider, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { PROJECT_STATUS } from 'lib/constants'
@@ -25,7 +25,7 @@ const PauseProjectButton = () => {
const projectRef = project?.ref ?? ''
const isPaused = project?.status === PROJECT_STATUS.INACTIVE
const canPauseProject = useCheckPermissions(
const { can: canPauseProject } = useAsyncCheckProjectPermissions(
PermissionAction.INFRA_EXECUTE,
'queue_jobs.projects.pause'
)

View File

@@ -10,7 +10,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useProjectRestartMutation } from 'data/projects/project-restart-mutation'
import { useProjectRestartServicesMutation } from 'data/projects/project-restart-services-mutation'
import { setProjectStatus } from 'data/projects/projects-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useIsAwsK8sCloudProvider, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useFlag } from 'hooks/ui/useFlag'
import {
@@ -35,7 +35,10 @@ const RestartServerButton = () => {
const projectRegion = project?.region ?? ''
const projectRestartDisabled = useFlag('disableProjectRestarts')
const canRestartProject = useCheckPermissions(PermissionAction.INFRA_EXECUTE, 'reboot')
const { can: canRestartProject } = useAsyncCheckProjectPermissions(
PermissionAction.INFRA_EXECUTE,
'reboot'
)
const { mutate: restartProject, isLoading: isRestartingProject } = useProjectRestartMutation({
onSuccess: () => {

View File

@@ -8,7 +8,7 @@ import { DocsButton } from 'components/ui/DocsButton'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useProjectTransferMutation } from 'data/projects/project-transfer-mutation'
import { useProjectTransferPreviewQuery } from 'data/projects/project-transfer-preview-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useFlag } from 'hooks/ui/useFlag'
import { Button, InfoIcon, Listbox, Loading, Modal, WarningIcon } from 'ui'
@@ -58,7 +58,10 @@ const TransferProjectButton = () => {
}
}, [isOpen])
const canTransferProject = useCheckPermissions(PermissionAction.UPDATE, 'organizations')
const { can: canTransferProject } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'organizations'
)
const toggle = () => {
setIsOpen(!isOpen)

View File

@@ -15,7 +15,7 @@ import {
ReplicaInitializationStatus,
useReadReplicasStatusesQuery,
} from 'data/read-replicas/replicas-status-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useIsOrioleDb, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { timeout } from 'lib/helpers'
import Link from 'next/link'
@@ -56,7 +56,10 @@ const InstanceConfigurationUI = () => {
const [selectedReplicaToDrop, setSelectedReplicaToDrop] = useState<Database>()
const [selectedReplicaToRestart, setSelectedReplicaToRestart] = useState<Database>()
const canManageReplicas = useCheckPermissions(PermissionAction.CREATE, 'projects')
const { can: canManageReplicas } = useAsyncCheckProjectPermissions(
PermissionAction.CREATE,
'projects'
)
const {
data: loadBalancers,

View File

@@ -6,6 +6,7 @@ import { parseAsBoolean, useQueryState } from 'nuqs'
import { Handle, NodeProps, Position } from 'reactflow'
import { useParams } from 'common'
import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip'
import SparkBar from 'components/ui/SparkBar'
import {
DatabaseInitEstimations,
@@ -13,7 +14,7 @@ import {
useReadReplicasStatusesQuery,
} from 'data/read-replicas/replicas-status-query'
import { formatDatabaseID } from 'data/read-replicas/replicas.utils'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { BASE_PATH } from 'lib/constants'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
import {
@@ -181,7 +182,10 @@ export const ReplicaNode = ({ data }: NodeProps<ReplicaNodeData>) => {
} = data
const { ref } = useParams()
const dbSelectorState = useDatabaseSelectorStateSnapshot()
const canManageReplicas = useCheckPermissions(PermissionAction.CREATE, 'projects')
const { can: canManageReplicas } = useAsyncCheckProjectPermissions(
PermissionAction.CREATE,
'projects'
)
const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false))
const { data: databaseStatuses } = useReadReplicasStatusesQuery({ projectRef: ref })
@@ -370,24 +374,18 @@ export const ReplicaNode = ({ data }: NodeProps<ReplicaNodeData>) => {
{/* <DropdownMenuItem className="gap-x-2" onClick={() => onSelectResizeReplica()}>
Resize replica
</DropdownMenuItem> */}
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuItem
className="gap-x-2 !pointer-events-auto"
disabled={!canManageReplicas}
onClick={() => {
if (canManageReplicas) onSelectDropReplica()
}}
>
Drop replica
</DropdownMenuItem>
</TooltipTrigger>
{!canManageReplicas && (
<TooltipContent side="left">
You need additional permissions to drop replicas
</TooltipContent>
)}
</Tooltip>
<DropdownMenuItemTooltip
className="gap-x-2 !pointer-events-auto"
disabled={!canManageReplicas}
onClick={() => {
if (canManageReplicas) onSelectDropReplica()
}}
tooltip={{
content: { side: 'left', text: 'You need additional permissions to drop replicas' },
}}
>
Drop replica
</DropdownMenuItemTooltip>
</DropdownMenuContent>
</DropdownMenu>
</div>

View File

@@ -16,9 +16,10 @@ import {
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip'
import { Database, useReadReplicasQuery } from 'data/read-replicas/replicas-query'
import { formatDatabaseID } from 'data/read-replicas/replicas.utils'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { BASE_PATH } from 'lib/constants'
import type { AWS_REGIONS_KEYS } from 'shared-data'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
@@ -31,9 +32,6 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
ScrollArea,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { AVAILABLE_REPLICA_REGIONS, REPLICA_STATUS } from './InstanceConfiguration.constants'
import GeographyData from './MapData.json'
@@ -62,7 +60,10 @@ const MapView = ({
y: number
region: { key: string; country?: string; name?: string }
}>()
const canManageReplicas = useCheckPermissions(PermissionAction.CREATE, 'projects')
const { can: canManageReplicas } = useAsyncCheckProjectPermissions(
PermissionAction.CREATE,
'projects'
)
const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false))
const { data } = useReadReplicasQuery({ projectRef: ref })
@@ -325,22 +326,20 @@ const MapView = ({
>
Restart replica
</DropdownMenuItem>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuItem
className="gap-x-2 !pointer-events-auto"
disabled={!canManageReplicas}
onClick={() => onSelectDropReplica(database)}
>
Drop replica
</DropdownMenuItem>
</TooltipTrigger>
{!canManageReplicas && (
<TooltipContent side="left">
You need additional permissions to drop replicas
</TooltipContent>
)}
</Tooltip>
<DropdownMenuItemTooltip
className="gap-x-2 !pointer-events-auto"
disabled={!canManageReplicas}
onClick={() => onSelectDropReplica(database)}
tooltip={{
content: {
side: 'left',
text: 'You need additional permissions to drop replicas',
},
}}
>
Drop replica
</DropdownMenuItemTooltip>
</DropdownMenuContent>
</DropdownMenu>
)}

View File

@@ -16,7 +16,7 @@ import { useGitHubConnectionDeleteMutation } from 'data/integrations/github-conn
import { useGitHubConnectionUpdateMutation } from 'data/integrations/github-connection-update-mutation'
import { useGitHubRepositoriesQuery } from 'data/integrations/github-repositories-query'
import type { GitHubConnection } from 'data/integrations/integrations.types'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { openInstallGitHubIntegrationWindow } from 'lib/github'
@@ -72,17 +72,16 @@ const GitHubIntegrationConnectionForm = ({
const [repoComboBoxOpen, setRepoComboboxOpen] = useState(false)
const isParentProject = !Boolean(selectedProject?.parent_project_ref)
const canUpdateGitHubConnection = useCheckPermissions(
const { can: canUpdateGitHubConnection } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'integrations.github_connections'
)
const canCreateGitHubConnection = useCheckPermissions(
const { can: canCreateGitHubConnection } = useAsyncCheckProjectPermissions(
PermissionAction.CREATE,
'integrations.github_connections'
)
const { data: gitHubAuthorization, isLoading: isLoadingGitHubAuthorization } =
useGitHubAuthorizationQuery()
const { data: gitHubAuthorization } = useGitHubAuthorizationQuery()
const { data: githubReposData, isLoading: isLoadingGitHubRepos } = useGitHubRepositoriesQuery<
any[]

View File

@@ -11,10 +11,11 @@ import {
import NoPermission from 'components/ui/NoPermission'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { BASE_PATH, IS_PLATFORM } from 'lib/constants'
import { cn } from 'ui'
import { GenericSkeletonLoader } from 'ui-patterns'
import GitHubIntegrationConnectionForm from './GitHubIntegrationConnectionForm'
const IntegrationImageHandler = ({ title }: { title: 'vercel' | 'github' }) => {
@@ -31,10 +32,8 @@ const GitHubSection = () => {
const { ref: projectRef } = useParams()
const { data: organization } = useSelectedOrganizationQuery()
const canReadGitHubConnection = useCheckPermissions(
PermissionAction.READ,
'integrations.github_connections'
)
const { can: canReadGitHubConnection, isLoading: isLoadingPermissions } =
useAsyncCheckProjectPermissions(PermissionAction.READ, 'integrations.github_connections')
const isProPlanAndUp = organization?.plan?.id !== 'free'
const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp
@@ -51,22 +50,6 @@ const GitHubSection = () => {
const GitHubTitle = `GitHub Integration`
if (!canReadGitHubConnection) {
return (
<ScaffoldContainer>
<ScaffoldSection>
<ScaffoldSectionDetail title={GitHubTitle}>
<p>Connect any of your GitHub repositories to a project.</p>
<IntegrationImageHandler title="github" />
</ScaffoldSectionDetail>
<ScaffoldSectionContent>
<NoPermission resourceText="view this organization's GitHub connections" />
</ScaffoldSectionContent>
</ScaffoldSection>
</ScaffoldContainer>
)
}
return (
<ScaffoldContainer>
<ScaffoldSection>
@@ -75,28 +58,34 @@ const GitHubSection = () => {
<IntegrationImageHandler title="github" />
</ScaffoldSectionDetail>
<ScaffoldSectionContent>
<div className="space-y-6">
<div>
<h5 className="text-foreground mb-2">How does the GitHub integration work?</h5>
<p className="text-foreground-light text-sm mb-6">
Connecting to GitHub allows you to sync preview branches with a chosen GitHub
branch, keep your production branch in sync, and automatically create preview
branches for every pull request.
</p>
{promptProPlanUpgrade && (
<div className="mb-6">
<UpgradeToPro
primaryText="Upgrade to unlock GitHub integration"
secondaryText="Connect your GitHub repository to automatically sync preview branches and deploy changes."
source="github-integration"
/>
{isLoadingPermissions ? (
<GenericSkeletonLoader />
) : !canReadGitHubConnection ? (
<NoPermission resourceText="view this organization's GitHub connections" />
) : (
<div className="space-y-6">
<div>
<h5 className="text-foreground mb-2">How does the GitHub integration work?</h5>
<p className="text-foreground-light text-sm mb-6">
Connecting to GitHub allows you to sync preview branches with a chosen GitHub
branch, keep your production branch in sync, and automatically create preview
branches for every pull request.
</p>
{promptProPlanUpgrade && (
<div className="mb-6">
<UpgradeToPro
primaryText="Upgrade to unlock GitHub integration"
secondaryText="Connect your GitHub repository to automatically sync preview branches and deploy changes."
source="github-integration"
/>
</div>
)}
<div className={cn(promptProPlanUpgrade && 'opacity-25 pointer-events-none')}>
<GitHubIntegrationConnectionForm connection={existingConnection} />
</div>
)}
<div className={cn(promptProPlanUpgrade && 'opacity-25 pointer-events-none')}>
<GitHubIntegrationConnectionForm connection={existingConnection} />
</div>
</div>
</div>
)}
</ScaffoldSectionContent>
</ScaffoldSection>
</ScaffoldContainer>

View File

@@ -25,13 +25,14 @@ import type {
IntegrationName,
IntegrationProjectConnection,
} from 'data/integrations/integrations.types'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { pluralize } from 'lib/helpers'
import { getIntegrationConfigurationUrl } from 'lib/integration-utils'
import { useSidePanelsStateSnapshot } from 'state/side-panels'
import { Button, cn } from 'ui'
import { GenericSkeletonLoader } from 'ui-patterns'
import { IntegrationImageHandler } from '../IntegrationsSettings'
import VercelIntegrationConnectionForm from './VercelIntegrationConnectionForm'
@@ -42,15 +43,13 @@ const VercelSection = ({ isProjectScoped }: { isProjectScoped: boolean }) => {
const sidePanelsStateSnapshot = useSidePanelsStateSnapshot()
const isBranch = project?.parent_project_ref !== undefined
const canReadVercelConnection = useCheckPermissions(
PermissionAction.READ,
'integrations.vercel_connections'
)
const canCreateVercelConnection = useCheckPermissions(
const { can: canReadVercelConnection, isLoading: isLoadingPermissions } =
useAsyncCheckProjectPermissions(PermissionAction.READ, 'integrations.vercel_connections')
const { can: canCreateVercelConnection } = useAsyncCheckProjectPermissions(
PermissionAction.CREATE,
'integrations.vercel_connections'
)
const canUpdateVercelConnection = useCheckPermissions(
const { can: canUpdateVercelConnection } = useAsyncCheckProjectPermissions(
PermissionAction.UPDATE,
'integrations.vercel_connections'
)
@@ -162,7 +161,9 @@ You can change the scope of the access for Supabase by configuring
<IntegrationImageHandler title="vercel" />
</ScaffoldSectionDetail>
<ScaffoldSectionContent>
{!canReadVercelConnection ? (
{isLoadingPermissions ? (
<GenericSkeletonLoader />
) : !canReadVercelConnection ? (
<NoPermission resourceText="view this organization's Vercel connections" />
) : (
<>

View File

@@ -10,8 +10,9 @@ import { IS_PLATFORM, useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { DownloadResultsButton } from 'components/ui/DownloadResultsButton'
import { useSelectedLog } from 'hooks/analytics/useSelectedLog'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useProfile } from 'lib/profile'
import { toast } from 'sonner'
import { ResponseError } from 'types'
import {
Button,
@@ -33,7 +34,6 @@ import { isDefaultLogPreviewFormat } from './Logs.utils'
import { DefaultErrorRenderer } from './LogsErrorRenderers/DefaultErrorRenderer'
import ResourcesExceededErrorRenderer from './LogsErrorRenderers/ResourcesExceededErrorRenderer'
import { LogsTableEmptyState } from './LogsTableEmptyState'
import { toast } from 'sonner'
interface Props {
data?: LogData[]
@@ -94,10 +94,14 @@ const LogTable = ({
const [selectionOpen, setSelectionOpen] = useState(false)
const [selectedRow, setSelectedRow] = useState<LogData | null>(null)
const canCreateLogQuery = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'log_sql', owner_id: profile?.id },
subject: { id: profile?.id },
})
const { can: canCreateLogQuery } = useAsyncCheckProjectPermissions(
PermissionAction.CREATE,
'user_content',
{
resource: { type: 'log_sql', owner_id: profile?.id },
subject: { id: profile?.id },
}
)
const firstRow = data[0]

View File

@@ -65,11 +65,13 @@ const Shimmer = () => (
const FormSectionContent = ({
children,
loading = true,
loaders,
fullWidth,
className,
}: {
children: React.ReactNode | string
loading?: boolean
loaders?: number
fullWidth?: boolean
className?: string
}) => {
@@ -81,7 +83,11 @@ const FormSectionContent = ({
${className}
`}
>
{loading ? Children.map(children, () => <Shimmer />) : children}
{loading
? !!loaders
? new Array(loaders).fill(0).map((_, idx) => <Shimmer key={idx} />)
: Children.map(children, (_, idx) => <Shimmer key={idx} />)
: children}
</div>
)
}

View File

@@ -5,7 +5,7 @@ import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout'
import type { NextPageWithLayout } from 'types'
const PageLayout: NextPageWithLayout = () => (
<div className="mx-auto flex flex-col gap-4 w-full">
<div className="mx-auto flex flex-col gap-4 w-full flex-grow">
<ReportPadding>
<Reports />
</ReportPadding>