清理:移除 RDP 遗留资料并纳管产品文档与运维工具

变更详情:清理 RDP 遗留文档与临时文件,纳管产品文档与运维工具脚本
This commit is contained in:
songyiming committed 2026-10-29 17:44:25 +08:00
1 parent 26fde0369e
commit 7fdcb53dd9
14 files changed
+759 -100

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
-33
View File
@@ -1,33 +0,0 @@
# FreeRDP 3 客户端 API 参考(Ubuntu 24.04 / freerdp3-dev)
> 用途:kiosk-shell 内嵌 FreeRDP 客户端时使用,避免拉起外部进程(开发方案 3.2 节)。
> 记录时间:2026-10-23
## 包名
- 运行时:`libfreerdp-client3-3`、`libfreerdp3-3`
- 开发:`freerdp3-dev`(Ubuntu 24.04 提供 `freerdp3-dev` 与 `libfreerdp-client3-3-dev`)
- pkg-config 名称:`freerdp3`、`freerdp3-client`(安装后以 `pkg-config --list-all | grep freerdp` 实测为准)
## 关键头文件
| 头文件 | 用途 |
|---|---|
| `freerdp/client.h` | 客户端主入口、freerdp_client_context_new / freerdp_client_context_free |
| `freerdp/freerdp.h` | 核心上下文 |
| `freerdp/settings.h` | 连接参数设置(freerdp_settings_new / set) |
| `freerdp/channels/cliprdr.h` | 剪贴板通道 |
| `freerdp/gdi/gfx.h` | GFX 图形管线(AVC420/AVC444/RFX 能力) |
| `freerdp/crypto/tls.h` | TLS |
## 最小连接流程(伪代码)
```c
freerdp_client_context* ctx = freerdp_client_context_new();
RdpClientContext* cctx = ...;
freerdp_settings_new(0); // 释放: freerdp_settings_free
freerdp_set_pointer / freerdp_set_keyboard ... // 输入回调
ctx->Connect = ...; // 事件表
freerdp_client_context_new / connect
```
## 注意事项
- `/gfx` 命令行语法在 3.32.1 上必须用 `|` 分隔的能力组合(如 `progressive|RFX|AVC420|AVC444`),
单独 `AVC420` 会解析失败。API 侧对应 `gfx` 能力位设置。
-67
View File
@@ -1,67 +0,0 @@
# FreeRDP 3 内嵌客户端实现笔记(Ubuntu 24.04 / freerdp3-dev 3.32.1)
> 对应《VDI 云桌面软件开发方案》3.2 / 3.3 节:以 libfreerdp3 API 内嵌(非拉起外部进程)。
> 记录时间:2026-10-23,实测于 RK3528 盒子(Armbian / Ubuntu Noble / aarch64)
## 1. API 结构(与 FreeRDP 2.x 差异很大)
| 用途 | FreeRDP 3 写法 |
|---|---|
| 创建实例 | `freerdp* instance = freerdp_new();` |
| 建立上下文 | `freerdp_context_new_ex(instance, settings)` |
| 上下文 | `instance->context`(`rdpContext*`,公开结构体) |
| 事件回调 | `instance->PostConnect` / `instance->PostDisconnect` / `instance->AuthenticateEx` |
| 更新回调 | `instance->context->update->BitmapUpdate` / `->EndPaint` |
| GDI | `gdi_init(instance, PIXEL_FORMAT_BGRX32)` |
| 取帧 | `context->gdi->primary_buffer` + `context->gdi->stride` |
设置项键名(与 2.x 不同):
`FreeRDP_ServerHostname` / `FreeRDP_Username` / `FreeRDP_Password` / `FreeRDP_Domain` /
`FreeRDP_DesktopWidth` / `FreeRDP_DesktopHeight` / `FreeRDP_ColorDepth` / `FreeRDP_Fullscreen` /
`FreeRDP_IgnoreCertificate` / `FreeRDP_GfxProgressive` / `FreeRDP_GfxH264` / `FreeRDP_GfxAVC444` …
## 2. 三个必须踩过才知道的坑
### 坑一:gdi_init 必须在 PostConnect 里调用
在 `freerdp_context_new_ex()` 之后立刻调用 `gdi_init()` 会 **SIGSEGV**
(`gdi_init_ex` 段错误)。官方 x11 客户端(`client/X11/xf_client.c` 的 `xf_post_connect`)
把 `gdi_init` 放在 PostConnect 回调内,因为 GDI 依赖连接协商后的桌面尺寸与颜色格式。
### 坑二:不能用 instance->ContextSize 扩展 rdpContext
常见做法是把自定义指针塞进扩展的 rdpContext,但 `gdi_init_ex()` 会按 `ContextSize`
重新分配 context 缓冲区,扩展字段随即失效 → 段错误。
改用静态注册表按 `freerdp*` 记录会话对象;GFX 通道回调则用官方的
`RdpgfxClientContext.custom` 字段。
### 坑三:NLA 认证必须传空字符串而非 nullptr
Windows 端启用 NLA(CredSSP)时,认证回调里把密码置为 `nullptr` 会导致
`SEC_E_NO_CREDENTIALS [0x8009030e]`。必须传 `strdup("")`。
认证回调签名:
`BOOL (*pAuthenticateEx)(freerdp*, char** username, char** password, char** domain, rdp_auth_reason)`
## 3. /gfx 语法(命令行与设置项的对应)
FreeRDP 3.32.1 的 `/gfx` **不接受单个管道名**,必须是 `|` 分隔的能力组合:
```
/gfx:progressive|RFX|AVC420|AVC444 ← 可解析
/gfx:AVC420 ← parsing failed [-1000]
/gfx:AVC444 ← parsing failed [-1000]
```
API 侧对应 `FreeRDP_GfxProgressive` / `FreeRDP_GfxH264` / `FreeRDP_GfxAVC444` 等布尔项。
## 4. 像素格式
`PIXEL_FORMAT_BGRX32` 的内存布局是 **B,G,R,X** 四字节/像素。
QImage 没有对应格式,输出前需手动做 BGR→RGB 转换。
注意 `PIXEL_FORMAT_*` 是计算宏 `FREERDP_PIXEL_FORMAT(...)`,**不能硬编码数值**。
## 5. 后续演进(对应 freerdp-rk 分支)
当前实现是 CPU 侧 GDI 缓冲 → Qt 绘制。开发方案 3.3 节要求改为:
```text
FreeRDP3 解复用(gfx 通道) → GStreamer h264parse → mppvideodec(VPU硬解, zero-copy DMABuf)
→ waylandsink(DRM plane)
```
盒子已具备 `/dev/mpp_service`(VPU)与 `/dev/rga`,但缺 `mppvideodec` GStreamer 插件
(Ubuntu 官方源没有,需 Rockchip BSP / Armbian 额外源或自研)。
+39
View File
@@ -0,0 +1,39 @@
@echo off
REM ============================================================
REM MttVDD 虚拟显示驱动 - 一键安装(管理员运行)
REM 用法:右键本文件 -> 以管理员身份运行
REM ============================================================
setlocal
cd /d "%~dp0"
echo [1/4] 关闭可能占用驱动的程序...
sc stop SunshineService >nul 2>&1
taskkill /f /im sunshine.exe >nul 2>&1
echo [2/4] 安装驱动签名证书...
certutil -addstore -f "Root" "Virtual_Display_Driver.cer"
if errorlevel 1 (
echo [错误] 证书安装失败,请确认以管理员身份运行
pause
exit /b 1
)
echo [3/4] 安装 MttVDD 驱动...
pnputil /add-driver "MttVDD.inf" /install
if errorlevel 1 (
echo [警告] 驱动安装返回错误,尝试另一种方式...
rundll32.exe setupapi.dll,InstallHinfSection DefaultInstall 132 .\MttVDD.inf
)
echo [4/4] 显示当前虚拟显示器状态...
pnputil /enum-devices /class Display
echo.
echo ============================================================
echo 安装完成。请重启 Windows 后:
echo 1) 设置 - 显示 - 找到 MttVDD 虚拟显示器
echo 2) 分辨率选 1920x1080,刷新率选 60Hz
echo 3) 重启 SunshineService 服务
echo 重启方式:开始菜单搜索 services.msc -> SunshineService -> 重新启动
echo ============================================================
pause
@@ -0,0 +1,82 @@
# RDP 与 Moonlight 实测对比(2026-10-28)
## 测试环境
```text
终端:RK3528 PRO DDR4,Armbian 25.05,aarch64,LightDM + Xorg
显示器:1920×1440 @ 75Hz
云桌面:VM 100(win10-test),Proxmox VE 9.2.21,节点 starhe
终端到云桌面:192.168.1.188 -> 192.168.1.70,同一局域网 WiFi
```
服务端显示环境改造(本次关键前提):
```text
改造前:Microsoft Basic Display Adapter,1920×1080 @ 1Hz
改造后:Virtual Display Driver 8.35.11.365,1920×1080 @ 60Hz
```
改造前 Sunshine 抓不到稳定帧(1Hz),改造后可正常编码。
## 对比数据
| 指标 | RDP(当前 kiosk-shell) | Moonlight 1080p30 | Moonlight 1080p60 |
|---|---|---|---|
| 协议/编码 | RDP GFX,CPU 软解 | H.264 / libx264 软编 | H.264 / libx264 软编 |
| 分辨率 | 1920×1440 | 1920×1080 | 1920×1080 |
| 终端 CPU | 约 111%(单核近满载) | 约 26% | 约 26% |
| 终端内存 | 约 232 MB | 约 192 MB | 约 165 MB |
| 终端温度 | 约 58~65℃ | 约 56℃ | 约 53℃ |
| 实际帧率 | 无可靠统计(观感为低帧+卷帘) | 12.83 FPS | 22.79 FPS |
| 网络丢包 | 未测 | 0.00% | 0.00% |
| 网络抖动丢帧 | 未测 | 0.08% | 15.28% |
| 网络延迟 | 未测 | 2 ms | 6 ms |
| 主机处理延迟 | 未测 | 14.4 / 27.6 / 平均 18.1 ms | 18.8 / 78.3 / 平均 38.2 ms |
| 平均渲染耗时 | 未测 | 3.34 ms | 46.26 ms |
| 画面观感 | 卷帘、翻页 | 完整清晰,无撕裂 | 完整清晰,无撕裂 |
## 结论
1. **卷帘问题不是 RDP 协议的限制,是当前 RDP 客户端实现路径的问题。**
Moonlight 在同一台盒子、同一台云桌面、同一网络下画面完整清晰,无卷帘无撕裂,
抓屏确认 1080p 画面文字锐利、任务栏时间正确。
2. **Moonlight 的瓶颈在服务端,不在终端,也不���网络。**
1080p30 时网络丢包 0.00%、延迟 2 ms、渲染 3.34 ms、终端 CPU 26%,
但实际帧率仅 12.83 FPS;1080p60 时主机处理延迟升到平均 38.2 ms、渲染 46.26 ms。
服务端当前使用 libx264 软件编码(VM 内无 NVIDIA 直通,`nvidia-smi` 不存在),
4 核 CPU 同时承担 1080p 编码与桌面捕获,是帧率上限的真正原因。
3. **RDP 路径的 CPU 代价约为 Moonlight 的 4 倍**(111% vs 26%),
因为 RDP 走 `FreeRDP GDI -> CPU 内存复制 -> QImage/QPixmap -> QLabel -> Xorg`,
而 Moonlight 只做一次解码和一次窗口呈现。
4. **RK3528 VPU 硬解在当前 moonlight-qt 6.2.0 下不可用。**
`h264_v4l2m2m` / `hevc_v4l2m2m` / `hevc_v4l2request` 均报
`Could not find a valid device`,即使盒子存在 /dev/mpp_service、/dev/rga 和
/dev/dri/renderD128。需 Rockchip MPP FFmpeg 补丁或 V4L2 request 驱动。
## 后续建议
优先级从高到低:
1. **服务端 GPU 直通**:将宿主机 GTX 965M(PCI 01:00.0,IOMMU Group 2 独占)
直通给 VM 100,使 Sunshine 可用 NVENC 硬编码。预期把 1080p 帧率从 13 FPS
提升到 60 FPS,同时释放服务端 CPU。需先确认内核 `intel_iommu=on`、
`vfio-pci` 已绑定,以及该笔记本 GPU 的 vBIOS ROM 提取。
2. **消除帧率随请求升高而抖动**(60 FPS 时抖动丢帧 15.28%):服务端稳定后复测。
3. **RK3528 硬解适配**:解决 V4L2 request/m2m 设备缺失,这是达成 4K 的前提。
4. **协议选型建议**:
- 若 GPU 直通可行,Moonlight 在 CAD/3D/视频场景优于 RDP,建议按产品方案采用双协议:
普通办公用 RDP,GPU/CAD/3D 用 Moonlight;
- 同时保留并优化 RDP 路径的 Weston + MPP/VPU + DMABUF/DRM 零拷贝改造,
使 RDP 在办公场景达到可接受体验。
5. 当前 Moonlight 未接 VPU,1080p 软件解码仅 26% CPU 但 4K 会超出能力,
需在 4K 目标确定后优先解决硬解。
## 附:本次变更
- VM 100:安装 Virtual Display Driver(MttVDD),提供 1920×1080@60Hz 虚拟显示器。
- VM 100:安装 Sunshine v2026.914.233613,Web UI 账号 admin / vdi12345。
- 终端:安装 moonlight-qt 6.2.0,已与 Sunshine 完成配对。
- 终端:kiosk-shell 自启动已恢复(~/.config/autostart/vdi-kiosk.desktop)。
+74
View File
@@ -0,0 +1,74 @@
# Sunshine 显卡环境配置指引(转给操作 VM 的同事)
## 背景
我们在 VM 100(win10-test,192.168.1.70)里装了 Sunshine 用于 Moonlight 串流验证。当前卡在:VM 没有可用 GPU,Windows 只有「Microsoft 基本显示适配器」,分辨率 1024×768、刷新率 1Hz,Sunshine 无法按 30/60fps 捕获编码。
不需要动 Proxmox,只需要在 Windows 里完成两件事:装虚拟显示器驱动 + 确认 Sunshine 配置。
## 第 1 步:安装虚拟显示器驱动(IddSampleDriver)
让 Windows 出现一个虚拟显卡,可自定义分辨率和刷新率:
1. 下载虚拟显示驱动包(ParrotGeek 的 IddSampleDriver,或等效的「Virtual Display Driver」)。
常用地址:https://github.com/itsmikethetech/Virtual-Display-Driver/releases
(如果 GitHub 打不开,用镜像:把链接前面加 https://gh-proxy.com/ )
2. 解压后右键 `IddSampleDriver.inf` →「安装」。
3. 重启 Windows。
4. 装好后设备管理器会出现「IddSampleDriver Device」,显示设置里会多出一个虚拟显示器。
5. 把虚拟显示器设置为:
- 分辨率:1920×1080(先用 1080p,稳定后再试 2K)
- 刷新率:60Hz(关键!当前 1Hz 是主要问题)
- 如果看到多显示器,把虚拟屏设为「扩展这些显示器」, Sunshine 默认抓主屏;也可以只保留虚拟屏。
## 第 2 步:确认 NVIDIA 独显是否可用(如果宿主已直通/有驱动)
打开 PowerShell 执行:
```powershell
nvidia-smi
```
- 如果能输出显卡信息(GTX 965M):很好,说明 VM 内能用 NVIDIA。
- 请安装 GeForce 驱动(如未装),然后告知我们,我们把 Sunshine 切到 NVENC 硬编码。
- 如果提示找不到命令或没有 NVIDIA 设备:跳过此步,走纯软件编码 + 虚拟显示器路线(也够验证流畅度)。
## 第 3 步:检查 Sunshine 服务状态
Sunshine 已装在 `C:\Program Files\Sunshine\`,服务名 `SunshineService`。
PowerShell 执行:
```powershell
Get-Service SunshineService
Restart-Service SunshineService
```
重启服务后再看日志确认识别到新显示器:
```powershell
Get-Content "C:\Program Files\Sunshine\config\sunshine.log" -Tail 60
```
需要看到的关键行(类似):
```
Desktop resolution [1920x1080]
Display refresh rate [60 Hz]
```
## 第 4 步:告知我们结果
只需回三条信息:
1. 虚拟显示器是否装好,分辨率/刷新率是多少;
2. `nvidia-smi` 是否有输出;
3. sunshine.log 最后几十行里的 Desktop resolution / Display refresh rate 数值。
我们这边会立刻重新发起 Moonlight 串流测试并采集性能数据。
## 备注
- Web UI(可选):https://192.168.1.70:47990 账号 admin / 密码 vdi12345
- 装驱动需要重启 VM,重启后 SunshineService 会自启,无需其他操作。
- Sunshine 的串流端口 47984/47989/47990/48010 已确认开放,防火墙无需改动。
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<display_config>
<target_display>
<target_x>0</target_x>
<target_y>0</target_y>
<target_width>1920</target_width>
<target_height>1080</target_height>
<target_refresh_rate>60</target_refresh_rate>
</target_display>
<adapter_index>0</adapter_index>
<modes>
<mode>
<width>1920</width>
<height>1080</height>
<refresh_rate>60</refresh_rate>
</mode>
</modes>
</display_config>
</root>
+287
View File
@@ -0,0 +1,287 @@
# VDI Web 管理控制台 MVP 实施方案
> 版本:v0.1(功能先行,UI 稿待项目经理提供)
> 日期:2026-10-28
> 依据:`VDI云桌面软件开发方案(1).docx` 第 2、4、5 节及 `VDI云桌面完整方案-云管端.docx` 管理平台模块表。
## 1. 先明确两个“登录”
本项目有两个不同的界面,必须分开:
| 界面 | 使用者 | 运行位置 | 职责 |
|---|---|---|---|
| 终端 kiosk-shell | 云桌面最终用户 | RK3528 盒子本地全屏 | 首配、设备绑定、用户登录、选择/进入被授权桌面、会话状态 |
| Web 管理控制台 console | 超级管理员、运维、只读人员 | PC 浏览器 | 终端、桌面池、用户绑定、GPU、监控、OTA、审计管理 |
正式文档中的 `console/` 指第二项。它不应运行在盒子上,也不应直接发起 RDP/Moonlight;它通过平台 API 管理资源和下发策略。
“盒子开机直连 Moonlight”只能作为当前串流技术验证,不是最终产品登录流程。最终终端流程应恢复为:
```text
盒子开机
-> kiosk-shell 全屏终端登录/首配页
-> vdt-agent 注册并获取终端身份
-> 用户登录或终端绑定校验
-> 平台返回该用户可用的桌面列表及短期连接信息
-> 用户选择桌面
-> kiosk-shell 按 protocol=rdp|moonlight 启动会话
-> 断开后回桌面列表/登录页
```
## 2. Web 控制台 MVP 目标
第一版目标不是做漂亮的大屏,而是跑通一个可演示、可替换 UI、可接真实后端的后台系统:
1. 用户能登录和退出;
2. 根据角色控制菜单和按钮;
3. 管理员能查看终端、桌面、用户及绑定关系;
4. 能对终端下发命令、对桌面执行开关机/重置;
5. 能查看基础状态、操作结果和审计记录;
6. 后端未完成时使用 Mock API,后端接口上线后不改页面业务逻辑即可切换。
## 3. 技术栈与工程结构
按定版文档执行:Vue 3 + Vite + TypeScript 严格模式 + Element Plus。
建议补充:Pinia(状态)、Vue Router、Axios、Vitest、Playwright、MSW(Mock API)。
```text
console/
├─ src/
│ ├─ api/ # OpenAPI 生成 SDK 的薄封装
│ ├─ assets/
│ ├─ components/ # 领域无关组件
│ ├─ layouts/ # 登录布局、控制台布局
│ ├─ mocks/ # MSW handlers 与演示数据
│ ├─ router/ # 路由、鉴权守卫、RBAC meta
│ ├─ stores/ # auth、app、notification
│ ├─ types/ # 页面 ViewModel;API 类型由生成器管理
│ ├─ views/
│ │ ├─ login/
│ │ ├─ dashboard/
│ │ ├─ terminals/
│ │ ├─ desktops/
│ │ ├─ users/
│ │ ├─ gpu/
│ │ ├─ monitoring/
│ │ ├─ ota/
│ │ └─ audit/
│ ├─ App.vue
│ └─ main.ts
├─ e2e/
├─ public/
├─ .env.development
├─ .env.mock
├─ package.json
└─ vite.config.ts
```
约束:页面不能直接拼 URL 或写 Axios;所有网络调用只经过 `api/`。这样 Mock 与真实后端可无感切换。
## 4. 页面与优先级
### M0:登录与应用框架
- 登录页:用户名、密码、登录按钮、错误状态、加载状态;
- 控制台壳:侧边导航、顶部栏、面包屑、当前账号、退出;
- 401 自动清理会话并回登录;
- 403 权限提示;
- 路由级 RBAC:`super_admin`、`operator`、`viewer`;
- 开发环境使用 Mock 登录,生产环境只调用真实 API。
### M1:核心演示闭环
1. 仪表盘:在线终端、活动会话、运行桌面、告警数量;
2. 终端管理:列表、搜索、状态筛选、详情、重启、重置会话;
3. 桌面管理:列表、状态筛选、开机、关机、重启、重置;
4. 用户与绑定:用户列表、用户详情、终端/桌面绑定与解绑;
5. 审计日志:操作者、动作、目标、结果、时间、请求 ID。
M1 完成后已经可以给项目经理演示完整业务流程。
### M2:完整文档范围
- 桌面池:模板、容量、回收策略;
- GPU 池:节点/PCI 地址/型号/状态/绑定桌面;
- 监控:会话在线数、FPS、RTT、节点水位、告警;
- OTA 中心:版本、批次、灰度比例、进度、失败终端;
- CSV 用户/绑定批量导入向导;
- 配置模板与配置版本下发。
## 5. 登录与权限方案
### 登录接口
```http
POST /api/v1/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "******"
}
```
建议响应:
```json
{
"accessToken": "jwt",
"expiresIn": 3600,
"user": {
"id": "usr_001",
"username": "admin",
"displayName": "系统管理员",
"role": "super_admin"
}
}
```
建议第一版 access token 仅存内存;刷新 token 使用 `HttpOnly + Secure + SameSite` Cookie。若后端尚未支持 refresh,MVP 可暂存 `sessionStorage`,但不要长期使用 `localStorage` 保存高权限令牌。
### 权限矩阵
| 能力 | super_admin | operator | viewer |
|---|---:|---:|---:|
| 查看所有页面 | 是 | 是 | 是 |
| 终端远程命令 | 是 | 是 | 否 |
| 桌面生命周期操作 | 是 | 是 | 否 |
| 用户/绑定修改 | 是 | 否 | 否 |
| GPU 绑定 | 是 | 否 | 否 |
| OTA 发布/灰度 | 是 | 是 | 否 |
| 只读监控/审计 | 是 | 是 | 是 |
前端 RBAC 只是交互约束,真正授权必须由 `api-gateway` 再验证,不能只隐藏按钮。
## 6. 第一版 API 契约
文档已明确的接口优先沿用:
```text
POST /api/v1/auth/login
GET /api/v1/users
POST /api/v1/desktop/assign
POST /api/v1/pool/{id}/action
GET /api/v1/gpu/cards
POST /api/v1/gpu/bind
GET /api/v1/monitor/query
```
为控制台 MVP 补齐建议接口(需后端确认并写入 OpenAPI):
```text
GET /api/v1/auth/me
POST /api/v1/auth/logout
GET /api/v1/terminals
GET /api/v1/terminals/{id}
POST /api/v1/terminals/{id}/commands
GET /api/v1/desktops
GET /api/v1/desktops/{id}
POST /api/v1/desktops/{id}/actions
GET /api/v1/bindings
DELETE /api/v1/bindings/{id}
GET /api/v1/audit-logs
```
所有列表统一查询参数:`page`、`pageSize`、`keyword`、`status`、`sort`;统一响应:
```json
{
"items": [],
"page": 1,
"pageSize": 20,
"total": 0,
"requestId": "req_xxx"
}
```
所有命令型接口必须返回 `commandId`,页面展示“已提交”,再轮询或通过 WebSocket/MQTT 网关显示最终结果,不能把 HTTP 200 当作设备已经执行成功。
## 7. Mock 先行策略
后端还没完成时,前端不应硬编码页面数据。使用 MSW 模拟真实 HTTP:
- 固定 3 个测试账号,对应三角色;
- 终端包含在线/离线/会话中/升级中;
- 桌面包含运行/停止/错误/任务中;
- 所有命令先进入 `pending`,1–3 秒后变成 `success` 或 `failed`;
- 登录失败、401、403、网络超时、空列表都要有测试场景;
- `VITE_API_MODE=mock|real` 切换数据源。
项目经理 UI 稿到后,Mock 数据仍然可用于逐页视觉验收。
## 8. 与终端登录流程的接口边界
管理员 Web 控制台不直接把 VM 密码写进终端,也不让终端调用 Proxmox API。
建议终端用户登录流程由 `auth-svc` 单独提供:
```text
POST /api/v1/terminal/session/login
GET /api/v1/terminal/session/desktops
POST /api/v1/terminal/session/connect-ticket
```
`connect-ticket` 返回短期、一次性连接信息:
```json
{
"desktopId": "desk_001",
"protocol": "moonlight",
"endpoint": "192.168.1.70",
"app": "Desktop",
"display": { "width": 1920, "height": 1080, "fps": 60 },
"ticket": "short-lived-token",
"expiresAt": "2026-10-28T16:30:00+08:00"
}
```
这部分不是 `console/` 页面,但必须和 Web 控制台的“用户/终端/桌面绑定”共享同一套后端数据。
## 9. UI 稿到来前的页面设计原则
第一版只做稳定工作台,不做营销式页面:
- 左侧导航 + 顶部工具栏 + 主内容区;
- 列表优先,详情使用独立页或抽屉;
- 状态使用图标/标签,不依赖单一颜色;
- 危险操作二次确认;
- 所有异步操作显示提交、执行中、成功、失败四态;
- 页面级空态、错误态、无权限态必须完整;
- 颜色、字号、间距集中为设计 token,避免 UI 稿到后逐页改 CSS;
- 表格列、筛选区、表单字段以配置驱动,减少视觉改版成本。
## 10. 验收标准
M1 应达到:
1. 三种角色都能登录,菜单/按钮权限正确;
2. 刷新页面仍能恢复会话,令牌过期自动回登录;
3. 终端、桌面、用户/绑定、审计五条流程可演示;
4. 命令有执行中与最终结果,不出现“点击无反馈”;
5. API 全走统一 SDK/适配层,无页面直连 Axios;
6. Vitest 覆盖 auth store、路由守卫、权限判断;
7. Playwright 覆盖登录、终端命令、桌面操作、用户绑定四条主路径;
8. `npm run lint`、`npm run typecheck`、`npm run test`、`npm run build` 全通过。
## 11. 推荐实施顺序
```text
第 1 步:初始化 console 工程、路由、布局、设计 token
第 2 步:OpenAPI 初稿 + API SDK 生成 + MSW Mock
第 3 步:登录、会话恢复、RBAC、错误处理
第 4 步:终端管理 + 桌面管理
第 5 步:用户与绑定 + 审计日志
第 6 步:仪表盘聚合数据
第 7 步:Playwright E2E 与生产构建
第 8 步:项目经理 UI 稿到后替换视觉层
第 9 步:接真实 Go API,关闭 Mock
```
## 12. 当前决策建议
- 现在先开发 `console/` 的 M0 + M1,不等 UI 稿;
- 同时为 `terminal/kiosk-shell` 恢复“登录/桌面列表后连接”的产品流程,不再把开机直连 Moonlight 当最终形态;
- Web 控制台第一版只接 Mock,API 契约先和后端负责人确认;
- Moonlight/RDP 统一抽象成终端连接票据的 `protocol` 字段,Web 控制台只管理策略,不直接操控串流客户端;
- UI 稿到后只调整布局组件与 design tokens,业务路由、权限、API、测试保留。