From 7a609c84f0e898fdc7710219929497992b47f89c Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Wed, 9 Oct 2024 13:49:19 +0100 Subject: [PATCH 1/8] Switch to regular modal --- app/forms/instance-resize.tsx | 120 ++++++++++++++++++++++++++++------ 1 file changed, 99 insertions(+), 21 deletions(-) diff --git a/app/forms/instance-resize.tsx b/app/forms/instance-resize.tsx index 9d916a10e0..dea1e0c39c 100644 --- a/app/forms/instance-resize.tsx +++ b/app/forms/instance-resize.tsx @@ -7,20 +7,24 @@ */ import { useForm } from 'react-hook-form' import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom' -import * as R from 'remeda' import { apiQueryClient, + INSTANCE_MAX_CPU, + INSTANCE_MAX_RAM_GiB, + instanceCan, useApiMutation, useApiQueryClient, usePrefetchedApiQuery, } from '@oxide/api' import { NumberField } from '~/components/form/fields/NumberField' -import { SideModalForm } from '~/components/form/SideModalForm' import { getInstanceSelector, useInstanceSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' +import { Message } from '~/ui/lib/Message' +import { Modal } from '~/ui/lib/Modal' import { pb } from '~/util/path-builder' +import { GiB } from '~/util/units' InstanceResizeForm.loader = async ({ params }: LoaderFunctionArgs) => { const { project, instance } = getInstanceSelector(params) @@ -41,35 +45,109 @@ export function InstanceResizeForm() { query: { project }, }) + const onDismiss = () => navigate(pb.instance({ project, instance: instanceName })) + const instanceUpdate = useApiMutation('instanceUpdate', { onSuccess(_updatedInstance) { queryClient.invalidateQueries('instanceView') navigate(pb.instance({ project, instance: instanceName })) - addToast({ title: 'Instance updated' }) + addToast({ title: 'Instance resized' }) + }, + onError: (err) => { + addToast({ title: 'Error', content: err.message, variant: 'error' }) }, + onSettled: onDismiss, }) - const form = useForm({ defaultValues: R.pick(instance, ['ncpus', 'memory']) }) + const form = useForm({ + defaultValues: { + ncpus: instance.ncpus, + memory: instance.memory / GiB, // memory is stored as bytes + }, + mode: 'onChange', + }) + + const canResize = instanceCan.update(instance) + const isDisabled = !form.formState.isValid || !canResize + + const onAction = form.handleSubmit(({ ncpus, memory }) => { + instanceUpdate.mutate({ + path: { instance: instanceName }, + query: { project }, + body: { ncpus, memory: memory * GiB, bootDisk: instance.bootDiskId }, + }) + }) return ( - navigate(pb.instance({ project, instance: instanceName }))} - onSubmit={({ ncpus, memory }) => { - instanceUpdate.mutate({ - path: { instance: instanceName }, - query: { project }, - // very important to include the boot disk or it will be unset - body: { ncpus, memory, bootDisk: instance.bootDiskId }, - }) - }} - loading={instanceUpdate.isPending} - submitError={instanceUpdate.error} > - - - + + + {!canResize ? ( + + ) : ( + + Currently using: {instance.ncpus} vCPUs /{' '} + {instance.memory / GiB} GiB + + } + /> + )} +
+ { + if (cpus < 1) { + return `Must be at least 1 vCPU` + } + if (cpus > INSTANCE_MAX_CPU) { + return `CPUs capped to ${INSTANCE_MAX_CPU}` + } + // We can show this error and therefore inform the user + // of the limit rather than preventing it completely + }} + disabled={!canResize} + /> + { + if (memory < 1) { + return `Must be at least 1 GiB` + } + if (memory > INSTANCE_MAX_RAM_GiB) { + return `Can be at most ${INSTANCE_MAX_RAM_GiB} GiB` + } + }} + disabled={!canResize} + /> + + {instanceUpdate.error && ( +

{instanceUpdate.error.message}

+ )} +
+
+ + ) } From cb7ef8292b21d5f56669f9114747bc7609cefb94 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Wed, 9 Oct 2024 13:56:43 +0100 Subject: [PATCH 2/8] Improve modal close button alignment --- app/ui/lib/Modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ui/lib/Modal.tsx b/app/ui/lib/Modal.tsx index 3994420c02..6f444baf97 100644 --- a/app/ui/lib/Modal.tsx +++ b/app/ui/lib/Modal.tsx @@ -75,7 +75,7 @@ export function Modal({ children, onDismiss, title, isOpen }: ModalProps) { )} {children} From be1d328a14853884f31381da10648d50d9e74eef Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Wed, 9 Oct 2024 14:06:43 +0100 Subject: [PATCH 3/8] Semi 'currently using:' and fix semi weight --- app/forms/instance-resize.tsx | 4 ++-- app/ui/styles/fonts.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/forms/instance-resize.tsx b/app/forms/instance-resize.tsx index dea1e0c39c..821d389252 100644 --- a/app/forms/instance-resize.tsx +++ b/app/forms/instance-resize.tsx @@ -93,8 +93,8 @@ export function InstanceResizeForm() { variant="info" content={ <> - Currently using: {instance.ncpus} vCPUs /{' '} - {instance.memory / GiB} GiB + Currently using:{' '} + {instance.ncpus} vCPUs / {instance.memory / GiB} GiB } /> diff --git a/app/ui/styles/fonts.css b/app/ui/styles/fonts.css index ea1d311a29..5b71957367 100644 --- a/app/ui/styles/fonts.css +++ b/app/ui/styles/fonts.css @@ -60,6 +60,6 @@ src: url('../assets/fonts/SuisseIntl-Book-WebS.woff2') format('woff2'), url('../assets/fonts/SuisseIntl-Book-WebS.woff') format('woff'); - font-weight: 600; + font-weight: 500; font-style: normal; } From aebd90f7ebf6c8e76f0d1cfc871101ec4601f87e Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Wed, 9 Oct 2024 14:22:03 +0100 Subject: [PATCH 4/8] Revert: fix semi weight Handled in #2496 instead --- app/ui/styles/fonts.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ui/styles/fonts.css b/app/ui/styles/fonts.css index 5b71957367..ea1d311a29 100644 --- a/app/ui/styles/fonts.css +++ b/app/ui/styles/fonts.css @@ -60,6 +60,6 @@ src: url('../assets/fonts/SuisseIntl-Book-WebS.woff2') format('woff2'), url('../assets/fonts/SuisseIntl-Book-WebS.woff') format('woff'); - font-weight: 500; + font-weight: 600; font-style: normal; } From b53885227b79f102b8e3fb815048aba0c9fe5f6b Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 10 Oct 2024 12:21:40 +0100 Subject: [PATCH 5/8] Switch route for modal --- app/forms/instance-resize.tsx | 153 ------------------ app/pages/project/instances/InstancesPage.tsx | 18 ++- app/pages/project/instances/actions.tsx | 12 +- .../instances/instance/InstancePage.tsx | 142 +++++++++++++++- app/routes.tsx | 12 -- app/util/path-builder.spec.ts | 1 - app/util/path-builder.ts | 1 - 7 files changed, 160 insertions(+), 179 deletions(-) delete mode 100644 app/forms/instance-resize.tsx diff --git a/app/forms/instance-resize.tsx b/app/forms/instance-resize.tsx deleted file mode 100644 index 821d389252..0000000000 --- a/app/forms/instance-resize.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 { useForm } from 'react-hook-form' -import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom' - -import { - apiQueryClient, - INSTANCE_MAX_CPU, - INSTANCE_MAX_RAM_GiB, - instanceCan, - useApiMutation, - useApiQueryClient, - usePrefetchedApiQuery, -} from '@oxide/api' - -import { NumberField } from '~/components/form/fields/NumberField' -import { getInstanceSelector, useInstanceSelector } from '~/hooks/use-params' -import { addToast } from '~/stores/toast' -import { Message } from '~/ui/lib/Message' -import { Modal } from '~/ui/lib/Modal' -import { pb } from '~/util/path-builder' -import { GiB } from '~/util/units' - -InstanceResizeForm.loader = async ({ params }: LoaderFunctionArgs) => { - const { project, instance } = getInstanceSelector(params) - await apiQueryClient.prefetchQuery('instanceView', { - path: { instance }, - query: { project }, - }) - return null -} - -export function InstanceResizeForm() { - const { instance: instanceName, project } = useInstanceSelector() - const queryClient = useApiQueryClient() - const navigate = useNavigate() - - const { data: instance } = usePrefetchedApiQuery('instanceView', { - path: { instance: instanceName }, - query: { project }, - }) - - const onDismiss = () => navigate(pb.instance({ project, instance: instanceName })) - - const instanceUpdate = useApiMutation('instanceUpdate', { - onSuccess(_updatedInstance) { - queryClient.invalidateQueries('instanceView') - navigate(pb.instance({ project, instance: instanceName })) - addToast({ title: 'Instance resized' }) - }, - onError: (err) => { - addToast({ title: 'Error', content: err.message, variant: 'error' }) - }, - onSettled: onDismiss, - }) - - const form = useForm({ - defaultValues: { - ncpus: instance.ncpus, - memory: instance.memory / GiB, // memory is stored as bytes - }, - mode: 'onChange', - }) - - const canResize = instanceCan.update(instance) - const isDisabled = !form.formState.isValid || !canResize - - const onAction = form.handleSubmit(({ ncpus, memory }) => { - instanceUpdate.mutate({ - path: { instance: instanceName }, - query: { project }, - body: { ncpus, memory: memory * GiB, bootDisk: instance.bootDiskId }, - }) - }) - - return ( - navigate(pb.instance({ project, instance: instanceName }))} - > - - - {!canResize ? ( - - ) : ( - - Currently using:{' '} - {instance.ncpus} vCPUs / {instance.memory / GiB} GiB - - } - /> - )} -
- { - if (cpus < 1) { - return `Must be at least 1 vCPU` - } - if (cpus > INSTANCE_MAX_CPU) { - return `CPUs capped to ${INSTANCE_MAX_CPU}` - } - // We can show this error and therefore inform the user - // of the limit rather than preventing it completely - }} - disabled={!canResize} - /> - { - if (memory < 1) { - return `Must be at least 1 GiB` - } - if (memory > INSTANCE_MAX_RAM_GiB) { - return `Can be at most ${INSTANCE_MAX_RAM_GiB} GiB` - } - }} - disabled={!canResize} - /> - - {instanceUpdate.error && ( -

{instanceUpdate.error.message}

- )} -
-
- -
- ) -} diff --git a/app/pages/project/instances/InstancesPage.tsx b/app/pages/project/instances/InstancesPage.tsx index 343f4c1871..9af371b8d7 100644 --- a/app/pages/project/instances/InstancesPage.tsx +++ b/app/pages/project/instances/InstancesPage.tsx @@ -7,7 +7,7 @@ */ import { createColumnHelper } from '@tanstack/react-table' import { filesize } from 'filesize' -import { useMemo, useRef } from 'react' +import { useMemo, useRef, useState } from 'react' import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, usePrefetchedApiQuery, type Instance } from '@oxide/api' @@ -33,6 +33,7 @@ import { toLocaleTimeString } from '~/util/date' import { pb } from '~/util/path-builder' import { useMakeInstanceActions } from './actions' +import { ResizeInstanceModal } from './instance/InstancePage' const EmptyState = () => ( (null) const makeActions = useMakeInstanceActions( { project }, - { onSuccess: refetchInstances, onDelete: refetchInstances } + { + onSuccess: refetchInstances, + onDelete: refetchInstances, + onResizeClick: (instance) => setResizeInstance(instance), + } ) // this is a whole thing. sit down. @@ -212,6 +218,14 @@ export function InstancesPage() { New Instance } /> + {resizeInstance && ( + setResizeInstance(null)} + onSuccess={() => apiQueryClient.invalidateQueries('instanceList')} + /> + )} ) } diff --git a/app/pages/project/instances/actions.tsx b/app/pages/project/instances/actions.tsx index 79d7a7accc..672285e165 100644 --- a/app/pages/project/instances/actions.tsx +++ b/app/pages/project/instances/actions.tsx @@ -6,7 +6,6 @@ * Copyright Oxide Computer Company */ import { useCallback } from 'react' -import { useNavigate } from 'react-router-dom' import { instanceCan, useApiMutation, type Instance } from '@oxide/api' @@ -15,7 +14,6 @@ import { confirmAction } from '~/stores/confirm-action' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' import type { MakeActions } from '~/table/columns/action-col' -import { pb } from '~/util/path-builder' import { fancifyStates } from './instance/tabs/common' @@ -26,14 +24,13 @@ type Options = { // hook has to expand to encompass the sum of all the APIs of these hooks it // call internally, the abstraction is not good onDelete?: () => void + onResizeClick?: (instance: Instance) => void } export const useMakeInstanceActions = ( { project }: { project: string }, options: Options = {} ): MakeActions => { - const navigate = useNavigate() - // if you also pass onSuccess to mutate(), this one is not overridden — this // one runs first, then the one passed to mutate(). // @@ -51,7 +48,6 @@ export const useMakeInstanceActions = ( return useCallback( (instance) => { - const instanceSelector = { project, instance: instance.name } const instanceParams = { path: { instance: instance.name }, query: { project } } return [ { @@ -119,8 +115,8 @@ export const useMakeInstanceActions = ( }, { label: 'Resize', - onActivate() { - navigate(pb.instanceResize(instanceSelector)) + onActivate: () => { + options.onResizeClick && options.onResizeClick(instance) }, disabled: !instanceCan.update(instance) && ( <>Only {fancifyStates(instanceCan.update.states)} instances can be resized @@ -147,11 +143,11 @@ export const useMakeInstanceActions = ( }, [ project, - navigate, deleteInstanceAsync, rebootInstance, startInstance, stopInstanceAsync, + options, ] ) } diff --git a/app/pages/project/instances/instance/InstancePage.tsx b/app/pages/project/instances/instance/InstancePage.tsx index 8e1a69ca3e..6ff4d3471c 100644 --- a/app/pages/project/instances/instance/InstancePage.tsx +++ b/app/pages/project/instances/instance/InstancePage.tsx @@ -6,33 +6,46 @@ * Copyright Oxide Computer Company */ import { filesize } from 'filesize' -import { useMemo } from 'react' +import { useMemo, useState } from 'react' +import { useForm } from 'react-hook-form' import { Link, useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import { apiQueryClient, + useApiMutation, useApiQuery, usePrefetchedApiQuery, + type Instance, type InstanceNetworkInterface, } from '@oxide/api' import { Instances24Icon } from '@oxide/design-system/icons/react' -import { instanceTransitioning } from '~/api/util' +import { + INSTANCE_MAX_CPU, + INSTANCE_MAX_RAM_GiB, + instanceCan, + instanceTransitioning, +} from '~/api/util' import { ExternalIps } from '~/components/ExternalIps' +import { NumberField } from '~/components/form/fields/NumberField' import { InstanceDocsPopover } from '~/components/InstanceDocsPopover' import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { RefreshButton } from '~/components/RefreshButton' import { RouteTabs, Tab } from '~/components/RouteTabs' import { InstanceStateBadge } from '~/components/StateBadge' import { getInstanceSelector, useInstanceSelector } from '~/hooks/use-params' +import { addToast } from '~/stores/toast' import { EmptyCell } from '~/table/cells/EmptyCell' import { DateTime } from '~/ui/lib/DateTime' +import { Message } from '~/ui/lib/Message' +import { Modal } from '~/ui/lib/Modal' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { Spinner } from '~/ui/lib/Spinner' import { Tooltip } from '~/ui/lib/Tooltip' import { Truncate } from '~/ui/lib/Truncate' import { pb } from '~/util/path-builder' +import { GiB } from '~/util/units' import { useMakeInstanceActions } from '../actions' @@ -90,6 +103,7 @@ const POLL_INTERVAL = 1000 export function InstancePage() { const instanceSelector = useInstanceSelector() + const [resizeInstance, setResizeInstance] = useState(false) const navigate = useNavigate() const makeActions = useMakeInstanceActions(instanceSelector, { @@ -99,6 +113,7 @@ export function InstancePage() { apiQueryClient.invalidateQueries('instanceList') navigate(pb.instances(instanceSelector)) }, + onResizeClick: () => setResizeInstance(true), }) const { data: instance } = usePrefetchedApiQuery( @@ -217,6 +232,129 @@ export function InstancePage() { Networking Connect + {resizeInstance && ( + setResizeInstance(false)} + onSuccess={() => apiQueryClient.invalidateQueries('instanceView')} + /> + )} ) } + +export function ResizeInstanceModal({ + instance, + project, + onDismiss, + onSuccess, +}: { + instance: Instance + project: string + onDismiss: () => void + onSuccess: () => void +}) { + const instanceUpdate = useApiMutation('instanceUpdate', { + onSuccess(_updatedInstance) { + onSuccess() + onDismiss() + addToast({ title: 'Instance resized' }) + }, + onError: (err) => { + addToast({ title: 'Error', content: err.message, variant: 'error' }) + }, + onSettled: onDismiss, + }) + + const form = useForm({ + defaultValues: { + ncpus: instance.ncpus, + memory: instance.memory / GiB, // memory is stored as bytes + }, + mode: 'onChange', + }) + + const canResize = instanceCan.update(instance) + const isDisabled = !form.formState.isValid || !canResize + + const onAction = form.handleSubmit(({ ncpus, memory }) => { + instanceUpdate.mutate({ + path: { instance: instance.name }, + query: { project }, + body: { ncpus, memory: memory * GiB, bootDisk: instance.bootDiskId }, + }) + }) + + return ( + + + + {!canResize ? ( + + ) : ( + +
+ {instance.ncpus}{' '} + vCPUs / {instance.memory / GiB} GiB +
+ + } + /> + )} +
+ { + if (cpus < 1) { + return `Must be at least 1 vCPU` + } + if (cpus > INSTANCE_MAX_CPU) { + return `CPUs capped to ${INSTANCE_MAX_CPU}` + } + // We can show this error and therefore inform the user + // of the limit rather than preventing it completely + }} + disabled={!canResize} + /> + { + if (memory < 1) { + return `Must be at least 1 GiB` + } + if (memory > INSTANCE_MAX_RAM_GiB) { + return `Can be at most ${INSTANCE_MAX_RAM_GiB} GiB` + } + }} + disabled={!canResize} + /> + + {instanceUpdate.error && ( +

{instanceUpdate.error.message}

+ )} +
+
+ +
+ ) +} diff --git a/app/routes.tsx b/app/routes.tsx index 2f1377547b..86b159f5fa 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -23,7 +23,6 @@ import { import { CreateImageFromSnapshotSideModalForm } from './forms/image-from-snapshot' import { CreateImageSideModalForm } from './forms/image-upload' import { CreateInstanceForm } from './forms/instance-create' -import { InstanceResizeForm } from './forms/instance-resize' import { CreateIpPoolSideModalForm } from './forms/ip-pool-create' import { EditIpPoolSideModalForm } from './forms/ip-pool-edit' import { IpPoolAddRangeSideModalForm } from './forms/ip-pool-range-add' @@ -320,17 +319,6 @@ export const routes = createRoutesFromElements( loader={StorageTab.loader} handle={{ crumb: 'Storage' }} /> - - - - - } - loader={StorageTab.loader} - handle={{ crumb: 'Resize' }} - /> } diff --git a/app/util/path-builder.spec.ts b/app/util/path-builder.spec.ts index 0f5789256d..76e39846ef 100644 --- a/app/util/path-builder.spec.ts +++ b/app/util/path-builder.spec.ts @@ -44,7 +44,6 @@ test('path builder', () => { "instanceConnect": "/projects/p/instances/i/connect", "instanceMetrics": "/projects/p/instances/i/metrics", "instanceNetworking": "/projects/p/instances/i/networking", - "instanceResize": "/projects/p/instances/i/resize", "instanceStorage": "/projects/p/instances/i/storage", "instances": "/projects/p/instances", "instancesNew": "/projects/p/instances-new", diff --git a/app/util/path-builder.ts b/app/util/path-builder.ts index b135a17dd6..1709b19c59 100644 --- a/app/util/path-builder.ts +++ b/app/util/path-builder.ts @@ -62,7 +62,6 @@ export const pb = { instanceStorage: (params: Instance) => `${instanceBase(params)}/storage`, instanceConnect: (params: Instance) => `${instanceBase(params)}/connect`, instanceNetworking: (params: Instance) => `${instanceBase(params)}/networking`, - instanceResize: (params: Instance) => `${instanceBase(params)}/resize`, serialConsole: (params: Instance) => `${instanceBase(params)}/serial-console`, disksNew: (params: Project) => `${projectBase(params)}/disks-new`, From de46cd059b7a67b0f75823bc691a8a710c9f52ad Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 10 Oct 2024 12:24:03 +0100 Subject: [PATCH 6/8] Disable submit if the specs are the same --- app/pages/project/instances/instance/InstancePage.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/pages/project/instances/instance/InstancePage.tsx b/app/pages/project/instances/instance/InstancePage.tsx index 6ff4d3471c..03a0929627 100644 --- a/app/pages/project/instances/instance/InstancePage.tsx +++ b/app/pages/project/instances/instance/InstancePage.tsx @@ -276,7 +276,9 @@ export function ResizeInstanceModal({ }) const canResize = instanceCan.update(instance) - const isDisabled = !form.formState.isValid || !canResize + const willChange = + form.watch('ncpus') !== instance.ncpus || form.watch('memory') !== instance.memory / GiB + const isDisabled = !form.formState.isValid || !canResize || !willChange const onAction = form.handleSubmit(({ ncpus, memory }) => { instanceUpdate.mutate({ From 1d5ad81f4b8ea251779e8c5cf9f0ac17abc4c8ad Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 10 Oct 2024 12:52:32 +0100 Subject: [PATCH 7/8] Improve toast --- app/pages/project/instances/InstancesPage.tsx | 2 +- .../instances/instance/InstancePage.tsx | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/pages/project/instances/InstancesPage.tsx b/app/pages/project/instances/InstancesPage.tsx index 9af371b8d7..9fce0627a1 100644 --- a/app/pages/project/instances/InstancesPage.tsx +++ b/app/pages/project/instances/InstancesPage.tsx @@ -223,7 +223,7 @@ export function InstancesPage() { instance={resizeInstance} project={project} onDismiss={() => setResizeInstance(null)} - onSuccess={() => apiQueryClient.invalidateQueries('instanceList')} + onListView /> )} diff --git a/app/pages/project/instances/instance/InstancePage.tsx b/app/pages/project/instances/instance/InstancePage.tsx index 03a0929627..6a892136c4 100644 --- a/app/pages/project/instances/instance/InstancePage.tsx +++ b/app/pages/project/instances/instance/InstancePage.tsx @@ -237,7 +237,6 @@ export function InstancePage() { instance={instance} project={instanceSelector.project} onDismiss={() => setResizeInstance(false)} - onSuccess={() => apiQueryClient.invalidateQueries('instanceView')} /> )} @@ -248,18 +247,30 @@ export function ResizeInstanceModal({ instance, project, onDismiss, - onSuccess, + onListView = false, }: { instance: Instance project: string onDismiss: () => void - onSuccess: () => void + onListView?: boolean }) { const instanceUpdate = useApiMutation('instanceUpdate', { onSuccess(_updatedInstance) { - onSuccess() + if (onListView) { + apiQueryClient.invalidateQueries('instanceList') + } else { + apiQueryClient.invalidateQueries('instanceView') + } onDismiss() - addToast({ title: 'Instance resized' }) + addToast({ + content: `${instance.name} has been resized`, + cta: onListView + ? { + text: `View instance`, + link: pb.instance({ project, instance: instance.name }), + } + : undefined, // Only link to the instance if we're not already on that page + }) }, onError: (err) => { addToast({ title: 'Error', content: err.message, variant: 'error' }) From e659493a1d388773ebc34df6394560b5587d81a9 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 10 Oct 2024 16:43:19 +0100 Subject: [PATCH 8/8] Fix lint error `no-unused-expressions` --- app/pages/project/instances/actions.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/pages/project/instances/actions.tsx b/app/pages/project/instances/actions.tsx index 672285e165..37e59a4757 100644 --- a/app/pages/project/instances/actions.tsx +++ b/app/pages/project/instances/actions.tsx @@ -116,7 +116,9 @@ export const useMakeInstanceActions = ( { label: 'Resize', onActivate: () => { - options.onResizeClick && options.onResizeClick(instance) + if (options.onResizeClick) { + options.onResizeClick(instance) + } }, disabled: !instanceCan.update(instance) && ( <>Only {fancifyStates(instanceCan.update.states)} instances can be resized