fix(ui): hide useless docker manager on desktop, auto-start hermes dashboard, polish layout

Four independent UI fixes the user spotted in one screenshot tour:

services.js — desktop docker manager
  ClawPanel is not a docker management tool. The "Docker 多实例管理"
  block on the OpenClaw services page only makes sense for users who
  deployed ClawPanel itself in Web mode (serve.js / dev-api) and want
  to orchestrate multiple OpenClaw containers from one panel.

  On desktop Tauri this block always degrades to either "未启用" with
  a connect ENOENT error (no docker daemon on the user box) or to a
  generic "unavailable" placeholder — pure visual noise. Skip the
  whole config-section in render() and bail out of loadDockerManager()
  when isTauriRuntime() is true. Web mode keeps the feature.

profiles.js — Hermes Profile manager could not load
  Profile API only exists on the Hermes Dashboard process at 9119,
  which the user has to start by hand. When it is offline, the page
  showed a raw "由于目标计算机积极拒绝, 无法连接 (10061)" error which
  is useless: users do not know they need to start a separate process.

  load() now does the same probe → auto-start dance that
  extensions.js / dashboard.js use for their 9119 links: probe first,
  call hermesDashboardStart() if not running, only then issue the
  /api/profiles request. If the start itself fails, we fall through to
  the original catch and humanize-error renders a real reason.

lazy-deps.js — "[object Object]" on load failure
  humanizeError() returns { message, hint, raw, action? }, not a
  string. The catch branch passed the object straight into
  escapeHtml(), so String(obj) coerced to "[object Object]". Switch
  to humanizeErrorText() which is exactly the (message + hint)
  one-liner string variant.

layout.css — header buttons crammed against the description
  Several pages (hermes profiles / lazy-deps / files / gateways /
  group-chat / kanban / oauth) put their header buttons inside
  <div class="config-actions"> nested in <div class="page-header">,
  but the existing flex layout rule only matched .page-actions. The
  buttons therefore stacked directly under .page-desc with zero
  visible gap. Add .config-actions to both the desktop flex selector
  and the @media (max-width:768px) column-stack selector so all
  these pages get the same title-left / actions-right layout.

## Verification
- npm run build (no warnings beyond the existing chunk size note)
- Playwright on /services in browser mode: docker section present;
  same page after mocking window.__TAURI_INTERNALS__: section gone
- Playwright on /lazy-deps: rendered content does not contain the
  string "[object Object]"
- Playwright dynamic-imports profiles / lazy-deps / files render():
  computed style on .page-header is display:flex, flex-direction:row,
  title and button share the same getBoundingClientRect().top, button
  pushed to the right edge (justify-content:space-between effective)
This commit is contained in:
晴天
2026-05-16 13:46:24 +08:00
parent 7d75486a53
commit 4b0d8e5042
4 changed files with 23 additions and 6 deletions

View File

@@ -10,7 +10,7 @@
import { t } from '../../../lib/i18n.js'
import { api } from '../../../lib/tauri-api.js'
import { toast } from '../../../components/toast.js'
import { humanizeError } from '../../../lib/humanize-error.js'
import { humanizeError, humanizeErrorText } from '../../../lib/humanize-error.js'
import { svgIcon } from '../lib/svg-icons.js'
// feature 分类配置(决定分组顺序 + 图标 + 文案)
@@ -72,7 +72,9 @@ async function loadAndRender(page) {
try {
featuresResp = await api.hermesLazyDepsFeatures()
} catch (e) {
content.innerHTML = `<div style="color:var(--error);padding:20px">${escapeHtml(humanizeError(e, t('hermesLazyDeps.loadFailed')))}</div>`
// humanizeError 返回 { message, hint, raw } 对象String(obj) 会变成 "[object Object]"。
// 这里用 humanizeErrorText 直接拿格式化后的字符串。
content.innerHTML = `<div style="color:var(--error);padding:20px">${escapeHtml(humanizeErrorText(e, t('hermesLazyDeps.loadFailed')))}</div>`
return
}

View File

@@ -114,6 +114,15 @@ export function render() {
loading = true
error = ''
draw()
// 9119 Dashboard 是独立进程profile/* API 只由它提供。
// 先 probe + 自动启动,避免用户看到「网络连接失败」这种无头错误。
// 启动失败也不在这里中断,下面 hermesDashboardApi 抛出的连接错误会由 humanizeError 显示。
try {
const probe = await api.hermesDashboardProbe()
if (!probe?.running) {
await api.hermesDashboardStart().catch(() => {})
}
} catch { /* probe 失败也继续尝试调用 */ }
try {
const resp = await api.hermesDashboardApi('GET', '/api/profiles')
const list = Array.isArray(resp) ? resp : (resp?.profiles || [])

View File

@@ -2,7 +2,7 @@
* 服务管理页面
* 服务启停 + 更新检测 + 配置备份管理
*/
import { api } from '../lib/tauri-api.js'
import { api, isTauriRuntime } from '../lib/tauri-api.js'
import { toast } from '../components/toast.js'
import { humanizeError } from '../lib/humanize-error.js'
import { showConfirm, showModal, showUpgradeModal } from '../components/modal.js'
@@ -34,11 +34,12 @@ export async function render() {
</div>
<div id="version-bar"><div class="stat-card loading-placeholder" style="height:80px;margin-bottom:var(--space-lg)"></div></div>
<div id="services-list"><div class="stat-card loading-placeholder" style="height:64px"></div></div>
${isTauriRuntime() ? '' : `
<div class="config-section" id="docker-manager-section">
<div class="config-section-title">${t('services.dockerManager')}</div>
<div class="form-hint" style="margin-bottom:var(--space-sm)">${t('services.dockerManagerHint')}</div>
<div id="docker-manager-bar"><div class="stat-card loading-placeholder" style="height:96px"></div></div>
</div>
</div>`}
<div class="config-section" id="config-editor-section" style="display:none">
<div class="config-section-title">${t('services.configEditor')}</div>
<div class="form-hint" style="margin-bottom:var(--space-sm)">${t('services.configEditorHint')}</div>
@@ -194,6 +195,9 @@ async function hasDockerManagerBackend() {
}
async function loadDockerManager(page) {
// Docker 多实例管理仅在 Web 部署模式serve.js / dev-api下有意义。
// 桌面 Tauri 用户不需要管理多个 OpenClaw 容器,整段 UI 已在 render() 里跳过渲染。
if (isTauriRuntime()) return
const bar = page.querySelector('#docker-manager-bar')
if (!bar) return
const backendReady = await hasDockerManagerBackend()

View File

@@ -539,7 +539,8 @@
margin-bottom: var(--space-xl);
}
.page-header:has(.page-actions) {
.page-header:has(.page-actions),
.page-header:has(.config-actions) {
display: flex;
align-items: flex-start;
justify-content: space-between;
@@ -849,7 +850,8 @@
.page-title {
font-size: var(--font-size-xl);
}
.page-header:has(.page-actions) {
.page-header:has(.page-actions),
.page-header:has(.config-actions) {
flex-direction: column;
gap: var(--space-sm);
}