hazo_admin 0.1.1 → 0.3.1
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/CHANGE_LOG.md +17 -0
- package/README.md +72 -4
- package/SETUP_CHECKLIST.md +9 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +109 -5
- package/dist/components/admin_app.d.ts.map +1 -1
- package/dist/components/admin_app.js +26 -0
- package/dist/components/admin_kinds.d.ts +15 -0
- package/dist/components/admin_kinds.d.ts.map +1 -0
- package/dist/components/admin_kinds.js +120 -0
- package/dist/components/admin_layout.d.ts.map +1 -1
- package/dist/components/admin_layout.js +18 -20
- package/dist/components/admin_nav.d.ts +17 -2
- package/dist/components/admin_nav.d.ts.map +1 -1
- package/dist/components/admin_nav.js +111 -1
- package/dist/components/api_keys_panel.d.ts +5 -0
- package/dist/components/api_keys_panel.d.ts.map +1 -0
- package/dist/components/api_keys_panel.js +21 -0
- package/dist/components/audit_panel.d.ts +5 -0
- package/dist/components/audit_panel.d.ts.map +1 -0
- package/dist/components/audit_panel.js +19 -0
- package/dist/components/blog_panel.d.ts +5 -0
- package/dist/components/blog_panel.d.ts.map +1 -0
- package/dist/components/blog_panel.js +23 -0
- package/dist/components/env_migration_panel.d.ts.map +1 -1
- package/dist/components/env_migration_panel.js +6 -8
- package/dist/components/feedback_panel.d.ts +5 -0
- package/dist/components/feedback_panel.d.ts.map +1 -0
- package/dist/components/feedback_panel.js +24 -0
- package/dist/components/files_panel.d.ts +5 -0
- package/dist/components/files_panel.d.ts.map +1 -0
- package/dist/components/files_panel.js +22 -0
- package/dist/components/masking_panel.d.ts +5 -0
- package/dist/components/masking_panel.d.ts.map +1 -0
- package/dist/components/masking_panel.js +65 -0
- package/dist/components/settings_panel.d.ts +5 -0
- package/dist/components/settings_panel.d.ts.map +1 -0
- package/dist/components/settings_panel.js +19 -0
- package/dist/index.client.d.ts +8 -0
- package/dist/index.client.d.ts.map +1 -1
- package/dist/index.client.js +8 -0
- package/dist/index.ui.d.ts +2 -0
- package/dist/index.ui.d.ts.map +1 -1
- package/dist/index.ui.js +1 -0
- package/dist/preset/page_factory.d.ts.map +1 -1
- package/dist/preset/page_factory.js +9 -13
- package/package.json +18 -8
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# hazo_admin Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 — 2026-06-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `admin_kinds.ts` kind registry: centralized metadata (slug, label, defaultPermission, icon, group) for all nav kinds; components derive metadata from the registry rather than duplicating it
|
|
7
|
+
- 6 new permission constants in `HAZO_ADMIN_PERMISSIONS`: `AUDIT_READ` (`audit.read`), `SETTINGS_MANAGE` (`settings.manage`), `APIKEYS_MANAGE` (`apikeys.manage`), `BLOG_MANAGE` (`blog.manage`), `FILES_MANAGE` (`files.manage`), `FEEDBACK_REVIEW` (`feedback.review`)
|
|
8
|
+
- `MaskingRulesPanel` (Phase 2 complete): edits masking ruleset via `loadRuleset`/`MaskRule` from `hazo_env`; gated behind `env.mask.manage`
|
|
9
|
+
- `/mask` API group (Phase 2 complete): list, upsert, delete masking rules; syncs via `syncRulesetFromIni`
|
|
10
|
+
- 6 Phase-4 stub panels: `AuditPanel`, `SettingsPanel`, `ApiKeysPanel`, `BlogAdminPanel`, `FilesPanel`, `FeedbackPanel` — scaffolded with placeholder UI and gated API groups
|
|
11
|
+
- Sidebar re-skinned to shadcn semantic tokens (`bg-sidebar`, `text-sidebar-foreground`, etc.) + `ScrollArea` for long nav lists + `Tooltip` on collapsed icon items
|
|
12
|
+
- `kind: 'metrics'` first-class nav kind (FR-001): resolves to path `/admin/analytics`, basePath `/api/admin/metrics`, permission `metrics.view` by default; accepts an optional consumer-supplied `component` slot (e.g. hazo_umetrics `<MetricsPanel/>`) — no new import, no hazo_umetrics dependency
|
|
13
|
+
- `HAZO_ADMIN_PERMISSIONS.METRICS_VIEW` constant (`'metrics.view'`)
|
|
14
|
+
- `BarChart3` icon registered for the metrics kind in `admin_kinds.ts`
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- `GET /env/list` now returns `{name, role}[]` instead of `string[]` — **breaking change** for any consumer reading this endpoint as a plain string array
|
|
18
|
+
- `EnvMigrationPanel` prod-confirm guard now keys off resolved `role === 'production'` (from `/env/list` response) rather than matching env name literals — makes it robust to arbitrarily-named production environments
|
|
19
|
+
|
|
3
20
|
## 0.1.0 — 2026-06-09
|
|
4
21
|
|
|
5
22
|
### Added
|
package/README.md
CHANGED
|
@@ -70,17 +70,85 @@ export const GET = withAdminGate(
|
|
|
70
70
|
```ts
|
|
71
71
|
import { HAZO_ADMIN_PERMISSIONS } from 'hazo_admin/client';
|
|
72
72
|
|
|
73
|
-
//
|
|
74
|
-
// HAZO_ADMIN_PERMISSIONS.
|
|
75
|
-
// HAZO_ADMIN_PERMISSIONS.
|
|
73
|
+
// Core
|
|
74
|
+
// HAZO_ADMIN_PERMISSIONS.ADMIN_SYSTEM = 'admin_system' — full admin access (default gate)
|
|
75
|
+
// HAZO_ADMIN_PERMISSIONS.ENV_MIGRATE = 'env.migrate' — run env migrations
|
|
76
|
+
// HAZO_ADMIN_PERMISSIONS.ENV_MASK_MANAGE = 'env.mask.manage' — edit masking ruleset
|
|
77
|
+
|
|
78
|
+
// Extended (v0.2.0)
|
|
79
|
+
// HAZO_ADMIN_PERMISSIONS.AUDIT_READ = 'audit.read' — view audit trail
|
|
80
|
+
// HAZO_ADMIN_PERMISSIONS.SETTINGS_MANAGE = 'admin.settings.manage' — edit app config settings
|
|
81
|
+
// HAZO_ADMIN_PERMISSIONS.APIKEYS_MANAGE = 'admin.apikeys.manage' — manage API keys
|
|
82
|
+
// HAZO_ADMIN_PERMISSIONS.BLOG_MANAGE = 'admin.blog.manage' — manage blog posts
|
|
83
|
+
// HAZO_ADMIN_PERMISSIONS.FILES_MANAGE = 'admin.files.manage' — browse / manage data files
|
|
84
|
+
// HAZO_ADMIN_PERMISSIONS.FEEDBACK_REVIEW = 'admin.feedback.review' — review user feedback
|
|
85
|
+
// HAZO_ADMIN_PERMISSIONS.METRICS_VIEW = 'metrics.view' — view metrics / analytics panel
|
|
76
86
|
```
|
|
77
87
|
|
|
78
88
|
**These must exist as rows in the consuming app's `hazo_permissions` table.** The consuming app is responsible for seeding them.
|
|
79
89
|
|
|
90
|
+
## Nav sections / manifest kinds
|
|
91
|
+
|
|
92
|
+
All nav kind metadata is centralized in `src/lib/admin_kinds.ts`. Each kind maps to a panel, a composed package, and a default permission. Contributors adding a new kind should add it to the registry there first.
|
|
93
|
+
|
|
94
|
+
| Kind | Default permission | Composed package | Status |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| `users` | `admin_system` | `hazo_auth` | done |
|
|
97
|
+
| `jobs` | `admin_system` | `hazo_jobs` | done |
|
|
98
|
+
| `logs` | `admin_system` | `hazo_logs` | done |
|
|
99
|
+
| `env` | `env.migrate` | `hazo_env` | done |
|
|
100
|
+
| `masking` | `env.mask.manage` | `hazo_env` | done |
|
|
101
|
+
| `health` | `admin_system` | `hazo_env` | done |
|
|
102
|
+
| `audit` | `audit.read` | `hazo_audit` | stub (Phase 4) |
|
|
103
|
+
| `settings` | `settings.manage` | `hazo_config` | stub (Phase 4) |
|
|
104
|
+
| `api_keys` | `apikeys.manage` | `hazo_api` | stub (Phase 4) |
|
|
105
|
+
| `blog` | `blog.manage` | `hazo_blog` | stub (Phase 4) |
|
|
106
|
+
| `files` | `files.manage` | `hazo_files` | stub (Phase 4) |
|
|
107
|
+
| `feedback` | `feedback.review` | `hazo_feedback` | stub (Phase 4) |
|
|
108
|
+
| `metrics` | `metrics.view` | consumer-supplied `component` (e.g. `hazo_umetrics`) | done (consumer-component) |
|
|
109
|
+
|
|
110
|
+
### `metrics` kind — consumer component pattern
|
|
111
|
+
|
|
112
|
+
The `metrics` kind has no hazo_admin dependency on `hazo_umetrics`. Instead, pass your metrics panel as a `component` prop on the section:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { MetricsPanel } from 'hazo_umetrics';
|
|
116
|
+
|
|
117
|
+
const MANIFEST = {
|
|
118
|
+
sections: [
|
|
119
|
+
{ kind: 'metrics' as const, component: <MetricsPanel /> },
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If no `component` is supplied, the panel renders a placeholder message. This keeps `hazo_admin` dependency-free from any metrics package.
|
|
125
|
+
|
|
126
|
+
## v0.2.0 Breaking changes
|
|
127
|
+
|
|
128
|
+
### `GET /env/list` returns `{name, role}[]` instead of `string[]`
|
|
129
|
+
|
|
130
|
+
Prior to v0.2.0, `GET /env/list` returned a plain string array of env names. As of v0.2.0 it returns an array of objects:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// Before (v0.1.x)
|
|
134
|
+
// GET /env/list → string[]
|
|
135
|
+
// e.g. ['dev', 'prod']
|
|
136
|
+
|
|
137
|
+
// After (v0.2.0+)
|
|
138
|
+
// GET /env/list → { name: string; role: HazoEnvRole }[]
|
|
139
|
+
// e.g. [{ name: 'dev', role: 'development' }, { name: 'prod', role: 'production' }]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Consumers of this endpoint (e.g. `EnvMigrationPanel`) must read the `role` field from the response rather than re-deriving it from the env name string. This makes the panel robust to arbitrarily-named production environments.
|
|
143
|
+
|
|
80
144
|
## Entries
|
|
81
145
|
|
|
82
146
|
| Import path | Contents | Server-safe |
|
|
83
147
|
|---|---|---|
|
|
84
148
|
| `hazo_admin` | `adminGate`, `withAdminGate`, `HAZO_ADMIN_PERMISSIONS`, `AdminGateResult` | Server only |
|
|
85
149
|
| `hazo_admin/client` | `HAZO_ADMIN_PERMISSIONS`, types | Yes |
|
|
86
|
-
| `hazo_admin/api` | `createAdminPresetRoutes`
|
|
150
|
+
| `hazo_admin/api` | `createAdminPresetRoutes` | Server only |
|
|
151
|
+
| `hazo_admin/jobs` | `ENV_MIGRATE_JOB_TYPE`, `envMigrateJobHandler` | Server only (no React) |
|
|
152
|
+
| `hazo_admin/ui` | All panel components + `resolveNav` | Client |
|
|
153
|
+
| `hazo_admin/ui/<component>` | Individual panel components (avoids barrel server-only leak) | Client |
|
|
154
|
+
| `hazo_admin/preset` | `AdminPresetPage` (drop-in Next.js page) | Server/RSC |
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -14,7 +14,15 @@ In your app's database migration, seed the permission strings that `hazo_admin`
|
|
|
14
14
|
INSERT INTO hazo_permissions (id, name) VALUES
|
|
15
15
|
(gen_random_uuid(), 'admin_system'),
|
|
16
16
|
(gen_random_uuid(), 'env.migrate'),
|
|
17
|
-
(gen_random_uuid(), 'env.mask.manage')
|
|
17
|
+
(gen_random_uuid(), 'env.mask.manage'),
|
|
18
|
+
-- Extended permissions (add only those your app uses)
|
|
19
|
+
(gen_random_uuid(), 'audit.read'),
|
|
20
|
+
(gen_random_uuid(), 'settings.manage'),
|
|
21
|
+
(gen_random_uuid(), 'apikeys.manage'),
|
|
22
|
+
(gen_random_uuid(), 'admin.blog.manage'),
|
|
23
|
+
(gen_random_uuid(), 'admin.files.manage'),
|
|
24
|
+
(gen_random_uuid(), 'admin.feedback.review'),
|
|
25
|
+
(gen_random_uuid(), 'metrics.view')
|
|
18
26
|
ON CONFLICT (name) DO NOTHING;
|
|
19
27
|
```
|
|
20
28
|
|
package/dist/api/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAIrB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;OAIG;IACH,cAAc,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE;QACL,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,wFAAwF;QACxF,OAAO,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAC;KAC3B,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE;QACJ,gFAAgF;QAChF,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH,CAAC;AAuBF,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,EAAE,uBAAuB;mBA+
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAIrB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;OAIG;IACH,cAAc,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE;QACL,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,wFAAwF;QACxF,OAAO,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAC;KAC3B,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE;QACJ,gFAAgF;QAChF,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH,CAAC;AAuBF,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,EAAE,uBAAuB;mBA+RlE,OAAO,OAAO;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAA;KAAE,KAAG,OAAO,CAAC,QAAQ,CAAC;oBAAzE,OAAO,OAAO;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAA;KAAE,KAAG,OAAO,CAAC,QAAQ,CAAC;mBAAzE,OAAO,OAAO;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAA;KAAE,KAAG,OAAO,CAAC,QAAQ,CAAC;sBAAzE,OAAO,OAAO;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAA;KAAE,KAAG,OAAO,CAAC,QAAQ,CAAC;EA8FnG;AAGD,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,GAChB,MAAM,0BAA0B,CAAC"}
|
package/dist/api/index.js
CHANGED
|
@@ -27,6 +27,20 @@ export function createAdminPresetRoutes(manifest, cfg) {
|
|
|
27
27
|
const logsPermission = logsSection?.permission ?? 'admin_system';
|
|
28
28
|
const envSection = manifest.sections.find((s) => s.kind === 'env_migration');
|
|
29
29
|
const envPermission = envSection?.permission ?? 'env.migrate';
|
|
30
|
+
const maskSection = manifest.sections.find((s) => s.kind === 'masking');
|
|
31
|
+
const maskPermission = maskSection?.permission ?? 'env.mask.manage';
|
|
32
|
+
const auditSection = manifest.sections.find((s) => s.kind === 'audit');
|
|
33
|
+
const auditPermission = auditSection?.permission ?? 'audit.read';
|
|
34
|
+
const settingsSection = manifest.sections.find((s) => s.kind === 'settings');
|
|
35
|
+
const settingsPermission = settingsSection?.permission ?? 'admin.settings.manage';
|
|
36
|
+
const apiKeysSection = manifest.sections.find((s) => s.kind === 'api_keys');
|
|
37
|
+
const apiKeysPermission = apiKeysSection?.permission ?? 'admin.apikeys.manage';
|
|
38
|
+
const blogSection = manifest.sections.find((s) => s.kind === 'blog');
|
|
39
|
+
const blogPermission = blogSection?.permission ?? 'admin.blog.manage';
|
|
40
|
+
const filesSection = manifest.sections.find((s) => s.kind === 'files');
|
|
41
|
+
const filesPermission = filesSection?.permission ?? 'admin.files.manage';
|
|
42
|
+
const feedbackSection = manifest.sections.find((s) => s.kind === 'feedback');
|
|
43
|
+
const feedbackPermission = feedbackSection?.permission ?? 'admin.feedback.review';
|
|
30
44
|
async function handleJobs(request, method, segments) {
|
|
31
45
|
const jobsMod = await import('hazo_jobs/server').catch(() => null);
|
|
32
46
|
if (!jobsMod)
|
|
@@ -97,6 +111,25 @@ export function createAdminPresetRoutes(manifest, cfg) {
|
|
|
97
111
|
return logsHandler.GET(request);
|
|
98
112
|
return logsHandler.POST(request);
|
|
99
113
|
}
|
|
114
|
+
async function handleMask(_request, method, segments) {
|
|
115
|
+
const hazoEnv = await import('hazo_env').catch(() => null);
|
|
116
|
+
if (!hazoEnv)
|
|
117
|
+
return notInstalled('hazo_env');
|
|
118
|
+
const s = segments;
|
|
119
|
+
// GET /mask/rules
|
|
120
|
+
if (method === 'GET' && s[0] === 'rules' && s.length === 1) {
|
|
121
|
+
const connect = await cfg.getHazoConnect();
|
|
122
|
+
const rules = await hazoEnv.loadRuleset(connect);
|
|
123
|
+
return Response.json(rules);
|
|
124
|
+
}
|
|
125
|
+
// POST /mask/seed-from-ini
|
|
126
|
+
if (method === 'POST' && s[0] === 'seed-from-ini' && s.length === 1) {
|
|
127
|
+
const connect = await cfg.getHazoConnect();
|
|
128
|
+
await hazoEnv.syncRulesetFromIni(connect);
|
|
129
|
+
return Response.json({ ok: true });
|
|
130
|
+
}
|
|
131
|
+
return notFound();
|
|
132
|
+
}
|
|
100
133
|
async function handleEnv(request, method, segments) {
|
|
101
134
|
const hazoEnv = await import('hazo_env').catch(() => null);
|
|
102
135
|
if (!hazoEnv)
|
|
@@ -113,11 +146,12 @@ export function createAdminPresetRoutes(manifest, cfg) {
|
|
|
113
146
|
}
|
|
114
147
|
// GET /env/list
|
|
115
148
|
if (method === 'GET' && s[0] === 'list' && s.length === 1) {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
149
|
+
const names = hazoEnv.listEnvs();
|
|
150
|
+
const envs = names.map((name) => ({
|
|
151
|
+
name,
|
|
152
|
+
role: hazoEnv.getEnvRole(name),
|
|
153
|
+
}));
|
|
154
|
+
return Response.json(envs);
|
|
121
155
|
}
|
|
122
156
|
// GET /env/health
|
|
123
157
|
if (method === 'GET' && s[0] === 'health' && s.length === 1) {
|
|
@@ -227,6 +261,48 @@ export function createAdminPresetRoutes(manifest, cfg) {
|
|
|
227
261
|
}
|
|
228
262
|
return notFound();
|
|
229
263
|
}
|
|
264
|
+
async function handleAudit(_request, _method, _segments) {
|
|
265
|
+
const pkg = await import('hazo_audit').catch(() => null);
|
|
266
|
+
if (!pkg)
|
|
267
|
+
return notInstalled('hazo_audit');
|
|
268
|
+
// Placeholder stub — TODO: implement real sub-routes
|
|
269
|
+
return Response.json({ placeholder: true, panel: 'audit' });
|
|
270
|
+
}
|
|
271
|
+
async function handleSettings(_request, _method, _segments) {
|
|
272
|
+
const pkg = await import('hazo_config').catch(() => null);
|
|
273
|
+
if (!pkg)
|
|
274
|
+
return notInstalled('hazo_config');
|
|
275
|
+
// Placeholder stub — TODO: implement real sub-routes
|
|
276
|
+
return Response.json({ placeholder: true, panel: 'settings' });
|
|
277
|
+
}
|
|
278
|
+
async function handleApiKeys(_request, _method, _segments) {
|
|
279
|
+
const pkg = await import('hazo_api').catch(() => null);
|
|
280
|
+
if (!pkg)
|
|
281
|
+
return notInstalled('hazo_api');
|
|
282
|
+
// Placeholder stub — TODO: implement real sub-routes
|
|
283
|
+
return Response.json({ placeholder: true, panel: 'apikeys' });
|
|
284
|
+
}
|
|
285
|
+
async function handleBlog(_request, _method, _segments) {
|
|
286
|
+
const pkg = await import('hazo_blog').catch(() => null);
|
|
287
|
+
if (!pkg)
|
|
288
|
+
return notInstalled('hazo_blog');
|
|
289
|
+
// Placeholder stub — TODO: implement real sub-routes
|
|
290
|
+
return Response.json({ placeholder: true, panel: 'blog' });
|
|
291
|
+
}
|
|
292
|
+
async function handleFiles(_request, _method, _segments) {
|
|
293
|
+
const pkg = await import('hazo_files').catch(() => null);
|
|
294
|
+
if (!pkg)
|
|
295
|
+
return notInstalled('hazo_files');
|
|
296
|
+
// Placeholder stub — TODO: implement real sub-routes
|
|
297
|
+
return Response.json({ placeholder: true, panel: 'files' });
|
|
298
|
+
}
|
|
299
|
+
async function handleFeedback(_request, _method, _segments) {
|
|
300
|
+
const pkg = await import('hazo_feedback').catch(() => null);
|
|
301
|
+
if (!pkg)
|
|
302
|
+
return notInstalled('hazo_feedback');
|
|
303
|
+
// Placeholder stub — TODO: implement real sub-routes
|
|
304
|
+
return Response.json({ placeholder: true, panel: 'feedback' });
|
|
305
|
+
}
|
|
230
306
|
function makeHandler(method) {
|
|
231
307
|
return async (request, ctx) => {
|
|
232
308
|
const { path: routePath } = await ctx.params;
|
|
@@ -243,6 +319,34 @@ export function createAdminPresetRoutes(manifest, cfg) {
|
|
|
243
319
|
const gated = withAdminGate((_req, _gate) => handleEnv(_req, method, rest), { required_permissions: [envPermission] });
|
|
244
320
|
return gated(request);
|
|
245
321
|
}
|
|
322
|
+
if (group === 'mask') {
|
|
323
|
+
const gated = withAdminGate((_req, _gate) => handleMask(_req, method, rest), { required_permissions: [maskPermission] });
|
|
324
|
+
return gated(request);
|
|
325
|
+
}
|
|
326
|
+
if (group === 'audit') {
|
|
327
|
+
const gated = withAdminGate((_req, _gate) => handleAudit(_req, method, rest), { required_permissions: [auditPermission] });
|
|
328
|
+
return gated(request);
|
|
329
|
+
}
|
|
330
|
+
if (group === 'settings') {
|
|
331
|
+
const gated = withAdminGate((_req, _gate) => handleSettings(_req, method, rest), { required_permissions: [settingsPermission] });
|
|
332
|
+
return gated(request);
|
|
333
|
+
}
|
|
334
|
+
if (group === 'apikeys') {
|
|
335
|
+
const gated = withAdminGate((_req, _gate) => handleApiKeys(_req, method, rest), { required_permissions: [apiKeysPermission] });
|
|
336
|
+
return gated(request);
|
|
337
|
+
}
|
|
338
|
+
if (group === 'blog') {
|
|
339
|
+
const gated = withAdminGate((_req, _gate) => handleBlog(_req, method, rest), { required_permissions: [blogPermission] });
|
|
340
|
+
return gated(request);
|
|
341
|
+
}
|
|
342
|
+
if (group === 'files') {
|
|
343
|
+
const gated = withAdminGate((_req, _gate) => handleFiles(_req, method, rest), { required_permissions: [filesPermission] });
|
|
344
|
+
return gated(request);
|
|
345
|
+
}
|
|
346
|
+
if (group === 'feedback') {
|
|
347
|
+
const gated = withAdminGate((_req, _gate) => handleFeedback(_req, method, rest), { required_permissions: [feedbackPermission] });
|
|
348
|
+
return gated(request);
|
|
349
|
+
}
|
|
246
350
|
return notFound();
|
|
247
351
|
};
|
|
248
352
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_app.d.ts","sourceRoot":"","sources":["../../src/components/admin_app.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"admin_app.d.ts","sourceRoot":"","sources":["../../src/components/admin_app.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAcvE,OAAO,KAAK,EAAE,aAAa,EAAgB,MAAM,gBAAgB,CAAC;AAElE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC/B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAwBD,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,aAAa,+BAkBrF"}
|
|
@@ -7,6 +7,13 @@ import { JobsPanel } from './jobs_panel.js';
|
|
|
7
7
|
import { LogsPanel } from './logs_panel.js';
|
|
8
8
|
import { EnvMigrationPanel } from './env_migration_panel.js';
|
|
9
9
|
import { HealthPanel } from './health_panel.js';
|
|
10
|
+
import { MaskingRulesPanel } from './masking_panel.js';
|
|
11
|
+
import { AuditPanel } from './audit_panel.js';
|
|
12
|
+
import { SettingsPanel } from './settings_panel.js';
|
|
13
|
+
import { ApiKeysPanel } from './api_keys_panel.js';
|
|
14
|
+
import { BlogPanel } from './blog_panel.js';
|
|
15
|
+
import { FilesPanel } from './files_panel.js';
|
|
16
|
+
import { FeedbackPanel } from './feedback_panel.js';
|
|
10
17
|
import { resolveNav } from './admin_nav.js';
|
|
11
18
|
function renderPanel(item) {
|
|
12
19
|
if (item.kind === 'users')
|
|
@@ -19,6 +26,25 @@ function renderPanel(item) {
|
|
|
19
26
|
return _jsx(EnvMigrationPanel, { basePath: item.basePath });
|
|
20
27
|
if (item.kind === 'health')
|
|
21
28
|
return _jsx(HealthPanel, { basePath: item.basePath });
|
|
29
|
+
if (item.kind === 'masking')
|
|
30
|
+
return _jsx(MaskingRulesPanel, { basePath: item.basePath ?? '/api/admin' });
|
|
31
|
+
if (item.kind === 'audit')
|
|
32
|
+
return _jsx(AuditPanel, { basePath: item.basePath });
|
|
33
|
+
if (item.kind === 'settings')
|
|
34
|
+
return _jsx(SettingsPanel, { basePath: item.basePath });
|
|
35
|
+
if (item.kind === 'api_keys')
|
|
36
|
+
return _jsx(ApiKeysPanel, { basePath: item.basePath });
|
|
37
|
+
if (item.kind === 'blog')
|
|
38
|
+
return _jsx(BlogPanel, { basePath: item.basePath });
|
|
39
|
+
if (item.kind === 'files')
|
|
40
|
+
return _jsx(FilesPanel, { basePath: item.basePath });
|
|
41
|
+
if (item.kind === 'feedback')
|
|
42
|
+
return _jsx(FeedbackPanel, { basePath: item.basePath });
|
|
43
|
+
if (item.kind === 'metrics') {
|
|
44
|
+
return item.component
|
|
45
|
+
? _jsx(_Fragment, { children: item.component })
|
|
46
|
+
: _jsx("div", { className: "p-6 text-sm text-gray-500", children: "Metrics panel not provided. Pass a `component` (e.g. hazo_umetrics <MetricsPanel/>) on the metrics section." });
|
|
47
|
+
}
|
|
22
48
|
if (item.kind === 'custom' && item.component)
|
|
23
49
|
return _jsx(_Fragment, { children: item.component });
|
|
24
50
|
return _jsx("div", { className: "p-6 text-sm text-gray-500", children: "Panel not found." });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface AdminKindDef {
|
|
3
|
+
kind: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
defaultLabel: string;
|
|
6
|
+
defaultPermission: string;
|
|
7
|
+
defaultBasePath?: string;
|
|
8
|
+
icon: () => React.ReactNode;
|
|
9
|
+
group: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const ADMIN_KINDS: AdminKindDef[];
|
|
12
|
+
export declare function getKindDef(kind: string): AdminKindDef | undefined;
|
|
13
|
+
export declare function getDefaultPermission(kind: string): string | undefined;
|
|
14
|
+
export declare function getDefaultIcon(kind: string): React.ReactNode | null;
|
|
15
|
+
//# sourceMappingURL=admin_kinds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin_kinds.d.ts","sourceRoot":"","sources":["../../src/components/admin_kinds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,WAAW,EAAE,YAAY,EA0GrC,CAAC;AAEF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEjE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,SAAS,GAAG,IAAI,CAGnE"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Users, Briefcase, FileText, RefreshCw, Activity, ShieldCheck, History, Settings, Key, BookOpen, FolderOpen, MessageSquare, BarChart3 } from 'lucide-react';
|
|
3
|
+
import { HAZO_ADMIN_PERMISSIONS } from '../index.client.js';
|
|
4
|
+
export const ADMIN_KINDS = [
|
|
5
|
+
{
|
|
6
|
+
kind: 'users',
|
|
7
|
+
slug: 'users',
|
|
8
|
+
defaultLabel: 'Users',
|
|
9
|
+
defaultPermission: 'admin_system',
|
|
10
|
+
group: 'operate',
|
|
11
|
+
icon: () => React.createElement(Users, { size: 16 }),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
kind: 'jobs',
|
|
15
|
+
slug: 'jobs',
|
|
16
|
+
defaultLabel: 'Jobs',
|
|
17
|
+
defaultPermission: 'admin_system',
|
|
18
|
+
group: 'operate',
|
|
19
|
+
icon: () => React.createElement(Briefcase, { size: 16 }),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
kind: 'logs',
|
|
23
|
+
slug: 'logs',
|
|
24
|
+
defaultLabel: 'Logs',
|
|
25
|
+
defaultPermission: 'admin_system',
|
|
26
|
+
group: 'operate',
|
|
27
|
+
icon: () => React.createElement(FileText, { size: 16 }),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
kind: 'env_migration',
|
|
31
|
+
slug: 'env-migration',
|
|
32
|
+
defaultLabel: 'Env Migration',
|
|
33
|
+
defaultPermission: 'env.migrate',
|
|
34
|
+
group: 'system',
|
|
35
|
+
icon: () => React.createElement(RefreshCw, { size: 16 }),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
kind: 'health',
|
|
39
|
+
slug: 'health',
|
|
40
|
+
defaultLabel: 'Health',
|
|
41
|
+
defaultPermission: 'env.migrate',
|
|
42
|
+
group: 'system',
|
|
43
|
+
icon: () => React.createElement(Activity, { size: 16 }),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
kind: 'masking',
|
|
47
|
+
slug: 'masking',
|
|
48
|
+
defaultLabel: 'Masking Rules',
|
|
49
|
+
defaultPermission: 'env.mask.manage',
|
|
50
|
+
group: 'system',
|
|
51
|
+
icon: () => React.createElement(ShieldCheck, { size: 18, className: 'shrink-0' }),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
kind: 'audit',
|
|
55
|
+
slug: 'audit',
|
|
56
|
+
defaultLabel: 'Audit Log',
|
|
57
|
+
defaultPermission: HAZO_ADMIN_PERMISSIONS.AUDIT_READ,
|
|
58
|
+
group: 'system',
|
|
59
|
+
icon: () => React.createElement(History, { size: 16 }),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
kind: 'settings',
|
|
63
|
+
slug: 'settings',
|
|
64
|
+
defaultLabel: 'Settings',
|
|
65
|
+
defaultPermission: HAZO_ADMIN_PERMISSIONS.SETTINGS_MANAGE,
|
|
66
|
+
group: 'system',
|
|
67
|
+
icon: () => React.createElement(Settings, { size: 16 }),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
kind: 'api_keys',
|
|
71
|
+
slug: 'apikeys',
|
|
72
|
+
defaultLabel: 'API Keys',
|
|
73
|
+
defaultPermission: HAZO_ADMIN_PERMISSIONS.APIKEYS_MANAGE,
|
|
74
|
+
group: 'system',
|
|
75
|
+
icon: () => React.createElement(Key, { size: 16 }),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
kind: 'blog',
|
|
79
|
+
slug: 'blog',
|
|
80
|
+
defaultLabel: 'Blog',
|
|
81
|
+
defaultPermission: HAZO_ADMIN_PERMISSIONS.BLOG_MANAGE,
|
|
82
|
+
group: 'content',
|
|
83
|
+
icon: () => React.createElement(BookOpen, { size: 16 }),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
kind: 'files',
|
|
87
|
+
slug: 'files',
|
|
88
|
+
defaultLabel: 'Files',
|
|
89
|
+
defaultPermission: HAZO_ADMIN_PERMISSIONS.FILES_MANAGE,
|
|
90
|
+
group: 'content',
|
|
91
|
+
icon: () => React.createElement(FolderOpen, { size: 16 }),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
kind: 'feedback',
|
|
95
|
+
slug: 'feedback',
|
|
96
|
+
defaultLabel: 'Feedback',
|
|
97
|
+
defaultPermission: HAZO_ADMIN_PERMISSIONS.FEEDBACK_REVIEW,
|
|
98
|
+
group: 'content',
|
|
99
|
+
icon: () => React.createElement(MessageSquare, { size: 16 }),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
kind: 'metrics',
|
|
103
|
+
slug: 'analytics',
|
|
104
|
+
defaultLabel: 'Metrics',
|
|
105
|
+
defaultPermission: HAZO_ADMIN_PERMISSIONS.METRICS_VIEW,
|
|
106
|
+
defaultBasePath: '/api/admin/metrics',
|
|
107
|
+
group: 'operate',
|
|
108
|
+
icon: () => React.createElement(BarChart3, { size: 16 }),
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
export function getKindDef(kind) {
|
|
112
|
+
return ADMIN_KINDS.find((k) => k.kind === kind);
|
|
113
|
+
}
|
|
114
|
+
export function getDefaultPermission(kind) {
|
|
115
|
+
return getKindDef(kind)?.defaultPermission;
|
|
116
|
+
}
|
|
117
|
+
export function getDefaultIcon(kind) {
|
|
118
|
+
const def = getKindDef(kind);
|
|
119
|
+
return def ? def.icon() : null;
|
|
120
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_layout.d.ts","sourceRoot":"","sources":["../../src/components/admin_layout.tsx"],"names":[],"mappings":"AACA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAKxC,OAAO,KAAK,EAAE,aAAa,EAAgB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"admin_layout.d.ts","sourceRoot":"","sources":["../../src/components/admin_layout.tsx"],"names":[],"mappings":"AACA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAKxC,OAAO,KAAK,EAAE,aAAa,EAAgB,MAAM,gBAAgB,CAAC;AAIlE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AASD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,gBAAgB,qBAsFrG"}
|
|
@@ -3,21 +3,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import Link from 'next/link';
|
|
5
5
|
import { usePathname } from 'next/navigation';
|
|
6
|
-
import { ChevronLeft, ChevronRight,
|
|
7
|
-
import { HazoContextProvider, cn } from 'hazo_ui';
|
|
6
|
+
import { ChevronLeft, ChevronRight, LayoutDashboard } from 'lucide-react';
|
|
7
|
+
import { HazoContextProvider, cn, ScrollArea, Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from 'hazo_ui';
|
|
8
8
|
import { resolveNav } from './admin_nav.js';
|
|
9
|
-
|
|
9
|
+
import { getDefaultIcon } from './admin_kinds.js';
|
|
10
|
+
// Default icon per kind — falls back to registry, then generic dashboard icon
|
|
10
11
|
function defaultIcon(kind) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return _jsx(Briefcase, { size: 16 });
|
|
15
|
-
if (kind === 'logs')
|
|
16
|
-
return _jsx(FileText, { size: 16 });
|
|
17
|
-
if (kind === 'env_migration')
|
|
18
|
-
return _jsx(ArrowLeftRight, { size: 16 });
|
|
19
|
-
if (kind === 'health')
|
|
20
|
-
return _jsx(Activity, { size: 16 });
|
|
12
|
+
const icon = getDefaultIcon(kind);
|
|
13
|
+
if (icon)
|
|
14
|
+
return icon;
|
|
21
15
|
return _jsx(LayoutDashboard, { size: 16 });
|
|
22
16
|
}
|
|
23
17
|
export function AdminLayout({ manifest, user, permissions, children, correlationId }) {
|
|
@@ -27,11 +21,15 @@ export function AdminLayout({ manifest, user, permissions, children, correlation
|
|
|
27
21
|
function isActive(item) {
|
|
28
22
|
return item.path === '/' ? pathname === item.path : pathname?.startsWith(item.path);
|
|
29
23
|
}
|
|
30
|
-
return (_jsx(HazoContextProvider, { correlationId: correlationId, userId: user?.id, children: _jsxs("div", { className: "flex min-h-screen", children: [_jsxs("aside", { className: cn('flex flex-col border-r border-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
return (_jsx(HazoContextProvider, { correlationId: correlationId, userId: user?.id, children: _jsx(TooltipProvider, { delayDuration: 300, children: _jsxs("div", { className: "flex min-h-screen", children: [_jsxs("aside", { className: cn('flex flex-col border-r border-border bg-sidebar transition-all duration-200', collapsed ? 'w-14' : 'w-56'), children: [_jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-3", children: [!collapsed && (_jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: manifest.title ?? 'Admin' })), _jsx("button", { onClick: () => setCollapsed((c) => !c), className: "ml-auto flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-accent-foreground", "aria-label": collapsed ? 'Expand sidebar' : 'Collapse sidebar', children: collapsed ? _jsx(ChevronRight, { size: 14 }) : _jsx(ChevronLeft, { size: 14 }) })] }), _jsx(ScrollArea, { className: "flex-1", children: _jsx("nav", { className: "flex flex-col gap-1 p-2", children: navItems.map((item) => {
|
|
25
|
+
const active = isActive(item);
|
|
26
|
+
const icon = item.icon ?? defaultIcon(item.kind);
|
|
27
|
+
const linkEl = (_jsxs(Link, { href: item.path, className: cn('flex items-center gap-2 rounded-md px-2 py-2 text-sm transition-colors', collapsed ? 'justify-center' : '', active
|
|
28
|
+
? 'bg-accent text-accent-foreground font-medium'
|
|
29
|
+
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'), children: [_jsx("span", { className: "shrink-0", children: icon }), !collapsed && _jsx("span", { className: "truncate", children: item.label })] }, item.path));
|
|
30
|
+
if (collapsed) {
|
|
31
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: linkEl }), _jsx(TooltipContent, { side: "right", children: item.label })] }, item.path));
|
|
32
|
+
}
|
|
33
|
+
return linkEl;
|
|
34
|
+
}) }) }), !collapsed && user && (_jsx("div", { className: "border-t border-border px-3 py-2 text-xs text-muted-foreground truncate", children: user.name ?? user.email_address }))] }), _jsx("main", { className: "flex-1 overflow-auto", children: children })] }) }) }));
|
|
37
35
|
}
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
export type AdminNavSection = {
|
|
3
|
-
kind: 'users' | 'jobs' | 'logs' | 'env_migration' | 'health';
|
|
3
|
+
kind: 'users' | 'jobs' | 'logs' | 'env_migration' | 'health' | 'masking' | 'audit' | 'settings' | 'api_keys' | 'blog' | 'files' | 'feedback';
|
|
4
4
|
label?: string;
|
|
5
5
|
icon?: React.ReactNode;
|
|
6
6
|
permission?: string;
|
|
7
7
|
basePath?: string;
|
|
8
|
+
group?: string;
|
|
9
|
+
order?: number;
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'metrics';
|
|
12
|
+
label?: string;
|
|
13
|
+
icon?: React.ReactNode;
|
|
14
|
+
permission?: string;
|
|
15
|
+
basePath?: string;
|
|
16
|
+
component?: React.ReactNode;
|
|
17
|
+
group?: string;
|
|
18
|
+
order?: number;
|
|
8
19
|
} | {
|
|
9
20
|
kind: 'custom';
|
|
10
21
|
label: string;
|
|
@@ -12,19 +23,23 @@ export type AdminNavSection = {
|
|
|
12
23
|
permission?: string;
|
|
13
24
|
path: string;
|
|
14
25
|
component: React.ReactNode;
|
|
26
|
+
group?: string;
|
|
27
|
+
order?: number;
|
|
15
28
|
};
|
|
16
29
|
export type AdminManifest = {
|
|
17
30
|
sections: AdminNavSection[];
|
|
18
31
|
title?: string;
|
|
19
32
|
};
|
|
20
33
|
export type AdminNavItem = {
|
|
21
|
-
kind: 'users' | 'jobs' | 'logs' | 'env_migration' | 'health' | 'custom';
|
|
34
|
+
kind: 'users' | 'jobs' | 'logs' | 'env_migration' | 'health' | 'masking' | 'audit' | 'settings' | 'api_keys' | 'blog' | 'files' | 'feedback' | 'metrics' | 'custom';
|
|
22
35
|
label: string;
|
|
23
36
|
path: string;
|
|
24
37
|
basePath: string;
|
|
25
38
|
icon?: React.ReactNode;
|
|
26
39
|
permission: string;
|
|
27
40
|
component?: React.ReactNode;
|
|
41
|
+
group?: string;
|
|
42
|
+
order?: number;
|
|
28
43
|
};
|
|
29
44
|
export declare function defaultPermissionForKind(kind: string): string;
|
|
30
45
|
export declare function resolveNav(manifest: AdminManifest, permissions: string[]): AdminNavItem[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_nav.d.ts","sourceRoot":"","sources":["../../src/components/admin_nav.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"admin_nav.d.ts","sourceRoot":"","sources":["../../src/components/admin_nav.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,MAAM,MAAM,eAAe,GACvB;IACE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IAC7I,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IACpK,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAuKzF"}
|