From 230d14644ae9fcc9045bb622ddbf759e8760f84e Mon Sep 17 00:00:00 2001 From: vapao Date: Mon, 12 Dec 2022 17:26:53 +0800 Subject: [PATCH] init pipeline --- spug_web/src/pages/pipeline/Editor.js | 132 ++++++++++++++++++ spug_web/src/pages/pipeline/Icon.js | 35 +++++ spug_web/src/pages/pipeline/Node.js | 76 ++++++++++ spug_web/src/pages/pipeline/NodeConfig.js | 111 +++++++++++++++ .../src/pages/pipeline/assets/icon_build.png | Bin 0 -> 2433 bytes .../pipeline/assets/icon_data_transfer.png | Bin 0 -> 2733 bytes .../pipeline/assets/icon_data_upload.png | Bin 0 -> 2567 bytes .../src/pages/pipeline/assets/icon_health.png | Bin 0 -> 2747 bytes .../pages/pipeline/assets/icon_parameter.png | Bin 0 -> 2088 bytes .../pages/pipeline/assets/icon_push_dd.png | Bin 0 -> 2675 bytes .../pages/pipeline/assets/icon_push_spug.png | Bin 0 -> 2839 bytes .../pipeline/assets/icon_remote_exec.png | Bin 0 -> 3012 bytes .../src/pages/pipeline/assets/icon_select.png | Bin 0 -> 1454 bytes spug_web/src/pages/pipeline/data.js | 51 +++++++ .../src/pages/pipeline/editor.module.less | 57 ++++++++ spug_web/src/pages/pipeline/index.js | 22 +++ spug_web/src/pages/pipeline/node.module.less | 115 +++++++++++++++ .../src/pages/pipeline/nodeConfig.module.less | 97 +++++++++++++ spug_web/src/pages/pipeline/store.js | 15 ++ spug_web/src/pages/pipeline/utils.js | 92 ++++++++++++ 20 files changed, 803 insertions(+) create mode 100644 spug_web/src/pages/pipeline/Editor.js create mode 100644 spug_web/src/pages/pipeline/Icon.js create mode 100644 spug_web/src/pages/pipeline/Node.js create mode 100644 spug_web/src/pages/pipeline/NodeConfig.js create mode 100644 spug_web/src/pages/pipeline/assets/icon_build.png create mode 100644 spug_web/src/pages/pipeline/assets/icon_data_transfer.png create mode 100644 spug_web/src/pages/pipeline/assets/icon_data_upload.png create mode 100644 spug_web/src/pages/pipeline/assets/icon_health.png create mode 100644 spug_web/src/pages/pipeline/assets/icon_parameter.png create mode 100644 spug_web/src/pages/pipeline/assets/icon_push_dd.png create mode 100644 spug_web/src/pages/pipeline/assets/icon_push_spug.png create mode 100644 spug_web/src/pages/pipeline/assets/icon_remote_exec.png create mode 100644 spug_web/src/pages/pipeline/assets/icon_select.png create mode 100644 spug_web/src/pages/pipeline/data.js create mode 100644 spug_web/src/pages/pipeline/editor.module.less create mode 100644 spug_web/src/pages/pipeline/index.js create mode 100644 spug_web/src/pages/pipeline/node.module.less create mode 100644 spug_web/src/pages/pipeline/nodeConfig.module.less create mode 100644 spug_web/src/pages/pipeline/store.js create mode 100644 spug_web/src/pages/pipeline/utils.js diff --git a/spug_web/src/pages/pipeline/Editor.js b/spug_web/src/pages/pipeline/Editor.js new file mode 100644 index 0000000..d5d3611 --- /dev/null +++ b/spug_web/src/pages/pipeline/Editor.js @@ -0,0 +1,132 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import React, { useState, useEffect } from 'react'; +import { observer } from 'mobx-react'; +import { PlusOutlined } from '@ant-design/icons'; +import { message } from 'antd'; +import NodeConfig from './NodeConfig'; +import Node from './Node'; +import { transfer } from './utils'; +import S from './store'; +import lds from 'lodash'; +import css from './editor.module.less'; + +function Editor(props) { + const [record, setRecord] = useState({}) + const [nodes, setNodes] = useState([]) + + useEffect(() => { + const data = transfer(record.pipeline || []) + setNodes(data) + }, [record]) + + function handleAction({key, domEvent}) { + domEvent.stopPropagation() + switch (key) { + case 'upstream': + return handleAddUpstream() + case 'downstream': + return handleAddDownstream() + case 'delete': + return handleDelNode() + default: + return + } + } + + function _findIndexAndUpNode() { + let index + let [upNode, streamIdx] = [null, null] + const id = S.actionNode.id + for (let idx in record.pipeline) { + const node = record.pipeline[idx] + if (node.id === id) { + index = Number(idx) + } + idx = lds.findIndex(node.downstream, {id}) + if (idx >= 0) { + upNode = node + streamIdx = idx + } + } + return [index, upNode, streamIdx] + } + + function handleAddUpstream() { + const oldID = S.actionNode.id + const newID = new Date().getTime() + const [index, upNode, streamIdx] = _findIndexAndUpNode() + if (upNode) upNode.downstream.splice(streamIdx, 1, {id: newID}) + record.pipeline.splice(index, 0, {id: newID, downstream: [{id: oldID}]}) + setRecord(Object.assign({}, record)) + } + + function handleAddDownstream(e) { + if (e) e.stopPropagation() + const oldID = S.actionNode.id + const newNode = {id: new Date().getTime()} + if (record.pipeline) { + const idx = lds.findIndex(record.pipeline, {id: oldID}) + if (record.pipeline[idx].downstream) { + record.pipeline[idx].downstream.push(newNode) + } else { + record.pipeline[idx].downstream = [newNode] + } + record.pipeline.splice(idx + 1, 0, newNode) + } else { + record.pipeline = [newNode] + } + setRecord(Object.assign({}, record)) + S.node = newNode + } + + function handleDelNode() { + const {downstream} = S.actionNode + const [index, upNode, streamIdx] = _findIndexAndUpNode() + if (index === 0 && downstream && downstream.length > 1) { + return message.error('该节点为起始节点且有多个下游节点无法删除') + } + if (upNode) { + upNode.downstream.splice(streamIdx, 1) + if (downstream) { + for (let item of downstream.slice().reverse()) { + upNode.downstream.splice(streamIdx, 0, item) + } + } + } + record.pipeline.splice(index, 1) + setRecord(Object.assign({}, record)) + } + + function handleRefresh(node) { + const index = lds.findIndex(record.pipeline, {id: node.id}) + record.pipeline.splice(index, 1, node) + setRecord(Object.assign({}, record)) + } + + return ( +
S.node = {}}> + {nodes.map((row, idx) => ( +
+ {row.map((item, idx) => ( + + ))} +
+ ))} + {nodes.length === 0 && ( +
+
+ +
+
点击添加节点
+
+ )} + +
+ ) +} + +export default observer(Editor) \ No newline at end of file diff --git a/spug_web/src/pages/pipeline/Icon.js b/spug_web/src/pages/pipeline/Icon.js new file mode 100644 index 0000000..dbe9364 --- /dev/null +++ b/spug_web/src/pages/pipeline/Icon.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { Avatar } from 'antd'; +import iconRemoteExec from './assets/icon_remote_exec.png'; +import iconBuild from './assets/icon_build.png'; +import iconParameter from './assets/icon_parameter.png'; +import iconDataTransfer from './assets/icon_data_transfer.png'; +import iconDataUpload from './assets/icon_data_upload.png'; +import iconPushSpug from './assets/icon_push_spug.png'; +import iconPushDD from './assets/icon_push_dd.png'; +import iconSelect from './assets/icon_select.png'; + +function Icon(props) { + switch (props.module) { + case 'remote_exec': + return + case 'build': + return + case 'parameter': + return + case 'data_transfer': + return + case 'data_upload': + return + case 'push_spug': + return + case 'push_dd': + return + case undefined: + return + default: + return + } +} + +export default Icon \ No newline at end of file diff --git a/spug_web/src/pages/pipeline/Node.js b/spug_web/src/pages/pipeline/Node.js new file mode 100644 index 0000000..7855005 --- /dev/null +++ b/spug_web/src/pages/pipeline/Node.js @@ -0,0 +1,76 @@ +import React from 'react' +import { observer } from 'mobx-react'; +import { Dropdown } from 'antd'; +import { MoreOutlined } from '@ant-design/icons'; +import Icon from './Icon'; +import { clsNames } from 'libs'; +import css from './node.module.less'; +import S from './store'; + +function Node(props) { + function handleNodeClick(e) { + e.stopPropagation() + S.node = props.node + } + + function handleActionClick(e) { + e.stopPropagation() + S.actionNode = props.node + } + + const menus = [ + { + key: 'upstream', + label: '添加上游节点', + onClick: props.onAction + }, + { + key: 'downstream', + label: '添加下游节点', + onClick: props.onAction + }, + { + key: 'delete', + danger: true, + label: '删除此节点', + onClick: props.onAction + } + ] + + const node = props.node + switch (node) { + case ' ': + return
+ case ' -': + return
+ case '--': + return
+ case ' 7': + return
+ case '-7': + return
+ case ' |': + return ( +
+
+
+ ) + default: + return ( +
+ + {node.name ? ( +
{node.name}
+ ) : ( +
请选择节点
+ )} + + + +
+ ) + } +} + +export default observer(Node) \ No newline at end of file diff --git a/spug_web/src/pages/pipeline/NodeConfig.js b/spug_web/src/pages/pipeline/NodeConfig.js new file mode 100644 index 0000000..d7de306 --- /dev/null +++ b/spug_web/src/pages/pipeline/NodeConfig.js @@ -0,0 +1,111 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import React, { useState, useEffect } from 'react'; +import { observer } from 'mobx-react'; +import { Drawer, Form, Radio, Button, Input, message } from 'antd'; +import { AppstoreOutlined, SettingOutlined } from '@ant-design/icons'; +import Icon from './Icon'; +import { ACEditor } from 'components'; +import HostSelector from 'pages/host/Selector'; +import { clsNames } from 'libs'; +import S from './store'; +import css from './nodeConfig.module.less'; +import { NODES } from './data' + +function NodeConfig(props) { + const [tab, setTab] = useState('node') + const [form] = Form.useForm() + + useEffect(() => { + setTab(S.node.module ? 'conf' : 'node') + form.setFieldsValue(S.node) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [S.node]) + + function handleNode({module, name}) { + S.node.module = module + if (!S.node.name) S.node.name = name + setTab('conf') + S.node = {...S.node} + } + + function handleSave() { + message.success('保存成功') + const data = form.getFieldsValue() + Object.assign(S.node, data) + props.doRefresh(S.node) + } + + const visible = !!S.node.id + return ( + +
e.stopPropagation()}> +
+
setTab('node')}> + + 选择节点 +
+
setTab('conf')}> + + 节点配置 +
+
+ +
+
内置节点
+
+ {NODES.map(item => ( +
handleNode(item)}> + +
{item.name}
+
+ ))} +
+
+ +
+
+ + + + + + + + + Shell + Python + + + p.interpreter !== c.interpreter}> + {({getFieldValue}) => ( + + console.log(val)} + width="464px" + height="220px"/> + + )} + +
+
+
+ +
+
+
+ ) +} + +export default observer(NodeConfig) \ No newline at end of file diff --git a/spug_web/src/pages/pipeline/assets/icon_build.png b/spug_web/src/pages/pipeline/assets/icon_build.png new file mode 100644 index 0000000000000000000000000000000000000000..f52fac4e4861ed3c185157a5f9b80d52c08184c5 GIT binary patch literal 2433 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 literal 0 HcmV?d00001 diff --git a/spug_web/src/pages/pipeline/assets/icon_health.png b/spug_web/src/pages/pipeline/assets/icon_health.png new file mode 100644 index 0000000000000000000000000000000000000000..5134ef326da03dfff7649564951049c155381e48 GIT binary patch literal 2747 zcmV;s3PkmZP)Px#+S*eTJ)pLN$WoS~{~&Nk(g1=ALR%*6Dj3LwHS9@Pl0a5w=6yZ)LB=dInfKHE9>`(Qp{`alz?cr8z0t<{kyix5$5rMV3l7F^D>X{DAprF{GP*^y$u$GO9|6P|Wn4gV zn2SO5Zn4|E;ArplHv^JUi%B;?R!7PpW?n{wZhDO|lAjsJiDl$4u11Vrlyh}5d=PSP__3g$7{Ju|=5VuvPx2ISesF=GV)ixwq!5v`L5 z6Lrv+7riL+agxvKApZd>a7BMktu9LMgqA65-CfavwNkVQ2Jz(EGTx;rj&fdWvH zBY7)IX*JcniDyqxRMp05;N&+$O(=&Sn@4W_) zvKA5bqr|-A-%3oX!jFxC7;Jzl3Y7{^Wd{0(~?IM5&{c3Gu zfZi`$hRl*LJX&9Fmx>o-MyWE>D1ZQ55&)G0XsJrGL2FRCy8_`R-zq;S{3kL>wtBRQ zb?4VHHq|B?1rRe=2!KR6o3`kc7gN=&L2h;=ku>(Y?!KIzvBx%10S(VhP~#ucUyxHDMa-@KKIf` z*mAvL@z>axQJCH?Rka*%gvY8*Vf>jiT`~mo?bp098^x^HRfsW1sWwXNkOfFvukZBm zjENqB-?bmFT81w|5y($F?+GjGk++qsxyc`a=w&@p1n7NMiR6?YGH!ao=v5wry@#e)3@bwM5ZWBBKGa<={ zkgJdrMxpq$^M?>=<~uR$Wd|_p!aIhj_Jmx8G$VrcHCFWqiN%smbJ0Rl8h4s(Tyhp+ zLVg;q82q3wSF1{md0Vt72}JoLJuZzpfIU33=WwR3X}1LwAmx`{*arQX!DH{-j8 zJc4;YnjN$vhMdYg{OQ7cTy+;4^}i}|yr!O&;)OOH^1;ToF-4hLV^)W05XB8f4L8eJ zb1f4quB?FzZllE3$hj8H=1jB|#4)j~_Q0l&c&#q-* zL&;`T$cA@Z);D#gJuah8`JwkBPLnH#5c~!?v7vlEY8XaD4n{=181*G*AvP3m!KTvf zD3e7@FSvt&<0Wo$pZZ;jUdw=!9JZ$c-qsXlz{2g;QUsoh9E6dPgCS;&h)M4%->{D z*mo~Cv$z=j_Vvs-=4<||YlkBK9Hg=G`)+^Ym|b8RrmG#Q#z$+OEtUBmYp5fPxA!oh zq0R%+z0{KF#&mn{jHPNOx!*^uwh+;p3BvMb+XVSWQd`T{;$toKHHw_T4@+s0ZKEQwThJH5VY zH9_XE3}yfT002ovPDHLkV1f&q BBDnwn literal 0 HcmV?d00001 diff --git a/spug_web/src/pages/pipeline/assets/icon_parameter.png b/spug_web/src/pages/pipeline/assets/icon_parameter.png new file mode 100644 index 0000000000000000000000000000000000000000..6861921b8c8081764b4f9cdfe13a5325aa2dc3b5 GIT binary patch literal 2088 zcmV+@2-o+CP)Px+;z>k7RCr$PTy1Py)fs-?<9sg7($;n=V&C)wC>o^GHqnCAO^ET`327=)(D<=d z3@D>)VZ<%v2P|wq*3}N9tSwX<2qsNqX!Or0D;u;@S2VGqvC&4N%hrZCN!!?I2>Gt# z^G@d)M|P6yT>IYZFSvhw-sgSKXR{8tY9X(Ta z>#>!fi@TRK#4GCB0Bi@S(Qaci1+Pn&i~6NT+Wtv3QMvmRywPTZVvBm6A6% z?Ck5#R(I9}UD(rfs}QbN0W@T*tibI@q(tkAo#8)cV@Fm5MLT`pWfs2%P??RAB5E5W zlJ|Mq`+sBBCes8(g1&7aI^f)y#;nO!+o`h?Kjhuff6xq)DS`$=zAXfLvGmDFQo@qK z_cjLlUtWx)MG3kPYP!V**I_Q*X=TJgOd|fR6@h`jWFjdeL6M&~T?Lox-4a}o7aJJK z-$xLSU*~;n;Ixr|AwlY#QzPjg0Io5stwh^(O$lCabWSoPC>qlCGw@J}b~-Dw892}w z2yb6VXhDMX>jR6oi}-|`6#*t1Gf3XF(6wY9LEM3=Xq|Sd=B($kG17 zGc#)e4^9cJN^bkJ1Pz9ow-ERxrz~x_`wyt}ZcMki0W*x{Uk(9;T?@~Y6KVe zud~!02HtE8gm0(!9zf;FwfmO|93s1K(ZN40J=w7z0Ey2g%mAa>>`K#O`` zOmvzcCx^jP!zS7qH(|y^?-#>MDH_t&Gw^pyXeNZKF25P|o1O(#ElF!V@$NoMoJc#o znn24>69P6+B;C=Bie6Y(Ztb_7$8%0C@XBqSZ z2ep(eG&Ap`!Jz*z5p62jUN~qtGapfF&<6}yTk;(=QbAXL>D#Doxf`wqEipB_Uc=PM z-#M&RKO~-{NJtw1aGB%Ua#XP7mffiN@;&pUr%%6*(bpY!c%Gw3&_52MFXU){npkD? zMu_?*l_F*X9HYW>6>8T%XoxlW{!5rS|L=6iCjoNwJYu~ktlPjmp%&>FgDR|}0q#f7 zf~YL68lTd^aeE0WqpL=OA&%QR#ma63sVQdXB3*WC5OA*OxA{WuVu@`&=s%1iKPVRJ z90bM@b&~!i5j||jE>>qj^unjKPa`Gq=@TNz(Topk>A%B~+!D(M!KIs&v{lM;~=E@3j)^gaV ze6AWi6N|06?D6wGDUns}uLOME%1%>8aj_HL)WD%4#g5Q(xkOe8wAC{=^z3xd?Zj&?ec_gdEz4ECyrKA zX8%7khF$To)rJXF3h_eFze5leXCyECE@vmfywkg*FD?D>^CZqBg^X^`c({e;R4FbP z437qV-((VReU{RIiRI0n9sNh=4mrbYfk;q00Me@o%l;$afG3+oa@}41w7)j`87W2Z z2wqPHC7YRxSvq}d!9rzz6h7(4nQ=Sdte}p-_j>Fos7sPmRwbF>?cPBD^Jd1~qEjptBvG0vepRYPy#&N0 zN$p_rfhA@;;BuP7f_nrpXm-_}ae%FN%(#}i#d z^unZ&ujcv$PNqN7mMNA=fgVzF(PoCYLywwRjo>mSRJjsC{wWscLruE|dYv4pjuVt> z{OmJX|?i|IGI@f&T}Vr(>1~ SmFEip0000Px>{v_-Gi|4(A`$^@ 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 IM6N<$g4$fG5C8xG literal 0 HcmV?d00001 diff --git a/spug_web/src/pages/pipeline/data.js b/spug_web/src/pages/pipeline/data.js new file mode 100644 index 0000000..9a1cf96 --- /dev/null +++ b/spug_web/src/pages/pipeline/data.js @@ -0,0 +1,51 @@ +export const NODES = [ + {module: 'remote_exec', name: '执行命令'}, + {module: 'build', name: '构建'}, + {module: 'parameter', name: '参数化'}, + {module: 'data_transfer', name: '数据传输'}, + {module: 'data_upload', name: '数据上传'}, + {module: 'push_dd', name: '钉钉推送'}, + {module: 'push_spug', name: '推送助手'}, +] + +export const DATAS = { + 'name': 'test', + 'pipeline': [ + { + 'module': 'build', + 'name': '构建', + 'id': 0, + 'repository': 1, + 'target': 2, + 'workspace': '/data/spug', + 'command': 'mvn build', + 'downstream': [ + {'id': 1, 'state': 'success'} + ] + }, + { + 'module': 'remote_exec', + 'name': '执行命令', + 'id': 1, + 'targets': [2, 3], + 'interpreter': 'sh', + 'command': 'date && sleep 3', + 'downstream': [ + {'id': 2, 'state': 'success'} + ] + }, + { + 'module': 'data_transfer', + 'name': '数据传输', + 'id': 2, + 'source': { + 'target': 1, + 'path': '/data/spug' + }, + 'dest': { + 'targets': [2, 3], + 'path': '/data/dist' + } + } + ] +} diff --git a/spug_web/src/pages/pipeline/editor.module.less b/spug_web/src/pages/pipeline/editor.module.less new file mode 100644 index 0000000..f1ab3df --- /dev/null +++ b/spug_web/src/pages/pipeline/editor.module.less @@ -0,0 +1,57 @@ +.container { + position: relative; + border-radius: 4px; + min-height: calc(100vh - 127px); + margin: -12px -12px 0 -12px; + padding: 12px 0 0 12px; +} + +.triangle { + width: 0; + height: 0; + border: 100px solid transparent; + border-top-color: #999999; +} + +.row { + display: flex; + flex-direction: row; +} + +.item { + width: 240px; + height: 80px; + display: flex; + flex-direction: row; + align-items: center; + box-shadow: 0 0 15px #9999994c; + border: 1px solid transparent; + border-radius: 6px; + padding: 0 12px; + background: #ffffff; + cursor: pointer; + + .title { + width: 164px; + margin-left: 12px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .add { + width: 40px; + height: 40px; + border-radius: 20px; + border: 2px dashed #dddddd; + background: #f5f5f5; + display: flex; + justify-content: center; + align-items: center; + + .icon { + font-size: 18px; + color: #999999; + } + } +} diff --git a/spug_web/src/pages/pipeline/index.js b/spug_web/src/pages/pipeline/index.js new file mode 100644 index 0000000..20ad22f --- /dev/null +++ b/spug_web/src/pages/pipeline/index.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import React from 'react'; +import { observer } from 'mobx-react'; +import { } from 'antd'; +import { AuthDiv, Breadcrumb } from 'components'; +import Editor from './Editor'; + +export default observer(function () { + return ( + + + 首页 + 流水线 + + + + ) +}) diff --git a/spug_web/src/pages/pipeline/node.module.less b/spug_web/src/pages/pipeline/node.module.less new file mode 100644 index 0000000..3cfca2e --- /dev/null +++ b/spug_web/src/pages/pipeline/node.module.less @@ -0,0 +1,115 @@ +.box { + position: relative; + width: 240px; + height: 80px; + margin-right: 24px; +} + +.triangle { + position: absolute; + bottom: -8px; + left: 112px; + width: 0; + height: 0; + border: 8px solid transparent; + border-top-color: #999999; +} + +.node { + box-shadow: 0 0 15px #9999994c; + background: #ffffff; + border: 1px solid transparent; + border-radius: 6px; + display: flex; + align-items: center; + cursor: pointer; + padding: 0 24px; + + &:hover { + box-shadow: 0 0 6px #2563fcbb; + .action { + display: block; + } + } + + .title { + flex: 1; + font-size: 20px; + margin-left: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .action { + display: none; + padding: 12px; + margin-right: -12px; + font-size: 18px; + } +} + +.active { + border-color: #4d8ffd; + box-shadow: 0 0 6px #2563fcbb; + background-color: #e6f7ff; +} + +.line { + &:after { + content: ' '; + height: 2px; + background: #999999; + position: absolute; + top: 39px; + left: -24px; + right: 0; + } +} + +.line2 { + &:after { + left: -144px; + } +} + +.angle { + &:before { + content: ' '; + position: absolute; + background: #999999; + top: 39px; + bottom: 39px; + left: -24px; + right: 119px; + } + + &:after { + content: ' '; + position: absolute; + background: #999999; + top: 40px; + left: 119px; + right: 119px; + bottom: 0; + } +} + +.angle2 { + &:before { + left: -144px; + } +} + +.arrow { + &:after { + content: ' '; + background: #999999; + position: absolute; + top: 0; + left: 119px; + right: 119px; + bottom: 4px; + } +} + diff --git a/spug_web/src/pages/pipeline/nodeConfig.module.less b/spug_web/src/pages/pipeline/nodeConfig.module.less new file mode 100644 index 0000000..73bf7da --- /dev/null +++ b/spug_web/src/pages/pipeline/nodeConfig.module.less @@ -0,0 +1,97 @@ +.container { + font-size: 12px; + + .header { + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 8px 16px 0 16px; + background: #f0f0f0; + display: flex; + flex-direction: row; + align-items: flex-end; + + .item { + width: 100px; + height: 40px; + border-radius: 6px 6px 0 0; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + margin-right: 6px; + font-size: 14px; + cursor: pointer; + + span { + margin-left: 4px; + } + + &:hover { + color: #2563fc; + } + } + + .active { + background: #ffffff; + color: #2563fc; + } + } + + .body { + padding: 24px 18px; + overflow: auto; + position: absolute; + top: 48px; + left: 0; + right: 0; + bottom: 48px; + } + + .footer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 48px; + border-top: 1px solid #dfdfdf; + padding: 8px 18px; + } + + .category { + font-size: 14px; + margin: 0 0 6px 18px; + } + + .items { + display: flex; + flex-direction: row; + flex-wrap: wrap; + padding: 0 12px; + + .item { + width: 150px; + height: 50px; + box-shadow: 0 0 3px #9999994c; + border: 1px solid transparent; + display: flex; + flex-direction: row; + align-items: center; + padding: 0 12px; + margin: 6px; + border-radius: 6px; + cursor: pointer; + + .title { + margin-left: 8px; + } + } + + .active { + border-color: #4d8ffd; + background: #f5faff; + color: #2563fc; + } + } +} diff --git a/spug_web/src/pages/pipeline/store.js b/spug_web/src/pages/pipeline/store.js new file mode 100644 index 0000000..c72e43c --- /dev/null +++ b/spug_web/src/pages/pipeline/store.js @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import { observable } from 'mobx'; + +class Store { + @observable nodes = []; + @observable node = {}; + @observable actionNode = {}; + @observable isFetching = true; +} + +export default new Store() diff --git a/spug_web/src/pages/pipeline/utils.js b/spug_web/src/pages/pipeline/utils.js new file mode 100644 index 0000000..498f63b --- /dev/null +++ b/spug_web/src/pages/pipeline/utils.js @@ -0,0 +1,92 @@ +let response = [] +let nodes = {} +let layer = 0 + +function loop(keys) { + const tmp = [] + let downKeys = [] + for (let key of keys) { + const node = nodes[key] + tmp.push(node.id) + for (let item of node.downstream || []) { + downKeys.push(item.id) + } + } + response[layer] = tmp + layer += 1 + if (downKeys.length) { + loop(downKeys) + } +} + +export function transfer(data) { + if (data.length === 0) return [] + response = [] + nodes = {} + layer = 0 + for (let item of data) { + nodes[item.id] = item + } + loop([data[0].id]) + + let idx = response.length - 2 + while (idx >= 0) { + let cIdx = 0 + const currentRow = response[idx] + while (cIdx < currentRow.length) { + const node = nodes[currentRow[cIdx]] + if (node.downstream) { + const downRow = response[idx + 1] + for (let item of node.downstream) { + const sKey = item.id + let dIdx = downRow.indexOf(sKey) + while (dIdx < cIdx) { // 下级在左侧,则在下级前补空 + let tIdx = idx + 1 + while (tIdx < response.length) { // 下下级对应位置也要补空 + response[tIdx].splice(dIdx, 0, ' ') + tIdx += 1 + } + dIdx += 1 + } + if (dIdx === cIdx) continue; + while (dIdx > cIdx + 1) { // 下级在右侧跨列,则当前级补- + const flag = [' 7', '-7'].includes(currentRow[cIdx]) ? '--' : ' -' + cIdx += 1 + currentRow.splice(cIdx, 0, flag) + } + if ([' 7', '-7'].includes(currentRow[cIdx])) { + currentRow.splice(cIdx + 1, 0, '-7') + } else { + currentRow.splice(cIdx + 1, 0, ' 7') + } + cIdx += 1 + } + } + cIdx += 1 + } + idx -= 1 + } + + for (let row of response) { + for (let idx in row) { + const key = row[idx] + row[idx] = nodes[key] || key + } + } + + idx = 1 + while (idx < response.length) { + const nRow = [] + for (let item of response[idx]) { + if (item.id) { + nRow.push(' |') + } else { + nRow.push(' ') + } + } + response.splice(idx, 0, nRow) + idx += 2 + } + + return response +} \ No newline at end of file