feat: Implement frontend production deployment and enhance admin dashboard functionality
This commit is contained in:
@@ -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"]
|
||||
|
153
frontend/PRODUCTION_DEPLOYMENT.md
Normal file
153
frontend/PRODUCTION_DEPLOYMENT.md
Normal 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
|
62
frontend/docker-compose.development.yml
Normal file
62
frontend/docker-compose.development.yml
Normal 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:
|
62
frontend/docker-compose.production.yml
Normal file
62
frontend/docker-compose.production.yml
Normal 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:
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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 [
|
||||
{
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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}
|
||||
/>
|
||||
))
|
||||
|
@@ -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],
|
||||
|
Reference in New Issue
Block a user