管理页面设备分页与完整统计 #6

Open
opened 2026-08-28 16:19:54 +08:00 by ila · 1 comment
Owner

基本信息

  • 类型:单元任务
  • 阶段:待实施
  • 原始需求来源:用户会话,2026-08-28(Asia/Shanghai)
  • 用户确认:工单 #5 已验收通过并关闭;确认建立本工单。
  • 前置工单:#3 管理页面生产实现、#4 轻量登录、#5 紧凑设备列表。
  • 是否允许并行:否;前端分页、完整统计与 CRUD 后的页码状态需要在同一工作树内顺序验证。

问题背景

MediaMTX Control API 的列表接口默认每页返回 100 条:

  • GET /v3/config/paths/list
  • GET /v3/paths/list

当前管理页面未传递 itemsPerPage / page,也未读取后续 pageCount,因此超过 100 个静态 RTSP 设备后:

  • 页面只显示第一页;
  • “路径总数 / 在线 / 离线 / 待请求”统计不完整;
  • 搜索和状态筛选只能覆盖已加载的第一页;
  • 用户可能误判设备配置丢失。

该问题是数据完整性边界,不是 MediaMTX 核心设备数量限制。

目标

  • 分批读取配置路径和运行路径的全部 API 页面。
  • 顶部统计、搜索和状态筛选覆盖全部已加载设备。
  • 浏览器只渲染当前分页的数据,避免大列表产生过多 DOM 节点。
  • 为桌面表格和窄屏卡片提供一致、轻量、可访问的分页操作。
  • 保持原生 HTML/CSS/JavaScript、零前端运行时依赖。

非目标

  • 不修改 MediaMTX Control API、分页协议或 Go 后端接口。
  • 不增加数据库、缓存服务、前端框架、构建器或虚拟滚动库。
  • 不承诺无限规模;本工单面向数百至低数千条路径的局域网管理场景。
  • 不改变认证、登录、路径 CRUD、预览协议、RTSP 凭据处理或媒体转发方式。
  • 不增加批量编辑、批量删除、设备分组、排序或跨服务器聚合。
  • 不提交本地摄像头 URL、账号密码、二进制、启动脚本或用户无关改动。

已确认方案

1. 分批读取全部数据

  • 配置路径与运行路径均以 itemsPerPage=100 请求。
  • 首先并行请求两个接口的第 0 页,读取 pageCount 与 itemCount。
  • 后续页面采用有上限的并发请求,建议最多同时 4 个,避免瞬时请求过多。
  • 合并页面前校验响应结构;按照路径名称去重并保持稳定排序。
  • 运行状态按路径名称合并到配置路径;缺少运行记录时继续沿用“离线 / 待请求”等现有判定语义。
  • 不使用任意大的 itemsPerPage 绕过分页,避免把限制延后到另一个隐藏阈值。

2. 完整统计和筛选

  • “路径总数 / 在线 / 离线 / 待请求”针对全部静态 RTSP 配置计算,不只针对当前页。
  • 搜索和状态筛选先作用于完整数据集,再进行客户端分页。
  • 搜索或状态筛选发生变化时回到第 1 页。
  • 无匹配结果时显示现有空状态并保留清除筛选的恢复路径。
  • 加载未完成时不显示误导性的部分统计;显示“正在加载设备 X / Y”进度或等效明确反馈。

3. 客户端分页

  • 默认每页 50 条。
  • 支持每页 25、50、100 条,切换后回到第 1 页。
  • 显示范围与总数,例如“1–50 / 共 236 条”。
  • 提供“上一页”“下一页”;第一页和最后一页使用原生 disabled 状态。
  • 显示当前页 / 总页数;不增加复杂页码跳转器。
  • 删除当前页最后一条后,如果当前页超出新总页数,自动回退到最后一个有效页。
  • 新增、编辑、删除或手动刷新后重新拉取完整数据;尽量保持当前筛选和有效页码。
  • 分页只决定渲染范围,不改变顶部完整统计。

