diff --git a/packages/manager/.changeset/pr-12712-upcoming-features-1755291113192.md b/packages/manager/.changeset/pr-12712-upcoming-features-1755291113192.md new file mode 100644 index 00000000000..3ab747f30e4 --- /dev/null +++ b/packages/manager/.changeset/pr-12712-upcoming-features-1755291113192.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Add node pool firewall selection to LKE-E create flow ([#12712](https://github.com/linode/manager/pull/12712)) diff --git a/packages/manager/src/features/Kubernetes/CreateCluster/CreateCluster.tsx b/packages/manager/src/features/Kubernetes/CreateCluster/CreateCluster.tsx index fab84c92d8b..63382255dd2 100644 --- a/packages/manager/src/features/Kubernetes/CreateCluster/CreateCluster.tsx +++ b/packages/manager/src/features/Kubernetes/CreateCluster/CreateCluster.tsx @@ -148,6 +148,7 @@ export const CreateCluster = () => { isLkeEnterpriseLAFeatureEnabled, isLkeEnterpriseLAFlagEnabled, isLkeEnterprisePhase2FeatureEnabled, + isLkeEnterprisePostLAFeatureEnabled, } = useIsLkeEnterpriseEnabled(); // Use React Hook Form for node pools to make updating pools and their configs easier. @@ -278,7 +279,7 @@ export const CreateCluster = () => { setSubmitting(true); const node_pools = nodePools.map( - pick(['type', 'count', 'update_strategy']) + pick(['type', 'count', 'update_strategy', 'firewall_id']) ) as CreateNodePoolData[]; const vpcId = form.getValues('vpc_id'); @@ -347,7 +348,11 @@ export const CreateCluster = () => { : createKubernetesCluster; // TODO: Improve error handling in M3-10429, at which point we shouldn't need this. - if (isLkeEnterprisePhase2FeatureEnabled && selectedTier === 'enterprise') { + if ( + (isLkeEnterprisePostLAFeatureEnabled || + isLkeEnterprisePhase2FeatureEnabled) && + selectedTier === 'enterprise' + ) { // Trigger the React Hook Form validation for BYO VPC selection. const isValid = await trigger(); // Don't submit the form while RHF errors persist. diff --git a/packages/manager/src/features/Kubernetes/KubernetesPlansPanel/NodePoolConfigDrawer.tsx b/packages/manager/src/features/Kubernetes/KubernetesPlansPanel/NodePoolConfigDrawer.tsx index bdad71529d6..c0fef24aaa0 100644 --- a/packages/manager/src/features/Kubernetes/KubernetesPlansPanel/NodePoolConfigDrawer.tsx +++ b/packages/manager/src/features/Kubernetes/KubernetesPlansPanel/NodePoolConfigDrawer.tsx @@ -27,8 +27,8 @@ import { NodePoolConfigOptions } from './NodePoolConfigOptions'; import type { CreateClusterFormValues } from '../CreateCluster/CreateCluster'; import type { + CreateNodePoolData, KubernetesTier, - NodePoolUpdateStrategy, Region, } from '@linode/api-v4'; import type { Theme } from '@linode/ui'; @@ -45,11 +45,6 @@ export interface Props { selectedTier: KubernetesTier; } -interface VersionUpdateFormFields { - nodeCount: number; - updateStrategy: NodePoolUpdateStrategy | undefined; -} - export const NodePoolConfigDrawer = (props: Props) => { const { onClose, @@ -74,13 +69,14 @@ export const NodePoolConfigDrawer = (props: Props) => { }); // Manage node pool options within this drawer form. - const { control, formState, setValue, ...form } = - useForm({ + const { control, formState, setValue, ...form } = useForm( + { defaultValues: { - nodeCount: DEFAULT_PLAN_COUNT, + count: DEFAULT_PLAN_COUNT, }, - shouldUnregister: true, // For conditionally defined fields (e.g. updateStrategy) - }); + shouldUnregister: true, // For conditionally defined fields (e.g. update_strategy, firewall_id) + } + ); const typesQuery = useSpecificTypes(planId ? [planId] : []); const planType = typesQuery[0]?.data @@ -88,9 +84,9 @@ export const NodePoolConfigDrawer = (props: Props) => { : undefined; // Keep track of the node count to display an accurate price. - const nodeCountWatcher = useWatch({ control, name: 'nodeCount' }); + const nodeCountWatcher = useWatch({ control, name: 'count' }); const updatedCount = - nodeCountWatcher ?? form.getValues('nodeCount') ?? DEFAULT_PLAN_COUNT; + nodeCountWatcher ?? form.getValues('count') ?? DEFAULT_PLAN_COUNT; const pricePerNode = getLinodeRegionPrice( planType, selectedRegion?.toString() @@ -107,31 +103,35 @@ export const NodePoolConfigDrawer = (props: Props) => { } // Ensure the update strategy resets when the tier is changed. setValue( - 'updateStrategy', + 'update_strategy', selectedTier === 'enterprise' ? 'on_recycle' : undefined ); + setValue('firewall_id', undefined); // If we're in edit mode, set the existing config values on the pool. if (!isAddMode && poolIndex !== undefined) { - setValue('nodeCount', _nodePools[poolIndex]?.count); - setValue('updateStrategy', _nodePools[poolIndex]?.update_strategy); + setValue('count', _nodePools[poolIndex]?.count); + setValue('update_strategy', _nodePools[poolIndex]?.update_strategy); + setValue('firewall_id', _nodePools[poolIndex]?.firewall_id); } }, [planId, open, selectedTier, setValue, isAddMode, poolIndex, _nodePools]); - const onSubmit = async (values: VersionUpdateFormFields) => { + const onSubmit = async (values: CreateNodePoolData) => { try { // If there's a pool index, the drawer is in edit mode. Else, it's in add mode. if (poolIndex !== undefined) { update(poolIndex, { ..._nodePools[poolIndex], - count: values.nodeCount, - update_strategy: values.updateStrategy, + count: values.count, + update_strategy: values.update_strategy, + firewall_id: values.firewall_id, }); } else if (planId) { append({ - count: values.nodeCount, + count: values.count, type: planId, - update_strategy: values.updateStrategy, + update_strategy: values.update_strategy, + firewall_id: values.firewall_id, }); } onClose(); @@ -181,7 +181,7 @@ export const NodePoolConfigDrawer = (props: Props) => { ( { - const { control } = useFormContext(); + const { control } = useFormContext(); return ( - ( - - )} - /> + <> + ( + + )} + /> + + ); }; diff --git a/packages/manager/src/features/Kubernetes/NodePoolFirewallSelect.tsx b/packages/manager/src/features/Kubernetes/NodePoolFirewallSelect.tsx new file mode 100644 index 00000000000..4f39d6561d9 --- /dev/null +++ b/packages/manager/src/features/Kubernetes/NodePoolFirewallSelect.tsx @@ -0,0 +1,77 @@ +import { + FormControlLabel, + Radio, + RadioGroup, + Stack, + Typography, +} from '@linode/ui'; +import React from 'react'; +import { Controller, useFormContext, useWatch } from 'react-hook-form'; + +import { FirewallSelect } from '../Firewalls/components/FirewallSelect'; + +import type { CreateNodePoolData } from '@linode/api-v4'; + +export const NodePoolFirewallSelect = () => { + const { control } = useFormContext(); + const watchedFirewallId = useWatch({ control, name: 'firewall_id' }); + + const [isUsingOwnFirewall, setIsUsingOwnFirewall] = React.useState( + Boolean(watchedFirewallId) + ); + + return ( + + ({ + font: theme.tokens.alias.Typography.Label.Bold.S, + })} + > + Firewall + + ) => { + setIsUsingOwnFirewall(e.target.value === 'yes'); + }} + value={isUsingOwnFirewall} + > + } + label="Use default firewall" + value="no" + /> + } + label="Select existing firewall" + value="yes" + /> + + {isUsingOwnFirewall && ( + ( + field.onChange(firewall?.id ?? null)} + placeholder="Select firewall" + value={field.value} + /> + )} + rules={{ + validate: (value) => { + if (isUsingOwnFirewall && !value) { + return 'You must either select a Firewall or select the default firewall.'; + } + return true; + }, + }} + /> + )} + + ); +}; diff --git a/packages/manager/src/features/Kubernetes/NodePoolUpdateStrategySelect.tsx b/packages/manager/src/features/Kubernetes/NodePoolUpdateStrategySelect.tsx index 4998f87e443..fb181d04ecd 100644 --- a/packages/manager/src/features/Kubernetes/NodePoolUpdateStrategySelect.tsx +++ b/packages/manager/src/features/Kubernetes/NodePoolUpdateStrategySelect.tsx @@ -7,7 +7,7 @@ interface Props { label?: string; noMarginTop?: boolean; onChange: (value: string | undefined) => void; - value: string; + value: string | undefined; } export const NodePoolUpdateStrategySelect = (props: Props) => {