diff --git a/backend/spaces.db-shm b/backend/spaces.db-shm index 83ea5fb..2643365 100644 Binary files a/backend/spaces.db-shm and b/backend/spaces.db-shm differ diff --git a/backend/spaces.db-wal b/backend/spaces.db-wal index 31d02e7..508294c 100644 Binary files a/backend/spaces.db-wal and b/backend/spaces.db-wal differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index d030f7a..9de4620 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,7 +1,8 @@ import { useState } from 'react' import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom' import { AuthProvider, useAuth } from './contexts/AuthContext' -import { PermissionsProvider, usePermissions } from './contexts/PermissionsContext' +import { PermissionsProvider } from './contexts/PermissionsContext' +import { usePermissions } from './hooks/usePermissions' import Sidebar from './components/Sidebar' import Footer from './components/Footer' import Home from './pages/Home' diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index 9b7ce3a..a5968e5 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -1,6 +1,6 @@ import { Link, useLocation, useNavigate } from 'react-router-dom' import { useAuth } from '../contexts/AuthContext' -import { usePermissions } from '../contexts/PermissionsContext' +import { usePermissions } from '../hooks/usePermissions' import { useState, useEffect } from 'react' const Sidebar = ({ isOpen, setIsOpen }) => { diff --git a/frontend/src/contexts/PermissionsContext.jsx b/frontend/src/contexts/PermissionsContext.jsx index 8ff1ee3..a59fbb8 100644 --- a/frontend/src/contexts/PermissionsContext.jsx +++ b/frontend/src/contexts/PermissionsContext.jsx @@ -35,21 +35,31 @@ export const PermissionsProvider = ({ children }) => { } const response = await authFetch('/api/user/permissions') if (response.ok && isMountedRef.current) { - const data = await response.json() - setPermissions({ - isAdmin: data.isAdmin || false, - hasFullAccess: data.hasFullAccess || false, - accessibleSpaces: data.accessibleSpaces || [], - canCreateSpace: data.permissions?.canCreateSpace || false, - canDeleteSpace: data.permissions?.canDeleteSpace || false, - canCreateFqdn: data.permissions?.canCreateFqdn || {}, - canDeleteFqdn: data.permissions?.canDeleteFqdn || {}, - canUploadCSR: data.permissions?.canUploadCSR || {}, - canSignCSR: data.permissions?.canSignCSR || {}, - }) + try { + const data = await response.json() + // Nur Permissions aktualisieren, wenn Daten erfolgreich geparst wurden + setPermissions({ + isAdmin: data.isAdmin || false, + hasFullAccess: data.hasFullAccess || false, + accessibleSpaces: Array.isArray(data.accessibleSpaces) ? data.accessibleSpaces : [], + canCreateSpace: data.permissions?.canCreateSpace || false, + canDeleteSpace: data.permissions?.canDeleteSpace || false, + canCreateFqdn: data.permissions?.canCreateFqdn || {}, + canDeleteFqdn: data.permissions?.canDeleteFqdn || {}, + canUploadCSR: data.permissions?.canUploadCSR || {}, + canSignCSR: data.permissions?.canSignCSR || {}, + }) + } catch (parseErr) { + console.error('Error parsing permissions response:', parseErr) + // Bei Parse-Fehler Permissions nicht zurücksetzen, nur loggen + } + } else if (response.status === 401 && isMountedRef.current) { + // Bei 401 Unauthorized werden Permissions zurückgesetzt (wird von AuthContext gehandelt) + console.log('Unauthorized - permissions will be cleared by auth context') } } catch (err) { console.error('Error fetching permissions:', err) + // Bei Netzwerkfehlern etc. Permissions nicht zurücksetzen } finally { if (isInitial && isMountedRef.current) { setLoading(false) diff --git a/frontend/src/hooks/usePermissions.js b/frontend/src/hooks/usePermissions.js index 24ea552..92003f9 100644 --- a/frontend/src/hooks/usePermissions.js +++ b/frontend/src/hooks/usePermissions.js @@ -1,3 +1 @@ -// Re-export from PermissionsContext for backward compatibility export { usePermissions } from '../contexts/PermissionsContext' - diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 0bcf51e..c486274 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -1,7 +1,7 @@ import { useEffect, useState, useRef, useCallback } from 'react' import { useLocation } from 'react-router-dom' import { useAuth } from '../contexts/AuthContext' -import { usePermissions } from '../contexts/PermissionsContext' +import { usePermissions } from '../hooks/usePermissions' const Home = () => { const { authFetch } = useAuth() diff --git a/frontend/src/pages/Profile.jsx b/frontend/src/pages/Profile.jsx index ade551d..b5f1b63 100644 --- a/frontend/src/pages/Profile.jsx +++ b/frontend/src/pages/Profile.jsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react' import { useAuth } from '../contexts/AuthContext' -import { usePermissions } from '../contexts/PermissionsContext' +import { usePermissions } from '../hooks/usePermissions' const Profile = () => { const { authFetch, user } = useAuth() diff --git a/frontend/src/pages/SpaceDetail.jsx b/frontend/src/pages/SpaceDetail.jsx index 0f28d2d..6e92f76 100644 --- a/frontend/src/pages/SpaceDetail.jsx +++ b/frontend/src/pages/SpaceDetail.jsx @@ -232,18 +232,50 @@ const SpaceDetail = () => { if (response.ok) { const csr = await response.json() - // Füge den neuen CSR zur History hinzu (nur wenn der Bereich bereits geöffnet ist) - if (showCSRDropdown[fqdn.id]) { - const newCsrWithFqdnId = { ...csr, fqdnId: fqdn.id } - setCsrHistory(prev => { - const filtered = prev.filter(csrItem => csrItem.fqdnId !== fqdn.id) - // Füge den neuen CSR am Anfang hinzu (neuester zuerst) - return [newCsrWithFqdnId, ...filtered] - }) - } - setCsrData(csr) setSelectedFqdn(fqdn) + + // Lade die komplette CSR History neu, um den neuen CSR anzuzeigen + // WICHTIG: Warte auf die History bevor der Dropdown geöffnet wird + try { + const historyResponse = await authFetch(`/api/spaces/${id}/fqdns/${fqdn.id}/csr`) + if (historyResponse.ok) { + const history = await historyResponse.json() + + // Stelle sicher, dass history ein Array ist + const historyArray = Array.isArray(history) ? history : [] + + // Füge fqdnId zu jedem CSR hinzu und stelle sicher dass sie immer gesetzt ist + // Auch wenn die API fqdnId bereits zurückgibt, überschreiben wir sie mit fqdn.id für Konsistenz + // Stelle sicher dass alle CSRs gültig sind und fqdnId gesetzt ist + const historyWithFqdnId = historyArray + .filter(csrItem => csrItem && csrItem.id) // Stelle sicher dass CSR gültig ist + .map(csrItem => ({ + ...csrItem, + fqdnId: String(fqdn.id) // Immer als String für konsistente Filterung + })) + + setCsrHistory(prev => { + // Entferne alte CSRs für diesen FQDN und füge die neuen hinzu + // Verwende String-Vergleich für Robustheit + const filtered = prev.filter(csrItem => String(csrItem?.fqdnId) !== String(fqdn.id)) + return [...filtered, ...historyWithFqdnId] + }) + + // Öffne den CSR History Dropdown NACH dem Laden der History + setShowCSRDropdown(prev => ({ ...prev, [fqdn.id]: true })) + } else { + setCsrHistory(prev => { + // Bei Fehler, entferne nur CSRs für diesen FQDN, behalte andere + return prev.filter(csrItem => String(csrItem?.fqdnId) !== String(fqdn.id)) + }) + } + } catch (err) { + console.error('Error fetching CSR history after upload:', err) + // Bei Fehler, entferne nur CSRs für diesen FQDN + setCsrHistory(prev => prev.filter(csrItem => String(csrItem?.fqdnId) !== String(fqdn.id))) + } + setShowCSRModal(true) // Aktualisiere die FQDN-Liste @@ -298,14 +330,23 @@ const SpaceDetail = () => { const historyResponse = await authFetch(`/api/spaces/${id}/fqdns/${fqdn.id}/csr`) if (historyResponse.ok) { const history = await historyResponse.json() - setCsrHistory(Array.isArray(history) ? history : []) + const historyArray = Array.isArray(history) ? history : [] + // Stelle sicher dass fqdnId gesetzt ist für konsistente Filterung + const historyWithFqdnId = historyArray + .filter(csr => csr && csr.id) + .map(csr => ({ ...csr, fqdnId: String(fqdn.id) })) + setCsrHistory(prev => { + const filtered = prev.filter(csr => String(csr?.fqdnId) !== String(fqdn.id)) + return [...filtered, ...historyWithFqdnId] + }) } else { - setCsrHistory([]) + setCsrHistory(prev => prev.filter(csr => String(csr?.fqdnId) !== String(fqdn.id))) } } catch (err) { console.error('Error fetching CSR:', err) setCsrData(null) - setCsrHistory([]) + // Entferne nur CSRs für diesen FQDN, behalte andere + setCsrHistory(prev => prev.filter(csr => String(csr?.fqdnId) !== String(fqdn.id))) } } @@ -319,7 +360,7 @@ const SpaceDetail = () => { setSelectedFqdn(null) setCsrData(null) setCsrError('') - setCsrHistory([]) + // csrHistory NICHT zurücksetzen - bleibt für Dropdown-Anzeige erhalten } const handleChange = (e) => { @@ -423,40 +464,64 @@ const SpaceDetail = () => { } const handleViewCertificates = async (fqdn) => { + if (!fqdn || !fqdn.id) { + console.error('Invalid FQDN provided to handleViewCertificates') + return + } + setSelectedFqdn(fqdn) setLoadingCertificates(true) setCertificates([]) + setShowCertificatesModal(true) // Öffne Modal sofort, auch wenn noch geladen wird try { const response = await authFetch(`/api/spaces/${id}/fqdns/${fqdn.id}/certificates`) + if (response.ok) { - const certs = await response.json() - setCertificates(certs) + try { + const certs = await response.json() + // Stelle sicher, dass certs ein Array ist + setCertificates(Array.isArray(certs) ? certs : []) + } catch (parseErr) { + console.error('Error parsing certificates response:', parseErr) + setCertificates([]) + } } else { - console.error('Fehler beim Laden der Zertifikate') + // Bei Fehler-Response (404, 403, etc.) setze leeres Array + console.error('Fehler beim Laden der Zertifikate:', response.status, response.statusText) + setCertificates([]) } } catch (err) { console.error('Error fetching certificates:', err) + setCertificates([]) } finally { setLoadingCertificates(false) - setShowCertificatesModal(true) } } const handleRefreshCertificate = async (cert) => { + if (!cert || !cert.id || !selectedFqdn || !selectedFqdn.id) { + console.error('Invalid certificate or FQDN for refresh') + return + } + setRefreshingCertificate(cert.id) try { const response = await authFetch(`/api/spaces/${id}/fqdns/${selectedFqdn.id}/certificates/${cert.id}/refresh`, { method: 'POST' }) if (response.ok) { - const result = await response.json() - // Aktualisiere Zertifikat in der Liste - setCertificates(prev => prev.map(c => - c.id === cert.id - ? { ...c, certificatePEM: result.certificatePEM } - : c - )) + try { + const result = await response.json() + // Aktualisiere Zertifikat in der Liste + setCertificates(prev => prev.map(c => + c.id === cert.id + ? { ...c, certificatePEM: result.certificatePEM } + : c + )) + } catch (parseErr) { + console.error('Error parsing refresh response:', parseErr) + } } } catch (err) { console.error('Error refreshing certificate:', err) @@ -798,11 +863,12 @@ const SpaceDetail = () => { if (response.ok) { const history = await response.json() // Speichere History mit FQDN-ID als Key - const historyWithFqdnId = Array.isArray(history) - ? history.map(csr => ({ ...csr, fqdnId: fqdn.id })) - : [] + const historyArray = Array.isArray(history) ? history : [] + const historyWithFqdnId = historyArray + .filter(csr => csr && csr.id) + .map(csr => ({ ...csr, fqdnId: String(fqdn.id) })) setCsrHistory(prev => { - const filtered = prev.filter(csr => csr.fqdnId !== fqdn.id) + const filtered = prev.filter(csr => String(csr?.fqdnId) !== String(fqdn.id)) return [...filtered, ...historyWithFqdnId] }) } @@ -866,12 +932,13 @@ const SpaceDetail = () => {
- Interne UID:{' '} - {cert.id} -
-- Erstellt:{' '} - {new Date(cert.createdAt).toLocaleString('de-DE')} -
-
- Status:{' '}
-
- {cert.status}
+ {certificates.map((cert, index) => {
+ // Sicherstellen, dass cert ein gültiges Objekt ist
+ if (!cert || !cert.id) {
+ return null
+ }
+
+ const certId = cert.id || 'unknown'
+ const certCertificateId = cert.certificateId || 'N/A'
+ const certCreatedAt = cert.createdAt ? new Date(cert.createdAt) : null
+ const certStatus = cert.status || 'unknown'
+ const certProviderId = cert.providerId
+ const certPEM = cert.certificatePEM
+
+ return (
+
- Provider:{' '}
- {cert.providerId}
+
+ Interne UID:{' '}
+ {certId}
+ Erstellt:{' '}
+ {certCreatedAt.toLocaleString('de-DE')}
+
+ Status:{' '}
+
+ {certStatus}
+
+
+ Provider:{' '}
+ {certProviderId}
+ CA-Zertifikat-ID: {certCertificateId}
+ Zertifikat (PEM):
+
+ {certPEM}
+
+ Zertifikat (PEM):
-
- {cert.certificatePEM}
-
-