Skip to content

feat!: operator redesign on ember-ui, Registry Service only (Phase 5 Workstream N) - #49

Merged
roncodes merged 19 commits into
release/v0.2.0from
feat/operator-console
Oct 24, 2026
Merged

roncodes merged 19 commits into
release/v0.2.0from
feat/operator-console

Conversation

@roncodes

@roncodes roncodes commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Phase 5 Workstream N: the operator redesign (canvas rows C and F) on ember-ui, plus making the bridge Registry Service (v2) only. Stacked on #46; retarget to release/v0.2.0 once #44–#46 land.

Breaking: v1 removed

This follows the review direction that v1 is dropped and v2 is the standard.

  • Server: removed the local registry models (extensions, bundles, installs, purchases, registry users, developer accounts) and the Verdaccio auth endpoints, plus:
    • developer accounts, bundle upload and review, publish
    • Stripe Connect payments and the embedded checkout
    • the cloud-catalog fallback (CentralCatalog) and the local-bundle installer source
    • the lookup and bundle-upload compatibility routes
    • the stripe, cashier and opis/json-schema dependencies
  • Kept: the migrations, so the old tables stay for the cloud import.
  • Config: REGISTRY_SERVICE_URL defaults to https://registry.fleetbase.io. REGISTRY_HOST, REGISTRY_TOKEN, REGISTRY_API_VERSION, REGISTRY_SELF_HOSTED, the Stripe keys and the facilitator fee are gone. registry:init is now a no-op.
  • Console: removed every developer and review screen (routes, forms, monetize, credentials, analytics, payments, review queue, pending publish), the bundle model, the Stripe service, and @stripe/connect-js, ember-radio-button and ember-tag-input.
  • Auth schema: keeps only the operator permissions (install, uninstall, purchase, see/list installed and purchased).

Console

  • One ExtensionCard with seven states: free, paid, on sale, installed, update available, purchased, and unavailable here. The two developer-only states and v1's self-managed state are gone.
  • cell/extension-identity, plus status, action, licence and release cells.
  • Explore and category on Layout::Resource::CardsGrid with a table toggle and FiltersPicker facets (category, pricing, hosting with self-hosted/cloud/both, hide installed) shown as chips. Also sort, search, paging, skeletons, and empty, no-results and error states.
  • Every hosting is listed. A listing this instance can't run shows the reason on the card and in the panel. An unregistered instance gets one banner explaining that it can browse but not install or buy.
  • Detail panel on Layout::Resource::Panel, with the CTA matrix and Overview, Screenshots, Details and Versions tabs (from the service releases endpoint).
  • Install, update and uninstall in one modal: confirm, named steps, success with a reload countdown, and failure with the stage, log, CLI fallback and Retry.
  • Purchase through the service's hosted checkout. A refusal (already licensed, publisher not set up, wrong hosting) is shown on the panel. On return the modal shows Finalizing, then Paid, Cancelled, Not completed, or Still processing.
  • Installed and Purchased on Layout::Resource::Tabular. Purchased shows activations and the Stripe receipt.
  • Sidebar: Installed (with the number of updates waiting), Purchased, and categories, each with counts; a Publisher portal link. The admin Registry Config page is v2-only and translated.
  • Translations: en-us, ar-ae, uk-ua and vi-vn share the same 278 keys, with valid ICU plurals.

Checkout against the real Phase 4 contract

This implements "Instance checkout" from fleetbase/registry feat/commerce:

  • The request sends extension, return_url, cancel_url and email.
  • Stripe appends handoff (and cancelled=1 on cancel); the console polls the hand-off every 2 seconds for about a minute.
  • Hand-off statuses are open, complete and expired.
  • Checkout is on for a registered instance. If the service has no checkout route, it's turned off for an hour (REGISTRY_SERVICE_CHECKOUT=auto|true|false).
  • The service's me licences carry no receipt yet, so the receipt from a completed hand-off is kept and shown on Purchased. A receipt_url the service sends on the licence always wins.
  • The instance now registers with the console origin as its host, because return URLs are being checked against the instance host.

