- Go gin 后端:流水线/节点/连线/运行/日志/GitOps/插件 REST API, SQLite(GORM) 自动建表 + seed,模拟执行引擎 + SSE 实时日志推送, 前端产物 go:embed 嵌入单二进制,一个端口同时服务 API 与页面 - React 19 前端:画布式流水线编辑器(React Flow + Tekton 任务库)、 GitOps 部署、批量发布、发布记录等 14 个模块页面, tRPC 全部替换为 REST(TanStack Query) - 首次启动生成随机密码管理员账号(日志 + data/initial-admin-password.txt) - run.sh 一键构建启动(默认端口 9527,支持 start/stop/restart/build/dev)
89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
import { Pencil, Trash2 } from 'lucide-react';
|
|
import { iconMap } from '@/data/pipelineData';
|
|
import type { PipelineTask } from '@/types/pipeline';
|
|
|
|
interface TaskCardProps {
|
|
task: PipelineTask;
|
|
isSelected: boolean;
|
|
onSelect: () => void;
|
|
onDelete: () => void;
|
|
}
|
|
|
|
// Subtle type indicator colors — just for the small dot/icon, NOT background
|
|
const typeConfig: Record<string, { color: string; label: string }> = {
|
|
build: { color: '#9a6700', label: '构建' },
|
|
deploy: { color: '#1a7f37', label: '部署' },
|
|
test: { color: '#8250df', label: '测试' },
|
|
custom: { color: '#6e7781', label: '自定义' },
|
|
};
|
|
|
|
export default function TaskCard({ task, isSelected, onSelect, onDelete }: TaskCardProps) {
|
|
const config = typeConfig[task.type] || typeConfig.custom;
|
|
const IconComponent = iconMap[task.icon] || iconMap.Settings;
|
|
|
|
const handleDelete = (e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
onDelete();
|
|
};
|
|
|
|
return (
|
|
<div
|
|
onClick={onSelect}
|
|
className="flex items-center gap-2.5 px-3 py-[10px] rounded-md cursor-pointer transition-all duration-150 group mb-[2px] last:mb-0"
|
|
style={{
|
|
background: isSelected ? '#f0f5ff' : 'transparent',
|
|
border: isSelected ? '1px solid #b7d4f0' : '1px solid transparent',
|
|
...(isSelected
|
|
? { boxShadow: '0 0 0 1px rgba(9,105,218,0.08)' }
|
|
: {}),
|
|
}}
|
|
>
|
|
{/* Type icon — small colored dot with icon inside */}
|
|
<div
|
|
className="w-6 h-6 rounded-[6px] flex items-center justify-center flex-shrink-0"
|
|
style={{
|
|
background: `${config.color}10`,
|
|
border: `1px solid ${config.color}25`,
|
|
}}
|
|
>
|
|
<IconComponent className="w-3 h-3" style={{ color: config.color }} />
|
|
</div>
|
|
|
|
{/* Task info */}
|
|
<div className="flex-1 min-w-0">
|
|
<div
|
|
className="text-[13px] font-medium truncate leading-tight"
|
|
style={{ color: isSelected ? 'var(--text-primary)' : 'var(--text-primary)' }}
|
|
>
|
|
{task.name}
|
|
</div>
|
|
<div
|
|
className="text-[11px] truncate leading-tight mt-[1px]"
|
|
style={{ color: 'var(--text-tertiary)' }}
|
|
>
|
|
{config.label}
|
|
{task.scriptType && ` · ${task.scriptType}`}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions — hover reveal */}
|
|
<div className="flex items-center gap-[2px] opacity-0 group-hover:opacity-100 transition-opacity duration-150 flex-shrink-0">
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); onSelect(); }}
|
|
className="p-1 rounded transition-colors hover:bg-black/5"
|
|
title="编辑"
|
|
>
|
|
<Pencil className="w-3 h-3" style={{ color: 'var(--text-tertiary)' }} />
|
|
</button>
|
|
<button
|
|
onClick={handleDelete}
|
|
className="p-1 rounded transition-colors hover:bg-red-50"
|
|
title="删除"
|
|
>
|
|
<Trash2 className="w-3 h-3" style={{ color: 'var(--text-tertiary)' }} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|