发布流水线前端展示

前言

我们在做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>
View Code

 

其原理就是定义好每个节点的位置、长度、宽度及连接线的起始点和终点。

 

我们自己的实现效果图如下:

 

 

 

job详情颜色显示

 

gitlab流水线获取job的接口返回的是带ANSI格式的内容,比如  [0KRunning with gitlab-runner 13.4.1 (e95f89a0)↵,如果直接展示在前端肯定是不行的。笔者在网上对比了很多工具,发现有一款名叫 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>
View Code

 

展示效果如下:

 

 

<!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>
posted @ 2021-09-22 11:31  独揽风月  阅读(821)  评论(0编辑  收藏  举报