Skip to content
Open
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 } from '~/ui/lib/PropertiesTable'

type FormMetadataProps = {
resource: { id: string; timeCreated: Date; timeModified: Date }
/** 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} />
{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
31 changes: 31 additions & 0 deletions app/forms/network-interface-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,36 @@ 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'
import { docLinks, links } from '~/util/links'

const transitIpTableColumns = [{ header: 'Transit IPs', cell: (ip: string) => ip }]

// IP addresses aren't editable — a new interface must be created to change them
// — so surface them as read-only metadata. A NIC has a v4 address, a v6
// address, or both, depending on its stack type.
const privateIps = (ipStack: InstanceNetworkInterface['ipStack']) =>
match(ipStack)
.with({ type: 'v4' }, ({ value }) => ({ v4: value.ip, v6: undefined }))
.with({ type: 'v6' }, ({ value }) => ({ v4: undefined, v6: value.ip }))
.with({ type: 'dual_stack' }, ({ value }) => ({ v4: value.v4.ip, v6: value.v6.ip }))
.exhaustive()

type EditNetworkInterfaceFormProps = {
editing: InstanceNetworkInterface
onDismiss: () => void
Expand All @@ -46,6 +60,7 @@ export function EditNetworkInterfaceForm({
editing,
}: EditNetworkInterfaceFormProps) {
const instanceSelector = useInstanceSelector()
const ips = privateIps(editing.ipStack)

const editNetworkInterface = useApiMutation(api.instanceNetworkInterfaceUpdate, {
onSuccess(nic) {
Expand Down Expand Up @@ -113,6 +128,22 @@ export function EditNetworkInterfaceForm({
loading={editNetworkInterface.isPending}
submitError={editNetworkInterface.error}
>
<FormMetadata resource={editing}>
{ips.v4 && (
<PropertiesTable.Row label="Private IPv4">
<CopyableIp ip={ips.v4} isLinked={false} />
</PropertiesTable.Row>
)}
{ips.v6 && (
<PropertiesTable.Row label="Private IPv6">
<CopyableIp ip={ips.v6} 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
8 changes: 8 additions & 0 deletions app/forms/vpc-router-route-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import { useNavigate, type LoaderFunctionArgs } from 'react-router'
import * as R from 'remeda'

import { api, q, queryClient, useApiMutation, usePrefetchedQuery } from '@oxide/api'
import { Badge } from '@oxide/design-system/ui'

import { FormMetadata } from '~/components/form/FormMetadata'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import {
Expand All @@ -22,6 +24,7 @@ import {
import { titleCrumb } from '~/hooks/use-crumbs'
import { getVpcRouterRouteSelector, useVpcRouterRouteSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
import { ALL_ISH } from '~/util/consts'
import { pb } from '~/util/path-builder'

Expand Down Expand Up @@ -99,6 +102,11 @@ export default function EditRouterRouteSideModalForm() {
submitError={updateRouterRoute.error}
submitDisabled={disabled ? routeFormMessage.vpcSubnetNotModifiable : undefined}
>
<FormMetadata resource={route}>
<PropertiesTable.Row label="Kind">
<Badge color="neutral">{route.kind.replace('_', ' ')}</Badge>
</PropertiesTable.Row>
</FormMetadata>
<RouteFormFields form={form} disabled={disabled} />
<RouteFormDocs />
</SideModalForm>
Expand Down
Loading
Loading