Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: devbox support gpu #5281

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions frontend/providers/devbox/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ DEVBOX_AFFINITY_ENABLE=
SQUASH_ENABLE=
NODE_TLS_REJECT_UNAUTHORIZED=
ROOT_RUNTIME_NAMESPACE=
GPU_ENABLE=
6 changes: 3 additions & 3 deletions frontend/providers/devbox/api/devbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import {
DevboxEditType,
DevboxListItemType,
DevboxPatchPropsType,
DevboxVersionListItemType,
runtimeNamespaceMapType
DevboxVersionListItemType
} from '@/types/devbox'
import {
adaptAppListItem,
Expand All @@ -14,6 +13,7 @@ import {
adaptDevboxVersionListItem,
adaptPod
} from '@/utils/adapt'
import { RuntimeNamespaceMap } from '@/types/static'
import { GET, POST, DELETE } from '@/services/request'
import { KBDevboxType, KBDevboxReleaseType } from '@/types/k8s'
import { MonitorDataResult, MonitorQueryKey } from '@/types/monitor'
Expand All @@ -34,7 +34,7 @@ export const applyYamlList = (yamlList: string[], type: 'create' | 'replace' | '

export const createDevbox = (payload: {
devboxForm: DevboxEditType
runtimeNamespaceMap: runtimeNamespaceMapType
runtimeNamespaceMap: RuntimeNamespaceMap
}) => POST(`/api/createDevbox`, payload)

export const updateDevbox = (payload: { patch: DevboxPatchPropsType; devboxName: string }) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,31 @@ import {
import { throttle } from 'lodash'
import dynamic from 'next/dynamic'
import { customAlphabet } from 'nanoid'
import { useEffect, useState } from 'react'
import { useTranslations } from 'next-intl'
import { UseFormReturn, useFieldArray } from 'react-hook-form'
import { MySelect, MySlider, Tabs, useMessage } from '@sealos/ui'
import { useEffect, useMemo, useState } from 'react'
import { MySelect, MySlider, MyTooltip, Tabs, useMessage } from '@sealos/ui'

import { useRouter } from '@/i18n'
import MyIcon from '@/components/Icon'
import PriceBox from '@/components/PriceBox'
import QuotaBox from '@/components/QuotaBox'

import { useEnvStore } from '@/stores/env'
import { usePriceStore } from '@/stores/price'
import { useDevboxStore } from '@/stores/devbox'
import { useRuntimeStore } from '@/stores/runtime'

import { ProtocolList } from '@/constants/devbox'
import type { DevboxEditType } from '@/types/devbox'
import { obj2Query } from '@/utils/tools'
import { useGlobalStore } from '@/stores/global'
import type { DevboxEditType } from '@/types/devbox'
import { CpuSlideMarkList, MemorySlideMarkList } from '@/constants/devbox'
import { GpuAmountMarkList, ProtocolList } from '@/constants/devbox'

const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 12)

const labelWidth = 100

export type CustomAccessModalParams = {
publicDomain: string
customDomain: string
Expand All @@ -48,11 +52,13 @@ const CustomAccessModal = dynamic(() => import('@/components/modals/CustomAccess
const Form = ({
formHook,
pxVal,
isEdit
isEdit,
countGpuInventory
}: {
formHook: UseFormReturn<DevboxEditType, any>
pxVal: number
isEdit: boolean
countGpuInventory: (type: string) => number
}) => {
const theme = useTheme()
const router = useRouter()
Expand Down Expand Up @@ -83,9 +89,12 @@ const Form = ({
osTypeList,
getRuntimeVersionList,
getRuntimeVersionDefault,
getRuntimeDetailLabel
getRuntimeDetailLabel,
isGPURuntimeType
} = useRuntimeStore()
const { env } = useEnvStore()
const { sourcePrice } = usePriceStore()
const { devboxList } = useDevboxStore()

const [customAccessModalData, setCustomAccessModalData] = useState<CustomAccessModalParams>()
const navList: { id: string; label: string; icon: string }[] = [
Expand All @@ -102,7 +111,6 @@ const Form = ({
]
const { message: toast } = useMessage()
const [activeNav, setActiveNav] = useState(navList[0].id)
const { devboxList } = useDevboxStore()

// listen scroll and set activeNav
useEffect(() => {
Expand Down Expand Up @@ -131,6 +139,50 @@ const Form = ({
// eslint-disable-next-line
}, [])

// add NoGPU select item
const gpuSelectList = useMemo(
() =>
sourcePrice?.gpu
? [
{
label: t('No GPU'),
value: ''
},
...sourcePrice.gpu.map((item) => ({
icon: 'nvidia',
label: (
<Flex>
<Box color={'myGray.900'}>{item.alias}</Box>
<Box mx={3} color={'grayModern.900'}>
|
</Box>
<Box color={'grayModern.900'}>
{t('vm')} : {Math.round(item.vm)}G
</Box>
<Box mx={3} color={'grayModern.900'}>
|
</Box>
<Flex pr={3}>
<Box color={'grayModern.900'}>{t('Inventory')}&ensp;:&ensp;</Box>
<Box color={'#FB7C3C'}>{countGpuInventory(item.type)}</Box>
</Flex>
</Flex>
),
value: item.type
}))
]
: [],
[countGpuInventory, t, sourcePrice?.gpu]
)
const selectedGpu = () => {
const selected = sourcePrice?.gpu?.find((item) => item.type === getValues('gpu.type'))
if (!selected) return
return {
...selected,
inventory: countGpuInventory(selected.type)
}
}

if (!formHook) return null

const Label = ({
Expand Down Expand Up @@ -248,7 +300,13 @@ const Form = ({
{
cpu: getValues('cpu'),
memory: getValues('memory'),
nodeports: devboxList.length
nodeports: devboxList.length,
gpu: !!getValues('gpu.type')
? {
type: getValues('gpu.type'),
amount: getValues('gpu.amount')
}
: undefined
}
]}
/>
Expand Down Expand Up @@ -356,6 +414,7 @@ const Form = ({
'runtimeVersion',
languageVersionMap[getValues('runtimeType')][0].id
)
setValue('gpu.type', '')
setValue(
'networks',
languageVersionMap[getValues('runtimeType')][0].defaultPorts.map(
Expand Down Expand Up @@ -437,6 +496,7 @@ const Form = ({
'runtimeVersion',
frameworkVersionMap[getValues('runtimeType')][0].id
)
setValue('gpu.type', '')
setValue(
'networks',
frameworkVersionMap[getValues('runtimeType')][0].defaultPorts.map(
Expand Down Expand Up @@ -518,6 +578,7 @@ const Form = ({
'runtimeVersion',
osVersionMap[getValues('runtimeType')][0].id
)
setValue('gpu.type', '')
setValue(
'networks',
osVersionMap[getValues('runtimeType')][0].defaultPorts.map(
Expand Down Expand Up @@ -576,7 +637,7 @@ const Form = ({
{...register('runtimeVersion', {
required: t('This runtime field is required')
})}
width={'200px'}
width={'300px'}
placeholder={`${t('runtime')} ${t('version')}`}
defaultValue={
getValues('runtimeVersion') ||
Expand Down Expand Up @@ -613,6 +674,79 @@ const Form = ({
/>
)}
</Flex>

{/* GPU */}
{sourcePrice?.gpu && isGPURuntimeType(getValues('runtimeType')) && (
<Box mb={7}>
<Flex alignItems={'center'}>
<Label w={100}>GPU</Label>
<MySelect
width={'300px'}
placeholder={t('No GPU') || ''}
value={getValues('gpu.type')}
list={gpuSelectList}
onchange={(type: any) => {
const selected = sourcePrice?.gpu?.find((item) => item.type === type)
const inventory = countGpuInventory(type)
if (type === '' || (selected && inventory > 0)) {
setValue('gpu.type', type)
}
}}
/>
</Flex>
{!!getValues('gpu.type') && (
<Box mt={4} pl={`${labelWidth}px`}>
<Box mb={1}>{t('Amount')}</Box>
<Flex alignItems={'center'}>
{GpuAmountMarkList.map((item) => {
const inventory = selectedGpu()?.inventory || 0

const hasInventory = item.value <= inventory

return (
<MyTooltip
key={item.value}
label={hasInventory ? '' : t('Under Stock')}>
<Center
mr={2}
w={'32px'}
h={'32px'}
borderRadius={'md'}
border={'1px solid'}
bg={'white'}
{...(getValues('gpu.amount') === item.value
? {
borderColor: 'brightBlue.500',
boxShadow: '0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)'
}
: {
borderColor: 'grayModern.200',
bgColor: 'grayModern.100'
})}
{...(hasInventory
? {
cursor: 'pointer',
onClick: () => {
setValue('gpu.amount', item.value)
}
}
: {
cursor: 'default',
opacity: 0.5
})}>
{item.label}
</Center>
</MyTooltip>
)
})}
<Box ml={3} color={'MyGray.500'}>
/ {t('Card')}
</Box>
</Flex>
</Box>
)}
</Box>
)}
{/* CPU */}
<Flex mb={10} pr={3} alignItems={'flex-start'}>
<Label w={100}>{t('cpu')}</Label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { useLoading } from '@/hooks/useLoading'
import { useEnvStore } from '@/stores/env'
import { useIDEStore } from '@/stores/ide'
import { useUserStore } from '@/stores/user'
import { usePriceStore } from '@/stores/price'
import { useDevboxStore } from '@/stores/devbox'
import { useGlobalStore } from '@/stores/global'
import { useRuntimeStore } from '@/stores/runtime'
Expand All @@ -47,12 +48,14 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 12)
const DevboxCreatePage = () => {
const router = useRouter()
const t = useTranslations()
const { Loading, setIsLoading } = useLoading()

const searchParams = useSearchParams()
const { message: toast } = useMessage()

const { env } = useEnvStore()
const { addDevboxIDE } = useIDEStore()
const { sourcePrice, setSourcePrice } = usePriceStore()
const { checkQuotaAllow } = useUserStore()
const { setDevboxDetail, devboxList } = useDevboxStore()
const { runtimeNamespaceMap, languageVersionMap, frameworkVersionMap, osVersionMap } =
Expand All @@ -62,7 +65,6 @@ const DevboxCreatePage = () => {
const formOldYamls = useRef<YamlItemType[]>([])
const oldDevboxEditData = useRef<DevboxEditType>()

const { Loading, setIsLoading } = useLoading()
const [errorMessage, setErrorMessage] = useState('')
const [forceUpdate, setForceUpdate] = useState(false)
const [yamlList, setYamlList] = useState<YamlItemType[]>([])
Expand Down Expand Up @@ -191,12 +193,26 @@ const DevboxCreatePage = () => {
[]
)

const countGpuInventory = useCallback(
(type?: string) => {
const inventory = sourcePrice?.gpu?.find((item) => item.type === type)?.inventory || 0

return inventory
},
[sourcePrice?.gpu]
)

// watch form change, compute new yaml
formHook.watch((data) => {
data && formOnchangeDebounce(data as DevboxEditType)
setForceUpdate(!forceUpdate)
})

const { refetch: refetchPrice } = useQuery(['init-price'], setSourcePrice, {
enabled: !!sourcePrice?.gpu,
refetchInterval: 6000
})

useQuery(
['initDevboxCreateData'],
() => {
Expand Down Expand Up @@ -259,6 +275,18 @@ const DevboxCreatePage = () => {
setIsLoading(true)

try {
// gpu inventory check
if (formData.gpu?.type) {
const inventory = countGpuInventory(formData.gpu?.type)
if (formData.gpu?.amount > inventory) {
return toast({
status: 'warning',
title: t('Gpu under inventory Tip', {
gputype: formData.gpu.type
})
})
}
}
// quote check
const quoteCheckRes = checkQuotaAllow(
{ ...formData, nodeports: devboxList.length + 1 } as DevboxEditType & {
Expand Down Expand Up @@ -310,11 +338,14 @@ const DevboxCreatePage = () => {
} else {
await createDevbox({ devboxForm: formData, runtimeNamespaceMap })
}
addDevboxIDE('cursor', formData.name)
addDevboxIDE('vscode', formData.name)
toast({
title: t(applySuccess),
status: 'success'
})
if (sourcePrice?.gpu) {
refetchPrice()
}
router.push(lastRoute)
} catch (error) {
console.error(error)
Expand Down Expand Up @@ -361,7 +392,12 @@ const DevboxCreatePage = () => {
/>
<Box flex={'1 0 0'} h={0} w={'100%'} pb={4}>
{tabType === 'form' ? (
<Form formHook={formHook} pxVal={pxVal} isEdit={isEdit} />
<Form
formHook={formHook}
pxVal={pxVal}
isEdit={isEdit}
countGpuInventory={countGpuInventory}
/>
) : (
<Yaml yamlList={yamlList} pxVal={pxVal} />
)}
Expand Down
Loading
Loading