Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ This creates `build.zip` with a top-level `dist/` directory, including `dist/ind
- Alerts: searchable, sortable lists with tag filters and 25-row pagination; PromQL and SQL threshold creation/editing, query previews without notifications, hold duration, runtime instances and delivery errors, evaluate/enable/mute/duplicate/delete actions, and Slack/Webhook/Alertmanager targets with masked secrets. PromQL creation requires explicit server capability; existing builder rules are read-only.
- Datasets: search, create logs/metrics/traces with an optional custom partition, manage information and ingestion/storage statistics, inspect schema, explore, set/clear retention, configure/disable hot tier when the server enables it, and delete with typed name confirmation. Time partitions cannot be set at creation. Static schema upload, tags/labels, existing partition editing and index management are not supported.
- Dashboards: server-backed lists and detail routes, search/tags/favourites/sort/pagination, ownership, full-document saves with conflict resolution, SQL and PromQL tiles, six variable types, URL time ranges and selections, classic import/export, and explicit migration of old browser-local dashboards. Demo dashboards use independent in-memory state per client.
- Team: native users and one-time passwords, role and privilege management, default OIDC role configuration, provider group mappings and role provenance, and API key creation/copy/deletion. Search and 25-row pagination apply to every tab; authorization stays with the backend.
- Team: native users and one-time passwords, role and privilege management, default OIDC role configuration, provider group mappings and role provenance, and API key creation/copy/deletion with configurable expiration from 30 minutes to 90 days or no expiration. New UI-created keys default to 90 days; existing keys keep their original lifetime. Search and 25-row pagination apply to every tab; authorization stays with the backend.
- Agents: schema-discovered GenAI telemetry datasets, bounded recent records, time ranges, local search, record inspection and trace handoff.
- Cluster: registered node reachability and resource metrics with independent request states; read-only. Errors: paginated OTLP error spans and trace handoff; classic grouped issue analytics remain unavailable.
- Workspace setup: `/onboarding` discovers accessible datasets and opens the matching explorer; `/new` explains server scope and provides connection/setup actions. Hosted workspace creation is not exposed by this server.
Expand Down
46 changes: 26 additions & 20 deletions frontend/docs/api-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,32 @@ Logs opt into row/column virtualization for limits above 100 or more than 20 vis

Team requests share the cookie session and `credentials: 'include'`. The administrative APIs use `/api/v1`, while API keys use the separate Prism prefix confirmed by `modal/server.rs`.

| Client method | HTTP endpoint | Success response |
| ------------------------------ | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `about(signal?)` | `GET /api/v1/about` | Normalizes `oidcActive`, `capabilities.oidcRoleMapping`, `capabilities.oidcRoleSync`; missing fields become false |
| `listUsers(signal?)` | `GET /api/v1/users` | Users with direct privilege arrays and group-inherited roles; accepts `groupRoles`/`userGroups` and snake_case equivalents; nullable email/picture become absent |
| `createUser(username, roles)` | `POST /api/v1/user/{encoded username}` | Bare JSON role-name array; returns a **text/plain** one-time password |
| `deleteUser(id)` | `DELETE /api/v1/user/{encoded id}` | Checked success; response body is not needed |
| `addUserRoles(id, roles)` | `PATCH /api/v1/user/{encoded id}/role/add` | Bare JSON role-name array; checked success |
| `removeUserRoles(id, roles)` | `PATCH /api/v1/user/{encoded id}/role/remove` | Bare JSON role-name array; only manual OAuth grants are removable |
| `resetPassword(id)` | `POST /api/v1/user/{encoded id}/generate-new-password` | **text/plain** one-time password; native users only |
| `userRoleSources(id, signal?)` | `GET /api/v1/user/{encoded id}/role` | Roles contain `{actions, roleType}`; group roles and optional OAuth `oidc` provenance; issuer and grant lists may be null |
| `listRoles(signal?)` | `GET /api/v1/roles` | Object of privilege arrays; internal roles excluded |
| `putRole(name, privileges)` | `PUT /api/v1/role/{encoded name}` | Bare JSON privilege array replaces the full role; an empty array is valid |
| `deleteRole(name)` | `DELETE /api/v1/role/{encoded name}` | Empty success; default roles and roles in use cannot be deleted |
| `defaultRole(signal?)` | `GET /api/v1/role/default` | JSON string or null |
| `setDefaultRole(name)` | `PUT /api/v1/role/default` | JSON string body; empty success |
| `clearDefaultRole()` | `DELETE /api/v1/role/default` | Empty success |
| `listApiKeys(signal?)` | `GET /api/prism/v1/apikeys` | Array of `{keyId, apiKey, keyName, roles, createdBy, createdAt, modifiedAt}`; `apiKey` is masked as `****` plus the last four characters |
| `createApiKey(name, roles)` | `POST /api/prism/v1/apikeys` | Body `{keyName, roles}`; returns the same shape with the full generated key |
| `getApiKey(id, signal?)` | `GET /api/prism/v1/apikeys/{encoded id}` | Full key, retrieved only on an explicit copy action |
| `deleteApiKey(id)` | `DELETE /api/prism/v1/apikeys/{encoded id}` | Checked success; backend returns `{keyId, keyName, message}` |
| Client method | HTTP endpoint | Success response |
| ---------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `about(signal?)` | `GET /api/v1/about` | Normalizes `oidcActive`, `capabilities.oidcRoleMapping`, `capabilities.oidcRoleSync`; missing fields become false |
| `listUsers(signal?)` | `GET /api/v1/users` | Users with direct privilege arrays and group-inherited roles; accepts `groupRoles`/`userGroups` and snake_case equivalents; nullable email/picture become absent |
| `createUser(username, roles)` | `POST /api/v1/user/{encoded username}` | Bare JSON role-name array; returns a **text/plain** one-time password |
| `deleteUser(id)` | `DELETE /api/v1/user/{encoded id}` | Checked success; response body is not needed |
| `addUserRoles(id, roles)` | `PATCH /api/v1/user/{encoded id}/role/add` | Bare JSON role-name array; checked success |
| `removeUserRoles(id, roles)` | `PATCH /api/v1/user/{encoded id}/role/remove` | Bare JSON role-name array; only manual OAuth grants are removable |
| `resetPassword(id)` | `POST /api/v1/user/{encoded id}/generate-new-password` | **text/plain** one-time password; native users only |
| `userRoleSources(id, signal?)` | `GET /api/v1/user/{encoded id}/role` | Roles contain `{actions, roleType}`; group roles and optional OAuth `oidc` provenance; issuer and grant lists may be null |
| `listRoles(signal?)` | `GET /api/v1/roles` | Object of privilege arrays; internal roles excluded |
| `putRole(name, privileges)` | `PUT /api/v1/role/{encoded name}` | Bare JSON privilege array replaces the full role; an empty array is valid |
| `deleteRole(name)` | `DELETE /api/v1/role/{encoded name}` | Empty success; default roles and roles in use cannot be deleted |
| `defaultRole(signal?)` | `GET /api/v1/role/default` | JSON string or null |
| `setDefaultRole(name)` | `PUT /api/v1/role/default` | JSON string body; empty success |
| `clearDefaultRole()` | `DELETE /api/v1/role/default` | Empty success |
| `listApiKeys(signal?)` | `GET /api/prism/v1/apikeys` | Array of `{keyId, apiKey, keyName, roles, createdBy, createdAt, modifiedAt, expiresAt}`; `apiKey` is masked as `****` plus the last four characters |
| `createApiKey(name, roles, ttlSeconds?)` | `POST /api/prism/v1/apikeys` | Body `{keyName, roles, ttlSeconds?}`; returns the same shape with the full generated key |
| `getApiKey(id, signal?)` | `GET /api/prism/v1/apikeys/{encoded id}` | Full key, retrieved only on an explicit copy action |
| `deleteApiKey(id)` | `DELETE /api/prism/v1/apikeys/{encoded id}` | Checked success; backend returns `{keyId, keyName, message}` |

API key expiry is optional: `ttlSeconds` must be an integer from 1800 (30 minutes) through 7776000 (90 days), inclusive; invalid values return 400. Omitted or null TTL means no expiration. Responses include `expiresAt`, a UTC timestamp or null; the frontend also accepts an omitted field from older servers for legacy records. Finite lifetimes are elapsed durations from creation, independent of time zones or daylight saving changes. The creation form defaults to 90 days and offers presets, custom minutes/hours/days, and No expiration. TTL cannot be edited after creation. Expired keys remain listed and deletable; their names remain reserved until deletion.

Both `X-API-KEY` and Bearer authentication return 401 once server time reaches `expiresAt`, and key validation reports false. Temporary API-key sessions, including those created for intra-cluster requests, cannot extend the deadline or refresh as OAuth sessions. Intra-cluster requests on behalf of an expired key return 401. Requests authorized before expiry may finish on the node that authorized them. Expiration changes credential validity, not its assigned roles or an administrator's authority to create other credentials.

Upgrade **every serving and metadata-writing node before creating finite API keys**. Older nodes ignore expiration and can strip `expiresAt` when rewriting stored user metadata. Rolling back to those versions with finite keys in service is unsafe. Keep node clocks synchronized.

All reads validate successful response shapes. Password methods read text, reject empty responses and never JSON-parse successful passwords. Every Team method reports 401 through `onUnauthorized`; 403 remains a visible permission state. Text errors, including root-admin protections, OIDC-managed grants and roles in use, appear in the active dialog. JSON `non_existent_roles` and `roles_not_assigned` errors become readable role lists even when the response advertises text/plain.

Expand Down
14 changes: 14 additions & 0 deletions frontend/docs/validation-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

This record distinguishes the current worktree's completed checks from the supplied earlier research. Original Prism source was not recovered; visual comparisons use actual rendered v3.2.4 assets and an independently implemented frontend. No deployed service or the reference checkout was modified.

## API key expiration (2026-10-12)

API keys support an optional fixed lifetime from 30 minutes through 90 days, or no expiration. The creation form defaults to 90 days and offers presets and custom minutes/hours/days. The server validates integer `ttlSeconds`, persists UTC `expiresAt`, rejects expired keys through both supported headers and the validation endpoint, and prevents temporary sessions from extending the deadline. Existing keys remain nonexpiring. Expired keys stay visible and deletable; the table updates their status without requiring a refresh.

Five Sol 6.1 workstreams implemented the backend, client/demo contracts, UI, browser regressions and independent Rust/server validation. Three Opus 5.5 rounds covered direction, integrated code quality and final review. The final review approved the changes with no actionable findings. Review fixes add a visible warning if an older server ignores a finite TTL, accept decimal durations such as 1.1 hours without accepting fractional seconds, preserve Basic-auth precedence for mixed headers, and avoid a duplicate user scan during API-key authentication.

- TypeScript and the production frontend build pass; **2,200 unit tests pass across 89 files**, with one existing skipped test.
- **50 browser tests pass**: 11 API-key expiration regressions and 39 existing Team cases. They cover exact POST durations, boundaries, no expiration, missing/null legacy responses, automatic expiry, deletion, native-user controls and mobile accessibility. Chromium runs used the shared Playwright lock and an isolated port so another checkout's Vite server could not answer them.
- Formatting and diff checks pass. The custom-duration form was also inspected in the collaborative browser.
- **20 unique focused Rust tests pass**, including malformed persisted expiry, inclusive TTL bounds, exact expiry instants, legacy deserialization and response masking. The final server build passes.
- **49 live HTTP/persistence assertions pass** against the final build on a disposable single-node server: 30-minute/90-day/null/omitted creation, invalid TTL rejection, both authentication headers, key validation, expiration after restart and retained list/detail records. Expiry was simulated by changing only temporary persisted metadata while the server was stopped; legacy missing-expiry records stayed active. The server was stopped after verification. This did not exercise a real 30-minute wait, multiple tenants, or a cluster upgrade.

The [Mayfly implementation and validation record](https://mayfly-celld.tail835e0d.ts.net/w/46ncdGypknzAj7dnRkJ8bQ?page=4152b588-95ed-4b57-a675-0f7c9d24a609#J63UryiLZKvb1G5vnY7RVQhXPDhXy-GpIhjXiCB_TzM) sits under the existing Team page. No production deployment was performed. All serving and metadata-writing nodes must be upgraded before creating finite keys: old nodes ignore expiration and may remove the field when rewriting metadata. Expiration does not interrupt requests already authorized or reduce an administrator's existing authority to create other credentials.

## Logs explorer quality follow-up (2026-10-12)

Opus 5.5's quality review identified false empty results when queries were blocked and stale filter notices. Both are fixed. The follow-up centralizes persisted explorer state in a reducer and shares compiled criteria between row and histogram queries while preserving captured bounds. Shared filter helpers keep group additions distinct from whole-expression narrowing. Both table modes now share sorting, labels, summary formatting and severity badges; histogram rendering consumes the aggregate's exact geometry. Demo implementations are separated from transport guards, saved-view metadata is typed and decoded once per response, and context-field selection is consistent. Ordinary demo SQL retains its 100-row default; aggregate inputs remain unlimited.
Expand Down
Loading
Loading