feat: 定时任务编辑入口全栈打通 + 修复ApprovalFlow编译错误 + 完成大文件拆分重构
- CronJobManager: 编辑按钮接通 onClick → openEdit(job),弹出编辑对话框预填数据,
复用新建/编辑共用 Dialog(editingId 区分模式),标题/按钮文案动态切换,
保存时根据 editingId 调用 createJob 或 updateJob — 消除占位按钮
- ApprovalFlow: 修复 API 方法名不匹配导致 tsc -b 编译失败
(getInstances→getProcesses, getMyApprovals→getProcesses({status:'pending'}),
approveInstance→approveProcess, rejectInstance→rejectProcess)
+ 添加 ApprovalFlowManager 导出别名兼容 App.tsx 导入
- 合并已完成的大文件拆分重构: automation.go→automation_cron/script/playbook,
ldap.go→ldap_ou/ldap_users, server.go→cloud_accounts/domain/platform/region,
user.go→user_admin; 前端 Dashboard/K8s/Server/Approval/Monitor 子组件拆分
// Logan.Li
This commit is contained in:
1 parent
8be63ac168
commit
a7e2351ad2
37 files changed
+7374
-4891
No files matched your search
@@ -0,0 +1,359 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import {
|
||||
Rocket,
|
||||
AlertTriangle,
|
||||
ArrowRight,
|
||||
Cloud,
|
||||
Shield,
|
||||
} from 'lucide-react';
|
||||
|
||||
// 告警消息卡片
|
||||
export function AlertMessagesCard({ alerts }: { alerts: any[] }) {
|
||||
const recentAlerts = alerts.filter((a) => a.status === 'firing').slice(0, 8);
|
||||
|
||||
return (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-slate-100">告警消息</CardTitle>
|
||||
<CardDescription className="text-slate-500">最近触发的告警事件</CardDescription>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="text-slate-400 hover:text-slate-100">
|
||||
查看全部
|
||||
<ArrowRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ScrollArea className="h-[320px]">
|
||||
<div className="space-y-2">
|
||||
{recentAlerts.map((alert) => (
|
||||
<div
|
||||
key={alert.id}
|
||||
className="flex items-start gap-3 p-3 rounded-lg bg-slate-800/50 hover:bg-slate-800 transition-colors"
|
||||
>
|
||||
<div className={`w-2 h-2 rounded-full mt-1.5 ${
|
||||
alert.severity === 'critical' ? 'bg-red-400' :
|
||||
alert.severity === 'warning' ? 'bg-amber-400' : 'bg-blue-400'
|
||||
}`} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-slate-200 truncate">{alert.rule_name}</span>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${
|
||||
alert.severity === 'critical'
|
||||
? 'border-red-500/30 text-red-400 bg-red-500/10'
|
||||
: alert.severity === 'warning'
|
||||
? 'border-amber-500/30 text-amber-400 bg-amber-500/10'
|
||||
: 'border-blue-500/30 text-blue-400 bg-blue-500/10'
|
||||
}`}
|
||||
>
|
||||
{alert.severity === 'critical' ? '严重' : alert.severity === 'warning' ? '警告' : '提示'}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 mt-1">{alert.target}</p>
|
||||
<p className="text-xs text-slate-500">{alert.message}</p>
|
||||
</div>
|
||||
<span className="text-xs text-slate-500 whitespace-nowrap">
|
||||
{alert.created_at ? new Date(alert.created_at).toLocaleString() : ''}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// 证书到期卡片
|
||||
export function CertificateExpiryCard({ expCerts }: { expCerts: any[] }) {
|
||||
const expiringDomains = (expCerts ?? [])
|
||||
.map((d) => ({
|
||||
...d,
|
||||
daysUntilExpiry: d.days_until_expiry ?? Math.ceil((new Date(d.expire_date).getTime() - Date.now()) / (1000 * 60 * 60 * 24)),
|
||||
}))
|
||||
.sort((a, b) => a.daysUntilExpiry - b.daysUntilExpiry);
|
||||
|
||||
const criticalCount = expiringDomains.filter((d) => d.daysUntilExpiry < 30).length;
|
||||
const warningCount = expiringDomains.filter((d) => d.daysUntilExpiry >= 30 && d.daysUntilExpiry < 60).length;
|
||||
|
||||
return (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-slate-100">证书到期提醒</CardTitle>
|
||||
<CardDescription className="text-slate-500">SSL证书到期时间监控</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{criticalCount > 0 && (
|
||||
<Badge className="bg-red-500/20 text-red-400 border-0">
|
||||
<AlertTriangle className="h-3 w-3 mr-1" />
|
||||
{criticalCount} 即将过期
|
||||
</Badge>
|
||||
)}
|
||||
{warningCount > 0 && (
|
||||
<Badge className="bg-amber-500/20 text-amber-400 border-0">
|
||||
{warningCount} 30天内过期
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ScrollArea className="h-[280px]">
|
||||
<div className="space-y-2">
|
||||
{expiringDomains.map((domain) => (
|
||||
<div
|
||||
key={domain.id}
|
||||
className="flex items-center justify-between p-3 rounded-lg bg-slate-800/50 hover:bg-slate-800 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${
|
||||
domain.daysUntilExpiry < 30 ? 'bg-red-500/10 text-red-400' :
|
||||
domain.daysUntilExpiry < 60 ? 'bg-amber-500/10 text-amber-400' :
|
||||
'bg-emerald-500/10 text-emerald-400'
|
||||
}`}>
|
||||
<Shield className="h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-200">{domain.name}</p>
|
||||
<p className="text-xs text-slate-500">到期: {domain.expire_date?.substring(0, 10)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${
|
||||
domain.daysUntilExpiry < 30
|
||||
? 'border-red-500/30 text-red-400 bg-red-500/10'
|
||||
: domain.daysUntilExpiry < 60
|
||||
? 'border-amber-500/30 text-amber-400 bg-amber-500/10'
|
||||
: 'border-emerald-500/30 text-emerald-400 bg-emerald-500/10'
|
||||
}`}
|
||||
>
|
||||
{domain.daysUntilExpiry < 0 ? '已过期' : `${domain.daysUntilExpiry} 天`}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// 发布历史卡片
|
||||
export function ReleaseHistoryCard({ releases }: { releases: any[] }) {
|
||||
return (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-slate-100">发布历史</CardTitle>
|
||||
<CardDescription className="text-slate-500">今日应用发布记录</CardDescription>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="text-slate-400 hover:text-slate-100">
|
||||
查看全部
|
||||
<ArrowRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
{releases.slice(0, 5).map((release) => (
|
||||
<div
|
||||
key={release.id}
|
||||
className="flex items-center gap-3 p-3 rounded-lg bg-slate-800/50 hover:bg-slate-800 transition-colors"
|
||||
>
|
||||
<div
|
||||
className={`w-8 h-8 rounded-lg flex items-center justify-center ${
|
||||
release.status === 'success'
|
||||
? 'bg-emerald-500/10 text-emerald-400'
|
||||
: release.status === 'failed'
|
||||
? 'bg-red-500/10 text-red-400'
|
||||
: release.status === 'deploying'
|
||||
? 'bg-blue-500/10 text-blue-400'
|
||||
: 'bg-amber-500/10 text-amber-400'
|
||||
}`}
|
||||
>
|
||||
<Rocket className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-slate-200">{release.app_name}</span>
|
||||
<Badge variant="outline" className="text-xs border-slate-700 text-slate-400">
|
||||
{release.version}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-0.5">
|
||||
<span className="text-xs text-slate-500">{release.environment}</span>
|
||||
<span className="text-xs text-slate-500">·</span>
|
||||
<span className="text-xs text-slate-500">{release.commit_id}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${
|
||||
release.status === 'success'
|
||||
? 'border-emerald-500/30 text-emerald-400 bg-emerald-500/10'
|
||||
: release.status === 'failed'
|
||||
? 'border-red-500/30 text-red-400 bg-red-500/10'
|
||||
: release.status === 'deploying'
|
||||
? 'border-blue-500/30 text-blue-400 bg-blue-500/10'
|
||||
: 'border-amber-500/30 text-amber-400 bg-amber-500/10'
|
||||
}`}
|
||||
>
|
||||
{release.status === 'success'
|
||||
? '成功'
|
||||
: release.status === 'failed'
|
||||
? '失败'
|
||||
: release.status === 'deploying'
|
||||
? '发布中'
|
||||
: release.status === 'rolled_back'
|
||||
? '已回滚'
|
||||
: '构建中'}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// 服务器状态卡片
|
||||
export function ServerStatusCard({ servers }: { servers: any[] }) {
|
||||
const runningServers = servers.filter((s) => s.status === 'running').length;
|
||||
const stoppedServers = servers.filter((s) => s.status === 'stopped').length;
|
||||
const maintenanceServers = servers.filter((s) => s.status === 'maintenance').length;
|
||||
|
||||
return (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-slate-100">服务器状态</CardTitle>
|
||||
<CardDescription className="text-slate-500">服务器运行状态分布</CardDescription>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="text-slate-400 hover:text-slate-100">
|
||||
查看全部
|
||||
<ArrowRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-3 gap-4 mb-4">
|
||||
<div className="text-center p-3 rounded-lg bg-emerald-500/10">
|
||||
<div className="text-2xl font-bold text-emerald-400">{runningServers}</div>
|
||||
<div className="text-xs text-slate-400 mt-1">运行中</div>
|
||||
</div>
|
||||
<div className="text-center p-3 rounded-lg bg-amber-500/10">
|
||||
<div className="text-2xl font-bold text-amber-400">{maintenanceServers}</div>
|
||||
<div className="text-xs text-slate-400 mt-1">维护中</div>
|
||||
</div>
|
||||
<div className="text-center p-3 rounded-lg bg-slate-500/10">
|
||||
<div className="text-2xl font-bold text-slate-400">{stoppedServers}</div>
|
||||
<div className="text-xs text-slate-400 mt-1">已停止</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{servers.slice(0, 4).map((server) => (
|
||||
<div key={server.id} className="flex items-center gap-3 p-2 rounded-lg bg-slate-800/50">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
server.status === 'running'
|
||||
? 'bg-emerald-400'
|
||||
: server.status === 'maintenance'
|
||||
? 'bg-amber-400'
|
||||
: 'bg-slate-400'
|
||||
}`}
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm text-slate-200 truncate">{server.hostname}</p>
|
||||
<p className="text-xs text-slate-500">{server.ip}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-xs text-slate-400">CPU {Number(server.cpu_usage ?? 0).toFixed(1)}%</p>
|
||||
<p className="text-xs text-slate-400">内存 {Number(server.memory_usage ?? 0).toFixed(1)}%</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// K8s集群状态卡片
|
||||
export function K8sClusterCard({ clusters }: { clusters: any[] }) {
|
||||
return (
|
||||
<Card className="bg-slate-900 border-slate-800">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-slate-100">K8s集群</CardTitle>
|
||||
<CardDescription className="text-slate-500">Kubernetes集群健康状态</CardDescription>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="text-slate-400 hover:text-slate-100">
|
||||
查看全部
|
||||
<ArrowRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{clusters.map((cluster) => {
|
||||
const cpuUsage = Number(cluster.cpu_usage ?? 0);
|
||||
const memoryUsage = Number(cluster.memory_usage ?? 0);
|
||||
return (
|
||||
<div key={cluster.id} className="p-3 rounded-lg bg-slate-800/50">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Cloud className="h-4 w-4 text-blue-400" />
|
||||
<span className="text-sm font-medium text-slate-200">{cluster.name}</span>
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${
|
||||
cluster.status === 'healthy'
|
||||
? 'border-emerald-500/30 text-emerald-400 bg-emerald-500/10'
|
||||
: cluster.status === 'warning'
|
||||
? 'border-amber-500/30 text-amber-400 bg-amber-500/10'
|
||||
: 'border-red-500/30 text-red-400 bg-red-500/10'
|
||||
}`}
|
||||
>
|
||||
{cluster.status === 'healthy' ? '健康' : cluster.status === 'warning' ? '警告' : '异常'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2 text-center">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-slate-200">{cluster.node_count ?? cluster.nodes ?? 0}</div>
|
||||
<div className="text-xs text-slate-500">节点</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-slate-200">{cluster.pod_count ?? cluster.pods ?? 0}</div>
|
||||
<div className="text-xs text-slate-500">Pod</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-slate-200">{cpuUsage.toFixed(1)}%</div>
|
||||
<div className="text-xs text-slate-500">CPU</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-slate-200">{memoryUsage.toFixed(1)}%</div>
|
||||
<div className="text-xs text-slate-500">内存</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user