diff --git a/dashboard/Dockerfile b/dashboard/Dockerfile
index 8385cd5..c62a1ce 100644
--- a/dashboard/Dockerfile
+++ b/dashboard/Dockerfile
@@ -1,39 +1,52 @@
# ==========================================
# dashboard/Dockerfile (Production)
+# 🔧 OPTIMIERT: Multi-Stage-Build für ein minimales Produktions-Image
# ==========================================
-FROM node:lts-alpine AS builder
+
+# Stage 1: Build-Umgebung
+FROM node:20-alpine AS build
WORKDIR /app
-# Copy package files
-COPY package*.json ./
-COPY pnpm-lock.yaml* ./
+# Kopiere package.json und pnpm-lock.yaml
+COPY package.json pnpm-lock.yaml ./
-# Install pnpm and dependencies
-RUN npm install -g pnpm
-RUN pnpm install --frozen-lockfile
+# Installiere pnpm und dann die Abhängigkeiten
+# --prod stellt sicher, dass nur Produktions-Abhängigkeiten installiert werden
+RUN npm install -g pnpm && pnpm install --prod --frozen-lockfile
-# Copy source code
+# Kopiere den restlichen Quellcode
COPY . .
-# Build arguments
-ARG NODE_ENV=production
+# Setze Build-Argumente als Umgebungsvariablen
ARG VITE_API_URL
+ENV VITE_API_URL=${VITE_API_URL}
-# Build the application
+# Baue die Anwendung für die Produktion
RUN pnpm build
-# Production stage with nginx
-FROM nginx:alpine
+# Stage 2: Produktions-Umgebung
+FROM nginx:1.25-alpine
-# Copy built files to nginx
-COPY --from=builder /app/dist /usr/share/nginx/html
+# Kopiere die gebauten statischen Dateien aus der Build-Stage
+COPY --from=build /app/dist /usr/share/nginx/html
-# Copy custom nginx config (optional)
-COPY nginx.conf /etc/nginx/nginx.conf
+# Optional: Eine Nginx-Konfiguration für Single-Page-Applications (SPA)
+# Diese leitet alle Anfragen, die keine Dateien sind, auf die index.html um.
+# Erstelle eine Datei `nginx.prod.conf` mit folgendem Inhalt:
+# server {
+# listen 80;
+# root /usr/share/nginx/html;
+# index index.html;
+# location / {
+# try_files $uri $uri/ /index.html;
+# }
+# }
+# COPY nginx.prod.conf /etc/nginx/conf.d/default.conf
-# Expose port
-EXPOSE 3000
+# Exponiere Port 80 (Standard-HTTP-Port von Nginx)
+EXPOSE 80
-# Start nginx
+# Starte Nginx
CMD ["nginx", "-g", "daemon off;"]
+
diff --git a/dashboard/Dockerfile.dev b/dashboard/Dockerfile.dev
index 0dfab4c..e4450ec 100644
--- a/dashboard/Dockerfile.dev
+++ b/dashboard/Dockerfile.dev
@@ -1,24 +1,36 @@
# ==========================================
# dashboard/Dockerfile.dev (Development)
+# 🔧 OPTIMIERT: Für schnelle Entwicklung mit Vite und pnpm
# ==========================================
-FROM node:lts-alpine
+FROM node:20-alpine
+# Setze das Arbeitsverzeichnis auf den Workspace-Root, um die Pfade aus
+# docker-compose.override.yml korrekt aufzulösen.
+WORKDIR /workspace
+
+# 🔧 HINZUGEFÜGT: Installiere curl, damit das wait-for-backend.sh Skript funktioniert
+RUN apk add --no-cache curl
+
+# Installiere pnpm, da es im Projekt verwendet wird.
+RUN npm install -g pnpm
+
+# Kopiere die package-Dateien in das korrekte Unterverzeichnis.
+# Dies nutzt den Docker-Cache: Wenn sich die Dateien nicht ändern,
+# wird der `pnpm install`-Schritt übersprungen.
+COPY package.json pnpm-lock.yaml* ./
+
+# Wechsle in das Dashboard-Verzeichnis, um die Befehle auszuführen.
WORKDIR /workspace/dashboard
-# Install dependencies manager (pnpm optional, npm reicht für Compose-Setup)
-# RUN npm install -g pnpm
+# Installiere ALLE Abhängigkeiten (inkl. devDependencies)
+RUN pnpm install
-# Copy package files
-COPY package*.json ./
+# Das Kopieren des restlichen Codes ist nicht nötig, da das gesamte
+# Verzeichnis `./:/workspace` in der docker-compose.override.yml gemountet wird.
-# Install dependencies (nutze npm, da Compose "npm run dev" nutzt)
-RUN npm install
+# Exponiere die Ports für Vite und Node-Debugging
+EXPOSE 5173 9229
-# Copy source code
-COPY . .
-
-# Expose ports
-EXPOSE 3000 9229
-
-# Standard-Dev-Command (wird von Compose überschrieben)
-CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "3000"]
\ No newline at end of file
+# Der Startbefehl wird in der docker-compose.override.yml definiert.
+# Ein Standard-CMD ist dennoch eine gute Praxis.
+CMD ["pnpm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5173"]
diff --git a/dashboard/package.json b/dashboard/package.json
index 04e62b2..439b759 100644
--- a/dashboard/package.json
+++ b/dashboard/package.json
@@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
- "dev": "vite --host 0.0.0.0 --port 3000",
+ "dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
diff --git a/dashboard/src/App.css b/dashboard/src/App.css
index bf101df..4429f16 100644
--- a/dashboard/src/App.css
+++ b/dashboard/src/App.css
@@ -17,6 +17,7 @@
body {
font-family: Inter, 'Segoe UI', Roboto, Arial, sans-serif;
+ overflow: hidden; /* Verhindert den Scrollbalken auf der obersten Ebene */
}
:root {
@@ -28,6 +29,7 @@ body {
/* Layout-Container für Sidebar und Content */
.layout-container {
display: flex;
+ position: relative; /* Wichtig für die absolute Positionierung des Inhalts */
height: 100vh; /* Feste Höhe auf die des Viewports setzen */
overflow: hidden; /* Verhindert, dass der Scrollbalken den gesamten Container betrifft */
}
@@ -40,6 +42,7 @@ body {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
flex-shrink: 0;
overflow: hidden;
+ z-index: 10; /* Stellt sicher, dass die Sidebar über dem Inhalt ist */
}
.sidebar-theme .sidebar-link {
@@ -73,16 +76,36 @@ body {
color: var(--sidebar-bg);
}
+/* === START: ROBUSTES ABSOLUTE-POSITIONING-LAYOUT === */
-/* Nur der Page-Content bekommt den horizontalen Scrollbalken */
-.page-content-scroll {
- flex: 1 1 auto;
- overflow-x: auto;
- padding: 16px; /* Optional: Abstand innerhalb des Contents */
- height: 100%;
- box-sizing: border-box; /* Padding wird in die Breite/Höhe einbezogen */
+/* Der Inhaltsbereich wird absolut positioniert, um den Rest des Bildschirms auszufüllen */
+.content-area {
+ position: absolute;
+ inset: 0 0 0 16rem; /* Shorthand für top, right, bottom, left */
+ display: flex;
+ flex-direction: column;
+ transition: inset-inline-start 0.3s ease-in-out; /* Animiert die 'left' Eigenschaft */
}
+/* Anpassung für die eingeklappte Sidebar */
+.content-area.collapsed {
+ left: 5rem; /* Breite der eingeklappten Sidebar (w-20) */
+}
+
+.content-header {
+ flex-shrink: 0; /* Header soll nicht schrumpfen */
+}
+
+.page-content {
+ flex-grow: 1; /* Füllt den verbleibenden Platz */
+ overflow-y: auto; /* NUR dieser Bereich scrollt */
+ padding: 2rem;
+ background-color: #f3f4f6;
+}
+
+/* === ENDE: ROBUSTES ABSOLUTE-POSITIONING-LAYOUT === */
+
+
/* Kanban-Karten im Sidebar-Style */
.e-kanban .e-card,
.e-kanban .e-card .e-card-content,
diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx
index 5e26fbc..454d31f 100644
--- a/dashboard/src/App.tsx
+++ b/dashboard/src/App.tsx
@@ -116,10 +116,10 @@ const Layout: React.FC = () => {
{/* Main Content */}
-
+
{/* Header */}
-
+
diff --git a/dashboard/src/programminfo.tsx b/dashboard/src/programminfo.tsx
index 23a96d3..ac4f57c 100644
--- a/dashboard/src/programminfo.tsx
+++ b/dashboard/src/programminfo.tsx
@@ -139,26 +139,30 @@ const Programminfo: React.FC = () => {
Verwendete Open-Source-Komponenten
-
-
Frontend
-
- {info.openSourceComponents.frontend.map(item => (
- -
- {item.name} ({item.license}-Lizenz)
-
- ))}
-
-
-
-
Backend
-
- {info.openSourceComponents.backend.map(item => (
- -
- {item.name} ({item.license}-Lizenz)
-
- ))}
-
-
+ {info.openSourceComponents.frontend && (
+
+
Frontend
+
+ {info.openSourceComponents.frontend.map(item => (
+ -
+ {item.name} ({item.license}-Lizenz)
+
+ ))}
+
+
+ )}
+ {info.openSourceComponents.backend && (
+
+
Backend
+
+ {info.openSourceComponents.backend.map(item => (
+ -
+ {item.name} ({item.license}-Lizenz)
+
+ ))}
+
+
+ )}
diff --git a/dashboard/vite.config.ts b/dashboard/vite.config.ts
index e04aa16..a0ef3f8 100644
--- a/dashboard/vite.config.ts
+++ b/dashboard/vite.config.ts
@@ -5,9 +5,16 @@ import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
+ host: '0.0.0.0',
+ watch: {
+ usePolling: true,
+ },
+ fs: {
+ strict: false,
+ },
proxy: {
- '/api': 'http://localhost:8000',
- '/screenshots': 'http://localhost:8000',
+ '/api': 'http://server:8000',
+ '/screenshots': 'http://server:8000',
},
},
});
diff --git a/dashboard/wait-for-backend.sh b/dashboard/wait-for-backend.sh
new file mode 100755
index 0000000..996f438
--- /dev/null
+++ b/dashboard/wait-for-backend.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+# wait-for-backend.sh
+
+# Stellt sicher, dass das Skript bei einem Fehler abbricht
+set -e
+
+# Der erste Parameter ist der Host, der erreicht werden soll
+host="$1"
+# Alle weiteren Parameter bilden den Befehl, der danach ausgeführt werden soll
+shift
+cmd="$@"
+
+# Schleife, die so lange läuft, bis der Host mit einem erfolgreichen HTTP-Status antwortet
+# curl -s: silent mode (kein Fortschrittsbalken)
+# curl -f: fail silently (gibt einen Fehlercode > 0 zurück, wenn der HTTP-Status nicht 2xx ist)
+until curl -s -f "$host" > /dev/null; do
+ >&2 echo "Backend ist noch nicht erreichbar - schlafe für 2 Sekunden"
+ sleep 2
+done
+
+# Wenn die Schleife beendet ist, ist das Backend erreichbar
+>&2 echo "Backend ist erreichbar - starte Vite-Server..."
+# Führe den eigentlichen Befehl aus (z.B. npm run dev)
+exec $cmd
diff --git a/docker-compose.yml b/docker-compose.yml
index 30a7d22..77a24b5 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,4 +1,3 @@
-
networks:
infoscreen-net:
driver: bridge
@@ -18,19 +17,19 @@ services:
condition: service_healthy
environment:
- DB_URL=mysql+pymysql://${DB_USER}:${DB_PASSWORD}@db/${DB_NAME}
- volumes:
- - ./listener:/app:rw
+ # 🔧 ENTFERNT: Volume-Mount ist nur für die Entwicklung
networks:
- infoscreen-net
+
proxy:
- image: nginx:stable
+ image: nginx:1.25 # 🔧 GEÄNDERT: Spezifische Version
container_name: infoscreen-proxy
ports:
- "80:80"
- "443:443"
volumes:
- - ${PWD}/nginx.conf:/etc/nginx/nginx.conf:ro
- - ${PWD}/certs:/etc/nginx/certs:ro
+ - ./nginx.conf:/etc/nginx/nginx.conf:ro # 🔧 GEÄNDERT: Relativer Pfad
+ - ./certs:/etc/nginx/certs:ro # 🔧 GEÄNDERT: Relativer Pfad
depends_on:
- server
- dashboard
@@ -38,7 +37,7 @@ services:
- infoscreen-net
db:
- image: mariadb:lts
+ image: mariadb:11.2 # 🔧 GEÄNDERT: Spezifische Version
container_name: infoscreen-db
restart: unless-stopped
environment:
@@ -60,7 +59,7 @@ services:
start_period: 30s
mqtt:
- image: eclipse-mosquitto:2.0.21
+ image: eclipse-mosquitto:2.0.21 # ✅ GUT: Version ist bereits spezifisch
container_name: infoscreen-mqtt
restart: unless-stopped
volumes:
@@ -114,9 +113,8 @@ services:
build:
context: ./dashboard
dockerfile: Dockerfile
- # ✅ HINZUGEFÜGT: Build-Args für React Production Build
+ # 🔧 VEREINFACHT: Build-Args werden durch Umgebungsvariablen gesetzt
args:
- - NODE_ENV=production
- VITE_API_URL=${API_URL}
image: infoscreen-dashboard:latest
container_name: infoscreen-dashboard
@@ -125,20 +123,20 @@ services:
server:
condition: service_healthy
environment:
- # ✅ GEÄNDERT: React-spezifische Umgebungsvariablen
- - VITE_API_URL=${API_URL}
- NODE_ENV=production
- ports:
- - "3000:3000" # ✅ GEÄNDERT: Standard React/Vite Port
+ - VITE_API_URL=${API_URL}
+ # 🔧 ENTFERNT: Port wird in Produktion nicht direkt freigegeben, Zugriff via Proxy
networks:
- infoscreen-net
- # ✅ GEÄNDERT: Healthcheck für React App
healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:3000/"]
+ # 🔧 GEÄNDERT: Healthcheck prüft den Nginx-Server im Container
+ test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 5s
retries: 3
- start_period: 30s
+ # 🔧 ERHÖHT: Gibt dem Backend mehr Zeit zum Starten, bevor dieser
+ # Container als "gesund" markiert wird.
+ start_period: 60s
scheduler:
build:
diff --git a/server/Dockerfile b/server/Dockerfile
index d649576..fa0945d 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -1,16 +1,33 @@
# server/Dockerfile
-# Produktions-Dockerfile für die Flask-API mit Gunicorn
+# 🔧 OPTIMIERT: Multi-Stage-Build für ein minimales und sicheres Produktions-Image
-# --- Basisimage ---
+# Stage 1: Builder - Installiert Abhängigkeiten
+FROM python:3.13-slim AS builder
+
+WORKDIR /app
+
+# Installiert nur die für den Build notwendigen Systemabhängigkeiten
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ libmariadb-dev-compat libmariadb-dev gcc \
+ && rm -rf /var/lib/apt/lists/*
+
+# Kopiert nur die requirements.txt, um den Docker-Cache optimal zu nutzen
+COPY requirements.txt .
+
+# Installiert die Python-Pakete in ein separates Verzeichnis
+RUN pip install --no-cache-dir --prefix="/install" -r requirements.txt
+
+# Stage 2: Final - Das eigentliche Produktions-Image
FROM python:3.13-slim
# --- Arbeitsverzeichnis ---
WORKDIR /app
-# --- Systemabhängigkeiten für MariaDB-Client & Locale ---
+# Installiert nur die für die Laufzeit notwendigen Systemabhängigkeiten
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
- libmariadb-dev-compat libmariadb-dev locales git\
+ libmariadb-dev-compat locales \
&& rm -rf /var/lib/apt/lists/*
# --- Locale konfigurieren ---
@@ -19,12 +36,12 @@ RUN sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen \
ENV LANG=de_DE.UTF-8 \
LC_ALL=de_DE.UTF-8
-# --- Python-Abhängigkeiten ---
-COPY requirements.txt .
-RUN pip install --no-cache-dir -r requirements.txt
+# Kopiert die installierten Pakete aus der Builder-Stage
+COPY --from=builder /install /usr/local
# --- Applikationscode ---
-COPY server/ /app
+# Kopiert den Server-Code in das Arbeitsverzeichnis
+COPY server/ .
# --- Non-Root User anlegen und Rechte setzen ---
ARG USER_ID=1000
@@ -40,3 +57,4 @@ EXPOSE 8000
# --- Startbefehl für Gunicorn ---
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "wsgi:app"]
+
diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev
index fa4492d..87b1780 100644
--- a/server/Dockerfile.dev
+++ b/server/Dockerfile.dev
@@ -1,15 +1,15 @@
# Datei: server/Dockerfile.dev
-# Entwicklungs-Dockerfile für die API (Flask + SQLAlchemy)
+# 🔧 OPTIMIERT: Für die Entwicklung im Dev-Container
+# ==========================================
FROM python:3.13-slim
-# Build args für UID/GID
+# Die Erstellung des non-root Users und die Locale-Konfiguration
+# sind für den Dev-Container nicht zwingend nötig, da VS Code sich als 'root'
+# verbindet (gemäß devcontainer.json). Sie schaden aber nicht.
ARG USER_ID=1000
ARG GROUP_ID=1000
-
-# Erstelle non-root User
-RUN apt-get update \
- && apt-get install -y --no-install-recommends locales curl \
+RUN apt-get update && apt-get install -y --no-install-recommends locales curl git \
&& groupadd -g ${GROUP_ID} infoscreen_taa \
&& useradd -u ${USER_ID} -g ${GROUP_ID} --shell /bin/bash --create-home infoscreen_taa \
&& sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen \
@@ -17,32 +17,26 @@ RUN apt-get update \
&& rm -rf /var/lib/apt/lists/*
ENV LANG=de_DE.UTF-8 \
- LANGUAGE=de_DE:de \
LC_ALL=de_DE.UTF-8
-# Arbeitsverzeichnis
-WORKDIR /app
+# Setze das Arbeitsverzeichnis auf den Workspace-Root, passend zu den Mounts.
+WORKDIR /workspace
-# Kopiere nur Requirements für schnellen Rebuild
-COPY requirements.txt ./
-COPY requirements-dev.txt ./
+# Kopiere die Anforderungsdateien in das korrekte Unterverzeichnis.
+# ✅ KORRIGIERT: Pfade sind jetzt relativ zum Build-Kontext (dem 'server'-Verzeichnis)
+COPY requirements.txt requirements-dev.txt ./
-# Installiere Python-Abhängigkeiten (Prod + Dev)
+# Installiere die Python-Abhängigkeiten
RUN pip install --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt \
- && pip install --no-cache-dir -r requirements-dev.txt
+ && pip install --no-cache-dir -r requirements-dev.txt
-# Expose Ports für Flask API
-EXPOSE 8000
-EXPOSE 5678
+# Das Kopieren des Codes ist nicht nötig, da das Verzeichnis gemountet wird.
-# Setze Env für Dev
-ENV FLASK_ENV=development
-ENV ENV_FILE=.env
+# Exponiere die Ports für die Flask API und den Debugger
+EXPOSE 8000 5678
-# Wechsle zum non-root User
-USER infoscreen_taa
+# Der Startbefehl wird in der docker-compose.override.yml definiert.
+# Ein Standard-CMD dient als Fallback.
+CMD ["python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "flask", "run", "--host=0.0.0.0", "--port=8000"]
-# Default Command: Flask Development Server
-CMD ["python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "wsgi.py"]
-# CMD ["sleep", "infinity"]