Skip to content

Commit 751a80e

Browse files
Improve "Jump To" (cmd-k) dialog (#3129)
- Closes: #3128 - Closes: #2572 I was initially excited about using `cmd-k` to navigate the UI with the keyboard but immediately ran into some limitations and so took a stab at improving them. - Fix multi-page search in cmd-k: Projects, Project Instances, Project VPCs, Project IP Pools, Silos - Add missing search to cmd-k: Project Floating IPs, Silo Images, Project Images - Add action from button to cmd-k: - Project Affinity: "New Group" - Project Access: "Add user or group" - Project Disks: "New disk" - Project Floating IPs: "New Floating IP" - Project Images: "Upload Image" - Project Snapshots: "New Snapshot" - Project VPC: "New VPC" - Silo Access: "Add user or Group" - Silo Images: Add "Promote image" This is my first PR to oxidecomputer/console and was Claude assisted. I have tested locally and verified these changes seem to work as expected. (see images in #3128) If this is the wrong approach / redundant, feel free to summarily close. --------- Co-authored-by: David Crespo <david.crespo@oxidecomputer.com>
1 parent ac30d2d commit 751a80e

16 files changed

Lines changed: 213 additions & 42 deletions

File tree

app/pages/ProjectsPage.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8+
import { useQuery } from '@tanstack/react-query'
89
import { createColumnHelper } from '@tanstack/react-table'
910
import { useCallback, useMemo } from 'react'
1011
import { Outlet, useNavigate } from 'react-router'
@@ -24,6 +25,7 @@ import { CreateLink } from '~/ui/lib/CreateButton'
2425
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
2526
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
2627
import { TableActions } from '~/ui/lib/Table'
28+
import { ALL_ISH } from '~/util/consts'
2729
import { docLinks } from '~/util/links'
2830
import { pb } from '~/util/path-builder'
2931

@@ -94,25 +96,27 @@ export default function ProjectsPage() {
9496
)
9597

9698
const columns = useColsWithActions(staticCols, makeActions)
97-
const {
98-
table,
99-
query: { data: projects },
100-
} = useQueryTable({ query: projectList, columns, emptyState: <EmptyState /> })
99+
const { table } = useQueryTable({
100+
query: projectList,
101+
columns,
102+
emptyState: <EmptyState />,
103+
})
101104

105+
const { data: allProjects } = useQuery(q(api.projectList, { query: { limit: ALL_ISH } }))
102106
useQuickActions(
103107
useMemo(
104108
() => [
105109
{
106110
value: 'New project',
107111
onSelect: () => navigate(pb.projectsNew()),
108112
},
109-
...(projects?.items || []).map((p) => ({
113+
...(allProjects?.items || []).map((p) => ({
110114
value: p.name,
111115
onSelect: () => navigate(pb.project({ project: p.name })),
112116
navGroup: 'Go to project',
113117
})),
114118
],
115-
[navigate, projects]
119+
[navigate, allProjects]
116120
)
117121
)
118122

@@ -128,7 +132,7 @@ export default function ProjectsPage() {
128132
/>
129133
</PageHeader>
130134
<TableActions>
131-
<CreateLink to={pb.projectsNew()}>New Project</CreateLink>
135+
<CreateLink to={pb.projectsNew()}>New project</CreateLink>
132136
</TableActions>
133137
{table}
134138
<Outlet />

app/pages/SiloAccessPage.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
SiloAccessEditUserSideModal,
3131
} from '~/forms/silo-access'
3232
import { useCurrentUser } from '~/hooks/use-current-user'
33+
import { useQuickActions } from '~/hooks/use-quick-actions'
3334
import { confirmDelete } from '~/stores/confirm-delete'
3435
import { addToast } from '~/stores/toast'
3536
import { getActionsCol } from '~/table/columns/action-col'
@@ -164,6 +165,18 @@ export default function SiloAccessPage() {
164165
getCoreRowModel: getCoreRowModel(),
165166
})
166167

168+
useQuickActions(
169+
useMemo(
170+
() => [
171+
{
172+
value: 'Add user or group',
173+
onSelect: () => setAddModalOpen(true),
174+
},
175+
],
176+
[]
177+
)
178+
)
179+
167180
return (
168181
<>
169182
<PageHeader>

app/pages/SiloImagesPage.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useQuery } from '@tanstack/react-query'
99
import { createColumnHelper } from '@tanstack/react-table'
1010
import { useCallback, useMemo, useState } from 'react'
1111
import { useForm } from 'react-hook-form'
12-
import { Outlet } from 'react-router'
12+
import { Outlet, useNavigate } from 'react-router'
1313

1414
import { api, getListQFn, q, queryClient, useApiMutation, type Image } from '@oxide/api'
1515
import { Images16Icon, Images24Icon } from '@oxide/design-system/icons/react'
@@ -20,6 +20,7 @@ import { toImageComboboxItem } from '~/components/form/fields/ImageSelectField'
2020
import { ListboxField } from '~/components/form/fields/ListboxField'
2121
import { ModalForm } from '~/components/form/ModalForm'
2222
import { HL } from '~/components/HL'
23+
import { useQuickActions } from '~/hooks/use-quick-actions'
2324
import { confirmDelete } from '~/stores/confirm-delete'
2425
import { addToast } from '~/stores/toast'
2526
import { makeLinkCell } from '~/table/cells/LinkCell'
@@ -32,6 +33,7 @@ import { EmptyMessage } from '~/ui/lib/EmptyMessage'
3233
import { Message } from '~/ui/lib/Message'
3334
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
3435
import { TableActions } from '~/ui/lib/Table'
36+
import { ALL_ISH } from '~/util/consts'
3537
import { docLinks } from '~/util/links'
3638
import { pb } from '~/util/path-builder'
3739

@@ -93,6 +95,26 @@ export default function SiloImagesPage() {
9395

9496
const columns = useColsWithActions(staticCols, makeActions)
9597
const { table } = useQueryTable({ query: imageList, columns, emptyState: <EmptyState /> })
98+
99+
const navigate = useNavigate()
100+
const { data: allImages } = useQuery(q(api.imageList, { query: { limit: ALL_ISH } }))
101+
useQuickActions(
102+
useMemo(
103+
() => [
104+
{
105+
value: 'Promote image',
106+
onSelect: () => setShowModal(true),
107+
},
108+
...(allImages?.items || []).map((i) => ({
109+
value: i.name,
110+
onSelect: () => navigate(pb.siloImageEdit({ image: i.name })),
111+
navGroup: 'Go to silo image',
112+
})),
113+
],
114+
[navigate, allImages]
115+
)
116+
)
117+
96118
return (
97119
<>
98120
<PageHeader>

app/pages/project/access/ProjectAccessPage.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
ProjectAccessEditUserSideModal,
3636
} from '~/forms/project-access'
3737
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
38+
import { useQuickActions } from '~/hooks/use-quick-actions'
3839
import { confirmDelete } from '~/stores/confirm-delete'
3940
import { addToast } from '~/stores/toast'
4041
import { getActionsCol } from '~/table/columns/action-col'
@@ -205,6 +206,18 @@ export default function ProjectAccessPage() {
205206
getCoreRowModel: getCoreRowModel(),
206207
})
207208

209+
useQuickActions(
210+
useMemo(
211+
() => [
212+
{
213+
value: 'Add user or group',
214+
onSelect: () => setAddModalOpen(true),
215+
},
216+
],
217+
[]
218+
)
219+
)
220+
208221
return (
209222
<>
210223
<PageHeader>

app/pages/project/affinity/AffinityPage.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
*/
88

99
import { createColumnHelper, getCoreRowModel, useReactTable } from '@tanstack/react-table'
10-
import { useCallback } from 'react'
11-
import { Outlet, type LoaderFunctionArgs } from 'react-router'
10+
import { useCallback, useMemo } from 'react'
11+
import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router'
1212

1313
import {
1414
api,
@@ -25,6 +25,7 @@ import { AffinityDocsPopover, AffinityPolicyHeader } from '~/components/Affinity
2525
import { HL } from '~/components/HL'
2626
import { antiAffinityGroupList, antiAffinityGroupMemberList } from '~/forms/affinity-util'
2727
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
28+
import { useQuickActions } from '~/hooks/use-quick-actions'
2829
import { confirmDelete } from '~/stores/confirm-delete'
2930
import { addToast } from '~/stores/toast'
3031
import { EmptyCell, SkeletonCell } from '~/table/cells/EmptyCell'
@@ -138,6 +139,25 @@ export default function AffinityPage() {
138139
getCoreRowModel: getCoreRowModel(),
139140
})
140141

142+
const navigate = useNavigate()
143+
useQuickActions(
144+
useMemo(
145+
() => [
146+
{
147+
value: 'New anti-affinity group',
148+
onSelect: () => navigate(pb.affinityNew({ project })),
149+
},
150+
...antiAffinityGroups.map((g) => ({
151+
value: g.name,
152+
onSelect: () =>
153+
navigate(pb.antiAffinityGroup({ project, antiAffinityGroup: g.name })),
154+
navGroup: 'Go to anti-affinity group',
155+
})),
156+
],
157+
[navigate, project, antiAffinityGroups]
158+
)
159+
)
160+
141161
return (
142162
<>
143163
<PageHeader>

app/pages/project/disks/DisksPage.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8+
import { useQuery } from '@tanstack/react-query'
89
import { createColumnHelper } from '@tanstack/react-table'
910
import { useCallback, useMemo } from 'react'
10-
import { Outlet, type LoaderFunctionArgs } from 'react-router'
11+
import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router'
1112

1213
import {
1314
api,
@@ -26,6 +27,7 @@ import { HL } from '~/components/HL'
2627
import { DiskStateBadge, DiskTypeBadge, ReadOnlyBadge } from '~/components/StateBadge'
2728
import { makeCrumb } from '~/hooks/use-crumbs'
2829
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
30+
import { useQuickActions } from '~/hooks/use-quick-actions'
2931
import { confirmDelete } from '~/stores/confirm-delete'
3032
import { addToast } from '~/stores/toast'
3133
import { InstanceLinkCell } from '~/table/cells/InstanceLinkCell'
@@ -37,6 +39,7 @@ import { CreateLink } from '~/ui/lib/CreateButton'
3739
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
3840
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
3941
import { TableActions } from '~/ui/lib/Table'
42+
import { ALL_ISH } from '~/util/consts'
4043
import { docLinks } from '~/util/links'
4144
import { pb } from '~/util/path-builder'
4245
import type * as PP from '~/util/path-params'
@@ -186,6 +189,27 @@ export default function DisksPage() {
186189
emptyState: <EmptyState />,
187190
})
188191

192+
const navigate = useNavigate()
193+
const { data: allDisks } = useQuery(
194+
q(api.diskList, { query: { project, limit: ALL_ISH } })
195+
)
196+
useQuickActions(
197+
useMemo(
198+
() => [
199+
{
200+
value: 'New disk',
201+
onSelect: () => navigate(pb.disksNew({ project })),
202+
},
203+
...(allDisks?.items || []).map((d) => ({
204+
value: d.name,
205+
onSelect: () => navigate(pb.disk({ project, disk: d.name })),
206+
navGroup: 'Go to disk',
207+
})),
208+
],
209+
[navigate, project, allDisks]
210+
)
211+
)
212+
189213
return (
190214
<>
191215
<PageHeader>
@@ -198,7 +222,7 @@ export default function DisksPage() {
198222
/>
199223
</PageHeader>
200224
<TableActions>
201-
<CreateLink to={pb.disksNew({ project })}>New Disk</CreateLink>
225+
<CreateLink to={pb.disksNew({ project })}>New disk</CreateLink>
202226
</TableActions>
203227
{table}
204228
<Outlet />

app/pages/project/floating-ips/FloatingIpsPage.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8+
import { useQuery } from '@tanstack/react-query'
89
import { createColumnHelper } from '@tanstack/react-table'
9-
import { useCallback, useState } from 'react'
10+
import { useCallback, useMemo, useState } from 'react'
1011
import { useForm } from 'react-hook-form'
1112
import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router'
1213

@@ -28,6 +29,7 @@ import { ModalForm } from '~/components/form/ModalForm'
2829
import { HL } from '~/components/HL'
2930
import { makeCrumb } from '~/hooks/use-crumbs'
3031
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
32+
import { useQuickActions } from '~/hooks/use-quick-actions'
3133
import { confirmAction } from '~/stores/confirm-action'
3234
import { confirmDelete } from '~/stores/confirm-delete'
3335
import { addToast } from '~/stores/toast'
@@ -211,6 +213,26 @@ export default function FloatingIpsPage() {
211213
emptyState: <EmptyState />,
212214
})
213215

216+
const { data: allFips } = useQuery(
217+
q(api.floatingIpList, { query: { project, limit: ALL_ISH } })
218+
)
219+
useQuickActions(
220+
useMemo(
221+
() => [
222+
{
223+
value: 'New floating IP',
224+
onSelect: () => navigate(pb.floatingIpsNew({ project })),
225+
},
226+
...(allFips?.items || []).map((f) => ({
227+
value: f.name,
228+
onSelect: () => navigate(pb.floatingIpEdit({ project, floatingIp: f.name })),
229+
navGroup: 'Go to floating IP',
230+
})),
231+
],
232+
[project, navigate, allFips]
233+
)
234+
)
235+
214236
return (
215237
<>
216238
<PageHeader>

app/pages/project/images/ImagesPage.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8+
import { useQuery } from '@tanstack/react-query'
89
import { createColumnHelper } from '@tanstack/react-table'
910
import { useCallback, useMemo, useState } from 'react'
10-
import { Outlet, type LoaderFunctionArgs } from 'react-router'
11+
import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router'
1112

12-
import { api, getListQFn, queryClient, useApiMutation, type Image } from '@oxide/api'
13+
import { api, getListQFn, q, queryClient, useApiMutation, type Image } from '@oxide/api'
1314
import { Images16Icon, Images24Icon } from '@oxide/design-system/icons/react'
1415

1516
import { DocsPopover } from '~/components/DocsPopover'
1617
import { HL } from '~/components/HL'
1718
import { makeCrumb } from '~/hooks/use-crumbs'
1819
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
20+
import { useQuickActions } from '~/hooks/use-quick-actions'
1921
import { confirmDelete } from '~/stores/confirm-delete'
2022
import { addToast } from '~/stores/toast'
2123
import { makeLinkCell } from '~/table/cells/LinkCell'
@@ -28,6 +30,7 @@ import { Message } from '~/ui/lib/Message'
2830
import { Modal } from '~/ui/lib/Modal'
2931
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
3032
import { TableActions } from '~/ui/lib/Table'
33+
import { ALL_ISH } from '~/util/consts'
3134
import { docLinks } from '~/util/links'
3235
import { pb } from '~/util/path-builder'
3336
import type * as PP from '~/util/path-params'
@@ -106,6 +109,27 @@ export default function ImagesPage() {
106109
emptyState: <EmptyState />,
107110
})
108111

112+
const navigate = useNavigate()
113+
const { data: allImages } = useQuery(
114+
q(api.imageList, { query: { project, limit: ALL_ISH } })
115+
)
116+
useQuickActions(
117+
useMemo(
118+
() => [
119+
{
120+
value: 'Upload image',
121+
onSelect: () => navigate(pb.projectImagesNew({ project })),
122+
},
123+
...(allImages?.items || []).map((i) => ({
124+
value: i.name,
125+
onSelect: () => navigate(pb.projectImageEdit({ project, image: i.name })),
126+
navGroup: 'Go to project image',
127+
})),
128+
],
129+
[project, navigate, allImages]
130+
)
131+
)
132+
109133
return (
110134
<>
111135
<PageHeader>

0 commit comments

Comments
 (0)