refactor: Move deletion log into ModerationGroupsPage
- Create DeletionLogSection component - Integrate deletion log at bottom of moderation page - Remove standalone DeletionLogPage and route - Remove admin nav link (log now in moderation) - Keep /api/admin routes for backend API access - Update nginx configs (remove /admin frontend route)
This commit is contained in:
parent
0f430af877
commit
3a2efd97c3
|
|
@ -107,20 +107,6 @@ 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";
|
||||
|
|
|
|||
|
|
@ -141,20 +141,6 @@ 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";
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ 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() {
|
||||
|
|
@ -21,7 +20,6 @@ function App() {
|
|||
<Route path="/groups" element={<GroupsOverviewPage />} />
|
||||
<Route path="/moderation" exact element={<ModerationGroupsPage />} />
|
||||
<Route path="/moderation/groups/:groupId" element={<ModerationGroupImagesPage />} />
|
||||
<Route path="/admin/deletion-log" element={<DeletionLogPage />} />
|
||||
<Route path="*" element={<FZF />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
|
|
|
|||
264
frontend/src/Components/ComponentUtils/DeletionLogSection.js
Normal file
264
frontend/src/Components/ComponentUtils/DeletionLogSection.js
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Card,
|
||||
Typography,
|
||||
Button,
|
||||
Box,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Chip,
|
||||
Grid,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
|
||||
const DeletionLogSection = () => {
|
||||
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();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [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 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]}`;
|
||||
};
|
||||
|
||||
const getReasonIcon = (reason) => {
|
||||
if (reason && reason.includes('unapproved')) {
|
||||
return <WarningIcon fontSize="small" color="warning" />;
|
||||
}
|
||||
return <DeleteIcon fontSize="small" color="action" />;
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||
<CircularProgress size={40} />
|
||||
<Typography variant="body2" sx={{ mt: 2, color: '#666' }}>
|
||||
Lade Lösch-Historie...
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 6, mb: 4 }}>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h5" component="h2" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<DeleteIcon />
|
||||
Lösch-Historie
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Automatisch gelöschte Gruppen (nicht innerhalb von 7 Tagen freigegeben)
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Card sx={{ p: 2, mb: 3, backgroundColor: '#ffebee' }}>
|
||||
<Typography color="error">{error}</Typography>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Statistics Cards */}
|
||||
{statistics && (
|
||||
<Grid container spacing={2} sx={{ mb: 3 }}>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Card sx={{ p: 2, textAlign: 'center', backgroundColor: '#f5f5f5' }}>
|
||||
<Typography variant="h4" color="primary" fontWeight="bold">
|
||||
{statistics.totalGroupsDeleted || 0}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Gelöschte Gruppen
|
||||
</Typography>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Card sx={{ p: 2, textAlign: 'center', backgroundColor: '#f5f5f5' }}>
|
||||
<Typography variant="h4" color="secondary" fontWeight="bold">
|
||||
{statistics.totalImagesDeleted || 0}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Gelöschte Bilder
|
||||
</Typography>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Card sx={{ p: 2, textAlign: 'center', backgroundColor: '#f5f5f5' }}>
|
||||
<Typography variant="h4" color="success.main" fontWeight="bold">
|
||||
{statistics.totalStorageFreed || '0 KB'}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Speicher freigegeben
|
||||
</Typography>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* Toggle Button */}
|
||||
<Box sx={{ mb: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography variant="subtitle1" fontWeight="bold">
|
||||
{showAll ? 'Alle Einträge' : 'Letzte 10 Einträge'}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
startIcon={<InfoIcon />}
|
||||
>
|
||||
{showAll ? 'Nur letzte 10' : 'Alle anzeigen'}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Deletion Log Table */}
|
||||
{deletions.length === 0 ? (
|
||||
<Card sx={{ p: 3, textAlign: 'center' }}>
|
||||
<InfoIcon sx={{ fontSize: 48, color: '#bdbdbd', mb: 1 }} />
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Keine Lösch-Einträge gefunden
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Es wurden bisher keine Gruppen automatisch gelöscht.
|
||||
</Typography>
|
||||
</Card>
|
||||
) : (
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow sx={{ backgroundColor: '#f5f5f5' }}>
|
||||
<TableCell><strong>Gruppe ID</strong></TableCell>
|
||||
<TableCell><strong>Jahr</strong></TableCell>
|
||||
<TableCell align="right"><strong>Bilder</strong></TableCell>
|
||||
<TableCell><strong>Upload-Datum</strong></TableCell>
|
||||
<TableCell><strong>Gelöscht am</strong></TableCell>
|
||||
<TableCell><strong>Grund</strong></TableCell>
|
||||
<TableCell align="right"><strong>Größe</strong></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{deletions.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
sx={{ '&:hover': { backgroundColor: '#fafafa' } }}
|
||||
>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={row.group_id}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{row.year || '-'}</TableCell>
|
||||
<TableCell align="right">{row.image_count || 0}</TableCell>
|
||||
<TableCell>{formatDate(row.upload_date)}</TableCell>
|
||||
<TableCell>{formatDate(row.deleted_at)}</TableCell>
|
||||
<TableCell>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{getReasonIcon(row.deletion_reason)}
|
||||
<Typography variant="body2">
|
||||
{row.deletion_reason || 'Unbekannt'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{formatFileSize(row.total_file_size)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
{/* Info Box */}
|
||||
<Card sx={{ mt: 3, p: 2, backgroundColor: '#e3f2fd' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
||||
<InfoIcon color="info" />
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight="bold" gutterBottom>
|
||||
Automatische Löschung
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Der Cleanup läuft täglich um 10:00 Uhr. Gruppen, die nicht innerhalb von 7 Tagen
|
||||
freigegeben werden, werden automatisch gelöscht. Alle Lösch-Vorgänge werden hier protokolliert.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeletionLogSection;
|
||||
|
|
@ -4,7 +4,7 @@ import { NavLink } from 'react-router-dom'
|
|||
import '../Css/Navbar.css'
|
||||
|
||||
import logo from '../../../Images/logo.png'
|
||||
import { Lock as LockIcon, AdminPanelSettings as AdminIcon } from '@mui/icons-material';
|
||||
import { Lock as LockIcon } from '@mui/icons-material';
|
||||
|
||||
function Navbar() {
|
||||
return (
|
||||
|
|
@ -15,7 +15,6 @@ function Navbar() {
|
|||
<li><NavLink to="/groups" activeClassName="active">Groups</NavLink></li>
|
||||
<li><NavLink to="/slideshow" activeClassName="active">Slideshow</NavLink></li>
|
||||
<li><NavLink to="/moderation" activeClassName="active"><LockIcon style={{ fontSize: 18, verticalAlign: 'text-bottom', marginRight: 6 }} aria-hidden="true" />Moderation</NavLink></li>
|
||||
<li><NavLink to="/admin/deletion-log" activeClassName="active"><AdminIcon style={{ fontSize: 18, verticalAlign: 'text-bottom', marginRight: 6 }} aria-hidden="true" />Lösch-Log</NavLink></li>
|
||||
<li><NavLink className="cta" exact to="/">Upload</NavLink></li>
|
||||
<li><a href="https://www.hobbyhimmel.de/ueber-uns/konzept/">About</a></li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1,285 +0,0 @@
|
|||
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();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [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 <WarningIcon fontSize="small" color="warning" />;
|
||||
}
|
||||
return <DeleteIcon fontSize="small" color="action" />;
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="allContainer">
|
||||
<Navbar />
|
||||
<Container maxWidth="lg" className="page-container">
|
||||
<div className="loading-container" style={{ textAlign: 'center', padding: '50px' }}>
|
||||
<CircularProgress size={60} color="primary" />
|
||||
<Typography variant="h6" style={{ marginTop: '20px', color: '#666666' }}>
|
||||
Lade Lösch-Historie...
|
||||
</Typography>
|
||||
</div>
|
||||
</Container>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="allContainer">
|
||||
<Helmet>
|
||||
<title>Lösch-Historie - Image Uploader</title>
|
||||
</Helmet>
|
||||
<Navbar />
|
||||
<Container maxWidth="lg" className="page-container" style={{ marginTop: '30px', marginBottom: '30px' }}>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography variant="h4" component="h1" gutterBottom>
|
||||
<DeleteIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
Lösch-Historie
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Übersicht über automatisch gelöschte Gruppen
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Card sx={{ p: 2, mb: 3, backgroundColor: '#ffebee' }}>
|
||||
<Typography color="error">{error}</Typography>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Statistics Cards */}
|
||||
{statistics && (
|
||||
<Grid container spacing={3} sx={{ mb: 4 }}>
|
||||
<Grid item xs={12} md={4}>
|
||||
<Card sx={{ p: 3, textAlign: 'center', backgroundColor: '#f5f5f5' }}>
|
||||
<Typography variant="h3" color="primary" fontWeight="bold">
|
||||
{statistics.totalGroupsDeleted || 0}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Gelöschte Gruppen
|
||||
</Typography>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
<Card sx={{ p: 3, textAlign: 'center', backgroundColor: '#f5f5f5' }}>
|
||||
<Typography variant="h3" color="secondary" fontWeight="bold">
|
||||
{statistics.totalImagesDeleted || 0}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Gelöschte Bilder
|
||||
</Typography>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
<Card sx={{ p: 3, textAlign: 'center', backgroundColor: '#f5f5f5' }}>
|
||||
<Typography variant="h3" color="success.main" fontWeight="bold">
|
||||
{statistics.totalStorageFreed || '0 KB'}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Speicher freigegeben
|
||||
</Typography>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* Toggle Button */}
|
||||
<Box sx={{ mb: 3, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography variant="h6">
|
||||
{showAll ? 'Alle Einträge' : 'Letzte 10 Einträge'}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
startIcon={<InfoIcon />}
|
||||
>
|
||||
{showAll ? 'Nur letzte 10 anzeigen' : 'Alle anzeigen'}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Deletion Log Table */}
|
||||
{deletions.length === 0 ? (
|
||||
<Card sx={{ p: 4, textAlign: 'center' }}>
|
||||
<InfoIcon sx={{ fontSize: 60, color: '#bdbdbd', mb: 2 }} />
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
Keine Lösch-Einträge gefunden
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Es wurden bisher keine Gruppen automatisch gelöscht.
|
||||
</Typography>
|
||||
</Card>
|
||||
) : (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }}>
|
||||
<TableHead>
|
||||
<TableRow sx={{ backgroundColor: '#f5f5f5' }}>
|
||||
<TableCell><strong>Gruppe ID</strong></TableCell>
|
||||
<TableCell><strong>Jahr</strong></TableCell>
|
||||
<TableCell align="right"><strong>Bilder</strong></TableCell>
|
||||
<TableCell><strong>Upload-Datum</strong></TableCell>
|
||||
<TableCell><strong>Gelöscht am</strong></TableCell>
|
||||
<TableCell><strong>Grund</strong></TableCell>
|
||||
<TableCell align="right"><strong>Größe</strong></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{deletions.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
sx={{
|
||||
'&:hover': { backgroundColor: '#fafafa' },
|
||||
'&:last-child td, &:last-child th': { border: 0 }
|
||||
}}
|
||||
>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={row.group_id}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{row.year || '-'}</TableCell>
|
||||
<TableCell align="right">{row.image_count || 0}</TableCell>
|
||||
<TableCell>{formatDate(row.upload_date)}</TableCell>
|
||||
<TableCell>{formatDate(row.deleted_at)}</TableCell>
|
||||
<TableCell>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{getReasonIcon(row.deletion_reason)}
|
||||
<Typography variant="body2">
|
||||
{row.deletion_reason || 'Unbekannt'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{formatFileSize(row.total_file_size)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
{/* Info Box */}
|
||||
<Card sx={{ mt: 4, p: 3, backgroundColor: '#e3f2fd' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
||||
<InfoIcon color="info" />
|
||||
<Box>
|
||||
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
||||
Hinweis zur automatischen Löschung
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
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).
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</Container>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
|
@ -6,6 +6,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js';
|
|||
import Navbar from '../ComponentUtils/Headers/Navbar';
|
||||
import Footer from '../ComponentUtils/Footer';
|
||||
import ImageGallery from '../ComponentUtils/ImageGallery';
|
||||
import DeletionLogSection from '../ComponentUtils/DeletionLogSection';
|
||||
import { getImageSrc } from '../../Utils/imageUtils';
|
||||
|
||||
const ModerationGroupsPage = () => {
|
||||
|
|
@ -221,6 +222,11 @@ const ModerationGroupsPage = () => {
|
|||
/>
|
||||
</section>
|
||||
|
||||
{/* Lösch-Historie */}
|
||||
<section className="moderation-section">
|
||||
<DeletionLogSection />
|
||||
</section>
|
||||
|
||||
{/* Bilder-Modal */}
|
||||
{showImages && selectedGroup && (
|
||||
<ImageModal
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user