From 8033500e50c1ba6a2c6733e8ae063bfd671f28ad Mon Sep 17 00:00:00 2001 From: rookiestar28 <151893693+rookiestar28@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:19:12 +0800 Subject: [PATCH] feat(r128): complete OpenClaw naming unification phase 3 with legacy telemetry, canonical UI selectors, and e2e alignment --- api/routes.py | 28 +- services/access_control.py | 36 +- services/metrics.py | 2 + services/sidecar/auth.py | 33 +- services/trace.py | 15 +- services/webhook_auth.py | 49 +- tests/e2e/specs/parameter_lab.spec.js | 12 +- tests/e2e/specs/r107_live_backend.spec.js | 4 +- tests/e2e/specs/settings.spec.js | 4 +- tests/e2e/specs/sidebar.spec.js | 6 +- tests/e2e/test-harness.html | 1 + tests/e2e/utils/helpers.js | 6 +- web/ErrorBoundary.js | 2 +- web/error_boundary.css | 3 + web/global_error_handler.js | 2 +- web/openclaw.css | 930 ++++++++++++++++++++++ web/openclaw_tabs.js | 27 +- web/openclaw_ui.js | 50 +- web/openclaw_utils.js | 18 +- web/tabs/approvals_tab.js | 56 +- web/tabs/explorer_tab.js | 36 +- web/tabs/job_monitor_tab.js | 17 +- web/tabs/library_tab.js | 88 +- web/tabs/packs_tab.js | 26 +- web/tabs/parameter_lab_tab.js | 110 +-- web/tabs/planner_tab.js | 58 +- web/tabs/refiner_tab.js | 64 +- web/tabs/settings_tab.js | 146 ++-- web/tabs/variants_tab.js | 42 +- 29 files changed, 1463 insertions(+), 408 deletions(-) diff --git a/api/routes.py b/api/routes.py index bef0e5d..b9d1a4b 100644 --- a/api/routes.py +++ b/api/routes.py @@ -514,15 +514,35 @@ def register_dual_route(server, method: str, path: str, handler) -> None: f"[OpenClaw] Warning: Skipping route {method} {path} because handler is missing (None)." ) return + # Phase 3 Deprecation wrapper for legacy paths + actual_handler = handler + if path.startswith("/moltbot"): + from functools import wraps + + @wraps(handler) + async def _deprecated_handler(request: web.Request) -> web.Response: + try: + # Assuming `metrics` is available in scope (from module level imports) + if metrics: + metrics.inc("legacy_api_hits") + except Exception: + pass + print( + f"[OpenClaw] DEPRECATION WARNING: Legacy route accessed: {request.path}. Please migrate to /openclaw/* equivalents." + ) + return await handler(request) + + actual_handler = _deprecated_handler + # 1. Standard ComfyUI registration if method == "GET": - server.routes.get(path)(handler) + server.routes.get(path)(actual_handler) elif method == "POST": - server.routes.post(path)(handler) + server.routes.post(path)(actual_handler) elif method == "PUT": - server.routes.put(path)(handler) + server.routes.put(path)(actual_handler) elif method == "DELETE": - server.routes.delete(path)(handler) + server.routes.delete(path)(actual_handler) # 2. Hardened direct registration if hasattr(server, "app") and hasattr(server.app, "router"): diff --git a/services/access_control.py b/services/access_control.py index b4fc078..a71b32c 100644 --- a/services/access_control.py +++ b/services/access_control.py @@ -160,13 +160,35 @@ def resolve_token_info(request) -> Optional[TokenInfo]: 2. Check Environment Variables (Static) """ # Extract token from headers - client_token = ( - request.headers.get("X-OpenClaw-Admin-Token") - or request.headers.get("X-Moltbot-Admin-Token") - or request.headers.get("X-OpenClaw-Obs-Token") - or request.headers.get("X-Moltbot-Obs-Token") - or "" - ) + client_token = "" + if request.headers.get("X-OpenClaw-Admin-Token"): + client_token = request.headers.get("X-OpenClaw-Admin-Token") + elif request.headers.get("X-Moltbot-Admin-Token"): + client_token = request.headers.get("X-Moltbot-Admin-Token") + try: + from .metrics import metrics + + if metrics: + metrics.inc("legacy_api_hits") + except ImportError: + pass + logger.warning( + "DEPRECATION WARNING: Legacy header X-Moltbot-Admin-Token used. Please migrate to X-OpenClaw-Admin-Token." + ) + elif request.headers.get("X-OpenClaw-Obs-Token"): + client_token = request.headers.get("X-OpenClaw-Obs-Token") + elif request.headers.get("X-Moltbot-Obs-Token"): + client_token = request.headers.get("X-Moltbot-Obs-Token") + try: + from .metrics import metrics + + if metrics: + metrics.inc("legacy_api_hits") + except ImportError: + pass + logger.warning( + "DEPRECATION WARNING: Legacy header X-Moltbot-Obs-Token used. Please migrate to X-OpenClaw-Obs-Token." + ) # 1. Registry Check if client_token: diff --git a/services/metrics.py b/services/metrics.py index 5c74d07..eda2b3f 100644 --- a/services/metrics.py +++ b/services/metrics.py @@ -27,6 +27,7 @@ class Metrics: "webhook_requests_deduped": 0, "webhook_requests_executed": 0, "webhook_requests_validated": 0, + "legacy_api_hits": 0, # R33: Budget denial metrics "budget_denied_total": 0, "budget_denied_global_concurrency": 0, @@ -68,6 +69,7 @@ class Metrics: "errors_captured": counters.get("errors", 0), # Log processing is not currently tracked; keep as 0 for now. "logs_processed": 0, + "legacy_api_hits": counters.get("legacy_api_hits", 0), } def reset(self) -> None: diff --git a/services/sidecar/auth.py b/services/sidecar/auth.py index a0e71d4..24842df 100644 --- a/services/sidecar/auth.py +++ b/services/sidecar/auth.py @@ -153,12 +153,33 @@ def validate_device_token( return False, "Bridge not enabled", None # Extract headers - device_id = request.headers.get(HEADER_DEVICE_ID, "") or request.headers.get( - LEGACY_HEADER_DEVICE_ID, "" - ) - device_token = request.headers.get(HEADER_DEVICE_TOKEN, "") or request.headers.get( - LEGACY_HEADER_DEVICE_TOKEN, "" - ) + device_id = request.headers.get(HEADER_DEVICE_ID) + if not device_id and request.headers.get(LEGACY_HEADER_DEVICE_ID): + device_id = request.headers.get(LEGACY_HEADER_DEVICE_ID) + try: + from ..metrics import metrics + + if metrics: + metrics.inc("legacy_api_hits") + except ImportError: + pass + logger.warning( + f"DEPRECATION WARNING: Legacy header {LEGACY_HEADER_DEVICE_ID} used. Please migrate to {HEADER_DEVICE_ID}." + ) + + device_token = request.headers.get(HEADER_DEVICE_TOKEN) + if not device_token and request.headers.get(LEGACY_HEADER_DEVICE_TOKEN): + device_token = request.headers.get(LEGACY_HEADER_DEVICE_TOKEN) + try: + from ..metrics import metrics + + if metrics: + metrics.inc("legacy_api_hits") + except ImportError: + pass + logger.warning( + f"DEPRECATION WARNING: Legacy header {LEGACY_HEADER_DEVICE_TOKEN} used. Please migrate to {HEADER_DEVICE_TOKEN}." + ) if not device_id: return False, "Missing device ID", None diff --git a/services/trace.py b/services/trace.py index b230634..7f0964d 100644 --- a/services/trace.py +++ b/services/trace.py @@ -75,7 +75,20 @@ def get_effective_trace_id(headers: dict, body_data: dict) -> str: 3. Body: traceId (camelCase, for JS clients) 4. Generated """ - header_trace = headers.get(TRACE_HEADER) or headers.get(LEGACY_TRACE_HEADER) + try: + from .metrics import metrics + except ImportError: + metrics = None + + header_trace = headers.get(TRACE_HEADER) + if not header_trace and headers.get(LEGACY_TRACE_HEADER): + header_trace = headers.get(LEGACY_TRACE_HEADER) + if metrics: + metrics.inc("legacy_api_hits") + logger.warning( + f"DEPRECATION WARNING: Legacy header used: {LEGACY_TRACE_HEADER}. Please migrate to {TRACE_HEADER}." + ) + body_trace = body_data.get("trace_id") or body_data.get("traceId") # Priority: Header > Body > Generated return get_or_create_trace_id(header_trace or body_trace) diff --git a/services/webhook_auth.py b/services/webhook_auth.py index 48408ce..869a208 100644 --- a/services/webhook_auth.py +++ b/services/webhook_auth.py @@ -145,9 +145,19 @@ def verify_hmac(request: RequestLike, raw_body: bytes) -> Tuple[bool, str]: if not secret: return False, "hmac_not_configured" - sig_header = request.headers.get("X-OpenClaw-Signature", "") or request.headers.get( - "X-Moltbot-Signature", "" - ) + sig_header = request.headers.get("X-OpenClaw-Signature", "") + if not sig_header and request.headers.get("X-Moltbot-Signature"): + sig_header = request.headers.get("X-Moltbot-Signature", "") + try: + from .metrics import metrics + + if metrics: + metrics.inc("legacy_api_hits") + except ImportError: + pass + logger.warning( + "DEPRECATION WARNING: Legacy header X-Moltbot-Signature is used. Please migrate to X-OpenClaw-Signature." + ) if not sig_header: return False, "missing_signature_header" @@ -168,12 +178,33 @@ def verify_hmac(request: RequestLike, raw_body: bytes) -> Tuple[bool, str]: return False, "invalid_signature" # Replay Protection (S2.1) - timestamp = request.headers.get("X-OpenClaw-Timestamp") or request.headers.get( - "X-Moltbot-Timestamp" - ) - nonce = request.headers.get("X-OpenClaw-Nonce") or request.headers.get( - "X-Moltbot-Nonce" - ) + timestamp = request.headers.get("X-OpenClaw-Timestamp") + if not timestamp and request.headers.get("X-Moltbot-Timestamp"): + timestamp = request.headers.get("X-Moltbot-Timestamp") + try: + from .metrics import metrics + + if metrics: + metrics.inc("legacy_api_hits") + except ImportError: + pass + logger.warning( + "DEPRECATION WARNING: Legacy header X-Moltbot-Timestamp is used. Please migrate to X-OpenClaw-Timestamp." + ) + + nonce = request.headers.get("X-OpenClaw-Nonce") + if not nonce and request.headers.get("X-Moltbot-Nonce"): + nonce = request.headers.get("X-Moltbot-Nonce") + try: + from .metrics import metrics + + if metrics: + metrics.inc("legacy_api_hits") + except ImportError: + pass + logger.warning( + "DEPRECATION WARNING: Legacy header X-Moltbot-Nonce is used. Please migrate to X-OpenClaw-Nonce." + ) # Enforced if headers present OR if strictly required configuration should_enforce = timestamp or nonce or should_require_replay_protection() diff --git a/tests/e2e/specs/parameter_lab.spec.js b/tests/e2e/specs/parameter_lab.spec.js index 8c4445a..f1cea64 100644 --- a/tests/e2e/specs/parameter_lab.spec.js +++ b/tests/e2e/specs/parameter_lab.spec.js @@ -43,7 +43,7 @@ test.describe('Parameter Lab - Dynamic Dimensions', () => { test('can select node, widget, and add values via dropdown', async ({ page }) => { // Add Dimension await page.click('#lab-add-dim'); - await expect(page.locator('.moltbot-lab-dim-row.dynamic')).toBeVisible(); + await expect(page.locator('.openclaw-lab-dim-row.dynamic')).toBeVisible(); // Select Node (KSampler id=10) await page.selectOption('.dim-node-select', { value: '10' }); @@ -59,15 +59,15 @@ test.describe('Parameter Lab - Dynamic Dimensions', () => { await page.selectOption('.dim-candidate-select', { value: 'ddim' }); // Verify chip added - await expect(page.locator('.moltbot-chip >> text=ddim')).toBeVisible(); + await expect(page.locator('.openclaw-chip >> text=ddim')).toBeVisible(); // Select another "uni_pc" await page.selectOption('.dim-candidate-select', { value: 'uni_pc' }); - await expect(page.locator('.moltbot-chip >> text=uni_pc')).toBeVisible(); + await expect(page.locator('.openclaw-chip >> text=uni_pc')).toBeVisible(); // Verify remove chip - await page.click('.moltbot-chip:has-text("ddim") .chip-rm'); - await expect(page.locator('.moltbot-chip >> text=ddim')).not.toBeVisible(); + await page.click('.openclaw-chip:has-text("ddim") .chip-rm'); + await expect(page.locator('.openclaw-chip >> text=ddim')).not.toBeVisible(); }); test('can add custom manual values', async ({ page }) => { @@ -84,7 +84,7 @@ test.describe('Parameter Lab - Dynamic Dimensions', () => { await page.press('.dim-manual-input', 'Enter'); // Verify chip - await expect(page.locator('.moltbot-chip >> text=9999')).toBeVisible(); + await expect(page.locator('.openclaw-chip >> text=9999')).toBeVisible(); }); test('generates correct plan payload', async ({ page }) => { diff --git a/tests/e2e/specs/r107_live_backend.spec.js b/tests/e2e/specs/r107_live_backend.spec.js index c3338f6..1589673 100644 --- a/tests/e2e/specs/r107_live_backend.spec.js +++ b/tests/e2e/specs/r107_live_backend.spec.js @@ -89,12 +89,12 @@ test.describe('R107 Live Backend Parity', () => { await page.getByText('Add').click(); // Assert Job Row Appears - const jobRow = page.locator('.moltbot-job-row').first(); + const jobRow = page.locator('.openclaw-job-row').first(); await expect(jobRow).toBeVisible(); await expect(jobRow).toContainText(jobId.substring(0, 16)); // Wait for status to become completed (polling) - await expect(page.locator('.moltbot-kv-val.ok')).toHaveText('completed', { timeout: 10000 }); + await expect(page.locator('.openclaw-kv-val.ok')).toHaveText('completed', { timeout: 10000 }); // Assert Image Output await expect(page.locator('img[src*="test_img.png"]')).toBeVisible(); diff --git a/tests/e2e/specs/settings.spec.js b/tests/e2e/specs/settings.spec.js index f4bacf7..53ca246 100644 --- a/tests/e2e/specs/settings.spec.js +++ b/tests/e2e/specs/settings.spec.js @@ -93,7 +93,7 @@ test.describe('Settings Tab Stability', () => { await savePromise; // Expect success message - await expect(page.locator('.moltbot-status.ok')).toContainText('Saved!'); - await expect(page.locator('.moltbot-status.ok')).toContainText('Applied immediately'); + await expect(page.locator('.openclaw-status.ok')).toContainText('Saved!'); + await expect(page.locator('.openclaw-status.ok')).toContainText('Applied immediately'); }); }); diff --git a/tests/e2e/specs/sidebar.spec.js b/tests/e2e/specs/sidebar.spec.js index dcb2716..de6c919 100644 --- a/tests/e2e/specs/sidebar.spec.js +++ b/tests/e2e/specs/sidebar.spec.js @@ -9,15 +9,15 @@ test.describe('OpenClaw Sidebar', () => { }); test('renders header + tabs', async ({ page }) => { - await expect(page.locator('.moltbot-title')).toHaveText('OpenClaw'); - await expect(page.locator('.moltbot-repo-link')).toContainText('View on GitHub'); + await expect(page.locator('.openclaw-title')).toHaveText('OpenClaw'); + await expect(page.locator('.openclaw-repo-link')).toContainText('View on GitHub'); }); test('switching tabs does not lose content', async ({ page }) => { // Click a few tabs and verify active pane is non-empty for (const t of ['Settings', 'Jobs', 'Planner', 'Variants', 'Refiner', 'Library', 'Approvals']) { await clickTab(page, t); - const active = page.locator('.moltbot-tab-pane.active'); + const active = page.locator('.openclaw-tab-pane.active'); await expect(active).toBeVisible(); await expect(active).not.toBeEmpty(); } diff --git a/tests/e2e/test-harness.html b/tests/e2e/test-harness.html index e86af29..7c51f48 100644 --- a/tests/e2e/test-harness.html +++ b/tests/e2e/test-harness.html @@ -75,6 +75,7 @@ window.__moltbotTestReady = true; document.getElementById('test-status').textContent = '✓ Ready for Testing'; document.getElementById('test-status').className = 'ready'; + window.dispatchEvent(new CustomEvent('openclaw-ready')); window.dispatchEvent(new CustomEvent('moltbot-ready')); } catch (e) { console.error('[Test Harness] Failed to load OpenClaw UI:', e); diff --git a/tests/e2e/utils/helpers.js b/tests/e2e/utils/helpers.js index 3a8c45e..2fb320a 100644 --- a/tests/e2e/utils/helpers.js +++ b/tests/e2e/utils/helpers.js @@ -54,11 +54,11 @@ export async function waitForMoltbotReady(page) { } // Basic sanity: header + tab bar exists - await expect(page.locator('.moltbot-header')).toBeVisible(); - await expect(page.locator('.moltbot-tabs')).toBeVisible(); + await expect(page.locator('.openclaw-header')).toBeVisible(); + await expect(page.locator('.openclaw-tabs')).toBeVisible(); } export async function clickTab(page, title) { - const tab = page.locator('.moltbot-tab', { hasText: title }); + const tab = page.locator('.openclaw-tab', { hasText: title }); await tab.click(); } diff --git a/web/ErrorBoundary.js b/web/ErrorBoundary.js index 730cb97..1ef323c 100644 --- a/web/ErrorBoundary.js +++ b/web/ErrorBoundary.js @@ -30,7 +30,7 @@ export class ErrorBoundary { } const box = document.createElement("div"); - box.className = "moltbot-error-boundary"; + box.className = "openclaw-error-boundary moltbot-error-boundary"; const h3 = document.createElement("h3"); h3.textContent = `Error in ${this.componentName}`; diff --git a/web/error_boundary.css b/web/error_boundary.css index fb07a5f..eb47d20 100644 --- a/web/error_boundary.css +++ b/web/error_boundary.css @@ -1,5 +1,6 @@ /* R6: Error Boundary Styles */ +.openclaw-error-boundary, .moltbot-error-boundary { padding: 1rem; border: 1px solid #ff4444; @@ -10,11 +11,13 @@ font-family: sans-serif; } +.openclaw-error-boundary h3, .moltbot-error-boundary h3 { margin-top: 0; color: #ff8888; } +.openclaw-error-boundary code, .moltbot-error-boundary code { display: block; background: #000; diff --git a/web/global_error_handler.js b/web/global_error_handler.js index f6d9319..5348d2d 100644 --- a/web/global_error_handler.js +++ b/web/global_error_handler.js @@ -9,7 +9,7 @@ export function installGlobalErrorHandlers() { if (installed) return; window.addEventListener("error", (event) => { - // Filter for moltbot-related errors if possible, or just log generic + // Filter for openclaw-related moltbot-related errors if possible, or just log generic // For MVP, we simply log to console with a specific prefix for easier debugging console.error("[OpenClaw Global Error]", event.error); }); diff --git a/web/openclaw.css b/web/openclaw.css index 3cc913e..3a6791f 100644 --- a/web/openclaw.css +++ b/web/openclaw.css @@ -1,5 +1,935 @@ /* Moltbot Design System */ +:root { + /* Spacing Scale */ + --openclaw-space-xs: 4px; + --openclaw-space-sm: 8px; + --openclaw-space-md: 12px; + --openclaw-space-lg: 16px; + --openclaw-space-xl: 24px; + + /* Typography */ + --openclaw-font-xs: 11px; + --openclaw-font-sm: 12px; + --openclaw-font-md: 13px; + --openclaw-font-lg: 14px; + --openclaw-font-mono: 'Consolas', 'Monaco', 'Courier New', monospace; + + /* Colors (inheriting from ComfyUI variable names where possible, fallbacks provided) */ + --openclaw-color-bg: var(--comfy-menu-bg, #222); + --openclaw-color-bg-light: var(--comfy-input-bg, #333); + --openclaw-color-fg: var(--fg-color, #eee); + --openclaw-color-fg-muted: #aaa; + --openclaw-color-border: var(--border-color, #444); + --openclaw-color-primary: var(--primary-color, #2a2); + --openclaw-color-danger: #d44; + --openclaw-color-warning: #ea0; + --openclaw-color-success: #2a2; +} + +/* Layout Primitives */ + +.openclaw-sidebar-container { + display: flex; + flex-direction: column; + height: 100%; + min-width: 560px; /* Keep sidebar readable: header tabs + Parameter Lab controls */ + min-height: 0; /* allow children to shrink for overflow handling */ + background: var(--openclaw-color-bg); + color: var(--openclaw-color-fg); + font-family: Arial, sans-serif; + font-size: var(--openclaw-font-md); +} + +/* CRITICAL: Keep this rule + !important; SplitterPanel inline sizing can override inner container min-width. */ +.side-bar-panel:has(.openclaw-sidebar-container) { + min-width: 560px !important; +} + +.openclaw-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--openclaw-space-md); + padding: var(--openclaw-space-md) var(--openclaw-space-lg); + border-bottom: 1px solid var(--openclaw-color-border); + background: rgba(0,0,0,0.15); +} + +.openclaw-status-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: #666; + flex: 0 0 auto; +} +.openclaw-status-dot.ok { background: #2a2; } +.openclaw-status-dot.warn { background: #ea0; } +.openclaw-status-dot.err { background: #c44; } + +.openclaw-title { + font-size: var(--openclaw-font-lg); + font-weight: 700; + flex: 1 1 auto; + min-width: 0; +} + +.openclaw-badges { + display: flex; + align-items: center; + justify-content: flex-end; + gap: var(--openclaw-space-sm); + flex: 0 0 auto; +} + +.openclaw-version { + font-size: var(--openclaw-font-sm); + color: var(--openclaw-color-fg-muted); +} + +.openclaw-repo-link { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + border: 1px solid var(--openclaw-color-border); + border-radius: 999px; + background: rgba(255,255,255,0.04); + color: var(--openclaw-color-fg); + text-decoration: none; + font-size: var(--openclaw-font-sm); + white-space: nowrap; +} +.openclaw-repo-link:hover { background: rgba(255,255,255,0.08); } + +.openclaw-banner { + padding: var(--openclaw-space-sm) var(--openclaw-space-lg); + border-bottom: 1px solid var(--openclaw-color-border); + font-size: var(--openclaw-font-sm); + display: flex; + align-items: center; + gap: var(--openclaw-space-md); + justify-content: space-between; +} +.openclaw-banner > span { flex: 1; } + +.openclaw-banner-warning { background: rgba(255, 170, 0, 0.10); color: #ffd27a; } +.openclaw-banner-info { background: rgba(0, 120, 255, 0.10); color: #a8d1ff; } +.openclaw-banner-success { background: rgba(42, 170, 42, 0.15); color: #bfffc0; } +.openclaw-banner-error { background: rgba(200, 50, 50, 0.15); color: #ffb4b4; } + +.openclaw-banner-action { + background: rgba(255,255,255,0.1); + border: 1px solid rgba(255,255,255,0.2); + color: inherit; + padding: 2px 8px; + border-radius: 4px; + cursor: pointer; + font-size: var(--openclaw-font-xs); + text-transform: uppercase; + font-weight: bold; +} +.openclaw-banner-action:hover { background: rgba(255,255,255,0.2); } + +.openclaw-banner-close { + background: none; + border: none; + color: inherit; + opacity: 0.7; + cursor: pointer; + font-size: 16px; + line-height: 1; + padding: 0 4px; +} +.openclaw-banner-close:hover { opacity: 1; } + +.openclaw-tabs { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); /* 4 tabs per row */ + grid-auto-flow: row; /* left-to-right, then top-to-bottom */ + gap: 6px; + padding: 8px var(--openclaw-space-lg); + border-bottom: 1px solid var(--openclaw-color-border); + background: rgba(0,0,0,0.10); +} + +.openclaw-tab { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border-radius: 6px; + border: 1px solid transparent; + cursor: pointer; + user-select: none; + color: var(--openclaw-color-fg-muted); + background: rgba(255,255,255,0.02); + font-size: var(--openclaw-font-sm); + line-height: 1; + width: 100%; + min-width: 0; + justify-content: center; +} + +.openclaw-tab:hover { + color: var(--openclaw-color-fg); + border-color: rgba(255,255,255,0.08); + background: rgba(255,255,255,0.04); +} + +.openclaw-tab.active { + color: var(--openclaw-color-fg); + border-color: rgba(42, 170, 42, 0.5); + background: rgba(42, 170, 42, 0.15); +} + +.openclaw-tab-icon { + font-size: 13px; + opacity: 0.9; +} + +.openclaw-tab-label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.openclaw-content { + flex: 1 1 auto; + min-height: 0; + overflow: hidden; +} + +.openclaw-tab-pane { + display: none; + height: 100%; + min-height: 0; +} +.openclaw-tab-pane.active { + display: block; +} + +.openclaw-panel { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + color: var(--openclaw-color-fg); + font-size: var(--openclaw-font-md); + background: var(--openclaw-color-bg); +} + +.openclaw-scroll-area { + flex: 1; + overflow-y: auto; + padding: var(--openclaw-space-lg); /* More breathing room */ + display: flex; + flex-direction: column; + gap: var(--openclaw-space-lg); +} + +.openclaw-card { + background: rgba(0, 0, 0, 0.2); + border: 1px solid var(--openclaw-color-border); + border-radius: 4px; + padding: var(--openclaw-space-md); + display: flex; + flex-direction: column; + gap: var(--openclaw-space-sm); +} + +.openclaw-section-header { + font-size: var(--openclaw-font-sm); + text-transform: uppercase; + color: var(--openclaw-color-fg-muted); + letter-spacing: 0.05em; + margin-bottom: var(--openclaw-space-xs); + font-weight: bold; + border-bottom: 1px solid var(--openclaw-color-border); + padding-bottom: var(--openclaw-space-xs); +} + +/* Grids & Splits */ +.openclaw-grid-2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--openclaw-space-md); +} + +.openclaw-split-v { + display: flex; + flex-direction: column; + gap: var(--openclaw-space-md); +} + +.openclaw-split-h { + display: flex; + flex-direction: row; + gap: var(--openclaw-space-md); + align-items: center; +} + +/* Form Elements */ +.openclaw-input-group { + display: flex; + flex-direction: column; + gap: var(--openclaw-space-xs); +} + +.openclaw-section { + background: rgba(0,0,0,0.18); + border: 1px solid var(--openclaw-color-border); + border-radius: 6px; + padding: var(--openclaw-space-lg); + display: flex; + flex-direction: column; + gap: var(--openclaw-space-md); +} + +.openclaw-section > h4 { + margin: 0; + font-size: var(--openclaw-font-lg); +} + +.openclaw-form-row { + display: flex; + flex-direction: column; + gap: var(--openclaw-space-xs); +} + +.openclaw-btn-row { + display: flex; + flex-direction: row; + gap: var(--openclaw-space-sm); + flex-wrap: wrap; +} + +.openclaw-note { + padding: var(--openclaw-space-sm) var(--openclaw-space-md); + border: 1px solid var(--openclaw-color-border); + border-radius: 4px; + background: rgba(255,255,255,0.03); + color: var(--openclaw-color-fg-muted); + font-size: var(--openclaw-font-sm); + line-height: 1.35; +} + +.openclaw-status { + font-size: var(--openclaw-font-sm); + color: var(--openclaw-color-fg-muted); + min-height: 16px; +} +.openclaw-status.ok { color: #bfffc0; } +.openclaw-status.error { color: #ffb4b4; } + +.openclaw-kv-row { + display: flex; + justify-content: space-between; + gap: var(--openclaw-space-md); +} +.openclaw-kv-key { color: var(--openclaw-color-fg-muted); } +.openclaw-kv-val { color: var(--openclaw-color-fg); } +.openclaw-kv-val.ok { color: #bfffc0; } +.openclaw-kv-val.error { color: #ffb4b4; } + +.openclaw-log-viewer { + white-space: pre-wrap; + font-family: var(--openclaw-font-mono); + font-size: var(--openclaw-font-sm); + background: rgba(0,0,0,0.25); + border: 1px solid var(--openclaw-color-border); + border-radius: 4px; + padding: var(--openclaw-space-md); + max-height: 260px; + overflow: auto; +} + +.openclaw-checkbox { + width: 16px; + height: 16px; + flex: 0 0 auto; +} + +.openclaw-help-btn { + width: 22px; + height: 22px; + border-radius: 999px; + border: 1px solid var(--openclaw-color-border); + background: rgba(255,255,255,0.04); + color: var(--openclaw-color-fg); + cursor: pointer; + font-size: var(--openclaw-font-sm); + line-height: 1; + flex: 0 0 auto; +} +.openclaw-help-btn:hover { + background: rgba(255,255,255,0.08); +} + +.openclaw-label { + font-size: var(--openclaw-font-sm); + color: var(--openclaw-color-fg-muted); +} + +.openclaw-input, .openclaw-select, .openclaw-textarea { + background: var(--openclaw-color-bg-light); + border: 1px solid var(--openclaw-color-border); + color: var(--openclaw-color-fg); + padding: 6px 8px; + border-radius: 2px; + font-size: var(--openclaw-font-md); + width: 100%; + box-sizing: border-box; +} + +.openclaw-input:focus, .openclaw-select:focus, .openclaw-textarea:focus { + border-color: var(--openclaw-color-primary); + outline: none; +} + +.openclaw-textarea { + font-family: var(--openclaw-font-mono); + resize: vertical; + min-height: 60px; +} + +.openclaw-textarea-sm { min-height: 80px; } +.openclaw-textarea-md { min-height: 150px; } +.openclaw-textarea-lg { min-height: 250px; } +.openclaw-textarea-xl { min-height: 400px; } + +/* Buttons */ +.openclaw-btn { + background: var(--openclaw-color-bg-light); + border: 1px solid var(--openclaw-color-border); + color: var(--openclaw-color-fg); + padding: 6px 12px; + cursor: pointer; + font-size: var(--openclaw-font-md); + border-radius: 2px; + text-align: center; + transition: background 0.1s; +} + +.openclaw-btn:hover:not(:disabled) { + background: #444; +} + +.openclaw-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.openclaw-btn-primary { + background: #2a2; /* Green-ish */ + color: #fff; + border-color: #181; +} +.openclaw-btn-primary:hover:not(:disabled) { background: #3b3; } + +.openclaw-btn-danger { + background: #a33; + color: #fff; + border-color: #822; +} +.openclaw-btn-danger:hover:not(:disabled) { background: #c44; } + +.openclaw-btn-sm { + padding: 2px 6px; + font-size: var(--openclaw-font-sm); +} + +.openclaw-btn-success { + background: rgba(42, 170, 42, 0.2); + border-color: rgba(42, 170, 42, 0.6); + color: #dfffe0; +} + +/* Compatibility aliases used by existing tabs */ +.openclaw-btn.primary { + background: rgba(42, 170, 42, 0.2); + border-color: rgba(42, 170, 42, 0.6); + color: #dfffe0; +} +.openclaw-btn.primary:hover:not(:disabled) { + background: rgba(42, 170, 42, 0.3); +} +.openclaw-btn.secondary { + background: rgba(255, 255, 255, 0.04); + border-color: var(--openclaw-color-border); + color: var(--openclaw-color-fg); +} + +.openclaw-btn.has-icon { + display: inline-flex; + align-items: center; + gap: 6px; +} + +.openclaw-btn-icon { + width: 26px; + height: 26px; + border: 1px solid var(--openclaw-color-border); + background: rgba(255, 255, 255, 0.04); + color: var(--openclaw-color-fg); + border-radius: 4px; + cursor: pointer; + font-size: var(--openclaw-font-sm); + line-height: 1; +} +.openclaw-btn-icon:hover { + background: rgba(255, 255, 255, 0.1); +} + +.openclaw-separator { + width: 1px; + height: 24px; + background: var(--openclaw-color-border); + opacity: 0.7; +} + +.openclaw-form-group { + display: flex; + flex-direction: column; + gap: 6px; +} + +.openclaw-form-group > label { + font-size: var(--openclaw-font-sm); + color: var(--openclaw-color-fg-muted); +} + +.openclaw-form-group > input, +.openclaw-form-group > select, +.openclaw-form-group > textarea { + background: var(--openclaw-color-bg-light); + border: 1px solid var(--openclaw-color-border); + color: var(--openclaw-color-fg); + padding: 6px 8px; + border-radius: 4px; + font-size: var(--openclaw-font-md); +} +.openclaw-form-group > input:focus, +.openclaw-form-group > select:focus, +.openclaw-form-group > textarea:focus { + outline: none; + border-color: rgba(42, 170, 42, 0.7); +} + +.openclaw-hint, +.openclaw-loading, +.openclaw-error { + padding: 10px 12px; + border-radius: 6px; + font-size: var(--openclaw-font-sm); + border: 1px solid var(--openclaw-color-border); + background: rgba(255, 255, 255, 0.03); + color: var(--openclaw-color-fg-muted); +} + +.openclaw-loading { + color: #d2e4ff; + border-color: rgba(90, 150, 255, 0.45); + background: rgba(90, 150, 255, 0.12); +} + +.openclaw-error { + color: #ffb4b4; + border-color: rgba(200, 60, 60, 0.6); + background: rgba(200, 60, 60, 0.18); +} + +/* Status Indicators */ +.openclaw-badge { + padding: 2px 6px; + border-radius: 4px; + font-size: var(--openclaw-font-xs); + font-weight: bold; + text-transform: uppercase; +} +.openclaw-badge.pending { background: #aa0; color: #000; } +.openclaw-badge.approved { background: #2a2; color: #fff; } +.openclaw-badge.rejected { background: #c00; color: #fff; } +.openclaw-badge.expired { background: #666; color: #ccc; } + +/* Error / Empty States */ +.openclaw-empty-state { + padding: var(--openclaw-space-xl); + text-align: center; + color: var(--openclaw-color-fg-muted); + border: 1px dashed var(--openclaw-color-border); + border-radius: 4px; + background: rgba(255, 255, 255, 0.02); +} + +.openclaw-error-box { + padding: var(--openclaw-space-md); + background: rgba(200, 50, 50, 0.1); + border: 1px solid #a33; + border-radius: 4px; + color: #eaa; + display: flex; + flex-direction: column; + gap: var(--openclaw-space-sm); +} + +.openclaw-error-title { + font-weight: bold; + display: flex; + align-items: center; + gap: var(--openclaw-space-sm); +} + +.openclaw-error-hint { + font-size: var(--openclaw-font-sm); + color: #ccc; + background: rgba(0,0,0,0.3); + padding: 4px; + border-radius: 2px; +} + +/* Loading Spinner (Simple) */ +.openclaw-spinner { + width: 20px; + height: 20px; + border: 2px solid rgba(255,255,255,0.3); + border-radius: 50%; + border-top-color: #fff; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Modals */ +.openclaw-modal-overlay { + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0,0,0,0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} + +.openclaw-modal { + background: var(--openclaw-color-bg); + border: 1px solid var(--openclaw-color-border); + box-shadow: 0 10px 25px rgba(0,0,0,0.5); + border-radius: 6px; + min-width: 400px; + max-width: 90vw; + max-height: 90vh; + display: flex; + flex-direction: column; +} + +.openclaw-modal-header { + padding: var(--openclaw-space-md); + border-bottom: 1px solid var(--openclaw-color-border); + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + font-size: var(--openclaw-font-lg); +} + +.openclaw-modal-body { + padding: var(--openclaw-space-lg); + overflow-y: auto; + flex: 1; +} + +.openclaw-modal-footer { + padding: var(--openclaw-space-md); + border-top: 1px solid var(--openclaw-color-border); + display: flex; + justify-content: flex-end; + gap: var(--openclaw-space-md); + background: rgba(0,0,0,0.1); +} + +/* Parameter Lab */ +.openclaw-lab-container { + display: flex; + flex-direction: column; + gap: var(--openclaw-space-md); + height: 100%; + min-height: 0; + padding: var(--openclaw-space-md); + box-sizing: border-box; + overflow: hidden; +} + +.openclaw-lab-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + flex-wrap: wrap; + gap: var(--openclaw-space-md); + border: 1px solid var(--openclaw-color-border); + background: rgba(255, 255, 255, 0.03); + border-radius: 8px; + padding: var(--openclaw-space-md); +} + +.openclaw-lab-title-wrap { + flex: 1 1 100%; + min-width: 0; +} + +.openclaw-lab-title-wrap > h3 { + margin: 0; + font-size: 20px; + line-height: 1.1; + word-break: normal; + overflow-wrap: normal; +} + +.openclaw-lab-title-wrap > p { + margin: 4px 0 0; + color: var(--openclaw-color-fg-muted); + font-size: var(--openclaw-font-sm); + line-height: 1.35; + max-width: 60ch; + word-break: normal; + overflow-wrap: normal; +} + +.openclaw-lab-actions { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: var(--openclaw-space-sm); + width: 100%; + margin: 0 auto; + align-items: stretch; +} + +.openclaw-lab-action-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + min-width: 0; + width: 100%; + text-align: center; + overflow: hidden; +} + +.openclaw-lab-actions .openclaw-btn.has-icon.openclaw-lab-action-btn { + justify-content: center; +} + +.openclaw-lab-action-icon { + flex: 0 0 auto; + line-height: 1; +} + +.openclaw-lab-action-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.openclaw-lab-action-btn.active { + border-color: rgba(42, 170, 42, 0.65); + background: rgba(42, 170, 42, 0.2); + color: #dfffe0; +} + +.openclaw-lab-action-btn:focus { + outline: none; +} + +.openclaw-lab-action-btn:focus-visible { + border-color: rgba(42, 170, 42, 0.75); + box-shadow: 0 0 0 1px rgba(42, 170, 42, 0.35) inset; +} + +.openclaw-lab-actions .openclaw-separator { + display: none; +} + +.openclaw-lab-main { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: var(--openclaw-space-md); + min-height: 0; + overflow-y: auto; + padding-right: 2px; +} + +.openclaw-lab-card { + border: 1px solid var(--openclaw-color-border); + background: rgba(0, 0, 0, 0.16); + border-radius: 8px; + padding: var(--openclaw-space-md); + display: flex; + flex-direction: column; + gap: var(--openclaw-space-md); +} + +.openclaw-lab-card-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--openclaw-space-md); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + padding-bottom: var(--openclaw-space-sm); +} + +.openclaw-lab-card-head > h4 { + margin: 0; + font-size: 15px; +} + +.openclaw-lab-meta { + color: var(--openclaw-color-fg-muted); + font-size: var(--openclaw-font-sm); +} + +.openclaw-lab-config, +.openclaw-lab-results { + display: flex; + flex-direction: column; + gap: var(--openclaw-space-sm); +} + +.openclaw-lab-dim-row { + display: grid; + grid-template-columns: 84px 110px 1fr 32px; + gap: var(--openclaw-space-sm); + align-items: end; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.02); + border-radius: 6px; + padding: var(--openclaw-space-sm); +} + +.openclaw-lab-dim-row .openclaw-form-group.wide { + min-width: 0; +} + +.openclaw-lab-plan-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--openclaw-space-sm); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 6px; + background: rgba(255, 255, 255, 0.02); + padding: var(--openclaw-space-sm); +} + +.openclaw-lab-plan-header > h4 { + margin: 0; + font-size: var(--openclaw-font-md); +} + +.openclaw-lab-run-list { + display: flex; + flex-direction: column; + gap: 6px; +} + +.openclaw-lab-run-item { + display: grid; + grid-template-columns: 72px 1fr auto 32px; + align-items: center; + gap: var(--openclaw-space-sm); + padding: 8px; + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 6px; + background: rgba(255, 255, 255, 0.02); +} + +.run-idx { + font-family: var(--openclaw-font-mono); + color: var(--openclaw-color-fg-muted); + font-size: var(--openclaw-font-sm); +} + +.run-params { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-family: var(--openclaw-font-mono); + font-size: var(--openclaw-font-sm); +} + +.run-status { + font-size: var(--openclaw-font-sm); + border-radius: 999px; + padding: 2px 8px; + border: 1px solid rgba(255, 255, 255, 0.15); + color: var(--openclaw-color-fg-muted); + background: rgba(255, 255, 255, 0.05); + white-space: nowrap; +} + +.run-status.pending, +.run-status.queued { + color: #ffe38f; + border-color: rgba(230, 180, 50, 0.55); + background: rgba(230, 180, 50, 0.14); +} + +.run-status.running { + color: #a8d1ff; + border-color: rgba(90, 150, 255, 0.55); + background: rgba(90, 150, 255, 0.16); +} + +.run-status.success, +.run-status.completed { + color: #bfffc0; + border-color: rgba(42, 170, 42, 0.55); + background: rgba(42, 170, 42, 0.16); +} + +.run-status.error, +.run-status.failed { + color: #ffb4b4; + border-color: rgba(200, 60, 60, 0.6); + background: rgba(200, 60, 60, 0.16); +} + +@media (max-width: 820px) { + .openclaw-lab-header { + flex-direction: column; + align-items: stretch; + } + + .openclaw-lab-actions { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .openclaw-lab-dim-row { + grid-template-columns: 1fr; + } + + .openclaw-lab-run-item { + grid-template-columns: 1fr; + align-items: start; + } +} + +@media (max-width: 720px) { + .openclaw-lab-actions { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + + +/*==================================== + * LEGACY COMPATIBILITY + *====================================*/ + +/* Moltbot Design System */ + :root { /* Spacing Scale */ --moltbot-space-xs: 4px; diff --git a/web/openclaw_tabs.js b/web/openclaw_tabs.js index 79c84b8..0402339 100644 --- a/web/openclaw_tabs.js +++ b/web/openclaw_tabs.js @@ -49,12 +49,12 @@ export class TabManager { this.tabs.forEach(tab => { const btn = document.createElement("div"); - btn.className = "moltbot-tab"; + btn.className = "openclaw-tab moltbot-tab"; if (tab.icon) { const icon = document.createElement("i"); - icon.className = `moltbot-tab-icon ${tab.icon}`; + icon.className = `openclaw-tab-icon moltbot-tab-icon ${tab.icon}`; const label = document.createElement("span"); - label.className = "moltbot-tab-label"; + label.className = "openclaw-tab-label moltbot-tab-label"; label.textContent = tab.title; btn.appendChild(icon); btn.appendChild(label); @@ -67,10 +67,10 @@ export class TabManager { this.tabsEl.appendChild(btn); // Create container for tab content if not exists - if (!this.contentEl.querySelector(`#moltbot-tab-${tab.id}`)) { + if (!this.contentEl.querySelector(`#openclaw-tab-${tab.id}`)) { const pane = document.createElement("div"); - pane.id = `moltbot-tab-${tab.id}`; - pane.className = "moltbot-tab-pane"; + pane.id = `openclaw-tab-${tab.id}`; + pane.className = "openclaw-tab-pane moltbot-tab-pane"; this.contentEl.appendChild(pane); } }); @@ -78,7 +78,7 @@ export class TabManager { activateTab(id) { this.activeTabId = id; - localStorage.setItem("moltbot-active-tab", id); + localStorage.setItem("openclaw-active-tab", id); // Update Tab Buttons Array.from(this.tabsEl.children).forEach((btn, idx) => { @@ -89,13 +89,17 @@ export class TabManager { // Update Panes Array.from(this.contentEl.children).forEach(pane => { - if (pane.id === `moltbot-tab-${id}`) pane.classList.add("active"); + if (pane.id === `openclaw-tab-${id}` || pane.id === `moltbot-tab-${id}`) { + pane.classList.add("active"); + } else pane.classList.remove("active"); }); // Lazy Render const tab = this.tabs.find(t => t.id === id); - const pane = this.contentEl.querySelector(`#moltbot-tab-${id}`); + const pane = + this.contentEl.querySelector(`#openclaw-tab-${id}`) || + this.contentEl.querySelector(`#moltbot-tab-${id}`); const shouldRender = tab && pane && (!tab.loaded || !pane.hasChildNodes()); if (shouldRender) { const boundary = new ErrorBoundary(`Tab: ${tab.title}`); @@ -110,7 +114,10 @@ export class TabManager { } _restoreActiveTab() { - const saved = localStorage.getItem("moltbot-active-tab"); + // CRITICAL: keep legacy key fallback to avoid tab-state loss across migration. + const saved = + localStorage.getItem("openclaw-active-tab") || + localStorage.getItem("moltbot-active-tab"); const defaultTab = this.tabs.length > 0 ? this.tabs[0].id : null; this.activateTab(saved && this.tabs.find(t => t.id === saved) ? saved : defaultTab); } diff --git a/web/openclaw_ui.js b/web/openclaw_ui.js index 5d3ce37..3f9ce95 100644 --- a/web/openclaw_ui.js +++ b/web/openclaw_ui.js @@ -27,7 +27,7 @@ export class OpenClawUI { } _enforceSidebarMinWidth(container) { - // IMPORTANT: Keep this value aligned with CSS .moltbot-sidebar-container/.side-bar-panel min-width. + // IMPORTANT: Keep this value aligned with CSS .openclaw-sidebar-container/.moltbot-sidebar-container min-width. const minWidthPx = 560; const applyMinWidth = () => { @@ -62,10 +62,10 @@ export class OpenClawUI { toggleFloatingPanel() { if (!this.floating.panel) { const panel = document.createElement("div"); - panel.className = "moltbot-floating-panel"; + panel.className = "openclaw-floating-panel moltbot-floating-panel"; const close = document.createElement("button"); - close.className = "moltbot-floating-close"; + close.className = "openclaw-floating-close moltbot-floating-close"; close.textContent = "\u00D7"; close.title = "Close"; close.addEventListener("click", () => { @@ -73,7 +73,7 @@ export class OpenClawUI { }); const content = document.createElement("div"); - content.className = "moltbot-floating-content"; + content.className = "openclaw-floating-content moltbot-floating-content"; panel.appendChild(close); panel.appendChild(content); @@ -98,31 +98,31 @@ export class OpenClawUI { _render(container) { container.innerHTML = ""; - container.className = "moltbot-sidebar-container"; + container.className = "openclaw-sidebar-container moltbot-sidebar-container"; // 1. Header const header = document.createElement("div"); - header.className = "moltbot-header"; + header.className = "openclaw-header moltbot-header"; const statusDot = document.createElement("div"); - statusDot.className = "moltbot-status-dot ok"; + statusDot.className = "openclaw-status-dot moltbot-status-dot ok"; statusDot.title = "System Status"; this.statusDot = statusDot; const title = document.createElement("div"); - title.className = "moltbot-title"; + title.className = "openclaw-title moltbot-title"; title.textContent = "OpenClaw"; // F9: About badges (version fetched from /openclaw/health; legacy /moltbot/health) const badges = document.createElement("div"); - badges.className = "moltbot-badges"; + badges.className = "openclaw-badges moltbot-badges"; const versionSpan = document.createElement("span"); - versionSpan.className = "moltbot-version"; + versionSpan.className = "openclaw-version moltbot-version"; versionSpan.textContent = "v..."; const repoLink = document.createElement("a"); repoLink.href = "https://github.com/rookiestar28/ComfyUI-OpenClaw"; repoLink.target = "_blank"; - repoLink.className = "moltbot-repo-link"; + repoLink.className = "openclaw-repo-link moltbot-repo-link"; repoLink.title = "View on GitHub"; repoLink.textContent = "View on GitHub"; badges.appendChild(versionSpan); @@ -139,7 +139,7 @@ export class OpenClawUI { // F55: Control plane mode indicator badge const cpMode = data?.control_plane?.mode || data?.deployment_profile || "local"; const modeBadge = document.createElement("span"); - modeBadge.className = `moltbot-mode-badge moltbot-mode-${cpMode}`; + modeBadge.className = `openclaw-mode-badge moltbot-mode-badge openclaw-mode-${cpMode} moltbot-mode-${cpMode}`; modeBadge.textContent = cpMode.toUpperCase(); modeBadge.title = `Control plane: ${cpMode}`; // Style inline for immediate visibility @@ -183,13 +183,13 @@ export class OpenClawUI { // 2. Tab Bar const tabBar = document.createElement("div"); - tabBar.className = "moltbot-tabs"; + tabBar.className = "openclaw-tabs moltbot-tabs"; this.tabBar = tabBar; container.appendChild(tabBar); // 3. Content Area const contentArea = document.createElement("div"); - contentArea.className = "moltbot-content"; + contentArea.className = "openclaw-content moltbot-content"; this.contentArea = contentArea; container.appendChild(contentArea); @@ -239,7 +239,7 @@ export class OpenClawUI { // 1. Priority Check // If an error is currently shown, don't replace with info/warning unless it's a new error - const currentBanner = this.container.querySelector(".moltbot-banner"); + const currentBanner = this.container.querySelector('.openclaw-banner'); if (currentBanner) { const currentSeverity = currentBanner.dataset.severity; const isCurrentError = currentSeverity === "error"; @@ -264,11 +264,11 @@ export class OpenClawUI { if (!bannerEl) { bannerEl = document.createElement("div"); // Insert after header - const header = this.container.querySelector(".moltbot-header"); + const header = this.container.querySelector('.openclaw-header'); header.after(bannerEl); } - bannerEl.className = `moltbot-banner moltbot-banner-${severity}`; + bannerEl.className = `openclaw-banner moltbot-banner openclaw-banner-${severity} moltbot-banner-${severity}`; bannerEl.dataset.id = id; bannerEl.dataset.severity = severity; bannerEl.innerHTML = ""; // Clear content @@ -281,7 +281,7 @@ export class OpenClawUI { // Action Button if (action) { const btn = document.createElement("button"); - btn.className = "moltbot-banner-action"; + btn.className = "openclaw-banner-action moltbot-banner-action"; btn.textContent = action.label; btn.addEventListener("click", () => this.handleAction(action)); bannerEl.appendChild(btn); @@ -290,7 +290,7 @@ export class OpenClawUI { // Dismiss Button if (dismissible) { const close = document.createElement("button"); - close.className = "moltbot-banner-close"; + close.className = "openclaw-banner-close moltbot-banner-close"; close.textContent = "\u00D7"; close.addEventListener("click", () => { bannerEl.remove(); @@ -345,10 +345,10 @@ export class OpenClawUI { showConfirm({ title, message, fatal = false, onConfirm }) { // Create modal overlay const overlay = document.createElement("div"); - overlay.className = "moltbot-modal-overlay"; + overlay.className = "openclaw-modal-overlay moltbot-modal-overlay"; const modal = document.createElement("div"); - modal.className = `moltbot-modal ${fatal ? "fatal" : ""}`; + modal.className = `openclaw-modal moltbot-modal ${fatal ? "fatal" : ""}`; const h3 = document.createElement("h3"); h3.textContent = title || "Confirm Action"; @@ -357,15 +357,15 @@ export class OpenClawUI { p.textContent = message || "Are you sure?"; const buttons = document.createElement("div"); - buttons.className = "moltbot-modal-buttons"; + buttons.className = "openclaw-modal-buttons moltbot-modal-buttons"; const cancelBtn = document.createElement("button"); - cancelBtn.className = "moltbot-btn secondary"; + cancelBtn.className = "openclaw-btn moltbot-btn secondary"; cancelBtn.textContent = "Cancel"; cancelBtn.onclick = () => overlay.remove(); const confirmBtn = document.createElement("button"); - confirmBtn.className = `moltbot-btn ${fatal ? "danger" : "primary"}`; + confirmBtn.className = `openclaw-btn moltbot-btn ${fatal ? "danger" : "primary"}`; confirmBtn.textContent = "Confirm"; confirmBtn.onclick = () => { overlay.remove(); @@ -574,7 +574,7 @@ export class OpenClawActions { */ _showBlockedToast(actionName, reason) { const toast = document.createElement("div"); - toast.className = "moltbot-blocked-toast"; + toast.className = "openclaw-blocked-toast moltbot-blocked-toast"; toast.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 99999; background: #1e1e2e; border: 1px solid #f59e0b; diff --git a/web/openclaw_utils.js b/web/openclaw_utils.js index 901b35e..400295c 100644 --- a/web/openclaw_utils.js +++ b/web/openclaw_utils.js @@ -25,7 +25,7 @@ export function makeEl(tag, className = "", text = "") { */ export function showToast(message, variant = "info") { const toast = document.createElement("div"); - toast.className = `moltbot-toast moltbot-toast-${variant}`; + toast.className = `openclaw-toast moltbot-toast openclaw-toast-${variant} moltbot-toast-${variant}`; toast.textContent = message; toast.style.position = "fixed"; toast.style.right = "16px"; @@ -45,19 +45,19 @@ export function showToast(message, variant = "info") { /** * Display an error message within a container. - * Looks for an existing .moltbot-error-box, or creates one at the top. + * Looks for an existing .openclaw-error-box (legacy: .moltbot-error-box), or creates one at the top. */ export function showError(container, message) { - let errorBox = container.querySelector(".moltbot-error-box"); + let errorBox = container.querySelector('.openclaw-error-box'); if (!errorBox) { // Try to find one by ID pattern if specific class missing? No, stick to class. // If not found, inject at top of panel - const panel = container.querySelector(".moltbot-panel") || container; + const panel = container.querySelector('.openclaw-panel') || container; errorBox = document.createElement("div"); - errorBox.className = "moltbot-error-box"; + errorBox.className = "openclaw-error-box moltbot-error-box"; // Insert after panel header or at top - const header = panel.querySelector(".moltbot-section-header"); + const header = panel.querySelector('.openclaw-section-header'); if (header && header.nextSibling) { panel.insertBefore(errorBox, header.nextSibling); } else { @@ -76,7 +76,7 @@ export function showError(container, message) { * Clear error message in container. */ export function clearError(container) { - const errorBox = container.querySelector(".moltbot-error-box"); + const errorBox = container.querySelector('.openclaw-error-box'); if (errorBox) { errorBox.style.display = "none"; errorBox.textContent = ""; @@ -93,11 +93,11 @@ export async function copyToClipboard(text, btnElement) { // Show feedback on button const origText = btnElement.textContent; btnElement.textContent = "Copied!"; - btnElement.classList.add("moltbot-btn-success"); + btnElement.classList.add("openclaw-btn-success", "moltbot-btn-success"); setTimeout(() => { btnElement.textContent = origText; - btnElement.classList.remove("moltbot-btn-success"); + btnElement.classList.remove("openclaw-btn-success", "moltbot-btn-success"); }, 1500); } catch (err) { diff --git a/web/tabs/approvals_tab.js b/web/tabs/approvals_tab.js index b694a40..243ac5e 100644 --- a/web/tabs/approvals_tab.js +++ b/web/tabs/approvals_tab.js @@ -20,41 +20,41 @@ export const ApprovalsTab = { render(container) { // --- 1. Static Layout --- container.innerHTML = ` -