@nsxbet/admin-sdk 0.5.0 → 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 +10 -2
- package/dist/vite/config.js +13 -10
- 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
package/dist/shell/AdminShell.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
3
|
-
import { BrowserRouter, Routes, Route, useNavigate } from "react-router-dom";
|
|
2
|
+
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
|
3
|
+
import { BrowserRouter, Routes, Route, useNavigate, useLocation } from "react-router-dom";
|
|
4
4
|
import { I18nextProvider } from "react-i18next";
|
|
5
5
|
import { TopBar } from "./components/TopBar";
|
|
6
6
|
import { LeftNav } from "./components/LeftNav";
|
|
7
7
|
import { MainContent } from "./components/MainContent";
|
|
8
8
|
import { CommandPalette } from "./components/CommandPalette";
|
|
9
|
+
import { UpdateBanner } from "./components/UpdateBanner";
|
|
9
10
|
import { ThemeProvider } from "./components/theme-provider";
|
|
10
11
|
import { ModuleOverview } from "./components/ModuleOverview";
|
|
11
12
|
import { ProfilePage } from "./components/ProfilePage";
|
|
@@ -13,13 +14,33 @@ import { SettingsPage } from "./components/SettingsPage";
|
|
|
13
14
|
import { RegistryPage } from "./components/RegistryPage";
|
|
14
15
|
import { HomePage } from "./components/HomePage";
|
|
15
16
|
import { AuthProvider, useAuthContext } from "../components/AuthProvider";
|
|
16
|
-
import { createInMemoryAuthClient } from "../auth/client/in-memory";
|
|
17
|
+
import { createInMemoryAuthClient, createMockUsersFromRoles, } from "../auth/client/in-memory";
|
|
17
18
|
import { createKeycloakAuthClient } from "../auth/client/keycloak";
|
|
18
19
|
import { createInMemoryRegistryClient } from "../registry/client/in-memory";
|
|
20
|
+
import { createCachedCatalog } from "../registry/cache/cached-catalog";
|
|
21
|
+
import { DevtoolsPanel } from "./components/DevtoolsPanel";
|
|
22
|
+
import { RegistryStatusBanner } from "./components/RegistryStatusBanner";
|
|
23
|
+
import { RegistryUnavailable } from "./components/RegistryUnavailable";
|
|
24
|
+
import { useRegistryPolling } from "../registry/useRegistryPolling";
|
|
19
25
|
import { DynamicModule } from "../router/DynamicModule";
|
|
20
26
|
import { SidebarProvider, SidebarInset } from "@nsxbet/admin-ui";
|
|
21
27
|
import { initTelemetry, track, trackError } from "./telemetry";
|
|
22
28
|
import { initI18n, saveLocale, isSupportedLocale, i18n } from "../i18n";
|
|
29
|
+
import { resolvePollingInterval } from "./polling-config";
|
|
30
|
+
const TIMEZONE_STORAGE_KEY = "admin-timezone-mode";
|
|
31
|
+
function getStoredTimezoneMode() {
|
|
32
|
+
if (typeof window === "undefined")
|
|
33
|
+
return "local";
|
|
34
|
+
const stored = localStorage.getItem(TIMEZONE_STORAGE_KEY);
|
|
35
|
+
if (stored === "utc" || stored === "local")
|
|
36
|
+
return stored;
|
|
37
|
+
return "local";
|
|
38
|
+
}
|
|
39
|
+
function saveTimezoneMode(mode) {
|
|
40
|
+
if (typeof window !== "undefined") {
|
|
41
|
+
localStorage.setItem(TIMEZONE_STORAGE_KEY, mode);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
23
44
|
/**
|
|
24
45
|
* Convert AdminModuleManifest to Module for internal use
|
|
25
46
|
*/
|
|
@@ -27,9 +48,7 @@ function manifestToModule(manifest, baseUrl) {
|
|
|
27
48
|
return {
|
|
28
49
|
id: manifest.id,
|
|
29
50
|
title: manifest.title,
|
|
30
|
-
|
|
31
|
-
description: manifest.description || "",
|
|
32
|
-
descriptionKey: manifest.descriptionKey,
|
|
51
|
+
description: manifest.description,
|
|
33
52
|
category: manifest.category || "Modules",
|
|
34
53
|
routeBase: manifest.routeBase,
|
|
35
54
|
baseUrl: baseUrl || "",
|
|
@@ -42,16 +61,23 @@ function manifestToModule(manifest, baseUrl) {
|
|
|
42
61
|
team: manifest.owners?.team || "Platform",
|
|
43
62
|
supportChannel: manifest.owners?.supportChannel || "",
|
|
44
63
|
},
|
|
45
|
-
status:
|
|
46
|
-
|
|
64
|
+
status: "active",
|
|
65
|
+
navigationOrder: manifest.navigationOrder,
|
|
47
66
|
icon: manifest.icon,
|
|
67
|
+
navigation: manifest.navigation ? {
|
|
68
|
+
style: manifest.navigation.style,
|
|
69
|
+
sections: manifest.navigation.sections?.map(s => ({
|
|
70
|
+
id: s.id,
|
|
71
|
+
label: s.label,
|
|
72
|
+
})),
|
|
73
|
+
} : undefined,
|
|
48
74
|
commands: manifest.commands?.map((cmd) => ({
|
|
49
75
|
id: cmd.id,
|
|
50
76
|
title: cmd.title,
|
|
51
|
-
titleKey: cmd.titleKey,
|
|
52
77
|
route: cmd.route,
|
|
53
78
|
icon: cmd.icon,
|
|
54
79
|
keywords: cmd.keywords,
|
|
80
|
+
section: cmd.section,
|
|
55
81
|
})),
|
|
56
82
|
};
|
|
57
83
|
}
|
|
@@ -62,9 +88,7 @@ function catalogModuleToModule(m) {
|
|
|
62
88
|
return {
|
|
63
89
|
id: m.id,
|
|
64
90
|
title: m.title,
|
|
65
|
-
titleKey: m.titleKey,
|
|
66
91
|
description: m.description,
|
|
67
|
-
descriptionKey: m.descriptionKey,
|
|
68
92
|
category: m.category,
|
|
69
93
|
routeBase: m.routeBase,
|
|
70
94
|
baseUrl: m.baseUrl,
|
|
@@ -72,20 +96,36 @@ function catalogModuleToModule(m) {
|
|
|
72
96
|
permissions: m.permissions,
|
|
73
97
|
owners: m.owners,
|
|
74
98
|
status: m.status,
|
|
75
|
-
|
|
99
|
+
navigationOrder: m.navigationOrder,
|
|
76
100
|
icon: m.icon,
|
|
101
|
+
navigation: m.navigation ? {
|
|
102
|
+
style: m.navigation.style,
|
|
103
|
+
sections: m.navigation.sections?.map(s => ({
|
|
104
|
+
id: s.id,
|
|
105
|
+
label: s.title,
|
|
106
|
+
})),
|
|
107
|
+
} : undefined,
|
|
77
108
|
commands: m.commands?.map(cmd => ({
|
|
78
|
-
|
|
79
|
-
|
|
109
|
+
id: cmd.id,
|
|
110
|
+
title: cmd.title,
|
|
111
|
+
route: cmd.route,
|
|
112
|
+
icon: cmd.icon,
|
|
113
|
+
keywords: cmd.keywords,
|
|
114
|
+
section: cmd.section,
|
|
80
115
|
})),
|
|
81
116
|
};
|
|
82
117
|
}
|
|
83
118
|
/**
|
|
84
119
|
* Inner shell component that has access to React Router hooks
|
|
85
120
|
*/
|
|
86
|
-
function ShellContent({ modules, children, environment, locale, onLocaleChange, onSearchClick, catalog, commandPaletteOpen, onCommandPaletteChange, apiUrl, registryClient, isStandaloneMode, }) {
|
|
121
|
+
function ShellContent({ modules, children, environment, locale, onLocaleChange, onSearchClick, catalog, commandPaletteOpen, onCommandPaletteChange, apiUrl, registryClient, isStandaloneMode, cacheStatus, onRetry, hasUpdates, onDismissUpdate, localeCallbacksRef, timezoneMode, onTimezoneToggle, timezoneCallbacksRef, }) {
|
|
87
122
|
const navigate = useNavigate();
|
|
123
|
+
const location = useLocation();
|
|
88
124
|
const auth = useAuthContext();
|
|
125
|
+
const [moduleBreadcrumbs, setModuleBreadcrumbs] = useState(null);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
setModuleBreadcrumbs(null);
|
|
128
|
+
}, [location.pathname]);
|
|
89
129
|
// Set up the platform API for modules to use
|
|
90
130
|
useEffect(() => {
|
|
91
131
|
const platformAPI = {
|
|
@@ -94,10 +134,9 @@ function ShellContent({ modules, children, environment, locale, onLocaleChange,
|
|
|
94
134
|
auth: {
|
|
95
135
|
getAccessToken: auth.getAccessToken,
|
|
96
136
|
hasPermission: auth.hasPermission,
|
|
97
|
-
getUser: () => auth.user || { id: "", email: "", displayName: "" },
|
|
137
|
+
getUser: () => auth.user || { id: "", email: "", displayName: "", roles: [] },
|
|
98
138
|
logout: () => {
|
|
99
139
|
auth.logout();
|
|
100
|
-
// Navigate to root after logout (for Keycloak, this will redirect)
|
|
101
140
|
navigate("/");
|
|
102
141
|
},
|
|
103
142
|
},
|
|
@@ -105,17 +144,29 @@ function ShellContent({ modules, children, environment, locale, onLocaleChange,
|
|
|
105
144
|
navigate: (path) => {
|
|
106
145
|
navigate(path);
|
|
107
146
|
},
|
|
108
|
-
setBreadcrumbs: (
|
|
109
|
-
|
|
147
|
+
setBreadcrumbs: (items) => {
|
|
148
|
+
setModuleBreadcrumbs(items.length > 0 ? items : null);
|
|
110
149
|
},
|
|
111
150
|
},
|
|
112
151
|
i18n: {
|
|
113
152
|
locale: locale,
|
|
114
153
|
setLocale: onLocaleChange,
|
|
115
154
|
onLocaleChange: (callback) => {
|
|
116
|
-
|
|
155
|
+
localeCallbacksRef.current.add(callback);
|
|
117
156
|
callback(locale);
|
|
118
|
-
return () => { };
|
|
157
|
+
return () => { localeCallbacksRef.current.delete(callback); };
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
timestamp: {
|
|
161
|
+
mode: timezoneMode,
|
|
162
|
+
setMode: (mode) => {
|
|
163
|
+
saveTimezoneMode(mode);
|
|
164
|
+
timezoneCallbacksRef.current.forEach(cb => cb(mode));
|
|
165
|
+
},
|
|
166
|
+
onModeChange: (callback) => {
|
|
167
|
+
timezoneCallbacksRef.current.add(callback);
|
|
168
|
+
callback(timezoneMode);
|
|
169
|
+
return () => { timezoneCallbacksRef.current.delete(callback); };
|
|
119
170
|
},
|
|
120
171
|
},
|
|
121
172
|
telemetry: {
|
|
@@ -123,7 +174,6 @@ function ShellContent({ modules, children, environment, locale, onLocaleChange,
|
|
|
123
174
|
trackError: trackError,
|
|
124
175
|
},
|
|
125
176
|
fetch: async (input, init) => {
|
|
126
|
-
// Add auth token to requests
|
|
127
177
|
const token = await auth.getAccessToken();
|
|
128
178
|
const headers = new Headers(init?.headers);
|
|
129
179
|
headers.set("Authorization", `Bearer ${token}`);
|
|
@@ -134,7 +184,7 @@ function ShellContent({ modules, children, environment, locale, onLocaleChange,
|
|
|
134
184
|
return () => {
|
|
135
185
|
delete window.__ADMIN_PLATFORM_API__;
|
|
136
186
|
};
|
|
137
|
-
}, [environment, locale, navigate, onLocaleChange, auth]);
|
|
187
|
+
}, [environment, locale, navigate, onLocaleChange, auth, timezoneMode]);
|
|
138
188
|
// Load initial sidebar state from localStorage
|
|
139
189
|
const getInitialSidebarState = () => {
|
|
140
190
|
try {
|
|
@@ -160,11 +210,15 @@ function ShellContent({ modules, children, environment, locale, onLocaleChange,
|
|
|
160
210
|
// Ignore
|
|
161
211
|
}
|
|
162
212
|
};
|
|
163
|
-
return (_jsxs(_Fragment, { children: [_jsxs(SidebarProvider, { open: sidebarOpen, onOpenChange: handleSidebarChange, children: [_jsx(LeftNav, { modules: modules }), _jsxs(SidebarInset, { className: "flex flex-col", children: [_jsx(TopBar, { onSearchClick: onSearchClick, environment: environment, locale: locale, onLocaleChange: onLocaleChange }), _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(MainContent, { modules: modules, children: _jsx(HomePage, {}) }) }), _jsx(Route, { path: "/_profile", element: _jsx(MainContent, { modules: modules, children: _jsx(ProfilePage, {}) }) }), _jsx(Route, { path: "/_settings", element: _jsx(MainContent, { modules: modules, children: _jsx(SettingsPage, {}) }) }), _jsx(Route, { path: "/_registry", element: _jsx(MainContent, { modules: modules, children: _jsx(RegistryPage, { apiUrl: apiUrl, registryClient: registryClient }) }) }), _jsx(Route, { path: "/_modules/*", element: _jsx(MainContent, { modules: modules, children: _jsx(ModuleOverview, { modules: modules }) }) }), isStandaloneMode
|
|
213
|
+
return (_jsxs(_Fragment, { children: [hasUpdates && (_jsx(UpdateBanner, { onReload: () => window.location.reload(), onDismiss: onDismissUpdate })), _jsxs(SidebarProvider, { open: sidebarOpen, onOpenChange: handleSidebarChange, children: [_jsx(LeftNav, { modules: modules }), _jsxs(SidebarInset, { className: "flex flex-col", children: [_jsx(TopBar, { onSearchClick: onSearchClick, environment: environment, locale: locale, onLocaleChange: onLocaleChange, timezoneMode: timezoneMode, onTimezoneToggle: onTimezoneToggle }), cacheStatus.state !== "fresh" && cacheStatus.state !== "unavailable" && (_jsx("div", { className: "px-4 pt-2", children: _jsx(RegistryStatusBanner, { status: cacheStatus, onRetry: onRetry }) })), _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(MainContent, { modules: modules, moduleBreadcrumbs: moduleBreadcrumbs, children: _jsx(HomePage, {}) }) }), _jsx(Route, { path: "/_profile", element: _jsx(MainContent, { modules: modules, moduleBreadcrumbs: moduleBreadcrumbs, children: _jsx(ProfilePage, {}) }) }), _jsx(Route, { path: "/_settings", element: _jsx(MainContent, { modules: modules, moduleBreadcrumbs: moduleBreadcrumbs, children: _jsx(SettingsPage, {}) }) }), _jsx(Route, { path: "/_registry", element: _jsx(MainContent, { modules: modules, moduleBreadcrumbs: moduleBreadcrumbs, children: _jsx(RegistryPage, { apiUrl: apiUrl, registryClient: registryClient }) }) }), _jsx(Route, { path: "/_modules/*", element: _jsx(MainContent, { modules: modules, moduleBreadcrumbs: moduleBreadcrumbs, children: _jsx(ModuleOverview, { modules: modules }) }) }), isStandaloneMode
|
|
164
214
|
? // Standalone mode: render children (module is imported directly)
|
|
165
|
-
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, children: children }) }, module.id)))
|
|
215
|
+
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, moduleBreadcrumbs: moduleBreadcrumbs, children: children }) }, module.id)))
|
|
166
216
|
: // Shell mode: load modules dynamically via React.lazy
|
|
167
|
-
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, children: module.baseUrl ? (_jsx(DynamicModule, { baseUrl: module.baseUrl
|
|
217
|
+
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, moduleBreadcrumbs: moduleBreadcrumbs, children: module.baseUrl ? (_jsx(DynamicModule, { baseUrl: module.baseUrl, moduleInfo: {
|
|
218
|
+
id: module.id,
|
|
219
|
+
title: module.title,
|
|
220
|
+
owners: module.owners,
|
|
221
|
+
} })) : (_jsxs("div", { className: "text-muted-foreground", children: ["Module ", module.id, " has no baseUrl configured"] })) }) }, module.id))), _jsx(Route, { path: "*", element: _jsx(MainContent, { modules: modules, moduleBreadcrumbs: moduleBreadcrumbs, children: isStandaloneMode ? children : null }) })] })] })] }), _jsx(CommandPalette, { open: commandPaletteOpen, onOpenChange: onCommandPaletteChange, catalog: catalog }), _jsx(DevtoolsPanel, { environment: environment, modules: modules, catalogVersion: catalog.version, catalogGeneratedAt: catalog.generatedAt, registryMode: registryClient ? "api" : "in-memory", cacheStatus: cacheStatus })] }));
|
|
168
222
|
}
|
|
169
223
|
export function AdminShell({ modules: manifests = [], children, keycloak, authClient: providedAuthClient, registryClient, apiUrl, inMemoryRegistry = true, environment = "local", }) {
|
|
170
224
|
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
|
@@ -174,13 +228,29 @@ export function AdminShell({ modules: manifests = [], children, keycloak, authCl
|
|
|
174
228
|
return i18n.language || "pt-BR";
|
|
175
229
|
});
|
|
176
230
|
const [apiModules, setApiModules] = useState([]);
|
|
231
|
+
const [initialCatalogVersion, setInitialCatalogVersion] = useState("");
|
|
232
|
+
const [catalogGeneratedAt, setCatalogGeneratedAt] = useState("");
|
|
177
233
|
const [isLoading, setIsLoading] = useState(!!registryClient);
|
|
234
|
+
const [cacheStatus, setCacheStatus] = useState({ state: 'fresh' });
|
|
235
|
+
const [cachedCatalogOps, setCachedCatalogOps] = useState(null);
|
|
236
|
+
const localeCallbacksRef = useRef(new Set());
|
|
237
|
+
const timezoneCallbacksRef = useRef(new Set());
|
|
238
|
+
const [timezoneMode, setTimezoneMode] = useState(getStoredTimezoneMode);
|
|
239
|
+
const handleTimezoneToggle = useCallback(() => {
|
|
240
|
+
setTimezoneMode((prev) => {
|
|
241
|
+
const next = prev === "utc" ? "local" : "utc";
|
|
242
|
+
saveTimezoneMode(next);
|
|
243
|
+
timezoneCallbacksRef.current.forEach(cb => cb(next));
|
|
244
|
+
return next;
|
|
245
|
+
});
|
|
246
|
+
}, []);
|
|
178
247
|
// Handle locale change - update both state and i18next
|
|
179
248
|
const handleLocaleChange = useCallback((newLocale) => {
|
|
180
249
|
if (isSupportedLocale(newLocale)) {
|
|
181
250
|
i18n.changeLanguage(newLocale);
|
|
182
251
|
saveLocale(newLocale);
|
|
183
252
|
setLocale(newLocale);
|
|
253
|
+
localeCallbacksRef.current.forEach(cb => cb(newLocale));
|
|
184
254
|
}
|
|
185
255
|
}, []);
|
|
186
256
|
// Sync locale state with i18next language changes
|
|
@@ -200,17 +270,50 @@ export function AdminShell({ modules: manifests = [], children, keycloak, authCl
|
|
|
200
270
|
if (providedAuthClient) {
|
|
201
271
|
return providedAuthClient;
|
|
202
272
|
}
|
|
273
|
+
const isProd = import.meta.env.PROD === true;
|
|
274
|
+
const mockAuthExplicit = import.meta.env.VITE_MOCK_AUTH === 'true';
|
|
275
|
+
const noAuthConfig = !keycloak;
|
|
276
|
+
// Production guard: require auth config unless explicitly opted in
|
|
277
|
+
if (typeof window !== 'undefined' &&
|
|
278
|
+
isProd &&
|
|
279
|
+
noAuthConfig &&
|
|
280
|
+
!mockAuthExplicit) {
|
|
281
|
+
throw new Error('[AdminShell] Authentication configuration is required in production. ' +
|
|
282
|
+
'Provide authClient or keycloak prop, or set VITE_MOCK_AUTH=true to explicitly opt in to mock auth.');
|
|
283
|
+
}
|
|
203
284
|
// Check if we should use mock auth
|
|
204
285
|
const useMockAuth = typeof window !== 'undefined' &&
|
|
205
|
-
(
|
|
286
|
+
(mockAuthExplicit || noAuthConfig);
|
|
206
287
|
if (useMockAuth) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
288
|
+
if (isProd && mockAuthExplicit) {
|
|
289
|
+
console.warn('[AdminShell] Mock auth is active in production build (VITE_MOCK_AUTH=true). ' +
|
|
290
|
+
'Use real authentication (Keycloak) for production deployments.');
|
|
291
|
+
}
|
|
292
|
+
// Default mock users with explicit roles for tasks and users modules
|
|
293
|
+
const defaultMockUsers = createMockUsersFromRoles({
|
|
294
|
+
admin: [
|
|
295
|
+
'admin.tasks.view',
|
|
296
|
+
'admin.tasks.edit',
|
|
297
|
+
'admin.tasks.delete',
|
|
298
|
+
'admin.users.view',
|
|
299
|
+
'admin.users.edit',
|
|
300
|
+
'admin.users.delete',
|
|
301
|
+
'admin.platform.view',
|
|
302
|
+
'admin.platform.edit',
|
|
303
|
+
'admin.platform.delete',
|
|
304
|
+
],
|
|
305
|
+
editor: [
|
|
306
|
+
'admin.tasks.view',
|
|
307
|
+
'admin.tasks.edit',
|
|
308
|
+
'admin.users.view',
|
|
309
|
+
'admin.users.edit',
|
|
310
|
+
'admin.platform.view',
|
|
311
|
+
'admin.platform.edit',
|
|
212
312
|
],
|
|
313
|
+
viewer: ['admin.tasks.view', 'admin.users.view', 'admin.platform.view'],
|
|
314
|
+
noAccess: [],
|
|
213
315
|
});
|
|
316
|
+
return createInMemoryAuthClient({ users: defaultMockUsers, gatewayUrl: import.meta.env.VITE_ADMIN_GATEWAY_URL });
|
|
214
317
|
}
|
|
215
318
|
// Use Keycloak
|
|
216
319
|
return createKeycloakAuthClient({
|
|
@@ -239,27 +342,30 @@ export function AdminShell({ modules: manifests = [], children, keycloak, authCl
|
|
|
239
342
|
});
|
|
240
343
|
}
|
|
241
344
|
}, [registryClient, inMemoryRegistry, manifests]);
|
|
242
|
-
// Fetch modules from registry client (modules load via React.lazy)
|
|
345
|
+
// Fetch modules from registry client with LKG caching (modules load via React.lazy)
|
|
243
346
|
useEffect(() => {
|
|
244
347
|
if (!registryClient) {
|
|
245
348
|
setIsLoading(false);
|
|
246
349
|
return;
|
|
247
350
|
}
|
|
248
351
|
let mounted = true;
|
|
352
|
+
const cached = createCachedCatalog(registryClient.catalog);
|
|
353
|
+
setCachedCatalogOps(cached);
|
|
249
354
|
async function fetchModules() {
|
|
250
355
|
try {
|
|
251
|
-
|
|
252
|
-
const catalog = await registryClient.catalog.get();
|
|
356
|
+
const catalog = await cached.get();
|
|
253
357
|
if (!mounted)
|
|
254
358
|
return;
|
|
255
|
-
// Convert catalog modules to Module type for UI
|
|
256
359
|
const modules = catalog.modules.map(catalogModuleToModule);
|
|
257
360
|
setApiModules(modules);
|
|
361
|
+
setCacheStatus(cached.getStatus());
|
|
362
|
+
setInitialCatalogVersion(catalog.version);
|
|
363
|
+
setCatalogGeneratedAt(catalog.generatedAt);
|
|
258
364
|
setIsLoading(false);
|
|
259
365
|
}
|
|
260
|
-
catch
|
|
261
|
-
console.error("[Shell] Failed to fetch modules from API:", error);
|
|
366
|
+
catch {
|
|
262
367
|
if (mounted) {
|
|
368
|
+
setCacheStatus(cached.getStatus());
|
|
263
369
|
setIsLoading(false);
|
|
264
370
|
}
|
|
265
371
|
}
|
|
@@ -275,14 +381,26 @@ export function AdminShell({ modules: manifests = [], children, keycloak, authCl
|
|
|
275
381
|
}, [manifests]);
|
|
276
382
|
// Use API modules if registry client is provided, otherwise use manifest modules
|
|
277
383
|
const modules = registryClient ? apiModules : manifestModules;
|
|
278
|
-
//
|
|
384
|
+
// Resolve polling interval (0 = disabled)
|
|
385
|
+
const pollingInterval = useMemo(() => {
|
|
386
|
+
if (!registryClient || !initialCatalogVersion)
|
|
387
|
+
return 0;
|
|
388
|
+
return resolvePollingInterval(environment);
|
|
389
|
+
}, [registryClient, initialCatalogVersion, environment]);
|
|
390
|
+
// Poll for catalog version changes
|
|
391
|
+
const { hasUpdates, dismiss: dismissUpdate } = useRegistryPolling({
|
|
392
|
+
registryClient: registryClient,
|
|
393
|
+
initialVersion: initialCatalogVersion,
|
|
394
|
+
interval: pollingInterval,
|
|
395
|
+
});
|
|
396
|
+
// Create catalog for command palette and devtools
|
|
279
397
|
const catalog = useMemo(() => {
|
|
280
398
|
return {
|
|
281
|
-
version: "1.0.0",
|
|
282
|
-
generatedAt: new Date().toISOString(),
|
|
399
|
+
version: initialCatalogVersion || "1.0.0",
|
|
400
|
+
generatedAt: catalogGeneratedAt || new Date().toISOString(),
|
|
283
401
|
modules: modules,
|
|
284
402
|
};
|
|
285
|
-
}, [modules]);
|
|
403
|
+
}, [modules, initialCatalogVersion, catalogGeneratedAt]);
|
|
286
404
|
// Keyboard shortcut for command palette
|
|
287
405
|
useEffect(() => {
|
|
288
406
|
const handleKeyDown = (e) => {
|
|
@@ -297,9 +415,26 @@ export function AdminShell({ modules: manifests = [], children, keycloak, authCl
|
|
|
297
415
|
const handleSearchClick = useCallback(() => {
|
|
298
416
|
setCommandPaletteOpen(true);
|
|
299
417
|
}, []);
|
|
418
|
+
const handleRetry = useCallback(async () => {
|
|
419
|
+
if (!cachedCatalogOps)
|
|
420
|
+
return;
|
|
421
|
+
try {
|
|
422
|
+
const catalog = await cachedCatalogOps.retry();
|
|
423
|
+
const modules = catalog.modules.map(catalogModuleToModule);
|
|
424
|
+
setApiModules(modules);
|
|
425
|
+
setCacheStatus(cachedCatalogOps.getStatus());
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
setCacheStatus(cachedCatalogOps.getStatus());
|
|
429
|
+
}
|
|
430
|
+
}, [cachedCatalogOps]);
|
|
300
431
|
// Show loading state while fetching from API
|
|
301
432
|
if (isLoading) {
|
|
302
433
|
return (_jsx("div", { className: "flex h-screen items-center justify-center", children: _jsx("div", { className: "text-muted-foreground", children: "Loading modules..." }) }));
|
|
303
434
|
}
|
|
304
|
-
|
|
435
|
+
// Registry completely unavailable (no cache, no API)
|
|
436
|
+
if (cacheStatus.state === "unavailable") {
|
|
437
|
+
return (_jsx(BrowserRouter, { children: _jsx(I18nextProvider, { i18n: i18n, children: _jsx(ThemeProvider, { children: _jsx(RegistryUnavailable, { onRetry: handleRetry }) }) }) }));
|
|
438
|
+
}
|
|
439
|
+
return (_jsx(BrowserRouter, { children: _jsx(I18nextProvider, { i18n: i18n, children: _jsx(ThemeProvider, { children: _jsx(AuthProvider, { authClient: authClient, children: _jsx(ShellContent, { modules: modules, environment: environment, locale: locale, onLocaleChange: handleLocaleChange, onSearchClick: handleSearchClick, catalog: catalog, commandPaletteOpen: commandPaletteOpen, onCommandPaletteChange: setCommandPaletteOpen, apiUrl: apiUrl, registryClient: registryClient, isStandaloneMode: isStandaloneMode, cacheStatus: cacheStatus, onRetry: handleRetry, hasUpdates: hasUpdates, onDismissUpdate: dismissUpdate, localeCallbacksRef: localeCallbacksRef, timezoneMode: timezoneMode, onTimezoneToggle: handleTimezoneToggle, timezoneCallbacksRef: timezoneCallbacksRef, children: children }) }) }) }) }));
|
|
305
440
|
}
|
|
@@ -35,4 +35,3 @@ export interface AdminShellProps {
|
|
|
35
35
|
environment?: string;
|
|
36
36
|
}
|
|
37
37
|
export declare function AdminShell({ modules: manifests, children, keycloak, authClient: providedAuthClient, registryClient, apiUrl, inMemoryRegistry, environment, }: AdminShellProps): import("react/jsx-runtime").JSX.Element;
|
|
38
|
-
//# sourceMappingURL=BackofficeShell.d.ts.map
|
|
@@ -12,10 +12,12 @@ import { ProfilePage } from "./components/ProfilePage";
|
|
|
12
12
|
import { SettingsPage } from "./components/SettingsPage";
|
|
13
13
|
import { RegistryPage } from "./components/RegistryPage";
|
|
14
14
|
import { HomePage } from "./components/HomePage";
|
|
15
|
+
import { DevtoolsPanel } from "./components/DevtoolsPanel";
|
|
15
16
|
import { AuthProvider, useAuthContext } from "../components/AuthProvider";
|
|
16
17
|
import { createInMemoryAuthClient } from "../auth/client/in-memory";
|
|
17
18
|
import { createKeycloakAuthClient } from "../auth/client/keycloak";
|
|
18
19
|
import { createInMemoryRegistryClient } from "../registry/client/in-memory";
|
|
20
|
+
import { createCachedCatalog } from "../registry/cache/cached-catalog";
|
|
19
21
|
import { DynamicModule } from "../router/DynamicModule";
|
|
20
22
|
import { SidebarProvider, SidebarInset } from "@nsxbet/admin-ui";
|
|
21
23
|
import { initTelemetry, track, trackError } from "./telemetry";
|
|
@@ -27,9 +29,7 @@ function manifestToModule(manifest, baseUrl) {
|
|
|
27
29
|
return {
|
|
28
30
|
id: manifest.id,
|
|
29
31
|
title: manifest.title,
|
|
30
|
-
|
|
31
|
-
description: manifest.description || "",
|
|
32
|
-
descriptionKey: manifest.descriptionKey,
|
|
32
|
+
description: manifest.description,
|
|
33
33
|
category: manifest.category || "Modules",
|
|
34
34
|
routeBase: manifest.routeBase,
|
|
35
35
|
baseUrl: baseUrl || "",
|
|
@@ -42,16 +42,23 @@ function manifestToModule(manifest, baseUrl) {
|
|
|
42
42
|
team: manifest.owners?.team || "Platform",
|
|
43
43
|
supportChannel: manifest.owners?.supportChannel || "",
|
|
44
44
|
},
|
|
45
|
-
status:
|
|
46
|
-
|
|
45
|
+
status: "active",
|
|
46
|
+
navigationOrder: manifest.navigationOrder,
|
|
47
47
|
icon: manifest.icon,
|
|
48
|
+
navigation: manifest.navigation ? {
|
|
49
|
+
style: manifest.navigation.style,
|
|
50
|
+
sections: manifest.navigation.sections?.map(s => ({
|
|
51
|
+
id: s.id,
|
|
52
|
+
label: s.label,
|
|
53
|
+
})),
|
|
54
|
+
} : undefined,
|
|
48
55
|
commands: manifest.commands?.map((cmd) => ({
|
|
49
56
|
id: cmd.id,
|
|
50
57
|
title: cmd.title,
|
|
51
|
-
titleKey: cmd.titleKey,
|
|
52
58
|
route: cmd.route,
|
|
53
59
|
icon: cmd.icon,
|
|
54
60
|
keywords: cmd.keywords,
|
|
61
|
+
section: cmd.section,
|
|
55
62
|
})),
|
|
56
63
|
};
|
|
57
64
|
}
|
|
@@ -62,9 +69,7 @@ function catalogModuleToModule(m) {
|
|
|
62
69
|
return {
|
|
63
70
|
id: m.id,
|
|
64
71
|
title: m.title,
|
|
65
|
-
titleKey: m.titleKey,
|
|
66
72
|
description: m.description,
|
|
67
|
-
descriptionKey: m.descriptionKey,
|
|
68
73
|
category: m.category,
|
|
69
74
|
routeBase: m.routeBase,
|
|
70
75
|
baseUrl: m.baseUrl,
|
|
@@ -72,18 +77,29 @@ function catalogModuleToModule(m) {
|
|
|
72
77
|
permissions: m.permissions,
|
|
73
78
|
owners: m.owners,
|
|
74
79
|
status: m.status,
|
|
75
|
-
|
|
80
|
+
navigationOrder: m.navigationOrder,
|
|
76
81
|
icon: m.icon,
|
|
82
|
+
navigation: m.navigation ? {
|
|
83
|
+
style: m.navigation.style,
|
|
84
|
+
sections: m.navigation.sections?.map(s => ({
|
|
85
|
+
id: s.id,
|
|
86
|
+
label: s.title,
|
|
87
|
+
})),
|
|
88
|
+
} : undefined,
|
|
77
89
|
commands: m.commands?.map(cmd => ({
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
id: cmd.id,
|
|
91
|
+
title: cmd.title,
|
|
92
|
+
route: cmd.route,
|
|
93
|
+
icon: cmd.icon,
|
|
94
|
+
keywords: cmd.keywords,
|
|
95
|
+
section: cmd.section,
|
|
80
96
|
})),
|
|
81
97
|
};
|
|
82
98
|
}
|
|
83
99
|
/**
|
|
84
100
|
* Inner shell component that has access to React Router hooks
|
|
85
101
|
*/
|
|
86
|
-
function ShellContent({ modules, children, environment, locale, onLocaleChange, onSearchClick, catalog, commandPaletteOpen, onCommandPaletteChange, apiUrl, registryClient, isStandaloneMode, }) {
|
|
102
|
+
function ShellContent({ modules, children, environment, locale, onLocaleChange, onSearchClick, catalog, commandPaletteOpen, onCommandPaletteChange, apiUrl, registryClient, isStandaloneMode, cacheStatus, }) {
|
|
87
103
|
const navigate = useNavigate();
|
|
88
104
|
const auth = useAuthContext();
|
|
89
105
|
// Set up the platform API for modules to use
|
|
@@ -94,7 +110,7 @@ function ShellContent({ modules, children, environment, locale, onLocaleChange,
|
|
|
94
110
|
auth: {
|
|
95
111
|
getAccessToken: auth.getAccessToken,
|
|
96
112
|
hasPermission: auth.hasPermission,
|
|
97
|
-
getUser: () => auth.user || { id: "", email: "", displayName: "" },
|
|
113
|
+
getUser: () => auth.user || { id: "", email: "", displayName: "", roles: [] },
|
|
98
114
|
logout: () => {
|
|
99
115
|
auth.logout();
|
|
100
116
|
// Navigate to root after logout (for Keycloak, this will redirect)
|
|
@@ -113,9 +129,16 @@ function ShellContent({ modules, children, environment, locale, onLocaleChange,
|
|
|
113
129
|
locale: locale,
|
|
114
130
|
setLocale: onLocaleChange,
|
|
115
131
|
onLocaleChange: (callback) => {
|
|
116
|
-
// Simple implementation - just call callback immediately with current locale
|
|
117
132
|
callback(locale);
|
|
118
|
-
return () => { };
|
|
133
|
+
return () => { };
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
timestamp: {
|
|
137
|
+
mode: "local",
|
|
138
|
+
setMode: () => { },
|
|
139
|
+
onModeChange: (callback) => {
|
|
140
|
+
callback("local");
|
|
141
|
+
return () => { };
|
|
119
142
|
},
|
|
120
143
|
},
|
|
121
144
|
telemetry: {
|
|
@@ -164,7 +187,11 @@ function ShellContent({ modules, children, environment, locale, onLocaleChange,
|
|
|
164
187
|
? // Standalone mode: render children (module is imported directly)
|
|
165
188
|
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, children: children }) }, module.id)))
|
|
166
189
|
: // Shell mode: load modules dynamically via React.lazy
|
|
167
|
-
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, children: module.baseUrl ? (_jsx(DynamicModule, { baseUrl: module.baseUrl
|
|
190
|
+
modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, children: module.baseUrl ? (_jsx(DynamicModule, { baseUrl: module.baseUrl, moduleInfo: {
|
|
191
|
+
id: module.id,
|
|
192
|
+
title: module.title,
|
|
193
|
+
owners: module.owners,
|
|
194
|
+
} })) : (_jsxs("div", { className: "text-muted-foreground", children: ["Module ", module.id, " has no baseUrl configured"] })) }) }, module.id))), _jsx(Route, { path: "*", element: _jsx(MainContent, { modules: modules, children: isStandaloneMode ? children : null }) })] })] })] }), _jsx(CommandPalette, { open: commandPaletteOpen, onOpenChange: onCommandPaletteChange, catalog: catalog }), _jsx(DevtoolsPanel, { environment: environment, modules: modules, catalogVersion: catalog.version, catalogGeneratedAt: catalog.generatedAt, registryMode: registryClient ? "api" : "in-memory", cacheStatus: cacheStatus })] }));
|
|
168
195
|
}
|
|
169
196
|
export function AdminShell({ modules: manifests = [], children, keycloak, authClient: providedAuthClient, registryClient, apiUrl, inMemoryRegistry = true, environment = "local", }) {
|
|
170
197
|
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
|
@@ -175,6 +202,9 @@ export function AdminShell({ modules: manifests = [], children, keycloak, authCl
|
|
|
175
202
|
});
|
|
176
203
|
const [apiModules, setApiModules] = useState([]);
|
|
177
204
|
const [isLoading, setIsLoading] = useState(!!registryClient);
|
|
205
|
+
const [cacheStatus, setCacheStatus] = useState({ state: 'fresh' });
|
|
206
|
+
const [catalogGeneratedAt, setCatalogGeneratedAt] = useState("");
|
|
207
|
+
const [catalogVersion, setCatalogVersion] = useState("");
|
|
178
208
|
// Handle locale change - update both state and i18next
|
|
179
209
|
const handleLocaleChange = useCallback((newLocale) => {
|
|
180
210
|
if (isSupportedLocale(newLocale)) {
|
|
@@ -210,6 +240,7 @@ export function AdminShell({ modules: manifests = [], children, keycloak, authCl
|
|
|
210
240
|
{ id: 'admin-user', email: 'admin@example.com', displayName: 'Admin User', roles: ['*'] },
|
|
211
241
|
{ id: 'viewer-user', email: 'viewer@example.com', displayName: 'Viewer User', roles: [] },
|
|
212
242
|
],
|
|
243
|
+
gatewayUrl: import.meta.env.VITE_ADMIN_GATEWAY_URL,
|
|
213
244
|
});
|
|
214
245
|
}
|
|
215
246
|
// Use Keycloak
|
|
@@ -239,27 +270,30 @@ export function AdminShell({ modules: manifests = [], children, keycloak, authCl
|
|
|
239
270
|
});
|
|
240
271
|
}
|
|
241
272
|
}, [registryClient, inMemoryRegistry, manifests]);
|
|
242
|
-
// Fetch modules from registry client (modules load via React.lazy)
|
|
273
|
+
// Fetch modules from registry client with LKG caching (modules load via React.lazy)
|
|
243
274
|
useEffect(() => {
|
|
244
275
|
if (!registryClient) {
|
|
245
276
|
setIsLoading(false);
|
|
246
277
|
return;
|
|
247
278
|
}
|
|
248
279
|
let mounted = true;
|
|
280
|
+
const cached = createCachedCatalog(registryClient.catalog);
|
|
249
281
|
async function fetchModules() {
|
|
250
282
|
try {
|
|
251
|
-
|
|
252
|
-
const catalog = await registryClient.catalog.get();
|
|
283
|
+
const catalog = await cached.get();
|
|
253
284
|
if (!mounted)
|
|
254
285
|
return;
|
|
255
|
-
// Convert catalog modules to Module type for UI
|
|
256
286
|
const modules = catalog.modules.map(catalogModuleToModule);
|
|
257
287
|
setApiModules(modules);
|
|
288
|
+
setCacheStatus(cached.getStatus());
|
|
289
|
+
setCatalogVersion(catalog.version);
|
|
290
|
+
setCatalogGeneratedAt(catalog.generatedAt);
|
|
258
291
|
setIsLoading(false);
|
|
259
292
|
}
|
|
260
293
|
catch (error) {
|
|
261
294
|
console.error("[Shell] Failed to fetch modules from API:", error);
|
|
262
295
|
if (mounted) {
|
|
296
|
+
setCacheStatus(cached.getStatus());
|
|
263
297
|
setIsLoading(false);
|
|
264
298
|
}
|
|
265
299
|
}
|
|
@@ -275,14 +309,14 @@ export function AdminShell({ modules: manifests = [], children, keycloak, authCl
|
|
|
275
309
|
}, [manifests]);
|
|
276
310
|
// Use API modules if registry client is provided, otherwise use manifest modules
|
|
277
311
|
const modules = registryClient ? apiModules : manifestModules;
|
|
278
|
-
// Create catalog for command palette
|
|
312
|
+
// Create catalog for command palette and devtools
|
|
279
313
|
const catalog = useMemo(() => {
|
|
280
314
|
return {
|
|
281
|
-
version: "1.0.0",
|
|
282
|
-
generatedAt: new Date().toISOString(),
|
|
315
|
+
version: catalogVersion || "1.0.0",
|
|
316
|
+
generatedAt: catalogGeneratedAt || new Date().toISOString(),
|
|
283
317
|
modules: modules,
|
|
284
318
|
};
|
|
285
|
-
}, [modules]);
|
|
319
|
+
}, [modules, catalogVersion, catalogGeneratedAt]);
|
|
286
320
|
// Keyboard shortcut for command palette
|
|
287
321
|
useEffect(() => {
|
|
288
322
|
const handleKeyDown = (e) => {
|
|
@@ -301,5 +335,5 @@ export function AdminShell({ modules: manifests = [], children, keycloak, authCl
|
|
|
301
335
|
if (isLoading) {
|
|
302
336
|
return (_jsx("div", { className: "flex h-screen items-center justify-center", children: _jsx("div", { className: "text-muted-foreground", children: "Loading modules..." }) }));
|
|
303
337
|
}
|
|
304
|
-
return (_jsx(BrowserRouter, { children: _jsx(I18nextProvider, { i18n: i18n, children: _jsx(ThemeProvider, { children: _jsx(AuthProvider, { authClient: authClient, children: _jsx(ShellContent, { modules: modules, environment: environment, locale: locale, onLocaleChange: handleLocaleChange, onSearchClick: handleSearchClick, catalog: catalog, commandPaletteOpen: commandPaletteOpen, onCommandPaletteChange: setCommandPaletteOpen, apiUrl: apiUrl, registryClient: registryClient, isStandaloneMode: isStandaloneMode, children: children }) }) }) }) }));
|
|
338
|
+
return (_jsx(BrowserRouter, { children: _jsx(I18nextProvider, { i18n: i18n, children: _jsx(ThemeProvider, { children: _jsx(AuthProvider, { authClient: authClient, children: _jsx(ShellContent, { modules: modules, environment: environment, locale: locale, onLocaleChange: handleLocaleChange, onSearchClick: handleSearchClick, catalog: catalog, commandPaletteOpen: commandPaletteOpen, onCommandPaletteChange: setCommandPaletteOpen, apiUrl: apiUrl, registryClient: registryClient, isStandaloneMode: isStandaloneMode, cacheStatus: cacheStatus, children: children }) }) }) }) }));
|
|
305
339
|
}
|