mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Chore/clean up feature flags 300626 (#47429)
## Context Cleans up a number of stale feature flags that have been enabled for all users for more than 3 months - enableSmartRegion - SentryLogDrain - axiomLogDrain - S3logdrain - Last9LogDrain - otlpLogDrain - ShowPrettyExplain - pgdeltaDiff - CustomOauthProviders - timezonePicker <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Timezone selector is now always available in the user menu. * Log drain “Type” options are expanded in the creation flow. * Custom auth providers now appear when the custom providers setting is enabled. * **Bug Fixes** * Smart region selection and related queries now follow the selected cloud provider. * PG Delta Diff preview availability and SQL editor “EXPLAIN” routing now follow the latest enabled settings (including platform-only preview behavior). * **Changes** * Removed the Storage List-V2 migration callout from the Storage settings page. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
963ab3e63b
commit
7203a97a90
12 files changed
+222
-431
No files matched your search
@@ -13,6 +13,7 @@ import {
|
||||
} from 'react'
|
||||
|
||||
import { useFeaturePreviews } from './useFeaturePreviews'
|
||||
import { IS_PLATFORM } from '@/lib/constants'
|
||||
import { EMPTY_OBJ } from '@/lib/void'
|
||||
|
||||
type FeaturePreviewContextType = {
|
||||
@@ -43,6 +44,11 @@ export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren) =
|
||||
const initializeFlags = useEffectEvent(() => {
|
||||
setFlags(
|
||||
featurePreviews.reduce((a, b) => {
|
||||
// Platform-only previews can never be enabled outside the hosted platform
|
||||
if (!IS_PLATFORM && b.isPlatformOnly) {
|
||||
return { ...a, [b.key]: false }
|
||||
}
|
||||
|
||||
const defaultOptIn = b.isDefaultOptIn
|
||||
const localStorageValue = safeLocalStorage.getItem(b.key)
|
||||
return {
|
||||
@@ -101,8 +107,7 @@ export const useUnifiedLogsPreview = () => {
|
||||
|
||||
export const useIsPgDeltaDiffEnabled = () => {
|
||||
const { flags } = useFeaturePreviewContext()
|
||||
const pgDeltaDiffEnabled = useFlag('pgdeltaDiff')
|
||||
return pgDeltaDiffEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_PG_DELTA_DIFF]
|
||||
return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_PG_DELTA_DIFF]
|
||||
}
|
||||
|
||||
export const useIsAdvisorRulesEnabled = () => {
|
||||
|
||||
@@ -20,7 +20,6 @@ export type FeaturePreview = {
|
||||
}
|
||||
|
||||
export const useFeaturePreviews = (): FeaturePreview[] => {
|
||||
const pgDeltaDiffEnabled = useFlag('pgdeltaDiff')
|
||||
const platformWebhooksEnabled = useFlag('platformWebhooks')
|
||||
const jitDbAccessEnabled = useFlag('jitDbAccess')
|
||||
const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
|
||||
@@ -68,7 +67,7 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
|
||||
isNew: false,
|
||||
isPlatformOnly: true,
|
||||
isDefaultOptIn: true,
|
||||
enabled: pgDeltaDiffEnabled,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_PLATFORM_WEBHOOKS,
|
||||
@@ -111,12 +110,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
|
||||
getRoute: (ref?: string) => `/project/${ref}/integrations`,
|
||||
},
|
||||
].sort((a, b) => Number(b.isNew) - Number(a.isNew)),
|
||||
[
|
||||
unifiedLogsDefaultOptIn,
|
||||
pgDeltaDiffEnabled,
|
||||
platformWebhooksEnabled,
|
||||
jitDbAccessEnabled,
|
||||
isMarketplaceEnabled,
|
||||
]
|
||||
[unifiedLogsDefaultOptIn, platformWebhooksEnabled, jitDbAccessEnabled, isMarketplaceEnabled]
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { IS_PLATFORM, useFlag } from 'common'
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import Link from 'next/link'
|
||||
import { ReactNode, useEffect, useMemo, useRef } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
logDrainHeaderEntriesSchema,
|
||||
type LogDrainHeaderRow,
|
||||
} from './LogDrains.utils'
|
||||
import { useEnabledLogDrainTypes } from './useEnabledLogDrainTypes'
|
||||
import { TaxDisclaimer } from '@/components/interfaces/Billing/TaxDisclaimer'
|
||||
import { Shortcut } from '@/components/ui/Shortcut'
|
||||
import { LogDrainData } from '@/data/log-drains/log-drains-query'
|
||||
@@ -347,12 +348,7 @@ export function LogDrainDestinationSheetForm({
|
||||
)
|
||||
}, [defaultValues, mode])
|
||||
|
||||
const sentryEnabled = useFlag('SentryLogDrain')
|
||||
const s3Enabled = useFlag('S3logdrain')
|
||||
const axiomEnabled = useFlag('axiomLogDrain')
|
||||
const otlpEnabled = useFlag('otlpLogDrain')
|
||||
const last9Enabled = useFlag('Last9LogDrain')
|
||||
const syslogEnabled = useFlag('syslogLogDrain')
|
||||
const enabledLogDrainTypes = useEnabledLogDrainTypes()
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null)
|
||||
|
||||
@@ -474,15 +470,7 @@ export function LogDrainDestinationSheetForm({
|
||||
{LOG_DRAIN_TYPES.find((t) => t.value === type)?.name}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LOG_DRAIN_TYPES.filter((t) => {
|
||||
if (t.value === 'sentry') return sentryEnabled
|
||||
if (t.value === 's3') return s3Enabled
|
||||
if (t.value === 'axiom') return axiomEnabled
|
||||
if (t.value === 'otlp') return otlpEnabled
|
||||
if (t.value === 'last9') return last9Enabled
|
||||
if (t.value === 'syslog') return syslogEnabled
|
||||
return true
|
||||
}).map((type) => (
|
||||
{enabledLogDrainTypes.map((type) => (
|
||||
<SelectItem
|
||||
value={type.value}
|
||||
key={type.value}
|
||||
|
||||
@@ -3,19 +3,9 @@ import { useFlag } from 'common'
|
||||
import { LOG_DRAIN_TYPES } from './LogDrains.constants'
|
||||
|
||||
export function useEnabledLogDrainTypes() {
|
||||
const sentryEnabled = useFlag('SentryLogDrain')
|
||||
const s3Enabled = useFlag('S3logdrain')
|
||||
const axiomEnabled = useFlag('axiomLogDrain')
|
||||
const otlpEnabled = useFlag('otlpLogDrain')
|
||||
const last9Enabled = useFlag('Last9LogDrain')
|
||||
const syslogEnabled = useFlag('syslogLogDrain')
|
||||
|
||||
return LOG_DRAIN_TYPES.filter((t) => {
|
||||
if (t.value === 'sentry') return sentryEnabled
|
||||
if (t.value === 's3') return s3Enabled
|
||||
if (t.value === 'axiom') return axiomEnabled
|
||||
if (t.value === 'otlp') return otlpEnabled
|
||||
if (t.value === 'last9') return last9Enabled
|
||||
if (t.value === 'syslog') return syslogEnabled
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useFeatureFlags, useFlag, useParams } from 'common'
|
||||
import { useFeatureFlags, useParams } from 'common'
|
||||
import { UseFormReturn } from 'react-hook-form'
|
||||
import type { CloudProvider } from 'shared-data'
|
||||
import {
|
||||
@@ -29,7 +29,7 @@ import { useDefaultRegionQuery } from '@/data/misc/get-default-region-query'
|
||||
import { useOrganizationAvailableRegionsQuery } from '@/data/organizations/organization-available-regions-query'
|
||||
import { useIncidentStatusQuery } from '@/data/platform/incident-status-query'
|
||||
import type { DesiredInstanceSize } from '@/data/projects/new-project.constants'
|
||||
import { BASE_PATH, PROVIDERS } from '@/lib/constants'
|
||||
import { BASE_PATH } from '@/lib/constants'
|
||||
|
||||
interface RegionSelectorProps {
|
||||
form: UseFormReturn<CreateProjectForm>
|
||||
@@ -70,7 +70,7 @@ export const RegionSelector = ({
|
||||
const cloudProvider = form.getValues('cloudProvider') as CloudProvider
|
||||
|
||||
const { hasLoaded: flagsLoaded } = useFeatureFlags()
|
||||
const smartRegionEnabled = useFlag('enableSmartRegion')
|
||||
const smartRegionEnabled = cloudProvider !== 'AWS_NIMBUS'
|
||||
|
||||
const { data: statusData } = useIncidentStatusQuery()
|
||||
const { incidents = [] } = statusData ?? {}
|
||||
@@ -100,7 +100,7 @@ export const RegionSelector = ({
|
||||
availableRegionsData?.recommendations.specific.map((region) => region.code)
|
||||
)
|
||||
|
||||
const availableRegions = getAvailableRegions(PROVIDERS[cloudProvider].id)
|
||||
const availableRegions = getAvailableRegions(cloudProvider)
|
||||
const regionsArray = Object.entries(availableRegions).map(([_key, value]) => {
|
||||
return {
|
||||
code: value.code,
|
||||
|
||||
@@ -126,7 +126,6 @@ export const SQLEditor = () => {
|
||||
const getImpersonatedRoleState = useGetImpersonatedRoleState()
|
||||
const databaseSelectorState = useDatabaseSelectorStateSnapshot()
|
||||
const { aiOptInLevel } = useOrgAiOptInLevel()
|
||||
const showPrettyExplain = useFlag('ShowPrettyExplain')
|
||||
|
||||
// [Ali] Kill switch to hide the SQL Editor Explain tab and its entry points
|
||||
const disablePrettyExplain = useFlag('DisablePrettyExplainOnSqlEditor')
|
||||
@@ -236,7 +235,7 @@ export const SQLEditor = () => {
|
||||
if (id) {
|
||||
sessionSnap.addResult(id, data.result, vars.autoLimit)
|
||||
|
||||
if (!disablePrettyExplain && showPrettyExplain && isExplainQuery(data.result)) {
|
||||
if (!disablePrettyExplain && isExplainQuery(data.result)) {
|
||||
sessionSnap.addExplainResult(id, data.result)
|
||||
setActiveUtilityTab('explain')
|
||||
} else if (activeUtilityTab === 'explain') {
|
||||
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useParams } from 'common'
|
||||
import dayjs from 'dayjs'
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogSection,
|
||||
DialogSectionSeparator,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from 'ui'
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
import { TimestampInfo } from 'ui-patterns/TimestampInfo'
|
||||
|
||||
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
||||
import { InlineLink } from '@/components/ui/InlineLink'
|
||||
import { useProjectStorageConfigUpdateUpdateMutation } from '@/data/config/project-storage-config-update-mutation'
|
||||
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
||||
|
||||
// [Joshen] Will be decided by Storage team, temp setting to 15th December 2025 UTC (3 months buffer)
|
||||
const MIGRATION_DEADLINE = '2025-12-15T00:00:00'
|
||||
|
||||
export const StorageListV2MigrationCallout = () => {
|
||||
const deadline = dayjs(MIGRATION_DEADLINE).utc(true)
|
||||
const currentDate = dayjs.utc()
|
||||
const remainingMonths = Math.ceil(deadline.diff(currentDate, 'months', true))
|
||||
|
||||
return (
|
||||
<Admonition
|
||||
type="note"
|
||||
title="A new version of Storage is available for your project"
|
||||
description={
|
||||
<>
|
||||
<p>
|
||||
Get access to the List-V2 endpoint for improved performance and the ability to enable
|
||||
Analytics buckets to your storage system.
|
||||
</p>
|
||||
{remainingMonths <= 1 && (
|
||||
<p>
|
||||
Your project's Storage will be automatically upgraded by{' '}
|
||||
<TimestampInfo
|
||||
displayAs="utc"
|
||||
utcTimestamp={MIGRATION_DEADLINE}
|
||||
className="text-sm text-inherit font-medium"
|
||||
labelFormat="DD MMM YYYY HH:mm (UTC)"
|
||||
/>{' '}
|
||||
if the upgrade is not completed by then.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
actions={<StorageListV2MigrationDialog />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const StorageListV2MigratingCallout = () => {
|
||||
return (
|
||||
<Admonition
|
||||
type="note"
|
||||
title="Project storage is currently upgrading"
|
||||
description="This notice will be closed once the upgrade has been completed. Hang tight!"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const StorageListV2MigrationDialog = () => {
|
||||
const { ref } = useParams()
|
||||
const { can: canUpdateStorageSettings } = useAsyncCheckPermissions(
|
||||
PermissionAction.STORAGE_ADMIN_WRITE,
|
||||
'*'
|
||||
)
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const { mutate: updateStorageConfig, isPending: isUpdating } =
|
||||
useProjectStorageConfigUpdateUpdateMutation({
|
||||
onSuccess: () => {
|
||||
toast.success(`Project's storage will be upgraded shortly!`)
|
||||
setOpen(false)
|
||||
},
|
||||
})
|
||||
|
||||
const onConfirmUpgrade = () => {
|
||||
if (!ref) return console.error('Project ref is required')
|
||||
updateStorageConfig({ projectRef: ref, external: { upstreamTarget: 'canary' } })
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<ButtonTooltip
|
||||
variant="primary"
|
||||
disabled={!canUpdateStorageSettings}
|
||||
tooltip={{
|
||||
content: {
|
||||
side: 'bottom',
|
||||
text: !canUpdateStorageSettings
|
||||
? 'You need additional permissions to upgrade storage'
|
||||
: undefined,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Upgrade Storage
|
||||
</ButtonTooltip>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Upgrade your project's Storage</DialogTitle>
|
||||
<DialogDescription>
|
||||
Get access to Analytics buckets and an improved list method
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogSectionSeparator />
|
||||
|
||||
<Admonition
|
||||
type="warning"
|
||||
className="rounded-none border-x-0 border-t-0"
|
||||
title="Migration required to optimise the database schema for upgrade"
|
||||
description="We recommend running the update during periods of lower activity, although minimal to no disruption is expected."
|
||||
/>
|
||||
|
||||
<DialogSection className="flex flex-col gap-y-2">
|
||||
<p className="text-sm">
|
||||
Depending on the number of objects in your Storage, the migration can take up to 24
|
||||
hours to finish.
|
||||
</p>
|
||||
|
||||
<p className="text-sm">
|
||||
The upgrade will increase your disk size to about 15 - 25% and IOPS will be used to
|
||||
create new efficient indexes as well as denormalising tables.
|
||||
</p>
|
||||
|
||||
<p className="text-sm">
|
||||
Ensure that your database instance has not{' '}
|
||||
<InlineLink href={`/project/${ref}/settings/compute-and-disk#disk-size`}>
|
||||
scaled disk
|
||||
</InlineLink>{' '}
|
||||
within the last 6h and you have at least 60%{' '}
|
||||
<InlineLink href={`/project/${ref}/settings/infrastructure#infrastructure-activity`}>
|
||||
CPU capacity
|
||||
</InlineLink>{' '}
|
||||
before proceeding.
|
||||
</p>
|
||||
</DialogSection>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="default" disabled={isUpdating} onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" loading={isUpdating} onClick={() => onConfirmUpgrade()}>
|
||||
Upgrade now
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { IS_PLATFORM, useFlag, useParams } from 'common'
|
||||
import { IS_PLATFORM, useParams } from 'common'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { SubmitHandler, useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
@@ -29,10 +29,6 @@ import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { StorageFileSizeLimitErrorMessage } from './StorageFileSizeLimitErrorMessage'
|
||||
import {
|
||||
StorageListV2MigratingCallout,
|
||||
StorageListV2MigrationCallout,
|
||||
} from './StorageListV2MigrationCallout'
|
||||
import {
|
||||
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_CAPPED,
|
||||
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED,
|
||||
@@ -70,8 +66,6 @@ export const StorageSettings = () => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
|
||||
const showMigrationCallout = useFlag('storageMigrationCallout')
|
||||
|
||||
const { can: canReadStorageSettings, isLoading: isLoadingPermissions } = useAsyncCheckPermissions(
|
||||
PermissionAction.STORAGE_ADMIN_READ,
|
||||
'*'
|
||||
@@ -88,10 +82,6 @@ export const StorageSettings = () => {
|
||||
isSuccess,
|
||||
isError,
|
||||
} = useProjectStorageConfigQuery({ projectRef })
|
||||
const isListV2UpgradeAvailable =
|
||||
!!config && !config.capabilities.list_v2 && config.external.upstreamTarget === 'main'
|
||||
const isListV2Upgrading =
|
||||
!!config && !config.capabilities.list_v2 && config.external.upstreamTarget === 'canary'
|
||||
|
||||
const {
|
||||
runCondition: sizeLimitCheckCondition,
|
||||
@@ -282,209 +272,200 @@ export const StorageSettings = () => {
|
||||
/>
|
||||
)}
|
||||
{isSuccess && (
|
||||
<>
|
||||
{showMigrationCallout && (
|
||||
<>
|
||||
{isListV2UpgradeAvailable && <StorageListV2MigrationCallout />}
|
||||
{isListV2Upgrading && <StorageListV2MigratingCallout />}
|
||||
</>
|
||||
)}
|
||||
<form id={formId} onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="imageTransformationEnabled"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Enable image transformation"
|
||||
description={
|
||||
<>
|
||||
Optimize and resize images on the fly.{' '}
|
||||
<InlineLink
|
||||
href={`${DOCS_URL}/guides/storage/serving/image-transformations`}
|
||||
>
|
||||
Learn more
|
||||
</InlineLink>
|
||||
.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FormControl>
|
||||
<Switch
|
||||
size="large"
|
||||
disabled={
|
||||
!hasAccessToImageTransformations || !canUpdateStorageSettings
|
||||
}
|
||||
checked={hasAccessToImageTransformations && field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<CardContent>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="fileSizeLimit"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
hideMessage
|
||||
layout="flex-row-reverse"
|
||||
label="Global file size limit"
|
||||
description={
|
||||
<>
|
||||
Restrict the size of files uploaded across all buckets.{' '}
|
||||
<InlineLink
|
||||
href={`${DOCS_URL}/guides/storage/uploads/file-limits`}
|
||||
>
|
||||
Learn more
|
||||
</InlineLink>
|
||||
.
|
||||
{!shouldAutoValidateBucketLimits && (
|
||||
<p>
|
||||
Ensure that the global limit is greater than that of
|
||||
individual buckets
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FormControl>
|
||||
<div className="flex items-center justify-end">
|
||||
<Input
|
||||
type="number"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e)
|
||||
form.clearErrors('fileSizeLimit')
|
||||
}}
|
||||
className="w-32 rounded-r-none border-r-0"
|
||||
disabled={
|
||||
!hasAccessToFileSizeConfiguration ||
|
||||
!canUpdateStorageSettings
|
||||
}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="unit"
|
||||
render={({ field: unitField }) => (
|
||||
<Select
|
||||
value={unitField.value}
|
||||
onValueChange={(val) => {
|
||||
unitField.onChange(val)
|
||||
form.clearErrors('fileSizeLimit')
|
||||
}}
|
||||
disabled={
|
||||
!hasAccessToFileSizeConfiguration ||
|
||||
!canUpdateStorageSettings
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-[90px] text-xs font-mono rounded-l-none bg-surface-300">
|
||||
<SelectValue placeholder="Choose a prefix">
|
||||
{storageUnit}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.values(StorageSizeUnits).map((unit: string) => (
|
||||
<SelectItem
|
||||
key={unit}
|
||||
disabled={!hasAccessToFileSizeConfiguration}
|
||||
value={unit}
|
||||
>
|
||||
{unit}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
{sizeLimitCheckCondition === 'confirm' && (
|
||||
<ValidateSizeLimit
|
||||
onValidate={sizeLimitCheckQuery}
|
||||
projectRef={projectRef}
|
||||
isLoadingBucketEstimate={isBucketEstimatePending}
|
||||
/>
|
||||
)}
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
{fileSizeLimitError && (
|
||||
<FormMessage className="ml-auto mt-2 text-right w-1/2">
|
||||
<StorageFileSizeLimitErrorMessage
|
||||
error={fileSizeLimitError}
|
||||
projectRef={projectRef}
|
||||
/>
|
||||
</FormMessage>
|
||||
)}
|
||||
</CardContent>
|
||||
{hasLimitedStorageAccess && (
|
||||
<UpgradeToPro
|
||||
fullWidth
|
||||
variant="primary"
|
||||
source="storageSizeLimit"
|
||||
featureProposition="configure upload file size limits in Storage"
|
||||
primaryText="Free Plan has a fixed upload file size limit of 50 MB"
|
||||
secondaryText={`Upgrade to Pro Plan for a configurable upload file size limit of ${formatBytes(
|
||||
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED
|
||||
)} and unlock image transformations.`}
|
||||
/>
|
||||
)}
|
||||
{isSpendCapOn && (
|
||||
<UpgradeToPro
|
||||
fullWidth
|
||||
addon="spendCap"
|
||||
variant="default"
|
||||
source="storageSizeLimit"
|
||||
featureProposition="increase the file upload size limits in Storage"
|
||||
buttonText="Disable spend cap"
|
||||
primaryText="Reduced max upload file size limit due to spend cap"
|
||||
secondaryText={`Disable your spend cap to allow file uploads of up to ${formatBytes(
|
||||
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED
|
||||
)}.`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!canUpdateStorageSettings && (
|
||||
<CardContent>
|
||||
<p className="text-sm text-foreground-light">
|
||||
You need additional permissions to update storage settings
|
||||
</p>
|
||||
</CardContent>
|
||||
)}
|
||||
|
||||
<CardFooter className="justify-end space-x-2">
|
||||
{form.formState.isDirty && (
|
||||
<Button
|
||||
variant="default"
|
||||
type="reset"
|
||||
onClick={() => form.reset()}
|
||||
disabled={
|
||||
!form.formState.isDirty || !canUpdateStorageSettings || isUpdating
|
||||
<form id={formId} onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="imageTransformationEnabled"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Enable image transformation"
|
||||
description={
|
||||
<>
|
||||
Optimize and resize images on the fly.{' '}
|
||||
<InlineLink
|
||||
href={`${DOCS_URL}/guides/storage/serving/image-transformations`}
|
||||
>
|
||||
Learn more
|
||||
</InlineLink>
|
||||
.
|
||||
</>
|
||||
}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<FormControl>
|
||||
<Switch
|
||||
size="large"
|
||||
disabled={
|
||||
!hasAccessToImageTransformations || !canUpdateStorageSettings
|
||||
}
|
||||
checked={hasAccessToImageTransformations && field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<CardContent>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="fileSizeLimit"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
hideMessage
|
||||
layout="flex-row-reverse"
|
||||
label="Global file size limit"
|
||||
description={
|
||||
<>
|
||||
Restrict the size of files uploaded across all buckets.{' '}
|
||||
<InlineLink
|
||||
href={`${DOCS_URL}/guides/storage/uploads/file-limits`}
|
||||
>
|
||||
Learn more
|
||||
</InlineLink>
|
||||
.
|
||||
{!shouldAutoValidateBucketLimits && (
|
||||
<p>
|
||||
Ensure that the global limit is greater than that of
|
||||
individual buckets
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FormControl>
|
||||
<div className="flex items-center justify-end">
|
||||
<Input
|
||||
type="number"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e)
|
||||
form.clearErrors('fileSizeLimit')
|
||||
}}
|
||||
className="w-32 rounded-r-none border-r-0"
|
||||
disabled={
|
||||
!hasAccessToFileSizeConfiguration || !canUpdateStorageSettings
|
||||
}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="unit"
|
||||
render={({ field: unitField }) => (
|
||||
<Select
|
||||
value={unitField.value}
|
||||
onValueChange={(val) => {
|
||||
unitField.onChange(val)
|
||||
form.clearErrors('fileSizeLimit')
|
||||
}}
|
||||
disabled={
|
||||
!hasAccessToFileSizeConfiguration ||
|
||||
!canUpdateStorageSettings
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-[90px] text-xs font-mono rounded-l-none bg-surface-300">
|
||||
<SelectValue placeholder="Choose a prefix">
|
||||
{storageUnit}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.values(StorageSizeUnits).map((unit: string) => (
|
||||
<SelectItem
|
||||
key={unit}
|
||||
disabled={!hasAccessToFileSizeConfiguration}
|
||||
value={unit}
|
||||
>
|
||||
{unit}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
{sizeLimitCheckCondition === 'confirm' && (
|
||||
<ValidateSizeLimit
|
||||
onValidate={sizeLimitCheckQuery}
|
||||
projectRef={projectRef}
|
||||
isLoadingBucketEstimate={isBucketEstimatePending}
|
||||
/>
|
||||
)}
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
{fileSizeLimitError && (
|
||||
<FormMessage className="ml-auto mt-2 text-right w-1/2">
|
||||
<StorageFileSizeLimitErrorMessage
|
||||
error={fileSizeLimitError}
|
||||
projectRef={projectRef}
|
||||
/>
|
||||
</FormMessage>
|
||||
)}
|
||||
</CardContent>
|
||||
{hasLimitedStorageAccess && (
|
||||
<UpgradeToPro
|
||||
fullWidth
|
||||
variant="primary"
|
||||
source="storageSizeLimit"
|
||||
featureProposition="configure upload file size limits in Storage"
|
||||
primaryText="Free Plan has a fixed upload file size limit of 50 MB"
|
||||
secondaryText={`Upgrade to Pro Plan for a configurable upload file size limit of ${formatBytes(
|
||||
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED
|
||||
)} and unlock image transformations.`}
|
||||
/>
|
||||
)}
|
||||
{isSpendCapOn && (
|
||||
<UpgradeToPro
|
||||
fullWidth
|
||||
addon="spendCap"
|
||||
variant="default"
|
||||
source="storageSizeLimit"
|
||||
featureProposition="increase the file upload size limits in Storage"
|
||||
buttonText="Disable spend cap"
|
||||
primaryText="Reduced max upload file size limit due to spend cap"
|
||||
secondaryText={`Disable your spend cap to allow file uploads of up to ${formatBytes(
|
||||
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED
|
||||
)}.`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!canUpdateStorageSettings && (
|
||||
<CardContent>
|
||||
<p className="text-sm text-foreground-light">
|
||||
You need additional permissions to update storage settings
|
||||
</p>
|
||||
</CardContent>
|
||||
)}
|
||||
|
||||
<CardFooter className="justify-end space-x-2">
|
||||
{form.formState.isDirty && (
|
||||
<Button
|
||||
variant={hasLimitedStorageAccess ? 'default' : 'primary'}
|
||||
type="submit"
|
||||
loading={isUpdating}
|
||||
variant="default"
|
||||
type="reset"
|
||||
onClick={() => form.reset()}
|
||||
disabled={
|
||||
!canUpdateStorageSettings || isUpdating || !form.formState.isDirty
|
||||
!form.formState.isDirty || !canUpdateStorageSettings || isUpdating
|
||||
}
|
||||
>
|
||||
Save
|
||||
Cancel
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
variant={hasLimitedStorageAccess ? 'default' : 'primary'}
|
||||
type="submit"
|
||||
loading={isUpdating}
|
||||
disabled={
|
||||
!canUpdateStorageSettings || isUpdating || !form.formState.isDirty
|
||||
}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</form>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useFlag } from 'common'
|
||||
import { FlaskConical, Loader2, ScrollText, User2 } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import Link from 'next/link'
|
||||
@@ -42,7 +41,6 @@ export function UserDropdown({
|
||||
const { theme, setTheme } = useTheme()
|
||||
const appStateSnapshot = useAppStateSnapshot()
|
||||
const profileShowEmailEnabled = useIsFeatureEnabled('profile:show_email')
|
||||
const timezonePickerEnabled = useFlag('timezonePicker')
|
||||
const { username, avatarUrl, primaryEmail, isLoading } = useProfileNameAndPicture()
|
||||
|
||||
const { toggleFeaturePreviewModal } = useFeaturePreviewModal()
|
||||
@@ -105,7 +103,9 @@ export function UserDropdown({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem className="flex gap-2 cursor-pointer" asChild>
|
||||
<Link
|
||||
@@ -141,6 +141,7 @@ export function UserDropdown({
|
||||
</DropdownMenuGroup>
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>Theme</DropdownMenuLabel>
|
||||
<DropdownMenuRadioGroup
|
||||
@@ -160,14 +161,13 @@ export function UserDropdown({
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuGroup>
|
||||
{timezonePickerEnabled && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<TimezoneDropdown />
|
||||
</DropdownMenuGroup>
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuGroup>
|
||||
<TimezoneDropdown />
|
||||
</DropdownMenuGroup>
|
||||
|
||||
{showUpgradeCta && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
@@ -23,8 +23,9 @@ import { useTrack } from '@/lib/telemetry/track'
|
||||
const AUTO_OPTION_VALUE = '__auto__'
|
||||
|
||||
export const TimezoneDropdown = () => {
|
||||
const { timezone, storedTimezone, setTimezone, isAutoDetected } = useTimezone()
|
||||
const track = useTrack()
|
||||
const { timezone, storedTimezone, setTimezone, isAutoDetected } = useTimezone()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
// The "Auto detect" row always advertises the browser's own timezone, even
|
||||
|
||||
@@ -107,7 +107,6 @@ const Wizard: NextPageWithLayout = () => {
|
||||
)
|
||||
|
||||
const { hasLoaded: flagsLoaded } = useFeatureFlags()
|
||||
const smartRegionEnabled = useFlag('enableSmartRegion')
|
||||
const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
const showInternalOnlyConfiguration = useFlag('newProjectInternalOnlyConfiguration')
|
||||
|
||||
@@ -172,6 +171,7 @@ const Wizard: NextPageWithLayout = () => {
|
||||
} = useWatch({ control: form.control })
|
||||
const { dirtyFields } = useFormState(form)
|
||||
const isDbRegionDirty = dirtyFields.dbRegion
|
||||
const smartRegionEnabled = cloudProvider !== 'AWS_NIMBUS'
|
||||
|
||||
// Read dirty state during render rather than depending on form.formState in the
|
||||
// effect — form.formState is a Proxy that gets a new reference every render, which
|
||||
@@ -221,9 +221,10 @@ const Wizard: NextPageWithLayout = () => {
|
||||
? 0
|
||||
: monthlyInstancePrice(instanceSize) - availableComputeCredits
|
||||
|
||||
const selectedCloudProvider = cloudProvider as CloudProvider
|
||||
const { data: autoDefaultRegion, error: defaultRegionError } = useDefaultRegionQuery(
|
||||
{
|
||||
cloudProvider: PROVIDERS[defaultProvider].id,
|
||||
cloudProvider: selectedCloudProvider,
|
||||
},
|
||||
{
|
||||
enabled: flagsLoaded && !smartRegionEnabled,
|
||||
@@ -255,7 +256,7 @@ const Wizard: NextPageWithLayout = () => {
|
||||
? availableRegionsData?.recommendations.smartGroup.name
|
||||
: ''
|
||||
|
||||
const fixedDefaultRegion = PROVIDERS[defaultProvider].default_region.displayName
|
||||
const fixedDefaultRegion = PROVIDERS[selectedCloudProvider].default_region.displayName
|
||||
const regionError =
|
||||
smartRegionEnabled && defaultProvider !== 'AWS_NIMBUS'
|
||||
? availableRegionsError
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useFlag } from 'common'
|
||||
import { PageContainer } from 'ui-patterns/PageContainer'
|
||||
|
||||
import { AuthProvidersForm } from '@/components/interfaces/Auth/AuthProvidersForm'
|
||||
@@ -12,13 +11,12 @@ import type { NextPageWithLayout } from '@/types'
|
||||
const ProvidersPage: NextPageWithLayout = () => {
|
||||
const showProviders = useIsFeatureEnabled('authentication:show_providers')
|
||||
const showCustomProviders = useIsFeatureEnabled('authentication:show_custom_providers')
|
||||
const isOauthProvidersEnabled = useFlag('CustomOauthProviders')
|
||||
|
||||
return (
|
||||
<PageContainer size="default">
|
||||
<BasicAuthSettingsForm />
|
||||
{showProviders && <AuthProvidersForm />}
|
||||
{showCustomProviders && isOauthProvidersEnabled && <CustomAuthProviders />}
|
||||
{showCustomProviders && <CustomAuthProviders />}
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user