multiple corrections on docker-compose and Dockerfile

robust start sequence
avoid scrolling of main content
This commit is contained in:
2025-08-31 07:30:53 +00:00
parent 2ca5f0060e
commit 4e74f72c9f
11 changed files with 211 additions and 118 deletions

View File

@@ -1,39 +1,52 @@
# ========================================== # ==========================================
# dashboard/Dockerfile (Production) # 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 WORKDIR /app
# Copy package files # Kopiere package.json und pnpm-lock.yaml
COPY package*.json ./ COPY package.json pnpm-lock.yaml ./
COPY pnpm-lock.yaml* ./
# Install pnpm and dependencies # Installiere pnpm und dann die Abhängigkeiten
RUN npm install -g pnpm # --prod stellt sicher, dass nur Produktions-Abhängigkeiten installiert werden
RUN pnpm install --frozen-lockfile RUN npm install -g pnpm && pnpm install --prod --frozen-lockfile
# Copy source code # Kopiere den restlichen Quellcode
COPY . . COPY . .
# Build arguments # Setze Build-Argumente als Umgebungsvariablen
ARG NODE_ENV=production
ARG VITE_API_URL ARG VITE_API_URL
ENV VITE_API_URL=${VITE_API_URL}
# Build the application # Baue die Anwendung für die Produktion
RUN pnpm build RUN pnpm build
# Production stage with nginx # Stage 2: Produktions-Umgebung
FROM nginx:alpine FROM nginx:1.25-alpine
# Copy built files to nginx # Kopiere die gebauten statischen Dateien aus der Build-Stage
COPY --from=builder /app/dist /usr/share/nginx/html COPY --from=build /app/dist /usr/share/nginx/html
# Copy custom nginx config (optional) # Optional: Eine Nginx-Konfiguration für Single-Page-Applications (SPA)
COPY nginx.conf /etc/nginx/nginx.conf # 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 # Exponiere Port 80 (Standard-HTTP-Port von Nginx)
EXPOSE 3000 EXPOSE 80
# Start nginx # Starte Nginx
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,24 +1,36 @@
# ========================================== # ==========================================
# dashboard/Dockerfile.dev (Development) # 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 WORKDIR /workspace/dashboard
# Install dependencies manager (pnpm optional, npm reicht für Compose-Setup) # Installiere ALLE Abhängigkeiten (inkl. devDependencies)
# RUN npm install -g pnpm RUN pnpm install
# Copy package files # Das Kopieren des restlichen Codes ist nicht nötig, da das gesamte
COPY package*.json ./ # Verzeichnis `./:/workspace` in der docker-compose.override.yml gemountet wird.
# Install dependencies (nutze npm, da Compose "npm run dev" nutzt) # Exponiere die Ports für Vite und Node-Debugging
RUN npm install EXPOSE 5173 9229
# Copy source code # Der Startbefehl wird in der docker-compose.override.yml definiert.
COPY . . # Ein Standard-CMD ist dennoch eine gute Praxis.
CMD ["pnpm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5173"]
# Expose ports
EXPOSE 3000 9229
# Standard-Dev-Command (wird von Compose überschrieben)
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "3000"]

View File

@@ -4,7 +4,7 @@
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host 0.0.0.0 --port 3000", "dev": "vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview" "preview": "vite preview"

View File

