This commit is contained in:
2025-08-31 01:33:35 -04:00
parent 01c940ea31
commit d05af3b385
14 changed files with 236 additions and 92 deletions

2
web/dist/main.js vendored

File diff suppressed because one or more lines are too long

View File

@ -3,10 +3,22 @@ import { useParams } from 'react-router-dom';
import { Loader, Center, Text, Stack, Alert } from '@mantine/core';
import { IconAlertCircle } from '@tabler/icons-react';
import Breadcrumbs from './Breadcrumbs';
import { microFrontends } from '../microfrontends.js';
const DemoApp = React.lazy(() => import('demo/App'));
const KMSApp = React.lazy(() => import('kms/App'));
const FaaSApp = React.lazy(() => import('faas/App'));
// Pre-defined lazy imports for each microfrontend
// @ts-ignore - These modules are loaded at runtime via Module Federation
const DemoApp = React.lazy(() => import('demo/src/App'));
// @ts-ignore - These modules are loaded at runtime via Module Federation
const KMSApp = React.lazy(() => import('kms/src/App'));
// @ts-ignore - These modules are loaded at runtime via Module Federation
const FaaSApp = React.lazy(() => import('faas/src/App'));
// Map app names to components
const appComponents: Record<string, React.LazyExoticComponent<React.ComponentType<any>>> = {
demo: DemoApp,
kms: KMSApp,
faas: FaaSApp,
};
const AppLoader: React.FC = () => {
const { appName } = useParams<{ appName: string }>();
@ -32,32 +44,21 @@ const AppLoader: React.FC = () => {
);
const renderApp = () => {
switch (appName) {
case 'demo':
return (
<Suspense fallback={<LoadingFallback />}>
<DemoApp />
</Suspense>
);
case 'kms':
return (
<Suspense fallback={<LoadingFallback />}>
<KMSApp />
</Suspense>
);
case 'faas':
return (
<Suspense fallback={<LoadingFallback />}>
<FaaSApp />
</Suspense>
);
default:
return (
<ErrorFallback
error={`Application "${appName}" is not available or not configured.`}
/>
);
// Check if the app is registered in our microfrontends registry
if (appName && appComponents[appName] && microFrontends[appName]) {
const App = appComponents[appName];
return (
<Suspense fallback={<LoadingFallback />}>
<App />
</Suspense>
);
}
return (
<ErrorFallback
error={`Application "${appName}" is not available or not configured.`}
/>
);
};
return (
@ -68,4 +69,4 @@ const AppLoader: React.FC = () => {
);
};
export default AppLoader;
export default AppLoader;

View File

@ -1,44 +1,89 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { NavLink, Stack, Text } from '@mantine/core';
import { useNavigate, useLocation } from 'react-router-dom';
import {
IconDashboard,
IconKey,
IconFunction,
IconApps,
} from '@tabler/icons-react';
import { microFrontends } from '../microfrontends.js';
// Map microfrontend IDs to display information
const getAppInfo = (id: string) => {
const appInfo: Record<string, { label: string; icon: React.ComponentType<any>; category: string }> = {
demo: {
label: 'Demo App',
icon: IconDashboard,
category: 'Development',
},
kms: {
label: 'Key Management',
icon: IconKey,
category: 'Security',
},
faas: {
label: 'Functions',
icon: IconFunction,
category: 'Development',
},
};
return appInfo[id] || {
label: id.charAt(0).toUpperCase() + id.slice(1),
icon: IconApps,
category: 'Application',
};
};
const Navigation: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
// Define all available applications
const allAvailableApplications = [
{
label: 'Demo App',
icon: IconDashboard,
path: '/app/demo',
},
{
label: 'Key Management',
icon: IconKey,
path: '/app/kms',
},
{
label: 'Functions',
icon: IconFunction,
path: '/app/faas',
},
];
// Load favorites from localStorage (same as HomePage)
const [favoriteApps, setFavoriteApps] = useState<string[]>(() => {
try {
const saved = localStorage.getItem('favoriteMicrofrontends');
return saved ? JSON.parse(saved) : ['demo'];
} catch {
return ['demo'];
}
});
// Define which apps are favorited (you could make this dynamic later)
const favoritePaths = ['/app/demo'];
// Listen for changes to favorites in localStorage (in case they change in HomePage)
useEffect(() => {
const handleStorageChange = () => {
try {
const saved = localStorage.getItem('favoriteMicrofrontends');
if (saved) {
setFavoriteApps(JSON.parse(saved));
}
} catch {
// If parsing fails, keep current state
}
};
window.addEventListener('storage', handleStorageChange);
return () => window.removeEventListener('storage', handleStorageChange);
}, []);
// Generate all available applications from the microfrontends registry
const allAvailableApplications = Object.keys(microFrontends).map(id => {
const info = getAppInfo(id);
return {
id,
label: info.label,
icon: info.icon,
path: `/app/${id}`,
};
});
const favoriteApplications = allAvailableApplications.filter(app =>
favoritePaths.includes(app.path)
favoriteApps.includes(app.id)
);
const nonFavoriteApplications = allAvailableApplications.filter(app =>
!favoritePaths.includes(app.path)
!favoriteApps.includes(app.id)
);
return (
@ -78,4 +123,4 @@ const Navigation: React.FC = () => {
);
};
export default Navigation;
export default Navigation;

59
web/src/microfrontends.js Normal file
View File

@ -0,0 +1,59 @@
// Centralized registry for all microfrontends
// This file contains the single source of truth for all microfrontend configurations
// Define all microfrontends
export const microFrontends = {
demo: {
name: 'demo',
url: 'http://localhost:3001',
port: 3001,
exposedModule: './src/App',
importPath: 'demo/src/App',
},
kms: {
name: 'kms',
url: 'http://localhost:3002',
port: 3002,
exposedModule: './src/App',
importPath: 'kms/src/App',
},
faas: {
name: 'faas',
url: 'http://localhost:3003',
port: 3003,
exposedModule: './src/App',
importPath: 'faas/src/App',
},
};
// Generate remotes configuration for Module Federation
export const getRemotesConfig = () => {
const remotes = {};
Object.values(microFrontends).forEach(mf => {
remotes[mf.name] = `${mf.name}@${mf.url}/remoteEntry.js`;
});
return remotes;
};
// Generate exposes configuration for Module Federation (for microfrontends)
export const getExposesConfig = (appName) => {
const appConfig = microFrontends[appName];
if (!appConfig) {
throw new Error(`Microfrontend ${appName} not found in registry`);
}
const exposes = {};
exposes[appConfig.exposedModule] = appConfig.exposedModule;
return exposes;
};
// Get app loader configuration for AppLoader component
export const getAppLoaderConfig = () => {
return Object.keys(microFrontends).map(name => ({
name,
importPath: microFrontends[name].importPath,
}));
};

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import {
SimpleGrid,
Card,
@ -16,8 +16,10 @@ import {
IconDashboard,
IconKey,
IconFunction,
IconApps,
} from '@tabler/icons-react';
import Breadcrumbs from '../components/Breadcrumbs';
import { microFrontends } from '../microfrontends.js';
interface App {
id: string;
@ -28,36 +30,71 @@ interface App {
category: string;
}
const HomePage: React.FC = () => {
const navigate = useNavigate();
const [favoriteApps, setFavoriteApps] = useState<string[]>(['demo']);
const availableApps: App[] = [
{
id: 'demo',
// Map microfrontend IDs to display information
const getAppInfo = (id: string) => {
const appInfo: Record<string, { name: string; description: string; icon: React.ComponentType<any>; category: string }> = {
demo: {
name: 'Demo App',
description: 'Sample microfrontend application for testing module federation',
icon: IconDashboard,
path: '/app/demo',
category: 'Development',
},
{
id: 'kms',
kms: {
name: 'Key Management System',
description: 'Manage API keys, tokens, and access permissions for applications',
icon: IconKey,
path: '/app/kms',
category: 'Security',
},
{
id: 'faas',
faas: {
name: 'Function-as-a-Service',
description: 'Deploy and manage serverless functions',
icon: IconFunction,
path: '/app/faas',
category: 'Development',
},
];
};
return appInfo[id] || {
name: id.charAt(0).toUpperCase() + id.slice(1),
description: `Microfrontend application: ${id}`,
icon: IconApps,
category: 'Application',
};
};
const HomePage: React.FC = () => {
const navigate = useNavigate();
// Load favorites from localStorage or default to demo app
const [favoriteApps, setFavoriteApps] = useState<string[]>(() => {
try {
const saved = localStorage.getItem('favoriteMicrofrontends');
return saved ? JSON.parse(saved) : ['demo'];
} catch {
return ['demo'];
}
});
// Save favorites to localStorage whenever they change
useEffect(() => {
try {
localStorage.setItem('favoriteMicrofrontends', JSON.stringify(favoriteApps));
} catch (e) {
console.warn('Failed to save favorites to localStorage', e);
}
}, [favoriteApps]);
// Generate available apps from the microfrontends registry
const availableApps: App[] = Object.keys(microFrontends).map(id => {
const info = getAppInfo(id);
return {
id,
name: info.name,
description: info.description,
icon: info.icon,
path: `/app/${id}`,
category: info.category,
};
});
const toggleFavorite = (appId: string) => {
setFavoriteApps(prev =>

View File

@ -1,5 +1,6 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const { getRemotesConfig } = require('./src/microfrontends.js');
module.exports = {
mode: 'development',
@ -44,11 +45,7 @@ module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
demo: 'demo@http://localhost:3001/remoteEntry.js',
kms: 'kms@http://localhost:3002/remoteEntry.js',
faas: 'faas@http://localhost:3003/remoteEntry.js',
},
remotes: getRemotesConfig(),
shared: {
react: {
singleton: true,
@ -86,4 +83,4 @@ module.exports = {
template: './public/index.html',
}),
],
};
};