Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ SESSION_SECRET=

FEEDBACK_SLACK_URL=
FEEDBACK_URL_LINK=

# frame-ancestors attribute of CSP. Separate multiple values with a space
FRAME_ANCESTORS=
72 changes: 52 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,72 @@

# ui-frontend

## About this project
## About This Project

This repository contains code relevant for the frontend component required in the Managed Control Plane UI (MCP UI), which is part of the @openmcp-project, more info [here](https://github.com/openmcp-project).

The MCP UI enables endusers to work with Managed Control Planes, without having to use kubectl. Note that the current focus of the UI is on displaying information about the various managed resources, as well as the MCP instances themselves. It is also possible to check the status of the resources, and display / copy their YAML representations.

Overall, the UI provides an easy jump-start for everyone interested in checking the status of Managed Control Planes, without having to use kubectl.

## Requirements and Setup
## Getting Started

### Development
### Development Setup

1. install dependencies: `npm i`
#### Install Dependencies

1. Copy the `frontend-config.json` to `public/frontend-config.json` and adapt the `backendUrl` according to your setup (see section Dynamic Frontend Config).
```bash
npm i
```

#### Configure Frontend

- Copy `frontend-config.json` to `public/frontend-config.json` and adapt the `backendUrl` according to your setup (see section Dynamic Frontend Config).
- Copy `.env.template` to `.env` and fill in the missing values.

1. Connect to the ui-backend server
**Run it locally**:
- See `https://github.com/openmcp-project/ui-backend`
#### Run the Application

```bash
npm run dev
```

1. Start the application:
The UI will be served on http://localhost:5173.

Run `npm run dev`

### Build
#### Safari Support

1. Build the application:
**Note:** The frontend is currently incompatible with Safari when running locally on `localhost`.

Run `npm run build`
To enable local development with Safari, follow these steps on your local machine:

2. Serve the application locally:
1. **Update Cookie Settings:**
In [`server/encrypted-session.js`](server/encrypted-session.js), set the `secure` property to `false` in both occurrences.

Run `npm run preview`
2. **Disable Helmet Registration:**
In [`server.js`](server.js), comment out or remove the registration of `helmet`.

3. For production:

Use the docker image which uses nginx for best performance and small bundle size.
`docker build -t my-label .`
### Build & Production

#### Build the Application

```bash
npm run build
```

#### Serve the Production Build Locally

```bash
npm run preview
```

#### Production Deployment

Use the docker image which uses nginx for best performance and small bundle size.

```bash
docker build -t my-label .
```

### Dynamic FrontendConfig

Expand All @@ -50,11 +78,11 @@ An example docker run command would be
docker run -p 5001:80 -e BACKEND_CONFIG="$(cat frontend-config.json)" -t ui-test
```

## Support, Feedback, Contributing
## Support & Contributing

This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/openmcp-project/ui-frontend/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md).

## Security / Disclosure
## Security & Disclosure
If you find any bug that may be a security problem, please follow our instructions at [in our security policy](https://github.com/openmcp-project/ui-frontend/security/policy) on how to report it. Please do not create GitHub issues for security-related doubts or problems.

## Code of Conduct
Expand All @@ -64,3 +92,7 @@ We as members, contributors, and leaders pledge to make participation in our com
## Licensing

Copyright 2025 SAP SE or an SAP affiliate company and ui-frontend contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/openmcp-project/ui-frontend).

---

**Happy contributing! 🚀**
6 changes: 1 addition & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@
<title>MCP</title>
</head>

<script>
window.global ||= window;
</script>

<body>
<div id="root"></div>
<script type="module" src="/src/mount.ts"></script>
</body>

</html>
</html>
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"npm": "^11.0.0"
},
"scripts": {
"dev": "node server.js --dev",
"dev": "node server.js --local-dev",
"start": "node server.js",
"build": "tsc && vite build",
"lint": "eslint ./src --report-unused-disable-directives --max-warnings 0",
Expand All @@ -23,9 +23,10 @@
"@fastify/autoload": "^6.3.0",
"@fastify/cookie": "^11.0.2",
"@fastify/env": "^5.0.2",
"@fastify/helmet": "^13.0.1",
"@fastify/http-proxy": "^11.1.2",
"@fastify/sensible": "^6.0.3",
"@fastify/secure-session": "^8.2.0",
"@fastify/sensible": "^6.0.3",
"@fastify/session": "^11.1.0",
"@fastify/static": "^8.1.1",
"@fastify/vite": "^8.1.3",
Expand Down
25 changes: 22 additions & 3 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import Fastify from 'fastify';
import FastifyVite from '@fastify/vite';
import helmet from '@fastify/helmet';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import dotenv from 'dotenv';
import proxy from './server/app.js';
import envPlugin from "./server/config/env.js";
import { copyFileSync } from 'node:fs';

dotenv.config();

const isDev = process.argv.includes('--dev');
const isLocalDev = process.argv.includes('--local-dev');

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const frontendConfigLocation = isDev ? 'public/frontend-config.json' : 'dist/client/frontend-config.json';
const frontendConfigLocation = isLocalDev
? 'public/frontend-config.json'
: 'dist/client/frontend-config.json';

if (process.env.FRONTEND_CONFIG_PATH !== undefined && process.env.FRONTEND_CONFIG_PATH.length > 0) {
console.log('FRONTEND_CONFIG_PATH is specified. Will copy the frontend-config from there.');
Expand All @@ -24,13 +28,28 @@ const fastify = Fastify({
logger: true,
});

await fastify.register(envPlugin);

fastify.register(
helmet,
{
contentSecurityPolicy: {
directives: {
"connect-src": ["'self'", "sdk.openui5.org"],
"script-src": isLocalDev ? ["'self'", "'unsafe-inline'"] : ["'self'"],
"frame-ancestors": [fastify.config.FRAME_ANCESTORS]
},
}
}
)

fastify.register(proxy, {
prefix: '/api',
});

await fastify.register(FastifyVite, {
root: __dirname,
dev: isDev,
dev: isLocalDev,
spa: true,
});

Expand Down
10 changes: 4 additions & 6 deletions server/app.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import path, { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import AutoLoad from '@fastify/autoload';
import envPlugin from './config/env.js';
import encryptedSession from './encrypted-session.js';
import path, { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import AutoLoad from "@fastify/autoload";
import encryptedSession from "./encrypted-session.js";

export const options = {};

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export default async function (fastify, opts) {
await fastify.register(envPlugin);
fastify.register(encryptedSession, {
...opts,
});
Expand Down
2 changes: 2 additions & 0 deletions server/config/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const schema = {
'COOKIE_SECRET',
'SESSION_SECRET',
'API_BACKEND_URL',
'FRAME_ANCESTORS',
],
properties: {
// Application variables (.env)
Expand All @@ -27,6 +28,7 @@ const schema = {
API_BACKEND_URL: { type: 'string' },
FEEDBACK_SLACK_URL: { type: 'string' },
FEEDBACK_URL_LINK: { type: 'string' },
FRAME_ANCESTORS: { type: 'string' },

// System variables
NODE_ENV: { type: 'string', enum: ['development', 'production'] },
Expand Down
12 changes: 7 additions & 5 deletions server/encrypted-session.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const ENCRYPTED_COOKIE_KEY_ENCRYPTION_KEY = 'encryptionKey';
export const SESSION_COOKIE_NAME = 'session-cookie';

async function encryptedSession(fastify) {
const { COOKIE_SECRET, SESSION_SECRET, NODE_ENV } = fastify.config;
const { COOKIE_SECRET, SESSION_SECRET } = fastify.config;

await fastify.register(fastifyCookie);

Expand All @@ -31,8 +31,9 @@ async function encryptedSession(fastify) {
cookie: {
path: '/',
httpOnly: true,
sameSite: 'lax',
secure: NODE_ENV === 'production',
sameSite: "None", // cross-site cookies are needed for the session to work when embedded. By setting CORS to None and CSP.frame-anchestors we restrict the api calls from the browser that contain the cookies to originating from our site only.
partitioned: true, // use for modern isolation of third party cookies when embedded, every embedded iframe (or not embedded) gets its own cookie partition
secure: true,
maxAge: 60 * 60 * 24 * 7, // 7 days
},
});
Expand All @@ -43,8 +44,9 @@ async function encryptedSession(fastify) {
cookie: {
path: '/',
httpOnly: true,
sameSite: 'lax',
secure: NODE_ENV === 'production',
sameSite: "None", // see secureSession cookie for explanation
partitioned: true, // see secureSession cookie for explanation
secure: true,
maxAge: 60 * 60 * 24 * 7, // 7 days
},
});
Expand Down
Loading