Skip to content

feat(admin-panel): integrated admin panel — WIP (docs + v0 core)#4145

Open
KvizadSaderah wants to merge 35 commits into
constructorfabric:mainfrom
KvizadSaderah:feat/admin-panel-4144
Open

feat(admin-panel): integrated admin panel — WIP (docs + v0 core)#4145
KvizadSaderah wants to merge 35 commits into
constructorfabric:mainfrom
KvizadSaderah:feat/admin-panel-4144

Conversation

@KvizadSaderah

@KvizadSaderah KvizadSaderah commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

WIP / draft for #4144 — integrated admin panel. ~45% complete; opening early for visibility and feedback on the open questions below.

Done

  • Docs (studio templates) under docs/arch/admin-panel/: PRD, DESIGN, ADRs 0001–0004 (placement, frontend=Refine, OpenAPI-first discovery, admin role+context).
  • Backend v0 (real, not mocked):
    • Two-role dev auth stub via config/admin.yaml + make admin (static_tokens: platform-admin-dev-token, tenant-admin-dev-token).
    • GET /account-management/v1/admin/context — principal, tenant, admin mode, capabilities (derived from subject_type); /me unchanged. Unit-tested.
    • Bootstrap seeding fixed (register tenant types + static-idp) so tenant views resolve.
  • Frontend apps/admin-panel/ (React + Refine + Vite + Ant Design): login (2 dev roles), context view (principal/mode/capabilities + enabled gears + non-prod banner), tenants/resource-groups/gears list screens, RFC-9457 error normalization. Dev: npm run dev proxies /cf to make admin.

Not done yet

  • CRUD/custom-action screens (currently read-only).
  • OpenAPI-driven resource discovery (v0 uses a hardcoded registry).
  • Production static-asset serving of the SPA at /cf/admin (dev uses Vite).
  • e2e tests, web-docs page, README update.
  • Reproducible demo-seed script (make admin-seed).

Notes

  • Auth/authz/idp are the framework's NON-PRODUCTION static dev plugins; the role stub is intentional for v0 and clearly marked. Backend remains the authority; nothing in the UI is mocked.

Open questions (for reviewers)

  • Host gear + final shape of the admin-context endpoint.
  • Production representation of admin mode (authz action / token scope / identity field).
  • Whether to add a global GET /tenants list for platform admin.
  • SPA auth flow against a non-auth_disabled deployment.

Part of #4144.

Summary by CodeRabbit

  • New Features
    • Added an integrated Admin Panel available at /cf/admin.
    • Added platform and tenant administrator sign-in with capability-based controls.
    • Added resource management screens for viewing, creating, editing, deleting, and performing custom actions.
    • Added tenant hierarchy browsing with lazy loading.
    • Added an admin context endpoint showing current role, tenant, capabilities, and enabled services.
  • Documentation
    • Added setup instructions, architecture decisions, design documentation, and admin panel guidance.
  • Tests
    • Added browser smoke tests and end-to-end coverage for authentication and admin context behavior.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@KvizadSaderah, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 seconds

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca086037-89f8-4749-aae4-c452042437a8

📥 Commits

Reviewing files that changed from the base of the PR and between 9e359bd and c5e3de4.

📒 Files selected for processing (8)
  • README.md
  • apps/admin-panel/src/pages/TenantTree.tsx
  • apps/admin-panel/src/resources/openapiOps.ts
  • docs/arch/admin-panel/ADR/0002-cpt-admin-panel-adr-frontend-framework.md
  • docs/arch/admin-panel/ADR/0003-cpt-admin-panel-adr-resource-discovery.md
  • docs/arch/admin-panel/ADR/0004-cpt-admin-panel-adr-admin-role-and-context.md
  • docs/arch/admin-panel/DESIGN.md
  • docs/arch/admin-panel/PRD.md
📝 Walkthrough

Walkthrough

Adds an embedded React/Refine admin panel served at /cf/admin, a backend admin-context endpoint, OpenAPI-driven resource discovery, descriptor-based CRUD and custom actions, local admin configuration, documentation, and frontend/backend end-to-end coverage.

Changes

Integrated admin panel

