refactor: move webapp from /user/page to /app

Simplified the webapp routing structure by consolidating the admin UI
to a single clean endpoint.

Changes:
- Moved webapp from /user/page to /app (root of mount)
- Removed /user JSON endpoint (no longer needed)
- Updated mount point from /user to /app in app.py
- Updated all route path checks (3 locations)
- Updated OAuth redirects to point to /app
- Updated all HTMX endpoint references
- Updated documentation (ADR-007, CHANGELOG)
- Added redirect from /app to /app/ for trailing slash handling

New Route Structure:
- /app - Main webapp (HTML UI with tabs)
- /app/revoke - Revoke background access
- /app/webhooks - Webhook management UI
- /app/webhooks/enable/{preset_id} - Enable webhook preset
- /app/webhooks/disable/{preset_id} - Disable webhook preset

Breaking Change: Existing bookmarks to /user or /user/page will no longer work.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2025-11-11 20:53:43 +01:00
co-authored by Claude
parent f4759e424d
commit d86a185e04
6 changed files with 30 additions and 27 deletions
@@ -1,7 +1,7 @@
"""Browser-based OAuth login routes for admin UI.
Separate from MCP OAuth flow - these routes establish browser sessions
for accessing admin UI endpoints like /user/page.
for accessing admin UI endpoints like /app.
"""
import hashlib
@@ -38,8 +38,8 @@ async def oauth_login(request: Request) -> RedirectResponse | JSONResponse:
"""
oauth_ctx = request.app.state.oauth_context
if not oauth_ctx:
# BasicAuth mode - no login needed, redirect to user page
return RedirectResponse("/user/page", status_code=302)
# BasicAuth mode - no login needed, redirect to app
return RedirectResponse("/app", status_code=302)
storage = oauth_ctx["storage"]
oauth_client = oauth_ctx["oauth_client"]
@@ -71,7 +71,7 @@ async def oauth_login(request: Request) -> RedirectResponse | JSONResponse:
await storage.store_oauth_session(
session_id=state, # Use state as session ID
client_id="browser-ui",
client_redirect_uri="/user/page",
client_redirect_uri="/app",
state=state,
code_challenge=code_challenge,
code_challenge_method="S256",
@@ -383,7 +383,7 @@ async def oauth_login_callback(request: Request) -> RedirectResponse | HTMLRespo
# Continue anyway - profile cache is optional for browser UI
# Create response and set session cookie
response = RedirectResponse("/user/page", status_code=302)
response = RedirectResponse("/app", status_code=302)
response.set_cookie(
key="mcp_session",
value=user_id,
+1 -1
View File
@@ -602,7 +602,7 @@ async def user_info_html(request: Request) -> HTMLResponse:
webhooks_tab_html = ""
if show_webhooks_tab:
webhooks_tab_html = """
<div hx-get="/user/webhooks" hx-trigger="load" hx-swap="outerHTML">
<div hx-get="/app/webhooks" hx-trigger="load" hx-swap="outerHTML">
<p style="color: #999;">Loading webhook management...</p>
</div>
"""
+5 -5
View File
@@ -32,7 +32,7 @@ def _get_storage(request: Request):
Returns:
RefreshTokenStorage instance or None
"""
# Try browser_app state first (for /user routes)
# Try browser_app state first (for /app routes)
storage = getattr(request.app.state, "storage", None)
# Try oauth_context if in OAuth mode
@@ -289,7 +289,7 @@ async def webhook_management_pane(request: Request) -> HTMLResponse:
status_badge = f'<span style="color: #4caf50; font-weight: bold;">✓ Enabled ({num_webhooks} webhooks)</span>'
action_button = f"""
<button
hx-delete="/user/webhooks/disable/{preset_id}"
hx-delete="/app/webhooks/disable/{preset_id}"
hx-target="#preset-{preset_id}"
hx-swap="outerHTML"
class="button"
@@ -301,7 +301,7 @@ async def webhook_management_pane(request: Request) -> HTMLResponse:
status_badge = '<span style="color: #999;">Not Enabled</span>'
action_button = f"""
<button
hx-post="/user/webhooks/enable/{preset_id}"
hx-post="/app/webhooks/enable/{preset_id}"
hx-target="#preset-{preset_id}"
hx-swap="outerHTML"
class="button button-primary">
@@ -429,7 +429,7 @@ async def enable_webhook_preset(request: Request) -> HTMLResponse:
<div><span style="color: #4caf50; font-weight: bold;">✓ Enabled ({num_webhooks} webhooks)</span></div>
<div>
<button
hx-delete="/user/webhooks/disable/{preset_id}"
hx-delete="/app/webhooks/disable/{preset_id}"
hx-target="#preset-{preset_id}"
hx-swap="outerHTML"
class="button"
@@ -520,7 +520,7 @@ async def disable_webhook_preset(request: Request) -> HTMLResponse:
<div><span style="color: #999;">Not Enabled</span></div>
<div>
<button
hx-post="/user/webhooks/enable/{preset_id}"
hx-post="/app/webhooks/enable/{preset_id}"
hx-target="#preset-{preset_id}"
hx-swap="outerHTML"
class="button button-primary">