ソースを参照

feat: 压测平台第一版

石玲燕 4 年 前
コミット
c7b287fb4e

+ 3 - 1
src/conf/config.js

@@ -2,6 +2,7 @@ let dc = '/back-apit/api'
 let tc = '/back-apit/api'
 let us = '/back-apit/api'
 let wop = '/wopt/api'
+let yc = 'http://halberd-t.wpt.la' // 压测平台
 const apiCdn = 'http://cdn01t.weipaitang.com/img/'
 let auth = '/back-autht/api'
 const hostDev = `${window.location.protocol}//${window.location.host}`
@@ -21,6 +22,7 @@ if (document.domain === 'back-admin.weipaitang.com') {
   // sTaskJobLog = '/ms-trace/api' // 定时任务日志跳转
   thanos = '/thanos-admin/api'
   ms = 'http://back-admin.weipaitang.com'
+  yc = 'http://halberd.wpt.la'
 }
 
 const menuIcon = {
@@ -36,4 +38,4 @@ const menuIcon = {
   '/smanagement': 'icon-fuwu'
 }
 
-export { dc, tc, us, apiCdn, auth, hostDev, sTaskJobLog, thanos, wop, ms, menuIcon }
+export { dc, tc, us, apiCdn, auth, hostDev, sTaskJobLog, thanos, wop, ms, menuIcon, yc }

+ 4 - 3
src/layouts/mainLayout/index.js

@@ -2,11 +2,9 @@ import React from 'react'
 import s from './index.less'
 import { Layout, Menu, Icon, Avatar, Dropdown, ConfigProvider, Tooltip } from 'antd'
 import zhCN from 'antd/es/locale/zh_CN'
-import router from 'umi/router'
 import { connect } from 'dva'
-import logo from '@/assets/logo.jpg'
 import { Link } from 'umi'
-
+import { HalberdIcon, BoomIcon } from './svgIcon.js'
 const { Header, Sider, Content, Footer } = Layout
 const { SubMenu } = Menu
 const IconFont = Icon.createFromIconfontCN({
@@ -79,6 +77,8 @@ class MainLayout extends React.PureComponent {
         return <SubMenu key={m.url} title={
           <span>
             {m.firstLevel && <IconFont type={m.icon} />}
+            {m.url === '/chaosBullet-p' && <Icon component={BoomIcon}/>}
+            {m.url === '/halberd-m' && <Icon component={HalberdIcon}/>}
             <span>{m.name}</span>
           </span>
         }>
@@ -94,6 +94,7 @@ class MainLayout extends React.PureComponent {
             </Menu.Item>
           )
         }
+
         return <Menu.Item key={m.url}>
           <Link to={m.url}>{(m.url === '/poster' || m.url === '/allseeing') && <IconFont type={m.icon} />}{m.name}</Link>
         </Menu.Item>

ファイルの差分が大きいため隠しています
+ 2 - 0
src/layouts/mainLayout/svgIcon.js


+ 248 - 0
src/pages/halberd/components/addTask/index.js

@@ -0,0 +1,248 @@
+import React, { Component } from 'react'
+import { Modal, Button, Input, message } from 'antd'
+import { FormItem } from 'wptpc-design'
+class Index extends Component {
+  state = {
+    data: null,
+    style: null,
+    params: null
+  };
+
+  constructor (props) {
+    super(props)
+
+    let params = null
+    if (props.params) {
+      params = props.params
+    }
+    this.state = {
+      params: {
+        detail: [],
+        needNew: 0,
+        name: '',
+        getTokenUrl: '',
+        ...params
+      }
+
+    }
+
+    if (typeof props.getValue === 'function') {
+      props.getValue(this.onOk)
+    }
+  }
+
+  // 设置属性字段
+  setDetail (i, v, k) {
+    const { params } = this.state
+    if (k === 'rate') {
+      const reg = /^-?[0-9]*(\.[0-9]*)?$/
+      if (!((!isNaN(v) && reg.test(v)) || v === '' || v === '-')) {
+        return
+      }
+    }
+    if (params.detail[i]) {
+      params.detail[i][k] = v
+    }
+    this.setState({
+      params: params
+    })
+  }
+
+  // 新增属性字段
+  addDetail = () => {
+    const { params } = this.state
+
+    this.setState({
+      params: {
+        ...params,
+        detail: [
+          ...params.detail,
+          {
+            rate: '',
+            url: ''
+          }
+        ]
+      }
+    })
+  };
+
+  // 删除属性字段
+  delDetail = (index) =>
+    this.setState((prevState) => ({
+      params: {
+        ...prevState.params,
+        detail: prevState.params.detail.filter((_, i) => i !== index)
+      }
+    }));
+
+  // 统一change
+  onParamsChange = (k, v) => {
+    const { params } = this.state
+    const newParams = { ...params }
+    this.setState(
+      {
+        params: { ...newParams, [k]: v }
+      }
+    )
+  };
+
+  onOk = (cb) => {
+    if (!this.getCheck()) {
+      return
+    }
+    const { params = {} } = this.state
+    const { detail = [] } = params
+    // 接口内容空值判断
+    if (!detail.length) {
+      message.warning('压测 接口内容 不能为空!')
+      return
+    }
+    // 接口 url 空值判断
+    if (!detail.every((value) => value.url)) {
+      message.warning('压测 接口 url 不能为空!')
+      return
+    }
+    // 接口占比 空值判断
+    if (!detail.every((value) => value.rate)) {
+      message.warning('压测 接口占比 不能为空!')
+      return
+    }
+    if (typeof this.props.onOk === 'function') { this.props.onOk({ ...params }) }
+    if (typeof cb === 'function') {
+      // eslint-disable-next-line standard/no-callback-literal
+      cb({ ...params })
+    }
+  };
+
+  render () {
+    const {
+      params = {}
+    } = this.state
+    const { onCancel, showModal, update } = this.props
+
+    const detail = params.detail || []
+    const settingProps = []
+    if (update) {
+      settingProps.push({
+        label: '需要新压测数据',
+        extra: '如果你不明白这个选项是什么,请不要修改~',
+        extraInline: true,
+        key: 'needNew',
+        type: 'switch',
+        checkedChildren: '是',
+        unCheckedChildren: '否'
+      })
+    }
+    this.formSetting = [
+      ...settingProps,
+      {
+        label: '任务名',
+        key: 'name',
+        placeholder: '请输入压测任务名',
+        isRequired: true,
+        type: 'input'
+      },
+      {
+        label: '登录 URL',
+        key: 'getTokenUrl',
+        placeholder: '请输入用户登录的 URL',
+        isRequired: true,
+        type: 'input'
+      },
+      {
+        label: '压测接口',
+        key: 'detail',
+        isRequired: true,
+        render: () => {
+          return (
+            <div>
+              {detail.map((d, i) => (
+                <div
+                  key={i}
+                  style={{
+                    display: 'flex',
+                    height: '40px',
+                    alignItems: 'center',
+                    flexWrap: 'wrap'
+                  }}
+                >
+                  <Input
+                    value={d.url}
+                    style={{ width: 380 }}
+                    placeholder="请输入压测的 URL"
+                    isRequired
+                    onChange={(e) =>
+                      this.setDetail(i, e.target.value, 'url')
+                    }
+                  />
+                  &nbsp;&nbsp;
+                  <Input
+                    value={d.rate}
+                    style={{ width: 100 }}
+                    placeholder="接口占比"
+                    onChange={(e) =>
+                      this.setDetail(i, e.target.value, 'rate')
+                    }
+                  />
+                  &nbsp;&nbsp;
+                  <Button
+                    type="dashed"
+                    icon="minus"
+                    style={{ marginRight: '2px' }}
+                    onClick={() => this.delDetail(i)}
+                  />
+                  {i === detail.length - 1 ? (
+                    <>
+                      <Button
+                        type="dashed"
+                        icon="plus"
+                        onClick={() => this.addDetail()}
+                      />
+                    </>
+                  ) : (
+                    <>
+                      <Button
+                        type="dashed"
+                        icon="plus"
+                        style={{
+                          opacity: '0'
+                        }}
+                      />
+                    </>
+                  )}
+                </div>
+              ))}
+              {!detail.length && (
+                <Button
+                  type="dashed"
+                  icon="plus"
+                  onClick={() => this.addDetail()}
+                />
+              )}
+            </div>
+          )
+        }
+      }
+    ]
+    return (
+      <Modal
+        title={update ? '查看压测任务' : '添加压测任务'}
+        visible={showModal}
+        width={900}
+        onOk={this.onOk}
+        okText={update ? '更新' : '确认'}
+        onCancel={onCancel}
+      >
+        <FormItem
+          getCheck={(cb) => {
+            this.getCheck = cb
+          }}
+          onChange={this.onParamsChange}
+          formSetting={this.formSetting}
+          params={params}
+        />
+      </Modal>
+    )
+  }
+}
+export default Index

+ 134 - 0
src/pages/halberd/components/startTask/index.js

@@ -0,0 +1,134 @@
+import React, { Component } from 'react'
+import { Modal, message } from 'antd'
+import { FormItem } from 'wptpc-design'
+class Index extends Component {
+  state = {
+    data: null,
+    params: null
+  };
+
+  constructor (props) {
+    super(props)
+
+    let params = null
+    if (props.params) {
+      params = props.params
+    }
+    this.state = {
+      params: {
+        ...params
+      }
+    }
+  }
+
+  // 统一change
+  onParamsChange = (k, v) => {
+    const { params } = this.state
+    const newParams = { ...params }
+    this.setState(
+      {
+        params: { ...newParams, [k]: v }
+      }
+    )
+  };
+
+  onOk = (cb) => {
+    if (!this.getCheck()) {
+      return
+    }
+    const { params = {} } = this.state
+    if (Object.values(params).some(item => item === '0')) {
+      message.warning('内容数字不可以为 0!')
+      return
+    }
+    if (typeof this.props.onOk === 'function') { this.props.onOk({ ...params }) }
+    if (typeof cb === 'function') {
+      // eslint-disable-next-line standard/no-callback-literal
+      cb({ ...params })
+    }
+  };
+
+  // 设置属性字段
+  setDetail (v, k) {
+    const { params } = this.state
+    if (params) {
+      params[k] = v
+    }
+    this.setState({
+      params: params
+    })
+  }
+
+  onChange = (e, key) => {
+    const { value } = e.target
+    const reg = /^-?[0-9]*(\.[0-9]*)?$/
+    if ((!isNaN(value) && reg.test(value)) || value === '' || value === '-') {
+      this.setDetail(value, key)
+    }
+  };
+
+  render () {
+    const {
+      params = {}
+    } = this.state
+    const { onCancel, showModal } = this.props
+
+    this.formSetting = [
+      {
+        label: '压测链接数',
+        key: 'connects',
+        value: params.connects,
+        placeholder: '请输入 压测链接数 (仅数字有效)',
+        isRequired: true,
+        type: 'input',
+        onChange: (e) => { this.onChange(e, 'connects') }
+      },
+      {
+        label: '压测线程数',
+        key: 'threads',
+        value: params.threads,
+        placeholder: '请输入 压测线程数 (仅数字有效)',
+        isRequired: true,
+        type: 'input',
+        onChange: (e) => { this.onChange(e, 'threads') }
+      },
+      {
+        label: '接口超过时间',
+        key: 'timeout',
+        value: params.timeout,
+        placeholder: '请输入 接口超过时间 (仅数字有效)',
+        isRequired: true,
+        type: 'input',
+        onChange: (e) => { this.onChange(e, 'timeout') }
+      },
+      {
+        label: '压测持续时间',
+        key: 'duration',
+        value: params.duration,
+        placeholder: '请输入 压测持续时间 (仅数字有效)',
+        isRequired: true,
+        type: 'input',
+        onChange: (e) => { this.onChange(e, 'duration') }
+      }
+    ]
+    return (
+      <Modal
+        title={'开始压测'}
+        visible={showModal}
+        width={900}
+        onOk={this.onOk}
+        onCancel={onCancel}
+      >
+        <FormItem
+          getCheck={(cb) => {
+            this.getCheck = cb
+          }}
+          onChange={this.onParamsChange}
+          formSetting={this.formSetting}
+          params={params}
+        />
+      </Modal>
+    )
+  }
+}
+export default Index

+ 361 - 0
src/pages/halberd/index.js

@@ -0,0 +1,361 @@
+import React from 'react'
+import { Button, Input, Table, Badge, Popconfirm } from 'antd'
+import AddTaskModal from './components/addTask'
+import StartTaskModal from './components/startTask'
+import router from 'umi/router'
+
+import {
+  taskBegin,
+  taskStop,
+  taskUpdate,
+  taskAdd,
+  taskCopy,
+  taskList
+} from './service'
+const { Search } = Input
+
+const scheduleState = {
+  0: {
+    text: '初始化',
+    color: 'orange'
+  },
+  1: {
+    text: '准备完成',
+    color: 'blue'
+  },
+  2: {
+    text: '压测中',
+    color: 'volcano'
+  },
+
+  3: {
+    text: '压测完成',
+    color: 'green'
+  }
+}
+
+class Halberd extends React.PureComponent {
+  state = {
+    data: [],
+    addFormVisible: false,
+    updateFormVisible: false,
+    startTaskVisible: false,
+    params: { detail: [] }
+  };
+
+  columns = [
+    {
+      title: '压测任务',
+      dataIndex: 'name',
+      width: 400,
+      key: 'name'
+    },
+    {
+      title: '添加人',
+      dataIndex: 'op_uid',
+      key: 'op_uid'
+    },
+    {
+      title: '压测进度',
+      dataIndex: 'state',
+      key: 'state',
+      render: state => (
+        <Badge color={scheduleState[state].color} text={scheduleState[state].text} />
+      )
+    },
+    {
+      title: '操作',
+      key: '_id',
+      render: value => {
+        return (
+          <div>
+            <Popconfirm
+              title="确定复制吗?"
+              onConfirm={() => this.copyTask(value._id)}
+              okText="确定"
+              cancelText="取消"
+            >
+              <Button type="primary" size={'small'} style={{ margin: '0 5px' }}>
+                复制任务
+              </Button>
+            </Popconfirm>
+
+            <Button
+              type="primary"
+              size={'small'}
+              style={{ margin: '0 5px' }}
+              onClick={() => {
+                this.viewTaskDetail(value)
+              }}
+            >
+              查看详情
+            </Button>
+            <Button
+              type="primary"
+              size={'small'}
+              disabled={value.state !== 1 && value.state !== 3}
+              style={{ margin: '0 5px' }}
+              onClick={() => {
+                this.showStartTaskModal(value._id)
+              }}
+            >
+              开始压测
+            </Button>
+            <Popconfirm
+              title="确定停止吗?"
+              onConfirm={() => this.stopTask(value._id)}
+              okText="确定"
+              cancelText="取消"
+            >
+              <Button type="primary" size={'small'} style={{ margin: '0 5px' }}>
+                停止压测
+              </Button>
+            </Popconfirm>
+            <Button
+              type="primary"
+              size={'small'}
+              style={{ margin: '0 5px' }}
+              onClick={() => {
+                this.showViewReport(value._id)
+              }}
+            >
+              查看报告
+            </Button>
+          </div>
+        )
+      }
+    }
+  ];
+
+  componentDidMount () {
+    this.getTackList()
+  }
+
+  /**
+   * 获取压测任务列表
+   * @param {*} name
+   */
+  getTackList = (name = '') => {
+    taskList({
+      page: 1, // 页数
+      pageNum: 10000, // 一页展示数量
+      taskName: name // 任务名称
+    }).then(res => {
+      const { code, data } = res
+      if (code === 0) {
+        this.setState({
+          data: data.list || []
+        })
+      }
+    })
+  };
+
+  /**
+   * 复制任务
+   * @param {*} id
+   */
+  copyTask = id => {
+    taskCopy({
+      id // 需要复制的任务id
+    }).then(res => {
+      const { code } = res
+      if (code === 0) {
+        this.getTackList()
+      }
+    })
+  };
+
+  /**
+   * 停止任务
+   * @param {*} id
+   */
+  stopTask = id => {
+    taskStop({
+      id // 需要停止的任务id
+    }).then(res => {
+      const { code } = res
+      if (code === 0) {
+        this.getTackList()
+      }
+    })
+  }
+
+  /**
+   * 打开查看详情弹窗
+   * @param {*} id
+   */
+  viewTaskDetail = value => {
+    const { data } = this.state
+    // eslint-disable-next-line camelcase
+    const { _id: id, name, get_token_url } = value
+    let detail = ''
+
+    data.forEach(item => {
+      if (item._id === id) {
+        console.log('item.detail', value, data)
+
+        detail = item.detail
+      }
+    })
+    this.setState({
+      updateFormVisible: true,
+      params: {
+        id,
+        name: name,
+        getTokenUrl: get_token_url,
+        detail: JSON.parse(detail)
+      }
+    })
+  };
+
+  /**
+   * 打开 开始压测弹窗
+   * @param {*} id
+   */
+  showStartTaskModal = id => {
+    this.setState({
+      startTaskVisible: true,
+      params: {
+        id
+      }
+    })
+  };
+
+  /**
+   * 打开 查看压测报告页面
+   * @param {*} id
+   */
+  showViewReport = id => {
+    router.push(`/halberd/viewReport?id=${id}`)
+  }
+
+  /**
+   * 打开添加弹窗
+   */
+  showAddTask = () => {
+    this.setState({
+      addFormVisible: true
+    })
+  };
+
+  /**
+   * 隐藏所有弹窗
+   */
+  hidden = () => {
+    this.setState({
+      addFormVisible: false,
+      updateFormVisible: false,
+      startTaskVisible: false
+    })
+  };
+
+  /**
+   * 添加压测任务
+   * @param {*} data
+   */
+  addTask = data => {
+    const { name = '', getTokenUrl, detail = [] } = data
+    taskAdd({
+      name,
+      getTokenUrl: getTokenUrl,
+      detail: JSON.stringify(detail.map(item => {
+        return {
+          ...item,
+          rate: Number(item.rate)
+        }
+      }))
+    }).then(res => {
+      const { code } = res
+      if (code === 0) {
+        this.hidden()
+        this.getTackList()
+      }
+    })
+  };
+
+  /**
+   * 更新压测任务
+   * @param {*} data
+   */
+  updateTask = data => {
+    const { name = '', getTokenUrl, detail = [] } = data
+
+    taskUpdate({
+      id: data.id,
+      name,
+      getTokenUrl: getTokenUrl,
+      needNew: data.needNew,
+      detail: JSON.stringify(detail)
+    }).then(res => {
+      const { code } = res
+      if (code === 0) {
+        this.hidden()
+      }
+    })
+  };
+
+  /**
+   * 开始压测任务
+   * @param {*} data
+   */
+  startTask = data => {
+    console.log(data)
+    const { id, connects, threads, timeout, duration } = data
+    taskBegin({
+      id,
+      connects: Number(connects),
+      threads: Number(threads),
+      timeout: Number(timeout),
+      duration: Number(duration)
+    }).then(res => {
+      const { code } = res
+      if (code === 0) {
+        this.hidden()
+        this.getTackList()
+      }
+    })
+  };
+
+  render () {
+    const { data, addFormVisible, updateFormVisible, startTaskVisible, params } = this.state
+    return (
+      <div style={{ margin: '0 auto' }}>
+        <Search
+          placeholder="请输入压测任务名进行搜索"
+          enterButton="搜索"
+          onSearch={value => this.getTackList(value)}
+          style={{ width: 400 }}
+        />
+
+        <div style={{ margin: '20px 0' }}>
+          <Button type="primary" onClick={this.showAddTask}>
+            添加
+          </Button>
+        </div>
+        <Table dataSource={data} columns={this.columns} rowKey="_id" />
+        {addFormVisible && (
+          <AddTaskModal showModal={addFormVisible} onOk={this.addTask} onCancel={this.hidden} />
+        )}
+        {updateFormVisible && (
+          <AddTaskModal
+            showModal={updateFormVisible}
+            update
+            params={params}
+            onOk={this.updateTask}
+            onCancel={this.hidden}
+          />
+        )}
+        {startTaskVisible && (
+          <StartTaskModal
+            showModal={startTaskVisible}
+            onOk={this.startTask}
+            onCancel={this.hidden}
+            params={params}
+          />
+        )}
+      </div>
+    )
+  }
+}
+
+export default Halberd

+ 46 - 0
src/pages/halberd/service.js

@@ -0,0 +1,46 @@
+
+import { fetchApi } from '@/apis/'
+
+import { yc } from '@/conf/config'
+
+// 开始压测任务
+export async function taskBegin (params) {
+  const url = `${yc}/schedule/task/begin`
+  return fetchApi(url, params)
+}
+
+// 停止压测任务
+export async function taskStop (params) {
+  const url = `${yc}/schedule/task/stop`
+  return fetchApi(url, params)
+}
+
+// 查看压测报告
+export async function viewReport (params) {
+  const url = `${yc}/schedule/task/view/report`
+  return fetchApi(url, params)
+}
+
+// 更新压测任务
+export async function taskUpdate (params) {
+  const url = `${yc}/schedule/task/update`
+  return fetchApi(url, params)
+}
+
+// 添加压测任务
+export async function taskAdd (params) {
+  const url = `${yc}/schedule/task/add`
+  return fetchApi(url, params)
+}
+
+// 复制压测报告
+export async function taskCopy (params) {
+  const url = `${yc}/schedule/task/copy`
+  return fetchApi(url, params)
+}
+
+// 获取压测任务列表
+export async function taskList (params) {
+  const url = `${yc}/schedule/task/list`
+  return fetchApi(url, params)
+}

+ 271 - 0
src/pages/halberd/viewReport/index.js

@@ -0,0 +1,271 @@
+import React, { Component } from 'react'
+import { Table, PageHeader } from 'antd'
+import get from 'lodash/get'
+import router from 'umi/router'
+import moment from 'moment'
+import {
+  viewReport
+} from '../service'
+import Styles from './index.less'
+class Index extends Component {
+  state = {
+    data: null,
+    params: null,
+    dataSource: [],
+    task: {
+      name: ''
+    }
+  };
+
+  constructor (props) {
+    super(props)
+
+    let params = null
+    if (props.params) {
+      params = props.params
+    }
+    this.state = {
+      params: {
+        ...params
+      }
+    }
+  }
+
+  componentDidMount () {
+    const _id = get(this.props.location, ['query', 'id'])
+    viewReport({
+      id: _id, // 压测详情 id
+      page: 1, // 页数
+      pageNum: 10000 // 一页展示数量
+    }).then((res) => {
+      const { code, data } = res
+      if (code === 0) {
+        this.setState({
+          dataSource: data.list.map(item => {
+            return {
+              ...item.report,
+              ...item,
+              finish_time: item.finish_time && moment.unix(item.finish_time).format('YYYY-MM-DD HH:mm:ss')
+            }
+          }),
+          task: data.task
+        })
+      }
+    })
+  }
+
+ columns = [
+   {
+     title: '执行人',
+     dataIndex: 'perform_user',
+     key: 'perform_user',
+     align: 'center',
+     className: Styles.head
+   },
+   {
+     title: '线程数',
+     dataIndex: 'threads',
+     key: 'threads',
+     align: 'center'
+   },
+   {
+     title: '链接数',
+     dataIndex: 'connects',
+     key: 'connects',
+     align: 'center'
+   },
+   {
+     title: '接口超过时间',
+     dataIndex: 'timeout',
+     key: 'timeout',
+     align: 'center'
+   },
+   {
+     title: '持续时间',
+     dataIndex: 'duration',
+     key: 'duration',
+     align: 'center'
+   },
+   {
+     title: '完成时间',
+     dataIndex: 'finish_time',
+     key: 'finish_time',
+     width: 150,
+     align: 'center'
+   },
+   {
+     title: '总计',
+     children: [
+       {
+         title: '发送数量',
+         dataIndex: 'summary_bytes',
+         key: 'summary_bytes',
+         align: 'center'
+       },
+       //  {
+       //    title: '持续时间',
+       //    dataIndex: 'summary_duration',
+       //    key: 'summary_duration',
+       //    align: 'center'
+       //  },
+       {
+         title: '总请求数',
+         dataIndex: 'summary_requests',
+         key: 'summary_requests',
+         align: 'center'
+       },
+       {
+         title: 'QPS',
+         dataIndex: 'summary_req_sec',
+         key: 'summary_req_sec',
+         align: 'center'
+       }
+     ]
+   },
+   {
+     title: '错误信息',
+     children: [
+       {
+         title: '连接错误数',
+         dataIndex: 'err_connect',
+         key: 'err_connect',
+         align: 'center'
+       },
+       {
+         title: '读错误',
+         dataIndex: 'err_read',
+         key: 'err_read',
+         align: 'center'
+       },
+       {
+         title: '非 2xx/3xx',
+         dataIndex: 'err_status',
+         key: 'err_status',
+         align: 'center'
+       },
+       {
+         title: '超时',
+         dataIndex: 'err_timeout',
+         key: 'err_timeout',
+         align: 'center'
+       },
+       {
+         title: '写错误',
+         dataIndex: 'err_write',
+         key: 'err_write',
+         align: 'center'
+       }
+
+     ]
+   },
+   {
+     title: '延迟分布',
+     children: [
+       {
+         title: '最大耗时',
+         dataIndex: 'latency_max',
+         key: 'latency_max',
+         align: 'center'
+       },
+       {
+         title: '平均',
+         dataIndex: 'latency_mean',
+         key: 'latency_mean',
+         align: 'center'
+       },
+       {
+         title: '最小耗时',
+         dataIndex: 'latency_min',
+         key: 'latency_min',
+         align: 'center'
+       },
+       {
+         title: '50%',
+         dataIndex: 'latency_p50',
+         key: 'latency_p50',
+         align: 'center'
+       },
+       {
+         title: '90%',
+         dataIndex: 'latency_p90',
+         key: 'latency_p90',
+         align: 'center'
+       },
+       {
+         title: '99%',
+         dataIndex: 'latency_p99',
+         key: 'latency_p99',
+         align: 'center'
+       },
+       {
+         title: '99.999%',
+         dataIndex: 'latency_p99.999',
+         key: 'latency_p99.999',
+         align: 'center'
+       },
+       {
+         title: '标准差',
+         dataIndex: 'latency_stdev',
+         key: 'latency_stdev',
+         align: 'center'
+       }
+     ]
+   },
+
+   {
+     title: '线程统计',
+     children: [
+       {
+         title: '最大值',
+         dataIndex: 'ts_max',
+         key: 'ts_max',
+         align: 'center'
+       },
+       {
+         title: '平均数',
+         dataIndex: 'ts_mean',
+         key: 'ts_mean',
+         align: 'center'
+       },
+       {
+         title: '最小值',
+         dataIndex: 'ts_min',
+         key: 'ts_min',
+         align: 'center'
+       },
+       {
+         title: '标准差',
+         dataIndex: 'ts_stdev',
+         key: 'ts_stdev',
+         align: 'center'
+       }
+     ]
+   }
+
+ ];
+
+ render () {
+   const { dataSource, task = {} } = this.state
+   return (
+     <div>
+       <PageHeader
+         style={{
+           border: '1px solid rgb(235, 237, 240)'
+         }}
+         onBack={() => {
+           router.push('/halberd')
+         }}
+         title={`${task.name || ''}-压测报告`}
+       />
+       <Table
+         columns={this.columns}
+         className={Styles.head}
+         bordered
+         rowKey="_id"
+         dataSource={dataSource}
+         scroll={{ x: '170%' }}/>
+     </div>
+   )
+ }
+}
+export default Index

+ 7 - 0
src/pages/halberd/viewReport/index.less

@@ -0,0 +1,7 @@
+.head {
+
+  thead tr th {
+    background-color: #CADBE9;
+
+  }
+}

+ 0 - 1
src/utils/getRealUrl.js

@@ -15,5 +15,4 @@ export default function (url) {
   } else {
     return url
   }
-  
 }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません