diff --git a/frontend/providers/devbox/.eslintrc.json b/frontend/providers/devbox/.eslintrc.json index cce8c3061f3..86921703f8e 100644 --- a/frontend/providers/devbox/.eslintrc.json +++ b/frontend/providers/devbox/.eslintrc.json @@ -10,14 +10,9 @@ }, { "name": "next/navigation", - "importNames": [ - "redirect", - "permanentRedirect", - "useRouter", - "usePathname" - ], + "importNames": ["redirect", "permanentRedirect", "useRouter", "usePathname"], "message": "Please import from `@/i18n` instead." } ] } -} \ No newline at end of file +} diff --git a/frontend/providers/devbox/.prettierrc.js b/frontend/providers/devbox/.prettierrc.js index b48f2c3caef..8bc336a3674 100644 --- a/frontend/providers/devbox/.prettierrc.js +++ b/frontend/providers/devbox/.prettierrc.js @@ -17,4 +17,4 @@ module.exports = { proseWrap: 'preserve', htmlWhitespaceSensitivity: 'css', endOfLine: 'lf' -} +}; diff --git a/frontend/providers/devbox/api/devbox.ts b/frontend/providers/devbox/api/devbox.ts index 9f94747da1e..2a8d8fddb4e 100644 --- a/frontend/providers/devbox/api/devbox.ts +++ b/frontend/providers/devbox/api/devbox.ts @@ -1,75 +1,80 @@ -import { V1Deployment, V1Pod, V1StatefulSet } from '@kubernetes/client-node' +import { V1Deployment, V1Pod, V1StatefulSet } from '@kubernetes/client-node'; -import { DELETE, GET, POST } from '@/services/request' -import { GetDevboxByNameReturn } from '@/types/adapt' +import { DELETE, GET, POST } from '@/services/request'; +import { GetDevboxByNameReturn } from '@/types/adapt'; import { DevboxEditTypeV2, DevboxListItemTypeV2, DevboxPatchPropsType, DevboxVersionListItemType -} from '@/types/devbox' -import { KBDevboxReleaseType, KBDevboxTypeV2 } from '@/types/k8s' -import { MonitorDataResult, MonitorQueryKey } from '@/types/monitor' +} from '@/types/devbox'; +import { KBDevboxReleaseType, KBDevboxTypeV2 } from '@/types/k8s'; import { adaptAppListItem, adaptDevboxDetailV2, adaptDevboxListItemV2, adaptDevboxVersionListItem, adaptPod -} from '@/utils/adapt' +} from '@/utils/adapt'; +import { MonitorDataResult, MonitorQueryKey } from '@/types/monitor'; +import { AxiosProgressEvent } from 'axios'; export const getMyDevboxList = () => - GET<[KBDevboxTypeV2, { - templateRepository: { - iconId: string | null; - }; - uid: string; - }][]>('/api/getDevboxList').then((data): DevboxListItemTypeV2[] => + GET< + [ + KBDevboxTypeV2, + { + templateRepository: { + iconId: string | null; + }; + uid: string; + } + ][] + >('/api/getDevboxList').then((data): DevboxListItemTypeV2[] => data.map(adaptDevboxListItemV2).sort((a, b) => { - return new Date(b.createTime).getTime() - new Date(a.createTime).getTime() + return new Date(b.createTime).getTime() - new Date(a.createTime).getTime(); }) - ) + ); export const getDevboxByName = (devboxName: string) => - GET('/api/getDevboxByName', { devboxName }).then(adaptDevboxDetailV2) + GET('/api/getDevboxByName', { devboxName }).then(adaptDevboxDetailV2); export const applyYamlList = (yamlList: string[], type: 'create' | 'replace' | 'update') => - POST('/api/applyYamlList', { yamlList, type }) + POST('/api/applyYamlList', { yamlList, type }); -export const createDevbox = (payload: { - devboxForm: DevboxEditTypeV2 -}) => POST(`/api/createDevbox`, payload) +export const createDevbox = (payload: { devboxForm: DevboxEditTypeV2 }) => + POST(`/api/createDevbox`, payload); export const updateDevbox = (payload: { patch: DevboxPatchPropsType; devboxName: string }) => - POST(`/api/updateDevbox`, payload) + POST(`/api/updateDevbox`, payload); -export const delDevbox = (devboxName: string) => DELETE('/api/delDevbox', { devboxName }) +export const delDevbox = (devboxName: string) => DELETE('/api/delDevbox', { devboxName }); -export const restartDevbox = (data: { devboxName: string }) => POST('/api/restartDevbox', data) +export const restartDevbox = (data: { devboxName: string }) => POST('/api/restartDevbox', data); -export const startDevbox = (data: { devboxName: string }) => POST('/api/startDevbox', data) +export const startDevbox = (data: { devboxName: string }) => POST('/api/startDevbox', data); -export const pauseDevbox = (data: { devboxName: string }) => POST('/api/pauseDevbox', data) +export const pauseDevbox = (data: { devboxName: string }) => POST('/api/pauseDevbox', data); export const getDevboxVersionList = (devboxName: string, devboxUid: string) => GET('/api/getDevboxVersionList', { devboxName, devboxUid }).then( (data): DevboxVersionListItemType[] => data.map(adaptDevboxVersionListItem).sort((a, b) => { - return new Date(b.createTime).getTime() - new Date(a.createTime).getTime() + return new Date(b.createTime).getTime() - new Date(a.createTime).getTime(); }) - ) + ); export const releaseDevbox = (data: { - devboxName: string - tag: string - releaseDes: string - devboxUid: string -}) => POST('/api/releaseDevbox', data) + devboxName: string; + tag: string; + releaseDes: string; + devboxUid: string; +}) => POST('/api/releaseDevbox', data); export const editDevboxVersion = (data: { name: string; releaseDes: string }) => - POST('/api/editDevboxVersion', data) + POST('/api/editDevboxVersion', data); export const delDevboxVersionByName = (versionName: string) => - DELETE('/api/delDevboxVersionByName', { versionName }) + DELETE('/api/delDevboxVersionByName', { versionName }); export const getSSHConnectionInfo = (data: { devboxName: string }) => GET<{ @@ -80,18 +85,32 @@ export const getSSHConnectionInfo = (data: { devboxName: string }) => workingDir: string; releaseCommand: string; releaseArgs: string; -}>('/api/getSSHConnectionInfo', data) + }>('/api/getSSHConnectionInfo', data); export const getDevboxPodsByDevboxName = (name: string) => - GET('/api/getDevboxPodsByDevboxName', { name }).then((item) => item.map(adaptPod)) + GET('/api/getDevboxPodsByDevboxName', { name }).then((item) => item.map(adaptPod)); export const getDevboxMonitorData = (payload: { - queryName: string - queryKey: keyof MonitorQueryKey - step: string -}) => GET(`/api/monitor/getMonitorData`, payload) + queryName: string; + queryKey: keyof MonitorQueryKey; + step: string; +}) => GET(`/api/monitor/getMonitorData`, payload); export const getAppsByDevboxId = (devboxId: string) => GET('/api/getAppsByDevboxId', { devboxId }).then((res) => res.map(adaptAppListItem) - ) + ); + +export const execCommandInDevboxPod = (data: { + devboxName: string; + command: string; + idePath: string; + onDownloadProgress: (progressEvent: AxiosProgressEvent) => void; + signal: AbortSignal; +}) => + POST('/api/execCommandInDevboxPod', data, { + // responseType: 'stream', + timeout: 0, + onDownloadProgress: data.onDownloadProgress, + signal: data.signal + }); diff --git a/frontend/providers/devbox/api/platform.ts b/frontend/providers/devbox/api/platform.ts index 57f020171e3..47ac9c08e17 100644 --- a/frontend/providers/devbox/api/platform.ts +++ b/frontend/providers/devbox/api/platform.ts @@ -1,27 +1,27 @@ -import { GET, POST } from '@/services/request' -import type { UserQuotaItemType, UserTask } from '@/types/user' -import type { Env } from '@/types/static' -import { getDesktopSessionFromSessionStorage, getSessionFromSessionStorage } from '@/utils/user' -export const getAppEnv = () => GET('/api/getEnv') +import { GET, POST } from '@/services/request'; +import type { UserQuotaItemType, UserTask } from '@/types/user'; +import type { Env } from '@/types/static'; +import { getDesktopSessionFromSessionStorage, getSessionFromSessionStorage } from '@/utils/user'; +export const getAppEnv = () => GET('/api/getEnv'); export const getUserQuota = () => GET<{ - quota: UserQuotaItemType[] - }>('/api/platform/getQuota') + quota: UserQuotaItemType[]; + }>('/api/platform/getQuota'); -export const getRuntime = () => GET('/api/platform/getRuntime') +export const getRuntime = () => GET('/api/platform/getRuntime'); -export const getResourcePrice = () => GET('/api/platform/resourcePrice') +export const getResourcePrice = () => GET('/api/platform/resourcePrice'); export const postAuthCname = (data: { publicDomain: string; customDomain: string }) => - POST('/api/platform/authCname', data) + POST('/api/platform/authCname', data); export const getUserTasks = () => POST<{ needGuide: boolean; task: UserTask }>('/api/guide/getTasks', { desktopToAppToken: getDesktopSessionFromSessionStorage()?.token - }) + }); export const checkUserTask = () => POST('/api/guide/checkTask', { desktopToAppToken: getDesktopSessionFromSessionStorage()?.token - }) + }); diff --git a/frontend/providers/devbox/api/template.ts b/frontend/providers/devbox/api/template.ts index 454568a3fc2..055e070455c 100644 --- a/frontend/providers/devbox/api/template.ts +++ b/frontend/providers/devbox/api/template.ts @@ -1,29 +1,38 @@ -import { Tag, TemplateRepositoryKind } from "@/prisma/generated/client"; -import { DELETE, GET, POST } from "@/services/request"; -import { CreateTemplateRepositoryType, UpdateTemplateRepositoryType, UpdateTemplateType } from "@/utils/vaildate"; +import { Tag, TemplateRepositoryKind } from '@/prisma/generated/client'; +import { DELETE, GET, POST } from '@/services/request'; +import { + CreateTemplateRepositoryType, + UpdateTemplateRepositoryType, + UpdateTemplateType +} from '@/utils/vaildate'; -export const listOfficialTemplateRepository = () => GET<{ - templateRepositoryList: { - uid: string; - name: string; - kind: TemplateRepositoryKind; - iconId: string; - description: string | null; - }[] -}>(`/api/templateRepository/listOfficial`) -export const listTemplateRepository = (page: { - page: number, - pageSize: number, -}, tags?: string[], search?: string) => { - const searchParams = new URLSearchParams() +export const listOfficialTemplateRepository = () => + GET<{ + templateRepositoryList: { + uid: string; + name: string; + kind: TemplateRepositoryKind; + iconId: string; + description: string | null; + }[]; + }>(`/api/templateRepository/listOfficial`); +export const listTemplateRepository = ( + page: { + page: number; + pageSize: number; + }, + tags?: string[], + search?: string +) => { + const searchParams = new URLSearchParams(); if (tags && tags.length > 0) { tags.forEach((tag) => { - searchParams.append('tags', tag) - }) + searchParams.append('tags', tag); + }); } - searchParams.append('page', page.page.toString()) - searchParams.append('pageSize', page.pageSize.toString()) - if (search) searchParams.append('search', search) + searchParams.append('page', page.page.toString()); + searchParams.append('pageSize', page.pageSize.toString()); + if (search) searchParams.append('search', search); return GET<{ templateRepositoryList: { uid: string; @@ -37,30 +46,29 @@ export const listTemplateRepository = (page: { templateRepositoryTags: { tag: Tag; }[]; - }[], + }[]; page: { - page: number, - pageSize: number, - totalItems: number, - totalPage: number, - } - }>(`/api/templateRepository/list?${searchParams.toString()}`) - -} + page: number; + pageSize: number; + totalItems: number; + totalPage: number; + }; + }>(`/api/templateRepository/list?${searchParams.toString()}`); +}; export const listPrivateTemplateRepository = ({ search, page, - pageSize, + pageSize }: { - search?: string, - page?: number, - pageSize?: number, + search?: string; + page?: number; + pageSize?: number; } = {}) => { - const searchParams = new URLSearchParams() + const searchParams = new URLSearchParams(); - if (search) searchParams.append('search', search) - if (page) searchParams.append('page', page.toString()) - if (pageSize) searchParams.append('pageSize', pageSize.toString()) + if (search) searchParams.append('search', search); + if (page) searchParams.append('page', page.toString()); + if (pageSize) searchParams.append('pageSize', pageSize.toString()); return GET<{ templateRepositoryList: { uid: string; @@ -75,58 +83,67 @@ export const listPrivateTemplateRepository = ({ templateRepositoryTags: { tag: Tag; }[]; - }[], + }[]; page: { - page: number, - pageSize: number, - totalItems: number, - totalPage: number, - } - }>(`/api/templateRepository/listPrivate?${searchParams.toString()}`) -} + page: number; + pageSize: number; + totalItems: number; + totalPage: number; + }; + }>(`/api/templateRepository/listPrivate?${searchParams.toString()}`); +}; -export const getTemplateRepository = (uid: string) => GET<{ - templateRepository: { - templates: { +export const getTemplateRepository = (uid: string) => + GET<{ + templateRepository: { + templates: { + name: string; + uid: string; + }[]; + uid: string; + isPublic: true; + name: string; + description: string | null; + iconId: string | null; + templateRepositoryTags: { + tag: Tag; + }[]; + }; + }>(`/api/templateRepository/get?uid=${uid}`); +export const getTemplateConfig = (uid: string) => + GET<{ + template: { name: string; uid: string; + config: string; + }; + }>(`/api/templateRepository/template/getConfig?uid=${uid}`); +export const listTemplate = (templateRepositoryUid: string) => + GET<{ + templateList: { + uid: string; + name: string; + config: string; + image: string; + createAt: Date; + updateAt: Date; }[]; - uid: string; - isPublic: true; - name: string; - description: string | null; - iconId: string | null; - templateRepositoryTags: { - tag: Tag; - }[]; - } -}>(`/api/templateRepository/get?uid=${uid}`) -export const getTemplateConfig = (uid: string) => GET<{ - template: { - name: string; - uid: string; - config: string; - } -}>(`/api/templateRepository/template/getConfig?uid=${uid}`) -export const listTemplate = (templateRepositoryUid: string) => GET<{ - templateList: { - uid: string; - name: string; - config: string; - image: string; - createAt: Date; - updateAt: Date; - }[] -}>(`/api/templateRepository/template/list?templateRepositoryUid=${templateRepositoryUid}`) -export const listTag = () => GET<{ - tagList: Tag[] -}>(`/api/templateRepository/tag/list`) + }>(`/api/templateRepository/template/list?templateRepositoryUid=${templateRepositoryUid}`); +export const listTag = () => + GET<{ + tagList: Tag[]; + }>(`/api/templateRepository/tag/list`); -export const createTemplateReposistory = (data: CreateTemplateRepositoryType) => POST(`/api/templateRepository/withTemplate/create`, data) -export const initUser = () => POST(`/api/auth/init`) +export const createTemplateReposistory = (data: CreateTemplateRepositoryType) => + POST(`/api/templateRepository/withTemplate/create`, data); +export const initUser = () => POST(`/api/auth/init`); -export const deleteTemplateRepository = (templateRepositoryUid: string) => DELETE(`/api/templateRepository/delete?templateRepositoryUid=${templateRepositoryUid}`) +export const deleteTemplateRepository = (templateRepositoryUid: string) => + DELETE(`/api/templateRepository/delete?templateRepositoryUid=${templateRepositoryUid}`); -export const updateTemplateReposistory = (data: UpdateTemplateRepositoryType) => POST(`/api/templateRepository/update`, data) -export const updateTemplate = (data: UpdateTemplateType) => POST(`/api/templateRepository/withTemplate/update`, data) -export const deleteTemplate = (templateUid: string) => DELETE(`/api/templateRepository/template/delete?uid=${templateUid}`) \ No newline at end of file +export const updateTemplateReposistory = (data: UpdateTemplateRepositoryType) => + POST(`/api/templateRepository/update`, data); +export const updateTemplate = (data: UpdateTemplateType) => + POST(`/api/templateRepository/withTemplate/update`, data); +export const deleteTemplate = (templateUid: string) => + DELETE(`/api/templateRepository/template/delete?uid=${templateUid}`); diff --git a/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxHeader.tsx b/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxHeader.tsx index 46ad42d6011..7c5fec29e08 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxHeader.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxHeader.tsx @@ -1,90 +1,94 @@ -import MyIcon from "@/components/Icon"; -import { TemplateState } from "@/constants/template"; -import { usePathname, useRouter } from "@/i18n"; -import { useTemplateStore } from "@/stores/template"; -import { Box, Button, Center, Flex, Text, useTheme } from "@chakra-ui/react"; -import { useTranslations } from "next-intl"; -import { useEffect } from "react"; +import MyIcon from '@/components/Icon'; +import { TemplateState } from '@/constants/template'; +import { usePathname, useRouter } from '@/i18n'; +import { useTemplateStore } from '@/stores/template'; +import { Box, Button, Center, Flex, Text, useTheme } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; +import { useEffect } from 'react'; export default function DevboxHeader({ listLength }: { listLength: number }) { - const { openTemplateModal, config, updateTemplateModalConfig } = useTemplateStore() - const theme = useTheme() - const router = useRouter() - const t = useTranslations() - const pathname = usePathname() - const lastRoute = '/?openTemplate=publicTemplate' + const { openTemplateModal, config, updateTemplateModalConfig } = useTemplateStore(); + const theme = useTheme(); + const router = useRouter(); + const t = useTranslations(); + const pathname = usePathname(); + const lastRoute = '/?openTemplate=publicTemplate'; useEffect(() => { - const refreshLastRoute = '/' + const refreshLastRoute = '/'; if (config.lastRoute.includes('openTemplate')) { openTemplateModal({ ...config, - lastRoute: refreshLastRoute - }) + lastRoute: refreshLastRoute + }); } else { updateTemplateModalConfig({ ...config, - lastRoute: refreshLastRoute - }) + lastRoute: refreshLastRoute + }); } - }, []) - return -
- -
- - {t('devbox_list')} - - - ( {listLength} ) - - { - // setLastRoute(pathname) - openTemplateModal({ - 'templateState': TemplateState.publicTemplate, - lastRoute, - }) - }} - > - +
+ +
+ + {t('devbox_list')} + + + ( {listLength} ) + + - { + // setLastRoute(pathname) + openTemplateModal({ + templateState: TemplateState.publicTemplate, + lastRoute + }); + }} + > + + + {t('scan_templates')} + + +
- -
; -} \ No newline at end of file + ); +} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxList.tsx b/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxList.tsx index 7c0ff942e5b..cc97d2010be 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxList.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxList.tsx @@ -10,12 +10,12 @@ import { useRouter } from '@/i18n'; import { useGlobalStore } from '@/stores/global'; import { DevboxListItemTypeV2 } from '@/types/devbox'; -import DevboxStatusTag from '@/components/DevboxStatusTag'; import MyIcon from '@/components/Icon'; import IDEButton from '@/components/IDEButton'; -import ReleaseModal from '@/components/modals/releaseModal'; import PodLineChart from '@/components/PodLineChart'; import { AdvancedTable } from '@/components/AdvancedTable'; +import DevboxStatusTag from '@/components/DevboxStatusTag'; +import ReleaseModal from '@/components/modals/ReleaseModal'; const DelModal = dynamic(() => import('@/components/modals/DelModal')); @@ -29,9 +29,6 @@ const DevboxList = ({ const router = useRouter(); const t = useTranslations(); const { message: toast } = useMessage(); - const duplicatedDevboxList = Array(20) - .fill(0) - .flatMap(() => [...devboxList]); // TODO: Unified Loading Behavior const { setLoading } = useGlobalStore(); @@ -224,6 +221,7 @@ const DevboxList = ({ sshPort={item.sshPort} status={item.status} mr={'8px'} + runtimeType={item.template.templateRepository.iconId as string} /> - ) -} + ); +}; -export default Header +export default Header; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Yaml.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Yaml.tsx index 059147e1500..94f1de458d6 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Yaml.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Yaml.tsx @@ -1,27 +1,28 @@ -import { useState } from 'react' -import { Tabs } from '@sealos/ui' -import { useTranslations } from 'next-intl' -import { useSearchParams } from 'next/navigation' -import { Box, Center, Flex, Grid, useTheme } from '@chakra-ui/react' +import { useState } from 'react'; +import { Tabs } from '@sealos/ui'; +import { useTranslations } from 'next-intl'; +import { useSearchParams } from 'next/navigation'; +import { Box, Center, Flex, Grid, useTheme } from '@chakra-ui/react'; -import { useRouter } from '@/i18n' -import MyIcon from '@/components/Icon' -import { obj2Query } from '@/utils/tools' -import type { YamlItemType } from '@/types' -import { useCopyData } from '@/utils/tools' -import YamlCode from '@/components/YamlCode/index' +import Code from '@/components/Code'; +import MyIcon from '@/components/Icon'; -import styles from './index.module.scss' +import { useRouter } from '@/i18n'; +import { obj2Query } from '@/utils/tools'; +import type { YamlItemType } from '@/types'; +import { useCopyData } from '@/utils/tools'; + +import styles from './index.module.scss'; const Yaml = ({ yamlList = [], pxVal }: { yamlList: YamlItemType[]; pxVal: number }) => { - const theme = useTheme() - const router = useRouter() - const t = useTranslations() - const { copyData } = useCopyData() - const searchParams = useSearchParams() - const [selectedIndex, setSelectedIndex] = useState(0) + const theme = useTheme(); + const router = useRouter(); + const t = useTranslations(); + const { copyData } = useCopyData(); + const searchParams = useSearchParams(); + const [selectedIndex, setSelectedIndex] = useState(0); - const devboxName = searchParams.get('name') as string + const devboxName = searchParams.get('name') as string; return ( + px={`${pxVal}px`} + > + border={theme.borders.base} + > {yamlList.map((file, index) => ( setSelectedIndex(index)}> + onClick={() => setSelectedIndex(index)} + > + opacity={selectedIndex === index ? 1 : 0} + > {file.filename} ))} @@ -97,7 +102,8 @@ const Yaml = ({ yamlList = [], pxVal }: { yamlList: YamlItemType[]; pxVal: numbe overflow={'hidden'} border={theme.borders.base} borderRadius={'md'} - position={'relative'}> + position={'relative'} + > {yamlList[selectedIndex].filename} @@ -106,17 +112,18 @@ const Yaml = ({ yamlList = [], pxVal }: { yamlList: YamlItemType[]; pxVal: numbe cursor={'pointer'} color={'grayModern.600'} _hover={{ color: '#219BF4' }} - onClick={() => copyData(yamlList[selectedIndex].value)}> + onClick={() => copyData(yamlList[selectedIndex].value)} + > - + )} - ) -} + ); +}; -export default Yaml +export default Yaml; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/CpuSelector.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/CpuSelector.tsx index fa527cb89c2..4617ba56f17 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/CpuSelector.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/CpuSelector.tsx @@ -1,28 +1,30 @@ -import { CpuSlideMarkList } from "@/constants/devbox" -import { DevboxEditTypeV2 } from "@/types/devbox" -import { Box, Flex, FlexProps } from "@chakra-ui/react" -import { MySlider } from "@sealos/ui" -import { useTranslations } from "next-intl" -import { useFormContext } from "react-hook-form" -import Label from "../Label" +import { CpuSlideMarkList } from '@/constants/devbox'; +import { DevboxEditTypeV2 } from '@/types/devbox'; +import { Box, Flex, FlexProps } from '@chakra-ui/react'; +import { MySlider } from '@sealos/ui'; +import { useTranslations } from 'next-intl'; +import { useFormContext } from 'react-hook-form'; +import Label from '../Label'; export default function CpuSelector(props: FlexProps) { - const t = useTranslations() - const { watch, setValue } = useFormContext() - return - - { - setValue('cpu', CpuSlideMarkList[e].value) - }} - max={CpuSlideMarkList.length - 1} - min={0} - step={1} - /> - - {t('core')} - - -} \ No newline at end of file + const t = useTranslations(); + const { watch, setValue } = useFormContext(); + return ( + + + { + setValue('cpu', CpuSlideMarkList[e].value); + }} + max={CpuSlideMarkList.length - 1} + min={0} + step={1} + /> + + {t('core')} + + + ); +} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/DevboxNameInput.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/DevboxNameInput.tsx index a4b7084dc81..902313b6a2b 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/DevboxNameInput.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/DevboxNameInput.tsx @@ -1,56 +1,58 @@ -import { DevboxEditTypeV2 } from "@/types/devbox" -import { nanoid } from "@/utils/tools" -import { devboxNameSchema } from "@/utils/vaildate" -import { Flex, FormControl, FormControlProps, Input } from "@chakra-ui/react" -import { useTranslations } from "next-intl" -import { useFieldArray, useFormContext } from "react-hook-form" -import Label from "../Label" +import { DevboxEditTypeV2 } from '@/types/devbox'; +import { nanoid } from '@/utils/tools'; +import { devboxNameSchema } from '@/utils/vaildate'; +import { Flex, FormControl, FormControlProps, Input } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; +import { useFieldArray, useFormContext } from 'react-hook-form'; +import Label from '../Label'; -export default function DevboxNameInput({isEdit, ...props}: {isEdit: boolean} & FormControlProps) { - const t = useTranslations() +export default function DevboxNameInput({ + isEdit, + ...props +}: { isEdit: boolean } & FormControlProps) { + const t = useTranslations(); const { register, setValue, formState: { errors }, control - } = useFormContext() - const { - fields: networks, - update: updateNetworks - } = useFieldArray({ + } = useFormContext(); + const { fields: networks, update: updateNetworks } = useFieldArray({ control, name: 'networks' - }) - return - - - - devboxNameSchema.safeParse(value).success || t('devbox_name_invalid') - } - })} - onBlur={(e) => { - const lowercaseValue = e.target.value.toLowerCase() - setValue('name', lowercaseValue) - networks.forEach((network, i) => { - updateNetworks(i, { - ...network, - networkName: `${lowercaseValue}-${nanoid()}` - }) - }) - }} - /> - - -} \ No newline at end of file + }); + return ( + + + + + devboxNameSchema.safeParse(value).success || t('devbox_name_invalid') + } + })} + onBlur={(e) => { + const lowercaseValue = e.target.value.toLowerCase(); + setValue('name', lowercaseValue); + networks.forEach((network, i) => { + updateNetworks(i, { + ...network, + networkName: `${lowercaseValue}-${nanoid()}` + }); + }); + }} + /> + + + ); +} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/MemorySelector.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/MemorySelector.tsx index 3af3ced3037..7adb6893caf 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/MemorySelector.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/MemorySelector.tsx @@ -1,25 +1,27 @@ -import { MemorySlideMarkList } from "@/constants/devbox" -import { DevboxEditTypeV2 } from "@/types/devbox" -import { Flex, FlexProps } from "@chakra-ui/react" -import { MySlider } from "@sealos/ui" -import { useTranslations } from "next-intl" -import { useFormContext } from "react-hook-form" -import Label from "../Label" +import { MemorySlideMarkList } from '@/constants/devbox'; +import { DevboxEditTypeV2 } from '@/types/devbox'; +import { Flex, FlexProps } from '@chakra-ui/react'; +import { MySlider } from '@sealos/ui'; +import { useTranslations } from 'next-intl'; +import { useFormContext } from 'react-hook-form'; +import Label from '../Label'; export default function MemorySelector(props: FlexProps) { - const t = useTranslations() - const { watch, setValue } = useFormContext() - return - - { - setValue('memory', MemorySlideMarkList[e].value) - }} - max={MemorySlideMarkList.length - 1} - min={0} - step={1} - /> - -} \ No newline at end of file + const t = useTranslations(); + const { watch, setValue } = useFormContext(); + return ( + + + { + setValue('memory', MemorySlideMarkList[e].value); + }} + max={MemorySlideMarkList.length - 1} + min={0} + step={1} + /> + + ); +} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositoryListNav.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositoryListNav.tsx index 18a9a22629d..15e576760ef 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositoryListNav.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositoryListNav.tsx @@ -1,21 +1,22 @@ -import MyIcon from '@/components/Icon' -import { TemplateState } from '@/constants/template' -import { useTemplateStore } from '@/stores/template' -import { Box, Flex, Text } from '@chakra-ui/react' -import { useTranslations } from 'next-intl' -import { usePathname } from '@/i18n' +import MyIcon from '@/components/Icon'; +import { TemplateState } from '@/constants/template'; +import { useTemplateStore } from '@/stores/template'; +import { Box, Flex, Text } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; +import { usePathname } from '@/i18n'; const TemplateRepositoryListNav = () => { - const t = useTranslations() - const { openTemplateModal, config, isOpen } = useTemplateStore() - const lastRoute = usePathname() + const t = useTranslations(); + const { openTemplateModal, config, isOpen } = useTemplateStore(); + const lastRoute = usePathname(); return ( + alignItems="center" + > {/* All Templates Tab */} { openTemplateModal({ templateState: TemplateState.publicTemplate, lastRoute - }) - }}> + }); + }} + > { fontWeight="500" lineHeight="16px" letterSpacing="0.5px" - color="#485264"> + color="#485264" + > {t('all_templates')} @@ -63,8 +66,9 @@ const TemplateRepositoryListNav = () => { openTemplateModal({ templateState: TemplateState.privateTemplate, lastRoute - }) - }}> + }); + }} + > { fontWeight="500" lineHeight="16px" letterSpacing="0.5px" - color="#485264"> + color="#485264" + > {t('my_templates')} - ) -} + ); +}; -export default TemplateRepositoryListNav +export default TemplateRepositoryListNav; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositorySelector/TemplateReposistoryItem.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositorySelector/TemplateReposistoryItem.tsx index 05ef402d9c6..b68d71f3f56 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositorySelector/TemplateReposistoryItem.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositorySelector/TemplateReposistoryItem.tsx @@ -1,68 +1,71 @@ -import { useDevboxStore } from "@/stores/devbox"; -import { DevboxEditTypeV2 } from "@/types/devbox"; -import { Center, Img, Text } from "@chakra-ui/react"; -import { useMessage } from "@sealos/ui"; -import { useTranslations } from "next-intl"; -import { useFormContext } from "react-hook-form"; +import { useDevboxStore } from '@/stores/devbox'; +import { DevboxEditTypeV2 } from '@/types/devbox'; +import { Center, Img, Text } from '@chakra-ui/react'; +import { useMessage } from '@sealos/ui'; +import { useTranslations } from 'next-intl'; +import { useFormContext } from 'react-hook-form'; -export default function TemplateRepositoryItem({ item, isEdit }: { item: { uid: string, iconId: string, name: string }; isEdit: boolean}) { - const { message: toast } = useMessage() - const t = useTranslations() - const { getValues, setValue, watch } = useFormContext() - const { startedTemplate, setStartedTemplate } = useDevboxStore() - return
(); + const { startedTemplate, setStartedTemplate } = useDevboxStore(); + return ( +
{ + if (isEdit) return; + const devboxName = getValues('name'); + if (!devboxName) { + toast({ + title: t('Please enter the devbox name first'), + status: 'warning' + }); + return; } - })} - onClick={() =>{ - if (isEdit) return - const devboxName = getValues('name') - if (!devboxName) { - toast({ - title: t('Please enter the devbox name first'), - status: 'warning' - }) - return - } - if (startedTemplate && startedTemplate.uid !== item.uid) { - setStartedTemplate(undefined) - } - setValue('templateRepositoryUid', item.uid) - }} - > - {item.uid} - - {item.name} - -
-} \ No newline at end of file + {item.uid} + + {item.name} + +
+ ); +} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositorySelector/index.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositorySelector/index.tsx index a233868741e..6feccff2280 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositorySelector/index.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateRepositorySelector/index.tsx @@ -1,61 +1,61 @@ -import { getTemplateRepository, listOfficialTemplateRepository } from '@/api/template' -import useDriver from '@/hooks/useDriver' -import { TemplateRepositoryKind } from '@/prisma/generated/client' -import { useDevboxStore } from '@/stores/devbox' -import { DevboxEditTypeV2 } from '@/types/devbox' -import { TemplateRepository } from '@/types/template' -import { Box, Flex, VStack } from '@chakra-ui/react' -import { useQuery } from '@tanstack/react-query' -import { useTranslations } from 'next-intl' -import { useEffect, useMemo } from 'react' -import { useFormContext } from 'react-hook-form' -import Label from '../../Label' -import TemplateRepositoryListNav from '../TemplateRepositoryListNav' -import TemplateRepositoryItem from './TemplateReposistoryItem' +import { getTemplateRepository, listOfficialTemplateRepository } from '@/api/template'; +import useDriver from '@/hooks/useDriver'; +import { TemplateRepositoryKind } from '@/prisma/generated/client'; +import { useDevboxStore } from '@/stores/devbox'; +import { DevboxEditTypeV2 } from '@/types/devbox'; +import { TemplateRepository } from '@/types/template'; +import { Box, Flex, VStack } from '@chakra-ui/react'; +import { useQuery } from '@tanstack/react-query'; +import { useTranslations } from 'next-intl'; +import { useEffect, useMemo } from 'react'; +import { useFormContext } from 'react-hook-form'; +import Label from '../../Label'; +import TemplateRepositoryListNav from '../TemplateRepositoryListNav'; +import TemplateRepositoryItem from './TemplateReposistoryItem'; interface TemplateRepositorySelectorProps { - isEdit: boolean + isEdit: boolean; } export default function TemplateRepositorySelector({ isEdit }: TemplateRepositorySelectorProps) { - const { startedTemplate, setStartedTemplate } = useDevboxStore() - const { setValue, getValues, watch } = useFormContext() - const t = useTranslations() - const { handleUserGuide } = useDriver() + const { startedTemplate, setStartedTemplate } = useDevboxStore(); + const { setValue, getValues, watch } = useFormContext(); + const t = useTranslations(); + const { handleUserGuide } = useDriver(); const templateRepositoryQuery = useQuery( ['list-official-template-repository'], listOfficialTemplateRepository, { onSuccess(res) { - console.log('res', res) - handleUserGuide() + console.log('res', res); + handleUserGuide(); }, staleTime: Infinity, cacheTime: 1000 * 60 * 30 } - ) - const curTemplateRepositoryUid = watch('templateRepositoryUid') + ); + const curTemplateRepositoryUid = watch('templateRepositoryUid'); const curTemplateRepositoryDetail = useQuery( ['get-template-repository', curTemplateRepositoryUid], () => { - return getTemplateRepository(curTemplateRepositoryUid) + return getTemplateRepository(curTemplateRepositoryUid); }, { enabled: !!isEdit && !!curTemplateRepositoryUid } - ) + ); const templateData = useMemo( () => templateRepositoryQuery.data?.templateRepositoryList || [], [templateRepositoryQuery.data] - ) + ); const categorizedData = useMemo(() => { return templateData.reduce( (acc, item) => { - acc[item.kind] = [...(acc[item.kind] || []), item] - return acc + acc[item.kind] = [...(acc[item.kind] || []), item]; + return acc; }, { LANGUAGE: [], @@ -63,26 +63,26 @@ export default function TemplateRepositorySelector({ isEdit }: TemplateRepositor OS: [], CUSTOM: [] } as Record - ) - }, [templateData]) + ); + }, [templateData]); useEffect(() => { if (!startedTemplate || isEdit) { - return + return; } - const templateUid = startedTemplate.uid + const templateUid = startedTemplate.uid; if ( templateData.findIndex((item) => { - return item.uid === templateUid + return item.uid === templateUid; }) > -1 ) { - setStartedTemplate(undefined) + setStartedTemplate(undefined); } - setValue('templateRepositoryUid', templateUid) - }, [startedTemplate, isEdit]) + setValue('templateRepositoryUid', templateUid); + }, [startedTemplate, isEdit]); useEffect(() => { if (startedTemplate || isEdit) { - return + return; } if ( !( @@ -91,14 +91,14 @@ export default function TemplateRepositorySelector({ isEdit }: TemplateRepositor templateRepositoryQuery.isFetched ) ) - return - const curTemplateRepositoryUid = getValues('templateRepositoryUid') + return; + const curTemplateRepositoryUid = getValues('templateRepositoryUid'); const curTemplateRepository = templateData.find((item) => { - return item.uid === curTemplateRepositoryUid - }) + return item.uid === curTemplateRepositoryUid; + }); if (!curTemplateRepository) { - const defaultTemplateRepositoryUid = templateData[0].uid - setValue('templateRepositoryUid', defaultTemplateRepositoryUid) + const defaultTemplateRepositoryUid = templateData[0].uid; + setValue('templateRepositoryUid', defaultTemplateRepositoryUid); } }, [ templateRepositoryQuery.isSuccess, @@ -106,7 +106,7 @@ export default function TemplateRepositorySelector({ isEdit }: TemplateRepositor templateData, templateRepositoryQuery.isFetched, isEdit - ]) + ]); useEffect(() => { if ( @@ -116,18 +116,18 @@ export default function TemplateRepositorySelector({ isEdit }: TemplateRepositor !curTemplateRepositoryDetail.isSuccess || !curTemplateRepositoryDetail.data ) { - return + return; } - const templateRepository = curTemplateRepositoryDetail.data.templateRepository + const templateRepository = curTemplateRepositoryDetail.data.templateRepository; // setStartedTemplate(templateRepository) - setValue('templateRepositoryUid', templateRepository.uid) + setValue('templateRepositoryUid', templateRepository.uid); if ( templateData.findIndex((item) => { - return item.uid === templateRepository.uid + return item.uid === templateRepository.uid; }) === -1 ) { - setStartedTemplate(templateRepository) + setStartedTemplate(templateRepository); } }, [ curTemplateRepositoryDetail.isSuccess, @@ -136,11 +136,11 @@ export default function TemplateRepositorySelector({ isEdit }: TemplateRepositor isEdit, templateData, templateRepositoryQuery.isSuccess - ]) + ]); return ( - + @@ -191,5 +191,5 @@ export default function TemplateRepositorySelector({ isEdit }: TemplateRepositor - ) + ); } diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateSelector/index.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateSelector/index.tsx index 1ac11c76f91..bc78d9a31d6 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateSelector/index.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/TemplateSelector/index.tsx @@ -1,36 +1,38 @@ // RuntimeVersionSelector.tsx -import { listTemplate } from '@/api/template' -import { useEnvStore } from '@/stores/env' -import { DevboxEditTypeV2 } from '@/types/devbox' -import { nanoid } from '@/utils/tools' -import { Flex, Input } from '@chakra-ui/react' -import { MySelect, useMessage } from '@sealos/ui' -import { useQuery } from '@tanstack/react-query' -import { useTranslations } from 'next-intl' -import { useEffect } from 'react' -import { useController, useFormContext } from 'react-hook-form' -import { z } from 'zod' -import Label from '../../Label' +import { listTemplate } from '@/api/template'; +import { useEnvStore } from '@/stores/env'; +import { DevboxEditTypeV2 } from '@/types/devbox'; +import { nanoid } from '@/utils/tools'; +import { Flex, Input } from '@chakra-ui/react'; +import { MySelect, useMessage } from '@sealos/ui'; +import { useQuery } from '@tanstack/react-query'; +import { useTranslations } from 'next-intl'; +import { useEffect } from 'react'; +import { useController, useFormContext } from 'react-hook-form'; +import { z } from 'zod'; +import Label from '../../Label'; interface TemplateSelectorProps { - isEdit: boolean + isEdit: boolean; } -export default function TemplateSelector({ - isEdit, -}: TemplateSelectorProps) { - const { getValues, setValue, watch, control } = useFormContext() - const { env } = useEnvStore() - const { message: toast } = useMessage() - const templateRepositoryUid = watch('templateRepositoryUid') - const isVaildTemplateRepositoryUid = z.string().uuid().safeParse(templateRepositoryUid).success - const templateListQuery = useQuery(['templateList', templateRepositoryUid], () => listTemplate(templateRepositoryUid), { - enabled: isVaildTemplateRepositoryUid, - }) - const templateList = (templateListQuery.data?.templateList || []) - const t = useTranslations() +export default function TemplateSelector({ isEdit }: TemplateSelectorProps) { + const { getValues, setValue, watch, control } = useFormContext(); + const { env } = useEnvStore(); + const { message: toast } = useMessage(); + const templateRepositoryUid = watch('templateRepositoryUid'); + const isVaildTemplateRepositoryUid = z.string().uuid().safeParse(templateRepositoryUid).success; + const templateListQuery = useQuery( + ['templateList', templateRepositoryUid], + () => listTemplate(templateRepositoryUid), + { + enabled: isVaildTemplateRepositoryUid + } + ); + const templateList = templateListQuery.data?.templateList || []; + const t = useTranslations(); // const defaultTemplateUid = watch('templateUid') - const menuList = templateList.map(v => ({ label: v.name, value: v.uid })) + const menuList = templateList.map((v) => ({ label: v.name, value: v.uid })); // const defaultTemplate = defaultTemplateUid ? templateList.find(t => t.uid === defaultTemplateUid) : templateList[0] const { field } = useController({ @@ -39,54 +41,50 @@ export default function TemplateSelector({ rules: { required: t('This runtime field is required') } - }) + }); const afterUpdateTemplate = (uid: string) => { - const template = templateList.find(v => v.uid === uid)! - setValue( - 'templateConfig', - template.config as string - ) - setValue( - 'image', - template.image - ) - - } + const template = templateList.find((v) => v.uid === uid)!; + setValue('templateConfig', template.config as string); + setValue('image', template.image); + }; const resetNetwork = () => { - const devboxName = getValues('name') - const config = getValues('templateConfig') - const parsedConfig = - JSON.parse(config as string) as { appPorts: [{ port: number, name: string, protocol: string }] } + const devboxName = getValues('name'); + const config = getValues('templateConfig'); + const parsedConfig = JSON.parse(config as string) as { + appPorts: [{ port: number; name: string; protocol: string }]; + }; setValue( 'networks', parsedConfig.appPorts.map( - ({ port }) => ({ - networkName: `${devboxName}-${nanoid()}`, - portName: nanoid(), - port: port, - protocol: 'HTTP', - openPublicDomain: true, - publicDomain: `${nanoid()}.${env.ingressDomain}`, - customDomain: '' - } as const) + ({ port }) => + ({ + networkName: `${devboxName}-${nanoid()}`, + portName: nanoid(), + port: port, + protocol: 'HTTP', + openPublicDomain: true, + publicDomain: `${nanoid()}.${env.ingressDomain}`, + customDomain: '' + } as const) ) - ) - } + ); + }; useEffect(() => { - if (!templateListQuery.isSuccess || !templateList.length || !templateListQuery.isFetched) return + if (!templateListQuery.isSuccess || !templateList.length || !templateListQuery.isFetched) + return; - const curTemplate = templateList.find(t => t.uid === field.value) - const isExist = !!curTemplate + const curTemplate = templateList.find((t) => t.uid === field.value); + const isExist = !!curTemplate; if (!isExist) { - const defaultTemplate = templateList[0] - setValue('templateUid', defaultTemplate.uid) - afterUpdateTemplate(defaultTemplate.uid) - resetNetwork() + const defaultTemplate = templateList[0]; + setValue('templateUid', defaultTemplate.uid); + afterUpdateTemplate(defaultTemplate.uid); + resetNetwork(); } else { - setValue('templateUid', curTemplate.uid) - afterUpdateTemplate(curTemplate.uid) + setValue('templateUid', curTemplate.uid); + afterUpdateTemplate(curTemplate.uid); } - }, [templateListQuery.isSuccess, templateList, templateListQuery.isFetched, isEdit]) + }, [templateListQuery.isSuccess, templateList, templateListQuery.isFetched, isEdit]); return ( @@ -94,7 +92,7 @@ export default function TemplateSelector({ t.uid === field.value)?.name} + defaultValue={templateList.find((t) => t.uid === field.value)?.name} disabled /> ) : ( @@ -108,21 +106,21 @@ export default function TemplateSelector({ name={field.name} onchange={(val) => { // if (isEdit) return - const devboxName = getValues('name') + const devboxName = getValues('name'); if (!devboxName) { toast({ title: t('Please enter the devbox name first'), status: 'warning' - }) - return + }); + return; } - const oldTemplateUid = getValues('templateUid') - field.onChange(val) - afterUpdateTemplate(val) - if(oldTemplateUid !== val) resetNetwork() + const oldTemplateUid = getValues('templateUid'); + field.onChange(val); + afterUpdateTemplate(val); + if (oldTemplateUid !== val) resetNetwork(); }} /> )} - ) -} \ No newline at end of file + ); +} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/index.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/index.tsx index 68e0a7ee4e3..4303c6e2600 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/index.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/BasicConfiguration/index.tsx @@ -1,15 +1,15 @@ -import MyIcon from '@/components/Icon' -import { Box, BoxProps } from '@chakra-ui/react' -import { useTranslations } from 'next-intl' -import ConfigurationHeader from '../ConfigurationHeader' -import CpuSelector from './CpuSelector' -import DevboxNameInput from './DevboxNameInput' -import MemorySelector from './MemorySelector' -import TemplateRepositorySelector from './TemplateRepositorySelector' -import TemplateSelector from './TemplateSelector' +import MyIcon from '@/components/Icon'; +import { Box, BoxProps } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; +import ConfigurationHeader from '../ConfigurationHeader'; +import CpuSelector from './CpuSelector'; +import DevboxNameInput from './DevboxNameInput'; +import MemorySelector from './MemorySelector'; +import TemplateRepositorySelector from './TemplateRepositorySelector'; +import TemplateSelector from './TemplateSelector'; export default function BasicConfiguration({ isEdit, ...props }: BoxProps & { isEdit: boolean }) { - const t = useTranslations() + const t = useTranslations(); return ( @@ -32,5 +32,5 @@ export default function BasicConfiguration({ isEdit, ...props }: BoxProps & { is - ) + ); } diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/ConfigurationHeader.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/ConfigurationHeader.tsx index d38d46c5e1b..7782c5c1ec3 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/ConfigurationHeader.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/ConfigurationHeader.tsx @@ -1,5 +1,5 @@ -import { Box, BoxProps } from "@chakra-ui/react"; -import { ReactNode } from "react"; +import { Box, BoxProps } from '@chakra-ui/react'; +import { ReactNode } from 'react'; export default function ConfigurationHeader({ children }: { children: ReactNode }) { const headerStyles: BoxProps = { @@ -12,8 +12,6 @@ export default function ConfigurationHeader({ children }: { children: ReactNode display: 'flex', alignItems: 'center', backgroundColor: 'grayModern.50' - } - return - {children} - -} \ No newline at end of file + }; + return {children}; +} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/Label.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/Label.tsx index 1804caecf39..ee8f45d9f9b 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/Label.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/Label.tsx @@ -1,20 +1,21 @@ -import { Box, BoxProps } from "@chakra-ui/react" +import { Box, BoxProps } from '@chakra-ui/react'; const Label = ({ children, w = 'auto', ...props }: { - children: string - w?: number | 'auto' + children: string; + w?: number | 'auto'; } & BoxProps) => ( + {...props} + > {children} -) -export default Label \ No newline at end of file +); +export default Label; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/NetworkConfiguration/index.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/NetworkConfiguration/index.tsx index b4bbcfbe21b..daa936cb313 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/NetworkConfiguration/index.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/NetworkConfiguration/index.tsx @@ -1,8 +1,8 @@ -import MyIcon from '@/components/Icon' -import { ProtocolList } from '@/constants/devbox' -import { useEnvStore } from '@/stores/env' -import { DevboxEditTypeV2 } from '@/types/devbox' -import { nanoid } from '@/utils/tools' +import MyIcon from '@/components/Icon'; +import { ProtocolList } from '@/constants/devbox'; +import { useEnvStore } from '@/stores/env'; +import { DevboxEditTypeV2 } from '@/types/devbox'; +import { nanoid } from '@/utils/tools'; import { Box, BoxProps, @@ -13,38 +13,39 @@ import { Input, Switch, useTheme -} from '@chakra-ui/react' -import { MySelect, useMessage } from '@sealos/ui' -import { useTranslations } from 'next-intl' -import dynamic from 'next/dynamic' -import { useState } from 'react' -import { useFieldArray, useFormContext } from 'react-hook-form' -import ConfigurationHeader from '../ConfigurationHeader' +} from '@chakra-ui/react'; +import { MySelect, useMessage } from '@sealos/ui'; +import { useTranslations } from 'next-intl'; +import dynamic from 'next/dynamic'; +import { useState } from 'react'; +import { useFieldArray, useFormContext } from 'react-hook-form'; +import ConfigurationHeader from '../ConfigurationHeader'; // const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 12) export type CustomAccessModalParams = { - publicDomain: string - customDomain: string -} + publicDomain: string; + customDomain: string; +}; -const CustomAccessModal = dynamic(() => import('@/components/modals/CustomAccessModal')) +const CustomAccessModal = dynamic(() => import('@/components/modals/CustomAccessModal')); const AppendNetworksButton = (props: ButtonProps) => { - const t = useTranslations() + const t = useTranslations(); return ( - ) -} + ); +}; export default function NetworkConfiguration({ isEdit, ...props }: BoxProps & { isEdit: boolean }) { - const { register, getValues, control } = useFormContext() - const theme = useTheme() - const [customAccessModalData, setCustomAccessModalData] = useState() - const { env } = useEnvStore() + const { register, getValues, control } = useFormContext(); + const theme = useTheme(); + const [customAccessModalData, setCustomAccessModalData] = useState(); + const { env } = useEnvStore(); const { fields: networks, update: updateNetworks, @@ -53,9 +54,9 @@ export default function NetworkConfiguration({ isEdit, ...props }: BoxProps & { } = useFieldArray({ control, name: 'networks' - }) - const t = useTranslations() - const { message: toast } = useMessage() + }); + const t = useTranslations(); + const { message: toast } = useMessage(); const appendNetworks = () => { _appendNetworks({ networkName: '', @@ -65,8 +66,8 @@ export default function NetworkConfiguration({ isEdit, ...props }: BoxProps & { openPublicDomain: false, publicDomain: '', customDomain: '' - }) - } + }); + }; // const networks = watch('networks') return ( <> @@ -83,7 +84,8 @@ export default function NetworkConfiguration({ isEdit, ...props }: BoxProps & { alignItems={'flex-start'} key={network.id} _notLast={{ pb: 6, borderBottom: theme.borders.base }} - _notFirst={{ pt: 6 }}> + _notFirst={{ pt: 6 }} + > {t('Container Port')} @@ -108,12 +110,12 @@ export default function NetworkConfiguration({ isEdit, ...props }: BoxProps & { const ports = getValues('networks').map((network, index) => ({ port: network.port, index - })) + })); // 排除当前正在编辑的端口 const isDuplicate = ports.some( (item) => item.port === value && item.index !== i - ) - return !isDuplicate || t('The port number cannot be repeated') + ); + return !isDuplicate || t('The port number cannot be repeated'); } } })} @@ -135,13 +137,13 @@ export default function NetworkConfiguration({ isEdit, ...props }: BoxProps & { id={`openPublicDomain-${i}`} isChecked={!!network.openPublicDomain} onChange={(e) => { - const devboxName = getValues('name') + const devboxName = getValues('name'); if (!devboxName) { toast({ title: t('Please enter the devbox name first'), status: 'warning' - }) - return + }); + return; } updateNetworks(i, { ...getValues('networks')[i], @@ -149,7 +151,7 @@ export default function NetworkConfiguration({ isEdit, ...props }: BoxProps & { protocol: network.protocol || 'HTTP', openPublicDomain: e.target.checked, publicDomain: network.publicDomain || `${nanoid()}.${env.ingressDomain}` - }) + }); }} /> @@ -171,7 +173,7 @@ export default function NetworkConfiguration({ isEdit, ...props }: BoxProps & { updateNetworks(i, { ...getValues('networks')[i], protocol: val - }) + }); }} /> + borderBottomRightRadius={'md'} + > {network.customDomain ? network.customDomain : network.publicDomain!} @@ -198,7 +201,8 @@ export default function NetworkConfiguration({ isEdit, ...props }: BoxProps & { publicDomain: network.publicDomain!, customDomain: network.customDomain! }) - }> + } + > {t('Custom Domain')} @@ -235,16 +239,16 @@ export default function NetworkConfiguration({ isEdit, ...props }: BoxProps & { onSuccess={(e) => { const i = networks.findIndex( (item) => item.publicDomain === customAccessModalData.publicDomain - ) - if (i === -1) return + ); + if (i === -1) return; updateNetworks(i, { ...networks[i], customDomain: e - }) - setCustomAccessModalData(undefined) + }); + setCustomAccessModalData(undefined); }} /> )} - ) + ); } diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/index.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/index.tsx index 4743877d35e..29cc60b31c2 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/index.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/form/index.tsx @@ -1,29 +1,29 @@ -'use client' +'use client'; -import { Box, Flex, Grid, useTheme } from '@chakra-ui/react' -import { Tabs } from '@sealos/ui' -import { throttle } from 'lodash' -import { useTranslations } from 'next-intl' -import { useEffect, useState } from 'react' -import { useFormContext } from 'react-hook-form' +import { Box, Flex, Grid, useTheme } from '@chakra-ui/react'; +import { Tabs } from '@sealos/ui'; +import { throttle } from 'lodash'; +import { useTranslations } from 'next-intl'; +import { useEffect, useState } from 'react'; +import { useFormContext } from 'react-hook-form'; -import MyIcon from '@/components/Icon' -import PriceBox from '@/components/PriceBox' -import QuotaBox from '@/components/QuotaBox' -import { useRouter } from '@/i18n' +import MyIcon from '@/components/Icon'; +import PriceBox from '@/components/PriceBox'; +import QuotaBox from '@/components/QuotaBox'; +import { useRouter } from '@/i18n'; -import { useDevboxStore } from '@/stores/devbox' +import { useDevboxStore } from '@/stores/devbox'; -import type { DevboxEditTypeV2 } from '@/types/devbox' -import { obj2Query } from '@/utils/tools' -import BasicConfiguration from './BasicConfiguration' -import NetworkConfiguration from './NetworkConfiguration' +import type { DevboxEditTypeV2 } from '@/types/devbox'; +import { obj2Query } from '@/utils/tools'; +import BasicConfiguration from './BasicConfiguration'; +import NetworkConfiguration from './NetworkConfiguration'; const Form = ({ pxVal, isEdit }: { pxVal: number; isEdit: boolean }) => { - const theme = useTheme() - const router = useRouter() - const t = useTranslations() - const { watch } = useFormContext() + const theme = useTheme(); + const router = useRouter(); + const t = useTranslations(); + const { watch } = useFormContext(); const navList: { id: string; label: string; icon: string }[] = [ { id: 'baseInfo', @@ -35,43 +35,43 @@ const Form = ({ pxVal, isEdit }: { pxVal: number; isEdit: boolean }) => { label: t('Network Configuration'), icon: 'network' } - ] - const [activeNav, setActiveNav] = useState(navList[0].id) - const { devboxList } = useDevboxStore() + ]; + const [activeNav, setActiveNav] = useState(navList[0].id); + const { devboxList } = useDevboxStore(); // listen scroll and set activeNav useEffect(() => { const scrollFn = throttle((e: Event) => { - if (!e.target) return + if (!e.target) return; const doms = navList.map((item) => ({ dom: document.getElementById(item.id), id: item.id - })) + })); - const dom = e.target as HTMLDivElement - const scrollTop = dom.scrollTop + const dom = e.target as HTMLDivElement; + const scrollTop = dom.scrollTop; for (let i = doms.length - 1; i >= 0; i--) { - const offsetTop = doms[i].dom?.offsetTop || 0 + const offsetTop = doms[i].dom?.offsetTop || 0; if (scrollTop + 500 >= offsetTop) { - setActiveNav(doms[i].id) - break + setActiveNav(doms[i].id); + break; } } - }, 200) - document.getElementById('form-container')?.addEventListener('scroll', scrollFn) + }, 200); + document.getElementById('form-container')?.addEventListener('scroll', scrollFn); return () => { - document.getElementById('form-container')?.removeEventListener('scroll', scrollFn) - } + document.getElementById('form-container')?.removeEventListener('scroll', scrollFn); + }; // eslint-disable-next-line - }, []) + }, []); const boxStyles = { border: theme.borders.base, borderRadius: 'lg', mb: 4, bg: 'white' - } + }; return ( { templateColumns={'220px 1fr'} gridGap={5} alignItems={'start'} - pl={`${pxVal}px`}> + pl={`${pxVal}px`} + > {/* left sidebar */} { overflow={'hidden'} backgroundColor={'white'} border={theme.borders.base} - p={'4px'}> + p={'4px'} + > {navList.map((item) => ( { - setActiveNav(item.id) - window.location.hash = item.id - }}> + setActiveNav(item.id); + window.location.hash = item.id; + }} + > { backgroundColor: 'grayModern.100' }} color="grayModern.900" - backgroundColor={activeNav === item.id ? 'grayModern.100' : 'transparent'}> + backgroundColor={activeNav === item.id ? 'grayModern.100' : 'transparent'} + > { pr={`${pxVal}px`} height={'100%'} position={'relative'} - overflowY={'scroll'}> + overflowY={'scroll'} + > {/* base info */} {/* network */} - ) -} + ); +}; -export default Form +export default Form; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx index 3aac4e86e37..fe04ea827da 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx @@ -1,251 +1,251 @@ -'use client' +'use client'; -import { useRouter } from '@/i18n' -import { Box, Flex } from '@chakra-ui/react' -import { useMessage } from '@sealos/ui' -import { useQuery } from '@tanstack/react-query' -import { useTranslations } from 'next-intl' -import dynamic from 'next/dynamic' -import { useSearchParams } from 'next/navigation' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { FormProvider, useForm } from 'react-hook-form' +import { useRouter } from '@/i18n'; +import { Box, Flex } from '@chakra-ui/react'; +import { useMessage } from '@sealos/ui'; +import { useQuery } from '@tanstack/react-query'; +import { useTranslations } from 'next-intl'; +import dynamic from 'next/dynamic'; +import { useSearchParams } from 'next/navigation'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; -import Form from './components/form' -import Header from './components/Header' -import Yaml from './components/Yaml' +import Form from './components/form'; +import Header from './components/Header'; +import Yaml from './components/Yaml'; -import type { YamlItemType } from '@/types' -import type { DevboxEditType, DevboxEditTypeV2, DevboxKindsType } from '@/types/devbox' +import type { YamlItemType } from '@/types'; +import type { DevboxEditType, DevboxEditTypeV2, DevboxKindsType } from '@/types/devbox'; -import { useConfirm } from '@/hooks/useConfirm' -import { useLoading } from '@/hooks/useLoading' +import { useConfirm } from '@/hooks/useConfirm'; +import { useLoading } from '@/hooks/useLoading'; -import { useDevboxStore } from '@/stores/devbox' -import { useEnvStore } from '@/stores/env' -import { useGlobalStore } from '@/stores/global' -import { useIDEStore } from '@/stores/ide' -import { useUserStore } from '@/stores/user' +import { useDevboxStore } from '@/stores/devbox'; +import { useEnvStore } from '@/stores/env'; +import { useGlobalStore } from '@/stores/global'; +import { useIDEStore } from '@/stores/ide'; +import { useUserStore } from '@/stores/user'; -import { createDevbox, updateDevbox } from '@/api/devbox' -import { defaultDevboxEditValueV2, editModeMap } from '@/constants/devbox' -import { useTemplateStore } from '@/stores/template' -import { generateYamlList } from '@/utils/json2Yaml' -import { patchYamlList } from '@/utils/tools' -import { debounce } from 'lodash' +import { createDevbox, updateDevbox } from '@/api/devbox'; +import { defaultDevboxEditValueV2, editModeMap } from '@/constants/devbox'; +import { useTemplateStore } from '@/stores/template'; +import { generateYamlList } from '@/utils/json2Yaml'; +import { patchYamlList } from '@/utils/tools'; +import { debounce } from 'lodash'; -const ErrorModal = dynamic(() => import('@/components/modals/ErrorModal')) +const ErrorModal = dynamic(() => import('@/components/modals/ErrorModal')); const DevboxCreatePage = () => { - const { env } = useEnvStore() - const generateDefaultYamlList = () => generateYamlList(defaultDevboxEditValueV2, env) - const router = useRouter() - const t = useTranslations() - const searchParams = useSearchParams() - const { message: toast } = useMessage() - const { addDevboxIDE } = useIDEStore() - const { checkQuotaAllow } = useUserStore() - const { setDevboxDetail, devboxList } = useDevboxStore() + const { env } = useEnvStore(); + const generateDefaultYamlList = () => generateYamlList(defaultDevboxEditValueV2, env); + const router = useRouter(); + const t = useTranslations(); + const searchParams = useSearchParams(); + const { message: toast } = useMessage(); + const { addDevboxIDE } = useIDEStore(); + const { checkQuotaAllow } = useUserStore(); + const { setDevboxDetail, devboxList } = useDevboxStore(); - const crOldYamls = useRef([]) - const formOldYamls = useRef([]) - const oldDevboxEditData = useRef() + const crOldYamls = useRef([]); + const formOldYamls = useRef([]); + const oldDevboxEditData = useRef(); - const { Loading, setIsLoading } = useLoading() - const [errorMessage, setErrorMessage] = useState('') - const [yamlList, setYamlList] = useState([]) + const { Loading, setIsLoading } = useLoading(); + const [errorMessage, setErrorMessage] = useState(''); + const [yamlList, setYamlList] = useState([]); - const tabType = searchParams.get('type') || 'form' - const devboxName = searchParams.get('name') || '' + const tabType = searchParams.get('type') || 'form'; + const devboxName = searchParams.get('name') || ''; // NOTE: need to explain why this is needed // fix a bug: searchParams will disappear when go into this page - const [captureDevboxName, setCaptureDevboxName] = useState('') - const { updateTemplateModalConfig, config: templateConfig } = useTemplateStore() + const [captureDevboxName, setCaptureDevboxName] = useState(''); + const { updateTemplateModalConfig, config: templateConfig } = useTemplateStore(); useEffect(() => { - const name = searchParams.get('name') + const name = searchParams.get('name'); if (name) { - setCaptureDevboxName(name) - router.replace(`/devbox/create?name=${captureDevboxName}`, undefined) + setCaptureDevboxName(name); + router.replace(`/devbox/create?name=${captureDevboxName}`, undefined); } - }, [searchParams, router, captureDevboxName]) + }, [searchParams, router, captureDevboxName]); // eslint-disable-next-line react-hooks/exhaustive-deps - const isEdit = useMemo(() => !!devboxName, []) + const isEdit = useMemo(() => !!devboxName, []); - const { title, applyBtnText, applyMessage, applySuccess, applyError } = editModeMap(isEdit) + const { title, applyBtnText, applyMessage, applySuccess, applyError } = editModeMap(isEdit); const { openConfirm, ConfirmChild } = useConfirm({ content: applyMessage - }) + }); // compute container width - const { screenWidth, lastRoute } = useGlobalStore() + const { screenWidth, lastRoute } = useGlobalStore(); const pxVal = useMemo(() => { - const val = Math.floor((screenWidth - 1050) / 2) + const val = Math.floor((screenWidth - 1050) / 2); if (val < 20) { - return 20 + return 20; } - return val - }, [screenWidth]) + return val; + }, [screenWidth]); const formHook = useForm({ defaultValues: defaultDevboxEditValueV2 - }) + }); // updateyamlList every time yamlList change const debouncedUpdateYaml = useMemo( () => debounce((data: DevboxEditTypeV2, env) => { try { - const newYamlList = generateYamlList(data, env) - setYamlList(newYamlList) + const newYamlList = generateYamlList(data, env); + setYamlList(newYamlList); } catch (error) { - console.error('Failed to generate yaml:', error) + console.error('Failed to generate yaml:', error); } }, 300), [] - ) + ); // 监听表单变化 useEffect(() => { const subscription = formHook.watch((value) => { if (value) { - debouncedUpdateYaml(value as DevboxEditTypeV2, env) + debouncedUpdateYaml(value as DevboxEditTypeV2, env); } - }) + }); return () => { - subscription.unsubscribe() - debouncedUpdateYaml.cancel() - } - }, [formHook, debouncedUpdateYaml, env]) + subscription.unsubscribe(); + debouncedUpdateYaml.cancel(); + }; + }, [formHook, debouncedUpdateYaml, env]); useQuery( ['initDevboxCreateData'], () => { if (!devboxName) { - setYamlList(generateDefaultYamlList()) - return null + setYamlList(generateDefaultYamlList()); + return null; } - setIsLoading(true) - return setDevboxDetail(devboxName, env.sealosDomain) + setIsLoading(true); + return setDevboxDetail(devboxName, env.sealosDomain); }, { onSuccess(res) { if (!res) { - return + return; } - oldDevboxEditData.current = res - formOldYamls.current = generateYamlList(res, env) - crOldYamls.current = generateYamlList(res, env) as DevboxKindsType[] - formHook.reset(res) + oldDevboxEditData.current = res; + formOldYamls.current = generateYamlList(res, env); + crOldYamls.current = generateYamlList(res, env) as DevboxKindsType[]; + formHook.reset(res); }, onError(err) { toast({ title: String(err), status: 'error' - }) + }); }, onSettled() { - setIsLoading(false) + setIsLoading(false); } } - ) + ); const submitSuccess = async (formData: DevboxEditTypeV2) => { - setIsLoading(true) + setIsLoading(true); try { // quote check const quoteCheckRes = checkQuotaAllow( { ...formData, nodeports: devboxList.length + 1 } as DevboxEditTypeV2 & { - nodeports: number + nodeports: number; }, { ...oldDevboxEditData.current, nodeports: devboxList.length } as DevboxEditType & { - nodeports: number + nodeports: number; } - ) + ); if (quoteCheckRes) { - setIsLoading(false) + setIsLoading(false); return toast({ status: 'warning', title: t(quoteCheckRes), duration: 5000, isClosable: true - }) + }); } // update if (isEdit) { - const yamlList = generateYamlList(formData, env) - setYamlList(yamlList) - const parsedNewYamlList = yamlList.map((item) => item.value) - const parsedOldYamlList = formOldYamls.current.map((item) => item.value) + const yamlList = generateYamlList(formData, env); + setYamlList(yamlList); + const parsedNewYamlList = yamlList.map((item) => item.value); + const parsedOldYamlList = formOldYamls.current.map((item) => item.value); const areYamlListsEqual = new Set(parsedNewYamlList).size === new Set(parsedOldYamlList).size && - [...new Set(parsedNewYamlList)].every((item) => new Set(parsedOldYamlList).has(item)) + [...new Set(parsedNewYamlList)].every((item) => new Set(parsedOldYamlList).has(item)); if (areYamlListsEqual) { - setIsLoading(false) + setIsLoading(false); return toast({ status: 'info', title: t('No changes detected'), duration: 5000, isClosable: true - }) + }); } if (!parsedNewYamlList) { // prevent empty yamlList - return setErrorMessage(t('submit_form_error')) + return setErrorMessage(t('submit_form_error')); } const patch = patchYamlList({ parsedOldYamlList: parsedOldYamlList, parsedNewYamlList: parsedNewYamlList, originalYamlList: crOldYamls.current - }) + }); await updateDevbox({ patch, devboxName: formData.name - }) + }); } else { - await createDevbox({ devboxForm: formData }) + await createDevbox({ devboxForm: formData }); } - addDevboxIDE('vscode', formData.name) + addDevboxIDE('vscode', formData.name); toast({ title: t(applySuccess), status: 'success' - }) + }); updateTemplateModalConfig({ ...templateConfig, lastRoute - }) - router.push(lastRoute) + }); + router.push(lastRoute); } catch (error) { - console.log('error', error) + console.log('error', error); if (error instanceof String && error.includes('402')) { - setErrorMessage(t('outstanding_tips')) - } else setErrorMessage(JSON.stringify(error)) + setErrorMessage(t('outstanding_tips')); + } else setErrorMessage(JSON.stringify(error)); } - setIsLoading(false) - } + setIsLoading(false); + }; const submitError = useCallback(() => { // deep search message const deepSearch = (obj: any): string => { if (!obj || typeof obj !== 'object') { - return t('submit_form_error') + return t('submit_form_error'); } if (!!obj.message) { - return obj.message + return obj.message; } - return deepSearch(Object.values(obj)[0]) - } + return deepSearch(Object.values(obj)[0]); + }; toast({ title: deepSearch(formHook.formState.errors), status: 'error', position: 'top', duration: 3000, isClosable: true - }) - }, [formHook.formState.errors, toast, t]) + }); + }, [formHook.formState.errors, toast, t]); return ( <> @@ -255,7 +255,8 @@ const DevboxCreatePage = () => { alignItems={'center'} h={'100vh'} minWidth={'1024px'} - backgroundColor={'grayModern.100'}> + backgroundColor={'grayModern.100'} + >
{ setErrorMessage('')} /> )} - ) -} + ); +}; -export default DevboxCreatePage +export default DevboxCreatePage; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/BasicInfo.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/BasicInfo.tsx index c24a8755923..2afc0b480b4 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/BasicInfo.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/BasicInfo.tsx @@ -1,58 +1,74 @@ -import { Box, Flex, Image, Spinner, Text, Tooltip } from '@chakra-ui/react' -import { useMessage } from '@sealos/ui' -import { useTranslations } from 'next-intl' -import { useCallback, useState } from 'react' +import { useMessage } from '@sealos/ui'; +import { useTranslations } from 'next-intl'; +import React, { useCallback, useState } from 'react'; +import { Box, Text, Flex, Image, Spinner, Tooltip, Button } from '@chakra-ui/react'; -import MyIcon from '@/components/Icon' +import MyIcon from '@/components/Icon'; +import { useEnvStore } from '@/stores/env'; +import { useDevboxStore } from '@/stores/devbox'; +import { getTemplateConfig } from '@/api/template'; +import { getSSHConnectionInfo } from '@/api/devbox'; +import { JetBrainsGuideData } from '@/components/IDEButton'; +import { downLoadBlob, parseTemplateConfig } from '@/utils/tools'; +import SshConnectModal from '@/components/modals/SshConnectModal'; -import { DevboxDetailType } from '@/types/devbox' +const BasicInfo = () => { + const t = useTranslations(); + const { message: toast } = useMessage(); -import { useDevboxStore } from '@/stores/devbox' -import { useEnvStore } from '@/stores/env' + const { env } = useEnvStore(); + const { devboxDetail } = useDevboxStore(); -const BasicInfo = () => { - const t = useTranslations() - const { message: toast } = useMessage() + const [loading, setLoading] = useState(false); + const [onOpenSsHConnect, setOnOpenSsHConnect] = useState(false); + const [sshConfigData, setSshConfigData] = useState(null); + + const handleOneClickConfig = useCallback(async () => { + const { base64PrivateKey, userName, token } = await getSSHConnectionInfo({ + devboxName: devboxDetail?.name as string + }); + + const result = await getTemplateConfig(devboxDetail?.templateUid as string); + const config = parseTemplateConfig(result.template.config); + console.log('config', config); + + const sshPrivateKey = Buffer.from(base64PrivateKey, 'base64').toString('utf-8'); + + if (!devboxDetail?.sshPort) return; + + setSshConfigData({ + devboxName: devboxDetail?.name, + runtimeType: devboxDetail?.templateRepositoryName, + privateKey: sshPrivateKey, + userName, + token, + workingDir: config.workingDir, + host: env.sealosDomain, + port: devboxDetail?.sshPort.toString(), + configHost: `${env.sealosDomain}_${env.namespace}_${devboxDetail?.name}` + }); - const { env } = useEnvStore() - const { devboxDetail } = useDevboxStore() - // const { getRuntimeDetailLabel } = useRuntimeStore() + setOnOpenSsHConnect(true); + }, [ + devboxDetail?.name, + devboxDetail?.templateUid, + devboxDetail?.sshPort, + devboxDetail?.templateRepositoryName, + env.sealosDomain, + env.namespace + ]); - const [loading, setLoading] = useState(false) - const handleCopySSHCommand = useCallback(() => { - const sshCommand = `ssh -i yourPrivateKeyPath ${devboxDetail?.sshConfig?.sshUser}@${env.sealosDomain} -p ${devboxDetail?.sshPort}` + const sshCommand = `ssh -i yourPrivateKeyPath ${devboxDetail?.sshConfig?.sshUser}@${env.sealosDomain} -p ${devboxDetail?.sshPort}`; navigator.clipboard.writeText(sshCommand).then(() => { toast({ title: t('copy_success'), status: 'success', duration: 2000, isClosable: true - }) - }) - }, [devboxDetail?.sshConfig?.sshUser, devboxDetail?.sshPort, env.sealosDomain, toast, t]) - - const handleDownloadConfig = useCallback( - async (config: DevboxDetailType['sshConfig']) => { - setLoading(true) - - const privateKey = config?.sshPrivateKey as string - - const blob = new Blob([privateKey], { type: 'application/octet-stream' }) - const url = window.URL.createObjectURL(blob) - const a = document.createElement('a') - a.style.display = 'none' - a.href = url - a.download = devboxDetail?.name || '' - document.body.appendChild(a) - a.click() - window.URL.revokeObjectURL(url) - document.body.removeChild(a) - - setLoading(false) - }, - [devboxDetail?.name] - ) + }); + }); + }, [devboxDetail?.sshConfig?.sshUser, devboxDetail?.sshPort, env.sealosDomain, toast, t]); return ( @@ -75,7 +91,7 @@ const BasicInfo = () => { width={'20px'} height={'20px'} onError={(e) => { - e.currentTarget.src = '/images/custom.svg' + e.currentTarget.src = '/images/custom.svg'; }} alt={devboxDetail?.iconId} src={`/images/${devboxDetail?.iconId}.svg`} @@ -89,7 +105,8 @@ const BasicInfo = () => { {`${env.registryAddr}/${env.namespace}/${devboxDetail?.name}`} + w={'full'} + >{`${env.registryAddr}/${env.namespace}/${devboxDetail?.name}`} @@ -105,14 +122,10 @@ const BasicInfo = () => { {t('start_runtime')} - + { - // getRuntimeDetailLabel(devboxDetail?., devboxDetail?.runtimeVersion) - `${devboxDetail?.templateRepositoryName}-${devboxDetail?.templateName}` + // getRuntimeDetailLabel(devboxDetail?., devboxDetail?.runtimeVersion) + `${devboxDetail?.templateRepositoryName}-${devboxDetail?.templateName}` } @@ -143,19 +156,35 @@ const BasicInfo = () => { {/* ssh config */} - - + + + + {t('ssh_config')} + + + @@ -173,13 +202,15 @@ const BasicInfo = () => { fontSize={'12px'} fontWeight={400} py={2} - borderRadius={'md'}> + borderRadius={'md'} + > + w={'full'} + > {`ssh -i yourPrivateKeyPath ${devboxDetail?.sshConfig?.sshUser}@${env.sealosDomain} -p ${devboxDetail?.sshPort}`} @@ -201,19 +232,28 @@ const BasicInfo = () => { fontSize={'12px'} fontWeight={400} py={2} - borderRadius={'md'}> + borderRadius={'md'} + > + }} + > handleDownloadConfig(devboxDetail?.sshConfig)} + onClick={() => + downLoadBlob( + devboxDetail?.sshConfig?.sshPrivateKey as string, + 'application/octet-stream', + `${devboxDetail?.name}` + ) + } /> @@ -258,14 +298,16 @@ const BasicInfo = () => { fontSize={'12px'} fontWeight={400} py={2} - borderRadius={'md'}> + borderRadius={'md'} + > + }} + > { + {onOpenSsHConnect && sshConfigData && ( + { + setOnOpenSsHConnect(false); + }} + onClose={() => { + setOnOpenSsHConnect(false); + }} + /> + )} - ) -} + ); +}; -export default BasicInfo +export default BasicInfo; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx index 8e920f49e10..894dd535a97 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx @@ -1,113 +1,113 @@ -import { Box, Button, Flex } from '@chakra-ui/react' -import { useMessage } from '@sealos/ui' -import { useTranslations } from 'next-intl' -import { Dispatch, useCallback, useMemo, useState } from 'react' +import { Box, Button, Flex } from '@chakra-ui/react'; +import { useMessage } from '@sealos/ui'; +import { useTranslations } from 'next-intl'; +import { Dispatch, useCallback, useMemo, useState } from 'react'; -import { pauseDevbox, restartDevbox, startDevbox } from '@/api/devbox' -import { useRouter } from '@/i18n' -import { useDevboxStore } from '@/stores/devbox' -import { useGlobalStore } from '@/stores/global' +import { pauseDevbox, restartDevbox, startDevbox } from '@/api/devbox'; +import { useRouter } from '@/i18n'; +import { useDevboxStore } from '@/stores/devbox'; +import { useGlobalStore } from '@/stores/global'; -import { DevboxDetailTypeV2 } from '@/types/devbox' +import { DevboxDetailTypeV2 } from '@/types/devbox'; -import DevboxStatusTag from '@/components/DevboxStatusTag' -import MyIcon from '@/components/Icon' -import IDEButton from '@/components/IDEButton' -import DelModal from '@/components/modals/DelModal' -import { sealosApp } from 'sealos-desktop-sdk/app' -import { useQuery } from '@tanstack/react-query' +import DevboxStatusTag from '@/components/DevboxStatusTag'; +import MyIcon from '@/components/Icon'; +import IDEButton from '@/components/IDEButton'; +import DelModal from '@/components/modals/DelModal'; +import { sealosApp } from 'sealos-desktop-sdk/app'; +import { useQuery } from '@tanstack/react-query'; const Header = ({ refetchDevboxDetail, setShowSlider, isLargeScreen = true }: { - refetchDevboxDetail: () => void - setShowSlider: Dispatch - isLargeScreen: boolean + refetchDevboxDetail: () => void; + setShowSlider: Dispatch; + isLargeScreen: boolean; }) => { - const router = useRouter() - const t = useTranslations() - const { message: toast } = useMessage() + const router = useRouter(); + const t = useTranslations(); + const { message: toast } = useMessage(); - const { devboxDetail, setDevboxList } = useDevboxStore() - const { screenWidth, setLoading } = useGlobalStore() + const { devboxDetail, setDevboxList } = useDevboxStore(); + const { screenWidth, setLoading } = useGlobalStore(); - const [delDevbox, setDelDevbox] = useState(null) - const isBigButton = useMemo(() => screenWidth > 1000, [screenWidth]) + const [delDevbox, setDelDevbox] = useState(null); + const isBigButton = useMemo(() => screenWidth > 1000, [screenWidth]); const { refetch: refetchDevboxList } = useQuery(['devboxListQuery'], setDevboxList, { onSettled(res) { - if (!res) return + if (!res) return; } - }) + }); const handlePauseDevbox = useCallback( async (devbox: DevboxDetailTypeV2) => { try { - setLoading(true) - await pauseDevbox({ devboxName: devbox.name }) + setLoading(true); + await pauseDevbox({ devboxName: devbox.name }); toast({ title: t('pause_success'), status: 'success' - }) + }); } catch (error: any) { toast({ title: typeof error === 'string' ? error : error.message || t('pause_error'), status: 'error' - }) - console.error(error) + }); + console.error(error); } - refetchDevboxDetail() - setLoading(false) + refetchDevboxDetail(); + setLoading(false); }, [refetchDevboxDetail, setLoading, t, toast] - ) + ); const handleRestartDevbox = useCallback( async (devbox: DevboxDetailTypeV2) => { try { - setLoading(true) - await restartDevbox({ devboxName: devbox.name }) + setLoading(true); + await restartDevbox({ devboxName: devbox.name }); toast({ title: t('restart_success'), status: 'success' - }) + }); } catch (error: any) { toast({ title: typeof error === 'string' ? error : error.message || t('restart_error'), status: 'error' - }) - console.error(error, '==') + }); + console.error(error, '=='); } - refetchDevboxDetail() - setLoading(false) + refetchDevboxDetail(); + setLoading(false); }, [setLoading, t, toast, refetchDevboxDetail] - ) + ); const handleStartDevbox = useCallback( async (devbox: DevboxDetailTypeV2) => { try { - setLoading(true) - await startDevbox({ devboxName: devbox.name }) + setLoading(true); + await startDevbox({ devboxName: devbox.name }); toast({ title: t('start_success'), status: 'success' - }) + }); } catch (error: any) { toast({ title: typeof error === 'string' ? error : error.message || t('start_error'), status: 'error' - }) - console.error(error, '==') + }); + console.error(error, '=='); } - refetchDevboxDetail() - setLoading(false) + refetchDevboxDetail(); + setLoading(false); }, [setLoading, t, toast, refetchDevboxDetail] - ) + ); const handleGoToTerminal = useCallback( async (devbox: DevboxDetailTypeV2) => { - const defaultCommand = `kubectl exec -it $(kubectl get po -l app.kubernetes.io/name=${devbox.name} -oname) -- sh -c "clear; (bash || ash || sh)"` + const defaultCommand = `kubectl exec -it $(kubectl get po -l app.kubernetes.io/name=${devbox.name} -oname) -- sh -c "clear; (bash || ash || sh)"`; try { sealosApp.runEvents('openDesktopApp', { appKey: 'system-terminal', @@ -115,18 +115,18 @@ const Header = ({ defaultCommand }, messageData: { type: 'new terminal', command: defaultCommand } - }) + }); } catch (error: any) { toast({ title: typeof error === 'string' ? error : error.message || t('jump_terminal_error'), status: 'error' - }) - console.error(error) + }); + console.error(error); } }, [t, toast] - ) - if (!devboxDetail) return null + ); + if (!devboxDetail) return null; return ( {/* left back button and title */} @@ -157,7 +157,8 @@ const Header = ({ _hover={{ color: 'brightBlue.600' }} - onClick={() => setShowSlider(true)}> + onClick={() => setShowSlider(true)} + > {t('detail')} @@ -168,6 +169,7 @@ const Header = ({ : undefined} - onClick={() => handleGoToTerminal(devboxDetail)}> + onClick={() => handleGoToTerminal(devboxDetail)} + > {isBigButton ? t('terminal') : } {devboxDetail.status.value === 'Running' && ( @@ -215,7 +218,8 @@ const Header = ({ }} borderWidth={1} leftIcon={isBigButton ? : undefined} - onClick={() => handlePauseDevbox(devboxDetail)}> + onClick={() => handlePauseDevbox(devboxDetail)} + > {isBigButton ? t('pause') : } )} @@ -230,7 +234,8 @@ const Header = ({ }} borderWidth={1} leftIcon={isBigButton ? : undefined} - onClick={() => handleStartDevbox(devboxDetail)}> + onClick={() => handleStartDevbox(devboxDetail)} + > {isBigButton ? t('start') : } )} @@ -244,7 +249,8 @@ const Header = ({ }} borderWidth={1} leftIcon={isBigButton ? : undefined} - onClick={() => router.push(`/devbox/create?name=${devboxDetail.name}`)}> + onClick={() => router.push(`/devbox/create?name=${devboxDetail.name}`)} + > {!isBigButton ? : t('update')} {devboxDetail.status.value !== 'Stopped' && ( @@ -258,7 +264,8 @@ const Header = ({ }} borderWidth={1} leftIcon={isBigButton ? : undefined} - onClick={() => handleRestartDevbox(devboxDetail)}> + onClick={() => handleRestartDevbox(devboxDetail)} + > {isBigButton ? t('restart') : } )} @@ -272,7 +279,8 @@ const Header = ({ }} borderWidth={1} leftIcon={isBigButton ? : undefined} - onClick={() => setDelDevbox(devboxDetail)}> + onClick={() => setDelDevbox(devboxDetail)} + > {isBigButton ? t('delete') : } @@ -281,14 +289,14 @@ const Header = ({ devbox={delDevbox} onClose={() => setDelDevbox(null)} onSuccess={() => { - setDelDevbox(null) - router.push('/') + setDelDevbox(null); + router.push('/'); }} refetchDevboxList={refetchDevboxList} /> )} - ) -} + ); +}; -export default Header +export default Header; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/MainBody.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/MainBody.tsx index 9878681fcc9..0884643b036 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/MainBody.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/MainBody.tsx @@ -1,33 +1,33 @@ -import { Box, Button, Flex, Text, Tooltip, useDisclosure } from '@chakra-ui/react' -import dayjs from 'dayjs' -import { useTranslations } from 'next-intl' -import dynamic from 'next/dynamic' +import { Box, Button, Flex, Text, Tooltip, useDisclosure } from '@chakra-ui/react'; +import dayjs from 'dayjs'; +import { useTranslations } from 'next-intl'; +import dynamic from 'next/dynamic'; -import MyIcon from '@/components/Icon' -import MyTable from '@/components/MyTable' -import PodLineChart from '@/components/PodLineChart' +import MyIcon from '@/components/Icon'; +import MyTable from '@/components/MyTable'; +import PodLineChart from '@/components/PodLineChart'; -import { NetworkType } from '@/types/devbox' -import { useCopyData } from '@/utils/tools' +import { NetworkType } from '@/types/devbox'; +import { useCopyData } from '@/utils/tools'; -import { useDevboxStore } from '@/stores/devbox' -import { useEnvStore } from '@/stores/env' +import { useDevboxStore } from '@/stores/devbox'; +import { useEnvStore } from '@/stores/env'; -const MonitorModal = dynamic(() => import('@/components/modals/MonitorModal')) +const MonitorModal = dynamic(() => import('@/components/modals/MonitorModal')); const MainBody = () => { - const t = useTranslations() - const { copyData } = useCopyData() - const { devboxDetail } = useDevboxStore() - const { env } = useEnvStore() - const { isOpen, onOpen, onClose } = useDisclosure() + const t = useTranslations(); + const { copyData } = useCopyData(); + const { devboxDetail } = useDevboxStore(); + const { env } = useEnvStore(); + const { isOpen, onOpen, onClose } = useDisclosure(); const networkColumn: { - title: string - dataIndex?: keyof NetworkType - key: string - render?: (item: NetworkType) => JSX.Element - width?: string + title: string; + dataIndex?: keyof NetworkType; + key: string; + render?: (item: NetworkType) => JSX.Element; + width?: string; }[] = [ { title: t('port'), @@ -37,7 +37,7 @@ const MainBody = () => { {item.port} - ) + ); }, width: '0.5fr' }, @@ -54,7 +54,8 @@ const MainBody = () => { fontSize={'12px'} fontWeight={400} py={2} - borderRadius={'md'}> + borderRadius={'md'} + > { copyData( `http://${devboxDetail?.name}.${env.namespace}.svc.cluster.local:${item.port}` ) - }>{`http://${devboxDetail?.name}.${env.namespace}.svc.cluster.local:${item.port}`} + } + >{`http://${devboxDetail?.name}.${env.namespace}.svc.cluster.local:${item.port}`} - ) + ); } }, { @@ -75,7 +77,7 @@ const MainBody = () => { key: 'externalAddress', render: (item: NetworkType) => { if (item.openPublicDomain) { - const address = item.customDomain || item.publicDomain + const address = item.customDomain || item.publicDomain; return ( { fontSize={'12px'} fontWeight={400} py={2} - borderRadius={'md'}> + borderRadius={'md'} + > window.open(`https://${address}`, '_blank')}> + onClick={() => window.open(`https://${address}`, '_blank')} + > https://{address} - ) + ); } - return - + return -; } } - ] + ]; return ( {/* monitor */} @@ -132,7 +136,8 @@ const MainBody = () => { position={'absolute'} right={'2px'} top={'-6px'} - onClick={onOpen}> + onClick={onOpen} + > @@ -169,7 +174,7 @@ const MainBody = () => { - ) -} + ); +}; -export default MainBody +export default MainBody; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Version.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Version.tsx index 439a1c88530..8dfbea00b2f 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Version.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Version.tsx @@ -1,64 +1,64 @@ -'use client' +'use client'; -import { Box, Button, Flex, MenuButton, Text, useDisclosure } from '@chakra-ui/react' -import { SealosMenu, useMessage } from '@sealos/ui' -import { useQuery } from '@tanstack/react-query' -import { customAlphabet } from 'nanoid' -import { useTranslations } from 'next-intl' -import { useCallback, useEffect, useState } from 'react' -import { sealosApp } from 'sealos-desktop-sdk/app' +import { Box, Button, Flex, MenuButton, Text, useDisclosure } from '@chakra-ui/react'; +import { SealosMenu, useMessage } from '@sealos/ui'; +import { useQuery } from '@tanstack/react-query'; +import { customAlphabet } from 'nanoid'; +import { useTranslations } from 'next-intl'; +import { useCallback, useEffect, useState } from 'react'; +import { sealosApp } from 'sealos-desktop-sdk/app'; -import { delDevboxVersionByName, getAppsByDevboxId } from '@/api/devbox' -import DevboxStatusTag from '@/components/DevboxStatusTag' -import MyIcon from '@/components/Icon' -import EditVersionDesModal from '@/components/modals/EditVersionDesModal' -import ReleaseModal from '@/components/modals/releaseModal' -import MyTable from '@/components/MyTable' -import { devboxIdKey, DevboxReleaseStatusEnum } from '@/constants/devbox' -import { DevboxVersionListItemType } from '@/types/devbox' +import { delDevboxVersionByName, getAppsByDevboxId } from '@/api/devbox'; +import DevboxStatusTag from '@/components/DevboxStatusTag'; +import EditVersionDesModal from '@/components/modals/EditVersionDesModal'; +import ReleaseModal from '@/components/modals/ReleaseModal'; +import MyTable from '@/components/MyTable'; +import { devboxIdKey, DevboxReleaseStatusEnum } from '@/constants/devbox'; +import { DevboxVersionListItemType } from '@/types/devbox'; -import { useConfirm } from '@/hooks/useConfirm' -import { useLoading } from '@/hooks/useLoading' +import { useConfirm } from '@/hooks/useConfirm'; +import { useLoading } from '@/hooks/useLoading'; -import { getTemplateConfig, listPrivateTemplateRepository } from '@/api/template' -import CreateTemplateModal from '@/app/[lang]/(platform)/template/updateTemplate/CreateTemplateModal' -import SelectTemplateModal from '@/app/[lang]/(platform)/template/updateTemplate/SelectActionModal' -import UpdateTemplateRepositoryModal from '@/app/[lang]/(platform)/template/updateTemplate/UpdateTemplateRepositoryModal' -import AppSelectModal from '@/components/modals/AppSelectModal' -import useReleaseDriver from '@/hooks/useReleaseDriver' -import { useDevboxStore } from '@/stores/devbox' -import { useEnvStore } from '@/stores/env' -import { AppListItemType } from '@/types/app' -import { parseTemplateConfig } from '@/utils/tools' +import { getTemplateConfig, listPrivateTemplateRepository } from '@/api/template'; +import CreateTemplateModal from '@/app/[lang]/(platform)/template/updateTemplate/CreateTemplateModal'; +import SelectTemplateModal from '@/app/[lang]/(platform)/template/updateTemplate/SelectActionModal'; +import UpdateTemplateRepositoryModal from '@/app/[lang]/(platform)/template/updateTemplate/UpdateTemplateRepositoryModal'; +import AppSelectModal from '@/components/modals/AppSelectModal'; +import useReleaseDriver from '@/hooks/useReleaseDriver'; +import { useDevboxStore } from '@/stores/devbox'; +import { useEnvStore } from '@/stores/env'; +import { AppListItemType } from '@/types/app'; +import { parseTemplateConfig } from '@/utils/tools'; +import MyIcon from '@/components/Icon'; -const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 6) +const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 6); const Version = () => { - const { startReleaseGuide } = useReleaseDriver() - const t = useTranslations() - const { message: toast } = useMessage() - const { Loading, setIsLoading } = useLoading() - const { isOpen: isOpenEdit, onOpen: onOpenEdit, onClose: onCloseEdit } = useDisclosure() + const { startReleaseGuide } = useReleaseDriver(); + const t = useTranslations(); + const { message: toast } = useMessage(); + const { Loading, setIsLoading } = useLoading(); + const { isOpen: isOpenEdit, onOpen: onOpenEdit, onClose: onCloseEdit } = useDisclosure(); - const { env } = useEnvStore() - const { devboxDetail: devbox, devboxVersionList, setDevboxVersionList } = useDevboxStore() + const { env } = useEnvStore(); + const { devboxDetail: devbox, devboxVersionList, setDevboxVersionList } = useDevboxStore(); - const [initialized, setInitialized] = useState(false) - const [onOpenRelease, setOnOpenRelease] = useState(false) - const [onOpenSelectApp, setOnOpenSelectApp] = useState(false) - const [apps, setApps] = useState([]) - const [deployData, setDeployData] = useState(null) - const [currentVersion, setCurrentVersion] = useState(null) + const [initialized, setInitialized] = useState(false); + const [onOpenRelease, setOnOpenRelease] = useState(false); + const [onOpenSelectApp, setOnOpenSelectApp] = useState(false); + const [apps, setApps] = useState([]); + const [deployData, setDeployData] = useState(null); + const [currentVersion, setCurrentVersion] = useState(null); const [updateTemplateRepo, setUpdateTemplateRepo] = useState< | null | Awaited>['templateRepositoryList'][number] - >(null) - const createTemplateModalHandler = useDisclosure() - const selectTemplalteModalHandler = useDisclosure() - const updateTemplateModalHandler = useDisclosure() + >(null); + const createTemplateModalHandler = useDisclosure(); + const selectTemplalteModalHandler = useDisclosure(); + const updateTemplateModalHandler = useDisclosure(); const { openConfirm, ConfirmChild } = useConfirm({ content: 'delete_version_confirm_info' - }) + }); const { refetch } = useQuery( ['initDevboxVersionList'], () => setDevboxVersionList(devbox!.name, devbox!.id), @@ -72,17 +72,17 @@ const Version = () => { ? 3000 : false, onSettled() { - setInitialized(true) + setInitialized(true); }, enabled: !!devbox } - ) + ); useEffect(() => { if (devboxVersionList?.length && devboxVersionList.length > 0) { - startReleaseGuide() + startReleaseGuide(); } - }, [devboxVersionList.length]) + }, [devboxVersionList.length]); const listPrivateTemplateRepositoryQuery = useQuery( ['template-repository-list', 'template-repository-private'], @@ -90,29 +90,28 @@ const Version = () => { return listPrivateTemplateRepository({ page: 1, pageSize: 100 - }) + }); } - ) + ); const templateRepositoryList = - listPrivateTemplateRepositoryQuery.data?.templateRepositoryList || [] + listPrivateTemplateRepositoryQuery.data?.templateRepositoryList || []; const handleDeploy = useCallback( async (version: DevboxVersionListItemType) => { - // const { releaseCommand, releaseArgs } = await getSSHRuntimeInfo(devbox.runtimeVersion) - if (!devbox) return - const result = await getTemplateConfig(devbox.templateUid) - const config = parseTemplateConfig(result.template.config) - const releaseArgs = config.releaseArgs.join(' ') - const releaseCommand = config.releaseCommand.join(' ') - const { cpu, memory, networks, name } = devbox + if (!devbox) return; + const result = await getTemplateConfig(devbox.templateUid); + const config = parseTemplateConfig(result.template.config); + const releaseArgs = config.releaseArgs.join(' '); + const releaseCommand = config.releaseCommand.join(' '); + const { cpu, memory, networks, name } = devbox; const newNetworks = networks.map((network) => { return { port: network.port, protocol: network.protocol, openPublicDomain: network.openPublicDomain, domain: env.ingressDomain - } - }) - const imageName = `${env.registryAddr}/${env.namespace}/${devbox.name}:${version.tag}` + }; + }); + const imageName = `${env.registryAddr}/${env.namespace}/${devbox.name}:${version.tag}`; const transformData = { appName: `${name}-release-${nanoid()}`, @@ -135,13 +134,13 @@ const Version = () => { labels: { [devboxIdKey]: devbox.id } - } - setDeployData(transformData) - const apps = await getAppsByDevboxId(devbox.id) + }; + setDeployData(transformData); + const apps = await getAppsByDevboxId(devbox.id); // when: there is no app,create a new app if (apps.length === 0) { - const tempFormDataStr = encodeURIComponent(JSON.stringify(transformData)) + const tempFormDataStr = encodeURIComponent(JSON.stringify(transformData)); sealosApp.runEvents('openDesktopApp', { appKey: 'system-applaunchpad', pathname: '/redirect', @@ -150,55 +149,55 @@ const Version = () => { type: 'InternalAppCall', formData: tempFormDataStr } - }) + }); } // when: there have apps,show the app select modal if (apps.length >= 1) { - setApps(apps) - setOnOpenSelectApp(true) + setApps(apps); + setOnOpenSelectApp(true); } }, [devbox, env.ingressDomain, env.namespace, env.registryAddr] - ) + ); const handleDelDevboxVersion = useCallback( async (versionName: string) => { try { - setIsLoading(true) - await delDevboxVersionByName(versionName) + setIsLoading(true); + await delDevboxVersionByName(versionName); toast({ title: t('delete_successful'), status: 'success' - }) - let retryCount = 0 - const maxRetries = 3 - const retryInterval = 3000 + }); + let retryCount = 0; + const maxRetries = 3; + const retryInterval = 3000; const retry = async () => { if (retryCount < maxRetries) { - await new Promise((resolve) => setTimeout(resolve, retryInterval)) - await refetch() - retryCount++ + await new Promise((resolve) => setTimeout(resolve, retryInterval)); + await refetch(); + retryCount++; } - } - retry() + }; + retry(); } catch (error: any) { toast({ title: typeof error === 'string' ? error : error.message || t('delete_failed'), status: 'error' - }) - console.error(error) + }); + console.error(error); } - setIsLoading(false) + setIsLoading(false); }, [setIsLoading, toast, t, refetch] - ) + ); const columns: { - title: string - dataIndex?: keyof DevboxVersionListItemType - key: string - render?: (item: DevboxVersionListItemType) => JSX.Element + title: string; + dataIndex?: keyof DevboxVersionListItemType; + key: string; + render?: (item: DevboxVersionListItemType) => JSX.Element; }[] = [ { title: t('version_number'), @@ -221,7 +220,7 @@ const Version = () => { dataIndex: 'createTime', key: 'createTime', render: (item: DevboxVersionListItemType) => { - return {item.createTime} + return {item.createTime}; } }, { @@ -254,13 +253,20 @@ const Version = () => { color: 'brightBlue.600' }} isDisabled={item.status.value !== DevboxReleaseStatusEnum.Success} - onClick={() => handleDeploy(item)}> + onClick={() => handleDeploy(item)} + > {t('deploy')} + { ), onClick: () => { - setCurrentVersion(item) - onOpenEdit() + setCurrentVersion(item); + onOpenEdit(); } }, { @@ -292,13 +298,13 @@ const Version = () => { ), onClick: () => { - setCurrentVersion(item) + setCurrentVersion(item); // onOpenEdit() // openTemplateModal({templateState: }) if (templateRepositoryList.length > 0) { - selectTemplalteModalHandler.onOpen() + selectTemplalteModalHandler.onOpen(); } else { - createTemplateModalHandler.onOpen() + createTemplateModalHandler.onOpen(); } } }, @@ -322,7 +328,7 @@ const Version = () => { ) } - ] + ]; return ( { pr={6} bg={'white'} h={'full'} - position={'relative'}> + position={'relative'} + > @@ -350,7 +357,8 @@ const Version = () => { leftIcon={} _hover={{ color: 'brightBlue.600' - }}> + }} + > {t('release_version')} @@ -361,7 +369,8 @@ const Version = () => { alignItems={'center'} mt={10} flexDirection={'column'} - gap={4}> + gap={4} + > {t('no_versions')} @@ -387,7 +396,7 @@ const Version = () => { { - setOnOpenRelease(false) + setOnOpenRelease(false); }} devbox={{ ...devbox, sshPort: devbox.sshPort || 0 }} /> @@ -411,9 +420,9 @@ const Version = () => { { - const repo = templateRepositoryList.find((item) => item.uid === uid) - setUpdateTemplateRepo(repo || null) - updateTemplateModalHandler.onOpen() + const repo = templateRepositoryList.find((item) => item.uid === uid); + setUpdateTemplateRepo(repo || null); + updateTemplateModalHandler.onOpen(); }} templateRepositoryList={templateRepositoryList} isOpen={selectTemplalteModalHandler.isOpen} @@ -429,7 +438,7 @@ const Version = () => { /> )} - ) -} + ); +}; -export default Version +export default Version; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/page.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/page.tsx index 79a651022db..90eaad9f208 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/page.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/page.tsx @@ -1,70 +1,70 @@ -'use client' +'use client'; -import { Box, Flex } from '@chakra-ui/react' -import { useQuery } from '@tanstack/react-query' -import { useMemo, useState } from 'react' +import { Box, Flex } from '@chakra-ui/react'; +import { useQuery } from '@tanstack/react-query'; +import { useMemo, useState } from 'react'; -import { useLoading } from '@/hooks/useLoading' -import BasicInfo from './components/BasicInfo' -import Header from './components/Header' -import MainBody from './components/MainBody' -import Version from './components/Version' +import { useLoading } from '@/hooks/useLoading'; +import BasicInfo from './components/BasicInfo'; +import Header from './components/Header'; +import MainBody from './components/MainBody'; +import Version from './components/Version'; -import { useDevboxStore } from '@/stores/devbox' -import { useEnvStore } from '@/stores/env' -import { useGlobalStore } from '@/stores/global' -import useDetailDriver from '@/hooks/useDetailDriver' +import { useDevboxStore } from '@/stores/devbox'; +import { useEnvStore } from '@/stores/env'; +import { useGlobalStore } from '@/stores/global'; +import useDetailDriver from '@/hooks/useDetailDriver'; const DevboxDetailPage = ({ params }: { params: { name: string } }) => { - const devboxName = params.name - const { Loading } = useLoading() - const { handleUserGuide } = useDetailDriver() + const devboxName = params.name; + const { Loading } = useLoading(); + const { handleUserGuide } = useDetailDriver(); - const { env } = useEnvStore() - const { screenWidth } = useGlobalStore() + const { env } = useEnvStore(); + const { screenWidth } = useGlobalStore(); const { devboxDetail, setDevboxDetail, loadDetailMonitorData, intervalLoadPods } = - useDevboxStore() + useDevboxStore(); - const [showSlider, setShowSlider] = useState(false) - const [initialized, setInitialized] = useState(false) - const isLargeScreen = useMemo(() => screenWidth > 1280, [screenWidth]) + const [showSlider, setShowSlider] = useState(false); + const [initialized, setInitialized] = useState(false); + const isLargeScreen = useMemo(() => screenWidth > 1280, [screenWidth]); const { refetch, data } = useQuery( ['initDevboxDetail'], () => setDevboxDetail(devboxName, env.sealosDomain), { onSettled() { - setInitialized(true) + setInitialized(true); }, onSuccess: () => { - handleUserGuide() + handleUserGuide(); } } - ) + ); useQuery( ['devbox-detail-pod'], () => { - if (devboxDetail?.isPause) return null - return intervalLoadPods(devboxName, true) + if (devboxDetail?.isPause) return null; + return intervalLoadPods(devboxName, true); }, { enabled: !devboxDetail?.isPause, refetchOnMount: true, refetchInterval: 3000 } - ) + ); useQuery( ['loadDetailMonitorData', devboxName, devboxDetail?.isPause], () => { - if (devboxDetail?.isPause) return null - return loadDetailMonitorData(devboxName) + if (devboxDetail?.isPause) return null; + return loadDetailMonitorData(devboxName); }, { refetchOnMount: true, refetchInterval: 2 * 60 * 1000 } - ) + ); return ( @@ -96,7 +96,8 @@ const DevboxDetailPage = ({ params }: { params: { name: string } }) => { left: 0, boxShadow: '7px 4px 12px rgba(165, 172, 185, 0.25)', transform: `translateX(${showSlider ? '0' : '-500'}px)` - })}> + })} + > { }, msOverflowStyle: 'none', // IE and Edge scrollbarWidth: 'none' // Firefox - }}> + }} + > @@ -134,7 +136,7 @@ const DevboxDetailPage = ({ params }: { params: { name: string } }) => { )} - ) -} + ); +}; -export default DevboxDetailPage +export default DevboxDetailPage; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/layout.tsx b/frontend/providers/devbox/app/[lang]/(platform)/layout.tsx index cbac0a74a99..a45ed63ebd1 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/layout.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/layout.tsx @@ -1,147 +1,146 @@ -'use client' +'use client'; -import { usePathname, useRouter } from '@/i18n' -import throttle from 'lodash/throttle' -import { useSearchParams } from 'next/navigation' -import { useEffect, useState } from 'react' -import { EVENT_NAME } from 'sealos-desktop-sdk' -import { createSealosApp, sealosApp } from 'sealos-desktop-sdk/app' +import { usePathname, useRouter } from '@/i18n'; +import throttle from 'lodash/throttle'; +import { useSearchParams } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { EVENT_NAME } from 'sealos-desktop-sdk'; +import { createSealosApp, sealosApp } from 'sealos-desktop-sdk/app'; // import { useConfirm } from '@/hooks/useConfirm' -import { useLoading } from '@/hooks/useLoading' +import { useLoading } from '@/hooks/useLoading'; -import { useEnvStore } from '@/stores/env' -import { useGlobalStore } from '@/stores/global' -import { usePriceStore } from '@/stores/price' +import { useEnvStore } from '@/stores/env'; +import { useGlobalStore } from '@/stores/global'; +import { usePriceStore } from '@/stores/price'; -import { initUser } from '@/api/template' -import ChakraProvider from '@/components/providers/MyChakraProvider' -import RouteHandlerProvider from '@/components/providers/MyRouteHandlerProvider' -import { useConfirm } from '@/hooks/useConfirm' -import { getLangStore, setLangStore } from '@/utils/cookie' -import { cleanSession, setSessionToSessionStorage } from '@/utils/user' -import { useQueryClient } from '@tanstack/react-query' -import TemplateModal from './template/TemplateModal' +import { initUser } from '@/api/template'; +import ChakraProvider from '@/components/providers/MyChakraProvider'; +import RouteHandlerProvider from '@/components/providers/MyRouteHandlerProvider'; +import { useConfirm } from '@/hooks/useConfirm'; +import { getLangStore, setLangStore } from '@/utils/cookie'; +import { cleanSession, setSessionToSessionStorage } from '@/utils/user'; +import { useQueryClient } from '@tanstack/react-query'; +import TemplateModal from './template/TemplateModal'; export default function PlatformLayout({ children }: { children: React.ReactNode }) { - const router = useRouter() - const pathname = usePathname() - const { Loading } = useLoading() - const { setEnv, env } = useEnvStore() - const searchParams = useSearchParams() - const { setSourcePrice } = usePriceStore() - const [refresh, setRefresh] = useState(false) - const { setScreenWidth, loading, setLastRoute } = useGlobalStore() + const router = useRouter(); + const pathname = usePathname(); + const { Loading } = useLoading(); + const { setEnv, env } = useEnvStore(); + const searchParams = useSearchParams(); + const { setSourcePrice } = usePriceStore(); + const [refresh, setRefresh] = useState(false); + const { setScreenWidth, loading, setLastRoute } = useGlobalStore(); const { openConfirm, ConfirmChild } = useConfirm({ title: 'jump_prompt', content: 'not_allow_standalone_use' - }) - const queryClient = useQueryClient() - const [init, setInit] = useState(false) + }); + const queryClient = useQueryClient(); + const [init, setInit] = useState(false); // init session useEffect(() => { - const response = createSealosApp() - ; (async () => { - try { - - const newSession = JSON.stringify(await sealosApp.getSession()) - const oldSession = sessionStorage.getItem('session') - if(newSession && newSession !== oldSession) { - sessionStorage.setItem('session', newSession) - return window.location.reload() - } - // init user - console.log('devbox: app init success') - const token = (await initUser()) - if (!!token) { - setSessionToSessionStorage(token) - setInit(true) - } - queryClient.clear() - } catch (err) { - console.log('devbox: app is not running in desktop') - if (!process.env.NEXT_PUBLIC_MOCK_USER) { - cleanSession() - openConfirm(() => { - window.open(`https://${env.sealosDomain}`, '_self') - })() - } + const response = createSealosApp(); + (async () => { + try { + const newSession = JSON.stringify(await sealosApp.getSession()); + const oldSession = sessionStorage.getItem('session'); + if (newSession && newSession !== oldSession) { + sessionStorage.setItem('session', newSession); + return window.location.reload(); } - })() - return response + // init user + console.log('devbox: app init success'); + const token = await initUser(); + if (!!token) { + setSessionToSessionStorage(token); + setInit(true); + } + queryClient.clear(); + } catch (err) { + console.log('devbox: app is not running in desktop'); + if (!process.env.NEXT_PUBLIC_MOCK_USER) { + cleanSession(); + openConfirm(() => { + window.open(`https://${env.sealosDomain}`, '_self'); + })(); + } + } + })(); + return response; // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + }, []); useEffect(() => { - if (!init) return - setSourcePrice() + if (!init) return; + setSourcePrice(); // setRuntime() - setEnv() + setEnv(); const changeI18n = async (data: any) => { - const lastLang = getLangStore() - const newLang = data.currentLanguage + const lastLang = getLangStore(); + const newLang = data.currentLanguage; if (lastLang !== newLang) { - router.push(pathname, { locale: newLang }) - setLangStore(newLang) - setRefresh((state) => !state) + router.push(pathname, { locale: newLang }); + setLangStore(newLang); + setRefresh((state) => !state); } - } - - ; (async () => { - try { - const lang = await sealosApp.getLanguage() - changeI18n({ - currentLanguage: lang.lng - }) - } catch (error) { - changeI18n({ - currentLanguage: 'zh' - }) - } - })() + }; + + (async () => { + try { + const lang = await sealosApp.getLanguage(); + changeI18n({ + currentLanguage: lang.lng + }); + } catch (error) { + changeI18n({ + currentLanguage: 'en' + }); + } + })(); - return sealosApp?.addAppEventListen(EVENT_NAME.CHANGE_I18N, changeI18n) + return sealosApp?.addAppEventListen(EVENT_NAME.CHANGE_I18N, changeI18n); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [init]) + }, [init]); // add resize event useEffect(() => { const resize = throttle((e: Event) => { - const documentWidth = document.documentElement.clientWidth || document.body.clientWidth - setScreenWidth(documentWidth) - }, 200) - window.addEventListener('resize', resize) - const documentWidth = document.documentElement.clientWidth || document.body.clientWidth - setScreenWidth(documentWidth) + const documentWidth = document.documentElement.clientWidth || document.body.clientWidth; + setScreenWidth(documentWidth); + }, 200); + window.addEventListener('resize', resize); + const documentWidth = document.documentElement.clientWidth || document.body.clientWidth; + setScreenWidth(documentWidth); return () => { - window.removeEventListener('resize', resize) - } - }, [setScreenWidth]) + window.removeEventListener('resize', resize); + }; + }, [setScreenWidth]); // record route useEffect(() => { return () => { - setLastRoute(pathname) - } + setLastRoute(pathname); + }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pathname]) + }, [pathname]); useEffect(() => { - const lang = getLangStore() || 'zh' - router.push(pathname, { locale: lang }) + const lang = getLangStore() || 'zh'; + router.push(pathname, { locale: lang }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [refresh, pathname]) + }, [refresh, pathname]); useEffect(() => { - const page = searchParams.get('page') - const runtime = searchParams.get('runtime') + const page = searchParams.get('page'); + const runtime = searchParams.get('runtime'); - const path = `${page ? `/devbox/${page}` : ''}${runtime ? `?runtime=${runtime}` : ''}` + const path = `${page ? `/devbox/${page}` : ''}${runtime ? `?runtime=${runtime}` : ''}`; - router.push(path) - }, [router, searchParams]) + router.push(path); + }, [router, searchParams]); return ( @@ -152,5 +151,5 @@ export default function PlatformLayout({ children }: { children: React.ReactNode - ) + ); } diff --git a/frontend/providers/devbox/app/[lang]/(platform)/template/TagCheckbox.tsx b/frontend/providers/devbox/app/[lang]/(platform)/template/TagCheckbox.tsx index b9e182097b9..fe22f46f08a 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/template/TagCheckbox.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/template/TagCheckbox.tsx @@ -2,16 +2,10 @@ import { CheckIcon } from '@chakra-ui/icons'; import { Box, chakra, CheckboxProps, useCheckbox } from '@chakra-ui/react'; export const TagCheckbox = (props: CheckboxProps) => { - const { state, getCheckboxProps, getInputProps, getLabelProps, htmlProps } = - useCheckbox(props); + const { state, getCheckboxProps, getInputProps, getLabelProps, htmlProps } = useCheckbox(props); return ( - + { borderRadius="4px" border="1px solid" cursor={'pointer'} - { - ...state.isChecked ? { - bg: "blue.50", - borderColor: "brightBlue.500", - boxShadow: "0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)" - }: { - borderColor: "grayModern.300", - } - } + {...(state.isChecked + ? { + bg: 'blue.50', + borderColor: 'brightBlue.500', + boxShadow: '0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)' + } + : { + borderColor: 'grayModern.300' + })} transition="all 0.2s" > - {state.isChecked && ( - - )} + {state.isChecked && } {props.children} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/template/TemplateModal/PrivatePanel.tsx b/frontend/providers/devbox/app/[lang]/(platform)/template/TemplateModal/PrivatePanel.tsx index f5aacfce782..2952f548d01 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/template/TemplateModal/PrivatePanel.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/template/TemplateModal/PrivatePanel.tsx @@ -1,117 +1,129 @@ -import { listPrivateTemplateRepository } from "@/api/template"; -import MyIcon from "@/components/Icon"; -import SwitchPage from "@/components/SwitchPage"; -import { Box, Flex, Grid, TabPanel, Text } from "@chakra-ui/react"; -import { useQuery } from "@tanstack/react-query"; -import { useTranslations } from "next-intl"; -import { useEffect, useState } from "react"; -import TemplateCard from "./TemplateCard"; +import { listPrivateTemplateRepository } from '@/api/template'; +import MyIcon from '@/components/Icon'; +import SwitchPage from '@/components/SwitchPage'; +import { Box, Flex, Grid, TabPanel, Text } from '@chakra-ui/react'; +import { useQuery } from '@tanstack/react-query'; +import { useTranslations } from 'next-intl'; +import { useEffect, useState } from 'react'; +import TemplateCard from './TemplateCard'; -export default function PrivatePanel({ - search, }: { - search: string, - }) { +export default function PrivatePanel({ search }: { search: string }) { const [pageQueryBody, setPageQueryBody] = useState({ page: 1, pageSize: 30, totalItems: 0, - totalPage: 0, - }) + totalPage: 0 + }); // reset query useEffect(() => { - if (!search) return - setPageQueryBody(prev => ({ + if (!search) return; + setPageQueryBody((prev) => ({ ...prev, page: 1, totalItems: 0, - totalPage: 0, - })) - }, [search]) + totalPage: 0 + })); + }, [search]); // reset query const queryBody = { page: pageQueryBody.page, pageSize: pageQueryBody.pageSize, - search, - } + search + }; const listPrivateTemplateReposistory = useQuery( ['template-repository-list', 'template-repository-private', queryBody], () => { - return listPrivateTemplateRepository(queryBody) + return listPrivateTemplateRepository(queryBody); } - ) + ); useEffect(() => { - if (listPrivateTemplateReposistory.isFetched && listPrivateTemplateReposistory.isSuccess && listPrivateTemplateReposistory.data) { - const data = listPrivateTemplateReposistory.data.page - setPageQueryBody(prev => ({ + if ( + listPrivateTemplateReposistory.isFetched && + listPrivateTemplateReposistory.isSuccess && + listPrivateTemplateReposistory.data + ) { + const data = listPrivateTemplateReposistory.data.page; + setPageQueryBody((prev) => ({ ...prev, totalItems: data.totalItems || 0, totalPage: data.totalPage || 0, page: data.page || 1 - })) + })); } - }, [listPrivateTemplateReposistory.data, listPrivateTemplateReposistory.isFetched, listPrivateTemplateReposistory.isSuccess]) + }, [ + listPrivateTemplateReposistory.data, + listPrivateTemplateReposistory.isFetched, + listPrivateTemplateReposistory.isSuccess + ]); - const t = useTranslations() - const privateTempalteReposistoryList = listPrivateTemplateReposistory.data?.templateRepositoryList || [] - return - - - {t('my_templates')} - - - - {privateTempalteReposistoryList.map((tr) => ( - t.tag)} /> - ))} - - {privateTempalteReposistoryList.length === 0 && + + + {t('my_templates')} + + + - - - {t('no_template_repository_versions')} - - } - - - { - setPageQueryBody(page => { - return { - ...page, - page: currentPage, - } - }) - }} - /> + {privateTempalteReposistoryList.map((tr) => ( + t.tag)} + /> + ))} + + {privateTempalteReposistoryList.length === 0 && ( + + + + {t('no_template_repository_versions')} + + + )} + + + { + setPageQueryBody((page) => { + return { + ...page, + page: currentPage + }; + }); + }} + /> + - - -} \ No newline at end of file + + ); +} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/template/TemplateModal/PromptModal.tsx b/frontend/providers/devbox/app/[lang]/(platform)/template/TemplateModal/PromptModal.tsx index aa76acb2d50..b15a5bb483d 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/template/TemplateModal/PromptModal.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/template/TemplateModal/PromptModal.tsx @@ -1,39 +1,66 @@ -import MyIcon from "@/components/Icon" -import { Button, ButtonGroup, Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, ModalProps, Text } from "@chakra-ui/react" -import { useTranslations } from "next-intl" +import MyIcon from '@/components/Icon'; +import { + Button, + ButtonGroup, + Flex, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + ModalProps, + Text +} from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; -const OverviewTemplateVersionModal = ({ onSubmit, version, template, ...props }: Omit & { onSubmit: () => void, version: string, template: string }) => { - const t = useTranslations() +const OverviewTemplateVersionModal = ({ + onSubmit, + version, + template, + ...props +}: Omit & { onSubmit: () => void; version: string; template: string }) => { + const t = useTranslations(); return ( - + - + {t('prompt')} {t.rich('overview_template_version_prompt', { - version: {version} as any, - name: {template} as any + version: ( + + {version} + + ) as any, + name: ( + + {template} + + ) as any })} - - {!inPublicStore && + {!inPublicStore && ( ), onClick: deleteTemplateHandle.onOpen - }, + } ]} /> - } + )} {/* Tags */} - {tags.filter(tag => tag.name !== 'official').map(tag => - {tag[lastLang === 'zh' ? 'zhName' : 'enName'] || tag.name} - )} + {tags + .filter((tag) => tag.name !== 'official') + .map((tag) => ( + + {tag[lastLang === 'zh' ? 'zhName' : 'enName'] || tag.name} + + ))} - + { - const t = useTranslations() - const { isOpen, config, closeTemplateModal, openTemplateModal, updateTemplateModalConfig } = useTemplateStore() - const [search, setsearch] = useState('') + const t = useTranslations(); + const { isOpen, config, closeTemplateModal, openTemplateModal, updateTemplateModalConfig } = + useTemplateStore(); + const [search, setsearch] = useState(''); const updateSearchVal = useCallback( debounce((val: string) => { - setsearch(val) + setsearch(val); }, 500), [] - ) - const lastRoute = usePathname() + ); + const lastRoute = usePathname(); return ( - { - closeTemplateModal() - updateTemplateModalConfig({ - templateState: TemplateState.publicTemplate, - lastRoute - }) - }} lockFocusAcrossFrames={false}> + { + closeTemplateModal(); + updateTemplateModalConfig({ + templateState: TemplateState.publicTemplate, + lastRoute + }); + }} + lockFocusAcrossFrames={false} + > - + {t('devbox_template')} {/* */} - { - if (idx === 0) openTemplateModal({ - templateState: TemplateState.publicTemplate, - lastRoute - }) - else openTemplateModal({ - templateState: TemplateState.privateTemplate, - lastRoute - }) - }}> + if (idx === 0) + openTemplateModal({ + templateState: TemplateState.publicTemplate, + lastRoute + }); + else + openTemplateModal({ + templateState: TemplateState.privateTemplate, + lastRoute + }); + }} + > {/* TabList must be direct child of Tabs */} { borderRadius="6px" color="grayModern.500" _selected={{ - bg: "grayModern.100", - color: "brightBlue.600", + bg: 'grayModern.100', + color: 'brightBlue.600' }} > - + { borderRadius="6px" color="grayModern.500" _selected={{ - bg: "grayModern.100", - color: "brightBlue.600", + bg: 'grayModern.100', + color: 'brightBlue.600' }} > - + { - + { rounded="md" placeholder={t('template_search')} onChange={(e) => { - updateSearchVal(e.target.value) + updateSearchVal(e.target.value); }} /> @@ -159,7 +162,7 @@ const TemplateModal = () => { - ) -} + ); +}; -export default TemplateModal \ No newline at end of file +export default TemplateModal; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/CreateTemplateModal.tsx b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/CreateTemplateModal.tsx index 9ed10002b9f..4a67bd8e601 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/CreateTemplateModal.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/CreateTemplateModal.tsx @@ -30,7 +30,7 @@ import TemplateRepositoryIsPublicField from './components/TemplateRepositoryIsPu import TemplateRepositoryNameField from './components/TemplateRepositoryNameField'; import TemplateRepositoryTagField from './components/TemplateRepositoryTagField'; const tagSchema = z.object({ - value: z.string(), + value: z.string() }); // 定义表单数据类型和验证规则 @@ -47,14 +47,17 @@ const CreateTemplateModal: FC = ({ // onSubmit devboxReleaseName }) => { - const t = useTranslations() + const t = useTranslations(); const formSchema = z.object({ name: z.string().min(1, t('input_template_name_placeholder')).pipe(templateNameSchema), version: z.string().min(1, t('input_template_version_placeholder')).pipe(versionSchema), isPublic: z.boolean().default(false), agreeTerms: z.boolean().refine((val) => val === true, t('privacy_and_security_agreement_tips')), - tags: z.array(tagSchema).min(1, t('select_at_least_1_tag')).max(3, t('select_lest_than_3_tags')), - description: z.string(), + tags: z + .array(tagSchema) + .min(1, t('select_at_least_1_tag')) + .max(3, t('select_lest_than_3_tags')), + description: z.string() }); type FormData = z.infer; @@ -66,7 +69,7 @@ const CreateTemplateModal: FC = ({ isPublic: false, agreeTerms: false, tags: [], - description: '', + description: '' }, mode: 'onSubmit' }); @@ -74,44 +77,44 @@ const CreateTemplateModal: FC = ({ control, handleSubmit, formState: { errors, isSubmitting }, - reset, - } = methods - const { openTemplateModal, config } = useTemplateStore() - const queryClient = useQueryClient() + reset + } = methods; + const { openTemplateModal, config } = useTemplateStore(); + const queryClient = useQueryClient(); const mutation = useMutation({ mutationFn: createTemplateReposistory // return await createTemplate(data) - }) - const { message: toast } = useMessage() - const lastRoute = usePathname() + }); + const { message: toast } = useMessage(); + const lastRoute = usePathname(); const onSubmitHandler: SubmitHandler = async (_data) => { try { - const result = formSchema.safeParse(_data) + const result = formSchema.safeParse(_data); if (!result.success) { // const title = result.error.errors[0] - const error = result.error.errors[0] - if(error.path[0] === 'name' && error.code === 'invalid_string') { + const error = result.error.errors[0]; + if (error.path[0] === 'name' && error.code === 'invalid_string') { toast({ title: t('invalide_template_name'), - status: 'error', + status: 'error' }); return; } - if(error.path[0] === 'version' && error.code === 'invalid_string') { + if (error.path[0] === 'version' && error.code === 'invalid_string') { toast({ title: t('invalide_template_version'), - status: 'error', + status: 'error' }); return; } - const title = error.message + const title = error.message; toast({ title, - status: 'error', + status: 'error' }); return; } - const data = result.data + const data = result.data; await mutation.mutateAsync({ templateRepositoryName: data.name, version: data.version, @@ -120,40 +123,34 @@ const CreateTemplateModal: FC = ({ tagUidList: data.tags.map((tag) => tag.value), devboxReleaseName }); - queryClient.invalidateQueries(['template-repository-list']) - queryClient.invalidateQueries(['template-repository-detail']) + queryClient.invalidateQueries(['template-repository-list']); + queryClient.invalidateQueries(['template-repository-detail']); reset(); onClose(); openTemplateModal({ templateState: TemplateState.privateTemplate, lastRoute - }) + }); toast({ title: t('create_template_success'), - status: 'success', + status: 'success' }); } catch (error) { - - if(error == '409:templateRepository name already exists') { + if (error == '409:templateRepository name already exists') { return toast({ title: t('template_repository_name_already_exists'), - status: 'error', + status: 'error' }); } toast({ title: error as string, - status: 'error', + status: 'error' }); } }; return ( - reset()} - > + reset()}> @@ -170,14 +167,16 @@ const CreateTemplateModal: FC = ({ {/* 版本号 */} - {t('version')} + + {t('version')} + ( + render={({ field }) => ( = ({ /> - {/* 公开 */} {/* */} @@ -195,13 +193,11 @@ const CreateTemplateModal: FC = ({ {/* 标签 */} - {/* 简介 */} - - + {/* 按钮组 */} - ) -} + ); +}; -export default DeleteTemplateReposistoryModal \ No newline at end of file +export default DeleteTemplateReposistoryModal; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/EditTemplateModal.tsx b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/EditTemplateModal.tsx index 3bec744dcd0..92b12a4201d 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/EditTemplateModal.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/EditTemplateModal.tsx @@ -21,11 +21,11 @@ import { FC, useState } from 'react'; import { z } from 'zod'; import DeleteTemplateVersionModal from '../updateTemplateVersion/DeleteTemplateVersionModal'; const tagSchema = z.object({ - value: z.string().min(1), + value: z.string().min(1) }); const versionSchema = z.object({ name: z.string(), - uid: z.string(), + uid: z.string() }); type VersionType = z.infer; interface CreateTemplateModalProps { @@ -42,18 +42,14 @@ const EditTemplateModal: FC = ({ uid, templateRepositoryName }) => { - const t = useTranslations() - const DeleteTemplateVersionHandle = useDisclosure() - const [deletedTemplateVersion, setDeletedTemplateVersion] = useState() - const templateRepositoryQuery = useQuery( - ['templateList', uid], - () => listTemplate(uid), - { - enabled: isOpen - } - ) + const t = useTranslations(); + const DeleteTemplateVersionHandle = useDisclosure(); + const [deletedTemplateVersion, setDeletedTemplateVersion] = useState(); + const templateRepositoryQuery = useQuery(['templateList', uid], () => listTemplate(uid), { + enabled: isOpen + }); const columns: { - title: string + title: string; dataIndex?: keyof { uid: string; name: string; @@ -61,9 +57,9 @@ const EditTemplateModal: FC = ({ image: string; createAt: Date; updateAt: Date; - } - minWidth?: string - key: string + }; + minWidth?: string; + key: string; render?: (item: { uid: string; name: string; @@ -71,112 +67,127 @@ const EditTemplateModal: FC = ({ image: string; createdAt: Date; updatedAt: Date; - }) => JSX.Element + }) => JSX.Element; }[] = [ - { - title: t('version'), - key: 'name', - render: (item) => { - return ( - - {item.name} - - ) - } - }, - { - title: t('creation_time'), - dataIndex: 'createAt', - key: 'createAt', - render: (item) => { - return {dayjs(item.createdAt).format('YYYY-MM-DD HH:mm')} - } - }, { - title: t('update_time'), - dataIndex: 'updateAt', - key: 'updateAt', - render: (item) => { - return {dayjs(item.updatedAt).format('YYYY-MM-DD HH:mm')} - } - }, - { - title: t('control'), - key: 'control', - minWidth: 'unset', - render: (item) => ( - // - } - minW={'unset'} - onClick={() => { - setDeletedTemplateVersion({ - name: item.name, - uid: item.uid, - }) - DeleteTemplateVersionHandle.onOpen() - }} - /> - // - ) + { + title: t('version'), + key: 'name', + render: (item) => { + return ( + + + {item.name} + + + ); } - ] - const templateList = - templateRepositoryQuery.data?.templateList || [] - return (<> - - - - - {t('version_manage')} - - - - - - - - {templateList.length === 0 && - - - {t('no_template_versions')} - - } + }, + { + title: t('creation_time'), + dataIndex: 'createAt', + key: 'createAt', + render: (item) => { + return ( + {dayjs(item.createdAt).format('YYYY-MM-DD HH:mm')} + ); + } + }, + { + title: t('update_time'), + dataIndex: 'updateAt', + key: 'updateAt', + render: (item) => { + return ( + {dayjs(item.updatedAt).format('YYYY-MM-DD HH:mm')} + ); + } + }, + { + title: t('control'), + key: 'control', + minWidth: 'unset', + render: (item) => ( + // + } + minW={'unset'} + onClick={() => { + setDeletedTemplateVersion({ + name: item.name, + uid: item.uid + }); + DeleteTemplateVersionHandle.onOpen(); + }} + /> + // + ) + } + ]; + const templateList = templateRepositoryQuery.data?.templateList || []; + return ( + <> + + + + + {t('version_manage')} + + + + + + + {templateList.length === 0 && ( + + + + {t('no_template_versions')} + + + )} + - - - - - {!!deletedTemplateVersion && } - + + + + {!!deletedTemplateVersion && ( + + )} + ); }; -export default EditTemplateModal \ No newline at end of file +export default EditTemplateModal; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/EditTemplateReposistoryModal.tsx b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/EditTemplateReposistoryModal.tsx index dcef3b9da3e..94a95e06ebd 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/EditTemplateReposistoryModal.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/EditTemplateReposistoryModal.tsx @@ -25,7 +25,7 @@ import TemplateRepositoryIsPublicField from './components/TemplateRepositoryIsPu import TemplateRepositoryNameField from './components/TemplateRepositoryNameField'; import TemplateRepositoryTagField from './components/TemplateRepositoryTagField'; const tagSchema = z.object({ - value: z.string().min(1), + value: z.string().min(1) }); const versionSchema = z.object({ name: z.string(), @@ -40,18 +40,17 @@ interface CreateTemplateModalProps { uid: string; } -const EditTemplateRepositoryModal: FC = ({ - isOpen, - onClose, - uid -}) => { - const t = useTranslations() +const EditTemplateRepositoryModal: FC = ({ isOpen, onClose, uid }) => { + const t = useTranslations(); const formSchema = z.object({ name: z.string().min(1, t('input_template_name_placeholder')), isPublic: z.boolean().default(false), agreeTerms: z.boolean().refine((val) => val === true, t('privacy_and_security_agreement_tips')), - tags: z.array(tagSchema).min(1, t('select_at_least_1_tag')).max(3, t('select_lest_than_3_tags')), - description: z.string(), + tags: z + .array(tagSchema) + .min(1, t('select_at_least_1_tag')) + .max(3, t('select_lest_than_3_tags')), + description: z.string() }); type FormData = z.infer; const methods = useForm({ @@ -60,66 +59,66 @@ const EditTemplateRepositoryModal: FC = ({ isPublic: false, agreeTerms: false, tags: [], - description: '', - }, + description: '' + } }); const { control, handleSubmit, formState: { errors, isSubmitting }, reset, - setValue, - } = methods + setValue + } = methods; - const DeleteTemplateVersionHandle = useDisclosure() - const [deletedTemplateVersion, setDeletedTemplateVersion] = useState() + const DeleteTemplateVersionHandle = useDisclosure(); + const [deletedTemplateVersion, setDeletedTemplateVersion] = useState(); const templateRepositoryQuery = useQuery( ['template-repository-detail', uid], () => getTemplateRepository(uid), { enabled: isOpen } - ) - const updateMutation = useMutation( - updateTemplateReposistory, - { - onSuccess() { - queryClient.invalidateQueries(['template-repository-list']) - queryClient.invalidateQueries(['template-repository-detail']) - } + ); + const updateMutation = useMutation(updateTemplateReposistory, { + onSuccess() { + queryClient.invalidateQueries(['template-repository-list']); + queryClient.invalidateQueries(['template-repository-detail']); } - ) - const templateRepository = templateRepositoryQuery.data?.templateRepository - const [publicIsDisabled, setPublicIsDisabled] = useState(false) + }); + const templateRepository = templateRepositoryQuery.data?.templateRepository; + const [publicIsDisabled, setPublicIsDisabled] = useState(false); useEffect(() => { if (isOpen && templateRepository && templateRepositoryQuery.isSuccess) { - setValue('tags', templateRepository.templateRepositoryTags.map(({ tag }) => ({ - value: tag.uid, - }))) + setValue( + 'tags', + templateRepository.templateRepositoryTags.map(({ tag }) => ({ + value: tag.uid + })) + ); - setValue('name', templateRepository.name) - setValue('description', templateRepository.description || '') - setValue('isPublic', templateRepository.isPublic) + setValue('name', templateRepository.name); + setValue('description', templateRepository.description || ''); + setValue('isPublic', templateRepository.isPublic); if (templateRepository.isPublic) { - setPublicIsDisabled(true) - setValue('agreeTerms', true) + setPublicIsDisabled(true); + setValue('agreeTerms', true); } } - }, [templateRepository, isOpen]) - const { message: toast } = useMessage() - const queryClient = useQueryClient() + }, [templateRepository, isOpen]); + const { message: toast } = useMessage(); + const queryClient = useQueryClient(); const onSubmitHandler = async (_data: FormData) => { try { - const result = formSchema.safeParse(_data) + const result = formSchema.safeParse(_data); if (!result.success) { - const error = result.error.errors[0] + const error = result.error.errors[0]; toast({ title: error.message, - status: 'error', + status: 'error' }); return; } - const data = result.data + const data = result.data; await updateMutation.mutateAsync({ uid, templateRepositoryName: data.name, @@ -127,84 +126,78 @@ const EditTemplateRepositoryModal: FC = ({ description: data.description, tagUidList: data.tags.map(({ value }) => value) }); - queryClient.invalidateQueries(['template-repository-list']) + queryClient.invalidateQueries(['template-repository-list']); reset(); onClose(); toast({ title: t('template_ssaved_successfully'), - status: 'success', + status: 'success' }); } catch (error) { toast({ title: error as string, - status: 'error', + status: 'error' }); } }; - return (<> - reset()} - > - - - -
- - {t('edit_template')} - - - - - - {/* 名称 */} - - - {/* 公开 */} - + return ( + <> + reset()}> + + + + + + {t('edit_template')} + + + - {/* 标签 */} - + + {/* 名称 */} + - {/* 简介 */} - - + {/* 公开 */} + - - - {/* 按钮组 */} - - - - - - - + {/* 标签 */} + - - - + {/* 简介 */} + + +
+ + {/* 按钮组 */} + + + + + + +
+
+
+ ); }; -export default EditTemplateRepositoryModal \ No newline at end of file +export default EditTemplateRepositoryModal; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/SelectActionModal/TemplateDropdown.tsx b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/SelectActionModal/TemplateDropdown.tsx index 34c6518c38d..7ab584287e7 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/SelectActionModal/TemplateDropdown.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/SelectActionModal/TemplateDropdown.tsx @@ -1,13 +1,34 @@ -import MyIcon from "@/components/Icon"; -import { ChevronDownIcon } from "@chakra-ui/icons"; -import { Box, Button, ButtonProps, HStack, Img, Popover, PopoverBody, PopoverContent, PopoverTrigger, Text, useDisclosure, VStack } from "@chakra-ui/react"; +import MyIcon from '@/components/Icon'; +import { ChevronDownIcon } from '@chakra-ui/icons'; +import { + Box, + Button, + ButtonProps, + HStack, + Img, + Popover, + PopoverBody, + PopoverContent, + PopoverTrigger, + Text, + useDisclosure, + VStack +} from '@chakra-ui/react'; -const TemplateButton = ({ isActive = false, icon, title, description, onClick, isInMenu = false, ...props }: ButtonProps & { +const TemplateButton = ({ + isActive = false, + icon, + title, + description, + onClick, + isInMenu = false, + ...props +}: ButtonProps & { icon: React.ReactNode; title: string; isActive?: boolean; - isInMenu?: boolean - description: string + isInMenu?: boolean; + description: string; }) => { return ( - @@ -80,4 +93,4 @@ const SelectTemplateModal = ({ ); }; -export default SelectTemplateModal \ No newline at end of file +export default SelectTemplateModal; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/UpdateTemplateRepositoryModal.tsx b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/UpdateTemplateRepositoryModal.tsx index 5bb60056ea6..24061c60ec6 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/UpdateTemplateRepositoryModal.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/UpdateTemplateRepositoryModal.tsx @@ -35,11 +35,10 @@ import TemplateRepositoryDescriptionField from './components/TemplateRepositoryD import TemplateRepositoryNameField from './components/TemplateRepositoryNameField'; import TemplateRepositoryTagField from './components/TemplateRepositoryTagField'; - interface CreateTemplateModalProps { isOpen: boolean; onClose: () => void; - devboxReleaseName: string + devboxReleaseName: string; templateRepository: { uid: string; name: string; @@ -59,147 +58,150 @@ interface CreateTemplateModalProps { }; } - -const VersionSelect = ({ templateList }: { templateList: { uid: string, name: string }[] }) => { - const { - watch, - setValue, - } = useFormContext(); - const [inputValue, setInputValue] = useState("") +const VersionSelect = ({ templateList }: { templateList: { uid: string; name: string }[] }) => { + const { watch, setValue } = useFormContext(); + const [inputValue, setInputValue] = useState(''); const handleVersionSelect = (version: string) => { - setInputValue(version) - setValue('version', inputValue) - handler.onClose() - } - const handler = useDisclosure() + setInputValue(version); + setValue('version', inputValue); + handler.onClose(); + }; + const handler = useDisclosure(); const handleCreateVersion = () => { // 处理创建新版本的逻辑 - setValue('version', inputValue) - handler.onClose() - } - const t = useTranslations() - return (<> - - - - {watch('version')} - - - - + - - { - setInputValue(e.target.value) - }} - // border="1px solid #219BF4" - // boxShadow="0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)" - borderRadius="4px" - fontSize="12px" - placeholder={t('search_or_add_version')} - _focus={{ - border: "1px solid #219BF4", - boxShadow: "0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)", - }} - /> - - {/* 已有版本列表 */} - {templateList - .filter(v => v.name.toLowerCase().includes(inputValue.toLowerCase())) - .map((v) => ( - + + + {watch('version')} + + + + + + + { + setInputValue(e.target.value); + }} + // border="1px solid #219BF4" + // boxShadow="0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)" + borderRadius="4px" + fontSize="12px" + placeholder={t('search_or_add_version')} + _focus={{ + border: '1px solid #219BF4', + boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)' + }} + /> + + {/* 已有版本列表 */} + {templateList + .filter((v) => v.name.toLowerCase().includes(inputValue.toLowerCase())) + .map((v) => ( + handleVersionSelect(v.name)} + > + {v.name} + + ))} + + {/* 创建新版本选项 */} + {inputValue && !templateList.find((v) => v.name === inputValue) && ( + handleVersionSelect(v.name)} + _hover={{ bg: 'rgba(17, 24, 36, 0.05)' }} + onClick={handleCreateVersion} > - {v.name} - - ))} - - {/* 创建新版本选项 */} - {inputValue && !templateList.find(v => v.name === inputValue) && ( - - - - {t('create_template_version', { - version: inputValue, - })} - - - )} - - - - - - ) -} + + + {t('create_template_version', { + version: inputValue + })} + + + )} + + + + + + ); +}; const UpdateTemplateRepositoryModal: FC = ({ isOpen, onClose, templateRepository, devboxReleaseName }) => { - const t = useTranslations() + const t = useTranslations(); const tagSchema = z.object({ - value: z.string(), + value: z.string() }); const formSchema = z.object({ name: z.string().min(1, t('input_template_name_placeholder')), version: z.string().min(1, t('input_template_version_placeholder')).pipe(versionSchema), - tags: z.array(tagSchema).min(1, t('select_at_least_1_tag')).max(3, t('select_lest_than_3_tags')), - description: z.string(), + tags: z + .array(tagSchema) + .min(1, t('select_at_least_1_tag')) + .max(3, t('select_lest_than_3_tags')), + description: z.string() }); - const queryClient = useQueryClient() + const queryClient = useQueryClient(); type FormData = z.infer; const mutation = useMutation(updateTemplate, { onSuccess() { - queryClient.invalidateQueries(['template-repository-list']) - queryClient.invalidateQueries(['template-repository-detail']) + queryClient.invalidateQueries(['template-repository-list']); + queryClient.invalidateQueries(['template-repository-detail']); } - }) + }); const methods = useForm({ defaultValues: { name: '', version: '', // agreeTerms: false, tags: [], - description: '', - }, + description: '' + } }); const { handleSubmit, @@ -207,141 +209,143 @@ const UpdateTemplateRepositoryModal: FC = ({ reset, setValue, watch - } = methods + } = methods; useEffect(() => { if (templateRepository && isOpen) { - setValue('tags', templateRepository.templateRepositoryTags.map(({ tag }) => ({ - value: tag.uid, - }))) - setValue('version', templateRepository.templates[0]?.name || '') - setValue('name', templateRepository.name) - setValue('description', templateRepository.description || '') + setValue( + 'tags', + templateRepository.templateRepositoryTags.map(({ tag }) => ({ + value: tag.uid + })) + ); + setValue('version', templateRepository.templates[0]?.name || ''); + setValue('name', templateRepository.name); + setValue('description', templateRepository.description || ''); } - }, [templateRepository, isOpen]) - const { message: toast } = useMessage() - const overviewHandler = useDisclosure() + }, [templateRepository, isOpen]); + const { message: toast } = useMessage(); + const overviewHandler = useDisclosure(); const submit = async (_data: FormData) => { try { - const result = formSchema.safeParse(_data) + const result = formSchema.safeParse(_data); if (!result.success) { - const error = result.error.errors[0] - if(error.path[0] === 'version' && error.code === 'invalid_string') { + const error = result.error.errors[0]; + if (error.path[0] === 'version' && error.code === 'invalid_string') { toast({ title: t('invalide_template_version'), - status: 'error', + status: 'error' }); return; } toast({ title: error.message, - status: 'error', + status: 'error' }); return; } - const data = result.data + const data = result.data; await mutation.mutateAsync({ templateRepositoryUid: templateRepository?.uid || '', version: data.version, devboxReleaseName, description: data.description, - tagUidList: data.tags.map(({ value }) => value), - }) + tagUidList: data.tags.map(({ value }) => value) + }); - queryClient.invalidateQueries(['template-repository-list']) + queryClient.invalidateQueries(['template-repository-list']); reset(); onClose(); toast({ title: t('update_template_success'), - status: 'success', + status: 'success' }); } catch (error) { toast({ title: error as string, - status: 'error', + status: 'error' }); } }; const onSubmitHandler = (data: FormData) => { - if (templateRepository.templates.findIndex(d => data.version === d.name) > -1) { - overviewHandler.onOpen() - return + if (templateRepository.templates.findIndex((d) => data.version === d.name) > -1) { + overviewHandler.onOpen(); + return; } - return submit(data) + return submit(data); }; - return (<> - reset()} - > - - - -
- - {t('update_template')} - - - - - {/* 名称 */} - + return ( + <> + reset()}> + + + + + + {t('update_template')} + + + + + {/* 名称 */} + - {/* 版本号 */} - - {t('version')} - - - - + {/* 版本号 */} + + + {t('version')} + + + + + - {/* 标签 */} - - - {/* 简介 */} - - - - - - - {/* 按钮组 */} - - - - - - - + {/* 标签 */} + - - - { - submit(methods.getValues()) - }} version={watch('version')} template={templateRepository.name} /> - + {/* 简介 */} + + +
+ + {/* 按钮组 */} + + + + + + +
+
+
+ { + submit(methods.getValues()); + }} + version={watch('version')} + template={templateRepository.name} + /> + ); }; -export default UpdateTemplateRepositoryModal \ No newline at end of file +export default UpdateTemplateRepositoryModal; diff --git a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/components/TemplateRepositoryDescriptionField.tsx b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/components/TemplateRepositoryDescriptionField.tsx index a9f978b66af..f62470da530 100644 --- a/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/components/TemplateRepositoryDescriptionField.tsx +++ b/frontend/providers/devbox/app/[lang]/(platform)/template/updateTemplate/components/TemplateRepositoryDescriptionField.tsx @@ -1,19 +1,22 @@ -import MyFormLabel from "@/components/MyFormControl"; -import { Flex, Textarea } from "@chakra-ui/react"; -import { useTranslations } from "next-intl"; -import { Controller, useFormContext } from "react-hook-form"; +import MyFormLabel from '@/components/MyFormControl'; +import { Flex, Textarea } from '@chakra-ui/react'; +import { useTranslations } from 'next-intl'; +import { Controller, useFormContext } from 'react-hook-form'; export default function TemplateRepositoryDescriptionField() { const { control } = useFormContext<{ description: string }>(); - const t = useTranslations() - return - {t('template_description')} + const t = useTranslations(); + return ( + + + {t('template_description')} + (