发布流水线前端展示
前言
我们在做CI/CD时,最常用的做法就是使用Jenkins或gitlab的流水线的功能,先由运维写好流水线脚本,然后人工执行或由平台调用流水线接口去执行。
发布进度展示
以Gitlab为例,它的后台后流水线发布进度如下图所示:
流水线是由多个Stage组成,而每个Stage下又可以有多个Job,因此前端常用的el-step组件就无法实现这种展示效果,笔者从网上找到一款名叫 vue-super-flow 的前端组件 https://caohuatao.github.io/,可以用来画流程图。
测试代码:
<template> <div> <div class="super-flow-base-demo"> <super-flow :draggable= true ref="superFlow" :node-list="nodeList" :link-list="linkList" :origin="origin" :node-menu="nodeMenuList" :link-menu="linkMenuList" :link-desc="linkDesc"> <template v-slot:node="{meta}"> <div :class="`flow-node flow-node-${meta.prop}`" @click="show"> <header> {{meta.name}} </header> <section> {{meta.desc}} </section> </div> </template> </super-flow> </div> </div> </template> <script> const drawerType = { node: 0, link: 1 } export default { data() { return { drawerType, drawerConf: { title: '', visible: false, type: null, info: null, open: (type, info) => { const conf = this.drawerConf conf.visible = true conf.type = type conf.info = info if (conf.type === drawerType.node) { conf.title = '节点' if (this.$refs.nodeSetting) this.$refs.nodeSetting.resetFields() this.$set(this.nodeSetting, 'name', info.meta.name) this.$set(this.nodeSetting, 'desc', info.meta.desc) } else { conf.title = '连线' if (this.$refs.linkSetting) this.$refs.linkSetting.resetFields() this.$set(this.linkSetting, 'desc', info.meta ? info.meta.desc : '') } }, cancel: () => { this.drawerConf.visible = false if (this.drawerConf.type === drawerType.node) { this.$refs.nodeSetting.clearValidate() } else { this.$refs.linkSetting.clearValidate() } } }, linkSetting: { desc: '' }, nodeSetting: { name: '', desc: '' }, origin: [0, 0], nodeList: [], linkList: [], nodeMenuList: [ [ { label: '删除', disable: false, hidden(node) { return node.meta.prop === 'start' }, selected(node, coordinate) { node.remove() } } ], [ { label: '编辑', selected: (node, coordinate) => { console.log(node, coordinate) } } ] ], linkMenuList: [ [ { label: '删除', disable: false, selected: (link, coordinate) => { link.remove() } } ], [ { label: '编辑', disable: false, selected: (link, coordinate) => { console.log(link, coordinate) } } ] ] } }, created() { const nodeList = [ { 'id': '开始节点', 'width': 100, 'height': 80, 'coordinate': [0, 0], 'meta': { 'prop': 'start', 'name': '开始节点', 'desc': '111' } }, { 'id': '条件节点1', 'width': 100, 'height': 80, 'coordinate': [150, 0], 'meta': { 'prop': 'condition', 'name': '条件节点1' } }, { 'id': '条件节点2', 'width': 100, 'height': 80, 'coordinate': [150, 100], 'meta': { 'prop': 'condition', 'name': '条件节点2' } }, { 'id': '抄送节点1', 'width': 100, 'height': 80, 'coordinate': [300, 0], 'meta': { 'prop': 'ccc', 'name': '抄送节点1' } }, { 'id': '结束节点', 'width': 100, 'height': 80, 'coordinate': [450, 0], 'meta': { 'prop': 'end', 'name': '结束节点' } }, ] const linkList = [ { 'id': 'linkcs9ZhumWeTHrtUy8', 'startId': '开始节点', 'endId': '条件节点1', 'startAt': [100, 40], 'endAt': [0, 40], 'meta': null }, { 'id': 'linknL75dQV0AWZA85sq', 'startId': '开始节点', 'endId': '条件节点2', 'startAt': [100, 40], 'endAt': [0, 40], 'meta': null }, { 'id': 'linkA0ZZxRlDI9AOonuq', 'startId': '条件节点2', 'endId': '抄送节点1', 'startAt': [160, 40], 'endAt': [0, 40], 'meta': null }, { 'id': 'linkhCKTpRAf89gcujGS', 'startId': '条件节点1', 'endId': '抄送节点1', 'startAt': [160, 40], 'endAt': [0, 40], 'meta': null }, { 'id': 'link2o7VZ7DRaSFKtB0g', 'startId': '抄送节点1', 'endId': '结束节点', 'startAt': [160, 40], 'endAt': [0, 25], 'meta': null }, ] setTimeout(() => { this.nodeList = nodeList this.linkList = linkList }, 100) }, methods: { linkDesc(link) { return link.meta ? link.meta.desc : '' }, show(){ alert(1) } } } </script> <style lang="less"> .super-flow-base-demo { width : 100%; height : 800px; margin : 0 auto; background-color : #f5f5f5; .super-flow__node { .flow-node { > header { font-size : 14px; height : 32px; line-height : 32px; padding : 0 12px; color : #ffffff; } > section { text-align : center; line-height : 20px; overflow : hidden; padding : 6px 12px; word-break : break-all; } &.flow-node-start { > header { background-color : #55abfc; } } &.flow-node-condition { > header { background-color : #BC1D16; } } &.flow-node-approval { > header { background-color : rgba(188, 181, 58, 0.76); } } &.flow-node-ccc { > header { background-color : #30b95c; } } &.flow-node-end { > header { height : 50px; line-height : 50px; background-color : rgb(0, 0, 0); } } } } } </style>
其原理就是定义好每个节点的位置、长度、宽度及连接线的起始点和终点。
我们自己的实现效果图如下:
job详情颜色显示
gitlab流水线获取job的接口返回的是带ANSI格式的内容,比如 [0KRunning with gitlab-runner 13.4.1 (e95f89a0)↵[0;m[0K,如果直接展示在前端肯定是不行的。笔者在网上对比了很多工具,发现有一款名叫 Xterm.js的前端组件,用来模拟终端,天生支持ansi格式的输出。
使用方式:
1、安装xterm:
npm install xterm
2、测试代码:
<!doctype html> <html> <head> <link rel="stylesheet" href="node_modules/xterm/css/xterm.css" /> <script src="node_modules/xterm/lib/xterm.js"></script> </head> <body> <div id="terminal"></div> <script> var term = new Terminal(); term.open(document.getElementById('terminal')); term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ') </script> </body> </html>
展示效果如下:
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
<script src="node_modules/xterm/lib/xterm.js"></script>
</head>
<body>
<div id="terminal"></div>
<script>
var term = new Terminal();
term.open(document.getElementById('terminal'));
term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
</script>
</body>
</html>