diff --git a/v1/frontend/.env.template b/v1/frontend/.env.template index 3859770..e027447 100644 --- a/v1/frontend/.env.template +++ b/v1/frontend/.env.template @@ -44,6 +44,9 @@ ACCOUNT_URL= # in dev: postgresql://username:password@127.0.0.1:26257/devboxdb?connection_limit=50&pool_timeout=20 # in prod: postgresql://username:password@cockroachdb-global.cockroach-operator-system:26257/devboxdb?connection_limit=50&pool_timeout=20 DATABASE_URL= +# database provider for prisma runtime/migration routing +# values: cockroachdb (default) | postgresql +DATABASE_PROVIDER="cockroachdb" # url for template retag # in dev: http://127.0.0.1:8092 # in prod: http://devbox-service.devbox-system.svc.cluster.local:8092 @@ -64,6 +67,10 @@ GPU_ENABLE= "false" ENABLE_IMPORT_FEATURE="false" # enable web IDE feature, default is false ENABLE_WEBIDE_FEATURE="false" +# IDE options in IDE button, comma-separated. Leave empty to show all. +# Available: vscode, webide, cursor, vscodeInsiders, windsurf, kiro, qoder, lingma, trae, traeCN, codebuddy, codebuddyCN, toolbox, gateway +# Note: webide also requires ENABLE_WEBIDE_FEATURE="true". +ENABLED_IDES='' # advanced config features (each can be enabled independently) # enable environment variables and configmap features, default is false ENABLE_ADVANCED_ENV_AND_CONFIGMAP="false" diff --git a/v1/frontend/AGENT.md b/v1/frontend/AGENT.md new file mode 100644 index 0000000..b11d0f5 --- /dev/null +++ b/v1/frontend/AGENT.md @@ -0,0 +1,60 @@ +# Devbox Provider Agent Notes + +## Scope + +This directory is the Devbox provider in the Sealos frontend workspace. It owns +the DevBox list, create/edit flow, detail page, template conversion UI, release +and deployment actions, and provider-side API routes under `app/api`. + +## Working Rules + +- Keep changes scoped to `frontend/providers/devbox` unless the task clearly + crosses a shared package boundary. +- Do not execute database writes or migrations unless the user explicitly asks. +- For production or test cloud images, build `linux/amd64` by default. +- For 70-cluster work, use `KUBECONFIG=/Users/mlhiter/.kube/70` and namespace + `devbox-frontend` unless the user gives a different target. +- The deployed image path uses the monorepo root `frontend/Dockerfile` with + `--build-arg name=devbox --build-arg path=providers/devbox`. +- The deployment has both `devbox-frontend-init` and `devbox-frontend` + containers. Inspect both image tags when verifying a rollout. +- Use the Codex in-app Browser for local browser verification. + +## Verification + +```bash +pnpm ts-lint +git diff --check +``` + +The package has no `test` script as of 2026-05-19, so generic test autodetection +that runs `npm test` will fail. Do not report that as a product regression; call +out the missing test script separately. + +## Important Paths + +- `app/[lang]/(platform)/(home)/page.tsx` - DevBox list entry. +- `app/[lang]/(platform)/devbox/create/page.tsx` - create/edit page shell. +- `app/[lang]/(platform)/devbox/create/components/Network.tsx` - network form, + public domain toggle, and custom domain drawer entry. +- `components/drawers/CustomAccessDrawer.tsx` - custom domain verification UX. +- `app/api/platform/authCname/route.ts` - CNAME verification endpoint. +- `app/api/platform/authDomainChallenge/route.ts` - fallback ownership + challenge verification endpoint. +- `utils/json2Yaml.ts` - form-to-Kubernetes manifest generation. +- `services/backend/kubernetes.ts` - Kubernetes client setup. +- `services/request.ts` - frontend request wrapper and auth headers. +- `services/db/init.ts` - Prisma client routing by `DATABASE_PROVIDER`. + +## Custom Domain Notes + +Custom domain verification first checks CNAME via `/api/platform/authCname`. +If that fails, it tries `/api/platform/authDomainChallenge`. A successful drawer +verification only updates the form field; the user still needs to click the +outer `update`/`变更` button to save the network configuration. + +For issue `labring-sigs/sealos-issues#171`, the reported "click confirm and +nothing happens" path was caused by dynamic DNS errors such as +`queryCname ENOTFOUND ` being treated as translation keys. Keep dynamic +DNS errors out of `next-intl` lookup paths; map known DNS codes to stable i18n +keys and show unknown messages as plain text. diff --git a/v1/frontend/DESIGN.md b/v1/frontend/DESIGN.md new file mode 100644 index 0000000..2fbb86f --- /dev/null +++ b/v1/frontend/DESIGN.md @@ -0,0 +1,36 @@ +# Devbox Design Notes + +Devbox is an operational product surface, not a marketing page. The interface +should be compact, predictable, and optimized for repeated configuration and +inspection. + +## UI Principles + +- Preserve the existing card and form structure when tuning layout. Prefer small + spacing and sizing improvements over broad redesigns. +- Keep status values in English unless the task explicitly asks for localization. +- Use concrete feedback for asynchronous actions. A button that validates or + mutates state must show loading, success, and failure states. +- Error copy should explain what the user can do next. Raw infrastructure + errors can appear only as fallback detail, not as the primary message. +- Keep dense configuration controls aligned and stable. Labels, buttons, and + dynamic text should not resize the surrounding network/resource form rows. +- Use `sonner` toasts for current provider UI feedback unless a component is + already bound to a different established local pattern. + +## Custom Domain UX + +The custom domain drawer is a validation step inside the larger DevBox edit +form. A successful validation does not persist the Kubernetes network change by +itself, so the success feedback must tell the user to save the outer form. + +DNS failure messages should use product language: + +- domain not found +- CNAME record missing +- CNAME target mismatch +- timeout +- network unreachable + +Avoid exposing raw `queryCname ENOTFOUND` or `queryCname ENODATA` as the only +visible explanation. diff --git a/v1/frontend/Dockerfile b/v1/frontend/Dockerfile index e0e3064..a6cb276 100644 --- a/v1/frontend/Dockerfile +++ b/v1/frontend/Dockerfile @@ -7,12 +7,9 @@ WORKDIR /app # Install dependencies based on the preferred package manager COPY package.json pnpm-lock.yaml* ./ COPY prisma ./prisma -RUN if [ -f pnpm-lock.yaml ]; then \ - pnpm install; \ - else \ - echo "Lockfile not found."; \ - exit 1; \ - fi +RUN \ + [ -f pnpm-lock.yaml ] && pnpm install || \ + (echo "Lockfile not found." && exit 1) # Rebuild the source code only when needed FROM node:current-alpine AS builder @@ -26,6 +23,8 @@ COPY --from=deps /app/prisma/generated ./prisma/generated # Uncomment the following line in case you want to disable telemetry during the build. ENV NEXT_TELEMETRY_DISABLED 1 ENV NEXT_PUBLIC_MOCK_USER '' +ARG DEVBOX_COMMIT_HASH=source-unavailable +ENV NEXT_PUBLIC_DEVBOX_COMMIT_HASH=${DEVBOX_COMMIT_HASH} RUN npm install -g pnpm && pnpm run build @@ -59,6 +58,10 @@ COPY --from=builder /app/package.json ./package.json # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +# Next standalone includes prisma schemas/clients but not migrations. +# Keep both providers' migrations for initContainer migrate deploy. +COPY --from=builder --chown=nextjs:nodejs /app/prisma/cockroach/migrations ./providers/devbox/prisma/cockroach/migrations +COPY --from=builder --chown=nextjs:nodejs /app/prisma/postgresql/migrations ./providers/devbox/prisma/postgresql/migrations USER nextjs diff --git a/v1/frontend/Makefile b/v1/frontend/Makefile index 0d5ae8d..9e12d53 100644 --- a/v1/frontend/Makefile +++ b/v1/frontend/Makefile @@ -1,13 +1,8 @@ -SERVICE_NAME ?= ghcr.io/sealos-apps/devbox-v1-frontend +SERVICE_NAME=sealos-devbox # Image URL to use all building/pushing image targets IMG ?= $(SERVICE_NAME):latest PNPM ?= pnpm PNPM_INSTALL_FLAGS ?= --frozen-lockfile -CONTAINER_TOOL ?= docker -DOCKERFILE ?= Dockerfile -DOCKER_BUILD_ARGS ?= -BUILDX_BUILDER ?= devbox-builder -PLATFORMS ?= linux/amd64,linux/arm64 SHELL = /usr/bin/env bash -o pipefail .SHELLFLAGS = -ec @@ -56,7 +51,7 @@ verify: ## Run the standard frontend verification pipeline. ##@ Build .PHONY: build -build: ## Build frontend application. +build: ## Build desktop-frontend binary. $(PNPM) run build .PHONY: dev @@ -64,20 +59,9 @@ dev: ## Run the frontend in development mode. $(PNPM) run dev .PHONY: run -run: ## Run the frontend in production mode. +run: ## Run a dev service from host. $(PNPM) run start .PHONY: docker-build -docker-build: ## Build docker image for the frontend. - $(CONTAINER_TOOL) build $(DOCKER_BUILD_ARGS) -t ${IMG} -f $(DOCKERFILE) . - -.PHONY: docker-buildx -docker-buildx: ## Build and push docker image for the frontend for cross-platform support. - - $(CONTAINER_TOOL) buildx create --name $(BUILDX_BUILDER) - $(CONTAINER_TOOL) buildx use $(BUILDX_BUILDER) - - $(CONTAINER_TOOL) buildx build --push $(DOCKER_BUILD_ARGS) --platform=$(PLATFORMS) --tag ${IMG} -f $(DOCKERFILE) . - - $(CONTAINER_TOOL) buildx rm $(BUILDX_BUILDER) - -.PHONY: docker-push -docker-push: ## Push docker image for the frontend. - $(CONTAINER_TOOL) push ${IMG} +docker-build: ## Build docker image with the desktop-frontend. + docker build -t sealos-devbox:latest . --network host --build-arg DEVBOX_COMMIT_HASH=$$(git rev-parse --short=12 HEAD) --build-arg HTTP_PROXY=http://127.0.0.1:7890 --build-arg HTTPS_PROXY=http://127.0.0.1:7890 diff --git a/v1/frontend/PRODUCT.md b/v1/frontend/PRODUCT.md new file mode 100644 index 0000000..c4c6501 --- /dev/null +++ b/v1/frontend/PRODUCT.md @@ -0,0 +1,36 @@ +# Devbox Product Context + +Devbox gives Sealos users a browser-managed development environment that can be +created from runtime templates, configured with resources and network access, +opened in IDEs, released, and deployed as an application. + +## Primary Users + +- Developers who need an on-demand cloud development environment. +- Template maintainers who convert existing DevBoxes into reusable templates. +- Platform operators who configure feature flags, runtime catalogs, pricing, + ingress domains, storage defaults, IDE availability, and database provider + settings for a region. + +## Core Jobs + +- Create a DevBox from a runtime/template with CPU, memory, GPU, storage, and + network settings. +- Open the DevBox in configured IDEs such as VS Code, Cursor, Web IDE, or other + enabled options. +- Edit an existing DevBox without rebuilding the mental model from YAML. +- Expose ports through generated public domains or user custom domains. +- Release a DevBox version and deploy it as an app. +- Convert a configured DevBox into a reusable template while reviewing + environment variables and ConfigMap defaults. + +## Product Boundaries + +- This provider owns the frontend and provider API surface. It does not own the + DevBox controller, runtime image internals, account service, monitor service, + registry service, or desktop shell. +- Provider-side changes can generate Kubernetes resources and patch existing + resources, but runtime behavior inside the user container belongs to the + runtime/controller layer. +- Database schema and migration execution must be explicit because this repo + supports both CockroachDB and PostgreSQL Prisma schemas. diff --git a/v1/frontend/README.md b/v1/frontend/README.md index 2aba0fb..425bc51 100644 --- a/v1/frontend/README.md +++ b/v1/frontend/README.md @@ -1,9 +1,16 @@ +# Devbox Frontend Provider + +Devbox is the Sealos frontend provider for creating, editing, releasing, and +operating DevBox development environments. It is a Next.js app inside the +Sealos frontend workspace. + ## How to dev 1. First,you should refer to `frontend/README.md` ’s `How to dev` part. 2. Then you should config your env. - 1. Create a new file `.env.local` in frontend/providers/kubepanel directory. + + 1. Create a new file `.env.local` in `frontend/providers/devbox`. > `SEALOS_DOMAIN` is anyone website you use in sealos. @@ -26,6 +33,7 @@ > Why you should have that? > > If you open your own dev in `localhost:3000` directly,you cannot have sealos desktop border,which maybe influence your style. + 1. This url:[website](https://cloud.sealos.run/?openapp=system-template%3FtemplateName%3Done-step-shortcuts) 2. ![image-20240423111024336](https://raw.githubusercontent.com/mlhiter/typora-images/master/202404231110609.png) @@ -35,3 +43,23 @@ 4. Then you can get your own dev in this. ![image-20240423111123308](https://raw.githubusercontent.com/mlhiter/typora-images/master/202404231111720.png) + +## Useful commands + +```bash +pnpm dev +pnpm build +pnpm ts-lint +pnpm gen-client +``` + +This workspace does not currently define a `test` script. Use `pnpm ts-lint` as +the focused typecheck until a real test command is added. + +## More docs + +- `AGENT.md` - agent operating notes for this provider. +- `docs/architecture.md` - module boundaries and request flow. +- `docs/runbook.md` - local verification and 70-cluster deployment notes. +- `docs/ia.md` - main pages and API route surfaces. +- `prisma/README.md` - Prisma schema and migration layout. diff --git a/v1/frontend/ROADMAP.md b/v1/frontend/ROADMAP.md new file mode 100644 index 0000000..56378e5 --- /dev/null +++ b/v1/frontend/ROADMAP.md @@ -0,0 +1,21 @@ +# Devbox Roadmap Notes + +This file tracks practical product/engineering gaps visible from the current +provider code. It is not a release commitment. + +## Near Term + +- Add regression coverage for custom-domain verification feedback so dynamic DNS + errors cannot be routed through i18n lookup again. +- Add a real package `test` script or document a project-level test command so + automated check scripts do not fail after typecheck. +- Keep template conversion defaults explicit and reviewable for environment + variables and ConfigMaps. + +## Operational Gaps + +- The current provider documentation is still lighter than the feature surface. + Keep `docs/architecture.md`, `docs/runbook.md`, and `docs/ia.md` updated as + future API or workflow changes land. +- ConfigMap single-file mounts use `subPath` for file shape. No-restart hot + updates require runtime/controller-level design, not only provider UI changes. diff --git a/v1/frontend/app/[lang]/(platform)/(home)/components/Empty.tsx b/v1/frontend/app/[lang]/(platform)/(home)/components/Empty.tsx index d640446..aa8083d 100644 --- a/v1/frontend/app/[lang]/(platform)/(home)/components/Empty.tsx +++ b/v1/frontend/app/[lang]/(platform)/(home)/components/Empty.tsx @@ -17,7 +17,7 @@ const Empty = () => { onClick={handleCreateDevbox} > {'list-empty'} -
+
{t('create_your_first_devbox')}
{t('click_here_to_create_devbox')} diff --git a/v1/frontend/app/[lang]/(platform)/(home)/components/List.tsx b/v1/frontend/app/[lang]/(platform)/(home)/components/List.tsx index a069927..a140961 100644 --- a/v1/frontend/app/[lang]/(platform)/(home)/components/List.tsx +++ b/v1/frontend/app/[lang]/(platform)/(home)/components/List.tsx @@ -32,7 +32,7 @@ import { import Image from 'next/image'; import dynamic from 'next/dynamic'; import { useTranslations } from 'next-intl'; -import { useCallback, useMemo, useState, useEffect } from 'react'; +import { useCallback, useMemo, useState, useEffect, useRef } from 'react'; import { useRouter } from '@/i18n'; import { useDateTimeStore } from '@/stores/date'; @@ -100,7 +100,7 @@ const DevboxList = ({ }) => { const router = useRouter(); const t = useTranslations(); - const { handleRestartDevbox, handleStartDevbox, handleGoToTerminal } = + const { handleRestartDevbox, handleStartDevbox, handleGoToTerminal, RestartConfirmChild } = useControlDevbox(refetchDevboxList); const { startDateTime: dateRangeStart } = useDateTimeStore(); @@ -140,7 +140,7 @@ const DevboxList = ({ header: ({ column }: HeaderContext) => ( -
+
{column.getIsSorted() === 'desc' ? ( ) : ( @@ -197,6 +197,7 @@ const DevboxList = ({ size: 220, cell: ({ row }: CellContext) => { const item = row.original; + const iconId = item.template.templateRepository.iconId || 'custom'; return (
@@ -205,8 +206,8 @@ const DevboxList = ({ {item.id}
@@ -216,14 +217,12 @@ const DevboxList = ({ {item.id}
-

- {item.template.templateRepository.iconId} -

+

{iconId}

{item.template.name}

@@ -239,7 +238,7 @@ const DevboxList = ({ {!item.remark && (
{ setOnOpenEditRemark(true); setEditRemarkItem(item); @@ -269,18 +268,18 @@ const DevboxList = ({
- {t('name')} + {t('name')} {item.name}
{!!item.remark && ( <>
- {t('remark')} + {t('remark')}
{item.remark}
@@ -335,7 +334,7 @@ const DevboxList = ({
-
+
{t('status')}
{statusOptions.map((option) => ( @@ -506,7 +505,7 @@ const DevboxList = ({ status={item.status} runtimeType={item.template.templateRepository.iconId as string} leftButtonProps={{ - className: 'border-r-1 w-36 rounded-r-none px-2' + className: 'w-36 rounded-r-none border-r px-2' }} /> diff --git a/v1/frontend/app/[lang]/(platform)/devbox/create/components/PriceBox.tsx b/v1/frontend/app/[lang]/(platform)/devbox/create/components/PriceBox.tsx index 490bb25..4ba1f00 100644 --- a/v1/frontend/app/[lang]/(platform)/devbox/create/components/PriceBox.tsx +++ b/v1/frontend/app/[lang]/(platform)/devbox/create/components/PriceBox.tsx @@ -107,7 +107,7 @@ const PriceBox = ({ components = [], className }: PriceBoxProps) => { return ( - + {t('estimated_price')} {t('daily')} @@ -117,7 +117,7 @@ const PriceBox = ({ components = [], className }: PriceBoxProps) => { key={item?.label} className={cn( 'flex h-12 items-center justify-between px-5 py-6', - index !== priceList.length - 1 && 'border-b-1 border-zinc-100' + index !== priceList.length - 1 && 'border-b border-zinc-100' )} >
diff --git a/v1/frontend/app/[lang]/(platform)/devbox/create/components/QuotaBox.tsx b/v1/frontend/app/[lang]/(platform)/devbox/create/components/QuotaBox.tsx index cf4150d..a931d7b 100644 --- a/v1/frontend/app/[lang]/(platform)/devbox/create/components/QuotaBox.tsx +++ b/v1/frontend/app/[lang]/(platform)/devbox/create/components/QuotaBox.tsx @@ -83,7 +83,7 @@ ${t('remaining')}: ${Math.max(0, limit - used).toFixed(2)} ${unit}`; return ( - + {t('resource_quota')} @@ -102,7 +102,7 @@ ${t('remaining')}: ${Math.max(0, limit - used).toFixed(2)} ${unit}`; key={item.type} className={cn( 'flex h-12 items-center justify-between px-5', - index !== quotaList.length - 1 && 'border-b-1 border-zinc-100' + index !== quotaList.length - 1 && 'border-b border-zinc-100' )} >
@@ -112,7 +112,7 @@ ${t('remaining')}: ${Math.max(0, limit - used).toFixed(2)} ${unit}`;
- +
diff --git a/v1/frontend/app/[lang]/(platform)/devbox/create/components/Runtime.tsx b/v1/frontend/app/[lang]/(platform)/devbox/create/components/Runtime.tsx index fa63c77..8012c81 100644 --- a/v1/frontend/app/[lang]/(platform)/devbox/create/components/Runtime.tsx +++ b/v1/frontend/app/[lang]/(platform)/devbox/create/components/Runtime.tsx @@ -15,6 +15,7 @@ import { useEnvStore } from '@/stores/env'; import { listOfficialTemplateRepository, listTemplate } from '@/api/template'; import { useDevboxStore } from '@/stores/devbox'; import { DevboxEditTypeV2 } from '@/types/devbox'; +import { getRuntimeTemplateConfig, getTemplateDefaults } from '@/utils/templateConfig'; import { Select, @@ -68,8 +69,11 @@ export default function Runtime({ isEdit = false }: RuntimeProps) { const afterUpdateTemplate = useCallback( (uid: string) => { const template = templateList.find((v) => v.uid === uid)!; - setValue('templateConfig', template.config as string); + const templateDefaults = getTemplateDefaults(template.config); + setValue('templateConfig', getRuntimeTemplateConfig(template.config)); setValue('image', template.image); + setValue('envs', templateDefaults.envs || []); + setValue('configMaps', templateDefaults.configMaps || []); }, [templateList, setValue] ); @@ -238,7 +242,7 @@ export default function Runtime({ isEdit = false }: RuntimeProps) { -
+