4. 桌面与移动端

  • 分页栏位于路径面板底部,桌面保持单行紧凑布局。
  • 375px 下允许分组换行,但按钮、选择框保持至少 44px。
  • 状态不只依靠颜色;当前范围、总数和禁用状态有文本及语义。
  • 键盘 Tab 顺序符合视觉顺序,分页状态变化通过合适的 aria-live 区域通知。
  • 不增加无限滚动:设备管理需要稳定页码、可预测位置和明确总数。

5. 分页失败处理

  • 任一批次失败时,不把部分数据伪装成完整结果。
  • 错误信息指出配置列表或运行状态列表加载失败,并在开发诊断信息中保留失败页码;页面不得回显认证头或敏感数据。
  • 提供“重试”入口,重试后重新获取完整快照。
  • 401 继续走现有清理会话并返回登录页的逻辑;403 与网络错误继续使用现有错误语义。
  • 避免失败页面的无限自动重试。

影响范围

预计主要涉及:

  • internal/api/admin/app.js:分批请求、合并、完整统计、筛选后分页、CRUD 后页码处理。
  • internal/api/admin/index.html:轻量分页栏与可访问状态文本。
  • internal/api/admin/app.css:桌面和 375px 分页布局。
  • 管理页前端测试与 internal/api 静态资源/路由相关测试。

如实现中发现需要修改 Go Control API,应停止并重新确认范围;本工单默认不改后端。

风险与控制

  • 风险:大量页面产生请求风暴。
    • 控制:每页 100、并发上限 4、失败停止并显式重试。
  • 风险:配置列表与运行列表在加载期间发生变化,形成短暂快照不一致。
    • 控制:以配置列表为设备基线、按名称合并运行状态、刷新获得新快照;不声称强事务一致。
  • 风险:搜索、删除和刷新后页码越界。
    • 控制:所有数据集变化后统一计算并钳制当前页。
  • 风险:部分页面失败导致统计错误。
    • 控制:完整加载成功前不提交新状态;保留上一次成功快照或显示明确失败状态,不展示部分统计。
  • 风险:路径名称或错误文本造成注入。
    • 控制:继续使用安全 DOM API,不通过 innerHTML 拼接动态数据。
  • 回退:回退本工单独立提交即可恢复最多显示 API 默认第一页的现状,无数据迁移。

验收标准

  • 使用至少 235 条虚构静态 RTSP 配置时,页面能加载全部设备而非前 100 条。
  • 配置与运行状态接口按 100 条分批读取,后续请求并发有明确上限。
  • 顶部总数及在线、离线、待请求统计覆盖全部设备。
  • 搜索与状态筛选覆盖全部设备,再按筛选结果分页。
  • 默认每页 50 条,可切换 25 / 50 / 100。
  • 正确显示当前范围、总数、当前页和总页数。
  • 第一页禁用“上一页”,最后一页禁用“下一页”。
  • 搜索、筛选、切换每页数量、刷新及删除最后一条后的页码行为正确。
  • 任一后续 API 页面失败时不显示不完整统计,并提供明确重试。
  • 401、403、网络错误和退出登录行为没有回归。
  • 桌面和 375px 无横向溢出,分页控件可通过键盘操作且触控目标至少 44px。
  • 页面仍为原生 HTML/CSS/JS,不新增第三方运行时依赖。
  • 受影响测试、Go 构建、Harness 检查和浏览器控制台检查通过。
  • 提交只包含工单范围,不包含本地配置、摄像头凭据、二进制、启动脚本或无关改动。
  • 提交推送并回写完整验证证据;工单保持开启,等待用户验收。

验证方法

  • 前端自动测试构造 0、1、50、51、100、101、235 条数据及多页运行状态。
  • 验证批次合并、稳定排序、去重、并发上限和失败页处理。
  • 验证完整统计、跨页搜索/筛选、每页数量、边界按钮和页码钳制。
  • 验证新增、编辑、删除、刷新、401、403、网络失败和部分页失败。
  • node --check internal/api/admin/app.js
  • 管理页相关前端测试。
  • go test ./internal/api -run '^TestAdmin' -count=1
  • go build ./...
  • python dev_scripts/harness.py check --strict
  • python dev_scripts/harness.py sync --check
  • git diff --check
  • 浏览器 1440×900 与 375×812 实测;控制台 warning/error 检查。

