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

+19
View File
@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}
+49
View File
@@ -0,0 +1,49 @@
import { useEffect, useState, useCallback } from 'react';
export interface LogMessage {
id?: number;
runId?: number;
stageId?: number;
level?: string;
message?: string;
source?: string;
timestamp?: string;
}
/**
* 订阅一次运行的实时日志(SSE)。
* runId 为后端生成的 pr-<timestamp>;不存在时连接会失败并保持 disconnected。
*/
export function usePipelineLogs(runId: string | null, enabled: boolean) {
const [logs, setLogs] = useState<LogMessage[]>([]);
const [connected, setConnected] = useState(false);
const clearLogs = useCallback(() => setLogs([]), []);
useEffect(() => {
if (!enabled || !runId) {
setConnected(false);
return;
}
const es = new EventSource(`/api/runs/${runId}/logs/stream`);
es.addEventListener('connected', () => setConnected(true));
es.addEventListener('log', (evt) => {
try {
const entry: LogMessage = JSON.parse((evt as MessageEvent).data);
setLogs((prev) => [...prev, entry]);
} catch {
// ignore invalid JSON
}
});
es.onerror = () => setConnected(false);
return () => {
es.close();
setConnected(false);
};
}, [runId, enabled]);
return { logs, connected, clearLogs };
}