Skip to content

Commit 5981812

Browse files
Instance card blocks (#2741)
* `CardBlock` for instance tab content * Test fixes * Remove somewhat obvious descriptions * Giving inline tables a smaller border radii * Improve empty states * Swap buttons (primary always trailing) * Simpler tw types * Revert "Simpler tw types" This reverts commit 82cedcd. * tweak component API * noBorder -> border=false * delete empty className --------- Co-authored-by: David Crespo <[email protected]>
1 parent 1fe4a7e commit 5981812

15 files changed

+333
-240
lines changed

app/components/CopyCode.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ export function EquivalentCliCommand({ project, instance }: EquivProps) {
9898

9999
return (
100100
<>
101-
<Button variant="ghost" size="sm" className="ml-2" onClick={() => setIsOpen(true)}>
102-
Equivalent CLI Command
101+
<Button variant="secondary" size="sm" onClick={() => setIsOpen(true)}>
102+
CLI Command
103103
</Button>
104104
<CopyCodeModal
105105
code={cmdParts.join(' ')}

app/components/TimeSeriesChart.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ const SkeletonMetric = ({
144144
<div
145145
className={cn(
146146
shimmer && 'motion-safe:animate-pulse',
147-
'absolute inset-0 bottom-11',
147+
'absolute inset-0 bottom-7',
148148
className
149149
)}
150150
>
@@ -236,7 +236,7 @@ export function TimeSeriesChart({
236236
width={width}
237237
height={height}
238238
data={data}
239-
margin={{ top: 0, right: hasBorder ? 16 : 0, bottom: 16, left: 0 }}
239+
margin={{ top: 0, right: hasBorder ? 16 : 0, bottom: 0, left: 0 }}
240240
>
241241
<CartesianGrid stroke={GRID_GRAY} vertical={false} />
242242
<XAxis

app/components/oxql-metrics/OxqlMetric.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { CopyCodeModal } from '~/components/CopyCode'
2020
import { MoreActionsMenu } from '~/components/MoreActionsMenu'
2121
import { getInstanceSelector } from '~/hooks/use-params'
2222
import { useMetricsContext } from '~/pages/project/instances/common'
23-
import { LearnMore } from '~/ui/lib/SettingsGroup'
23+
import { LearnMore } from '~/ui/lib/CardBlock'
2424
import { classed } from '~/util/classed'
2525
import { links } from '~/util/links'
2626

@@ -77,7 +77,7 @@ export function OxqlMetric({ title, description, unit, ...queryObj }: OxqlMetric
7777

7878
return (
7979
<div className="flex w-full grow flex-col rounded-lg border border-default">
80-
<div className="flex items-center justify-between border-b px-6 py-5 border-secondary">
80+
<div className="flex items-center justify-between border-b px-5 pb-4 pt-5 border-secondary">
8181
<div>
8282
<h2 className="flex items-baseline gap-1.5">
8383
<div className="text-sans-semi-lg">{title}</div>
@@ -113,7 +113,7 @@ export function OxqlMetric({ title, description, unit, ...queryObj }: OxqlMetric
113113
<HighlightedOxqlQuery {...queryObj} />
114114
</CopyCodeModal>
115115
</div>
116-
<div className="px-6 py-5 pt-8">
116+
<div className="px-5 pb-5 pt-8">
117117
<TimeSeriesChart
118118
title={title}
119119
startTime={startTime}

app/pages/project/instances/ConnectTab.tsx

+48-48
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import { apiQueryClient, usePrefetchedApiQuery } from '~/api'
1212
import { EquivalentCliCommand } from '~/components/CopyCode'
1313
import { getInstanceSelector, useInstanceSelector } from '~/hooks/use-params'
1414
import { buttonStyle } from '~/ui/lib/Button'
15+
import { CardBlock, LearnMore } from '~/ui/lib/CardBlock'
1516
import { InlineCode } from '~/ui/lib/InlineCode'
16-
import { LearnMore, SettingsGroup } from '~/ui/lib/SettingsGroup'
1717
import { links } from '~/util/links'
1818
import { pb } from '~/util/path-builder'
1919

@@ -40,53 +40,53 @@ export default function ConnectTab() {
4040
const externalIp = floatingIp?.ip || ephemeralIp?.ip
4141

4242
return (
43-
<div className="space-y-6">
44-
<SettingsGroup.Container>
45-
<SettingsGroup.Body>
46-
<SettingsGroup.Title>Serial console</SettingsGroup.Title>
47-
Connect to your instance&rsquo;s serial console
48-
</SettingsGroup.Body>
49-
<SettingsGroup.Footer>
50-
<div>
51-
<LearnMore text="Serial Console" href={links.serialConsoleDocs} />
52-
</div>
53-
<div className="flex gap-3">
54-
<EquivalentCliCommand project={project} instance={instance} />
55-
<Link
56-
to={pb.serialConsole({ project, instance })}
57-
className={buttonStyle({ size: 'sm' })}
58-
>
59-
Connect
60-
</Link>
61-
</div>
62-
</SettingsGroup.Footer>
63-
</SettingsGroup.Container>
64-
<SettingsGroup.Container>
65-
<SettingsGroup.Body>
66-
<SettingsGroup.Title>SSH</SettingsGroup.Title>
67-
<p>
68-
If your instance allows SSH access, connect with{' '}
69-
<InlineCode>ssh [username]@{externalIp || '[external IP]'}</InlineCode>.
70-
</p>
71-
{!externalIp && (
72-
<p className="mt-2">
73-
This instance has no external IP address. You can add one on the{' '}
74-
<Link
75-
className="link-with-underline"
76-
to={pb.instanceNetworking({ project, instance })}
77-
>
78-
networking
79-
</Link>{' '}
80-
tab.
81-
</p>
82-
)}
83-
</SettingsGroup.Body>
84-
<SettingsGroup.Footer>
85-
<div>
86-
<LearnMore text="SSH" href={links.sshDocs} />
87-
</div>
88-
</SettingsGroup.Footer>
89-
</SettingsGroup.Container>
43+
<div className="space-y-5">
44+
<CardBlock width="medium">
45+
<CardBlock.Header
46+
title="Serial console"
47+
description="Connect to your instances serial console"
48+
>
49+
<EquivalentCliCommand project={project} instance={instance} />
50+
<Link
51+
to={pb.serialConsole({ project, instance })}
52+
className={buttonStyle({ size: 'sm' })}
53+
>
54+
Connect
55+
</Link>
56+
</CardBlock.Header>
57+
<CardBlock.Footer>
58+
<LearnMore href={links.serialConsoleDocs} text="Serial Console" />
59+
</CardBlock.Footer>
60+
</CardBlock>
61+
62+
<CardBlock width="medium">
63+
<CardBlock.Header
64+
title="SSH"
65+
description={
66+
<>
67+
<div>
68+
If your instance allows SSH access, connect with{' '}
69+
<InlineCode>ssh [username]@{externalIp || '[external IP]'}</InlineCode>.
70+
</div>
71+
{!externalIp && (
72+
<div className="mt-2">
73+
This instance has no external IP address. You can add one on the{' '}
74+
<Link
75+
className="link-with-underline"
76+
to={pb.instanceNetworking({ project, instance })}
77+
>
78+
networking
79+
</Link>{' '}
80+
tab.
81+
</div>
82+
)}
83+
</>
84+
}
85+
/>
86+
<CardBlock.Footer>
87+
<LearnMore href={links.sshDocs} text="SSH" />
88+
</CardBlock.Footer>
89+
</CardBlock>
9090
</div>
9191
)
9292
}

app/pages/project/instances/DiskMetricsTab.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default function DiskMetricsTab() {
5050
})
5151
if (disks.items.length === 0) {
5252
return (
53-
<TableEmptyBox>
53+
<TableEmptyBox border={false}>
5454
<EmptyMessage
5555
icon={<Storage24Icon />}
5656
title="No disk metrics available"

app/pages/project/instances/NetworkingTab.tsx

+87-66
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
4444
import { Columns } from '~/table/columns/common'
4545
import { Table } from '~/table/Table'
4646
import { Badge } from '~/ui/lib/Badge'
47+
import { Button } from '~/ui/lib/Button'
48+
import { CardBlock } from '~/ui/lib/CardBlock'
4749
import { CopyableIp } from '~/ui/lib/CopyableIp'
48-
import { CreateButton } from '~/ui/lib/CreateButton'
4950
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
50-
import { TableControls, TableEmptyBox, TableTitle } from '~/ui/lib/Table'
51+
import { TableEmptyBox } from '~/ui/lib/Table'
5152
import { TipIcon } from '~/ui/lib/TipIcon'
5253
import { ALL_ISH } from '~/util/consts'
5354
import { pb } from '~/util/path-builder'
@@ -390,28 +391,49 @@ export default function NetworkingTab() {
390391
: null
391392

392393
return (
393-
<>
394-
<TableControls>
395-
<TableTitle id="attached-ips-label">External IPs</TableTitle>
396-
<div className="flex gap-3">
397-
{/*
398-
We normally wouldn't hide this button and would just have a disabled state on it,
399-
but it is very rare for this button to be necessary, and it would be disabled
400-
most of the time, for most users. To reduce clutter on the screen, we're hiding it.
401-
*/}
402-
{enableEphemeralAttachButton && (
403-
<CreateButton onClick={() => setAttachEphemeralModalOpen(true)}>
404-
Attach ephemeral IP
405-
</CreateButton>
394+
<div className="space-y-5">
395+
<CardBlock>
396+
<CardBlock.Header title="External IPs" titleId="attached-ips-label">
397+
<div className="flex gap-3">
398+
{/*
399+
We normally wouldn't hide this button and would just have a disabled state on it,
400+
but it is very rare for this button to be necessary, and it would be disabled
401+
most of the time, for most users. To reduce clutter on the screen, we're hiding it.
402+
*/}
403+
{enableEphemeralAttachButton && (
404+
<Button size="sm" onClick={() => setAttachEphemeralModalOpen(true)}>
405+
Attach ephemeral IP
406+
</Button>
407+
)}
408+
<Button
409+
size="sm"
410+
onClick={() => setAttachFloatingModalOpen(true)}
411+
disabled={!!floatingDisabledReason}
412+
disabledReason={floatingDisabledReason}
413+
>
414+
Attach floating IP
415+
</Button>
416+
</div>
417+
</CardBlock.Header>
418+
419+
<CardBlock.Body>
420+
{eips.items.length > 0 ? (
421+
<Table
422+
aria-labelledby="attached-ips-label"
423+
table={ipTableInstance}
424+
className="table-inline"
425+
/>
426+
) : (
427+
<TableEmptyBox border={false}>
428+
<EmptyMessage
429+
icon={<IpGlobal24Icon />}
430+
title="No external IPs"
431+
body="Attach an external IP to see it here"
432+
/>
433+
</TableEmptyBox>
406434
)}
407-
<CreateButton
408-
onClick={() => setAttachFloatingModalOpen(true)}
409-
disabled={!!floatingDisabledReason}
410-
disabledReason={floatingDisabledReason}
411-
>
412-
Attach floating IP
413-
</CreateButton>
414-
</div>
435+
</CardBlock.Body>
436+
415437
{attachEphemeralModalOpen && (
416438
<AttachEphemeralIpModal onDismiss={() => setAttachEphemeralModalOpen(false)} />
417439
)}
@@ -422,56 +444,55 @@ export default function NetworkingTab() {
422444
onDismiss={() => setAttachFloatingModalOpen(false)}
423445
/>
424446
)}
425-
</TableControls>
426-
{eips.items.length > 0 ? (
427-
<Table aria-labelledby="attached-ips-label" table={ipTableInstance} />
428-
) : (
429-
<TableEmptyBox>
430-
<EmptyMessage
431-
icon={<IpGlobal24Icon />}
432-
title="No external IPs"
433-
body="Attach an external IP to see it here"
434-
/>
435-
</TableEmptyBox>
436-
)}
437-
438-
<TableControls className="mt-8">
439-
<TableTitle id="nics-label">Network interfaces</TableTitle>
440-
<CreateButton
441-
onClick={() => setCreateModalOpen(true)}
442-
disabled={!instanceCan.updateNic(instance)}
443-
disabledReason={
444-
<>
445-
A network interface cannot be created or edited unless the instance is{' '}
446-
{updateNicStates}.
447-
</>
448-
}
449-
>
450-
Add network interface
451-
</CreateButton>
447+
</CardBlock>
448+
449+
<CardBlock>
450+
<CardBlock.Header title="Network interfaces" titleId="nics-label">
451+
<Button
452+
size="sm"
453+
onClick={() => setCreateModalOpen(true)}
454+
disabled={!instanceCan.updateNic(instance)}
455+
disabledReason={
456+
<>
457+
A network interface cannot be created or edited unless the instance is{' '}
458+
{updateNicStates}.
459+
</>
460+
}
461+
>
462+
Add network interface
463+
</Button>
464+
</CardBlock.Header>
465+
466+
<CardBlock.Body>
467+
{nics.length > 0 ? (
468+
<Table
469+
aria-labelledby="nics-label"
470+
table={tableInstance}
471+
className="table-inline"
472+
/>
473+
) : (
474+
<TableEmptyBox border={false}>
475+
<EmptyMessage
476+
icon={<Networking24Icon />}
477+
title="No network interfaces"
478+
body="Create a network interface to see it here"
479+
/>
480+
</TableEmptyBox>
481+
)}
482+
</CardBlock.Body>
483+
452484
{createModalOpen && (
453485
<CreateNetworkInterfaceForm
454486
onDismiss={() => setCreateModalOpen(false)}
455487
onSubmit={(body) => createNic.mutate({ query: instanceSelector, body })}
456488
submitError={createNic.error}
457489
/>
458490
)}
459-
</TableControls>
460-
{nics.length > 0 ? (
461-
<Table aria-labelledby="nics-label" table={tableInstance} />
462-
) : (
463-
<TableEmptyBox>
464-
<EmptyMessage
465-
icon={<Networking24Icon />}
466-
title="No network interfaces"
467-
body="Create a network interface to see it here"
468-
/>
469-
</TableEmptyBox>
470-
)}
471491

472-
{editing && (
473-
<EditNetworkInterfaceForm editing={editing} onDismiss={() => setEditing(null)} />
474-
)}
475-
</>
492+
{editing && (
493+
<EditNetworkInterfaceForm editing={editing} onDismiss={() => setEditing(null)} />
494+
)}
495+
</CardBlock>
496+
</div>
476497
)
477498
}

0 commit comments

Comments
 (0)