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

+95
View File
@@ -0,0 +1,95 @@
// FlowStack gin 后端 REST 客户端
export class ApiError extends Error {
status: number
constructor(message: string, status: number) {
super(message)
this.status = status
}
}
export async function api<T = any>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(path, {
headers: { 'Content-Type': 'application/json', ...(init?.headers || {}) },
...init,
})
if (!res.ok) {
let message = `请求失败 (HTTP ${res.status})`
try {
const body = await res.json()
if (body?.error) message = body.error
} catch {
// 非 JSON 响应体
}
throw new ApiError(message, res.status)
}
return res.json()
}
// ---- 类型定义(与后端 model 对齐) ----
export interface PipelineNodeDTO {
dbId?: number
pipelineId?: number
nodeId: string
type: string
name: string
positionX: number
positionY: number
width: number
height: number
category: string
config?: Record<string, any>
}
export interface PipelineEdgeDTO {
dbId?: number
pipelineId?: number
edgeId: string
source: string
target: string
label?: string
animated?: boolean
}
export interface PipelineDetail {
pipeline: any
nodes: PipelineNodeDTO[]
edges: PipelineEdgeDTO[]
}
export interface PipelineRunDTO {
id: number
runId: string
pipelineId: number
pipelineName: string
branch: string
env: string
status: 'pending' | 'running' | 'success' | 'failed' | 'cancelled'
durationMs: number
startedAt: string
completedAt?: string
}
export const apiClient = {
// 流水线
listPipelines: () => api<any[]>('/api/pipelines'),
getPipeline: (id: number | string) => api<PipelineDetail>(`/api/pipelines/${id}`),
createPipeline: (body: any) => api('/api/pipelines', { method: 'POST', body: JSON.stringify(body) }),
updatePipeline: (id: number | string, body: any) => api(`/api/pipelines/${id}`, { method: 'PUT', body: JSON.stringify(body) }),
deletePipeline: (id: number | string) => api(`/api/pipelines/${id}`, { method: 'DELETE' }),
saveNodes: (id: number | string, nodes: PipelineNodeDTO[]) =>
api(`/api/pipelines/${id}/nodes`, { method: 'PUT', body: JSON.stringify({ nodes }) }),
saveEdges: (id: number | string, edges: PipelineEdgeDTO[]) =>
api(`/api/pipelines/${id}/edges`, { method: 'PUT', body: JSON.stringify({ edges }) }),
// 运行
createRun: (pipelineId: number | string, body?: any) =>
api<PipelineRunDTO>(`/api/pipelines/${pipelineId}/runs`, { method: 'POST', body: JSON.stringify(body || {}) }),
listRuns: (params?: { pipelineId?: number | string; status?: string }) => {
const q = new URLSearchParams()
if (params?.pipelineId) q.set('pipelineId', String(params.pipelineId))
if (params?.status && params.status !== 'all') q.set('status', params.status)
const qs = q.toString()
return api<PipelineRunDTO[]>(`/api/runs${qs ? `?${qs}` : ''}`)
},
getRun: (runId: string) => api<any>(`/api/runs/${runId}`),
}