Admin Portal¶
The Admin Portal is an interactive web dashboard for managing and monitoring the platform. Enable it with portal.enabled: true in your configuration.
portal:
enabled: true
title: "ACME Data Platform"
logo: https://example.com/logo.svg
logo_light: https://example.com/logo-for-light-bg.svg
logo_dark: https://example.com/logo-for-dark-bg.svg
admin:
enabled: true
persona: admin
The portal is served at /portal/. Authentication is required — use the same credentials as the Admin API. The sidebar is divided into User pages (see User Portal) and Admin pages (described here).
Branding¶
Customize the sidebar title and logo via portal.title, portal.logo, portal.logo_light, and portal.logo_dark. The portal picks the theme-appropriate logo automatically:
- Light theme:
logo_light→logo→ built-in default - Dark theme:
logo_dark→logo→ built-in default
The resolved logo is also used as the browser favicon. A built-in activity icon is used when no logo is configured. Logos should be square SVGs for best results.
Email logo¶
Notification emails need a separate asset. Mail clients strip inline SVG, so portal.logo cannot be reused; set portal.logo_email to a raster PNG URL:
The PNG is fetched once at startup and attached to each message as an inline part, so it renders even in the clients that block remote images by default. Recipients never request the URL themselves, which means it only has to be reachable from the server, not from the public internet.
The logo is additive: the brand wordmark still renders beneath it, and doubles as the image's alt text. Leave logo_email unset and emails render the wordmark alone. A URL that is unreachable or does not serve image/png logs a warning at startup and falls back to the wordmark; it never blocks notification delivery.
Public Viewer Branding¶
Shared artifact links (the public viewer at /portal/view/{token}) display a two-zone header. The right zone shows the platform brand (portal.title and portal.logo). The left zone is an optional implementor brand for the organization deploying the platform:
portal:
implementor:
name: "ACME Corp" # Display name (left zone of public viewer header)
logo: "https://acme.com/logo.svg" # URL to SVG logo (fetched once at startup, max 1 MB)
url: "https://acme.com" # Clickable link wrapping name + logo
All three fields are optional. When omitted, the left zone is hidden and only the platform brand appears. The logo URL must point to an SVG file; it is fetched at server startup and inlined into the HTML.
Public Viewer Features¶
The public viewer includes:
- Light/dark mode — Defaults to the system
prefers-color-schemesetting. A toggle button in the header allows switching; the choice is persisted tolocalStorage. - Expiration notice — When the share has an expiration, a notice bar shows the relative time remaining (e.g., "This page expires in 6 hours"). Hidden when the share has no expiry or
hide_expirationwas set at share creation. - Notice text — Configurable per-share via
notice_text. Defaults to "Proprietary & Confidential. Only share with authorized viewers." Set to""to hide the notice entirely.
These fields are set per-share when creating a share via POST /api/v1/portal/assets/{id}/shares:
| Field | Type | Default | Description |
|---|---|---|---|
expires_in |
string | - | Duration string (e.g., "24h", "72h") |
shared_with_user_id |
string | - | Target user ID for private shares |
shared_with_email |
string | - | Target email for private shares |
hide_expiration |
bool | false |
Hide the expiration countdown in the public viewer |
notice_text |
string|null | "Proprietary & Confidential. Only share with authorized viewers." |
Custom notice text for the public viewer. Omit or null for the default. Set to "" to hide the notice entirely. Max 500 characters. |
access_mode |
string | restricted with a recipient, authenticated without |
Who the token opens for: restricted (named recipient and the creator), authenticated (any signed-in user), or public (anyone with the link). public is never implied; restricted without a recipient is rejected with 400. |
Anonymous access is opt-in. Every route under /portal/view/ (the page, the
raw content, both thumbnail routes, and the three collection-item routes)
resolves the share's access mode before serving anything, and refuses with 403
when the caller is not admitted.
A refused browser navigation renders a branded landing page instead of bare
text: sign-in with a return path for account holders, and, on shares naming an
email address, a request button for a single-use, 15-minute view link emailed
only to that stored address. A claimed link opens a view-only guest session
scoped to that one share (a signed cookie derived from the browser-session
signing key); guests never reach the portal or its API, and revoking the share
ends their access immediately. Link issuance is rate-limited per IP and capped
per share, and requires the notification substrate (SMTP), a database, browser
sessions, and portal.public_base_url. Subresource fetches and API-style
callers keep plain-status refusals.
Dashboard¶
The Dashboard is the admin home page, providing a real-time overview of platform health across configurable time ranges (1h, 6h, 24h, 7d).


