diff --git a/libs/domains/clusters/feature/src/lib/nodepools-resources-settings/nodepool-modal/nodepool-modal.tsx b/libs/domains/clusters/feature/src/lib/nodepools-resources-settings/nodepool-modal/nodepool-modal.tsx index 0ba750954f6..9ee846548a1 100644 --- a/libs/domains/clusters/feature/src/lib/nodepools-resources-settings/nodepool-modal/nodepool-modal.tsx +++ b/libs/domains/clusters/feature/src/lib/nodepools-resources-settings/nodepool-modal/nodepool-modal.tsx @@ -1,26 +1,73 @@ import { type Cluster, - ClusterFeatureKarpenterParameters, - type KarpenterDefaultNodePoolOverride, - type KarpenterStableNodePoolOverride, + type ClusterFeatureKarpenterParameters, + KarpenterDefaultNodePoolOverride, + type KarpenterNodePool, + KarpenterStableNodePoolOverride, WeekdayEnum, } from 'qovery-typescript-axios' -import { Controller, FormProvider, useForm } from 'react-hook-form' -import { P, match } from 'ts-pattern' +import { Controller, FormProvider, useForm, useFormContext } from 'react-hook-form' import { Callout, Icon, InputSelect, InputText, InputToggle, ModalCrud, Tooltip, useModal } from '@qovery/shared/ui' import { upperCaseFirstLetter } from '@qovery/shared/util-js' -import { useEditCluster } from '../../hooks/use-edit-cluster/use-edit-cluster' + +function LimitsFields({ type }: { type: 'default' | 'stable' }) { + const { control } = useFormContext() + + const name = `${type === 'default' ? 'default_override' : 'stable_override'}.limits` + + return ( + <> + ( + + )} + /> + ( + + )} + /> + + ) +} export interface NodepoolModalProps { type: 'stable' | 'default' cluster: Cluster + onChange: (data: KarpenterNodePool) => void + defaultValues?: KarpenterStableNodePoolOverride | KarpenterDefaultNodePoolOverride } const CPU_MIN = 6 const MEMORY_MIN = 10 -export function NodepoolModal({ type, cluster }: NodepoolModalProps) { - const { mutateAsync: editCluster, isLoading: isLoadingEditCluster } = useEditCluster() +export function NodepoolModal({ type, cluster, onChange, defaultValues }: NodepoolModalProps) { const { closeModal } = useModal() const karpenterNodePools = ( @@ -28,68 +75,49 @@ export function NodepoolModal({ type, cluster }: NodepoolModalProps) { ?.value as ClusterFeatureKarpenterParameters ).qovery_node_pools - const methods = useForm({ + const methods = useForm>({ mode: 'onChange', - defaultValues: type === 'stable' ? karpenterNodePools.stable_override : karpenterNodePools.default_override, + defaultValues: { + default_override: defaultValues, + stable_override: defaultValues, + }, }) - console.log(karpenterNodePools) - - const watchConsolidation = methods.watch('consolidation.enabled') + const watchConsolidation = methods.watch('stable_override.consolidation.enabled') const onSubmit = methods.handleSubmit(async (data) => { - // TODO: Fix duration format and other format issues - try { - await editCluster({ - organizationId: cluster.organization.id, - clusterId: cluster.id, - clusterRequest: { - ...cluster, - features: cluster.features?.map((feature) => { - if (feature.id === 'KARPENTER') { - return { - id: 'KARPENTER', - value: { - ...(feature.value_object?.value as ClusterFeatureKarpenterParameters), - ...match({ type, data }) - .with( - { - type: 'stable', - data: P.when((d): d is KarpenterStableNodePoolOverride => 'consolidation' in d), - }, - ({ data }) => { - return { - qovery_node_pools: { - ...karpenterNodePools, - stable_override: { - ...data, - consolidation: { - ...data.consolidation, - enabled: data.consolidation?.enabled ?? false, - duration: `PT${data.consolidation?.duration}`, - }, - }, - }, - } - } - ) - .with({ type: 'default' }, ({ data }) => ({ - ...karpenterNodePools, - default_override: data, - })) - .exhaustive(), - }, - } - } - return feature + onChange({ + ...karpenterNodePools, + ...(type === 'default' + ? { + default_override: { + limits: { + max_cpu_in_vcpu: data.default_override?.limits?.max_cpu_in_vcpu ?? CPU_MIN, + max_memory_in_gibibytes: data.default_override?.limits?.max_memory_in_gibibytes ?? MEMORY_MIN, + }, + }, + } + : { + stable_override: { + limits: { + max_cpu_in_vcpu: data.stable_override?.limits?.max_cpu_in_vcpu ?? CPU_MIN, + max_memory_in_gibibytes: data.stable_override?.limits?.max_memory_in_gibibytes ?? MEMORY_MIN, + }, + consolidation: { + enabled: data.stable_override?.consolidation?.enabled ?? false, + days: data.stable_override?.consolidation?.days ?? [], + start_time: data.stable_override?.consolidation?.start_time + ? `PT${data.stable_override?.consolidation?.start_time}` + : '', + duration: data.stable_override?.consolidation?.duration + ? `PT${data.stable_override?.consolidation?.duration.toUpperCase()}` + : '', + }, + }, }), - }, - }) + }) - // closeModal() - } catch (error) { - console.error(error) - } + closeModal() }) const daysOptions = Object.keys(WeekdayEnum).map((key) => ({ @@ -104,8 +132,7 @@ export function NodepoolModal({ type, cluster }: NodepoolModalProps) { description="Used for single instances and internal Qovery applications, such as containerized databases, to maintain stability." onSubmit={onSubmit} onClose={closeModal} - submitLabel="Save" - loading={isLoadingEditCluster} + submitLabel="Confirm" >
@@ -119,42 +146,7 @@ export function NodepoolModal({ type, cluster }: NodepoolModalProps) {
- ( - - )} - /> - ( - - )} - /> +
{type === 'default' && ( @@ -175,7 +167,7 @@ export function NodepoolModal({ type, cluster }: NodepoolModalProps) { {type === 'stable' && ( <> ( Some downtime may occur during this process. ( @@ -212,7 +204,7 @@ export function NodepoolModal({ type, cluster }: NodepoolModalProps) { )} /> ( @@ -227,7 +219,7 @@ export function NodepoolModal({ type, cluster }: NodepoolModalProps) { )} /> ( diff --git a/libs/domains/clusters/feature/src/lib/nodepools-resources-settings/nodepools-resources-settings.tsx b/libs/domains/clusters/feature/src/lib/nodepools-resources-settings/nodepools-resources-settings.tsx index ccf712cec1e..86e3f51be3c 100644 --- a/libs/domains/clusters/feature/src/lib/nodepools-resources-settings/nodepools-resources-settings.tsx +++ b/libs/domains/clusters/feature/src/lib/nodepools-resources-settings/nodepools-resources-settings.tsx @@ -1,6 +1,62 @@ -import { type Cluster } from 'qovery-typescript-axios' +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 NodepoolModal from './nodepool-modal/nodepool-modal' +import { upperCaseFirstLetter } from '@qovery/shared/util-js' +import { NodepoolModal } from './nodepool-modal/nodepool-modal' + +export const formatTimeRange = ( + startTime?: string, + duration?: string +): { + start: string + end: string +} => { + if (startTime === undefined || duration === undefined) return { start: '', end: '' } + + const baseDate = parse(startTime.replace('PT', ''), 'HH:mm', new Date()) + + const durationHours = parseInt(duration.match(/(\d+)H/)?.[1] || '0') + const durationMinutes = parseInt(duration.match(/(\d+)M/)?.[1] || '0') + + const endDate = add(baseDate, { + hours: durationHours, + minutes: durationMinutes, + }) + + return { + start: format(baseDate, 'h:mm a').toLowerCase(), + end: format(endDate, 'h:mm a').toLowerCase(), + } +} + +export const shortenDay = (day: string): string => { + return upperCaseFirstLetter(day).slice(0, 3) +} + +export const formatWeekdays = (days: string[]): string => { + if (days.length === 0) return '' + + const fullWeek = days.length === 7 + if (fullWeek) { + return 'Operates every day' + } + + const weekdayOrder = Object.keys(WeekdayEnum).map((day) => day) + const daysIndices = days.map((day) => weekdayOrder.indexOf(day)).sort((a, b) => a - b) + + const isConsecutive = daysIndices.every((day, index) => { + if (index === 0) return true + return day === daysIndices[index - 1] + 1 + }) + + if (isConsecutive) { + return `${upperCaseFirstLetter(days[0])} to ${upperCaseFirstLetter(days[days.length - 1])}` + } + + return days.map(shortenDay).join(', ') +} export interface NodepoolsResourcesSettingsProps { cluster: Cluster @@ -8,6 +64,12 @@ export interface NodepoolsResourcesSettingsProps { export function NodepoolsResourcesSettings({ cluster }: NodepoolsResourcesSettingsProps) { const { openModal } = useModal() + const { watch, setValue } = useFormContext() + + const watchStable = watch('karpenter.qovery_node_pools.stable_override') + const watchDefault = watch('karpenter.qovery_node_pools.default_override') + + const { start, end } = formatTimeRange(watchStable?.consolidation?.start_time, watchStable?.consolidation?.duration) return (
-
+
+
+ {watchStable?.consolidation?.days && ( + + + {formatWeekdays(watchStable?.consolidation?.days)}, + + + + + + + + {start} to {end} + + + )} + {watchStable?.limits && ( + + {watchStable.limits.max_cpu_in_vcpu && ( + vCPU limit: {watchStable?.limits?.max_cpu_in_vcpu} vCPU; + )} + {watchStable.limits.max_memory_in_gibibytes && ( + Memory list: {watchStable?.limits?.max_memory_in_gibibytes} GiB + )} + + )} +
-
+
+
+ + + Operates every day, + + + + + + + 24 hours a day + + + {watchDefault?.limits?.max_cpu_in_vcpu && ( + vCPU limit: {watchDefault?.limits?.max_cpu_in_vcpu} vCPU; + )} + {watchDefault?.limits?.max_memory_in_gibibytes && ( + Memory list: {watchDefault?.limits?.max_memory_in_gibibytes} GiB + )} + +