@nsxbet/admin-sdk 0.7.0 → 0.8.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.
Files changed (76) hide show
  1. package/CHECKLIST.md +48 -13
  2. package/README.md +24 -74
  3. package/dist/auth/client/bff.d.ts +38 -0
  4. package/dist/auth/client/bff.js +270 -0
  5. package/dist/auth/client/in-memory.d.ts +1 -1
  6. package/dist/auth/client/in-memory.js +2 -2
  7. package/dist/auth/client/index.d.ts +1 -1
  8. package/dist/auth/client/index.js +2 -2
  9. package/dist/auth/client/interface.d.ts +4 -4
  10. package/dist/auth/client/interface.js +1 -1
  11. package/dist/auth/client/private-network-guidance.d.ts +2 -0
  12. package/dist/auth/client/private-network-guidance.js +38 -0
  13. package/dist/auth/components/LoginPage.d.ts +8 -0
  14. package/dist/auth/components/LoginPage.js +32 -0
  15. package/dist/auth/components/UserSelector.d.ts +29 -0
  16. package/dist/auth/components/UserSelector.js +38 -10
  17. package/dist/auth/components/UserSelector.stories.d.ts +9 -0
  18. package/dist/auth/components/UserSelector.stories.js +70 -0
  19. package/dist/auth/components/index.d.ts +2 -0
  20. package/dist/auth/components/index.js +1 -0
  21. package/dist/auth/index.d.ts +3 -2
  22. package/dist/auth/index.js +2 -2
  23. package/dist/components/AuthProvider.d.ts +3 -3
  24. package/dist/components/AuthProvider.js +35 -17
  25. package/dist/env.d.ts +17 -0
  26. package/dist/env.js +50 -0
  27. package/dist/hooks/useAuth.d.ts +3 -3
  28. package/dist/hooks/useAuth.js +26 -21
  29. package/dist/hooks/useCallbackRef.d.ts +7 -0
  30. package/dist/hooks/useCallbackRef.js +14 -0
  31. package/dist/hooks/useFetch.js +6 -1
  32. package/dist/hooks/useI18n.js +2 -2
  33. package/dist/hooks/usePlatformAPI.d.ts +0 -3
  34. package/dist/hooks/usePlatformAPI.js +6 -4
  35. package/dist/i18n/config.d.ts +2 -1
  36. package/dist/i18n/config.js +4 -3
  37. package/dist/i18n/index.d.ts +1 -1
  38. package/dist/i18n/index.js +1 -1
  39. package/dist/i18n/locales/en-US.json +7 -0
  40. package/dist/i18n/locales/es.json +7 -0
  41. package/dist/i18n/locales/pt-BR.json +7 -0
  42. package/dist/i18n/locales/ro.json +7 -0
  43. package/dist/index.d.ts +7 -5
  44. package/dist/index.js +6 -2
  45. package/dist/registry/AdminShellRegistry.js +4 -3
  46. package/dist/registry/client/http.js +6 -1
  47. package/dist/registry/client/in-memory.js +20 -5
  48. package/dist/registry/types/manifest.d.ts +5 -0
  49. package/dist/registry/types/manifest.js +4 -1
  50. package/dist/registry/types/module.d.ts +6 -2
  51. package/dist/sdk-version.d.ts +5 -0
  52. package/dist/sdk-version.js +5 -0
  53. package/dist/shell/AdminShell.d.ts +12 -9
  54. package/dist/shell/AdminShell.js +56 -70
  55. package/dist/shell/components/ModuleOverview.js +1 -5
  56. package/dist/shell/components/RegistryPage.js +1 -1
  57. package/dist/shell/components/TopBar.js +2 -2
  58. package/dist/shell/components/theme-provider.js +6 -8
  59. package/dist/shell/index.d.ts +1 -1
  60. package/dist/shell/polling-config.d.ts +4 -3
  61. package/dist/shell/polling-config.js +11 -9
  62. package/dist/shell/types.d.ts +3 -1
  63. package/dist/types/platform.d.ts +2 -11
  64. package/dist/vite/config.d.ts +4 -9
  65. package/dist/vite/config.js +85 -27
  66. package/dist/vite/index.d.ts +1 -1
  67. package/dist/vite/index.js +1 -1
  68. package/dist/vite/plugins.js +6 -1
  69. package/package.json +20 -6
  70. package/scripts/write-sdk-version.mjs +21 -0
  71. package/dist/auth/client/keycloak.d.ts +0 -18
  72. package/dist/auth/client/keycloak.js +0 -129
  73. package/dist/shell/BackofficeShell.d.ts +0 -37
  74. package/dist/shell/BackofficeShell.js +0 -339
  75. package/dist/types/keycloak.d.ts +0 -25
  76. package/dist/types/keycloak.js +0 -1
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, writeFileSync, existsSync } from "fs";
2
2
  import { resolve, join } from "path";
