- 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,测试卡片正常
158 lines
7.1 KiB
TypeScript
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' }),
|
|
}
|