@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 +4 -4
- package/package.json +1 -1
- package/src/overview/Overview.tsx +3 -3
- package/src/overview/config.ts +7 -6
- package/src/overview/superadmin.test.ts +36 -0
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}
|
|
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`,
|
|
182
|
-
`FadeIn`, and `States` — `
|
|
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 `
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
77
|
+
}, [range, allOrgs, superAdmin, tick, config])
|
|
78
78
|
|
|
79
79
|
const onRange = (k: OverviewRange) => {
|
|
80
80
|
setRange(k)
|
package/src/overview/config.ts
CHANGED
|
@@ -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
|
-
/**
|
|
145
|
+
/** SuperAdmin all-orgs god view. */
|
|
146
146
|
allOrgs?: boolean
|
|
147
147
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
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
|
-
|
|
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
|
+
})
|