Skip to content

Commit c1314a1

Browse files
committed
instance affinity tab
2 parents 7bb2f16 + 653ba4c commit c1314a1

12 files changed

Lines changed: 323 additions & 2 deletions

File tree

OMICRON_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
467e0aff77e73f093b9da8317e1abcaff7ee5dfc
1+
5fd1c3588a45adfba0d8c59ff847487b28e6968b

app/api/__generated__/Api.ts

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/api/__generated__/OMICRON_VERSION

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/api/__generated__/msw-handlers.ts

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/api/__generated__/validate.ts

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright Oxide Computer Company
7+
*/
8+
import { createColumnHelper, getCoreRowModel, useReactTable } from '@tanstack/react-table'
9+
import { useMemo } from 'react'
10+
import { type LoaderFunctionArgs } from 'react-router'
11+
12+
import {
13+
apiq,
14+
getListQFn,
15+
queryClient,
16+
usePrefetchedQuery,
17+
type AffinityGroup,
18+
type AntiAffinityGroup,
19+
} from '@oxide/api'
20+
import { Affinity24Icon } from '@oxide/design-system/icons/react'
21+
22+
import { getInstanceSelector, useInstanceSelector } from '~/hooks/use-params'
23+
import { makeLinkCell } from '~/table/cells/LinkCell'
24+
import { Columns } from '~/table/columns/common'
25+
import { Table } from '~/table/Table'
26+
import { Badge } from '~/ui/lib/Badge'
27+
import { CardBlock } from '~/ui/lib/CardBlock'
28+
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
29+
import { TableEmptyBox } from '~/ui/lib/Table'
30+
import { TipIcon } from '~/ui/lib/TipIcon'
31+
import { ALL_ISH } from '~/util/consts'
32+
import { pb } from '~/util/path-builder'
33+
import type * as PP from '~/util/path-params'
34+
35+
const instanceView = ({ project, instance }: PP.Instance) =>
36+
apiq('instanceView', { path: { instance }, query: { project } })
37+
const antiAffinityGroupList = ({ project, instance }: PP.Instance) =>
38+
getListQFn('instanceAntiAffinityGroupList', {
39+
path: { instance },
40+
query: { project, limit: ALL_ISH },
41+
})
42+
43+
export async function clientLoader({ params }: LoaderFunctionArgs) {
44+
const instanceSelector = getInstanceSelector(params)
45+
await Promise.all([
46+
// This is covered by the InstancePage loader but there's no downside to
47+
// being redundant. If it were removed there, we'd still want it here.
48+
queryClient.prefetchQuery(instanceView(instanceSelector)),
49+
queryClient.prefetchQuery(antiAffinityGroupList(instanceSelector).optionsFn()),
50+
])
51+
return null
52+
}
53+
54+
const colHelper = createColumnHelper<AffinityGroup | AntiAffinityGroup>()
55+
const staticCols = [
56+
colHelper.accessor('description', Columns.description),
57+
colHelper.accessor('policy', {
58+
header: () => (
59+
<>
60+
Policy
61+
<TipIcon className="ml-2">
62+
The affinity policy describes what to do when a request cannot be satisfied.
63+
&lsquo;allow&rsquo; means a best-effort approach, while &lsquo;fail&rsquo; means
64+
fail explicitly.
65+
</TipIcon>
66+
</>
67+
),
68+
cell: (info) => <Badge color="neutral">{info.getValue()}</Badge>,
69+
}),
70+
colHelper.accessor('failureDomain', {
71+
header: () => (
72+
<>
73+
Failure Domain
74+
<TipIcon className="ml-2">
75+
Describes the scope of affinity for the purposes of co-location. Currently, only
76+
&lsquo;sled&rsquo; is supported.
77+
</TipIcon>
78+
</>
79+
),
80+
cell: (info) => <Badge color="neutral">{info.getValue()}</Badge>,
81+
}),
82+
]
83+
84+
export const handle = { crumb: 'Affinity' }
85+
86+
export default function AffinityTab() {
87+
const instanceSelector = useInstanceSelector()
88+
const { project } = instanceSelector
89+
90+
const { data: antiAffinityGroups } = usePrefetchedQuery(
91+
antiAffinityGroupList(instanceSelector).optionsFn()
92+
)
93+
94+
const antiAffinityCols = useMemo(
95+
() => [
96+
colHelper.accessor('name', {
97+
header: 'Group Name',
98+
cell: makeLinkCell((antiAffinityGroup) =>
99+
pb.antiAffinityGroup({ project, antiAffinityGroup })
100+
),
101+
}),
102+
...staticCols,
103+
],
104+
[project]
105+
)
106+
107+
// Create tables for both types of groups
108+
const antiAffinityTable = useReactTable({
109+
columns: antiAffinityCols,
110+
data: antiAffinityGroups.items,
111+
getCoreRowModel: getCoreRowModel(),
112+
})
113+
114+
return (
115+
<CardBlock>
116+
<CardBlock.Header title="Anti-affinity groups" titleId="anti-affinity-groups-label" />
117+
118+
<CardBlock.Body>
119+
{antiAffinityGroups.items.length > 0 ? (
120+
<Table
121+
aria-labelledby="anti-affinity-groups-label"
122+
table={antiAffinityTable}
123+
className="table-inline"
124+
/>
125+
) : (
126+
<TableEmptyBox border={false}>
127+
<EmptyMessage
128+
icon={<Affinity24Icon />}
129+
title="No Anti-Affinity Groups"
130+
body="This instance is not a member of any anti-affinity groups"
131+
/>
132+
</TableEmptyBox>
133+
)}
134+
</CardBlock.Body>
135+
</CardBlock>
136+
)
137+
}

app/pages/project/instances/InstancePage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ export default function InstancePage() {
273273
Metrics
274274
</Tab>
275275
<Tab to={pb.instanceConnect(instanceSelector)}>Connect</Tab>
276+
<Tab to={pb.instanceAffinity(instanceSelector)}>Affinity</Tab>
276277
<Tab to={pb.instanceSettings(instanceSelector)}>Settings</Tab>
277278
</RouteTabs>
278279
{resizeInstance && (

app/routes.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,10 @@ export const routes = createRoutesFromElements(
335335
path="settings"
336336
lazy={() => import('./pages/project/instances/SettingsTab').then(convert)}
337337
/>
338+
<Route
339+
path="affinity"
340+
lazy={() => import('./pages/project/instances/AffinityTab').then(convert)}
341+
/>
338342
</Route>
339343
</Route>
340344
</Route>

0 commit comments

Comments
 (0)