223 lines
6.3 KiB
TypeScript
223 lines
6.3 KiB
TypeScript
/**
|
||
* AdminAutomationEventsPage
|
||
*
|
||
* Admin page for viewing and managing automation scheduler events.
|
||
* SysAdmin-only: displays all automation definitions with scheduler status.
|
||
* Uses FormGeneratorTable for consistent look with other admin pages.
|
||
*/
|
||
|
||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||
import { FaSync } from 'react-icons/fa';
|
||
import api from '../../api';
|
||
import styles from './Admin.module.css';
|
||
import { FormGeneratorTable, type ColumnConfig } from '../../components/FormGenerator/FormGeneratorTable';
|
||
|
||
interface AutomationEvent {
|
||
eventId: string;
|
||
automationId: string;
|
||
name: string;
|
||
nextRunTime: string | null;
|
||
trigger: string | null;
|
||
createdBy: string;
|
||
mandate: string;
|
||
featureInstance: string;
|
||
}
|
||
|
||
const _formatNextRun = (nextRunTime: string | null): string => {
|
||
if (!nextRunTime || nextRunTime === 'None') return '';
|
||
try {
|
||
const date = new Date(nextRunTime);
|
||
return date.toLocaleString('de-CH', {
|
||
day: '2-digit',
|
||
month: '2-digit',
|
||
year: 'numeric',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
});
|
||
} catch {
|
||
return nextRunTime;
|
||
}
|
||
};
|
||
|
||
export const AdminAutomationEventsPage: React.FC = () => {
|
||
const [events, setEvents] = useState<AutomationEvent[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [syncing, setSyncing] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [syncResult, setSyncResult] = useState<string | null>(null);
|
||
|
||
const _fetchEvents = useCallback(async () => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const response = await api.get('/api/admin/automation-events');
|
||
// Map eventId to id for FormGeneratorTable compatibility
|
||
setEvents(response.data.map((e: any) => ({ ...e, id: e.eventId })));
|
||
} catch (err: any) {
|
||
setError(err.response?.data?.detail || 'Fehler beim Laden der Events');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
_fetchEvents();
|
||
}, [_fetchEvents]);
|
||
|
||
const _handleSync = async () => {
|
||
try {
|
||
setSyncing(true);
|
||
setError(null);
|
||
setSyncResult(null);
|
||
const response = await api.post('/api/admin/automation-events/sync');
|
||
const data = response.data;
|
||
setSyncResult(`Sync erfolgreich: ${data.synced} Automationen synchronisiert`);
|
||
await _fetchEvents();
|
||
} catch (err: any) {
|
||
setError(err.response?.data?.detail || 'Fehler beim Synchronisieren');
|
||
} finally {
|
||
setSyncing(false);
|
||
}
|
||
};
|
||
|
||
const _handleDelete = useCallback(async (eventId: string) => {
|
||
try {
|
||
setError(null);
|
||
const _event = events.find(e => e.eventId === eventId);
|
||
const encodedId = encodeURIComponent(eventId);
|
||
await api.post(`/api/admin/automation-events/${encodedId}/remove`);
|
||
setEvents(prev => prev.filter(e => e.eventId !== eventId));
|
||
} catch (err: any) {
|
||
setError(err.response?.data?.detail || 'Fehler beim Entfernen des Events');
|
||
throw err;
|
||
}
|
||
}, [events]);
|
||
|
||
const columns: ColumnConfig[] = useMemo(() => [
|
||
{
|
||
key: 'name',
|
||
label: 'Name',
|
||
type: 'string' as const,
|
||
sortable: true,
|
||
searchable: true,
|
||
width: 200,
|
||
minWidth: 120,
|
||
},
|
||
{
|
||
key: 'mandate',
|
||
label: 'Mandant',
|
||
type: 'string' as const,
|
||
sortable: true,
|
||
filterable: true,
|
||
width: 150,
|
||
minWidth: 100,
|
||
},
|
||
{
|
||
key: 'createdBy',
|
||
label: 'Erstellt von',
|
||
type: 'string' as const,
|
||
sortable: true,
|
||
filterable: true,
|
||
width: 130,
|
||
minWidth: 80,
|
||
},
|
||
{
|
||
key: 'featureInstance',
|
||
label: 'Feature',
|
||
type: 'string' as const,
|
||
sortable: true,
|
||
filterable: true,
|
||
width: 130,
|
||
minWidth: 80,
|
||
},
|
||
{
|
||
key: 'nextRunTime',
|
||
label: 'Nächste Ausführung',
|
||
type: 'string' as const,
|
||
sortable: true,
|
||
width: 170,
|
||
minWidth: 130,
|
||
formatter: (value: any) => {
|
||
const formatted = _formatNextRun(value);
|
||
if (!formatted) return <span style={{ color: 'var(--text-tertiary, #999)' }}>–</span>;
|
||
return formatted;
|
||
},
|
||
},
|
||
{
|
||
key: 'trigger',
|
||
label: 'Trigger',
|
||
type: 'string' as const,
|
||
sortable: false,
|
||
width: 160,
|
||
minWidth: 100,
|
||
},
|
||
], []);
|
||
|
||
return (
|
||
<div className={styles.adminPage}>
|
||
<div className={styles.pageHeader}>
|
||
<div>
|
||
<h1 className={styles.pageTitle}>Automation Events</h1>
|
||
<p className={styles.pageSubtitle}>
|
||
Aktive Scheduler-Jobs ({events.length} Events)
|
||
</p>
|
||
</div>
|
||
<div className={styles.headerActions}>
|
||
<button
|
||
className={styles.secondaryButton}
|
||
onClick={_fetchEvents}
|
||
disabled={loading}
|
||
>
|
||
<FaSync className={loading ? 'spinning' : ''} /> Aktualisieren
|
||
</button>
|
||
<button
|
||
className={styles.primaryButton}
|
||
onClick={_handleSync}
|
||
disabled={syncing}
|
||
>
|
||
<FaSync className={syncing ? 'spinning' : ''} /> Sync All
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{syncResult && (
|
||
<div className={styles.infoBox} style={{ background: 'var(--success-bg, #f0fff4)', borderColor: 'var(--success-color, #38a169)' }}>
|
||
<span style={{ marginRight: 8, color: 'var(--success-color, #38a169)' }}>✓</span>
|
||
{syncResult}
|
||
</div>
|
||
)}
|
||
|
||
{error && (
|
||
<div className={styles.infoBox} style={{ background: 'var(--warning-bg, #fffbeb)', borderColor: 'var(--warning-color, #d69e2e)' }}>
|
||
<span style={{ marginRight: 8, color: 'var(--warning-color, #d69e2e)' }}>!</span>
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
<FormGeneratorTable
|
||
data={events}
|
||
columns={columns}
|
||
loading={loading}
|
||
pagination={true}
|
||
pageSize={25}
|
||
searchable={true}
|
||
filterable={true}
|
||
sortable={true}
|
||
selectable={false}
|
||
actionButtons={[
|
||
{
|
||
type: 'delete' as const,
|
||
title: 'Event entfernen',
|
||
},
|
||
]}
|
||
hookData={{
|
||
handleDelete: _handleDelete,
|
||
refetch: _fetchEvents,
|
||
}}
|
||
emptyMessage="Keine Automationen gefunden. Nutzen Sie 'Sync All', um Automationen zu synchronisieren."
|
||
/>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default AdminAutomationEventsPage;
|