mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
feat: revamped instance size selection (#20765)
This commit is contained in:
1 parent
e41137ee03
commit
becd39cc23
7 files changed
+326
-244
No files matched your search
+40
-13
@@ -1,3 +1,4 @@
|
||||
import { ProjectInfo } from 'data/projects/projects-query'
|
||||
import { OrgSubscription, ProjectAddon } from 'data/subscriptions/types'
|
||||
import { PricingInformation } from 'shared-data'
|
||||
import { Alert, IconAlertOctagon, IconMinusCircle, IconPauseCircle, Modal } from 'ui'
|
||||
@@ -8,6 +9,7 @@ export interface DowngradeModalProps {
|
||||
subscription?: OrgSubscription
|
||||
onClose: () => void
|
||||
onConfirm: () => void
|
||||
projects: ProjectInfo[]
|
||||
}
|
||||
|
||||
const ProjectDowngradeListItem = ({ projectAddon }: { projectAddon: ProjectAddon }) => {
|
||||
@@ -39,7 +41,26 @@ const DowngradeModal = ({
|
||||
subscription,
|
||||
onClose,
|
||||
onConfirm,
|
||||
projects,
|
||||
}: DowngradeModalProps) => {
|
||||
// Filter out the micro addon as we're dealing with that separately
|
||||
const previousProjectAddons =
|
||||
subscription?.project_addons.flatMap((projectAddons) => {
|
||||
const addons = projectAddons.addons.filter((it) => it.variant.identifier !== 'ci_micro')
|
||||
if (!addons.length) {
|
||||
return []
|
||||
} else {
|
||||
return {
|
||||
...projectAddons,
|
||||
// Overwrite addons, filtered out the micro addon
|
||||
addons,
|
||||
}
|
||||
}
|
||||
}) || []
|
||||
|
||||
const hasInstancesOnMicro = projects.some((project) => project.infra_compute_size === 'micro')
|
||||
const downgradingToNano = subscription?.nano_enabled === true
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="large"
|
||||
@@ -63,19 +84,25 @@ const DowngradeModal = ({
|
||||
</p>
|
||||
</Alert>
|
||||
|
||||
{(subscription?.project_addons.length ?? 0) > 0 && (
|
||||
<Alert
|
||||
title={`A total of ${subscription?.project_addons.length} project(s) will be affected from the downgrade`}
|
||||
variant="warning"
|
||||
withIcon
|
||||
>
|
||||
<ul className="space-y-1 max-h-[100px] overflow-y-auto">
|
||||
{subscription?.project_addons.map((project) => (
|
||||
<ProjectDowngradeListItem key={project.ref} projectAddon={project} />
|
||||
))}
|
||||
</ul>
|
||||
</Alert>
|
||||
)}
|
||||
{(previousProjectAddons.length ?? 0) > 0 ||
|
||||
(hasInstancesOnMicro && downgradingToNano && (
|
||||
<Alert title={`Projects affected by the downgrade`} variant="warning" withIcon>
|
||||
<ul className="space-y-1 max-h-[100px] overflow-y-auto">
|
||||
{previousProjectAddons.map((project) => (
|
||||
<ProjectDowngradeListItem key={project.ref} projectAddon={project} />
|
||||
))}
|
||||
|
||||
{projects
|
||||
.filter((it) => it.infra_compute_size === 'micro')
|
||||
.map((project) => (
|
||||
<li className="list-disc ml-6" key={project.id}>
|
||||
{project.name}: Compute will be downgraded. Project will also{' '}
|
||||
<span className="font-bold">need to be restarted</span>.
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Alert>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<ul className="mt-4 space-y-5 text-sm">
|
||||
|
||||
+7
@@ -35,6 +35,7 @@ import ExitSurveyModal from './ExitSurveyModal'
|
||||
import MembersExceedLimitModal from './MembersExceedLimitModal'
|
||||
import PaymentMethodSelection from './PaymentMethodSelection'
|
||||
import { formatCurrency } from 'lib/helpers'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
|
||||
const PlanUpdateSidePanel = () => {
|
||||
const { ui } = useStore()
|
||||
@@ -52,6 +53,11 @@ const PlanUpdateSidePanel = () => {
|
||||
PermissionAction.BILLING_WRITE,
|
||||
'stripe.subscriptions'
|
||||
)
|
||||
const { data: allProjects } = useProjectsQuery()
|
||||
const orgProjects = (allProjects || []).filter(
|
||||
(it) => it.organization_id === selectedOrganization?.id
|
||||
)
|
||||
|
||||
const snap = useOrgSettingsPageStateSnapshot()
|
||||
const visible = snap.panelKey === 'subscriptionPlan'
|
||||
const onClose = () => {
|
||||
@@ -329,6 +335,7 @@ const PlanUpdateSidePanel = () => {
|
||||
subscription={subscription}
|
||||
onClose={() => setSelectedTier(undefined)}
|
||||
onConfirm={onConfirmDowngrade}
|
||||
projects={orgProjects}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
IconAlertTriangle,
|
||||
IconChevronRight,
|
||||
IconExternalLink,
|
||||
IconInfo,
|
||||
} from 'ui'
|
||||
|
||||
import {
|
||||
@@ -42,13 +43,14 @@ import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { ProjectAddonVariantMeta } from 'data/subscriptions/types'
|
||||
import { useFlag, useProjectByRef, useSelectedOrganization } from 'hooks'
|
||||
import { getCloudProviderArchitecture } from 'lib/cloudprovider-utils'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import { BASE_PATH, INSTANCE_MICRO_SPECS, INSTANCE_NANO_SPECS } from 'lib/constants'
|
||||
import { getDatabaseMajorVersion, getSemanticVersion } from 'lib/helpers'
|
||||
import { SUBSCRIPTION_PANEL_KEYS, useSubscriptionPageStateSnapshot } from 'state/subscription-page'
|
||||
import ComputeInstanceSidePanel from './ComputeInstanceSidePanel'
|
||||
import CustomDomainSidePanel from './CustomDomainSidePanel'
|
||||
import IPv4SidePanel from './IPv4SidePanel'
|
||||
import PITRSidePanel from './PITRSidePanel'
|
||||
import IPv4SidePanel from './IPv4SidePanel'
|
||||
import { capitalize } from 'lodash'
|
||||
|
||||
const Addons = () => {
|
||||
const { resolvedTheme } = useTheme()
|
||||
@@ -101,7 +103,17 @@ const Addons = () => {
|
||||
const selectedAddons = addons?.selected_addons ?? []
|
||||
const { computeInstance, pitr, customDomain, ipv4 } = getAddons(selectedAddons)
|
||||
|
||||
const meta = computeInstance?.variant?.meta as ProjectAddonVariantMeta | undefined
|
||||
const meta = useMemo(() => {
|
||||
const computeMeta = computeInstance?.variant?.meta as ProjectAddonVariantMeta | undefined
|
||||
|
||||
if (!computeMeta && selectedProject?.infra_compute_size === 'nano') {
|
||||
return INSTANCE_NANO_SPECS
|
||||
} else if (selectedProject?.infra_compute_size === 'micro') {
|
||||
return INSTANCE_MICRO_SPECS
|
||||
}
|
||||
|
||||
return computeMeta
|
||||
}, [selectedProject, computeInstance])
|
||||
|
||||
const canUpdateIPv4 = projectSettings?.project.db_ip_addr_config === 'ipv6'
|
||||
|
||||
@@ -159,11 +171,23 @@ const Addons = () => {
|
||||
|
||||
{isSuccess && (
|
||||
<>
|
||||
{selectedProject?.infra_compute_size === 'nano' && subscription?.plan.id !== 'free' && (
|
||||
<ScaffoldContainer className="mt-4">
|
||||
<Alert_Shadcn_ variant="default">
|
||||
<IconInfo strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>Free compute upgrade to Micro</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
Paid Plans include a free upgrade to Micro compute. Your project is ready to
|
||||
upgrade for no additional charges.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
</ScaffoldContainer>
|
||||
)}
|
||||
<ScaffoldContainer>
|
||||
<ScaffoldSection>
|
||||
<ScaffoldSectionDetail>
|
||||
<div className="space-y-6">
|
||||
<p className="m-0">Optimized compute</p>
|
||||
<p className="m-0">Compute Size</p>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground-light m-0">More information</p>
|
||||
<div>
|
||||
@@ -198,15 +222,15 @@ const Addons = () => {
|
||||
<div>
|
||||
<div className="rounded-md bg-surface-100 border border-muted w-[160px] h-[96px] overflow-hidden">
|
||||
<Image
|
||||
alt="Optimized Compute"
|
||||
alt="Compute size"
|
||||
width={160}
|
||||
height={96}
|
||||
src={
|
||||
computeInstance !== undefined
|
||||
? `${BASE_PATH}/img/optimized-compute-on${
|
||||
['nano', 'micro'].includes(selectedProject?.infra_compute_size || 'micro')
|
||||
? `${BASE_PATH}/img/optimized-compute-off${
|
||||
resolvedTheme?.includes('dark') ? '' : '--light'
|
||||
}.svg`
|
||||
: `${BASE_PATH}/img/optimized-compute-off${
|
||||
: `${BASE_PATH}/img/optimized-compute-on${
|
||||
resolvedTheme?.includes('dark') ? '' : '--light'
|
||||
}.svg`
|
||||
}
|
||||
@@ -215,7 +239,11 @@ const Addons = () => {
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
<p className="text-sm text-foreground-light">Current option:</p>
|
||||
<p>{computeInstance?.variant.name ?? 'Micro'}</p>
|
||||
<p>
|
||||
{computeInstance?.variant.name ??
|
||||
capitalize(selectedProject?.infra_compute_size) ??
|
||||
'Micro'}
|
||||
</p>
|
||||
<ProjectUpdateDisabledTooltip
|
||||
projectUpdateDisabled={projectUpdateDisabled}
|
||||
projectNotActive={!isProjectActive}
|
||||
@@ -239,7 +267,7 @@ const Addons = () => {
|
||||
>
|
||||
<p>
|
||||
Your workload is currently running at the baseline disk IO bandwidth at{' '}
|
||||
{meta?.baseline_disk_io_mbs?.toLocaleString() ?? 87} Mbps and may suffer
|
||||
{meta?.baseline_disk_io_mbs?.toLocaleString() ?? '-'} Mbps and may suffer
|
||||
degradation in performance.
|
||||
</p>
|
||||
<p className="mt-1">
|
||||
@@ -257,7 +285,7 @@ const Addons = () => {
|
||||
<p>
|
||||
If the disk IO budget drops to zero, your workload will run at the
|
||||
baseline disk IO bandwidth at{' '}
|
||||
{meta?.baseline_disk_io_mbs?.toLocaleString() ?? 87} Mbps and may suffer
|
||||
{meta?.baseline_disk_io_mbs?.toLocaleString() ?? '-'} Mbps and may suffer
|
||||
degradation in performance.
|
||||
</p>
|
||||
<p className="mt-1">
|
||||
@@ -280,7 +308,7 @@ const Addons = () => {
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
<p className="text-sm">{meta?.memory_gb ?? 1} GB</p>
|
||||
<p className="text-sm">{meta?.memory_gb ?? '-'} GB</p>
|
||||
</div>
|
||||
<div className="w-full flex items-center justify-between border-b py-2">
|
||||
<Link href={`/project/${projectRef}/settings/infrastructure#cpu`}>
|
||||
@@ -296,17 +324,17 @@ const Addons = () => {
|
||||
</div>
|
||||
</Link>
|
||||
<p className="text-sm">
|
||||
{meta?.cpu_cores ?? 2}-core {cpuArchitecture}{' '}
|
||||
{meta?.cpu_cores ?? '?'}-core {cpuArchitecture}{' '}
|
||||
{meta?.cpu_dedicated ? '(Dedicated)' : '(Shared)'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex items-center justify-between border-b py-2">
|
||||
<p className="text-sm text-foreground-light">No. of direct connections</p>
|
||||
<p className="text-sm">{meta?.connections_direct ?? 60}</p>
|
||||
<p className="text-sm">{meta?.connections_direct ?? '-'}</p>
|
||||
</div>
|
||||
<div className="w-full flex items-center justify-between border-b py-2">
|
||||
<p className="text-sm text-foreground-light">No. of pooler connections</p>
|
||||
<p className="text-sm">{meta?.connections_pooler ?? 200}</p>
|
||||
<p className="text-sm">{meta?.connections_pooler ?? '-'}</p>
|
||||
</div>
|
||||
<div className="w-full flex items-center justify-between border-b py-2">
|
||||
<Link href={`/project/${projectRef}/settings/infrastructure#disk_io`}>
|
||||
@@ -322,7 +350,7 @@ const Addons = () => {
|
||||
</div>
|
||||
</Link>
|
||||
<p className="text-sm">
|
||||
{meta?.max_disk_io_mbs?.toLocaleString() ?? '2,085'} Mbps
|
||||
{meta?.max_disk_io_mbs?.toLocaleString() ?? '-'} Mbps
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex items-center justify-between py-2">
|
||||
@@ -339,7 +367,7 @@ const Addons = () => {
|
||||
</div>
|
||||
</Link>
|
||||
<p className="text-sm">
|
||||
{meta?.baseline_disk_io_mbs?.toLocaleString() ?? 87} Mbps
|
||||
{meta?.baseline_disk_io_mbs?.toLocaleString() ?? '-'} Mbps
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useQueryClient } from '@tanstack/react-query'
|
||||
import clsx from 'clsx'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { useTheme } from 'next-themes'
|
||||
@@ -14,13 +14,15 @@ import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-
|
||||
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks'
|
||||
import { getCloudProviderArchitecture } from 'lib/cloudprovider-utils'
|
||||
import { BASE_PATH, PROJECT_STATUS } from 'lib/constants'
|
||||
import { INSTANCE_MICRO_SPECS, PROJECT_STATUS } from 'lib/constants'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useSubscriptionPageStateSnapshot } from 'state/subscription-page'
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Badge,
|
||||
Button,
|
||||
IconAlertTriangle,
|
||||
IconExternalLink,
|
||||
@@ -32,41 +34,17 @@ import {
|
||||
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { AddonVariantId } from 'data/subscriptions/types'
|
||||
import { formatCurrency } from 'lib/helpers'
|
||||
|
||||
const COMPUTE_CATEGORY_OPTIONS: {
|
||||
id: 'micro' | 'optimized'
|
||||
name: string
|
||||
imageUrl: string
|
||||
imageUrlLight: string
|
||||
}[] = [
|
||||
{
|
||||
id: 'micro',
|
||||
name: 'Micro Compute',
|
||||
imageUrl: `${BASE_PATH}/img/optimized-compute-off.svg`,
|
||||
imageUrlLight: `${BASE_PATH}/img/optimized-compute-off--light.svg`,
|
||||
},
|
||||
{
|
||||
id: 'optimized',
|
||||
name: 'Optimized Compute',
|
||||
imageUrl: `${BASE_PATH}/img/optimized-compute-on.svg`,
|
||||
imageUrlLight: `${BASE_PATH}/img/optimized-compute-on--light.svg`,
|
||||
},
|
||||
]
|
||||
import { AddonVariantId, ProjectAddonVariantMeta } from 'data/subscriptions/types'
|
||||
|
||||
const ComputeInstanceSidePanel = () => {
|
||||
const queryClient = useQueryClient()
|
||||
const { ui } = useStore()
|
||||
const router = useRouter()
|
||||
const { ref: projectRef } = useParams()
|
||||
const { resolvedTheme } = useTheme()
|
||||
const { project: selectedProject } = useProjectContext()
|
||||
const organization = useSelectedOrganization()
|
||||
|
||||
const [showConfirmationModal, setShowConfirmationModal] = useState(false)
|
||||
const [selectedCategory, setSelectedCategory] = useState<'micro' | 'optimized'>('micro')
|
||||
const [selectedOption, setSelectedOption] = useState<string>('ci_micro')
|
||||
|
||||
const canUpdateCompute = useCheckPermissions(
|
||||
PermissionAction.BILLING_WRITE,
|
||||
@@ -109,7 +87,7 @@ const ComputeInstanceSidePanel = () => {
|
||||
ui.setNotification({
|
||||
duration: 8000,
|
||||
category: 'success',
|
||||
message: `Successfully updated compute instance to Micro. Your project is currently being restarted to update its instance`,
|
||||
message: `Successfully updated compute instance. Your project is currently being restarted to update its instance`,
|
||||
})
|
||||
setProjectStatus(queryClient, projectRef!, PROJECT_STATUS.RESTORING)
|
||||
onClose()
|
||||
@@ -123,32 +101,77 @@ const ComputeInstanceSidePanel = () => {
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState<string>('')
|
||||
|
||||
const isSubmitting = isUpdating || isRemoving
|
||||
|
||||
const projectId = selectedProject?.id
|
||||
const cpuArchitecture = getCloudProviderArchitecture(selectedProject?.cloud_provider)
|
||||
const selectedAddons = addons?.selected_addons ?? []
|
||||
const availableAddons = addons?.available_addons ?? []
|
||||
const availableAddons = useMemo(() => {
|
||||
return addons?.available_addons ?? []
|
||||
}, [addons])
|
||||
|
||||
const isFreePlan = subscription?.plan?.id === 'free'
|
||||
const subscriptionCompute = selectedAddons.find((addon) => addon.type === 'compute_instance')
|
||||
const pitrAddon = selectedAddons.find((addon) => addon.type === 'pitr')
|
||||
const availableOptions =
|
||||
availableAddons.find((addon) => addon.type === 'compute_instance')?.variants ?? []
|
||||
const selectedCompute = availableOptions.find((option) => option.identifier === selectedOption)
|
||||
const hasChanges = selectedOption !== (subscriptionCompute?.variant.identifier ?? 'ci_micro')
|
||||
|
||||
const blockMicroDowngradeDueToPitr =
|
||||
pitrAddon !== undefined && selectedOption === 'ci_micro' && hasChanges
|
||||
const hasMicroOptionFromApi = useMemo(() => {
|
||||
return (
|
||||
availableAddons.find((addon) => addon.type === 'compute_instance')?.variants ?? []
|
||||
).some((variant) => variant.identifier === 'ci_micro')
|
||||
}, [availableAddons])
|
||||
|
||||
const availableOptions = useMemo(() => {
|
||||
const computeOptions =
|
||||
availableAddons.find((addon) => addon.type === 'compute_instance')?.variants ?? []
|
||||
|
||||
// Backwards comp until API is deployed
|
||||
if (!hasMicroOptionFromApi) {
|
||||
// Unshift to push to start of array
|
||||
computeOptions.unshift({
|
||||
identifier: 'ci_micro',
|
||||
name: 'Micro',
|
||||
price_description: '$0.01344/hour (~$10/month)',
|
||||
price: 0.01344,
|
||||
price_interval: 'hourly',
|
||||
price_type: 'usage',
|
||||
// @ts-ignore API types it as Record<string, never>
|
||||
meta: {
|
||||
cpu_cores: INSTANCE_MICRO_SPECS.cpu_cores,
|
||||
cpu_dedicated: INSTANCE_MICRO_SPECS.cpu_dedicated,
|
||||
memory_gb: INSTANCE_MICRO_SPECS.memory_gb,
|
||||
baseline_disk_io_mbs: INSTANCE_MICRO_SPECS.baseline_disk_io_mbs,
|
||||
max_disk_io_mbs: INSTANCE_MICRO_SPECS.max_disk_io_mbs,
|
||||
connections_direct: INSTANCE_MICRO_SPECS.connections_direct,
|
||||
connections_pooler: INSTANCE_MICRO_SPECS.connections_pooler,
|
||||
} as ProjectAddonVariantMeta,
|
||||
})
|
||||
}
|
||||
|
||||
return computeOptions
|
||||
}, [availableAddons, hasMicroOptionFromApi])
|
||||
|
||||
const defaultInstanceSize = useMemo(() => {
|
||||
if (!selectedProject) return ''
|
||||
|
||||
return selectedProject.infra_compute_size === 'nano' ? 'ci_nano' : 'ci_micro'
|
||||
}, [selectedProject])
|
||||
|
||||
const selectedCompute = availableOptions.find((option) => option.identifier === selectedOption)
|
||||
const hasChanges =
|
||||
selectedOption !== (subscriptionCompute?.variant.identifier ?? defaultInstanceSize)
|
||||
|
||||
const blockDowngradeDueToPitr =
|
||||
pitrAddon !== undefined && ['ci_micro', 'ci_nano'].includes(selectedOption) && hasChanges
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
if (subscriptionCompute !== undefined) {
|
||||
setSelectedCategory('optimized')
|
||||
setSelectedOption(subscriptionCompute.variant.identifier)
|
||||
} else {
|
||||
setSelectedCategory('micro')
|
||||
setSelectedOption('ci_micro')
|
||||
setSelectedOption(defaultInstanceSize)
|
||||
}
|
||||
Telemetry.sendActivity(
|
||||
{
|
||||
@@ -169,7 +192,12 @@ const ComputeInstanceSidePanel = () => {
|
||||
if (!projectRef) return console.error('Project ref is required')
|
||||
if (!projectId) return console.error('Project ID is required')
|
||||
|
||||
if (selectedOption === 'ci_micro' && subscriptionCompute !== undefined) {
|
||||
// Temporary backwards compatibility fix
|
||||
if (
|
||||
!hasMicroOptionFromApi &&
|
||||
selectedOption === 'ci_micro' &&
|
||||
subscriptionCompute !== undefined
|
||||
) {
|
||||
removeAddon({ projectRef, variant: subscriptionCompute.variant.identifier })
|
||||
} else {
|
||||
updateAddon({
|
||||
@@ -189,11 +217,7 @@ const ComputeInstanceSidePanel = () => {
|
||||
onConfirm={() => setShowConfirmationModal(true)}
|
||||
loading={isLoading}
|
||||
disabled={
|
||||
isFreePlan ||
|
||||
isLoading ||
|
||||
!hasChanges ||
|
||||
blockMicroDowngradeDueToPitr ||
|
||||
!canUpdateCompute
|
||||
isFreePlan || isLoading || !hasChanges || blockDowngradeDueToPitr || !canUpdateCompute
|
||||
}
|
||||
tooltip={
|
||||
isFreePlan
|
||||
@@ -219,172 +243,129 @@ const ComputeInstanceSidePanel = () => {
|
||||
>
|
||||
<SidePanel.Content>
|
||||
<div className="py-6 space-y-4">
|
||||
{selectedProject?.infra_compute_size === 'nano' && subscription?.plan.id !== 'free' && (
|
||||
<Alert_Shadcn_ variant="default">
|
||||
<IconInfo strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>Free compute upgrade to Micro</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
Paid Plans include a free upgrade to Micro compute. Your project is ready to
|
||||
upgrade for no additional charges.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
|
||||
<p className="text-sm">
|
||||
For the database, compute size refers to the amount of CPU and memory allocated to the
|
||||
database instance.
|
||||
</p>
|
||||
|
||||
<div className="!mt-8 pb-4">
|
||||
<div className="flex gap-3">
|
||||
{COMPUTE_CATEGORY_OPTIONS.map((option) => {
|
||||
const isSelected = selectedCategory === option.id
|
||||
return (
|
||||
<div
|
||||
key={option.id}
|
||||
className={clsx('col-span-3 group space-y-1', isFreePlan && 'opacity-75')}
|
||||
onClick={() => {
|
||||
setSelectedCategory(option.id)
|
||||
if (option.id === 'micro') setSelectedOption('ci_micro')
|
||||
Telemetry.sendActivity(
|
||||
{
|
||||
activity: 'Option Selected',
|
||||
source: 'Dashboard',
|
||||
data: {
|
||||
title: 'Change project compute size',
|
||||
section: 'Add ons',
|
||||
option: option.name,
|
||||
},
|
||||
projectRef,
|
||||
},
|
||||
router
|
||||
)
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="Compute Instance"
|
||||
className={clsx(
|
||||
'relative rounded-xl transition border bg-no-repeat bg-center bg-cover cursor-pointer w-[160px] h-[96px]',
|
||||
isSelected
|
||||
? 'border-foreground'
|
||||
: 'border-foreground-muted opacity-50 group-hover:border-foreground-lighter group-hover:opacity-100'
|
||||
)}
|
||||
width={160}
|
||||
height={96}
|
||||
src={
|
||||
resolvedTheme?.includes('dark') ? option.imageUrl : option.imageUrlLight
|
||||
}
|
||||
/>
|
||||
|
||||
<p
|
||||
className={clsx(
|
||||
'text-sm transition',
|
||||
isSelected ? 'text-foreground' : 'text-foreground-light'
|
||||
)}
|
||||
>
|
||||
{option.name}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedCategory === 'optimized' && (
|
||||
<div className="pb-4">
|
||||
{isFreePlan && (
|
||||
<Alert
|
||||
withIcon
|
||||
className="mb-4"
|
||||
variant="info"
|
||||
title="Changing your compute size is only available on the Pro plan"
|
||||
actions={
|
||||
<Button asChild type="default">
|
||||
<Link href={`/org/${organization?.slug}/billing?panel=subscriptionPlan`}>
|
||||
View available plans
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
Upgrade your plan to change the compute size of your project
|
||||
</Alert>
|
||||
)}
|
||||
<Radio.Group
|
||||
type="large-cards"
|
||||
size="tiny"
|
||||
id="compute-instance"
|
||||
label={<p className="text-sm">Choose the compute size you want to use</p>}
|
||||
onChange={(event: any) => setSelectedOption(event.target.value)}
|
||||
<div className="pb-4">
|
||||
{isFreePlan && (
|
||||
<Alert
|
||||
withIcon
|
||||
className="mb-4"
|
||||
variant="info"
|
||||
title="Changing your compute size is only available on the Pro plan"
|
||||
actions={
|
||||
<Button asChild type="default">
|
||||
<Link href={`/org/${organization?.slug}/billing?panel=subscriptionPlan`}>
|
||||
View available plans
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{availableOptions.map((option) => (
|
||||
<Radio
|
||||
className="col-span-3 !p-0"
|
||||
disabled={isFreePlan}
|
||||
name="compute-instance"
|
||||
key={option.identifier}
|
||||
checked={selectedOption === option.identifier}
|
||||
label={option.name}
|
||||
value={option.identifier}
|
||||
>
|
||||
<div className="w-full group">
|
||||
<div className="border-b border-default px-4 py-2">
|
||||
<p className="text-sm">{option.name}</p>
|
||||
</div>
|
||||
<div className="px-4 py-2">
|
||||
<p className="text-foreground-light">
|
||||
{option.meta?.memory_gb ?? 0} GB memory
|
||||
</p>
|
||||
<p className="text-foreground-light">
|
||||
{option.meta?.cpu_cores ?? 0}-core {cpuArchitecture} CPU (
|
||||
{option.meta?.cpu_dedicated ? 'Dedicated' : 'Shared'})
|
||||
</p>
|
||||
<div className="flex justify-between items-center mt-2">
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-foreground text-sm">
|
||||
{formatCurrency(option.price)}
|
||||
</span>
|
||||
<span className="text-foreground-light translate-y-[1px]">
|
||||
{' '}
|
||||
/ {option.price_interval === 'monthly' ? 'month' : 'hour'}
|
||||
</span>
|
||||
</div>
|
||||
{option.price_interval === 'hourly' && (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<div className="flex items-center">
|
||||
<IconInfo
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
className="hover:text-foreground-light"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow',
|
||||
'border border-background',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="flex items-center space-x-1">
|
||||
<p className="text-foreground text-sm">
|
||||
${Number(option.price * 672).toFixed(0)} - $
|
||||
{Number(option.price * 744).toFixed(0)} per month
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
Upgrade your plan to change the compute size of your project
|
||||
</Alert>
|
||||
)}
|
||||
<Radio.Group
|
||||
type="large-cards"
|
||||
size="tiny"
|
||||
id="compute-instance"
|
||||
label={<p className="text-sm">Choose the compute size you want to use</p>}
|
||||
onChange={(event: any) => setSelectedOption(event.target.value)}
|
||||
>
|
||||
{availableOptions.map((option) => (
|
||||
<Radio
|
||||
className="col-span-3 !p-0"
|
||||
disabled={isFreePlan}
|
||||
name="compute-instance"
|
||||
key={option.identifier}
|
||||
checked={selectedOption === option.identifier}
|
||||
label={option.name}
|
||||
value={option.identifier}
|
||||
>
|
||||
<div className="w-full group">
|
||||
<div className="border-b border-default px-4 py-2">
|
||||
<p className="text-sm flex justify-between">
|
||||
{option.name}{' '}
|
||||
{(subscriptionCompute?.variant.identifier === option.identifier ||
|
||||
(!subscriptionCompute &&
|
||||
option.identifier ===
|
||||
`ci_${selectedProject?.infra_compute_size}`)) && (
|
||||
<Badge>Current</Badge>
|
||||
)}
|
||||
{selectedProject?.infra_compute_size === 'nano' &&
|
||||
option.identifier === 'ci_micro' && <Badge>Free Upgrade</Badge>}
|
||||
</p>
|
||||
</div>
|
||||
<div className="px-4 py-2">
|
||||
<p className="text-foreground-light">
|
||||
{option.meta?.memory_gb ?? 0} GB memory
|
||||
</p>
|
||||
<p className="text-foreground-light">
|
||||
{option.meta?.cpu_cores ?? 0}-core {cpuArchitecture} CPU (
|
||||
{option.meta?.cpu_dedicated ? 'Dedicated' : 'Shared'})
|
||||
</p>
|
||||
<div className="flex justify-between items-center mt-2">
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-foreground text-sm">
|
||||
{/* Price needs to be exact here */}${option.price}
|
||||
</span>
|
||||
<span className="text-foreground-light translate-y-[1px]">
|
||||
{' '}
|
||||
/ {option.price_interval === 'monthly' ? 'month' : 'hour'}
|
||||
</span>
|
||||
</div>
|
||||
{option.price_interval === 'hourly' && (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<div className="flex items-center">
|
||||
<IconInfo
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
className="hover:text-foreground-light"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow',
|
||||
'border border-background',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="flex items-center space-x-1">
|
||||
<p className="text-foreground text-sm">
|
||||
${Number(option.price * 672).toFixed(0)} - $
|
||||
{Number(option.price * 744).toFixed(0)} per month
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Radio>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Radio>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
||||
{selectedCategory === 'micro' && (
|
||||
<p className="text-sm text-foreground-light">
|
||||
Your database will use the standard Micro size instance of 2-core {cpuArchitecture}{' '}
|
||||
CPU (Shared) with 1GB of memory.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{hasChanges && selectedCategory !== 'micro' && (
|
||||
{hasChanges && (
|
||||
<p className="text-sm text-foreground-light">
|
||||
There are no immediate charges when changing compute. Compute Hours are a
|
||||
usage-based item and you're billed at the end of your billing cycle based on your
|
||||
@@ -401,7 +382,7 @@ const ComputeInstanceSidePanel = () => {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{hasChanges && !blockMicroDowngradeDueToPitr && (
|
||||
{hasChanges && !blockDowngradeDueToPitr && (
|
||||
<Alert
|
||||
withIcon
|
||||
variant="info"
|
||||
@@ -411,12 +392,12 @@ const ComputeInstanceSidePanel = () => {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{blockMicroDowngradeDueToPitr && (
|
||||
{blockDowngradeDueToPitr && (
|
||||
<Alert
|
||||
withIcon
|
||||
variant="info"
|
||||
className="mb-4"
|
||||
title="Disable PITR before downgrading to Micro Compute"
|
||||
title="Disable PITR before downgrading"
|
||||
actions={
|
||||
<Button type="default" onClick={() => snap.setPanelKey('pitr')}>
|
||||
Change PITR
|
||||
@@ -427,7 +408,10 @@ const ComputeInstanceSidePanel = () => {
|
||||
You currently have PITR enabled. The minimum compute instance size for using PITR
|
||||
is the Small Compute.
|
||||
</p>
|
||||
<p>You need to disable PITR before downgrading to Micro Compute.</p>
|
||||
<p>
|
||||
You need to disable PITR before downgrading Compute as it requires at least a
|
||||
Small compute instance.
|
||||
</p>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -438,7 +422,7 @@ const ComputeInstanceSidePanel = () => {
|
||||
<IconAlertTriangle className="h-4 w-4" />
|
||||
<AlertDescription_Shadcn_>
|
||||
You have a scheduled subscription change that will be canceled if you change
|
||||
your Optimized Compute add on.
|
||||
your compute size.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
|
||||
+13
-9
@@ -26,14 +26,17 @@ import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
|
||||
import { useFlag, useSelectedOrganization } from 'hooks'
|
||||
import { useSelectedOrganization, useSelectedProject } from 'hooks'
|
||||
import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants/metrics'
|
||||
import { INFRA_ACTIVITY_METRICS } from './Infrastructure.constants'
|
||||
import { capitalize } from 'lodash'
|
||||
import { INSTANCE_MICRO_SPECS, INSTANCE_NANO_SPECS } from 'lib/constants'
|
||||
|
||||
const InfrastructureActivity = () => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const organization = useSelectedOrganization()
|
||||
const [dateRange, setDateRange] = useState<any>()
|
||||
const project = useSelectedProject()
|
||||
|
||||
const { data: subscription, isLoading: isLoadingSubscription } = useOrgSubscriptionQuery({
|
||||
orgSlug: organization?.slug,
|
||||
@@ -47,13 +50,10 @@ const InfrastructureActivity = () => {
|
||||
const selectedAddons = addons?.selected_addons ?? []
|
||||
|
||||
const { computeInstance } = getAddons(selectedAddons)
|
||||
const currentComputeInstanceSpecs = computeInstance?.variant?.meta ?? {
|
||||
baseline_disk_io_mbs: 87,
|
||||
max_disk_io_mbs: 2085,
|
||||
cpu_cores: 2,
|
||||
cpu_dedicated: true,
|
||||
memory_gb: 1,
|
||||
}
|
||||
const currentComputeInstanceSpecs =
|
||||
computeInstance?.variant?.meta ?? project?.infra_compute_size === 'nano'
|
||||
? INSTANCE_NANO_SPECS
|
||||
: INSTANCE_MICRO_SPECS
|
||||
|
||||
const currentBillingCycleSelected = useMemo(() => {
|
||||
// Selected by default
|
||||
@@ -273,7 +273,11 @@ const InfrastructureActivity = () => {
|
||||
<p className="text-sm mb-2">Overview</p>
|
||||
<div className="flex items-center justify-between border-b py-1">
|
||||
<p className="text-xs text-foreground-light">Current compute instance</p>
|
||||
<p className="text-xs">{computeInstance?.variant?.name ?? 'Micro'}</p>
|
||||
<p className="text-xs">
|
||||
{computeInstance?.variant?.name ??
|
||||
capitalize(project?.infra_compute_size) ??
|
||||
'Micro'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-b py-1">
|
||||
<p className="text-xs text-foreground-light">
|
||||
|
||||
Vendored
+2
@@ -2808,6 +2808,7 @@ export interface components {
|
||||
ProjectAddonType: 'custom_domain' | 'compute_instance' | 'pitr' | 'ipv4'
|
||||
/** @enum {string} */
|
||||
AddonVariantId:
|
||||
| 'ci_micro'
|
||||
| 'ci_small'
|
||||
| 'ci_medium'
|
||||
| 'ci_large'
|
||||
@@ -2919,6 +2920,7 @@ export interface components {
|
||||
billing_partner: 'fly'
|
||||
scheduled_plan_change: components['schemas']['ScheduledPlanChange'] | null
|
||||
customer_balance: number
|
||||
nano_enabled: boolean
|
||||
}
|
||||
UpdateSubscriptionBody: {
|
||||
payment_method?: string
|
||||
|
||||
@@ -115,3 +115,33 @@ export const PASSWORD_STRENGTH_PERCENTAGE = {
|
||||
}
|
||||
|
||||
export const DEFAULT_PROJECT_API_SERVICE_ID = 1
|
||||
|
||||
type InstanceSpecs = {
|
||||
baseline_disk_io_mbs: number
|
||||
connections_direct: number
|
||||
connections_pooler: number
|
||||
cpu_cores: number
|
||||
cpu_dedicated: boolean
|
||||
max_disk_io_mbs: number
|
||||
memory_gb: number
|
||||
}
|
||||
|
||||
export const INSTANCE_NANO_SPECS: InstanceSpecs = {
|
||||
baseline_disk_io_mbs: 43,
|
||||
connections_direct: 30,
|
||||
connections_pooler: 200,
|
||||
cpu_cores: 2,
|
||||
cpu_dedicated: false,
|
||||
max_disk_io_mbs: 2085,
|
||||
memory_gb: 0.5,
|
||||
}
|
||||
|
||||
export const INSTANCE_MICRO_SPECS: InstanceSpecs = {
|
||||
baseline_disk_io_mbs: 87,
|
||||
connections_direct: 60,
|
||||
connections_pooler: 200,
|
||||
cpu_cores: 2,
|
||||
cpu_dedicated: false,
|
||||
max_disk_io_mbs: 2085,
|
||||
memory_gb: 1,
|
||||
}
|
||||
Reference in new issue
Block a user