feat(web): rename /install → /setup; nav label 'Setup local agent'
- Add GET /setup serving install.html (CLI + Claude Code setup page) - Add GET /install → 301 redirect to /setup for backwards compat - Move first-time setup wizard from /setup to /first-time-setup - Update nav link: href=/setup, label 'Setup local agent', active on both /setup and /install paths - Update page <title> to 'Setup local agent — …' - Update /dashboard and /setup comment in _claude_setup_instructions.jinja - Update tests and OpenAPI snapshot accordingly
This commit is contained in:
parent
92fd78cfb4
commit
85967e14ca
10 changed files with 532 additions and 54 deletions
|
|
@ -120,7 +120,7 @@ _URL_MAP = {
|
|||
"email_auth.login_email_form": "/login/email",
|
||||
"email_auth.send_magic_link": "/auth/email/send-link",
|
||||
"register": "/auth/password/setup",
|
||||
"setup": "/setup",
|
||||
"setup": "/first-time-setup",
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -322,9 +322,9 @@ async def index(request: Request, user: Optional[dict] = Depends(get_optional_us
|
|||
return RedirectResponse(url="/login", status_code=302)
|
||||
|
||||
|
||||
@router.get("/setup", response_class=HTMLResponse)
|
||||
@router.get("/first-time-setup", response_class=HTMLResponse)
|
||||
async def setup_wizard(request: Request, conn: duckdb.DuckDBPyConnection = Depends(_get_db)):
|
||||
"""First-time setup wizard. Redirects to dashboard if users already exist."""
|
||||
"""First-time setup wizard. Redirects to login if users already exist."""
|
||||
try:
|
||||
user_count = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
|
||||
if user_count > 0:
|
||||
|
|
@ -720,13 +720,13 @@ async def activity_center(
|
|||
return templates.TemplateResponse(request, "activity_center.html", ctx)
|
||||
|
||||
|
||||
@router.get("/install", response_class=HTMLResponse)
|
||||
async def install_page(
|
||||
@router.get("/setup", response_class=HTMLResponse)
|
||||
async def setup_page(
|
||||
request: Request,
|
||||
user: Optional[dict] = Depends(get_optional_user),
|
||||
conn: duckdb.DuckDBPyConnection = Depends(_get_db),
|
||||
):
|
||||
"""Public install instructions for the CLI."""
|
||||
"""Setup instructions for the local agent (CLI + Claude Code)."""
|
||||
base_url = str(request.base_url).rstrip("/")
|
||||
ctx = _build_context(
|
||||
request,
|
||||
|
|
@ -738,6 +738,12 @@ async def install_page(
|
|||
return templates.TemplateResponse(request, "install.html", ctx)
|
||||
|
||||
|
||||
@router.get("/install", response_class=HTMLResponse)
|
||||
async def install_redirect(request: Request):
|
||||
"""Backwards-compat redirect: /install → /setup (301)."""
|
||||
return RedirectResponse(url="/setup", status_code=301)
|
||||
|
||||
|
||||
@router.get("/admin/tables", response_class=HTMLResponse)
|
||||
async def admin_tables(
|
||||
request: Request,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<div class="app-header-right">
|
||||
{% set _path = request.url.path %}
|
||||
<a class="app-nav-link {% if _path == '/dashboard' or _path == '/' %}is-active{% endif %}" href="/dashboard">Dashboard</a>
|
||||
<a class="app-nav-link {% if _path.startswith('/install') %}is-active{% endif %}" href="/install">Install CLI</a>
|
||||
<a class="app-nav-link {% if _path.startswith('/setup') or _path.startswith('/install') %}is-active{% endif %}" href="/setup">Setup local agent</a>
|
||||
{% if session.user.is_admin %}
|
||||
<a class="app-nav-link {% if _path.startswith('/admin/marketplaces') %}is-active{% endif %}" href="/admin/marketplaces">Marketplaces</a>
|
||||
{% set _admin_active = _path.startswith('/admin/tables') or _path.startswith('/admin/tokens') or _path.startswith('/admin/users') or _path.startswith('/admin/groups') or _path.startswith('/admin/access') or _path.startswith('/admin/server-config') or _path.startswith('/admin/welcome') %}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* preview_mode=True → emits a read-only HTML <pre><code> block rendered
|
||||
with the real server_url and a visible placeholder
|
||||
for the token. Used inline on /dashboard and
|
||||
/install so the reader can see exactly what will
|
||||
/setup so the reader can see exactly what will
|
||||
land in their clipboard.
|
||||
* preview_mode=False → emits the JS `SETUP_INSTRUCTIONS_TEMPLATE` array +
|
||||
`renderSetupInstructions(server, token)` function.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Install CLI — {{ config.INSTANCE_NAME }}</title>
|
||||
<title>Setup local agent — {{ config.INSTANCE_NAME }}</title>
|
||||
{% if not config.THEME_FONT_URL %}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
|
|
|||
|
|
@ -1893,6 +1893,8 @@
|
|||
},
|
||||
"profile_after_sync": {
|
||||
"default": true,
|
||||
"deprecated": true,
|
||||
"description": "DEPRECATED: not consumed by the runtime (Agent 1 finding 2026-05-01). Profiler runs unconditionally on every synced table; this flag has no effect. Field stays for back-compat.",
|
||||
"title": "Profile After Sync",
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
@ -1901,6 +1903,17 @@
|
|||
"title": "Query Mode",
|
||||
"type": "string"
|
||||
},
|
||||
"source_query": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Source Query"
|
||||
},
|
||||
"source_table": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
|
@ -1936,6 +1949,8 @@
|
|||
},
|
||||
"sync_strategy": {
|
||||
"default": "full_refresh",
|
||||
"deprecated": true,
|
||||
"description": "DEPRECATED: catalog/profiler metadata only. No extractor reads this field; every sync is a full overwrite regardless of value. profiler.is_partitioned() consumes it for parquet-layout detection. Field stays for back-compat; will be removed in a future major release.",
|
||||
"title": "Sync Strategy",
|
||||
"type": "string"
|
||||
}
|
||||
|
|
@ -2067,6 +2082,83 @@
|
|||
"title": "TableSubscriptionUpdate",
|
||||
"type": "object"
|
||||
},
|
||||
"TemplateGetResponse": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Content"
|
||||
},
|
||||
"default": {
|
||||
"title": "Default",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Updated At"
|
||||
},
|
||||
"updated_by": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Updated By"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content",
|
||||
"default"
|
||||
],
|
||||
"title": "TemplateGetResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"TemplatePreviewRequest": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"maxLength": 200000,
|
||||
"minLength": 1,
|
||||
"title": "Content",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"title": "TemplatePreviewRequest",
|
||||
"type": "object"
|
||||
},
|
||||
"TemplatePutRequest": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"maxLength": 200000,
|
||||
"minLength": 1,
|
||||
"title": "Content",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"title": "TemplatePutRequest",
|
||||
"type": "object"
|
||||
},
|
||||
"TokenListItem": {
|
||||
"properties": {
|
||||
"created_at": {
|
||||
|
|
@ -2329,6 +2421,8 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"deprecated": true,
|
||||
"description": "DEPRECATED: not consumed by the runtime. See RegisterTableRequest.profile_after_sync.",
|
||||
"title": "Profile After Sync"
|
||||
},
|
||||
"query_mode": {
|
||||
|
|
@ -2342,6 +2436,17 @@
|
|||
],
|
||||
"title": "Query Mode"
|
||||
},
|
||||
"source_query": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Source Query"
|
||||
},
|
||||
"source_table": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
|
@ -2384,6 +2489,8 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"deprecated": true,
|
||||
"description": "DEPRECATED: catalog/profiler metadata only. See RegisterTableRequest.sync_strategy.",
|
||||
"title": "Sync Strategy"
|
||||
}
|
||||
},
|
||||
|
|
@ -2641,13 +2748,26 @@
|
|||
],
|
||||
"title": "VoteRequest",
|
||||
"type": "object"
|
||||
},
|
||||
"WelcomeResponse": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"title": "Content",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"title": "WelcomeResponse",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"description": "Data distribution platform for AI analytical systems",
|
||||
"title": "AI Data Analyst",
|
||||
"version": "2.1.0"
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
|
|
@ -3238,6 +3358,55 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/admin/welcome": {
|
||||
"get": {
|
||||
"operationId": "admin_welcome_page_admin_welcome_get",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "authorization",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Authorization"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"text/html": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Admin Welcome Page",
|
||||
"tags": [
|
||||
"web"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/admin/access-overview": {
|
||||
"get": {
|
||||
"description": "One-shot snapshot for the /admin/access page.\n\nReturns:\n - ``groups``: every user_group with member + grant counts\n - ``grants``: every (group_id, resource_type, resource_id) row\n - ``resources``: per-resource-type hierarchical layout, where each\n type has a list of *blocks* (parent entities, e.g. a marketplace)\n and each block has *items* (concrete grantable resources).\n\nUI stitches the three pieces into the two-column layout: groups on\nthe left, resources tree on the right with per-item checkboxes whose\nstate derives from ``grants``.",
|
||||
|
|
@ -3398,9 +3567,25 @@
|
|||
},
|
||||
"/api/admin/discover-tables": {
|
||||
"get": {
|
||||
"description": "Discover all available tables from the configured data source.",
|
||||
"description": "Discover available tables from the configured data source.\n\nFor ``data_source.type='keboola'`` returns the full Storage API table\nlist (single round-trip). For ``data_source.type='bigquery'``:\n\n- Without ``dataset``: list datasets in the configured project.\n- With ``dataset=name``: list tables (BASE TABLE + VIEW) in that dataset.\n\nTwo-step shape avoids paying the per-dataset list_tables cost up-front\non projects with hundreds of datasets \u2014 the UI populates the dataset\ndropdown first, then fetches tables only for the selected dataset.",
|
||||
"operationId": "discover_tables_api_admin_discover_tables_get",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "dataset",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Dataset"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "authorization",
|
||||
|
|
@ -4591,7 +4776,7 @@
|
|||
},
|
||||
"/api/admin/registry": {
|
||||
"get": {
|
||||
"description": "Get full table registry.",
|
||||
"description": "Get full table registry.\n\nEach table row is enriched with `last_sync_error` from sync_state so\noperators can see WHY a row isn't materializing without trawling\nscheduler logs. None for rows that have never errored or have already\nrecovered (status='ok'); the per-row error message string otherwise.",
|
||||
"operationId": "list_registry_api_admin_registry_get",
|
||||
"parameters": [
|
||||
{
|
||||
|
|
@ -4639,7 +4824,7 @@
|
|||
},
|
||||
"/api/admin/registry/{table_id}": {
|
||||
"delete": {
|
||||
"description": "Unregister a table from the system.\n\nFor BQ rows, schedules a background rebuild so the dropped row's\nmaster view is removed from analytics.duckdb (rather than hanging\naround until the next scheduled sync).",
|
||||
"description": "Unregister a table from the system.\n\nFor BQ rows, schedules a background rebuild so the dropped row's\nmaster view is removed from analytics.duckdb (rather than hanging\naround until the next scheduled sync).\n\nFor materialized rows, also removes the canonical parquet at\n`${DATA_DIR}/extracts/<source_type>/data/<id>.parquet` and clears\nthe matching `sync_state` row. Without these two cleanups, the\nmanifest endpoint kept advertising the dropped table to `da sync`\n(sync_state-driven) and the orchestrator's next rebuild could\nresurrect a master view from the leftover parquet (E2E sub-agent\nfinding 2026-05-01).",
|
||||
"operationId": "unregister_table_api_admin_registry__table_id__delete",
|
||||
"parameters": [
|
||||
{
|
||||
|
|
@ -5163,6 +5348,210 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/api/admin/welcome-template": {
|
||||
"delete": {
|
||||
"operationId": "admin_reset_template_api_admin_welcome_template_delete",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "authorization",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Authorization"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Admin Reset Template",
|
||||
"tags": [
|
||||
"welcome"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "admin_get_template_api_admin_welcome_template_get",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "authorization",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Authorization"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/TemplateGetResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Admin Get Template",
|
||||
"tags": [
|
||||
"welcome"
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "admin_put_template_api_admin_welcome_template_put",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "authorization",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Authorization"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/TemplatePutRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Admin Put Template",
|
||||
"tags": [
|
||||
"welcome"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/admin/welcome-template/preview": {
|
||||
"post": {
|
||||
"description": "Render arbitrary template content against the live context for the\ncalling admin, without persisting. Used by the /admin/welcome editor's\nPreview button so admins can see their edits before saving.",
|
||||
"operationId": "admin_preview_template_api_admin_welcome_template_preview_post",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "authorization",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Authorization"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/TemplatePreviewRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/WelcomeResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Admin Preview Template",
|
||||
"tags": [
|
||||
"welcome"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/catalog/metrics/{metric_path}": {
|
||||
"get": {
|
||||
"deprecated": true,
|
||||
|
|
@ -9900,6 +10289,67 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/api/welcome": {
|
||||
"get": {
|
||||
"description": "Render the welcome prompt for the calling user. Returns rendered markdown.",
|
||||
"operationId": "get_welcome_api_welcome_get",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The server URL the analyst is bootstrapping against",
|
||||
"in": "query",
|
||||
"name": "server_url",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"description": "The server URL the analyst is bootstrapping against",
|
||||
"title": "Server Url",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "authorization",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Authorization"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/WelcomeResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Get Welcome",
|
||||
"tags": [
|
||||
"welcome"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/auth/admin/tokens": {
|
||||
"get": {
|
||||
"operationId": "admin_list_tokens_auth_admin_tokens_get",
|
||||
|
|
@ -11153,28 +11603,10 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/install": {
|
||||
"/first-time-setup": {
|
||||
"get": {
|
||||
"description": "Public install instructions for the CLI.",
|
||||
"operationId": "install_page_install_get",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "authorization",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Authorization"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "First-time setup wizard. Redirects to login if users already exist.",
|
||||
"operationId": "setup_wizard_first_time_setup_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
|
|
@ -11185,19 +11617,31 @@
|
|||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"summary": "Setup Wizard",
|
||||
"tags": [
|
||||
"web"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/install": {
|
||||
"get": {
|
||||
"description": "Backwards-compat redirect: /install \u2192 /setup (301).",
|
||||
"operationId": "install_redirect_install_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"text/html": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Validation Error"
|
||||
"description": "Successful Response"
|
||||
}
|
||||
},
|
||||
"summary": "Install Page",
|
||||
"summary": "Install Redirect",
|
||||
"tags": [
|
||||
"web"
|
||||
]
|
||||
|
|
@ -11511,8 +11955,26 @@
|
|||
},
|
||||
"/setup": {
|
||||
"get": {
|
||||
"description": "First-time setup wizard. Redirects to dashboard if users already exist.",
|
||||
"operationId": "setup_wizard_setup_get",
|
||||
"description": "Setup instructions for the local agent (CLI + Claude Code).",
|
||||
"operationId": "setup_page_setup_get",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "header",
|
||||
"name": "authorization",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Authorization"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
|
|
@ -11523,9 +11985,19 @@
|
|||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Setup Wizard",
|
||||
"description": "Validation Error"
|
||||
}
|
||||
},
|
||||
"summary": "Setup Page",
|
||||
"tags": [
|
||||
"web"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ def test_install_page_renders_with_server_url(tmp_path, monkeypatch):
|
|||
from fastapi.testclient import TestClient
|
||||
from app.main import app
|
||||
client = TestClient(app)
|
||||
resp = client.get("/install", headers={"host": "agnes.test", "Accept": "text/html"})
|
||||
resp = client.get("/setup", headers={"host": "agnes.test", "Accept": "text/html"})
|
||||
assert resp.status_code == 200
|
||||
assert "agnes.test" in resp.text
|
||||
assert "da auth whoami" in resp.text
|
||||
|
|
|
|||
|
|
@ -41,20 +41,20 @@ def test_parquet_path_is_not_gzipped(isolated_client, tmp_path, monkeypatch):
|
|||
|
||||
|
||||
def test_install_page_is_gzipped(isolated_client):
|
||||
"""/install is HTML above the threshold — gzip should kick in when the
|
||||
"""/setup is HTML above the threshold — gzip should kick in when the
|
||||
client advertises gzip support. TestClient may decompress transparently,
|
||||
so we accept either the header or readable body as proof that the
|
||||
middleware decided to handle the response (i.e. did not skip)."""
|
||||
resp = isolated_client.get("/install", headers={"Accept-Encoding": "gzip"})
|
||||
resp = isolated_client.get("/setup", headers={"Accept-Encoding": "gzip"})
|
||||
assert resp.status_code == 200
|
||||
enc = resp.headers.get("content-encoding", "")
|
||||
# Either we see the encoding on the wire OR TestClient auto-decoded it.
|
||||
assert "gzip" in enc or "install" in resp.text.lower()
|
||||
assert "gzip" in enc or "setup" in resp.text.lower()
|
||||
|
||||
|
||||
def test_no_accept_encoding_means_no_gzip_anywhere(isolated_client):
|
||||
"""Client that doesn't advertise gzip gets uncompressed body."""
|
||||
resp = isolated_client.get("/install", headers={"Accept-Encoding": "identity"})
|
||||
resp = isolated_client.get("/setup", headers={"Accept-Encoding": "identity"})
|
||||
assert resp.status_code == 200
|
||||
assert "gzip" not in resp.headers.get("content-encoding", "")
|
||||
|
||||
|
|
|
|||
|
|
@ -789,7 +789,7 @@ def test_install_page_uses_versioned_wheel_url(monkeypatch, tmp_path):
|
|||
from fastapi.testclient import TestClient
|
||||
from app.main import app
|
||||
client = TestClient(app)
|
||||
resp = client.get("/install", headers={"host": "agnes.test", "Accept": "text/html"})
|
||||
resp = client.get("/setup", headers={"host": "agnes.test", "Accept": "text/html"})
|
||||
assert resp.status_code == 200
|
||||
assert "/cli/wheel/agnes_the_ai_analyst-2.0.0-py3-none-any.whl" in resp.text
|
||||
# The bare alias must no longer appear in the rendered snippet.
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ def app_no_toolbar(monkeypatch, tmp_path, reset_logging_state):
|
|||
@pytest.mark.integration
|
||||
def test_no_toolbar_when_debug_off(app_no_toolbar):
|
||||
client = TestClient(app_no_toolbar)
|
||||
resp = client.get("/setup", follow_redirects=False)
|
||||
resp = client.get("/first-time-setup", follow_redirects=False)
|
||||
if resp.status_code in (302, 401):
|
||||
# Auth redirect — toolbar wouldn't render anyway. The point of this
|
||||
# test is to assert markup ABSENCE; no markup, no failure.
|
||||
|
|
@ -76,7 +76,7 @@ def test_toolbar_html_present_when_debug(app_with_toolbar):
|
|||
client = TestClient(app_with_toolbar)
|
||||
# Try several HTML routes — at least one should respond 200 under
|
||||
# LOCAL_DEV_MODE=1 (auth bypass).
|
||||
for path in ("/dashboard", "/setup", "/login", "/admin/access"):
|
||||
for path in ("/dashboard", "/first-time-setup", "/login", "/admin/access"):
|
||||
resp = client.get(path, follow_redirects=False)
|
||||
if resp.status_code == 200 and "text/html" in resp.headers.get("content-type", ""):
|
||||
body = resp.text.lower()
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ class TestClaudeSetupPreview:
|
|||
"""
|
||||
|
||||
def test_install_preview_visible_for_signed_in_user(self, web_client, admin_cookie):
|
||||
resp = web_client.get("/install", cookies=admin_cookie)
|
||||
resp = web_client.get("/setup", cookies=admin_cookie)
|
||||
assert resp.status_code == 200
|
||||
body = resp.text
|
||||
# Preview card + placeholder token render
|
||||
|
|
@ -243,10 +243,10 @@ class TestClaudeSetupPreview:
|
|||
assert "<will be generated on click>" in body
|
||||
|
||||
def test_install_mcp_card_removed(self, web_client):
|
||||
"""The stale 'Use with Claude Code / MCP' card on /install has been
|
||||
"""The stale 'Use with Claude Code / MCP' card on /setup has been
|
||||
removed — there is no Agnes MCP server today.
|
||||
"""
|
||||
resp = web_client.get("/install")
|
||||
resp = web_client.get("/setup")
|
||||
assert resp.status_code == 200
|
||||
body = resp.text
|
||||
assert "Use with Claude Code / MCP" not in body
|
||||
|
|
|
|||
Loading…
Reference in a new issue