-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add TLS Cert input #1784
Add TLS Cert input #1784
Changes from 5 commits
fc5d419
f9fe2f4
1153dc6
053b95d
42ab6e8
16c4c16
e059e19
ce5ac12
fc253c1
b6ea919
3107381
cc4fa1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
/* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, you can obtain one at https://mozilla.org/MPL/2.0/. | ||
* | ||
* Copyright Oxide Computer Company | ||
*/ | ||
import { useState } from 'react' | ||
import type { Control, FieldPath, FieldValues } from 'react-hook-form' | ||
import { useController } from 'react-hook-form' | ||
|
||
import { Button, Error16Icon, FieldLabel, MiniTable, Modal } from '@oxide/ui' | ||
import { capitalize } from '@oxide/util' | ||
|
||
import { | ||
DescriptionField, | ||
FileField, | ||
TextField, | ||
type TextFieldProps, | ||
} from 'app/components/form' | ||
import type { SiloCreateInput } from 'app/forms/silo-create' | ||
import { useForm } from 'app/hooks' | ||
|
||
export function TlsCertsField({ control }: { control: Control<SiloCreateInput> }) { | ||
const [showAddCert, setShowAddCert] = useState(false) | ||
|
||
const { | ||
field: { value: items, onChange }, | ||
} = useController({ control, name: 'tlsCertificates' }) | ||
|
||
return ( | ||
<> | ||
<div className="max-w-lg"> | ||
<FieldLabel id="tls-certificates-label" className="mb-3"> | ||
TLS Certificates | ||
</FieldLabel> | ||
{!!items.length && ( | ||
<MiniTable.Table className="mb-4"> | ||
<MiniTable.Header> | ||
<MiniTable.HeadCell>Name</MiniTable.HeadCell> | ||
{/* For remove button */} | ||
<MiniTable.HeadCell className="w-12" /> | ||
</MiniTable.Header> | ||
<MiniTable.Body> | ||
{items.map((item, index) => ( | ||
<MiniTable.Row | ||
tabIndex={0} | ||
aria-rowindex={index + 1} | ||
aria-label={`Name: ${item.name}, Description: ${item.description}`} | ||
key={item.name} | ||
> | ||
<MiniTable.Cell>{item.name}</MiniTable.Cell> | ||
<MiniTable.Cell> | ||
<button | ||
onClick={() => onChange(items.filter((i) => i.name !== item.name))} | ||
> | ||
<Error16Icon title={`remove ${item.name}`} /> | ||
</button> | ||
</MiniTable.Cell> | ||
</MiniTable.Row> | ||
))} | ||
</MiniTable.Body> | ||
</MiniTable.Table> | ||
)} | ||
|
||
<Button size="sm" onClick={() => setShowAddCert(true)}> | ||
Add TLS certificate | ||
</Button> | ||
</div> | ||
|
||
{showAddCert && ( | ||
<AddCertModal | ||
onDismiss={() => setShowAddCert(false)} | ||
onSubmit={async (values) => { | ||
const certCreate: (typeof items)[number] = { | ||
...values, | ||
cert: await values.cert.text(), | ||
key: await values.key.text(), | ||
} | ||
onChange([...items, certCreate]) | ||
setShowAddCert(false) | ||
}} | ||
allNames={items.map((item) => item.name)} | ||
/> | ||
)} | ||
</> | ||
) | ||
} | ||
|
||
export type TlsCertificate = Omit< | ||
SiloCreateInput['tlsCertificates'][number], | ||
'key' | 'cert' | ||
> & { | ||
key: File | ||
cert: File | ||
} | ||
|
||
const defaultValues: Partial<TlsCertificate> = { | ||
description: '', | ||
name: '', | ||
service: 'external_api', | ||
} | ||
|
||
function UniqueNameField< | ||
TFieldValues extends FieldValues, | ||
TName extends FieldPath<TFieldValues> | ||
>({ | ||
required = true, | ||
name, | ||
label = capitalize(name), | ||
allNames, | ||
...textFieldProps | ||
}: Omit<TextFieldProps<TFieldValues, TName>, 'validate'> & { | ||
label?: string | ||
allNames: string[] | ||
}) { | ||
return ( | ||
<TextField | ||
validate={(name) => | ||
allNames.includes(name) ? 'Certificate with this name already exists' : true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did attempt to get this going in the But the benefit of this approach is we don't need to mess around with Also the key is guaranteed to be unique because if a name is not, it will never be added. |
||
} | ||
required={required} | ||
label={label} | ||
name={name} | ||
{...textFieldProps} | ||
/> | ||
) | ||
} | ||
|
||
const AddCertModal = ({ | ||
onDismiss, | ||
onSubmit, | ||
allNames, | ||
}: { | ||
onDismiss: () => void | ||
onSubmit: (values: TlsCertificate) => void | ||
allNames: string[] | ||
}) => { | ||
const { control, handleSubmit } = useForm<TlsCertificate>({ defaultValues }) | ||
|
||
return ( | ||
<Modal isOpen onDismiss={onDismiss} title="Add TLS certificate"> | ||
<Modal.Body> | ||
<form | ||
autoComplete="off" | ||
onSubmit={(e) => { | ||
e.stopPropagation() | ||
handleSubmit(onSubmit)(e) | ||
}} | ||
> | ||
<Modal.Section> | ||
<UniqueNameField name="name" control={control} allNames={allNames} /> | ||
<DescriptionField name="description" control={control} /> | ||
<FileField | ||
id="cert-input" | ||
name="cert" | ||
label="Cert" | ||
required | ||
control={control} | ||
/> | ||
<FileField id="key-input" name="key" label="Key" required control={control} /> | ||
</Modal.Section> | ||
</form> | ||
</Modal.Body> | ||
<Modal.Footer | ||
onDismiss={onDismiss} | ||
onAction={handleSubmit(onSubmit)} | ||
actionText="Add Certificate" | ||
/> | ||
</Modal> | ||
) | ||
} |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently we aren't checking for uniqueness around name. Do we want to do so? If not, we should use something like
${item.name}-${index}
to ensure the keys do not clash.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we probably should enforce uniqueness. I guess we’d check that on submit in the modal? Refuse to submit if there’s a duplicate.