3
+ import { SDK_PACKAGE_VERSION } from "../sdk-version.js";
3
4
  /**
4
5
  * Vite plugin to generate module.manifest.json after build.
5
6
  * Merges admin.module.json metadata with the build entry path.
@@ -22,7 +23,11 @@ export function generateModuleManifestPlugin(options = {}) {
22
23
  if (!spaEntry)
23
24
  return;
24
25
  const metadata = JSON.parse(readFileSync(manifestPath, "utf-8"));
25
- const manifest = { entry: spaEntry, ...metadata };
26
+ const manifest = {
27
+ entry: spaEntry,
28
+ sdkVersion: SDK_PACKAGE_VERSION,
29
+ ...metadata,
30
+ };
26
31
  const outDir = outputOptions.dir || "dist";
27
32
  const outputPath = join(outDir, "module.manifest.json");
28
33
  writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nsxbet/admin-sdk",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "SDK for building NSX Admin modules with integrated shell",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,6 +19,12 @@
19
19
  "import": "./dist/vite/index.js",
20
20
  "default": "./dist/vite/index.js"
21
21
  },
22
+ "./env": {
23
+ "source": "./src/env.ts",
24
+ "types": "./dist/env.d.ts",
25
+ "import": "./dist/env.js",
26
+ "default": "./dist/env.js"
27
+ },
22
28
  "./tailwind": {
23
29
  "source": "./src/tailwind/index.ts",
24
30
  "types": "./dist/tailwind/index.d.ts",
@@ -34,10 +40,12 @@
34
40
  ],
35
41
  "scripts": {
36
42
  "postinstall": "node scripts/postinstall.js",
37
- "build": "tsc",
38
- "dev": "tsc --watch --preserveWatchOutput",
39
- "type-check": "tsc --noEmit",
40
- "test": "vitest run --passWithNoTests"
43
+ "build": "node scripts/write-sdk-version.mjs && tsc",
44
+ "dev": "node scripts/write-sdk-version.mjs && tsc --watch --preserveWatchOutput",
45
+ "type-check": "node scripts/write-sdk-version.mjs && tsc --noEmit",
46
+ "test": "vitest run --passWithNoTests",
47
+ "storybook": "storybook dev -p 6007 --host 0.0.0.0",
48
+ "build-storybook": "storybook build -o storybook-static"
41
49
  },
42
50
  "peerDependencies": {
43
51
  "@vitejs/plugin-react": "^4.0.0",
@@ -61,19 +69,25 @@
61
69
  }
62
70
  },
63
71
  "dependencies": {
64
- "keycloak-js": "^23.0.0",
65
72
  "lucide-react": "^0.460.0"
66
73
  },
67
74
  "devDependencies": {
68
75
  "@nsxbet/admin-ui": "workspace:*",
76
+ "@storybook/react-vite": "^10.3.0",
69
77
  "@testing-library/jest-dom": "^6.9.1",
70
78
  "@testing-library/react": "^16.3.2",
79
+ "@types/node": "^20.3.1",
71
80
  "@types/react": "^18.2.0",
72
81
  "@types/react-dom": "^18.2.0",
73
82
  "@vitejs/plugin-react": "^4.3.4",
83
+ "autoprefixer": "^10.4.27",
74
84
  "i18next": "^25.7.4",
85
+ "postcss": "^8.5.8",
75
86
  "react-i18next": "^16.5.3",
76
87
  "react-router-dom": "^6.20.1",
88
+ "storybook": "^10.3.0",
89
+ "tailwindcss": "^3.4.0",
90
+ "tailwindcss-animate": "^1.0.7",
77
91
  "typescript": "^5.7.0",
78
92
  "vite": "^6.0.0",
79
93
  "vitest": "^1.0.0"
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Writes src/sdk-version.ts from package.json version so tsc can use rootDir "./src"
3
+ * without importing files outside src/ (which breaks dist/ layout vs package exports).
4
+ */
5
+ import { readFileSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const pkgPath = join(__dirname, "..", "package.json");
11
+ const outPath = join(__dirname, "..", "src", "sdk-version.ts");
12
+ const { version } = JSON.parse(readFileSync(pkgPath, "utf8"));
13
+
14
+ const contents = `/**
15
+ * Semver of @nsxbet/admin-sdk (synced from package.json by scripts/write-sdk-version.mjs).
16
+ * Do not edit manually — run \`node scripts/write-sdk-version.mjs\` after version bumps.
17
+ */
18
+ export const SDK_PACKAGE_VERSION: string = ${JSON.stringify(version)};
19
+ `;
20
+
21
+ writeFileSync(outPath, contents, "utf8");
@@ -1,18 +0,0 @@
1
- /**
2
- * Keycloak Auth Client
3
- *
4
- * Provides real authentication via Keycloak for production environments.
5
- */
6
- import type { KeycloakConfig } from '../../types/keycloak';
7
- import type { AuthClient } from './interface';
8
- /**
9
- * Options for creating a Keycloak auth client
10
- */
11
- export interface KeycloakAuthClientOptions {
12
- /** Keycloak configuration */
13
- config: KeycloakConfig;
14
- }
15
- /**
16
- * Create a Keycloak auth client for production
17
- */
18
- export declare function createKeycloakAuthClient(options: KeycloakAuthClientOptions): AuthClient;
@@ -1,129 +0,0 @@
1
- /**
2
- * Keycloak Auth Client
3
- *
4
- * Provides real authentication via Keycloak for production environments.
5
- */
6
- import Keycloak from 'keycloak-js';
7
- /**
8
- * Create a Keycloak auth client for production
9
- */
10
- export function createKeycloakAuthClient(options) {
11
- const { config } = options;
12
- let keycloak = null;
13
- let currentUser = null;
14
- let isInitialized = false;
15
- const subscribers = new Set();
16
- /**
17
- * Parse user from Keycloak token
18
- */
19
- function parseUser(tokenParsed) {
20
- return {
21
- id: tokenParsed.sub || '',
22
- email: tokenParsed.email || '',
23
- displayName: tokenParsed.name || tokenParsed.preferred_username || '',
24
- roles: tokenParsed.realm_access?.roles ?? [],
25
- };
26
- }
27
- /**
28
- * Notify subscribers of state change
29
- */
30
- function notifySubscribers() {
31
- const state = {
32
- isAuthenticated: currentUser !== null,
33
- user: currentUser,
34
- };
35
- subscribers.forEach((callback) => callback(state));
36
- }
37
- const client = {
38
- type: 'keycloak',
39
- async initialize() {
40
- if (keycloak) {
41
- return keycloak.authenticated ?? false;
42
- }
43
- keycloak = new Keycloak({
44
- url: config.url || 'http://localhost:8080',
45
- realm: config.realm || 'admin',
46
- clientId: config.clientId,
47
- });
48
- try {
49
- const authenticated = await keycloak.init({
50
- onLoad: 'login-required',
51
- checkLoginIframe: false,
52
- pkceMethod: 'S256',
53
- });
54
- if (authenticated && keycloak.tokenParsed) {
55
- currentUser = parseUser(keycloak.tokenParsed);
56
- }
57
- isInitialized = true;
58
- // Notify subscribers that auth is ready - this triggers React re-render
59
- // which will re-evaluate permissions with the now-available token
60
- notifySubscribers();
61
- // Setup token refresh
62
- keycloak.onTokenExpired = () => {
63
- keycloak?.updateToken(30).catch(() => {
64
- console.error('[AuthClient] Failed to refresh token');
65
- client.logout();
66
- });
67
- };
68
- // Setup auth state changes
69
- keycloak.onAuthLogout = () => {
70
- currentUser = null;
71
- notifySubscribers();
72
- };
73
- keycloak.onAuthRefreshError = () => {
74
- currentUser = null;
75
- notifySubscribers();
76
- };
77
- return authenticated;
78
- }
79
- catch (error) {
80
- console.error('[AuthClient] Keycloak initialization failed:', error);
81
- throw error;
82
- }
83
- },
84
- isAuthenticated() {
85
- return keycloak?.authenticated ?? false;
86
- },
87
- getUser() {
88
- return currentUser;
89
- },
90
- async getAccessToken() {
91
- if (!keycloak) {
92
- throw new Error('Keycloak not initialized');
93
- }
94
- try {
95
- await keycloak.updateToken(5);
96
- return keycloak.token || '';
97
- }
98
- catch (error) {
99
- console.error('[AuthClient] Failed to refresh token');
100
- client.logout();
101
- throw error;
102
- }
103
- },
104
- hasPermission(permission) {
105
- // Deny-by-default: return false until Keycloak is fully initialized.
106
- // This prevents a window where all actions appear permitted during auth init.
107
- // AuthProvider gates rendering behind isAuthenticated, so modules won't
108
- // render permission-gated UI until auth completes.
109
- if (!isInitialized || !keycloak) {
110
- return false;
111
- }
112
- return keycloak.hasRealmRole(permission);
113
- },
114
- logout() {
115
- currentUser = null;
116
- if (keycloak) {
117
- keycloak.logout();
118
- }
119
- notifySubscribers();
120
- },
121
- subscribe(callback) {
122
- subscribers.add(callback);
123
- return () => {
124
- subscribers.delete(callback);
125
- };
126
- },
127
- };
128
- return client;
129
- }
@@ -1,37 +0,0 @@
1
- import type { AuthClient } from "../auth/client/interface";
2
- import type { AdminModuleManifest, RegistryClient } from "../registry";
3
- export interface AdminShellProps {
4
- /**
5
- * Module manifests to load (used for standalone mode when no registryClient)
6
- * In standalone mode, modules are rendered via children prop
7
- */
8
- modules?: AdminModuleManifest[];
9
- /**
10
- * Content to render for standalone module development.
11
- * Only used in standalone mode (when modules prop is provided)
12
- */
13
- children?: React.ReactNode;
14
- /**
15
- * Keycloak configuration
16
- * If not provided, uses in-memory (mock) authentication
17
- */
18
- keycloak?: {
19
- url: string;
20
- realm: string;
21
- clientId: string;
22
- };
23
- /**
24
- * Auth client to use for authentication.
25
- * If not provided, creates one based on keycloak prop or environment.
26
- */
27
- authClient?: AuthClient;
28
- /** Registry client for fetching modules from API */
29
- registryClient?: RegistryClient;
30
- /** API URL for registry management (e.g., "http://localhost:4000/api") */
31
- apiUrl?: string;
32
- /** Use in-memory registry (default: true, ignored if registryClient is provided) */
33
- inMemoryRegistry?: boolean;
34
- /** Environment (default: "local") */
35
- environment?: string;
36
- }
37
- export declare function AdminShell({ modules: manifests, children, keycloak, authClient: providedAuthClient, registryClient, apiUrl, inMemoryRegistry, environment, }: AdminShellProps): import("react/jsx-runtime").JSX.Element;
@@ -1,339 +0,0 @@
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";
4
- import { I18nextProvider } from "react-i18next";
5
- import { TopBar } from "./components/TopBar";
6
- import { LeftNav } from "./components/LeftNav";
7
- import { MainContent } from "./components/MainContent";
8
- import { CommandPalette } from "./components/CommandPalette";
9
- import { ThemeProvider } from "./components/theme-provider";
10
- import { ModuleOverview } from "./components/ModuleOverview";
11
- import { ProfilePage } from "./components/ProfilePage";
12
- import { SettingsPage } from "./components/SettingsPage";
13
- import { RegistryPage } from "./components/RegistryPage";
14
- import { HomePage } from "./components/HomePage";
15
- import { DevtoolsPanel } from "./components/DevtoolsPanel";
16
- import { AuthProvider, useAuthContext } from "../components/AuthProvider";
17
- import { createInMemoryAuthClient } from "../auth/client/in-memory";
18
- import { createKeycloakAuthClient } from "../auth/client/keycloak";
19
- import { createInMemoryRegistryClient } from "../registry/client/in-memory";
20
- import { createCachedCatalog } from "../registry/cache/cached-catalog";
21
- import { DynamicModule } from "../router/DynamicModule";
22
- import { SidebarProvider, SidebarInset } from "@nsxbet/admin-ui";
23
- import { initTelemetry, track, trackError } from "./telemetry";
24
- import { initI18n, saveLocale, isSupportedLocale, i18n } from "../i18n";
25
- /**
26
- * Convert AdminModuleManifest to Module for internal use
27
- */
28
- function manifestToModule(manifest, baseUrl) {
29
- return {
30
- id: manifest.id,
31
- title: manifest.title,
32
- description: manifest.description,
33
- category: manifest.category || "Modules",
34
- routeBase: manifest.routeBase,
35
- baseUrl: baseUrl || "",
36
- keywords: manifest.keywords || [],
37
- permissions: {
38
- view: manifest.permissions?.view || [],
39
- dangerous: manifest.permissions?.dangerous,
40
- },
41
- owners: {
42
- team: manifest.owners?.team || "Platform",
43
- supportChannel: manifest.owners?.supportChannel || "",
44
- },
45
- status: "active",
46
- navigationOrder: manifest.navigationOrder,
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,
55
- commands: manifest.commands?.map((cmd) => ({
56
- id: cmd.id,
57
- title: cmd.title,
58
- route: cmd.route,
59
- icon: cmd.icon,
60
- keywords: cmd.keywords,
61
- section: cmd.section,
62
- })),
63
- };
64
- }
65
- /**
66
- * Convert CatalogModule to Module for internal use
67
- */
68
- function catalogModuleToModule(m) {
69
- return {
70
- id: m.id,
71
- title: m.title,
72
- description: m.description,
73
- category: m.category,
74
- routeBase: m.routeBase,
75
- baseUrl: m.baseUrl,
76
- keywords: m.keywords,
77
- permissions: m.permissions,
78
- owners: m.owners,
79
- status: m.status,
80
- navigationOrder: m.navigationOrder,
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,
89
- commands: m.commands?.map(cmd => ({
90
- id: cmd.id,
91
- title: cmd.title,
92
- route: cmd.route,
93
- icon: cmd.icon,
94
- keywords: cmd.keywords,
95
- section: cmd.section,
96
- })),
97
- };
98
- }
99
- /**
100
- * Inner shell component that has access to React Router hooks
101
- */
102
- function ShellContent({ modules, children, environment, locale, onLocaleChange, onSearchClick, catalog, commandPaletteOpen, onCommandPaletteChange, apiUrl, registryClient, isStandaloneMode, cacheStatus, }) {
103
- const navigate = useNavigate();
104
- const auth = useAuthContext();
105
- // Set up the platform API for modules to use
106
- useEffect(() => {
107
- const platformAPI = {
108
- env: environment,
109
- locale: locale,
110
- auth: {
111
- getAccessToken: auth.getAccessToken,
112
- hasPermission: auth.hasPermission,
113
- getUser: () => auth.user || { id: "", email: "", displayName: "", roles: [] },
114
- logout: () => {
115
- auth.logout();
116
- // Navigate to root after logout (for Keycloak, this will redirect)
117
- navigate("/");
118
- },
119
- },
120
- nav: {
121
- navigate: (path) => {
122
- navigate(path);
123
- },
124
- setBreadcrumbs: (_items) => {
125
- // Breadcrumbs are managed by MainContent based on route
126
- },
127
- },
128
- i18n: {
129
- locale: locale,
130
- setLocale: onLocaleChange,
131
- onLocaleChange: (callback) => {
132
- callback(locale);
133
- return () => { };
134
- },
135
- },
136
- timestamp: {
137
- mode: "local",
138
- setMode: () => { },
139
- onModeChange: (callback) => {
140
- callback("local");
141
- return () => { };
142
- },
143
- },
144
- telemetry: {
145
- track: track,
146
- trackError: trackError,
147
- },
148
- fetch: async (input, init) => {
149
- // Add auth token to requests
150
- const token = await auth.getAccessToken();
151
- const headers = new Headers(init?.headers);
152
- headers.set("Authorization", `Bearer ${token}`);
153
- return fetch(input, { ...init, headers });
154
- },
155
- };
156
- window.__ADMIN_PLATFORM_API__ = platformAPI;
157
- return () => {
158
- delete window.__ADMIN_PLATFORM_API__;
159
- };
160
- }, [environment, locale, navigate, onLocaleChange, auth]);
161
- // Load initial sidebar state from localStorage
162
- const getInitialSidebarState = () => {
163
- try {
164
- const stored = localStorage.getItem("adminPlatform.sidebarCollapsed");
165
- if (stored) {
166
- return !JSON.parse(stored); // stored is "collapsed", we need "open"
167
- }
168
- }
169
- catch {
170
- // Ignore
171
- }
172
- return true; // Default to open
173
- };
174
- // Controlled sidebar state
175
- const [sidebarOpen, setSidebarOpen] = useState(getInitialSidebarState);
176
- // Save sidebar state to localStorage when it changes
177
- const handleSidebarChange = (open) => {
178
- setSidebarOpen(open);
179
- try {
180
- localStorage.setItem("adminPlatform.sidebarCollapsed", JSON.stringify(!open));
181
- }
182
- catch {
183
- // Ignore
184
- }
185
- };
186
- 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
187
- ? // Standalone mode: render children (module is imported directly)
188
- modules.map((module) => (_jsx(Route, { path: `${module.routeBase}/*`, element: _jsx(MainContent, { modules: modules, children: children }) }, module.id)))
189
- : // Shell mode: load modules dynamically via React.lazy
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 })] }));
195
- }
196
- export function AdminShell({ modules: manifests = [], children, keycloak, authClient: providedAuthClient, registryClient, apiUrl, inMemoryRegistry = true, environment = "local", }) {
197
- const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
198
- const [locale, setLocale] = useState(() => {
199
- // Initialize i18n and get the current language
200
- initI18n();
201
- return i18n.language || "pt-BR";
202
- });
203
- const [apiModules, setApiModules] = useState([]);
204
- const [isLoading, setIsLoading] = useState(!!registryClient);
205
- const [cacheStatus, setCacheStatus] = useState({ state: 'fresh' });
206
- const [catalogGeneratedAt, setCatalogGeneratedAt] = useState("");
207
- const [catalogVersion, setCatalogVersion] = useState("");
208
- // Handle locale change - update both state and i18next
209
- const handleLocaleChange = useCallback((newLocale) => {
210
- if (isSupportedLocale(newLocale)) {
211
- i18n.changeLanguage(newLocale);
212
- saveLocale(newLocale);
213
- setLocale(newLocale);
214
- }
215
- }, []);
216
- // Sync locale state with i18next language changes
217
- useEffect(() => {
218
- const handleLanguageChanged = (lng) => {
219
- setLocale(lng);
220
- };
221
- i18n.on("languageChanged", handleLanguageChanged);
222
- return () => {
223
- i18n.off("languageChanged", handleLanguageChanged);
224
- };
225
- }, []);
226
- // Determine if we're in standalone mode (module imported directly) or shell mode (dynamic loading)
227
- const isStandaloneMode = !registryClient && manifests.length > 0;
228
- // Create or use provided auth client
229
- const authClient = useMemo(() => {
230
- if (providedAuthClient) {
231
- return providedAuthClient;
232
- }
233
- // Check if we should use mock auth
234
- const useMockAuth = typeof window !== 'undefined' &&
235
- (import.meta.env.VITE_MOCK_AUTH === 'true' || !keycloak);
236
- if (useMockAuth) {
237
- // Create default mock users with wildcard permissions for shell development
238
- return createInMemoryAuthClient({
239
- users: [
240
- { id: 'admin-user', email: 'admin@example.com', displayName: 'Admin User', roles: ['*'] },
241
- { id: 'viewer-user', email: 'viewer@example.com', displayName: 'Viewer User', roles: [] },
242
- ],
243
- gatewayUrl: import.meta.env.VITE_ADMIN_GATEWAY_URL,
244
- });
245
- }
246
- // Use Keycloak
247
- return createKeycloakAuthClient({
248
- config: keycloak,
249
- });
250
- }, [providedAuthClient, keycloak]);
251
- // Initialize telemetry
252
- useEffect(() => {
253
- initTelemetry(environment);
254
- }, [environment]);
255
- // Initialize Keycloak configuration (for legacy support)
256
- useEffect(() => {
257
- if (keycloak) {
258
- window.__KEYCLOAK_CONFIG__ = {
259
- url: keycloak.url,
260
- realm: keycloak.realm,
261
- clientId: keycloak.clientId,
262
- };
263
- }
264
- }, [keycloak]);
265
- // Initialize in-memory registry if enabled and no registryClient
266
- useEffect(() => {
267
- if (!registryClient && inMemoryRegistry && manifests.length > 0) {
268
- createInMemoryRegistryClient({
269
- seed: manifests,
270
- });
271
- }
272
- }, [registryClient, inMemoryRegistry, manifests]);
273
- // Fetch modules from registry client with LKG caching (modules load via React.lazy)
274
- useEffect(() => {
275
- if (!registryClient) {
276
- setIsLoading(false);
277
- return;
278
- }
279
- let mounted = true;
280
- const cached = createCachedCatalog(registryClient.catalog);
281
- async function fetchModules() {
282
- try {
283
- const catalog = await cached.get();
284
- if (!mounted)
285
- return;
286
- const modules = catalog.modules.map(catalogModuleToModule);
287
- setApiModules(modules);
288
- setCacheStatus(cached.getStatus());
289
- setCatalogVersion(catalog.version);
290
- setCatalogGeneratedAt(catalog.generatedAt);
291
- setIsLoading(false);
292
- }
293
- catch (error) {
294
- console.error("[Shell] Failed to fetch modules from API:", error);
295
- if (mounted) {
296
- setCacheStatus(cached.getStatus());
297
- setIsLoading(false);
298
- }
299
- }
300
- }
301
- fetchModules();
302
- return () => {
303
- mounted = false;
304
- };
305
- }, [registryClient]);
306
- // Convert manifests to modules (for standalone mode)
307
- const manifestModules = useMemo(() => {
308
- return manifests.map((m) => manifestToModule(m));
309
- }, [manifests]);
310
- // Use API modules if registry client is provided, otherwise use manifest modules
311
- const modules = registryClient ? apiModules : manifestModules;
312
- // Create catalog for command palette and devtools
313
- const catalog = useMemo(() => {
314
- return {
315
- version: catalogVersion || "1.0.0",
316
- generatedAt: catalogGeneratedAt || new Date().toISOString(),
317
- modules: modules,
318
- };
319
- }, [modules, catalogVersion, catalogGeneratedAt]);
320
- // Keyboard shortcut for command palette
321
- useEffect(() => {
322
- const handleKeyDown = (e) => {
323
- if ((e.metaKey || e.ctrlKey) && e.key === "k") {
324
- e.preventDefault();
325
- setCommandPaletteOpen((prev) => !prev);
326
- }
327
- };
328
- document.addEventListener("keydown", handleKeyDown);
329
- return () => document.removeEventListener("keydown", handleKeyDown);
330
- }, []);
331
- const handleSearchClick = useCallback(() => {
332
- setCommandPaletteOpen(true);
333
- }, []);
334
- // Show loading state while fetching from API
335
- if (isLoading) {
336
- return (_jsx("div", { className: "flex h-screen items-center justify-center", children: _jsx("div", { className: "text-muted-foreground", children: "Loading modules..." }) }));
337
- }
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 }) }) }) }) }));
339
- }
@@ -1,25 +0,0 @@
1
- /**
2
- * Keycloak configuration for standalone mode
3
- */
4
- export interface KeycloakConfig {
5
- /** Keycloak server URL (default: http://localhost:8080) */
6
- url?: string;
7
- /** Keycloak realm (default: admin) */
8
- realm?: string;
9
- /** Keycloak client ID (required) */
10
- clientId: string;
11
- }
12
- /**
13
- * Keycloak token parsed data
14
- */
15
- export interface KeycloakTokenParsed {
16
- sub?: string;
17
- email?: string;
18
- name?: string;
19
- preferred_username?: string;
20
- given_name?: string;
21
- family_name?: string;
22
- realm_access?: {
23
- roles?: string[];
24
- };
25
- }
@@ -1 +0,0 @@
1
- export {};