feat: Ansible 模板克隆 (后端 ClonePlaybook API + 前端卡片悬浮克隆按钮 + 模板管理入口)

This commit is contained in:
attacker committed 2026-06-19 13:52:37 +00:00
1 parent 2d02d63806
commit 7be277658a
4 files changed
+78 -1

No files matched your search

@@ -20,6 +20,7 @@ import {
Play, Server, ChevronRight, ChevronLeft, CheckCircle, Loader2,
Search, RefreshCw, Terminal, Clock, XCircle, FileText, Zap,
Boxes, Shield, Database, Activity, Wrench, Network,
Copy, Plus, ExternalLink,
} from 'lucide-react';
import {
mapPlaybook, categories, getStatusBadge, formatDate,
@@ -100,6 +101,18 @@ export default function AnsibleWorkflow() {
finally { setLoadingPB(false); }
}, []);
// 克隆 Playbook 为自定义模板 // Logan.Li
const clonePlaybook = async (pb: Playbook, e: React.MouseEvent) => {
e.stopPropagation();
try {
const res = await ansibleApi.clonePlaybook(pb.id);
const cloned = mapPlaybook((res as any)?.data ?? res);
toast.success(`已克隆「${cloned.name}」,可在模板管理中编辑`);
await fetchPlaybooks();
setSelectedPB(cloned);
} catch { toast.error('克隆失败'); }
};
const fetchExecutions = useCallback(async () => {
setLoadingExecs(true);
try {
@@ -301,6 +314,22 @@ export default function AnsibleWorkflow() {
</Button>
</div>
{/* 快捷操作栏 */}
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => window.open('/ansible/manage', '_blank')}
className="border-slate-700 text-slate-400 hover:text-slate-200 text-xs h-8"
>
<ExternalLink className="h-3 w-3 mr-1" />
模板管理
</Button>
<span className="text-xs text-slate-600">
点击卡片选择模板执行 · 悬停卡片可克隆为自定义模板
</span>
</div>
{loadingPB ? (
<div className="text-center py-12 text-slate-500">
<Loader2 className="h-6 w-6 animate-spin mx-auto mb-2" />加载中…
@@ -314,12 +343,20 @@ export default function AnsibleWorkflow() {
<div
key={pb.id}
onClick={() => setSelectedPB(selected ? null : pb)}
className={`p-4 rounded-lg border cursor-pointer transition-all ${
className={`group relative p-4 rounded-lg border cursor-pointer transition-all ${
selected
? 'border-blue-500 bg-blue-500/10 ring-1 ring-blue-500/30'
: 'border-slate-700 bg-slate-800/50 hover:border-slate-600'
}`}
>
{/* 悬浮克隆按钮 */}
<button
onClick={(e) => clonePlaybook(pb, e)}
title="克隆为自定义模板"
className="absolute top-2 right-2 p-1.5 rounded-md opacity-0 group-hover:opacity-100 transition-opacity bg-slate-700 hover:bg-blue-600 text-slate-400 hover:text-white"
>
<Copy className="h-3.5 w-3.5" />
</button>
<div className="flex items-start gap-3">
<div className={`p-2 rounded-lg ${selected ? 'bg-blue-500/20' : 'bg-slate-700/50'}`}>
<Icon className={`h-5 w-5 ${selected ? 'text-blue-400' : 'text-slate-400'}`} />