diff --git a/docker/dev/frontend/nginx.conf b/docker/dev/frontend/nginx.conf index 5da7c6a..33c7a0d 100644 --- a/docker/dev/frontend/nginx.conf +++ b/docker/dev/frontend/nginx.conf @@ -55,6 +55,18 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } + # Protected API - Admin API routes (password protected) + location /api/admin { + auth_basic "Restricted Area - Admin API"; + auth_basic_user_file /etc/nginx/.htpasswd; + + proxy_pass http://backend-dev:5000/api/admin; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # Protected API - Moderation API routes (password protected) - must come before /groups location /moderation/groups { auth_basic "Restricted Area - Moderation API"; @@ -95,6 +107,20 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } + # Protected routes - Admin (password protected) - React Dev Server + location /admin { + auth_basic "Restricted Area - Admin"; + auth_basic_user_file /etc/nginx/.htpasswd; + + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + # Protected routes - Moderation (password protected) - React Dev Server location /moderation { auth_basic "Restricted Area - Moderation"; diff --git a/docker/prod/frontend/nginx.conf b/docker/prod/frontend/nginx.conf index 8464f72..d5f8c3d 100644 --- a/docker/prod/frontend/nginx.conf +++ b/docker/prod/frontend/nginx.conf @@ -89,6 +89,18 @@ http { proxy_set_header X-Forwarded-Proto $scheme; } + # Protected API - Admin API routes (password protected) + location /api/admin { + auth_basic "Restricted Area - Admin API"; + auth_basic_user_file /etc/nginx/.htpasswd; + + proxy_pass http://image-uploader-backend:5000/api/admin; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # Protected API - Moderation API routes (password protected) - must come before /groups location /moderation/groups { auth_basic "Restricted Area - Moderation API"; @@ -129,6 +141,20 @@ http { add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive" always; } + # Protected routes - Admin (password protected) + location /admin { + auth_basic "Restricted Area - Admin"; + auth_basic_user_file /etc/nginx/.htpasswd; + + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + expires -1; + + # Prevent indexing + add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive" always; + } + # Protected routes - Moderation (password protected) location /moderation { auth_basic "Restricted Area - Moderation"; diff --git a/frontend/src/App.js b/frontend/src/App.js index 9f60ec2..f2c8fc3 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -8,6 +8,7 @@ import GroupsOverviewPage from './Components/Pages/GroupsOverviewPage'; import ModerationGroupsPage from './Components/Pages/ModerationGroupsPage'; import ModerationGroupImagesPage from './Components/Pages/ModerationGroupImagesPage'; import PublicGroupImagesPage from './Components/Pages/PublicGroupImagesPage'; +import DeletionLogPage from './Components/Pages/DeletionLogPage'; import FZF from './Components/Pages/404Page.js' function App() { @@ -20,6 +21,7 @@ function App() { } /> } /> } /> + } /> } /> diff --git a/frontend/src/Components/ComponentUtils/Headers/Navbar.js b/frontend/src/Components/ComponentUtils/Headers/Navbar.js index bc8f5a7..968cde7 100644 --- a/frontend/src/Components/ComponentUtils/Headers/Navbar.js +++ b/frontend/src/Components/ComponentUtils/Headers/Navbar.js @@ -4,7 +4,7 @@ import { NavLink } from 'react-router-dom' import '../Css/Navbar.css' import logo from '../../../Images/logo.png' -import { Lock as LockIcon } from '@mui/icons-material'; +import { Lock as LockIcon, AdminPanelSettings as AdminIcon } from '@mui/icons-material'; function Navbar() { return ( @@ -15,6 +15,7 @@ function Navbar() {
  • Groups
  • Slideshow
  • +
  • Upload
  • About
  • diff --git a/frontend/src/Components/Pages/DeletionLogPage.js b/frontend/src/Components/Pages/DeletionLogPage.js new file mode 100644 index 0000000..1457e1f --- /dev/null +++ b/frontend/src/Components/Pages/DeletionLogPage.js @@ -0,0 +1,284 @@ +import React, { useState, useEffect } from 'react'; +import { Helmet } from 'react-helmet'; +import { + Container, + Card, + Typography, + Button, + Box, + CircularProgress, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Chip, + Grid +} from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import WarningIcon from '@mui/icons-material/Warning'; +import InfoIcon from '@mui/icons-material/Info'; +import Navbar from '../ComponentUtils/Headers/Navbar'; +import Footer from '../ComponentUtils/Footer'; + +const DeletionLogPage = () => { + const [deletions, setDeletions] = useState([]); + const [statistics, setStatistics] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [showAll, setShowAll] = useState(false); + + useEffect(() => { + loadDeletionLog(); + loadStatistics(); + }, [showAll]); + + const loadDeletionLog = async () => { + try { + setLoading(true); + const endpoint = showAll + ? '/api/admin/deletion-log/all' + : '/api/admin/deletion-log?limit=10'; + + const response = await fetch(endpoint); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + setDeletions(data.deletions || []); + setError(null); + } catch (error) { + console.error('Fehler beim Laden des Lösch-Logs:', error); + setError('Fehler beim Laden des Lösch-Logs'); + } finally { + setLoading(false); + } + }; + + const loadStatistics = async () => { + try { + const response = await fetch('/api/admin/deletion-log/stats'); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + setStatistics(data.statistics || null); + } catch (error) { + console.error('Fehler beim Laden der Statistiken:', error); + } + }; + + const formatDate = (dateString) => { + if (!dateString) return '-'; + const date = new Date(dateString); + return date.toLocaleString('de-DE', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }); + }; + + const getReasonIcon = (reason) => { + if (reason.includes('unapproved')) { + return ; + } + return ; + }; + + if (loading) { + return ( +
    + + +
    + + + Lade Lösch-Historie... + +
    +
    +
    +
    + ); + } + + return ( +
    + + Lösch-Historie - Image Uploader + + + + + + + Lösch-Historie + + + Übersicht über automatisch gelöschte Gruppen + + + + {error && ( + + {error} + + )} + + {/* Statistics Cards */} + {statistics && ( + + + + + {statistics.totalGroupsDeleted || 0} + + + Gelöschte Gruppen + + + + + + + {statistics.totalImagesDeleted || 0} + + + Gelöschte Bilder + + + + + + + {statistics.totalStorageFreed || '0 KB'} + + + Speicher freigegeben + + + + + )} + + {/* Toggle Button */} + + + {showAll ? 'Alle Einträge' : 'Letzte 10 Einträge'} + + + + + {/* Deletion Log Table */} + {deletions.length === 0 ? ( + + + + Keine Lösch-Einträge gefunden + + + Es wurden bisher keine Gruppen automatisch gelöscht. + + + ) : ( + + + + + Gruppe ID + Jahr + Bilder + Upload-Datum + Gelöscht am + Grund + Größe + + + + {deletions.map((row) => ( + + + + + {row.year || '-'} + {row.image_count || 0} + {formatDate(row.upload_date)} + {formatDate(row.deleted_at)} + + + {getReasonIcon(row.deletion_reason)} + + {row.deletion_reason || 'Unbekannt'} + + + + + + {formatFileSize(row.total_file_size)} + + + + ))} + +
    +
    + )} + + {/* Info Box */} + + + + + + Hinweis zur automatischen Löschung + + + Gruppen, die nicht innerhalb von 7 Tagen nach dem Upload freigegeben werden, + werden automatisch gelöscht. Der Cleanup läuft täglich um 10:00 Uhr. + Alle Lösch-Vorgänge werden hier protokolliert (ohne personenbezogene Daten). + + + + +
    +
    + ); +}; + +// Helper function for file size formatting +const formatFileSize = (bytes) => { + if (!bytes || bytes === 0) return '0 KB'; + + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`; +}; + +export default DeletionLogPage;