Skip to content
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
1 change: 0 additions & 1 deletion .env.production
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
VITE_PUBLIC_URL=/mlrun
VITE_FEDERATION=false
VITE_MLRUN_API_URL=${MLRUN_API_PROXY_URL}
VITE_IGUAZIO_API_URL=
VITE_NUCLIO_API_URL=${MLRUN_NUCLIO_API_URL}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/publish-mlrun-ui-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
#
name: Publish mlrun-ui Image (feature/ig4)

# Builds the mlrun-ui Docker image (micro-frontend variant, IS_MF=true) and
# Builds the mlrun-ui Docker image (single build, works both standalone and
# as a Module Federation remote β€” see IS_MF_MODE in src/constants.js) and
# publishes it to JFrog Artifactory. Job flow adapted from nuclio-ui / oris-ui
# / airun-ui (golden-frog code-scan omitted β€” not supported in this repo):
# quality-gate β†’ lint + format + audit
# publish-mlrun-image β†’ docker build (--build-arg IS_MF=true) and push
# publish-mlrun-image β†’ docker build and push
#
# Requires repository/org secrets:
# - DOCKER_IMAGE_REGISTRY Artifactory Docker registry host
Expand Down Expand Up @@ -107,7 +108,6 @@ jobs:
- name: Build Image
run: |
docker build --no-cache --pull \
--build-arg IS_MF=true \
-t ${{ env.FULL_IMAGE_NAME }} \
.

Expand Down
40 changes: 22 additions & 18 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,6 @@ RUN npm install

COPY . .

# build arg
ARG IS_MF=false

RUN echo ">>> IS_MF ARG = $IS_MF" && \
sed -i "/^VITE_FEDERATION=/d" .env.production && \
echo "VITE_FEDERATION=$IS_MF" >> .env.production && \
sed -i "s|^VITE_PUBLIC_URL=/mlrun|VITE_PUBLIC_URL=|" .env.production && \
echo ">>> Final .env.production:" && grep '^VITE_' .env.production

RUN npm run build

ARG COMMIT_HASH
Expand All @@ -41,9 +32,11 @@ RUN echo "${COMMIT_HASH}" > ./build/COMMIT_HASH && \
# production stage
FROM gcr.io/iguazio/nginx-unprivileged:1.31.2-alpine3.23 AS production-stage

ARG GIT_COMMIT_SHA
LABEL org.opencontainers.image.revision=$GIT_COMMIT_SHA

ARG UID=101
ARG GID=101
ARG IS_MF=false

USER root
# curl/libcurl are unused at runtime and the patched build (8.21.0-r0) is not yet
Expand All @@ -55,18 +48,13 @@ RUN apk update --no-cache && apk upgrade --no-cache \
USER $UID

COPY --from=build-stage /app/build /usr/share/nginx/html
COPY --from=build-stage /app/.env.production /usr/share/nginx/html/
COPY config.json.tmpl /usr/share/nginx/html/

COPY nginx/nginx.conf.tmpl /etc/nginx/conf.d/
COPY nginx/nginx.conf.tmpl nginx/nginx-mf.conf.tmpl /etc/nginx/conf.d/
COPY nginx/run_nginx /etc/nginx/

USER root
RUN if [ "$IS_MF" \
= "true" ]; then \
INDEX=/usr/share/nginx/html/index.html; \
[ -f "$INDEX" ] && sed -i 's|<base href="/mlrun"|<base href="/projects"|g' "$INDEX"; \
fi && \
chown -R $UID:0 /usr/share/nginx/html && \
RUN chown -R $UID:0 /usr/share/nginx/html && \
chmod -R g+w /usr/share/nginx/html && \
chmod 755 /etc/nginx/run_nginx

Expand All @@ -77,10 +65,26 @@ USER $UID
FROM scratch AS flatten-stage
COPY --from=production-stage / /

