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:
1 parent
97c4207ff8
commit
ff0b268984
39 files changed
+4283
-1878
No files matched your search
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user