Skip to content

feat(extensions-portal): add Extensions dashboard page#673

Merged
Lightheartdevs merged 5 commits intoLight-Heart-Labs:resources/devfrom
yasinBursali:feat/extensions-portal-ui
Apr 2, 2026
Merged

feat(extensions-portal): add Extensions dashboard page#673
Lightheartdevs merged 5 commits intoLight-Heart-Labs:resources/devfrom
yasinBursali:feat/extensions-portal-ui

Conversation

@yasinBursali
Copy link
Copy Markdown
Contributor

Summary

Full-featured Extensions page for the DreamServer dashboard:

  • Card grid with status badges, toggle switches, icon backgrounds
  • Status filters (All/Enabled/Disabled/Not Installed/Incompatible) + category filters + search
  • Modal detail view with env vars, dependencies, copyable credential commands
  • Live container logs console with 2s auto-refresh
  • Agent status indicator with graceful offline fallback
  • Confirmation dialogs for all actions
  • Direct links to extension web UIs

Test plan

  • Page renders at /extensions with Puzzle icon in sidebar
  • Install/enable/disable/remove work from the UI
  • Console modal shows live container logs
  • Detail modal shows credential retrieval command
  • Agent offline: shows restart message after mutations

Part 5 of 5 for the Extensions Portal feature.

Copy link
Copy Markdown
Collaborator

@Lightheartdevs Lightheartdevs left a comment

Choose a reason for hiding this comment

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

Dashboard UI looks good — card grid, status filters, search, detail modal, live logs console, confirmation dialogs. Nice UX.

Must fix (2 items)

  1. No auth headers on API calls. `fetchJson` is a bare `fetch(url)` with no Authorization header. Unless the nginx proxy injects the API key, all calls will return 401 and the entire page won't work. Check how the existing dashboard pages handle auth — they likely pass the API key as a header or cookie.

  2. `const logRef = { current: null }` should be `useRef(null)`. The current code creates a new plain object every render, so the auto-scroll-to-bottom in the console modal won't work reliably. Quick fix:
    ```jsx
    const logRef = useRef(null)
    ```

Non-blocking

  • Hardcoded HTTP links to extensions. `http://${window.location.hostname}:${port}` assumes HTTP, not HTTPS. Fine for localhost but would break behind a reverse proxy.
  • Category derived from first feature only. Extensions with multiple features in different categories only show under one filter category.

Fix items 1 and 2 and this is ready.

@yasinBursali
Copy link
Copy Markdown
Contributor Author

Thanks! Both items fixed:

  1. Auth headers: nginx already injects Authorization: Bearer ${DASHBOARD_API_KEY} via proxy_set_header in nginx.conf (the B1/B2 fix). Frontend fetch calls go through the nginx proxy at /api/ which adds the header before forwarding to dashboard-api. No explicit auth needed in JS.
  2. useRef fixed. const logRef = useRef(null) + added useRef to react imports.

