Skip to content

refactor:优化Dashboard 审查页与设置页,并支持 AstrBot 插件内嵌页面#174

Closed
YumemiDream wants to merge 7 commits into
NickCharlie:mainfrom
YumemiDream:main
Closed

refactor:优化Dashboard 审查页与设置页,并支持 AstrBot 插件内嵌页面#174
YumemiDream wants to merge 7 commits into
NickCharlie:mainfrom
YumemiDream:main

Conversation

@YumemiDream

@YumemiDream YumemiDream commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

改动概览

主要优化 Self Learning Dashboard 的前端体验,并新增 AstrBot Plugin Pages 支持,使用户可以直接从 AstrBot WebUI 的插件详情页进入 Dashboard。

Dashboard 设置页重构

  • 为设置页面增加侧边栏分类导航,降低大量配置项堆叠带来的阅读压力。
  • 将右侧设置项从单列展示优化为响应式网格布局,更充分利用页面空间。
  • 将布尔类型配置项改为切换开关样式,交互更直观。
  • 优化长文本、列表、复杂配置项的布局宽度,减少页面纵向滚动。
  • 优化依赖安装区域的展示、图标对齐、镜像选择和滚动条样式。
  • 保留手动保存按钮和未保存状态提示,避免配置修改反馈不清晰。

Dashboard 体验优化

  • 简化顶部品牌区域,减少重复标题和过多说明文字占用空间。
  • 移除黑话列表中容易产生误解的“按出现次数排序”。
  • 保留更准确的样本数、审查信息和上下文展示。

AstrBot Plugin Pages 支持

  • 新增 pages/dashboard/index.html,支持在 AstrBot 插件详情页中打开 Dashboard。
  • 新增 .astrbot-plugin/i18n/zh-CN.json.astrbot-plugin/i18n/en-US.json,注册 Dashboard 页面标题。
  • 通过 AstrBot Plugin Page bridge 调用插件 API 获取独立 Dashboard 地址。
  • WebUIManager 中注册 dashboard_url Web API,用于返回当前 WebUI Dashboard 地址。
  • 使用 iframe 嵌入原有 Dashboard,因此后续修改原 Dashboard 时,AstrBot 内嵌页面也会同步生效。
  • 移除 Plugin Page 内额外标题栏和无效的“直接打开/刷新”按钮,避免与 AstrBot 自带页面标题重复。

回归测试

  • 增加 Plugin Page 入口相关断言,确认 bridge、iframe、dashboard_url 调用存在。
  • 增加 WebUIManager 单元测试,确认 dashboard_url API 会被注册。
  • 将 Plugin Page HTML 纳入外部 CDN 引用检查。
  • 增加设置页布局、开关控件、依赖安装区域等前端结构断言。

验证

已执行:

  • python -m py_compile tests\integration\test_webui_static_assets.py webui\manager.py tests\unit\test_webui_manager.py
  • Node 内联脚本语法检查:pages/dashboard/index.htmlweb_res/static/html/dashboard.html
  • git diff --check

Summary by Sourcery

Refine the Self Learning dashboard UI and settings layout while adding AstrBot Plugin Page support for opening the dashboard from AstrBot WebUI, with corresponding tests and Web API registration.

Enhancements:

  • Redesign the dashboard review queue into a categorized sidebar workspace and compact global header, and restructure the settings page with a sticky navigation sidebar, denser grid layout, and switch-style boolean controls.
  • Improve dashboard resilience and UX by guarding summary text access, adjusting jargon metrics labelling, and using safe localStorage access compatible with sandboxed plugin environments.
  • Expose a public WebUI base URL helper in WebUIManager and register a dashboard_url Web API for external consumers such as AstrBot Plugin Pages.

Tests:

  • Add integration tests to verify AstrBot Plugin Page dashboard embedding, new dashboard review/settings layouts, and updated jargon sorting behavior.
  • Add unit tests ensuring WebUIManager registers the dashboard_url API and computes the public Web UI base URL from configuration.

