feat: Implement frontend production deployment and enhance admin dashboard functionality

This commit is contained in:
2025-05-26 21:54:13 +02:00
parent c2ea6c34ea
commit 7aa70cf976
59 changed files with 9161 additions and 10894 deletions

View File

@@ -4,7 +4,7 @@ FROM node:20-bookworm-slim
RUN mkdir -p /usr/src/app
# Set environment variables
ENV PORT=3000
ENV PORT=80
ENV NEXT_TELEMETRY_DISABLED=1
WORKDIR /usr/src/app
@@ -28,7 +28,7 @@ RUN pnpm run db
# Build the application
RUN pnpm run build
EXPOSE 3000
EXPOSE 80
# Start the application
CMD ["/bin/sh", "-c", "if [ ! -f ./db/sqlite.db ]; then pnpm db; fi && pnpm start"]

View File

@@ -0,0 +1,153 @@
# MYP Frontend Produktions-Deployment
## Übersicht
Das Frontend läuft jetzt auf **Port 80/443** mit **selbstsigniertem Zertifikat** über **Caddy** als Reverse Proxy. Port 3000 wurde komplett entfernt.
## Architektur
```
Internet/LAN → Caddy (Port 80/443) → Next.js Frontend (Port 80) → Backend API (raspberrypi:443)
```
## Deployment
### Schnellstart
```bash
cd frontend
./deploy-production.sh
```
### Manuelles Deployment
```bash
cd frontend
# Container stoppen
docker-compose -f docker-compose.production.yml down
# Neu bauen und starten
docker-compose -f docker-compose.production.yml up --build -d
# Status prüfen
docker-compose -f docker-compose.production.yml ps
# Logs anzeigen
docker-compose -f docker-compose.production.yml logs -f
```
## Konfiguration
### SSL-Zertifikate
- **Automatisch generiert**: Caddy generiert automatisch selbstsignierte Zertifikate
- **Speicherort**: `./certs/` (wird automatisch erstellt)
- **Konfiguration**: `tls internal` in der Caddyfile
### Ports
- **HTTP**: Port 80
- **HTTPS**: Port 443
- **Frontend intern**: Port 80 (nicht nach außen exponiert)
### Backend-Verbindung
- **Backend URL**: `https://raspberrypi:443`
- **API Prefix**: `/api/*` wird an Backend weitergeleitet
- **Health Check**: `/health` wird an Backend weitergeleitet
## Dateien
### Wichtige Konfigurationsdateien
- `docker-compose.production.yml` - Produktions-Docker-Konfiguration
- `docker/caddy/Caddyfile` - Caddy Reverse Proxy Konfiguration
- `Dockerfile` - Frontend Container (Port 80)
- `next.config.js` - Next.js Konfiguration (SSL entfernt)
### Verzeichnisstruktur
```
frontend/
├── certs/ # SSL-Zertifikate (automatisch generiert)
├── docker/
│ └── caddy/
│ └── Caddyfile # Caddy Konfiguration
├── docker-compose.production.yml # Produktions-Deployment
├── deploy-production.sh # Deployment-Script
├── Dockerfile # Produktions-Container
└── next.config.js # Next.js Konfiguration
```
## Troubleshooting
### Container Status prüfen
```bash
docker-compose -f docker-compose.production.yml ps
```
### Logs anzeigen
```bash
# Alle Logs
docker-compose -f docker-compose.production.yml logs -f
# Nur Frontend
docker-compose -f docker-compose.production.yml logs -f frontend
# Nur Caddy
docker-compose -f docker-compose.production.yml logs -f caddy
```
### SSL-Zertifikate neu generieren
```bash
# Container stoppen
docker-compose -f docker-compose.production.yml down
# Caddy Daten löschen
docker volume rm frontend_caddy_data frontend_caddy_config
# Neu starten
docker-compose -f docker-compose.production.yml up --build -d
```
### Container neu bauen
```bash
# Alles stoppen und entfernen
docker-compose -f docker-compose.production.yml down --volumes --remove-orphans
# Images entfernen
docker rmi frontend_frontend frontend_caddy
# Neu bauen
docker-compose -f docker-compose.production.yml up --build -d
```
## Sicherheit
### HTTPS-Header
Caddy setzt automatisch sichere HTTP-Header:
- `Strict-Transport-Security`
- `X-Content-Type-Options`
- `X-Frame-Options`
- `Referrer-Policy`
### Netzwerk-Isolation
- Frontend und Caddy laufen in eigenem Docker-Netzwerk
- Nur Ports 80 und 443 sind nach außen exponiert
- Backend-Verbindung über gesichertes HTTPS
## Offline-Betrieb
Das Frontend ist für Offline-Betrieb konfiguriert:
- Keine externen Dependencies zur Laufzeit
- Alle Assets sind im Container enthalten
- Selbstsignierte Zertifikate benötigen keine externe CA

View File

