diff --git a/backend/app/main.py b/backend/app/main.py index 4a5b7f6..b50203e 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -384,6 +384,16 @@ def create_app(): res.append(obj) return TableResponse(data=res, total=len(res)) + @app.get("/api/flows/detail", tags=["Flow"], description="任务详情", response_model=BaseResponse) + async def flows_detail(work_uuid: str, user: DB.User = Depends(get_current_user)): + work = await DB.Work.filter(uuid=work_uuid).first() + if not work: + return BaseResponse(success=False, message="not found") + work_content = await DB.WorkContent.filter(work_id=work.id).first() + res = work.json() + res["content"] = work_content.json() + return BaseResponse(success=True, data=res) + @app.get("/api/flows/status", tags=["Flow"], description="获取任务状态") async def flows_status(token: str = ""): # 如果出现长时间链接被中断,可以设置 nginx 的 proxy_read_timeout diff --git a/frontend/src/api/flow.js b/frontend/src/api/flow.js index 409282c..371a657 100644 --- a/frontend/src/api/flow.js +++ b/frontend/src/api/flow.js @@ -12,6 +12,12 @@ export const flowStop = async (id, data = {}) => { return request("/api/stop?work_uuid=" + id, "POST", data) } +export const flowDetail = async (id) => { + return request("/api/flows/detail?work_uuid=" + id, "GET") +} + + + diff --git a/frontend/src/pages/Flow/Detail.css b/frontend/src/pages/Flow/Detail.css index ce80b59..30b2f9f 100644 --- a/frontend/src/pages/Flow/Detail.css +++ b/frontend/src/pages/Flow/Detail.css @@ -5,4 +5,5 @@ padding: 16px 8px; box-sizing: border-box; text-align: center; -} \ No newline at end of file +} + diff --git a/frontend/src/pages/Flow/Detail.jsx b/frontend/src/pages/Flow/Detail.jsx index fdc007f..9d53374 100644 --- a/frontend/src/pages/Flow/Detail.jsx +++ b/frontend/src/pages/Flow/Detail.jsx @@ -1,6 +1,6 @@ -import { Button, Card, Col, Row, Typography } from "antd" -import { useNavigate } from "react-router-dom"; -import { useCallback } from 'react'; +import { Button, Card, Col, message, Row, Typography } from "antd" +import { useNavigate, useParams } from "react-router-dom"; +import { useCallback, useEffect, useRef, useState } from 'react'; import { ReactFlow, MiniMap, @@ -10,8 +10,12 @@ import { useEdgesState, addEdge, Position, + useReactFlow, + ReactFlowProvider, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; +import { flowDetail } from "../../api/flow"; +import EditNode from "./components/EditNode"; const COLORS = { input: "#89A8B2", @@ -32,23 +36,18 @@ const initialNodes = [ backgroundColor: COLORS["input"], } }, - { - id: '2', - position: { x: 200, y: 0 }, - data: { label: '2' }, - sourcePosition: 'right', - targetPosition: 'left', - }, ]; const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }]; - - const FlowDetail = () => { + const { work_uuid } = useParams() const navigate = useNavigate(); const backButton = const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const [record, setRecord] = useState({}); + const editRef = useRef(null); + const controlRef = useRef(null); const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]); const onBeforeDelete = async (params) => { if (params?.nodes?.find(e => e.type === "input")) { @@ -56,40 +55,119 @@ const FlowDetail = () => { } return true; } - return - - -
- - - - - -
-
- - - - -
+ const onAdd = (type) => { + setNodes([ + ...nodes, + { + id: new Date().getTime() + '_' + nodes.length, + position: { x: nodes.length * 5, y: 0 }, + data: { label: '2' }, + sourcePosition: 'right', + targetPosition: 'left', + style: { backgroundColor: COLORS[type] }, + type: type, + }, + ]); + } + const onNodeClick = (_, record) => { + if (record?.type === 'input') return; + editRef?.current?.open(record); + } + const loadDetail = async () => { + try { + const res = await flowDetail(work_uuid); + const { data: { content: { content: infoList } } } = res; + let startX = 0; + let startIndex = 1; + if (res?.success) { + setRecord(res.data); + const tempEdges = []; + const tempNodes = [{ + id: '1', + position: { x: startX, y: 0 }, + sourcePosition: 'right', + type: 'input', + style: { backgroundColor: 'input' }, + data: { label: '开始' }, + }] + infoList.forEach((e, i) => { + tempNodes.push({ + id: e.uuid, + position: { x: startX + 200, y: 0 }, + sourcePosition: 'right', + targetPosition: 'left', + type: e.type, + style: { backgroundColor: COLORS[e.type] }, + data: { label: e.title }, + detail: e + }) + + const sourceNode = tempNodes[startIndex - 1]; + const targetNode = tempNodes[startIndex]; + tempEdges.push({ + id: `${sourceNode.id}-${targetNode.id}`, + source: sourceNode.id, + target: targetNode.id + }) + + startX += 200; + startIndex += 1; + + }); + + setNodes(tempNodes); + setEdges(tempEdges); + } else { + message.error(res.message); + } + } catch (error) { + console.log(error); + } + } + + useEffect(() => { + loadDetail(); + }, [work_uuid]) + + return + + + +
+ + + + + (controlRef.current = obj)} /> + +
+
+ + + + + +
+
} -const RightCard = ({ onCreate}) => { - const nodes = [{ title: 'Git' }, {title: "Cmd"}, {title: "Status"}]; +const RightCard = ({ onCreate }) => { + const nodes = [{ title: 'Git' }, { title: "Cmd" }, { title: "Status" }]; return {nodes?.map((e, i) =>
{ @@ -104,4 +182,12 @@ const RightCard = ({ onCreate}) => { } +const ControlButtons = ({ onLoad }) => { + const reactFlow = useReactFlow(); + useEffect(() => { + reactFlow && onLoad(reactFlow) + }, [reactFlow]) + return <> +} + export default FlowDetail; \ No newline at end of file diff --git a/frontend/src/pages/Flow/components/StartNode.jsx b/frontend/src/pages/Flow/components/ClientTable.jsx similarity index 100% rename from frontend/src/pages/Flow/components/StartNode.jsx rename to frontend/src/pages/Flow/components/ClientTable.jsx diff --git a/frontend/src/pages/Flow/components/EditNode.jsx b/frontend/src/pages/Flow/components/EditNode.jsx new file mode 100644 index 0000000..d16a968 --- /dev/null +++ b/frontend/src/pages/Flow/components/EditNode.jsx @@ -0,0 +1,43 @@ +import { DrawerForm, ModalForm, ProFormSelect, ProFormText, ProFormTextArea } from "@ant-design/pro-components"; +import { Button, Flex, Space } from "antd"; +import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react"; + +const EditNode = forwardRef(({ onFinish }, ref) => { + const [open, setOpen] = useState(false); + const formRef = useRef(null); + const [record, setRecord] = useState(null); + const [clients, setClients] = useState([{ label: "Local", value: 'local'}]); + useImperativeHandle(ref, () => ({ + open: (data) => { + setOpen(true); + setRecord(data); + } + })) + + useEffect(() => { + if (record && formRef.current) { + formRef.current.setFieldsValue(record.detail); + } + }, [record, formRef.current]); + + return } + open={open} + onOpenChange={e => setOpen(e)} + formRef={formRef} + width={600} + drawerProps={{ maskClosable: false, destroyOnHidden: true}} + > + + + + + + + + + +}) + +export default EditNode; \ No newline at end of file