@hanzo/dashboard 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -84,7 +84,7 @@ const config: OverviewConfig = {
84
84
  load: async ({ range }) => fromMyApi(await MyApi.overview(range)), // → OverviewData
85
85
  }
86
86
 
87
- <Overview config={config} isGlobalAdmin={amAdmin} />
87
+ <Overview config={config} superAdmin={superAdmin(claims)} /> // @hanzo/iam's one reading
88
88
  ```
89
89
 
90
90
  The loader returns the normalized **`OverviewData`** (`kpi` / `series` / `distribution`
@@ -178,8 +178,8 @@ Extracted (and generalized: no `~/lib/*` / `~/config` imports) from
178
178
 
179
179
  - **Icons** are consumer-supplied (`IconComponent` = a `size`/`color` icon), replacing
180
180
  the console's `ProductIcon` from its registry.
181
- - **`Overview`** drops `~/lib/api` `ApiError`, `useIsGlobalAdmin`, `PageHeader`,
182
- `FadeIn`, and `States` — `isGlobalAdmin` is now a prop and the header/fade/error
181
+ - **`Overview`** drops `~/lib/api` `ApiError`, the console's SuperAdmin hook, `PageHeader`,
182
+ `FadeIn`, and `States` — `superAdmin` is now a prop (the host's verdict) and the header/fade/error
183
183
  chrome is inlined (self-contained).
184
184
  - **Landing** takes brand/docs/support/discord/accent from `LandingConfig` (was
185
185
  `~/config`). `LandingHero` inlines the hero graphic + accent button (was
@@ -205,7 +205,7 @@ per component:
205
205
  `@hanzo/dashboard`; `RailwayDeploy` + `LandingMetrics` import these hooks.
206
206
  3. **Overview** — replace `overview/living/{config,logic,tiles,LivingOverview}` with
207
207
  thin wrappers over the package. The console's `LivingOverviewModule` keeps its
208
- registry-router wrapper and passes `isGlobalAdmin={useIsGlobalAdmin()}` +
208
+ registry-router wrapper and passes `superAdmin={useIsSuperAdmin()}` +
209
209
  maps `ApiError` into the driver's caught error. `PageHeader`/`FadeIn`/`States`
210
210
  stay in console (they're used elsewhere) — the driver now inlines its own.
211
211
  4. **Landing** — replace `products/landing/*` with the package; the console builds a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/dashboard",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "Hanzo Dashboard — the reusable dashboard layer on @hanzo/gui: monochrome SVG charts, a videogame-like LivingOverview (count-up KPIs, live sparklines, streaming feed, health), the ProductLanding kit, and a Pipeline deploy animation. Pure (data via props/loaders), typed, reduced-motion-guarded. Extracted from the Hanzo Cloud Console so every Hanzo app composes ONE dashboard system.",
6
6
  "exports": {
@@ -35,7 +35,7 @@ const RANGES: { key: OverviewRange; label: string }[] = [
35
35
  { key: '30d', label: '30D' },
36
36
  ]
37
37
 
38
- export function Overview({ config, allOrgs, isGlobalAdmin = false }: { config: OverviewConfig; allOrgs?: boolean; isGlobalAdmin?: boolean }) {
38
+ export function Overview({ config, allOrgs, superAdmin = false }: { config: OverviewConfig; allOrgs?: boolean; superAdmin?: boolean }) {
39
39
  const [range, setRange] = useState<OverviewRange>('24h')
40
40
  const [state, setState] = useState<Loadable<OverviewData>>({ s: 'loading' })
41
41
  const [refreshing, setRefreshing] = useState(false)
@@ -57,7 +57,7 @@ export function Overview({ config, allOrgs, isGlobalAdmin = false }: { config: O
57
57
  const first = state.s === 'loading'
58
58
  if (!first) setRefreshing(true)
59
59
  config
60
- .load({ range, allOrgs, isGlobalAdmin })
60
+ .load({ range, allOrgs, superAdmin })
61
61
  .then((v) => {
62
62
  if (id === reqRef.current) setState({ s: 'ready', v })
63
63
  })
@@ -74,7 +74,7 @@ export function Overview({ config, allOrgs, isGlobalAdmin = false }: { config: O
74
74
  if (id === reqRef.current) setRefreshing(false)
75
75
  })
76
76
  // eslint-disable-next-line react-hooks/exhaustive-deps
77
- }, [range, allOrgs, isGlobalAdmin, tick, config])
77
+ }, [range, allOrgs, superAdmin, tick, config])
78
78
 
79
79
  const onRange = (k: OverviewRange) => {
80
80
  setRange(k)
@@ -142,15 +142,16 @@ export type LiveConfig = {
142
142
  /** The context passed to a loader (range + org scope). */
