Skip to content

feat: 引入模板驱动的实例管理闭环(市场创建、配置重建、实时状态与网页控制台)#4

Open
MF-B wants to merge 10 commits intomainfrom
feat/game-server-clean-r2
Open

feat: 引入模板驱动的实例管理闭环(市场创建、配置重建、实时状态与网页控制台)#4
MF-B wants to merge 10 commits intomainfrom
feat/game-server-clean-r2

Conversation

@MF-B
Copy link
Copy Markdown
Owner

@MF-B MF-B commented Apr 4, 2026

变更概览

本 PR 将 MineDock 从“基础容器增删启停”扩展为“模板驱动创建 + 实时状态同步 + 在线控制台 + 配置重建”的完整链路,同时补齐文档体系与任务流水线。

主要改动

  • 后端新增游戏目录与 YAML 模板能力,支持按游戏 ID 加载模板并做结构校验(镜像、参数、端口、卷挂载等)。
  • 后端实例创建流程升级:创建时可注入模板参数、覆盖宿主机端口映射、自动应用卷挂载命名规则,并在本地持久化实例元数据。
  • 后端新增实例配置读写接口:支持读取当前生效配置,并在容器停止状态下通过“重建容器”应用新参数与端口映射。
  • 后端新增事件中心与 WebSocket 推送,基于 Docker 事件向前端广播实例快照,实现状态实时同步。
  • 后端新增网页控制台 WebSocket 桥接,打通容器 stdin/stdout/stderr 的双向通信。
  • 存储层从内存实现演进为 SQLite 持久化,实例记录包含 game_id,重启后可恢复元数据。
  • 前端新增镜像市场页、实例创建页、实例详情页(控制台/配置双 Tab),并接入模板参数表单、端口编辑与重建结果回写。
  • 前端新增实例同步机制:优先使用 WebSocket 实时更新,断线自动降级轮询并指数退避重连。
  • 前端新增 xterm 控制台集成,支持在线输入、输出渲染与自适应布局。
  • 文档结构重组为 api、standards、design-docs、exec-plans 目录,并补齐 API 合约与设计文档。
  • 工程与 CI 更新:Taskfile 统一开发/构建/测试/文档检查任务,CI 与 Release 流程同步到 Go 1.25.x,新增文档 lint/fmt/todo 生成链路。

API 变化

  • 新增 GET /api/games
  • 新增 GET /api/games/:id/template
  • 新增 GET /api/ws/events
  • 新增 GET /api/ws/console/:id
  • 新增 GET /api/instances/:id/config
  • 新增 PUT /api/instances/:id/config
  • 扩展 POST /api/instances 请求体,支持 params 与 ports

行为说明与注意事项

  • 实例配置更新通过重建容器生效,成功后会返回新的 container_id。
  • 配置更新仅允许在容器停止状态下执行,运行中会返回冲突错误。
  • WebSocket 连接采用同源策略;连接失败时前端自动回退轮询。
  • 删除实例默认不删除关联卷,数据默认保留。

影响范围

  • 后端 API、服务层、存储层、路由和主启动流程
  • 前端路由、状态管理、页面与实时通信
  • 文档目录结构、任务脚本、CI 与发布流程

Copilot AI review requested due to automatic review settings April 4, 2026 15:35
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 将 MineDock 扩展为“模板驱动创建 + SQLite 持久化 + 实时状态同步 + 在线控制台 + 配置读取/重建”的完整闭环,并同步补齐前端页面、i18n 与文档/API 合约。

Changes:

  • 后端新增 GameService(games.json + YAML 模板)与实例元数据(game_id)持久化,并补齐创建/配置读写/重建相关 API。
  • 后端新增基于 Docker Events 的 EventHub + WebSocket 推送,以及容器控制台 WebSocket 桥接。
  • 前端新增模板市场/创建/详情(控制台+配置)页面,接入实例 WebSocket 同步、xterm 控制台,并更新路由与多语言文案。

Reviewed changes

