From c22b4f13b172bb735e48645c191fff35776baaca Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Thu, 28 Nov 2024 13:54:07 +0800 Subject: [PATCH 1/7] feat: update deploy --- frontend/providers/devbox/api/devbox.ts | 8 +- .../detail/[name]/components/Version.tsx | 58 ++++-- .../devbox/app/api/getAppsByDevboxId/route.ts | 52 ++++++ .../components/modals/AppSelectModal.tsx | 176 ++++++++++++++++++ frontend/providers/devbox/constants/devbox.ts | 3 +- frontend/providers/devbox/message/en.json | 7 + frontend/providers/devbox/message/zh.json | 7 + frontend/providers/devbox/types/app.d.ts | 5 + frontend/providers/devbox/utils/adapt.ts | 12 +- 9 files changed, 310 insertions(+), 18 deletions(-) create mode 100644 frontend/providers/devbox/app/api/getAppsByDevboxId/route.ts create mode 100644 frontend/providers/devbox/components/modals/AppSelectModal.tsx create mode 100644 frontend/providers/devbox/types/app.d.ts diff --git a/frontend/providers/devbox/api/devbox.ts b/frontend/providers/devbox/api/devbox.ts index 0e3296a42c4..6cec0b4fc2e 100644 --- a/frontend/providers/devbox/api/devbox.ts +++ b/frontend/providers/devbox/api/devbox.ts @@ -1,4 +1,4 @@ -import { V1Pod } from '@kubernetes/client-node' +import { V1Deployment, V1Pod, V1StatefulSet } from '@kubernetes/client-node' import { DevboxEditType, @@ -8,6 +8,7 @@ import { runtimeNamespaceMapType } from '@/types/devbox' import { + adaptAppListItem, adaptDevboxDetail, adaptDevboxListItem, adaptDevboxVersionListItem, @@ -82,3 +83,8 @@ export const getDevboxMonitorData = (payload: { export const getSSHRuntimeInfo = (runtimeName: string) => GET('/api/getSSHRuntimeInfo', { runtimeName }) + +export const getAppsByDevboxId = (devboxId: string) => + GET('/api/getAppsByDevboxId', { devboxId }).then((res) => + res.map(adaptAppListItem) + ) 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 20b250b4f7a..ef294366311 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 @@ -12,14 +12,16 @@ import ReleaseModal from '@/components/modals/releaseModal' import EditVersionDesModal from '@/components/modals/EditVersionDesModal' import { DevboxVersionListItemType } from '@/types/devbox' -import { DevboxReleaseStatusEnum } from '@/constants/devbox' -import { delDevboxVersionByName, getSSHRuntimeInfo } from '@/api/devbox' +import { DevboxReleaseStatusEnum, devboxIdKey } from '@/constants/devbox' +import { delDevboxVersionByName, getAppsByDevboxId, getSSHRuntimeInfo } from '@/api/devbox' import { useConfirm } from '@/hooks/useConfirm' import { useLoading } from '@/hooks/useLoading' import { useEnvStore } from '@/stores/env' import { useDevboxStore } from '@/stores/devbox' +import AppSelectModal from '@/components/modals/AppSelectModal' +import { AppListItemType } from '@/types/app' const Version = () => { const t = useTranslations() @@ -32,6 +34,9 @@ const Version = () => { 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 { openConfirm, ConfirmChild } = useConfirm({ @@ -55,6 +60,8 @@ const Version = () => { const handleDeploy = useCallback( async (version: DevboxVersionListItemType) => { + const devboxId = devbox.id + const { releaseCommand, releaseArgs } = await getSSHRuntimeInfo(devbox.runtimeVersion) const { cpu, memory, networks, name } = devbox const newNetworks = networks.map((network) => { @@ -65,12 +72,13 @@ const Version = () => { domain: env.ingressDomain } }) + const imageName = `${env.registryAddr}/${env.namespace}/${devbox.name}:${version.tag}` const transformData = { appName: `${name}-release`, cpu: cpu, memory: memory, - imageName: `${env.registryAddr}/${env.namespace}/${devbox.name}:${version.tag}`, + imageName: imageName, networks: newNetworks.length > 0 ? newNetworks @@ -83,20 +91,33 @@ const Version = () => { } ], runCMD: releaseCommand, - cmdParam: releaseArgs + cmdParam: releaseArgs, + labels: { + [devboxIdKey]: devboxId + } } + setDeployData(transformData) + const apps = await getAppsByDevboxId(devboxId) - const formData = encodeURIComponent(JSON.stringify(transformData)) + // when: there is no app,create a new app + if (apps.length === 0) { + const tempFormDataStr = encodeURIComponent(JSON.stringify(transformData)) + sealosApp.runEvents('openDesktopApp', { + appKey: 'system-applaunchpad', + pathname: '/redirect', + query: { formData: tempFormDataStr }, + messageData: { + type: 'InternalAppCall', + formData: tempFormDataStr + } + }) + } - sealosApp.runEvents('openDesktopApp', { - appKey: 'system-applaunchpad', - pathname: '/app/edit', - query: { formData }, - messageData: { - type: 'InternalAppCall', - formData: formData - } - }) + // when: there have apps,show the app select modal + if (apps.length >= 1) { + setApps(apps) + setOnOpenSelectApp(true) + } }, [devbox, env.ingressDomain, env.namespace, env.registryAddr] ) @@ -228,6 +249,7 @@ const Version = () => { ) } ] + return ( { devbox={{ ...devbox, sshPort: devbox.sshPort || 0 }} /> )} + {!!onOpenSelectApp && ( + setOnOpenSelectApp(false)} + onClose={() => setOnOpenSelectApp(false)} + /> + )} ) diff --git a/frontend/providers/devbox/app/api/getAppsByDevboxId/route.ts b/frontend/providers/devbox/app/api/getAppsByDevboxId/route.ts new file mode 100644 index 00000000000..548f81c89d7 --- /dev/null +++ b/frontend/providers/devbox/app/api/getAppsByDevboxId/route.ts @@ -0,0 +1,52 @@ +import type { NextRequest } from 'next/server' + +import { devboxIdKey } from '@/constants/devbox' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' +import { jsonRes } from '@/services/backend/response' + +export async function GET(req: NextRequest) { + try { + const apps = await getApps(req) + return jsonRes({ data: apps }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} + +export async function getApps(req: NextRequest) { + const { searchParams } = req.nextUrl + const devboxId = searchParams.get('devboxId') as string + + const { k8sApp, namespace } = await getK8s({ + kubeconfig: await authSession(req.headers) + }) + + const response = await Promise.allSettled([ + k8sApp.listNamespacedDeployment( + namespace, + undefined, + undefined, + undefined, + undefined, + `${devboxIdKey}=${devboxId}` + ), + k8sApp.listNamespacedStatefulSet( + namespace, + undefined, + undefined, + undefined, + undefined, + `${devboxIdKey}=${devboxId}` + ) + ]) + const apps = response + .filter((item) => item.status === 'fulfilled') + .map((item: any) => item?.value?.body?.items) + .filter((item) => item) + .flat() + return apps +} diff --git a/frontend/providers/devbox/components/modals/AppSelectModal.tsx b/frontend/providers/devbox/components/modals/AppSelectModal.tsx new file mode 100644 index 00000000000..83589772f6e --- /dev/null +++ b/frontend/providers/devbox/components/modals/AppSelectModal.tsx @@ -0,0 +1,176 @@ +import { + Box, + Flex, + Modal, + ModalBody, + ModalContent, + ModalOverlay, + Button, + ModalHeader, + Text +} from '@chakra-ui/react' +import { useTranslations } from 'next-intl' +import { useCallback } from 'react' +import { customAlphabet } from 'nanoid' +import { sealosApp } from 'sealos-desktop-sdk/app' + +import { AppListItemType } from '@/types/app' + +import MyIcon from '../Icon' +import MyTable from '../MyTable' + +const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 6) + +interface NetworkConfig { + port: number + protocol: string + openPublicDomain: boolean + domain: string +} + +interface DeployData { + appName: string + cpu: number + memory: number + imageName: string + networks: NetworkConfig[] + runCMD: string + cmdParam: string[] + labels: { + [key: string]: string + } +} + +const AppSelectModal = ({ + apps, + deployData, + onSuccess, + onClose +}: { + apps: AppListItemType[] + deployData: DeployData + onSuccess: () => void + onClose: () => void +}) => { + const t = useTranslations() + + const handleCreate = useCallback(() => { + const tempFormData = { ...deployData, appName: `${deployData.appName}-${nanoid()}` } + const tempFormDataStr = encodeURIComponent(JSON.stringify(tempFormData)) + sealosApp.runEvents('openDesktopApp', { + appKey: 'system-applaunchpad', + pathname: '/redirect', + query: { formData: tempFormDataStr }, + messageData: { + type: 'InternalAppCall', + formData: tempFormDataStr + } + }) + }, [deployData]) + + const handleUpdate = useCallback( + (item: AppListItemType) => { + const tempFormData = { appName: item.name, imageName: deployData.imageName } + const tempFormDataStr = encodeURIComponent(JSON.stringify(tempFormData)) + sealosApp.runEvents('openDesktopApp', { + appKey: 'system-applaunchpad', + pathname: '/redirect', + query: { formData: tempFormDataStr }, + messageData: { + type: 'InternalAppCall', + formData: tempFormDataStr + } + }) + onSuccess() + }, + [deployData, onSuccess] + ) + + const columns: { + title: string + dataIndex?: keyof AppListItemType + key: string + render?: (item: AppListItemType) => JSX.Element + }[] = [ + { + title: t('app_name'), + dataIndex: 'name', + key: 'name' + }, + { + title: t('create_time'), + dataIndex: 'createTime', + key: 'createTime', + render: (item: AppListItemType) => { + return {item.createTime} + } + }, + { + title: t('control'), + key: 'control', + render: (item: AppListItemType) => ( + + + + ) + } + ] + + return ( + + + + + {t('deploy')} + + + {t('create_directly')} + + + + + + + {t('matched_apps')} + + + + + + + + + ) +} + +export default AppSelectModal diff --git a/frontend/providers/devbox/constants/devbox.ts b/frontend/providers/devbox/constants/devbox.ts index c1e825d878c..d0de8b413c0 100644 --- a/frontend/providers/devbox/constants/devbox.ts +++ b/frontend/providers/devbox/constants/devbox.ts @@ -2,8 +2,9 @@ import { DevboxEditType, DevboxDetailType } from '@/types/devbox' export const crLabelKey = 'sealos-devbox-cr' export const devboxKey = 'cloud.sealos.io/devbox-manager' -export const publicDomainKey = `cloud.sealos.io/app-deploy-manager-domain` +export const devboxIdKey = 'cloud.sealos.io/app-devbox-id' export const ingressProtocolKey = 'nginx.ingress.kubernetes.io/backend-protocol' +export const publicDomainKey = `cloud.sealos.io/app-deploy-manager-domain` export enum LanguageTypeEnum { java = 'java', diff --git a/frontend/providers/devbox/message/en.json b/frontend/providers/devbox/message/en.json index 9c9f12d2bb3..aeb42cc2903 100644 --- a/frontend/providers/devbox/message/en.json +++ b/frontend/providers/devbox/message/en.json @@ -20,6 +20,7 @@ "The maximum number of exposed ports is 65535": "Maximum port number is 65535", "The minimum exposed port is 1": "Minimum port number is 1", "This runtime field is required": "The runtime version field is required", + "app_name": "App Name", "basic_configuration": "Basic", "basic_info": "Basic", "cancel": "Cancel", @@ -38,6 +39,7 @@ "cpu_exceeds_quota": "CPU requested exceeds quota. Contact admin.", "create": "Create", "create_devbox": "Create Devbox", + "create_directly": "Want to deploy a new app directly?", "create_failed": "Creation failed", "create_success": "Creation succeeded", "create_time": "Created At", @@ -51,6 +53,7 @@ "delete_warning_content": "Are you sure you want to delete Devbox?", "delete_warning_content_2": "Deleting Devbox will cause the remote environment to be deleted and you will not be able to access the remote development environment. (Your released version will be remaining).", "deploy": "Deploy", + "deploy_a_new_app": "Deploy a new app", "detail": "Detail", "devbox_creation": "Project Creation", "devbox_empty": "You have not created a new devbox yet.", @@ -81,6 +84,7 @@ "jump_prompt": "Jump prompt", "jump_terminal_error": "Jump terminal failed", "language": "Language", + "matched_apps": "Deployed apps", "memory": "Memory", "memory_exceeds_quota": "Memory requested exceeds quota. Contact admin.", "monitor": "Monitor", @@ -123,6 +127,7 @@ "runtime": "Runtime", "runtime_environment": "Runtime", "save": "Save", + "select_existing_app": "Update an existing application or deploy a new application...", "shutdown": "Shutdown", "ssh_config": "SSH Configuration", "ssh_connect_info": "SSH Connection String", @@ -138,10 +143,12 @@ "tag_format_error": "Tag format error", "tag_required": "Need to input tag", "terminal": "Terminal", + "to_update": "Update", "total": "Total", "total_price": "Total", "update": "Update", "update Time": "Updated At", + "update_app": "update/create", "update_devbox": "Update devbox", "update_failed": "Update failed", "update_success": "Update succeeded", diff --git a/frontend/providers/devbox/message/zh.json b/frontend/providers/devbox/message/zh.json index 7a71635534b..9a775eb5ffc 100644 --- a/frontend/providers/devbox/message/zh.json +++ b/frontend/providers/devbox/message/zh.json @@ -20,6 +20,7 @@ "The maximum number of exposed ports is 65535": "暴露端口最大为 65535", "The minimum exposed port is 1": "暴露端口最小为 1", "This runtime field is required": "运行时版本是必填项", + "app_name": "应用名称", "basic_configuration": "基础配置", "basic_info": "基础信息", "cancel": "取消", @@ -38,6 +39,7 @@ "cpu_exceeds_quota": "申请的 CPU 超出限制,请联系管理员", "create": "创建", "create_devbox": "新建项目", + "create_directly": "想要直接上线新应用?", "create_failed": "创建失败", "create_success": "创建成功", "create_time": "创建时间", @@ -52,6 +54,7 @@ "delete_warning_content": "您确定要删除该云沙箱嘛?", "delete_warning_content_2": "删除云沙箱会导致云沙箱远程环境被删除,您将无法访问远程开发环境。(您已发布的版本仍然会保留)。", "deploy": "上线", + "deploy_a_new_app": "上线一个新应用", "detail": "详情", "devbox_creation": "项目创建", "devbox_empty": "您还没有新建项目", @@ -83,6 +86,7 @@ "jump_prompt": "跳转提示", "jump_terminal_error": "跳转终端失败", "language": "语言", + "matched_apps": "已部署的同项目应用", "memory": "内存", "memory_exceeds_quota": "申请的 '内存' 超出限制,请联系管理员", "monitor": "实时监控", @@ -125,6 +129,7 @@ "runtime": "运行环境", "runtime_environment": "运行环境", "save": "保存", + "select_existing_app": "更新一个现存的应用或者上线新应用...", "shutdown": "关机", "ssh_config": "SSH 配置", "ssh_connect_info": "连接串", @@ -140,10 +145,12 @@ "tag_format_error": "版本号格式错误", "tag_required": "需要填写版本号", "terminal": "终端", + "to_update": "去更新", "total": "总共", "total_price": "总价格", "update": "变更", "update Time": "更新时间", + "update_app": "更新应用镜像/上线新应用", "update_devbox": "变更项目", "update_failed": "变更失败", "update_success": "变更成功", diff --git a/frontend/providers/devbox/types/app.d.ts b/frontend/providers/devbox/types/app.d.ts new file mode 100644 index 00000000000..f813e11c01f --- /dev/null +++ b/frontend/providers/devbox/types/app.d.ts @@ -0,0 +1,5 @@ +export interface AppListItemType { + id: string + name: string + createTime: string +} diff --git a/frontend/providers/devbox/utils/adapt.ts b/frontend/providers/devbox/utils/adapt.ts index 50e632ce21f..5d33bcf1989 100644 --- a/frontend/providers/devbox/utils/adapt.ts +++ b/frontend/providers/devbox/utils/adapt.ts @@ -14,9 +14,10 @@ import { DevboxVersionListItemType, PodDetailType } from '@/types/devbox' -import { V1Ingress, V1Pod } from '@kubernetes/client-node' +import { V1Deployment, V1Ingress, V1Pod, V1StatefulSet } from '@kubernetes/client-node' import { DBListItemType, KbPgClusterType } from '@/types/cluster' import { IngressListItemType } from '@/types/ingress' +import { AppListItemType } from '@/types/app' export const adaptDevboxListItem = (devbox: KBDevboxType): DevboxListItemType => { return { @@ -187,7 +188,6 @@ export const adaptIngressListItem = (ingress: V1Ingress): IngressListItemType => const firstRule = ingress.spec?.rules?.[0] const firstPath = firstRule?.http?.paths?.[0] const protocol = ingress.metadata?.annotations?.['nginx.ingress.kubernetes.io/backend-protocol'] - console.log('ingress', ingress.spec) return { name: ingress.metadata?.name || '', namespace: ingress.metadata?.namespace || '', @@ -196,3 +196,11 @@ export const adaptIngressListItem = (ingress: V1Ingress): IngressListItemType => protocol: protocol || 'http' } } + +export const adaptAppListItem = (app: V1Deployment & V1StatefulSet): AppListItemType => { + return { + id: app.metadata?.uid || ``, + name: app.metadata?.name || 'app name', + createTime: dayjs(app.metadata?.creationTimestamp).format('YYYY/MM/DD HH:mm') + } +} From 4551e8eef6e1b4126362e064e525299f6e5b7718 Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Thu, 28 Nov 2024 14:16:28 +0800 Subject: [PATCH 2/7] chore: deploy yaml add usw coin --- frontend/providers/devbox/deploy/manifests/deploy.yaml.tmpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/providers/devbox/deploy/manifests/deploy.yaml.tmpl b/frontend/providers/devbox/deploy/manifests/deploy.yaml.tmpl index a9c9e5343d2..feef4308e72 100644 --- a/frontend/providers/devbox/deploy/manifests/deploy.yaml.tmpl +++ b/frontend/providers/devbox/deploy/manifests/deploy.yaml.tmpl @@ -55,6 +55,8 @@ spec: value: devbox-system - name: INGRESS_DOMAIN value: sealosusw.site + - name: CURRENCY_SYMBOL + value: usd # 'shellCoin' | 'cny' | 'usd' securityContext: runAsNonRoot: true runAsUser: 1001 From 895f096c5aa3d5a0d0ebcaee67ffbec9bfc0fbc2 Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Thu, 28 Nov 2024 14:31:44 +0800 Subject: [PATCH 3/7] chore: text adjust --- frontend/providers/devbox/message/en.json | 2 +- frontend/providers/devbox/message/zh.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/providers/devbox/message/en.json b/frontend/providers/devbox/message/en.json index aeb42cc2903..8d2a5535ff3 100644 --- a/frontend/providers/devbox/message/en.json +++ b/frontend/providers/devbox/message/en.json @@ -68,7 +68,7 @@ "edit_successful": "Edit succeeded", "edit_version_description": "Edit Version Description", "enter_devbox_name": "Please enter the devbox name", - "enter_version_description": "Please enter the description", + "enter_version_description": "Please enter the description...", "enter_version_number": "Please enter the tag", "estimated_price": "The Estimated Cost", "event": "Event", diff --git a/frontend/providers/devbox/message/zh.json b/frontend/providers/devbox/message/zh.json index 9a775eb5ffc..2e507bb4201 100644 --- a/frontend/providers/devbox/message/zh.json +++ b/frontend/providers/devbox/message/zh.json @@ -69,7 +69,7 @@ "edit_successful": "编辑成功", "edit_version_description": "编辑版本描述", "enter_devbox_name": "请输入项目名称", - "enter_version_description": "请输入版本描述", + "enter_version_description": "请输入版本描述...", "enter_version_number": "请输入版本号,例如:v1.0.0", "estimated_price": "预估价格", "event": "事件", From ea443f86308bb4646ad36bd47e0c15ef69b9de69 Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Thu, 28 Nov 2024 14:33:09 +0800 Subject: [PATCH 4/7] style: release tag color --- frontend/providers/devbox/constants/devbox.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/providers/devbox/constants/devbox.ts b/frontend/providers/devbox/constants/devbox.ts index d0de8b413c0..9d9f86e4841 100644 --- a/frontend/providers/devbox/constants/devbox.ts +++ b/frontend/providers/devbox/constants/devbox.ts @@ -150,14 +150,14 @@ export const devboxReleaseStatusMap = { [DevboxReleaseStatusEnum.Pending]: { label: 'release_pending', value: DevboxReleaseStatusEnum.Pending, - color: '#787A90', + color: '#0884DD', backgroundColor: '#F5F5F8', dotColor: '#787A90' }, [DevboxReleaseStatusEnum.Failed]: { label: 'release_failed', value: DevboxReleaseStatusEnum.Failed, - color: '#F04438', + color: '#D92D20', backgroundColor: '#FEF3F2', dotColor: '#F04438' } From 8e9172b0ce6defa303965e4e9eb7c4df74277e15 Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Thu, 28 Nov 2024 14:56:43 +0800 Subject: [PATCH 5/7] feat: add port column in detail --- .../devbox/detail/[name]/components/MainBody.tsx | 14 +++++++++++++- frontend/providers/devbox/components/MyTable.tsx | 5 +++-- 2 files changed, 16 insertions(+), 3 deletions(-) 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 8cf38bb7f81..09d44dcfa37 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 @@ -27,7 +27,20 @@ const MainBody = () => { dataIndex?: keyof NetworkType key: string render?: (item: NetworkType) => JSX.Element + width?: string }[] = [ + { + title: t('port'), + key: 'port', + render: (item: NetworkType) => { + return ( + + {item.port} + + ) + }, + width: '0.5fr' + }, { title: t('internal_address'), key: 'internalAddress', @@ -47,7 +60,6 @@ const MainBody = () => { _hover={{ textDecoration: 'underline' }} - ml={4} color={'grayModern.600'} onClick={() => copyData( diff --git a/frontend/providers/devbox/components/MyTable.tsx b/frontend/providers/devbox/components/MyTable.tsx index fdc2db58f42..0b702558650 100644 --- a/frontend/providers/devbox/components/MyTable.tsx +++ b/frontend/providers/devbox/components/MyTable.tsx @@ -8,6 +8,7 @@ interface Props extends BoxProps { key: string render?: (item: any) => JSX.Element minWidth?: string + width?: string }[] data: any[] itemClass?: string @@ -18,7 +19,7 @@ const MyTable = ({ columns, data, itemClass = '', alternateRowColors = false }: return ( <> col.width || '1fr').join(' ')} overflowX={'auto'} borderTopRadius={'md'} fontSize={'base'} @@ -41,7 +42,7 @@ const MyTable = ({ columns, data, itemClass = '', alternateRowColors = false }: {data.map((item: any, index1) => ( col.width || '1fr').join(' ')} overflowX={'auto'} key={index1} bg={alternateRowColors ? (index1 % 2 === 0 ? '#FBFBFC' : '#F4F4F7') : 'white'} From 2d2073ebee7b87cceae72201212982f946f8e644 Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Thu, 28 Nov 2024 16:29:34 +0800 Subject: [PATCH 6/7] fix&style: adjust update modal style and some value --- .../detail/[name]/components/Version.tsx | 1 + .../devbox/components/Icon/icons/rocket.svg | 3 + .../devbox/components/Icon/index.tsx | 3 +- .../components/modals/AppSelectModal.tsx | 70 +++++++++++++------ frontend/providers/devbox/message/en.json | 2 + frontend/providers/devbox/message/zh.json | 2 + frontend/providers/devbox/types/app.d.ts | 1 + frontend/providers/devbox/utils/adapt.ts | 6 +- 8 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 frontend/providers/devbox/components/Icon/icons/rocket.svg 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 ef294366311..de64902cb8b 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 @@ -316,6 +316,7 @@ const Version = () => { {!!onOpenSelectApp && ( setOnOpenSelectApp(false)} onClose={() => setOnOpenSelectApp(false)} diff --git a/frontend/providers/devbox/components/Icon/icons/rocket.svg b/frontend/providers/devbox/components/Icon/icons/rocket.svg new file mode 100644 index 00000000000..b97d4d81815 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/rocket.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/index.tsx b/frontend/providers/devbox/components/Icon/index.tsx index 9096d965663..530ff784ed0 100644 --- a/frontend/providers/devbox/components/Icon/index.tsx +++ b/frontend/providers/devbox/components/Icon/index.tsx @@ -63,7 +63,8 @@ const map = { check: require('./icons/check.svg').default, empty: require('./icons/empty.svg').default, shutdown: require('./icons/shutdown.svg').default, - windsurf: require('./icons/windsurf.svg').default + windsurf: require('./icons/windsurf.svg').default, + rocket: require('./icons/rocket.svg').default } const MyIcon = ({ diff --git a/frontend/providers/devbox/components/modals/AppSelectModal.tsx b/frontend/providers/devbox/components/modals/AppSelectModal.tsx index 83589772f6e..665ca3c5ee5 100644 --- a/frontend/providers/devbox/components/modals/AppSelectModal.tsx +++ b/frontend/providers/devbox/components/modals/AppSelectModal.tsx @@ -7,7 +7,8 @@ import { ModalOverlay, Button, ModalHeader, - Text + Text, + Divider } from '@chakra-ui/react' import { useTranslations } from 'next-intl' import { useCallback } from 'react' @@ -18,6 +19,7 @@ import { AppListItemType } from '@/types/app' import MyIcon from '../Icon' import MyTable from '../MyTable' +import { useEnvStore } from '@/stores/env' const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 6) @@ -44,15 +46,18 @@ interface DeployData { const AppSelectModal = ({ apps, deployData, + devboxName, onSuccess, onClose }: { apps: AppListItemType[] + devboxName: string deployData: DeployData onSuccess: () => void onClose: () => void }) => { const t = useTranslations() + const { env } = useEnvStore() const handleCreate = useCallback(() => { const tempFormData = { ...deployData, appName: `${deployData.appName}-${nanoid()}` } @@ -90,12 +95,34 @@ const AppSelectModal = ({ title: string dataIndex?: keyof AppListItemType key: string + width?: string render?: (item: AppListItemType) => JSX.Element }[] = [ { title: t('app_name'), dataIndex: 'name', - key: 'name' + key: 'name', + render: (item: AppListItemType) => { + return ( + + {item.name} + + ) + } + }, + { + title: t('current_image_name'), + dataIndex: 'imageName', + key: 'imageName', + render: (item: AppListItemType) => { + // note: no same devbox matched image will be dealt. + const dealImageName = item.imageName.startsWith( + `${env.registryAddr}/${env.namespace}/${devboxName}` + ) + ? item.imageName.replace(`${env.registryAddr}/${env.namespace}/`, '') + : '-' + return {dealImageName} + } }, { title: t('create_time'), @@ -133,35 +160,34 @@ const AppSelectModal = ({ - + {t('deploy')} - {t('create_directly')} + direction={'column'} + mb={2} + justifyContent={'space-between'} + p={4}> + + {t('create_directly')} + - - - - - {t('matched_apps')} + + + + + {t('update_matched_apps_notes')} diff --git a/frontend/providers/devbox/message/en.json b/frontend/providers/devbox/message/en.json index 8d2a5535ff3..9fe45270117 100644 --- a/frontend/providers/devbox/message/en.json +++ b/frontend/providers/devbox/message/en.json @@ -43,6 +43,7 @@ "create_failed": "Creation failed", "create_success": "Creation succeeded", "create_time": "Created At", + "current_image_name": "Current Image", "cursor": "Cursor", "daily": "/day", "delete": "Delete", @@ -151,6 +152,7 @@ "update_app": "update/create", "update_devbox": "Update devbox", "update_failed": "Update failed", + "update_matched_apps_notes": "Or you can update application: ", "update_success": "Update succeeded", "used": "Used", "version": "Release", diff --git a/frontend/providers/devbox/message/zh.json b/frontend/providers/devbox/message/zh.json index 2e507bb4201..e85d7a73f73 100644 --- a/frontend/providers/devbox/message/zh.json +++ b/frontend/providers/devbox/message/zh.json @@ -44,6 +44,7 @@ "create_success": "创建成功", "create_time": "创建时间", "creation_time": "创建时间", + "current_image_name": "当前镜像", "cursor": "Cursor", "daily": "/天", "delete": "删除", @@ -153,6 +154,7 @@ "update_app": "更新应用镜像/上线新应用", "update_devbox": "变更项目", "update_failed": "变更失败", + "update_matched_apps_notes": "或者你可以更新已有应用:", "update_success": "变更成功", "used": "已用", "version": "版本", diff --git a/frontend/providers/devbox/types/app.d.ts b/frontend/providers/devbox/types/app.d.ts index f813e11c01f..6345369db14 100644 --- a/frontend/providers/devbox/types/app.d.ts +++ b/frontend/providers/devbox/types/app.d.ts @@ -2,4 +2,5 @@ export interface AppListItemType { id: string name: string createTime: string + imageName: string } diff --git a/frontend/providers/devbox/utils/adapt.ts b/frontend/providers/devbox/utils/adapt.ts index 5d33bcf1989..979727d7084 100644 --- a/frontend/providers/devbox/utils/adapt.ts +++ b/frontend/providers/devbox/utils/adapt.ts @@ -201,6 +201,10 @@ export const adaptAppListItem = (app: V1Deployment & V1StatefulSet): AppListItem return { id: app.metadata?.uid || ``, name: app.metadata?.name || 'app name', - createTime: dayjs(app.metadata?.creationTimestamp).format('YYYY/MM/DD HH:mm') + createTime: dayjs(app.metadata?.creationTimestamp).format('YYYY/MM/DD HH:mm'), + imageName: + app?.metadata?.annotations?.originImageName || + app.spec?.template?.spec?.containers?.[0]?.image || + '' } } From f3c57f07e9bb8168ee61c295ab8cede0d8331730 Mon Sep 17 00:00:00 2001 From: mlhiter <3076438032@qq.com> Date: Thu, 28 Nov 2024 17:32:34 +0800 Subject: [PATCH 7/7] fix: next build bug --- frontend/providers/devbox/app/api/getAppsByDevboxId/route.ts | 4 +++- frontend/providers/devbox/app/api/v1/getDBSecretList/route.ts | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/providers/devbox/app/api/getAppsByDevboxId/route.ts b/frontend/providers/devbox/app/api/getAppsByDevboxId/route.ts index 548f81c89d7..de3650a24ba 100644 --- a/frontend/providers/devbox/app/api/getAppsByDevboxId/route.ts +++ b/frontend/providers/devbox/app/api/getAppsByDevboxId/route.ts @@ -5,6 +5,8 @@ import { authSession } from '@/services/backend/auth' import { getK8s } from '@/services/backend/kubernetes' import { jsonRes } from '@/services/backend/response' +export const dynamic = 'force-dynamic' + export async function GET(req: NextRequest) { try { const apps = await getApps(req) @@ -17,7 +19,7 @@ export async function GET(req: NextRequest) { } } -export async function getApps(req: NextRequest) { +async function getApps(req: NextRequest) { const { searchParams } = req.nextUrl const devboxId = searchParams.get('devboxId') as string diff --git a/frontend/providers/devbox/app/api/v1/getDBSecretList/route.ts b/frontend/providers/devbox/app/api/v1/getDBSecretList/route.ts index d01e397a119..04fa4bad6e5 100644 --- a/frontend/providers/devbox/app/api/v1/getDBSecretList/route.ts +++ b/frontend/providers/devbox/app/api/v1/getDBSecretList/route.ts @@ -94,6 +94,8 @@ const buildConnectionInfo = ( } } +export const dynamic = 'force-dynamic' + export async function GET(req: NextRequest) { try { const { payload, token } = getPayloadWithoutVerification(req.headers)