Skip to content

Commit 7d5550d

Browse files
feat(admin-ui): unify edit pages on shared design system and add anchor coverage
- Introduce admin-ui/DESIGN.md with four page templates and shared primitives (PageHeader, SecondaryNav, SectionPane, DangerZone, ModalShell, Status, ConfirmDeleteModal, ConfirmDialog) - Migrate Policy, Role, User, and Attribute Definition edit pages to the T1 sidebar template with consistent save/delete/soft-delete behavior - Add table_relationship and column_anchor entities with migrations, admin handlers, and anchor coverage panel for row_filter policies
1 parent 683fb2f commit 7d5550d

87 files changed

Lines changed: 9787 additions & 975 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
## [0.16.2] - 2026-04-14
10+
### Added
11+
12+
- **[Admin UI] Design standard and shared layout primitives** — new `admin-ui/DESIGN.md` defines four page templates (T1 sidebar detail, T2 single form, T3 list, T4 audit) plus the header / save / destructive / modal rules they share. Eight new primitives land under `src/components/` and `src/components/layout/`: `PageHeader`, `SecondaryNav` + `useSectionParam`, `SectionPane`, `DangerZone` + `DangerRow`, `ModalShell` (with focus trap, Esc, click-outside), `StatusDot` + `StatusChip`, `ConfirmDeleteModal`, and `ConfirmDialog`. Each ships with unit tests. `admin-ui/CLAUDE.md` gained a "Design standard" pointer so future sessions pick up the rules before building a new page.
13+
- **[Admin UI] Every edit page now uses the sidebar template** — Policy, Role, User, and Attribute Definition edit pages all adopted the shared T1 shape pioneered by DataSource, so the admin UI shows a single consistent visual language across resources. Section layouts vary by content: Policy has Details / Assignments / Anchor coverage (row_filter only) / View as code / Activity; Role consolidated its former six tabs into Details / Membership (direct members + parent/child inheritance) / Access grants (datasource access + policy assignments) / Activity; User has Profile / Password / Activity; Attribute Definition has Details / Activity. Save shows a toast and stays on the page (previously Policy, Role, and User each had different auto-navigate quirks). All edit pages carry a danger zone with typed-name delete through the shared `ConfirmDeleteModal`; soft-delete escapes are "Disable instead" for policies (`is_enabled`), "Deactivate instead" for roles and users (`is_active`), and a two-step force-delete for attribute definitions that orphan user values.
14+
- **[Proxy] Transitive column resolution for row filters** — row-filter policies can now reference columns that aren't on the matched table, resolved via admin-curated `table_relationship` + `column_anchor` catalog. Anchors come in two XOR-exclusive shapes: (a) **FK walk** (`relationship_id`), where the rewriter injects an `InnerJoin` to a parent table that carries the column, up to a hardcoded depth of 3; (b) **Same-table alias** (`actual_column_name`), where the rewriter substitutes the column name inside the filter expression with no join — covers the case where one broad policy targets tables whose tenant-isolation column is spelled differently (`tenant_id` on one table, `org_id` on another). Resolution failures (missing anchor, depth > 3, cycle, qualified parent ref, or column missing from scan schema — including alias anchors pointing at a non-existent column) substitute `Filter(lit(false))` with a `column_resolution_unresolved` warn log. See `docs/security-vectors.md` vector 73 for the threat model and `docs/permission-system.md` for the operator-facing description.
15+
- **[Proxy] Admin REST endpoints for relationships and column anchors**`GET/POST /datasources/{id}/relationships`, `DELETE /datasources/{id}/relationships/{rel_id}`, `GET/POST /datasources/{id}/column-anchors`, `DELETE /datasources/{id}/column-anchors/{anchor_id}`, and `GET /datasources/{id}/fk-suggestions` (live `pg_constraint` introspection filtered to single-column FKs whose parent column is a PK or single-column unique). All mutations go through `AuditedTxn` and are tagged on the `admin_audit_log`. The XOR between `relationship_id` and `actual_column_name` is enforced server-side with a clear 422.
16+
- **[Admin UI] "Relationships & column anchors" panel on the datasource edit page** — new `RelationshipsPanel.tsx` lets admins promote live FK suggestions or add relationships manually, and designate column anchors with a "Resolve via" radio for either the FK-walk shape (relationship dropdown scoped to the selected child table) or the same-table alias shape (column name with a datalist of the table's own columns). Long `child → parent` labels stack onto two lines so narrow-container layouts don't clip the action column.
17+
- **[Proxy] Anchor-coverage preview endpoint**`GET /policies/{id}/anchor-coverage` dry-runs the same column-resolution logic the runtime uses. For every `(assigned table × column referenced in the row-filter expression)` it returns a verdict: `on_table`, `anchor_walk` (with FK edge metadata), `anchor_alias`, `missing_anchor` (the silent-deny case), or `missing_column_on_alias_target`. Reuses `RelationshipSnapshot` and `expr_column_names`; no caching needed (sub-millisecond per table after the one-time parse). Returns empty coverage for non-row-filter policy types.
18+
- **[Admin UI] Anchor-coverage warning on the policy edit page** — new `PolicyAnchorCoveragePanel.tsx` renders on `PolicyEditPage` for `row_filter` policies. Green banner when every assigned-table × referenced-column pair resolves; red panel listing each broken pair with a deep link to the datasource page where the missing anchor can be added. Catches the silent-deny failure mode (filter references a column that isn't on the target table and has no anchor → runtime substitutes `Filter(false)`) at edit time instead of when users notice empty result sets. Refetches automatically after save via the existing `policy.version` invalidation; hidden for non-row-filter policy types.
1119

1220
### Changed
1321

22+
- **[Admin UI] Destructive row actions removed from list pages; attribute-definition delete ported to the edit page**`UsersListPage`, `RolesListPage`, `PoliciesListPage`, and `AttributeDefinitionsPage` no longer show a per-row Delete button. Deletion now happens on each resource's edit page through the danger-zone flow (typed-name confirmation + soft-delete escape). The attribute-definition page's custom "force delete on FK-constraint error" flow was ported to the edit page's danger-zone modal: a first Delete attempt may fail with a force-required error, and the button then re-labels itself "Force delete" for a second click. Inline relationship + column-anchor deletes in `RelationshipsPanel` use the new `ConfirmDialog` instead of native `confirm()`.
23+
- **[Admin UI] Create pages use breadcrumb headers and navigate to the new resource's edit page**`DataSourceCreatePage`, `RoleCreatePage`, `PolicyCreatePage`, and `AttributeDefinitionCreatePage` replaced the `← Back to X` button with a `PageHeader` breadcrumb matching the detail-page pattern. DataSource and Policy creation now land on `/{resource}/{newId}/edit` on success instead of the list, so users can immediately continue configuring the thing they just made.
24+
- **[Admin UI] Policy "View as code" is always expanded in its section** — the legacy click-to-expand toggle made sense when the code view was one of several stacked cards on a scroll page, but in the new sidebar layout the user has already clicked "View as code" in the secondary nav to reach the section. The second click is pure friction. The YAML/JSON switcher and Copy button now sit in the section header alongside an always-rendered code block.
1425
- **[Admin UI] Sidebar logo and favicon** — added a `logo.svg` next to the "BetweenRows" brand text in the admin sidebar header, and a `favicon.svg` for the browser tab.
1526
- **[Docs] Public documentation site sidebar restructure** — reorganized the VitePress sidebar into five top-level sections (Start, Concepts, Features, Guides, About), with Policy Types, Template Expressions, and Decision Functions nested under Policies. Deleted standalone reference pages (`reference/policy-types`, `reference/audit-log-fields`, `reference/cli`, `reference/admin-rest-api`, `about/changelog`, `about/license`) — content either folded into the guide it documents or replaced with an external link to the canonical source on GitHub (`LICENSE`, `CHANGELOG.md`).
1627

admin-ui/CLAUDE.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
## Key Versions
44
React 19, Vite 7, Tailwind 4, TanStack Query 5, react-router-dom 7, Vitest 4, @testing-library/react 16, CodeMirror 6 (`@uiw/react-codemirror`)
55

6+
## Design standard
7+
Before building a new page or extending an existing one, read `admin-ui/DESIGN.md`. It defines the four page templates (T1 sidebar detail, T2 single form, T3 list, T4 audit), the header/save/destructive/modal rules, and the shared primitives inventory. Every edit page uses T1. Pick a template first, then follow the rules under it. `DataSourceEditPage` is the T1 reference implementation.
8+
69
## Key Files
710
- `vite.config.ts` — proxies `/api``http://localhost:5435`
811
- `src/api/client.ts` — axios instance with JWT interceptor and 401 redirect
@@ -14,23 +17,27 @@ React 19, Vite 7, Tailwind 4, TanStack Query 5, react-router-dom 7, Vitest 4, @t
1417
- `src/components/RoleMemberPanel.tsx` — Effective member list (direct + inherited with source badges), add/remove for direct members
1518
- `src/components/RoleInheritancePanel.tsx` — Parent/child role management with cycle detection feedback
1619
- `src/components/RoleAccessPanel.tsx` — Checkbox-based role access panel for datasource edit page
20+
- `src/components/PolicyAnchorCoveragePanel.tsx` — Edit-time silent-deny warning on `PolicyEditPage` for `row_filter` policies. Calls `GET /policies/{id}/anchor-coverage` (keyed on `(policyId, version)` so saves auto-refetch via the existing `['policy', policyId]` invalidation); hidden for non-row-filter types. Renders a green banner when every (assigned table × referenced column) resolves cleanly, or a red panel listing each broken `(table, column)` pair with a link to the datasource page where the missing anchor can be added. Rendered inside the `Anchor coverage` section of the policy edit page's T1 sidebar (omitted from the nav for non-row-filter policies).
21+
- `src/components/RelationshipsPanel.tsx` — Datasource edit page panel for admin-curated `table_relationship` + `column_anchor` CRUD. Surfaces live FK candidates from `GET /datasources/{id}/fk-suggestions` (already-added tuples greyed out) and the resolved-column → anchor designations that drive transitive row-filter resolution. The anchor form's "Resolve via" radio picks between **Relationship (FK walk)** — rendered via a dropdown of relationships scoped to the selected child table — and **Same-table alias** — rendered as a text input with a `<datalist>` populated from the child table's discovered columns. Long child→parent labels stack on two lines so Delete buttons stay reachable inside the narrow `max-w-2xl` page container. See `docs/security-vectors.md` vector 73 for the trust model.
22+
- `src/api/catalog.ts` — API client for discovery catalog + `table_relationship` / `column_anchor` / FK suggestions (`listRelationships`, `createRelationship`, `deleteRelationship`, `listFkSuggestions`, `listColumnAnchors`, `createColumnAnchor`, `deleteColumnAnchor`)
23+
- `src/types/catalog.ts` — TypeScript interfaces for catalog + relationships (`TableRelationship`, `CreateTableRelationshipRequest`, `ColumnAnchor`, `CreateColumnAnchorRequest`, `FkSuggestion`)
1724
- `src/components/AuditTimeline.tsx` — Reusable admin audit timeline (used on role/user/policy/datasource detail pages)
1825
- `src/utils/auditBadge.ts` — Shared `actionBadgeClass()` for audit action badge styling (used by AuditTimeline + AdminAuditPage)
1926
- `src/components/PolicyAssignmentPanel.tsx` — Three components: `PolicyAssignmentsReadonly`, `PolicyAssignmentEditPanel` (with scope selector: all/user/role), `DatasourceAssignmentsReadonly`
2027
- `src/components/DecisionFunctionModal.tsx` — Modal for creating/editing decision functions with CodeMirror 6 editors (JS + JSON). Features: `ctx.*`/`config.*` autocomplete (activates on dot), templates (create mode), test panel with client-side pre-check → server WASM test, fire/skip/error badges, shared function warning, optimistic concurrency. Three linters: JS linter (executes function against mock context for syntax + runtime errors, highlights exact error line), JSON linter on config + test context editors (validates JSON.parse, highlights error position).
2128
- `src/components/PolicyForm.tsx` — Policy create/edit form with funnel-framed sections (Policy → Effect → Targets → Decision Function), toggle-based decision function attachment (create new / select existing / edit / detach)
2229
- `src/pages/RolesListPage.tsx` — Paginated list with search, member counts, active/inactive badges
2330
- `src/pages/RoleCreatePage.tsx` — Create form
24-
- `src/pages/RoleEditPage.tsx`Tabbed view (Details, Members, Inheritance, Data Sources, Policies, Activity)
31+
- `src/pages/RoleEditPage.tsx`T1 sidebar edit page. Sections: Details, Membership (direct members + parent/child role inheritance), Access grants (datasource access + policy assignments), Activity. Danger zone offers typed-name delete with a "Deactivate instead" escape via `is_active`.
2532
- `src/pages/AdminAuditPage.tsx` — Centralized admin audit log with filters (resource type, actor, date range)
2633
- `src/types/policy.ts` — TypeScript interfaces for policies, assignments (`PolicyType`, `AssignmentScope`, `TargetEntry`)
2734
- `src/types/role.ts` — TypeScript interfaces for roles, members, audit entries
2835
- `src/types/decisionFunction.ts` — TypeScript interfaces (`DecisionFunctionResponse`, `DecisionFunctionSummary`, `CreateDecisionFunctionPayload`, `UpdateDecisionFunctionPayload`, `TestDecisionFnPayload`, `TestDecisionFnResponse`, `EvaluateContext`, `OnErrorBehavior`, `LogLevel`)
2936
- `src/api/decisionFunctions.ts` — API client: `listDecisionFunctions`, `getDecisionFunction`, `createDecisionFunction`, `updateDecisionFunction`, `deleteDecisionFunction`, `testDecisionFn`
3037
- `src/api/attributeDefinitions.ts` — API client: `listAttributeDefinitions`, `getAttributeDefinition`, `createAttributeDefinition`, `updateAttributeDefinition`, `deleteAttributeDefinition`
3138
- `src/types/attributeDefinition.ts` — TypeScript interfaces (`AttributeDefinition`, `CreateAttributeDefinitionPayload`, `UpdateAttributeDefinitionPayload`, `ValueType`, `EntityType`)
32-
- `src/pages/AttributeDefinitionsPage.tsx` — List attribute definitions with entity type filter, force-delete support
33-
- `src/pages/AttributeDefinitionEditPage.tsx` — Create/edit attribute definitions (exports `AttributeDefinitionCreatePage` and `AttributeDefinitionEditPage`). Uses `AttributeDefinitionForm` component with standard card wrapper + back button layout.
39+
- `src/pages/AttributeDefinitionsPage.tsx` — List attribute definitions with entity-type filter. Edit is the only row action; delete lives on the edit page.
40+
- `src/pages/AttributeDefinitionEditPage.tsx` — Create/edit attribute definitions (exports `AttributeDefinitionCreatePage` and `AttributeDefinitionEditPage`). Create uses the single-form template; edit uses T1 sidebar with sections Details + Activity. Edit page has a danger zone whose typed-name delete falls back to a "Force delete" button when the server returns the `force=true` hint (i.e., users still hold values under this key).
3441
- `src/components/AttributeDefinitionForm.tsx` — Reusable form for attribute definition create/edit (matches `RoleForm`/`DataSourceForm` pattern). Supports value types: string, integer, boolean, list.
3542
- `src/components/UserAttributeEditor.tsx` — Inline editor for user attributes on UserEditPage. Loads attribute definitions to show type-appropriate inputs (text, number, boolean toggle, enum dropdown, tag/chip input for lists, multi-select checkboxes for lists with allowed values). Shows `{user.KEY}` syntax hint per attribute.
3643
- `src/components/ExpressionEditor.tsx` — CodeMirror-based expression editor for filter/mask expressions
@@ -40,7 +47,7 @@ React 19, Vite 7, Tailwind 4, TanStack Query 5, react-router-dom 7, Vitest 4, @t
4047
- `src/components/Layout.tsx` — App shell layout with sidebar navigation
4148
- `src/pages/PoliciesListPage.tsx` — Paginated policy list
4249
- `src/pages/PolicyCreatePage.tsx` — Policy creation page
43-
- `src/pages/PolicyEditPage.tsx`Policy edit page with form + assignment panel
50+
- `src/pages/PolicyEditPage.tsx`T1 sidebar edit page. Sections: Details, Assignments, Anchor coverage (row_filter only), View as code, Activity. Danger zone offers typed-name delete with a "Disable instead" escape via `is_enabled`. Save keeps the user on the page (toast-only); 409 conflicts show an inline banner.
4451
- `src/pages/QueryAuditPage.tsx` — Query audit log viewer
4552
- `src/test/test-utils.tsx``renderWithProviders` (QueryClient + AuthProvider + MemoryRouter)
4653
- `src/test/factories.ts``makeUser`, `makeDataSource`, `makeDataSourceType`, `makeDiscoveredSchema/Table/Column`, `makeDecisionFunction`, `makePolicy`, `makePolicyAssignment`

0 commit comments

Comments
 (0)