From 8f79e7efdb9bfe4fbeb4036029f3959db2e3734f Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Tue, 4 Mar 2025 16:05:17 +0000 Subject: [PATCH 01/10] First pass on new instance badges --- app/api/util.ts | 8 ++-- app/components/StateBadge.tsx | 40 +++++++++++++------ app/pages/project/instances/InstancePage.tsx | 13 +----- app/pages/project/instances/InstancesPage.tsx | 2 +- app/ui/lib/Badge.tsx | 2 +- app/ui/lib/Button.tsx | 11 ++++- app/ui/lib/Spinner.tsx | 27 ++++++++++--- app/ui/styles/components/spinner.css | 24 +++-------- 8 files changed, 71 insertions(+), 56 deletions(-) diff --git a/app/api/util.ts b/app/api/util.ts index 4018e4041b..5f0efc2e72 100644 --- a/app/api/util.ts +++ b/app/api/util.ts @@ -142,12 +142,14 @@ export const instanceCan = R.mapValues(instanceActions, (states: InstanceState[] return test }) -export function instanceTransitioning({ runState }: Instance) { +export function instanceTransitioning(runState: InstanceState) { return ( runState === 'creating' || runState === 'starting' || - runState === 'stopping' || - runState === 'rebooting' + runState === 'rebooting' || + runState === 'migrating' || + runState === 'repairing' || + runState === 'stopping' ) } diff --git a/app/components/StateBadge.tsx b/app/components/StateBadge.tsx index 79d933e1a3..356309140b 100644 --- a/app/components/StateBadge.tsx +++ b/app/components/StateBadge.tsx @@ -5,25 +5,39 @@ * * Copyright Oxide Computer Company */ -import type { DiskState, InstanceState, SnapshotState } from '@oxide/api' +import cn from 'classnames' + +import { + instanceTransitioning, + type DiskState, + type InstanceState, + type SnapshotState, +} from '@oxide/api' import { Badge, type BadgeColor, type BadgeProps } from '~/ui/lib/Badge' +import { Spinner } from '~/ui/lib/Spinner' -const INSTANCE_COLORS: Record> = { - creating: { color: 'purple', variant: 'solid' }, - starting: { color: 'blue', variant: 'solid' }, - running: { color: 'default' }, - rebooting: { color: 'notice' }, - stopping: { color: 'notice' }, - stopped: { color: 'neutral', variant: 'solid' }, - repairing: { color: 'notice', variant: 'solid' }, - migrating: { color: 'notice', variant: 'solid' }, - failed: { color: 'destructive', variant: 'solid' }, - destroyed: { color: 'neutral', variant: 'solid' }, +const INSTANCE_COLORS: Record = { + running: 'default', + stopped: 'neutral', + failed: 'destructive', + destroyed: 'destructive', + creating: 'default', + starting: 'blue', + rebooting: 'blue', + migrating: 'purple', + repairing: 'notice', + stopping: 'neutral', } export const InstanceStateBadge = (props: { state: InstanceState; className?: string }) => ( - + + {instanceTransitioning(props.state) && ( + + )} {props.state} ) diff --git a/app/pages/project/instances/InstancePage.tsx b/app/pages/project/instances/InstancePage.tsx index fc3bf8de46..d1c7bedcf9 100644 --- a/app/pages/project/instances/InstancePage.tsx +++ b/app/pages/project/instances/InstancePage.tsx @@ -48,8 +48,6 @@ 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 { pluralize } from '~/util/str' @@ -112,14 +110,6 @@ const sec = 1000 // ms, obviously const POLL_INTERVAL_FAST = 2 * sec const POLL_INTERVAL_SLOW = 30 * sec -const PollingSpinner = () => ( - - - -) - export default function InstancePage() { const instanceSelector = useInstanceSelector() const [resizeInstance, setResizeInstance] = useState(false) @@ -151,7 +141,7 @@ export default function InstancePage() { // polling on the list page. refetchInterval: ({ state: { data: instance } }) => { if (!instance) return false - if (instanceTransitioning(instance)) return POLL_INTERVAL_FAST + if (instanceTransitioning(instance.runState)) return POLL_INTERVAL_FAST if (instance.runState === 'failed' && instance.autoRestartEnabled) { return instanceAutoRestartingSoon(instance) @@ -230,7 +220,6 @@ export default function InstancePage() {
- {instanceTransitioning(instance) && }
diff --git a/app/pages/project/instances/InstancesPage.tsx b/app/pages/project/instances/InstancesPage.tsx index 2c4977ce92..785c666154 100644 --- a/app/pages/project/instances/InstancesPage.tsx +++ b/app/pages/project/instances/InstancesPage.tsx @@ -153,7 +153,7 @@ export default function InstancesPage() { const nextTransitioning = new Set( // Data will never actually be undefined because of the prefetch but whatever (data?.items || []) - .filter(instanceTransitioning) + .filter((instance) => instanceTransitioning(instance.runState)) // These are strings of instance ID + current state. This is done because // of the case where an instance is stuck in starting (for example), polling // times out, and then you manually stop it. Without putting the state in the diff --git a/app/ui/lib/Badge.tsx b/app/ui/lib/Badge.tsx index 92ffc05cd8..746c0bc9e9 100644 --- a/app/ui/lib/Badge.tsx +++ b/app/ui/lib/Badge.tsx @@ -53,7 +53,7 @@ export const Badge = ({ className={cn( 'ox-badge', `variant-${variant}`, - 'inline-flex h-4 items-center whitespace-nowrap rounded-sm px-[3px] py-[1px] uppercase text-mono-sm', + 'inline-flex h-[18px] items-center whitespace-nowrap rounded px-1 uppercase text-mono-sm', badgeColors[variant][color], className )} diff --git a/app/ui/lib/Button.tsx b/app/ui/lib/Button.tsx index e7b27f383d..f2cd3a3931 100644 --- a/app/ui/lib/Button.tsx +++ b/app/ui/lib/Button.tsx @@ -13,6 +13,8 @@ import { Spinner } from '~/ui/lib/Spinner' import { Tooltip } from '~/ui/lib/Tooltip' import { Wrap } from '~/ui/util/wrap' +import { type BadgeColor } from './Badge' + export const buttonSizes = ['sm', 'icon', 'base'] as const export const variants = ['primary', 'secondary', 'ghost', 'danger'] as const @@ -26,6 +28,13 @@ const sizeStyle: Record = { base: 'h-10 px-4 text-mono-sm [&>svg]:w-5', } +const variantToBadgeColorMap: Record = { + primary: 'default', + danger: 'destructive', + secondary: 'neutral', + ghost: 'neutral', +} + type ButtonStyleProps = { size?: ButtonSize variant?: Variant @@ -117,7 +126,7 @@ export const Button = ({ transition={{ type: 'spring', duration: 0.3, bounce: 0 }} className="absolute left-1/2 top-1/2" > - + )} = { + default: 'text-accent-secondary', + neutral: 'text-secondary', + destructive: 'text-error-secondary', + notice: 'text-notice-secondary', + purple: 'text-[--base-purple-700]', + blue: 'text-[--base-blue-700]', +} + export const Spinner = ({ className, size = 'base', - variant = 'primary', + variant = 'default', }: SpinnerProps) => { const dimensions = SPINNER_DIMENSIONS[size] const { frameSize, center, radius, strokeWidth } = dimensions @@ -56,7 +71,7 @@ export const Spinner = ({ fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="Spinner" - className={cn('spinner', `spinner-${variant}`, `spinner-${size}`, className)} + className={cn('spinner', SPINNER_COLORS[variant], `spinner-${size}`, className)} > Date: Thu, 6 Mar 2025 11:57:53 +0000 Subject: [PATCH 02/10] Disk and snapshot badges --- app/api/util.ts | 11 +++++++ app/components/StateBadge.tsx | 48 ++++++++++++++++------------ app/ui/lib/Button.tsx | 2 +- app/ui/lib/Spinner.tsx | 2 +- app/ui/styles/components/spinner.css | 5 +++ mock-api/disk.ts | 40 +++++++++++++++++++++-- mock-api/snapshot.ts | 33 +++++++++++++++++++ 7 files changed, 116 insertions(+), 25 deletions(-) diff --git a/app/api/util.ts b/app/api/util.ts index 5f0efc2e72..a3801350b0 100644 --- a/app/api/util.ts +++ b/app/api/util.ts @@ -187,6 +187,17 @@ const diskActions = { setAsBootDisk: ['attached'], } satisfies Record +export function diskTransitioning(diskState: DiskState['state']) { + return ( + diskState === 'attaching' || + diskState === 'creating' || + diskState === 'detaching' || + diskState === 'importing_from_url' || + diskState === 'importing_from_bulk_writes' || + diskState === 'finalizing' + ) +} + export const diskCan = R.mapValues(diskActions, (states: DiskState['state'][]) => { // only have to Pick because we want this to work for both Disk and // Json, which we pass to it in the MSW handlers diff --git a/app/components/StateBadge.tsx b/app/components/StateBadge.tsx index 356309140b..c56fdcc2ff 100644 --- a/app/components/StateBadge.tsx +++ b/app/components/StateBadge.tsx @@ -8,13 +8,14 @@ import cn from 'classnames' import { + diskTransitioning, instanceTransitioning, type DiskState, type InstanceState, type SnapshotState, } from '@oxide/api' -import { Badge, type BadgeColor, type BadgeProps } from '~/ui/lib/Badge' +import { Badge, type BadgeColor } from '~/ui/lib/Badge' import { Spinner } from '~/ui/lib/Spinner' const INSTANCE_COLORS: Record = { @@ -30,11 +31,10 @@ const INSTANCE_COLORS: Record = { stopping: 'neutral', } +const badgeClasses = 'children:flex children:items-center children:gap-1' + export const InstanceStateBadge = (props: { state: InstanceState; className?: string }) => ( - + {instanceTransitioning(props.state) && ( )} @@ -44,36 +44,42 @@ export const InstanceStateBadge = (props: { state: InstanceState; className?: st type DiskStateStr = DiskState['state'] -const DISK_COLORS: Record> = { - attached: { color: 'default' }, - attaching: { color: 'blue', variant: 'solid' }, - creating: { color: 'purple', variant: 'solid' }, - detaching: { color: 'notice', variant: 'solid' }, - detached: { color: 'neutral', variant: 'solid' }, - destroyed: { color: 'destructive', variant: 'solid' }, // should we ever see this? - faulted: { color: 'destructive', variant: 'solid' }, - maintenance: { color: 'notice', variant: 'solid' }, - import_ready: { color: 'blue', variant: 'solid' }, - importing_from_url: { color: 'purple', variant: 'solid' }, - importing_from_bulk_writes: { color: 'purple', variant: 'solid' }, - finalizing: { color: 'blue', variant: 'solid' }, +const DISK_COLORS: Record = { + attached: 'default', + attaching: 'blue', + creating: 'default', + detaching: 'blue', + detached: 'neutral', + destroyed: 'destructive', // should we ever see this? + faulted: 'destructive', + maintenance: 'notice', + import_ready: 'blue', + importing_from_url: 'purple', + importing_from_bulk_writes: 'purple', + finalizing: 'blue', } export const DiskStateBadge = (props: { state: DiskStateStr; className?: string }) => ( - + + {diskTransitioning(props.state) && ( + + )} {props.state} ) const SNAPSHOT_COLORS: Record = { - creating: 'notice', + creating: 'default', destroyed: 'neutral', faulted: 'destructive', ready: 'default', } export const SnapshotStateBadge = (props: { state: SnapshotState; className?: string }) => ( - + + {props.state === 'creating' && ( + + )} {props.state} ) diff --git a/app/ui/lib/Button.tsx b/app/ui/lib/Button.tsx index f2cd3a3931..bdca8d48c4 100644 --- a/app/ui/lib/Button.tsx +++ b/app/ui/lib/Button.tsx @@ -124,7 +124,7 @@ export const Button = ({ animate={{ opacity: 1, y: '-50%', x: '-50%' }} initial={{ opacity: 0, y: 'calc(-50% - 25px)', x: '-50%' }} transition={{ type: 'spring', duration: 0.3, bounce: 0 }} - className="absolute left-1/2 top-1/2" + className="absolute left-1/2 top-1/2 flex items-center justify-center" > diff --git a/app/ui/lib/Spinner.tsx b/app/ui/lib/Spinner.tsx index e7f1d643ca..993b67437d 100644 --- a/app/ui/lib/Spinner.tsx +++ b/app/ui/lib/Spinner.tsx @@ -49,7 +49,7 @@ const SPINNER_DIMENSIONS = { const SPINNER_COLORS: Record = { default: 'text-accent-secondary', neutral: 'text-secondary', - destructive: 'text-error-secondary', + destructive: 'text-destructive-secondary', notice: 'text-notice-secondary', purple: 'text-[--base-purple-700]', blue: 'text-[--base-blue-700]', diff --git a/app/ui/styles/components/spinner.css b/app/ui/styles/components/spinner.css index 636bc94bdd..f07f28dce9 100644 --- a/app/ui/styles/components/spinner.css +++ b/app/ui/styles/components/spinner.css @@ -18,6 +18,11 @@ stroke: currentColor; } +.spinner.spinner-sm { + --radius: 5; + --circumference: calc(var(--PI) * var(--radius) * 1.5px); +} + .spinner.spinner-md { --radius: 8; --circumference: calc(var(--PI) * var(--radius) * 2px); diff --git a/mock-api/disk.ts b/mock-api/disk.ts index 59a93ec826..386b0b8890 100644 --- a/mock-api/disk.ts +++ b/mock-api/disk.ts @@ -5,7 +5,7 @@ * * Copyright Oxide Computer Company */ -import type { Disk } from '@oxide/api' +import type { Disk, DiskState } from '@oxide/api' import { GiB } from '~/util/units' @@ -13,6 +13,42 @@ import { instance } from './instance' import type { Json } from './json-type' import { project, project2 } from './project' +const randomDiskState = (): DiskState => { + const states: DiskState['state'][] = [ + 'attached', + 'attaching', + 'creating', + 'detaching', + 'detached', + 'destroyed', + 'faulted', + 'maintenance', + 'import_ready', + 'importing_from_url', + 'importing_from_bulk_writes', + 'finalizing', + ] + + const state = states[Math.floor(Math.random() * states.length)] + + switch (state) { + case 'attached': + case 'attaching': + case 'detaching': + return { state, instance: instance.id } + case 'detached': + case 'creating': + case 'destroyed': + case 'faulted': + case 'maintenance': + case 'import_ready': + case 'importing_from_url': + case 'importing_from_bulk_writes': + case 'finalizing': + return { state } + } +} + export const disks: Json[] = [ { id: '7f2309a5-13e3-47e0-8a4c-2a3b3bc992fd', @@ -157,7 +193,7 @@ export const disks: Json[] = [ project_id: project2.id, time_created: new Date().toISOString(), time_modified: new Date().toISOString(), - state: { state: 'detached' as const }, + state: randomDiskState(), device_path: '/jkl', size: 12 * GiB, block_size: 2048, diff --git a/mock-api/snapshot.ts b/mock-api/snapshot.ts index 6bf129ddbf..a8ad255967 100644 --- a/mock-api/snapshot.ts +++ b/mock-api/snapshot.ts @@ -54,6 +54,39 @@ export const snapshots: Json[] = [ disk_id: disks[0].id, state: 'ready', }, + { + id: '0ad35199-357c-4df9-81c3-1b09126a6615', + name: 'faulted-snapshot', + description: '', + project_id: project.id, + time_created: new Date().toISOString(), + time_modified: new Date().toISOString(), + size: 2048, + disk_id: disks[0].id, + state: 'faulted', + }, + { + id: 'fe95fecf-26a4-4192-8869-313d576722c6', + name: 'destroyed-snapshot', + description: '', + project_id: project.id, + time_created: new Date().toISOString(), + time_modified: new Date().toISOString(), + size: 2048, + disk_id: disks[0].id, + state: 'destroyed', + }, + { + id: 'ebb31677-a435-4187-9ad1-9aaea0df077e', + name: 'creating-snapshot', + description: '', + project_id: project.id, + time_created: new Date().toISOString(), + time_modified: new Date().toISOString(), + size: 2048, + disk_id: disks[0].id, + state: 'creating', + }, { id: 'dc598369-4554-4ccd-aa89-a837e6ca487d', name: 'snapshot-4', From f115d899cab99366e58f3d1c5df7775cf549a2eb Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 6 Mar 2025 11:58:36 +0000 Subject: [PATCH 03/10] Reduce layout jumping as time changes initially --- app/components/TimeAgo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/TimeAgo.tsx b/app/components/TimeAgo.tsx index afa22d8289..612579198f 100644 --- a/app/components/TimeAgo.tsx +++ b/app/components/TimeAgo.tsx @@ -28,7 +28,7 @@ export const TimeAgo = ({ ) return ( - {timeAgoAbbr(datetime)} + {timeAgoAbbr(datetime)} ) } From 764c3d93fdc46831808353dff0dd585e03948c1d Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 6 Mar 2025 12:08:35 +0000 Subject: [PATCH 04/10] Improve mock random states --- mock-api/disk.ts | 6 +++- mock-api/msw/util.ts | 2 +- mock-api/snapshot.ts | 80 ++++++++++++++++++-------------------------- 3 files changed, 39 insertions(+), 49 deletions(-) diff --git a/mock-api/disk.ts b/mock-api/disk.ts index 386b0b8890..4d6b77e45d 100644 --- a/mock-api/disk.ts +++ b/mock-api/disk.ts @@ -11,8 +11,12 @@ import { GiB } from '~/util/units' import { instance } from './instance' import type { Json } from './json-type' +import { Rando } from './msw/util' import { project, project2 } from './project' +// Use seeded random for consistent states across runs +const rando = new Rando(0) + const randomDiskState = (): DiskState => { const states: DiskState['state'][] = [ 'attached', @@ -29,7 +33,7 @@ const randomDiskState = (): DiskState => { 'finalizing', ] - const state = states[Math.floor(Math.random() * states.length)] + const state = states[Math.floor(rando.next() * states.length)] switch (state) { case 'attached': diff --git a/mock-api/msw/util.ts b/mock-api/msw/util.ts index 0ab4b437df..fdea7f3161 100644 --- a/mock-api/msw/util.ts +++ b/mock-api/msw/util.ts @@ -151,7 +151,7 @@ export const errIfInvalidDiskSize = (disk: Json) => { } } -class Rando { +export class Rando { private a: number private c: number private m: number diff --git a/mock-api/snapshot.ts b/mock-api/snapshot.ts index a8ad255967..bb8d010974 100644 --- a/mock-api/snapshot.ts +++ b/mock-api/snapshot.ts @@ -14,8 +14,41 @@ import { GiB } from '~/util/units' import { disks } from './disk' import type { Json } from './json-type' +import { Rando } from './msw/util' import { project } from './project' +// Use seeded random for consistent states across runs +const rando = new Rando(0) + +function randomSnapshotState() { + const num = rando.next() + + // We still want it to be mostly ready states + if (num > 0.1) { + return 'ready' + } else if (num > 0.066) { + return 'destroyed' + } else if (num > 0.033) { + return 'faulted' + } else { + return 'creating' + } +} + +function generateSnapshot(index: number): Json { + return { + id: uuid(), + name: `disk-1-snapshot-${index + 8}`, + description: '', + project_id: project.id, + time_created: new Date().toISOString(), + time_modified: new Date().toISOString(), + size: 1024 * (index + 1), + disk_id: disks[0].id, + state: randomSnapshotState(), + } +} + const generatedSnapshots: Json[] = Array.from({ length: 80 }, (_, i) => generateSnapshot(i) ) @@ -54,39 +87,6 @@ export const snapshots: Json[] = [ disk_id: disks[0].id, state: 'ready', }, - { - id: '0ad35199-357c-4df9-81c3-1b09126a6615', - name: 'faulted-snapshot', - description: '', - project_id: project.id, - time_created: new Date().toISOString(), - time_modified: new Date().toISOString(), - size: 2048, - disk_id: disks[0].id, - state: 'faulted', - }, - { - id: 'fe95fecf-26a4-4192-8869-313d576722c6', - name: 'destroyed-snapshot', - description: '', - project_id: project.id, - time_created: new Date().toISOString(), - time_modified: new Date().toISOString(), - size: 2048, - disk_id: disks[0].id, - state: 'destroyed', - }, - { - id: 'ebb31677-a435-4187-9ad1-9aaea0df077e', - name: 'creating-snapshot', - description: '', - project_id: project.id, - time_created: new Date().toISOString(), - time_modified: new Date().toISOString(), - size: 2048, - disk_id: disks[0].id, - state: 'creating', - }, { id: 'dc598369-4554-4ccd-aa89-a837e6ca487d', name: 'snapshot-4', @@ -133,17 +133,3 @@ export const snapshots: Json[] = [ }, ...generatedSnapshots, ] - -function generateSnapshot(index: number): Json { - return { - id: uuid(), - name: `disk-1-snapshot-${index + 8}`, - description: '', - project_id: project.id, - time_created: new Date().toISOString(), - time_modified: new Date().toISOString(), - size: 1024 * (index + 1), - disk_id: disks[0].id, - state: 'ready', - } -} From 4d7906702f86ea421a96f641c8279b6dcf0c3499 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 6 Mar 2025 12:21:19 +0000 Subject: [PATCH 05/10] Mock data tweaks --- mock-api/disk.ts | 4 ++-- mock-api/msw/rando.ts | 18 ++++++++++++++++++ mock-api/msw/util.ts | 20 +------------------- mock-api/snapshot.ts | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 mock-api/msw/rando.ts diff --git a/mock-api/disk.ts b/mock-api/disk.ts index 4d6b77e45d..f119d086c8 100644 --- a/mock-api/disk.ts +++ b/mock-api/disk.ts @@ -11,7 +11,7 @@ import { GiB } from '~/util/units' import { instance } from './instance' import type { Json } from './json-type' -import { Rando } from './msw/util' +import { Rando } from './msw/rando' import { project, project2 } from './project' // Use seeded random for consistent states across runs @@ -39,7 +39,7 @@ const randomDiskState = (): DiskState => { case 'attached': case 'attaching': case 'detaching': - return { state, instance: instance.id } + return { state, instance: 'ac8aff03-bd10-4adf-acca-d5db8e8b2a4c' } case 'detached': case 'creating': case 'destroyed': diff --git a/mock-api/msw/rando.ts b/mock-api/msw/rando.ts new file mode 100644 index 0000000000..fa7944eb34 --- /dev/null +++ b/mock-api/msw/rando.ts @@ -0,0 +1,18 @@ +export class Rando { + private a: number + private c: number + private m: number + private seed: number + + constructor(seed: number, a = 1664525, c = 1013904223, m = 2 ** 32) { + this.seed = seed + this.a = a + this.c = c + this.m = m + } + + public next(): number { + this.seed = (this.a * this.seed + this.c) % this.m + return this.seed / this.m + } +} diff --git a/mock-api/msw/util.ts b/mock-api/msw/util.ts index fdea7f3161..e469492c43 100644 --- a/mock-api/msw/util.ts +++ b/mock-api/msw/util.ts @@ -35,6 +35,7 @@ import { GiB, TiB } from '~/util/units' import type { DbRoleAssignmentResourceType } from '..' import { genI64Data } from '../metrics' import { db } from './db' +import { Rando } from './rando' interface PaginateOptions { limit?: number @@ -151,25 +152,6 @@ export const errIfInvalidDiskSize = (disk: Json) => { } } -export class Rando { - private a: number - private c: number - private m: number - private seed: number - - constructor(seed: number, a = 1664525, c = 1013904223, m = 2 ** 32) { - this.seed = seed - this.a = a - this.c = c - this.m = m - } - - public next(): number { - this.seed = (this.a * this.seed + this.c) % this.m - return this.seed / this.m - } -} - export function generateUtilization( metricName: SystemMetricName, startTime: Date, diff --git a/mock-api/snapshot.ts b/mock-api/snapshot.ts index bb8d010974..0efea2e423 100644 --- a/mock-api/snapshot.ts +++ b/mock-api/snapshot.ts @@ -14,7 +14,7 @@ import { GiB } from '~/util/units' import { disks } from './disk' import type { Json } from './json-type' -import { Rando } from './msw/util' +import { Rando } from './msw/rando' import { project } from './project' // Use seeded random for consistent states across runs From e0af818c2fde1d1c67582d79316fd49613e59034 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 6 Mar 2025 12:23:22 +0000 Subject: [PATCH 06/10] Missing license --- mock-api/msw/rando.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mock-api/msw/rando.ts b/mock-api/msw/rando.ts index fa7944eb34..437ad617ec 100644 --- a/mock-api/msw/rando.ts +++ b/mock-api/msw/rando.ts @@ -1,3 +1,10 @@ +/* + * 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 + */ export class Rando { private a: number private c: number From 151b8f57dc0d4d3fecd22af4b62d622c2a419e5c Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 6 Mar 2025 14:18:06 +0000 Subject: [PATCH 07/10] Test fix --- test/e2e/instance-create.e2e.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/e2e/instance-create.e2e.ts b/test/e2e/instance-create.e2e.ts index 878d67557d..74bb3c729e 100644 --- a/test/e2e/instance-create.e2e.ts +++ b/test/e2e/instance-create.e2e.ts @@ -509,25 +509,25 @@ test('attaching additional disks allows for combobox filtering', async ({ page } await attachExistingDiskButton.click() await selectADisk.click() // several disks should be shown - await expect(page.getByRole('option', { name: 'disk-0001' })).toBeVisible() - await expect(page.getByRole('option', { name: 'disk-0002' })).toBeVisible() - await expect(page.getByRole('option', { name: 'disk-1000' })).toBeVisible() + await expect(page.getByRole('option', { name: 'disk-0005' })).toBeVisible() + await expect(page.getByRole('option', { name: 'disk-0007' })).toBeVisible() + await expect(page.getByRole('option', { name: 'disk-0988' })).toBeVisible() // type in a string to use as a filter - await selectADisk.fill('disk-010') + await selectADisk.fill('disk-02') // only disks with that substring should be shown - await expect(page.getByRole('option', { name: 'disk-0100' })).toBeVisible() - await expect(page.getByRole('option', { name: 'disk-0101' })).toBeVisible() - await expect(page.getByRole('option', { name: 'disk-0102' })).toBeVisible() - await expect(page.getByRole('option', { name: 'disk-0001' })).toBeHidden() + await expect(page.getByRole('option', { name: 'disk-0023' })).toBeVisible() + await expect(page.getByRole('option', { name: 'disk-0125' })).toBeVisible() + await expect(page.getByRole('option', { name: 'disk-0211' })).toBeVisible() + await expect(page.getByRole('option', { name: 'disk-0220' })).toBeHidden() await expect(page.getByRole('option', { name: 'disk-1000' })).toBeHidden() // select one - await page.getByRole('option', { name: 'disk-0102' }).click() + await page.getByRole('option', { name: 'disk-0211' }).click() // now options hidden and only the selected one is visible in the button/input await expect(page.getByRole('option')).toBeHidden() - await expect(page.getByRole('combobox', { name: 'Disk name' })).toHaveValue('disk-0102') + await expect(page.getByRole('combobox', { name: 'Disk name' })).toHaveValue('disk-0211') // a random string should give a disabled option await selectADisk.click() From a1ae45771299531210e8113a19e09c3759eae343 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 6 Mar 2025 18:18:14 +0000 Subject: [PATCH 08/10] Fix missing instance with mock data --- mock-api/disk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock-api/disk.ts b/mock-api/disk.ts index f119d086c8..b277b6746a 100644 --- a/mock-api/disk.ts +++ b/mock-api/disk.ts @@ -39,7 +39,7 @@ const randomDiskState = (): DiskState => { case 'attached': case 'attaching': case 'detaching': - return { state, instance: 'ac8aff03-bd10-4adf-acca-d5db8e8b2a4c' } + return { state, instance: '32a0249f-3a5c-4d30-a154-2476e372aa62' } case 'detached': case 'creating': case 'destroyed': From 73f4be9a03c6cb3ee9bbc63b9a4f295990bad809 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 6 Mar 2025 18:18:24 +0000 Subject: [PATCH 09/10] Replace `_` in disk states --- app/components/StateBadge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/StateBadge.tsx b/app/components/StateBadge.tsx index c56fdcc2ff..a53cab8b63 100644 --- a/app/components/StateBadge.tsx +++ b/app/components/StateBadge.tsx @@ -64,7 +64,7 @@ export const DiskStateBadge = (props: { state: DiskStateStr; className?: string {diskTransitioning(props.state) && ( )} - {props.state} + {props.state.replace(/_/g, ' ')}
) From 639648ece6bce71c8ee6a5256e43d29de4caa7d6 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Thu, 6 Mar 2025 18:24:23 +0000 Subject: [PATCH 10/10] Remove non-transitional states --- app/api/util.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/api/util.ts b/app/api/util.ts index a3801350b0..305ee90a02 100644 --- a/app/api/util.ts +++ b/app/api/util.ts @@ -148,7 +148,6 @@ export function instanceTransitioning(runState: InstanceState) { runState === 'starting' || runState === 'rebooting' || runState === 'migrating' || - runState === 'repairing' || runState === 'stopping' ) } @@ -192,8 +191,6 @@ export function diskTransitioning(diskState: DiskState['state']) { diskState === 'attaching' || diskState === 'creating' || diskState === 'detaching' || - diskState === 'importing_from_url' || - diskState === 'importing_from_bulk_writes' || diskState === 'finalizing' ) }