YumemiDream and others added 7 commits June 6, 2026 14:45
- Add a dedicated review workspace layout with sticky sidebar
  containing 6 category buttons: persona / style / jargon /
  persona-state / reviewed / batches
- Each button shows live count badges; counts update whenever
  persona, style, jargon, persona-state, backup or batch data
  is loaded
- Add setReviewTab(tab) / updateReviewNavCounts(counts) helpers
  and bind button clicks + page-revisit restore (state.reviews.tab)
- Add new integration test test_dashboard_review_queue_uses_sidebar_categories
  to lock in the new structure
Merge the Dashboard settings page refactor together with AstrBot Plugin Pages support.

Includes:
- categorized settings sidebar and denser right-pane controls
- improved dependency install controls and scrollbars
- compact Dashboard header and removal of misleading jargon occurrence sorting
- AstrBot Plugin Page entry for the Dashboard using bridge API + iframe
- WebUI dashboard_url bridge endpoint and regression tests
@sourcery-ai

sourcery-ai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors the Self Learning Dashboard review and settings pages with sidebar-driven layouts and boolean switch controls, and adds an AstrBot Plugin Page that embeds the dashboard via a new dashboard_url Web API in WebUIManager, covered by integration and unit tests.

Sequence diagram for AstrBot Plugin Page loading the dashboard via dashboard_url

sequenceDiagram
    actor AstrBotUser
    participant PluginPage as dashboard/index.html
    participant AstrBotPluginPage
    participant WebUIManager

    AstrBotUser->>PluginPage: Open plugin details page
    PluginPage->>AstrBotPluginPage: ready()
    AstrBotPluginPage-->>PluginPage: resolved
    PluginPage->>AstrBotPluginPage: apiGet('dashboard_url')
    AstrBotPluginPage->>WebUIManager: GET dashboard_url
    WebUIManager->>WebUIManager: _public_webui_base_url()
    WebUIManager-->>AstrBotPluginPage: { url: /static/html/dashboard.html }
    AstrBotPluginPage-->>PluginPage: payload with url
    PluginPage->>PluginPage: setDashboardUrl(payload.url)
    PluginPage->>DashboardFrame: load iframe src
    DashboardFrame-->>AstrBotUser: Render Self Learning Dashboard
Loading

File-Level Changes

Change Details Files
Reworked the Dashboard review page into a sidebar-based, tabbed workspace with clearer queues and metrics while simplifying the global header and jargon stats.
  • Replaced the top brand block with a compact .app-brand title and made the toolbar-only summary optional, guarding summaryText usage in JS.
  • Introduced review-workspace and review-sidebar layouts with sticky categorized navigation buttons wired via setReviewTab and updateReviewNavCounts to show different queue panels.
  • Split the previous flat review panels into data-review-panel sections for personas, styles, jargon, persona state/backups, reviewed history, and batches, each tied to corresponding nav buttons with badges.
  • Removed the misleading jargon "按出现次数" sort option and its implementation, renaming occurrences labels to "样本" and updating jargon stats and pagination to keep counts consistent.
web_res/static/html/dashboard.html
Refactored the Dashboard settings page into a two-column workspace with a scrollable category sidebar and denser, card-like config controls including toggle switches for booleans.
  • Added settings-workspace and settings-sidebar with a categorized settingsNav, summary text, custom scrollbar styling, and responsive behavior on small screens.
  • Changed the settings grid to a single-column list of groups while config-fields now form a responsive grid of card-style config-field elements with wide/list/longtext layout classes computed by configFieldLayoutClasses.
  • Replaced checkbox-based boolean controls with a switch-style UI rendered by renderConfigSwitch, including hidden checkbox inputs, switch-track/thumb visuals, bool-state labels, and syncConfigSwitchState wiring.
  • Introduced settings navigation metadata (SETTINGS_NAV_SECTIONS, configGroupIcon, settingsNavGroupMeta) plus state.config.activeGroup and setConfigGroup/applyConfigSearch logic so users can filter by config group and keep dependencyInstallPanel visibility in sync.