@@ -17,6 +17,7 @@
body { body {
font-family: Inter, 'Segoe UI', Roboto, Arial, sans-serif; font-family: Inter, 'Segoe UI', Roboto, Arial, sans-serif;
overflow: hidden; /* Verhindert den Scrollbalken auf der obersten Ebene */
} }
:root { :root {
@@ -28,6 +29,7 @@ body {
/* Layout-Container für Sidebar und Content */ /* Layout-Container für Sidebar und Content */
.layout-container { .layout-container {
display: flex; display: flex;
position: relative; /* Wichtig für die absolute Positionierung des Inhalts */
height: 100vh; /* Feste Höhe auf die des Viewports setzen */ height: 100vh; /* Feste Höhe auf die des Viewports setzen */
overflow: hidden; /* Verhindert, dass der Scrollbalken den gesamten Container betrifft */ 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; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
flex-shrink: 0; flex-shrink: 0;
overflow: hidden; overflow: hidden;
z-index: 10; /* Stellt sicher, dass die Sidebar über dem Inhalt ist */
} }
.sidebar-theme .sidebar-link { .sidebar-theme .sidebar-link {
@@ -73,16 +76,36 @@ body {
color: var(--sidebar-bg); color: var(--sidebar-bg);
} }
/* === START: ROBUSTES ABSOLUTE-POSITIONING-LAYOUT === */
/* Nur der Page-Content bekommt den horizontalen Scrollbalken */ /* Der Inhaltsbereich wird absolut positioniert, um den Rest des Bildschirms auszufüllen */
.page-content-scroll { .content-area {
flex: 1 1 auto; position: absolute;
overflow-x: auto; inset: 0 0 0 16rem; /* Shorthand für top, right, bottom, left */
padding: 16px; /* Optional: Abstand innerhalb des Contents */ display: flex;
height: 100%; flex-direction: column;
box-sizing: border-box; /* Padding wird in die Breite/Höhe einbezogen */ 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 */ /* Kanban-Karten im Sidebar-Style */
.e-kanban .e-card, .e-kanban .e-card,
.e-kanban .e-card .e-card-content, .e-kanban .e-card .e-card-content,

View File

@@ -116,10 +116,10 @@ const Layout: React.FC = () => {
</div> </div>
</aside> </aside>
{/* Main Content */} {/* Main Content */}
<div className="flex-1 flex flex-col"> <div className={`content-area ${collapsed ? 'collapsed' : ''}`}>
{/* Header */} {/* Header */}
<header <header
className="flex items-center px-8 shadow" className="content-header flex items-center px-8 shadow"
style={{ style={{
backgroundColor: '#e5d8c7', backgroundColor: '#e5d8c7',
color: '#78591c', color: '#78591c',
@@ -140,7 +140,7 @@ const Layout: React.FC = () => {
[Organisationsname] [Organisationsname]
</span> </span>
</header> </header>
<main className="flex-1 p-8 bg-gray-100 page-content-scroll"> <main className="page-content">
<Outlet /> <Outlet />
</main> </main>
</div> </div>

View File

@@ -139,6 +139,7 @@ const Programminfo: React.FC = () => {
<div> <div>
<h3 className="text-xl font-semibold mb-4">Verwendete Open-Source-Komponenten</h3> <h3 className="text-xl font-semibold mb-4">Verwendete Open-Source-Komponenten</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8"> <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{info.openSourceComponents.frontend && (
<div className="bg-white p-6 rounded-lg shadow"> <div className="bg-white p-6 rounded-lg shadow">
<h4 className="font-bold mb-3">Frontend</h4> <h4 className="font-bold mb-3">Frontend</h4>
<ul className="list-disc list-inside space-y-1"> <ul className="list-disc list-inside space-y-1">
@@ -149,6 +150,8 @@ const Programminfo: React.FC = () => {
))} ))}
</ul> </ul>
</div> </div>
)}
{info.openSourceComponents.backend && (
<div className="bg-white p-6 rounded-lg shadow"> <div className="bg-white p-6 rounded-lg shadow">
<h4 className="font-bold mb-3">Backend</h4> <h4 className="font-bold mb-3">Backend</h4>
<ul className="list-disc list-inside space-y-1"> <ul className="list-disc list-inside space-y-1">
@@ -159,6 +162,7 @@ const Programminfo: React.FC = () => {
))} ))}
</ul> </ul>
</div> </div>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,9 +5,16 @@ import react from '@vitejs/plugin-react';
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { server: {
host: '0.0.0.0',
watch: {
usePolling: true,
},
fs: {
strict: false,
},
proxy: { proxy: {
'/api': 'http://localhost:8000', '/api': 'http://server:8000',
'/screenshots': 'http://localhost:8000', '/screenshots': 'http://server:8000',
}, },
}, },
}); });

24
dashboard/wait-for-backend.sh Executable file
View File

@@ -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

View File

@@ -1,4 +1,3 @@
networks: networks:
infoscreen-net: infoscreen-net:
driver: bridge driver: bridge
@@ -18,19 +17,19 @@ services:
condition: service_healthy condition: service_healthy
environment: environment:
- DB_URL=mysql+pymysql://${DB_USER}:${DB_PASSWORD}@db/${DB_NAME} - DB_URL=mysql+pymysql://${DB_USER}:${DB_PASSWORD}@db/${DB_NAME}
volumes: # 🔧 ENTFERNT: Volume-Mount ist nur für die Entwicklung
- ./listener:/app:rw
networks: networks:
- infoscreen-net - infoscreen-net
proxy: proxy:
image: nginx:stable image: nginx:1.25 # 🔧 GEÄNDERT: Spezifische Version
container_name: infoscreen-proxy container_name: infoscreen-proxy
ports: ports:
- "80:80" - "80:80"
- "443:443" - "443:443"
volumes: volumes:
- ${PWD}/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx.conf:/etc/nginx/nginx.conf:ro # 🔧 GEÄNDERT: Relativer Pfad
- ${PWD}/certs:/etc/nginx/certs:ro - ./certs:/etc/nginx/certs:ro # 🔧 GEÄNDERT: Relativer Pfad
depends_on: depends_on:
- server - server
- dashboard - dashboard
@@ -38,7 +37,7 @@ services:
- infoscreen-net - infoscreen-net
db: db:
image: mariadb:lts image: mariadb:11.2 # 🔧 GEÄNDERT: Spezifische Version
container_name: infoscreen-db container_name: infoscreen-db
restart: unless-stopped restart: unless-stopped
environment: environment:
@@ -60,7 +59,7 @@ services:
start_period: 30s start_period: 30s
mqtt: mqtt:
image: eclipse-mosquitto:2.0.21 image: eclipse-mosquitto:2.0.21 # ✅ GUT: Version ist bereits spezifisch
container_name: infoscreen-mqtt container_name: infoscreen-mqtt
restart: unless-stopped restart: unless-stopped
volumes: volumes:
@@ -114,9 +113,8 @@ services:
build: build:
context: ./dashboard context: ./dashboard
dockerfile: Dockerfile dockerfile: Dockerfile
# ✅ HINZUGEFÜGT: Build-Args für React Production Build # 🔧 VEREINFACHT: Build-Args werden durch Umgebungsvariablen gesetzt
args: args:
- NODE_ENV=production
- VITE_API_URL=${API_URL} - VITE_API_URL=${API_URL}
image: infoscreen-dashboard:latest image: infoscreen-dashboard:latest
container_name: infoscreen-dashboard container_name: infoscreen-dashboard
@@ -125,20 +123,20 @@ services:
server: server:
condition: service_healthy condition: service_healthy
environment: environment:
# ✅ GEÄNDERT: React-spezifische Umgebungsvariablen
- VITE_API_URL=${API_URL}
- NODE_ENV=production - NODE_ENV=production
ports: - VITE_API_URL=${API_URL}
- "3000:3000" # ✅ GEÄNDERT: Standard React/Vite Port # 🔧 ENTFERNT: Port wird in Produktion nicht direkt freigegeben, Zugriff via Proxy
networks: networks:
- infoscreen-net - infoscreen-net
# ✅ GEÄNDERT: Healthcheck für React App
healthcheck: 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 interval: 30s
timeout: 5s timeout: 5s
retries: 3 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: scheduler:
build: build:

