diff --git a/app/pages/system/inventory/InventoryPage.tsx b/app/pages/system/inventory/InventoryPage.tsx index 8e1a10df9..0673f6759 100644 --- a/app/pages/system/inventory/InventoryPage.tsx +++ b/app/pages/system/inventory/InventoryPage.tsx @@ -43,6 +43,7 @@ export function InventoryPage() { Sleds Disks + Switches ) diff --git a/app/pages/system/inventory/SwitchesTab.tsx b/app/pages/system/inventory/SwitchesTab.tsx new file mode 100644 index 000000000..bcd4c9662 --- /dev/null +++ b/app/pages/system/inventory/SwitchesTab.tsx @@ -0,0 +1,44 @@ +/* + * 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 { createColumnHelper } from '@tanstack/react-table' + +import { getListQFn, queryClient, type Switch } from '@oxide/api' +import { Servers24Icon } from '@oxide/design-system/icons/react' + +import { useQueryTable } from '~/table/QueryTable' +import { EmptyMessage } from '~/ui/lib/EmptyMessage' + +const EmptyState = () => ( + } + title="Something went wrong" + body="We expected some switches here, but none were found" + /> +) + +const switchList = getListQFn('switchList', {}) + +export async function loader() { + await queryClient.prefetchQuery(switchList.optionsFn()) + return null +} + +const colHelper = createColumnHelper() +const staticCols = [ + colHelper.accessor('id', {}), + colHelper.accessor('baseboard.part', { header: 'part number' }), + colHelper.accessor('baseboard.serial', { header: 'serial number' }), + colHelper.accessor('baseboard.revision', { header: 'revision' }), +] + +Component.displayName = 'SwitchesTab' +export function Component() { + const emptyState = + const { table } = useQueryTable({ query: switchList, columns: staticCols, emptyState }) + return table +} diff --git a/app/routes.tsx b/app/routes.tsx index 19e8e671a..ed4d0a508 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -81,6 +81,7 @@ import { InventoryPage } from './pages/system/inventory/InventoryPage' import * as SledInstances from './pages/system/inventory/sled/SledInstancesTab' import * as SledPage from './pages/system/inventory/sled/SledPage' import * as SledsTab from './pages/system/inventory/SledsTab' +import * as SwitchesTab from './pages/system/inventory/SwitchesTab' import * as IpPool from './pages/system/networking/IpPoolPage' import * as IpPools from './pages/system/networking/IpPoolsPage' import * as SiloImages from './pages/system/SiloImagesPage' @@ -162,6 +163,7 @@ export const routes = createRoutesFromElements( } loader={SledsTab.loader} /> + diff --git a/app/util/__snapshots__/path-builder.spec.ts.snap b/app/util/__snapshots__/path-builder.spec.ts.snap index debb608d8..01a5a29e4 100644 --- a/app/util/__snapshots__/path-builder.spec.ts.snap +++ b/app/util/__snapshots__/path-builder.spec.ts.snap @@ -581,6 +581,16 @@ exports[`breadcrumbs 2`] = ` "path": "/settings/ssh-keys", }, ], + "switchInventory (/system/inventory/switches)": [ + { + "label": "Inventory", + "path": "/system/inventory/sleds", + }, + { + "label": "Switches", + "path": "/system/inventory/switches", + }, + ], "systemUtilization (/system/utilization)": [ { "label": "Utilization", diff --git a/app/util/path-builder.spec.ts b/app/util/path-builder.spec.ts index 291447a1b..a7698083b 100644 --- a/app/util/path-builder.spec.ts +++ b/app/util/path-builder.spec.ts @@ -86,6 +86,7 @@ test('path builder', () => { "sshKeyEdit": "/settings/ssh-keys/ss/edit", "sshKeys": "/settings/ssh-keys", "sshKeysNew": "/settings/ssh-keys-new", + "switchInventory": "/system/inventory/switches", "systemUtilization": "/system/utilization", "vpc": "/projects/p/vpcs/v/firewall-rules", "vpcEdit": "/projects/p/vpcs/v/edit", diff --git a/app/util/path-builder.ts b/app/util/path-builder.ts index f2ec29204..a7071721f 100644 --- a/app/util/path-builder.ts +++ b/app/util/path-builder.ts @@ -98,6 +98,7 @@ export const pb = { sledInventory: () => '/system/inventory/sleds', diskInventory: () => '/system/inventory/disks', + switchInventory: () => '/system/inventory/switches', sled: ({ sledId }: PP.Sled) => `/system/inventory/sleds/${sledId}/instances`, sledInstances: ({ sledId }: PP.Sled) => `/system/inventory/sleds/${sledId}/instances`, diff --git a/mock-api/index.ts b/mock-api/index.ts index e03311145..4f034680d 100644 --- a/mock-api/index.ts +++ b/mock-api/index.ts @@ -21,6 +21,7 @@ export * from './silo' export * from './sled' export * from './snapshot' export * from './sshKeys' +export * from './switch' export * from './user' export * from './user-group' export * from './user' diff --git a/mock-api/msw/db.ts b/mock-api/msw/db.ts index 13efe3d9b..d59019e17 100644 --- a/mock-api/msw/db.ts +++ b/mock-api/msw/db.ts @@ -418,6 +418,7 @@ const initDb = { siloProvisioned: [...mock.siloProvisioned], identityProviders: [...mock.identityProviders], sleds: [...mock.sleds], + switches: [...mock.switches], snapshots: [...mock.snapshots], sshKeys: [...mock.sshKeys], users: [...mock.users], diff --git a/mock-api/msw/handlers.ts b/mock-api/msw/handlers.ts index 4386ebca1..d9284c642 100644 --- a/mock-api/msw/handlers.ts +++ b/mock-api/msw/handlers.ts @@ -1501,6 +1501,11 @@ export const handlers = makeHandlers({ return paginated(query, db.users) }, + switchList: ({ query, cookies }) => { + requireFleetViewer(cookies) + return paginated(query, db.switches) + }, + systemPolicyView({ cookies }) { requireFleetViewer(cookies) @@ -1587,7 +1592,6 @@ export const handlers = makeHandlers({ sledAdd: NotImplemented, sledListUninitialized: NotImplemented, sledSetProvisionPolicy: NotImplemented, - switchList: NotImplemented, switchView: NotImplemented, systemPolicyUpdate: NotImplemented, systemQuotasList: NotImplemented, diff --git a/mock-api/switch.ts b/mock-api/switch.ts new file mode 100644 index 000000000..afa9e9978 --- /dev/null +++ b/mock-api/switch.ts @@ -0,0 +1,25 @@ +/* + * 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 { Switch } from '@oxide/api' + +import type { Json } from './json-type' +import { rack } from './rack' + +export const switches: Json = [ + { + baseboard: { + part: '832-0431906', + serial: 'BDS02141689', + revision: 1, + }, + id: 'ed66617e-4955-465e-b810-0d0dc55d4511', + rack_id: rack.id, + time_created: rack.time_created, + time_modified: rack.time_modified, + }, +] diff --git a/test/e2e/inventory.e2e.ts b/test/e2e/inventory.e2e.ts index 5ec3a0a34..79d5e3c38 100644 --- a/test/e2e/inventory.e2e.ts +++ b/test/e2e/inventory.e2e.ts @@ -6,7 +6,7 @@ * Copyright Oxide Computer Company */ -import { physicalDisks, sleds } from '@oxide/api-mocks' +import { physicalDisks, sleds, switches } from '@oxide/api-mocks' import { expect, expectRowVisible, expectVisible, test } from './utils' @@ -85,3 +85,21 @@ test('Disk inventory page', async ({ page }) => { state: 'decommissioned', }) }) + +test('Switch inventory page', async ({ page }) => { + await page.goto('/system/inventory/switches') + + await expectVisible(page, ['role=heading[name*="Inventory"]']) + + const switchesTab = page.getByRole('tab', { name: 'Switches' }) + await expect(switchesTab).toBeVisible() + await expect(switchesTab).toHaveClass(/is-selected/) + + const table = page.getByRole('table') + await expectRowVisible(table, { + id: switches[0].id, + 'part number': '832-0431906', + 'serial number': 'BDS02141689', + revision: '1', + }) +})