diff --git a/app/pages/ProjectsPage.tsx b/app/pages/ProjectsPage.tsx
index 2e351e0c4..f55030d8b 100644
--- a/app/pages/ProjectsPage.tsx
+++ b/app/pages/ProjectsPage.tsx
@@ -5,6 +5,7 @@
*
* Copyright Oxide Computer Company
*/
+import { useQuery } from '@tanstack/react-query'
import { createColumnHelper } from '@tanstack/react-table'
import { useCallback, useMemo } from 'react'
import { Outlet, useNavigate } from 'react-router'
@@ -24,6 +25,7 @@ import { CreateLink } from '~/ui/lib/CreateButton'
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { TableActions } from '~/ui/lib/Table'
+import { ALL_ISH } from '~/util/consts'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'
@@ -94,11 +96,13 @@ export default function ProjectsPage() {
)
const columns = useColsWithActions(staticCols, makeActions)
- const {
- table,
- query: { data: projects },
- } = useQueryTable({ query: projectList, columns, emptyState: })
+ const { table } = useQueryTable({
+ query: projectList,
+ columns,
+ emptyState: ,
+ })
+ const { data: allProjects } = useQuery(q(api.projectList, { query: { limit: ALL_ISH } }))
useQuickActions(
useMemo(
() => [
@@ -106,13 +110,13 @@ export default function ProjectsPage() {
value: 'New project',
onSelect: () => navigate(pb.projectsNew()),
},
- ...(projects?.items || []).map((p) => ({
+ ...(allProjects?.items || []).map((p) => ({
value: p.name,
onSelect: () => navigate(pb.project({ project: p.name })),
navGroup: 'Go to project',
})),
],
- [navigate, projects]
+ [navigate, allProjects]
)
)
diff --git a/app/pages/SiloAccessPage.tsx b/app/pages/SiloAccessPage.tsx
index 6b274f91b..87ae0a5c2 100644
--- a/app/pages/SiloAccessPage.tsx
+++ b/app/pages/SiloAccessPage.tsx
@@ -30,6 +30,7 @@ import {
SiloAccessEditUserSideModal,
} from '~/forms/silo-access'
import { useCurrentUser } from '~/hooks/use-current-user'
+import { useQuickActions } from '~/hooks/use-quick-actions'
import { confirmDelete } from '~/stores/confirm-delete'
import { addToast } from '~/stores/toast'
import { getActionsCol } from '~/table/columns/action-col'
@@ -164,6 +165,18 @@ export default function SiloAccessPage() {
getCoreRowModel: getCoreRowModel(),
})
+ useQuickActions(
+ useMemo(
+ () => [
+ {
+ value: 'Add user or group',
+ onSelect: () => setAddModalOpen(true),
+ },
+ ],
+ []
+ )
+ )
+
return (
<>
diff --git a/app/pages/SiloImagesPage.tsx b/app/pages/SiloImagesPage.tsx
index e395d4e44..7cc6f5e53 100644
--- a/app/pages/SiloImagesPage.tsx
+++ b/app/pages/SiloImagesPage.tsx
@@ -9,7 +9,7 @@ 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 { Outlet } from 'react-router'
+import { Outlet, useNavigate } from 'react-router'
import { api, getListQFn, q, queryClient, useApiMutation, type Image } from '@oxide/api'
import { Images16Icon, Images24Icon } from '@oxide/design-system/icons/react'
@@ -20,6 +20,7 @@ import { toImageComboboxItem } from '~/components/form/fields/ImageSelectField'
import { ListboxField } from '~/components/form/fields/ListboxField'
import { ModalForm } from '~/components/form/ModalForm'
import { HL } from '~/components/HL'
+import { useQuickActions } from '~/hooks/use-quick-actions'
import { confirmDelete } from '~/stores/confirm-delete'
import { addToast } from '~/stores/toast'
import { makeLinkCell } from '~/table/cells/LinkCell'
@@ -32,6 +33,7 @@ import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { Message } from '~/ui/lib/Message'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { TableActions } from '~/ui/lib/Table'
+import { ALL_ISH } from '~/util/consts'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'
@@ -93,6 +95,26 @@ export default function SiloImagesPage() {
const columns = useColsWithActions(staticCols, makeActions)
const { table } = useQueryTable({ query: imageList, columns, emptyState: })
+
+ const navigate = useNavigate()
+ const { data: allImages } = useQuery(q(api.imageList, { query: { limit: ALL_ISH } }))
+ useQuickActions(
+ useMemo(
+ () => [
+ {
+ value: 'Promote image',
+ onSelect: () => setShowModal(true),
+ },
+ ...(allImages?.items || []).map((i) => ({
+ value: i.name,
+ onSelect: () => navigate(pb.siloImageEdit({ image: i.name })),
+ navGroup: 'Go to silo image',
+ })),
+ ],
+ [navigate, allImages]
+ )
+ )
+
return (
<>
diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx
index 460bf3aae..254144480 100644
--- a/app/pages/project/access/ProjectAccessPage.tsx
+++ b/app/pages/project/access/ProjectAccessPage.tsx
@@ -35,6 +35,7 @@ import {
ProjectAccessEditUserSideModal,
} from '~/forms/project-access'
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
+import { useQuickActions } from '~/hooks/use-quick-actions'
import { confirmDelete } from '~/stores/confirm-delete'
import { addToast } from '~/stores/toast'
import { getActionsCol } from '~/table/columns/action-col'
@@ -205,6 +206,18 @@ export default function ProjectAccessPage() {
getCoreRowModel: getCoreRowModel(),
})
+ useQuickActions(
+ useMemo(
+ () => [
+ {
+ value: 'Add user or group',
+ onSelect: () => setAddModalOpen(true),
+ },
+ ],
+ []
+ )
+ )
+
return (
<>
diff --git a/app/pages/project/affinity/AffinityPage.tsx b/app/pages/project/affinity/AffinityPage.tsx
index 941407de6..8a5adcf92 100644
--- a/app/pages/project/affinity/AffinityPage.tsx
+++ b/app/pages/project/affinity/AffinityPage.tsx
@@ -7,8 +7,8 @@
*/
import { createColumnHelper, getCoreRowModel, useReactTable } from '@tanstack/react-table'
-import { useCallback } from 'react'
-import { Outlet, type LoaderFunctionArgs } from 'react-router'
+import { useCallback, useMemo } from 'react'
+import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router'
import {
api,
@@ -25,6 +25,7 @@ import { AffinityDocsPopover, AffinityPolicyHeader } from '~/components/Affinity
import { HL } from '~/components/HL'
import { antiAffinityGroupList, antiAffinityGroupMemberList } from '~/forms/affinity-util'
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
+import { useQuickActions } from '~/hooks/use-quick-actions'
import { confirmDelete } from '~/stores/confirm-delete'
import { addToast } from '~/stores/toast'
import { EmptyCell, SkeletonCell } from '~/table/cells/EmptyCell'
@@ -138,6 +139,19 @@ export default function AffinityPage() {
getCoreRowModel: getCoreRowModel(),
})
+ const navigate = useNavigate()
+ useQuickActions(
+ useMemo(
+ () => [
+ {
+ value: 'New group',
+ onSelect: () => navigate(pb.affinityNew({ project })),
+ },
+ ],
+ [navigate, project]
+ )
+ )
+
return (
<>
diff --git a/app/pages/project/disks/DisksPage.tsx b/app/pages/project/disks/DisksPage.tsx
index 05908d411..0acbe56d7 100644
--- a/app/pages/project/disks/DisksPage.tsx
+++ b/app/pages/project/disks/DisksPage.tsx
@@ -7,7 +7,7 @@
*/
import { createColumnHelper } from '@tanstack/react-table'
import { useCallback, useMemo } from 'react'
-import { Outlet, type LoaderFunctionArgs } from 'react-router'
+import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router'
import {
api,
@@ -26,6 +26,7 @@ import { HL } from '~/components/HL'
import { DiskStateBadge, DiskTypeBadge, ReadOnlyBadge } from '~/components/StateBadge'
import { makeCrumb } from '~/hooks/use-crumbs'
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
+import { useQuickActions } from '~/hooks/use-quick-actions'
import { confirmDelete } from '~/stores/confirm-delete'
import { addToast } from '~/stores/toast'
import { InstanceLinkCell } from '~/table/cells/InstanceLinkCell'
@@ -186,6 +187,19 @@ export default function DisksPage() {
emptyState: ,
})
+ const navigate = useNavigate()
+ useQuickActions(
+ useMemo(
+ () => [
+ {
+ value: 'New disk',
+ onSelect: () => navigate(pb.disksNew({ project })),
+ },
+ ],
+ [navigate, project]
+ )
+ )
+
return (
<>
diff --git a/app/pages/project/floating-ips/FloatingIpsPage.tsx b/app/pages/project/floating-ips/FloatingIpsPage.tsx
index 1b0958c68..120a73a28 100644
--- a/app/pages/project/floating-ips/FloatingIpsPage.tsx
+++ b/app/pages/project/floating-ips/FloatingIpsPage.tsx
@@ -5,8 +5,9 @@
*
* Copyright Oxide Computer Company
*/
+import { useQuery } from '@tanstack/react-query'
import { createColumnHelper } from '@tanstack/react-table'
-import { useCallback, useState } from 'react'
+import { useCallback, useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'
import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router'
@@ -28,6 +29,7 @@ import { ModalForm } from '~/components/form/ModalForm'
import { HL } from '~/components/HL'
import { makeCrumb } from '~/hooks/use-crumbs'
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
+import { useQuickActions } from '~/hooks/use-quick-actions'
import { confirmAction } from '~/stores/confirm-action'
import { confirmDelete } from '~/stores/confirm-delete'
import { addToast } from '~/stores/toast'
@@ -211,6 +213,26 @@ export default function FloatingIpsPage() {
emptyState: ,
})
+ const { data: allFips } = useQuery(
+ q(api.floatingIpList, { query: { project, limit: ALL_ISH } })
+ )
+ useQuickActions(
+ useMemo(
+ () => [
+ {
+ value: 'New floating IP',
+ onSelect: () => navigate(pb.floatingIpsNew({ project })),
+ },
+ ...(allFips?.items || []).map((f) => ({
+ value: f.name,
+ onSelect: () => navigate(pb.floatingIpEdit({ project, floatingIp: f.name })),
+ navGroup: 'Go to floating IP',
+ })),
+ ],
+ [project, navigate, allFips]
+ )
+ )
+
return (
<>
diff --git a/app/pages/project/images/ImagesPage.tsx b/app/pages/project/images/ImagesPage.tsx
index cd0d559d0..852fa60a0 100644
--- a/app/pages/project/images/ImagesPage.tsx
+++ b/app/pages/project/images/ImagesPage.tsx
@@ -5,17 +5,19 @@
*
* Copyright Oxide Computer Company
*/
+import { useQuery } from '@tanstack/react-query'
import { createColumnHelper } from '@tanstack/react-table'
import { useCallback, useMemo, useState } from 'react'
-import { Outlet, type LoaderFunctionArgs } from 'react-router'
+import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router'
-import { api, getListQFn, queryClient, useApiMutation, type Image } from '@oxide/api'
+import { api, getListQFn, q, queryClient, useApiMutation, type Image } from '@oxide/api'
import { Images16Icon, Images24Icon } from '@oxide/design-system/icons/react'
import { DocsPopover } from '~/components/DocsPopover'
import { HL } from '~/components/HL'
import { makeCrumb } from '~/hooks/use-crumbs'
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
+import { useQuickActions } from '~/hooks/use-quick-actions'
import { confirmDelete } from '~/stores/confirm-delete'
import { addToast } from '~/stores/toast'
import { makeLinkCell } from '~/table/cells/LinkCell'
@@ -28,6 +30,7 @@ import { Message } from '~/ui/lib/Message'
import { Modal } from '~/ui/lib/Modal'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { TableActions } from '~/ui/lib/Table'
+import { ALL_ISH } from '~/util/consts'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'
import type * as PP from '~/util/path-params'
@@ -106,6 +109,27 @@ export default function ImagesPage() {
emptyState: ,
})
+ const navigate = useNavigate()
+ const { data: allImages } = useQuery(
+ q(api.imageList, { query: { project, limit: ALL_ISH } })
+ )
+ useQuickActions(
+ useMemo(
+ () => [
+ {
+ value: 'Upload image',
+ onSelect: () => navigate(pb.projectImagesNew({ project })),
+ },
+ ...(allImages?.items || []).map((i) => ({
+ value: i.name,
+ onSelect: () => navigate(pb.projectImageEdit({ project, image: i.name })),
+ navGroup: 'Go to project image',
+ })),
+ ],
+ [project, navigate, allImages]
+ )
+ )
+
return (
<>
diff --git a/app/pages/project/instances/InstancesPage.tsx b/app/pages/project/instances/InstancesPage.tsx
index 9f313cace..9e4b0117b 100644
--- a/app/pages/project/instances/InstancesPage.tsx
+++ b/app/pages/project/instances/InstancesPage.tsx
@@ -6,6 +6,7 @@
* Copyright Oxide Computer Company
*/
import { type UseQueryOptions } from '@tanstack/react-query'
+import { useQuery } from '@tanstack/react-query'
import { createColumnHelper } from '@tanstack/react-table'
import { filesize } from 'filesize'
import { useMemo, useRef, useState } from 'react'
@@ -14,6 +15,7 @@ import { useNavigate, type LoaderFunctionArgs } from 'react-router'
import {
api,
getListQFn,
+ q,
queryClient,
type ApiError,
type Instance,
@@ -37,6 +39,7 @@ import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { TableActions } from '~/ui/lib/Table'
import { Tooltip } from '~/ui/lib/Tooltip'
import { setDiff } from '~/util/array'
+import { ALL_ISH } from '~/util/consts'
import { toLocaleTimeString } from '~/util/date'
import { pb } from '~/util/path-builder'
import { pluralize } from '~/util/str'
@@ -191,8 +194,11 @@ export default function InstancesPage() {
emptyState: ,
})
- const { data: instances, dataUpdatedAt } = query
+ const { dataUpdatedAt } = query
+ const { data: allInstances } = useQuery(
+ q(api.instanceList, { query: { project, limit: ALL_ISH } })
+ )
const navigate = useNavigate()
useQuickActions(
useMemo(
@@ -201,13 +207,13 @@ export default function InstancesPage() {
value: 'New instance',
onSelect: () => navigate(pb.instancesNew({ project })),
},
- ...(instances?.items || []).map((i) => ({
+ ...(allInstances?.items || []).map((i) => ({
value: i.name,
onSelect: () => navigate(pb.instance({ project, instance: i.name })),
navGroup: 'Go to instance',
})),
],
- [project, instances, navigate]
+ [project, allInstances, navigate]
)
)
diff --git a/app/pages/project/snapshots/SnapshotsPage.tsx b/app/pages/project/snapshots/SnapshotsPage.tsx
index fbf4c2a57..a9f30404a 100644
--- a/app/pages/project/snapshots/SnapshotsPage.tsx
+++ b/app/pages/project/snapshots/SnapshotsPage.tsx
@@ -7,7 +7,7 @@
*/
import { useQuery } from '@tanstack/react-query'
import { createColumnHelper } from '@tanstack/react-table'
-import { useCallback, useState } from 'react'
+import { useCallback, useMemo, useState } from 'react'
import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router'
import {
@@ -27,6 +27,7 @@ import { DocsPopover } from '~/components/DocsPopover'
import { SnapshotStateBadge } from '~/components/StateBadge'
import { makeCrumb } from '~/hooks/use-crumbs'
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
+import { useQuickActions } from '~/hooks/use-quick-actions'
import { DiskDetailSideModal } from '~/pages/project/disks/DiskDetailSideModal'
import { confirmDelete } from '~/stores/confirm-delete'
import { SkeletonCell } from '~/table/cells/EmptyCell'
@@ -164,6 +165,19 @@ export default function SnapshotsPage() {
columns,
emptyState: ,
})
+
+ useQuickActions(
+ useMemo(
+ () => [
+ {
+ value: 'New snapshot',
+ onSelect: () => navigate(pb.snapshotsNew({ project })),
+ },
+ ],
+ [navigate, project]
+ )
+ )
+
return (
<>
diff --git a/app/pages/project/vpcs/VpcsPage.tsx b/app/pages/project/vpcs/VpcsPage.tsx
index 76b7245c3..ab3999cee 100644
--- a/app/pages/project/vpcs/VpcsPage.tsx
+++ b/app/pages/project/vpcs/VpcsPage.tsx
@@ -29,6 +29,7 @@ import { CreateLink } from '~/ui/lib/CreateButton'
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { TableActions } from '~/ui/lib/Table'
+import { ALL_ISH } from '~/util/consts'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'
import type * as PP from '~/util/path-params'
@@ -128,23 +129,27 @@ export default function VpcsPage() {
[project, makeActions]
)
- const { table, query } = useQueryTable({
+ const { table } = useQueryTable({
query: vpcList(project),
columns,
emptyState: ,
})
- const { data: vpcs } = query
-
+ const { data: allVpcs } = useQuery(q(api.vpcList, { query: { project, limit: ALL_ISH } }))
useQuickActions(
useMemo(
- () =>
- (vpcs?.items || []).map((v) => ({
+ () => [
+ {
+ value: 'New VPC',
+ onSelect: () => navigate(pb.vpcsNew({ project })),
+ },
+ ...(allVpcs?.items || []).map((v) => ({
value: v.name,
onSelect: () => navigate(pb.vpc({ project, vpc: v.name })),
navGroup: 'Go to VPC',
})),
- [project, vpcs, navigate]
+ ],
+ [project, allVpcs, navigate]
)
)
diff --git a/app/pages/system/networking/IpPoolsPage.tsx b/app/pages/system/networking/IpPoolsPage.tsx
index 2a16f59c1..48b590230 100644
--- a/app/pages/system/networking/IpPoolsPage.tsx
+++ b/app/pages/system/networking/IpPoolsPage.tsx
@@ -31,6 +31,7 @@ import { CreateLink } from '~/ui/lib/CreateButton'
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { TableActions } from '~/ui/lib/Table'
+import { ALL_ISH } from '~/util/consts'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'
@@ -120,15 +121,15 @@ export default function IpPoolsPage() {
)
const columns = useColsWithActions(staticColumns, makeActions)
- const { table, query } = useQueryTable({
+ const { table } = useQueryTable({
query: ipPoolList,
columns,
// turn this back on if we expect to see IPv6 ranges regularly
// rowHeight: 'large',
emptyState: ,
})
- const { data: pools } = query
+ const { data: allPools } = useQuery(q(api.ipPoolList, { query: { limit: ALL_ISH } }))
useQuickActions(
useMemo(
() => [
@@ -136,13 +137,13 @@ export default function IpPoolsPage() {
value: 'New IP pool',
onSelect: () => navigate(pb.ipPoolsNew()),
},
- ...(pools?.items || []).map((p) => ({
+ ...(allPools?.items || []).map((p) => ({
value: p.name,
onSelect: () => navigate(pb.ipPool({ pool: p.name })),
navGroup: 'Go to IP pool',
})),
],
- [navigate, pools]
+ [navigate, allPools]
)
)
diff --git a/app/pages/system/silos/SilosPage.tsx b/app/pages/system/silos/SilosPage.tsx
index 9a10ff047..6613ddf87 100644
--- a/app/pages/system/silos/SilosPage.tsx
+++ b/app/pages/system/silos/SilosPage.tsx
@@ -5,11 +5,12 @@
*
* Copyright Oxide Computer Company
*/
+import { useQuery } from '@tanstack/react-query'
import { createColumnHelper } from '@tanstack/react-table'
import { useCallback, useMemo } from 'react'
import { Outlet, useNavigate } from 'react-router'
-import { api, getListQFn, queryClient, useApiMutation, type Silo } from '@oxide/api'
+import { api, getListQFn, q, queryClient, useApiMutation, type Silo } from '@oxide/api'
import { Cloud16Icon, Cloud24Icon } from '@oxide/design-system/icons/react'
import { Badge } from '@oxide/design-system/ui'
@@ -28,6 +29,7 @@ import { CreateLink } from '~/ui/lib/CreateButton'
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { TableActions } from '~/ui/lib/Table'
+import { ALL_ISH } from '~/util/consts'
import { docLinks } from '~/util/links'
import { pb } from '~/util/path-builder'
@@ -91,24 +93,24 @@ export default function SilosPage() {
)
const columns = useColsWithActions(staticCols, makeActions)
- const { table, query } = useQueryTable({
+ const { table } = useQueryTable({
query: siloList(),
columns,
emptyState: ,
})
- const { data: silos } = query
+ const { data: allSilos } = useQuery(q(api.siloList, { query: { limit: ALL_ISH } }))
useQuickActions(
useMemo(
() => [
{ value: 'New silo', onSelect: () => navigate(pb.silosNew()) },
- ...(silos?.items || []).map((o) => ({
+ ...(allSilos?.items || []).map((o) => ({
value: o.name,
onSelect: () => navigate(pb.silo({ silo: o.name })),
navGroup: 'Silo detail',
})),
],
- [navigate, silos]
+ [navigate, allSilos]
)
)