web_res/static/html/dashboard.html
Hardened dashboard state persistence and insights navigation, aligning with the new review/settings layouts and AstrBot embedding constraints.
  • Wrapped localStorage access in safeLocalStorageGet/Set and switched theme and pip mirror persistence to use these helpers to tolerate sandboxed environments.
  • Extended jumpToInsightTarget to derive a review tab via getReviewTabForInsightTarget and call setReviewTab so insight clicks land on the appropriate queue panel.
  • Ensured renderAll, renderDashboardSummary, renderBatchPanel, and renderJargonPanel update review nav counts via updateReviewNavCounts and re-apply the active review tab.
  • Made multiple DOM updates resilient by null-checking summaryText before writing, and updated loadDashboard to avoid errors when the new compact header omits this element in some contexts.
web_res/static/html/dashboard.html
Implemented an AstrBot Plugin Page that embeds the Dashboard via a bridge-exposed dashboard_url endpoint, with graceful fallback when the bridge or API is unavailable.
  • Added pages/dashboard/index.html that loads bridge-sdk.js, calls AstrBotPluginPage.ready().apiGet('dashboard_url') to get the dashboard URL, sets the dashboardFrame src, and displays a status overlay with error messaging; includes a fallbackDashboardUrl for when the bridge is missing.
  • Styled the plugin page with a full-page iframe, themable background, and a centered status-card for loading/error states, keeping the outer shell minimal to avoid duplicating AstrBot UI chrome.
  • Introduced .astrbot-plugin/i18n/zh-CN.json and en-US.json entries registering the "dashboard" page label used by AstrBot Plugin Pages.
  • Ensured the plugin page avoids extra toolbar/title/open/refresh controls so AstrBot’s own header remains the single source of navigation within the plugin detail view.
pages/dashboard/index.html
.astrbot-plugin/i18n/en-US.json
.astrbot-plugin/i18n/zh-CN.json
Added a WebUIManager API that exposes a public dashboard_url for AstrBot Plugin Pages and verified its registration and URL formation via unit tests.
  • Hooked _register_plugin_pages_api into WebUIManager.init, constructing a plugin-specific route name and calling a context.register_web_api hook when available.
  • Implemented _public_webui_base_url to derive an externally reachable http://host:port, handling 0.0.0.0/:: by consulting quart.request.host and normalizing IPv6 literals.
  • Defined an async dashboard_url handler that returns JSON (or a plain dict if jsonify is unavailable) with url and base_url fields pointing at /static/html/dashboard.html.
  • Added test_webui_manager_registers_astrbot_plugin_page_dashboard_url_api and test_webui_manager_public_webui_url_uses_configured_host to ensure the API is registered with GET and that URL generation respects the configured host and port.
webui/manager.py
tests/unit/test_webui_manager.py
Extended integration tests to cover AstrBot Plugin Page assets, dashboard layout refactors, and jargon/switch behavior.
  • Registered pages/dashboard/index.html in the static assets integration test to ensure it’s checked alongside other HTML resources.
  • Added test_astrbot_plugin_pages_dashboard_entry_exists to assert the plugin index.html uses the bridge SDK, calls apiGet('dashboard_url'), embeds an iframe with id=dashboardFrame, and omits deprecated toolbar/title/open/reload elements; also checks i18n files include the "dashboard" entry.
  • Introduced tests verifying the new review sidebar/categories, settings sidebar/nav, dense controls (grid-template-columns, switch classes, helper functions), compact header (.app-brand), and removal of the occurrences sort option while renaming labels to "样本".
  • Ensured regression coverage for the new helpers (setReviewTab, updateReviewNavCounts, configFieldLayoutClasses, renderConfigSwitch, syncConfigSwitchState) by checking their presence in dashboard.html via string assertions.
