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
14 changes: 8 additions & 6 deletions app/pages/project/disks/DisksPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { DiskStatusBadge } from '~/components/StatusBadge'
import { getProjectSelector, useProjectSelector, useToast } from '~/hooks'
import { confirmDelete } from '~/stores/confirm-delete'
import { DateCell } from '~/table/cells/DateCell'
import { defaultCell } from '~/table/cells/DefaultCell'
import { InstanceLinkCell } from '~/table/cells/InstanceLinkCell'
import { SizeCell } from '~/table/cells/SizeCell'
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
Expand Down Expand Up @@ -74,13 +73,16 @@ DisksPage.loader = async ({ params }: LoaderFunctionArgs) => {
const colHelper = createColumnHelper<Disk>()

const staticCols = [
colHelper.accessor('name', { cell: defaultCell }),
colHelper.accessor('name', {}),
// sneaky: rather than looking at particular states, just look at
// whether it has an instance field
colHelper.accessor((disk) => ('instance' in disk.state ? disk.state.instance : null), {
header: 'Attached to',
cell: (props) => <InstanceLinkCell value={props.getValue()} />,
}),
colHelper.accessor(
(disk) => ('instance' in disk.state ? disk.state.instance : undefined),
{
header: 'Attached to',
cell: (props) => <InstanceLinkCell instanceId={props.getValue()} />,
}
),
colHelper.accessor('size', { cell: (props) => <SizeCell value={props.getValue()} /> }),
colHelper.accessor('state.state', {
header: 'Status',
Expand Down
181 changes: 99 additions & 82 deletions app/pages/project/floating-ips/FloatingIpsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
*
* Copyright Oxide Computer Company
*/
import { useState } from 'react'
import { createColumnHelper } from '@tanstack/react-table'
import { useCallback, useState } from 'react'
import { useForm } from 'react-hook-form'
import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router-dom'

Expand All @@ -26,8 +27,9 @@ import { confirmAction } from '~/stores/confirm-action'
import { confirmDelete } from '~/stores/confirm-delete'
import { addToast } from '~/stores/toast'
import { InstanceLinkCell } from '~/table/cells/InstanceLinkCell'
import type { MenuAction } from '~/table/columns/action-col'
import { useQueryTable } from '~/table/QueryTable'
import { makeLinkCell } from '~/table/cells/LinkCell'
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
import { useQueryTable2 } from '~/table/QueryTable2'
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { Listbox } from '~/ui/lib/Listbox'
import { Message } from '~/ui/lib/Message'
Expand Down Expand Up @@ -68,8 +70,6 @@ export function FloatingIpsPage() {
query: { project },
})
const navigate = useNavigate()
const getInstanceName = (instanceId: string) =>
instances.items.find((i) => i.id === instanceId)?.name

const floatingIpDetach = useApiMutation('floatingIpDetach', {
onSuccess() {
Expand All @@ -88,76 +88,102 @@ export function FloatingIpsPage() {
},
})

const makeActions = (floatingIp: FloatingIp): MenuAction[] => {
const isAttachedToAnInstance = !!floatingIp.instanceId
const attachOrDetachAction = isAttachedToAnInstance
? {
label: 'Detach',
onActivate: () =>
confirmAction({
actionType: 'danger',
doAction: () =>
floatingIpDetach.mutateAsync({
path: { floatingIp: floatingIp.name },
query: { project },
}),
modalTitle: 'Detach Floating IP',
modalContent: (
<p>
Are you sure you want to detach floating IP <HL>{floatingIp.name}</HL>{' '}
from instance{' '}
<HL>
{
// instanceId is guaranteed to be non-null here
getInstanceName(floatingIp.instanceId!)
}
</HL>
? The instance will no longer be reachable at <HL>{floatingIp.ip}</HL>.
</p>
),
errorTitle: 'Error detaching floating IP',
}),
}
: {
label: 'Attach',
onActivate() {
setFloatingIpToModify(floatingIp)
},
}
return [
{
label: 'Edit',
onActivate: () => {
apiQueryClient.setQueryData(
'floatingIpView',
{
path: { floatingIp: floatingIp.name },
query: { project },
const colHelper = createColumnHelper<FloatingIp>()

const staticCols = [
colHelper.accessor('name', {
cell: makeLinkCell((name) => pb.floatingIp({ floatingIp: name, project })),
}),
colHelper.accessor('description', {}),
colHelper.accessor('ip', {}),
colHelper.accessor('instanceId', {
cell: (props) => <InstanceLinkCell instanceId={props.getValue()} />,
header: 'Attached to instance',
}),
]

const makeActions = useCallback(
(floatingIp: FloatingIp): MenuAction[] => {
const instanceName = floatingIp.instanceId
? instances.items.find((i) => i.id === floatingIp.instanceId)?.name
: undefined
// handling the rather unlikely case where the instance is not in the 1000 we fetched
const fromInstance = instanceName ? (
<>
{' ' /* important */}
from instance <HL>{instanceName}</HL>
</>
) : null

const isAttachedToAnInstance = !!floatingIp.instanceId
const attachOrDetachAction = isAttachedToAnInstance
? {
label: 'Detach',
onActivate: () =>
confirmAction({
actionType: 'danger',
doAction: () =>
floatingIpDetach.mutateAsync({
path: { floatingIp: floatingIp.name },
query: { project },
}),
modalTitle: 'Detach Floating IP',
// instanceName! non-null because we only see this if there is an instance
modalContent: (
<p>
Are you sure you want to detach floating IP <HL>{floatingIp.name}</HL>
{fromInstance}? The instance will no longer be reachable at{' '}
<HL>{floatingIp.ip}</HL>.
</p>
),
errorTitle: 'Error detaching floating IP',
}),
}
: {
label: 'Attach',
onActivate() {
setFloatingIpToModify(floatingIp)
},
floatingIp
)
navigate(pb.floatingIpEdit({ project, floatingIp: floatingIp.name }))
}
return [
{
label: 'Edit',
onActivate: () => {
apiQueryClient.setQueryData(
'floatingIpView',
{
path: { floatingIp: floatingIp.name },
query: { project },
},
floatingIp
)
navigate(pb.floatingIpEdit({ project, floatingIp: floatingIp.name }))
},
},
attachOrDetachAction,
{
label: 'Delete',
disabled: isAttachedToAnInstance
? 'This floating IP must be detached from the instance before it can be deleted'
: false,
onActivate: confirmDelete({
doDelete: () =>
deleteFloatingIp.mutateAsync({
path: { floatingIp: floatingIp.name },
query: { project },
}),
label: floatingIp.name,
}),
},
},
attachOrDetachAction,
{
label: 'Delete',
disabled: isAttachedToAnInstance
? 'This floating IP must be detached from the instance before it can be deleted'
: false,
onActivate: confirmDelete({
doDelete: () =>
deleteFloatingIp.mutateAsync({
path: { floatingIp: floatingIp.name },
query: { project },
}),
label: floatingIp.name,
}),
},
]
}
]
},
[deleteFloatingIp, floatingIpDetach, navigate, project, instances]
)

const { Table } = useQueryTable2('floatingIpList', { query: { project } })

const columns = useColsWithActions(staticCols, makeActions)

const { Table, Column } = useQueryTable('floatingIpList', { query: { project } })
return (
<>
<PageHeader>
Expand All @@ -173,16 +199,7 @@ export function FloatingIpsPage() {
New Floating IP
</TableControlsLink>
</TableControls>
<Table emptyState={<EmptyState />} makeActions={makeActions}>
<Column accessor="name" />
<Column accessor="description" />
<Column accessor="ip" />
<Column
accessor="instanceId"
header="Attached to instance"
cell={InstanceLinkCell}
/>
</Table>
<Table emptyState={<EmptyState />} columns={columns} />
<Outlet />
{floatingIpToModify && (
<AttachFloatingIpModal
Expand Down
3 changes: 1 addition & 2 deletions app/pages/system/networking/IpPoolsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { IpUtilCell } from '~/components/IpPoolUtilization'
import { useQuickActions } from '~/hooks'
import { confirmDelete } from '~/stores/confirm-delete'
import { DateCell } from '~/table/cells/DateCell'
import { defaultCell } from '~/table/cells/DefaultCell'
import { SkeletonCell } from '~/table/cells/EmptyCell'
import { makeLinkCell } from '~/table/cells/LinkCell'
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
Expand Down Expand Up @@ -53,7 +52,7 @@ const colHelper = createColumnHelper<IpPool>()

const staticColumns = [
colHelper.accessor('name', { cell: makeLinkCell((pool) => pb.ipPool({ pool })) }),
colHelper.accessor('description', { cell: defaultCell }),
colHelper.accessor('description', {}),
colHelper.accessor('name', {
id: 'Utilization',
header: 'Utilization',
Expand Down
Loading