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
Moderation
+ Lösch-Log
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'}
+
+ setShowAll(!showAll)}
+ startIcon={ }
+ >
+ {showAll ? 'Nur letzte 10 anzeigen' : 'Alle anzeigen'}
+
+
+
+ {/* 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;