This is an example Angular microfrontend that demonstrates how to build a custom UI for your Platform Mesh provider. It shows how to integrate with the Luigi shell, authenticate API calls, and manage Kubernetes custom resources via GraphQL.
┌─────────────────────────────────────────────────────────────────┐
│ Platform Mesh Portal │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Luigi Shell │ │
│ │ ┌─────────────────┐ ┌─────────────────────────────┐ │ │
│ │ │ Navigation │ │ Your Microfrontend │ │ │
│ │ │ │ │ ┌─────────────────────────┐│ │ │
│ │ │ - Dashboard │ │ │ CowboysComponent ││ │ │
│ │ │ - Settings │ │ │ ││ │ │
│ │ │ - Cowboys ◄────┼──┼──│ Receives context: ││ │ │
│ │ │ (your MFE) │ │ │ - auth token ││ │ │
│ │ │ │ │ │ - API URLs ││ │ │
│ │ └─────────────────┘ │ │ - account info ││ │ │
│ │ │ └─────────────────────────┘│ │ │
│ │ └──────────────────────────────────┘ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ CRD GraphQL Gateway │ │
│ │ /api/kubernetes-graphql-gateway/... │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
The Portal uses Luigi as its microfrontend framework. Your microfrontend receives context from Luigi containing:
token- Bearer token for API authentication (user's session)portalContext.crdGatewayApiUrl- GraphQL endpoint for Kubernetes resourcesaccountId- Current account context
// In your component
import { LuigiContextService } from '@luigi-project/client-support-angular';
@Component({...})
export class MyComponent {
private luigiContextService = inject(LuigiContextService);
// Convert to Angular signal for reactivity
public luigiContext = toSignal(this.luigiContextService.contextObservable());
ngOnInit() {
// IMPORTANT: Wait for Luigi handshake before making API calls
LuigiClient.addInitListener(() => {
this.loadData();
});
}
}The Platform Mesh CRD Gateway exposes your Kubernetes resources via GraphQL. The schema is auto-generated from your CRDs:
# Query pattern: {apiGroup}_{version}_{Kind}s
query ListCowboys {
wildwest_platform_mesh_io {
v1alpha1 {
Cowboys {
items {
metadata { name namespace }
spec { intent }
status { result }
}
}
}
}
}
# Mutation pattern: create{Kind}(namespace, object)
mutation CreateCowboy($name: String!, $namespace: String!) {
wildwest_platform_mesh_io {
v1alpha1 {
createCowboy(namespace: $namespace, object: { metadata: { name: $name } }) {
metadata { name }
}
}
}
}Your microfrontend registers with the Portal via a ContentConfiguration resource:
apiVersion: ui.platform-mesh.io/v1alpha1
kind: ContentConfiguration
metadata:
labels:
ui.platform-mesh.io/content-for: wildwest.platform-mesh.io # Links to your APIExport
name: cowboys-ui
spec:
inlineConfiguration:
contentType: json
content: |
{
"name": "cowboys",
"luigiConfigFragment": {
"data": {
"nodes": [{
"pathSegment": "cowboys",
"label": "Cowboys",
"entityType": "main.core_platform-mesh_io_account:1",
"url": "https://your-mfe-host/index.html",
"icon": "person-placeholder",
"context": {
"accountId": ":core_platform-mesh_io_accountId"
}
}]
}
}
}Key fields:
entityType: Where in the navigation tree to show your MFEmain- Root levelmain.core_platform-mesh_io_account:1- Under account level
url: URL where your microfrontend is hostedcontext: Variables passed to your MFE from the URL
portal/
├── public/
│ └── content-configuration.json # Luigi navigation config (local dev)
├── src/
│ ├── app/
│ │ ├── app.component.ts # Root component
│ │ ├── app.config.ts # Angular DI config (LuigiContextService)
│ │ └── cowboys/
│ │ ├── cowboys.component.ts # Main UI component
│ │ ├── cowboys.component.html # Template with UI5 components
│ │ ├── cowboys.component.scss # Styles
│ │ └── cowboys.service.ts # GraphQL API service
│ └── styles.scss # Global styles
├── proxy.conf.json # Dev proxy for API calls
└── angular.json # Angular configuration
- Node.js 18+
- Platform Mesh local setup running (see main repo README)
- Your provider registered and bound to an account
cd portal
npm install-
Start the dev server:
npm start
This serves the microfrontend at
http://localhost:4200 -
Enable local development mode in Portal:
- Open the Portal UI in your browser
- Enable local development mode (usually via developer tools or settings)
- The Portal will load your local MFE instead of the deployed version
-
Configure proxy for API calls:
The
proxy.conf.mjsroutes/apirequests to the Platform Mesh gateway:export default { '/api': { target: 'https://bob.portal.localhost:8443', secure: false, changeOrigin: true } };
Update the
targetto match your local Portal URL.Important: After changing proxy settings, restart the dev server.
- Your MFE serves
content-configuration.jsonat its root - Portal fetches this and merges it with server-side configs
- Luigi renders your MFE in an iframe
- Context (token, URLs) flows from Portal to your MFE
For production, you need to:
-
Build the microfrontend:
npm run build
-
Host the built files on a web server accessible to Portal users
-
Create a ContentConfiguration resource pointing to your hosted URL:
apiVersion: ui.platform-mesh.io/v1alpha1 kind: ContentConfiguration metadata: labels: ui.platform-mesh.io/content-for: wildwest.platform-mesh.io name: cowboys-ui spec: inlineConfiguration: contentType: json content: | { "name": "cowboys", "luigiConfigFragment": { "data": { "nodes": [{ "pathSegment": "cowboys", "label": "Cowboys", "url": "https://your-production-host/index.html", ... }] } } }
This example uses SAP UI5 Web Components for consistent styling with the Portal. Key components used:
ui5-dynamic-page- Page layout with collapsible headerui5-avatar- User/resource iconsui5-toolbar- Action barsui5-button- Buttonsui5-dialog- Modal dialogsui5-input,ui5-select- Form controlsui5-title,ui5-label,ui5-text- Typography
Browse available components: https://sap.github.io/ui5-webcomponents/playground/
Common Luigi client methods:
import LuigiClient from '@luigi-project/client';
// Wait for initialization
LuigiClient.addInitListener(() => { ... });
// Show/hide loading indicator
LuigiClient.uxManager().showLoadingIndicator();
LuigiClient.uxManager().hideLoadingIndicator();
// Show alerts
LuigiClient.uxManager().showAlert({
text: 'Operation successful',
type: 'success', // 'success' | 'info' | 'warning' | 'error'
closeAfter: 3000
});
// Show confirmation dialog
LuigiClient.uxManager().showConfirmationModal({
header: 'Confirm Delete',
body: 'Are you sure?',
buttonConfirm: 'Delete',
buttonDismiss: 'Cancel'
}).then(() => { /* confirmed */ }).catch(() => { /* cancelled */ });
// Navigate within Portal
LuigiClient.linkManager().navigate('/path/to/page');Full API docs: https://docs.luigi-project.io/docs/luigi-client-api
When running locally, you may see CORS errors. Solutions:
- Use the Angular proxy - Ensure
proxy.conf.jsonis configured andng serveuses it - Update Portal CORS settings - Add
http://localhost:4200to allowed origins
If luigiContext is empty:
- Ensure you wait for
LuigiClient.addInitListener()before accessing context - Check that local development mode is enabled in Portal
- Verify your
content-configuration.jsonis being served correctly
- Check the GraphQL endpoint URL in browser dev tools
- Verify the auth token is being sent in the
Authorizationheader - Use GraphQL introspection to discover available types:
query { __schema { types { name } } }