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.
Files changed (47) hide show
  1. package/CHANGE_LOG.md +17 -0
  2. package/README.md +72 -4
  3. package/SETUP_CHECKLIST.md +9 -1
  4. package/dist/api/index.d.ts.map +1 -1
  5. package/dist/api/index.js +109 -5
  6. package/dist/components/admin_app.d.ts.map +1 -1
  7. package/dist/components/admin_app.js +26 -0
  8. package/dist/components/admin_kinds.d.ts +15 -0
  9. package/dist/components/admin_kinds.d.ts.map +1 -0
  10. package/dist/components/admin_kinds.js +120 -0
  11. package/dist/components/admin_layout.d.ts.map +1 -1
  12. package/dist/components/admin_layout.js +18 -20
  13. package/dist/components/admin_nav.d.ts +17 -2
  14. package/dist/components/admin_nav.d.ts.map +1 -1
  15. package/dist/components/admin_nav.js +111 -1
  16. package/dist/components/api_keys_panel.d.ts +5 -0
  17. package/dist/components/api_keys_panel.d.ts.map +1 -0
  18. package/dist/components/api_keys_panel.js +21 -0
  19. package/dist/components/audit_panel.d.ts +5 -0
  20. package/dist/components/audit_panel.d.ts.map +1 -0
  21. package/dist/components/audit_panel.js +19 -0
  22. package/dist/components/blog_panel.d.ts +5 -0
  23. package/dist/components/blog_panel.d.ts.map +1 -0
  24. package/dist/components/blog_panel.js +23 -0
  25. package/dist/components/env_migration_panel.d.ts.map +1 -1
  26. package/dist/components/env_migration_panel.js +6 -8
  27. package/dist/components/feedback_panel.d.ts +5 -0
  28. package/dist/components/feedback_panel.d.ts.map +1 -0
  29. package/dist/components/feedback_panel.js +24 -0
  30. package/dist/components/files_panel.d.ts +5 -0
  31. package/dist/components/files_panel.d.ts.map +1 -0
  32. package/dist/components/files_panel.js +22 -0
  33. package/dist/components/masking_panel.d.ts +5 -0
  34. package/dist/components/masking_panel.d.ts.map +1 -0
  35. package/dist/components/masking_panel.js +65 -0
  36. package/dist/components/settings_panel.d.ts +5 -0
  37. package/dist/components/settings_panel.d.ts.map +1 -0
  38. package/dist/components/settings_panel.js +19 -0
  39. package/dist/index.client.d.ts +8 -0
  40. package/dist/index.client.d.ts.map +1 -1
  41. package/dist/index.client.js +8 -0
  42. package/dist/index.ui.d.ts +2 -0
  43. package/dist/index.ui.d.ts.map +1 -1
  44. package/dist/index.ui.js +1 -0
  45. package/dist/preset/page_factory.d.ts.map +1 -1
  46. package/dist/preset/page_factory.js +9 -13
  47. 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
- // HAZO_ADMIN_PERMISSIONS.ADMIN_SYSTEM = 'admin_system'
74
- // HAZO_ADMIN_PERMISSIONS.ENV_MIGRATE = 'env.migrate'
75
- // HAZO_ADMIN_PERMISSIONS.ENV_MASK_MANAGE = 'env.mask.manage'
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` stub | Server only |
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 |
@@ -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
 
@@ -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+MlE,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;EAsCnG;AAGD,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,GAChB,MAAM,0BAA0B,CAAC"}
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 envs = hazoEnv.listEnvs();
117
- return new Response(JSON.stringify(envs), {
118
- status: 200,
119
- headers: { 'Content-Type': 'application/json' },
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;AAOvE,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;AAYD,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,aAAa,+BAkBrF"}
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;AAGlE,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;AAYD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,gBAAgB,qBAwErG"}
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, Users, Briefcase, FileText, LayoutDashboard, ArrowLeftRight, Activity } from 'lucide-react';
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
- // Default icon per kind
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
- if (kind === 'users')
12
- return _jsx(Users, { size: 16 });
13
- if (kind === 'jobs')
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-gray-200 bg-white transition-all duration-200', collapsed ? 'w-14' : 'w-56'), children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-200 px-3 py-3", children: [!collapsed && (_jsx("span", { className: "truncate text-sm font-semibold text-gray-800", children: manifest.title ?? 'Admin' })), _jsx("button", { onClick: () => setCollapsed((c) => !c), className: "ml-auto flex h-6 w-6 items-center justify-center rounded text-gray-400 hover:bg-gray-100 hover:text-gray-600", "aria-label": collapsed ? 'Expand sidebar' : 'Collapse sidebar', children: collapsed ? _jsx(ChevronRight, { size: 14 }) : _jsx(ChevronLeft, { size: 14 }) })] }), _jsx("nav", { className: "flex flex-col gap-1 p-2 flex-1", children: navItems.map((item) => {
31
- const active = isActive(item);
32
- const icon = item.icon ?? defaultIcon(item.kind);
33
- return (_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
34
- ? 'bg-blue-50 text-blue-700 font-medium'
35
- : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'), title: collapsed ? item.label : undefined, children: [_jsx("span", { className: "shrink-0", children: icon }), !collapsed && _jsx("span", { className: "truncate", children: item.label })] }, item.path));
36
- }) }), !collapsed && user && (_jsx("div", { className: "border-t border-gray-200 px-3 py-2 text-xs text-gray-400 truncate", children: user.name ?? user.email_address }))] }), _jsx("main", { className: "flex-1 overflow-auto", children: children })] }) }));
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;AAI/B,MAAM,MAAM,eAAe,GACvB;IACE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,eAAe,GAAG,QAAQ,CAAC;IAC7D,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;CACnB,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;CAC5B,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,QAAQ,CAAC;IACxE,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;CAC7B,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,CAkEzF"}
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"}