View File

@@ -1,16 +1,33 @@
# server/Dockerfile # 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 FROM python:3.13-slim
# --- Arbeitsverzeichnis --- # --- Arbeitsverzeichnis ---
WORKDIR /app WORKDIR /app
# --- Systemabhängigkeiten für MariaDB-Client & Locale --- # Installiert nur die für die Laufzeit notwendigen Systemabhängigkeiten
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
libmariadb-dev-compat libmariadb-dev locales git\ libmariadb-dev-compat locales \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# --- Locale konfigurieren --- # --- 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 \ ENV LANG=de_DE.UTF-8 \
LC_ALL=de_DE.UTF-8 LC_ALL=de_DE.UTF-8
# --- Python-Abhängigkeiten --- # Kopiert die installierten Pakete aus der Builder-Stage
COPY requirements.txt . COPY --from=builder /install /usr/local
RUN pip install --no-cache-dir -r requirements.txt
# --- Applikationscode --- # --- Applikationscode ---
COPY server/ /app # Kopiert den Server-Code in das Arbeitsverzeichnis
COPY server/ .
# --- Non-Root User anlegen und Rechte setzen --- # --- Non-Root User anlegen und Rechte setzen ---
ARG USER_ID=1000 ARG USER_ID=1000
@@ -40,3 +57,4 @@ EXPOSE 8000
# --- Startbefehl für Gunicorn --- # --- Startbefehl für Gunicorn ---
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "wsgi:app"] CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "wsgi:app"]

View File

@@ -1,15 +1,15 @@
# Datei: server/Dockerfile.dev # 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 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 USER_ID=1000
ARG GROUP_ID=1000 ARG GROUP_ID=1000
RUN apt-get update && apt-get install -y --no-install-recommends locales curl git \
# Erstelle non-root User
RUN apt-get update \
&& apt-get install -y --no-install-recommends locales curl \
&& groupadd -g ${GROUP_ID} infoscreen_taa \ && groupadd -g ${GROUP_ID} infoscreen_taa \
&& useradd -u ${USER_ID} -g ${GROUP_ID} --shell /bin/bash --create-home 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 \ && 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/* && rm -rf /var/lib/apt/lists/*
ENV LANG=de_DE.UTF-8 \ ENV LANG=de_DE.UTF-8 \
LANGUAGE=de_DE:de \
LC_ALL=de_DE.UTF-8 LC_ALL=de_DE.UTF-8
# Arbeitsverzeichnis # Setze das Arbeitsverzeichnis auf den Workspace-Root, passend zu den Mounts.
WORKDIR /app WORKDIR /workspace
# Kopiere nur Requirements für schnellen Rebuild # Kopiere die Anforderungsdateien in das korrekte Unterverzeichnis.
COPY requirements.txt ./ # ✅ KORRIGIERT: Pfade sind jetzt relativ zum Build-Kontext (dem 'server'-Verzeichnis)
COPY requirements-dev.txt ./ COPY requirements.txt requirements-dev.txt ./
# Installiere Python-Abhängigkeiten (Prod + Dev) # Installiere die Python-Abhängigkeiten
RUN pip install --upgrade pip \ RUN pip install --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt \ && 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 # Das Kopieren des Codes ist nicht nötig, da das Verzeichnis gemountet wird.
EXPOSE 8000
EXPOSE 5678
# Setze Env für Dev # Exponiere die Ports für die Flask API und den Debugger
ENV FLASK_ENV=development EXPOSE 8000 5678
ENV ENV_FILE=.env
# Wechsle zum non-root User # Der Startbefehl wird in der docker-compose.override.yml definiert.
USER infoscreen_taa # 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"]