设计证据

用户已确认采用以下轻量方向:

  • 明确分页,不使用无限滚动;
  • 自动分批读取全部 API 页面;
  • 默认每页渲染 50 条;
  • 支持 25 / 50 / 100;
  • 完整统计与全量搜索/筛选;
  • 不新增后端、框架或数据库。

这是现有设备列表底部的局部扩展,不改变主要页面结构、权限模型或 CRUD 流程。采用原生分页控件、稳定位置、明确总数和 44px 操作目标,无需另做完整交互原型;实施时以桌面与 375px 浏览器截图作为审阅证据。

文档影响

分页属于管理页面长期能力。实现稳定后评估是否需要更新 Product-Requirements-Overview、Architecture-and-Code-Map 与 Common-Changes;如长期事实变化,必须先更新 Gitea Wiki,再通过 Harness 单向同步 docs/maintainer/,不得直接编辑镜像。

## 基本信息 - 类型:单元任务 - 阶段:待实施 - 原始需求来源:用户会话,2026-08-28(Asia/Shanghai) - 用户确认:工单 #5 已验收通过并关闭;确认建立本工单。 - 前置工单:#3 管理页面生产实现、#4 轻量登录、#5 紧凑设备列表。 - 是否允许并行:否;前端分页、完整统计与 CRUD 后的页码状态需要在同一工作树内顺序验证。 ## 问题背景 MediaMTX Control API 的列表接口默认每页返回 100 条: - `GET /v3/config/paths/list` - `GET /v3/paths/list` 当前管理页面未传递 `itemsPerPage` / `page`,也未读取后续 `pageCount`,因此超过 100 个静态 RTSP 设备后: - 页面只显示第一页; - “路径总数 / 在线 / 离线 / 待请求”统计不完整; - 搜索和状态筛选只能覆盖已加载的第一页; - 用户可能误判设备配置丢失。 该问题是数据完整性边界,不是 MediaMTX 核心设备数量限制。 ## 目标 - 分批读取配置路径和运行路径的全部 API 页面。 - 顶部统计、搜索和状态筛选覆盖全部已加载设备。 - 浏览器只渲染当前分页的数据,避免大列表产生过多 DOM 节点。 - 为桌面表格和窄屏卡片提供一致、轻量、可访问的分页操作。 - 保持原生 HTML/CSS/JavaScript、零前端运行时依赖。 ## 非目标 - 不修改 MediaMTX Control API、分页协议或 Go 后端接口。 - 不增加数据库、缓存服务、前端框架、构建器或虚拟滚动库。 - 不承诺无限规模;本工单面向数百至低数千条路径的局域网管理场景。 - 不改变认证、登录、路径 CRUD、预览协议、RTSP 凭据处理或媒体转发方式。 - 不增加批量编辑、批量删除、设备分组、排序或跨服务器聚合。 - 不提交本地摄像头 URL、账号密码、二进制、启动脚本或用户无关改动。 ## 已确认方案 ### 1. 分批读取全部数据 - 配置路径与运行路径均以 `itemsPerPage=100` 请求。 - 首先并行请求两个接口的第 0 页,读取 `pageCount` 与 `itemCount`。 - 后续页面采用有上限的并发请求,建议最多同时 4 个,避免瞬时请求过多。 - 合并页面前校验响应结构;按照路径名称去重并保持稳定排序。 - 运行状态按路径名称合并到配置路径;缺少运行记录时继续沿用“离线 / 待请求”等现有判定语义。 - 不使用任意大的 `itemsPerPage` 绕过分页,避免把限制延后到另一个隐藏阈值。 ### 2. 完整统计和筛选 - “路径总数 / 在线 / 离线 / 待请求”针对全部静态 RTSP 配置计算,不只针对当前页。 - 搜索和状态筛选先作用于完整数据集,再进行客户端分页。 - 搜索或状态筛选发生变化时回到第 1 页。 - 无匹配结果时显示现有空状态并保留清除筛选的恢复路径。 - 加载未完成时不显示误导性的部分统计;显示“正在加载设备 X / Y”进度或等效明确反馈。 ### 3. 客户端分页 - 默认每页 50 条。 - 支持每页 25、50、100 条,切换后回到第 1 页。 - 显示范围与总数,例如“1–50 / 共 236 条”。 - 提供“上一页”“下一页”;第一页和最后一页使用原生 `disabled` 状态。 - 显示当前页 / 总页数;不增加复杂页码跳转器。 - 删除当前页最后一条后,如果当前页超出新总页数,自动回退到最后一个有效页。 - 新增、编辑、删除或手动刷新后重新拉取完整数据;尽量保持当前筛选和有效页码。 - 分页只决定渲染范围,不改变顶部完整统计。 ### 4. 桌面与移动端 - 分页栏位于路径面板底部,桌面保持单行紧凑布局。 - 375px 下允许分组换行,但按钮、选择框保持至少 44px。 - 状态不只依靠颜色;当前范围、总数和禁用状态有文本及语义。 - 键盘 Tab 顺序符合视觉顺序,分页状态变化通过合适的 `aria-live` 区域通知。 - 不增加无限滚动:设备管理需要稳定页码、可预测位置和明确总数。 ### 5. 分页失败处理 - 任一批次失败时,不把部分数据伪装成完整结果。 - 错误信息指出配置列表或运行状态列表加载失败,并在开发诊断信息中保留失败页码;页面不得回显认证头或敏感数据。 - 提供“重试”入口,重试后重新获取完整快照。 - 401 继续走现有清理会话并返回登录页的逻辑;403 与网络错误继续使用现有错误语义。 - 避免失败页面的无限自动重试。 ## 影响范围 预计主要涉及: - `internal/api/admin/app.js`:分批请求、合并、完整统计、筛选后分页、CRUD 后页码处理。 - `internal/api/admin/index.html`:轻量分页栏与可访问状态文本。 - `internal/api/admin/app.css`:桌面和 375px 分页布局。 - 管理页前端测试与 `internal/api` 静态资源/路由相关测试。 如实现中发现需要修改 Go Control API,应停止并重新确认范围;本工单默认不改后端。 ## 风险与控制 - 风险:大量页面产生请求风暴。 - 控制:每页 100、并发上限 4、失败停止并显式重试。 - 风险:配置列表与运行列表在加载期间发生变化,形成短暂快照不一致。 - 控制:以配置列表为设备基线、按名称合并运行状态、刷新获得新快照;不声称强事务一致。 - 风险:搜索、删除和刷新后页码越界。 - 控制:所有数据集变化后统一计算并钳制当前页。 - 风险:部分页面失败导致统计错误。 - 控制:完整加载成功前不提交新状态;保留上一次成功快照或显示明确失败状态,不展示部分统计。 - 风险:路径名称或错误文本造成注入。 - 控制:继续使用安全 DOM API,不通过 `innerHTML` 拼接动态数据。 - 回退:回退本工单独立提交即可恢复最多显示 API 默认第一页的现状,无数据迁移。 ## 验收标准 - [ ] 使用至少 235 条虚构静态 RTSP 配置时,页面能加载全部设备而非前 100 条。 - [ ] 配置与运行状态接口按 100 条分批读取,后续请求并发有明确上限。 - [ ] 顶部总数及在线、离线、待请求统计覆盖全部设备。 - [ ] 搜索与状态筛选覆盖全部设备,再按筛选结果分页。 - [ ] 默认每页 50 条,可切换 25 / 50 / 100。 - [ ] 正确显示当前范围、总数、当前页和总页数。 - [ ] 第一页禁用“上一页”,最后一页禁用“下一页”。 - [ ] 搜索、筛选、切换每页数量、刷新及删除最后一条后的页码行为正确。 - [ ] 任一后续 API 页面失败时不显示不完整统计,并提供明确重试。 - [ ] 401、403、网络错误和退出登录行为没有回归。 - [ ] 桌面和 375px 无横向溢出,分页控件可通过键盘操作且触控目标至少 44px。 - [ ] 页面仍为原生 HTML/CSS/JS,不新增第三方运行时依赖。 - [ ] 受影响测试、Go 构建、Harness 检查和浏览器控制台检查通过。 - [ ] 提交只包含工单范围,不包含本地配置、摄像头凭据、二进制、启动脚本或无关改动。 - [ ] 提交推送并回写完整验证证据;工单保持开启,等待用户验收。 ## 验证方法 - 前端自动测试构造 0、1、50、51、100、101、235 条数据及多页运行状态。 - 验证批次合并、稳定排序、去重、并发上限和失败页处理。 - 验证完整统计、跨页搜索/筛选、每页数量、边界按钮和页码钳制。 - 验证新增、编辑、删除、刷新、401、403、网络失败和部分页失败。 - `node --check internal/api/admin/app.js` - 管理页相关前端测试。 - `go test ./internal/api -run '^TestAdmin' -count=1` - `go build ./...` - `python dev_scripts/harness.py check --strict` - `python dev_scripts/harness.py sync --check` - `git diff --check` - 浏览器 1440×900 与 375×812 实测;控制台 warning/error 检查。 ## 设计证据 用户已确认采用以下轻量方向: - 明确分页,不使用无限滚动; - 自动分批读取全部 API 页面; - 默认每页渲染 50 条; - 支持 25 / 50 / 100; - 完整统计与全量搜索/筛选; - 不新增后端、框架或数据库。 这是现有设备列表底部的局部扩展,不改变主要页面结构、权限模型或 CRUD 流程。采用原生分页控件、稳定位置、明确总数和 44px 操作目标,无需另做完整交互原型;实施时以桌面与 375px 浏览器截图作为审阅证据。 ## 文档影响 分页属于管理页面长期能力。实现稳定后评估是否需要更新 Product-Requirements-Overview、Architecture-and-Code-Map 与 Common-Changes;如长期事实变化,必须先更新 Gitea Wiki,再通过 Harness 单向同步 `docs/maintainer/`,不得直接编辑镜像。
Author
Owner

