fixed tables and forms
This commit is contained in:
parent
e6d28c436b
commit
05317e64ca
9 changed files with 233 additions and 34 deletions
|
|
@ -38,7 +38,7 @@ import { SettingsPage } from './pages/Settings';
|
|||
import { GDPRPage } from './pages/GDPR';
|
||||
import StorePage from './pages/Store';
|
||||
import { FeatureViewPage } from './pages/FeatureView';
|
||||
import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage, AdminAutomationEventsPage, AdminLogsPage } from './pages/admin';
|
||||
import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage, AdminAutomationEventsPage, AdminAutomationLogsPage, AdminLogsPage } from './pages/admin';
|
||||
import { AdminMandateWizardPage, AdminInvitationWizardPage } from './pages/admin/wizards';
|
||||
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
|
||||
import { BillingDataView, BillingAdmin, BillingMandateView, AdminSubscriptionsPage } from './pages/billing';
|
||||
|
|
@ -188,9 +188,10 @@ function App() {
|
|||
<Route path="billing">
|
||||
<Route index element={<BillingAdmin />} />
|
||||
<Route path="mandates" element={<BillingMandateView />} />
|
||||
<Route path="subscriptions" element={<AdminSubscriptionsPage />} />
|
||||
</Route>
|
||||
<Route path="subscriptions" element={<AdminSubscriptionsPage />} />
|
||||
<Route path="automation-events" element={<AdminAutomationEventsPage />} />
|
||||
<Route path="automation-logs" element={<AdminAutomationLogsPage />} />
|
||||
<Route path="logs" element={<AdminLogsPage />} />
|
||||
<Route path="mandate-wizard" element={<AdminMandateWizardPage />} />
|
||||
<Route path="invitation-wizard" element={<AdminInvitationWizardPage />} />
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ export const PAGE_ICONS: Record<string, React.ReactNode> = {
|
|||
'page.admin.subscriptions': <FaFileContract />,
|
||||
'page.admin.automationEvents': <FaClock />,
|
||||
'page.admin.automation-events': <FaClock />,
|
||||
'page.admin.automationLogs': <FaClipboardList />,
|
||||
'page.admin.automation-logs': <FaClipboardList />,
|
||||
'page.admin.logs': <FaFileAlt />,
|
||||
'page.admin.mandate-wizard': <FaHatWizard />,
|
||||
'page.admin.mandateWizard': <FaHatWizard />,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import { ChatbotConversationsView } from './views/chatbot/ChatbotConversationsVi
|
|||
import { RealEstatePekView, RealEstateInstanceRolesPlaceholder } from './views/realestate';
|
||||
|
||||
// Automation Views
|
||||
import { AutomationDefinitionsView, AutomationTemplatesView, AutomationLogsView } from './views/automation';
|
||||
import { AutomationDefinitionsView, AutomationTemplatesView } from './views/automation';
|
||||
|
||||
// Workspace Views
|
||||
import { WorkspacePage } from './views/workspace/WorkspacePage';
|
||||
|
|
@ -126,7 +126,6 @@ const VIEW_COMPONENTS: Record<string, Record<string, ViewComponent>> = {
|
|||
automation: {
|
||||
definitions: AutomationDefinitionsView,
|
||||
templates: AutomationTemplatesView,
|
||||
logs: AutomationLogsView,
|
||||
},
|
||||
workspace: {
|
||||
dashboard: WorkspacePage,
|
||||
|
|
|
|||
223
src/pages/admin/AdminAutomationLogsPage.tsx
Normal file
223
src/pages/admin/AdminAutomationLogsPage.tsx
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
/**
|
||||
* AdminAutomationLogsPage
|
||||
*
|
||||
* SysAdmin-only page for viewing consolidated automation execution logs
|
||||
* across all mandates and feature instances.
|
||||
* Uses FormGeneratorTable with backend-driven pagination.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { FaSync, FaCheck, FaExclamationCircle, FaTimes } from 'react-icons/fa';
|
||||
import api from '../../api';
|
||||
import styles from './Admin.module.css';
|
||||
import { FormGeneratorTable, type ColumnConfig } from '../../components/FormGenerator/FormGeneratorTable';
|
||||
|
||||
interface AutomationLogEntry {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
automationId: string;
|
||||
automationLabel: string;
|
||||
mandateName: string;
|
||||
featureInstanceName: string;
|
||||
executedBy: string;
|
||||
status: string;
|
||||
workflowId: string;
|
||||
messages: string;
|
||||
}
|
||||
|
||||
const _formatTimestamp = (ts: unknown): React.ReactNode => {
|
||||
if (!ts || typeof ts !== 'number') return <span style={{ color: 'var(--text-tertiary, #999)' }}>–</span>;
|
||||
return new Date(ts * 1000).toLocaleString('de-CH', {
|
||||
day: '2-digit', month: '2-digit', year: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||||
});
|
||||
};
|
||||
|
||||
const _formatStatus = (value: unknown): React.ReactNode => {
|
||||
const status = String(value || '');
|
||||
const map: Record<string, { icon: React.ReactNode; color: string; label: string }> = {
|
||||
completed: { icon: <FaCheck style={{ marginRight: 4 }} />, color: 'var(--success-color, #16a34a)', label: 'Abgeschlossen' },
|
||||
error: { icon: <FaExclamationCircle style={{ marginRight: 4 }} />, color: 'var(--error-color, #dc2626)', label: 'Fehler' },
|
||||
failed: { icon: <FaExclamationCircle style={{ marginRight: 4 }} />, color: 'var(--error-color, #dc2626)', label: 'Fehlgeschlagen' },
|
||||
stopped: { icon: <FaTimes style={{ marginRight: 4 }} />, color: 'var(--warning-color, #d97706)', label: 'Gestoppt' },
|
||||
};
|
||||
const entry = map[status];
|
||||
if (!entry) return status || '–';
|
||||
return (
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', color: entry.color, fontWeight: 500 }}>
|
||||
{entry.icon}{entry.label}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const AdminAutomationLogsPage: React.FC = () => {
|
||||
const [logs, setLogs] = useState<AutomationLogEntry[]>([]);
|
||||
const [pagination, setPagination] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const _fetchLogs = useCallback(async (params?: any) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const requestParams: Record<string, string> = {};
|
||||
if (params && typeof params === 'object') {
|
||||
const paginationObj: any = {};
|
||||
if (params.page !== undefined) paginationObj.page = params.page;
|
||||
if (params.pageSize !== undefined) paginationObj.pageSize = params.pageSize;
|
||||
if (params.sort) paginationObj.sort = params.sort;
|
||||
if (params.filters) paginationObj.filters = params.filters;
|
||||
if (params.search) paginationObj.search = params.search;
|
||||
if (Object.keys(paginationObj).length > 0) {
|
||||
requestParams.pagination = JSON.stringify(paginationObj);
|
||||
}
|
||||
}
|
||||
const response = await api.get('/api/admin/automation-logs', { params: requestParams });
|
||||
const data = response.data;
|
||||
if (data && typeof data === 'object' && 'items' in data) {
|
||||
setLogs(data.items || []);
|
||||
if (data.pagination) setPagination(data.pagination);
|
||||
} else {
|
||||
setLogs(Array.isArray(data) ? data : []);
|
||||
setPagination(null);
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.response?.data?.detail || 'Fehler beim Laden der Ausführungsprotokolle');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => { _fetchLogs(); }, [_fetchLogs]);
|
||||
|
||||
const columns: ColumnConfig[] = useMemo(() => [
|
||||
{
|
||||
key: 'timestamp',
|
||||
label: 'Zeitpunkt',
|
||||
type: 'number' as const,
|
||||
sortable: true,
|
||||
filterable: false,
|
||||
width: 170,
|
||||
minWidth: 140,
|
||||
formatter: _formatTimestamp,
|
||||
},
|
||||
{
|
||||
key: 'automationLabel',
|
||||
label: 'Automatisierung',
|
||||
type: 'string' as const,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
searchable: true,
|
||||
width: 200,
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
key: 'mandateName',
|
||||
label: 'Mandant',
|
||||
type: 'string' as const,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: 150,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
key: 'featureInstanceName',
|
||||
label: 'Feature-Instanz',
|
||||
type: 'string' as const,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: 150,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
key: 'executedBy',
|
||||
label: 'Ausgeführt von',
|
||||
type: 'string' as const,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: 140,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
type: 'string' as const,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: 140,
|
||||
minWidth: 100,
|
||||
formatter: _formatStatus,
|
||||
},
|
||||
{
|
||||
key: 'workflowId',
|
||||
label: 'Workflow-ID',
|
||||
type: 'string' as const,
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
width: 120,
|
||||
minWidth: 80,
|
||||
formatter: (v: unknown) =>
|
||||
v ? <code style={{ fontSize: '0.8em', color: 'var(--text-secondary)' }}>{String(v).slice(0, 8)}…</code> : '–',
|
||||
},
|
||||
{
|
||||
key: 'messages',
|
||||
label: 'Meldungen',
|
||||
type: 'string' as const,
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
searchable: true,
|
||||
width: 300,
|
||||
minWidth: 150,
|
||||
maxWidth: 500,
|
||||
},
|
||||
], []);
|
||||
|
||||
return (
|
||||
<div className={`${styles.adminPage} ${styles.adminPageFill}`}>
|
||||
<div className={styles.pageHeader}>
|
||||
<div>
|
||||
<h1 className={styles.pageTitle}>Ausführungsprotokolle</h1>
|
||||
<p className={styles.pageSubtitle}>
|
||||
Konsolidierte Automation-Logs über alle Mandanten
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.headerActions}>
|
||||
<button
|
||||
className={styles.secondaryButton}
|
||||
onClick={() => _fetchLogs()}
|
||||
disabled={loading}
|
||||
>
|
||||
<FaSync className={loading ? 'spinning' : ''} /> Aktualisieren
|
||||
</button>
|
||||
</div>
|
||||
</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={logs}
|
||||
columns={columns}
|
||||
apiEndpoint="/api/admin/automation-logs"
|
||||
loading={loading}
|
||||
pagination={true}
|
||||
pageSize={25}
|
||||
searchable={true}
|
||||
filterable={true}
|
||||
sortable={true}
|
||||
selectable={false}
|
||||
hookData={{
|
||||
refetch: _fetchLogs,
|
||||
pagination,
|
||||
}}
|
||||
emptyMessage="Keine Ausführungsprotokolle vorhanden"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminAutomationLogsPage;
|
||||
|
|
@ -16,4 +16,5 @@ export { AdminFeatureInstanceUsersPage } from './AdminFeatureInstanceUsersPage';
|
|||
export { AdminMandateRolePermissionsPage } from './AdminMandateRolePermissionsPage';
|
||||
export { AdminUserAccessOverviewPage } from './AdminUserAccessOverviewPage';
|
||||
export { AdminAutomationEventsPage } from './AdminAutomationEventsPage';
|
||||
export { AdminAutomationLogsPage } from './AdminAutomationLogsPage';
|
||||
export { AdminLogsPage } from './AdminLogsPage';
|
||||
|
|
|
|||
|
|
@ -721,7 +721,7 @@ export const BillingAdmin: React.FC = () => {
|
|||
</div>
|
||||
{isSysAdmin && (
|
||||
<Link
|
||||
to="/admin/billing/subscriptions"
|
||||
to="/admin/subscriptions"
|
||||
className={`${styles.button} ${styles.buttonPrimary}`}
|
||||
style={{ whiteSpace: 'nowrap', marginTop: 4 }}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
/**
|
||||
* AutomationLogsView
|
||||
*
|
||||
* Placeholder view for automation execution logs.
|
||||
*/
|
||||
import React from 'react';
|
||||
import styles from '../../FeatureView.module.css';
|
||||
|
||||
export const AutomationLogsView: React.FC = () => (
|
||||
<div className={styles.placeholder}>
|
||||
<h2>Execution Logs</h2>
|
||||
<p>Automatisierungs-Ausführungsprotokolle</p>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -4,4 +4,3 @@
|
|||
|
||||
export { AutomationDefinitionsView } from './AutomationDefinitionsView';
|
||||
export { AutomationTemplatesView } from './AutomationTemplatesView';
|
||||
export { AutomationLogsView } from './AutomationLogsView';
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { useInstanceId } from '../../../hooks/useCurrentInstance';
|
|||
import { useApiRequest } from '../../../hooks/useApi';
|
||||
import { FormGeneratorTable, type ColumnConfig } from '../../../components/FormGenerator/FormGeneratorTable';
|
||||
import { FormGeneratorForm } from '../../../components/FormGenerator/FormGeneratorForm';
|
||||
import { FaSync, FaReceipt, FaDownload } from 'react-icons/fa';
|
||||
import { FaSync, FaDownload } from 'react-icons/fa';
|
||||
import { useToast } from '../../../contexts/ToastContext';
|
||||
import api from '../../../api';
|
||||
import { fetchSyncStatus, syncPositionsToAccounting, type AccountingSyncStatus } from '../../../api/trusteeApi';
|
||||
|
|
@ -293,19 +293,7 @@ export const TrusteePositionsView: React.FC = () => {
|
|||
return col;
|
||||
});
|
||||
|
||||
const createdAtCol = {
|
||||
key: '_createdAt',
|
||||
label: 'Erstellt am',
|
||||
type: 'timestamp' as any,
|
||||
sortable: true,
|
||||
filterable: false,
|
||||
searchable: false,
|
||||
width: 150,
|
||||
minWidth: 120,
|
||||
maxWidth: 200,
|
||||
};
|
||||
|
||||
const allColumns = [...attrColumns, belegeColumn, syncStatusColumn, createdAtCol];
|
||||
const allColumns = [...attrColumns, belegeColumn, syncStatusColumn];
|
||||
const byKey = new Map(allColumns.map(c => [c.key, c]));
|
||||
|
||||
const ordered: typeof allColumns = [];
|
||||
|
|
|
|||
Loading…
Reference in a new issue