diff --git a/app/pages/system/silos/SiloFleetRolesTab.tsx b/app/pages/system/silos/SiloFleetRolesTab.tsx
new file mode 100644
index 000000000..713691b29
--- /dev/null
+++ b/app/pages/system/silos/SiloFleetRolesTab.tsx
@@ -0,0 +1,53 @@
+/*
+ * 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 { usePrefetchedApiQuery } from '@oxide/api'
+import { Cloud24Icon, NextArrow12Icon } from '@oxide/design-system/icons/react'
+
+import { useSiloSelector } from '~/hooks/use-params'
+import { Badge } from '~/ui/lib/Badge'
+import { EmptyMessage } from '~/ui/lib/EmptyMessage'
+import { TableEmptyBox } from '~/ui/lib/Table'
+
+export default function SiloFleetRolesTab() {
+ const siloSelector = useSiloSelector()
+ const { data: silo } = usePrefetchedApiQuery('siloView', { path: siloSelector })
+
+ const roleMapPairs = Object.entries(silo.mappedFleetRoles).flatMap(
+ ([fleetRole, siloRoles]) =>
+ siloRoles.map((siloRole) => [siloRole, fleetRole] as [string, string])
+ )
+
+ if (roleMapPairs.length === 0) {
+ return (
+
+ }
+ title="Mapped fleet roles"
+ // TODO: better empty state explaining that no roles are mapped so nothing will happen
+ body="Silo roles can automatically grant a fleet role. This silo has no role mappings configured."
+ />
+
+ )
+ }
+
+ return (
+ <>
+
Silo roles can automatically grant a fleet role.
+
+ {roleMapPairs.map(([siloRole, fleetRole]) => (
+ -
+ Silo {siloRole}
+
+ Fleet {fleetRole}
+
+ ))}
+
+ >
+ )
+}
diff --git a/app/pages/system/silos/SiloIdpsTab.tsx b/app/pages/system/silos/SiloIdpsTab.tsx
index d457c66d7..539e19ca9 100644
--- a/app/pages/system/silos/SiloIdpsTab.tsx
+++ b/app/pages/system/silos/SiloIdpsTab.tsx
@@ -7,12 +7,12 @@
*/
import { createColumnHelper } from '@tanstack/react-table'
import { useMemo } from 'react'
-import { Outlet } from 'react-router'
+import { Outlet, type LoaderFunctionArgs } from 'react-router'
import { Cloud24Icon } from '@oxide/design-system/icons/react'
-import { getListQFn, type IdentityProvider } from '~/api'
-import { useSiloSelector } from '~/hooks/use-params'
+import { getListQFn, queryClient, type IdentityProvider } from '~/api'
+import { getSiloSelector, useSiloSelector } from '~/hooks/use-params'
import { LinkCell } from '~/table/cells/LinkCell'
import { Columns } from '~/table/columns/common'
import { useQueryTable } from '~/table/QueryTable'
@@ -30,7 +30,13 @@ const colHelper = createColumnHelper()
export const siloIdpList = (silo: string) =>
getListQFn('siloIdentityProviderList', { query: { silo } })
-export function SiloIdpsTab() {
+export async function clientLoader({ params }: LoaderFunctionArgs) {
+ const { silo } = getSiloSelector(params)
+ await queryClient.prefetchQuery(siloIdpList(silo).optionsFn())
+ return null
+}
+
+export default function SiloIdpsTab() {
const { silo } = useSiloSelector()
const columns = useMemo(
diff --git a/app/pages/system/silos/SiloIpPoolsTab.tsx b/app/pages/system/silos/SiloIpPoolsTab.tsx
index 726820a6f..ddeb6feb6 100644
--- a/app/pages/system/silos/SiloIpPoolsTab.tsx
+++ b/app/pages/system/silos/SiloIpPoolsTab.tsx
@@ -10,13 +10,20 @@ import { useQuery } from '@tanstack/react-query'
import { createColumnHelper } from '@tanstack/react-table'
import { useCallback, useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'
+import { type LoaderFunctionArgs } from 'react-router'
-import { getListQFn, useApiMutation, useApiQueryClient, type SiloIpPool } from '@oxide/api'
+import {
+ getListQFn,
+ queryClient,
+ useApiMutation,
+ useApiQueryClient,
+ type SiloIpPool,
+} from '@oxide/api'
import { Networking24Icon } from '@oxide/design-system/icons/react'
import { ComboboxField } from '~/components/form/fields/ComboboxField'
import { HL } from '~/components/HL'
-import { useSiloSelector } from '~/hooks/use-params'
+import { getSiloSelector, useSiloSelector } from '~/hooks/use-params'
import { confirmAction } from '~/stores/confirm-action'
import { addToast } from '~/stores/toast'
import { DefaultPoolCell } from '~/table/cells/DefaultPoolCell'
@@ -62,7 +69,13 @@ const allSiloPoolsQuery = (silo: string) =>
export const siloIpPoolsQuery = (silo: string) =>
getListQFn('siloIpPoolList', { path: { silo } })
-export function SiloIpPoolsTab() {
+export async function clientLoader({ params }: LoaderFunctionArgs) {
+ const { silo } = getSiloSelector(params)
+ await queryClient.prefetchQuery(siloIpPoolsQuery(silo).optionsFn())
+ return null
+}
+
+export default function SiloIpPoolsTab() {
const { silo } = useSiloSelector()
const [showLinkModal, setShowLinkModal] = useState(false)
const queryClient = useApiQueryClient()
diff --git a/app/pages/system/silos/SiloPage.tsx b/app/pages/system/silos/SiloPage.tsx
index 50ac9653a..1088509fc 100644
--- a/app/pages/system/silos/SiloPage.tsx
+++ b/app/pages/system/silos/SiloPage.tsx
@@ -7,33 +7,21 @@
*/
import { type LoaderFunctionArgs } from 'react-router'
-import { apiQueryClient, queryClient, usePrefetchedApiQuery } from '@oxide/api'
-import { Cloud16Icon, Cloud24Icon, NextArrow12Icon } from '@oxide/design-system/icons/react'
+import { Cloud16Icon, Cloud24Icon } from '@oxide/design-system/icons/react'
+import { apiq, queryClient, usePrefetchedQuery } from '~/api'
import { DocsPopover } from '~/components/DocsPopover'
-import { QueryParamTabs } from '~/components/QueryParamTabs'
+import { RouteTabs, Tab } from '~/components/RouteTabs'
import { makeCrumb } from '~/hooks/use-crumbs'
import { getSiloSelector, useSiloSelector } from '~/hooks/use-params'
-import { Badge } from '~/ui/lib/Badge'
-import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
-import { TableEmptyBox } from '~/ui/lib/Table'
-import { Tabs } from '~/ui/lib/Tabs'
import { docLinks } from '~/util/links'
-
-import { siloIdpList, SiloIdpsTab } from './SiloIdpsTab'
-import { siloIpPoolsQuery, SiloIpPoolsTab } from './SiloIpPoolsTab'
-import { SiloQuotasTab } from './SiloQuotasTab'
+import { pb } from '~/util/path-builder'
export async function clientLoader({ params }: LoaderFunctionArgs) {
const { silo } = getSiloSelector(params)
- await Promise.all([
- apiQueryClient.prefetchQuery('siloView', { path: { silo } }),
- apiQueryClient.prefetchQuery('siloUtilizationView', { path: { silo } }),
- queryClient.prefetchQuery(siloIdpList(silo).optionsFn()),
- queryClient.prefetchQuery(siloIpPoolsQuery(silo).optionsFn()),
- ])
+ await queryClient.prefetchQuery(apiq('siloView', { path: { silo } }))
return null
}
@@ -42,12 +30,7 @@ export const handle = makeCrumb((p) => p.silo!)
export default function SiloPage() {
const siloSelector = useSiloSelector()
- const { data: silo } = usePrefetchedApiQuery('siloView', { path: siloSelector })
-
- const roleMapPairs = Object.entries(silo.mappedFleetRoles).flatMap(
- ([fleetRole, siloRoles]) =>
- siloRoles.map((siloRole) => [siloRole, fleetRole] as [string, string])
- )
+ const { data: silo } = usePrefetchedQuery(apiq('siloView', { path: siloSelector }))
return (
<>
@@ -73,50 +56,12 @@ export default function SiloPage() {
-
-
- Identity Providers
- IP Pools
- Quotas
- Fleet roles
-
-
-
-
-
-
-
-
-
-
-
- {/* TODO: better empty state explaining that no roles are mapped so nothing will happen */}
- {roleMapPairs.length === 0 ? (
-
- }
- title="Mapped fleet roles"
- body="Silo roles can automatically grant a fleet role. This silo has no role mappings configured."
- />
-
- ) : (
- <>
-
- Silo roles can automatically grant a fleet role.
-
-
- {roleMapPairs.map(([siloRole, fleetRole]) => (
- -
- Silo {siloRole}
-
- Fleet {fleetRole}
-
- ))}
-
- >
- )}
-
-
+
+ Identity Providers
+ IP Pools
+ Quotas
+ Fleet roles
+
>
)
}
diff --git a/app/pages/system/silos/SiloQuotasTab.tsx b/app/pages/system/silos/SiloQuotasTab.tsx
index 5d513cd93..c65ca1055 100644
--- a/app/pages/system/silos/SiloQuotasTab.tsx
+++ b/app/pages/system/silos/SiloQuotasTab.tsx
@@ -8,17 +8,19 @@
import { useState } from 'react'
import { useForm } from 'react-hook-form'
+import { type LoaderFunctionArgs } from 'react-router'
import type { SetNonNullable } from 'type-fest'
import {
- apiQueryClient,
+ apiq,
+ queryClient,
useApiMutation,
- usePrefetchedApiQuery,
+ usePrefetchedQuery,
type SiloQuotasUpdate,
} from '~/api'
import { NumberField } from '~/components/form/fields/NumberField'
import { SideModalForm } from '~/components/form/SideModalForm'
-import { useSiloSelector } from '~/hooks/use-params'
+import { getSiloSelector, useSiloSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { Button } from '~/ui/lib/Button'
import { Message } from '~/ui/lib/Message'
@@ -29,11 +31,17 @@ import { bytesToGiB, GiB } from '~/util/units'
const Unit = classed.span`ml-1 text-secondary`
-export function SiloQuotasTab() {
+export async function clientLoader({ params }: LoaderFunctionArgs) {
+ const { silo } = getSiloSelector(params)
+ await queryClient.prefetchQuery(apiq('siloUtilizationView', { path: { silo } }))
+ return null
+}
+
+export default function SiloQuotasTab() {
const { silo } = useSiloSelector()
- const { data: utilization } = usePrefetchedApiQuery('siloUtilizationView', {
- path: { silo: silo },
- })
+ const { data: utilization } = usePrefetchedQuery(
+ apiq('siloUtilizationView', { path: { silo } })
+ )
const { allocated: quotas, provisioned } = utilization
@@ -91,9 +99,11 @@ export function SiloQuotasTab() {
function EditQuotasForm({ onDismiss }: { onDismiss: () => void }) {
const { silo } = useSiloSelector()
- const { data: utilization } = usePrefetchedApiQuery('siloUtilizationView', {
- path: { silo: silo },
- })
+ const { data: utilization } = usePrefetchedQuery(
+ apiq('siloUtilizationView', {
+ path: { silo: silo },
+ })
+ )
const quotas = utilization.allocated
// required because we need to rule out undefined because NumberField hates that
@@ -107,7 +117,7 @@ function EditQuotasForm({ onDismiss }: { onDismiss: () => void }) {
const updateQuotas = useApiMutation('siloQuotasUpdate', {
onSuccess() {
- apiQueryClient.invalidateQueries('siloUtilizationView')
+ queryClient.invalidateEndpoint('siloUtilizationView')
addToast({ content: 'Quotas updated' })
onDismiss()
},
diff --git a/app/routes.tsx b/app/routes.tsx
index f653b18af..a085800cf 100644
--- a/app/routes.tsx
+++ b/app/routes.tsx
@@ -129,13 +129,30 @@ export const routes = createRoutesFromElements(
path=":silo"
lazy={() => import('./pages/system/silos/SiloPage').then(convert)}
>
+ {/* Nesting keeps IdPs tab contents rendered when side modals are open*/}
+ } />
+ import('./pages/system/silos/SiloIdpsTab').then(convert)}>
+
+ import('./forms/idp/create').then(convert)}
+ />
+ import('./forms/idp/edit').then(convert)}
+ />
+
+ import('./pages/system/silos/SiloIpPoolsTab').then(convert)}
+ />
import('./forms/idp/create').then(convert)}
+ path="quotas"
+ lazy={() => import('./pages/system/silos/SiloQuotasTab').then(convert)}
/>
import('./forms/idp/edit').then(convert)}
+ path="fleet-roles"
+ lazy={() => import('./pages/system/silos/SiloFleetRolesTab').then(convert)}
/>
diff --git a/app/util/__snapshots__/path-builder.spec.ts.snap b/app/util/__snapshots__/path-builder.spec.ts.snap
index 2393aa324..b52f8f530 100644
--- a/app/util/__snapshots__/path-builder.spec.ts.snap
+++ b/app/util/__snapshots__/path-builder.spec.ts.snap
@@ -537,7 +537,7 @@ exports[`breadcrumbs 2`] = `
"path": "/projects/p/instances/i/serial-console",
},
],
- "silo (/system/silos/s)": [
+ "silo (/system/silos/s/idps)": [
{
"label": "Silos",
"path": "/system/silos",
@@ -553,6 +553,26 @@ exports[`breadcrumbs 2`] = `
"path": "/access",
},
],
+ "siloFleetRoles (/system/silos/s/fleet-roles)": [
+ {
+ "label": "Silos",
+ "path": "/system/silos",
+ },
+ {
+ "label": "s",
+ "path": "/system/silos/s",
+ },
+ ],
+ "siloIdps (/system/silos/s/idps)": [
+ {
+ "label": "Silos",
+ "path": "/system/silos",
+ },
+ {
+ "label": "s",
+ "path": "/system/silos/s",
+ },
+ ],
"siloIdpsNew (/system/silos/s/idps-new)": [
{
"label": "Silos",
@@ -575,7 +595,17 @@ exports[`breadcrumbs 2`] = `
"path": "/images",
},
],
- "siloIpPools (/system/silos/s?tab=ip-pools)": [
+ "siloIpPools (/system/silos/s/ip-pools)": [
+ {
+ "label": "Silos",
+ "path": "/system/silos",
+ },
+ {
+ "label": "s",
+ "path": "/system/silos/s",
+ },
+ ],
+ "siloQuotas (/system/silos/s/quotas)": [
{
"label": "Silos",
"path": "/system/silos",
diff --git a/app/util/path-builder.spec.ts b/app/util/path-builder.spec.ts
index 202994c02..2a94d2262 100644
--- a/app/util/path-builder.spec.ts
+++ b/app/util/path-builder.spec.ts
@@ -79,12 +79,15 @@ test('path builder', () => {
"projectsNew": "/projects-new",
"samlIdp": "/system/silos/s/idps/saml/pr",
"serialConsole": "/projects/p/instances/i/serial-console",
- "silo": "/system/silos/s",
+ "silo": "/system/silos/s/idps",
"siloAccess": "/access",
+ "siloFleetRoles": "/system/silos/s/fleet-roles",
+ "siloIdps": "/system/silos/s/idps",
"siloIdpsNew": "/system/silos/s/idps-new",
"siloImageEdit": "/images/im/edit",
"siloImages": "/images",
- "siloIpPools": "/system/silos/s?tab=ip-pools",
+ "siloIpPools": "/system/silos/s/ip-pools",
+ "siloQuotas": "/system/silos/s/quotas",
"siloUtilization": "/utilization",
"silos": "/system/silos",
"silosNew": "/system/silos-new",
diff --git a/app/util/path-builder.ts b/app/util/path-builder.ts
index 1a75b7354..18a1744fa 100644
--- a/app/util/path-builder.ts
+++ b/app/util/path-builder.ts
@@ -18,6 +18,7 @@ const vpcBase = ({ project, vpc }: PP.Vpc) => `${pb.vpcs({ project })}/${vpc}`
export const instanceMetricsBase = ({ project, instance }: PP.Instance) =>
`${instanceBase({ project, instance })}/metrics`
export const inventoryBase = () => '/system/inventory'
+const siloBase = ({ silo }: PP.Silo) => `/system/silos/${silo}`
export const pb = {
projects: () => `/projects`,
@@ -122,11 +123,15 @@ export const pb = {
silos: () => '/system/silos',
silosNew: () => '/system/silos-new',
- silo: ({ silo }: PP.Silo) => `/system/silos/${silo}`,
- siloIpPools: (params: PP.Silo) => `${pb.silo(params)}?tab=ip-pools`,
- siloIdpsNew: (params: PP.Silo) => `${pb.silo(params)}/idps-new`,
+ // canonical route for silo is first tab
+ silo: (params: PP.Silo) => pb.siloIdps(params),
+ siloIdps: (params: PP.Silo) => `${siloBase(params)}/idps`,
+ siloIdpsNew: (params: PP.Silo) => `${siloBase(params)}/idps-new`,
+ siloIpPools: (params: PP.Silo) => `${siloBase(params)}/ip-pools`,
+ siloQuotas: (params: PP.Silo) => `${siloBase(params)}/quotas`,
+ siloFleetRoles: (params: PP.Silo) => `${siloBase(params)}/fleet-roles`,
samlIdp: (params: PP.IdentityProvider) =>
- `${pb.silo(params)}/idps/saml/${params.provider}`,
+ `${siloBase(params)}/idps/saml/${params.provider}`,
profile: () => '/settings/profile',
sshKeys: () => '/settings/ssh-keys',
diff --git a/package-lock.json b/package-lock.json
index be45e16dd..f2f269aa1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -198,7 +198,6 @@
"resolved": "https://registry.npmjs.org/@asciidoctor/opal-runtime/-/opal-runtime-3.0.1.tgz",
"integrity": "sha512-iW7ACahOG0zZft4A/4CqDcc7JX+fWRNjV5tFAVkNCzwZD+EnFolPaUOPYt8jzadc0+Bgd80cQTtRMQnaaV1kkg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"glob": "8.1.0",
"unxhr": "1.2.0"
@@ -1108,6 +1107,7 @@
"integrity": "sha512-F0/Hrcfpy8WuxlQyAWJTEren/uxKhYonOGY4OyWmwRdeTvkh9mMSCxowZLjNkhwi/2ipqCgtXwwOk7tW0mWXkA==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@babel/generator": "^7.26.2",
"@babel/parser": "^7.26.2",
@@ -4358,6 +4358,7 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.56.2.tgz",
"integrity": "sha512-SR0GzHVo6yzhN72pnRhkEFRAHMsUo5ZPzAxfTMvUxFIDVS6W9LYUp6nXW3fcHVdg0ZJl8opSH85jqahvm6DSVg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@tanstack/query-core": "5.56.2"
},
@@ -4860,6 +4861,7 @@
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
"devOptional": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4870,6 +4872,7 @@
"integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==",
"devOptional": true,
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"@types/react": "^19.0.0"
}
@@ -4970,6 +4973,7 @@
"integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.39.0",
"@typescript-eslint/types": "8.39.0",
@@ -5344,7 +5348,8 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@yarnpkg/lockfile": {
"version": "1.1.0",
@@ -5373,6 +5378,7 @@
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5870,6 +5876,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001669",
"electron-to-chromium": "^1.5.41",
@@ -5998,9 +6005,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001716",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz",
- "integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==",
+ "version": "1.0.30001749",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz",
+ "integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==",
"dev": true,
"funding": [
{
@@ -6909,7 +6916,6 @@
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
@@ -6936,7 +6942,6 @@
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
- "peer": true,
"dependencies": {
"domelementtype": "^2.3.0"
},
@@ -6952,7 +6957,6 @@
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"license": "BSD-2-Clause",
- "peer": true,
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
@@ -7324,6 +7328,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -7380,6 +7385,7 @@
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -8622,7 +8628,6 @@
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"license": "ISC",
- "peer": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -8654,7 +8659,6 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"license": "ISC",
- "peer": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -8881,7 +8885,6 @@
"resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-5.0.13.tgz",
"integrity": "sha512-B7JonBuAfG32I7fDouUQEogBrz3jK9gAuN1r1AaXpED6dIhtg/JwiSRhjGL7aOJwRz3HU4efowCjQBaoXiREqg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"domhandler": "5.0.3",
"htmlparser2": "10.0.0"
@@ -8937,7 +8940,6 @@
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-5.2.2.tgz",
"integrity": "sha512-yA5012CJGSFWYZsgYzfr6HXJgDap38/AEP4ra8Cw+WHIi2ZRDXRX/QVYdumRf1P8zKyScKd6YOrWYvVEiPfGKg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"domhandler": "5.0.3",
"html-dom-parser": "5.0.13",
@@ -8976,7 +8978,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
@@ -8989,7 +8990,6 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
"license": "BSD-2-Clause",
- "peer": true,
"engines": {
"node": ">=0.12"
},
@@ -9152,8 +9152,7 @@
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz",
"integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/internal-slot": {
"version": "1.1.0",
@@ -10484,6 +10483,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@bundled-es-modules/cookie": "^2.0.1",
"@bundled-es-modules/statuses": "^1.0.1",
@@ -11457,6 +11457,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -11655,6 +11656,7 @@
"integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -11708,6 +11710,7 @@
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -11977,6 +11980,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -12035,6 +12039,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -12090,8 +12095,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.2.tgz",
"integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/react-remove-scroll": {
"version": "2.6.3",
@@ -13410,7 +13414,6 @@
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz",
"integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"style-to-object": "1.0.8"
}
@@ -13420,7 +13423,6 @@
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
"integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"inline-style-parser": "0.2.4"
}
@@ -13536,6 +13538,7 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -13709,6 +13712,7 @@
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -13934,6 +13938,7 @@
"integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
@@ -14114,6 +14119,7 @@
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -14146,8 +14152,7 @@
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/unist-util-is": {
"version": "6.0.0",
@@ -14242,7 +14247,6 @@
"resolved": "https://registry.npmjs.org/unxhr/-/unxhr-1.2.0.tgz",
"integrity": "sha512-6cGpm8NFXPD9QbSNx0cD2giy7teZ6xOkCUH3U89WKVkL9N9rBrWjlCwhR94Re18ZlAop4MOc3WU1M3Hv/bgpIw==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=8.11"
}
@@ -14468,6 +14472,7 @@
"integrity": "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.6",
@@ -14647,6 +14652,7 @@
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
diff --git a/test/e2e/ip-pools.e2e.ts b/test/e2e/ip-pools.e2e.ts
index 07742b3f9..d85be8373 100644
--- a/test/e2e/ip-pools.e2e.ts
+++ b/test/e2e/ip-pools.e2e.ts
@@ -74,7 +74,7 @@ test('IP pool silo list', async ({ page }) => {
// clicking silo takes you to silo page
const siloLink = page.getByRole('link', { name: 'maze-war' })
await siloLink.click()
- await expect(page).toHaveURL('/system/silos/maze-war?tab=ip-pools')
+ await expect(page).toHaveURL('/system/silos/maze-war/ip-pools')
await page.goBack()
// unlink silo and the row is gone
diff --git a/test/e2e/silos.e2e.ts b/test/e2e/silos.e2e.ts
index e0b8dd1dc..a7d1885fd 100644
--- a/test/e2e/silos.e2e.ts
+++ b/test/e2e/silos.e2e.ts
@@ -155,7 +155,11 @@ test('Create silo', async ({ page }) => {
await expectRowVisible(table, { Resource: 'Memory', Quota: '58 GiB' })
await expectRowVisible(table, { Resource: 'Storage', Quota: '735 GiB' })
- await page.goBack()
+ // Go back to the silos list page to delete the silo using breadcrumbs
+ await page
+ .getByRole('navigation', { name: 'Breadcrumbs' })
+ .getByRole('link', { name: 'Silos' })
+ .click()
// now delete it
await clickRowAction(page, 'other-silo', 'Delete')
@@ -257,7 +261,7 @@ test('Identity providers', async ({ page }) => {
})
test('Silo IP pools', async ({ page }) => {
- await page.goto('/system/silos/maze-war?tab=ip-pools')
+ await page.goto('/system/silos/maze-war/ip-pools')
const table = page.getByRole('table')
await expectRowVisible(table, { name: 'ip-pool-1', Default: 'default' })
@@ -305,7 +309,7 @@ test('Silo IP pools', async ({ page }) => {
})
test('Silo IP pools link pool', async ({ page }) => {
- await page.goto('/system/silos/maze-war?tab=ip-pools')
+ await page.goto('/system/silos/maze-war/ip-pools')
const table = page.getByRole('table')
await expectRowVisible(table, { name: 'ip-pool-1', Default: 'default' })