1
1
import {
2
2
Button ,
3
+ Flex ,
4
+ FlexItem ,
3
5
Form ,
6
+ FormGroup ,
4
7
FormHelperText ,
5
8
FormSelect ,
6
9
FormSelectOption ,
7
- Grid ,
8
- GridItem ,
9
10
HelperText ,
10
11
HelperTextItem ,
12
+ Popover ,
11
13
TextInput ,
12
14
} from "@patternfly/react-core" ;
13
- import { ReactNode , useEffect } from "react" ;
15
+ import { ReactNode , useEffect , useState } from "react" ;
14
16
import { Controller , RegisterOptions , useForm } from "react-hook-form" ;
15
17
import { Attribute , ATTRIBUTES } from "../api/rekor_api" ;
16
- import { ExclamationCircleIcon } from "@patternfly/react-icons" ;
18
+ import { ExclamationCircleIcon , HelpIcon } from "@patternfly/react-icons" ;
19
+ import styles from "@patternfly/react-styles/css/components/Form/form" ;
17
20
18
21
export interface FormProps {
19
22
defaultValues ?: FormInputs ;
@@ -34,67 +37,80 @@ type Rules = Omit<
34
37
interface InputConfig {
35
38
name : string ;
36
39
helperText ?: ReactNode ;
40
+ placeholder ?: string ;
37
41
rules : Rules ;
42
+ tooltipText ?: ReactNode ;
38
43
}
39
44
40
45
const inputConfigByAttribute : Record < FormInputs [ "attribute" ] , InputConfig > = {
41
46
email : {
42
47
name : "Email" ,
48
+ placeholder : "jdoe@example.com" ,
43
49
rules : {
44
50
pattern : {
45
51
value : / \S + @ \S + \. \S + / ,
46
52
message : "Entered value does not match the email format: 'S+@S+.S+'" ,
47
53
} ,
48
54
} ,
55
+ tooltipText : < > Search by the signer's email address.</ > ,
49
56
} ,
50
57
hash : {
51
58
name : "Hash" ,
59
+ placeholder :
60
+ "sha256:8ceb4ab8127731473a9ec81140cb6849cf8e970cda31baef099df48ba3264441" ,
52
61
rules : {
53
62
pattern : {
54
63
value : / ^ ( s h a 2 5 6 : ) ? [ 0 - 9 a - f A - F ] { 64 } $ | ^ ( s h a 1 : ) ? [ 0 - 9 a - f A - F ] { 40 } $ / ,
55
64
message :
56
65
"Entered value does not match the hash format: '^(sha256:)?[0-9a-fA-F]{64}$|^(sha1:)?[0-9a-fA-F]{40}$'" ,
57
66
} ,
58
67
} ,
68
+ tooltipText : < > Search by the SHA1 or SHA2 hash value.</ > ,
59
69
} ,
60
70
commitSha : {
61
71
name : "Commit SHA" ,
62
72
helperText : (
63
73
< >
64
74
Only compatible with{ " " }
65
75
< a
66
- href = "https://github. com/sigstore/ gitsign"
76
+ href = "https://access.redhat. com/documentation/en-us/red_hat_trusted_artifact_signer/2024-q1/html/deployment_guide/verify_the_trusted_artifact_signer_installation#signing-and-verifying-commits-by-using- gitsign-from-the-command-line-interface_deploy "
67
77
target = "_blank"
68
78
rel = "noopener noreferrer"
69
79
style = { {
70
80
textDecoration : "underline" ,
71
81
} }
72
82
>
73
- sigstore/ gitsign
83
+ gitsign
74
84
</ a > { " " }
75
85
entries
76
86
</ >
77
87
) ,
88
+ placeholder : "6d78e27dfcf83eaad6ef73c4695d1ddc663f5555" ,
78
89
rules : {
79
90
pattern : {
80
91
value : / ^ [ 0 - 9 a - f A - F ] { 40 } $ / ,
81
92
message :
82
93
"Entered value does not match the commit SHA format: '^[0-9a-fA-F]{40}$'" ,
83
94
} ,
84
95
} ,
96
+ tooltipText : < > Search by the commit hash.</ > ,
85
97
} ,
86
98
uuid : {
87
99
name : "Entry UUID" ,
100
+ placeholder :
101
+ "24296fb24b8ad77a71b9c1374e207537bafdd75b4f591dcee10f3f697f150d7cc5d0b725eea641e7" ,
88
102
rules : {
89
103
pattern : {
90
104
value : / ^ [ 0 - 9 a - f A - F ] { 64 } | [ 0 - 9 a - f A - F ] { 80 } $ / ,
91
105
message :
92
106
"Entered value does not match the entry UUID format: '^[0-9a-fA-F]{64}|[0-9a-fA-F]{80}$'" ,
93
107
} ,
94
108
} ,
109
+ tooltipText : < > Search by the universally unique identifier value.</ > ,
95
110
} ,
96
111
logIndex : {
97
112
name : "Log Index" ,
113
+ placeholder : "1234567" ,
98
114
rules : {
99
115
min : {
100
116
value : 0 ,
@@ -105,6 +121,7 @@ const inputConfigByAttribute: Record<FormInputs["attribute"], InputConfig> = {
105
121
message : "Entered value must be of type int64" ,
106
122
} ,
107
123
} ,
124
+ tooltipText : < > Search by the log index number.</ > ,
108
125
} ,
109
126
} ;
110
127
@@ -148,81 +165,129 @@ export function SearchForm({ defaultValues, onSubmit, isLoading }: FormProps) {
148
165
149
166
return (
150
167
< Form onSubmit = { handleSubmit ( onSubmit ) } >
151
- < Grid hasGutter = { true } >
152
- < GridItem sm = { 4 } >
153
- < Controller
154
- name = "attribute"
155
- control = { control }
156
- render = { ( { field } ) => (
157
- < FormSelect
158
- id = "rekor-search-type"
159
- { ...field }
160
- label = "Attribute"
161
- >
162
- { ATTRIBUTES . map ( attribute => (
163
- < FormSelectOption
164
- label = { inputConfigByAttribute [ attribute ] . name }
165
- key = { attribute }
166
- value = { attribute }
167
- />
168
- ) ) }
169
- </ FormSelect >
170
- ) }
171
- />
172
- </ GridItem >
173
- < GridItem
174
- sm = { 8 }
175
- md = { 6 }
168
+ < Flex >
169
+ < Flex
170
+ direction = { { default : "column" } }
171
+ flex = { { default : "flex_3" } }
176
172
>
177
- < Controller
178
- name = "value"
179
- control = { control }
180
- rules = { rules }
181
- render = { ( { field, fieldState } ) => (
182
- < >
183
- < TextInput
184
- aria-label = { `${ inputConfigByAttribute [ watchAttribute ] . name } input field` }
185
- { ...field }
186
- label = { inputConfigByAttribute [ watchAttribute ] . name }
187
- placeholder = { inputConfigByAttribute [ watchAttribute ] . name }
188
- type = { "email" }
189
- validated = { fieldState . invalid ? "error" : "default" }
190
- />
191
- { fieldState . invalid && (
192
- < FormHelperText >
193
- < HelperText >
194
- < HelperTextItem
195
- icon = { < ExclamationCircleIcon /> }
196
- variant = { fieldState . invalid ? "error" : "success" }
173
+ < FlexItem >
174
+ < Controller
175
+ name = "attribute"
176
+ control = { control }
177
+ render = { ( { field } ) => (
178
+ < FormGroup
179
+ label = { "Attribute" }
180
+ fieldId = { "rekor-search-attribute" }
181
+ labelIcon = {
182
+ < Popover
183
+ bodyContent = {
184
+ inputConfigByAttribute [ watchAttribute ] . tooltipText
185
+ }
186
+ position = { "right" }
187
+ >
188
+ < button
189
+ type = "button"
190
+ aria-label = "More info for attribute field"
191
+ onClick = { e => e . preventDefault ( ) }
192
+ aria-describedby = "attribute-info"
193
+ className = { styles . formGroupLabelHelp }
197
194
>
198
- { fieldState . invalid
199
- ? fieldState . error ?. message
200
- : inputConfigByAttribute [ watchAttribute ] . helperText }
201
- </ HelperTextItem >
202
- </ HelperText >
203
- </ FormHelperText >
204
- ) }
205
- </ >
206
- ) }
207
- />
208
- </ GridItem >
209
- < GridItem
210
- sm = { 12 }
211
- md = { 2 }
195
+ < HelpIcon />
196
+ </ button >
197
+ </ Popover >
198
+ }
199
+ >
200
+ < FormSelect
201
+ id = "rekor-search-attribute"
202
+ { ...field }
203
+ label = "Attribute"
204
+ >
205
+ { ATTRIBUTES . map ( attribute => (
206
+ < FormSelectOption
207
+ label = { inputConfigByAttribute [ attribute ] . name }
208
+ key = { attribute }
209
+ value = { attribute }
210
+ />
211
+ ) ) }
212
+ </ FormSelect >
213
+ </ FormGroup >
214
+ ) }
215
+ />
216
+ </ FlexItem >
217
+ </ Flex >
218
+ < Flex
219
+ direction = { { default : "column" } }
220
+ flex = { { default : "flex_3" } }
221
+ >
222
+ < FlexItem >
223
+ < Controller
224
+ name = "value"
225
+ control = { control }
226
+ rules = { rules }
227
+ render = { ( { field, fieldState } ) => (
228
+ < FormGroup
229
+ label = { inputConfigByAttribute [ watchAttribute ] . name }
230
+ labelInfo = { inputConfigByAttribute [ watchAttribute ] . helperText }
231
+ fieldId = { `rekor-search-${ inputConfigByAttribute [
232
+ watchAttribute
233
+ ] . name . toLowerCase ( ) } `}
234
+ >
235
+ < TextInput
236
+ aria-label = { `${ inputConfigByAttribute [ watchAttribute ] . name } input field` }
237
+ { ...field }
238
+ id = { `rekor-search-${ inputConfigByAttribute [
239
+ watchAttribute
240
+ ] . name . toLowerCase ( ) } `}
241
+ label = { inputConfigByAttribute [ watchAttribute ] . name }
242
+ placeholder = {
243
+ inputConfigByAttribute [ watchAttribute ] . placeholder
244
+ }
245
+ type = {
246
+ inputConfigByAttribute [ watchAttribute ] . name === "email"
247
+ ? "email"
248
+ : "text"
249
+ }
250
+ validated = { fieldState . invalid ? "error" : "default" }
251
+ />
252
+ { fieldState . invalid && (
253
+ < FormHelperText >
254
+ < HelperText >
255
+ < HelperTextItem
256
+ icon = { < ExclamationCircleIcon /> }
257
+ variant = { fieldState . invalid ? "error" : "success" }
258
+ >
259
+ { fieldState . invalid
260
+ ? fieldState . error ?. message
261
+ : inputConfigByAttribute [ watchAttribute ] . helperText }
262
+ </ HelperTextItem >
263
+ </ HelperText >
264
+ </ FormHelperText >
265
+ ) }
266
+ </ FormGroup >
267
+ ) }
268
+ />
269
+ </ FlexItem >
270
+ </ Flex >
271
+ < Flex
272
+ direction = { { default : "column" } }
273
+ alignSelf = { { default : "alignSelfFlexStart" } }
274
+ flex = { { default : "flex_1" } }
212
275
>
213
- < Button
214
- variant = "primary"
215
- id = "search-form-button"
216
- isBlock = { true }
217
- isLoading = { isLoading }
218
- type = "submit"
219
- spinnerAriaLabel = { "Loading" }
220
- spinnerAriaLabelledBy = { "search-form-button" }
221
- >
222
- Search
223
- </ Button >
224
- </ GridItem >
225
- </ Grid >
276
+ < FlexItem style = { { marginTop : "2em" } } >
277
+ < Button
278
+ variant = "primary"
279
+ id = "search-form-button"
280
+ isBlock = { true }
281
+ isLoading = { isLoading }
282
+ type = "submit"
283
+ spinnerAriaLabel = { "Loading" }
284
+ spinnerAriaLabelledBy = { "search-form-button" }
285
+ >
286
+ Search
287
+ </ Button >
288
+ </ FlexItem >
289
+ </ Flex >
290
+ </ Flex >
226
291
</ Form >
227
292
) ;
228
293
}
0 commit comments