diff --git a/src/App.tsx b/src/App.tsx index 595379d..172ae21 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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() { } /> } /> - } /> + } /> } /> + } /> } /> } /> } /> diff --git a/src/config/pageRegistry.tsx b/src/config/pageRegistry.tsx index 31922b5..da41d00 100644 --- a/src/config/pageRegistry.tsx +++ b/src/config/pageRegistry.tsx @@ -70,6 +70,8 @@ export const PAGE_ICONS: Record = { 'page.admin.subscriptions': , 'page.admin.automationEvents': , 'page.admin.automation-events': , + 'page.admin.automationLogs': , + 'page.admin.automation-logs': , 'page.admin.logs': , 'page.admin.mandate-wizard': , 'page.admin.mandateWizard': , diff --git a/src/pages/FeatureView.tsx b/src/pages/FeatureView.tsx index 56deae2..02a4563 100644 --- a/src/pages/FeatureView.tsx +++ b/src/pages/FeatureView.tsx @@ -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> = { automation: { definitions: AutomationDefinitionsView, templates: AutomationTemplatesView, - logs: AutomationLogsView, }, workspace: { dashboard: WorkspacePage, diff --git a/src/pages/admin/AdminAutomationLogsPage.tsx b/src/pages/admin/AdminAutomationLogsPage.tsx new file mode 100644 index 0000000..1fcb1dd --- /dev/null +++ b/src/pages/admin/AdminAutomationLogsPage.tsx @@ -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 ; + 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 = { + completed: { icon: , color: 'var(--success-color, #16a34a)', label: 'Abgeschlossen' }, + error: { icon: , color: 'var(--error-color, #dc2626)', label: 'Fehler' }, + failed: { icon: , color: 'var(--error-color, #dc2626)', label: 'Fehlgeschlagen' }, + stopped: { icon: , color: 'var(--warning-color, #d97706)', label: 'Gestoppt' }, + }; + const entry = map[status]; + if (!entry) return status || '–'; + return ( + + {entry.icon}{entry.label} + + ); +}; + +export const AdminAutomationLogsPage: React.FC = () => { + const [logs, setLogs] = useState([]); + const [pagination, setPagination] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const _fetchLogs = useCallback(async (params?: any) => { + try { + setLoading(true); + setError(null); + const requestParams: Record = {}; + 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 ? {String(v).slice(0, 8)}… : '–', + }, + { + key: 'messages', + label: 'Meldungen', + type: 'string' as const, + sortable: false, + filterable: false, + searchable: true, + width: 300, + minWidth: 150, + maxWidth: 500, + }, + ], []); + + return ( +
+
+
+

Ausführungsprotokolle

+

+ Konsolidierte Automation-Logs über alle Mandanten +

+
+
+ +
+
+ + {error && ( +
+ ! + {error} +
+ )} + + +
+ ); +}; + +export default AdminAutomationLogsPage; diff --git a/src/pages/admin/index.ts b/src/pages/admin/index.ts index 34656d2..8b7cfee 100644 --- a/src/pages/admin/index.ts +++ b/src/pages/admin/index.ts @@ -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'; diff --git a/src/pages/billing/BillingAdmin.tsx b/src/pages/billing/BillingAdmin.tsx index d16a47f..67e0cc9 100644 --- a/src/pages/billing/BillingAdmin.tsx +++ b/src/pages/billing/BillingAdmin.tsx @@ -721,7 +721,7 @@ export const BillingAdmin: React.FC = () => { {isSysAdmin && ( diff --git a/src/pages/views/automation/AutomationLogsView.tsx b/src/pages/views/automation/AutomationLogsView.tsx deleted file mode 100644 index 355fae8..0000000 --- a/src/pages/views/automation/AutomationLogsView.tsx +++ /dev/null @@ -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 = () => ( -
-

Execution Logs

-

Automatisierungs-Ausführungsprotokolle

-
-); diff --git a/src/pages/views/automation/index.ts b/src/pages/views/automation/index.ts index 855bc6e..e611c92 100644 --- a/src/pages/views/automation/index.ts +++ b/src/pages/views/automation/index.ts @@ -4,4 +4,3 @@ export { AutomationDefinitionsView } from './AutomationDefinitionsView'; export { AutomationTemplatesView } from './AutomationTemplatesView'; -export { AutomationLogsView } from './AutomationLogsView'; diff --git a/src/pages/views/trustee/TrusteePositionsView.tsx b/src/pages/views/trustee/TrusteePositionsView.tsx index 1c2d1a1..7cf9e43 100644 --- a/src/pages/views/trustee/TrusteePositionsView.tsx +++ b/src/pages/views/trustee/TrusteePositionsView.tsx @@ -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 = [];