6
6
* Copyright Oxide Computer Company
7
7
*/
8
8
9
+ import { useEffect } from 'react'
9
10
import {
10
11
useController ,
11
12
useForm ,
@@ -27,7 +28,7 @@ import { CheckboxField } from '~/components/form/fields/CheckboxField'
27
28
import { ComboboxField } from '~/components/form/fields/ComboboxField'
28
29
import { DescriptionField } from '~/components/form/fields/DescriptionField'
29
30
import { ListboxField } from '~/components/form/fields/ListboxField'
30
- import { NameField } from '~/components/form/fields/NameField'
31
+ import { NameField , validateName } from '~/components/form/fields/NameField'
31
32
import { NumberField } from '~/components/form/fields/NumberField'
32
33
import { RadioField } from '~/components/form/fields/RadioField'
33
34
import { TextField , TextFieldInner } from '~/components/form/fields/TextField'
@@ -39,6 +40,7 @@ import * as MiniTable from '~/ui/lib/MiniTable'
39
40
import { TextInputHint } from '~/ui/lib/TextInput'
40
41
import { KEYS } from '~/ui/util/keys'
41
42
import { ALL_ISH } from '~/util/consts'
43
+ import { validateIp , validateIpNet } from '~/util/ip'
42
44
import { links } from '~/util/links'
43
45
import { capitalize } from '~/util/str'
44
46
@@ -62,7 +64,6 @@ type TargetAndHostFilterType =
62
64
type TargetAndHostFormValues = {
63
65
type : TargetAndHostFilterType
64
66
value : string
65
- subnetVpc ?: string
66
67
}
67
68
68
69
// these are part of the target and host filter form;
@@ -132,8 +133,13 @@ const DynamicTypeAndValueFields = ({
132
133
items = { items }
133
134
allowArbitraryValues
134
135
hideOptionalTag
135
- // TODO: validate here, but it's complicated because it's conditional
136
- // on which type is selected
136
+ validate = { ( value ) =>
137
+ // required: false arg is desirable because otherwise if you put in
138
+ // a bad name and submit, causing it to revalidate onChange, then
139
+ // clear the field you're left with a BS "Target name is required"
140
+ // error message
141
+ validateName ( value , `${ capitalize ( sectionType ) } name` , false )
142
+ }
137
143
/>
138
144
) : (
139
145
< TextField
@@ -146,8 +152,11 @@ const DynamicTypeAndValueFields = ({
146
152
onSubmitTextField ( e )
147
153
}
148
154
} }
149
- // TODO: validate here, but it's complicated because it's conditional
150
- // on which type is selected
155
+ validate = { ( value ) =>
156
+ ( valueType === 'ip' && validateIp ( value ) ) ||
157
+ ( valueType === 'ip_net' && validateIpNet ( value ) ) ||
158
+ undefined
159
+ }
151
160
/>
152
161
) }
153
162
</ >
@@ -233,14 +242,14 @@ type CommonFieldsProps = {
233
242
error : ApiError | null
234
243
}
235
244
245
+ const targetAndHostDefaultValues : TargetAndHostFormValues = {
246
+ type : 'vpc' ,
247
+ value : '' ,
248
+ }
249
+
236
250
export const CommonFields = ( { control, nameTaken, error } : CommonFieldsProps ) => {
237
251
const { project, vpc } = useVpcSelector ( )
238
- const targetAndHostDefaultValues : TargetAndHostFormValues = {
239
- type : 'vpc' ,
240
- value : '' ,
241
- // only becomes relevant when the type is 'VPC subnet'; ignored otherwise
242
- subnetVpc : vpc ,
243
- }
252
+
244
253
// prefetchedApiQueries below are prefetched in firewall-rules-create and -edit
245
254
const {
246
255
data : { items : instances } ,
@@ -255,8 +264,7 @@ export const CommonFields = ({ control, nameTaken, error }: CommonFieldsProps) =
255
264
// Targets
256
265
const targetForm = useForm ( { defaultValues : targetAndHostDefaultValues } )
257
266
const targets = useController ( { name : 'targets' , control } ) . field
258
- const targetType = targetForm . watch ( 'type' )
259
- const targetValue = targetForm . watch ( 'value' )
267
+ const [ targetType , targetValue ] = targetForm . watch ( [ 'type' , 'value' ] )
260
268
// get the list of items that are not already in the list of targets
261
269
const targetItems = {
262
270
vpc : availableItems ( targets . value , 'vpc' , vpcs ) ,
@@ -272,8 +280,20 @@ export const CommonFields = ({ control, nameTaken, error }: CommonFieldsProps) =
272
280
if ( ! type || ! value ) return
273
281
if ( targets . value . some ( ( t ) => t . value === value && t . type === type ) ) return
274
282
targets . onChange ( [ ...targets . value , { type, value } ] )
275
- targetForm . reset ( )
283
+ targetForm . reset ( targetAndHostDefaultValues )
276
284
} )
285
+ // HACK: we need to reset the target form completely after a successful submit,
286
+ // including especially `isSubmitted`, because that governs whether we're in
287
+ // the validate regime (which doesn't validate until submit) or the reValidate
288
+ // regime (which validate on every keypress). The reset inside `handleSubmit`
289
+ // doesn't do that for us because `handleSubmit` set `isSubmitted: true` after
290
+ // running the callback. So we have to watch for a successful submit and call
291
+ // the reset again here.
292
+ // https://github.com/react-hook-form/react-hook-form/blob/9a497a70a/src/logic/createFormControl.ts#L1194-L1203
293
+ const { isSubmitSuccessful : targetSubmitSuccessful } = targetForm . formState
294
+ useEffect ( ( ) => {
295
+ if ( targetSubmitSuccessful ) targetForm . reset ( targetAndHostDefaultValues )
296
+ } , [ targetSubmitSuccessful , targetForm ] )
277
297
278
298
// Ports
279
299
const portRangeForm = useForm ( { defaultValues : { portRange : '' } } )
@@ -290,8 +310,7 @@ export const CommonFields = ({ control, nameTaken, error }: CommonFieldsProps) =
290
310
// Host Filters
291
311
const hostForm = useForm ( { defaultValues : targetAndHostDefaultValues } )
292
312
const hosts = useController ( { name : 'hosts' , control } ) . field
293
- const hostType = hostForm . watch ( 'type' )
294
- const hostValue = hostForm . watch ( 'value' )
313
+ const [ hostType , hostValue ] = hostForm . watch ( [ 'type' , 'value' ] )
295
314
// get the list of items that are not already in the list of host filters
296
315
const hostFilterItems = {
297
316
vpc : availableItems ( hosts . value , 'vpc' , vpcs ) ,
@@ -306,8 +325,13 @@ export const CommonFields = ({ control, nameTaken, error }: CommonFieldsProps) =
306
325
if ( ! type || ! value ) return
307
326
if ( hosts . value . some ( ( t ) => t . value === value && t . type === type ) ) return
308
327
hosts . onChange ( [ ...hosts . value , { type, value } ] )
309
- hostForm . reset ( )
328
+ hostForm . reset ( targetAndHostDefaultValues )
310
329
} )
330
+ // HACK: see comment above about doing the same for target form
331
+ const { isSubmitSuccessful : hostSubmitSuccessful } = hostForm . formState
332
+ useEffect ( ( ) => {
333
+ if ( hostSubmitSuccessful ) hostForm . reset ( targetAndHostDefaultValues )
334
+ } , [ hostSubmitSuccessful , hostForm ] )
311
335
312
336
return (
313
337
< >
@@ -411,13 +435,18 @@ export const CommonFields = ({ control, nameTaken, error }: CommonFieldsProps) =
411
435
control = { targetForm . control }
412
436
valueType = { targetType }
413
437
items = { targetItems [ targetType ] }
414
- onTypeChange = { ( ) => targetForm . setValue ( 'value' , '' ) }
438
+ // HACK: reset the whole subform, keeping type (because we just set
439
+ // it). most importantly, this resets isSubmitted so the form can go
440
+ // back to validating on submit instead of change
441
+ onTypeChange = { ( ) =>
442
+ targetForm . reset ( { type : targetForm . getValues ( 'type' ) , value : '' } )
443
+ }
415
444
onInputChange = { ( value ) => targetForm . setValue ( 'value' , value ) }
416
445
onSubmitTextField = { submitTarget }
417
446
/>
418
447
< MiniTable . ClearAndAddButtons
419
448
addButtonCopy = "Add target"
420
- disableClear = { ! ! targetValue }
449
+ disableClear = { ! targetValue }
421
450
onClear = { ( ) => targetForm . reset ( ) }
422
451
onSubmit = { submitTarget }
423
452
/>
@@ -468,8 +497,8 @@ export const CommonFields = ({ control, nameTaken, error }: CommonFieldsProps) =
468
497
</ div >
469
498
< MiniTable . ClearAndAddButtons
470
499
addButtonCopy = "Add port filter"
471
- disableClear = { ! ! portValue }
472
- onClear = { portRangeForm . reset }
500
+ disableClear = { ! portValue }
501
+ onClear = { ( ) => portRangeForm . reset ( ) }
473
502
onSubmit = { submitPortRange }
474
503
/>
475
504
</ div >
@@ -518,13 +547,18 @@ export const CommonFields = ({ control, nameTaken, error }: CommonFieldsProps) =
518
547
control = { hostForm . control }
519
548
valueType = { hostType }
520
549
items = { hostFilterItems [ hostType ] }
521
- onTypeChange = { ( ) => hostForm . setValue ( 'value' , '' ) }
550
+ // HACK: reset the whole subform, keeping type (because we just set
551
+ // it). most importantly, this resets isSubmitted so the form can go
552
+ // back to validating on submit instead of change
553
+ onTypeChange = { ( ) =>
554
+ hostForm . reset ( { type : hostForm . getValues ( 'type' ) , value : '' } )
555
+ }
522
556
onInputChange = { ( value ) => hostForm . setValue ( 'value' , value ) }
523
557
onSubmitTextField = { submitHost }
524
558
/>
525
559
< MiniTable . ClearAndAddButtons
526
560
addButtonCopy = "Add host filter"
527
- disableClear = { ! ! hostValue }
561
+ disableClear = { ! hostValue }
528
562
onClear = { ( ) => hostForm . reset ( ) }
529
563
onSubmit = { submitHost }
530
564
/>
0 commit comments