feat: Implement SSL support and kiosk mode enhancements
- Added SSL configuration to the backend, including self-signed certificate generation and management. - Updated `setup_myp.sh` to create SSL certificates during installation. - Enhanced `app.py` to support SSL context for secure communication. - Introduced a new SSL management menu in the setup script for easier certificate handling. - Updated frontend API calls to use HTTPS for secure data transmission. - Implemented kiosk mode features, including automatic browser launch with SSL support. - Improved documentation in `SUMMARY.md` to reflect new features and network topology changes.
This commit is contained in:
parent
af3761707a
commit
0d5b87f163
85
SUMMARY.md
85
SUMMARY.md
@ -10,6 +10,8 @@ Die MYP-Plattform (My Young Printer) ist ein Verwaltungssystem für 3D-Drucker m
|
|||||||
- Offline-Fähigkeit durch Service Worker Integration
|
- Offline-Fähigkeit durch Service Worker Integration
|
||||||
- REST-API für Frontend und externe Dienste
|
- REST-API für Frontend und externe Dienste
|
||||||
- Automatisierte Auftrags-Planung und -Ausführung via Scheduler
|
- Automatisierte Auftrags-Planung und -Ausführung via Scheduler
|
||||||
|
- Verschlüsselte Kommunikation über HTTPS mit selbstsignierten Zertifikaten
|
||||||
|
- Kiosk-Modus für Informationsdisplays und öffentliche Terminals
|
||||||
|
|
||||||
Das Backend basiert auf Flask (Python) und nutzt eine SQLite-Datenbank für die Datenspeicherung.
|
Das Backend basiert auf Flask (Python) und nutzt eine SQLite-Datenbank für die Datenspeicherung.
|
||||||
|
|
||||||
@ -24,22 +26,40 @@ Die MYP-Plattform setzt auf eine verteilte Netzwerk-Architektur:
|
|||||||
- **3D-Drucker**: Werden als Netzwerkgeräte mit festen IP-Adressen konfiguriert
|
- **3D-Drucker**: Werden als Netzwerkgeräte mit festen IP-Adressen konfiguriert
|
||||||
- **Frontend**: Als Web-App mit Offline-Funktionalität konzipiert
|
- **Frontend**: Als Web-App mit Offline-Funktionalität konzipiert
|
||||||
- **PWA-Funktionalität**: Service Worker für Offline-Betrieb und Cache-Management
|
- **PWA-Funktionalität**: Service Worker für Offline-Betrieb und Cache-Management
|
||||||
|
- **Kiosk-Displays**: Raspberry Pi mit Chrome im Kiosk-Modus
|
||||||
|
|
||||||
### Netzwerk-Topologie
|
### Netzwerk-Topologie
|
||||||
|
|
||||||
```
|
```
|
||||||
[Frontend/PWA] <-- HTTP/REST --> [Flask-Backend] <-- PyP110-Lib --> [Smart-Plugs]
|
┌──────────────────┐
|
||||||
^ ^ |
|
│ Command Center │
|
||||||
| | v
|
│ (192.168.0.1) │
|
||||||
+------ PWA Offline Cache -------+ [3D-Drucker]
|
└──────────┬───────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌───────────────┐ ┌─────────────────────┐ ┌──────────────┐
|
||||||
|
│ Frontend/PWA │◄────►│ Flask-Backend │◄────►│ Smart-Plugs │
|
||||||
|
│(192.168.0.105)│ │ (192.168.0.105) │ │(192.168.0.1xx)│
|
||||||
|
└───────┬───────┘ └─────────┬───────────┘ └──────┬────────┘
|
||||||
|
│ │ │
|
||||||
|
│ │ ▼
|
||||||
|
┌───────▼───────┐ │ ┌────────────────┐
|
||||||
|
│ Kiosk-Mode │ │ │ 3D-Drucker │
|
||||||
|
│ (10.0.0.1-10) │ │ │(192.168.0.1xx) │
|
||||||
|
└───────────────┘ ▼ └────────────────┘
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ Ad-hoc Pi-Netzwerk │
|
||||||
|
│ (10.0.0.x/24) │
|
||||||
|
└─────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
### Kommunikationsprotokolle
|
### Kommunikationsprotokolle
|
||||||
|
|
||||||
- **HTTP/REST**: Zwischen Frontend und Backend
|
- **HTTPS/REST**: Zwischen Frontend und Backend (Port 5000)
|
||||||
- **JSON**: Standardformat für Datenaustausch
|
- **JSON**: Standardformat für Datenaustausch
|
||||||
- **Tapo-Protokoll**: Für Smart-Plug-Steuerung via PyP110-Bibliothek
|
- **Tapo-Protokoll**: Für Smart-Plug-Steuerung via PyP110-Bibliothek
|
||||||
- **SQLite**: Lokale Datenbankanbindung
|
- **SQLite**: Lokale Datenbankanbindung
|
||||||
|
- **TLS 1.2/1.3**: Verschlüsselte Kommunikation mit selbstsignierten Zertifikaten
|
||||||
|
|
||||||
### Vernetzungs-Features
|
### Vernetzungs-Features
|
||||||
|
|
||||||
@ -47,6 +67,8 @@ Die MYP-Plattform setzt auf eine verteilte Netzwerk-Architektur:
|
|||||||
- SmartPlug-Integration für Fernsteuerung der Stromversorgung
|
- SmartPlug-Integration für Fernsteuerung der Stromversorgung
|
||||||
- Offline-Betriebsmodus mit synchronisierenden Service Workern
|
- Offline-Betriebsmodus mit synchronisierenden Service Workern
|
||||||
- Automatische Erkennung der Drucker-Status via Netzwerk-Polling
|
- Automatische Erkennung der Drucker-Status via Netzwerk-Polling
|
||||||
|
- Kiosk-Modus mit automatischem Start nach Systemneustart
|
||||||
|
- Command Center für zentrale Verwaltung aller Komponenten
|
||||||
|
|
||||||
## 3. Hauptkomponenten des Backends
|
## 3. Hauptkomponenten des Backends
|
||||||
|
|
||||||
@ -64,6 +86,7 @@ Das Backend stellt eine umfassende REST-API bereit:
|
|||||||
- **Printer Management**: Druckerstatus, Steuerung
|
- **Printer Management**: Druckerstatus, Steuerung
|
||||||
- **Job Management**: Auftragsplanung, -verwaltung und -überwachung
|
- **Job Management**: Auftragsplanung, -verwaltung und -überwachung
|
||||||
- **Stats API**: Statistiken und Auswertungen
|
- **Stats API**: Statistiken und Auswertungen
|
||||||
|
- **Kiosk API**: Steuerung der Kiosk-Displays
|
||||||
|
|
||||||
### SmartPlug-Integration
|
### SmartPlug-Integration
|
||||||
|
|
||||||
@ -71,6 +94,13 @@ Das Backend stellt eine umfassende REST-API bereit:
|
|||||||
- Automatisierte Steuerung der Stromversorgung basierend auf Jobplanung
|
- Automatisierte Steuerung der Stromversorgung basierend auf Jobplanung
|
||||||
- Status-Monitoring und Fehlerbehandlung
|
- Status-Monitoring und Fehlerbehandlung
|
||||||
|
|
||||||
|
### SSL/HTTPS-Implementierung
|
||||||
|
|
||||||
|
- Selbstsignierte Zertifikate für verschlüsselte Kommunikation
|
||||||
|
- Automatische Zertifikatsgenerierung während der Installation
|
||||||
|
- Konfigurierbare Zertifikatslaufzeit (Standard: 10 Jahre)
|
||||||
|
- Prüfwerkzeuge für Zertifikatsstatus und -gültigkeit
|
||||||
|
|
||||||
## 4. Datenbankmodell & Scheduler-Logik
|
## 4. Datenbankmodell & Scheduler-Logik
|
||||||
|
|
||||||
### Datenbankmodelle
|
### Datenbankmodelle
|
||||||
@ -111,6 +141,9 @@ Der BackgroundTaskScheduler bietet:
|
|||||||
| /api/scheduler/status | GET | Scheduler-Status | Ja |
|
| /api/scheduler/status | GET | Scheduler-Status | Ja |
|
||||||
| /api/scheduler/start | POST | Scheduler starten | Ja, Admin |
|
| /api/scheduler/start | POST | Scheduler starten | Ja, Admin |
|
||||||
| /api/scheduler/stop | POST | Scheduler stoppen | Ja, Admin |
|
| /api/scheduler/stop | POST | Scheduler stoppen | Ja, Admin |
|
||||||
|
| /api/kiosk/status | GET | Kiosk-Status | Ja |
|
||||||
|
| /api/kiosk/activate | POST | Kiosk aktivieren | Ja, Admin |
|
||||||
|
| /api/kiosk/deactivate | POST | Kiosk deaktivieren | Ja, Admin |
|
||||||
| /auth/login | POST | Anmelden | Nein |
|
| /auth/login | POST | Anmelden | Nein |
|
||||||
| /auth/logout | GET/POST | Abmelden | Ja |
|
| /auth/logout | GET/POST | Abmelden | Ja |
|
||||||
|
|
||||||
@ -125,6 +158,7 @@ Der BackgroundTaskScheduler bietet:
|
|||||||
|
|
||||||
### Netzwerksicherheit
|
### Netzwerksicherheit
|
||||||
|
|
||||||
|
- HTTPS mit selbstsignierten TLS-Zertifikaten
|
||||||
- CORS-Konfiguration für sichere Cross-Origin-Requests
|
- CORS-Konfiguration für sichere Cross-Origin-Requests
|
||||||
- Sicherheitsheader im Response (X-Content-Type-Options, X-Frame-Options)
|
- Sicherheitsheader im Response (X-Content-Type-Options, X-Frame-Options)
|
||||||
- Keine sensiblen Daten in URLs oder Query-Parametern
|
- Keine sensiblen Daten in URLs oder Query-Parametern
|
||||||
@ -137,17 +171,57 @@ Der BackgroundTaskScheduler bietet:
|
|||||||
|
|
||||||
## 7. Build- & Deployment-Ablauf
|
## 7. Build- & Deployment-Ablauf
|
||||||
|
|
||||||
|
### Installationsprozess
|
||||||
|
|
||||||
|
Die Installation der MYP-Plattform erfolgt über verschiedene Shell-Skripte:
|
||||||
|
|
||||||
|
- **setup_myp.sh**: Hauptinstallationsskript (Command Center)
|
||||||
|
- Standardinstallation mit HTTPS-Unterstützung
|
||||||
|
- Kiosk-Modus-Installation (gehärtete Variante)
|
||||||
|
- Netzwerk- und DNS-Konfiguration
|
||||||
|
- Systemüberwachung und Logging
|
||||||
|
|
||||||
|
- **create_ssl_cert.sh**: Generiert selbstsignierte Zertifikate
|
||||||
|
- Unterstützt Multiple-SAN-Entries (Hostname, IP-Adressen)
|
||||||
|
- Konfigurierbare Zertifikatslaufzeit
|
||||||
|
- Automatische Fehlerbehandlung
|
||||||
|
|
||||||
|
- **ssl_check.sh**: Prüft SSL-Zertifikatsstatus
|
||||||
|
- Validiert Gültigkeitsdauer
|
||||||
|
- Zeigt Fingerprint und Subject-Details
|
||||||
|
- Warnt vor bald ablaufenden Zertifikaten
|
||||||
|
|
||||||
### Backend-Deployment
|
### Backend-Deployment
|
||||||
|
|
||||||
- Python 3.11 venv-Umgebung
|
- Python 3.11 venv-Umgebung
|
||||||
- Konfigurierbare Entwicklungs- und Produktionsumgebungen
|
- Konfigurierbare Entwicklungs- und Produktionsumgebungen
|
||||||
- Log-Rotation und strukturierte Logging-Hierarchie
|
- Log-Rotation und strukturierte Logging-Hierarchie
|
||||||
|
- SSL/TLS-Unterstützung mit automatischer Zertifikatsgenerierung
|
||||||
|
|
||||||
|
### Kiosk-Modus
|
||||||
|
|
||||||
|
- Basiert auf Raspberry Pi mit Chromium Browser
|
||||||
|
- Automatischer Start im Vollbildmodus nach Boot
|
||||||
|
- Deaktivierung von Fehlerdialogen und Warnungen
|
||||||
|
- Integrierte Überwachung und Watchdog-Funktionalität
|
||||||
|
- Spezielle Konfiguration für öffentliche Informationsdisplays
|
||||||
|
- Ignoriert SSL-Zertifikatswarnungen für selbstsignierte Zertifikate
|
||||||
|
|
||||||
|
### Command Center
|
||||||
|
|
||||||
|
- Zentrales Verwaltungstool mit CLI-Interface
|
||||||
|
- Integrierte Systemdiagnose und -überwachung
|
||||||
|
- Einfache Verwaltung von SSL-Zertifikaten
|
||||||
|
- Fernsteuerung von Kiosk-Displays
|
||||||
|
- Netzwerk- und IP-Konfiguration
|
||||||
|
- Dienst-Management (Start/Stop/Neustart)
|
||||||
|
|
||||||
### Frontend-Integration
|
### Frontend-Integration
|
||||||
|
|
||||||
- Tailwind CSS für responsive UI
|
- Tailwind CSS für responsive UI
|
||||||
- CLI-Befehle für Tailwind-Kompilierung
|
- CLI-Befehle für Tailwind-Kompilierung
|
||||||
- Service Worker für PWA-Funktionalität
|
- Service Worker für PWA-Funktionalität
|
||||||
|
- HTTPS-Unterstützung mit Proxy-Konfiguration
|
||||||
|
|
||||||
### Systemd-Integration
|
### Systemd-Integration
|
||||||
|
|
||||||
@ -169,6 +243,7 @@ Der BackgroundTaskScheduler bietet:
|
|||||||
- OAuth2-Integration für externe Authentifizierungsquellen
|
- OAuth2-Integration für externe Authentifizierungsquellen
|
||||||
- Zwei-Faktor-Authentifizierung
|
- Zwei-Faktor-Authentifizierung
|
||||||
- Zertifikatsbasierte Geräteauthentifizierung für SmartPlugs
|
- Zertifikatsbasierte Geräteauthentifizierung für SmartPlugs
|
||||||
|
- Letsencrypt-Integration für vertrauenswürdige Zertifikate
|
||||||
|
|
||||||
### Skalierbarkeit
|
### Skalierbarkeit
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ from flask_wtf.csrf import CSRFProtect
|
|||||||
from config.settings import (
|
from config.settings import (
|
||||||
SECRET_KEY, TAPO_USERNAME, TAPO_PASSWORD, PRINTERS,
|
SECRET_KEY, TAPO_USERNAME, TAPO_PASSWORD, PRINTERS,
|
||||||
FLASK_HOST, FLASK_PORT, FLASK_DEBUG, SESSION_LIFETIME,
|
FLASK_HOST, FLASK_PORT, FLASK_DEBUG, SESSION_LIFETIME,
|
||||||
SCHEDULER_INTERVAL, SCHEDULER_ENABLED
|
SCHEDULER_INTERVAL, SCHEDULER_ENABLED, get_ssl_context
|
||||||
)
|
)
|
||||||
from utils.logging_config import setup_logging, get_logger, log_startup_info
|
from utils.logging_config import setup_logging, get_logger, log_startup_info
|
||||||
from models import User, Printer, Job, Stats, get_db_session, init_database, create_initial_admin
|
from models import User, Printer, Job, Stats, get_db_session, init_database, create_initial_admin
|
||||||
@ -1265,16 +1265,19 @@ def tailwind_watch():
|
|||||||
|
|
||||||
# Auto-Kompilierung beim Serverstart im Debug-Modus
|
# Auto-Kompilierung beim Serverstart im Debug-Modus
|
||||||
def compile_tailwind_if_debug():
|
def compile_tailwind_if_debug():
|
||||||
if app.debug:
|
"""Kompiliert Tailwind CSS im Debug-Modus, falls notwendig."""
|
||||||
|
if FLASK_DEBUG:
|
||||||
try:
|
try:
|
||||||
subprocess.run(["npx", "tailwindcss", "-i", "./static/css/input.css",
|
app_logger.info("Kompiliere Tailwind CSS...")
|
||||||
"-o", "./static/css/tailwind-dark-consolidated.min.css"],
|
subprocess.run([
|
||||||
check=True, capture_output=True)
|
"npx", "tailwindcss", "-i", "static/css/input.css",
|
||||||
print("Tailwind CSS für Debug-Modus kompiliert.")
|
"-o", "static/css/tailwind.min.css", "--minify"
|
||||||
except subprocess.CalledProcessError as e:
|
], check=True)
|
||||||
print(f"Warnung: Konnte Tailwind CSS nicht kompilieren: {e}")
|
app_logger.info("Tailwind CSS erfolgreich kompiliert.")
|
||||||
except FileNotFoundError:
|
except subprocess.CalledProcessError:
|
||||||
print("Warnung: Node.js/npm nicht gefunden. Tailwind CSS wurde nicht kompiliert.")
|
app_logger.warning("Tailwind konnte nicht kompiliert werden. Möglicherweise ist npx/Node.js nicht installiert.")
|
||||||
|
except Exception as e:
|
||||||
|
app_logger.error(f"Fehler beim Kompilieren von Tailwind CSS: {str(e)}")
|
||||||
|
|
||||||
# Tailwind CSS kompilieren, wenn im Debug-Modus
|
# Tailwind CSS kompilieren, wenn im Debug-Modus
|
||||||
if FLASK_DEBUG:
|
if FLASK_DEBUG:
|
||||||
@ -1282,34 +1285,65 @@ if FLASK_DEBUG:
|
|||||||
|
|
||||||
# Initialisierung der Datenbank beim Start
|
# Initialisierung der Datenbank beim Start
|
||||||
def init_app():
|
def init_app():
|
||||||
try:
|
"""Initialisiert die App-Komponenten und startet den Scheduler."""
|
||||||
# Datenbank initialisieren
|
# Datenbank initialisieren
|
||||||
|
try:
|
||||||
init_database()
|
init_database()
|
||||||
# Admin-Benutzer erstellen oder zurücksetzen
|
|
||||||
create_initial_admin()
|
create_initial_admin()
|
||||||
|
except Exception as e:
|
||||||
|
app_logger.error(f"Fehler bei der Datenbank-Initialisierung: {str(e)}")
|
||||||
|
|
||||||
# Template-Helper registrieren
|
# Jinja2-Helfer registrieren
|
||||||
register_template_helpers(app)
|
register_template_helpers(app)
|
||||||
app_logger.info("Template-Helper registriert")
|
|
||||||
|
# Tailwind im Debug-Modus kompilieren
|
||||||
|
compile_tailwind_if_debug()
|
||||||
|
|
||||||
# Scheduler starten, wenn aktiviert
|
# Scheduler starten, wenn aktiviert
|
||||||
if SCHEDULER_ENABLED:
|
if SCHEDULER_ENABLED:
|
||||||
|
try:
|
||||||
|
# Scheduler-Task für Druckauftrags-Prüfung registrieren
|
||||||
|
scheduler.register_task(
|
||||||
|
"check_jobs",
|
||||||
|
check_jobs,
|
||||||
|
interval=SCHEDULER_INTERVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
# Scheduler starten
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
app_logger.info("Job-Scheduler gestartet")
|
app_logger.info(f"Scheduler gestartet mit Intervall {SCHEDULER_INTERVAL} Sekunden.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app_logger.error(f"Fehler bei der Initialisierung: {str(e)}")
|
app_logger.error(f"Fehler beim Starten des Schedulers: {str(e)}")
|
||||||
|
|
||||||
|
# SSL-Kontext protokollieren
|
||||||
|
ssl_context = get_ssl_context()
|
||||||
|
if ssl_context:
|
||||||
|
app_logger.info(f"SSL aktiviert mit Zertifikat {ssl_context[0]}")
|
||||||
|
else:
|
||||||
|
app_logger.warning("SSL ist deaktiviert. Die Verbindung ist unverschlüsselt!")
|
||||||
|
|
||||||
# App starten
|
# App starten
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Initialisierung ausführen
|
try:
|
||||||
|
# App initialisieren
|
||||||
init_app()
|
init_app()
|
||||||
|
|
||||||
# Flask-Server starten
|
# SSL-Kontext ermitteln
|
||||||
|
ssl_context = get_ssl_context()
|
||||||
|
|
||||||
|
# Konsolen-Ausgabe für HTTPS
|
||||||
|
protocol = "HTTPS" if ssl_context else "HTTP"
|
||||||
|
app_logger.info(f"MYP startet auf {protocol}://{FLASK_HOST}:{FLASK_PORT} (Debug: {FLASK_DEBUG})")
|
||||||
|
|
||||||
|
# App starten
|
||||||
app.run(
|
app.run(
|
||||||
host=FLASK_HOST,
|
host=FLASK_HOST,
|
||||||
port=FLASK_PORT,
|
port=FLASK_PORT,
|
||||||
debug=FLASK_DEBUG
|
debug=FLASK_DEBUG,
|
||||||
|
ssl_context=ssl_context
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
app_logger.critical(f"Kritischer Fehler beim Starten der Anwendung: {str(e)}")
|
||||||
|
|
||||||
# Content Security Policy anpassen
|
# Content Security Policy anpassen
|
||||||
@app.after_request
|
@app.after_request
|
||||||
|
@ -31,6 +31,11 @@ FLASK_PORT = 5000
|
|||||||
FLASK_DEBUG = True
|
FLASK_DEBUG = True
|
||||||
SESSION_LIFETIME = timedelta(days=7)
|
SESSION_LIFETIME = timedelta(days=7)
|
||||||
|
|
||||||
|
# SSL-Konfiguration
|
||||||
|
SSL_ENABLED = True
|
||||||
|
SSL_CERT_PATH = "/opt/myp/ssl/myp.crt"
|
||||||
|
SSL_KEY_PATH = "/opt/myp/ssl/myp.key"
|
||||||
|
|
||||||
# Scheduler-Konfiguration
|
# Scheduler-Konfiguration
|
||||||
SCHEDULER_INTERVAL = 60 # Sekunden
|
SCHEDULER_INTERVAL = 60 # Sekunden
|
||||||
SCHEDULER_ENABLED = True
|
SCHEDULER_ENABLED = True
|
||||||
@ -64,3 +69,46 @@ def ensure_database_directory():
|
|||||||
db_dir = os.path.dirname(DATABASE_PATH)
|
db_dir = os.path.dirname(DATABASE_PATH)
|
||||||
if db_dir:
|
if db_dir:
|
||||||
os.makedirs(db_dir, exist_ok=True)
|
os.makedirs(db_dir, exist_ok=True)
|
||||||
|
|
||||||
|
def ensure_ssl_directory():
|
||||||
|
"""Erstellt das SSL-Verzeichnis, falls es nicht existiert."""
|
||||||
|
ssl_dir = os.path.dirname(SSL_CERT_PATH)
|
||||||
|
if ssl_dir and not os.path.exists(ssl_dir):
|
||||||
|
os.makedirs(ssl_dir, exist_ok=True)
|
||||||
|
|
||||||
|
def get_ssl_context():
|
||||||
|
"""
|
||||||
|
Gibt den SSL-Kontext für Flask zurück, wenn SSL aktiviert ist.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple oder None: Tuple mit Zertifikat- und Schlüsselpfad, wenn SSL aktiviert ist, sonst None
|
||||||
|
"""
|
||||||
|
if not SSL_ENABLED:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Wenn Zertifikate nicht existieren, diese automatisch erstellen
|
||||||
|
if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH):
|
||||||
|
ensure_ssl_directory()
|
||||||
|
|
||||||
|
# Prüfen, ob wir uns im Entwicklungsmodus befinden
|
||||||
|
if FLASK_DEBUG:
|
||||||
|
print("SSL-Zertifikate nicht gefunden. Erstelle selbstsignierte Zertifikate...")
|
||||||
|
|
||||||
|
# Pfad zum create_ssl_cert.sh-Skript ermitteln
|
||||||
|
script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
||||||
|
"install", "create_ssl_cert.sh")
|
||||||
|
|
||||||
|
# Ausführungsrechte setzen
|
||||||
|
if os.path.exists(script_path):
|
||||||
|
os.system(f"chmod +x {script_path}")
|
||||||
|
|
||||||
|
# Zertifikate erstellen
|
||||||
|
os.system(f"{script_path} -c {SSL_CERT_PATH} -k {SSL_KEY_PATH}")
|
||||||
|
else:
|
||||||
|
print(f"WARNUNG: SSL-Zertifikat-Generator nicht gefunden: {script_path}")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
print("WARNUNG: SSL-Zertifikate nicht gefunden und Nicht-Debug-Modus. SSL wird deaktiviert.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return (SSL_CERT_PATH, SSL_KEY_PATH)
|
@ -87,7 +87,7 @@ self.addEventListener('fetch', (event) => {
|
|||||||
const { request } = event;
|
const { request } = event;
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
|
|
||||||
// Skip non-GET requests and unsupported schemes for caching
|
// Unterstütze sowohl HTTP als auch HTTPS
|
||||||
if (request.method !== 'GET' ||
|
if (request.method !== 'GET' ||
|
||||||
(url.protocol !== 'http:' && url.protocol !== 'https:')) {
|
(url.protocol !== 'http:' && url.protocol !== 'https:')) {
|
||||||
return;
|
return;
|
||||||
|
160
backend/install/create_ssl_cert.sh
Executable file
160
backend/install/create_ssl_cert.sh
Executable file
@ -0,0 +1,160 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# MYP V2 - SSL-Zertifikat-Generator
|
||||||
|
# Erstellt selbstsignierte Zertifikate für die HTTPS-Kommunikation
|
||||||
|
|
||||||
|
# Fehlerabbruch aktivieren
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Farben für bessere Lesbarkeit
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Funktion für Titel
|
||||||
|
print_header() {
|
||||||
|
echo -e "${BLUE}================================================================${NC}"
|
||||||
|
echo -e "${BLUE} MYP V2 - SSL-Zertifikat-Generator ${NC}"
|
||||||
|
echo -e "${BLUE}================================================================${NC}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Standardwerte
|
||||||
|
CERT_DIR="/opt/myp/ssl"
|
||||||
|
CERT_FILE="$CERT_DIR/myp.crt"
|
||||||
|
KEY_FILE="$CERT_DIR/myp.key"
|
||||||
|
DAYS_VALID=3650 # 10 Jahre
|
||||||
|
HOSTNAME=$(hostname -f)
|
||||||
|
IP_ADDRESS=$(hostname -I | awk '{print $1}')
|
||||||
|
|
||||||
|
# Hilfe-Funktion
|
||||||
|
show_help() {
|
||||||
|
echo "Verwendung: $0 [Optionen]"
|
||||||
|
echo ""
|
||||||
|
echo "Optionen:"
|
||||||
|
echo " -d, --dir DIR Verzeichnis für Zertifikate (Standard: $CERT_DIR)"
|
||||||
|
echo " -c, --cert DATEI Pfad zur Zertifikatsdatei (Standard: $CERT_FILE)"
|
||||||
|
echo " -k, --key DATEI Pfad zur Schlüsseldatei (Standard: $KEY_FILE)"
|
||||||
|
echo " -h, --hostname NAME Hostname für das Zertifikat (Standard: $HOSTNAME)"
|
||||||
|
echo " -i, --ip IP IP-Adresse für das Zertifikat (Standard: $IP_ADDRESS)"
|
||||||
|
echo " -v, --valid TAGE Gültigkeitsdauer in Tagen (Standard: $DAYS_VALID)"
|
||||||
|
echo " --help Diese Hilfe anzeigen"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Argumente verarbeiten
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-d|--dir)
|
||||||
|
CERT_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-c|--cert)
|
||||||
|
CERT_FILE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-k|--key)
|
||||||
|
KEY_FILE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--hostname)
|
||||||
|
HOSTNAME="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-i|--ip)
|
||||||
|
IP_ADDRESS="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-v|--valid)
|
||||||
|
DAYS_VALID="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Unbekannte Option: $1${NC}"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Header anzeigen
|
||||||
|
print_header
|
||||||
|
|
||||||
|
# Verzeichnis erstellen, falls es nicht existiert
|
||||||
|
if [ ! -d "$CERT_DIR" ]; then
|
||||||
|
echo -e "${YELLOW}Erstelle Verzeichnis $CERT_DIR...${NC}"
|
||||||
|
mkdir -p "$CERT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Überprüfen, ob openssl installiert ist
|
||||||
|
if ! command -v openssl &> /dev/null; then
|
||||||
|
echo -e "${RED}OpenSSL ist nicht installiert!${NC}"
|
||||||
|
echo -e "${YELLOW}Installiere OpenSSL...${NC}"
|
||||||
|
apt-get update && apt-get install -y openssl
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Zertifikat erstellen
|
||||||
|
echo -e "${GREEN}Erstelle selbstsigniertes SSL-Zertifikat...${NC}"
|
||||||
|
echo -e "${BLUE}Hostname: ${NC}$HOSTNAME"
|
||||||
|
echo -e "${BLUE}IP-Adresse: ${NC}$IP_ADDRESS"
|
||||||
|
echo -e "${BLUE}Gültigkeitsdauer: ${NC}$DAYS_VALID Tage"
|
||||||
|
echo -e "${BLUE}Zertifikatsdatei: ${NC}$CERT_FILE"
|
||||||
|
echo -e "${BLUE}Schlüsseldatei: ${NC}$KEY_FILE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# OpenSSL-Konfiguration erstellen
|
||||||
|
CONFIG_FILE="$CERT_DIR/openssl.cnf"
|
||||||
|
cat > "$CONFIG_FILE" << EOF
|
||||||
|
[req]
|
||||||
|
default_bits = 2048
|
||||||
|
prompt = no
|
||||||
|
default_md = sha256
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
x509_extensions = v3_req
|
||||||
|
|
||||||
|
[req_distinguished_name]
|
||||||
|
C = DE
|
||||||
|
ST = Baden-Wuerttemberg
|
||||||
|
L = Stuttgart
|
||||||
|
O = Mercedes-Benz AG
|
||||||
|
OU = MYP Platform
|
||||||
|
CN = $HOSTNAME
|
||||||
|
|
||||||
|
[v3_req]
|
||||||
|
keyUsage = critical, digitalSignature, keyAgreement
|
||||||
|
extendedKeyUsage = serverAuth
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = $HOSTNAME
|
||||||
|
DNS.2 = localhost
|
||||||
|
IP.1 = $IP_ADDRESS
|
||||||
|
IP.2 = 127.0.0.1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Schlüssel und Zertifikat generieren
|
||||||
|
openssl req -x509 -nodes -days "$DAYS_VALID" -newkey rsa:2048 \
|
||||||
|
-keyout "$KEY_FILE" -out "$CERT_FILE" \
|
||||||
|
-config "$CONFIG_FILE"
|
||||||
|
|
||||||
|
# Berechtigungen setzen
|
||||||
|
chmod 600 "$KEY_FILE"
|
||||||
|
chmod 644 "$CERT_FILE"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}SSL-Zertifikat erfolgreich erstellt!${NC}"
|
||||||
|
echo -e "${YELLOW}Fingerprint:${NC}"
|
||||||
|
openssl x509 -noout -fingerprint -sha256 -in "$CERT_FILE"
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}Um diese Zertifikate mit Flask zu verwenden:${NC}"
|
||||||
|
echo " 1. Importiere die SSL-Einstellungen in der app.py"
|
||||||
|
echo " 2. Starte Flask mit SSL-Unterstützung"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Beispiel:${NC}"
|
||||||
|
echo " app.run(host='0.0.0.0', port=5000, ssl_context=('$CERT_FILE', '$KEY_FILE'))"
|
||||||
|
echo ""
|
@ -14,6 +14,11 @@ sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' \
|
|||||||
sed -i 's/"exit_type":"Crashed"/"exit_type":"Normal"/' \
|
sed -i 's/"exit_type":"Crashed"/"exit_type":"Normal"/' \
|
||||||
"$HOME/.config/chromium/Default/Preferences" 2>/dev/null || true
|
"$HOME/.config/chromium/Default/Preferences" 2>/dev/null || true
|
||||||
|
|
||||||
# Browser starten
|
# Hostname und IP ermitteln
|
||||||
|
HOSTNAME=$(hostname -f)
|
||||||
|
IP_ADDRESS=$(hostname -I | awk '{print $1}')
|
||||||
|
|
||||||
|
# Browser starten mit SSL-Warnung deaktiviert
|
||||||
chromium-browser --kiosk --noerrdialogs --disable-infobars \
|
chromium-browser --kiosk --noerrdialogs --disable-infobars \
|
||||||
--window-position=0,0 --app=http://localhost:5000/ &
|
--window-position=0,0 --ignore-certificate-errors \
|
||||||
|
--app=https://${IP_ADDRESS}:5000/ &
|
99
backend/install/ssl_check.sh
Executable file
99
backend/install/ssl_check.sh
Executable file
@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# MYP SSL-Zertifikat-Prüfskript
|
||||||
|
# Prüft den Status der SSL-Zertifikate und gibt Informationen aus
|
||||||
|
|
||||||
|
# Fehlerabbruch aktivieren
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Farben für bessere Lesbarkeit
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Standardwerte
|
||||||
|
CERT_PATH="/opt/myp/ssl/myp.crt"
|
||||||
|
KEY_PATH="/opt/myp/ssl/myp.key"
|
||||||
|
|
||||||
|
# Argumente verarbeiten
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-c|--cert)
|
||||||
|
CERT_PATH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-k|--key)
|
||||||
|
KEY_PATH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Unbekannte Option: $1${NC}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Header anzeigen
|
||||||
|
echo -e "${BLUE}================================================================${NC}"
|
||||||
|
echo -e "${BLUE} MYP V2 - SSL-Zertifikat-Statusprüfung ${NC}"
|
||||||
|
echo -e "${BLUE}================================================================${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Prüfen, ob OpenSSL installiert ist
|
||||||
|
if ! command -v openssl &> /dev/null; then
|
||||||
|
echo -e "${RED}OpenSSL ist nicht installiert!${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prüfen, ob Zertifikat existiert
|
||||||
|
if [ ! -f "$CERT_PATH" ]; then
|
||||||
|
echo -e "${RED}Zertifikat nicht gefunden: $CERT_PATH${NC}"
|
||||||
|
echo -e "${YELLOW}Führen Sie 'create_ssl_cert.sh' aus, um ein neues Zertifikat zu erstellen.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prüfen, ob Schlüssel existiert
|
||||||
|
if [ ! -f "$KEY_PATH" ]; then
|
||||||
|
echo -e "${RED}Schlüssel nicht gefunden: $KEY_PATH${NC}"
|
||||||
|
echo -e "${YELLOW}Führen Sie 'create_ssl_cert.sh' aus, um einen neuen Schlüssel zu erstellen.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Zertifikatsinformationen anzeigen
|
||||||
|
echo -e "${GREEN}Zertifikatsinformationen:${NC}"
|
||||||
|
echo -e "${BLUE}Zertifikatsdatei: ${NC}$CERT_PATH"
|
||||||
|
echo -e "${BLUE}Schlüsseldatei: ${NC}$KEY_PATH"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Zertifikatsdetails anzeigen
|
||||||
|
echo -e "${YELLOW}Zertifikatsdetails:${NC}"
|
||||||
|
openssl x509 -in "$CERT_PATH" -noout -subject -issuer -dates -fingerprint -sha256
|
||||||
|
|
||||||
|
# Gültigkeit prüfen
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Gültigkeitsprüfung:${NC}"
|
||||||
|
not_after=$(openssl x509 -in "$CERT_PATH" -noout -enddate | cut -d= -f2)
|
||||||
|
not_after_seconds=$(date -d "$not_after" +%s)
|
||||||
|
now_seconds=$(date +%s)
|
||||||
|
days_left=$(( (not_after_seconds - now_seconds) / 86400 ))
|
||||||
|
|
||||||
|
if [ $days_left -le 0 ]; then
|
||||||
|
echo -e "${RED}Zertifikat ist ABGELAUFEN!${NC}"
|
||||||
|
elif [ $days_left -le 30 ]; then
|
||||||
|
echo -e "${YELLOW}Zertifikat läuft in $days_left Tagen ab!${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}Zertifikat ist noch $days_left Tage gültig.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Zertifikatsinhalte prüfen
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Zertifikatsinhalte:${NC}"
|
||||||
|
echo -e "${BLUE}Alternative Namen (SAN):${NC}"
|
||||||
|
openssl x509 -in "$CERT_PATH" -noout -text | grep -A1 "Subject Alternative Name"
|
||||||
|
|
||||||
|
# Abschluss
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}SSL-Prüfung abgeschlossen.${NC}"
|
||||||
|
echo -e "${BLUE}Um die Zertifikate zu erneuern, führen Sie 'create_ssl_cert.sh' aus.${NC}"
|
||||||
|
echo ""
|
@ -61,6 +61,7 @@ show_main_menu() {
|
|||||||
echo "7) MYP-Dienst starten/stoppen/neustarten"
|
echo "7) MYP-Dienst starten/stoppen/neustarten"
|
||||||
echo "8) Logs anzeigen"
|
echo "8) Logs anzeigen"
|
||||||
echo "9) Dokumentation anzeigen"
|
echo "9) Dokumentation anzeigen"
|
||||||
|
echo "10) SSL-Zertifikat-Management"
|
||||||
echo ""
|
echo ""
|
||||||
echo "q) Beenden"
|
echo "q) Beenden"
|
||||||
echo ""
|
echo ""
|
||||||
@ -98,6 +99,9 @@ process_main_menu() {
|
|||||||
9)
|
9)
|
||||||
show_documentation
|
show_documentation
|
||||||
;;
|
;;
|
||||||
|
10)
|
||||||
|
show_ssl_management
|
||||||
|
;;
|
||||||
q|Q)
|
q|Q)
|
||||||
echo -e "${GREEN}Auf Wiedersehen!${NC}"
|
echo -e "${GREEN}Auf Wiedersehen!${NC}"
|
||||||
exit 0
|
exit 0
|
||||||
@ -131,7 +135,7 @@ standard_installation() {
|
|||||||
echo "Installiere System-Abhängigkeiten..."
|
echo "Installiere System-Abhängigkeiten..."
|
||||||
apt update
|
apt update
|
||||||
apt install -y python3.11 python3.11-pip python3.11-venv python3.11-dev \
|
apt install -y python3.11 python3.11-pip python3.11-venv python3.11-dev \
|
||||||
build-essential git curl
|
build-essential git curl openssl
|
||||||
|
|
||||||
# Verzeichnis für MYP erstellen/aktualisieren
|
# Verzeichnis für MYP erstellen/aktualisieren
|
||||||
mkdir -p /opt/myp
|
mkdir -p /opt/myp
|
||||||
@ -156,6 +160,9 @@ standard_installation() {
|
|||||||
# Datenbank-Verzeichnis erstellen
|
# Datenbank-Verzeichnis erstellen
|
||||||
mkdir -p /opt/myp/data
|
mkdir -p /opt/myp/data
|
||||||
|
|
||||||
|
# SSL-Verzeichnis erstellen
|
||||||
|
mkdir -p /opt/myp/ssl
|
||||||
|
|
||||||
# Python-Umgebung und Abhängigkeiten einrichten
|
# Python-Umgebung und Abhängigkeiten einrichten
|
||||||
echo "Richte Python-Umgebung ein..."
|
echo "Richte Python-Umgebung ein..."
|
||||||
cd /opt/myp
|
cd /opt/myp
|
||||||
@ -166,12 +173,18 @@ standard_installation() {
|
|||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# SSL-Zertifikate erstellen
|
||||||
|
echo "Erstelle SSL-Zertifikate..."
|
||||||
|
chmod +x /opt/myp/install/create_ssl_cert.sh
|
||||||
|
/opt/myp/install/create_ssl_cert.sh -d /opt/myp/ssl
|
||||||
|
|
||||||
# Berechtigungen setzen
|
# Berechtigungen setzen
|
||||||
echo "Setze Berechtigungen..."
|
echo "Setze Berechtigungen..."
|
||||||
chown -R www-data:www-data /opt/myp
|
chown -R www-data:www-data /opt/myp
|
||||||
chmod -R 755 /opt/myp
|
chmod -R 755 /opt/myp
|
||||||
chmod -R 775 /opt/myp/logs
|
chmod -R 775 /opt/myp/logs
|
||||||
chmod -R 775 /opt/myp/data
|
chmod -R 775 /opt/myp/data
|
||||||
|
chmod 600 /opt/myp/ssl/myp.key
|
||||||
|
|
||||||
echo -e "${GREEN}Installation abgeschlossen.${NC}"
|
echo -e "${GREEN}Installation abgeschlossen.${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
@ -181,6 +194,7 @@ standard_installation() {
|
|||||||
echo " cd /opt/myp && source .venv/bin/activate && python3.11 app/app.py"
|
echo " cd /opt/myp && source .venv/bin/activate && python3.11 app/app.py"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BLUE}Oder verwenden Sie Option 7 für Dienst-Management${NC}"
|
echo -e "${BLUE}Oder verwenden Sie Option 7 für Dienst-Management${NC}"
|
||||||
|
echo -e "${GREEN}MYP V2 ist unter https://$(hostname -I | awk '{print $1}'):5000 erreichbar${NC}"
|
||||||
|
|
||||||
read -p "Drücken Sie eine Taste, um zum Hauptmenü zurückzukehren..."
|
read -p "Drücken Sie eine Taste, um zum Hauptmenü zurückzukehren..."
|
||||||
show_main_menu
|
show_main_menu
|
||||||
@ -1267,40 +1281,61 @@ system_status() {
|
|||||||
echo -e "${GREEN}System-Status${NC}"
|
echo -e "${GREEN}System-Status${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# MYP-Status
|
# Systeminfos anzeigen
|
||||||
|
echo -e "${YELLOW}Systeminformationen:${NC}"
|
||||||
|
echo -e "Hostname: $(hostname)"
|
||||||
|
echo -e "IP-Adresse: $(hostname -I | awk '{print $1}')"
|
||||||
|
echo -e "Betriebssystem: $(lsb_release -ds 2>/dev/null || cat /etc/*release 2>/dev/null | head -n1 || uname -om)"
|
||||||
|
echo -e "Kernel: $(uname -r)"
|
||||||
|
echo -e "CPU: $(grep -c ^processor /proc/cpuinfo) Kerne"
|
||||||
|
echo -e "RAM: $(free -h | awk '/^Mem/ {print $2}')"
|
||||||
|
echo -e "Festplatte: $(df -h / | awk 'NR==2 {print $2}')"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# MYP-Status anzeigen
|
||||||
echo -e "${YELLOW}MYP-Status:${NC}"
|
echo -e "${YELLOW}MYP-Status:${NC}"
|
||||||
if is_myp_installed; then
|
if is_myp_installed; then
|
||||||
echo "MYP ist installiert in /opt/myp"
|
echo -e "MYP ist installiert: ${GREEN}Ja${NC}"
|
||||||
if systemctl is-active --quiet myp.service; then
|
|
||||||
echo -e "MYP-Dienst: ${GREEN}Aktiv${NC}"
|
# Prüfen, ob der MYP-Service läuft
|
||||||
|
if systemctl is-active --quiet myp.service 2>/dev/null; then
|
||||||
|
echo -e "MYP-Service: ${GREEN}Aktiv${NC}"
|
||||||
else
|
else
|
||||||
echo -e "MYP-Dienst: ${RED}Inaktiv${NC}"
|
echo -e "MYP-Service: ${RED}Inaktiv${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pfadangaben
|
||||||
|
echo -e "Installationspfad: /opt/myp"
|
||||||
|
echo -e "Datenbank: /opt/myp/data"
|
||||||
|
echo -e "Logs: /opt/myp/logs"
|
||||||
|
echo -e "SSL-Zertifikate: /opt/myp/ssl"
|
||||||
|
|
||||||
|
# SSL-Status überprüfen
|
||||||
|
if [ -f "/opt/myp/ssl/myp.crt" ] && [ -f "/opt/myp/ssl/myp.key" ]; then
|
||||||
|
echo -e "SSL-Zertifikate: ${GREEN}Vorhanden${NC}"
|
||||||
|
|
||||||
|
# Zertifikatsinformationen anzeigen
|
||||||
|
if command -v openssl &> /dev/null; then
|
||||||
|
cert_expiry=$(openssl x509 -enddate -noout -in /opt/myp/ssl/myp.crt | cut -d= -f 2)
|
||||||
|
cert_subject=$(openssl x509 -subject -noout -in /opt/myp/ssl/myp.crt | sed 's/^subject=//')
|
||||||
|
echo -e "Zertifikat für: ${BLUE}$cert_subject${NC}"
|
||||||
|
echo -e "Gültig bis: ${BLUE}$cert_expiry${NC}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo -e "MYP ist ${RED}nicht installiert${NC}"
|
echo -e "SSL-Zertifikate: ${RED}Fehlen${NC}"
|
||||||
fi
|
fi
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Netzwerkstatus
|
# MYP-URLs anzeigen
|
||||||
echo -e "${YELLOW}Netzwerkstatus:${NC}"
|
echo -e ""
|
||||||
ip -o addr show | awk '$3 == "inet" {print $2 ": " $4}'
|
echo -e "${YELLOW}MYP-Zugriff:${NC}"
|
||||||
echo ""
|
ip_address=$(hostname -I | awk '{print $1}')
|
||||||
|
echo -e "${GREEN}https://$ip_address:5000${NC} (verschlüsselt)"
|
||||||
|
echo -e "${YELLOW}http://$ip_address:5000${NC} (unverschlüsselt)"
|
||||||
|
else
|
||||||
|
echo -e "MYP ist installiert: ${RED}Nein${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
# DNS-Server
|
|
||||||
echo -e "${YELLOW}DNS-Server:${NC}"
|
|
||||||
grep "nameserver" /etc/resolv.conf 2>/dev/null || echo "Keine DNS-Server konfiguriert."
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Systemressourcen
|
|
||||||
echo -e "${YELLOW}Systemressourcen:${NC}"
|
|
||||||
echo "CPU-Auslastung:"
|
|
||||||
top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4 "% genutzt"}'
|
|
||||||
echo "Speichernutzung:"
|
|
||||||
free -h | grep "Mem:" | awk '{print $3 " von " $2 " genutzt"}'
|
|
||||||
echo "Festplattenbelegung:"
|
|
||||||
df -h / | grep -v "Filesystem" | awk '{print $3 " von " $2 " genutzt (" $5 ")"}'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
read -p "Drücken Sie eine Taste, um zum Hauptmenü zurückzukehren..."
|
read -p "Drücken Sie eine Taste, um zum Hauptmenü zurückzukehren..."
|
||||||
show_main_menu
|
show_main_menu
|
||||||
}
|
}
|
||||||
@ -1491,6 +1526,57 @@ show_documentation() {
|
|||||||
show_main_menu
|
show_main_menu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 10) SSL-Zertifikat-Management
|
||||||
|
show_ssl_management() {
|
||||||
|
print_header
|
||||||
|
echo -e "${GREEN}SSL-Zertifikat-Management${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "Bitte wählen Sie eine Option:"
|
||||||
|
echo ""
|
||||||
|
echo "1) SSL-Zertifikatsstatus anzeigen"
|
||||||
|
echo "2) Neue SSL-Zertifikate erstellen"
|
||||||
|
echo "3) SSL-Einstellungen in settings.py anzeigen/bearbeiten"
|
||||||
|
echo ""
|
||||||
|
echo "b) Zurück zum Hauptmenü"
|
||||||
|
echo ""
|
||||||
|
read -p "Ihre Auswahl: " ssl_option
|
||||||
|
|
||||||
|
case $ssl_option in
|
||||||
|
1)
|
||||||
|
# SSL-Status anzeigen
|
||||||
|
chmod +x /opt/myp/install/ssl_check.sh
|
||||||
|
/opt/myp/install/ssl_check.sh
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
# Neue Zertifikate erstellen
|
||||||
|
echo -e "${YELLOW}Erstelle neue SSL-Zertifikate...${NC}"
|
||||||
|
chmod +x /opt/myp/install/create_ssl_cert.sh
|
||||||
|
/opt/myp/install/create_ssl_cert.sh -d /opt/myp/ssl
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
# SSL-Einstellungen anzeigen/bearbeiten
|
||||||
|
if command -v nano &> /dev/null; then
|
||||||
|
nano /opt/myp/app/config/settings.py
|
||||||
|
else
|
||||||
|
vi /opt/myp/app/config/settings.py
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
b|B)
|
||||||
|
show_main_menu
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Ungültige Option.${NC}"
|
||||||
|
sleep 2
|
||||||
|
show_ssl_management
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
read -p "Drücken Sie eine Taste, um zum SSL-Menü zurückzukehren..."
|
||||||
|
show_ssl_management
|
||||||
|
}
|
||||||
|
|
||||||
# Hauptprogramm
|
# Hauptprogramm
|
||||||
check_root
|
check_root
|
||||||
show_main_menu
|
show_main_menu
|
@ -27,12 +27,12 @@ const nextConfig = {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/api/backend/:path*',
|
source: '/api/backend/:path*',
|
||||||
destination: 'http://192.168.0.105:5000/api/:path*',
|
destination: 'https://192.168.0.105:5000/api/:path*',
|
||||||
},
|
},
|
||||||
// Direkter Proxy für Health-Checks
|
// Direkter Proxy für Health-Checks
|
||||||
{
|
{
|
||||||
source: '/backend-health',
|
source: '/backend-health',
|
||||||
destination: 'http://192.168.0.105:5000/health',
|
destination: 'https://192.168.0.105:5000/health',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Header } from "@/components/header";
|
import { Header } from "@/components/header";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import { SSLWarning } from "@/components/ui/ssl-warning";
|
||||||
|
|
||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ export default function RootLayout(props: RootLayoutProps) {
|
|||||||
<head />
|
<head />
|
||||||
<body className={"min-h-dvh bg-neutral-200 font-sans antialiased"}>
|
<body className={"min-h-dvh bg-neutral-200 font-sans antialiased"}>
|
||||||
<Header />
|
<Header />
|
||||||
|
<SSLWarning />
|
||||||
<main className="flex-grow max-w-screen-2xl w-full mx-auto flex flex-col p-8 gap-4 text-foreground">
|
<main className="flex-grow max-w-screen-2xl w-full mx-auto flex flex-col p-8 gap-4 text-foreground">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
|
86
frontend/src/components/ui/ssl-warning.tsx
Normal file
86
frontend/src/components/ui/ssl-warning.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { API_BASE_URL } from "@/utils/api-config";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSLWarning - Zeigt eine Warnung für selbstsignierte SSL-Zertifikate an
|
||||||
|
* und bietet eine Möglichkeit, diese zu akzeptieren
|
||||||
|
*/
|
||||||
|
export function SSLWarning() {
|
||||||
|
const [showWarning, setShowWarning] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Prüfe, ob die Warnung angezeigt werden soll
|
||||||
|
const hasSeenWarning = localStorage.getItem("ssl-warning-dismissed");
|
||||||
|
|
||||||
|
// Zeige die Warnung nur an, wenn der Benutzer sie noch nicht gesehen hat
|
||||||
|
if (!hasSeenWarning) {
|
||||||
|
setShowWarning(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Teste die Backend-Verbindung und prüfe auf Zertifikatsprobleme
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showWarning) return;
|
||||||
|
|
||||||
|
const testConnection = async () => {
|
||||||
|
try {
|
||||||
|
// Teste HTTPS-Verbindung
|
||||||
|
await fetch(`${API_BASE_URL}/health`, {
|
||||||
|
method: 'GET',
|
||||||
|
mode: 'no-cors' // Verwende no-cors für SSL-Tests
|
||||||
|
});
|
||||||
|
// Bei erfolgreicher Verbindung: Blende die Warnung aus
|
||||||
|
setShowWarning(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("SSL-Verbindungstest fehlgeschlagen:", error);
|
||||||
|
// Bei Fehlern: Zeige die Warnung an
|
||||||
|
setShowWarning(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
testConnection();
|
||||||
|
}, [showWarning]);
|
||||||
|
|
||||||
|
const dismissWarning = () => {
|
||||||
|
localStorage.setItem("ssl-warning-dismissed", "true");
|
||||||
|
setShowWarning(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openBackendDirectly = () => {
|
||||||
|
// Öffne das Backend direkt in einem neuen Tab
|
||||||
|
window.open(API_BASE_URL, "_blank");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!showWarning) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 mb-4 mx-8 mt-4 rounded shadow-md">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex-grow">
|
||||||
|
<p className="font-bold">SSL-Sicherheitshinweis</p>
|
||||||
|
<p className="text-sm mt-1">
|
||||||
|
Diese Anwendung verwendet ein selbstsigniertes SSL-Zertifikat für sichere Kommunikation.
|
||||||
|
Um Verbindungsprobleme zu vermeiden, öffnen Sie bitte einmalig die folgende URL und akzeptieren Sie das Zertifikat:
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={openBackendDirectly}
|
||||||
|
className="mt-2 text-blue-600 hover:text-blue-800 underline text-sm"
|
||||||
|
>
|
||||||
|
Backend-Verbindung bestätigen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={dismissWarning}
|
||||||
|
className="ml-4 text-gray-500 hover:text-gray-700"
|
||||||
|
aria-label="Warnung schließen"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
// Basis-URL für Backend-API
|
// Basis-URL für Backend-API
|
||||||
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://192.168.0.105:5000";
|
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "https://192.168.0.105:5000";
|
||||||
|
|
||||||
// Frontend-URL für Callbacks - unterstützt mehrere Domains
|
// Frontend-URL für Callbacks - unterstützt mehrere Domains
|
||||||
const getFrontendUrl = () => {
|
const getFrontendUrl = () => {
|
||||||
@ -15,12 +15,12 @@ const getFrontendUrl = () => {
|
|||||||
if (hostname === 'm040tbaraspi001' ||
|
if (hostname === 'm040tbaraspi001' ||
|
||||||
hostname === 'm040tbaraspi001.de040.corpintra.net' ||
|
hostname === 'm040tbaraspi001.de040.corpintra.net' ||
|
||||||
hostname.includes('corpintra.net')) {
|
hostname.includes('corpintra.net')) {
|
||||||
return `http://${hostname}`;
|
return `https://${hostname}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priorität 3: Default für Localhost
|
// Priorität 3: Default für Localhost
|
||||||
return "http://localhost:3000";
|
return "https://localhost:3000";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FRONTEND_URL = getFrontendUrl();
|
export const FRONTEND_URL = getFrontendUrl();
|
||||||
@ -37,6 +37,23 @@ export const ALLOWED_CALLBACK_HOSTS = [
|
|||||||
'192.168.0.105'
|
'192.168.0.105'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Funktion zum Testen der SSL-Verbindung
|
||||||
|
export const testSSLConnection = async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
// Führe einen einfachen GET-Request zum Backend aus
|
||||||
|
const response = await fetch(`${API_BASE_URL}/health`, {
|
||||||
|
method: 'GET',
|
||||||
|
mode: 'no-cors', // Keine CORS-Fehler erzeugen
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wenn kein Fehler auftritt, ist die SSL-Verbindung erfolgreich
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('SSL-Verbindungstest fehlgeschlagen:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Endpunkte für die verschiedenen Ressourcen
|
// Endpunkte für die verschiedenen Ressourcen
|
||||||
export const API_ENDPOINTS = {
|
export const API_ENDPOINTS = {
|
||||||
PRINTERS: `${API_BASE_URL}/api/printers`,
|
PRINTERS: `${API_BASE_URL}/api/printers`,
|
||||||
|
75
frontend/src/utils/api-helper.ts
Normal file
75
frontend/src/utils/api-helper.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { ExternalAPI } from './external-api';
|
||||||
|
|
||||||
|
// Typdefinitionen für API-Responses
|
||||||
|
export interface Printer {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
ip: string;
|
||||||
|
status: string;
|
||||||
|
is_enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Job {
|
||||||
|
id: string;
|
||||||
|
printer_id: string;
|
||||||
|
user_id: string;
|
||||||
|
start_time: string;
|
||||||
|
end_time: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instanz der ExternalAPI erstellen
|
||||||
|
const externalAPI = new ExternalAPI();
|
||||||
|
|
||||||
|
// Fetcher für SWR mit Fehlerbehandlung und HTTPS-Unterstützung
|
||||||
|
const fetchWithErrorHandling = async (endpoint: string) => {
|
||||||
|
try {
|
||||||
|
return await externalAPI.fetch(endpoint);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API-Fehler:', error);
|
||||||
|
throw new Error('Ein Fehler ist bei der API-Anfrage aufgetreten');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// API-Funktionen, die mit der bisherigen API-Struktur kompatibel sind
|
||||||
|
export const api = {
|
||||||
|
// Drucker-Endpunkte
|
||||||
|
printers: {
|
||||||
|
getAll: () => fetchWithErrorHandling('/api/printers'),
|
||||||
|
getById: (id: string) => fetchWithErrorHandling(`/api/printers/${id}`),
|
||||||
|
create: (data: Partial<Printer>) =>
|
||||||
|
externalAPI.fetch('/api/printers', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}),
|
||||||
|
update: (id: string, data: Partial<Printer>) =>
|
||||||
|
externalAPI.fetch(`/api/printers/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}),
|
||||||
|
delete: (id: string) =>
|
||||||
|
externalAPI.fetch(`/api/printers/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Jobs-Endpunkte
|
||||||
|
jobs: {
|
||||||
|
getAll: () => fetchWithErrorHandling('/api/jobs'),
|
||||||
|
getById: (id: string) => fetchWithErrorHandling(`/api/jobs/${id}`),
|
||||||
|
create: (data: Partial<Job>) =>
|
||||||
|
externalAPI.fetch('/api/jobs', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}),
|
||||||
|
update: (id: string, data: Partial<Job>) =>
|
||||||
|
externalAPI.fetch(`/api/jobs/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}),
|
||||||
|
delete: (id: string) =>
|
||||||
|
externalAPI.fetch(`/api/jobs/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
@ -1,78 +1,57 @@
|
|||||||
import { API_ENDPOINTS } from './api-config';
|
import { API_BASE_URL } from './api-config';
|
||||||
|
|
||||||
// Typdefinitionen für API-Responses
|
/**
|
||||||
export interface Printer {
|
* ExternalAPI - Wrapper für externe API-Aufrufe mit HTTPS-Unterstützung
|
||||||
id: string;
|
* Enthält Logik für Offline-Fallback und Behandlung von selbstsignierten Zertifikaten
|
||||||
name: string;
|
*/
|
||||||
ip: string;
|
export class ExternalAPI {
|
||||||
status: string;
|
private baseURL: string;
|
||||||
is_enabled: boolean;
|
|
||||||
|
constructor() {
|
||||||
|
this.baseURL = API_BASE_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Job {
|
/**
|
||||||
id: string;
|
* Führt einen API-Request durch mit Unterstützung für selbstsignierte Zertifikate
|
||||||
printer_id: string;
|
* im Entwicklungsmodus
|
||||||
user_id: string;
|
*/
|
||||||
start_time: string;
|
async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||||||
end_time: string;
|
const url = `${this.baseURL}${endpoint}`;
|
||||||
status: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetcher für SWR mit Fehlerbehandlung
|
try {
|
||||||
const fetchWithErrorHandling = async (url: string) => {
|
const response = await fetch(url, {
|
||||||
const response = await fetch(url);
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers,
|
||||||
|
},
|
||||||
|
// Im Browser werden selbstsignierte Zertifikate über die Browser-Einstellungen akzeptiert
|
||||||
|
// Die fetch API im Browser hat keine Option zum Ignorieren von Zertifikaten
|
||||||
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = new Error('Ein Fehler ist bei der API-Anfrage aufgetreten');
|
throw new Error(`API Error: ${response.status}`);
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return await response.json();
|
||||||
};
|
} catch (error) {
|
||||||
|
console.error('API Fehler:', error);
|
||||||
|
|
||||||
// API-Funktionen
|
// Prüfen auf Zertifikatsfehler
|
||||||
export const api = {
|
if (error instanceof Error && error.message.includes('certificate')) {
|
||||||
// Drucker-Endpunkte
|
console.warn('Zertifikatsfehler: Bitte akzeptieren Sie das selbstsignierte Zertifikat manuell, indem Sie direkt auf https://192.168.0.105:5000 zugreifen und es bestätigen.');
|
||||||
printers: {
|
}
|
||||||
getAll: () => fetchWithErrorHandling(API_ENDPOINTS.PRINTERS),
|
|
||||||
getById: (id: string) => fetchWithErrorHandling(`${API_ENDPOINTS.PRINTERS}/${id}`),
|
|
||||||
create: (data: Partial<Printer>) =>
|
|
||||||
fetch(API_ENDPOINTS.PRINTERS, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
}).then(res => res.json()),
|
|
||||||
update: (id: string, data: Partial<Printer>) =>
|
|
||||||
fetch(`${API_ENDPOINTS.PRINTERS}/${id}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
}).then(res => res.json()),
|
|
||||||
delete: (id: string) =>
|
|
||||||
fetch(`${API_ENDPOINTS.PRINTERS}/${id}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
}).then(res => res.json()),
|
|
||||||
},
|
|
||||||
|
|
||||||
// Jobs-Endpunkte
|
throw error;
|
||||||
jobs: {
|
}
|
||||||
getAll: () => fetchWithErrorHandling(API_ENDPOINTS.JOBS),
|
}
|
||||||
getById: (id: string) => fetchWithErrorHandling(`${API_ENDPOINTS.JOBS}/${id}`),
|
|
||||||
create: (data: Partial<Job>) =>
|
// API-Methoden für verschiedene Endpoints
|
||||||
fetch(API_ENDPOINTS.JOBS, {
|
async getPrinters() {
|
||||||
method: 'POST',
|
return this.fetch<any>('/api/printers');
|
||||||
headers: { 'Content-Type': 'application/json' },
|
}
|
||||||
body: JSON.stringify(data),
|
|
||||||
}).then(res => res.json()),
|
async getJobs() {
|
||||||
update: (id: string, data: Partial<Job>) =>
|
return this.fetch<any>('/api/jobs');
|
||||||
fetch(`${API_ENDPOINTS.JOBS}/${id}`, {
|
}
|
||||||
method: 'PUT',
|
}
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
}).then(res => res.json()),
|
|
||||||
delete: (id: string) =>
|
|
||||||
fetch(`${API_ENDPOINTS.JOBS}/${id}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
}).then(res => res.json()),
|
|
||||||
},
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user