feat(q1): 前端类型安全治理完成 — any 83→40, P0 全部 done

- types/monitor.ts: 新增 6 个运行时记录类型(AlertEventRecord/AlertRuleRecord/
  AgentHostRecord/CronJobRecord/CertExpiryRecord/ApprovalWorkflowStepRecord)
- types/common.ts: 新增 OptionItem 通用下拉类型
- 组件级 any 清理 43 处:
  - AlertEventsTab/AlertRulesTab/AlertChannelDialog/alertShared (19处)
  - AuditOperationsTab normalizeLog + 筛选器 (8处)
  - CronJobManager 任务列表/编辑/操作 (6处)
  - AgentMonitor 主机列表/命令执行 (5处)
  - DashboardRecent 5 张卡片组件 (5处)
  - AlertManager 状态类型对齐 (联动修复)
  - Dashboard.tsx 调用端类型对齐 (联动修复)
- AlertChannel 接口新增 test_status 字段
- ESLint any: 83→40 (<50 目标达成)
- tsc -b / tsc --noEmit: 零错误
- npm run build: ✓ built in 17.11s
- 9/9 核心 API 回归通过
This commit is contained in:
attacker committed 2026-06-27 02:31:26 +08:00
1 parent 0535f30c87
commit bb2b91ebe0
15 files changed
+259 -93

No files matched your search

+19 -17
View File
@@ -22,22 +22,22 @@ import {
import { auditApi } from '@/api';
import { useAuditLogs } from '@/hooks/api';
import { useConfirm } from '@/components/ui/confirm-dialog';
import type { OperationLog } from '@/types';
import type { OperationLog, OptionItem } from '@/types';
// Normalize backend log to frontend type
function normalizeLog(raw: any): OperationLog {
function normalizeLog(raw: Record<string, unknown>): OperationLog {
return {
id: String(raw.id ?? ''),
userId: String(raw.user_id ?? raw.userId ?? ''),
username: raw.username ?? '',
module: raw.module ?? raw.resource ?? '',
action: raw.action ?? '',
resource: raw.resource ?? '',
ip: raw.ip ?? '',
userAgent: raw.user_agent ?? raw.userAgent ?? '',
username: String(raw.username ?? ''),
module: String(raw.module ?? raw.resource ?? ''),
action: String(raw.action ?? ''),
resource: String(raw.resource ?? ''),
ip: String(raw.ip ?? ''),
userAgent: String(raw.user_agent ?? raw.userAgent ?? ''),
result: raw.result === 'failure' ? 'failure' : 'success',
message: raw.message ?? raw.detail ?? '',
createdAt: raw.createdAt ?? raw.created_at ?? '',
message: String(raw.message ?? raw.detail ?? ''),
createdAt: String(raw.createdAt ?? raw.created_at ?? ''),
};
}
@@ -70,8 +70,8 @@ export function AuditOperationsTab() {
() => (auditData?.list || []).map((raw) => normalizeLog(raw)),
[auditData],
);
const [modules, setModules] = useState<string[]>([]);
const [actions, setActions] = useState<string[]>([]);
const [modules, setModules] = useState<OptionItem[]>([]);
const [actions, setActions] = useState<OptionItem[]>([]);
// Detail modal
const [detailLog, setDetailLog] = useState<OperationLog | null>(null);
@@ -84,8 +84,10 @@ export function AuditOperationsTab() {
const fetchOptions = useCallback(async () => {
try {
const [modRes, actRes] = await Promise.all([auditApi.getModules(), auditApi.getActions()]);
setModules((modRes as any)?.list ?? (modRes as any) ?? []);
setActions((actRes as any)?.list ?? (actRes as any) ?? []);
const modData = modRes as { list?: OptionItem[] } | OptionItem[];
const actData = actRes as { list?: OptionItem[] } | OptionItem[];
setModules(Array.isArray(modData) ? modData : (modData?.list ?? []));
setActions(Array.isArray(actData) ? actData : (actData?.list ?? []));
} catch {
/* noop */
}
@@ -120,7 +122,7 @@ export function AuditOperationsTab() {
const handleExport = async () => {
try {
const params: any = {};
const params: Record<string, string> = {};
if (moduleFilter) params.module = moduleFilter;
if (actionFilter) params.action = actionFilter;
await auditApi.exportLogs(params);
@@ -167,7 +169,7 @@ export function AuditOperationsTab() {
className="h-9 px-3 text-sm rounded-md bg-slate-800/50 border border-slate-700 text-slate-300 focus:outline-none focus:border-blue-500"
>
<option value="">全部模块</option>
{modules.map((m: any) => (
{modules.map((m) => (
<option key={m.value} value={m.value}>
{m.label}
</option>
@@ -181,7 +183,7 @@ export function AuditOperationsTab() {
className="h-9 px-3 text-sm rounded-md bg-slate-800/50 border border-slate-700 text-slate-300 focus:outline-none focus:border-blue-500"
>
<option value="">全部操作</option>
{actions.map((a: any) => (
{actions.map((a) => (
<option key={a.value} value={a.value}>
{a.label}
</option>