实施完成,待用户验收

最终差异

  • 配置路径与运行状态均按 itemsPerPage=100 完整读取,两个资源共用最大 4 个并发请求槽。
  • 任一 API 分页失败时抛出包含资源名称和失败页码的错误;完整快照成功前不替换现有页面数据。
  • 完整设备集合稳定去重、排序后再计算“路径总数 / 在线 / 离线 / 待请求”、搜索和状态筛选。
  • 客户端默认每页 50 条,可切换 25 / 50 / 100;提供范围、总数、当前页、上一页和下一页,数据变化后自动钳制有效页。
  • 桌面与 375px 均使用原生语义化按钮/选择框和 aria-live,无前端运行时依赖。
  • 新增独立 list-tools.js,浏览器直接加载,Node 内建测试可直接复用。

自动验证

  • node --check internal/api/admin/list-tools.js:通过
  • node --check internal/api/admin/app.js:通过
  • node --test tests/admin_list_tools_test.js:8/8 通过
    • 覆盖 0 / 1 / 50 / 51 / 100 / 101 / 235 边界
    • 覆盖 235 条三批读取、共享并发上限、稳定去重排序、完整统计/跨页筛选、失败页和页码钳制
  • go test ./internal/api -run '^TestAdmin' -count=1:通过
  • go build ./...:通过
  • python dev_scripts/harness.py check --strict:通过
  • python dev_scripts/harness.py sync --check:通过
  • git diff --check:通过