@@ -0,0 +1,62 @@
version: '3'
services:
# Next.js Frontend
frontend:
build:
context: .
dockerfile: Dockerfile
container_name: myp-frontend
restart: unless-stopped
environment:
- NODE_ENV=production
- NEXT_PUBLIC_API_URL=https://raspberrypi:443
- NEXT_PUBLIC_BACKEND_HOST=raspberrypi:443
- PORT=80
volumes:
- ./certs:/app/certs
ports:
- "80"
networks:
- myp-network
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:80/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Caddy Proxy für SSL-Terminierung
caddy:
image: caddy:2.7-alpine
container_name: myp-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile
- ./certs:/etc/caddy/certs
- caddy_data:/data
- caddy_config:/config
networks:
- myp-network
depends_on:
- frontend
extra_hosts:
- "host.docker.internal:host-gateway"
- "raspberrypi:192.168.0.105"
- "m040tbaraspi001.de040.corpintra.net:127.0.0.1"
environment:
- CADDY_HOST=m040tbaraspi001.de040.corpintra.net
- CADDY_DOMAIN=m040tbaraspi001.de040.corpintra.net
cap_add:
- NET_ADMIN
networks:
myp-network:
driver: bridge
volumes:
caddy_data:
caddy_config:

View File

@@ -0,0 +1,62 @@
version: '3.8'
services:
# Next.js Frontend
frontend:
build:
context: .
dockerfile: Dockerfile
container_name: myp-frontend-prod
restart: unless-stopped
environment:
- NODE_ENV=production
- NEXT_PUBLIC_API_URL=https://raspberrypi:443
- NEXT_PUBLIC_BACKEND_HOST=raspberrypi:443
- PORT=80
volumes:
- ./certs:/app/certs
- frontend_data:/usr/src/app/db
networks:
- myp-network
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:80/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Caddy Proxy für SSL-Terminierung
caddy:
image: caddy:2.7-alpine
container_name: myp-caddy-prod
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile
- ./certs:/etc/caddy/certs
- caddy_data:/data
- caddy_config:/config
networks:
- myp-network
depends_on:
- frontend
extra_hosts:
- "host.docker.internal:host-gateway"
- "raspberrypi:192.168.0.105"
- "m040tbaraspi001.de040.corpintra.net:127.0.0.1"
environment:
- CADDY_HOST=m040tbaraspi001.de040.corpintra.net
- CADDY_DOMAIN=m040tbaraspi001.de040.corpintra.net
cap_add:
- NET_ADMIN
networks:
myp-network:
driver: bridge
volumes:
caddy_data:
caddy_config:
frontend_data:

View File