Verification

  • Live: ran the Phase 4 service locally and ran the bridge's classes against it (19 of 19 checks): every hosting with reasons, facets, releases, the purchased listing installable, checkout refusals passed through (already licensed, wrong hosting, unknown extension), hand-offs open/expired/complete, and the receipt plus activations on Purchased.
  • Fixtures: the responses are recorded under server/tests/Fixtures/registry-service (record_phase4.py) and covered by ServiceCheckoutContractTest. A real Stripe session wasn't created; the local service has no Stripe keys.
  • Pest: 153 tests. The only failures are 3 SandboxConnectionTest cases, caused by the stale shared vendor (GeneaLabs missing).
  • Ember: 111 passing.
  • Not yet tried in a running console.

- Explore facets: pricing, hosting=both, sort and hide_installed (paged over the
  whole filtered catalog because installed state is only known here)
- GET registry-extensions/{id} reads Registry Service listings; GET
  registry-extensions/{id}/releases maps the service releases endpoint
- GET registry-extensions/context: service, registration, instance kind,
  publisher portal URL and checkout availability for every user
- POST purchases/checkout and GET purchases/{handoff} for the Phase 4 service
  checkout; off until the service advertises features.checkout
  (REGISTRY_SERVICE_CHECKOUT=auto|true|false)
- install blocks carry a code (not_registered, hosting, licence_required,
  no_release) and listings say whether purchase is available
- POST installer/update; install and update accept a version
- InstallExtensionRequest no longer requires a local registry_extensions row,
  which refused every Registry Service install from the console
- One ExtensionCard with the nine canvas states (utils/extension-state), shared
  with cell/extension-identity and the other table cells
- Explore and category on Layout::Resource::CardsGrid with a table toggle,
  FiltersPicker facets (category, pricing, hosting, hide installed) as chips,
  sort, skeletons, and empty, no-results and error states
- Detail panel on Layout::Resource::Panel with the CTA matrix and Overview,
  Screenshots, Details and Versions (service releases) tabs, kept in
  ?extension_id=
- One installer modal for install, update and uninstall: confirm, named steps,
  success with reload countdown, failure with stage, log, CLI fallback, Retry;
  typed-name guard on uninstall
- Purchase: service hosted checkout with Finalizing hand-off polling; blocked
  with the service reason until checkout is offered; v1 embedded Stripe kept
- Installed and Purchased on Layout::Resource::Tabular with grid toggle,
  activations and receipts
- Sidebar categories with counts; Developers panel replaced by a Publisher
  portal link; review and publish admin panels unregistered
- Copy deck in translations/en-us.yaml; 148 Ember tests
BREAKING: the v1 registry is gone from the bridge. Removed the local
registry tables' models, the Verdaccio auth endpoints, developer accounts,
bundle upload and review, Stripe Connect payments, the cloud catalog
fallback (CentralCatalog), the local-bundle installer source, the
lookup/bundle-upload compatibility routes, and the stripe, cashier and
opis/json-schema dependencies. Migrations stay so the old tables remain
for the cloud import. The service URL defaults to registry.fleetbase.io;
registry:init is a no-op; the auth schema keeps the operator permissions.

- Explore lists every hosting; listings this instance cannot run carry
  install_blocked_code hosting and the reason; hosting facet takes cloud,
  self_hosted or both
- Checkout follows the service contract (return_url, cancel_url, email;
  hand-off open, complete, expired; Stripe appends handoff and cancelled):
  on for a registered instance unless the service has no route (remembered
  for an hour), refusals pass through with their status and message
- Receipts: kept from the completed hand-off and shown on the licence until
  the service sends receipt_url itself, which always wins
- GET registry-extensions/counts for the sidebar
- Fixtures recorded from the Phase 4 service (record_phase4.py) and contract
  tests; fixed a stale apiVersion in the client cache key
… counts and receipts

BREAKING: removed every developer and review screen (routes, forms, monetize,
credentials, analytics, payments, review queue, pending publish), the bundle
model, the Stripe service, the embedded checkout, v1 self-managed install
instructions and the v1 admin registry host/token, plus @stripe/connect-js,
ember-radio-button and ember-tag-input. Publishing lives on the website.

