191 lines
9.5 KiB
Markdown
191 lines
9.5 KiB
Markdown
# 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 <http://127.0.0.1:3000/login> to log in. The admin panel is at
|
|
<http://127.0.0.1:3000/admin>, roles and permissions are at
|
|
<http://127.0.0.1:3000/permissions>, and analytics remains at
|
|
<http://127.0.0.1:3000>.
|
|
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 <html>, <head>, 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
|
|
permissions/ permissions/
|
|
common/ tabs.html the section switcher
|
|
loader logic state ui
|
|
roles/ roles/
|
|
mod loader logic state ui roles.html
|
|
users/ users/
|
|
mod loader logic state ui users.html
|
|
grants/ grants/
|
|
mod loader logic state ui grants.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` |
|
|
| `GET /permissions` | `pages/permissions/` | redirect to the first open section |
|
|
| `GET /permissions/roles` | `pages/permissions/roles/` | `roles.html` |
|
|
| `GET /permissions/users` | `pages/permissions/users/` | `users.html` |
|
|
| `GET /permissions/grants` | `pages/permissions/grants/` | `grants.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.
|
|
|
|
## Permissions
|
|
|
|
Permissions are a nav section of their own, not a page inside the admin panel:
|
|
managing people is a different job from designing tables, and the two are held
|
|
by different accounts. The section is three pages, one per decision, with a tab
|
|
bar between them:
|
|
|
|
- **`/permissions/roles`** — which roles exist, what they inherit from, how many
|
|
people hold each, and removing the ones nobody holds. Creating a role can hand
|
|
it starter access (`Read everything` or `Read and write everything`) in the
|
|
same submission.
|
|
- **`/permissions/users`** — who holds which role, and password resets. Only
|
|
users whose role the caller outranks carry controls.
|
|
- **`/permissions/grants`** — what a role may do, as one matrix per profile:
|
|
objects down the side, `read / insert / update / delete` across the top. A
|
|
cell is a grant held directly (click to revoke), an inherited one (changed on
|
|
the parent role), or an empty one (click to grant).
|
|
|
|
Every button on the grants page — one cell, one row's `All`, a profile's
|
|
`Full access`, the page's `Full access to everything` — posts the same form to
|
|
`POST /permissions/grants/apply`, and differs only in the list of
|
|
`object|action` pairs it carries. The lists are built in Rust, so the server is
|
|
never asked to work out what "everything" meant, and the shortcuts grant the
|
|
wildcard objects (`data:*`, `data:<profile>/*`, `journal:*`, `ecb:*`), which
|
|
keeps them covering profiles and tables created later. `ecb:*` is read-only
|
|
everywhere, because the server writes it.
|
|
|
|
The table-definition workspace still edits one table's grants in place; it posts
|
|
to the same endpoint and returns to itself.
|
|
|
|
## 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.
|