@@ -5,47 +5,47 @@ services:
frontend:
build:
context: .
dockerfile: Dockerfile.dev
container_name: myp-rp-dev
dockerfile: Dockerfile
container_name: myp-frontend
restart: unless-stopped
environment:
- NODE_ENV=development
- NEXT_PUBLIC_API_URL=http://192.168.0.105:5000
- NEXT_PUBLIC_BACKEND_HOST=192.168.0.105:5000
- DEBUG=true
- NEXT_DEBUG=true
- NODE_ENV=production
- NEXT_PUBLIC_API_URL=https://raspberrypi:443
- NEXT_PUBLIC_BACKEND_HOST=raspberrypi:443
- PORT=80
volumes:
- .:/app
- /app/node_modules
- /app/.next
- ./certs:/app/certs
ports:
- "3000:3000"
- "80"
networks:
- myp-network
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3000/health"]
test: ["CMD", "wget", "--spider", "http://localhost:80/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Caddy Proxy (Entwicklung)
# Caddy Proxy für SSL-Terminierung
caddy:
image: caddy:2.7-alpine
container_name: myp-caddy-dev
container_name: myp-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile
- ./certs:/etc/caddy/certs
- caddy_data:/data
- caddy_config:/config
networks:
- myp-network
depends_on:
- frontend
extra_hosts:
- "host.docker.internal:host-gateway"
- "raaspberry:192.168.0.105"
- "raspberrypi:192.168.0.105"
- "m040tbaraspi001.de040.corpintra.net:127.0.0.1"
environment:
- CADDY_HOST=m040tbaraspi001.de040.corpintra.net

View File

@@ -1,13 +1,65 @@
{
debug
auto_https disable_redirects
auto_https off
local_certs
}
# Produktionsumgebung - Spezifischer Hostname für Mercedes-Benz Werk 040 Berlin
# Produktionsumgebung - Frontend auf Port 80/443 mit selbstsigniertem Zertifikat
:80, :443 {
# TLS mit automatisch generierten selbstsignierten Zertifikaten
tls internal {
on_demand
}
# API Anfragen zum Backend (Raspberry Pi) weiterleiten
@api {
path /api/* /health
}
handle @api {
uri strip_prefix /api
reverse_proxy raspberrypi:443 {
transport http {
tls
tls_insecure_skip_verify
}
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
# Alle anderen Anfragen zum Frontend weiterleiten (auf Port 80 intern)
handle {
reverse_proxy frontend:80 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
# OAuth Callbacks
@oauth path /auth/login/callback*
handle @oauth {
header Cache-Control "no-cache"
reverse_proxy frontend:80
}
# Produktions-Header
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
# Spezifische Hostname-Konfiguration für Mercedes-Benz Werk 040 Berlin (falls benötigt)
m040tbaraspi001.de040.corpintra.net {
# TLS mit selbstsignierten Zertifikaten für die Produktionsumgebung
tls /etc/caddy/ssl/frontend.crt /etc/caddy/ssl/frontend.key {
protocols tls1.2 tls1.3
# TLS mit automatisch generierten selbstsignierten Zertifikaten
tls internal {
on_demand
}
# API Anfragen zum Backend (Raspberry Pi) weiterleiten
@@ -30,7 +82,7 @@ m040tbaraspi001.de040.corpintra.net {
# Alle anderen Anfragen zum Frontend weiterleiten
handle {
reverse_proxy myp-rp-dev:3000 {
reverse_proxy frontend:80 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
@@ -42,7 +94,7 @@ m040tbaraspi001.de040.corpintra.net {
@oauth path /auth/login/callback*
handle @oauth {
header Cache-Control "no-cache"
reverse_proxy myp-rp-dev:3000
reverse_proxy frontend:80
}
# Produktions-Header

View File

@@ -1,28 +1,10 @@
/** @type {import('next').NextConfig} */
const fs = require('fs');
const path = require('path');
const nextConfig = {
reactStrictMode: true,
webpack: (config) => {
return config;
},
// HTTPS-Konfiguration für die Entwicklung
devServer: {
https: {
key: fs.readFileSync(path.resolve(__dirname, 'ssl/myp.key')),
cert: fs.readFileSync(path.resolve(__dirname, 'ssl/myp.crt')),
},
},
// Konfiguration für selbstsignierte Zertifikate
serverOptions: {
https: {
key: fs.readFileSync(path.resolve(__dirname, 'ssl/myp.key')),
cert: fs.readFileSync(path.resolve(__dirname, 'ssl/myp.crt')),
},
},
// Zusätzliche Konfigurationen
// Zusätzliche Konfigurationen für Backend-Weiterleitung
async rewrites() {
return [
{

View File

@@ -59,3 +59,67 @@
--chart-5: 340 75% 55%;
}
}
@layer utilities {
/* Enhanced Glassmorphism Effects */
.glass-light {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px) saturate(180%) brightness(110%);
-webkit-backdrop-filter: blur(20px) saturate(180%) brightness(110%);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.glass-dark {
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(20px) saturate(180%) brightness(110%);
-webkit-backdrop-filter: blur(20px) saturate(180%) brightness(110%);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.05);
}
.glass-strong {
backdrop-filter: blur(24px) saturate(200%) brightness(120%);
-webkit-backdrop-filter: blur(24px) saturate(200%) brightness(120%);
box-shadow: 0 35px 60px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.glass-subtle {
backdrop-filter: blur(16px) saturate(150%) brightness(105%);
-webkit-backdrop-filter: blur(16px) saturate(150%) brightness(105%);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.05);
}
.glass-card {
@apply glass-light dark:glass-dark rounded-xl transition-all duration-300;
}
.glass-navbar {
@apply glass-strong rounded-none;
background: rgba(255, 255, 255, 0.5);
}
.dark .glass-navbar {
background: rgba(0, 0, 0, 0.5);
}
}
@layer components {
/* Enhanced Card Components */
.enhanced-card {
@apply glass-card p-6 hover:shadow-glass-xl transform hover:-translate-y-1 transition-all duration-300;
}
.enhanced-navbar {
@apply glass-navbar border-b border-white/20 dark:border-black/20;
}
.enhanced-dropdown {
@apply glass-strong rounded-xl border border-white/20 dark:border-white/10;
background: rgba(255, 255, 255, 0.8);
}
.dark .enhanced-dropdown {
background: rgba(0, 0, 0, 0.8);
}
}

View File

@@ -9,9 +9,16 @@ const Card = React.forwardRef<
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
"rounded-xl border bg-white/70 dark:bg-black/70 text-card-foreground shadow-glass backdrop-blur-xl",
"border-white/20 dark:border-white/10",
"hover:shadow-glass-lg transform hover:-translate-y-1 transition-all duration-300",
className
)}
style={{
backdropFilter: 'blur(20px) saturate(180%) brightness(110%)',
WebkitBackdropFilter: 'blur(20px) saturate(180%) brightness(110%)',
boxShadow: '0 25px 50px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.1)',
}}
{...props}
/>
))

View File

@@ -202,6 +202,17 @@ module.exports = {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
backdropBlur: {
'xs': '2px',
'3xl': '64px',
'4xl': '80px',
},
boxShadow: {
'glass': '0 8px 32px 0 rgba(31, 38, 135, 0.37)',
'glass-dark': '0 8px 32px 0 rgba(0, 0, 0, 0.37)',
'glass-lg': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
'glass-xl': '0 35px 60px -12px rgba(0, 0, 0, 0.3)',
},
},
},
plugins: [require("tailwindcss-animate"), ...tremor.plugins],