mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
## Context Reverts copy changes from the following PRs: - Docs: https://github.com/supabase/supabase/pull/42184 - FE: https://github.com/supabase/supabase/pull/47646 Our platform's disk management configuration limitation still follows the 4 hour cooldown at the moment, doesn't align with AWS's 4 changes in 24 hours rule just yet. This is just to prevent any confusion for now, and we'll need to update the copy again once behaviour matches AWS on our BE <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Updates** * Disk changes are now subject to an approximately four-hour cooldown after each modification, replacing the previous limit of four changes in a rolling 24-hour period. * Disk management screens now show the cooldown status, remaining wait time, and next available update time. * Updated platform guides and troubleshooting instructions to reflect the cooldown and explain available recovery options. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
611 lines
24 KiB
TypeScript
611 lines
24 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod'
|
|
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
|
import { useParams } from 'common'
|
|
import { AnimatePresence, motion } from 'framer-motion'
|
|
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
import { useForm, useWatch } from 'react-hook-form'
|
|
import { CloudProvider } from 'shared-data'
|
|
import { toast } from 'sonner'
|
|
import { Button, cn, Form } from 'ui'
|
|
import { PageContainer } from 'ui-patterns/PageContainer'
|
|
import {
|
|
PageSection,
|
|
PageSectionContent,
|
|
PageSectionMeta,
|
|
PageSectionSummary,
|
|
PageSectionTitle,
|
|
} from 'ui-patterns/PageSection'
|
|
|
|
import { ComputeAndDiskUsageCharts } from './ComputeAndDiskUsageCharts'
|
|
import { CreateDiskStorageSchema, DiskStorageSchemaType } from './DiskManagement.schema'
|
|
import { DiskManagementMessage } from './DiskManagement.types'
|
|
import {
|
|
calculateDiskSizeRequiredForIopsWithGp3,
|
|
mapComputeSizeNameToAddonVariantId,
|
|
} from './DiskManagement.utils'
|
|
import { AdvancedSection, ComputeSection, DiskSection } from './DiskManagementForm.sections'
|
|
import { DiskMangementRestartRequiredSection } from './DiskManagementRestartRequiredSection'
|
|
import { DiskManagementReviewAndSubmitDialog } from './DiskManagementReviewAndSubmitDialog/DiskManagementReviewAndSubmitDialog'
|
|
import { useDiskManagementReviewChanges } from './DiskManagementReviewAndSubmitDialog/DiskManagementReviewAndSubmitDialog.hooks'
|
|
import { BillingChangeBadge } from './ui/BillingChangeBadge'
|
|
import {
|
|
DISK_LIMITS,
|
|
DiskType,
|
|
PLAN_DETAILS,
|
|
RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3,
|
|
} from './ui/DiskManagement.constants'
|
|
import { NoticeBar } from './ui/NoticeBar'
|
|
import type { RecommendedComputeForReadReplicas } from '@/components/interfaces/Settings/Infrastructure/ReadReplicas/recommendCompute'
|
|
import { useMainScrollContainer } from '@/components/layouts/MainScrollContainerContext'
|
|
import { PADDING_CLASSES } from '@/components/layouts/Scaffold'
|
|
import { UpgradeToPro } from '@/components/ui/UpgradeToPro'
|
|
import {
|
|
useDiskAttributesQuery,
|
|
useRemainingDurationForDiskAttributeUpdate,
|
|
} from '@/data/config/disk-attributes-query'
|
|
import { useUpdateDiskAttributesMutation } from '@/data/config/disk-attributes-update-mutation'
|
|
import { useDiskAutoscaleCustomConfigQuery } from '@/data/config/disk-autoscale-config-query'
|
|
import { useUpdateDiskAutoscaleConfigMutation } from '@/data/config/disk-autoscale-config-update-mutation'
|
|
import { useDiskUtilizationQuery } from '@/data/config/disk-utilization-query'
|
|
import { useSetProjectStatus } from '@/data/projects/project-detail-query'
|
|
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
|
|
import { useProjectAddonUpdateMutation } from '@/data/subscriptions/project-addon-update-mutation'
|
|
import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query'
|
|
import { AddonVariantId } from '@/data/subscriptions/types'
|
|
import { useResourceWarningsQuery } from '@/data/usage/resource-warnings-query'
|
|
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
|
|
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
|
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
|
import {
|
|
useIsAwsCloudProvider,
|
|
useIsAwsK8sCloudProvider,
|
|
useIsAwsNimbusCloudProvider,
|
|
useIsHighAvailability,
|
|
useSelectedProjectQuery,
|
|
} from '@/hooks/misc/useSelectedProject'
|
|
import { GB, PROJECT_STATUS } from '@/lib/constants'
|
|
|
|
export function DiskManagementForm({
|
|
chartsClassName,
|
|
overviewExtra,
|
|
beforeScaling,
|
|
recommendedCompute,
|
|
onRecommendedComputeApplied,
|
|
}: {
|
|
chartsClassName?: string
|
|
/** Rendered above usage charts in the overview block (for example topology). */
|
|
overviewExtra?: ReactNode
|
|
/** Rendered between overview and the Scaling section (for example read replicas). */
|
|
beforeScaling?: ReactNode
|
|
recommendedCompute?: RecommendedComputeForReadReplicas | null
|
|
onRecommendedComputeApplied?: () => void
|
|
} = {}) {
|
|
const { ref: projectRef } = useParams()
|
|
const { data: project, isPending: isProjectPending } = useSelectedProjectQuery()
|
|
const { data: org } = useSelectedOrganizationQuery()
|
|
const { setProjectStatus } = useSetProjectStatus()
|
|
|
|
const autoscaleSettingsRef = useRef<HTMLDivElement>(null)
|
|
const storageSettingsRef = useRef<HTMLDivElement>(null)
|
|
const computeSettingsRef = useRef<HTMLDivElement>(null)
|
|
const diskSizeSettingsRef = useRef<HTMLDivElement>(null)
|
|
const mainScrollContainer = useMainScrollContainer()
|
|
|
|
const isSpendCapEnabled = org?.plan.id !== 'free' && !org?.usage_billing_enabled
|
|
|
|
const { data: resourceWarnings } = useResourceWarningsQuery({ ref: projectRef })
|
|
// [Joshen Cleanup] JFYI this client side filtering can be cleaned up once BE changes are live which will only return the warnings based on the provided ref
|
|
const projectResourceWarnings = (resourceWarnings ?? [])?.find(
|
|
(warning) => warning.project === project?.ref
|
|
)
|
|
const isReadOnlyMode = projectResourceWarnings?.is_readonly_mode_enabled
|
|
const isAws = useIsAwsCloudProvider()
|
|
const isAwsK8s = useIsAwsK8sCloudProvider()
|
|
const isAwsNimbus = useIsAwsNimbusCloudProvider()
|
|
const isHighAvailability = useIsHighAvailability()
|
|
|
|
const { can: canUpdateDiskConfiguration, isSuccess: isPermissionsLoaded } =
|
|
useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', {
|
|
resource: {
|
|
project_id: project?.id,
|
|
},
|
|
})
|
|
|
|
const { hasAccess, isSuccess: isEntitlementsLoaded } = useCheckEntitlements(
|
|
'instances.compute_update_available_sizes'
|
|
)
|
|
|
|
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false)
|
|
const [refetchInterval, setRefetchInterval] = useState<number | false>(false)
|
|
const [message, setMessageState] = useState<DiskManagementMessage | null>(null)
|
|
|
|
const { data: databases, isSuccess: isReadReplicasSuccess } = useReadReplicasQuery({ projectRef })
|
|
const { data, isSuccess: isDiskAttributesSuccess } = useDiskAttributesQuery(
|
|
{ projectRef },
|
|
{
|
|
refetchInterval,
|
|
refetchOnWindowFocus: false,
|
|
enabled: project != null && isAws,
|
|
}
|
|
)
|
|
|
|
const { isSuccess: isAddonsSuccess } = useProjectAddonsQuery({ projectRef })
|
|
const { isWithinCooldownWindow, isSuccess: isCooldownSuccess } =
|
|
useRemainingDurationForDiskAttributeUpdate({
|
|
projectRef,
|
|
enabled: project != null && isAws,
|
|
})
|
|
const { data: diskUtil, isSuccess: isDiskUtilizationSuccess } = useDiskUtilizationQuery(
|
|
{
|
|
projectRef,
|
|
},
|
|
{ enabled: project != null && isAws }
|
|
)
|
|
|
|
const { data: diskAutoscaleConfig, isSuccess: isDiskAutoscaleConfigSuccess } =
|
|
useDiskAutoscaleCustomConfigQuery({ projectRef }, { enabled: project != null && isAws })
|
|
|
|
const computeSize = project?.infra_compute_size
|
|
? mapComputeSizeNameToAddonVariantId(project?.infra_compute_size)
|
|
: undefined
|
|
|
|
// @ts-ignore
|
|
const { type, iops, throughput_mbps, size_gb } = data?.attributes ?? { size_gb: 0, iops: 0 }
|
|
const { growth_percent, max_size_gb, min_increment_gb } = diskAutoscaleConfig ?? {}
|
|
const defaultValues = {
|
|
storageType: type ?? DiskType.GP3,
|
|
provisionedIOPS: iops,
|
|
throughput: throughput_mbps,
|
|
totalSize: size_gb,
|
|
computeSize: computeSize ?? 'ci_micro',
|
|
growthPercent: growth_percent,
|
|
minIncrementGb: min_increment_gb,
|
|
maxSizeGb: max_size_gb,
|
|
}
|
|
|
|
const form = useForm<DiskStorageSchemaType>({
|
|
resolver: zodResolver(
|
|
CreateDiskStorageSchema({
|
|
defaultTotalSize: defaultValues.totalSize,
|
|
cloudProvider: project?.cloud_provider as CloudProvider,
|
|
isSpendCapEnabled,
|
|
})
|
|
),
|
|
defaultValues,
|
|
mode: 'onBlur',
|
|
reValidateMode: 'onChange',
|
|
})
|
|
|
|
const modifiedComputeSize = useWatch({ control: form.control, name: 'computeSize' })
|
|
|
|
const isSuccess =
|
|
isAddonsSuccess &&
|
|
isDiskAttributesSuccess &&
|
|
isDiskUtilizationSuccess &&
|
|
isReadReplicasSuccess &&
|
|
isDiskAutoscaleConfigSuccess &&
|
|
isCooldownSuccess
|
|
|
|
const isRequestingChanges = data?.requested_modification !== undefined
|
|
const readReplicas = (databases ?? []).filter((db) => db.identifier !== projectRef)
|
|
const isPlanUpgradeRequired = !hasAccess
|
|
|
|
const {
|
|
computeSizePrice,
|
|
diskSizePrice,
|
|
totalBeforePrice,
|
|
totalAfterPrice,
|
|
advancedBeforePrice,
|
|
advancedAfterPrice,
|
|
showComputeBillingBadge,
|
|
showDiskBillingBadge,
|
|
showAdvancedBillingBadge,
|
|
} = useDiskManagementReviewChanges(form, readReplicas.length)
|
|
|
|
const { formState } = form
|
|
const errors = formState.errors
|
|
const usedSize = Math.round(((diskUtil?.metrics.fs_used_bytes ?? 0) / GB) * 100) / 100
|
|
const totalSize = formState.defaultValues?.totalSize || 0
|
|
const usedPercentage = (usedSize / totalSize) * 100
|
|
|
|
const disableIopsThroughputConfig =
|
|
isHighAvailability ||
|
|
(modifiedComputeSize &&
|
|
!isSpendCapEnabled &&
|
|
RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(modifiedComputeSize))
|
|
|
|
const watchedTotalSize = useWatch({ control: form.control, name: 'totalSize' }) ?? 0
|
|
const watchedStorageType = useWatch({ control: form.control, name: 'storageType' })
|
|
// Minimum disk size where the platform API will accept an IOPS payload (500 IOPS/GB rule).
|
|
const minDiskSizeForCustomIops = calculateDiskSizeRequiredForIopsWithGp3(
|
|
DISK_LIMITS[DiskType.GP3].minIops
|
|
)
|
|
// Suggested target when prompting a resize, sits above the floor so users
|
|
// aren't pinned at the minimum during the 4-hour disk-config cooldown.
|
|
const suggestedDiskSizeForCustomIops = PLAN_DETAILS.pro.includedDiskGB.gp3
|
|
const isDiskTooSmallForCustomIops =
|
|
watchedStorageType === 'gp3' && watchedTotalSize < minDiskSizeForCustomIops
|
|
|
|
const isBranch = project?.parent_project_ref !== undefined
|
|
|
|
const disableDiskSizeInput =
|
|
isRequestingChanges ||
|
|
isPlanUpgradeRequired ||
|
|
isWithinCooldownWindow ||
|
|
isHighAvailability ||
|
|
!canUpdateDiskConfiguration ||
|
|
!isAws
|
|
|
|
const disableDiskInputs = disableDiskSizeInput || isSpendCapEnabled || isHighAvailability
|
|
|
|
// Compute resizing is not supported for High Availability projects during Alpha
|
|
const disableComputeInputs = isPlanUpgradeRequired || isHighAvailability
|
|
const isDirty = !!Object.keys(form.formState.dirtyFields).length
|
|
const isProjectResizing = project?.status === PROJECT_STATUS.RESIZING
|
|
const isProjectRequestingDiskChanges = isRequestingChanges && !isProjectResizing
|
|
const noPermissions = isPermissionsLoaded && !canUpdateDiskConfiguration
|
|
|
|
const isDiskNoticeVisible = !isProjectPending && !(isAws || isAwsNimbus)
|
|
|
|
const { mutateAsync: updateDiskConfiguration, isPending: isUpdatingDisk } =
|
|
useUpdateDiskAttributesMutation({
|
|
// this is to suppress to toast message
|
|
onError: () => {},
|
|
onSuccess: () => setRefetchInterval(2000),
|
|
})
|
|
const { mutateAsync: updateSubscriptionAddon, isPending: isUpdatingCompute } =
|
|
useProjectAddonUpdateMutation({
|
|
// this is to suppress to toast message
|
|
onError: () => {},
|
|
onSuccess: () => {
|
|
//Manually set project status to RESIZING, Project status should be RESIZING on next project status request.
|
|
if (projectRef) setProjectStatus({ ref: projectRef, status: PROJECT_STATUS.RESIZING })
|
|
},
|
|
})
|
|
const { mutateAsync: updateDiskAutoscaleConfig, isPending: isUpdatingDiskAutoscaleConfig } =
|
|
useUpdateDiskAutoscaleConfigMutation({
|
|
// this is to suppress to toast message
|
|
onError: () => {},
|
|
})
|
|
|
|
const isUpdatingConfig = isUpdatingDisk || isUpdatingCompute || isUpdatingDiskAutoscaleConfig
|
|
|
|
const onSubmit = async (data: DiskStorageSchemaType) => {
|
|
let payload = data
|
|
let willUpdateDiskConfiguration = false
|
|
setMessageState(null)
|
|
|
|
// [Joshen] Skip disk configuration related stuff for AWS Nimbus
|
|
try {
|
|
if (
|
|
!isAwsK8s &&
|
|
!isAwsNimbus &&
|
|
(payload.storageType !== form.formState.defaultValues?.storageType ||
|
|
payload.provisionedIOPS !== form.formState.defaultValues?.provisionedIOPS ||
|
|
payload.throughput !== form.formState.defaultValues?.throughput ||
|
|
payload.totalSize !== form.formState.defaultValues?.totalSize)
|
|
) {
|
|
willUpdateDiskConfiguration = true
|
|
|
|
await updateDiskConfiguration({
|
|
ref: projectRef,
|
|
provisionedIOPS: payload.provisionedIOPS!,
|
|
storageType: payload.storageType,
|
|
totalSize: payload.totalSize!,
|
|
throughput: payload.throughput,
|
|
})
|
|
}
|
|
|
|
if (
|
|
!isAwsK8s &&
|
|
!isAwsNimbus &&
|
|
(payload.growthPercent !== form.formState.defaultValues?.growthPercent ||
|
|
payload.minIncrementGb !== form.formState.defaultValues?.minIncrementGb ||
|
|
payload.maxSizeGb !== form.formState.defaultValues?.maxSizeGb)
|
|
) {
|
|
await updateDiskAutoscaleConfig({
|
|
projectRef,
|
|
growthPercent: payload.growthPercent,
|
|
minIncrementGb: payload.minIncrementGb,
|
|
maxSizeGb: payload.maxSizeGb,
|
|
})
|
|
}
|
|
|
|
if (
|
|
!isHighAvailability &&
|
|
payload.computeSize !== form.formState.defaultValues?.computeSize
|
|
) {
|
|
await updateSubscriptionAddon({
|
|
projectRef: projectRef,
|
|
// cast variant to AddonVariantId to satisfy type
|
|
variant: payload.computeSize as AddonVariantId,
|
|
type: 'compute_instance',
|
|
suppressToast: true,
|
|
})
|
|
}
|
|
|
|
setIsDialogOpen(false)
|
|
form.reset(data as DiskStorageSchemaType)
|
|
// Disk resizes get their own completion toast once polling confirms it's applied
|
|
if (!willUpdateDiskConfiguration) {
|
|
toast.success('Successfully updated disk settings!')
|
|
}
|
|
} catch (error: unknown) {
|
|
setMessageState({
|
|
message: error instanceof Error ? error.message : 'An unknown error occurred',
|
|
type: 'error',
|
|
})
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!isDiskAttributesSuccess) return
|
|
// @ts-ignore
|
|
const { type, iops, throughput_mbps, size_gb } = data?.attributes ?? { size_gb: 0 }
|
|
const formValues = {
|
|
storageType: type,
|
|
provisionedIOPS: iops,
|
|
throughput: throughput_mbps,
|
|
totalSize: size_gb,
|
|
computeSize: form.getValues('computeSize'),
|
|
}
|
|
|
|
if (!('requested_modification' in data)) {
|
|
if (refetchInterval !== false) {
|
|
form.reset(formValues)
|
|
setRefetchInterval(false)
|
|
toast.success('Disk configuration changes have been successfully applied!')
|
|
}
|
|
} else {
|
|
setRefetchInterval(2000)
|
|
}
|
|
}, [data, isDiskAttributesSuccess, form, refetchInterval])
|
|
|
|
// We only support disk configurations for >=Large instances
|
|
// If a customer downgrades back to <Large, we should reset the storage settings to avoid incurring unnecessary costs
|
|
useEffect(() => {
|
|
if (modifiedComputeSize && project?.infra_compute_size && isDialogOpen) {
|
|
if (RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(modifiedComputeSize)) {
|
|
form.setValue('storageType', DiskType.GP3)
|
|
form.setValue('throughput', DISK_LIMITS['gp3'].minThroughput)
|
|
form.setValue('provisionedIOPS', DISK_LIMITS['gp3'].minIops)
|
|
}
|
|
}
|
|
}, [modifiedComputeSize, form, isDialogOpen, project])
|
|
|
|
useEffect(() => {
|
|
// Initialize field values properly when data has been loaded, preserving any user changes.
|
|
// Disk attribute queries never run for High Availability projects (non-AWS cloud provider),
|
|
// so the project loading is the initialization signal there.
|
|
if (isDiskAttributesSuccess || isSuccess || isHighAvailability) {
|
|
form.reset(defaultValues, {})
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [isSuccess, isDiskAttributesSuccess, isHighAvailability])
|
|
|
|
// Apply the recommendation only after the sheet's close lifecycle has completed.
|
|
useEffect(() => {
|
|
// The compute add-on supplies the option. Keep the recommendation pending
|
|
// until disk attributes have initialised the form, so a later reset cannot
|
|
// overwrite it. Other infrastructure requests are unrelated to this handoff.
|
|
if (!recommendedCompute || !isAddonsSuccess || isHighAvailability) return
|
|
|
|
form.setValue('computeSize', recommendedCompute, {
|
|
shouldDirty: true,
|
|
shouldValidate: true,
|
|
})
|
|
void form.trigger(['provisionedIOPS', 'throughput'])
|
|
if (isDiskAttributesSuccess) onRecommendedComputeApplied?.()
|
|
|
|
const element = computeSettingsRef.current
|
|
if (!element) return
|
|
|
|
element
|
|
.querySelector<HTMLElement>(`[id="${recommendedCompute}"]`)
|
|
?.focus({ preventScroll: true })
|
|
|
|
if (!mainScrollContainer) {
|
|
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
return
|
|
}
|
|
|
|
const scrollMarginTop = Number.parseFloat(getComputedStyle(element).scrollMarginTop) || 0
|
|
const top =
|
|
mainScrollContainer.scrollTop +
|
|
element.getBoundingClientRect().top -
|
|
mainScrollContainer.getBoundingClientRect().top -
|
|
scrollMarginTop
|
|
|
|
mainScrollContainer.scrollTo({ top: Math.max(0, top), behavior: 'smooth' })
|
|
}, [
|
|
form,
|
|
isAddonsSuccess,
|
|
isDiskAttributesSuccess,
|
|
isHighAvailability,
|
|
mainScrollContainer,
|
|
onRecommendedComputeApplied,
|
|
recommendedCompute,
|
|
])
|
|
|
|
// Deep links from billing / UpgradePlanButton (e.g. #compute).
|
|
useEffect(() => {
|
|
if (typeof window === 'undefined') return
|
|
if (window.location.hash !== '#compute') return
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
computeSettingsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
}, 100)
|
|
|
|
return () => clearTimeout(timeoutId)
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
const fieldErrors = Object.keys(errors)
|
|
if (fieldErrors.length === 0) return
|
|
|
|
const scrollTargets = [
|
|
{
|
|
hasError:
|
|
fieldErrors.includes('maxSizeGb') ||
|
|
fieldErrors.includes('growthPercent') ||
|
|
fieldErrors.includes('minIncrementGb'),
|
|
ref: autoscaleSettingsRef,
|
|
},
|
|
{
|
|
hasError: fieldErrors.includes('throughput') || fieldErrors.includes('provisionedIOPS'),
|
|
ref: storageSettingsRef,
|
|
},
|
|
{ hasError: fieldErrors.includes('totalSize'), ref: diskSizeSettingsRef },
|
|
{ hasError: fieldErrors.includes('computeSize'), ref: computeSettingsRef },
|
|
]
|
|
const scrollTarget = scrollTargets.find(({ hasError }) => hasError)?.ref ?? null
|
|
|
|
if (!scrollTarget) return
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
scrollTarget.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
}, 100)
|
|
|
|
return () => clearTimeout(timeoutId)
|
|
}, [errors])
|
|
|
|
return (
|
|
<Form {...form}>
|
|
<form id="disk-compute-form" onSubmit={form.handleSubmit(onSubmit)}>
|
|
<PageContainer size="default" className="pb-16">
|
|
<PageSection>
|
|
<PageSectionContent>
|
|
{overviewExtra}
|
|
<ComputeAndDiskUsageCharts className={cn(overviewExtra && 'mt-6', chartsClassName)} />
|
|
</PageSectionContent>
|
|
</PageSection>
|
|
|
|
{(isProjectResizing ||
|
|
isProjectRequestingDiskChanges ||
|
|
(isEntitlementsLoaded && !isPlanUpgradeRequired && noPermissions)) && (
|
|
<div className="relative flex flex-col gap-10">
|
|
<DiskMangementRestartRequiredSection
|
|
visible={isProjectResizing}
|
|
title="Your project will now automatically restart."
|
|
description="Your project will be unavailable for up to 2 mins."
|
|
/>
|
|
<NoticeBar
|
|
type="default"
|
|
visible={isProjectRequestingDiskChanges}
|
|
title="Disk configuration changes have been requested"
|
|
description="The requested changes will be applied to your disk shortly"
|
|
/>
|
|
<NoticeBar
|
|
type="default"
|
|
visible={isEntitlementsLoaded && !isPlanUpgradeRequired && noPermissions}
|
|
title="You do not have permission to update disk configuration"
|
|
description="Please contact your organization administrator to update your disk configuration"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{beforeScaling}
|
|
|
|
<PageSection>
|
|
<PageSectionMeta>
|
|
<PageSectionSummary>
|
|
<PageSectionTitle>Scaling</PageSectionTitle>
|
|
</PageSectionSummary>
|
|
</PageSectionMeta>
|
|
{isEntitlementsLoaded && isPlanUpgradeRequired && (
|
|
<UpgradeToPro
|
|
featureProposition="configure compute and disk"
|
|
primaryText="Only available on Pro Plan and above"
|
|
secondaryText="Upgrade to the Pro Plan to configure compute and disk settings."
|
|
/>
|
|
)}
|
|
<PageSectionContent>
|
|
<ComputeSection
|
|
form={form}
|
|
settingsRef={computeSettingsRef}
|
|
showBillingBadge={showComputeBillingBadge}
|
|
beforePrice={Number(computeSizePrice.oldPrice)}
|
|
afterPrice={Number(computeSizePrice.newPrice)}
|
|
disabled={disableComputeInputs}
|
|
/>
|
|
|
|
<DiskSection
|
|
form={form}
|
|
settingsRef={diskSizeSettingsRef}
|
|
showBillingBadge={showDiskBillingBadge}
|
|
beforePrice={Number(diskSizePrice.oldPrice)}
|
|
afterPrice={Number(diskSizePrice.newPrice)}
|
|
isAws={isAws}
|
|
isAwsK8s={isAwsK8s}
|
|
isBranch={isBranch}
|
|
isNoticeVisible={isDiskNoticeVisible}
|
|
isReadOnlyMode={!!isReadOnlyMode}
|
|
usedPercentage={usedPercentage}
|
|
isWithinCooldownWindow={isWithinCooldownWindow}
|
|
currentDiskSizeGb={defaultValues.totalSize}
|
|
disableDiskSizeInput={disableDiskSizeInput}
|
|
/>
|
|
|
|
{isAws && (
|
|
<AdvancedSection
|
|
form={form}
|
|
autoscaleSettingsRef={autoscaleSettingsRef}
|
|
storageSettingsRef={storageSettingsRef}
|
|
showBillingBadge={showAdvancedBillingBadge}
|
|
beforePrice={advancedBeforePrice}
|
|
afterPrice={advancedAfterPrice}
|
|
disableIopsThroughputConfig={disableIopsThroughputConfig}
|
|
canUpdateDiskConfiguration={canUpdateDiskConfiguration}
|
|
isDiskTooSmallForCustomIops={isDiskTooSmallForCustomIops}
|
|
disableDiskInputs={disableDiskInputs}
|
|
disableDiskSizeInput={disableDiskSizeInput}
|
|
suggestedDiskSizeForCustomIops={suggestedDiskSizeForCustomIops}
|
|
/>
|
|
)}
|
|
</PageSectionContent>
|
|
</PageSection>
|
|
</PageContainer>
|
|
|
|
<AnimatePresence>
|
|
{isDirty ? (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: 20 }}
|
|
transition={{ duration: 0.1, delay: 0.2 }}
|
|
className="z-10 w-full left-0 right-0 sticky bottom-0 bg-surface-100 border-t h-16 items-center flex"
|
|
>
|
|
<div
|
|
className={cn(
|
|
'mx-auto w-full max-w-[1200px]',
|
|
PADDING_CLASSES,
|
|
'flex items-center justify-end gap-3'
|
|
)}
|
|
>
|
|
<BillingChangeBadge
|
|
show={isDirty}
|
|
beforePrice={totalBeforePrice}
|
|
afterPrice={totalAfterPrice}
|
|
/>
|
|
<Button onClick={() => form.reset()} disabled={!isDirty} size="medium">
|
|
Cancel
|
|
</Button>
|
|
<DiskManagementReviewAndSubmitDialog
|
|
loading={isUpdatingConfig}
|
|
disabled={noPermissions}
|
|
form={form}
|
|
numReplicas={readReplicas.length}
|
|
isDialogOpen={isDialogOpen}
|
|
onSubmit={onSubmit}
|
|
setIsDialogOpen={setIsDialogOpen}
|
|
message={message}
|
|
/>
|
|
</div>
|
|
</motion.div>
|
|
) : null}
|
|
</AnimatePresence>
|
|
</form>
|
|
</Form>
|
|
)
|
|
}
|