The dashboard includes:
- System info bar — Platform name, version, transport, config mode, and enabled features (Audit, Knowledge, OAuth)
- Summary cards — Total calls, success rate, average duration, unique users, unique tools, enrichment rate, and error count
- Activity timeline — Tool call volume over time (green) with error overlay (red)
- Top Tools / Top Users — Horizontal bar charts showing the most active tools and users
- Performance — Response time percentiles (P50, P95, P99) and average response size
- Recent Errors — Clickable error list with detail drawer
- Knowledge Insights — Summary statistics and category breakdown with pending review queue
- Connections — All configured toolkit connections with tool counts
Indexing¶
The Indexing tab of the Dashboard (alongside MCP, API Gateway, Health, and Events) is an admin-only, cross-kind view of embedding-index health for every consumer of the shared index_jobs queue (pkg/indexjobs): api-catalog operation vectors, tool descriptors, and any future consumer, which gets visibility here for free the moment it registers. Embedding work runs off the request path, so a provider outage, a model dimension mismatch, or repeated retries can silently degrade ranking=semantic/hybrid to lexical with only a log line as signal; this tab is the single place to answer whether indexing is healthy, what is covered, what failed, and why.
It is system-wide and admin-only by platform convention (operators see all indexing; it is not a per-persona capability). All data is real index_jobs and vector-table state — no mocked dimensions. The page polls every 5 seconds so it reflects work as the worker, reconciler, and reaper complete it.