浏览器验收

使用隔离配置和 235 条虚构 RTSP 按需路径验证:

  • 1440×900:总数 235,默认 1–50 / 共 235 条、1 / 5 页,首尾为 device-001 / device-050。
  • 下一页:51–100;切换每页 100 后末页为 201–235、35 条,下一页正确禁用。
  • 跨 API 页搜索 device-235:顶部总数仍为 235,列表为 1 条且 1 / 1 页。
  • 375×812:无横向溢出;分页控件同排,按钮和选择框均为 44px;可见结果为 device-235。
  • 浏览器控制台 warning/error:0。
  • 为避免改动当前真实摄像头配置,未在真实运行实例执行删除;删除最后一条后的页码回退由纯函数边界测试覆盖,CRUD 后仍复用同一全量刷新与分页钳制流程。

长期文档

已先更新 Gitea Wiki并回读,再由 Harness 同步:

  • Product-Requirements-Overview:2c63353ad6789d3155b136d7f96b8064cc66c36e
  • Architecture-and-Code-Map:2b5014fc8d752f58255ebc4318acd8d798bc29be
  • Common-Changes:dad147556fe40e39f0d73131830f8b38ce8e9d55

提交与本地运行

  • 提交:9068712bb13599c9e2dc8899f07ecf53c00cdc96
  • 已推送:origin/main
  • 已实际运行 build_mediamtx.bat 重建本地 mediamtx.exe,并重新启动当前本地实例。
  • 已从 http://127.0.0.1:19997/admin/ 回读确认新分页 HTML 与 /admin/list-tools.js 已生效。
  • 提交未包含 mediamtx.yml、真实摄像头信息、二进制、启动/构建脚本、用户已有的 .github 删除或其他无关改动。

