Consolidate tools into a secure Docker architecture with landing page
- Implement a central reverse proxy (Nginx) with Basic Auth on port 8090. - Create a unified landing page (dashboard) to access B2B Assistant and Market Intelligence. - Update frontends with relative API paths and base paths for subdirectory routing (/b2b/, /market/). - Optimize Docker builds with .dockerignore and a Python-based image for market-backend. - Enable code sideloading for Python logic via Docker volumes. - Fix TypeScript errors in general-market-intelligence regarding ImportMeta.
This commit is contained in:
44
.dockerignore
Normal file
44
.dockerignore
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Node
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
env
|
||||||
|
venv
|
||||||
|
.venv
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile*
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Project specific
|
||||||
|
Log
|
||||||
|
Log_from_docker
|
||||||
|
tmp
|
||||||
|
output
|
||||||
|
*.md
|
||||||
|
# Allow these txt files
|
||||||
|
!gemini_api_key.txt
|
||||||
|
!serpapikey.txt
|
||||||
|
!market-intel.requirements.txt
|
||||||
|
!b2b-marketing-assistant/requirements.txt
|
||||||
|
!requirements.txt
|
||||||
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -61,3 +61,7 @@ auth_output.txt
|
|||||||
auth_url.txt
|
auth_url.txt
|
||||||
\ngemini_api_key.txt
|
\ngemini_api_key.txt
|
||||||
.venv/
|
.venv/
|
||||||
|
|
||||||
|
# Node.js specific
|
||||||
|
!package.json
|
||||||
|
!package-lock.json
|
||||||
|
|||||||
55
Dockerfile.b2b
Normal file
55
Dockerfile.b2b
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Stage 1: Build the React frontend
|
||||||
|
FROM node:20-slim AS frontend-builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package.json and install all dependencies
|
||||||
|
# Paths are relative to the build context (project root)
|
||||||
|
COPY b2b-marketing-assistant/package.json ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy the rest of the frontend application code
|
||||||
|
COPY b2b-marketing-assistant/ .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
|
# Stage 2: Final application image
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies (minimal)
|
||||||
|
# We use NodeSource to get a clean, modern Node.js install without bloat
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends curl ca-certificates && \
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
||||||
|
apt-get install -y --no-install-recommends nodejs && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
COPY b2b-marketing-assistant/requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy the Node.js server and its production dependencies manifest
|
||||||
|
COPY b2b-marketing-assistant/server.cjs .
|
||||||
|
COPY b2b-marketing-assistant/package.json .
|
||||||
|
|
||||||
|
# Install only production dependencies for the Node.js server
|
||||||
|
RUN npm install --omit=dev
|
||||||
|
|
||||||
|
# Copy the built React app from the builder stage
|
||||||
|
COPY --from=frontend-builder /app/dist ./dist
|
||||||
|
|
||||||
|
# Copy the main Python orchestrator script from the project root
|
||||||
|
COPY b2b_marketing_orchestrator.py .
|
||||||
|
# Copy Gemini API Key file if it exists in root
|
||||||
|
COPY gemini_api_key.txt .
|
||||||
|
|
||||||
|
# Expose the port the Node.js server will run on
|
||||||
|
EXPOSE 3002
|
||||||
|
|
||||||
|
# The command to run the application
|
||||||
|
CMD ["node", "server.cjs"]
|
||||||
35
Dockerfile.market
Normal file
35
Dockerfile.market
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Basis: Python (da die Installation von Python-Deps oft am längsten dauert und Kompilierung braucht)
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 1. System-Updates & Node.js Installation (v20)
|
||||||
|
# curl ist nötig, um Node zu holen.
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||||
|
&& apt-get install -y nodejs \
|
||||||
|
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 2. Python Dependencies (Cached Layer)
|
||||||
|
COPY market-intel.requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r market-intel.requirements.txt
|
||||||
|
|
||||||
|
# 3. Node Dependencies (Cached Layer)
|
||||||
|
COPY general-market-intelligence/package.json ./general-market-intelligence/
|
||||||
|
# Wir installieren nur Production-Deps für den Server, falls nötig.
|
||||||
|
# ACHTUNG: Der Backend-Container führt hier server.cjs aus.
|
||||||
|
RUN cd general-market-intelligence && npm install --omit=dev
|
||||||
|
|
||||||
|
# 4. App Code
|
||||||
|
COPY general-market-intelligence/server.cjs ./general-market-intelligence/
|
||||||
|
COPY market_intel_orchestrator.py .
|
||||||
|
COPY config.py .
|
||||||
|
COPY gemini_api_key.txt .
|
||||||
|
# (Falls helpers.py existiert, wird sie durch docker-compose volume gemountet, aber wir kopieren sie für Standalone-Builds)
|
||||||
|
COPY helpers.py .
|
||||||
|
|
||||||
|
EXPOSE 3001
|
||||||
|
|
||||||
|
CMD ["node", "general-market-intelligence/server.cjs"]
|
||||||
9
Dockerfile.proxy
Normal file
9
Dockerfile.proxy
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Install apache2-utils to generate .htpasswd natively
|
||||||
|
RUN apk add --no-cache apache2-utils
|
||||||
|
|
||||||
|
# Create the user 'admin' with password 'gemini' using bcrypt (most secure & compatible)
|
||||||
|
RUN htpasswd -bc /etc/nginx/.htpasswd admin gemini
|
||||||
|
|
||||||
|
COPY nginx-proxy.conf /etc/nginx/nginx.conf
|
||||||
@@ -8,7 +8,7 @@ import { translations } from './constants';
|
|||||||
import type { AnalysisStep, AnalysisData, InputData } from './types';
|
import type { AnalysisStep, AnalysisData, InputData } from './types';
|
||||||
import { generateMarkdown, downloadFile } from './services/export';
|
import { generateMarkdown, downloadFile } from './services/export';
|
||||||
|
|
||||||
const API_BASE_URL = '/api';
|
const API_BASE_URL = 'api';
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const [inputData, setInputData] = useState<InputData>({
|
const [inputData, setInputData] = useState<InputData>({
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import react from '@vitejs/plugin-react';
|
|||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, '.', '');
|
const env = loadEnv(mode, '.', '');
|
||||||
return {
|
return {
|
||||||
|
base: '/b2b/',
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
|
|||||||
3
dashboard/Dockerfile.dashboard
Normal file
3
dashboard/Dockerfile.dashboard
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
FROM nginx:alpine
|
||||||
|
COPY index.html /usr/share/nginx/html/index.html
|
||||||
|
RUN chmod 644 /usr/share/nginx/html/index.html
|
||||||
151
dashboard/index.html
Normal file
151
dashboard/index.html
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Marketing & Intelligence Dashboard</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-color: #0f172a;
|
||||||
|
--card-bg: #1e293b;
|
||||||
|
--text-primary: #f1f5f9;
|
||||||
|
--text-secondary: #94a3b8;
|
||||||
|
--accent: #3b82f6;
|
||||||
|
--accent-hover: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #020617;
|
||||||
|
padding: 2rem 0;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(to right, #60a5fa, #a78bfa);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 3rem auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
border-radius: 1rem;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: var(--accent);
|
||||||
|
color: white;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background-color: var(--accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 2rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>Marketing & Intelligence Hub</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<!-- B2B Marketing Assistant -->
|
||||||
|
<div class="card">
|
||||||
|
<span class="card-icon">🚀</span>
|
||||||
|
<h2>B2B Marketing Assistant</h2>
|
||||||
|
<p>
|
||||||
|
KI-gestützte Analyse von Unternehmens-Websites zur Erstellung von Personas, Pain-Points und Marketing-Botschaften.
|
||||||
|
</p>
|
||||||
|
<!-- WICHTIG: Relativer Link für Reverse Proxy -->
|
||||||
|
<a href="/b2b/" class="btn">Starten →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- General Market Intelligence -->
|
||||||
|
<div class="card">
|
||||||
|
<span class="card-icon">📊</span>
|
||||||
|
<h2>Market Intelligence</h2>
|
||||||
|
<p>
|
||||||
|
Allgemeine Marktanalyse und Recherche-Tool.
|
||||||
|
Nutzt Web-Scraping und KI für tiefe Einblicke.
|
||||||
|
</p>
|
||||||
|
<!-- WICHTIG: Relativer Link für Reverse Proxy -->
|
||||||
|
<a href="/market/" class="btn">Starten →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
© 2025 Local AI Suite | Secured Access
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,38 +1,73 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
# --- CENTRAL GATEWAY (Reverse Proxy with Auth) ---
|
||||||
|
proxy:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile.proxy
|
||||||
container_name: market-intel-backend
|
container_name: gemini-gateway
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8090:80"
|
||||||
|
depends_on:
|
||||||
|
- dashboard
|
||||||
|
- b2b-app
|
||||||
|
- market-frontend
|
||||||
|
|
||||||
|
# --- DASHBOARD (Landing Page) ---
|
||||||
|
dashboard:
|
||||||
|
build:
|
||||||
|
context: ./dashboard
|
||||||
|
dockerfile: Dockerfile.dashboard
|
||||||
|
container_name: gemini-dashboard
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# --- B2B MARKETING ASSISTANT ---
|
||||||
|
b2b-app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.b2b
|
||||||
|
container_name: b2b-assistant
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
# Persist Logs
|
# Sideloading: Python Logic
|
||||||
- ./Log:/app/Log
|
- ./b2b_marketing_orchestrator.py:/app/b2b_marketing_orchestrator.py
|
||||||
# API Keys & Config (Bind Mounts for easy editing)
|
# Logs
|
||||||
|
- ./Log_from_docker:/app/Log_from_docker
|
||||||
|
# Keys
|
||||||
- ./gemini_api_key.txt:/app/gemini_api_key.txt
|
- ./gemini_api_key.txt:/app/gemini_api_key.txt
|
||||||
- ./serpapikey.txt:/app/serpapikey.txt
|
|
||||||
- ./config.py:/app/config.py
|
|
||||||
# Optional: Mount Python script for hot-reloading logic changes
|
|
||||||
- ./market_intel_orchestrator.py:/app/market_intel_orchestrator.py
|
|
||||||
environment:
|
environment:
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
# Backend port is internal-only, accessed by frontend via Docker network
|
# Port 3002 is internal only
|
||||||
expose:
|
|
||||||
- "3001"
|
|
||||||
|
|
||||||
frontend:
|
# --- MARKET INTELLIGENCE BACKEND ---
|
||||||
|
market-backend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.market
|
||||||
|
container_name: market-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
# Sideloading: Python Logic & Config
|
||||||
|
- ./market_intel_orchestrator.py:/app/market_intel_orchestrator.py
|
||||||
|
- ./config.py:/app/config.py
|
||||||
|
- ./helpers.py:/app/helpers.py
|
||||||
|
# Logs & Keys
|
||||||
|
- ./Log:/app/Log
|
||||||
|
- ./gemini_api_key.txt:/app/gemini_api_key.txt
|
||||||
|
- ./serpapikey.txt:/app/serpapikey.txt
|
||||||
|
environment:
|
||||||
|
- PYTHONUNBUFFERED=1
|
||||||
|
# Port 3001 is internal only
|
||||||
|
|
||||||
|
# --- MARKET INTELLIGENCE FRONTEND ---
|
||||||
|
market-frontend:
|
||||||
build:
|
build:
|
||||||
context: ./general-market-intelligence
|
context: ./general-market-intelligence
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: market-intel-frontend
|
container_name: market-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
|
||||||
# Expose Frontend to Host (Synology)
|
|
||||||
# Access via http://<Synology-IP>:8085
|
|
||||||
- "8085:80"
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- market-backend
|
||||||
|
# Port 80 is internal only
|
||||||
# Optional: Define a specific network if needed, otherwise 'default' is fine.
|
|
||||||
@@ -10,9 +10,9 @@ server {
|
|||||||
|
|
||||||
# 2. Proxy API Requests to Backend Container
|
# 2. Proxy API Requests to Backend Container
|
||||||
location /api/ {
|
location /api/ {
|
||||||
# 'backend' is the service name in docker-compose.yml
|
# 'market-backend' is the service name in docker-compose.yml
|
||||||
# Port 3001 is where the Node.js bridge listens
|
# Port 3001 is where the Node.js bridge listens
|
||||||
proxy_pass http://backend:3001/api/;
|
proxy_pass http://market-backend:3001/api/;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection 'upgrade';
|
proxy_set_header Connection 'upgrade';
|
||||||
|
|||||||
3692
general-market-intelligence/package-lock.json
generated
Normal file
3692
general-market-intelligence/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
@@ -7,42 +6,20 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = 3001; // Node.js Server läuft auf einem anderen Port als React (3000)
|
const PORT = 3001;
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
app.use(cors()); // Ermöglicht Cross-Origin-Requests von der React-App
|
app.use(cors());
|
||||||
app.use(bodyParser.json()); // Parst JSON-Anfragen
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
// API-Endpunkt für generateSearchStrategy
|
// Helper für Python-Aufrufe, um Code-Duplizierung zu vermeiden
|
||||||
app.post('/api/generate-search-strategy', async (req, res) => {
|
const runPython = (args, res, tempFilesToDelete = []) => {
|
||||||
console.log(`[${new Date().toISOString()}] HIT: /api/generate-search-strategy`);
|
// Im Docker (python:3.11-slim Image) nutzen wir das globale python3
|
||||||
const { referenceUrl, contextContent } = req.body;
|
const pythonExecutable = 'python3';
|
||||||
|
|
||||||
if (!referenceUrl || !contextContent) {
|
console.log(`Spawning command: ${pythonExecutable} ${args.join(' ')}`);
|
||||||
console.error('Validation Error: Missing referenceUrl or contextContent.');
|
|
||||||
return res.status(400).json({ error: 'Missing referenceUrl or contextContent' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const tempContextFilePath = path.join(__dirname, 'tmp', `context_${Date.now()}.md`);
|
const pythonProcess = spawn(pythonExecutable, args);
|
||||||
const tmpDir = path.join(__dirname, 'tmp');
|
|
||||||
if (!fs.existsSync(tmpDir)) {
|
|
||||||
fs.mkdirSync(tmpDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(tempContextFilePath, contextContent);
|
|
||||||
console.log(`Successfully wrote context to ${tempContextFilePath}`);
|
|
||||||
|
|
||||||
const pythonExecutable = path.join(__dirname, '..', '.venv', 'bin', 'python3');
|
|
||||||
const pythonScript = path.join(__dirname, '..', 'market_intel_orchestrator.py');
|
|
||||||
const scriptArgs = [pythonScript, '--mode', 'generate_strategy', '--reference_url', referenceUrl, '--context_file', tempContextFilePath];
|
|
||||||
|
|
||||||
console.log(`Spawning command: ${pythonExecutable}`);
|
|
||||||
console.log(`With arguments: ${JSON.stringify(scriptArgs)}`);
|
|
||||||
|
|
||||||
const pythonProcess = spawn(pythonExecutable, scriptArgs, {
|
|
||||||
env: { ...process.env, PYTHONPATH: path.join(__dirname, '..', '.venv', 'lib', 'python3.11', 'site-packages') }
|
|
||||||
});
|
|
||||||
|
|
||||||
let pythonOutput = '';
|
let pythonOutput = '';
|
||||||
let pythonError = '';
|
let pythonError = '';
|
||||||
@@ -57,18 +34,36 @@ app.post('/api/generate-search-strategy', async (req, res) => {
|
|||||||
|
|
||||||
pythonProcess.on('close', (code) => {
|
pythonProcess.on('close', (code) => {
|
||||||
console.log(`Python script finished with exit code: ${code}`);
|
console.log(`Python script finished with exit code: ${code}`);
|
||||||
|
if (pythonOutput.length > 500) {
|
||||||
|
console.log(`--- STDOUT (Truncated) ---`);
|
||||||
|
console.log(pythonOutput.substring(0, 500) + '...');
|
||||||
|
} else {
|
||||||
console.log(`--- STDOUT ---`);
|
console.log(`--- STDOUT ---`);
|
||||||
console.log(pythonOutput);
|
console.log(pythonOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pythonError) {
|
||||||
console.log(`--- STDERR ---`);
|
console.log(`--- STDERR ---`);
|
||||||
console.log(pythonError);
|
console.log(pythonError);
|
||||||
|
}
|
||||||
console.log(`----------------`);
|
console.log(`----------------`);
|
||||||
|
|
||||||
fs.unlinkSync(tempContextFilePath);
|
// Aufräumen
|
||||||
|
tempFilesToDelete.forEach(file => {
|
||||||
|
if (fs.existsSync(file)) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(file);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to delete temp file ${file}:`, e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
console.error(`Python script exited with error.`);
|
console.error(`Python script exited with error.`);
|
||||||
return res.status(500).json({ error: 'Python script failed', details: pythonError });
|
return res.status(500).json({ error: 'Python script failed', details: pythonError });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = JSON.parse(pythonOutput);
|
const result = JSON.parse(pythonOutput);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
@@ -80,11 +75,34 @@ app.post('/api/generate-search-strategy', async (req, res) => {
|
|||||||
|
|
||||||
pythonProcess.on('error', (err) => {
|
pythonProcess.on('error', (err) => {
|
||||||
console.error('FATAL: Failed to start python process itself.', err);
|
console.error('FATAL: Failed to start python process itself.', err);
|
||||||
if (fs.existsSync(tempContextFilePath)) {
|
tempFilesToDelete.forEach(file => {
|
||||||
fs.unlinkSync(tempContextFilePath);
|
if (fs.existsSync(file)) fs.unlinkSync(file);
|
||||||
}
|
});
|
||||||
res.status(500).json({ error: 'Failed to start Python process', details: err.message });
|
res.status(500).json({ error: 'Failed to start Python process', details: err.message });
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// API-Endpunkt für generateSearchStrategy
|
||||||
|
app.post('/api/generate-search-strategy', async (req, res) => {
|
||||||
|
console.log(`[${new Date().toISOString()}] HIT: /api/generate-search-strategy`);
|
||||||
|
const { referenceUrl, contextContent } = req.body;
|
||||||
|
|
||||||
|
if (!referenceUrl || !contextContent) {
|
||||||
|
return res.status(400).json({ error: 'Missing referenceUrl or contextContent' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const tmpDir = path.join(__dirname, 'tmp');
|
||||||
|
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir);
|
||||||
|
const tempContextFilePath = path.join(tmpDir, `context_${Date.now()}.md`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(tempContextFilePath, contextContent);
|
||||||
|
|
||||||
|
const pythonScript = path.join(__dirname, '..', 'market_intel_orchestrator.py');
|
||||||
|
const scriptArgs = [pythonScript, '--mode', 'generate_strategy', '--reference_url', referenceUrl, '--context_file', tempContextFilePath];
|
||||||
|
|
||||||
|
runPython(scriptArgs, res, [tempContextFilePath]);
|
||||||
|
|
||||||
} catch (writeError) {
|
} catch (writeError) {
|
||||||
console.error('Failed to write temporary context file:', writeError);
|
console.error('Failed to write temporary context file:', writeError);
|
||||||
@@ -98,25 +116,21 @@ app.post('/api/identify-competitors', async (req, res) => {
|
|||||||
const { referenceUrl, targetMarket, contextContent, referenceCity, referenceCountry, summaryOfOffer } = req.body;
|
const { referenceUrl, targetMarket, contextContent, referenceCity, referenceCountry, summaryOfOffer } = req.body;
|
||||||
|
|
||||||
if (!referenceUrl || !targetMarket) {
|
if (!referenceUrl || !targetMarket) {
|
||||||
console.error('Validation Error: Missing referenceUrl or targetMarket for identify_competitors.');
|
|
||||||
return res.status(400).json({ error: 'Missing referenceUrl or targetMarket' });
|
return res.status(400).json({ error: 'Missing referenceUrl or targetMarket' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const tempContextFilePath = path.join(__dirname, 'tmp', `context_comp_${Date.now()}.md`);
|
|
||||||
const tmpDir = path.join(__dirname, 'tmp');
|
const tmpDir = path.join(__dirname, 'tmp');
|
||||||
if (!fs.existsSync(tmpDir)) {
|
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir);
|
||||||
fs.mkdirSync(tmpDir);
|
const tempContextFilePath = path.join(tmpDir, `context_comp_${Date.now()}.md`);
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const cleanupFiles = [];
|
||||||
if (contextContent) {
|
if (contextContent) {
|
||||||
fs.writeFileSync(tempContextFilePath, contextContent);
|
fs.writeFileSync(tempContextFilePath, contextContent);
|
||||||
console.log(`Successfully wrote context to ${tempContextFilePath} for competitors.`);
|
cleanupFiles.push(tempContextFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pythonExecutable = path.join(__dirname, '..', '.venv', 'bin', 'python3');
|
|
||||||
const pythonScript = path.join(__dirname, '..', 'market_intel_orchestrator.py');
|
const pythonScript = path.join(__dirname, '..', 'market_intel_orchestrator.py');
|
||||||
|
|
||||||
const scriptArgs = [
|
const scriptArgs = [
|
||||||
pythonScript,
|
pythonScript,
|
||||||
'--mode', 'identify_competitors',
|
'--mode', 'identify_competitors',
|
||||||
@@ -124,72 +138,14 @@ app.post('/api/identify-competitors', async (req, res) => {
|
|||||||
'--target_market', targetMarket
|
'--target_market', targetMarket
|
||||||
];
|
];
|
||||||
|
|
||||||
if (contextContent) {
|
if (contextContent) scriptArgs.push('--context_file', tempContextFilePath);
|
||||||
scriptArgs.push('--context_file', tempContextFilePath);
|
if (referenceCity) scriptArgs.push('--reference_city', referenceCity);
|
||||||
}
|
if (referenceCountry) scriptArgs.push('--reference_country', referenceCountry);
|
||||||
if (referenceCity) {
|
if (summaryOfOffer) scriptArgs.push('--summary_of_offer', summaryOfOffer);
|
||||||
scriptArgs.push('--reference_city', referenceCity);
|
|
||||||
}
|
|
||||||
if (referenceCountry) {
|
|
||||||
scriptArgs.push('--reference_country', referenceCountry);
|
|
||||||
}
|
|
||||||
if (summaryOfOffer) {
|
|
||||||
scriptArgs.push('--summary_of_offer', summaryOfOffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Spawning command: ${pythonExecutable}`);
|
runPython(scriptArgs, res, cleanupFiles);
|
||||||
console.log(`With arguments: ${JSON.stringify(scriptArgs)}`);
|
|
||||||
|
|
||||||
const pythonProcess = spawn(pythonExecutable, scriptArgs, {
|
|
||||||
env: { ...process.env, PYTHONPATH: path.join(__dirname, '..', '.venv', 'lib', 'python3.11', 'site-packages') }
|
|
||||||
});
|
|
||||||
|
|
||||||
let pythonOutput = '';
|
|
||||||
let pythonError = '';
|
|
||||||
|
|
||||||
pythonProcess.stdout.on('data', (data) => {
|
|
||||||
pythonOutput += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.stderr.on('data', (data) => {
|
|
||||||
pythonError += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.on('close', (code) => {
|
|
||||||
console.log(`Python script (identify_competitors) finished with exit code: ${code}`);
|
|
||||||
console.log(`--- STDOUT (identify_competitors) ---`);
|
|
||||||
console.log(pythonOutput);
|
|
||||||
console.log(`--- STDERR (identify_competitors) ---`);
|
|
||||||
console.log(pythonError);
|
|
||||||
console.log(`-------------------------------------`);
|
|
||||||
|
|
||||||
if (contextContent) {
|
|
||||||
fs.unlinkSync(tempContextFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code !== 0) {
|
|
||||||
console.error(`Python script (identify_competitors) exited with error.`);
|
|
||||||
return res.status(500).json({ error: 'Python script failed', details: pythonError });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const result = JSON.parse(pythonOutput);
|
|
||||||
res.json(result);
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error('Failed to parse Python output (identify_competitors) as JSON:', parseError);
|
|
||||||
res.status(500).json({ error: 'Invalid JSON from Python script', rawOutput: pythonOutput, details: pythonError });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.on('error', (err) => {
|
|
||||||
console.error('FATAL: Failed to start python process itself (identify_competitors).', err);
|
|
||||||
if (contextContent) {
|
|
||||||
fs.unlinkSync(tempContextFilePath);
|
|
||||||
}
|
|
||||||
res.status(500).json({ error: 'Failed to start Python process', details: err.message });
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (writeError) {
|
} catch (writeError) {
|
||||||
console.error('Failed to write temporary context file (identify_competitors):', writeError);
|
|
||||||
res.status(500).json({ error: 'Failed to write temporary file', details: writeError.message });
|
res.status(500).json({ error: 'Failed to write temporary file', details: writeError.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -200,14 +156,10 @@ app.post('/api/analyze-company', async (req, res) => {
|
|||||||
const { companyName, strategy, targetMarket } = req.body;
|
const { companyName, strategy, targetMarket } = req.body;
|
||||||
|
|
||||||
if (!companyName || !strategy) {
|
if (!companyName || !strategy) {
|
||||||
console.error('Validation Error: Missing companyName or strategy for analyze-company.');
|
|
||||||
return res.status(400).json({ error: 'Missing companyName or strategy' });
|
return res.status(400).json({ error: 'Missing companyName or strategy' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const pythonExecutable = path.join(__dirname, '..', '.venv', 'bin', 'python3');
|
|
||||||
const pythonScript = path.join(__dirname, '..', 'market_intel_orchestrator.py');
|
const pythonScript = path.join(__dirname, '..', 'market_intel_orchestrator.py');
|
||||||
|
|
||||||
const scriptArgs = [
|
const scriptArgs = [
|
||||||
pythonScript,
|
pythonScript,
|
||||||
'--mode', 'analyze_company',
|
'--mode', 'analyze_company',
|
||||||
@@ -216,57 +168,7 @@ app.post('/api/analyze-company', async (req, res) => {
|
|||||||
'--target_market', targetMarket || 'Germany'
|
'--target_market', targetMarket || 'Germany'
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log(`Spawning Audit for ${companyName}: ${pythonExecutable} ...`);
|
runPython(scriptArgs, res);
|
||||||
|
|
||||||
const pythonProcess = spawn(pythonExecutable, scriptArgs, {
|
|
||||||
env: { ...process.env, PYTHONPATH: path.join(__dirname, '..', '.venv', 'lib', 'python3.11', 'site-packages') }
|
|
||||||
});
|
|
||||||
|
|
||||||
let pythonOutput = '';
|
|
||||||
let pythonError = '';
|
|
||||||
|
|
||||||
pythonProcess.stdout.on('data', (data) => {
|
|
||||||
pythonOutput += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.stderr.on('data', (data) => {
|
|
||||||
pythonError += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.on('close', (code) => {
|
|
||||||
console.log(`Audit for ${companyName} finished with exit code: ${code}`);
|
|
||||||
|
|
||||||
// Log stderr nur bei Fehler oder wenn nötig, um Logs nicht zu fluten
|
|
||||||
if (pythonError) {
|
|
||||||
console.log(`--- STDERR (Audit ${companyName}) ---`);
|
|
||||||
console.log(pythonError);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code !== 0) {
|
|
||||||
console.error(`Python script (analyze_company) exited with error.`);
|
|
||||||
return res.status(500).json({ error: 'Python script failed', details: pythonError });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Versuche JSON zu parsen. Manchmal gibt Python zusätzlichen Text aus, den wir filtern müssen.
|
|
||||||
// Da wir stderr für Logs nutzen, sollte stdout rein sein.
|
|
||||||
const result = JSON.parse(pythonOutput);
|
|
||||||
res.json(result);
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error('Failed to parse Python output (analyze_company) as JSON:', parseError);
|
|
||||||
console.log('Raw Output:', pythonOutput);
|
|
||||||
res.status(500).json({ error: 'Invalid JSON from Python script', rawOutput: pythonOutput, details: pythonError });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.on('error', (err) => {
|
|
||||||
console.error(`FATAL: Failed to start python process for ${companyName}.`, err);
|
|
||||||
res.status(500).json({ error: 'Failed to start Python process', details: err.message });
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Internal Server Error in /api/analyze-company: ${err.message}`);
|
|
||||||
res.status(500).json({ error: err.message });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// API-Endpunkt für generate-outreach
|
// API-Endpunkt für generate-outreach
|
||||||
@@ -275,25 +177,19 @@ app.post('/api/generate-outreach', async (req, res) => {
|
|||||||
const { companyData, knowledgeBase, referenceUrl } = req.body;
|
const { companyData, knowledgeBase, referenceUrl } = req.body;
|
||||||
|
|
||||||
if (!companyData || !knowledgeBase) {
|
if (!companyData || !knowledgeBase) {
|
||||||
console.error('Validation Error: Missing companyData or knowledgeBase for generate-outreach.');
|
|
||||||
return res.status(400).json({ error: 'Missing companyData or knowledgeBase' });
|
return res.status(400).json({ error: 'Missing companyData or knowledgeBase' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const tempDataFilePath = path.join(__dirname, 'tmp', `outreach_data_${Date.now()}.json`);
|
|
||||||
const tempContextFilePath = path.join(__dirname, 'tmp', `outreach_context_${Date.now()}.md`);
|
|
||||||
const tmpDir = path.join(__dirname, 'tmp');
|
const tmpDir = path.join(__dirname, 'tmp');
|
||||||
if (!fs.existsSync(tmpDir)) {
|
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir);
|
||||||
fs.mkdirSync(tmpDir);
|
const tempDataFilePath = path.join(tmpDir, `outreach_data_${Date.now()}.json`);
|
||||||
}
|
const tempContextFilePath = path.join(tmpDir, `outreach_context_${Date.now()}.md`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(tempDataFilePath, JSON.stringify(companyData));
|
fs.writeFileSync(tempDataFilePath, JSON.stringify(companyData));
|
||||||
fs.writeFileSync(tempContextFilePath, knowledgeBase);
|
fs.writeFileSync(tempContextFilePath, knowledgeBase);
|
||||||
console.log(`Successfully wrote temporary files for outreach.`);
|
|
||||||
|
|
||||||
const pythonExecutable = path.join(__dirname, '..', '.venv', 'bin', 'python3');
|
|
||||||
const pythonScript = path.join(__dirname, '..', 'market_intel_orchestrator.py');
|
const pythonScript = path.join(__dirname, '..', 'market_intel_orchestrator.py');
|
||||||
|
|
||||||
const scriptArgs = [
|
const scriptArgs = [
|
||||||
pythonScript,
|
pythonScript,
|
||||||
'--mode', 'generate_outreach',
|
'--mode', 'generate_outreach',
|
||||||
@@ -302,57 +198,13 @@ app.post('/api/generate-outreach', async (req, res) => {
|
|||||||
'--reference_url', referenceUrl || ''
|
'--reference_url', referenceUrl || ''
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log(`Spawning Outreach Generation for ${companyData.companyName}...`);
|
runPython(scriptArgs, res, [tempDataFilePath, tempContextFilePath]);
|
||||||
|
|
||||||
const pythonProcess = spawn(pythonExecutable, scriptArgs, {
|
|
||||||
env: { ...process.env, PYTHONPATH: path.join(__dirname, '..', '.venv', 'lib', 'python3.11', 'site-packages') }
|
|
||||||
});
|
|
||||||
|
|
||||||
let pythonOutput = '';
|
|
||||||
let pythonError = '';
|
|
||||||
|
|
||||||
pythonProcess.stdout.on('data', (data) => {
|
|
||||||
pythonOutput += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.stderr.on('data', (data) => {
|
|
||||||
pythonError += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.on('close', (code) => {
|
|
||||||
console.log(`Outreach Generation finished with exit code: ${code}`);
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
if (fs.existsSync(tempDataFilePath)) fs.unlinkSync(tempDataFilePath);
|
|
||||||
if (fs.existsSync(tempContextFilePath)) fs.unlinkSync(tempContextFilePath);
|
|
||||||
|
|
||||||
if (code !== 0) {
|
|
||||||
console.error(`Python script (generate_outreach) exited with error.`);
|
|
||||||
return res.status(500).json({ error: 'Python script failed', details: pythonError });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const result = JSON.parse(pythonOutput);
|
|
||||||
res.json(result);
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error('Failed to parse Python output (generate_outreach) as JSON:', parseError);
|
|
||||||
res.status(500).json({ error: 'Invalid JSON from Python script', rawOutput: pythonOutput, details: pythonError });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.on('error', (err) => {
|
|
||||||
console.error(`FATAL: Failed to start python process for outreach.`, err);
|
|
||||||
if (fs.existsSync(tempDataFilePath)) fs.unlinkSync(tempDataFilePath);
|
|
||||||
if (fs.existsSync(tempContextFilePath)) fs.unlinkSync(tempContextFilePath);
|
|
||||||
res.status(500).json({ error: 'Failed to start Python process', details: err.message });
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Internal Server Error in /api/generate-outreach: ${err.message}`);
|
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start des Servers
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Node.js API Bridge running on http://localhost:${PORT}`);
|
console.log(`Node.js API Bridge running on http://localhost:${PORT}`);
|
||||||
});
|
});
|
||||||
@@ -6,8 +6,8 @@ import { LeadStatus, AnalysisResult, Competitor, Language, Tier, EmailDraft, Sea
|
|||||||
// URL Konfiguration:
|
// URL Konfiguration:
|
||||||
// Im Production-Build (Docker/Nginx) nutzen wir den relativen Pfad '/api', da Nginx als Reverse Proxy fungiert.
|
// Im Production-Build (Docker/Nginx) nutzen wir den relativen Pfad '/api', da Nginx als Reverse Proxy fungiert.
|
||||||
// Im Development-Modus (lokal) greifen wir direkt auf den Port 3001 zu.
|
// Im Development-Modus (lokal) greifen wir direkt auf den Port 3001 zu.
|
||||||
const API_BASE_URL = import.meta.env.PROD
|
const API_BASE_URL = (import.meta as any).env.PROD
|
||||||
? '/api'
|
? 'api'
|
||||||
: `http://${window.location.hostname}:3001/api`;
|
: `http://${window.location.hostname}:3001/api`;
|
||||||
|
|
||||||
// Helper to extract JSON (kann ggf. entfernt werden, wenn das Backend immer sauberes JSON liefert)
|
// Helper to extract JSON (kann ggf. entfernt werden, wenn das Backend immer sauberes JSON liefert)
|
||||||
|
|||||||
10
general-market-intelligence/vite-env.d.ts
vendored
Normal file
10
general-market-intelligence/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_API_BASE_URL: string;
|
||||||
|
// more env variables...
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import react from '@vitejs/plugin-react';
|
|||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, '.', '');
|
const env = loadEnv(mode, '.', '');
|
||||||
return {
|
return {
|
||||||
|
base: '/market/',
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
|
|||||||
44
nginx-proxy.conf
Normal file
44
nginx-proxy.conf
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
access_log /dev/stdout;
|
||||||
|
error_log /dev/stderr;
|
||||||
|
|
||||||
|
# Resolver ist wichtig für Docker
|
||||||
|
resolver 127.0.0.11 valid=30s ipv6=off;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
# Basic Auth wieder aktiviert
|
||||||
|
auth_basic "Restricted Access - Local AI Suite";
|
||||||
|
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://dashboard:80;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /b2b/ {
|
||||||
|
# Der Trailing Slash am Ende ist wichtig!
|
||||||
|
proxy_pass http://b2b-app:3002/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
|
||||||
|
location /market/ {
|
||||||
|
# Der Trailing Slash am Ende ist wichtig!
|
||||||
|
proxy_pass http://market-frontend:80/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user