Files
komp_ac/web
2026-08-15 15:00:01 +02:00
..
2026-08-15 15:00:01 +02:00
2026-08-15 15:00:01 +02:00
2026-08-14 18:46:31 +02:00
2026-08-15 15:00:01 +02:00
2026-08-14 20:35:18 +02:00

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:

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:

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: 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.