Compare commits
2 Commits
16043e2577
...
e96fa8f367
| Author | SHA1 | Date | |
|---|---|---|---|
| e96fa8f367 | |||
| 97163becfa |
Binary file not shown.
Binary file not shown.
BIN
backend/uploads/avatars/5ff86878-5277-4c18-af6b-1a63faaf3371.png
Normal file
BIN
backend/uploads/avatars/5ff86878-5277-4c18-af6b-1a63faaf3371.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
@@ -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)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// Re-export from PermissionsContext for backward compatibility
|
||||
export { usePermissions } from '../contexts/PermissionsContext'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { usePermissions } from '../hooks/usePermissions'
|
||||
import { usePermissions } from '../contexts/PermissionsContext'
|
||||
|
||||
const Permissions = () => {
|
||||
const { authFetch } = useAuth()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { usePermissions } from '../hooks/usePermissions'
|
||||
import { usePermissions } from '../contexts/PermissionsContext'
|
||||
|
||||
const SpaceDetail = () => {
|
||||
const { id } = useParams()
|
||||
@@ -423,40 +423,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)
|
||||
@@ -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="p-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
|
||||
onClick={closeCertificatesModal}
|
||||
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">
|
||||
<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
|
||||
</p>
|
||||
</div>
|
||||
{certificates.map((cert, index) => (
|
||||
<div key={cert.id} 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-1">
|
||||
<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">
|
||||
#{certificates.length - index}
|
||||
</span>
|
||||
<h4 className="text-white font-semibold">CA-Zertifikat-ID: {cert.certificateId}</h4>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-slate-400 text-xs">
|
||||
<span className="font-semibold text-slate-300">Interne UID:</span>{' '}
|
||||
<span className="font-mono text-xs">{cert.id}</span>
|
||||
</p>
|
||||
<p className="text-slate-400 text-sm">
|
||||
<span className="font-semibold text-slate-300">Erstellt:</span>{' '}
|
||||
{new Date(cert.createdAt).toLocaleString('de-DE')}
|
||||
</p>
|
||||
<p className="text-slate-400 text-sm">
|
||||
<span className="font-semibold text-slate-300">Status:</span>{' '}
|
||||
<span className={`inline-block px-2 py-0.5 rounded text-xs ${
|
||||
cert.status === 'issued'
|
||||
? 'bg-green-500/20 text-green-400'
|
||||
: cert.status === 'pending'
|
||||
? 'bg-yellow-500/20 text-yellow-400'
|
||||
: 'bg-red-500/20 text-red-400'
|
||||
}`}>
|
||||
{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 (
|
||||
<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-1">
|
||||
<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">
|
||||
#{certificates.length - index}
|
||||
</span>
|
||||
</p>
|
||||
{cert.providerId && (
|
||||
<p className="text-slate-400 text-sm">
|
||||
<span className="font-semibold text-slate-300">Provider:</span>{' '}
|
||||
{cert.providerId}
|
||||
<h4 className="text-white font-semibold">CA-Zertifikat-ID: {certCertificateId}</h4>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-slate-400 text-xs">
|
||||
<span className="font-semibold text-slate-300">Interne UID:</span>{' '}
|
||||
<span className="font-mono text-xs">{certId}</span>
|
||||
</p>
|
||||
)}
|
||||
{certCreatedAt && !isNaN(certCreatedAt.getTime()) && (
|
||||
<p className="text-slate-400 text-sm">
|
||||
<span className="font-semibold text-slate-300">Erstellt:</span>{' '}
|
||||
{certCreatedAt.toLocaleString('de-DE')}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-slate-400 text-sm">
|
||||
<span className="font-semibold text-slate-300">Status:</span>{' '}
|
||||
<span className={`inline-block px-2 py-0.5 rounded text-xs ${
|
||||
certStatus === 'issued'
|
||||
? 'bg-green-500/20 text-green-400'
|
||||
: certStatus === 'pending'
|
||||
? 'bg-yellow-500/20 text-yellow-400'
|
||||
: 'bg-red-500/20 text-red-400'
|
||||
}`}>
|
||||
{certStatus}
|
||||
</span>
|
||||
</p>
|
||||
{certProviderId && (
|
||||
<p className="text-slate-400 text-sm">
|
||||
<span className="font-semibold text-slate-300">Provider:</span>{' '}
|
||||
{certProviderId}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleRefreshCertificate(cert)}
|
||||
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"
|
||||
title="Zertifikat von CA abrufen"
|
||||
>
|
||||
{refreshingCertificate === certId ? 'Aktualisiere...' : 'Aktualisieren'}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleRefreshCertificate(cert)}
|
||||
disabled={refreshingCertificate === cert.id}
|
||||
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"
|
||||
>
|
||||
{refreshingCertificate === cert.id ? 'Aktualisiere...' : 'Aktualisieren'}
|
||||
</button>
|
||||
{certPEM && (
|
||||
<div className="mt-3">
|
||||
<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">
|
||||
{certPEM}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{cert.certificatePEM && (
|
||||
<div className="mt-3">
|
||||
<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">
|
||||
{cert.certificatePEM}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { usePermissions } from '../hooks/usePermissions'
|
||||
import { usePermissions } from '../contexts/PermissionsContext'
|
||||
|
||||
const Spaces = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { usePermissions } from '../hooks/usePermissions'
|
||||
import { usePermissions } from '../contexts/PermissionsContext'
|
||||
|
||||
const Users = () => {
|
||||
const { authFetch } = useAuth()
|
||||
|
||||
Reference in New Issue
Block a user