@nsxbet/admin-sdk 0.5.1 → 0.6.0
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/CHECKLIST.md +40 -10
- package/README.md +337 -36
- package/dist/auth/client/gateway-token.d.ts +19 -0
- package/dist/auth/client/gateway-token.js +89 -0
- package/dist/auth/client/in-memory.d.ts +5 -1
- package/dist/auth/client/in-memory.js +75 -38
- package/dist/auth/client/index.d.ts +0 -1
- package/dist/auth/client/interface.d.ts +6 -3
- package/dist/auth/client/keycloak.d.ts +0 -1
- package/dist/auth/client/keycloak.js +6 -3
- package/dist/auth/components/UserSelector.d.ts +0 -1
- package/dist/auth/components/UserSelector.js +89 -7
- package/dist/auth/components/index.d.ts +0 -1
- package/dist/auth/index.d.ts +0 -1
- package/dist/components/AuthProvider.d.ts +0 -1
- package/dist/components/Timestamp.d.ts +7 -0
- package/dist/components/Timestamp.js +50 -0
- package/dist/hooks/useAuth.d.ts +0 -1
- package/dist/hooks/useAuth.js +1 -1
- package/dist/hooks/useFetch.d.ts +0 -1
- package/dist/hooks/useI18n.d.ts +0 -1
- package/dist/hooks/usePlatformAPI.d.ts +0 -1
- package/dist/hooks/useTelemetry.d.ts +0 -1
- package/dist/hooks/useTimestamp.d.ts +8 -0
- package/dist/hooks/useTimestamp.js +122 -0
- package/dist/i18n/config.d.ts +20 -2
- package/dist/i18n/config.js +48 -0
- package/dist/i18n/index.d.ts +2 -3
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/locales/en-US.json +95 -18
- package/dist/i18n/locales/es.json +95 -18
- package/dist/i18n/locales/pt-BR.json +95 -18
- package/dist/i18n/locales/ro.json +95 -18
- package/dist/index.d.ts +11 -7
- package/dist/index.js +5 -1
- package/dist/registry/AdminShellRegistry.d.ts +1 -2
- package/dist/registry/cache/cached-catalog.d.ts +11 -0
- package/dist/registry/cache/cached-catalog.js +42 -0
- package/dist/registry/cache/catalog-cache.d.ts +10 -0
- package/dist/registry/cache/catalog-cache.js +58 -0
- package/dist/registry/cache/index.d.ts +5 -0
- package/dist/registry/cache/index.js +3 -0
- package/dist/registry/cache/types.d.ts +20 -0
- package/dist/registry/cache/types.js +3 -0
- package/dist/registry/client/http.d.ts +0 -1
- package/dist/registry/client/http.js +13 -0
- package/dist/registry/client/in-memory.d.ts +0 -1
- package/dist/registry/client/in-memory.js +117 -12
- package/dist/registry/client/index.d.ts +0 -1
- package/dist/registry/client/interface.d.ts +21 -6
- package/dist/registry/index.d.ts +5 -2
- package/dist/registry/index.js +4 -0
- package/dist/registry/types/index.d.ts +2 -3
- package/dist/registry/types/manifest.d.ts +20 -24
- package/dist/registry/types/manifest.js +17 -18
- package/dist/registry/types/module.d.ts +43 -14
- package/dist/registry/useRegistryPolling.d.ts +15 -0
- package/dist/registry/useRegistryPolling.js +66 -0
- package/dist/router/DynamicModule.d.ts +6 -22
- package/dist/router/DynamicModule.js +25 -48
- package/dist/router/ModuleErrorBoundary.d.ts +39 -0
- package/dist/router/ModuleErrorBoundary.js +101 -0
- package/dist/router/index.d.ts +1 -1
- package/dist/router/url-allowlist.d.ts +22 -0
- package/dist/router/url-allowlist.js +65 -0
- package/dist/shell/AdminShell.d.ts +0 -1
- package/dist/shell/AdminShell.js +178 -43
- package/dist/shell/BackofficeShell.d.ts +0 -1
- package/dist/shell/BackofficeShell.js +59 -25
- package/dist/shell/components/CommandPalette.d.ts +0 -1
- package/dist/shell/components/CommandPalette.js +26 -50
- package/dist/shell/components/DevtoolsPanel.d.ts +11 -0
- package/dist/shell/components/DevtoolsPanel.js +145 -0
- package/dist/shell/components/HomePage.d.ts +0 -1
- package/dist/shell/components/HomePage.js +9 -4
- package/dist/shell/components/LeftNav.d.ts +0 -1
- package/dist/shell/components/LeftNav.js +91 -93
- package/dist/shell/components/MainContent.d.ts +3 -2
- package/dist/shell/components/MainContent.js +8 -23
- package/dist/shell/components/ModuleOverview.d.ts +0 -1
- package/dist/shell/components/ModuleOverview.js +4 -20
- package/dist/shell/components/ProfilePage.d.ts +0 -1
- package/dist/shell/components/ProfilePage.js +1 -1
- package/dist/shell/components/RegistryPage.d.ts +0 -1
- package/dist/shell/components/RegistryPage.js +154 -64
- package/dist/shell/components/RegistryStatusBanner.d.ts +6 -0
- package/dist/shell/components/RegistryStatusBanner.js +31 -0
- package/dist/shell/components/RegistryUnavailable.d.ts +4 -0
- package/dist/shell/components/RegistryUnavailable.js +7 -0
- package/dist/shell/components/SettingsPage.d.ts +0 -1
- package/dist/shell/components/StackedPanel.d.ts +15 -0
- package/dist/shell/components/StackedPanel.js +45 -0
- package/dist/shell/components/TopBar.d.ts +4 -2
- package/dist/shell/components/TopBar.js +9 -3
- package/dist/shell/components/UpdateBanner.d.ts +5 -0
- package/dist/shell/components/UpdateBanner.js +8 -0
- package/dist/shell/components/index.d.ts +4 -1
- package/dist/shell/components/index.js +2 -0
- package/dist/shell/components/theme-provider.d.ts +0 -1
- package/dist/shell/components/theme-provider.js +8 -5
- package/dist/shell/hooks/useCspViolations.d.ts +12 -0
- package/dist/shell/hooks/useCspViolations.js +34 -0
- package/dist/shell/index.d.ts +1 -2
- package/dist/shell/polling-config.d.ts +10 -0
- package/dist/shell/polling-config.js +26 -0
- package/dist/shell/search/fuzzy.d.ts +0 -1
- package/dist/shell/search/index.d.ts +0 -1
- package/dist/shell/telemetry.d.ts +0 -1
- package/dist/shell/types.d.ts +34 -18
- package/dist/tailwind/index.d.ts +0 -1
- package/dist/types/keycloak.d.ts +0 -1
- package/dist/types/platform.d.ts +12 -1
- package/dist/vite/AdminShellSharedDeps.d.ts +64 -0
- package/dist/vite/AdminShellSharedDeps.js +215 -0
- package/dist/vite/config.d.ts +2 -2
- package/dist/vite/config.js +5 -7
- package/dist/vite/i18n-plugin.d.ts +13 -0
- package/dist/vite/i18n-plugin.js +81 -0
- package/dist/vite/index.d.ts +2 -1
- package/dist/vite/index.js +2 -0
- package/dist/vite/plugins.d.ts +0 -1
- package/package.json +6 -2
- package/dist/auth/client/in-memory.d.ts.map +0 -1
- package/dist/auth/client/index.d.ts.map +0 -1
- package/dist/auth/client/interface.d.ts.map +0 -1
- package/dist/auth/client/keycloak.d.ts.map +0 -1
- package/dist/auth/components/UserSelector.d.ts.map +0 -1
- package/dist/auth/components/index.d.ts.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/components/AuthProvider.d.ts.map +0 -1
- package/dist/hooks/useAuth.d.ts.map +0 -1
- package/dist/hooks/useFetch.d.ts.map +0 -1
- package/dist/hooks/useI18n.d.ts.map +0 -1
- package/dist/hooks/usePlatformAPI.d.ts.map +0 -1
- package/dist/hooks/useTelemetry.d.ts.map +0 -1
- package/dist/i18n/config.d.ts.map +0 -1
- package/dist/i18n/index.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/registry/AdminShellRegistry.d.ts.map +0 -1
- package/dist/registry/client/http.d.ts.map +0 -1
- package/dist/registry/client/in-memory.d.ts.map +0 -1
- package/dist/registry/client/index.d.ts.map +0 -1
- package/dist/registry/client/interface.d.ts.map +0 -1
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/types/index.d.ts.map +0 -1
- package/dist/registry/types/manifest.d.ts.map +0 -1
- package/dist/registry/types/module.d.ts.map +0 -1
- package/dist/router/DynamicModule.d.ts.map +0 -1
- package/dist/router/index.d.ts.map +0 -1
- package/dist/shell/AdminShell.d.ts.map +0 -1
- package/dist/shell/BackofficeShell.d.ts.map +0 -1
- package/dist/shell/components/CommandPalette.d.ts.map +0 -1
- package/dist/shell/components/HomePage.d.ts.map +0 -1
- package/dist/shell/components/LeftNav.d.ts.map +0 -1
- package/dist/shell/components/MainContent.d.ts.map +0 -1
- package/dist/shell/components/ModuleOverview.d.ts.map +0 -1
- package/dist/shell/components/ProfilePage.d.ts.map +0 -1
- package/dist/shell/components/RegistryPage.d.ts.map +0 -1
- package/dist/shell/components/SettingsPage.d.ts.map +0 -1
- package/dist/shell/components/TopBar.d.ts.map +0 -1
- package/dist/shell/components/index.d.ts.map +0 -1
- package/dist/shell/components/theme-provider.d.ts.map +0 -1
- package/dist/shell/index.d.ts.map +0 -1
- package/dist/shell/search/fuzzy.d.ts.map +0 -1
- package/dist/shell/search/index.d.ts.map +0 -1
- package/dist/shell/telemetry.d.ts.map +0 -1
- package/dist/shell/types.d.ts.map +0 -1
- package/dist/tailwind/index.d.ts.map +0 -1
- package/dist/types/keycloak.d.ts.map +0 -1
- package/dist/types/platform.d.ts.map +0 -1
- package/dist/vite/config.d.ts.map +0 -1
- package/dist/vite/index.d.ts.map +0 -1
- package/dist/vite/plugins.d.ts.map +0 -1
|
@@ -11,8 +11,37 @@ const DEFAULT_STORAGE_KEY = '@nsxbet/registry';
|
|
|
11
11
|
*/
|
|
12
12
|
export function createInMemoryRegistryClient(options = {}) {
|
|
13
13
|
const storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
14
|
+
const versionsKey = `${storageKey}-versions`;
|
|
14
15
|
// Initialize or load storage
|
|
15
16
|
let data = loadStorage(storageKey);
|
|
17
|
+
function loadVersions() {
|
|
18
|
+
try {
|
|
19
|
+
const stored = localStorage.getItem(versionsKey);
|
|
20
|
+
if (stored)
|
|
21
|
+
return JSON.parse(stored);
|
|
22
|
+
}
|
|
23
|
+
catch { /* ignore */ }
|
|
24
|
+
return { versions: [], autoIncrement: 0 };
|
|
25
|
+
}
|
|
26
|
+
function saveVersions(vData) {
|
|
27
|
+
try {
|
|
28
|
+
localStorage.setItem(versionsKey, JSON.stringify(vData));
|
|
29
|
+
}
|
|
30
|
+
catch { /* ignore */ }
|
|
31
|
+
}
|
|
32
|
+
function recordVersion(registeredModuleId, version, baseUrl, metadata) {
|
|
33
|
+
const vData = loadVersions();
|
|
34
|
+
vData.versions.push({
|
|
35
|
+
id: ++vData.autoIncrement,
|
|
36
|
+
registeredModuleId,
|
|
37
|
+
version,
|
|
38
|
+
baseUrl,
|
|
39
|
+
publishedAt: new Date().toISOString(),
|
|
40
|
+
active: false,
|
|
41
|
+
metadata,
|
|
42
|
+
});
|
|
43
|
+
saveVersions(vData);
|
|
44
|
+
}
|
|
16
45
|
// Apply seed if provided and storage is empty
|
|
17
46
|
// Note: seed is for standalone mode where modules are imported directly (no baseUrl needed)
|
|
18
47
|
if (options.seed && data.modules.length === 0) {
|
|
@@ -24,7 +53,7 @@ export function createInMemoryRegistryClient(options = {}) {
|
|
|
24
53
|
moduleId: validated.id,
|
|
25
54
|
baseUrl: '',
|
|
26
55
|
manifest: validated,
|
|
27
|
-
|
|
56
|
+
navigationOrder: validated.navigationOrder ?? data.autoIncrement * 10,
|
|
28
57
|
enabled: true,
|
|
29
58
|
registeredAt: now,
|
|
30
59
|
updatedAt: now,
|
|
@@ -43,7 +72,7 @@ export function createInMemoryRegistryClient(options = {}) {
|
|
|
43
72
|
moduleId: validated.id,
|
|
44
73
|
baseUrl,
|
|
45
74
|
manifest: validated,
|
|
46
|
-
|
|
75
|
+
navigationOrder: validated.navigationOrder ?? data.autoIncrement * 10,
|
|
47
76
|
enabled: true,
|
|
48
77
|
registeredAt: now,
|
|
49
78
|
updatedAt: now,
|
|
@@ -63,10 +92,10 @@ export function createInMemoryRegistryClient(options = {}) {
|
|
|
63
92
|
result = result.filter((m) => m.manifest.category === filters.category);
|
|
64
93
|
}
|
|
65
94
|
if (filters?.status) {
|
|
66
|
-
result = result.filter((
|
|
95
|
+
result = result.filter(() => filters.status === 'active');
|
|
67
96
|
}
|
|
68
|
-
// Sort by
|
|
69
|
-
result.sort((a, b) => a.
|
|
97
|
+
// Sort by navigationOrder
|
|
98
|
+
result.sort((a, b) => a.navigationOrder - b.navigationOrder);
|
|
70
99
|
return result;
|
|
71
100
|
},
|
|
72
101
|
async find(id) {
|
|
@@ -107,7 +136,7 @@ export function createInMemoryRegistryClient(options = {}) {
|
|
|
107
136
|
moduleId: validated.id,
|
|
108
137
|
baseUrl,
|
|
109
138
|
manifest: validated,
|
|
110
|
-
|
|
139
|
+
navigationOrder: validated.navigationOrder ?? data.autoIncrement * 10,
|
|
111
140
|
enabled: true,
|
|
112
141
|
registeredAt: now,
|
|
113
142
|
updatedAt: now,
|
|
@@ -125,12 +154,17 @@ export function createInMemoryRegistryClient(options = {}) {
|
|
|
125
154
|
const module = data.modules[index];
|
|
126
155
|
const updated = {
|
|
127
156
|
...module,
|
|
128
|
-
...(updateData.
|
|
157
|
+
...(updateData.navigationOrder !== undefined && { navigationOrder: updateData.navigationOrder }),
|
|
129
158
|
...(updateData.enabled !== undefined && { enabled: updateData.enabled }),
|
|
159
|
+
...(updateData.baseUrl !== undefined && { baseUrl: updateData.baseUrl }),
|
|
160
|
+
...(updateData.version !== undefined && { version: updateData.version }),
|
|
130
161
|
updatedAt: new Date().toISOString(),
|
|
131
162
|
};
|
|
132
163
|
data.modules[index] = updated;
|
|
133
164
|
saveStorage(storageKey, data);
|
|
165
|
+
if (updateData.version !== undefined) {
|
|
166
|
+
recordVersion(id, updated.version, updated.baseUrl);
|
|
167
|
+
}
|
|
134
168
|
return updated;
|
|
135
169
|
},
|
|
136
170
|
async delete(id) {
|
|
@@ -145,16 +179,71 @@ export function createInMemoryRegistryClient(options = {}) {
|
|
|
145
179
|
async reorder(order) {
|
|
146
180
|
data = loadStorage(storageKey);
|
|
147
181
|
const now = new Date().toISOString();
|
|
148
|
-
for (const { id,
|
|
182
|
+
for (const { id, navigationOrder } of order) {
|
|
149
183
|
const module = data.modules.find((m) => m.id === id);
|
|
150
184
|
if (module) {
|
|
151
|
-
module.
|
|
185
|
+
module.navigationOrder = navigationOrder;
|
|
152
186
|
module.updatedAt = now;
|
|
153
187
|
}
|
|
154
188
|
}
|
|
155
189
|
saveStorage(storageKey, data);
|
|
156
190
|
},
|
|
191
|
+
async listVersions(id) {
|
|
192
|
+
data = loadStorage(storageKey);
|
|
193
|
+
const module = data.modules.find((m) => m.id === id);
|
|
194
|
+
if (!module) {
|
|
195
|
+
throw new Error(`Module with id ${id} not found`);
|
|
196
|
+
}
|
|
197
|
+
const vData = loadVersions();
|
|
198
|
+
return vData.versions
|
|
199
|
+
.filter((v) => v.registeredModuleId === id)
|
|
200
|
+
.sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime())
|
|
201
|
+
.map(({ registeredModuleId: _, ...v }) => ({
|
|
202
|
+
...v,
|
|
203
|
+
active: v.version === module.version,
|
|
204
|
+
}));
|
|
205
|
+
},
|
|
206
|
+
async rollback(id, targetVersion) {
|
|
207
|
+
data = loadStorage(storageKey);
|
|
208
|
+
const index = data.modules.findIndex((m) => m.id === id);
|
|
209
|
+
if (index === -1) {
|
|
210
|
+
throw new Error(`Module with id ${id} not found`);
|
|
211
|
+
}
|
|
212
|
+
const module = data.modules[index];
|
|
213
|
+
if (module.version === targetVersion) {
|
|
214
|
+
return module;
|
|
215
|
+
}
|
|
216
|
+
const vData = loadVersions();
|
|
217
|
+
const versionEntry = vData.versions.find((v) => v.registeredModuleId === id && v.version === targetVersion);
|
|
218
|
+
if (!versionEntry) {
|
|
219
|
+
throw new Error(`Version ${targetVersion} not found for module ${module.moduleId}`);
|
|
220
|
+
}
|
|
221
|
+
const previousVersion = module.version;
|
|
222
|
+
const updated = {
|
|
223
|
+
...module,
|
|
224
|
+
version: targetVersion,
|
|
225
|
+
baseUrl: versionEntry.baseUrl,
|
|
226
|
+
updatedAt: new Date().toISOString(),
|
|
227
|
+
};
|
|
228
|
+
data.modules[index] = updated;
|
|
229
|
+
saveStorage(storageKey, data);
|
|
230
|
+
recordVersion(id, targetVersion, versionEntry.baseUrl, {
|
|
231
|
+
rolledBackFrom: previousVersion,
|
|
232
|
+
});
|
|
233
|
+
return updated;
|
|
234
|
+
},
|
|
157
235
|
};
|
|
236
|
+
function computeLocalVersion(storageData) {
|
|
237
|
+
const enabledModules = storageData.modules.filter((m) => m.enabled);
|
|
238
|
+
const maxUpdatedAt = enabledModules.reduce((max, m) => (m.updatedAt > max ? m.updatedAt : max), '');
|
|
239
|
+
const raw = `${maxUpdatedAt}:${enabledModules.length}`;
|
|
240
|
+
let hash = 0;
|
|
241
|
+
for (let i = 0; i < raw.length; i++) {
|
|
242
|
+
const chr = raw.charCodeAt(i);
|
|
243
|
+
hash = ((hash << 5) - hash + chr) | 0;
|
|
244
|
+
}
|
|
245
|
+
return Math.abs(hash).toString(16).padStart(8, '0');
|
|
246
|
+
}
|
|
158
247
|
// Catalog operations
|
|
159
248
|
const catalog = {
|
|
160
249
|
async get() {
|
|
@@ -175,23 +264,39 @@ export function createInMemoryRegistryClient(options = {}) {
|
|
|
175
264
|
team: m.manifest.owners?.team ?? 'unknown',
|
|
176
265
|
supportChannel: m.manifest.owners?.supportChannel ?? '#general',
|
|
177
266
|
},
|
|
178
|
-
status:
|
|
179
|
-
|
|
267
|
+
status: 'active',
|
|
268
|
+
navigationOrder: m.navigationOrder,
|
|
180
269
|
icon: m.manifest.icon,
|
|
270
|
+
navigation: m.manifest.navigation ? {
|
|
271
|
+
style: m.manifest.navigation.style,
|
|
272
|
+
sections: m.manifest.navigation.sections?.map(s => ({
|
|
273
|
+
id: s.id,
|
|
274
|
+
title: s.label,
|
|
275
|
+
})),
|
|
276
|
+
} : undefined,
|
|
181
277
|
commands: m.manifest.commands?.map((cmd) => ({
|
|
182
278
|
id: cmd.id,
|
|
183
279
|
title: cmd.title,
|
|
184
280
|
keywords: cmd.keywords,
|
|
185
281
|
route: cmd.route,
|
|
186
282
|
icon: cmd.icon,
|
|
283
|
+
section: cmd.section,
|
|
187
284
|
})),
|
|
188
285
|
}));
|
|
286
|
+
data = loadStorage(storageKey);
|
|
189
287
|
return {
|
|
190
|
-
version:
|
|
288
|
+
version: computeLocalVersion(data),
|
|
191
289
|
generatedAt: new Date().toISOString(),
|
|
192
290
|
modules: catalogModules,
|
|
193
291
|
};
|
|
194
292
|
},
|
|
293
|
+
async version() {
|
|
294
|
+
data = loadStorage(storageKey);
|
|
295
|
+
return {
|
|
296
|
+
version: computeLocalVersion(data),
|
|
297
|
+
generatedAt: new Date().toISOString(),
|
|
298
|
+
};
|
|
299
|
+
},
|
|
195
300
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
196
301
|
async getImportMap(_environment) {
|
|
197
302
|
const allModules = await modules.findAll({ enabled: true });
|
|
@@ -4,4 +4,3 @@
|
|
|
4
4
|
export type { RegistryClient, ModuleOperations, CatalogOperations } from './interface';
|
|
5
5
|
export { createInMemoryRegistryClient, clearInMemoryRegistry, type InMemoryRegistryOptions, type PreConfiguredModule, } from './in-memory';
|
|
6
6
|
export { createHttpRegistryClient, type HttpRegistryOptions, } from './http';
|
|
7
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Defines the interface for registry operations.
|
|
5
5
|
* Can be implemented by in-memory (localStorage) or HTTP clients.
|
|
6
6
|
*/
|
|
7
|
-
import type { RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, ImportMap, AdminModuleManifest } from '../types';
|
|
7
|
+
import type { RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, CatalogVersion, ImportMap, AdminModuleManifest, ModuleVersion } from '../types';
|
|
8
8
|
/**
|
|
9
9
|
* Module operations interface
|
|
10
10
|
*/
|
|
@@ -42,10 +42,21 @@ export interface ModuleOperations {
|
|
|
42
42
|
*/
|
|
43
43
|
delete(id: number): Promise<void>;
|
|
44
44
|
/**
|
|
45
|
-
* Reorder modules (batch update
|
|
46
|
-
* @param order Array of id and
|
|
45
|
+
* Reorder modules (batch update navigationOrder)
|
|
46
|
+
* @param order Array of id and navigationOrder pairs
|
|
47
47
|
*/
|
|
48
48
|
reorder(order: ReorderModuleDto[]): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* List published versions for a module
|
|
51
|
+
* @param id Module ID
|
|
52
|
+
*/
|
|
53
|
+
listVersions(id: number): Promise<ModuleVersion[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Rollback a module to a previously published version
|
|
56
|
+
* @param id Module ID
|
|
57
|
+
* @param targetVersion Version string to roll back to
|
|
58
|
+
*/
|
|
59
|
+
rollback(id: number, targetVersion: string): Promise<RegisteredModule>;
|
|
49
60
|
}
|
|
50
61
|
/**
|
|
51
62
|
* Catalog operations interface (read-only)
|
|
@@ -55,6 +66,11 @@ export interface CatalogOperations {
|
|
|
55
66
|
* Get the full catalog (denormalized for shell consumption)
|
|
56
67
|
*/
|
|
57
68
|
get(): Promise<Catalog>;
|
|
69
|
+
/**
|
|
70
|
+
* Get the current catalog version without fetching the full catalog.
|
|
71
|
+
* Used for lightweight polling to detect changes.
|
|
72
|
+
*/
|
|
73
|
+
version(): Promise<CatalogVersion>;
|
|
58
74
|
/**
|
|
59
75
|
* Get the import map for a specific environment
|
|
60
76
|
* @param environment Environment name (e.g., 'production', 'local')
|
|
@@ -79,8 +95,8 @@ export interface CatalogOperations {
|
|
|
79
95
|
*
|
|
80
96
|
* // Reorder
|
|
81
97
|
* await registry.modules.reorder([
|
|
82
|
-
* { id: 1,
|
|
83
|
-
* { id: 2,
|
|
98
|
+
* { id: 1, navigationOrder: 2 },
|
|
99
|
+
* { id: 2, navigationOrder: 1 },
|
|
84
100
|
* ]);
|
|
85
101
|
*
|
|
86
102
|
* // Get catalog
|
|
@@ -93,4 +109,3 @@ export interface RegistryClient {
|
|
|
93
109
|
/** Catalog operations (read-only) */
|
|
94
110
|
catalog: CatalogOperations;
|
|
95
111
|
}
|
|
96
|
-
//# sourceMappingURL=interface.d.ts.map
|
package/dist/registry/index.d.ts
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides types and clients for module registry management.
|
|
5
5
|
*/
|
|
6
|
-
export type { AdminModuleManifest, ModuleCommand, ModulePermissions, ModuleOwners,
|
|
6
|
+
export type { AdminModuleManifest, ModuleCommand, ModulePermissions, ModuleOwners, RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, CatalogModule, CatalogVersion, ImportMap, ModuleVersion, } from './types';
|
|
7
7
|
export { validateManifest, parseManifest } from './types';
|
|
8
8
|
export type { RegistryClient, ModuleOperations, CatalogOperations } from './client';
|
|
9
9
|
export { createInMemoryRegistryClient, clearInMemoryRegistry, createHttpRegistryClient, type InMemoryRegistryOptions, type PreConfiguredModule, type HttpRegistryOptions, } from './client';
|
|
10
|
+
export { CatalogCache, createCachedCatalog, CACHE_SCHEMA_VERSION, CACHE_STORAGE_KEY, DEFAULT_STALE_THRESHOLD_MS, } from './cache';
|
|
11
|
+
export type { CachedCatalogOperations, CachedCatalogOptions, CachedCatalog, RegistryCacheStatus, } from './cache';
|
|
12
|
+
export { useRegistryPolling } from './useRegistryPolling';
|
|
13
|
+
export type { RegistryPollingState, UseRegistryPollingOptions } from './useRegistryPolling';
|
|
10
14
|
export type { RegistryContextValue, AdminShellRegistryProps, } from './AdminShellRegistry';
|
|
11
15
|
export { AdminShellRegistry, useRegistry, useRegistryClient, } from './AdminShellRegistry';
|
|
12
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/registry/index.js
CHANGED
|
@@ -5,4 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { validateManifest, parseManifest } from './types';
|
|
7
7
|
export { createInMemoryRegistryClient, clearInMemoryRegistry, createHttpRegistryClient, } from './client';
|
|
8
|
+
// Cache
|
|
9
|
+
export { CatalogCache, createCachedCatalog, CACHE_SCHEMA_VERSION, CACHE_STORAGE_KEY, DEFAULT_STALE_THRESHOLD_MS, } from './cache';
|
|
10
|
+
// Polling
|
|
11
|
+
export { useRegistryPolling } from './useRegistryPolling';
|
|
8
12
|
export { AdminShellRegistry, useRegistry, useRegistryClient, } from './AdminShellRegistry';
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Re-exports all registry-related types.
|
|
5
5
|
*/
|
|
6
|
-
export type { AdminModuleManifest, ModuleCommand, ModulePermissions, ModuleOwners,
|
|
6
|
+
export type { AdminModuleManifest, ModuleCommand, ModulePermissions, ModuleOwners, } from './manifest';
|
|
7
7
|
export { validateManifest, parseManifest } from './manifest';
|
|
8
|
-
export type { RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, CatalogModule, ImportMap, } from './module';
|
|
9
|
-
//# sourceMappingURL=index.d.ts.map
|
|
8
|
+
export type { RegisteredModule, UpdateModuleDto, ReorderModuleDto, ModuleFilters, Catalog, CatalogModule, CatalogVersion, ImportMap, ModuleVersion, } from './module';
|
|
@@ -4,22 +4,23 @@
|
|
|
4
4
|
* This interface defines the structure of admin.module.json files
|
|
5
5
|
* that module developers create to register their modules.
|
|
6
6
|
*/
|
|
7
|
+
import type { LocalizedField } from '../../i18n/config.js';
|
|
7
8
|
/**
|
|
8
9
|
* Command within a module that can be searched and invoked
|
|
9
10
|
*/
|
|
10
11
|
export interface ModuleCommand {
|
|
11
12
|
/** Unique identifier for the command within the module */
|
|
12
13
|
id: string;
|
|
13
|
-
/** Human-readable title */
|
|
14
|
-
title:
|
|
15
|
-
/** i18n key for the title (for translation) */
|
|
16
|
-
titleKey?: string;
|
|
14
|
+
/** Human-readable title (localized per locale) */
|
|
15
|
+
title: LocalizedField;
|
|
17
16
|
/** Route path for this command */
|
|
18
17
|
route: string;
|
|
19
18
|
/** Optional Lucide icon name in kebab-case (e.g., "clipboard-list", "file-text") */
|
|
20
19
|
icon?: string;
|
|
21
20
|
/** Keywords for search */
|
|
22
21
|
keywords?: string[];
|
|
22
|
+
/** Section ID for grouping in stacked navigation */
|
|
23
|
+
section?: string;
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* Permission configuration for a module
|
|
@@ -43,10 +44,6 @@ export interface ModuleOwners {
|
|
|
43
44
|
/** Support channel (e.g., Slack channel) */
|
|
44
45
|
supportChannel?: string;
|
|
45
46
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Module status
|
|
48
|
-
*/
|
|
49
|
-
export type ModuleStatus = 'active' | 'deprecated' | 'disabled';
|
|
50
47
|
/**
|
|
51
48
|
* Admin Module Manifest
|
|
52
49
|
*
|
|
@@ -55,30 +52,30 @@ export type ModuleStatus = 'active' | 'deprecated' | 'disabled';
|
|
|
55
52
|
export interface AdminModuleManifest {
|
|
56
53
|
/** Unique identifier for the module (e.g., "@admin/tasks") */
|
|
57
54
|
id: string;
|
|
58
|
-
/** Human-readable title */
|
|
59
|
-
title:
|
|
55
|
+
/** Human-readable title (localized per locale) */
|
|
56
|
+
title: LocalizedField;
|
|
60
57
|
/** Base route where module is mounted (e.g., "/admin/tasks") */
|
|
61
58
|
routeBase: string;
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
/** URL slug (derived from id if not provided) */
|
|
65
|
-
slug?: string;
|
|
66
|
-
/** Description of what the module does */
|
|
67
|
-
description?: string;
|
|
68
|
-
/** i18n key for the description (for translation) */
|
|
69
|
-
descriptionKey?: string;
|
|
59
|
+
/** Description of what the module does (localized per locale, required) */
|
|
60
|
+
description: LocalizedField;
|
|
70
61
|
/** Category for navigation grouping (e.g., "Tools", "Customer") */
|
|
71
62
|
category?: string;
|
|
72
|
-
/** Semantic version */
|
|
73
|
-
version?: string;
|
|
74
63
|
/** Lucide icon name in kebab-case (e.g., "users", "clipboard-list") */
|
|
75
64
|
icon?: string;
|
|
76
65
|
/** Keywords for search */
|
|
77
66
|
keywords?: string[];
|
|
78
67
|
/** Navigation order (lower = higher priority) */
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
|
|
68
|
+
navigationOrder?: number;
|
|
69
|
+
/** Navigation configuration */
|
|
70
|
+
navigation?: {
|
|
71
|
+
/** Navigation style: "collapsible" (default) or "stacked" (dedicated panel) */
|
|
72
|
+
style: "collapsible" | "stacked";
|
|
73
|
+
/** Sections for grouping commands in stacked mode */
|
|
74
|
+
sections?: Array<{
|
|
75
|
+
id: string;
|
|
76
|
+
label: LocalizedField;
|
|
77
|
+
}>;
|
|
78
|
+
};
|
|
82
79
|
/** Commands/actions available in this module */
|
|
83
80
|
commands?: ModuleCommand[];
|
|
84
81
|
/** Permission configuration */
|
|
@@ -95,4 +92,3 @@ export declare function validateManifest(value: unknown): value is AdminModuleMa
|
|
|
95
92
|
* @throws Error if validation fails
|
|
96
93
|
*/
|
|
97
94
|
export declare function parseManifest(value: unknown): AdminModuleManifest;
|
|
98
|
-
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
* This interface defines the structure of admin.module.json files
|
|
5
5
|
* that module developers create to register their modules.
|
|
6
6
|
*/
|
|
7
|
+
import { SUPPORTED_LOCALES } from '../../i18n/config.js';
|
|
8
|
+
function isLocalizedField(value) {
|
|
9
|
+
if (typeof value !== 'object' || value === null)
|
|
10
|
+
return false;
|
|
11
|
+
const obj = value;
|
|
12
|
+
for (const locale of SUPPORTED_LOCALES) {
|
|
13
|
+
if (typeof obj[locale] !== 'string')
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
7
18
|
/**
|
|
8
19
|
* Validates that an object is a valid AdminModuleManifest
|
|
9
20
|
*/
|
|
@@ -16,35 +27,23 @@ export function validateManifest(value) {
|
|
|
16
27
|
if (typeof obj.id !== 'string' || obj.id.trim() === '') {
|
|
17
28
|
return false;
|
|
18
29
|
}
|
|
19
|
-
if (
|
|
30
|
+
if (!isLocalizedField(obj.title)) {
|
|
20
31
|
return false;
|
|
21
32
|
}
|
|
22
33
|
if (typeof obj.routeBase !== 'string' || !obj.routeBase.startsWith('/')) {
|
|
23
34
|
return false;
|
|
24
35
|
}
|
|
25
|
-
|
|
26
|
-
if (obj.slug !== undefined && typeof obj.slug !== 'string') {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
if (obj.description !== undefined && typeof obj.description !== 'string') {
|
|
36
|
+
if (!isLocalizedField(obj.description)) {
|
|
30
37
|
return false;
|
|
31
38
|
}
|
|
39
|
+
// Optional field validations
|
|
32
40
|
if (obj.category !== undefined && typeof obj.category !== 'string') {
|
|
33
41
|
return false;
|
|
34
42
|
}
|
|
35
|
-
if (obj.version !== undefined && typeof obj.version !== 'string') {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
43
|
if (obj.icon !== undefined && typeof obj.icon !== 'string') {
|
|
39
44
|
return false;
|
|
40
45
|
}
|
|
41
|
-
if (obj.
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
if (obj.status !== undefined &&
|
|
45
|
-
obj.status !== 'active' &&
|
|
46
|
-
obj.status !== 'deprecated' &&
|
|
47
|
-
obj.status !== 'disabled') {
|
|
46
|
+
if (obj.navigationOrder !== undefined && typeof obj.navigationOrder !== 'number') {
|
|
48
47
|
return false;
|
|
49
48
|
}
|
|
50
49
|
if (obj.keywords !== undefined) {
|
|
@@ -61,7 +60,7 @@ export function validateManifest(value) {
|
|
|
61
60
|
return false;
|
|
62
61
|
if (typeof cmd.id !== 'string')
|
|
63
62
|
return false;
|
|
64
|
-
if (
|
|
63
|
+
if (!isLocalizedField(cmd.title))
|
|
65
64
|
return false;
|
|
66
65
|
if (typeof cmd.route !== 'string')
|
|
67
66
|
return false;
|
|
@@ -75,7 +74,7 @@ export function validateManifest(value) {
|
|
|
75
74
|
*/
|
|
76
75
|
export function parseManifest(value) {
|
|
77
76
|
if (!validateManifest(value)) {
|
|
78
|
-
throw new Error('Invalid manifest: must have id, title, and routeBase (starting with /)');
|
|
77
|
+
throw new Error('Invalid manifest: must have id, title (localized), description (localized), and routeBase (starting with /). Title and description must be objects with all 4 locales: en-US, pt-BR, es, ro.');
|
|
79
78
|
}
|
|
80
79
|
return value;
|
|
81
80
|
}
|
|
@@ -14,10 +14,12 @@ export interface RegisteredModule {
|
|
|
14
14
|
moduleId: string;
|
|
15
15
|
/** Base URL where the module is hosted */
|
|
16
16
|
baseUrl: string;
|
|
17
|
+
/** Currently active version (semver) */
|
|
18
|
+
version?: string;
|
|
17
19
|
/** Full parsed module.manifest.json (without entry - fetched at runtime) */
|
|
18
20
|
manifest: AdminModuleManifest;
|
|
19
21
|
/** Admin-controlled navigation order */
|
|
20
|
-
|
|
22
|
+
navigationOrder: number;
|
|
21
23
|
/** Whether the module is enabled */
|
|
22
24
|
enabled: boolean;
|
|
23
25
|
/** ISO timestamp when registered */
|
|
@@ -31,9 +33,13 @@ export interface RegisteredModule {
|
|
|
31
33
|
*/
|
|
32
34
|
export interface UpdateModuleDto {
|
|
33
35
|
/** Update navigation order */
|
|
34
|
-
|
|
36
|
+
navigationOrder?: number;
|
|
35
37
|
/** Enable or disable the module */
|
|
36
38
|
enabled?: boolean;
|
|
39
|
+
/** Update base URL */
|
|
40
|
+
baseUrl?: string;
|
|
41
|
+
/** Update active version */
|
|
42
|
+
version?: string;
|
|
37
43
|
}
|
|
38
44
|
/**
|
|
39
45
|
* DTO for reordering modules
|
|
@@ -42,7 +48,7 @@ export interface ReorderModuleDto {
|
|
|
42
48
|
/** Module ID */
|
|
43
49
|
id: number;
|
|
44
50
|
/** New navigation order */
|
|
45
|
-
|
|
51
|
+
navigationOrder: number;
|
|
46
52
|
}
|
|
47
53
|
/**
|
|
48
54
|
* Filters for listing modules
|
|
@@ -66,18 +72,17 @@ export interface Catalog {
|
|
|
66
72
|
/** Array of modules in catalog format */
|
|
67
73
|
modules: CatalogModule[];
|
|
68
74
|
}
|
|
75
|
+
import type { LocalizedField } from '../../i18n/config.js';
|
|
69
76
|
/**
|
|
70
77
|
* Module in catalog format (denormalized for shell)
|
|
71
78
|
* Note: entry is not included - DynamicModule fetches module.manifest.json at runtime
|
|
72
79
|
*/
|
|
73
80
|
export interface CatalogModule {
|
|
74
81
|
id: string;
|
|
75
|
-
title
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
description:
|
|
79
|
-
/** i18n key for the description (for translation) */
|
|
80
|
-
descriptionKey?: string;
|
|
82
|
+
/** Human-readable title (localized per locale) */
|
|
83
|
+
title: LocalizedField;
|
|
84
|
+
/** Description of what the module does (localized per locale) */
|
|
85
|
+
description: LocalizedField;
|
|
81
86
|
category: string;
|
|
82
87
|
routeBase: string;
|
|
83
88
|
/** Base URL where the module is hosted */
|
|
@@ -92,18 +97,32 @@ export interface CatalogModule {
|
|
|
92
97
|
supportChannel: string;
|
|
93
98
|
};
|
|
94
99
|
status: 'active' | 'deprecated' | 'disabled';
|
|
95
|
-
|
|
100
|
+
navigationOrder?: number;
|
|
96
101
|
icon?: string;
|
|
102
|
+
navigation?: {
|
|
103
|
+
style: "collapsible" | "stacked";
|
|
104
|
+
sections?: Array<{
|
|
105
|
+
id: string;
|
|
106
|
+
title: LocalizedField;
|
|
107
|
+
}>;
|
|
108
|
+
};
|
|
97
109
|
commands?: Array<{
|
|
98
110
|
id: string;
|
|
99
|
-
title
|
|
100
|
-
|
|
101
|
-
titleKey?: string;
|
|
111
|
+
/** Human-readable title (localized per locale) */
|
|
112
|
+
title: LocalizedField;
|
|
102
113
|
keywords?: string[];
|
|
103
114
|
route: string;
|
|
104
115
|
icon?: string;
|
|
116
|
+
section?: string;
|
|
105
117
|
}>;
|
|
106
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Lightweight catalog version (no full payload)
|
|
121
|
+
*/
|
|
122
|
+
export interface CatalogVersion {
|
|
123
|
+
version: string;
|
|
124
|
+
generatedAt: string;
|
|
125
|
+
}
|
|
107
126
|
/**
|
|
108
127
|
* Import map structure for dynamic module loading
|
|
109
128
|
*/
|
|
@@ -112,4 +131,14 @@ export interface ImportMap {
|
|
|
112
131
|
[moduleName: string]: string;
|
|
113
132
|
};
|
|
114
133
|
}
|
|
115
|
-
|
|
134
|
+
/**
|
|
135
|
+
* A published version entry from the version history
|
|
136
|
+
*/
|
|
137
|
+
export interface ModuleVersion {
|
|
138
|
+
id: number;
|
|
139
|
+
version: string;
|
|
140
|
+
baseUrl: string;
|
|
141
|
+
publishedAt: string;
|
|
142
|
+
active: boolean;
|
|
143
|
+
metadata?: Record<string, unknown>;
|
|
144
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RegistryClient } from './client/interface';
|
|
2
|
+
export interface RegistryPollingState {
|
|
3
|
+
/** Whether newer catalog version is available */
|
|
4
|
+
hasUpdates: boolean;
|
|
5
|
+
/** Dismiss the current banner; re-shows on a subsequent newer version */
|
|
6
|
+
dismiss: () => void;
|
|
7
|
+
}
|
|
8
|
+
export interface UseRegistryPollingOptions {
|
|
9
|
+
registryClient: RegistryClient;
|
|
10
|
+
/** Version string from the initial catalog.get() response */
|
|
11
|
+
initialVersion: string;
|
|
12
|
+
/** Polling interval in milliseconds. 0 or undefined disables polling. */
|
|
13
|
+
interval: number | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare function useRegistryPolling({ registryClient, initialVersion, interval, }: UseRegistryPollingOptions): RegistryPollingState;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
export function useRegistryPolling({ registryClient, initialVersion, interval, }) {
|
|
3
|
+
const [hasUpdates, setHasUpdates] = useState(false);
|
|
4
|
+
const dismissedVersionRef = useRef(null);
|
|
5
|
+
const latestServerVersionRef = useRef(initialVersion);
|
|
6
|
+
const loadedVersionRef = useRef(initialVersion);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
loadedVersionRef.current = initialVersion;
|
|
9
|
+
latestServerVersionRef.current = initialVersion;
|
|
10
|
+
dismissedVersionRef.current = null;
|
|
11
|
+
setHasUpdates(false);
|
|
12
|
+
}, [initialVersion]);
|
|
13
|
+
const dismiss = useCallback(() => {
|
|
14
|
+
dismissedVersionRef.current = latestServerVersionRef.current;
|
|
15
|
+
setHasUpdates(false);
|
|
16
|
+
}, []);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!interval || interval <= 0)
|
|
19
|
+
return;
|
|
20
|
+
let timerId = null;
|
|
21
|
+
let destroyed = false;
|
|
22
|
+
async function check() {
|
|
23
|
+
try {
|
|
24
|
+
const { version } = await registryClient.catalog.version();
|
|
25
|
+
if (destroyed)
|
|
26
|
+
return;
|
|
27
|
+
latestServerVersionRef.current = version;
|
|
28
|
+
const changed = version !== loadedVersionRef.current;
|
|
29
|
+
const dismissed = version === dismissedVersionRef.current;
|
|
30
|
+
setHasUpdates(changed && !dismissed);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Network errors are silently ignored per spec
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function scheduleNext() {
|
|
37
|
+
if (destroyed)
|
|
38
|
+
return;
|
|
39
|
+
timerId = setTimeout(async () => {
|
|
40
|
+
await check();
|
|
41
|
+
scheduleNext();
|
|
42
|
+
}, interval);
|
|
43
|
+
}
|
|
44
|
+
function handleVisibilityChange() {
|
|
45
|
+
if (document.hidden) {
|
|
46
|
+
if (timerId !== null) {
|
|
47
|
+
clearTimeout(timerId);
|
|
48
|
+
timerId = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
check();
|
|
53
|
+
scheduleNext();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
scheduleNext();
|
|
57
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
58
|
+
return () => {
|
|
59
|
+
destroyed = true;
|
|
60
|
+
if (timerId !== null)
|
|
61
|
+
clearTimeout(timerId);
|
|
62
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
63
|
+
};
|
|
64
|
+
}, [registryClient, interval]);
|
|
65
|
+
return { hasUpdates, dismiss };
|
|
66
|
+
}
|