Layer / File(s) Summary
Backend context and SPA delivery
gears/system/account-management/..., gears/system/api-gateway/..., config/admin.yaml, Makefile
Adds authenticated admin-context projection, static SPA serving, admin runtime configuration, development tokens, and make admin workflows.
Declarative resource and OpenAPI discovery
apps/admin-panel/src/resources/*
Defines resource descriptors, declarative resource configuration, field handling, and runtime OpenAPI schema and CRUD-path enrichment.
Frontend shell, authentication, and API providers
apps/admin-panel/src/App.tsx, apps/admin-panel/src/authProvider.ts, apps/admin-panel/src/dataProvider.ts, apps/admin-panel/src/httpClient.ts
Adds routing, bearer-token authentication, cached admin context, UI capability gating, normalized API errors, and Refine data operations.
Admin resource screens and interactions
apps/admin-panel/src/pages/*, apps/admin-panel/src/main.tsx
Adds login/context views, generated resource list/detail/form screens, tenant-tree expansion, custom record actions, and application bootstrap.
Documentation and validation
docs/arch/admin-panel/*, docs/web-docs/build-with-gears/admin-panel.md, apps/admin-panel/tests/*, testing/e2e/gears/admin/*
Documents architecture and operation, and adds browser smoke tests plus backend admin-context E2E tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main change: integrating the admin panel with v0 docs/core work.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@KvizadSaderah
KvizadSaderah force-pushed the feat/admin-panel-4144 branch 2 times, most recently from 232ffab to b0280c0 Compare June 29, 2026 09:40
## Considered Options

- **Option A**: Embed the panel in the gears-rust monorepo; build the SPA into static assets served by the example server under `/cf/admin`.
- **Option B**: Develop the panel in a separate `constructorfabric/` repository; run it as a sidecar via a `make admin` target.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider an idea to store all the static files in a dedicated constructorfabric/repo and have somewhere a pre-built version that can parse a config or openapi spec and be loaded by simple Rust app and make example from somewhere?

I'm not sure it's good to have so much *.ts code in apps/admin-panel, because then we would need to have similar code in other projects, like imagine I have another gears repo project and I want to enable admin panel there as well

For example in Django, you do not need to copy the code of admin panel to every repo but yet you can easily run it for any django project

@KvizadSaderah KvizadSaderah Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update after digging into what the aggregated spec can actually express:

Most of what I thought needed a new mechanism is already discoverable from /cf/openapi.json — field types/required/readOnly (component schemas), custom actions (e.g. POST …/suspend and …/unsuspend are real operations), and tenant-scope (the {tenant_id} path parameter). The panel now derives those directly; 8 of 9 resources already source their fields from the spec.

What's genuinely left is pure presentation — list columns, labels, grouping, confirm/safety level, an action's button label. That's a UX concern, not the API contract, so pushing it into OpenAPI vendor extensions would leak presentation into the API layer. So the plan is a split:

  • OpenAPI → everything API-intrinsic (fields, operations, tenant-scope).
  • A small panel-side config → presentation overrides only — Django's admin.py-style registration, not copied panel code.

This makes the SPA generic with no per-project TS and no framework changes — I'm dropping the hardcoded registry.ts. The x-cf-admin-* extension idea (option b) stays available as a later optimization if something genuinely API-intrinsic turns up, but it isn't needed for this.

The one thing I'd value your steer on is distribution: keep the pre-built panel + a thin Rust loader (mount_admin_spa() already exists) and extract the SPA source to a dedicated constructorfabric/ repo as a follow-up — does that match what you had in mind?

KvizadSaderah added a commit to KvizadSaderah/gears-rust that referenced this pull request Jun 30, 2026
…e panel

Reviewer (constructorfabric#4145 r3495062634) reopened ADR-0001's placement decision: embedding
the SPA's TypeScript under apps/admin-panel forces every gears-rust project to
copy it. Agreed target is a generic, reusable panel driven at runtime by
/cf/openapi.json + gear-emitted metadata, shipped pre-built with zero
per-project TS, eventually extracted to a dedicated constructorfabric/ repo.

Add dated revision notes (2026-06-30) to ADR-0001 (placement), ADR-0003
(discovery promoted from deferred to v1), and DESIGN.md. Original Option A
analysis retained as the v0 record. Metadata transport (config /
x-cf-admin-* extensions / descriptor endpoint) left open pending reviewer.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Add the architecture documentation set for the integrated admin panel
under docs/arch/admin-panel/, following the studio SDLC templates:

- PRD.md: requirements, actors, scope, FR/NFR (v0=p1), interfaces
- DESIGN.md: architecture, component model, admin-context contract, topology
- ADR/0001: embed in monorepo, serve SPA from example server at /cf/admin
- ADR/0002: React + Refine + Vite + Ant Design frontend
- ADR/0003: OpenAPI-first discovery + hardcoded v0 resource registry
- ADR/0004: static two-role stub + dedicated admin-context endpoint

Part of constructorfabric#4144.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Add config/admin.yaml and a `make admin` target to bring up the example
server for admin-panel development. The config enables bearer-token auth
and runs static-authn-plugin in static_tokens mode with two NON-PRODUCTION
dev tokens:

- platform-admin-dev-token -> subject_type=platform_admin (root tenant)
- tenant-admin-dev-token    -> subject_type=tenant_admin  (engineering tenant)

This realizes the v0 admin-role stub (cpt-admin-panel-fr-role-stub) without
touching the security model: the role marker rides on subject_type, which
the admin-context endpoint will read to select the admin mode. IdP is left
unrequired so user management surfaces as unavailable per the PRD.

Verified: GET /me returns the expected subject_type per token; missing or
unknown tokens return 401.

Part of constructorfabric#4144.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Add GET /account-management/v1/admin/context: a richer sibling of /me that
projects the authenticated SecurityContext plus a derived admin mode and
coarse capability hints for the admin panel's startup
(cpt-admin-panel-fr-admin-context, cpt-admin-panel-interface-admin-context).

Mode and capabilities derive from the subject_type role marker set by the
non-production static auth stub (platform_admin -> platform, anything else
-> least-privileged tenant). non_production_auth flags when the stub is in
effect. The backend remains the final authority; capabilities are UI hints
only. Enabled gears are intentionally omitted (the frontend reads them from
the gear orchestrator), keeping this endpoint self-contained in AM. /me is
unchanged.

Pure context projection, no service or I/O. Adds AdminContextDto, the
handler, route registration, and unit tests covering the platform, tenant,
unknown, and absent role-marker cases.

Verified: endpoint returns the expected mode/capabilities per token; missing
or unknown tokens return 401.

Part of constructorfabric#4144.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Add the admin-panel SPA under apps/admin-panel/ (React + Refine + Vite + Ant
Design, TypeScript), realizing the v0 admin shell, current-context view, and
capability-driven navigation (cpt-admin-panel-fr-admin-shell,
cpt-admin-panel-fr-admin-context, cpt-admin-panel-fr-generated-screens).

- authProvider: bearer-token login over the platform auth flow; the login
  screen offers the two non-production dev roles.
- dataProvider: minimal Gears provider over /cf with a hardcoded v0 resource
  map (tenants, resource-groups, types, gears) and RFC-9457 error
  normalization; OpenAPI/registry-driven generation lands later (ADR-0003).
- accessControlProvider: can() maps Refine actions to admin-context
  capability hints (UI gating only; backend stays authoritative).
- ContextView: principal, tenant, admin mode, capabilities, and the enabled
  gears summary (from the gear orchestrator), with a non-production banner.
- TenantList + GenericList: list screens that degrade gracefully on backend
  errors.

Dev: `npm run dev` serves the SPA at :5173 and proxies /cf to the example
server (`make admin`, :8087). Production base is /cf/admin/ for serving from
the example server. Verified: build passes; login fetches admin context;
platform/tenant roles resolve the expected mode and capabilities.

Part of constructorfabric#4144.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
The admin config could not seed the AM platform root: the bootstrap saga
failed first on an unregistered tenant type, then on the noop IdP's missing
provision_tenant, leaving the tenants table empty (every tenant view 404'd).

Fix the admin.yaml stack:
- Register the platform and customer tenant types under
  types-registry.config.entities so the AM tenant-type checker validates the
  bootstrap root type.
- Enable the static-idp-plugin (feature static-idp) and set idp.required so
  the bootstrap saga's provision_tenant step succeeds and the root persists.
- Home the tenant-admin dev token at the seeded platform root (AM assigns
  child tenant ids, so a child id is not stable across a fresh db); the
  platform-vs-tenant distinction is still shown via admin_mode/capabilities.

Verified: bootstrap saga completes, the root is seeded, and both dev roles
get 200 from the tenant children endpoint.

Part of constructorfabric#4144.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Replace the read-only v0 data provider with a metadata-driven resource
registry (apps/admin-panel/src/resources): each manageable object is
described once — paths, fields (with per-view visibility and create-time
immutability), capabilities, safety level, tenant scope, and custom
actions. The data provider and the generated List/Show/Create/Edit
screens are driven entirely off these descriptors, so new resources are
added by appending a descriptor rather than editing the core app.

Covers full CRUD for tenants and resource-groups, read-only types/gears,
and custom actions: tenant suspend/unsuspend/soft-delete and conversion
approve/reject/cancel — each capability-gated in the UI and confirmed for
destructive operations, with the backend remaining the final authority.

Verified live against config/admin.yaml: tenant create/patch/suspend/
unsuspend/delete and the list endpoints for every resource.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
ADR-0003 now notes what shipped (curated descriptor registry + CRUD/
actions) vs what remains deferred (runtime OpenAPI field derivation,
gear-contributed descriptors).

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Add testing/e2e/gears/admin covering GET /account-management/v1/admin/
context: 401 when unauthenticated, the default (untyped) token projecting
least-privileged tenant mode, and the platform_admin / tenant_admin role
markers projecting platform vs tenant mode with the expected capability
sets. Adds two subject_type-typed static tokens to config/e2e-local.yaml
to drive the role projection (homed at the platform root).

Verified locally: make e2e-local E2E_TARGET=testing/e2e/gears/admin/ -> passed.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Add docs/web-docs/guides/admin-panel.md (two admin modes, the admin-
context endpoint, running it locally, and the descriptor-driven resource
model) plus its guides-index entry, and a README 'Admin panel' section
pointing at make admin and the design set.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Add a tenant Users resource (list/create/delete via the account-
management IdP passthrough; no get-one/update on the API, so the
descriptor advertises only those verbs and the UI degrades gracefully).
Add users:read/users:write capability hints to the platform and tenant
admin-context projections so the screen is capability-gated, and extend
the admin-context e2e to assert them.

Verified live: list/create/delete against config/admin.yaml; e2e passes.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Add a Tenant metadata resource (list/read/upsert/delete) for the home
tenant. Metadata entries are keyed by their GTS type_id and upserted via
PUT with the bare value as the body, so the descriptor model gains two
generic hooks: createKeyField (create is an upsert to the entry path with
the id taken from a form field) and bodyField (send only that field's
value as a transparent payload). No new per-resource provider code.

Verified live against config/admin.yaml: register schema -> PUT value ->
list -> get-one -> delete (204).

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Add a local Playwright smoke (apps/admin-panel/tests/smoke.spec.ts) that
signs in as the platform-admin dev role and asserts the descriptor-driven
console renders end-to-end: the admin-context view (mode, capabilities,
non-production banner), navigation to every registered resource's list
screen, and the generated tenant create form. Requires the Gears backend
on :8087 (make admin); Playwright starts the Vite dev server itself. Not
wired into the Rust CI gate (needs a browser + running backend).

Verified: npx playwright test -> 1 passed.
Signed-off-by: KvizadSaderah <kvizad@gmail.com>
The tenant_type is a GTS chain (the API rejects anything not starting
with gts.), but the create form offered a bare text field with no
default, so a plain word like "user" failed with a 400. Pre-fill the
form with the registered customer tenant-type id, and surface the real
HTTP status on ApiError (statusCode) so failed-write notifications show
the actual code instead of "undefined".

Verified in-browser: create now succeeds and the row appears in the list.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Replace the flat tenant list with a hierarchy tree (the v0 "tenants
list/tree/detail" item): the home tenant is the root and each node's
children load lazily via /tenants/{id}/children on expand. Reuses the
tenants descriptor for columns, row actions, and navigation, and is wired
through a per-resource list-override hook (the ADR-0003 override escape
hatch) so other resources keep the generated list.

Verified in-browser: root -> child -> grandchild expand and load; row
actions (view/edit/delete/suspend) render per node.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Surface OAGW egress upstreams and routes as read-only resources (list +
detail). Their bodies are deeply nested (endpoints, auth, headers,
plugins, rate limits, CORS, match rules), so create/update is deferred to
a dedicated config editor rather than a guessed flat form.

Make accessControlProvider descriptor-driven: a verb is gated only when
the descriptor declares a capability for it, so these capability-less
read-only views are ungated while existing gated resources are unchanged.
Add both to the Playwright smoke's navigation set.

Verified live: /oagw/v1/upstreams and /routes return 200; smoke passes.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Replace the tenant create form's free-text Type field with a searchable
select whose options are the registered tenant types (loaded from the
types registry, filtered to the tenant_type GTS prefix). Adds a generic
options hook to the field model + an AsyncSelect control, so any field can
become a backend-backed select.

Also auto-expand the tenant tree's root so children — including a
just-created tenant — are visible immediately.

Verified in-browser: Type shows [platform, customer], create succeeds and
the new tenant appears under the expanded root.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Add an optional api-gateway config flag `admin_spa_dir`: when set, the
gateway serves the built admin-panel SPA (dist/) from that path at
`{prefix}/admin` (e.g. /cf/admin) via tower-http ServeDir.

The SPA is mounted on the top-level router, OUTSIDE the auth/middleware
stack, so the static assets are public (no bearer) while the SPA's own
API calls still hit the prefixed, authenticated routes. Unmatched paths
fall back to index.html with a 200 so client-side routes deep-link and
survive a refresh (`fallback`, not `not_found_service`, to avoid the
SetStatus(404) wrapper). The flag defaults to unset, so all other
deployments are unaffected.

`make admin` now builds the SPA first (npm install && npm run build) and
admin.yaml points `admin_spa_dir` at apps/admin-panel/dist, so the
server serves the panel without a separate vite dev process.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
make admin now builds and serves the SPA from the example server; explain
the admin_spa_dir api-gateway config flag and that static assets are served
outside the auth stack with an index.html fallback for client-side routes.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
…e panel

Reviewer (constructorfabric#4145 r3495062634) reopened ADR-0001's placement decision: embedding
the SPA's TypeScript under apps/admin-panel forces every gears-rust project to
copy it. Agreed target is a generic, reusable panel driven at runtime by
/cf/openapi.json + gear-emitted metadata, shipped pre-built with zero
per-project TS, eventually extracted to a dedicated constructorfabric/ repo.

Add dated revision notes (2026-06-30) to ADR-0001 (placement), ADR-0003
(discovery promoted from deferred to v1), and DESIGN.md. Original Option A
analysis retained as the v0 record. Metadata transport (config /
x-cf-admin-* extensions / descriptor endpoint) left open pending reviewer.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
ADR-0003 (revised): start shrinking the hand-curated registry by reading
the gateway-aggregated /cf/openapi.json at boot and deriving each resource's
field types, required, and readOnly from its component schema. The curated
descriptor now carries only what OpenAPI can't express (visibility, labels,
relations, the GTS type select, create-only, tag render override); derived
facts fill the rest, and derived-only fields are appended.

- resources/openapi.ts: pure schemaToFields mapper (utoipa shape: uuid/
  date-time formats, ["T","null"] nullable, root required[], readOnly,
  $ref/object/array -> json), cached spec fetch, idempotent best-effort
  enrichRegistry run from the auth gate (login/check) before any render.
- types.ts: optional `schema` anchor on ResourceDescriptor.
- registry.ts: tenants -> TenantDto, resource-groups -> GroupDto; drop
  redundant per-field type/required/readOnly, keep presentation overrides.

Verified against the live aggregated spec: TenantDto/GroupDto resolve and
map correctly; typecheck + production build green. (Schema names are the
runtime utoipa DTO names, e.g. GroupDto, not the stale per-gear doc names.)

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
The Types/GTS get-one endpoint keys on the GTS id; passing the internal
uuid 404s ("no entity with GTS ID: <uuid>") and React Query retries it in a
loop. Set idField to gts_id and url-encode it in the get-one path.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
mount_admin_spa fell back to index.html (200) for every unmatched path,
including missing hashed /assets/*. A browser holding a stale cached
index.html then requested a removed asset hash, got the HTML shell as a
"module script", failed the MIME check, and rendered blank.

Fix: 404 missing /assets/* instead of returning the shell; serve the shell
with Cache-Control: no-cache; and route the entry-point directory request
through the fallback (append_index_html_on_directories(false)) so even
`/admin/` carries no-cache rather than being served straight from disk.
The cached shell can no longer keep pointing at an asset hash a rebuild
dropped. Hashed assets stay immutable and disk-streamed.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
…sources

Roll the runtime OpenAPI field-discovery (introduced for tenants/resource-
groups) onto the remaining resources whose list/detail DTO has a named
component schema: conversions (OwnConversionRequestDto), users (UserDto),
tenant-metadata (TenantMetadataEntryDto), types (GtsEntityDto), egress
upstreams (UpstreamResponse) and routes (RouteResponse). Eight of nine
resources now source field type/required/readOnly from the gateway-aggregated
spec; only gears stays hand-curated (its list returns an unnamed Vec element
with no component schema).

Each descriptor keeps only the presentation the spec can't express (tag
render, labels, list visibility, create-only, relations); pure schema-
duplicate field entries are dropped and reappear via derivation. This is the
genericity step the reviewer asked for: in another gears-rust project the
field set comes from that project's own OpenAPI, with no hand-written field
TS. Net registry LOC is flat (anchor comments offset the removed entries);
the win is field truth moving to the API, not line count.

Verified against the live aggregated /cf/openapi.json (all eight schema
names resolve) and the merge result (every dropped field reappears, no
orphaned curated field); typecheck + production build green.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
…-0003)

Re-examining what the aggregated /cf/openapi.json expresses closes the
previously-open metadata-transport question without a new backend mechanism.
Split discovery by the nature of each fact:

- API-intrinsic (field types/required/readOnly, CRUD verbs, custom actions
  such as POST .../suspend, tenant-scope from the {tenant_id} path param) is
  derived from OpenAPI at runtime.
- Presentation-only (list columns, labels, grouping, confirm/safety level,
  action labels, irregular-list hints) lives in a small panel-side config —
  Django admin.py-style registration, not copied panel code. Encoding it in
  OpenAPI vendor extensions would leak presentation into the API contract.

Consequence: the SPA becomes fully generic with no per-project TypeScript and
no framework changes; the hardcoded registry.ts is dropped. The x-cf-admin-*
vendor-extension idea (Option-B transport) is retained only as a later
optimization, not a prerequisite. Distribution (pre-built artifact + thin
loader, later extracted to a dedicated repo) is tracked in ADR-0001.

The op-derivation algorithm was validated against the live aggregated spec for
all nine resources; the edge cases it surfaced (irregular tenant list via
/children, PATCH-status conversion actions, read-only types despite a POST
route, POST-only-leaf action detection) map exactly onto the config surface
above.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
… paths

Resource routes are API-intrinsic, so derive them from the aggregated
/cf/openapi.json instead of hand-writing a path builder per resource
(ADR-0003 concern-split). Each descriptor now declares a `basePath` (its
collection path in the spec) and the standard list/one/create/update/remove
paths are built at boot from the operations the spec actually exposes; the
API is the single source of truth for which verbs exist.

New: resources/openapiOps.ts — `deriveOps(spec, basePath)` (item path =
base/{param}; list/create on the collection, one/update/remove on the item;
custom actions detected as POST-only leaves), a `resolvePath` that fills the
trailing record id and any earlier tenant param from context, and
`buildPaths` applying list overrides + verb suppression.

Descriptors keep only what the spec can't express (presentation + policy):
- `listPath` for the irregular tenants list (home-tenant `/children`);
- `suppressVerbs` where policy offers less than the API (conversions edit
  state via actions, not a generic form); a `read-only` safety level
  suppresses writes automatically (types, egress upstreams/routes);
- custom actions (suspend/unsuspend, approve/reject/cancel) stay curated —
  their label/safety/confirm/visibility are presentation, not API contract.

Registry resolution moved to a blocking bootstrap in main.tsx: the spec is
public, so it is fetched and the registry resolved (paths + fields) before the
App module evaluates its route/nav tables. dataProvider reads paths via a
`resourcePaths()` accessor; route/button visibility optional-chains on the
resolved paths.

No framework changes and no per-project TypeScript for the wiring — adding a
resource in another gears project is a descriptor with a basePath plus a few
presentation lines. registry.ts drops the hand-written path functions
(354->335 LOC, and the removed lines are the actual per-resource logic).

Verified live against config/admin.yaml on :8087 via the Playwright smoke:
login, context, all nine resource lists render through the derived paths
(including tenant-scoped /tenants/{home}/conversions|users|metadata, which
exercises the resolver's tenant substitution), and the tenant create form
renders with its async type select. tsc + production build green. Fixed a
stale smoke assertion (getByLabel on an async Select that carries no label
association) to assert the rendered combobox + loaded default.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Vite sets BASE_URL to "/cf/admin/" (trailing slash) and the router used it
verbatim as the BrowserRouter basename. react-router v6 does not match a
no-trailing-slash location ("/cf/admin") against a trailing-slash basename,
so hitting the panel at /cf/admin (the natural URL) rendered a blank page with
no console error; only /cf/admin/ worked. Strip the trailing slash from the
basename so both forms match.

Verified headless against the prod-served build on :8087: /cf/admin,
/cf/admin/, and a deep route (/cf/admin/tenants) all render and redirect to
login. (Note: the api-gateway caches the SPA shell in memory, so a server
restart is required to pick up a rebuilt dist — asset hashes change.)

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
The shell declared no icon, so browsers issued the default /favicon.ico
request, which resolves to the origin root (outside the /cf/admin base) and
returns 404 — a spurious console error on every load. Add an inline SVG
data-URI favicon so no separate request is made.

Verified headless against the prod-served build: the panel loads at /cf/admin
with zero responses >= 400.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Complete the concern-split (ADR-0003): per-resource registration is now DATA,
not TypeScript. src/resources/admin.config.json declares each resource; a
generic interpreter (configLoader.ts) turns it into the runtime descriptors,
building the few behavioral bits declaratively — path/list templates with
{tenant}/{id} placeholders, static-body actions with a visibleWhen predicate,
create-form defaults, and field option loaders referenced by name
(optionsSource -> a reusable loader, not per-resource code).

registry.ts drops from ~336 hand-written lines to ~46 (config import + build +
icons). The API-intrinsic parts (field types, CRUD verbs, tenant-scope) still
fill in at boot from the aggregated OpenAPI spec. Net: registering an admin
resource — here or in another gears-rust project — is a JSON edit with zero
TypeScript, which is the Django-admin-style genericity the reviewer asked for.

Behavior-preserving: descriptor shape and all screens are unchanged. Verified
against the prod build on :8087 — the Playwright smoke (login, context, all
nine config-driven resource lists via derived paths, tenant create form with
the async type select) passes; tsc + production build green.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Bring DESIGN.md and the web-docs guide in line with the implemented model:
resources are declared in admin.config.json (data), API-intrinsic facts
(fields, CRUD routes, tenant-scope) derive from the aggregated OpenAPI spec,
and adding a resource is a JSON edit with no per-project TypeScript. DESIGN
gets a 2026-07-02 revision note superseding the curated-in-app-registry
wording; the web-docs 'add a resource' example is now JSON and the deferred
list is updated (runtime discovery + JSON registration done; repo extraction,
production role model, raw-DB fallback remain).

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Re-add the /account-management/v1/admin/context path and AdminContextDto
to the aggregated spec after rebasing onto upstream/main.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
@KvizadSaderah
KvizadSaderah force-pushed the feat/admin-panel-4144 branch from 3bfcdfc to b5abeb5 Compare July 13, 2026 09:42
Signed-off-by: KvizadSaderah <kvizad@gmail.com>
…on as follow-up

The reviewer's reusability concern is met in place (declarative JSON
registration + runtime OpenAPI derivation, zero per-project TS). Deliverable
4 delegates placement to this ADR, so decide it rather than block: v0 ships
embedded, repo extraction to constructorfabric/ is tracked as a follow-up.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
@KvizadSaderah

Copy link
Copy Markdown
Contributor Author

@Artifizer rebased onto the latest main (was 150+ commits behind) — the PR is now mergeable and CI is green. Ready for another look.

On the reusability concern (your main point): resolved in place. Resource registration moved to a declarative apps/admin-panel/src/resources/admin.config.json, and every API-intrinsic fact — field types/required/readOnly, CRUD verb→path mapping, custom actions, tenant-scope — is now derived at runtime from the aggregated /cf/openapi.json. Adding an admin object is a JSON edit with zero per-project TypeScript and no framework changes (registry.ts went 336 → 46 LOC). That's the Django-admin property you asked for — no copying panel code into each project.

On distribution: since deliverable 4 delegates placement to our ADRs, I've decided it rather than hold the PR — v0 ships embedded in the monorepo; extraction to a dedicated constructorfabric/ repo + pre-built artifact is a follow-up, tracked in #4213 (to do when a second consumer project exists). ADR-0001 updated with the rationale. The metadata-transport question is closed too: no x-cf-admin-* vendor extension or descriptor endpoint is needed for v0 (concern-split in ADR-0003).

Moving the PR out of draft for review.

@KvizadSaderah
KvizadSaderah marked this pull request as ready for review July 13, 2026 13:27
@KvizadSaderah
KvizadSaderah requested a review from Artifizer July 13, 2026 13:35

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (3)
apps/admin-panel/src/resources/openapiOps.ts (1)

138-141: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

list path silently returns empty string when no list operation is derived.

Unlike one, create, update, and remove — which use the enabled() guard and return undefined when no template exists — list always returns a function. When t.list is undefined (no GET on the basePath in the OpenAPI spec) and no opts.listPath override is provided, resolvePath(t.list ?? "", ctx) returns "". The downstream dataProvider.getList would then call apiFetch(""), hitting an unintended endpoint.

Consider making list optional in ResourcePaths (consistent with the other verbs) and applying the same enabled("list") guard, or throw a descriptive error at build time so misconfigurations fail fast.

♻️ Suggested fix: make list optional and guard with enabled()

In types.ts:

 export interface ResourcePaths {
-  list: (ctx: AdminContext) => string;
+  list?: (ctx: AdminContext) => string;
   one?: (ctx: AdminContext, id: string) => string;

In openapiOps.ts:

   return {
     list: opts.listPath
       ? opts.listPath
-      : (ctx) => resolvePath(t.list ?? "", ctx),
+      : enabled("list") ? (ctx) => resolvePath(t.list!, ctx) : undefined,
     one: enabled("one") ? (ctx, id) => resolvePath(t.one!, ctx, id) : undefined,

In dataProvider.ts (add guard mirroring the other verbs):

   getList: async ({ resource }) => {
     const d = getDescriptor(resource);
-    const payload = await apiFetch<unknown>(resourcePaths(d).list(requireContext()));
+    const list = resourcePaths(d).list;
+    if (!list) unsupported("getList", resource);
+    const payload = await apiFetch<unknown>(list(requireContext()));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin-panel/src/resources/openapiOps.ts` around lines 138 - 141, Update
the list path construction in openapiOps to use the same enabled("list") guard
as one, create, update, and remove, returning undefined when neither
opts.listPath nor the list template exists instead of resolving an empty path.
Make ResourcePaths.list optional and add the corresponding guard in dataProvider
wherever list paths are consumed, preserving the existing behavior when a list
operation is configured.
apps/admin-panel/src/pages/ResourceActions.tsx (1)

68-72: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer onClick on the Button over a wrapping clickable <span>.

The non-destructive branch wraps a no-op Button in a <span onClick>. It works via click bubbling, but attaching onClick directly to the Button is more idiomatic and avoids the extra clickable wrapper.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin-panel/src/pages/ResourceActions.tsx` around lines 68 - 72, Update
the non-destructive branch in ResourceActions to attach the existing run(a)
handler directly to the Button component instead of wrapping it in a clickable
span. Remove the wrapper while preserving the button rendering and key
assignment behavior.
apps/admin-panel/src/dataProvider.ts (1)

69-74: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

total is the current page length; cursor pages won't paginate.

getList ignores Refine's pagination/sort/filter params and returns total: data.length. For the { items, page_info } cursor envelope this under-reports the real total, so Refine's pager treats the first response as the entire set and never advances. If v0 intentionally shows a single unpaginated page, consider disabling pagination on the consuming useList to avoid a misleading pager; otherwise thread page_info through.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin-panel/src/dataProvider.ts` around lines 69 - 74, The getList
method currently reports the current page length as total and does not handle
cursor pagination. Update getList and the related normalizeList/page_info flow
to preserve cursor metadata and return the actual total or cursor-aware
pagination information expected by Refine; if this provider intentionally
remains single-page, disable pagination in its consuming useList instead of
exposing a misleading pager.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/admin-panel/src/authProvider.ts`:
- Line 20: Replace the localStorage persistence in the auth token flow around
TOKEN_STORAGE_KEY with a non-readable session mechanism: prefer
backend-coordinated httpOnly, secure, appropriately scoped cookies, or use
in-memory access-token storage with refresh-token rotation if cookie support is
unavailable. Remove reads and writes that persist bearer tokens in localStorage
while preserving the existing authentication behavior.

In `@apps/admin-panel/src/pages/TenantTree.tsx`:
- Around line 21-39: Thread the dynamic idKey from idField(d) through
withPlaceholder, isPlaceholder, and injectChildren, using r[idKey] for all ID
comparisons and placeholder generation instead of hardcoded r.id. Update every
affected call site, including onExpand-related tree updates, and change the Ant
Table rowKey from "id" to idKey so non-default primary keys work consistently.
- Around line 83-93: Update the onExpand handler to wrap fetchChildren(id) and
the subsequent child injection in try-catch, following the error-handling
pattern used by load. On failure, call setError with the caught error and
prevent the rejection from escaping, while preserving the existing expansion and
successful child-loading behavior.

In `@apps/admin-panel/src/resources/admin.config.json`:
- Line 40: Update the tenant resource base paths used by deriveOps() to use the
OpenAPI placeholder name {tenant_id} instead of {tenant}, including conversions,
users, and metadata entries in admin.config.json. Preserve the existing path
structure so exact matching populates paths and buildPaths() produces the
correct list URLs.

In `@docs/arch/admin-panel/ADR/0002-cpt-admin-panel-adr-frontend-framework.md`:
- Line 60: The ADR’s Data description incorrectly claims a generated OpenAPI
client. Update the line describing the custom Refine data provider to reflect
that apps/admin-panel/src/dataProvider.ts and adminContext.ts call apiFetch
directly, with OpenAPI processing handled at runtime elsewhere.

In `@docs/arch/admin-panel/ADR/0003-cpt-admin-panel-adr-resource-discovery.md`:
- Around line 62-84: Synchronize both architecture documents with the
implemented discovery contracts: in
docs/arch/admin-panel/ADR/0003-cpt-admin-panel-adr-resource-discovery.md lines
62-84, replace the obsolete hardcoded-registry Option A outcome and deferred
runtime-OpenAPI status with the current runtime OpenAPI metadata plus
declarative admin.config.json architecture; in docs/arch/admin-panel/DESIGN.md
lines 37-41, describe admin.config.json and runtime OpenAPI as the current
discovery sources. Update related consequences or status statements in the ADR
so they no longer claim the registry is the active source or runtime parsing is
deferred.

In `@docs/arch/admin-panel/DESIGN.md`:
- Around line 353-370: Make the admin-context contract consistent across all
specified documents: in docs/arch/admin-panel/DESIGN.md lines 353-370, document
/account-management/v1/admin/context with the frontend’s actual flat
account-management DTO and describe enabled gears as retrieved separately; in
docs/arch/admin-panel/ADR/0004-cpt-admin-panel-adr-admin-role-and-context.md
lines 59-63, remove the claim that the context endpoint returns enabled gears;
in docs/arch/admin-panel/PRD.md lines 352-356, specify the separate
enabled-gears request.

In `@docs/arch/admin-panel/PRD.md`:
- Around line 576-584: Update the “Open Questions” section to remove or mark as
resolved the decisions already settled by the PR objectives and ADR revisions,
specifically implementation placement, admin-context ownership, discovery
strategy, and frontend-stack selection. Retain only genuinely pending questions,
including production admin-mode representation and authentication for
non-auth_disabled deployments.

In `@README.md`:
- Around line 100-116: Update the Admin panel guide link in the README to
reference docs/web-docs/build-with-gears/admin-panel.md instead of the outdated
path, leaving the surrounding admin panel documentation unchanged.

---

Nitpick comments:
In `@apps/admin-panel/src/dataProvider.ts`:
- Around line 69-74: The getList method currently reports the current page
length as total and does not handle cursor pagination. Update getList and the
related normalizeList/page_info flow to preserve cursor metadata and return the
actual total or cursor-aware pagination information expected by Refine; if this
provider intentionally remains single-page, disable pagination in its consuming
useList instead of exposing a misleading pager.

In `@apps/admin-panel/src/pages/ResourceActions.tsx`:
- Around line 68-72: Update the non-destructive branch in ResourceActions to
attach the existing run(a) handler directly to the Button component instead of
wrapping it in a clickable span. Remove the wrapper while preserving the button
rendering and key assignment behavior.

In `@apps/admin-panel/src/resources/openapiOps.ts`:
- Around line 138-141: Update the list path construction in openapiOps to use
the same enabled("list") guard as one, create, update, and remove, returning
undefined when neither opts.listPath nor the list template exists instead of
resolving an empty path. Make ResourcePaths.list optional and add the
corresponding guard in dataProvider wherever list paths are consumed, preserving
the existing behavior when a list operation is configured.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b89b313c-2d8a-4ad1-903d-883042c9f274

📥 Commits

Reviewing files that changed from the base of the PR and between a95106e and 9e359bd.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • apps/admin-panel/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (55)
  • Cargo.toml
  • Makefile
  • README.md
  • apps/admin-panel/.gitignore
  • apps/admin-panel/index.html
  • apps/admin-panel/package.json
  • apps/admin-panel/playwright.config.ts
  • apps/admin-panel/src/App.tsx
  • apps/admin-panel/src/accessControlProvider.ts
  • apps/admin-panel/src/adminContext.ts
  • apps/admin-panel/src/authProvider.ts
  • apps/admin-panel/src/config.ts
  • apps/admin-panel/src/dataProvider.ts
  • apps/admin-panel/src/httpClient.ts
  • apps/admin-panel/src/main.tsx
  • apps/admin-panel/src/pages/ContextView.tsx
  • apps/admin-panel/src/pages/Login.tsx
  • apps/admin-panel/src/pages/ResourceActions.tsx
  • apps/admin-panel/src/pages/ResourceForm.tsx
  • apps/admin-panel/src/pages/ResourceList.tsx
  • apps/admin-panel/src/pages/ResourceShow.tsx
  • apps/admin-panel/src/pages/TenantTree.tsx
  • apps/admin-panel/src/resources/admin.config.json
  • apps/admin-panel/src/resources/configLoader.ts
  • apps/admin-panel/src/resources/fields.tsx
  • apps/admin-panel/src/resources/index.ts
  • apps/admin-panel/src/resources/openapi.ts
  • apps/admin-panel/src/resources/openapiOps.ts
  • apps/admin-panel/src/resources/registry.ts
  • apps/admin-panel/src/resources/types.ts
  • apps/admin-panel/src/vite-env.d.ts
  • apps/admin-panel/tests/smoke.spec.ts
  • apps/admin-panel/tsconfig.json
  • apps/admin-panel/vite.config.ts
  • config/admin.yaml
  • config/e2e-local.yaml
  • docs/api/api.json
  • docs/arch/admin-panel/ADR/0001-cpt-admin-panel-adr-placement-and-delivery.md
  • docs/arch/admin-panel/ADR/0002-cpt-admin-panel-adr-frontend-framework.md
  • docs/arch/admin-panel/ADR/0003-cpt-admin-panel-adr-resource-discovery.md
  • docs/arch/admin-panel/ADR/0004-cpt-admin-panel-adr-admin-role-and-context.md
  • docs/arch/admin-panel/DESIGN.md
  • docs/arch/admin-panel/PRD.md
  • docs/web-docs/build-with-gears/admin-panel.md
  • gears/system/account-management/account-management/src/api/rest/dto.rs
  • gears/system/account-management/account-management/src/api/rest/handlers/admin_context.rs
  • gears/system/account-management/account-management/src/api/rest/handlers/admin_context_tests.rs
  • gears/system/account-management/account-management/src/api/rest/handlers/mod.rs
  • gears/system/account-management/account-management/src/api/rest/routes/admin_context.rs
  • gears/system/account-management/account-management/src/api/rest/routes/mod.rs
  • gears/system/api-gateway/src/config.rs
  • gears/system/api-gateway/src/gear.rs
  • testing/e2e/gears/admin/__init__.py
  • testing/e2e/gears/admin/conftest.py
  • testing/e2e/gears/admin/test_admin_context.py

error: { name: "LoginError", message: "A bearer token is required" },
};
}
localStorage.setItem(TOKEN_STORAGE_KEY, token);

@coderabbitai coderabbitai Bot Jul 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Bearer token stored in localStorage is vulnerable to XSS.

Static analysis flagged this as CWE-312. Any script executing on the page can read the token from localStorage, and it persists across sessions. While the PR scopes this to a non-production dev setup, the pattern will need to change before any production deployment. Consider migrating to httpOnly cookie-based session management (requires backend coordination) or an in-memory token with refresh-token rotation for production use.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin-panel/src/authProvider.ts` at line 20, Replace the localStorage
persistence in the auth token flow around TOKEN_STORAGE_KEY with a non-readable
session mechanism: prefer backend-coordinated httpOnly, secure, appropriately
scoped cookies, or use in-memory access-token storage with refresh-token
rotation if cookie support is unavailable. Remove reads and writes that persist
bearer tokens in localStorage while preserving the existing authentication
behavior.

Source: Linters/SAST tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged, and intentional for v0. The bearer token here is a non-production dev-stub token from the static-auth plugin (the UI marks it as such with a demo banner). The design already defers the production auth model — when a real IdP/session flow replaces the stub, token storage should move to an httpOnly cookie (or in-memory + silent refresh) rather than localStorage. Tracking it as a hardening item for the production auth work, not a v0 blocker.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KvizadSaderah, that makes sense — thanks for the context. Given the non-production dev-stub token and the documented plan to move to httpOnly cookies or in-memory + refresh rotation when the real auth model lands, this is reasonable to defer for v0. I'll leave it as-is for now.

Comment thread apps/admin-panel/src/pages/TenantTree.tsx
Comment thread apps/admin-panel/src/pages/TenantTree.tsx
Comment thread apps/admin-panel/src/resources/admin.config.json
Comment thread docs/arch/admin-panel/ADR/0002-cpt-admin-panel-adr-frontend-framework.md Outdated
Comment thread docs/arch/admin-panel/DESIGN.md
Comment thread docs/arch/admin-panel/PRD.md Outdated
Comment thread README.md
…esources resolve

deriveOps() matched a descriptor's basePath against the spec by exact string,
so tenant-scoped resources whose config basePath uses the panel's {tenant}
placeholder never lined up with the spec's {tenant_id} paths — leaving
conversions/users/tenant-metadata with empty derived routes. Collapse every
path parameter to a wildcard before matching (the returned templates are the
spec's real keys, so resolvePath still fills the real params).

Also surface a failed lazy child-fetch in the tenant tree instead of silently
swallowing the rejection.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
…ed contracts

- admin-context is a flat AdminContextDto (subject, home tenant, mode,
  capabilities); enabled gears are read separately from the gear orchestrator,
  not returned by the endpoint (DESIGN, ADR-0004, PRD).
- record the implemented discovery: declarative JSON config + runtime OpenAPI,
  no in-app registry (ADR-0003 revision); data provider uses a hand-written
  apiFetch, not a generated client (ADR-0002).
- mark resolved open questions in the PRD; fix the moved web-docs guide link.

Signed-off-by: KvizadSaderah <kvizad@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants