feat: FlowStack 发布系统首个可运行版本 (gin + React)

- 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)
This commit is contained in:
Logan.Li committed 2026-10-27 02:44:10 +08:00
commit 04d1342b27
115 files changed
+23268

No files matched your search

+167
View File
@@ -0,0 +1,167 @@
import {
GitBranch,
Server,
Key,
Cpu,
Wrench,
Download,
ChevronLeft,
ChevronRight,
Rocket,
Zap,
History,
Webhook,
CloudSync,
Puzzle,
Settings
} from 'lucide-react';
type ModuleType = 'pipelines' | 'gitops' | 'batch-release' | 'release-records' | 'environments' | 'credentials' | 'build-nodes' | 'tools' | 'jenkins-import' | 'webhooks' | 'plugins' | 'settings';
interface SidebarProps {
activeModule: ModuleType;
onModuleChange: (module: ModuleType) => void;
collapsed: boolean;
onToggleCollapse: () => void;
}
const menuGroups: { label: string; items: { id: ModuleType; label: string; icon: typeof GitBranch; badge: string | null }[] }[] = [
{
label: '发布',
items: [
{ id: 'pipelines', label: '流水线', icon: GitBranch, badge: null },
{ id: 'gitops', label: 'GitOps 部署', icon: CloudSync, badge: 'New' },
{ id: 'batch-release', label: '批量发布', icon: Zap, badge: null },
{ id: 'release-records', label: '发布记录', icon: History, badge: null },
],
},
{
label: '资源',
items: [
{ id: 'environments', label: '环境管理', icon: Server, badge: null },
{ id: 'credentials', label: '凭证管理', icon: Key, badge: null },
{ id: 'build-nodes', label: '构建节点', icon: Cpu, badge: '3' },
],
},
{
label: '集成',
items: [
{ id: 'jenkins-import', label: 'Jenkins导入', icon: Download, badge: null },
{ id: 'webhooks', label: 'Webhook', icon: Webhook, badge: null },
{ id: 'tools', label: '工具配置', icon: Wrench, badge: null },
],
},
{
label: '系统',
items: [
{ id: 'plugins', label: '插件市场', icon: Puzzle, badge: '12' },
{ id: 'settings', label: '系统设置', icon: Settings, badge: null },
],
},
];
export default function Sidebar({ activeModule, onModuleChange, collapsed, onToggleCollapse }: SidebarProps) {
return (
<div
className="flex flex-col border-r transition-all duration-300 ease-in-out h-full"
style={{
width: collapsed ? '56px' : '200px',
background: 'var(--panel-bg)',
borderColor: 'var(--border-color)',
}}
>
{/* Logo area */}
<div
className="flex items-center gap-2 px-3 h-12 border-b flex-shrink-0"
style={{ borderColor: 'var(--border-color)' }}
>
<div
className="w-7 h-7 rounded-md flex items-center justify-center flex-shrink-0"
style={{ background: 'var(--accent-blue)' }}
>
<Rocket className="w-4 h-4 text-white" />
</div>
{!collapsed && (
<span className="text-[13px] font-semibold truncate" style={{ color: 'var(--text-primary)' }}>
应用发布
</span>
)}
</div>
{/* Menu groups */}
<div className="flex-1 py-2 overflow-y-auto custom-scrollbar">
{menuGroups.map((group) => (
<div key={group.label} className="mb-2">
{!collapsed && (
<div
className="px-3 py-1 text-[10px] font-semibold uppercase tracking-wider"
style={{ color: 'var(--text-tertiary)' }}
>
{group.label}
</div>
)}
<div className="px-2 space-y-[2px]">
{group.items.map((item) => {
const Icon = item.icon;
const isActive = activeModule === item.id;
return (
<button
key={item.id}
onClick={() => onModuleChange(item.id)}
className="w-full flex items-center gap-2.5 px-2.5 py-[7px] rounded-md transition-all duration-150 group relative"
style={{
background: isActive ? 'var(--accent-blue-light)' : 'transparent',
color: isActive ? 'var(--accent-blue)' : 'var(--text-secondary)',
}}
title={collapsed ? item.label : undefined}
>
<Icon className="w-[18px] h-[18px] flex-shrink-0" />
{!collapsed && (
<>
<span className="text-[13px] font-medium truncate flex-1 text-left">
{item.label}
</span>
{item.badge && (
<span
className="text-[9px] px-1 py-[1px] rounded font-medium"
style={{
background: item.badge === 'New' ? '#ddf4ff' : 'var(--panel-bg-secondary)',
color: item.badge === 'New' ? 'var(--accent-blue)' : 'var(--text-tertiary)',
}}
>
{item.badge}
</span>
)}
</>
)}
{isActive && !collapsed && (
<div
className="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-5 rounded-r-full"
style={{ background: 'var(--accent-blue)' }}
/>
)}
</button>
);
})}
</div>
</div>
))}
</div>
{/* Collapse toggle */}
<div
className="px-2 py-2 border-t flex justify-center"
style={{ borderColor: 'var(--border-color)' }}
>
<button
onClick={onToggleCollapse}
className="p-1.5 rounded-md transition-colors hover:bg-black/5"
style={{ color: 'var(--text-tertiary)' }}
title={collapsed ? '展开' : '收起'}
>
{collapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
</button>
</div>
</div>
);
}