feat: systematic functional audit — fix 18 issues across Phase A/B

Phase A (P1 production blockers):
- A1: Apply IP rate limiting to public routes (login/refresh)
- A2: Publish domain events for workflow instance state transitions
  (completed/suspended/resumed/terminated) via outbox pattern
- A3: Replace hardcoded nil UUID default tenant with dynamic DB lookup
- A4: Add GET /api/v1/audit-logs query endpoint with pagination
- A5: Enhance CORS wildcard warning for production environments

Phase B (P2 functional gaps):
- B1: Remove dead erp-common crate (zero references in codebase)
- B2: Refactor 5 settings pages to use typed API modules instead of
  direct client calls; create api/themes.ts; delete dead errors.ts
- B3: Add resume/suspend buttons to InstanceMonitor page
- B4: Remove unused EventHandler trait from erp-core
- B5: Handle task.completed events in message module (send notifications)
- B6: Wire TimeoutChecker as 60s background task
- B7: Auto-skip ServiceTask nodes instead of crashing the process
- B8: Remove empty register_routes() from ErpModule trait and modules
This commit is contained in:
iven
2026-04-12 15:22:28 +08:00
parent 685df5e458
commit 14f431efff
34 changed files with 785 additions and 304 deletions

View File

@@ -3,6 +3,8 @@ import { Button, message, Modal, Table, Tag } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import {
listInstances,
resumeInstance,
suspendInstance,
terminateInstance,
type ProcessInstanceInfo,
} from '../../api/workflowInstances';
@@ -77,6 +79,35 @@ export default function InstanceMonitor() {
});
};
const handleSuspend = async (id: string) => {
Modal.confirm({
title: '确认挂起',
content: '确定要挂起该流程实例吗?挂起后可通过"恢复"按钮继续执行。',
okText: '确定挂起',
okType: 'warning',
cancelText: '取消',
onOk: async () => {
try {
await suspendInstance(id);
message.success('已挂起');
fetchData();
} catch {
message.error('操作失败');
}
},
});
};
const handleResume = async (id: string) => {
try {
await resumeInstance(id);
message.success('已恢复');
fetchData();
} catch {
message.error('操作失败');
}
};
const columns: ColumnsType<ProcessInstanceInfo> = [
{ title: '流程', dataIndex: 'definition_name', key: 'definition_name' },
{ title: '业务键', dataIndex: 'business_key', key: 'business_key' },
@@ -91,14 +122,26 @@ export default function InstanceMonitor() {
render: (v: string) => new Date(v).toLocaleString(),
},
{
title: '操作', key: 'action', width: 150,
title: '操作', key: 'action', width: 220,
render: (_, record) => (
<>
<Button size="small" onClick={() => handleViewFlow(record)} style={{ marginRight: 8 }}>
</Button>
{record.status === 'running' && (
<Button size="small" danger onClick={() => handleTerminate(record.id)}></Button>
<>
<Button size="small" onClick={() => handleSuspend(record.id)} style={{ marginRight: 8 }}>
</Button>
<Button size="small" danger onClick={() => handleTerminate(record.id)}>
</Button>
</>
)}
{record.status === 'suspended' && (
<Button size="small" type="primary" onClick={() => handleResume(record.id)}>
</Button>
)}
</>
),