update FDISK sync

This commit is contained in:
Matthias Hochmeister
2026-03-13 10:27:57 +01:00
parent 501b697ca2
commit 11fb533ad6
6 changed files with 273 additions and 1 deletions

View File

@@ -0,0 +1,129 @@
import { useRef, useEffect } from 'react';
import {
Box, Button, Card, CardContent, Chip, CircularProgress, Typography,
} from '@mui/material';
import SyncIcon from '@mui/icons-material/Sync';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { adminApi } from '../../services/admin';
import { useNotification } from '../../contexts/NotificationContext';
function FdiskSyncTab() {
const queryClient = useQueryClient();
const { showSuccess, showError } = useNotification();
const logBoxRef = useRef<HTMLDivElement>(null);
const { data, isLoading, isError } = useQuery({
queryKey: ['admin', 'fdisk-sync', 'logs'],
queryFn: adminApi.fdiskSyncLogs,
refetchInterval: 5000,
});
// Auto-scroll log box to bottom when new lines arrive
useEffect(() => {
if (logBoxRef.current) {
logBoxRef.current.scrollTop = logBoxRef.current.scrollHeight;
}
}, [data?.logs.length]);
const triggerMutation = useMutation({
mutationFn: adminApi.fdiskSyncTrigger,
onSuccess: () => {
showSuccess('Sync gestartet');
queryClient.invalidateQueries({ queryKey: ['admin', 'fdisk-sync', 'logs'] });
},
onError: (err: unknown) => {
const msg = (err as { response?: { status?: number } })?.response?.status === 409
? 'Sync läuft bereits'
: 'Sync konnte nicht gestartet werden';
showError(msg);
},
});
const running = data?.running ?? false;
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<Card>
<CardContent sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}>
<Box sx={{ flex: 1 }}>
<Typography variant="h6" gutterBottom>FDISK Mitglieder-Sync</Typography>
<Typography variant="body2" color="text.secondary">
Synchronisiert Mitgliederdaten und Ausbildungen aus FDISK in die Datenbank.
Läuft automatisch täglich um Mitternacht.
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Chip
label={running ? 'Läuft…' : 'Bereit'}
color={running ? 'warning' : 'success'}
size="small"
icon={running ? <CircularProgress size={12} color="inherit" /> : undefined}
/>
<Button
variant="contained"
startIcon={<SyncIcon />}
onClick={() => triggerMutation.mutate()}
disabled={running || triggerMutation.isPending}
>
Jetzt synchronisieren
</Button>
</Box>
</CardContent>
</Card>
<Card>
<CardContent>
<Typography variant="subtitle2" gutterBottom>Protokoll (letzte 500 Zeilen)</Typography>
{isLoading && (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress size={28} />
</Box>
)}
{isError && (
<Typography color="error" variant="body2">
Sync-Dienst nicht erreichbar. Läuft der fdisk-sync Container?
</Typography>
)}
{!isLoading && !isError && (
<Box
ref={logBoxRef}
sx={{
fontFamily: 'monospace',
fontSize: '0.75rem',
bgcolor: 'grey.900',
color: 'grey.100',
borderRadius: 1,
p: 1.5,
maxHeight: 500,
overflowY: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
}}
>
{(data?.logs ?? []).length === 0 ? (
<Typography variant="caption" color="grey.500">Noch keine Logs vorhanden.</Typography>
) : (
(data?.logs ?? []).map((entry, i) => (
<Box
key={i}
component="span"
sx={{
display: 'block',
color: entry.line.includes('ERROR') || entry.line.includes('WARN')
? (entry.line.includes('ERROR') ? 'error.light' : 'warning.light')
: 'inherit',
}}
>
{entry.line}
</Box>
))
)}
</Box>
)}
</CardContent>
</Card>
</Box>
);
}
export default FdiskSyncTab;