Compare commits

..

2 Commits

9 changed files with 134 additions and 84 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -35,11 +35,13 @@ export const PermissionsProvider = ({ children }) => {
} }
const response = await authFetch('/api/user/permissions') const response = await authFetch('/api/user/permissions')
if (response.ok && isMountedRef.current) { if (response.ok && isMountedRef.current) {
try {
const data = await response.json() const data = await response.json()
// Nur Permissions aktualisieren, wenn Daten erfolgreich geparst wurden
setPermissions({ setPermissions({
isAdmin: data.isAdmin || false, isAdmin: data.isAdmin || false,
hasFullAccess: data.hasFullAccess || false, hasFullAccess: data.hasFullAccess || false,
accessibleSpaces: data.accessibleSpaces || [], accessibleSpaces: Array.isArray(data.accessibleSpaces) ? data.accessibleSpaces : [],
canCreateSpace: data.permissions?.canCreateSpace || false, canCreateSpace: data.permissions?.canCreateSpace || false,
canDeleteSpace: data.permissions?.canDeleteSpace || false, canDeleteSpace: data.permissions?.canDeleteSpace || false,
canCreateFqdn: data.permissions?.canCreateFqdn || {}, canCreateFqdn: data.permissions?.canCreateFqdn || {},
@@ -47,9 +49,17 @@ export const PermissionsProvider = ({ children }) => {
canUploadCSR: data.permissions?.canUploadCSR || {}, canUploadCSR: data.permissions?.canUploadCSR || {},
canSignCSR: data.permissions?.canSignCSR || {}, 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) { } catch (err) {
console.error('Error fetching permissions:', err) console.error('Error fetching permissions:', err)
// Bei Netzwerkfehlern etc. Permissions nicht zurücksetzen
} finally { } finally {
if (isInitial && isMountedRef.current) { if (isInitial && isMountedRef.current) {
setLoading(false) setLoading(false)

View File

@@ -1,3 +0,0 @@
// Re-export from PermissionsContext for backward compatibility
export { usePermissions } from '../contexts/PermissionsContext'

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../hooks/usePermissions' import { usePermissions } from '../contexts/PermissionsContext'
const Permissions = () => { const Permissions = () => {
const { authFetch } = useAuth() const { authFetch } = useAuth()

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom' import { useParams, useNavigate } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../hooks/usePermissions' import { usePermissions } from '../contexts/PermissionsContext'
const SpaceDetail = () => { const SpaceDetail = () => {
const { id } = useParams() const { id } = useParams()
@@ -423,33 +423,54 @@ const SpaceDetail = () => {
} }
const handleViewCertificates = async (fqdn) => { const handleViewCertificates = async (fqdn) => {
if (!fqdn || !fqdn.id) {
console.error('Invalid FQDN provided to handleViewCertificates')
return
}
setSelectedFqdn(fqdn) setSelectedFqdn(fqdn)
setLoadingCertificates(true) setLoadingCertificates(true)
setCertificates([]) setCertificates([])
setShowCertificatesModal(true) // Öffne Modal sofort, auch wenn noch geladen wird
try { try {
const response = await authFetch(`/api/spaces/${id}/fqdns/${fqdn.id}/certificates`) const response = await authFetch(`/api/spaces/${id}/fqdns/${fqdn.id}/certificates`)
if (response.ok) { if (response.ok) {
try {
const certs = await response.json() const certs = await response.json()
setCertificates(certs) // Stelle sicher, dass certs ein Array ist
setCertificates(Array.isArray(certs) ? certs : [])
} catch (parseErr) {
console.error('Error parsing certificates response:', parseErr)
setCertificates([])
}
} else { } 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) { } catch (err) {
console.error('Error fetching certificates:', err) console.error('Error fetching certificates:', err)
setCertificates([])
} finally { } finally {
setLoadingCertificates(false) setLoadingCertificates(false)
setShowCertificatesModal(true)
} }
} }
const handleRefreshCertificate = async (cert) => { const handleRefreshCertificate = async (cert) => {
if (!cert || !cert.id || !selectedFqdn || !selectedFqdn.id) {
console.error('Invalid certificate or FQDN for refresh')
return
}
setRefreshingCertificate(cert.id) setRefreshingCertificate(cert.id)
try { try {
const response = await authFetch(`/api/spaces/${id}/fqdns/${selectedFqdn.id}/certificates/${cert.id}/refresh`, { const response = await authFetch(`/api/spaces/${id}/fqdns/${selectedFqdn.id}/certificates/${cert.id}/refresh`, {
method: 'POST' method: 'POST'
}) })
if (response.ok) { if (response.ok) {
try {
const result = await response.json() const result = await response.json()
// Aktualisiere Zertifikat in der Liste // Aktualisiere Zertifikat in der Liste
setCertificates(prev => prev.map(c => setCertificates(prev => prev.map(c =>
@@ -457,6 +478,9 @@ const SpaceDetail = () => {
? { ...c, certificatePEM: result.certificatePEM } ? { ...c, certificatePEM: result.certificatePEM }
: c : c
)) ))
} catch (parseErr) {
console.error('Error parsing refresh response:', parseErr)
}
} }
} catch (err) { } catch (err) {
console.error('Error refreshing certificate:', err) console.error('Error refreshing certificate:', err)
@@ -1478,10 +1502,13 @@ const SpaceDetail = () => {
<div className="bg-slate-800 rounded-lg border border-slate-600 max-w-4xl w-full max-h-[90vh] overflow-y-auto"> <div className="bg-slate-800 rounded-lg border border-slate-600 max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<div className="p-6"> <div className="p-6">
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-white">Zertifikate für {selectedFqdn.fqdn}</h2> <h2 className="text-2xl font-bold text-white">
Zertifikate für {selectedFqdn?.fqdn || 'Unbekannter FQDN'}
</h2>
<button <button
onClick={closeCertificatesModal} onClick={closeCertificatesModal}
className="text-slate-400 hover:text-white transition-colors" className="text-slate-400 hover:text-white transition-colors"
aria-label="Schließen"
> >
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -1501,64 +1528,80 @@ const SpaceDetail = () => {
{certificates.length} {certificates.length === 1 ? 'Zertifikat' : 'Zertifikate'} gefunden {certificates.length} {certificates.length === 1 ? 'Zertifikat' : 'Zertifikate'} gefunden
</p> </p>
</div> </div>
{certificates.map((cert, index) => ( {certificates.map((cert, index) => {
<div key={cert.id} className="bg-slate-700/30 rounded-lg p-4 border border-slate-600"> // 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 (
<div key={certId} className="bg-slate-700/30 rounded-lg p-4 border border-slate-600">
<div className="flex justify-between items-start mb-3"> <div className="flex justify-between items-start mb-3">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<span className="text-xs font-semibold text-purple-400 bg-purple-500/20 px-2 py-1 rounded"> <span className="text-xs font-semibold text-purple-400 bg-purple-500/20 px-2 py-1 rounded">
#{certificates.length - index} #{certificates.length - index}
</span> </span>
<h4 className="text-white font-semibold">CA-Zertifikat-ID: {cert.certificateId}</h4> <h4 className="text-white font-semibold">CA-Zertifikat-ID: {certCertificateId}</h4>
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<p className="text-slate-400 text-xs"> <p className="text-slate-400 text-xs">
<span className="font-semibold text-slate-300">Interne UID:</span>{' '} <span className="font-semibold text-slate-300">Interne UID:</span>{' '}
<span className="font-mono text-xs">{cert.id}</span> <span className="font-mono text-xs">{certId}</span>
</p> </p>
{certCreatedAt && !isNaN(certCreatedAt.getTime()) && (
<p className="text-slate-400 text-sm"> <p className="text-slate-400 text-sm">
<span className="font-semibold text-slate-300">Erstellt:</span>{' '} <span className="font-semibold text-slate-300">Erstellt:</span>{' '}
{new Date(cert.createdAt).toLocaleString('de-DE')} {certCreatedAt.toLocaleString('de-DE')}
</p> </p>
)}
<p className="text-slate-400 text-sm"> <p className="text-slate-400 text-sm">
<span className="font-semibold text-slate-300">Status:</span>{' '} <span className="font-semibold text-slate-300">Status:</span>{' '}
<span className={`inline-block px-2 py-0.5 rounded text-xs ${ <span className={`inline-block px-2 py-0.5 rounded text-xs ${
cert.status === 'issued' certStatus === 'issued'
? 'bg-green-500/20 text-green-400' ? 'bg-green-500/20 text-green-400'
: cert.status === 'pending' : certStatus === 'pending'
? 'bg-yellow-500/20 text-yellow-400' ? 'bg-yellow-500/20 text-yellow-400'
: 'bg-red-500/20 text-red-400' : 'bg-red-500/20 text-red-400'
}`}> }`}>
{cert.status} {certStatus}
</span> </span>
</p> </p>
{cert.providerId && ( {certProviderId && (
<p className="text-slate-400 text-sm"> <p className="text-slate-400 text-sm">
<span className="font-semibold text-slate-300">Provider:</span>{' '} <span className="font-semibold text-slate-300">Provider:</span>{' '}
{cert.providerId} {certProviderId}
</p> </p>
)} )}
</div> </div>
</div> </div>
<button <button
onClick={() => handleRefreshCertificate(cert)} onClick={() => handleRefreshCertificate(cert)}
disabled={refreshingCertificate === cert.id} disabled={refreshingCertificate === certId || !selectedFqdn}
className="ml-4 px-3 py-1 bg-blue-600 hover:bg-blue-700 disabled:bg-slate-600 disabled:cursor-not-allowed text-white text-sm rounded-lg transition-colors" className="ml-4 px-3 py-1 bg-blue-600 hover:bg-blue-700 disabled:bg-slate-600 disabled:cursor-not-allowed text-white text-sm rounded-lg transition-colors"
title="Zertifikat von CA abrufen" title="Zertifikat von CA abrufen"
> >
{refreshingCertificate === cert.id ? 'Aktualisiere...' : 'Aktualisieren'} {refreshingCertificate === certId ? 'Aktualisiere...' : 'Aktualisieren'}
</button> </button>
</div> </div>
{cert.certificatePEM && ( {certPEM && (
<div className="mt-3"> <div className="mt-3">
<h5 className="text-sm font-semibold text-slate-300 mb-2">Zertifikat (PEM):</h5> <h5 className="text-sm font-semibold text-slate-300 mb-2">Zertifikat (PEM):</h5>
<pre className="text-xs text-slate-200 bg-slate-900/50 p-3 rounded overflow-auto max-h-60 font-mono"> <pre className="text-xs text-slate-200 bg-slate-900/50 p-3 rounded overflow-auto max-h-60 font-mono">
{cert.certificatePEM} {certPEM}
</pre> </pre>
</div> </div>
)} )}
</div> </div>
))} )
})}
</div> </div>
)} )}
</div> </div>

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../hooks/usePermissions' import { usePermissions } from '../contexts/PermissionsContext'
const Spaces = () => { const Spaces = () => {
const navigate = useNavigate() const navigate = useNavigate()

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { usePermissions } from '../hooks/usePermissions' import { usePermissions } from '../contexts/PermissionsContext'
const Users = () => { const Users = () => {
const { authFetch } = useAuth() const { authFetch } = useAuth()