工单保持开启,等待用户明确验收。

## 实施完成,待用户验收 ### 最终差异 - 配置路径与运行状态均按 `itemsPerPage=100` 完整读取,两个资源共用最大 4 个并发请求槽。 - 任一 API 分页失败时抛出包含资源名称和失败页码的错误;完整快照成功前不替换现有页面数据。 - 完整设备集合稳定去重、排序后再计算“路径总数 / 在线 / 离线 / 待请求”、搜索和状态筛选。 - 客户端默认每页 50 条,可切换 25 / 50 / 100;提供范围、总数、当前页、上一页和下一页,数据变化后自动钳制有效页。 - 桌面与 375px 均使用原生语义化按钮/选择框和 `aria-live`,无前端运行时依赖。 - 新增独立 `list-tools.js`,浏览器直接加载,Node 内建测试可直接复用。 ### 自动验证 - `node --check internal/api/admin/list-tools.js`:通过 - `node --check internal/api/admin/app.js`:通过 - `node --test tests/admin_list_tools_test.js`:8/8 通过 - 覆盖 0 / 1 / 50 / 51 / 100 / 101 / 235 边界 - 覆盖 235 条三批读取、共享并发上限、稳定去重排序、完整统计/跨页筛选、失败页和页码钳制 - `go test ./internal/api -run '^TestAdmin' -count=1`:通过 - `go build ./...`:通过 - `python dev_scripts/harness.py check --strict`:通过 - `python dev_scripts/harness.py sync --check`:通过 - `git diff --check`:通过 ### 浏览器验收 使用隔离配置和 235 条虚构 RTSP 按需路径验证: - 1440×900:总数 235,默认 1–50 / 共 235 条、1 / 5 页,首尾为 device-001 / device-050。 - 下一页:51–100;切换每页 100 后末页为 201–235、35 条,下一页正确禁用。 - 跨 API 页搜索 `device-235`:顶部总数仍为 235,列表为 1 条且 1 / 1 页。 - 375×812:无横向溢出;分页控件同排,按钮和选择框均为 44px;可见结果为 device-235。 - 浏览器控制台 warning/error:0。 - 为避免改动当前真实摄像头配置,未在真实运行实例执行删除;删除最后一条后的页码回退由纯函数边界测试覆盖,CRUD 后仍复用同一全量刷新与分页钳制流程。 ### 长期文档 已先更新 Gitea Wiki并回读,再由 Harness 同步: - Product-Requirements-Overview:`2c63353ad6789d3155b136d7f96b8064cc66c36e` - Architecture-and-Code-Map:`2b5014fc8d752f58255ebc4318acd8d798bc29be` - Common-Changes:`dad147556fe40e39f0d73131830f8b38ce8e9d55` ### 提交与本地运行 - 提交:`9068712bb13599c9e2dc8899f07ecf53c00cdc96` - 已推送:`origin/main` - 已实际运行 `build_mediamtx.bat` 重建本地 `mediamtx.exe`,并重新启动当前本地实例。 - 已从 `http://127.0.0.1:19997/admin/` 回读确认新分页 HTML 与 `/admin/list-tools.js` 已生效。 - 提交未包含 `mediamtx.yml`、真实摄像头信息、二进制、启动/构建脚本、用户已有的 `.github` 删除或其他无关改动。 工单保持开启,等待用户明确验收。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/mediamtx#6