feat: 修复仪表盘数据管线 — 补充应用发布种子数据 + 修复limit参数解析 + LDAP认证集成 + User类型完善

后端改进:
- 修复 dashboard/recent-alerts 的 limit 参数解析 (原代码错误地将数字解析为日期)
- 为 dashboard/recent-releases 添加 limit 参数解析支持
- 新增 generateAppReleases 种子数据生成函数 (10条应用发布记录)
- 修复 AlertEvent 的 rule_name/rule_id/severity 数据一致性
- 添加 go-ldap/ldap/v3 依赖并集成 LDAP 认证处理器
- 添加 seed.go 自动种子数据初始化 (admin用户+LDAP配置)
- User model 添加 AuthSource/LDAPConfig 字段

前端改进:
- 修复 User 类型定义以匹配后端 API (real_name/is_admin/auth_source等)
- 修复 Header.tsx 的 real_name 属性引用导致 TypeScript 编译错误
- 统一 API 服务层、暗色主题样式、组件联调优化

验证:
- go build ./... 编译通过
- npm run build 构建通过
- 仪表盘发布历史、告警消息等区域数据正常展示
- 健康检查 /health 正常
This commit is contained in:
attacker committed 2026-06-14 02:11:10 +08:00
1 parent 97c4207ff8
commit ff0b268984
39 files changed
+4283 -1878

No files matched your search

