diff --git a/CHANGELOG.md b/CHANGELOG.md index 82fa0ed..7ea27f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C ### Added -- Admin-editable banner on `/setup` page — admins can author a Jinja2/HTML banner displayed above the auto-generated bootstrap commands. Empty by default (no banner shown). Edit at `/admin/setup-banner`. Endpoints: `GET /api/admin/setup-banner` (returns content + audit), `PUT` to set, `DELETE` to clear, `POST /api/admin/setup-banner/preview` for live preview. Useful for org-specific notes: VPN requirements, support channel, data classification, platform prerequisites. +- Admin-editable banner on `/setup` page — admins can author a Jinja2/HTML banner displayed above the auto-generated bootstrap commands. Empty by default (no banner shown). Edit at `/admin/setup-banner`. Endpoints: `GET /api/admin/setup-banner` (returns content + audit), `PUT` to set, `DELETE` to clear, `POST /api/admin/setup-banner/preview` for live preview. Useful for org-specific notes: VPN requirements, support channel, data classification, platform prerequisites. See `docs/setup-banner.md` for the full placeholder reference and security notes. - DuckDB schema v22: `setup_banner` singleton table for the per-instance banner. Auto-migration v21→v22 on first start. - Customizable analyst welcome prompt (`CLAUDE.md` generated by `da analyst setup`). Default ships at `config/claude_md_template.txt` (now Jinja2 syntax). Admins override per instance via the `/admin/welcome` editor or `PUT /api/admin/welcome-template`. New endpoint `GET /api/welcome` returns the rendered prompt for the calling user, with `marketplaces` filtered by RBAC. See `docs/welcome-template.md` for the full placeholder reference. - `POST /api/admin/welcome-template/preview` — renders arbitrary template content against the calling admin's live context without persisting. Backs the editor's Preview button. diff --git a/docs/setup-banner.md b/docs/setup-banner.md new file mode 100644 index 0000000..4aa11f7 --- /dev/null +++ b/docs/setup-banner.md @@ -0,0 +1,94 @@ +# Setup page banner + +The setup banner is a block of HTML (or plain text) shown **above** the +auto-generated bootstrap commands on the `/setup` page. Use it for +org-specific operational notes that analysts need before they install the +client: VPN requirements, support channel, data-classification policy, +platform prerequisites, etc. + +The banner is empty by default — no content is shown until an admin sets one. + +## How to edit + +- **Admin UI:** `/admin/setup-banner` — split-pane editor with a placeholder + cheatsheet and a live HTML preview. Click **Save banner** to persist, + **Remove banner** to clear. +- **REST API:** + - `GET /api/admin/setup-banner` — returns `{content, updated_at, updated_by}`. + `content` is `null` when no banner is set. + - `PUT /api/admin/setup-banner` with body `{"content": "..."}` — validates + Jinja2 syntax and stores the banner. + - `DELETE /api/admin/setup-banner` — clears the banner; `/setup` shows no + banner until one is set again. + - `POST /api/admin/setup-banner/preview` with body `{"content": "..."}` — + renders arbitrary content against the calling admin's context without + persisting. Backs the editor's live preview. + +The banner lives in `system.duckdb` (table `setup_banner`, singleton row id=1). + +## Available placeholders + +| Placeholder | Type | Notes | +|---|---|---| +| `instance.name` | string | `instance.name` in `instance.yaml` | +| `instance.subtitle` | string | `instance.subtitle` in `instance.yaml` | +| `server.url` | string | full origin of the Agnes server | +| `server.hostname` | string | host part only (no port or path) | +| `user.email` | string | logged-in user, or `null` for anonymous visitors | +| `user.name` | string | logged-in user display name | +| `user.is_admin` | bool | `true` when the visitor is in the Admin group | +| `now` | datetime (UTC, tz-aware) | server time at render | +| `today` | string (`YYYY-MM-DD`) | server date at render | + +> **`user` may be `null`** — `/setup` is partly public (anonymous visitors +> get the install one-liner). Always guard user-specific placeholders: +> +> ```jinja2 +> {% if user %}Welcome back, {{ user.name }}!{% endif %} +> ``` + +## Autoescape semantics + +The Jinja2 environment runs with `autoescape=True`, which means template +**variable output** (`{{ ... }}`) is HTML-escaped automatically. Literal HTML +in the template source is passed through unchanged — that is how the banner +outputs `
` tags, ``, etc. + +To output a literal `<` or `&` from a variable, use the `| safe` filter only +when you are certain the value is trusted: + +```jinja2 +{# Safe — admin-authored constant: #} +{{ "VPN required" | safe }} + +{# Dangerous — never pipe user-controlled values through | safe: #} +{{ user.name | safe }} {# do NOT do this #} +``` + +## Security note + +Admin-authored banner content is rendered for **all `/setup` visitors**, +including anonymous users. As a defense-in-depth measure, inline `