tests/integration/test_webui_static_assets.py
web_res/static/html/dashboard.html

Possibly linked issues

  • #0: PR implements AstrBot Plugin Page embedding of the dashboard from WebUI plugin details, exactly matching the feature request

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • The dashboard URL helpers (_public_webui_base_url and the AstrBot Plugin Page fallbackDashboardUrl) always use http://, which can cause mixed-content issues if AstrBot or the WebUI are served over HTTPS; consider deriving the scheme from window.location.protocol on the frontend and from config or request on the backend.
  • In applyConfigSearch, the dependency panel visibility relies on a hardcoded search string ('Dependency_Install_Guide 依赖安装 手动安装依赖 dependencies'), which can drift from the actual schema; consider deriving this label/search text from the config schema or a shared constant to avoid future mismatches.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The dashboard URL helpers (`_public_webui_base_url` and the AstrBot Plugin Page `fallbackDashboardUrl`) always use `http://`, which can cause mixed-content issues if AstrBot or the WebUI are served over HTTPS; consider deriving the scheme from `window.location.protocol` on the frontend and from config or request on the backend.
- In `applyConfigSearch`, the dependency panel visibility relies on a hardcoded search string (`'Dependency_Install_Guide 依赖安装 手动安装依赖 dependencies'`), which can drift from the actual schema; consider deriving this label/search text from the config schema or a shared constant to avoid future mismatches.

## Individual Comments

### Comment 1
<location path="pages/dashboard/index.html" line_range="136-139" />
<code_context>
+            const statusTextEl = document.getElementById('statusText');
+            const frameEl = document.getElementById('dashboardFrame');
+
+            function fallbackDashboardUrl() {
+                const host = window.location.hostname || '127.0.0.1';
+                const safeHost = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
+                return `http://${safeHost}:7833/static/html/dashboard.html`;
+            }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Consider deriving the fallback URL scheme from the current page to avoid mixed-content issues.

`fallbackDashboardUrl` always uses `http://`. When AstrBot runs over HTTPS, this will trigger mixed-content blocking and the iframe won’t load. Please derive the scheme from `window.location.protocol` (or the current origin) so the dashboard URL matches the page’s protocol.

For example:
```js
const scheme = window.location.protocol === 'https:' ? 'https' : 'http';
return `${scheme}://${safeHost}:7833/static/html/dashboard.html`;
```
</issue_to_address>

### Comment 2
<location path="webui/manager.py" line_range="57-70" />
<code_context>

     # 创建

+    def _public_webui_base_url(self) -> str:
+        host = str(getattr(self._config, "web_interface_host", "127.0.0.1") or "127.0.0.1")
+        port = int(getattr(self._config, "web_interface_port", 7833) or 7833)
+        if host in {"0.0.0.0", "::", "[::]"}:
+            try:
+                from quart import request
+
+                host_header = request.host.split(":", 1)[0]
+            except (ImportError, RuntimeError):
+                host_header = ""
+            host = host_header or "127.0.0.1"
+        if ":" in host and not host.startswith("["):
+            host = f"[{host}]"
+        return f"http://{host}:{port}"
+
+    def _register_plugin_pages_api(self) -> None:
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Deriving the WebUI base URL purely as http may cause mixed-content or reverse-proxy issues.

Building the URL as `http://{host}:{port}` will be wrong when AstrBot is served over HTTPS or behind a TLS-terminating proxy that rewrites scheme/port, causing mixed-content issues or broken links from the plugin page.

Where possible, derive scheme/host/port from the incoming request (e.g. `request.scheme` and `request.host`) or rely on the front-end’s origin instead of hardcoding `http`. This ensures the dashboard URL exposed via the plugin pages API matches the actual public endpoint.

