Files
compliance-scanner-agent/compliance-dashboard/src/app.rs
T
Sharang Parnerkar eb88656c43
CI / Check (pull_request) Failing after 4m33s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
feat(dashboard): UI for managing MCP tokens
Adds /mcp-tokens page that lets a logged-in user mint, list, and
revoke bearer tokens for the MCP server. Stacks on #92 (which added
the agent endpoints + middleware) — once both land, the loop is
closed: a user can copy a token from the dashboard straight into
their Claude Desktop / Cursor / ChatGPT MCP config.

UX
- "Create Token" button → inline form with name input.
- On submit, server function calls `POST /api/v1/mcp-tokens`. The
  raw token is shown ONCE in a prominent yellow banner with a copy
  button and a "won't be shown again" warning, then the user
  dismisses it manually.
- List view: card per token with name, prefix `mcpt_xxxx…`, created
  date, last_used (or "never"). Revoked tokens render dimmed with a
  "revoked" pill. Active tokens have a trash button → confirm
  modal → soft delete.
- Toast feedback on create/revoke success/failure.

Files
- infrastructure/mcp_tokens.rs (new) — three #[server] functions:
  fetch_mcp_tokens, create_mcp_token, revoke_mcp_token. All go
  through agent_client so the Keycloak Bearer is auto-attached;
  the agent then enforces tenant scoping on every endpoint.
- pages/mcp_tokens.rs (new) — the page component itself.
- app.rs — adds Route::McpTokensPage at /mcp-tokens.
- pages/mod.rs, infrastructure/mod.rs — module + re-export wiring.

Timestamp format
- The agent serializes BSON DateTime as extended JSON
  `{"$date":{"$numberLong":"..."}}`. Page has a small helper that
  accepts that shape, plain ISO strings, or anything else
  (best-effort). Same approach used elsewhere in the dashboard so
  there's no new dependency.

Test plan
- cargo fmt --all clean
- cargo clippy -p compliance-dashboard --features server
  -- -D warnings clean
- cargo clippy -p compliance-dashboard --features web
  --no-default-features -- -D warnings clean
- cargo check on both feature sets clean

Followup
- No sidebar entry yet (matches mcp_servers — settings-style
  pages are reached via direct URL today). Worth adding a
  Settings sub-menu in a separate UX pass.
- Token expiry + per-tool scope when those land on the agent side
  will need a small UI for the create modal (extra fields).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-30 17:27:36 +02:00

67 lines
2.1 KiB
Rust

use dioxus::prelude::*;
use crate::components::app_shell::AppShell;
use crate::pages::*;
#[derive(Debug, Clone, Routable, PartialEq)]
#[rustfmt::skip]
#[allow(clippy::enum_variant_names)]
pub enum Route {
#[layout(AppShell)]
#[route("/")]
OverviewPage {},
#[route("/repositories")]
RepositoriesPage {},
#[route("/findings")]
FindingsPage {},
#[route("/findings/:id")]
FindingDetailPage { id: String },
#[route("/sbom")]
SbomPage {},
#[route("/issues")]
IssuesPage {},
#[route("/graph")]
GraphIndexPage {},
#[route("/graph/:repo_id")]
GraphExplorerPage { repo_id: String },
#[route("/graph/:repo_id/impact/:finding_id")]
ImpactAnalysisPage { repo_id: String, finding_id: String },
#[route("/chat")]
ChatIndexPage {},
#[route("/chat/:repo_id")]
ChatPage { repo_id: String },
#[route("/dast")]
DastOverviewPage {},
#[route("/dast/targets")]
DastTargetsPage {},
#[route("/dast/findings")]
DastFindingsPage {},
#[route("/dast/findings/:id")]
DastFindingDetailPage { id: String },
#[route("/pentest")]
PentestDashboardPage {},
#[route("/pentest/:session_id")]
PentestSessionPage { session_id: String },
#[route("/mcp-servers")]
McpServersPage {},
#[route("/mcp-tokens")]
McpTokensPage {},
}
const FAVICON: Asset = asset!("/assets/favicon.svg");
const MAIN_CSS: Asset = asset!("/assets/main.css");
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css");
const VIS_NETWORK_JS: Asset = asset!("/assets/vis-network.min.js");
const GRAPH_VIZ_JS: Asset = asset!("/assets/graph-viz.js");
#[component]
pub fn App() -> Element {
rsx! {
document::Link { rel: "icon", href: FAVICON }
document::Link { rel: "stylesheet", href: TAILWIND_CSS }
document::Link { rel: "stylesheet", href: MAIN_CSS }
document::Script { src: VIS_NETWORK_JS }
document::Script { src: GRAPH_VIZ_JS }
Router::<Route> {}
}
}