- Card states are free, paid, on sale, installed, update, purchased and
  unavailable here; an unavailable listing shows the reason on the card, its
  badge and the panel; an unregistered instance gets one Explore banner
- Hosting facet: self-hosted, cloud, both
- Purchase follows the service contract: return and cancel URLs, Stripe's
  handoff and cancelled params, 2 s polling for about a minute, Cancelled
  phase, paid amount; a refusal is shown on the panel instead of a toast
- Sidebar counts for Installed (with updates waiting) and Purchased, refreshed
  after jobs and purchases; Purchased shows activations and receipts
- Admin Registry Config is v2-only and translated; registration sends the
  console origin as the instance host
- en-us, ar-ae, uk-ua and vi-vn share one 278-key set with valid ICU plurals
- README updated; 111 Ember tests
@roncodes roncodes changed the title feat(console): operator redesign on ember-ui (Phase 5 Workstream N) feat!: operator redesign on ember-ui, Registry Service only (Phase 5 Workstream N) Oct 23, 2026
…s-827e43' into feat/operator-console

# Conflicts:
#	server/src/Support/Installer/EloquentInstallRecorder.php
… shows price and refunded/revoked licences

D10: the purchase dialogs and the Purchased empty state name the registry
organization the instance is registered to (new `organization` on
GET registry-extensions/context, from RegistryConfig::organization()),
not the console company.

D11: the Purchased Price column shows the amount paid (receipt total, else
the listing price) through cell/purchase-price instead of the catalog status
chips; install status stays in the Install status column.

D20: Purchased keeps paid licences the service revoked (refund, dispute,
staff) with a Licence column (Active / Refunded / Revoked, reason from
`revoked_reason`) and their receipt. ServiceCatalog::isPaidLicence stays
active-only, so a revoked licence is not purchased, not installable and not
counted; buildState prefers an active paid licence over a revoked one.
Loading Purchased refreshes the sidebar counts.
…sole rebuild

Listings carry meta.install_mode (runtime, build or server) for the release an
install or update would take, and meta.uninstall_mode, estimated the way the
installer decides: a prebuilt dist, a console host-modules.json with the same
ember-ui and ember-core minor, and the engine not already built into the
console. The install note, the install, update and uninstall dialogs say a
rebuild can take 15 to 40 minutes or longer when one is needed, and stop
saying every install rebuilds the console (D15).
…s-827e43' into feat/operator-console

RegistrySettings keeps only the Registry Service keys here (service URL,
instance token, instance): the legacy v1 host, token and API version
settings are gone on this branch.
…sole

# Conflicts:
#	README.md
#	server/config/registry-bridge.php
#	server/src/Http/Controllers/Internal/v1/RegistryExtensionController.php
D17: once registered, the admin Registry Config page offered only
Unregister, so re-registering meant forgetting the token first. A
'Register again' action now takes a new code while registered; the bridge
sends the current token and the service rotates the same instance. The toast
says so when instance.rotated is set (same id, new token, installs and
licence seats kept), and the Unregister dialog says that registering this
console again later keeps the same instance.

Also: the RegistrySettings cache test uses a key that exists on this branch,
and Purchased gains its 'updates blocked' strings (next commit) in the same
translation files.
…esh on their own

D20 follow-up. Refunded and revoked licences stay on Purchased with their
state and receipt (991ca5a), but the 'no more updates' explanation was only a
tooltip on the Licence badge. The header now shows a warning badge,
'N licences refunded or revoked: updates blocked', with the reason as help.

The sidebar counts were refreshed only when Purchased loaded or after an
install or purchase, so a refund that lands on the service while the user is
on another page still left the old count. registry-context.watchCounts()
refreshes them when the console tab becomes visible again and on navigation
between extensions pages, at most once every 15 s.
@roncodes
roncodes changed the base branch from feature/cli-job-register-commands-827e43 to release/v0.2.0 October 24, 2026 09:49
@roncodes
roncodes marked this pull request as ready for review October 24, 2026 09:49
@roncodes
roncodes merged commit 0d23857 into release/v0.2.0 Oct 24, 2026
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.

1 participant