diff --git a/packages/manager/.changeset/pr-12241-added-1747755312168.md b/packages/manager/.changeset/pr-12241-added-1747755312168.md new file mode 100644 index 00000000000..b5ca2fcb9d8 --- /dev/null +++ b/packages/manager/.changeset/pr-12241-added-1747755312168.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Added +--- + +Support for Linodes and NodeBalancers to have multiple firewalls ([#12241](https://github.com/linode/manager/pull/12241)) diff --git a/packages/manager/src/features/Firewalls/components/FirewallSelect.tsx b/packages/manager/src/features/Firewalls/components/FirewallSelect.tsx index 3e0ae7cf68d..25313578e4b 100644 --- a/packages/manager/src/features/Firewalls/components/FirewallSelect.tsx +++ b/packages/manager/src/features/Firewalls/components/FirewallSelect.tsx @@ -28,9 +28,14 @@ interface Props label?: string; /** * Optionally pass your own array of Firewalls. - * All Firewall will show if this is omitted. + * All Firewalls will show if this is omitted. */ options?: Firewall[]; + /** + * Determine which firewalls should be available as options. + * If the options prop is used, this is filter ignored. + */ + optionsFilter?: (firewall: Firewall) => boolean; /** * The ID of the selected Firewall */ @@ -47,9 +52,20 @@ interface Props export const FirewallSelect = ( props: Props ) => { - const { errorText, hideDefaultChips, loading, value, ...rest } = props; + const { + errorText, + hideDefaultChips, + loading, + value, + options, + optionsFilter, + ...rest + } = props; + + const { data: firewalls, error, isLoading } = useAllFirewallsQuery(!options); - const { data: firewalls, error, isLoading } = useAllFirewallsQuery(); + const firewallOptions = + options || (optionsFilter ? firewalls?.filter(optionsFilter) : firewalls); const { defaultNumEntities, isDefault, tooltipText } = useDefaultFirewallChipInformation(value, hideDefaultChips); @@ -65,7 +81,7 @@ export const FirewallSelect = ( label="Firewall" loading={isLoading || loading} noMarginTop - options={firewalls ?? []} + options={firewallOptions ?? []} placeholder="None" renderOption={({ key, ...props }, option, state) => ( void; } export const AddFirewallForm = (props: Props) => { - const { entityId, entityType, onCancel } = props; + const { attachedFirewalls, entityId, entityType, onCancel } = props; + const hasEnabledFirewall = attachedFirewalls?.some( + (firewall) => firewall.status === 'enabled' + ); + const { enqueueSnackbar } = useSnackbar(); const entityLabel = formattedTypes[entityType] ?? entityType; @@ -51,6 +56,13 @@ export const AddFirewallForm = (props: Props) => { } }; + const optionsFilter = (firewall: Firewall) => { + return ( + !(hasEnabledFirewall && firewall.status === 'enabled') && + !attachedFirewalls?.some((fw) => fw.id === firewall.id) + ); + }; + return (
@@ -66,6 +78,7 @@ export const AddFirewallForm = (props: Props) => { errorText={fieldState.error?.message} label="Firewall" onChange={(e, value) => field.onChange(value?.id)} + optionsFilter={optionsFilter} placeholder="Select a Firewall" textFieldProps={{ inputRef: field.ref, diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeFirewalls/LinodeFirewalls.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeFirewalls/LinodeFirewalls.tsx index 6175971d01a..b4ff1630515 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeFirewalls/LinodeFirewalls.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeFirewalls/LinodeFirewalls.tsx @@ -86,9 +86,7 @@ export const LinodeFirewalls = (props: LinodeFirewallsProps) => { @@ -118,6 +116,7 @@ export const LinodeFirewalls = (props: LinodeFirewallsProps) => { title="Add Firewall" > setIsAddFirewalDrawerOpen(false)} diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerFirewalls.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerFirewalls.tsx index 5866fe61d69..737d047e880 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerFirewalls.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerFirewalls.tsx @@ -112,14 +112,12 @@ export const NodeBalancerFirewalls = (props: Props) => { @@ -168,6 +166,7 @@ export const NodeBalancerFirewalls = (props: Props) => { title="Add Firewall" > diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx index 37be0910ee0..f9a698b72d2 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx @@ -34,13 +34,11 @@ export const SummaryPanel = () => { const { data: attachedFirewallData } = useNodeBalancersFirewallsQuery( Number(id) ); - const linkText = attachedFirewallData?.data[0]?.label; - const linkID = attachedFirewallData?.data[0]?.id; + const firewalls = attachedFirewallData?.data ?? []; const region = regions?.find((r) => r.id === nodebalancer?.region); const { mutateAsync: updateNodeBalancer } = useNodebalancerUpdateMutation( Number(id) ); - const displayFirewallLink = !!attachedFirewallData?.data?.length; const isNodeBalancerReadOnly = useIsResourceRestricted({ grantLevel: 'read_only', @@ -182,20 +180,24 @@ export const SummaryPanel = () => { - {displayFirewallLink && ( + {firewalls.length > 0 && ( - Firewall + {firewalls.length > 1 ? 'Firewalls' : 'Firewall'} - - - {linkText} - - + {firewalls.map((firewall) => { + return ( + + + {firewall.label} + + + ); + })} )}