Copilot reviewed 61 out of 63 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
Readme.md 更新待办结构,反映模板/实例/交互/运维规划拆分
frontend/vite.config.js 调整 dev server host/HMR 与代理 WS 配置
frontend/src/views/InstanceDetail.vue 新增实例详情页(控制台/配置 Tab)与控制台挂载
frontend/src/views/InstanceConfig.vue 新增实例配置读取与重建提交 UI(params/ports)
frontend/src/views/ImageRegistry.vue 新增模板市场页(分类筛选、卡片入口)
frontend/src/views/CreateInstance.vue 新增模板驱动的实例创建表单(params/ports)
frontend/src/views/ContainerList.vue 接入实时同步、卡片跳转详情、创建入口改为市场页
frontend/src/stores/games.ts 新增 games store:列表缓存与模板加载
frontend/src/stores/containers.ts 扩展 create 入参、WS 状态标记、快照覆盖与稳定排序
frontend/src/router/index.ts 新增 /registry、/registry/:gameId/create、/instances/:id 路由
frontend/src/locales/zh-CN.json 新增控制台/配置/市场/同步状态等中文文案
frontend/src/locales/en-US.json 新增控制台/配置/市场/同步状态等英文文案
frontend/src/composables/useInstanceSync.ts 新增 WS 实时同步 + 断线轮询降级 + 退避重连
frontend/src/composables/useConsole.ts 新增 xterm 控制台:WS 双向收发、Fit/ResizeObserver
frontend/src/components/TopBar.vue 新增实时同步连接指示点并保留语言切换
frontend/src/components/Sidebar.vue 新增“游戏模板/市场”导航入口与移动端样式调整
frontend/src/api/index.ts 新增 games/template/config API、WS URL 构造、类型扩展
frontend/package.json 增加 xterm 相关依赖
frontend/package-lock.json 锁定新增依赖版本
docs/standards/ops.md 更新后端环境变量:games/模板目录
docs/standards/frontend.md 更新路由说明(新增市场/详情)
docs/standards/directory.md 更新目录结构说明(games.json/templates)
docs/exec-plans/completed/20260401-realtime-status-sync.md 记录实时状态同步方案与落地步骤(完成)
docs/exec-plans/completed/20260331-static-image-registry.md 历史计划文档归档(完成)
docs/exec-plans/completed/20260331-image-marketplace-page.md 历史计划文档归档(完成)
docs/exec-plans/active/20260402-container-ports-volumes.md 在途计划文档:端口/卷挂载实现路线
docs/exec-plans/active/20260402-container-console.md 在途计划文档:控制台实现路线
docs/exec-plans/.markdownlint.json exec-plans 子目录 markdownlint 配置
docs/design-docs/instance_lifecycle.md 补齐 game_id、配置重建、WS 同步、控制台交互策略
docs/api/contracts.md 新增 games/template/ws/config 合约与更新 POST/PUT 语义
backend/templates/terraria.yaml 新增 Terraria 模板(ports/volumes/resources/params)
backend/templates/minecraft-java.yaml 新增 Minecraft Java 模板(env/health_check/params)
backend/templates/minecraft-bedrock.yaml 新增 Minecraft Bedrock 模板(udp port/params)
backend/main.go 注入 GameService/EventHub/WS/Console/Config handler 并启动 hub
backend/internal/store/sqlite.go instances 表新增 game_id 列并迁移;Save/Get/List/Upsert 扩展
backend/internal/store/sqlite_test.go SQLite store 测试补齐 game_id 断言与 upsert 覆盖
backend/internal/service/game_service.go 新增 games.json 加载 + YAML 模板读取与结构校验
backend/internal/service/game_service_test.go 新增 GameService 单测(加载/缺模板/模板校验)
backend/internal/service/event_hub.go 新增 Docker Events 监听、快照去重、WS 广播、退避重连
backend/internal/service/event_hub_test.go EventHub 单测:增删 client、快照去重广播
backend/internal/service/console_service.go 新增容器 attach 服务(运行态校验 + attach)
backend/internal/service/console_service_test.go ConsoleService 单测:运行/停止/不存在容器
backend/internal/model/template.go 新增模板领域模型(ports/volumes/resources/params 等)
backend/internal/model/instance.go Instance 增加 game_id 字段
backend/internal/model/game.go 新增 Game 领域模型
backend/internal/model/errors.go 新增 game/template/params/config 相关领域错误
backend/internal/api/ws_handler.go 新增 /api/ws/events handler(同源 WS)
backend/internal/api/ws_handler_test.go WsHandler 单测:hub 不可用、Add/Remove 调用
backend/internal/api/router.go 路由注册扩展(games/ws/console/config)+ CORS 方法更新
backend/internal/api/handlers.go CreateInstance 请求体扩展(game_id/params/ports)+ 错误映射
backend/internal/api/handlers_test.go Handler 测试适配新创建签名 + games/template 场景覆盖
backend/internal/api/game_handlers.go 新增 games 列表与模板获取 API handler
backend/internal/api/console_handler.go 新增 /api/ws/console/:id handler(WS ↔ Docker attach 双向桥接)
backend/internal/api/config_handler.go 新增 GET/PUT /instances/:id/config handler
backend/internal/api/config_handler_test.go ConfigHandler 单测:get/update/非法 JSON/冲突状态码
backend/go.sum 引入 coder/websocket 等依赖校验和
backend/go.mod 新增 coder/websocket 依赖与 yaml 依赖记录
backend/games.json 新增游戏目录索引数据(market 列表来源)
.markdownlint.json 根目录 markdownlint 配置
Files not reviewed (1)
  • frontend/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +162 to +174
