Files
FlowStack/frontend/src/lib/api.ts
T
Logan.Li d9d1dd5e99 feat: 发布成功/失败飞书&Lark 群卡片通知
- internal/lark:飞书自定义机器人交互卡片(wide_screen_mode + 彩色头部
  green/red/yellow + lark_md 字段区 + 查看构建详情按钮),支持签名加签
  (timestamp+sign HMAC-SHA256,密钥 enc:v1: 加密存储)
- 双引擎收口:原生模拟引擎与 Jenkins 真实引擎运行结束均推送卡片;
  应用/分支/构建(#N 或 runId)/状态/时间;Jenkins 构建附
  externalURL 按钮直达构建详情页
- Webhook 优先级:流水线「通知 Webhook」> 全局设置(需启用)
- 系统设置新增飞书通知配置区(启用开关/Webhook/签名密钥只写/
  发送测试卡片);POST /api/settings/test-lark
- E2E 已验证:本地模拟机器人收到完整卡片 JSON,测试卡片正常
2026-10-27 09:37:23 +08:00

158 lines
7.1 KiB
TypeScript

// 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: string
durationMs: number
triggerBy?: string
triggerType?: string
startedAt: string
completedAt?: string
buildNumber?: number
externalRunId?: string
externalUrl?: 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}`),
retryRun: (runId: string) => api<PipelineRunDTO>(`/api/runs/${runId}/retry`, { method: 'POST' }),
// 摘要/设置
getSummary: () => api<any>('/api/summary'),
getSettings: () => api<any>('/api/settings'),
updateSettings: (body: any) => api('/api/settings', { method: 'PUT', body: JSON.stringify(body) }),
// 主机
listHosts: () => api<any[]>('/api/hosts'),
createHost: (b: any) => api('/api/hosts', { method: 'POST', body: JSON.stringify(b) }),
updateHost: (id: any, b: any) => api(`/api/hosts/${id}`, { method: 'PUT', body: JSON.stringify(b) }),
deleteHost: (id: any) => api(`/api/hosts/${id}`, { method: 'DELETE' }),
testHost: (id: any) => api<any>(`/api/hosts/${id}/test`, { method: 'POST' }),
// 集群
listClusters: () => api<any[]>('/api/clusters'),
createCluster: (b: any) => api('/api/clusters', { method: 'POST', body: JSON.stringify(b) }),
updateCluster: (id: any, b: any) => api(`/api/clusters/${id}`, { method: 'PUT', body: JSON.stringify(b) }),
deleteCluster: (id: any) => api(`/api/clusters/${id}`, { method: 'DELETE' }),
testCluster: (id: any) => api<any>(`/api/clusters/${id}/test`, { method: 'POST' }),
// 凭证
listCredentials: (type?: string) => api<any[]>(`/api/credentials${type && type !== 'all' ? `?type=${type}` : ''}`),
createCredential: (b: any) => api('/api/credentials', { method: 'POST', body: JSON.stringify(b) }),
updateCredential: (id: any, b: any) => api(`/api/credentials/${id}`, { method: 'PUT', body: JSON.stringify(b) }),
deleteCredential: (id: any) => api(`/api/credentials/${id}`, { method: 'DELETE' }),
// 构建节点
listBuildNodes: () => api<any[]>('/api/build-nodes'),
createBuildNode: (b: any) => api('/api/build-nodes', { method: 'POST', body: JSON.stringify(b) }),
updateBuildNode: (id: any, b: any) => api(`/api/build-nodes/${id}`, { method: 'PUT', body: JSON.stringify(b) }),
deleteBuildNode: (id: any) => api(`/api/build-nodes/${id}`, { method: 'DELETE' }),
testBuildNode: (id: any) => api<any>(`/api/build-nodes/${id}/test`, { method: 'POST' }),
// 工具
listTools: () => api<any[]>('/api/tools'),
updateTool: (id: any, b: any) => api(`/api/tools/${id}`, { method: 'PUT', body: JSON.stringify(b) }),
checkTool: (id: any) => api<any>(`/api/tools/${id}/check`, { method: 'POST' }),
installTool: (id: any) => api<any>(`/api/tools/${id}/install`, { method: 'POST' }),
// Webhook
listWebhooks: () => api<any[]>('/api/webhooks'),
createWebhook: (b: any) => api('/api/webhooks', { method: 'POST', body: JSON.stringify(b) }),
updateWebhook: (id: any, b: any) => api(`/api/webhooks/${id}`, { method: 'PUT', body: JSON.stringify(b) }),
deleteWebhook: (id: any) => api(`/api/webhooks/${id}`, { method: 'DELETE' }),
testWebhook: (id: any) => api<any>(`/api/webhooks/${id}/test`, { method: 'POST' }),
// Jenkins
listJenkinsServers: () => api<any[]>('/api/jenkins-servers'),
createJenkinsServer: (b: any) => api('/api/jenkins-servers', { method: 'POST', body: JSON.stringify(b) }),
updateJenkinsServer: (id: any, b: any) => api(`/api/jenkins-servers/${id}`, { method: 'PUT', body: JSON.stringify(b) }),
deleteJenkinsServer: (id: any) => api(`/api/jenkins-servers/${id}`, { method: 'DELETE' }),
testJenkinsServer: (id: any) => api<any>(`/api/jenkins-servers/${id}/test`, { method: 'POST' }),
listJenkinsJobs: (id: any) => api<any[]>(`/api/jenkins-servers/${id}/jobs`),
importJenkinsJobs: (id: any, jobs: string[]) =>
api<any>(`/api/jenkins-servers/${id}/import`, { method: 'POST', body: JSON.stringify({ jobs }) }),
// AI 助手与批量发布
aiChat: (messages: { role: string; content: string }[]) =>
api<any>('/api/ai/chat', { method: 'POST', body: JSON.stringify({ messages }) }),
aiRelease: (b: { jobs: string[]; branch?: string; env?: string; parallel?: boolean; jenkinsServerId?: number; autoCreate?: boolean }) =>
api<any>('/api/ai/release', { method: 'POST', body: JSON.stringify(b) }),
batchParse: (b: { text: string; refType?: string; fixedRef?: string }) =>
api<any>('/api/batch/parse', { method: 'POST', body: JSON.stringify(b) }),
testLLM: () => api<any>('/api/settings/test-llm', { method: 'POST' }),
testLark: () => api<any>('/api/settings/test-lark', { method: 'POST' }),
}