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:
commit
04d1342b27
115 files changed
+23268
No files matched your search
@@ -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}`),
|
||||
}
|
||||
Reference in new issue
Block a user