```suggestion
    def _public_webui_base_url(self) -> str:
        """
        Derive the public WebUI base URL.

        Prefer the incoming request's scheme/host (including proxy headers) when available
        to avoid mixed-content and reverse-proxy issues. Fall back to configuration if
        there is no active request context.
        """
        # Try to derive from the current HTTP request (respecting common proxy headers)
        try:
            from quart import request  # type: ignore

            # Accessing request.* without a context raises RuntimeError; keep this in the try.
            scheme = request.headers.get("X-Forwarded-Proto", request.scheme)
            # X-Forwarded-Host may contain host[:port]; if absent, request.host already does.
            host = request.headers.get("X-Forwarded-Host", request.host)

            if host:
                # Normalise IPv6 literal hosts if needed
                if ":" in host and not host.startswith("["):
                    # host may already contain a port (e.g. "example.com:8443" or "[::1]:8443"),
                    # so we only wrap the bare host portion when it's an IPv6 literal.
                    host_part, *port_part = host.rsplit(":", 1)
                    if ":" in host_part and not host_part.startswith("["):
                        host_part = f"[{host_part}]"
                    host = ":".join([host_part] + port_part) if port_part else host_part
                return f"{scheme}://{host}"
        except (ImportError, RuntimeError):
            # No Quart available or no active request context; fall back to config
            pass

        # Fallback: derive from configured bind host/port, using http as a reasonable default.
        host = str(getattr(self._config, "web_interface_host", "127.0.0.1") or "127.0.0.1")
        port = int(getattr(self._config, "web_interface_port", 7833) or 7833)

        # When bound to all interfaces, pick a more specific host for generating URLs.
        if host in {"0.0.0.0", "::", "[::]"}:
            host = "127.0.0.1"

        # Normalise IPv6 literal host
        if ":" in host and not host.startswith("["):
            host = f"[{host}]"

        return f"http://{host}:{port}"
```
</issue_to_address>

### Comment 3
<location path="tests/integration/test_webui_static_assets.py" line_range="108-131" />
<code_context>
+    assert "updateReviewNavCounts" in text
+
+
+def test_dashboard_settings_uses_sidebar_categories():
+    text = (PLUGIN_ROOT / "web_res" / "static" / "html" / "dashboard.html").read_text(encoding="utf-8")
+
+    assert "settings-sidebar" in text
+    assert "settings-workspace" in text
+    assert "settingsSidebarSummary" in text
+    assert "settingsNav" in text
+    assert 'data-config-group="all"' in text
+    assert "dependencyInstallPanel" in text
+    assert "renderSettingsNav" in text
+    assert "setConfigGroup" in text
+    assert "configGroupIcon" in text
+    assert "SETTINGS_NAV_SECTIONS" in text
+    assert "常用入口" in text
+    assert "学习链路" in text
+    assert "人格与关系" in text
+    assert "数据与运行" in text
+    assert "settings-nav::-webkit-scrollbar" in text
+    assert "scrollbar-width: thin" in text
+    assert "Dependency_Install_Guide: 'extension'" in text
+    assert "deployed_code" not in text
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Avoid over-coupling dashboard settings tests to exact copy and raw substrings

These assertions validate the new sidebar structure and key labels, but relying on long exact strings (full Chinese labels, `settings-nav::-webkit-scrollbar`, exact CSS declarations) and negative substring checks (like ensuring `"deployed_code"` is absent) makes the test brittle to harmless copy or CSS changes. Prefer asserting on stable structural markers (ids, data attributes, function names), and loosen or narrow text/style checks so they confirm the intended layout/behavior without depending on exact CSS serialization or full label text.

