diff --git a/Makefile b/Makefile index 28a376e..1753f24 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,7 @@ dev: prod: $(MAKE) check-env - docker compose up -d - $(MAKE) frontend_prod + docker compose -f docker-compose.prod.yml up -d --build check-env: @echo "🔎 Checking .env files..." @@ -38,12 +37,11 @@ install: fi poetry config virtualenvs.in-project true --local poetry env use python3.12 - docker compose up -d + docker compose up -d postgres redis neo4j poetry install cd $(PROJECT_ROOT)/flowsint-core && poetry install cd $(PROJECT_ROOT)/flowsint-transforms && poetry install cd $(PROJECT_ROOT)/flowsint-api && poetry install && poetry run alembic upgrade head - cd $(PROJECT_ROOT)/flowsint-app && yarn install @echo "✅ All modules installed successfully!" infra: @@ -53,7 +51,9 @@ api: cd $(PROJECT_ROOT)/flowsint-api && poetry run uvicorn app.main:app --host 0.0.0.0 --port 5001 --reload frontend: - cd $(PROJECT_ROOT)/flowsint-app && npm run dev + @echo "🚀 Starting frontend in Docker and opening browser..." + @docker compose up -d flowsint-app + @bash -c 'until curl -s http://localhost:5173 > /dev/null 2>&1; do sleep 1; done; open http://localhost:5173 2>/dev/null || xdg-open http://localhost:5173 2>/dev/null || echo "✅ Frontend ready at http://localhost:5173"' frontend_prod: cd $(PROJECT_ROOT)/flowsint-app && npm run build @@ -61,8 +61,14 @@ frontend_prod: celery: cd $(PROJECT_ROOT)/flowsint-core && poetry run celery -A flowsint_core.core.celery worker --loglevel=info --pool=solo -run: infra - $(MAKE) -j3 api frontend celery +run: + @echo "🚀 Starting all services..." + docker compose up -d + @echo "⏳ Waiting for frontend to be ready..." + @bash -c 'until curl -s http://localhost:5173 > /dev/null 2>&1; do sleep 1; done' + @echo "🌐 Opening browser..." + @open http://localhost:5173 2>/dev/null || xdg-open http://localhost:5173 2>/dev/null || echo "✅ All services ready! Frontend at http://localhost:5173" + $(MAKE) -j2 api celery stop: @echo "🛑 Stopping all services..." diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e9b5199..c1c17e7 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -67,6 +67,20 @@ services: networks: - flowsint_network + # Flowsint frontend app (production) + flowsint-app: + build: + context: ./flowsint-app + dockerfile: Dockerfile + container_name: flowsint-app + ports: + - "5173:80" + depends_on: + - flowsint_api + networks: + - flowsint_network + restart: unless-stopped + networks: flowsint_network: name: flowsint_network diff --git a/docker-compose.yml b/docker-compose.yml index 0a8b853..31ff0b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,24 @@ services: networks: - flowsint_network + # Flowsint frontend app (development) + flowsint-app: + build: + context: ./flowsint-app + dockerfile: Dockerfile.dev + container_name: flowsint-app + ports: + - "5173:5173" + volumes: + - ./flowsint-app:/app + - /app/node_modules + environment: + - VITE_API_URL=http://localhost:5001 + networks: + - flowsint_network + stdin_open: true + tty: true + networks: flowsint_network: name: flowsint_network diff --git a/flowsint-app/Dockerfile b/flowsint-app/Dockerfile index e070be4..69e48da 100644 --- a/flowsint-app/Dockerfile +++ b/flowsint-app/Dockerfile @@ -19,8 +19,8 @@ FROM nginx:alpine # Copy built files to nginx COPY --from=builder /app/dist /usr/share/nginx/html -# Copy nginx configuration if needed -# COPY nginx.conf /etc/nginx/nginx.conf +# Copy nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 diff --git a/flowsint-app/Dockerfile.dev b/flowsint-app/Dockerfile.dev new file mode 100644 index 0000000..36b80df --- /dev/null +++ b/flowsint-app/Dockerfile.dev @@ -0,0 +1,16 @@ +FROM node:20 + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +RUN yarn install + +# Copy source code +COPY . . + +# Expose Vite dev server port +EXPOSE 5173 + +# Run development server with host flag to make it accessible from outside container +CMD ["yarn", "dev", "--host", "0.0.0.0"] diff --git a/flowsint-app/nginx.conf b/flowsint-app/nginx.conf new file mode 100644 index 0000000..ac421ca --- /dev/null +++ b/flowsint-app/nginx.conf @@ -0,0 +1,38 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 10240; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json; + gzip_disable "MSIE [1-6]\."; + + # Proxy API requests to backend + location /api { + proxy_pass http://flowsint_api:5001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + 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; + } + + # Handle SPA routing - serve index.html for all routes + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} diff --git a/flowsint-app/src/stores/graph-settings-store.ts b/flowsint-app/src/stores/graph-settings-store.ts index 06e8ff6..02cfece 100644 --- a/flowsint-app/src/stores/graph-settings-store.ts +++ b/flowsint-app/src/stores/graph-settings-store.ts @@ -200,12 +200,13 @@ export const useGraphSettingsStore = create()( // Core methods updateSetting: (category, key, value) => set((state) => { + const categorySettings = state.settings[category as keyof typeof state.settings] as any const newSettings = { ...state.settings, [category]: { - ...state.settings[category], + ...categorySettings, [key]: { - ...state.settings[category][key], + ...(categorySettings?.[key] || {}), value: value } } @@ -217,7 +218,7 @@ export const useGraphSettingsStore = create()( newForceSettings = { ...state.forceSettings, [key]: { - ...state.forceSettings[key], + ...(state.forceSettings[key] || {}), value: value } } @@ -249,6 +250,7 @@ export const useGraphSettingsStore = create()( getCategorySettings: (category: string) => { const categorySettings: Record = {} + // @ts-ignore const settings = get().settings[category] if (settings) { Object.entries(settings as Record).forEach(([key, setting]) => { @@ -266,8 +268,8 @@ export const useGraphSettingsStore = create()( if (!preset) return set((state) => { - const newSettings = { ...state.settings } - const newForceSettings = { ...state.forceSettings } + const newSettings = { ...state.settings } as any + const newForceSettings = { ...state.forceSettings } as any Object.entries(preset).forEach(([key, value]) => { if (newSettings.graph[key]) { @@ -297,14 +299,25 @@ export const useGraphSettingsStore = create()( setKeyboardShortcutsOpen: (open) => set({ keyboardShortcutsOpen: open }), // Helper methods - getSettingValue: (category: string, key: string) => get().settings[category]?.[key]?.value, - getSettingType: (category: string, key: string) => get().settings[category]?.[key]?.type, - getSettingOptions: (category: string, key: string) => - get().settings[category]?.[key]?.options, - getSettingDescription: (category: string, key: string) => - get().settings[category]?.[key]?.description, + getSettingValue: (category: string, key: string) => { + const categorySettings = get().settings[category as keyof typeof DEFAULT_SETTINGS] as any + return categorySettings?.[key]?.value + }, + getSettingType: (category: string, key: string) => { + const categorySettings = get().settings[category as keyof typeof DEFAULT_SETTINGS] as any + return categorySettings?.[key]?.type + }, + getSettingOptions: (category: string, key: string) => { + const categorySettings = get().settings[category as keyof typeof DEFAULT_SETTINGS] as any + return categorySettings?.[key]?.options + }, + getSettingDescription: (category: string, key: string) => { + const categorySettings = get().settings[category as keyof typeof DEFAULT_SETTINGS] as any + return categorySettings?.[key]?.description + }, getSettingConstraints: (category: string, key: string) => { - const setting = get().settings[category]?.[key] + const categorySettings = get().settings[category as keyof typeof DEFAULT_SETTINGS] as any + const setting = categorySettings?.[key] if (setting && 'min' in setting) { return { min: setting.min,