@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.
- package/CHECKLIST.md +48 -13
- package/README.md +24 -74
- package/dist/auth/client/bff.d.ts +38 -0
- package/dist/auth/client/bff.js +270 -0
- package/dist/auth/client/in-memory.d.ts +1 -1
- package/dist/auth/client/in-memory.js +2 -2
- package/dist/auth/client/index.d.ts +1 -1
- package/dist/auth/client/index.js +2 -2
- package/dist/auth/client/interface.d.ts +4 -4
- package/dist/auth/client/interface.js +1 -1
- package/dist/auth/client/private-network-guidance.d.ts +2 -0
- package/dist/auth/client/private-network-guidance.js +38 -0
- package/dist/auth/components/LoginPage.d.ts +8 -0
- package/dist/auth/components/LoginPage.js +32 -0
- package/dist/auth/components/UserSelector.d.ts +29 -0
- package/dist/auth/components/UserSelector.js +38 -10
- package/dist/auth/components/UserSelector.stories.d.ts +9 -0
- package/dist/auth/components/UserSelector.stories.js +70 -0
- package/dist/auth/components/index.d.ts +2 -0
- package/dist/auth/components/index.js +1 -0
- package/dist/auth/index.d.ts +3 -2
- package/dist/auth/index.js +2 -2
- package/dist/components/AuthProvider.d.ts +3 -3
- package/dist/components/AuthProvider.js +35 -17
- package/dist/env.d.ts +17 -0
- package/dist/env.js +50 -0
- package/dist/hooks/useAuth.d.ts +3 -3
- package/dist/hooks/useAuth.js +26 -21
- package/dist/hooks/useCallbackRef.d.ts +7 -0
- package/dist/hooks/useCallbackRef.js +14 -0
- package/dist/hooks/useFetch.js +6 -1
- package/dist/hooks/useI18n.js +2 -2
- package/dist/hooks/usePlatformAPI.d.ts +0 -3
- package/dist/hooks/usePlatformAPI.js +6 -4
- package/dist/i18n/config.d.ts +2 -1
- package/dist/i18n/config.js +4 -3
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/locales/en-US.json +7 -0
- package/dist/i18n/locales/es.json +7 -0
- package/dist/i18n/locales/pt-BR.json +7 -0
- package/dist/i18n/locales/ro.json +7 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +6 -2
- package/dist/registry/AdminShellRegistry.js +4 -3
- package/dist/registry/client/http.js +6 -1
- package/dist/registry/client/in-memory.js +20 -5
- package/dist/registry/types/manifest.d.ts +5 -0
- package/dist/registry/types/manifest.js +4 -1
- package/dist/registry/types/module.d.ts +6 -2
- package/dist/sdk-version.d.ts +5 -0
- package/dist/sdk-version.js +5 -0
- package/dist/shell/AdminShell.d.ts +12 -9
- package/dist/shell/AdminShell.js +56 -70
- package/dist/shell/components/ModuleOverview.js +1 -5
- package/dist/shell/components/RegistryPage.js +1 -1
- package/dist/shell/components/TopBar.js +2 -2
- package/dist/shell/components/theme-provider.js +6 -8
- package/dist/shell/index.d.ts +1 -1
- package/dist/shell/polling-config.d.ts +4 -3
- package/dist/shell/polling-config.js +11 -9
- package/dist/shell/types.d.ts +3 -1
- package/dist/types/platform.d.ts +2 -11
- package/dist/vite/config.d.ts +4 -9
- package/dist/vite/config.js +85 -27
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +1 -1
- package/dist/vite/plugins.js +6 -1
- package/package.json +20 -6
- package/scripts/write-sdk-version.mjs +21 -0
- package/dist/auth/client/keycloak.d.ts +0 -18
- package/dist/auth/client/keycloak.js +0 -129
- package/dist/shell/BackofficeShell.d.ts +0 -37
- package/dist/shell/BackofficeShell.js +0 -339
- package/dist/types/keycloak.d.ts +0 -25
- package/dist/types/keycloak.js +0 -1
package/dist/vite/plugins.js
CHANGED
|
@@ -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 = {
|
|
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.
|
|
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
|
-
}
|
package/dist/types/keycloak.d.ts
DELETED
|
@@ -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
|
-
}
|
package/dist/types/keycloak.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|