fix(console): platform.hanzo.ai boots into the fleet control plane, not the per-org deploy home
The platform face was a single-product shell (rootId/home = 'platform' → the
per-org PaaS deploy home, PlatformModule). Per the CTO call — and the FOLLOW-UP the
OSS App Store commit explicitly flagged ('upgrade the platform face from
single-product to a MULTI-product nav … a separate CTO call') — platform.<brand> is
the fleet CONTROL PLANE: it must show every deployment of every app + manage all
services.
Retarget the platform shell to a full-catalog control-plane shape:
rootId: 'platform' → null (full admin nav — a global admin sees Deploy,
Applications, Clusters, Kubernetes, Tenants, Storage,
… every service; not a single-product scope)
home: 'platform' → 'gitops' (boots into the Deploy fleet map — every operator
App CR's declared/running/latest/drift + builds/logs/
rollback), not the customer home.
Additive to the concurrent OSS-store work: the per-org deploy home (the 'platform'
product / PlatformHome) stays reachable as one nav item among many; only the LANDING
moves. isProductShell stays false for platform (a home alone never scopes the nav —
only a rootId does), so nav rendering is the full catalog exactly like console.
Auth unchanged (brand cloud app; a global admin resolves owner==='admin' on this
go:embed host exactly as on cloud.<brand>, so the admin fleet board works).
tsc clean; shell.test + config/index.test green (56); build:embed ✓ (go:embed gate).
This commit is contained in:
@@ -19,8 +19,8 @@ import {
|
||||
// env restored), so no window mocking.
|
||||
|
||||
const ALL: ShellId[] = ['console', 'billing', 'marketing', 'ads', 'social', 'sentry', 'dns', 'platform']
|
||||
/** The single-product FACES (everything but the full console). */
|
||||
const FACES: ShellId[] = ['billing', 'marketing', 'ads', 'social', 'sentry', 'dns', 'platform']
|
||||
/** The single-product FACES (everything but the full-catalog console + platform). */
|
||||
const FACES: ShellId[] = ['billing', 'marketing', 'ads', 'social', 'sentry', 'dns']
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.NEXT_PUBLIC_PRODUCT_SHELL
|
||||
@@ -56,6 +56,14 @@ describe('product-shell descriptor', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('platform is the CONTROL-PLANE shell — full catalog (rootId null, not a product shell), boots into the gitops fleet map', () => {
|
||||
const p = shellFor('platform')
|
||||
expect(p.rootId).toBeNull() // full catalog nav — a global admin sees every surface
|
||||
expect(isProductShell('platform')).toBe(false) // NOT a single-product scope
|
||||
expect(p.home).toBe('gitops') // …but boots into the Deploy fleet map, not the per-org home
|
||||
expect(p.indexLabel).toBe('Overview')
|
||||
})
|
||||
|
||||
it('wordmarks: billing keeps mark-only; the newer faces wear their product wordmark', () => {
|
||||
expect(shellFor('billing').wordmark).toBe('') // legacy look, unchanged
|
||||
expect(shellFor('marketing').wordmark).toBe('Marketing')
|
||||
@@ -69,7 +77,7 @@ describe('product-shell descriptor', () => {
|
||||
it('sentry boots into Issues, dns into Zones; every other face indexes on Overview', () => {
|
||||
expect(shellFor('sentry').indexLabel).toBe('Issues')
|
||||
expect(shellFor('dns').indexLabel).toBe('Zones')
|
||||
for (const id of ['billing', 'marketing', 'ads', 'social', 'platform'] as ShellId[]) {
|
||||
for (const id of ['billing', 'marketing', 'ads', 'social'] as ShellId[]) {
|
||||
expect(shellFor(id).indexLabel).toBe('Overview')
|
||||
}
|
||||
})
|
||||
@@ -118,6 +126,9 @@ describe('shellFromHost — ONE resolver, five faces', () => {
|
||||
process.env.NEXT_PUBLIC_PRODUCT_SHELL = id
|
||||
expect(shellFromHost('cloud.hanzo.ai')).toBe(id)
|
||||
}
|
||||
// the control-plane shell is overridable too (preview any host as platform)
|
||||
process.env.NEXT_PUBLIC_PRODUCT_SHELL = 'platform'
|
||||
expect(shellFromHost('cloud.hanzo.ai')).toBe('platform')
|
||||
process.env.NEXT_PUBLIC_PRODUCT_SHELL = 'console'
|
||||
expect(shellFromHost('sentry.hanzo.ai')).toBe('console')
|
||||
})
|
||||
|
||||
@@ -39,6 +39,8 @@ export type ProductShell = {
|
||||
* look); the newer faces wear their product wordmark next to the Hanzo brand mark.
|
||||
* The single-screen faces (marketing/ads/social — one route, no sub-pages) surface a
|
||||
* lone "Overview" nav item; billing/sentry surface their root module's sub-pages.
|
||||
* `platform` is the ONE exception — a full-catalog control-plane shell (rootId null,
|
||||
* like console) that merely boots into the fleet map (see its descriptor below).
|
||||
*/
|
||||
export const PRODUCT_SHELLS: Record<ShellId, ProductShell> = {
|
||||
console: { id: 'console', rootId: null, indexLabel: 'Overview', wordmark: '', home: '' },
|
||||
@@ -48,15 +50,25 @@ export const PRODUCT_SHELLS: Record<ShellId, ProductShell> = {
|
||||
social: { id: 'social', rootId: 'social', indexLabel: 'Overview', wordmark: 'Publish', home: 'social' },
|
||||
sentry: { id: 'sentry', rootId: 'sentry', indexLabel: 'Issues', wordmark: 'Sentry', home: 'sentry' },
|
||||
dns: { id: 'dns', rootId: 'dns', indexLabel: 'Zones', wordmark: 'DNS', home: 'dns' },
|
||||
platform: { id: 'platform', rootId: 'platform', indexLabel: 'Overview', wordmark: 'Platform', home: 'platform' },
|
||||
// The CONTROL-PLANE shell (platform.<brand>) — the CTO-called upgrade of the initial
|
||||
// single-product platform face. A third shape: like `console` it keeps the FULL catalog
|
||||
// nav (rootId null → NOT a single-product scope, so a global admin sees every platform/
|
||||
// admin surface — Deploy, Applications, Clusters, Kubernetes, Tenants, Storage, …), but
|
||||
// it BOOTS into the Deploy fleet map (`gitops` — the operator App-CR drift board: every
|
||||
// app's declared/running/latest/drift + builds/logs/rollback) instead of the customer
|
||||
// home. This is what makes platform.hanzo.ai the fleet control plane. The per-org deploy
|
||||
// home (the `platform` product) stays reachable as one nav item among many.
|
||||
platform: { id: 'platform', rootId: null, indexLabel: 'Overview', wordmark: 'Platform', home: 'gitops' },
|
||||
}
|
||||
|
||||
/** The descriptor for a shell id. */
|
||||
export const shellFor = (id: ShellId): ProductShell => PRODUCT_SHELLS[id]
|
||||
|
||||
/**
|
||||
* True when a shell scopes the console to a SINGLE product face (billing/sentry) —
|
||||
* i.e. it has a root module and a home to boot into. The full `console` is false.
|
||||
* ONE predicate the nav + home + catalog gates share.
|
||||
* True when a shell scopes the console nav to a SINGLE product face (billing/sentry) —
|
||||
* i.e. it has a ROOT module. The full-catalog shells are false: `console` (no home) and
|
||||
* `platform` (the control plane — full nav, but boots into `gitops` via `home`, not a
|
||||
* scope). A `home` alone never scopes the nav; only a `rootId` does. ONE predicate the
|
||||
* nav + home + catalog gates share.
|
||||
*/
|
||||
export const isProductShell = (id: ShellId): boolean => shellFor(id).rootId !== null
|
||||
|
||||
Reference in New Issue
Block a user