|
8 | 8 |
|
9 | 9 | import type { UseFormReturn } from 'react-hook-form' |
10 | 10 |
|
11 | | -import type { |
12 | | - RouteDestination, |
13 | | - RouterRouteCreate, |
14 | | - RouterRouteUpdate, |
15 | | - RouteTarget, |
| 11 | +import { |
| 12 | + usePrefetchedApiQuery, |
| 13 | + type Instance, |
| 14 | + type RouteDestination, |
| 15 | + type RouterRouteCreate, |
| 16 | + type RouterRouteUpdate, |
| 17 | + type RouteTarget, |
| 18 | + type VpcSubnet, |
16 | 19 | } from '~/api' |
| 20 | +import { ComboboxField } from '~/components/form/fields/ComboboxField' |
17 | 21 | import { DescriptionField } from '~/components/form/fields/DescriptionField' |
18 | 22 | import { ListboxField } from '~/components/form/fields/ListboxField' |
19 | 23 | import { NameField } from '~/components/form/fields/NameField' |
20 | 24 | import { TextField } from '~/components/form/fields/TextField' |
| 25 | +import { useVpcRouterSelector } from '~/hooks/use-params' |
21 | 26 | import { Message } from '~/ui/lib/Message' |
22 | 27 |
|
23 | 28 | export type RouteFormValues = RouterRouteCreate | Required<RouterRouteUpdate> |
@@ -52,65 +57,122 @@ const targetTypes: Record<Exclude<RouteTarget['type'], 'subnet' | 'vpc'>, string |
52 | 57 | drop: 'Drop', |
53 | 58 | } |
54 | 59 |
|
55 | | -const toItems = (mapping: Record<string, string>) => |
| 60 | +const destinationValuePlaceholder: Record<RouteDestination['type'], string | undefined> = { |
| 61 | + ip: 'Enter an IP', |
| 62 | + ip_net: 'Enter an IP network', |
| 63 | + subnet: 'Select a subnet', |
| 64 | + vpc: undefined, |
| 65 | +} |
| 66 | + |
| 67 | +const destinationValueDescription: Record<RouteDestination['type'], string | undefined> = { |
| 68 | + ip: 'An IP address, like 192.168.1.222', |
| 69 | + ip_net: 'An IP network, like 192.168.0.0/16', |
| 70 | + subnet: undefined, |
| 71 | + vpc: undefined, |
| 72 | +} |
| 73 | + |
| 74 | +/** possible targetTypes needing placeholders are instances or IPs (internet_gateway has no placeholder) */ |
| 75 | +const targetValuePlaceholder: Record<RouteTarget['type'], string | undefined> = { |
| 76 | + ip: 'Enter an IP', |
| 77 | + instance: 'Select an instance', |
| 78 | + internet_gateway: undefined, |
| 79 | + drop: undefined, |
| 80 | + subnet: undefined, |
| 81 | + vpc: undefined, |
| 82 | +} |
| 83 | + |
| 84 | +const targetValueDescription: Record<RouteTarget['type'], string | undefined> = { |
| 85 | + ip: 'An IP address, like 10.0.1.5', |
| 86 | + instance: undefined, |
| 87 | + internet_gateway: routeFormMessage.internetGatewayTargetValue, |
| 88 | + drop: undefined, |
| 89 | + subnet: undefined, |
| 90 | + vpc: undefined, |
| 91 | +} |
| 92 | + |
| 93 | +const toListboxItems = (mapping: Record<string, string>) => |
56 | 94 | Object.entries(mapping).map(([value, label]) => ({ value, label })) |
57 | 95 |
|
| 96 | +const toComboboxItems = (items: Array<Instance | VpcSubnet>) => |
| 97 | + items.map(({ name }) => ({ value: name, label: name })) |
| 98 | + |
58 | 99 | type RouteFormFieldsProps = { |
59 | 100 | form: UseFormReturn<RouteFormValues> |
60 | | - isDisabled?: boolean |
| 101 | + disabled?: boolean |
61 | 102 | } |
62 | | -export const RouteFormFields = ({ form, isDisabled }: RouteFormFieldsProps) => { |
| 103 | +export const RouteFormFields = ({ form, disabled }: RouteFormFieldsProps) => { |
| 104 | + const routerSelector = useVpcRouterSelector() |
| 105 | + const { project, vpc } = routerSelector |
| 106 | + // usePrefetchedApiQuery items below are initially fetched in the loaders in vpc-router-route-create and -edit |
| 107 | + const { |
| 108 | + data: { items: vpcSubnets }, |
| 109 | + } = usePrefetchedApiQuery('vpcSubnetList', { query: { project, vpc, limit: 1000 } }) |
| 110 | + const { |
| 111 | + data: { items: instances }, |
| 112 | + } = usePrefetchedApiQuery('instanceList', { query: { project, limit: 1000 } }) |
| 113 | + |
63 | 114 | const { control } = form |
| 115 | + const destinationType = form.watch('destination.type') |
64 | 116 | const targetType = form.watch('target.type') |
| 117 | + const destinationValueProps = { |
| 118 | + name: 'destination.value' as const, |
| 119 | + label: 'Destination value', |
| 120 | + control, |
| 121 | + placeholder: destinationValuePlaceholder[destinationType], |
| 122 | + required: true, |
| 123 | + disabled, |
| 124 | + description: destinationValueDescription[destinationType], |
| 125 | + } |
| 126 | + const targetValueProps = { |
| 127 | + name: 'target.value' as const, |
| 128 | + label: 'Target value', |
| 129 | + control, |
| 130 | + placeholder: targetValuePlaceholder[targetType], |
| 131 | + required: true, |
| 132 | + // 'internet_gateway' targetTypes can only have the value 'outbound', so we disable the field |
| 133 | + disabled: disabled || targetType === 'internet_gateway', |
| 134 | + description: targetValueDescription[targetType], |
| 135 | + } |
65 | 136 | return ( |
66 | 137 | <> |
67 | | - {isDisabled && ( |
| 138 | + {disabled && ( |
68 | 139 | <Message variant="info" content={routeFormMessage.vpcSubnetNotModifiable} /> |
69 | 140 | )} |
70 | | - <NameField name="name" control={control} disabled={isDisabled} /> |
71 | | - <DescriptionField name="description" control={control} disabled={isDisabled} /> |
| 141 | + <NameField name="name" control={control} disabled={disabled} /> |
| 142 | + <DescriptionField name="description" control={control} disabled={disabled} /> |
72 | 143 | <ListboxField |
73 | 144 | name="destination.type" |
74 | 145 | label="Destination type" |
75 | 146 | control={control} |
76 | | - items={toItems(destTypes)} |
| 147 | + items={toListboxItems(destTypes)} |
77 | 148 | placeholder="Select a destination type" |
78 | 149 | required |
79 | | - disabled={isDisabled} |
80 | | - /> |
81 | | - <TextField |
82 | | - name="destination.value" |
83 | | - label="Destination value" |
84 | | - control={control} |
85 | | - placeholder="Enter a destination value" |
86 | | - required |
87 | | - disabled={isDisabled} |
| 150 | + onChange={() => { |
| 151 | + form.setValue('destination.value', '') |
| 152 | + }} |
| 153 | + disabled={disabled} |
88 | 154 | /> |
| 155 | + {destinationType === 'subnet' ? ( |
| 156 | + <ComboboxField {...destinationValueProps} items={toComboboxItems(vpcSubnets)} /> |
| 157 | + ) : ( |
| 158 | + <TextField {...destinationValueProps} /> |
| 159 | + )} |
89 | 160 | <ListboxField |
90 | 161 | name="target.type" |
91 | 162 | label="Target type" |
92 | 163 | control={control} |
93 | | - items={toItems(targetTypes)} |
| 164 | + items={toListboxItems(targetTypes)} |
94 | 165 | placeholder="Select a target type" |
95 | 166 | required |
96 | 167 | onChange={(value) => { |
97 | 168 | form.setValue('target.value', value === 'internet_gateway' ? 'outbound' : '') |
98 | 169 | }} |
99 | | - disabled={isDisabled} |
| 170 | + disabled={disabled} |
100 | 171 | /> |
101 | | - {targetType !== 'drop' && ( |
102 | | - <TextField |
103 | | - name="target.value" |
104 | | - label="Target value" |
105 | | - control={control} |
106 | | - placeholder="Enter a target value" |
107 | | - required |
108 | | - // 'internet_gateway' targetTypes can only have the value 'outbound', so we disable the field |
109 | | - disabled={isDisabled || targetType === 'internet_gateway'} |
110 | | - description={ |
111 | | - targetType === 'internet_gateway' && routeFormMessage.internetGatewayTargetValue |
112 | | - } |
113 | | - /> |
| 172 | + {targetType === 'drop' ? null : targetType === 'instance' ? ( |
| 173 | + <ComboboxField {...targetValueProps} items={toComboboxItems(instances)} /> |
| 174 | + ) : ( |
| 175 | + <TextField {...targetValueProps} /> |
114 | 176 | )} |
115 | 177 | </> |
116 | 178 | ) |
|
0 commit comments