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,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
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
Reference in new issue
Block a user