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 { GDPRPage } from './pages/GDPR';
|
||||||
import StorePage from './pages/Store';
|
import StorePage from './pages/Store';
|
||||||
import { FeatureViewPage } from './pages/FeatureView';
|
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 { AdminMandateWizardPage, AdminInvitationWizardPage } from './pages/admin/wizards';
|
||||||
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
|
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
|
||||||
import { BillingDataView, BillingAdmin, BillingMandateView, AdminSubscriptionsPage } from './pages/billing';
|
import { BillingDataView, BillingAdmin, BillingMandateView, AdminSubscriptionsPage } from './pages/billing';
|
||||||
|
|
@ -188,9 +188,10 @@ function App() {
|
||||||
<Route path="billing">
|
<Route path="billing">
|
||||||
<Route index element={<BillingAdmin />} />
|
<Route index element={<BillingAdmin />} />
|
||||||
<Route path="mandates" element={<BillingMandateView />} />
|
<Route path="mandates" element={<BillingMandateView />} />
|
||||||
<Route path="subscriptions" element={<AdminSubscriptionsPage />} />
|
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="subscriptions" element={<AdminSubscriptionsPage />} />
|
||||||
<Route path="automation-events" element={<AdminAutomationEventsPage />} />
|
<Route path="automation-events" element={<AdminAutomationEventsPage />} />
|
||||||
|
<Route path="automation-logs" element={<AdminAutomationLogsPage />} />
|
||||||
<Route path="logs" element={<AdminLogsPage />} />
|
<Route path="logs" element={<AdminLogsPage />} />
|
||||||
<Route path="mandate-wizard" element={<AdminMandateWizardPage />} />
|
<Route path="mandate-wizard" element={<AdminMandateWizardPage />} />
|
||||||
<Route path="invitation-wizard" element={<AdminInvitationWizardPage />} />
|
<Route path="invitation-wizard" element={<AdminInvitationWizardPage />} />
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ export const PAGE_ICONS: Record<string, React.ReactNode> = {
|
||||||
'page.admin.subscriptions': <FaFileContract />,
|
'page.admin.subscriptions': <FaFileContract />,
|
||||||
'page.admin.automationEvents': <FaClock />,
|
'page.admin.automationEvents': <FaClock />,
|
||||||
'page.admin.automation-events': <FaClock />,
|
'page.admin.automation-events': <FaClock />,
|
||||||
|
'page.admin.automationLogs': <FaClipboardList />,
|
||||||
|
'page.admin.automation-logs': <FaClipboardList />,
|
||||||
'page.admin.logs': <FaFileAlt />,
|
'page.admin.logs': <FaFileAlt />,
|
||||||
'page.admin.mandate-wizard': <FaHatWizard />,
|
'page.admin.mandate-wizard': <FaHatWizard />,
|
||||||
'page.admin.mandateWizard': <FaHatWizard />,
|
'page.admin.mandateWizard': <FaHatWizard />,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { ChatbotConversationsView } from './views/chatbot/ChatbotConversationsVi
|
||||||
import { RealEstatePekView, RealEstateInstanceRolesPlaceholder } from './views/realestate';
|
import { RealEstatePekView, RealEstateInstanceRolesPlaceholder } from './views/realestate';
|
||||||
|
|
||||||
// Automation Views
|
// Automation Views
|
||||||
import { AutomationDefinitionsView, AutomationTemplatesView, AutomationLogsView } from './views/automation';
|
import { AutomationDefinitionsView, AutomationTemplatesView } from './views/automation';
|
||||||
|
|
||||||
// Workspace Views
|
// Workspace Views
|
||||||
import { WorkspacePage } from './views/workspace/WorkspacePage';
|
import { WorkspacePage } from './views/workspace/WorkspacePage';
|
||||||
|
|
@ -126,7 +126,6 @@ const VIEW_COMPONENTS: Record<string, Record<string, ViewComponent>> = {
|
||||||
automation: {
|
automation: {
|
||||||
definitions: AutomationDefinitionsView,
|
definitions: AutomationDefinitionsView,
|
||||||
templates: AutomationTemplatesView,
|
templates: AutomationTemplatesView,
|
||||||
logs: AutomationLogsView,
|
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
dashboard: WorkspacePage,
|
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 { AdminMandateRolePermissionsPage } from './AdminMandateRolePermissionsPage';
|
||||||
export { AdminUserAccessOverviewPage } from './AdminUserAccessOverviewPage';
|
export { AdminUserAccessOverviewPage } from './AdminUserAccessOverviewPage';
|
||||||
export { AdminAutomationEventsPage } from './AdminAutomationEventsPage';
|
export { AdminAutomationEventsPage } from './AdminAutomationEventsPage';
|
||||||
|
export { AdminAutomationLogsPage } from './AdminAutomationLogsPage';
|
||||||
export { AdminLogsPage } from './AdminLogsPage';
|
export { AdminLogsPage } from './AdminLogsPage';
|
||||||
|
|
|
||||||
|
|
@ -721,7 +721,7 @@ export const BillingAdmin: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
{isSysAdmin && (
|
{isSysAdmin && (
|
||||||
<Link
|
<Link
|
||||||
to="/admin/billing/subscriptions"
|
to="/admin/subscriptions"
|
||||||
className={`${styles.button} ${styles.buttonPrimary}`}
|
className={`${styles.button} ${styles.buttonPrimary}`}
|
||||||
style={{ whiteSpace: 'nowrap', marginTop: 4 }}
|
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 { AutomationDefinitionsView } from './AutomationDefinitionsView';
|
||||||
export { AutomationTemplatesView } from './AutomationTemplatesView';
|
export { AutomationTemplatesView } from './AutomationTemplatesView';
|
||||||
export { AutomationLogsView } from './AutomationLogsView';
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { useInstanceId } from '../../../hooks/useCurrentInstance';
|
||||||
import { useApiRequest } from '../../../hooks/useApi';
|
import { useApiRequest } from '../../../hooks/useApi';
|
||||||
import { FormGeneratorTable, type ColumnConfig } from '../../../components/FormGenerator/FormGeneratorTable';
|
import { FormGeneratorTable, type ColumnConfig } from '../../../components/FormGenerator/FormGeneratorTable';
|
||||||
import { FormGeneratorForm } from '../../../components/FormGenerator/FormGeneratorForm';
|
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 { useToast } from '../../../contexts/ToastContext';
|
||||||
import api from '../../../api';
|
import api from '../../../api';
|
||||||
import { fetchSyncStatus, syncPositionsToAccounting, type AccountingSyncStatus } from '../../../api/trusteeApi';
|
import { fetchSyncStatus, syncPositionsToAccounting, type AccountingSyncStatus } from '../../../api/trusteeApi';
|
||||||
|
|
@ -293,19 +293,7 @@ export const TrusteePositionsView: React.FC = () => {
|
||||||
return col;
|
return col;
|
||||||
});
|
});
|
||||||
|
|
||||||
const createdAtCol = {
|
const allColumns = [...attrColumns, belegeColumn, syncStatusColumn];
|
||||||
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 byKey = new Map(allColumns.map(c => [c.key, c]));
|
const byKey = new Map(allColumns.map(c => [c.key, c]));
|
||||||
|
|
||||||
const ordered: typeof allColumns = [];
|
const ordered: typeof allColumns = [];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue