-
Notifications
You must be signed in to change notification settings - Fork 19
Add TLS Cert input #1784
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
Changes from all 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,147 @@ | ||
| /* | ||
| * 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 } from 'react-hook-form' | ||
| import { useController } from 'react-hook-form' | ||
| import type { Merge } from 'type-fest' | ||
|
|
||
| import type { CertificateCreate } from '@oxide/api' | ||
| import { Button, Error16Icon, FieldLabel, MiniTable, Modal } from '@oxide/ui' | ||
|
|
||
| import { DescriptionField, FileField, TextField, validateName } from 'app/components/form' | ||
| import type { SiloCreateFormValues } from 'app/forms/silo-create' | ||
| import { useForm } from 'app/hooks' | ||
|
|
||
| export function TlsCertsField({ control }: { control: Control<SiloCreateFormValues> }) { | ||
| 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} | ||
|
Contributor
Author
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. Currently we aren't checking for uniqueness around name. Do we want to do so? If not, we should use something like
Collaborator
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. 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. |
||
| > | ||
| <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 and key are required fields. they will always be present if we get here | ||
| cert: await values.cert!.text(), | ||
| key: await values.key!.text(), | ||
| } | ||
| onChange([...items, certCreate]) | ||
| setShowAddCert(false) | ||
| }} | ||
| allNames={items.map((item) => item.name)} | ||
| /> | ||
| )} | ||
| </> | ||
| ) | ||
| } | ||
|
|
||
| export type CertFormValues = Merge< | ||
| CertificateCreate, | ||
| { key: File | null; cert: File | null } // swap strings for Files | ||
| > | ||
|
|
||
| const defaultValues: CertFormValues = { | ||
| description: '', | ||
| name: '', | ||
| service: 'external_api', | ||
| key: null, | ||
| cert: null, | ||
| } | ||
|
|
||
| type AddCertModalProps = { | ||
| onDismiss: () => void | ||
| onSubmit: (values: CertFormValues) => void | ||
| allNames: string[] | ||
| } | ||
|
|
||
| const AddCertModal = ({ onDismiss, onSubmit, allNames }: AddCertModalProps) => { | ||
| const { control, handleSubmit } = useForm<CertFormValues>({ defaultValues }) | ||
|
|
||
| return ( | ||
| <Modal isOpen onDismiss={onDismiss} title="Add TLS certificate"> | ||
| <Modal.Body> | ||
| <form autoComplete="off" onSubmit={handleSubmit(onSubmit)}> | ||
| <Modal.Section> | ||
| <TextField | ||
| name="name" | ||
| control={control} | ||
| required | ||
| // this field is identical to NameField (which just does | ||
| // validateName for you) except we also want to check that the | ||
| // name is not in the list of certs you've already added | ||
| validate={(name) => { | ||
| if (allNames.includes(name)) { | ||
| return 'A certificate with this name already exists' | ||
| } | ||
| return validateName(name, 'Name', true) | ||
| }} | ||
| /> | ||
| <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.
one year of thinking is enough