From 70ee5e33da119884350ce92d9bf64e209f84328f Mon Sep 17 00:00:00 2001 From: Maofeng Date: Thu, 30 Jul 2026 09:21:55 +0800 Subject: [PATCH] feat(console-web): replace TanStack UI with Solid host pages - move the management frontend under the wasmeld-console crate - use a persistent Host shell with disposable iframe page documents - version Host/Page postMessage state and command contracts - migrate runtime, service, WIT, invocation, and settings workflows - add Tailwind CSS v4, responsive layouts, and Host-owned dialogs - emit a dependency-free PWA worker with API cache exclusions - remove the superseded root TanStack Start application --- console/.oxfmtrc.json | 4 - console/.oxlintrc.json | 14 - console/README.md | 36 - console/package.json | 37 - console/src/components/dialogs.tsx | 379 --- console/src/components/views.tsx | 986 ------- console/src/routeTree.gen.ts | 68 - console/src/router.tsx | 16 - console/src/routes/__root.tsx | 60 - console/src/routes/index.tsx | 489 ---- console/src/styles/app.css | 2110 -------------- console/tests/rendered-html.test.mjs | 127 - console/vite.config.ts | 19 - .../wasmeld-console/web}/.gitignore | 1 - crates/wasmeld-console/web/.oxfmtrc.json | 8 + crates/wasmeld-console/web/.oxlintrc.json | 12 + crates/wasmeld-console/web/index.html | 16 + .../wasmeld-console/web}/package-lock.json | 2480 +++++++++-------- crates/wasmeld-console/web/package.json | 32 + crates/wasmeld-console/web/page.html | 13 + crates/wasmeld-console/web/public/icon.svg | 5 + .../web/public/manifest.webmanifest | 18 + .../wasmeld-console/web}/public/og.png | Bin .../web/src/components/ui/empty-state.tsx | 13 + .../web/src/components/ui/page-heading.tsx | 19 + .../web/src/components/ui/service-table.tsx | 195 ++ .../web/src/components/ui/status-badge.tsx | 6 + crates/wasmeld-console/web/src/host/app.tsx | 444 +++ .../wasmeld-console/web/src/host/dialogs.tsx | 357 +++ crates/wasmeld-console/web/src/host/main.tsx | 13 + .../wasmeld-console/web/src/lib}/api.ts | 88 +- .../wasmeld-console/web/src/lib/model.ts | 154 +- .../wasmeld-console/web/src/lib/view-state.ts | 17 + .../web/src/pages/activity/index.tsx | 61 + .../web/src/pages/instances/index.tsx | 108 + crates/wasmeld-console/web/src/pages/main.tsx | 59 + .../web/src/pages/overview/index.tsx | 258 ++ .../web/src/pages/services/index.tsx | 76 + .../web/src/pages/settings/index.tsx | 88 + crates/wasmeld-console/web/src/pages/types.ts | 7 + .../web/src/pages/wit-packages/index.tsx | 105 + .../web/src/primitives/create-frame-bridge.ts | 40 + crates/wasmeld-console/web/src/sdk/host.ts | 32 + crates/wasmeld-console/web/src/sdk/page.ts | 27 + .../wasmeld-console/web/src/sdk/protocol.ts | 95 + crates/wasmeld-console/web/src/styles/app.css | 329 +++ .../web/tests/build-output.test.mjs | 23 + .../wasmeld-console/web}/tsconfig.json | 12 +- crates/wasmeld-console/web/vite.config.ts | 88 + 49 files changed, 3996 insertions(+), 5648 deletions(-) delete mode 100644 console/.oxfmtrc.json delete mode 100644 console/.oxlintrc.json delete mode 100644 console/README.md delete mode 100644 console/package.json delete mode 100644 console/src/components/dialogs.tsx delete mode 100644 console/src/components/views.tsx delete mode 100644 console/src/routeTree.gen.ts delete mode 100644 console/src/router.tsx delete mode 100644 console/src/routes/__root.tsx delete mode 100644 console/src/routes/index.tsx delete mode 100644 console/src/styles/app.css delete mode 100644 console/tests/rendered-html.test.mjs delete mode 100644 console/vite.config.ts rename {console => crates/wasmeld-console/web}/.gitignore (85%) create mode 100644 crates/wasmeld-console/web/.oxfmtrc.json create mode 100644 crates/wasmeld-console/web/.oxlintrc.json create mode 100644 crates/wasmeld-console/web/index.html rename {console => crates/wasmeld-console/web}/package-lock.json (58%) create mode 100644 crates/wasmeld-console/web/package.json create mode 100644 crates/wasmeld-console/web/page.html create mode 100644 crates/wasmeld-console/web/public/icon.svg create mode 100644 crates/wasmeld-console/web/public/manifest.webmanifest rename {console => crates/wasmeld-console/web}/public/og.png (100%) create mode 100644 crates/wasmeld-console/web/src/components/ui/empty-state.tsx create mode 100644 crates/wasmeld-console/web/src/components/ui/page-heading.tsx create mode 100644 crates/wasmeld-console/web/src/components/ui/service-table.tsx create mode 100644 crates/wasmeld-console/web/src/components/ui/status-badge.tsx create mode 100644 crates/wasmeld-console/web/src/host/app.tsx create mode 100644 crates/wasmeld-console/web/src/host/dialogs.tsx create mode 100644 crates/wasmeld-console/web/src/host/main.tsx rename {console/src => crates/wasmeld-console/web/src/lib}/api.ts (66%) rename console/src/console-model.ts => crates/wasmeld-console/web/src/lib/model.ts (65%) create mode 100644 crates/wasmeld-console/web/src/lib/view-state.ts create mode 100644 crates/wasmeld-console/web/src/pages/activity/index.tsx create mode 100644 crates/wasmeld-console/web/src/pages/instances/index.tsx create mode 100644 crates/wasmeld-console/web/src/pages/main.tsx create mode 100644 crates/wasmeld-console/web/src/pages/overview/index.tsx create mode 100644 crates/wasmeld-console/web/src/pages/services/index.tsx create mode 100644 crates/wasmeld-console/web/src/pages/settings/index.tsx create mode 100644 crates/wasmeld-console/web/src/pages/types.ts create mode 100644 crates/wasmeld-console/web/src/pages/wit-packages/index.tsx create mode 100644 crates/wasmeld-console/web/src/primitives/create-frame-bridge.ts create mode 100644 crates/wasmeld-console/web/src/sdk/host.ts create mode 100644 crates/wasmeld-console/web/src/sdk/page.ts create mode 100644 crates/wasmeld-console/web/src/sdk/protocol.ts create mode 100644 crates/wasmeld-console/web/src/styles/app.css create mode 100644 crates/wasmeld-console/web/tests/build-output.test.mjs rename {console => crates/wasmeld-console/web}/tsconfig.json (53%) create mode 100644 crates/wasmeld-console/web/vite.config.ts diff --git a/console/.oxfmtrc.json b/console/.oxfmtrc.json deleted file mode 100644 index fce110d..0000000 --- a/console/.oxfmtrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "./node_modules/oxfmt/configuration_schema.json", - "ignorePatterns": ["src/routeTree.gen.ts"] -} diff --git a/console/.oxlintrc.json b/console/.oxlintrc.json deleted file mode 100644 index 5f58139..0000000 --- a/console/.oxlintrc.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "./node_modules/oxlint/configuration_schema.json", - "plugins": ["typescript", "unicorn", "oxc", "react", "jsx-a11y", "node"], - "categories": { - "correctness": "error" - }, - "rules": {}, - "ignorePatterns": ["src/routeTree.gen.ts"], - "env": { - "builtin": true, - "browser": true, - "node": true - } -} diff --git a/console/README.md b/console/README.md deleted file mode 100644 index ffa25b5..0000000 --- a/console/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Wasmeld Console - -WebAssembly Runtime 的管理控制台,使用 TanStack Start、TanStack Router、 -React 和 srvx 构建,通过 Node.js 运行。页面只调用 `wasmeld-console` HTTP API, -不直接访问数据库或 Wasmeld Runtime。 - -运行概览可以启动、停止和重启整个 Runtime;服务版本与运行实例页面负责管理单个 -Wasm Actor。停止 Runtime 不会断开管理 API。 - -## Development - -```bash -npm install -npm run dev -``` - -默认访问 `http://localhost:3000`。 - -管理 API 默认使用 `http://127.0.0.1:8080`。可以在控制台的“运行设置”中修改, -或在构建时设置 `VITE_WASMELD_API_URL`。 - -## Validation - -```bash -npm run format:check -npm run lint -npm test -``` - -生产构建完成后可以运行: - -```bash -npm start -``` - -`npm test` 会生成 Node.js 生产构建,并验证 srvx 服务端渲染和静态资源。 diff --git a/console/package.json b/console/package.json deleted file mode 100644 index 2482b22..0000000 --- a/console/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "wasmeld-console", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite dev", - "build": "vite build && tsc --noEmit", - "start": "srvx serve --prod --entry dist/server/server.js --static ../client", - "preview": "vite preview", - "test": "npm run build && node --test tests/rendered-html.test.mjs", - "lint": "oxlint . --deny-warnings", - "format": "oxfmt .", - "format:check": "oxfmt --check ." - }, - "dependencies": { - "@tanstack/react-router": "^1.170.18", - "@tanstack/react-start": "^1.168.32", - "lucide-react": "^1.27.0", - "react": "19.2.6", - "react-dom": "19.2.6", - "srvx": "^0.12.4" - }, - "devDependencies": { - "@types/node": "22.19.19", - "@types/react": "19.2.14", - "@types/react-dom": "19.2.3", - "@vitejs/plugin-react": "6.0.2", - "oxfmt": "^0.60.0", - "oxlint": "^1.75.0", - "typescript": "5.9.3", - "vite": "^8.1.5" - }, - "engines": { - "node": ">=22.13.0" - } -} diff --git a/console/src/components/dialogs.tsx b/console/src/components/dialogs.tsx deleted file mode 100644 index 65c79c6..0000000 --- a/console/src/components/dialogs.tsx +++ /dev/null @@ -1,379 +0,0 @@ -import { - ArrowRight, - Clock3, - CloudUpload, - FileCode2, - Package, - Play, - RadioTower, - RefreshCw, - ShieldCheck, - SquareTerminal, - X, -} from "lucide-react"; -import { FormEvent, ReactNode, useState } from "react"; -import { InvokeResult, RegisterComponentInput } from "../api"; -import { - FormattedInvocationOutput, - Service, - formatInvocationOutput, - invocationInputSize, - parseInvocationInput, -} from "../console-model"; - -function DialogFrame({ - title, - description, - icon, - onClose, - closeDisabled = false, - children, -}: { - title: string; - description: string; - icon: ReactNode; - onClose: () => void; - closeDisabled?: boolean; - children: ReactNode; -}) { - return ( -
- { - if (closeDisabled) event.preventDefault(); - else onClose(); - }} - onKeyDown={(event) => { - if (event.key === "Escape" && !closeDisabled) onClose(); - }} - > -
-
- {icon} -
-

