diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..1625b0d --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,33 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-error.log + +# Build output (rebuilt in container) +dist/ +out-tsc/ + +# Angular cache +.angular/cache/ + +# IDE +.idea/ +.vscode/ +*.sublime-workspace + +# OS files +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Docker +Dockerfile +.dockerignore + +# Misc +coverage/ +tmp/ +*.log \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..4ef33cf --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,38 @@ +# ============================================================ +# Control Center Frontend — Multi-stage Docker Build +# Angular 21 + nginx for static serving + API proxy +# ============================================================ + +# --- Build Stage --- +FROM node:22-slim AS builder + +WORKDIR /app + +# Install dependencies first (layer caching) +COPY package.json package-lock.json ./ +RUN npm ci + +# Copy source and build production bundle +COPY . . +RUN npm run build + +# --- Runtime Stage --- +FROM nginx:1.27-alpine AS runtime + +# Remove default nginx config +RUN rm /etc/nginx/conf.d/default.conf + +# Copy custom nginx config +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Copy built Angular app from builder stage +COPY --from=builder /app/dist/frontend/browser /usr/share/nginx/html + +# Expose HTTP port +EXPOSE 80 + +# Health check — confirm nginx is serving +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -qO- http://localhost/ || exit 1 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/angular.json b/frontend/angular.json index 8c5cf7c..7285303 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -56,7 +56,8 @@ ], "stylePreprocessorOptions": { "includePaths": [ - "src" +"src", + "src/styles" ] } }, diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..e36d2d7 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,54 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; + + # Cache static assets (Angular uses content hashes) + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Cache hashed JS/CSS bundles + location ~* \.(js|css)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Proxy API requests to backend + location /api/ { + proxy_pass http://backend:8080/api/; + proxy_http_version 1.1; + 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; + } + + # Proxy SignalR WebSocket connections to backend + location /hubs/ { + proxy_pass http://backend:8080/hubs/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + 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; + proxy_read_timeout 86400; + } + + # Angular SPA — all other routes fall back to index.html + location / { + try_files $uri $uri/ /index.html; + } +} \ No newline at end of file diff --git a/frontend/src/app/command-hub/components/agent-card/agent-card.component.html b/frontend/src/app/command-hub/components/agent-card/agent-card.component.html index 6b49bfc..42be39a 100644 --- a/frontend/src/app/command-hub/components/agent-card/agent-card.component.html +++ b/frontend/src/app/command-hub/components/agent-card/agent-card.component.html @@ -2,11 +2,13 @@ +
-
+

- {{ status === 'error' ? errorMessage || task : task }} + {{ isError() ? errorMessage || task : task }}

{{ progress }}%
+ +
+ + {{ taskElapsed }} +
+