Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions app/forms/instance-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ import {
TextField,
type DiskTableItem,
} from 'app/components/form'
import { getProjectSelector, useForm, useProjectSelector, useToast } from 'app/hooks'
import {
getProjectSelector,
useForm,
useInstanceTemplate,
useProjectSelector,
useToast,
} from 'app/hooks'
import { readBlobAsBase64 } from 'app/util/file'
import { links } from 'app/util/links'
import { pb } from 'app/util/path-builder'
Expand All @@ -71,7 +77,7 @@ export type InstanceCreateInput = Assign<
}
>

const baseDefaultValues: InstanceCreateInput = {
export const baseDefaultValues: InstanceCreateInput = {
name: '',
description: '',
/**
Expand Down Expand Up @@ -144,6 +150,8 @@ export function CreateInstanceForm() {
const form = useForm({ defaultValues })
const { control, setValue } = form

useInstanceTemplate(setValue)

const imageInput = useWatch({ control: control, name: 'image' })
const image = allImages.find((i) => i.id === imageInput)
const imageSize = image?.size ? Math.ceil(image.size / GiB) : undefined
Expand Down Expand Up @@ -560,7 +568,7 @@ const renderLargeRadioCards = (category: string) => {
))
}

const PRESETS = [
export const PRESETS = [
{ category: 'general', id: 'general-xs', memory: 8, ncpus: 2 },
{ category: 'general', id: 'general-sm', memory: 16, ncpus: 4 },
{ category: 'general', id: 'general-md', memory: 32, ncpus: 8 },
Expand Down
1 change: 1 addition & 0 deletions app/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from './use-params'
export * from './use-quick-actions'
export * from './use-reduce-motion'
export * from './use-toast'
export * from './use-template'
119 changes: 119 additions & 0 deletions app/hooks/use-template.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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 { useEffect } from 'react'
import { type FieldValues, type Path, type UseFormSetValue } from 'react-hook-form'
import { useSearchParams } from 'react-router-dom'

import { genName, useApiQuery, type BlockSize } from '@oxide/api'
import { bytesToGiB } from '@oxide/util'

import type { DiskTableItem } from 'app/components/form'
import {
baseDefaultValues as instanceDefaultValues,
type InstanceCreateInput,
} from 'app/forms/instance-create'

function incrementName(str: string) {
let name = str
const match = name.match(/(.*)-(\d+)$/)

if (match) {
const base = match[1]
const num = parseInt(match[2], 10)
name = `${base}-${num + 1}`
} else {
name = `${name}-1`
}

return name
}

export function useInstanceTemplate(setValue: UseFormSetValue<InstanceCreateInput>) {
const [searchParams, setSearchParams] = useSearchParams()
const instanceId = searchParams.get('template') || ''

const { data: instance, isFetched: instanceFetched } = useApiQuery(
'instanceView',
{
path: { instance: instanceId },
},
{ enabled: !!instanceId }
)

const { data: disksData, isFetched: disksFetched } = useApiQuery(
'instanceDiskList',
{
path: { instance: instanceId },
},
{ enabled: !!instanceId }
)

useEffect(() => {
if (!instanceFetched || !disksFetched || !instance) return

const disks = disksData?.items || []
const bootDisk = disks.length > 0 ? disks[0] : null

const additionalDisks: DiskTableItem[] = disks.slice(1).map((disk) => ({
description: disk.description,
diskSource: {
type: 'blank',
blockSize: disk.blockSize as BlockSize,
},
name: disk.name,
size: disk.size,
type: 'create',
}))

const template: InstanceCreateInput = {
name: incrementName(instance.name),
description: instanceDefaultValues.description,
presetId: 'custom',
memory: bytesToGiB(instance.memory),
ncpus: instance.ncpus,
hostname: instance.hostname,

bootDiskName: bootDisk ? genName(bootDisk.name) : instanceDefaultValues.bootDiskName,
bootDiskSize: bootDisk?.size
? bytesToGiB(bootDisk?.size)
: instanceDefaultValues.bootDiskSize,
image: bootDisk?.imageId || '',

disks: additionalDisks,
networkInterfaces: { type: 'default' },

start: instanceDefaultValues.start,

userData: instanceDefaultValues.userData,
}

setTemplateFormValues(setValue, template)

searchParams.delete('template')
setSearchParams(searchParams)
}, [
instance,
instanceFetched,
disksFetched,
disksData,
setValue,
setSearchParams,
searchParams,
])
}

export function setTemplateFormValues<TFieldValues extends FieldValues>(
setValue: UseFormSetValue<TFieldValues>,
values: TFieldValues
) {
Object.keys(values).forEach((key) => {
if (values[key]) {
setValue(key as Path<TFieldValues>, values[key])
}
})
}
6 changes: 6 additions & 0 deletions app/pages/project/instances/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ export const useMakeInstanceActions = (
navigate(pb.serialConsole(instanceSelector))
},
},
{
label: 'New similar instance',
onActivate() {
navigate(`${pb.instanceNew(projectSelector)}?template=${instance.id}`)
},
},
{
label: 'Delete',
onActivate: confirmDelete({
Expand Down
2 changes: 2 additions & 0 deletions libs/api-mocks/disk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import type { Disk } from '@oxide/api'
import { GiB } from '@oxide/util'

import { images } from './image'
import { instance } from './instance'
import type { Json } from './json-type'
import { project } from './project'
Expand All @@ -22,6 +23,7 @@ export const disks: Json<Disk>[] = [
time_modified: new Date().toISOString(),
state: { state: 'attached', instance: instance.id },
device_path: '/abc',
image_id: images[0].id,
size: 2 * GiB,
block_size: 2048,
},
Expand Down
12 changes: 6 additions & 6 deletions libs/api-mocks/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { project } from './project'
export const instance: Json<Instance> = {
id: '935499b3-fd96-432a-9c21-83a3dc1eece4',
name: 'db1',
ncpus: 7,
memory: 1024 * 1024 * 256,
ncpus: 4,
memory: 1024 * 1024 * 1024 * 16,
description: 'an instance',
hostname: 'oxide.com',
project_id: project.id,
Expand All @@ -27,8 +27,8 @@ export const instance: Json<Instance> = {
const failedInstance: Json<Instance> = {
id: 'b5946edc-5bed-4597-88ab-9a8beb9d32a4',
name: 'you-fail',
ncpus: 7,
memory: 1024 * 1024 * 256,
ncpus: 8,
memory: 1024 * 1024 * 1024 * 16,
description: 'a failed instance',
hostname: 'oxide.com',
project_id: project.id,
Expand All @@ -41,8 +41,8 @@ const failedInstance: Json<Instance> = {
const startingInstance: Json<Instance> = {
id: '16737f54-1f76-4c96-8b7c-9d24971c1d62',
name: 'not-there-yet',
ncpus: 7,
memory: 1024 * 1024 * 256,
ncpus: 2,
memory: 1024 * 1024 * 1024 * 8,
description: 'a starting instance',
hostname: 'oxide.com',
project_id: project.id,
Expand Down