ARG GIT_COMMIT_SHA
LABEL org.opencontainers.image.revision=$GIT_COMMIT_SHA

ENV PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ARG UID=101
USER $UID

EXPOSE 8090

# Runtime configuration, common to igz3, igz4 (Module Federation), and CE.
# Substituted into nginx.conf and config.json at container start (see
# nginx/run_nginx); MLRUN_IGZ_UI_ALLOWED_ORIGIN selects igz3/CE vs igz4.
ENV MLRUN_API_PROXY_URL="${MLRUN_API_PROXY_URL:-http://localhost:8090}" \
MLRUN_BETA_MODE="${MLRUN_BETA_MODE:-enabled}" \
MLRUN_FUNCTION_CATALOG_URL="${MLRUN_FUNCTION_CATALOG_URL:-https://raw.githubusercontent.com}" \
MLRUN_FUNCTION_CATALOG_PATH="${MLRUN_FUNCTION_CATALOG_PATH:-/mlrun/functions/master}" \
MLRUN_IGZ_UI_ALLOWED_ORIGIN="${MLRUN_IGZ_UI_ALLOWED_ORIGIN:-}" \
MLRUN_NUCLIO_API_URL="${MLRUN_NUCLIO_API_URL:-http://localhost:8070}" \
MLRUN_NUCLIO_MODE="${MLRUN_NUCLIO_MODE:-disabled}" \
MLRUN_NUCLIO_UI_URL="${MLRUN_NUCLIO_UI_URL:-http://localhost:8070}" \
MLRUN_V3IO_ACCESS_KEY="${MLRUN_V3IO_ACCESS_KEY:-\"\"}"

CMD ["/etc/nginx/run_nginx"]
11 changes: 1 addition & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,6 @@ Examples:
| `npm run docker` | `mlrun/mlrun-ui:latest` |
| `MLRUN_DOCKER_REGISTRY=quay.io/ MLRUN_DOCKER_REPO=iguazio MLRUN_DOCKER_TAG=0.4.9 npm run docker` | `quay.io/iguazio/mlrun-ui:0.4.9` |

### Docker build argument

The Docker build supports an optional argument:

`--build-arg IS_MF=${npm_config_IS_MF:-false}`

By default, `IS_MF` is `false`, and the image is built as a standard **mlrun-ui** build.<br />
When set to `true`, the image is built in **Module Federation** mode.

### `docker run` environment variables

The Docker container runs a Nginx server, which listens on exposed port number 8090, serves the web-app, and proxies to the backend API.
Expand All @@ -44,7 +35,7 @@ You can pass the following environment variables to the `docker run` command to
| `MLRUN_NUCLIO_UI_URL` | Sets the base URL of the Nuclio UI<br />Default: `http://localhost:8070`<br />Example: `http://17.220.101.245:30070` |
| `MLRUN_FUNCTION_CATALOG_URL` | Sets the base URL of the function-template catalog <br />Default: `https://raw.githubusercontent.com` |
| `MLRUN_FUNCTION_CATALOG_PATH` | Sets the base URI of the function-template catalog <br />Default: `/mlrun/functions/master` |
| `MLRUN_IGZ_UI_ALLOWED_ORIGIN` | Allowed origin for Module Federation and CORS<br />Example: `https://igz-ui.pini.vmdev90ig4.lab.iguazeng.com` |
| `MLRUN_IGZ_UI_ALLOWED_ORIGIN` | Allowed origin for Module Federation and CORS<br />Example: `https://someorigin.com` |