Also: Remove button now only shows on disabled extensions (matching the disable-before-uninstall guard in #672).

Re: non-blocking items:

  • HTTP links: correct for localhost use case, reverse proxy users would need their own URL scheme
  • Category from first feature: noted for follow-up

Copy link
Copy Markdown
Collaborator

@Lightheartdevs Lightheartdevs left a comment

Choose a reason for hiding this comment

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

Audit Review

Blocking: fetchJson doesn't pass auth headers

fetchJson does a plain fetch() with no Authorization header:

const fetchJson = async (url, ms = 8000) => {
  const c = new AbortController()
  const t = setTimeout(() => c.abort(), ms)
  try {
    return await fetch(url, { signal: c.signal })
  } finally {
    clearTimeout(t)
  }
}

If /api/extensions/catalog and the mutation endpoints require verify_api_key, every request from this page will fail with 401/403. Same issue in handleMutation. Need to pass Authorization: Bearer ${key} — check how other dashboard pages handle this (shared fetch wrapper, proxy-injected header, or cookie-based auth).

Note: Log viewer exposes raw container output

The ConsoleModal renders container stdout/stderr verbatim. Container logs can contain secrets, API keys, connection strings, database passwords. This is visible to anyone with dashboard access. Consider redacting known secret patterns (env vars matching *_KEY, *_SECRET, *_PASSWORD) in a follow-up.

Good

  • No XSS risk — React auto-escaping everywhere, no dangerouslySetInnerHTML
  • All destructive actions behind confirmation dialogs
  • ICON_MAP lookup falls back safely to Package for unknown icons
  • AbortController timeout on all fetches
  • Clean component structure

@yasinBursali
Copy link
Copy Markdown
Contributor Author

Auth issue fully addressed across all dashboard files.

Root cause: nginx.conf:29 injects Authorization: Bearer ${DASHBOARD_API_KEY} via proxy_set_header for all /api/ requests. The frontend has no access to the API key (it's server-side). This is the same pattern used by Dashboard.jsx (fetch('/api/features') — no auth headers, works fine).

Fix: Removed VITE_API_URL / API_BASE from all 5 files — not just Extensions.jsx:

  • Extensions.jsx — 5 fetch calls
  • Settings.jsx — 4 fetch calls
  • Voice.jsx — 1 fetch call
  • useVersion.js — 2 fetch calls (including triggerUpdate)
  • useVoiceAgent.js — 1 fetch call (VITE_LIVEKIT_URL kept — WebSocket, different auth)

All fetches now use relative URLs (/api/...) which route through nginx where the header is injected. Zero VITE_API_URL or API_BASE references remain in src/.

Verified: npm run build succeeds, no VITE_API_URL in build output.

@yasinBursali
Copy link
Copy Markdown
Contributor Author

Fix commit added

f2511d1 — fix: map API error details to user-friendly messages in extensions portal

Added friendlyError() utility that maps known API 400 error detail strings to user-friendly messages:

API detail User sees
"...build context..." / "...local build..." "This extension requires a local build and cannot be installed through the portal yet."
"...already installed..." "This extension is already installed."
"...already enabled..." "This extension is already enabled."
"...already disabled..." "This extension is already disabled."
"Disable extension before..." "Please disable this extension before removing it."
"Missing dependencies..." Passed through as-is (already descriptive)
Unknown errors Passed through as-is

Handles null/undefined gracefully. Applied in handleMutation catch block.

@Lightheartdevs
Copy link
Copy Markdown
Collaborator

Code is approved. Please rebase onto main — the tier-*-env-validation checks are failing because the branch is missing recent schema changes from merged PRs. A rebase will fix all 5 failures. This PR merges after #669 lands.

yasinBursali and others added 4 commits April 2, 2026 20:24
Full-featured Extensions page for the DreamServer dashboard. Browse,
install, enable/disable, and remove extensions from the browser.

- Card grid with status badges, toggle switches, icon backgrounds
- Status filters (All/Enabled/Disabled/Not Installed/Incompatible)
- Category filters and text search
- Modal detail view with env vars, dependencies, credential commands
- Live container logs console with 2s auto-refresh
- Agent status indicator with graceful offline fallback
- Copyable CLI commands and docker exec credential retrieval
- Confirmation dialogs for all destructive actions
- Direct links to extension web UIs on their ports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses maintainer review feedback:
1. logRef uses useRef(null) instead of plain object for reliable auto-scroll
2. Remove button only shows on disabled extensions (disable-before-uninstall)
3. Added useRef to react imports

Note: Auth headers are handled by nginx proxy (proxy_set_header Authorization)
so frontend fetch calls don't need explicit auth headers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auth is handled by nginx proxy_set_header injecting the Authorization
header for all /api/ requests. Removed API_BASE/VITE_API_URL to prevent
accidentally bypassing nginx and losing auth. All fetches now use
relative URLs through the nginx proxy, matching Dashboard.jsx pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rtal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@yasinBursali yasinBursali force-pushed the feat/extensions-portal-ui branch from 34ac979 to 35734cb Compare April 2, 2026 17:24
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@yasinBursali yasinBursali force-pushed the feat/extensions-portal-ui branch from 35734cb to 834d81e Compare April 2, 2026 17:30
@Lightheartdevs Lightheartdevs merged commit 8481a94 into Light-Heart-Labs:resources/dev Apr 2, 2026
17 checks passed
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.

2 participants