diff --git a/api/Dockerfile b/api/Dockerfile index 8df84fa..422ea97 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1 + FROM python:3.11-slim WORKDIR /app @@ -5,6 +7,7 @@ WORKDIR /app # System-Abhängigkeiten installieren RUN apt-get update && apt-get install -y \ gcc \ + curl \ && rm -rf /var/lib/apt/lists/* # Python-Abhängigkeiten installieren diff --git a/collector/Dockerfile b/collector/Dockerfile index 2fa77d8..e63ceb0 100644 --- a/collector/Dockerfile +++ b/collector/Dockerfile @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1 + FROM python:3.13-slim WORKDIR /app diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..d59261d --- /dev/null +++ b/deploy.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +# Deploy Script für laufschrift +# Baut das Docker Image und lädt es zu docker.citysensor.de hoch + +set -e + + +# Konfiguration +REGISTRY="docker.citysensor.de" +PROJEKT="wetterstation" +IMAGE_NAME=("${PROJEKT}-frontend" "${PROJEKT}-collector" "${PROJEKT}-api") +TAG="${TAG:-$(date +%Y%m%d%H%M)}" # default Datum + +# Build-Datum +BUILD_DATE=$(date +%d.%m.%Y) + +echo "==========================================" +echo " Deploy Script" +echo "==========================================" +echo "Registry: ${REGISTRY}" +echo "Images: ${IMAGE_NAME[*]}" +echo "Tag: ${TAG}" +echo "Build-Datum: ${BUILD_DATE}" +echo "==========================================" +echo "" + +# 1. Login zur Registry (falls noch nicht eingeloggt) +echo ">>> Login zu ${REGISTRY}..." +docker login "${REGISTRY}" +echo "" + +# 2. Multiplatform Builder einrichten (docker-container driver erforderlich) +echo ">>> Richte Multiplatform Builder ein..." +if ! docker buildx inspect multiplatform-builder &>/dev/null; then + docker buildx create --name multiplatform-builder --driver docker-container --bootstrap +fi +docker buildx use multiplatform-builder +echo "" + +for image in "${IMAGE_NAME[@]}"; do + # Entferne Projekt-Präfix für Verzeichnisnamen + IMAGE_DIR="${image#${PROJEKT}-}" + FULL_IMAGE="${REGISTRY}/${image}:${TAG}" + + echo "==========================================" + echo ">>> Baue ${image}..." + echo ">>> Image: ${FULL_IMAGE}" + echo "==========================================" + + # Build-Args vorbereiten (für Frontend Version und Build-Date) + BUILD_ARGS="--build-arg BUILD_DATE=${BUILD_DATE}" + if [ "${IMAGE_DIR}" = "frontend" ]; then + VERSION=$(grep '"version"' "${IMAGE_DIR}/package.json" | head -1 | sed 's/.*"version": "\(.*\)".*/\1/') + BUILD_ARGS="${BUILD_ARGS} --build-arg VERSION=${VERSION}" + fi + + # 3. Docker Image bauen und pushen (Multiplatform) + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + ${BUILD_ARGS} \ + -t "${FULL_IMAGE}" \ + --push \ + "./${IMAGE_DIR}" + + # 4. Tagge auch als :latest + echo ">>> Tagge ${image} als :latest..." + docker buildx imagetools create \ + -t "${REGISTRY}/${image}:latest" \ + "${FULL_IMAGE}" + + echo "✓ ${image} erfolgreich gebaut und gepusht!" + echo "" +done + +echo ">>> Alle Builds erfolgreich!" + +echo "" +echo "==========================================" +echo "✓ Deploy erfolgreich abgeschlossen!" +echo "==========================================" +echo "Registry: ${REGISTRY}" +echo "Projekt: ${PROJEKT}" +echo "Tag: ${TAG}" +echo "" +echo "Auf dem Server ausführen:" +echo " docker compose -f docker-compose.prod.yml pull" +echo " docker compose -f docker-compose.prod.yml up -d" +echo "" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 8486575..8089ab4 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -56,6 +56,12 @@ services: networks: - internal - proxy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s labels: - "traefik.enable=true" - "traefik.docker.network=dockge_default" @@ -72,7 +78,8 @@ services: container_name: wetterstation_frontend_prod restart: unless-stopped depends_on: - - api + api: + condition: service_healthy networks: - internal - proxy diff --git a/frontend/Dockerfile b/frontend/Dockerfile index ed37093..ac483b9 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,5 +1,11 @@ +# syntax=docker/dockerfile:1 + # Build stage -FROM node:20-alpine AS builder +FROM --platform=$BUILDPLATFORM node:20-alpine AS builder + +# Build arguments +ARG BUILD_DATE=unknown +ARG VERSION=unknown WORKDIR /app @@ -12,14 +18,18 @@ RUN npm ci # Copy source code COPY . . -# Build app +# Build app with build info +ENV VITE_BUILD_DATE=${BUILD_DATE} +ENV VITE_VERSION=${VERSION} RUN npm run build # Production stage FROM nginx:alpine +WORKDIR /usr/share/nginx/html + # Copy built app from builder -COPY --from=builder /app/dist /usr/share/nginx/html +COPY --from=builder /app/dist . # Copy nginx configuration COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/frontend/nginx.conf b/frontend/nginx.conf index c41aaec..2fc3e3b 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -4,6 +4,10 @@ server { root /usr/share/nginx/html; index index.html; + # Docker DNS resolver für dynamische Service-Auflösung + resolver 127.0.0.11 valid=30s; + resolver_timeout 5s; + # Gzip compression gzip on; gzip_vary on; @@ -12,7 +16,8 @@ server { # API proxy (wird im Docker-Compose-Netzwerk aufgelöst) location /api/ { - proxy_pass http://api:8000/; + set $upstream_api api:8000; + proxy_pass http://$upstream_api/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; diff --git a/frontend/package.json b/frontend/package.json index 679b2d8..85f5b49 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "wetterstation-frontend", "private": true, - "version": "1.0.0", + "version": "1.1.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/components/WeatherDashboard.css b/frontend/src/components/WeatherDashboard.css index 07935e0..e8026f8 100644 --- a/frontend/src/components/WeatherDashboard.css +++ b/frontend/src/components/WeatherDashboard.css @@ -1,6 +1,6 @@ .dashboard { width: 100%; - max-width: 795px; + max-width: 1900px; margin: 0 auto; } @@ -65,3 +65,83 @@ color: #666; font-weight: 500; } + +.dashboard-footer { +/* margin-top: 2rem; +*/ padding-top: 1rem; +} + +.footer-divider { + border: none; + border-top: 1px solid #ccc; + margin: 0 0 1rem 0; +} + +.footer-credits { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.85rem; + color: #666; + margin-bottom: 0.75rem; +} + +.footer-left { + text-align: left; +} + +.footer-right { + text-align: right; +} + +.footer-sponsor { + text-align: center; + font-size: 0.85rem; + color: #666; +} + +.footer-sponsor a { + color: #0066cc; + text-decoration: none; +} + +.footer-sponsor a:hover { + text-decoration: underline; +} + +.version-line { + display: flex; + justify-content: space-between; + margin-bottom: 15px; +} + +.version-short { + display: none; +} + +.version-full { + display: inline; +} + +/* Responsive Design für schmale Bildschirme (Smartphones) */ +@media (max-width: 768px) { + .charts-grid { + grid-template-columns: 1fr; + } + + .current-values { + grid-template-columns: repeat(2, 1fr); + } + + .dashboard { + padding: 0 0.5rem; + } + + .version-short { + display: inline; + } + + .version-full { + display: none; + } +} diff --git a/frontend/src/components/WeatherDashboard.jsx b/frontend/src/components/WeatherDashboard.jsx index 725dd77..fe1c592 100644 --- a/frontend/src/components/WeatherDashboard.jsx +++ b/frontend/src/components/WeatherDashboard.jsx @@ -4,7 +4,9 @@ import HighchartsReact from 'highcharts-react-official' import { format } from 'date-fns' import { de } from 'date-fns/locale' import './WeatherDashboard.css' - +// Build-Informationen (werden beim Build eingef\u00fcgt) +const buildDate = __BUILD_DATE__ +const version = __VERSION__ // Deutsche Lokalisierung für Highcharts Highcharts.setOptions({ lang: { @@ -426,6 +428,30 @@ const WeatherDashboard = ({ data }) => { + + {/* Footer */} +