Compare commits
7 Commits
16043e2577
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ea0ebae1b | |||
| 2f2be739f2 | |||
| d1e9c2433c | |||
| f0c23cad35 | |||
| 39148bbb56 | |||
| e96fa8f367 | |||
| 97163becfa |
327
.gitignore
vendored
327
.gitignore
vendored
@@ -1,29 +1,342 @@
|
|||||||
|
# ============================================
|
||||||
|
# Certigo Addon - Comprehensive .gitignore
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Go / Backend
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Binaries and executables
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
backend/bin/
|
||||||
|
backend/myapp
|
||||||
|
backend/certigo-addon
|
||||||
|
backend/certigo-addon-*
|
||||||
|
/tmp/certigo-addon-*
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool
|
||||||
|
*.out
|
||||||
|
coverage.html
|
||||||
|
coverage.txt
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# Go module cache (optional, but recommended for CI/CD)
|
||||||
|
# .go/
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Node.js / Frontend
|
||||||
|
# ============================================
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
frontend/node_modules/
|
frontend/node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
# Build outputs
|
# Build outputs
|
||||||
dist/
|
dist/
|
||||||
|
dist-ssr/
|
||||||
frontend/dist/
|
frontend/dist/
|
||||||
backend/bin/
|
*.local
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
.vite/
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# ============================================
|
||||||
# Database
|
# Database
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# SQLite databases
|
||||||
*.db
|
*.db
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
backend/spaces.db
|
backend/spaces.db
|
||||||
|
backend/*.db
|
||||||
|
|
||||||
# Environment variables
|
# Database backups
|
||||||
.env
|
*.sql.backup
|
||||||
.env.local
|
*.db.backup
|
||||||
|
|
||||||
# IDE
|
# ============================================
|
||||||
|
# Uploads & User-generated Content
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# User uploads (avatars, files, etc.)
|
||||||
|
backend/uploads/
|
||||||
|
backend/uploads/**
|
||||||
|
!backend/uploads/.gitkeep
|
||||||
|
frontend/public/uploads/
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Logs
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
backend/*.log
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# IDE & Editors
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# VSCode
|
||||||
.vscode/
|
.vscode/
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# IntelliJ IDEA / WebStorm
|
||||||
.idea/
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Sublime Text
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Vim
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
*~
|
||||||
|
.vim/
|
||||||
|
|
||||||
# OS
|
# Emacs
|
||||||
|
*~
|
||||||
|
\#*\#
|
||||||
|
/.emacs.desktop
|
||||||
|
/.emacs.desktop.lock
|
||||||
|
*.elc
|
||||||
|
auto-save-list
|
||||||
|
tramp
|
||||||
|
.\#*
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# OS Files
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
Icon
|
||||||
|
._*
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
*.stackdump
|
||||||
|
[Dd]esktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
*~
|
||||||
|
.fuse_hidden*
|
||||||
|
.directory
|
||||||
|
.Trash-*
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Temporary & Cache Files
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.bak
|
||||||
|
*.backup
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
# Cache directories
|
||||||
|
.cache/
|
||||||
|
.parcel-cache/
|
||||||
|
.eslintcache
|
||||||
|
.stylelintcache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
.node_repl_history
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Testing & Coverage
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Test coverage
|
||||||
|
coverage/
|
||||||
|
*.lcov
|
||||||
|
.nyc_output/
|
||||||
|
.coverage/
|
||||||
|
htmlcov/
|
||||||
|
.pytest_cache/
|
||||||
|
.tox/
|
||||||
|
|
||||||
|
# Jest
|
||||||
|
.jest/
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Build Tools & CI/CD
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
build/
|
||||||
|
out/
|
||||||
|
target/
|
||||||
|
.next/
|
||||||
|
.nuxt/
|
||||||
|
.cache/
|
||||||
|
|
||||||
|
# CI/CD
|
||||||
|
.github/workflows/*.yml.local
|
||||||
|
.circleci/
|
||||||
|
.travis.yml.local
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Security & Secrets
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Secrets and credentials
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
*.cert
|
||||||
|
secrets/
|
||||||
|
.secrets/
|
||||||
|
*.secret
|
||||||
|
config/secrets.*
|
||||||
|
|
||||||
|
# API keys and tokens
|
||||||
|
.env.secret
|
||||||
|
.env.production
|
||||||
|
.env.staging
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Documentation Build
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Generated documentation
|
||||||
|
docs/_build/
|
||||||
|
site/
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Misc
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Package manager lock files (optional - uncomment if you want to ignore)
|
||||||
|
# package-lock.json
|
||||||
|
# yarn.lock
|
||||||
|
# pnpm-lock.yaml
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env.development
|
||||||
|
.env.test
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Project-specific
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# OpenAPI generated files (if any)
|
||||||
|
backend/generated/
|
||||||
|
backend/api/
|
||||||
|
|
||||||
|
# Provider test outputs
|
||||||
|
backend/test-outputs/
|
||||||
|
|
||||||
|
# Script outputs
|
||||||
|
backend/scripts/output/
|
||||||
|
|
||||||
|
# Keep directory structure but ignore contents
|
||||||
|
!backend/uploads/.gitkeep
|
||||||
|
!backend/config/providers/.gitkeep
|
||||||
|
|||||||
BIN
backend/myapp
BIN
backend/myapp
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 90 KiB |
@@ -1,7 +1,8 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
|
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
|
||||||
import { AuthProvider, useAuth } from './contexts/AuthContext'
|
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 Sidebar from './components/Sidebar'
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import Home from './pages/Home'
|
import Home from './pages/Home'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { useAuth } from '../contexts/AuthContext'
|
import { useAuth } from '../contexts/AuthContext'
|
||||||
import { usePermissions } from '../contexts/PermissionsContext'
|
import { usePermissions } from '../hooks/usePermissions'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
const Sidebar = ({ isOpen, setIsOpen }) => {
|
const Sidebar = ({ isOpen, setIsOpen }) => {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,3 +1 @@
|
|||||||
// Re-export from PermissionsContext for backward compatibility
|
|
||||||
export { usePermissions } from '../contexts/PermissionsContext'
|
export { usePermissions } from '../contexts/PermissionsContext'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState, useRef, useCallback } from 'react'
|
import { useEffect, useState, useRef, useCallback } from 'react'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { useAuth } from '../contexts/AuthContext'
|
import { useAuth } from '../contexts/AuthContext'
|
||||||
import { usePermissions } from '../contexts/PermissionsContext'
|
import { usePermissions } from '../hooks/usePermissions'
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const { authFetch } = useAuth()
|
const { authFetch } = useAuth()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 '../contexts/PermissionsContext'
|
import { usePermissions } from '../hooks/usePermissions'
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const { authFetch, user } = useAuth()
|
const { authFetch, user } = useAuth()
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -232,18 +232,50 @@ const SpaceDetail = () => {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const csr = await response.json()
|
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)
|
setCsrData(csr)
|
||||||
setSelectedFqdn(fqdn)
|
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)
|
setShowCSRModal(true)
|
||||||
|
|
||||||
// Aktualisiere die FQDN-Liste
|
// Aktualisiere die FQDN-Liste
|
||||||
@@ -298,14 +330,23 @@ const SpaceDetail = () => {
|
|||||||
const historyResponse = await authFetch(`/api/spaces/${id}/fqdns/${fqdn.id}/csr`)
|
const historyResponse = await authFetch(`/api/spaces/${id}/fqdns/${fqdn.id}/csr`)
|
||||||
if (historyResponse.ok) {
|
if (historyResponse.ok) {
|
||||||
const history = await historyResponse.json()
|
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 {
|
} else {
|
||||||
setCsrHistory([])
|
setCsrHistory(prev => prev.filter(csr => String(csr?.fqdnId) !== String(fqdn.id)))
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching CSR:', err)
|
console.error('Error fetching CSR:', err)
|
||||||
setCsrData(null)
|
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)
|
setSelectedFqdn(null)
|
||||||
setCsrData(null)
|
setCsrData(null)
|
||||||
setCsrError('')
|
setCsrError('')
|
||||||
setCsrHistory([])
|
// csrHistory NICHT zurücksetzen - bleibt für Dropdown-Anzeige erhalten
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
@@ -423,33 +464,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 +519,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)
|
||||||
@@ -798,11 +863,12 @@ const SpaceDetail = () => {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const history = await response.json()
|
const history = await response.json()
|
||||||
// Speichere History mit FQDN-ID als Key
|
// Speichere History mit FQDN-ID als Key
|
||||||
const historyWithFqdnId = Array.isArray(history)
|
const historyArray = Array.isArray(history) ? history : []
|
||||||
? history.map(csr => ({ ...csr, fqdnId: fqdn.id }))
|
const historyWithFqdnId = historyArray
|
||||||
: []
|
.filter(csr => csr && csr.id)
|
||||||
|
.map(csr => ({ ...csr, fqdnId: String(fqdn.id) }))
|
||||||
setCsrHistory(prev => {
|
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]
|
return [...filtered, ...historyWithFqdnId]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -866,12 +932,13 @@ const SpaceDetail = () => {
|
|||||||
<div className="border-t border-slate-600/50 bg-slate-800/50 p-4">
|
<div className="border-t border-slate-600/50 bg-slate-800/50 p-4">
|
||||||
<h5 className="text-sm font-semibold text-slate-300 mb-3">CSR History</h5>
|
<h5 className="text-sm font-semibold text-slate-300 mb-3">CSR History</h5>
|
||||||
{(() => {
|
{(() => {
|
||||||
|
// Filtere CSRs für diesen FQDN - verwende String-Vergleich für Robustheit
|
||||||
const fqdnHistory = csrHistory
|
const fqdnHistory = csrHistory
|
||||||
.filter(csr => csr.fqdnId === fqdn.id)
|
.filter(csr => csr && String(csr.fqdnId) === String(fqdn.id))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
// Sortiere nach created_at, neueste zuerst
|
// Sortiere nach created_at, neueste zuerst
|
||||||
const dateA = new Date(a.createdAt).getTime()
|
const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0
|
||||||
const dateB = new Date(b.createdAt).getTime()
|
const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0
|
||||||
return dateB - dateA
|
return dateB - dateA
|
||||||
})
|
})
|
||||||
return fqdnHistory.length > 0 ? (
|
return fqdnHistory.length > 0 ? (
|
||||||
@@ -1478,10 +1545,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 +1571,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>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user