socket.onclose = (event: CloseEvent) => {
if (disposed) {
return;
}
connected.value = false;
if (event.reason) {
error.value = event.reason;
return;
}
if (event.code !== 1000) {
error.value = "console.disconnected";
}
};
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useConsole sets error.value = "console.disconnected" on abnormal WebSocket close, but the i18n dictionaries only define console.disconnectedLabel (no console.disconnected). This will surface a raw key or fallback text in the UI. Consider reusing the existing console.disconnectedLabel key, or add a dedicated console.disconnected translation in both locales.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +93
func pipeDockerToWebSocket(
ctx context.Context,
conn *websocket.Conn,
hijacked types.HijackedResponse,
) error {
writer := &wsBinaryWriter{ctx: ctx, conn: conn}
_, err := io.Copy(writer, hijacked.Reader)
if errors.Is(err, io.EOF) {
return nil
}
return err
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Console WebSocket 输出目前直接 io.CopyContainerAttachhijacked.Reader 到前端。但当容器未开启 TTY 时,Docker attach 输出是 stdcopy 多路复用格式(stdout/stderr 带 8-byte header),前端 xterm 会出现乱码。建议在 Attach 前读取容器 Config.Tty:TTY=true 时可直传;TTY=false 时应使用 stdcopy.StdCopy(或等价解复用)后再写入 WebSocket。

Copilot uses AI. Check for mistakes.
Comment on lines +199 to +205
for i := range tpl.Container.Volumes {
tpl.Container.Volumes[i].Name = strings.TrimSpace(tpl.Container.Volumes[i].Name)
tpl.Container.Volumes[i].ContainerPath = strings.TrimSpace(tpl.Container.Volumes[i].ContainerPath)
if tpl.Container.Volumes[i].ContainerPath == "" {
return fmt.Errorf("template %q volume index %d container_path is required: %w", gameID, i, model.ErrTemplateInvalid)
}
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalizeTemplatecontainer.volumes 仅校验了 container_path,但没有强制要求 volumes[i].name 非空。后续卷命名/挂载通常依赖该字段(例如 minedock-{instanceName}-{volumeName}),name 为空会导致不可读/冲突的卷名。建议在此处将空 name 视为 ErrTemplateInvalid 并返回明确错误。

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +142
function buildPortsPayload(): PortMapping[] {
return ports.value.map((port) => ({
host: Number(port.host),
container: Number(port.container),
protocol: port.protocol,
}));
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildPortsPayload() 使用 Number(port.host/container) 直接转换并提交端口;当输入框被清空或包含非数字时会转换成 0/NaNJSON.stringify(NaN) 会变成 null),从而向后端发送不可用端口值。建议在提交前对每个端口做显式校验(整数且在有效范围内),并在不合法时阻止保存/给出错误提示。

Copilot uses AI. Check for mistakes.
Comment on lines +148 to +154
function getCreatePortsPayload(): PortMapping[] {
return ports.value.map((port) => ({
host: Number(port.host),
container: Number(port.container),
protocol: port.protocol,
}));
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCreatePortsPayload() 使用 Number(port.host/container) 直接生成端口 payload;当用户清空输入或输入非法字符时会得到 0/NaN(序列化后可能变成 null),导致创建请求携带无效端口。建议在创建前校验端口为有效整数范围,并在不合法时阻止提交/提示用户修正。

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +18
"@xterm/addon-attach": "^0.11.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增了 @xterm/addon-attach 依赖,但当前代码中没有任何引用(控制台实现也未使用 AttachAddon)。这会增加包体积与依赖维护成本。若短期内不会用到,建议移除该依赖;若计划使用,请在实现中落地并补充对应引用。

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +73
- 当前路由:
- `/` 映射容器列表页(`ContainerList.vue`)
- `/registry` 映射游戏模板市场页(`ImageRegistry.vue`)
- `/instances/:id` 映射容器详情页(`InstanceDetail.vue`)
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs/standards/frontend.md 的“当前路由”列表遗漏了本 PR 新增的创建页路由 /registry/:gameId/createCreateInstance.vue)。建议补充该路由项,避免文档与实际 router 配置不一致。

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +103
<div
v-for="item in store.instances"
:key="item.container_id"
class="card"
@click="openInstanceDetail(item.container_id)"
>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

容器卡片使用 <div class="card" @click=...> 作为可点击入口,但没有 role="button" / tabindex="0" 以及键盘触发(Enter/Space)支持,键盘用户无法打开详情页。建议将卡片改为 <button>/<RouterLink>,或补齐可访问性属性与键盘事件处理。

Copilot uses AI. Check for mistakes.
Comment on lines +131 to +134
<header class="page-header">
<div class="header-left">
<button class="back-btn" @click="backToList">&lt;</button>
</div>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

返回按钮仅显示符号“<”,缺少可读的可访问性名称(screen reader 会读成“less-than”)。建议为按钮添加 aria-label(可复用已有 i18n key 如 console.back),并可考虑用图标 + 文案提升可用性。

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants