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:
@@ -27,12 +27,12 @@ const nextConfig = {
|
||||
return [
|
||||
{
|
||||
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
|
||||
{
|
||||
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 { Toaster } from "@/components/ui/toaster";
|
||||
import type { Metadata } from "next";
|
||||
import { SSLWarning } from "@/components/ui/ssl-warning";
|
||||
|
||||
import "@/app/globals.css";
|
||||
|
||||
@@ -26,6 +27,7 @@ export default function RootLayout(props: RootLayoutProps) {
|
||||
<head />
|
||||
<body className={"min-h-dvh bg-neutral-200 font-sans antialiased"}>
|
||||
<Header />
|
||||
<SSLWarning />
|
||||
<main className="flex-grow max-w-screen-2xl w-full mx-auto flex flex-col p-8 gap-4 text-foreground">
|
||||
{children}
|
||||
</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
|
||||
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
|
||||
const getFrontendUrl = () => {
|
||||
@@ -15,12 +15,12 @@ const getFrontendUrl = () => {
|
||||
if (hostname === 'm040tbaraspi001' ||
|
||||
hostname === 'm040tbaraspi001.de040.corpintra.net' ||
|
||||
hostname.includes('corpintra.net')) {
|
||||
return `http://${hostname}`;
|
||||
return `https://${hostname}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Priorität 3: Default für Localhost
|
||||
return "http://localhost:3000";
|
||||
return "https://localhost:3000";
|
||||
};
|
||||
|
||||
export const FRONTEND_URL = getFrontendUrl();
|
||||
@@ -37,6 +37,23 @@ export const ALLOWED_CALLBACK_HOSTS = [
|
||||
'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
|
||||
export const API_ENDPOINTS = {
|
||||
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 {
|
||||
id: string;
|
||||
name: string;
|
||||
ip: string;
|
||||
status: string;
|
||||
is_enabled: boolean;
|
||||
}
|
||||
/**
|
||||
* ExternalAPI - Wrapper für externe API-Aufrufe mit HTTPS-Unterstützung
|
||||
* Enthält Logik für Offline-Fallback und Behandlung von selbstsignierten Zertifikaten
|
||||
*/
|
||||
export class ExternalAPI {
|
||||
private baseURL: string;
|
||||
|
||||
export interface Job {
|
||||
id: string;
|
||||
printer_id: string;
|
||||
user_id: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
// Fetcher für SWR mit Fehlerbehandlung
|
||||
const fetchWithErrorHandling = async (url: string) => {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = new Error('Ein Fehler ist bei der API-Anfrage aufgetreten');
|
||||
throw error;
|
||||
constructor() {
|
||||
this.baseURL = API_BASE_URL;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
// API-Funktionen
|
||||
export const api = {
|
||||
// Drucker-Endpunkte
|
||||
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
|
||||
jobs: {
|
||||
getAll: () => fetchWithErrorHandling(API_ENDPOINTS.JOBS),
|
||||
getById: (id: string) => fetchWithErrorHandling(`${API_ENDPOINTS.JOBS}/${id}`),
|
||||
create: (data: Partial<Job>) =>
|
||||
fetch(API_ENDPOINTS.JOBS, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}).then(res => res.json()),
|
||||
update: (id: string, data: Partial<Job>) =>
|
||||
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()),
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Führt einen API-Request durch mit Unterstützung für selbstsignierte Zertifikate
|
||||
* im Entwicklungsmodus
|
||||
*/
|
||||
async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||||
const url = `${this.baseURL}${endpoint}`;
|
||||
|
||||
try {
|
||||
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) {
|
||||
throw new Error(`API Error: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('API Fehler:', error);
|
||||
|
||||
// Prüfen auf Zertifikatsfehler
|
||||
if (error instanceof Error && error.message.includes('certificate')) {
|
||||
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.');
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// API-Methoden für verschiedene Endpoints
|
||||
async getPrinters() {
|
||||
return this.fetch<any>('/api/printers');
|
||||
}
|
||||
|
||||
async getJobs() {
|
||||
return this.fetch<any>('/api/jobs');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user