Skip to content

Commit

Permalink
feat(cluster-resources): update Karpenter UI (#1811)
Browse files Browse the repository at this point in the history
* feat(cluster-resources): update Karpenter UI

* Update wording
  • Loading branch information
RemiBonnet authored Jan 22, 2025
1 parent c388299 commit 54c427b
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('NodepoolModal', () => {
it('should render correctly for default type', () => {
renderWithProviders(<NodepoolModal {...defaultProps} type="default" />)

expect(screen.getByText('Nodepool stable')).toBeInTheDocument()
expect(screen.getByText('Nodepool default')).toBeInTheDocument()
expect(screen.getByLabelText('vCPU')).toBeInTheDocument()
expect(screen.getByLabelText('Memory (GiB)')).toBeInTheDocument()
expect(screen.getByText('Operates every day, 24 hours a day')).toBeInTheDocument()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,11 @@ export function NodepoolModal({ type, cluster, onChange, defaultValues }: Nodepo
return (
<FormProvider {...methods}>
<ModalCrud
title="Nodepool stable"
title={type === 'stable' ? 'Nodepool stable' : 'Nodepool default'}
description={
type === 'stable'
? 'Used for single instances and internal Qovery applications, such as containerized databases, to maintain stability.'
: 'Used for single instances and internal Qovery applications, such as containerized databases, to maintain stability.'
: 'Designed to handle general workloads and serves as the foundation for deploying most applications.'
}
onSubmit={onSubmit}
onClose={closeModal}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { add, format, parse } from 'date-fns'
import { type Cluster, WeekdayEnum } from 'qovery-typescript-axios'
import { useFormContext } from 'react-hook-form'
import { type ClusterResourcesData } from '@qovery/shared/interfaces'
import { BlockContent, Button, Icon, Tooltip, useModal } from '@qovery/shared/ui'
import { Button, Icon, Tooltip, useModal } from '@qovery/shared/ui'
import { upperCaseFirstLetter } from '@qovery/shared/util-js'
import { NodepoolModal } from './nodepool-modal/nodepool-modal'

Expand Down Expand Up @@ -77,10 +77,9 @@ export function NodepoolsResourcesSettings({ cluster }: NodepoolsResourcesSettin
const { start, end } = formatTimeRange(watchStable?.consolidation?.start_time, watchStable?.consolidation?.duration)

return (
<BlockContent
title="Nodepools configuration"
classNameContent="p-0"
headRight={
<div>
<div className="flex h-9 items-center justify-between border-y border-neutral-250 px-4">
<h2 className="text-ssm font-medium text-neutral-400">Nodepools configuration</h2>
<Tooltip
classNameContent="w-80"
content="A NodePool is a group of nodes within a cluster that share the same configuration and characteristics. It allows you to manage resources efficiently by defining specific limits and settings for the group."
Expand All @@ -89,146 +88,152 @@ export function NodepoolsResourcesSettings({ cluster }: NodepoolsResourcesSettin
<Icon iconName="circle-info" iconStyle="regular" />
</span>
</Tooltip>
}
>
<div className="flex flex-col gap-4 border-b border-neutral-250 p-4 text-sm">
<div className="flex flex-col gap-1.5">
<p className="font-medium text-neutral-400">Stable nodepool</p>
<span className="text-ssm text-neutral-350">
Used for single instances and internal Qovery applications, such as containerized databases, to maintain
stability.
</span>
</div>
<div className="flex flex-col gap-4 p-4">
<p className="text-ssm text-neutral-350">Karpenter settings above will be applied to the following nodepools</p>
<div className="flex flex-col gap-4 rounded border border-neutral-200 bg-neutral-150 p-4 text-sm">
<div className="flex gap-10">
<div className="flex flex-col gap-1.5">
<p className="font-medium text-neutral-400">Stable nodepool</p>
<span className="text-ssm text-neutral-350">
Used for single instances and internal Qovery applications, such as containerized databases, to maintain
stability.
</span>
</div>
<Button
type="button"
variant="surface"
color="neutral"
onClick={() =>
openModal({
content: (
<NodepoolModal
type="stable"
cluster={cluster}
onChange={(data) => {
setValue('karpenter.qovery_node_pools.stable_override', data.stable_override)
}}
defaultValues={watchStable}
/>
),
})
}
>
<Icon iconName="pen" iconStyle="solid" />
</Button>
</div>
<div className="flex justify-between gap-4">
<div className="flex w-1/2 flex-col gap-1">
<span className="text-neutral-350">Consolidation</span>
<div className="flex flex-col justify-between gap-4 text-sm text-neutral-400">
{watchStable?.consolidation?.enabled ? (
<span className="flex flex-col justify-center">
<span className="flex gap-1.5">
{formatWeekdays(watchStable?.consolidation?.days)},
<Tooltip content={`Schedule (${cluster.region})`}>
<span className="text-sm">
<Icon iconName="circle-info" iconStyle="regular" />
</span>
</Tooltip>
</span>
<span>
{start} to {end}
</span>
</span>
) : (
<span>Disabled</span>
)}
</div>
</div>
<div className="flex w-1/2 flex-col gap-1">
<span className="text-neutral-350">Resources limit</span>
{watchStable?.limits?.enabled ? (
<span>
{watchStable.limits.max_cpu_in_vcpu && (
<span>vCPU limit: {watchStable?.limits?.max_cpu_in_vcpu} vCPU; </span>
)}
{watchStable.limits.max_memory_in_gibibytes && (
<>
<br />
<span>Memory limit: {watchStable?.limits?.max_memory_in_gibibytes} GiB</span>
</>
)}
</span>
) : (
<span>No limit</span>
)}
</div>
</div>
</div>
<div className="flex justify-between gap-4">
<div className="flex w-1/2 flex-col gap-1">
<span className="text-neutral-350">Consolidation</span>
<div className="flex flex-col justify-between gap-4 text-sm text-neutral-400">
{watchStable?.consolidation?.enabled ? (
<div className="flex flex-col gap-4 rounded border border-neutral-200 bg-neutral-150 p-4 text-sm">
<div className="flex gap-10">
<div className="flex flex-col gap-1.5">
<p className="font-medium text-neutral-400">Default nodepool</p>
<span className="text-ssm text-neutral-350">
Designed to handle general workloads and serves as the foundation for deploying most applications.
</span>
</div>
<Button
type="button"
variant="surface"
color="neutral"
onClick={() =>
openModal({
content: (
<NodepoolModal
type="default"
cluster={cluster}
defaultValues={watchDefault}
onChange={(data) => {
setValue('karpenter.qovery_node_pools.default_override', data.default_override)
}}
/>
),
})
}
>
<Icon iconName="pen" iconStyle="solid" />
</Button>
</div>
<div className="flex gap-4">
<div className="flex w-1/2 flex-col gap-1">
<span className="text-neutral-350">Consolidation</span>
<div className="flex flex-col justify-between gap-4 text-sm text-neutral-400">
<span className="flex flex-col justify-center">
<span className="flex gap-1.5">
{formatWeekdays(watchStable?.consolidation?.days)},
Operates every day,
<Tooltip content={`Schedule (${cluster.region})`}>
<span className="text-sm">
<Icon iconName="circle-info" iconStyle="regular" />
</span>
</Tooltip>
</span>
<span>
{start} to {end}
</span>
<span>24 hours a day</span>
</span>
) : (
<span>Disabled</span>
)}
</div>
</div>
</div>
<div className="flex w-1/2 flex-col gap-1">
<span className="text-neutral-350">Resources limit</span>
{watchStable?.limits?.enabled ? (
<span>
{watchStable.limits.max_cpu_in_vcpu && (
<span>vCPU limit: {watchStable?.limits?.max_cpu_in_vcpu} vCPU; </span>
)}
{watchStable.limits.max_memory_in_gibibytes && (
<>
<br />
<span>Memory limit: {watchStable?.limits?.max_memory_in_gibibytes} GiB</span>
</>
)}
</span>
) : (
<span>No limit</span>
)}
</div>
<Button
type="button"
variant="surface"
color="neutral"
onClick={() =>
openModal({
content: (
<NodepoolModal
type="stable"
cluster={cluster}
onChange={(data) => {
setValue('karpenter.qovery_node_pools.stable_override', data.stable_override)
}}
defaultValues={watchStable}
/>
),
})
}
>
<Icon iconName="pen" iconStyle="solid" />
</Button>
</div>
</div>
<div className="flex flex-col gap-4 p-4 text-sm">
<div className="flex flex-col gap-1.5">
<p className="mb-1 font-medium text-neutral-400">Default nodepool</p>
<span className="text-ssm text-neutral-350">
Designed to handle general workloads and serves as the foundation for deploying most applications.
</span>
</div>
<div className="flex gap-4">
<div className="flex w-1/2 flex-col gap-1">
<span className="text-neutral-350">Consolidation</span>
<div className="flex flex-col justify-between gap-4 text-sm text-neutral-400">
<span className="flex flex-col justify-center">
<span className="flex gap-1.5">
Operates every day,
<Tooltip content={`Schedule (${cluster.region})`}>
<span className="text-sm">
<Icon iconName="circle-info" iconStyle="regular" />
</span>
</Tooltip>
<div className="flex w-1/2 flex-col gap-1">
<span className="text-neutral-350">Resources limit</span>
{watchDefault?.limits?.enabled ? (
<span>
{watchDefault.limits.max_cpu_in_vcpu && (
<span>vCPU limit: {watchDefault?.limits?.max_cpu_in_vcpu} vCPU; </span>
)}
{watchDefault.limits.max_memory_in_gibibytes && (
<>
<br />
<span>Memory limit: {watchDefault?.limits?.max_memory_in_gibibytes} GiB</span>
</>
)}
</span>
<span>24 hours a day</span>
</span>
) : (
<span>No limit</span>
)}
</div>
</div>
<div className="flex w-1/2 flex-col gap-1">
<span className="text-neutral-350">Resources limit</span>
{watchDefault?.limits?.enabled ? (
<span>
{watchDefault.limits.max_cpu_in_vcpu && (
<span>vCPU limit: {watchDefault?.limits?.max_cpu_in_vcpu} vCPU; </span>
)}
{watchDefault.limits.max_memory_in_gibibytes && (
<>
<br />
<span>Memory limit: {watchDefault?.limits?.max_memory_in_gibibytes} GiB</span>
</>
)}
</span>
) : (
<span>No limit</span>
)}
</div>
<Button
type="button"
variant="surface"
color="neutral"
onClick={() =>
openModal({
content: (
<NodepoolModal
type="default"
cluster={cluster}
defaultValues={watchDefault}
onChange={(data) => {
setValue('karpenter.qovery_node_pools.default_override', data.default_override)
}}
/>
),
})
}
>
<Icon iconName="pen" iconStyle="solid" />
</Button>
</div>
</div>
</BlockContent>
</div>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function PageSettingsResources({ cluster, onSubmit, loading }: PageSettin
hasAlreadyKarpenter={hasAlreadyKarpenter}
fromDetail
/>
<div className="flex justify-end">
<div className="mt-6 flex justify-end">
<Button data-testid="submit-button" type="submit" size="lg" loading={loading} disabled={!formState.isValid}>
Save
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function ClusterResourcesSettings(props: ClusterResourcesSettingsProps) {
const watchKarpenter = watch('karpenter')
const watchDiskSize = watch('disk_size')
const watchKarpenterQoveryNodePools = watch('karpenter.qovery_node_pools.requirements')
const watchSpotEnabled = watch('karpenter.spot_enabled')

const { data: cloudProviderInstanceTypes } = useCloudProviderInstanceTypes(
match(props.cloudProvider || CloudProviderEnum.AWS)
Expand Down Expand Up @@ -319,7 +320,7 @@ export function ClusterResourcesSettings(props: ClusterResourcesSettingsProps) {
Edit <Icon iconName="pen" iconStyle="solid" />
</Button>
</div>
<div className="flex border-t border-neutral-250 p-4">
<div className="flex flex-col gap-4 border-t border-neutral-250 p-4">
<Controller
name="karpenter.spot_enabled"
control={control}
Expand All @@ -333,6 +334,27 @@ export function ClusterResourcesSettings(props: ClusterResourcesSettingsProps) {
/>
)}
/>
{props.isProduction && watchSpotEnabled && (
<Callout.Root color="yellow">
<Callout.Icon>
<Icon iconName="info-circle" iconStyle="regular" />
</Callout.Icon>
<Callout.Text>
<Callout.TextDescription>
Activating spot instances on a production cluster may lead to potential downtime for
applications deployed on the stable node pool. However, you can specify in the
advanced settings to force the use of on-demand instances for your service or
database to avoid this risk.{' '}
<ExternalLink
size="sm"
href="https://hub.qovery.com/docs/using-qovery/configuration/clusters/aws-with-karpenter/#assigning-specific-instances-to-services"
>
See documentation
</ExternalLink>
</Callout.TextDescription>
</Callout.Text>
</Callout.Root>
)}
</div>
{props.fromDetail && (
<div className="flex border-t border-neutral-250 p-4">
Expand All @@ -358,6 +380,9 @@ export function ClusterResourcesSettings(props: ClusterResourcesSettingsProps) {
</div>
)}
</div>
{watchKarpenterEnabled && props.cluster && (
<NodepoolsResourcesSettings cluster={props.cluster} />
)}
</motion.div>
</motion.div>
)}
Expand All @@ -368,8 +393,6 @@ export function ClusterResourcesSettings(props: ClusterResourcesSettingsProps) {
</BlockContent>
)}

{watchKarpenterEnabled && props.cluster && <NodepoolsResourcesSettings cluster={props.cluster} />}

{!watchKarpenterEnabled && (
<Section className="gap-4">
<Heading>Resources configuration</Heading>
Expand Down

0 comments on commit 54c427b

Please sign in to comment.