diff --git a/Dockerfile b/Dockerfile index babdcd6..d8890ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,14 +23,21 @@ RUN npm run build # Production stage - serve with nginx FROM nginx:alpine +# Install envsubst (part of gettext package) +RUN apk add --no-cache gettext + # Copy built assets from builder stage COPY --from=builder /app/build /usr/share/nginx/html -# Copy nginx configuration (optional - uses default if not provided) -# COPY nginx.conf /etc/nginx/conf.d/default.conf +# Copy nginx configuration template +COPY nginx.conf.template /etc/nginx/conf.d/default.conf.template + +# Copy and set up entrypoint script +COPY docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh # Expose port 80 EXPOSE 80 -# Start nginx -CMD ["nginx", "-g", "daemon off;"] +# Use custom entrypoint +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..8b3c28e --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -e + +# Substitute environment variables in nginx config template +API_SERVER_URL=${API_SERVER_URL:-http://forkspacer-api-server:8080} + +echo "Configuring nginx with API_SERVER_URL: $API_SERVER_URL" + +# Replace the placeholder with actual API server URL +envsubst '${API_SERVER_URL}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf + +# Verify configuration +nginx -t + +# Start nginx +exec nginx -g 'daemon off;' diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index d60fa9e..6b1ac7d 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -33,10 +33,10 @@ spec: containerPort: {{ .Values.service.targetPort }} protocol: TCP env: - - name: REACT_APP_API_BASE_URL - value: {{ include "operator-ui.apiUrl" . | quote }} + - name: API_SERVER_URL + value: {{ .Values.env.API_SERVER_URL | default (include "operator-ui.apiUrl" .) | quote }} {{- range $key, $value := .Values.env }} - {{- if ne $key "REACT_APP_API_BASE_URL" }} + {{- if and (ne $key "API_SERVER_URL") (ne $key "REACT_APP_API_BASE_URL") }} - name: {{ $key }} value: {{ $value | quote }} {{- end }} diff --git a/helm/values.yaml b/helm/values.yaml index e8d861a..9d51d8c 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -40,7 +40,11 @@ resources: memory: 128Mi env: - REACT_APP_API_BASE_URL: "http://forkspacer-api-server:8080" + # API server URL for nginx server-side proxying (internal cluster service) + API_SERVER_URL: "http://forkspacer-api-server:8080" + # Leave REACT_APP_API_BASE_URL empty to use relative paths (/api/v1) + # which nginx will proxy to API_SERVER_URL + REACT_APP_API_BASE_URL: "" livenessProbe: httpGet: diff --git a/nginx.conf.template b/nginx.conf.template new file mode 100644 index 0000000..5a99142 --- /dev/null +++ b/nginx.conf.template @@ -0,0 +1,54 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Enable gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + + # Proxy API requests to backend server + location /api/v1/ { + proxy_pass ${API_SERVER_URL}/api/v1/; + proxy_http_version 1.1; + + # Forward headers + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + + # Buffering + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + } + + # Serve static files + location / { + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +} diff --git a/src/components/WorkspaceCard.tsx b/src/components/WorkspaceCard.tsx index bb61beb..ff06048 100644 --- a/src/components/WorkspaceCard.tsx +++ b/src/components/WorkspaceCard.tsx @@ -117,7 +117,7 @@ export const WorkspaceCard: React.FC = ({
{module.name}
-
SERVICE
+
{module.type}
diff --git a/src/components/WorkspaceList.tsx b/src/components/WorkspaceList.tsx index 59efc01..8a60981 100644 --- a/src/components/WorkspaceList.tsx +++ b/src/components/WorkspaceList.tsx @@ -98,22 +98,19 @@ export const WorkspaceList: React.FC = () => { // Load all modules once const allModules = await apiService.listModules(); - // Group workspaces by namespace and only show modules under the first workspace in each namespace + // Group modules by workspace reference (not namespace) const modulesMap = new Map(); - const namespacesProcessed = new Set(); workspacesData.forEach((workspace) => { const workspaceKey = `${workspace.namespace}-${workspace.name}`; - if (!namespacesProcessed.has(workspace.namespace)) { - // This is the first workspace in this namespace, assign all modules from this namespace - const namespaceModules = allModules.filter(module => module.namespace === workspace.namespace); - modulesMap.set(workspaceKey, namespaceModules); - namespacesProcessed.add(workspace.namespace); - } else { - // Not the first workspace in this namespace, no modules - modulesMap.set(workspaceKey, []); - } + // Filter modules that reference this specific workspace + const workspaceModules = allModules.filter(module => + module.workspace?.name === workspace.name && + module.workspace?.namespace === workspace.namespace + ); + + modulesMap.set(workspaceKey, workspaceModules); }); setModulesByWorkspace(modulesMap); diff --git a/src/types/module.ts b/src/types/module.ts index b43d8b3..52301ba 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -4,6 +4,7 @@ export interface Module { phase: ModulePhase; message?: string; hibernated: boolean; + type: string; workspace?: ModuleWorkspaceReference; }