Skip to content

Commit

Permalink
Reorder IDP form elements (#2511)
Browse files Browse the repository at this point in the history
* Reorder IDP create form elements

* Update edit form order

* Add docs link for Identity Providers

* Remove checkbox for Signed Requests

* Add side modal heading (#2533)

* Add side modal heading

* Remove commented input legend

* very important mt-2

* cut one word to cut a whole line out of the targets info box

---------

Co-authored-by: David Crespo <[email protected]>

* change form section headings to SideModal.Heading, remove "General"

---------

Co-authored-by: Benjamin Leonard <[email protected]>
Co-authored-by: David Crespo <[email protected]>
  • Loading branch information
3 people authored Nov 6, 2024
1 parent b01ca85 commit 7dcd41c
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 109 deletions.
150 changes: 79 additions & 71 deletions app/forms/firewall-rules-common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ import { useVpcSelector } from '~/hooks/use-params'
import { Badge } from '~/ui/lib/Badge'
import { toComboboxItems, type ComboboxItem } from '~/ui/lib/Combobox'
import { FormDivider } from '~/ui/lib/Divider'
import { FieldLabel } from '~/ui/lib/FieldLabel'
import { Message } from '~/ui/lib/Message'
import * as MiniTable from '~/ui/lib/MiniTable'
import { SideModal } from '~/ui/lib/SideModal'
import { TextInputHint } from '~/ui/lib/TextInput'
import { KEYS } from '~/ui/util/keys'
import { ALL_ISH } from '~/util/consts'
Expand Down Expand Up @@ -415,46 +417,50 @@ export const CommonFields = ({ control, nameTaken, error }: CommonFieldsProps) =

{/* Really this should be its own <form>, but you can't have a form inside a form,
so we just stick the submit handler in a button onClick */}
<div className="flex flex-col gap-3">
<h3 className="mb-4 text-sans-2xl">Targets</h3>
<Message
variant="info"
content={
<>
<SideModal.Heading>Targets</SideModal.Heading>

<Message
variant="info"
content={
<>
<p>
Targets determine the instances to which this rule applies. You can target
instances directly by name, or specify a VPC, VPC subnet, IP, or IP subnet,
which will apply the rule to traffic going to all matching instances. Targets
are additive: the rule applies to instances matching{' '}
instances directly or specify a VPC, VPC subnet, IP, or IP subnet, which will
apply the rule to traffic going to all matching instances.
</p>
<p className="mt-2">
Targets are additive: the rule applies to instances matching{' '}
<span className="underline">any</span> target.
</>
}
/>
<DynamicTypeAndValueFields
sectionType="target"
control={targetForm.control}
valueType={targetType}
items={toComboboxItems(targetItems[targetType])}
// HACK: reset the whole subform, keeping type (because we just set
// it). most importantly, this resets isSubmitted so the form can go
// back to validating on submit instead of change
onTypeChange={() =>
targetForm.reset({ type: targetForm.getValues('type'), value: '' })
}
onInputChange={(value) => targetForm.setValue('value', value)}
onSubmitTextField={submitTarget}
/>
<MiniTable.ClearAndAddButtons
addButtonCopy="Add target"
disableClear={!targetValue}
onClear={() => targetForm.reset()}
onSubmit={submitTarget}
/>
</div>
</p>
</>
}
/>

<DynamicTypeAndValueFields
sectionType="target"
control={targetForm.control}
valueType={targetType}
items={toComboboxItems(targetItems[targetType])}
// HACK: reset the whole subform, keeping type (because we just set
// it). most importantly, this resets isSubmitted so the form can go
// back to validating on submit instead of change
onTypeChange={() =>
targetForm.reset({ type: targetForm.getValues('type'), value: '' })
}
onInputChange={(value) => targetForm.setValue('value', value)}
onSubmitTextField={submitTarget}
/>
<MiniTable.ClearAndAddButtons
addButtonCopy="Add target"
disableClear={!targetValue}
onClear={() => targetForm.reset()}
onSubmit={submitTarget}
/>
{!!targets.value.length && <TypeAndValueTable sectionType="target" items={targets} />}

<FormDivider />

<h3 className="mb-4 text-sans-2xl">Filters</h3>
<SideModal.Heading>Filters</SideModal.Heading>
<Message
variant="info"
content={
Expand All @@ -468,12 +474,12 @@ export const CommonFields = ({ control, nameTaken, error }: CommonFieldsProps) =
/>

<div className="flex flex-col gap-3">
{/* We have to blow this up instead of using TextField to get better
{/* We have to blow this up instead of using TextField to get better
text styling on the label */}
<div className="mt-2">
<label id="portRange-label" htmlFor="portRange" className="text-sans-lg">
<FieldLabel id="portRange-label" htmlFor="portRange">
Port filters
</label>
</FieldLabel>
<TextInputHint id="portRange-help-text" className="mb-2">
A single destination port (1234) or a range (1234&ndash;2345)
</TextInputHint>
Expand Down Expand Up @@ -523,45 +529,47 @@ export const CommonFields = ({ control, nameTaken, error }: CommonFieldsProps) =
)}

<fieldset className="space-y-0.5">
<legend className="mb-2 mt-4 text-sans-lg">Protocol filters</legend>
{/* todo: abstract this label and checkbox pattern */}
<FieldLabel id="portRange-label" htmlFor="portRange" className="mb-2">
Protocol filters
</FieldLabel>
<ProtocolField control={control} protocol="TCP" />
<ProtocolField control={control} protocol="UDP" />
<ProtocolField control={control} protocol="ICMP" />
</fieldset>

<div className="flex flex-col gap-3">
<h3 className="mt-4 text-sans-lg">Host filters</h3>
<Message
variant="info"
content={
<>
Host filters match the &ldquo;other end&rdquo; of traffic from the
target&rsquo;s perspective: for an inbound rule, they match the source of
traffic. For an outbound rule, they match the destination.
</>
}
/>
<DynamicTypeAndValueFields
sectionType="host"
control={hostForm.control}
valueType={hostType}
items={toComboboxItems(hostFilterItems[hostType])}
// HACK: reset the whole subform, keeping type (because we just set
// it). most importantly, this resets isSubmitted so the form can go
// back to validating on submit instead of change
onTypeChange={() =>
hostForm.reset({ type: hostForm.getValues('type'), value: '' })
}
onInputChange={(value) => hostForm.setValue('value', value)}
onSubmitTextField={submitHost}
/>
<MiniTable.ClearAndAddButtons
addButtonCopy="Add host filter"
disableClear={!hostValue}
onClear={() => hostForm.reset()}
onSubmit={submitHost}
/>
</div>
<FormDivider />

<SideModal.Heading>Host filters</SideModal.Heading>

<Message
variant="info"
content={
<>
Host filters match the &ldquo;other end&rdquo; of traffic from the
target&rsquo;s perspective: for an inbound rule, they match the source of
traffic. For an outbound rule, they match the destination.
</>
}
/>
<DynamicTypeAndValueFields
sectionType="host"
control={hostForm.control}
valueType={hostType}
items={toComboboxItems(hostFilterItems[hostType])}
// HACK: reset the whole subform, keeping type (because we just set
// it). most importantly, this resets isSubmitted so the form can go
// back to validating on submit instead of change
onTypeChange={() => hostForm.reset({ type: hostForm.getValues('type'), value: '' })}
onInputChange={(value) => hostForm.setValue('value', value)}
onSubmitTextField={submitHost}
/>
<MiniTable.ClearAndAddButtons
addButtonCopy="Add host filter"
disableClear={!hostValue}
onClear={() => hostForm.reset()}
onSubmit={submitHost}
/>
{!!hosts.value.length && <TypeAndValueTable sectionType="host" items={hosts} />}

{error && (
Expand Down
52 changes: 31 additions & 21 deletions app/forms/idp/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { useSiloSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { FormDivider } from '~/ui/lib/Divider'
import { SideModal } from '~/ui/lib/SideModal'
import { readBlobAsBase64 } from '~/util/file'
import { pb } from '~/util/path-builder'

Expand Down Expand Up @@ -109,21 +111,15 @@ export function CreateIdpSideModalForm() {
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} required />
<TextField
name="acsUrl"
label="ACS URL"
description="Service provider endpoint for the IdP to send the SAML response"
required
control={form.control}
/>
{/* TODO: help text */}
<TextField name="idpEntityId" label="Entity ID" required control={form.control} />
<TextField
name="sloUrl"
label="Single Logout (SLO) URL"
description="Service provider endpoint for log out requests"
name="technicalContactEmail"
label="Technical contact email"
required
control={form.control}
/>

<FormDivider />

<SideModal.Heading>Service provider</SideModal.Heading>
{/* TODO: help text */}
<TextField
name="spClientId"
Expand All @@ -132,22 +128,23 @@ export function CreateIdpSideModalForm() {
control={form.control}
/>
<TextField
name="groupAttributeName"
label="Group attribute name"
description="Name of the SAML attribute in the IdP response listing the user’s groups"
name="acsUrl"
label="ACS URL"
description="Service provider endpoint for the IdP to send the SAML response"
required
control={form.control}
/>
{/* TODO: Email field, probably */}
<TextField
name="technicalContactEmail"
label="Technical contact email"
name="sloUrl"
label="Single Logout (SLO) URL"
description="Service provider endpoint for log out requests"
required
control={form.control}
/>
<MetadataSourceField control={form.control} />

{/* We don't bother validating that you have both of these or neither even
though the API requires that because we are going to change the API to
always require both, at which point these become simple `required` fields */}
though the API requires that because we are going to change the API to
always require both, at which point these become simple `required` fields */}
<FileField
id="public-cert-file-input"
name="signingKeypair.publicCert"
Expand All @@ -162,6 +159,19 @@ export function CreateIdpSideModalForm() {
label="Private key"
control={form.control}
/>

<FormDivider />

<SideModal.Heading>Identity Provider</SideModal.Heading>
{/* TODO: help text */}
<TextField name="idpEntityId" label="Entity ID" required control={form.control} />
<TextField
name="groupAttributeName"
label="Group attribute name"
description="Name of the SAML attribute in the IdP response listing the user’s groups"
control={form.control}
/>
<MetadataSourceField control={form.control} />
</SideModalForm>
)
}
40 changes: 24 additions & 16 deletions app/forms/idp/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import { TextField } from '~/components/form/fields/TextField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { getIdpSelector, useIdpSelector } from '~/hooks/use-params'
import { DateTime } from '~/ui/lib/DateTime'
import { FormDivider } from '~/ui/lib/Divider'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
import { ResourceLabel } from '~/ui/lib/SideModal'
import { ResourceLabel, SideModal } from '~/ui/lib/SideModal'
import { Truncate } from '~/ui/lib/Truncate'
import { pb } from '~/util/path-builder'

Expand Down Expand Up @@ -73,17 +74,28 @@ export function EditIdpSideModalForm() {
<NameField name="name" control={form.control} disabled />
<DescriptionField name="description" control={form.control} required disabled />
<TextField
name="acsUrl"
label="ACS URL"
description="Service provider endpoint for the IdP to send the SAML response"
name="technicalContactEmail"
label="Technical contact email"
required
control={form.control}
disabled
/>

<FormDivider />

<SideModal.Heading>Service provider</SideModal.Heading>
{/* TODO: help text */}
<TextField
name="idpEntityId"
label="Entity ID"
name="spClientId"
label="Service provider client ID"
required
control={form.control}
disabled
/>
<TextField
name="acsUrl"
label="ACS URL"
description="Service provider endpoint for the IdP to send the SAML response"
required
control={form.control}
disabled
Expand All @@ -96,10 +108,14 @@ export function EditIdpSideModalForm() {
control={form.control}
disabled
/>

<FormDivider />

<SideModal.Heading>Identity Provider</SideModal.Heading>
{/* TODO: help text */}
<TextField
name="spClientId"
label="Service provider client ID"
name="idpEntityId"
label="Entity ID"
required
control={form.control}
disabled
Expand All @@ -112,14 +128,6 @@ export function EditIdpSideModalForm() {
control={form.control}
disabled
/>
{/* TODO: Email field, probably */}
<TextField
name="technicalContactEmail"
label="Technical contact email"
required
control={form.control}
disabled
/>
</SideModalForm>
)
}
7 changes: 6 additions & 1 deletion app/pages/system/silos/SiloPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ export function SiloPage() {
heading="silos"
icon={<Cloud16Icon />}
summary="Silos provide strict tenancy separation between groups of users. Each silo has its own resource limits and access policies as well as its own subdomain for the web console and API."
links={[docLinks.systemSilo, docLinks.systemIpPools, docLinks.access]}
links={[
docLinks.systemSilo,
docLinks.identityProviders,
docLinks.systemIpPools,
docLinks.access,
]}
/>
</PageHeader>

Expand Down
2 changes: 2 additions & 0 deletions app/ui/lib/SideModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ function SideModalBody({ children }: { children?: ReactNode }) {

SideModal.Body = SideModalBody

SideModal.Heading = classed.div`text-sans-semi-xl`

SideModal.Section = classed.div`p-8 space-y-6 border-secondary`

SideModal.Footer = ({ children, error }: { children: ReactNode; error?: boolean }) => (
Expand Down
Loading

0 comments on commit 7dcd41c

Please sign in to comment.