From 572da9a3656335f9e550c4a148d14b818a773185 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 2 Sep 2026 08:48:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E6=94=B6=E6=95=9B=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=A1=B5=E5=B9=B6=E5=88=86=E9=A1=B5=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_static_vendor.py | 20 ++++++-- tests/test_web_routes_nodb.py | 10 +++- web/routers/misc.py | 17 +++++-- web/static/admin.html | 2 +- web/static/dev.html | 8 ++++ web/static/js/admin.js | 90 +++++++++++++++++++---------------- web/static/js/changelog.js | 78 ++++++++++++++++++++++-------- 7 files changed, 154 insertions(+), 71 deletions(-) diff --git a/tests/test_static_vendor.py b/tests/test_static_vendor.py index 595b469..13bf1e4 100644 --- a/tests/test_static_vendor.py +++ b/tests/test_static_vendor.py @@ -72,7 +72,7 @@ class StaticVendorTests(unittest.TestCase): admin_js = (JS_DIR / "admin.js").read_text(encoding="utf-8") self.assertIn('id="node-enrollment-modal" class="modal"', html) - self.assertIn("生成 Windows Node 注册码", html) + self.assertIn("生成专业软件节点注册码", html) self.assertIn('"/v1/admin/software-node-enrollments"', admin_js) self.assertNotIn('capabilities: ["origin.plot@v2"]', admin_js) self.assertIn("节点能力由客户端自动发现并上报", html) @@ -103,11 +103,11 @@ class StaticVendorTests(unittest.TestCase): self.assertIn('id="ops-summary" class="ops-summary"', admin_js) self.assertIn(".ops-summary {", html) labels = ( - "运行容量", + "容器状态", "任务状态", "活跃用户", "Token 与缓存", - "Windows Node", + "专业软件节点", "存储", "成本", "工具健康", @@ -129,6 +129,20 @@ class StaticVendorTests(unittest.TestCase): self.assertIn('apiGet("/v1/admin/storage/users?page=0&page_size=1")', admin_js) self.assertIn('target: "s-windows-node"', admin_js) self.assertIn('target: "s-toolfail"', admin_js) + self.assertNotIn('"s-sandbox-capacity"', admin_js) + self.assertNotIn("function renderSandboxCapacity", admin_js) + self.assertIn('["s-sandbox-packages", "容器依赖"]', admin_js) + self.assertIn('sandboxCapacityData = await apiGet("/v1/admin/sandbox/capacity")', admin_js) + self.assertIn("containerStatusHTML(capacity)", admin_js) + + def test_changelog_loads_incremental_pages(self) -> None: + source = (JS_DIR / "changelog.js").read_text(encoding="utf-8") + + self.assertIn("const PAGE_SIZE = 5", source) + self.assertIn("offset=${_nextOffset}", source) + self.assertIn("d.has_more", source) + self.assertIn("加载更多", source) + self.assertNotIn("/v1/changelog?limit=50", source) def test_dev_html_uses_local_markdown_vendor_assets(self) -> None: html = DEV_HTML.read_text(encoding="utf-8") diff --git a/tests/test_web_routes_nodb.py b/tests/test_web_routes_nodb.py index 2a4ad85..6be0709 100644 --- a/tests/test_web_routes_nodb.py +++ b/tests/test_web_routes_nodb.py @@ -73,11 +73,17 @@ class PublicEndpointTests(unittest.TestCase): self.assertIn("brand", body) def test_changelog_public(self): - r = _client.get("/v1/changelog?limit=2") + r = _client.get("/v1/changelog?limit=2&offset=0") self.assertEqual(r.status_code, 200) - self.assertIn("entries", r.json()) + first = r.json() + self.assertEqual(len(first["entries"]), 2) + self.assertGreaterEqual(first["total"], 2) + self.assertEqual(first["next_offset"], 2) + second = _client.get("/v1/changelog?limit=2&offset=2").json() + self.assertNotEqual(first["entries"][0]["version"], second["entries"][0]["version"]) # limit 越界 clamp,不 500 self.assertEqual(_client.get("/v1/changelog?limit=9999").status_code, 200) + self.assertEqual(_client.get("/v1/changelog?offset=-1").status_code, 200) def test_root_redirects_to_dev_spa(self): r = _client.get("/", follow_redirects=False) diff --git a/web/routers/misc.py b/web/routers/misc.py index 2a5b9bb..176ee6e 100644 --- a/web/routers/misc.py +++ b/web/routers/misc.py @@ -53,7 +53,7 @@ def register_misc_routes(app, *, require_user) -> None: "brand": BRAND} @app.get("/v1/changelog", tags=["misc"]) - def changelog(limit: int = 20): + def changelog(limit: int = 20, offset: int = 0): """用户版更新日志(仓库根 CHANGELOG.md 解析,新在前)。 公开端点(无鉴权):内容本就是写给用户看的,登录页脚也展示版本号。 @@ -61,15 +61,26 @@ def register_misc_routes(app, *, require_user) -> None: 前端:点左栏底部版本号弹层展示,配 localStorage 红点提示新版本。 """ limit = max(1, min(limit, 100)) + offset = max(0, offset) try: mtime = _CHANGELOG_PATH.stat().st_mtime except OSError: - return {"current": __version__, "entries": []} + return {"current": __version__, "entries": [], "total": 0, + "has_more": False, "next_offset": offset} if _changelog_cache["mtime"] != mtime: _changelog_cache["entries"] = _parse_changelog( _CHANGELOG_PATH.read_text(encoding="utf-8")) _changelog_cache["mtime"] = mtime - return {"current": __version__, "entries": _changelog_cache["entries"][:limit]} + entries = _changelog_cache["entries"] + page = entries[offset:offset + limit] + next_offset = offset + len(page) + return { + "current": __version__, + "entries": page, + "total": len(entries), + "has_more": next_offset < len(entries), + "next_offset": next_offset, + } @app.get("/WW_verify_{token}.txt", include_in_schema=False) def wecom_domain_verify(token: str): diff --git a/web/static/admin.html b/web/static/admin.html index de393fd..14d837b 100644 --- a/web/static/admin.html +++ b/web/static/admin.html @@ -425,7 +425,7 @@