From 9b292149b6673b75b74dc701f313d6c5251c22a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=AE=E9=9B=A8?= Date: Tue, 11 Aug 2026 18:51:01 +0800 Subject: [PATCH] refactor: align dashboard with shared design system --- .github/workflows/ci.yml | 8 +- README.md | 4 +- admin-dashboard/README.md | 25 +- admin-dashboard/package.json | 6 +- admin-dashboard/pnpm-lock.yaml | 21 +- .../scripts/check-design-contract.mjs | 68 + admin-dashboard/src/pages/_app.tsx | 5 +- admin-dashboard/src/pages/_document.tsx | 9 +- admin-dashboard/src/pages/index.tsx | 845 ++++---- .../src/style/dashboard.module.css | 1757 ++++++++++------- admin-dashboard/src/style/global.css | 215 +- .../src/theme/control-room-theme.tsx | 191 +- 12 files changed, 1949 insertions(+), 1205 deletions(-) create mode 100644 admin-dashboard/scripts/check-design-contract.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df9c84e..cf88133 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,6 +132,12 @@ jobs: set -Eeuo pipefail corepack pnpm --dir admin-dashboard typecheck + - name: Check dashboard design contract + shell: bash + run: | + set -Eeuo pipefail + corepack pnpm --dir admin-dashboard check:design + production-build: name: Production build runs-on: ubuntu-24.04 @@ -355,7 +361,7 @@ jobs: dashboard_html="$(curl --fail --silent --show-error --retry 10 --retry-all-errors --retry-delay 2 \ "http://127.0.0.1:${DASHBOARD_PORT}/")" - grep --fixed-strings --quiet 'WaitQueue Workbench' <<< "$dashboard_html" + grep --fixed-strings --quiet 'data-product="waitqueue-console"' <<< "$dashboard_html" node -e ' let html = ""; process.stdin.setEncoding("utf8"); diff --git a/README.md b/README.md index f0e15e2..9aa6393 100644 --- a/README.md +++ b/README.md @@ -644,10 +644,10 @@ curl -X POST http://127.0.0.1:3000/waitqueue/admin/deadLetters/replay \ 控制台位于 `admin-dashboard/`,不是 mock 模板,也不是后端启动依赖。它提供: - 10 秒自动刷新,页面不可见时暂停轮询; -- 基于 Ant Design 6 的紧凑工作台、浅/深主题和移动端布局; +- 基于 Ant Design 6、对齐 fullstack-ai-infra Warm Agent Workspace 的浅/深主题与移动端布局; - 真实 waiting、running、retrying、DLQ、最老等待与容量利用率; - 当前进程 callback、claim、recovery 计数及起始时间; -- 左侧实时队列目录、Workbench 摘要与并发槽位视图; +- 72px 模块栏、256px 队列上下文目录、60px 顶栏与独立队列详情页; - 队列搜索、并发占用和三类 cron 展示; - 注册/更新队列、向指定队列提交任务、分页查询与 generation-safe DLQ 重放; - 离线、过期、加载和空数据状态。 diff --git a/admin-dashboard/README.md b/admin-dashboard/README.md index 2497600..d92b368 100644 --- a/admin-dashboard/README.md +++ b/admin-dashboard/README.md @@ -1,19 +1,21 @@ -# WaitQueue Workbench +# WaitQueue 队列运行中心 waitqueue.js 的轻量实时运维控制台。它直接读取后端队列配置、Redis 运行快照和当前进程计数,不使用 mock 数据,也不展示当前系统无法证明的历史趋势。 -界面沿用 DWS Backend 的开发者工作台语言:数据目录、紧凑顶栏、黑色 Workbench 横幅、1px 深色描边和薄荷绿运行状态。交互控件使用 Ant Design 6,布局与视觉 token 保持项目自身风格,没有引入 Ant Design Pro、图表运行时或额外状态库。 +界面遵循 [fullstack-ai-infra/design-system@9d048faa](https://github.com/fullstack-ai-infra/design-system/tree/9d048faaabe0429a6a8720bfbb31418544237b6b) 的 **Warm Agent Workspace** 视觉契约:暖象牙画布、stone 导航、paper 表面、charcoal 文字和 sage 主操作。布局采用 72px 模块栏、256px 队列上下文栏和 60px 顶栏;表格、表单、抽屉、弹窗及反馈继续由 Ant Design 6 提供。 + +设计系统当前尚未发布稳定 npm 版本,且其 React 18 peer contract 与本项目 React 19 不兼容,因此这里使用轻量 semantic adapter 将上游 token 映射到 Ant Design `ConfigProvider`,不引入重复的 Radix、Tailwind 或状态管理运行时。待设计系统正式支持 React 19 后,可直接切换到包依赖。 ## 页面怎么用 -顶部四个工作区分别承担不同职责: +左侧模块栏的四个页面分别承担不同职责: -1. **总览**:先看 backlog、最老 waiting、Retry、DLQ、容量利用率,以及本进程 callback/claim/recovery 计数。计数的起始时间显示在卡片底部,服务重启后会归零。 -2. **队列**:在左侧目录搜索 namespace、回调 origin 或 queue ID;点选队列后查看运行槽位、Cron 和真实运行状态,也可提交 taskId 或更新配置。 +1. **总览**:先看等待任务、最老等待、运行容量、Retry、DLQ,以及本进程 callback/claim/recovery 计数。计数的起始时间显示在卡片底部,服务重启后会归零。 +2. **队列**:在上下文目录搜索 namespace、回调 origin 或 queue ID;点选队列后查看运行槽位、Cron 和真实运行状态,也可提交 taskId 或更新配置。 3. **死信**:选择队列后分页查看 DLQ;重放前必须二次确认,后端用 `entryId` 校验 generation,避免把已更新的旧记录误重放。 4. **诊断**:分别查看进程 liveness、MySQL/Redis readiness,并核对 Prometheus 抓取契约。 -页面每 10 秒自动刷新,浏览器切到后台时暂停;手动刷新会同时更新队列快照和健康状态。顶栏的 `ONLINE` 只有在 liveness 与 readiness 都成功时才出现,依赖异常会明确显示为 `DEGRADED`。桌面端使用表格与固定队列目录,窄屏切换为卡片、抽屉目录和底部视图导航;浅色/深色主题共用同一套 Ant Design token 状态。 +页面每 10 秒自动刷新,浏览器切到后台时暂停;手动刷新会同时更新队列快照和健康状态。顶栏只有在 liveness 与 readiness 都成功时才显示“服务在线”,依赖异常会显示“依赖异常”。桌面端使用模块栏、固定队列目录和表格,窄屏隐藏目录并通过抽屉选择队列,表格自动切换为卡片;浅色/深色主题共用同一套语义 token。 当前没有任务历史存储,因此页面不会展示吞吐趋势、成功率、平均耗时或任务明细。 @@ -64,6 +66,7 @@ DASHBOARD_ALLOWED_HOSTS=127.0.0.1,localhost,[::1] ```bash corepack pnpm --dir admin-dashboard typecheck +corepack pnpm --dir admin-dashboard check:design corepack pnpm --dir admin-dashboard build corepack pnpm --dir admin-dashboard start ``` @@ -101,17 +104,17 @@ Browser ## 技术与目录 -运行时直接依赖只有 Next.js、React、Ant Design、图标与其 SSR 样式运行时;没有 Ant Design Pro、Redux、Axios、Mock.js 或图表库。Pages Router 使用 `_document.tsx` 提取 Ant Design CSS-in-JS 样式,避免首屏闪烁。Next 16 的开发与生产构建显式使用 Webpack,以确保 Ant Design 与提取器共享同一个样式上下文;CI 会检查产物中存在真实 Ant 组件规则,而不只检查空的 style 标签。 +运行时直接依赖只有 Next.js、React、Ant Design、Lucide 图标与 Ant SSR 样式运行时;没有 Ant Design Pro、Redux、Axios、Mock.js、Tailwind、Radix 或图表库。Lucide 与设计系统的图标契约一致,并按组件 tree-shaking。Pages Router 使用 `_document.tsx` 提取 Ant Design CSS-in-JS 样式,避免首屏闪烁。Next 16 的开发与生产构建显式使用 Webpack,以确保 Ant Design 与提取器共享同一个样式上下文;CI 会检查产物中存在真实 Ant 组件规则,而不只检查空的 style 标签。 ```text admin-dashboard/ ├── src/pages/_app.tsx # 全局样式与主题入口 ├── src/pages/_document.tsx # Ant Design 服务端样式提取 -├── src/pages/index.tsx # 数据读取、交互与控制室页面 +├── src/pages/index.tsx # 数据读取、交互与四个运行页面 ├── src/pages/api/waitqueue/ # 运行时白名单代理与 token 注入 -├── src/style/global.css # 设计 token、主题与基础样式 -├── src/style/dashboard.module.css # 工作台布局、状态组件和响应式样式 -├── src/theme/ # Ant Design token 与浅/深主题状态 +├── src/style/global.css # design-system 语义适配与基础样式 +├── src/style/dashboard.module.css # Warm Queue Console 布局与响应式样式 +├── src/theme/ # 语义 token 到 Ant Design 的映射 ├── next.config.js # API 同源代理 └── .env.example # 后端地址示例 ``` diff --git a/admin-dashboard/package.json b/admin-dashboard/package.json index e5bfc3f..9fa7f8f 100644 --- a/admin-dashboard/package.json +++ b/admin-dashboard/package.json @@ -11,12 +11,14 @@ "dev": "next dev --webpack -p 3001", "build": "next build --webpack", "start": "next start -p 3001", - "typecheck": "next typegen && tsc --noEmit" + "typecheck": "next typegen && tsc --noEmit", + "check:design": "node scripts/check-design-contract.mjs" }, "dependencies": { "@ant-design/cssinjs": "2.1.2", - "@ant-design/icons": "6.3.2", + "@fontsource/inter": "5.3.0", "antd": "6.6.0", + "lucide-react": "0.468.0", "next": "16.3.0", "react": "19.2.0", "react-dom": "19.2.0" diff --git a/admin-dashboard/pnpm-lock.yaml b/admin-dashboard/pnpm-lock.yaml index 8ca10a6..f59a723 100644 --- a/admin-dashboard/pnpm-lock.yaml +++ b/admin-dashboard/pnpm-lock.yaml @@ -8,12 +8,15 @@ dependencies: '@ant-design/cssinjs': specifier: 2.1.2 version: 2.1.2(react-dom@19.2.0)(react@19.2.0) - '@ant-design/icons': - specifier: 6.3.2 - version: 6.3.2(react-dom@19.2.0)(react@19.2.0) + '@fontsource/inter': + specifier: 5.3.0 + version: 5.3.0 antd: specifier: 6.6.0 version: 6.6.0(react-dom@19.2.0)(react@19.2.0) + lucide-react: + specifier: 0.468.0 + version: 0.468.0(react@19.2.0) next: specifier: 16.3.0 version: 16.3.0(@types/node@20.19.43)(react-dom@19.2.0)(react@19.2.0) @@ -139,6 +142,10 @@ packages: resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} dev: false + /@fontsource/inter@5.3.0: + resolution: {integrity: sha512-RofMylZmjlJEfELXeNHFWBRcSs75rGU/6bV2S2jfnvv/3rPXPGe0LgUJTklcHZ9lM4OZmAVFhcJPnACfb91A3g==} + dev: false + /@img/colour@1.1.0: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} @@ -1189,6 +1196,14 @@ packages: string-convert: 0.2.1 dev: false + /lucide-react@0.468.0(react@19.2.0): + resolution: {integrity: sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + dependencies: + react: 19.2.0 + dev: false + /nanoid@3.3.18: resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} diff --git a/admin-dashboard/scripts/check-design-contract.mjs b/admin-dashboard/scripts/check-design-contract.mjs new file mode 100644 index 0000000..38fb09e --- /dev/null +++ b/admin-dashboard/scripts/check-design-contract.mjs @@ -0,0 +1,68 @@ +import { readFileSync } from 'node:fs'; + +const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8'); +const fail = (message) => { + throw new Error(`design contract: ${message}`); +}; +const expectText = (source, value, context) => { + if (!source.includes(value)) fail(`${context} is missing ${value}`); +}; + +const globalCss = read('src/style/global.css'); +const dashboardCss = read('src/style/dashboard.module.css'); +const page = read('src/pages/index.tsx'); +const documentSource = read('src/pages/_document.tsx'); +const theme = read('src/theme/control-room-theme.tsx'); +const packageJson = JSON.parse(read('package.json')); + +expectText(globalCss, '9d048faaabe0429a6a8720bfbb31418544237b6b', 'semantic adapter pin'); +expectText(theme, 'fullstack-ai-infra/design-system@9d048faa', 'Ant theme pin'); +expectText(documentSource, "matchMedia('(prefers-color-scheme: dark)')", 'pre-paint system theme bootstrap'); +expectText(documentSource, "localStorage.getItem('waitqueue-theme')", 'legacy theme migration'); +expectText(documentSource, "dataset.themeReady=mode==='dark'?'false':'true'", 'dark first-paint guard'); +expectText(globalCss, "[data-theme-ready='false']", 'dark first-paint CSS guard'); +expectText(theme, 'colorTextPlaceholder: colors.foregroundSubtle', 'accessible placeholder mapping'); +expectText(theme, "dataset.themeReady = 'true'", 'hydrated theme readiness'); + +const requiredTokens = [ + '--ui-canvas:', + '--ui-navigation:', + '--ui-surface:', + '--ui-foreground:', + '--ui-primary:', + '--ui-ai:', + '--ui-border:', + '--ui-font-sans:', + '--ui-space-4:', + '--ui-radius-lg:', + '--ui-shadow-md:', + '--ui-duration-normal:', + '--ui-rail-width: 72px', + '--ui-sidebar-width: 256px', + '--ui-topbar-height: 60px', +]; +for (const token of requiredTokens) expectText(globalCss, token, 'semantic token contract'); + +if (/#[\da-f]{3,8}\b/i.test(dashboardCss) || /#[\da-f]{3,8}\b/i.test(page)) { + fail('product layout must use semantic variables instead of raw palette values'); +} + +for (const legacy of ['WaitQueue Workbench', 'Runtime Pulse', 'Delivery & Recovery', '#00c98b', '#17181b']) { + if (page.includes(legacy) || dashboardCss.includes(legacy)) fail(`legacy visual language remains: ${legacy}`); +} + +expectText(page, 'data-product="waitqueue-console"', 'stable dashboard marker'); +expectText(page, 'aria-label="主模块导航"', 'primary navigation landmark'); +expectText(page, 'aria-label="队列目录"', 'queue catalog landmark'); +expectText(page, 'aria-label="运行模块"', 'rail menu name'); +expectText(page, 'tabIndex={-1}', 'skip-link focus target'); +expectText(dashboardCss, 'min-height: 44px', 'mobile touch targets'); +for (const view of ["key: 'overview'", "key: 'queues'", "key: 'deadLetters'", "key: 'diagnostics'"]) { + expectText(page, view, 'module navigation'); +} + +if (packageJson.dependencies['@ant-design/icons']) fail('product icons must not depend directly on @ant-design/icons'); +if (packageJson.dependencies['lucide-react'] !== '0.468.0') fail('lucide-react must stay aligned with the pinned design-system commit'); +if (packageJson.dependencies['@fontsource/inter'] !== '5.3.0') fail('Inter assets must stay aligned with the pinned design-system commit'); + +console.log('Warm Agent Workspace contract verified.'); diff --git a/admin-dashboard/src/pages/_app.tsx b/admin-dashboard/src/pages/_app.tsx index 2b98b9f..936a084 100644 --- a/admin-dashboard/src/pages/_app.tsx +++ b/admin-dashboard/src/pages/_app.tsx @@ -1,5 +1,8 @@ import Head from 'next/head'; import type { AppProps } from 'next/app'; +import '@fontsource/inter/400.css'; +import '@fontsource/inter/500.css'; +import '@fontsource/inter/600.css'; import '../style/global.css'; import { ControlRoomTheme } from '../theme/control-room-theme'; @@ -8,7 +11,7 @@ export default function WaitQueueApp({ Component, pageProps }: AppProps) { <> - + diff --git a/admin-dashboard/src/pages/_document.tsx b/admin-dashboard/src/pages/_document.tsx index d74e305..ee4c793 100644 --- a/admin-dashboard/src/pages/_document.tsx +++ b/admin-dashboard/src/pages/_document.tsx @@ -31,7 +31,14 @@ export default class WaitQueueDocument extends Document { render() { return ( - + +