82 lines
3.4 KiB
TypeScript
82 lines
3.4 KiB
TypeScript
// Ansible Playbook 卡片(复选框 + 名称 + 分类徽章 + 描述 + 标签 + 元信息 + 5 行操作按钮) // Logan.Li
|
|
import React from 'react';
|
|
import { FileCode, Eye, Edit, Copy, Trash2, Play, Loader2 } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
import { categories, type Playbook } from './ansible-helpers';
|
|
|
|
interface AnsiblePlaybookCardProps {
|
|
playbook: Playbook;
|
|
selected: boolean;
|
|
executing: boolean;
|
|
onToggleSelect: (id: number) => void;
|
|
onView: (pb: Playbook) => void;
|
|
onExecute: (pb: Playbook) => void;
|
|
onEdit: (pb: Playbook) => void;
|
|
onClone: (pb: Playbook) => void;
|
|
onDelete: (pb: Playbook) => void;
|
|
}
|
|
|
|
const AnsiblePlaybookCard: React.FC<AnsiblePlaybookCardProps> = ({
|
|
playbook, selected, executing,
|
|
onToggleSelect, onView, onExecute, onEdit, onClone, onDelete,
|
|
}) => {
|
|
return (
|
|
<Card className="hover:shadow-md transition-shadow">
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-start justify-between gap-2">
|
|
<div className="flex items-center gap-2 min-w-0">
|
|
<Checkbox
|
|
checked={selected}
|
|
onCheckedChange={() => onToggleSelect(playbook.id)}
|
|
className="shrink-0"
|
|
/>
|
|
<FileCode className="w-5 h-5 text-blue-500 shrink-0" />
|
|
<CardTitle className="text-base truncate">{playbook.name}</CardTitle>
|
|
</div>
|
|
<Badge variant="outline" className="shrink-0">
|
|
{categories.find(c => c.value === playbook.category)?.label || playbook.category}
|
|
</Badge>
|
|
</div>
|
|
<CardDescription className="line-clamp-2">{playbook.description || '暂无描述'}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="pt-0">
|
|
{playbook.tags.length > 0 && (
|
|
<div className="flex flex-wrap gap-1 mb-3">
|
|
{playbook.tags.map((tag, idx) => (
|
|
<Badge key={idx} variant="secondary" className="text-xs">{tag}</Badge>
|
|
))}
|
|
</div>
|
|
)}
|
|
<div className="flex items-center justify-between text-xs text-muted-foreground mb-3">
|
|
<span>执行 {playbook.run_count} 次</span>
|
|
<span>超时 {playbook.timeout}s</span>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<Button variant="ghost" size="sm" title="查看内容" onClick={() => onView(playbook)}>
|
|
<Eye className="w-4 h-4" />
|
|
</Button>
|
|
<Button variant="ghost" size="sm" title="执行" disabled={executing} onClick={() => onExecute(playbook)}>
|
|
{executing
|
|
? <Loader2 className="w-4 h-4 animate-spin text-blue-600" />
|
|
: <Play className="w-4 h-4 text-green-600" />}
|
|
</Button>
|
|
<Button variant="ghost" size="sm" title="编辑" onClick={() => onEdit(playbook)}>
|
|
<Edit className="w-4 h-4" />
|
|
</Button>
|
|
<Button variant="ghost" size="sm" title="复制" onClick={() => onClone(playbook)}>
|
|
<Copy className="w-4 h-4" />
|
|
</Button>
|
|
<Button variant="ghost" size="sm" title="删除" onClick={() => onDelete(playbook)}>
|
|
<Trash2 className="w-4 h-4 text-red-600" />
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export default AnsiblePlaybookCard;
|