```suggestion
    assert "updateReviewNavCounts" in text
+
+
+def test_dashboard_settings_uses_sidebar_categories():
+    text = (PLUGIN_ROOT / "web_res" / "static" / "html" / "dashboard.html").read_text(encoding="utf-8")
+
+    # Structural hooks for the settings sidebar and workspace layout
+    assert "settings-sidebar" in text
+    assert "settings-workspace" in text
+    assert "settingsSidebarSummary" in text
+    assert "settingsNav" in text
+
+    # Config grouping and navigation rendering hooks
+    assert 'data-config-group="all"' in text
+    assert "dependencyInstallPanel" in text
+    assert "renderSettingsNav" in text
+    assert "setConfigGroup" in text
+    assert "configGroupIcon" in text
+    assert "SETTINGS_NAV_SECTIONS" in text
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +136 to +139
function fallbackDashboardUrl() {
const host = window.location.hostname || '127.0.0.1';
const safeHost = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
return `http://${safeHost}:7833/static/html/dashboard.html`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Consider deriving the fallback URL scheme from the current page to avoid mixed-content issues.

fallbackDashboardUrl always uses http://. When AstrBot runs over HTTPS, this will trigger mixed-content blocking and the iframe won’t load. Please derive the scheme from window.location.protocol (or the current origin) so the dashboard URL matches the page’s protocol.

For example:

const scheme = window.location.protocol === 'https:' ? 'https' : 'http';
return `${scheme}://${safeHost}:7833/static/html/dashboard.html`;

Comment thread webui/manager.py
Comment on lines +57 to +70
def _public_webui_base_url(self) -> str:
host = str(getattr(self._config, "web_interface_host", "127.0.0.1") or "127.0.0.1")
port = int(getattr(self._config, "web_interface_port", 7833) or 7833)
if host in {"0.0.0.0", "::", "[::]"}:
try:
from quart import request

host_header = request.host.split(":", 1)[0]
except (ImportError, RuntimeError):
host_header = ""
host = host_header or "127.0.0.1"
if ":" in host and not host.startswith("["):
host = f"[{host}]"
return f"http://{host}:{port}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Deriving the WebUI base URL purely as http may cause mixed-content or reverse-proxy issues.

Building the URL as http://{host}:{port} will be wrong when AstrBot is served over HTTPS or behind a TLS-terminating proxy that rewrites scheme/port, causing mixed-content issues or broken links from the plugin page.

Where possible, derive scheme/host/port from the incoming request (e.g. request.scheme and request.host) or rely on the front-end’s origin instead of hardcoding http. This ensures the dashboard URL exposed via the plugin pages API matches the actual public endpoint.

Suggested change
def _public_webui_base_url(self) -> str:
host = str(getattr(self._config, "web_interface_host", "127.0.0.1") or "127.0.0.1")
port = int(getattr(self._config, "web_interface_port", 7833) or 7833)
if host in {"0.0.0.0", "::", "[::]"}:
try:
from quart import request
host_header = request.host.split(":", 1)[0]
except (ImportError, RuntimeError):
host_header = ""
host = host_header or "127.0.0.1"
if ":" in host and not host.startswith("["):
host = f"[{host}]"
return f"http://{host}:{port}"
def _public_webui_base_url(self) -> str:
"""
Derive the public WebUI base URL.
Prefer the incoming request's scheme/host (including proxy headers) when available
to avoid mixed-content and reverse-proxy issues. Fall back to configuration if
there is no active request context.
"""
# Try to derive from the current HTTP request (respecting common proxy headers)
try:
from quart import request # type: ignore
# Accessing request.* without a context raises RuntimeError; keep this in the try.
scheme = request.headers.get("X-Forwarded-Proto", request.scheme)
# X-Forwarded-Host may contain host[:port]; if absent, request.host already does.
host = request.headers.get("X-Forwarded-Host", request.host)
if host:
# Normalise IPv6 literal hosts if needed
if ":" in host and not host.startswith("["):
# host may already contain a port (e.g. "example.com:8443" or "[::1]:8443"),
# so we only wrap the bare host portion when it's an IPv6 literal.
host_part, *port_part = host.rsplit(":", 1)
if ":" in host_part and not host_part.startswith("["):
host_part = f"[{host_part}]"
host = ":".join([host_part] + port_part) if port_part else host_part
return f"{scheme}://{host}"
except (ImportError, RuntimeError):
# No Quart available or no active request context; fall back to config
pass
# Fallback: derive from configured bind host/port, using http as a reasonable default.
host = str(getattr(self._config, "web_interface_host", "127.0.0.1") or "127.0.0.1")
port = int(getattr(self._config, "web_interface_port", 7833) or 7833)
# When bound to all interfaces, pick a more specific host for generating URLs.
if host in {"0.0.0.0", "::", "[::]"}:
host = "127.0.0.1"
# Normalise IPv6 literal host
if ":" in host and not host.startswith("["):
host = f"[{host}]"
return f"http://{host}:{port}"