{title}

-

{description}

-
-
- -
- {children} -
-
- ); -} - -export function ActivateDeploymentDialog({ - service, - currentRevision, - submitting, - onClose, - onConfirm, -}: { - service: Service; - currentRevision: string | null; - submitting: boolean; - onClose: () => void; - onConfirm: () => void; -}) { - return ( - } - onClose={onClose} - closeDisabled={submitting} - > -
-
-
- 当前版本 - {currentRevision ?? "尚未部署"} -
- -
- 目标版本 - {service.revision} -
-
-

- Wasmeld 将先确认 {service.id}@{service.revision} 的 Actor 可用,再更新对外路由。 -

-
-
- - -
-
- ); -} - -export function DeployDialog({ - onClose, - onRegister, -}: { - onClose: () => void; - onRegister: (input: RegisterComponentInput) => Promise; -}) { - const [file, setFile] = useState(null); - const [submitting, setSubmitting] = useState(false); - const [error, setError] = useState(""); - const fileName = file?.name ?? ""; - - async function submit(event: FormEvent) { - event.preventDefault(); - if (!file) return; - if (!file.name.toLowerCase().endsWith(".wasmpkg")) { - setError("请选择 .wasmpkg 组件包"); - return; - } - setSubmitting(true); - setError(""); - try { - await onRegister({ file }); - } catch (submitError) { - setError(submitError instanceof Error ? submitError.message : "组件注册失败"); - setSubmitting(false); - } - } - - return ( - } - onClose={onClose} - > -
- - - {error &&
{error}
} - -
- - -
-
-
- ); -} - -export function WitPackageDialog({ - onClose, - onPublish, -}: { - onClose: () => void; - onPublish: (file: File) => Promise; -}) { - const [file, setFile] = useState(null); - const [submitting, setSubmitting] = useState(false); - const [error, setError] = useState(""); - const fileName = file?.name ?? ""; - - async function submit(event: FormEvent) { - event.preventDefault(); - if (!file) return; - if (!file.name.toLowerCase().endsWith(".wasm")) { - setError("请选择由 wasmeld wit build 生成的 .wasm 文件"); - return; - } - if (file.size > 4 * 1024 * 1024) { - setError("WIT 包不能超过 4 MB"); - return; - } - setSubmitting(true); - setError(""); - try { - await onPublish(file); - } catch (submitError) { - setError(submitError instanceof Error ? submitError.message : "WIT 包发布失败"); - setSubmitting(false); - } - } - - return ( - } - onClose={onClose} - > -
- - - {error &&
{error}
} - -
- - -
-
-
- ); -} - -export function InvokeDialog({ - service, - onClose, - onInvoke, -}: { - service: Service; - onClose: () => void; - onInvoke: (service: Service, input: Uint8Array) => Promise; -}) { - const [input, setInput] = useState(service.id === "echo" ? "hello wasm" : ""); - const [format, setFormat] = useState<"utf8" | "hex">("utf8"); - const [output, setOutput] = useState(""); - const [outputBytes, setOutputBytes] = useState(null); - const [outputFormat, setOutputFormat] = useState(null); - const [running, setRunning] = useState(false); - const [failed, setFailed] = useState(false); - const [latency, setLatency] = useState(null); - - async function invoke() { - setRunning(true); - setOutput(""); - setOutputBytes(null); - setOutputFormat(null); - setFailed(false); - setLatency(null); - try { - const result = await onInvoke(service, parseInvocationInput(input, format)); - const formatted = formatInvocationOutput(service, result.output, format); - setOutput(formatted.text); - setOutputBytes(result.outputBytes); - setOutputFormat(formatted); - setLatency(result.latencyMs); - } catch (invokeError) { - setFailed(true); - setOutput(invokeError instanceof Error ? invokeError.message : "组件调用失败"); - } finally { - setRunning(false); - } - } - - return ( - } - onClose={onClose} - > -
-
-
- - -
- {invocationInputSize(input, format)} B -
-