+131 -44
View File
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { cn } from '@/lib/utils';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@@ -34,45 +34,106 @@ import {
Eye,
Edit,
GitBranch,
Loader2,
} from 'lucide-react';
import { approvalFlows, approvalInstances } from '@/mocks/data';
import type { ApprovalFlow as ApprovalFlowType, ApprovalInstance } from '@/types';
import { approvalApi } from '@/services/api';
function parseSteps(steps: any) {
if (Array.isArray(steps)) return steps;
if (typeof steps === 'string') {
try {
const parsed = JSON.parse(steps);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
return [];
}
export function ApprovalFlowManager() {
const [activeTab, setActiveTab] = useState('instances');
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState<string>('all');
const [selectedInstance, setSelectedInstance] = useState<ApprovalInstance | null>(null);
const [processes, setProcesses] = useState<any[]>([]);
const [templates, setTemplates] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [selectedInstance, setSelectedInstance] = useState<any | null>(null);
const filteredInstances = approvalInstances.filter((instance) => {
const loadData = async () => {
try {
setLoading(true);
const [processesData, templatesData] = await Promise.all([
approvalApi.getProcesses(),
approvalApi.getTemplates(),
]);
setProcesses((processesData as any)?.list || (processesData as any) || []);
setTemplates(templatesData || []);
} catch (error) {
console.error('Failed to load approval data:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadData();
}, []);
const filteredInstances = processes.filter((instance) => {
const matchesSearch =
instance.flowName.toLowerCase().includes(searchQuery.toLowerCase()) ||
instance.applicantName.toLowerCase().includes(searchQuery.toLowerCase());
(instance.title || '').toLowerCase().includes(searchQuery.toLowerCase()) ||
(instance.applicant_name || '').toLowerCase().includes(searchQuery.toLowerCase());
const matchesStatus = statusFilter === 'all' || instance.status === statusFilter;
return matchesSearch && matchesStatus;
});
const getStatusBadge = (status: ApprovalInstance['status']) => {
const configs = {
const getStatusBadge = (status: any) => {
const configs: Record<string, any> = {
pending: { label: '审批中', className: 'border-amber-500/30 text-amber-400 bg-amber-500/10', icon: Clock },
approved: { label: '已通过', className: 'border-emerald-500/30 text-emerald-400 bg-emerald-500/10', icon: CheckCircle },
rejected: { label: '已拒绝', className: 'border-red-500/30 text-red-400 bg-red-500/10', icon: XCircle },
};
const config = configs[status];
const config = configs[status] || configs.pending;
const Icon = config.icon;
return (
<Badge variant="outline" className={config.className}>
<config.icon className="h-3 w-3 mr-1" />
<Icon className="h-3 w-3 mr-1" />
{config.label}
</Badge>
);
};
const handleApprove = async (id: any) => {
try {
await approvalApi.approveProcess(id);
setSelectedInstance(null);
loadData();
} catch (error) {
console.error('Failed to approve process:', error);
}
};
const handleReject = async (id: any) => {
try {
await approvalApi.rejectProcess(id);
setSelectedInstance(null);
loadData();
} catch (error) {
console.error('Failed to reject process:', error);
}
};
// 模拟审批流程图
const renderFlowChart = (flow: ApprovalFlowType) => {
const renderFlowChart = (flow: any) => {
const nodes = parseSteps(flow.steps);
if (nodes.length === 0) {
return <div className="text-slate-500 text-center py-4">暂无流程节点</div>;
}
return (
<div className="flex items-center gap-2 overflow-x-auto py-4">
{flow.nodes.map((node, index) => (
<div key={node.id} className="flex items-center">
{nodes.map((node: any, index: number) => (
<div key={node.id || index} className="flex items-center">
<div
className={cn(
'px-4 py-2 rounded-lg border min-w-[100px] text-center',
@@ -84,10 +145,12 @@ export function ApprovalFlowManager() {
>
<p className="text-sm font-medium">{node.name}</p>
{node.approvers && (
<p className="text-xs text-slate-500 mt-1">{node.approvers.join(', ')}</p>
<p className="text-xs text-slate-500 mt-1">
{Array.isArray(node.approvers) ? node.approvers.join(', ') : node.approvers}
</p>
)}
</div>
{index < flow.nodes.length - 1 && (
{index < nodes.length - 1 && (
<ArrowRight className="h-4 w-4 text-slate-600 mx-2" />
)}
</div>
@@ -124,7 +187,7 @@ export function ApprovalFlowManager() {
<div>
<p className="text-sm text-slate-500">待审批</p>
<p className="text-2xl font-bold text-amber-400">
{approvalInstances.filter((i) => i.status === 'pending').length}
{processes.filter((i) => i.status === 'pending').length}
</p>
</div>
<div className="p-3 rounded-lg bg-amber-500/10 text-amber-400">
@@ -139,7 +202,7 @@ export function ApprovalFlowManager() {
<div>
<p className="text-sm text-slate-500">已通过</p>
<p className="text-2xl font-bold text-emerald-400">
{approvalInstances.filter((i) => i.status === 'approved').length}
{processes.filter((i) => i.status === 'approved').length}
</p>
</div>
<div className="p-3 rounded-lg bg-emerald-500/10 text-emerald-400">
@@ -154,7 +217,7 @@ export function ApprovalFlowManager() {
<div>
<p className="text-sm text-slate-500">已拒绝</p>
<p className="text-2xl font-bold text-red-400">
{approvalInstances.filter((i) => i.status === 'rejected').length}
{processes.filter((i) => i.status === 'rejected').length}
</p>
</div>
<div className="p-3 rounded-lg bg-red-500/10 text-red-400">
@@ -168,7 +231,7 @@ export function ApprovalFlowManager() {
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-slate-500">流程模板</p>
<p className="text-2xl font-bold text-slate-100">{approvalFlows.length}</p>
<p className="text-2xl font-bold text-slate-100">{templates.length}</p>
</div>
<div className="p-3 rounded-lg bg-blue-500/10 text-blue-400">
<GitBranch className="h-5 w-5" />
@@ -233,35 +296,42 @@ export function ApprovalFlowManager() {
</TableRow>
</TableHeader>
<TableBody>
{filteredInstances.map((instance) => (
{loading ? (
<TableRow className="border-slate-800">
<TableCell colSpan={7} className="text-center text-slate-500 py-8">
<Loader2 className="h-5 w-5 animate-spin inline mr-2" />
加载中...
</TableCell>
</TableRow>
) : filteredInstances.map((instance) => (
<TableRow key={instance.id} className="border-slate-800 hover:bg-slate-800/50">
<TableCell>
<div className="flex items-center gap-2">
<GitBranch className="h-4 w-4 text-slate-500" />
<span className="text-sm font-medium text-slate-200">{instance.flowName}</span>
<span className="text-sm font-medium text-slate-200">{instance.title}</span>
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-1 text-sm text-slate-300">
<User className="h-3.5 w-3.5 text-slate-500" />
{instance.applicantName}
{instance.applicant_name || instance.applicant}
</div>
</TableCell>
<TableCell>{getStatusBadge(instance.status)}</TableCell>
<TableCell>
<Badge variant="outline" className="border-slate-700 text-slate-400">
{instance.currentNode}
{instance.current_step || '--'}
</Badge>
</TableCell>
<TableCell>
<span className="text-sm text-slate-400">
{new Date(instance.createdAt).toLocaleString()}
{instance.created_at ? new Date(instance.created_at).toLocaleString() : '--'}
</span>
</TableCell>
<TableCell>
<span className="text-sm text-slate-400">
{instance.completedAt
? new Date(instance.completedAt).toLocaleString()
{instance.completed_at
? new Date(instance.completed_at).toLocaleString()
: '--'}
</span>
</TableCell>
@@ -278,11 +348,21 @@ export function ApprovalFlowManager() {
</Button>
{instance.status === 'pending' && (
<>
<Button variant="ghost" size="sm" className="h-8 text-emerald-400 hover:text-emerald-400">
<Button
variant="ghost"
size="sm"
className="h-8 text-emerald-400 hover:text-emerald-400"
onClick={() => handleApprove(instance.id)}
>
<CheckCircle className="h-4 w-4 mr-1" />
通过
</Button>
<Button variant="ghost" size="sm" className="h-8 text-red-400 hover:text-red-400">
<Button
variant="ghost"
size="sm"
className="h-8 text-red-400 hover:text-red-400"
onClick={() => handleReject(instance.id)}
>
<XCircle className="h-4 w-4 mr-1" />
拒绝
</Button>
@@ -301,7 +381,7 @@ export function ApprovalFlowManager() {
<TabsContent value="flows" className="mt-4">
<div className="grid grid-cols-1 gap-4">
{approvalFlows.map((flow) => (
{templates.map((flow) => (
<Card key={flow.id} className="bg-slate-900 border-slate-800">
<CardHeader className="pb-2">
<div className="flex items-start justify-between">
@@ -318,12 +398,12 @@ export function ApprovalFlowManager() {
<Badge
variant="outline"
className={
flow.enabled
flow.status === 1 || flow.status === 'active' || flow.status === true
? 'border-emerald-500/30 text-emerald-400 bg-emerald-500/10'
: 'border-slate-500/30 text-slate-400 bg-slate-500/10'
}
>
{flow.enabled ? '已启用' : '已停用'}
{flow.status === 1 || flow.status === 'active' || flow.status === true ? '已启用' : '已停用'}
</Badge>
<Button variant="ghost" size="icon" className="h-8 w-8 text-slate-400 hover:text-slate-100">
<Edit className="h-4 w-4" />
@@ -336,8 +416,8 @@ export function ApprovalFlowManager() {
{renderFlowChart(flow)}
</div>
<div className="flex items-center justify-between mt-4 text-sm text-slate-500">
<span>创建于 {new Date(flow.createdAt).toLocaleDateString()}</span>
<span>{flow.nodes.length} 个节点</span>
<span>创建于 {flow.created_at ? new Date(flow.created_at).toLocaleDateString() : '--'}</span>
<span>{parseSteps(flow.steps).length} 个节点</span>
</div>
</CardContent>
</Card>
@@ -364,11 +444,11 @@ export function ApprovalFlowManager() {
<DialogHeader>
<DialogTitle className="text-slate-100 flex items-center gap-2">
<GitBranch className="h-5 w-5 text-blue-400" />
{selectedInstance?.flowName}
{selectedInstance?.title}
</DialogTitle>
<DialogDescription className="text-slate-500">
申请人: {selectedInstance?.applicantName} · 创建于{' '}
{selectedInstance && new Date(selectedInstance.createdAt).toLocaleString()}
申请人: {selectedInstance?.applicant_name || selectedInstance?.applicant} · 创建于{' '}
{selectedInstance && selectedInstance.created_at ? new Date(selectedInstance.created_at).toLocaleString() : '--'}
</DialogDescription>
</DialogHeader>
@@ -388,10 +468,10 @@ export function ApprovalFlowManager() {
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-slate-200">提交申请</span>
<span className="text-xs text-slate-500">
{selectedInstance && new Date(selectedInstance.createdAt).toLocaleString()}
{selectedInstance && selectedInstance.created_at ? new Date(selectedInstance.created_at).toLocaleString() : '--'}
</span>
</div>
<p className="text-sm text-slate-400 mt-1">申请人: {selectedInstance?.applicantName}</p>
<p className="text-sm text-slate-400 mt-1">申请人: {selectedInstance?.applicant_name || selectedInstance?.applicant}</p>
</div>
</div>
@@ -418,10 +498,10 @@ export function ApprovalFlowManager() {
</div>
<div className="flex-1">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-slate-200">{selectedInstance?.currentNode}</span>
<span className="text-sm font-medium text-slate-200">{selectedInstance?.current_step || '审批节点'}</span>
<span className="text-xs text-slate-500">
{selectedInstance?.completedAt
? new Date(selectedInstance.completedAt).toLocaleString()
{selectedInstance?.completed_at
? new Date(selectedInstance.completed_at).toLocaleString()
: '进行中'}
</span>
</div>
@@ -441,11 +521,18 @@ export function ApprovalFlowManager() {
<DialogFooter className="gap-2">
{selectedInstance?.status === 'pending' && (
<>
<Button variant="outline" className="border-red-700 text-red-400 hover:bg-red-500/10">
<Button
variant="outline"
className="border-red-700 text-red-400 hover:bg-red-500/10"
onClick={() => handleReject(selectedInstance.id)}
>
<XCircle className="h-4 w-4 mr-2" />
拒绝
</Button>
<Button className="bg-emerald-600 hover:bg-emerald-700 text-white">
<Button
className="bg-emerald-600 hover:bg-emerald-700 text-white"
onClick={() => handleApprove(selectedInstance.id)}
>
<CheckCircle className="h-4 w-4 mr-2" />
通过
</Button>