The tab includes:
- Provider health banner — The embedding provider's kind, model, and dimension, or a clear degraded state (noop / unconfigured) since a bad provider makes the whole index meaningless and pauses indexing.
- Per-kind health cards (summary-first) — Each kind leads with one plain health verdict computed server-side: Up to date (the single resting state for a fully-indexed, quiescent, failure-free kind), Indexing… (work in flight), or Degraded (an open failure or a coverage shortfall). Equivalent states look identical: every up-to-date kind shows the same green badge and the same
N / M · 100%coverage bar (api-catalog's expected comes fromoperation_count; tools writes its complete registered set atomically, so its indexed count is also its expected), and a recency line ("last indexed", or "fully indexed" for a kind seeded outside the queue, never "never"). The per-unit job-state breakdown ("units by last run") is shown only when there is active work or an open failure, so an up-to-date card is not cluttered with an all-zero or stale stat row. A Re-index button re-enqueues every out-of-sync unit of the kind. - Throughput timeline — Completed jobs over time (d3 area), so an operator can see indexing keeping up or stalling.
- Embed latency — Per-kind started-to-completed duration (p50 with a p95 marker), surfacing slow passes such as the CPU-only embedder case.
- In flight — Running jobs with worker id, lease countdown, and items-done progress for long passes.
- Retry backoff — Pending jobs that already failed once, with attempt count and next run time.
- Failure triage (self-resolving) — Units with open failures, grouped by error signature. Each unit shows first-seen / last-seen timestamps, occurrence and attempt counts, and "last succeeded" context, with an expandable drill-in to the un-redacted error and the underlying job id. A failure auto-resolves (leaves the panel) once a later job for the same unit succeeds; Retry re-enqueues the unit and the card clears when it next succeeds, and Dismiss is the explicit fallback that resolves a failure (such as a removed consumer's leftover rows) that no future success will supersede.
- Jobs drill-down — A filterable table (by kind and status) of recent jobs with trigger, attempts, last update, and error. Routine timer-driven reconciler successes for a unit (which every replica re-runs on its own schedule) are collapsed into a single "synced ×N" row so they do not drown the table.
The existing per-catalog embedding badges in the API Catalogs panel remain; this tab is the cross-kind superset.
Tools¶
The Tools page is a master-detail view. The list on the left groups every registered tool by connection (Trino, DataHub, S3, platform, and gateway-proxied MCP) with search filtering; selecting a tool opens its detail across five tabs.
Overview¶


The Overview tab shows the selected tool's description (with an inline override editor), toolkit kind, connection, title, the JSON input schema, and per-persona access — which personas can call the tool and the rule that decided it.
Try It¶


An interactive execution environment for the selected tool:
- Dynamic parameter form — Auto-generated from the tool's JSON schema with type-appropriate inputs (text areas for SQL, number fields for limits, dropdowns for enums)
- Result display — Rendered markdown tables for structured data, with a Raw toggle for JSON output
- Execution history — Timestamped log of tool calls with duration, status, and replay capability
Activity¶


Aggregated call volume, success rate, and average duration for the selected tool over the recent window, with a deep link to the audit log filtered to this tool.
Enrichment¶


Shown for gateway-proxied (MCP) tools with a connection. Lists the cross-enrichment enrichment rules attached to the tool — each rule's predicate, action source and operation, merge strategy, and enabled state. This is where the platform's bidirectional context injection is configured per tool.
Visibility¶


Toggle the tool's membership in the platform-wide deny list, and preview whether a given persona can access it before committing the change.
Activity (Dashboard tabs)¶
The admin Dashboard hosts the platform activity views as tabs: MCP, API Gateway, Health, Indexing, and Events. (Indexing is documented above.) Each works across configurable time ranges (1h, 6h, 24h, 7d).
MCP¶
The MCP tab provides platform-wide analytics over MCP tool-call activity.


Includes summary cards, the activity timeline, and top tools / top users charts — focused on MCP tool calls with performance percentiles and error tracking.
API Gateway¶
The API Gateway tab visualizes outbound REST gateway traffic proxied through the platform.


Includes the connection-to-operation traffic flow (Sankey), an inbound-vs-outbound health split by status category, and breakdowns by status class, method, and calling identity.
Health¶
The Health tab reports per-node platform health scraped from Prometheus.


Per-node uptime, CPU, resident memory, heap, and goroutine counts across the platform fleet, with any missing metric rendered as a dash.
Events¶
The Events tab provides a searchable, filterable log of every tool call.


Features:
- Filters — User, tool, status (success/failure), and time range dropdowns
- Sortable columns — Timestamp, user, tool, toolkit, connection, duration, status, and enrichment
- Export — Export CSV and Export JSON buttons
- Event detail drawer — Click any row to open the full detail:
- Identity — User email, persona, session ID
- Execution — Tool name, toolkit, connection, duration
- Status — Success/failure, enrichment status
- Transport — HTTP or stdio, request/response sizes, content block count
- Parameters — Full request parameters as JSON


Knowledge & Memory (review and promotion)¶
The separate admin Knowledge & Memory page was merged into the unified Knowledge page in the user portal (see Portal User Guide). Review and promotion gate on the apply_knowledge capability, not an admin role: whoever holds the tool sees the review surfaces inside the Knowledge page, whether or not they are an admin.
Inside the Knowledge page, apply_knowledge holders get:
- Review queue (Insights tab) - All captured insights across users, with status/category/confidence filters and an insight detail drawer (full metadata, entity URNs, suggested actions, related columns, review notes, approve/reject actions). A pending-review count is badged on the sidebar Knowledge item and the Insights tab.
- Changesets (Knowledge tab) - The record of insights promoted into knowledge: the target DataHub URN or knowledge page, change type, who applied it, and status, with rollback to revert applied changes. They sit with the promoted knowledge rather than with the unpromoted insights in the review pipeline.
Opening an insight from the review queue shows the full review drawer: the captured statement, entity URNs, suggested catalog actions, related columns, the capture/review/apply audit trail, and approve/reject controls.


The Memory tab is personal to each user; there is no all-user memory view, because the only memory that crosses between users is an insight (handled in the review queue above).
Assets (Admin)¶
The admin Assets page shows all platform assets across all users with search and filtering.


The table displays name, owner email, content type, file size, sharing status, and creation date. Click any asset to open the detail view:


The admin asset detail renders the asset content in a full-screen viewer with Preview/Source toggle, owner display, and management actions (Delete, Download, Share).
Resources (Admin)¶
The admin Resources page shows managed resources across all personas and scopes.


Features:
- Scope tabs — All Resources, Global, and per-persona tabs (admin, data-engineer, finance-executive, etc.)
- Search and filter — Text search and category dropdown
- Upload button — Upload new resources scoped to any persona
- Resource table — Name, scope badge, category, MIME type, tags, file size, uploader email, and last updated date
Prompts (Admin)¶
The admin Prompts page provides global prompt management across all scopes and personas.


The create/edit form is a markdown editor with auto-extracted {argument} placeholders, a scope selector (global, persona, personal), persona targeting, and lifecycle status. When an author requests promotion, a review-queue banner surfaces pending prompts for approval or rejection.


Features:
- Scope filter — Dropdown to filter by Global, Persona, Personal, or System scope
- Search — Full-text search across name and description
- New Prompt — Create prompts with scope, persona assignment, tags, and enabled/disabled state
- Sortable table — Name, scope badge, description, owner, category, and actions
- Scope badges — Global (blue), Persona (purple), Personal (gray), System (amber)
- Status badges — Lifecycle state next to each name: draft (gray), approved (emerald), deprecated (amber), superseded (rose)
- Lifecycle controls — Editing a prompt exposes a status selector to move it through draft -> approved -> deprecated/superseded; approval stamps the acting admin. Selecting superseded reveals a field to record the replacement prompt name.
- Tags — Comma-separated labels set on create and edit, shown as chips in the expanded row
- Promotion review queue — A panel at the top of the page lists personal prompts whose owners have requested promotion, showing the owner, the requested scope (persona with the target personas, or global), and the description. Approve applies the requested scope/personas and marks the prompt approved; Reject clears the request and leaves it personal. If the promoted name already exists in the shared namespace, approval is blocked with a conflict so the owner renames first. The panel is hidden when no requests are pending.
Agent Instructions¶
The Agent Instructions page edits the operating guidance every agent session receives. The editor is a split markdown view (source on the left, live preview on the right); a Database override badge appears when the value is stored in the database rather than the config file.


Above the editor sits the read-only Platform baseline: the platform-owned "how to operate" guidance composed beneath your instructions. It names only the tools this deployment exposes (search, query, save, capture), so you can see what is already covered and add only your business and deployment context on top.
Description¶
The Description page sets the platform's identity string, surfaced to MCP clients (for example in platform_info). Same split markdown editor and database-override semantics as Agent Instructions.


API Catalogs¶
API Catalogs are versioned, globally-owned bundles of OpenAPI 3.x specs that kind: api connections share. One catalog can back many connections, so a single upload (for example a Salesforce or Stripe spec) documents every connection that points at that vendor.


Left pane: Catalogs grouped by name, each showing its component-spec count and how many connections reference it.
Right pane: The selected catalog's component specs, each with an embedding-health badge (78/78 indexed, or a live running count while a spec re-embeds), source badge (URL / upload / inline), and last-fetched timestamp. A banner summarizes catalog-wide readiness ("all specs indexed; semantic ranking is active"). Per-spec actions cover refresh-from-URL, retry-embedding, edit, and delete; catalog actions are Edit, Clone, and Delete (blocked while any connection references the catalog).
Ingest a spec by paste, file upload, or a public HTTPS URL (fetched once, ETag captured). Per-operation embeddings power semantic endpoint ranking in api_list_endpoints.




See API Catalogs for the full catalog model, ingestion paths, and the embedding job queue.
Connections¶
The Connections page manages toolkit backend instances (Trino, DataHub, S3, MCP gateway) using a split-pane layout.


Left pane — Connection list grouped by kind (DataHub, S3, Trino), with source badges (file or database), descriptions, and tool counts.
Right pane — Selected connection detail showing:
- Metadata — Kind, created by, and last updated
- Configuration — Key-value pairs with "Show sensitive" toggle for passwords and tokens
- Actions — Edit and Delete buttons
Source tracking:
| Badge | Meaning |
|---|---|
| file | Defined in the YAML config file. Read-only in the admin UI. |
| database | Created via the admin UI. Fully editable. |
| both | Defined in config with a database override. Database version is active. |
- File connections are read-only. Editing creates a database override (source becomes "both").
- Deleting a "both" connection removes the override and reverts to the file version.
- + Add Connection at the bottom creates database-only connections.
Creating or editing a connection opens a kind-aware editor: a markdown description plus the configuration fields for the selected kind (Trino host/port/catalog, S3 bucket/region, DataHub server, or an API-gateway base URL and catalog picker), with TLS material and auth handled inline.




MCP Gateway Connections¶
Connections of kind mcp proxy upstream MCP servers and re-expose
their tools as <connection_name>__<remote_tool> (e.g.
vendor__list_contacts). They share the same split-pane layout as other
connections; the right pane adds a row of gateway-specific actions
beneath the metadata block:
| Action | What it does |
|---|---|
| Test connection | Dials the upstream with the current form values (without saving) and reports whether tool discovery succeeded. Use to validate credentials before persisting. |
| Refresh tools | Re-dials a saved connection and re-registers its tool catalog on the live MCP server. Use after the upstream changes its tools. |
| Enrichment rules | Opens a side drawer for the cross-enrichment rule editor (see below). |
Add MCP Connection¶
The + Add Connection form for kind mcp exposes:
- Endpoint — URL of the upstream MCP server (streamable HTTP).
- Connection name — local prefix for the proxied tools.
- Auth mode —
None/Bearer token/API key/OAuth 2.1. - Credential (bearer/api_key) — encrypted at rest with
ENCRYPTION_KEY. - OAuth fields (when
auth_mode=OAuth 2.1): - Grant type —
client_credentials(machine-to-machine) orauthorization_code + PKCE (browser sign-in)for upstreams like Salesforce Hosted MCP that require human sign-in. - Authorization URL — appears only for
authorization_code; e.g.https://login.salesforce.com/services/oauth2/authorize. - Token URL — OAuth token endpoint.
- Client ID / Client Secret — from the upstream's OAuth app registration.
- Scope — for
authorization_code, includerefresh_tokenso cron jobs and scheduled prompts survive access-token expiry. - Connect timeout / Call timeout — bounds the dial + tool-call durations.
After saving an authorization_code connection, the right pane shows an
amber Not connected banner with a Connect button.
OAuth Connect Button¶
For authorization_code connections:
- Click Connect on the connection card.
- A new tab opens to the upstream's
/authorizeURL with PKCE state andredirect_uri=<platform-host>/api/v1/admin/oauth/callback. - Operator authenticates with the upstream provider.
- The upstream redirects back to the platform's callback. The platform
exchanges the code for tokens and stores them encrypted at rest in
gateway_oauth_tokens(AES-256-GCM viaENCRYPTION_KEY). - The card now shows Authorized by
<email><time ago>and the tool list populates.
The platform refreshes the access token automatically using the stored refresh token, so cron jobs and scheduled prompts run untouched until the upstream invalidates the refresh token. Click Reconnect to re-authorize manually if needed; click Refresh now to force an immediate refresh.
Cross-Enrichment Rules Drawer¶
Clicking Enrichment rules on a saved gateway connection opens a slide-out drawer for managing rules that join proxied tool responses with native warehouse / catalog context:
- Rule list — one row per rule, with toggle for enable/disable, edit, delete, and Dry-run preview.
- New rule — opens the rule editor with three structured sections:
- Tool name (autocomplete from this connection's discovered tools).
- When predicate —
alwaysorresponse_containswith JSONPath. - Enrich action — source (
trinoordatahub), operation, and parameters with JSONPath bindings ($.args,$.response,$.user). - Merge strategy — where the enrichment lands in the response
(
enrichmentby default; configurable path). - Dry-run — paste a sample tool call, get the merged response back without executing any side effects.
Rule failures attach a warning: text content to the response and never
fail the parent tool call. See Gateway Toolkit
for the full rule schema.
Personas¶
The Personas page manages role-based tool access rules and context overrides using the same split-pane layout as Connections.


Creating or editing a persona opens the editor: an identity panel (name, display name, roles, priority) beside a live Permissions explorer that previews exactly which tools and connections the allow/deny patterns resolve to, with a running allowed/denied count and a resolution trace. Quick templates (Administrator, Read Only, Analyst, Engineer) seed common policies. A separate AI Assistant Behavior tab tunes the persona's prompts and hints.


Left pane — Persona list with display name, slug, role count, and resolved tool count.
Right pane — Selected persona detail showing:
- Metadata — Priority, resolved tools count, and assigned roles
- Tool Access Rules — Allow patterns (green badges, e.g.,
trino_*,datahub_*) and deny patterns (red badges, e.g.,memory_capture) - Resolved Tools — Expandable list of the actual tools this persona can access
- Context Overrides — Description prefix and agent instructions suffix that customize AI behavior for this persona
See Personas for configuration details.
API Keys¶
The Keys page manages API keys for programmatic authentication.


The add-key form collects a name, optional owner email and description, roles (with a role browser), and an expiration. The generated key is shown once in a copy-now banner and never again.


Features:
- Key table — Name, source badge (file/database), email, description, roles badge, expiration date, and actions
- Expired keys — Shown with dimmed text and "Expired" badge
- + Add Key — Create keys with name, email, description, roles, and expiration preset (Never, 24h, 7d, 30d, 90d, 1yr). The plaintext key is shown only once at creation.
- Delete — Available for database-managed keys only; file keys are read-only
- Source badges — Same file/database/both system as Connections
Users¶
The Users page manages the known-users directory: a record of people (first name, last name, email) used to make sharing easier. It is not an authorization layer and grants no access; it only gives the share picker names to resolve and suggest.


Features:
- User table — Name, email, status badge, and last-seen date
- Status badge — Active (green) for someone seen via a real sign-in, or Invited (amber) for someone an admin pre-added who has not logged in yet
- + Add User — Pre-add a person by email (with optional first and last name) so they are selectable for sharing before they have ever signed in
- Edit — Change a person's first and last name. Admin-entered names take precedence: a later sign-in only fills blank name fields, it never overwrites a name an admin set
- Search — Filter the directory by name or email
- Auto-recording — Anyone who authenticates (OIDC/OAuth) is upserted into the directory automatically with the name from their token claims; API-key and anonymous sessions are not recorded
Requires a database. Without one the directory is disabled and the share dialog falls back to free-typed email only.
Settings¶
The Settings page holds global platform settings; the first section is Email (SMTP), which configures outbound mail for email notifications. Host, port, credentials, sender address, and TLS mode are stored in the database (the password encrypted at rest and write-only), and a Send test action verifies the configuration by delivering a test email. Like other admin configuration, editing requires database config mode.
Change Log¶
The Change Log page provides an audit trail of all configuration changes made via the admin UI.


Each entry shows:
- Config key — The configuration path that changed (e.g.,
server.description,server.agent_instructions) - Action — Set (red badge) indicating a value was written
- Timestamp — When the change was made
Local Development¶
Run the portal locally with demo data using Mock Service Worker:
Open http://localhost:5173/portal/ — no backend required. The mock data includes realistic ACME Corporation demo content with 200+ audit events, 50 knowledge insights, 6 personas, and 12 users.
For full-stack development with a real backend:
make dev-up # Start PostgreSQL
go run ./cmd/mcp-data-platform --config dev/platform.yaml # Start server
psql -h localhost -U platform -d mcp_platform -f dev/seed.sql # Seed demo data
cd ui && npm run dev # Start React dev server
See dev/README.md for complete local development instructions.
Generating Screenshots¶
Automated screenshot generation captures every portal page in light and dark modes:
cd ui
npm run screenshots # Generate PNG screenshots
npm run screenshots:convert # Convert to optimized WebP
Screenshots are saved to docs/images/screenshots/light/ and docs/images/screenshots/dark/. See ui/e2e/screenshots/README.md for configuration options including custom branding.