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

@@ -13,25 +13,25 @@ import {
Tag,
} from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import client from '../../api/client';
import {
listDictionaries,
createDictionary,
updateDictionary,
deleteDictionary,
createDictionaryItem,
updateDictionaryItem,
deleteDictionaryItem,
type DictionaryInfo,
type DictionaryItemInfo,
type CreateDictionaryRequest,
type CreateDictionaryItemRequest,
type UpdateDictionaryItemRequest,
} from '../../api/dictionaries';
// --- Types ---
interface DictItem {
id: string;
label: string;
value: string;
sort_order: number;
color?: string;
}
interface Dictionary {
id: string;
name: string;
code: string;
description?: string;
items: DictItem[];
}
type DictItem = DictionaryItemInfo;
type Dictionary = DictionaryInfo;
// --- Component ---
@@ -49,8 +49,8 @@ export default function DictionaryManager() {
const fetchDictionaries = useCallback(async () => {
setLoading(true);
try {
const { data: resp } = await client.get('/config/dictionaries');
setDictionaries(resp.data ?? resp);
const result = await listDictionaries();
setDictionaries(Array.isArray(result) ? result : result.items ?? []);
} catch {
message.error('加载字典列表失败');
}
@@ -63,17 +63,13 @@ export default function DictionaryManager() {
// --- Dictionary CRUD ---
const handleDictSubmit = async (values: {
name: string;
code: string;
description?: string;
}) => {
const handleDictSubmit = async (values: CreateDictionaryRequest) => {
try {
if (editDict) {
await client.put(`/config/dictionaries/${editDict.id}`, values);
await updateDictionary(editDict.id, values);
message.success('字典更新成功');
} else {
await client.post('/config/dictionaries', values);
await createDictionary(values);
message.success('字典创建成功');
}
closeDictModal();
@@ -88,7 +84,7 @@ export default function DictionaryManager() {
const handleDeleteDict = async (id: string) => {
try {
await client.delete(`/config/dictionaries/${id}`);
await deleteDictionary(id);
message.success('字典已删除');
fetchDictionaries();
} catch {
@@ -139,22 +135,14 @@ export default function DictionaryManager() {
setItemModalOpen(true);
};
const handleItemSubmit = async (values: {
label: string;
value: string;
sort_order: number;
color?: string;
}) => {
const handleItemSubmit = async (values: CreateDictionaryItemRequest & { sort_order: number }) => {
if (!activeDictId) return;
try {
if (editItem) {
await client.put(
`/config/dictionaries/${activeDictId}/items/${editItem.id}`,
values,
);
await updateDictionaryItem(activeDictId, editItem.id, values as UpdateDictionaryItemRequest);
message.success('字典项更新成功');
} else {
await client.post(`/config/dictionaries/${activeDictId}/items`, values);
await createDictionaryItem(activeDictId, values);
message.success('字典项添加成功');
}
closeItemModal();
@@ -169,7 +157,7 @@ export default function DictionaryManager() {
const handleDeleteItem = async (dictId: string, itemId: string) => {
try {
await client.delete(`/config/dictionaries/${dictId}/items/${itemId}`);
await deleteDictionaryItem(dictId, itemId);
message.success('字典项已删除');
fetchDictionaries();
} catch {