feat: revamped instance size selection (#20765)

This commit is contained in:
Kevin Grüneberg authored and GitHub committed 2024-02-05 12:03:43 +08:00
1 parent e41137ee03
commit becd39cc23
7 files changed
+326 -244

No files matched your search

@@ -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">
@@ -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_>
)}
@@ -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">
+2
View File
@@ -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,
}