143
143
  export type OverviewContext = {
144
144
  range: OverviewRange
145
- /** Global-admin all-orgs god view. */
145
+ /** SuperAdmin all-orgs god view. */
146
146
  allOrgs?: boolean
147
147
  /**
148
- * True when the viewer is a global (cross-tenant) admin. Loaders use this to
149
- * SKIP admin-only aggregates that a tenant user's browser would only ever get a
150
- * 403 from, going straight to the org-scoped source instead — the board renders
151
- * the same, minus the console noise.
148
+ * The host's SuperAdmin verdict (a person whose home org is the reserved `admin`
149
+ * org, as the server or @hanzo/iam `superAdmin` states it — never derived here).
150
+ * Loaders use this to SKIP admin-only aggregates that a tenant user's browser
151
+ * would only ever get a 403 from, going straight to the org-scoped source
152
+ * instead — the board renders the same, minus the console noise.
152
153
  */
153
- isGlobalAdmin?: boolean
154
+ superAdmin?: boolean
154
155
  }
155
156
 
156
157
  export type OverviewRange = '24h' | '7d' | '30d'
@@ -0,0 +1,36 @@
1
+ // The dashboard decides no authority. SuperAdmin is the HOST's verdict (the server's
2
+ // `isSuperAdmin`, or @hanzo/iam `superAdmin(claims)` — a person whose home org is the
3
+ // reserved `admin` org), and the overview only carries it to the product's loader
4
+ // under that one name, so a loader can skip a fleet aggregate the viewer would be
5
+ // refused. These hold the package to that: no predicate of its own, one name.
6
+ import { describe, expect, test } from 'bun:test'
7
+ import { readFileSync, readdirSync, statSync } from 'node:fs'
8
+ import { join } from 'node:path'
9
+
10
+ const SRC = join(import.meta.dir, '..')
11
+
12
+ function sources(dir: string): string[] {
13
+ return readdirSync(dir).flatMap((name) => {
14
+ const p = join(dir, name)
15
+ if (statSync(p).isDirectory()) return sources(p)
16
+ return /\.(ts|tsx)$/.test(name) && !/\.test\.tsx?$/.test(name) ? [p] : []
17
+ })
18
+ }
19
+
20
+ describe('SuperAdmin in @hanzo/dashboard', () => {
21
+ test('the overview hands the loader the host verdict as superAdmin', () => {
22
+ const overview = readFileSync(join(SRC, 'overview', 'Overview.tsx'), 'utf8')
23
+ expect(overview).toMatch(/superAdmin = false \}: \{ config: OverviewConfig; allOrgs\?: boolean; superAdmin\?: boolean \}/)
24
+ expect(overview).toContain('.load({ range, allOrgs, superAdmin })')
25
+ const config = readFileSync(join(SRC, 'overview', 'config.ts'), 'utf8')
26
+ expect(config).toMatch(/\n superAdmin\?: boolean\n/)
27
+ })
28
+
29
+ test('no source decides SuperAdmin itself or names it another way', () => {
30
+ for (const file of sources(SRC)) {
31
+ const text = readFileSync(file, 'utf8')
32
+ expect({ file, hit: /isGlobalAdmin|global[ -]admin/i.test(text) }).toEqual({ file, hit: false })
33
+ expect({ file, hit: /owner\s*===?\s*['"]admin['"]|orgs\?*\.?\[0\]/.test(text) }).toEqual({ file, hit: false })
34
+ }
35
+ })
36
+ })