Example:

Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<base href="/mlrun"/>
<base href="/mlrun/"/>
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MLRun</title>
Expand Down
35 changes: 35 additions & 0 deletions nginx/nginx-mf.conf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
server {
listen 8090;
server_name localhost;

root /usr/share/nginx/html;

gzip on;

# Safe for Module Federation: the igz4-ui host loads this app's
# remoteEntry.js via a plain <script> tag, which is unaffected by
# X-Frame-Options / frame-ancestors (those only govern framing this page
# itself in an <iframe>, not script loading).
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none';" always;

location /mlrun {
rewrite ^/mlrun(/|$)(.*) /$2 last;
}

location / {
# Landing page shown when hitting this container directly, without
# going through the igz4-ui host.
index landing.html;
try_files $uri $uri/ /landing.html;

if ($request_filename ~* .*\.(?:js|json)$ ) {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
62 changes: 40 additions & 22 deletions nginx/nginx.conf.tmpl
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
map $http_x_v3io_session_key $v3io_session_key {
default $http_x_v3io_session_key;
"" ${MLRUN_V3IO_ACCESS_KEY};
}

server {
listen 8090;
server_name localhost;

root /usr/share/nginx/html;
listen 8090;

# Basic Gzip as per standard defaults
gzip on;
include resolvers.conf;

# Clickjacking protection
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none';" always;
# https://raw.githubusercontent.com
set $function_catalog ${MLRUN_FUNCTION_CATALOG_URL};
location /function-catalog {
rewrite /function-catalog/(.*) ${MLRUN_FUNCTION_CATALOG_PATH}/$1 break;
proxy_pass $function_catalog;
}

location / {
# Show landing page when accessing the remote directly
index landing.html;
try_files $uri $uri/ /landing.html;
location /mlrun {
rewrite ^/mlrun(/|$)(.*) /$2 last;
}

# Ensure remote assets are always fresh
if ($request_filename ~* .*\.(?:js|json)$ ) {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
set $backend ${MLRUN_API_PROXY_URL};
location /api {
proxy_set_header x-v3io-session-key $v3io_session_key;
proxy_pass $backend;
}

set $nuclio_backend ${MLRUN_NUCLIO_API_URL};
location /nuclio {
rewrite ^/nuclio(/|$)(.*) /$2 break;
proxy_pass $nuclio_backend;
}

error_page 500 502 503 504 /50x.html;

location = /50x.html {
root /usr/share/nginx/html;
}

}
28 changes: 25 additions & 3 deletions nginx/run_nginx
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
#!/bin/sh
set -e

# Simply copy the template as the final config
cp /etc/nginx/conf.d/nginx.conf.tmpl /etc/nginx/conf.d/nginx.conf
echo resolver $(awk 'BEGIN{ORS=" "} $1=="nameserver" {if ($2 ~ /:/) print "["$2"]"; else print $2}' /etc/resolv.conf) ";" > /etc/nginx/resolvers.conf

# Start Nginx in foreground
if [ -n "${MLRUN_IGZ_UI_ALLOWED_ORIGIN}" ]; then
NGINX_CONF_SRC=/etc/nginx/conf.d/nginx-mf.conf.tmpl
ENVSUBST_VARS='${MLRUN_IGZ_UI_ALLOWED_ORIGIN}'
else
NGINX_CONF_SRC=/etc/nginx/conf.d/nginx.conf.tmpl
ENVSUBST_VARS='${MLRUN_API_PROXY_URL} \
${MLRUN_FUNCTION_CATALOG_URL} \
${MLRUN_FUNCTION_CATALOG_PATH} \
${MLRUN_NUCLIO_API_URL} \
${MLRUN_V3IO_ACCESS_KEY}'

envsubst '${MLRUN_BETA_MODE} \
${MLRUN_NUCLIO_MODE} \
${MLRUN_NUCLIO_UI_URL}' \
< /usr/share/nginx/html/config.json.tmpl \
> /usr/share/nginx/html/config.json
fi

envsubst "$ENVSUBST_VARS" \
< "$NGINX_CONF_SRC" \
> /etc/nginx/conf.d/nginx.conf

nginx -t
exec nginx -g 'daemon off;'
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"name": "mlrun.ui",
"version": "1.10.0",
"private": true,
"homepage": "/mlrun",
"dependencies": {
"@dagrejs/dagre": "^1.1.5",
"@module-federation/runtime": "0.23.0",
Expand Down Expand Up @@ -87,7 +86,7 @@
"preview": "vite preview",
"preinstall": "npx force-resolutions",
"test:coverage": "npm run test -- --coverage --watchAll=false",
"docker": "docker buildx build --platform linux/amd64 -t ${MLRUN_DOCKER_REGISTRY}${MLRUN_DOCKER_REPO:-mlrun}/mlrun-ui:${MLRUN_DOCKER_TAG:-latest} --build-arg COMMIT_HASH=\"`git rev-parse --short HEAD`\" --build-arg DATE=\"`date -u`\" --build-arg IS_MF=${npm_config_IS_MF:-false} -f Dockerfile .",
"docker": "docker buildx build --platform linux/amd64 -t ${MLRUN_DOCKER_REGISTRY}${MLRUN_DOCKER_REPO:-mlrun}/mlrun-ui:${MLRUN_DOCKER_TAG:-latest} --build-arg COMMIT_HASH=\"`git rev-parse --short HEAD`\" --build-arg GIT_COMMIT_SHA=\"`git rev-parse HEAD`\" --build-arg DATE=\"`date -u`\" -f Dockerfile .",
"generate-rn": "./generate-release-notes.js ${MLRUN_OLD_VERSION} ${MLRUN_VERSION} ${MLRUN_RELEASE_BRANCH} ${MLRUN_RELEASE_TYPE}",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook",
Expand Down
5 changes: 3 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ import {
JOBS_MONITORING_WORKFLOWS_TAB,
JOBS_MONITORING_SCHEDULED_TAB,
INACTIVE_JOBS_TAB,
IS_MF_MODE
IS_MF_MODE,
PUBLIC_URL
} from './constants'

import 'reactflow/dist/style.css'
Expand Down Expand Up @@ -465,7 +466,7 @@ const App = () => {
</Route>
</>
),
{ basename: import.meta.env.VITE_PUBLIC_URL }
{ basename: PUBLIC_URL }
)

return (
Expand Down
3 changes: 2 additions & 1 deletion src/api/projects-iguazio-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ under the Apache 2.0 license is conditioned upon your compliance with
such restriction.
*/
import { iguazioHttpClient } from '../httpClient'
import { IS_MF_MODE } from '../constants'

const igz3Api = {
editProject: (projectId, data) => iguazioHttpClient.put(`/projects/${projectId}`, data),
Expand Down Expand Up @@ -66,4 +67,4 @@ const igz4Api = {
iguazioHttpClient.get('/v1/authentication/self', { params: { format: 'full' } })
}

export default import.meta.env.VITE_FEDERATION === 'true' ? igz4Api : igz3Api
export default IS_MF_MODE ? igz4Api : igz3Api
15 changes: 14 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,20 @@ such restriction.
*/
/*=========== GENERAL =============*/

export const IS_MF_MODE = import.meta.env.VITE_FEDERATION === 'true'
// Detected at runtime rather than baked in at build time: the igz4-ui host sets
// window.mlrunConfig synchronously before it ever imports this app's exposed
// Module Federation modules (./loadRemoteConfig, ./app), so by the time this
// module graph is first evaluated under MF, window.mlrunConfig is already
// populated. In the standalone entry (index.jsx), this module also evaluates
// before the async loadRemoteConfig() fetch resolves, so it correctly reads as
// "not MF" here regardless of that later, unrelated config fetch.
export const IS_MF_MODE =
typeof window !== 'undefined' &&
window.mlrunConfig !== undefined &&
window.mlrunConfig !== null &&
Object.keys(window.mlrunConfig).length > 0

export const PUBLIC_URL = IS_MF_MODE ? '' : import.meta.env.VITE_PUBLIC_URL

export const SET_LOADING = 'SET_LOADING'

Expand Down
Loading
Loading