# Web admin and analytics An Axum SSR/HTMX web frontend for the existing komp_ac gRPC backend. It provides the browser version of the client admin panel and the ECharts analytics view. The browser never connects to gRPC directly: Axum authenticates with an HTTP-only cookie, creates typed Tonic requests, and returns full HTML pages or HTMX fragments. The backend remains the authority; this crate does not add another HTTP API to it. ## Run The web UI is a library started by the main server; it is no longer a standalone binary. From the parent workspace, start the server normally: ```sh cargo run -p server -- server ``` Open to log in. The admin panel is at and analytics remains at . The access token is kept in an HTTP-only cookie. The default gRPC endpoint is `http://[::1]:50051`. Both addresses can be changed: ```sh ANALYTICS_GRPC_ENDPOINT=http://127.0.0.1:50051 \ LISTEN_ADDRESS=127.0.0.1:8080 \ cargo run -p server -- server ``` ## Structure HTML lives in `.html` files, never in Rust strings. Templates are [askama](https://docs.rs/askama): compiled at build time, type-checked against their struct, and HTML-escaped automatically. **`templates/` mirrors `src/` directory for directory.** The Rust for a page and the markup for that page live at the same path in the two trees, and `src/pages/` itself matches the client crate — the same page names, the same `loader / logic / state / ui` split. A `#[template(path = …)]` is therefore the module's own path, which is also where the file is in `templates/`. ``` src/ templates/ ui/ ui/ mod.rs base.html , , navbar (Nav, Alert, Notice, navbar.html the one navbar ErrorPage, render) form_page.html admin form card layout alert.html error/success macros alert_fragment.html standalone POST reply notice.html one-line inline notice error.html full-page load failure pages/ pages/ admin/admin/ admin/admin/ mod loader logic state ui admin.html workspace.html add_table/ add_table/ mod loader logic state ui add_table.html add_logic/ add_logic/ … add_logic.html add_validation/ add_validation/ … field.html rule.html set.html shared_fields.html analytics/ analytics/ … analytics.html catalog.html profile_options.html query_result.html import_export/import/ import_export/import/ … import.html import_export/export/ import_export/export/ … export.html login/ login/ mod logic state ui login.html static/app.css the only stylesheet, at /static/app.css ``` Every full page extends `ui/base.html`, which links the stylesheet and renders `ui/navbar.html`, so the navbar is on every page without a page opting in. The six admin forms extend `ui/form_page.html` for the back link and card chrome on top of that. Each endpoint maps to exactly one template. The Rust side of the mapping is the `#[derive(Template)]` struct in the page's `ui.rs`; the template names its endpoint in a comment on line 1. | Endpoint | Page directory | Template | | --- | --- | --- | | `GET /` | `pages/analytics/` | `analytics.html` | | `GET /api/profiles` | `pages/analytics/` | `profile_options.html` | | `POST /api/catalog` | `pages/analytics/` | `catalog.html` | | `POST /api/query` | `pages/analytics/` | `query_result.html` | | `GET /login` | `pages/login/` | `login.html` | | `POST /login` | `pages/login/` | sets the cookie, `hx-redirect` | | `GET /admin` | `pages/admin/admin/` | `admin.html` | | `GET /admin/workspace` | `pages/admin/admin/` | `workspace.html` | | `POST /logout` | `pages/admin/admin/` | clears the cookie, `hx-redirect` | | `GET /admin/tables/new` | `pages/add_table/` | `add_table.html` | | `GET /admin/logic/new` | `pages/add_logic/` | `add_logic.html` | | `GET /admin/validation/new` | `pages/add_validation/` | `field.html` | | `GET /admin/validation/rules/new` | `pages/add_validation/` | `rule.html` | | `GET /admin/validation/sets/new` | `pages/add_validation/` | `set.html` | | `GET /admin/import` | `pages/import_export/import/` | `import.html` | | `GET /admin/export` | `pages/import_export/export/` | `export.html` | Every form `POST` answers with `ui/alert_fragment.html`, swapped into the page's `#submission-status`. Analytics errors use the lighter `ui/notice.html`. Inside a page module the split is the same everywhere, and the same as the client: `mod.rs` declares the routes, `loader.rs` calls gRPC and shapes the result, `state.rs` holds the view model and form parsing, `logic.rs` is the handlers, and `ui.rs` binds state to the templates in that directory. ## Admin panel The admin panel mirrors the client workflow with browser-oriented pages: - profile, table, and physical-column browsing; - table creation, including indexes, links, money settings, and row labels; - Steel table-script creation; - field validations, reusable global rules, validation sets, and set application; - browser CSV import with live schema validation and chunked bulk insertion; - CSV downloads for one or multiple tables; - login, role checks, logout, and navigation to analytics. CSV files use the same column-header convention as the client. Multi-table CSV files include a table-name header row before the column-name row. Browser files are read locally and submitted to Axum; the backend is accessed only through the existing `TablesData` gRPC service. ## Analytics The first SQL result column is used for category labels. Bar and line charts use all remaining columns as numeric series, pie uses the second column, and scatter uses the first two columns. Table view displays every returned value. The profile selector is populated automatically through `TableDefinition.GetProfileTree`. Choosing a profile fetches its live public analytics catalog. The sidebar shows tables, columns, types, and links and can create starter queries. Its **LLM schema context** section generates a complete text summary that can be copied into an LLM prompt to request a valid analytics SQL query. HTMX, Alpine.js, and ECharts are loaded from jsDelivr, so the browser needs network access when opening the page. HTMX owns server communication and fragment swaps; Alpine is limited to browser-local editor, clipboard, and chart behavior.