Comment on lines +108 to +131
assert "updateReviewNavCounts" in text


def test_dashboard_settings_uses_sidebar_categories():
text = (PLUGIN_ROOT / "web_res" / "static" / "html" / "dashboard.html").read_text(encoding="utf-8")

assert "settings-sidebar" in text
assert "settings-workspace" in text
assert "settingsSidebarSummary" in text
assert "settingsNav" in text
assert 'data-config-group="all"' in text
assert "dependencyInstallPanel" in text
assert "renderSettingsNav" in text
assert "setConfigGroup" in text
assert "configGroupIcon" in text
assert "SETTINGS_NAV_SECTIONS" in text
assert "常用入口" in text
assert "学习链路" in text
assert "人格与关系" in text
assert "数据与运行" in text
assert "settings-nav::-webkit-scrollbar" in text
assert "scrollbar-width: thin" in text
assert "Dependency_Install_Guide: 'extension'" in text
assert "deployed_code" not in text

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Avoid over-coupling dashboard settings tests to exact copy and raw substrings

These assertions validate the new sidebar structure and key labels, but relying on long exact strings (full Chinese labels, settings-nav::-webkit-scrollbar, exact CSS declarations) and negative substring checks (like ensuring "deployed_code" is absent) makes the test brittle to harmless copy or CSS changes. Prefer asserting on stable structural markers (ids, data attributes, function names), and loosen or narrow text/style checks so they confirm the intended layout/behavior without depending on exact CSS serialization or full label text.

Suggested change
assert "updateReviewNavCounts" in text
def test_dashboard_settings_uses_sidebar_categories():
text = (PLUGIN_ROOT / "web_res" / "static" / "html" / "dashboard.html").read_text(encoding="utf-8")
assert "settings-sidebar" in text
assert "settings-workspace" in text
assert "settingsSidebarSummary" in text
assert "settingsNav" in text
assert 'data-config-group="all"' in text
assert "dependencyInstallPanel" in text
assert "renderSettingsNav" in text
assert "setConfigGroup" in text
assert "configGroupIcon" in text
assert "SETTINGS_NAV_SECTIONS" in text
assert "常用入口" in text
assert "学习链路" in text
assert "人格与关系" in text
assert "数据与运行" in text
assert "settings-nav::-webkit-scrollbar" in text
assert "scrollbar-width: thin" in text
assert "Dependency_Install_Guide: 'extension'" in text
assert "deployed_code" not in text
assert "updateReviewNavCounts" in text
+
+
+def test_dashboard_settings_uses_sidebar_categories():
+ text = (PLUGIN_ROOT / "web_res" / "static" / "html" / "dashboard.html").read_text(encoding="utf-8")
+
+ # Structural hooks for the settings sidebar and workspace layout
+ assert "settings-sidebar" in text
+ assert "settings-workspace" in text
+ assert "settingsSidebarSummary" in text
+ assert "settingsNav" in text
+
+ # Config grouping and navigation rendering hooks
+ assert 'data-config-group="all"' in text
+ assert "dependencyInstallPanel" in text
+ assert "renderSettingsNav" in text
+ assert "setConfigGroup" in text
+ assert "configGroupIcon" in text
+ assert "SETTINGS_NAV_SECTIONS" in text

@YumemiDream YumemiDream closed this Jun 7, 2026
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.

1 participant