Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions app/components/form/FormMetadata.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 type { ReactNode } from 'react'

import { FormDivider } from '~/ui/lib/Divider'
import { PropertiesTable, type ResourceMetadata } from '~/ui/lib/PropertiesTable'

type FormMetadataProps = {
resource: ResourceMetadata
/** Resource-specific `PropertiesTable.*` rows, rendered after the standard three */
children?: ReactNode
}

/**
* Read-only resource metadata at the top of an edit or view side modal, followed
* by the divider separating it from the fields. Bundled together so every sidebar
* gets the same treatment — the divider had already been missed in two forms.
*/
export const FormMetadata = ({ resource, children }: FormMetadataProps) => (
<>
<PropertiesTable>
<PropertiesTable.ResourceRows resource={resource} />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cute

{children}
</PropertiesTable>
<FormDivider />
</>
)
8 changes: 3 additions & 5 deletions app/forms/external-subnet-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
Expand Down Expand Up @@ -100,18 +101,15 @@ export default function EditExternalSubnetSideModalForm() {
loading={editExternalSubnet.isPending}
submitError={editExternalSubnet.error}
>
<PropertiesTable>
<PropertiesTable.IdRow id={subnet.id} />
<PropertiesTable.DateRow label="Created" date={subnet.timeCreated} />
<PropertiesTable.DateRow label="Updated" date={subnet.timeModified} />
<FormMetadata resource={subnet}>
<PropertiesTable.Row label="Subnet">{subnet.subnet}</PropertiesTable.Row>
<PropertiesTable.Row label="Subnet Pool">
<SubnetPoolCell subnetPoolId={subnet.subnetPoolId} />
</PropertiesTable.Row>
<PropertiesTable.Row label="Instance">
<InstanceLink instanceId={subnet.instanceId} tab="networking" />
</PropertiesTable.Row>
</PropertiesTable>
</FormMetadata>
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
<SideModalFormDocs docs={[docLinks.externalSubnets]} />
Expand Down
2 changes: 2 additions & 0 deletions app/forms/firewall-rules-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '@oxide/api'

import { trigger404 } from '~/components/ErrorBoundary'
import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
Expand Down Expand Up @@ -134,6 +135,7 @@ export default function EditFirewallRuleForm() {
loading={updateRules.isPending}
submitError={updateRules.error}
>
<FormMetadata resource={originalRule} />
<CommonFields
control={form.control}
// error if name is being changed to something that conflicts with some other rule
Expand Down
8 changes: 3 additions & 5 deletions app/forms/floating-ip-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
Expand Down Expand Up @@ -99,10 +100,7 @@ export default function EditFloatingIpSideModalForm() {
loading={editFloatingIp.isPending}
submitError={editFloatingIp.error}
>
<PropertiesTable>
<PropertiesTable.IdRow id={floatingIp.id} />
<PropertiesTable.DateRow label="Created" date={floatingIp.timeCreated} />
<PropertiesTable.DateRow label="Updated" date={floatingIp.timeModified} />
<FormMetadata resource={floatingIp}>
<PropertiesTable.Row label="IP Address">
<CopyableIp ip={floatingIp.ip} isLinked={false} />
</PropertiesTable.Row>
Expand All @@ -112,7 +110,7 @@ export default function EditFloatingIpSideModalForm() {
<PropertiesTable.Row label="Instance">
<InstanceLink instanceId={floatingIp.instanceId} tab="networking" />
</PropertiesTable.Row>
</PropertiesTable>
</FormMetadata>
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
<SideModalFormDocs docs={[docLinks.floatingIps]} />
Expand Down
8 changes: 2 additions & 6 deletions app/forms/idp/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import { Access16Icon } from '@oxide/design-system/icons/react'
import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { TextField } from '~/components/form/fields/TextField'
import { FormMetadata } from '~/components/form/FormMetadata'
import { ReadOnlySideModalForm } from '~/components/form/ReadOnlySideModalForm'
import { titleCrumb } from '~/hooks/use-crumbs'
import { getIdpSelector, useIdpSelector } from '~/hooks/use-params'
import { FormDivider } from '~/ui/lib/Divider'
import { SideModalFormDocs } from '~/ui/lib/ModalLinks'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
import { ResourceLabel, SideModal } from '~/ui/lib/SideModal'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'
Expand Down Expand Up @@ -55,11 +55,7 @@ export default function EditIdpSideModalForm() {
</ResourceLabel>
}
>
<PropertiesTable>
<PropertiesTable.IdRow id={idp.id} />
<PropertiesTable.DateRow date={idp.timeCreated} label="Created" />
<PropertiesTable.DateRow date={idp.timeModified} label="Updated" />
</PropertiesTable>
<FormMetadata resource={idp} />

<NameField name="name" control={form.control} disabled />
<DescriptionField name="description" control={form.control} required disabled />
Expand Down
2 changes: 2 additions & 0 deletions app/forms/image-from-snapshot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
import { getProjectSnapshotSelector, useProjectSnapshotSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { FormDivider } from '~/ui/lib/Divider'
import { SideModalFormDocs } from '~/ui/lib/ModalLinks'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
import { docLinks } from '~/util/links'
Expand Down Expand Up @@ -97,6 +98,7 @@ export default function CreateImageFromSnapshotSideModalForm() {
{formatBytes(data.size).label}
</PropertiesTable.Row>
</PropertiesTable>
<FormDivider />

<NameField name="name" control={form.control} required />
<DescriptionField name="description" control={form.control} required />
Expand Down
2 changes: 2 additions & 0 deletions app/forms/ip-pool-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { api, q, queryClient, useApiMutation, usePrefetchedQuery } from '@oxide/

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { makeCrumb } from '~/hooks/use-crumbs'
Expand Down Expand Up @@ -73,6 +74,7 @@ export default function EditIpPoolSideModalForm() {
loading={editPool.isPending}
submitError={editPool.error}
>
<FormMetadata resource={pool} />
<IpPoolVisibilityMessage />
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
Expand Down
40 changes: 33 additions & 7 deletions app/forms/network-interface-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ import {
import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { TextFieldInner } from '~/components/form/fields/TextField'
import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { useInstanceSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { SubnetNameFromId } from '~/table/cells/SubnetNameCell'
import { CopyableIp } from '~/ui/lib/CopyableIp'
import { FormDivider } from '~/ui/lib/Divider'
import { FieldLabel } from '~/ui/lib/FieldLabel'
import { Message } from '~/ui/lib/Message'
import { ClearAndAddButtons, MiniTable } from '~/ui/lib/MiniTable'
import { SideModalFormDocs } from '~/ui/lib/ModalLinks'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
import { HintLink, TextInputHint } from '~/ui/lib/TextInput'
import { KEYS } from '~/ui/util/keys'
import { parseIpNet, validateIpNet } from '~/util/ip'
Expand Down Expand Up @@ -56,23 +60,29 @@ export function EditNetworkInterfaceForm({
},
})

// Extract transitIps from ipStack for the form
const extractedTransitIps =
editing.ipStack.type === 'dual_stack'
? [...editing.ipStack.value.v4.transitIps, ...editing.ipStack.value.v6.transitIps]
: editing.ipStack.value.transitIps
// A NIC has an IPv4 stack, an IPv6 stack, or both
const { ipStack } = editing
const v4 = match(ipStack)
.with({ type: 'v4' }, ({ value }) => value)
.with({ type: 'dual_stack' }, ({ value }) => value.v4)
.with({ type: 'v6' }, () => null)
.exhaustive()
const v6 = match(ipStack)
.with({ type: 'v6' }, ({ value }) => value)
.with({ type: 'dual_stack' }, ({ value }) => value.v6)
.with({ type: 'v4' }, () => null)
.exhaustive()

const defaultValues = {
name: editing.name,
description: editing.description,
transitIps: extractedTransitIps,
transitIps: [...(v4?.transitIps ?? []), ...(v6?.transitIps ?? [])],
} satisfies InstanceNetworkInterfaceUpdate

const form = useForm({ defaultValues })
const transitIps = form.watch('transitIps') || []

// Determine what IP versions this NIC supports
const { ipStack } = editing
const { supportedVersions, exampleIPs } = match(ipStack.type)
.with('v4', () => ({ supportedVersions: 'IPv4', exampleIPs: '192.168.0.0/16' }))
.with('v6', () => ({ supportedVersions: 'IPv6', exampleIPs: 'fd00::/64' }))
Expand Down Expand Up @@ -113,6 +123,22 @@ export function EditNetworkInterfaceForm({
loading={editNetworkInterface.isPending}
submitError={editNetworkInterface.error}
>
<FormMetadata resource={editing}>
{v4 && (
<PropertiesTable.Row label="Private IPv4">
<CopyableIp ip={v4.ip} isLinked={false} />
</PropertiesTable.Row>
)}
{v6 && (
<PropertiesTable.Row label="Private IPv6">
<CopyableIp ip={v6.ip} isLinked={false} />
</PropertiesTable.Row>
)}
<PropertiesTable.Row label="MAC address">{editing.mac}</PropertiesTable.Row>
<PropertiesTable.Row label="Subnet">
<SubnetNameFromId subnetId={editing.subnetId} />
</PropertiesTable.Row>
</FormMetadata>
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
<FormDivider />
Expand Down
2 changes: 2 additions & 0 deletions app/forms/project-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { api, q, queryClient, useApiMutation, usePrefetchedQuery } from '@oxide/

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
Expand Down Expand Up @@ -68,6 +69,7 @@ export default function EditProjectSideModalForm() {
loading={editProject.isPending}
submitError={editProject.error}
>
<FormMetadata resource={project} />
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
<SideModalFormDocs docs={[docLinks.projects]} />
Expand Down
8 changes: 2 additions & 6 deletions app/forms/ssh-key-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import { Key16Icon } from '@oxide/design-system/icons/react'
import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { TextField } from '~/components/form/fields/TextField'
import { FormMetadata } from '~/components/form/FormMetadata'
import { ReadOnlySideModalForm } from '~/components/form/ReadOnlySideModalForm'
import { titleCrumb } from '~/hooks/use-crumbs'
import { getSshKeySelector, useSshKeySelector } from '~/hooks/use-params'
import { CopyToClipboard } from '~/ui/lib/CopyToClipboard'
import { SideModalFormDocs } from '~/ui/lib/ModalLinks'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
import { ResourceLabel } from '~/ui/lib/SideModal'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'
Expand Down Expand Up @@ -55,11 +55,7 @@ export default function EditSSHKeySideModalForm() {
</ResourceLabel>
}
>
<PropertiesTable>
<PropertiesTable.IdRow id={data.id} />
<PropertiesTable.DateRow date={data.timeCreated} label="Created" />
<PropertiesTable.DateRow date={data.timeModified} label="Updated" />
</PropertiesTable>
<FormMetadata resource={data} />
<NameField name="name" control={form.control} disabled />
<DescriptionField name="description" control={form.control} disabled />
<div className="relative">
Expand Down
6 changes: 6 additions & 0 deletions app/forms/subnet-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ import {
customRouterFormToData,
useCustomRouterItems,
} from '~/components/form/fields/useItemsList'
import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
import { getVpcSubnetSelector, useVpcSubnetSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { FormDivider } from '~/ui/lib/Divider'
import { SideModalFormDocs } from '~/ui/lib/ModalLinks'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'
import type * as PP from '~/util/path-params'
Expand Down Expand Up @@ -95,6 +97,10 @@ export default function EditSubnetForm() {
loading={updateSubnet.isPending}
submitError={updateSubnet.error}
>
<FormMetadata resource={subnet}>
<PropertiesTable.Row label="IPv4 block">{subnet.ipv4Block}</PropertiesTable.Row>
<PropertiesTable.Row label="IPv6 block">{subnet.ipv6Block}</PropertiesTable.Row>
</FormMetadata>
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
<FormDivider />
Expand Down
2 changes: 2 additions & 0 deletions app/forms/subnet-pool-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { api, q, queryClient, useApiMutation, usePrefetchedQuery } from '@oxide/

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { makeCrumb } from '~/hooks/use-crumbs'
Expand Down Expand Up @@ -72,6 +73,7 @@ export default function EditSubnetPoolSideModalForm() {
loading={editPool.isPending}
submitError={editPool.error}
>
<FormMetadata resource={pool} />
<SubnetPoolVisibilityMessage />
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
Expand Down
2 changes: 2 additions & 0 deletions app/forms/vpc-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { api, q, queryClient, useApiMutation, usePrefetchedQuery } from '@oxide/

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
Expand Down Expand Up @@ -75,6 +76,7 @@ export default function EditVpcSideModalForm() {
loading={editVpc.isPending}
submitError={editVpc.error}
>
<FormMetadata resource={vpc} />
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
<NameField name="dnsName" label="DNS name" required={false} control={form.control} />
Expand Down
8 changes: 8 additions & 0 deletions app/forms/vpc-router-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ import {
usePrefetchedQuery,
type VpcRouterUpdate,
} from '@oxide/api'
import { Badge } from '@oxide/design-system/ui'

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { SideModalFormDocs } from '~/ui/lib/ModalLinks'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'
import type * as PP from '~/util/path-params'
Expand Down Expand Up @@ -78,6 +81,11 @@ export default function EditRouterSideModalForm() {
loading={editRouter.isPending}
submitError={editRouter.error}
>
<FormMetadata resource={routerData}>
<PropertiesTable.Row label="Kind">
<Badge color="neutral">{routerData.kind}</Badge>
</PropertiesTable.Row>
</FormMetadata>
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
<SideModalFormDocs docs={[docLinks.routers]} />
Expand Down
Loading
Loading