Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update frontend to support module federation #798

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
13 changes: 13 additions & 0 deletions clients/ui/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ dev-frontend:
dev-start:
make -j 2 dev-bff dev-frontend

########### Dev Integrated ############
.PHONY: dev-start-kubeflow
dev-start-kubeflow:
make -j 2 dev-bff-kubeflow dev-frontend-kubeflow

.PHONY: dev-frontend-kubeflow
dev-frontend-kubeflow:
DEPLOYMENT_MODE=integrated && cd frontend && npm run start:dev

.PHONY: dev-bff-kubeflow
dev-bff-kubeflow:
cd bff && make run PORT=4000 MOCK_K8S_CLIENT=false MOCK_MR_CLIENT=false DEV_MODE=true STANDALONE_MODE=false DEV_MODE_PORT=8085

############ Build ############

.PHONY: docker-build
Expand Down
14 changes: 8 additions & 6 deletions clients/ui/bff/internal/api/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ import (
const (
Version = "1.0.0"

PathPrefix = "/api/v1"
PathPrefix = "/model-registry"
ApiPathPrefix = "/api/v1"
ModelRegistryId = "model_registry_id"
RegisteredModelId = "registered_model_id"
ModelVersionId = "model_version_id"
ModelArtifactId = "model_artifact_id"
ArtifactId = "artifact_id"
HealthCheckPath = PathPrefix + "/healthcheck"
UserPath = PathPrefix + "/user"
ModelRegistryListPath = PathPrefix + "/model_registry"
NamespaceListPath = PathPrefix + "/namespaces"
HealthCheckPath = ApiPathPrefix + "/healthcheck"
UserPath = ApiPathPrefix + "/user"
ModelRegistryListPath = ApiPathPrefix + "/model_registry"
NamespaceListPath = ApiPathPrefix + "/namespaces"
ModelRegistryPath = ModelRegistryListPath + "/:" + ModelRegistryId
RegisteredModelListPath = ModelRegistryPath + "/registered_models"
RegisteredModelPath = RegisteredModelListPath + "/:" + RegisteredModelId
Expand Down Expand Up @@ -130,7 +131,8 @@ func (app *App) Routes() http.Handler {
appMux := http.NewServeMux()

// handler for api calls
appMux.Handle("/api/v1/", apiRouter)
appMux.Handle(ApiPathPrefix+"/", apiRouter)
appMux.Handle(PathPrefix+ApiPathPrefix+"/", http.StripPrefix(PathPrefix, apiRouter))

// file server for the frontend file and SPA routes
staticDir := http.Dir(app.config.StaticAssetsDir)
Expand Down
11 changes: 6 additions & 5 deletions clients/ui/bff/internal/api/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"runtime/debug"
"strings"

"github.com/google/uuid"
"github.com/julienschmidt/httprouter"
"github.com/kubeflow/model-registry/ui/bff/internal/config"
"github.com/kubeflow/model-registry/ui/bff/internal/constants"
"github.com/kubeflow/model-registry/ui/bff/internal/integrations"
"github.com/rs/cors"
"log/slog"
"net/http"
"runtime/debug"
"strings"
)

func (app *App) RecoverPanic(next http.Handler) http.Handler {
Expand All @@ -34,7 +35,7 @@ func (app *App) InjectUserHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

//skip use headers check if we are not on /api/v1
if !strings.HasPrefix(r.URL.Path, PathPrefix) {
if !strings.HasPrefix(r.URL.Path, ApiPathPrefix) && !strings.HasPrefix(r.URL.Path, PathPrefix+ApiPathPrefix) {
next.ServeHTTP(w, r)
return
}
Expand Down
2 changes: 2 additions & 0 deletions clients/ui/docs/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[Local deployment]: ./local-deployment-guide.md
[Local deployment UI]: ./local-deployment-guide-ui.md
[Kubeflow development]: ./kubeflow-development-guide.md

# Model Registry UI Docs

Expand All @@ -9,3 +10,4 @@ This directory contains documentation for the Model Registry UI.

* [Local Deployment Guide][Local deployment]
* [Local Deployment Guide UI][Local deployment UI]
* [Kubeflow Development Guide][Kubeflow development]
56 changes: 56 additions & 0 deletions clients/ui/docs/kubeflow-development-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Set up local development with kubeflow

**Note: this should only be needed in edge cases in which we need to test a local integration with the kubeflow dashboard.**

## Prerequisites

- [Kubeflow repo](https://github.com/kubeflow/kubeflow/tree/master/components/centraldashboard#development)
- [Model Registry repo](../README.md)

## Setup

### Kubeflow repo (under centraldashboard)

1. Change the [webpack config](https://github.com/kubeflow/kubeflow/blob/master/components/centraldashboard/webpack.config.js#L186) proxies to allow Model Registry:

```javascript
proxy: {
...
'/model-registry': {
target: 'http://localhost:9000',
changeOrigin: true,
ws: true,
secure: false,
},
},
```

2. Run the centraldashboard:

```shell
npm run dev
```

### Model Registry repo

1. Just run the repo in kubeflow dev mode

```shell
make dev-start-kubeflow
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
make dev-start-kubeflow
cd clients/ui && make dev-start-kubeflow

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the commands in all the clients/ui/docs are relative to the clients/ui dir

```

### Access the cluster

You need to have a kubeflow cluster up and running, to get the Model Registry working you'll need to port-forward these two services:

```shell
kubectl port-forward service/model-registry-service 8085:8080 -n <targeted-namespace-of-the-mr-service>
```

```shell
kubectl port-forward svc/profiles-kfam 8081:8081 -n kubeflow
```
Copy link
Member

@jenny-s51 jenny-s51 Feb 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```
After setting up port forwarding, you can access the UI by navigating to:
http://localhost:8080


After setting up port forwarding, you can access the UI by navigating to:

http://localhost:8080
29 changes: 23 additions & 6 deletions clients/ui/docs/local-deployment-guide-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Deploying the Model Registry UI in a local cluster

For this guide, we will be using kind for locally deploying our cluster. See
the [Model registry server set up] guide for prerequisites on setting up kind
the [Model registry server set up] guide for prerequisites on setting up kind
and deploying the model registry server.

## Setup
Expand All @@ -24,16 +24,33 @@ Create a namespace for model registry to run in, by default this is kubeflow, ru
kubectl create namespace kubeflow
```

### 3. Deploy Model Registry UI to cluster
### 3. Build a standalone image for the UI

Right now, the default image is targeted for the KF Central Dashboard. To build a standalone image for the UI, run:

You can now deploy the UI and BFF to your newly created cluster using the kustomize configs in the root manifest directory:
```shell
cd manifests/kustomize/options/ui/overlays/standalone
make docker-build-standalone
make docker-push-standalone
```

**Note: You will need to set up `IMG_UI_STANDALONE` in your .env.local file to push the image to your own registry.**

### 4. Deploy Model Registry UI to cluster

You can now deploy the UI and BFF to your newly created cluster using the kustomize configs in the root manifest directory:

First you need to set up your new image

```shell
kustomize edit set namespace kubeflow
cd manifests/kustomize/options/ui/base
kustomize edit set image model-registry-ui-image=${IMG_UI_STANDALONE}
```

Now you can set the namespace to kubeflow and apply the manifests:

```shell
cd manifests/kustomize/options/ui/overlays/standalone
kustomize edit set namespace kubeflow
kubectl apply -k .
```

Expand All @@ -48,7 +65,7 @@ NAME READY STATUS RESTARTS AGE
model-registry-ui-58755c4754-zdrnr 1/1 Running 0 11s
```

### 4. Access the Model Registry UI running in the cluster
### 5. Access the Model Registry UI running in the cluster

Now that the pods are up and running you can access the UI.

Expand Down
3 changes: 2 additions & 1 deletion clients/ui/docs/local-deployment-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ kubectl cluster-info

If everything is working correctly you should see output similar to:

```
``` shell
Kubernetes control plane is running at https://127.0.0.1:58635
CoreDNS is running at https://127.0.0.1:58635/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
```
Expand Down Expand Up @@ -105,6 +105,7 @@ curl http://localhost:8080/api/model_registry/v1alpha3/registered_models
```

You should receive a 200 response if everything is working correctly, the body should look like:

```json
{"items":[],"nextPageToken":"","pageSize":0,"size":0}
```
Expand Down
4 changes: 2 additions & 2 deletions clients/ui/frontend/config/dotenv.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,19 @@ const setupDotenvFilesForEnv = ({ env }) => {
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '.env.local'));
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '.env'));

const DEPLOYMENT_MODE = process.env.DEPLOYMENT_MODE || 'integrated';
const IMAGES_DIRNAME = process.env.IMAGES_DIRNAME || 'images';
const PUBLIC_PATH = process.env.PUBLIC_PATH || '/';
const SRC_DIR = path.resolve(RELATIVE_DIRNAME, process.env.SRC_DIR || TS_BASE_URL || 'src');
const COMMON_DIR = path.resolve(RELATIVE_DIRNAME, process.env.COMMON_DIR || '../common');
const DIST_DIR = path.resolve(RELATIVE_DIRNAME, process.env.DIST_DIR || TS_OUT_DIR || 'public');
const HOST = process.env.HOST || 'localhost';
const HOST = process.env.HOST || DEPLOYMENT_MODE === 'integrated' ? '0.0.0.0' : 'localhost';
const PORT = process.env.PORT || '9000';
const PROXY_PROTOCOL = process.env.PROXY_PROTOCOL || 'http';
const PROXY_HOST = process.env.PROXY_HOST || 'localhost';
const PROXY_PORT = process.env.PROXY_PORT || process.env.PORT || 4000;
const DEV_MODE = process.env.DEV_MODE || undefined;
const OUTPUT_ONLY = process.env._OUTPUT_ONLY === 'true';
const DEPLOYMENT_MODE = process.env.DEPLOYMENT_MODE || 'integrated';

process.env._RELATIVE_DIRNAME = RELATIVE_DIRNAME;
process.env._IS_PROJECT_ROOT_DIR = IS_ROOT;
Expand Down
27 changes: 27 additions & 0 deletions clients/ui/frontend/config/moduleFederation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const path = require('path');
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');

const deps = require('../package.json').dependencies;

const moduleFederationConfig = {
name: 'modelRegistry',
filename: 'remoteEntry.js',
shared: {
react: { singleton: true, eager: true, requiredVersion: deps.react },
'react-dom': { singleton: true, eager: true, requiredVersion: deps['react-dom'] },
'react-router': { singleton: true, eager: true, requiredVersion: deps['react-router'] },
'react-router-dom': { singleton: true, eager: true, requiredVersion: deps['react-router-dom'] },
},
exposes: {
// TODO expose modules. eg:
// './index': './src/plugin/index.tsx',
// './plugin': './src/plugin/index.tsx',
},
// For module federation to work when optimization.runtimeChunk="single":
// See https://github.com/webpack/webpack/issues/18810
runtime: false,
};

module.exports = {
moduleFederationPlugins: [new ModuleFederationPlugin(moduleFederationConfig)],
};
Loading