You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by mi...@apache.org on 2019/03/21 02:06:08 UTC

[incubator-dubbo-admin] branch develop updated: add echarts to implement metrics

This is an automated email from the ASF dual-hosted git repository.

min pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-dubbo-admin.git


The following commit(s) were added to refs/heads/develop by this push:
     new 1faaec0  add echarts to implement metrics
1faaec0 is described below

commit 1faaec0b747604897c4a9af6c67222744231ecd7
Author: nzomkxia <z8...@gmail.com>
AuthorDate: Thu Mar 21 10:05:58 2019 +0800

    add echarts to implement metrics
---
 dubbo-admin-ui/index.html                          |   1 +
 dubbo-admin-ui/src/api/chart.js                    |  96 +++++++++
 .../src/components/metrics/ServiceMetrics.vue      | 228 +++++++++++++++++++--
 dubbo-admin-ui/src/components/public/MiniChart.vue |  86 ++++++++
 dubbo-admin-ui/src/lang/en.js                      |   5 +
 dubbo-admin-ui/src/lang/zh.js                      |   5 +
 dubbo-admin-ui/src/util/echart.js                  | 215 +++++++++++++++++++
 7 files changed, 622 insertions(+), 14 deletions(-)

diff --git a/dubbo-admin-ui/index.html b/dubbo-admin-ui/index.html
index 2718725..a4f69a5 100644
--- a/dubbo-admin-ui/index.html
+++ b/dubbo-admin-ui/index.html
@@ -23,6 +23,7 @@
     <title>Dubbo Admin</title>
     <link href='/static/OpenSans.css' rel="stylesheet" type="text/css">
     <link rel="shortcut icon" href="/static/dubbo.ico" type="image/x-icon">
+    <script src="https://cdn.bootcss.com/echarts/4.0.4/echarts-en.min.js"></script>
   </head>
   <body>
     <div id="app"></div>
diff --git a/dubbo-admin-ui/src/api/chart.js b/dubbo-admin-ui/src/api/chart.js
new file mode 100644
index 0000000..4190c7b
--- /dev/null
+++ b/dubbo-admin-ui/src/api/chart.js
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const range = (start, end) => new Array(end - start).fill(start).map((el, i) => start + i)
+
+const shortMonth = [
+  'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
+]
+const monthVisitData = shortMonth.map(m => {
+  return {
+    'month': m,
+    'Unique Visit': Math.floor(Math.random() * 1000) + 200,
+    'Page View': Math.floor(Math.random() * 1000) + 250
+  }
+})
+
+const campaignData = [
+  {
+    value: 335,
+    name: 'Website'
+  },
+  {
+    value: 310,
+    name: 'Email'
+  },
+  {
+    value: 234,
+    name: 'Ads'
+  },
+  {
+    value: 135,
+    name: 'Video'
+  },
+  {
+    value: 1548,
+    name: 'Search'
+  }
+]
+const locationData = [
+  {
+    value: 50,
+    name: 'China'
+  },
+  {
+    value: 35,
+    name: 'USA'
+  },
+  {
+    value: 25,
+    name: 'EU'
+  },
+  {
+    value: 10,
+    name: 'Russia'
+  },
+  {
+    value: 10,
+    name: 'Other'
+  }
+]
+
+const StackMainData = [220, 182, 191, 234, 290, 330, 310, 123, 442, 321, 90, 149, 210, 122, 133, 334, 198, 123, 125, 220]
+const StackData = StackMainData.map((item, key) => {
+  return {
+    'label': key + 'D',
+    'max': 500,
+    'sales': item
+  }
+})
+const SinData = range(1, 12).map(i => {
+  return {
+    'cate': 'Cat' + i,
+    'value': ((Math.sin(i / 5) * (i / 5 - 0.1) + i / 6) * 5)
+  }
+})
+
+export {
+  monthVisitData,
+  campaignData,
+  locationData,
+  StackData,
+  SinData
+}
diff --git a/dubbo-admin-ui/src/components/metrics/ServiceMetrics.vue b/dubbo-admin-ui/src/components/metrics/ServiceMetrics.vue
index 204083a..e1765a9 100644
--- a/dubbo-admin-ui/src/components/metrics/ServiceMetrics.vue
+++ b/dubbo-admin-ui/src/components/metrics/ServiceMetrics.vue
@@ -16,27 +16,227 @@
   -->
 
 <template>
-  <v-container
-    fill-height
-  >
-    <v-layout align-center>
-      <v-flex text-xs-center>
-        <h1 class="display-2 primary--text">{{$t('later.metrics')}}</h1>
-        <v-btn
-          href="#/service"
-          color="primary"
-          outline
-        >
-          {{$t('goIndex')}}
-        </v-btn>
+  <v-container grid-list-xl fluid>
+    <v-layout row wrap>
+      <v-flex lg12>
+        <breadcrumb title="metrics" :items="breads"></breadcrumb>
+      </v-flex>
+      <v-flex xs12 >
+        <search id="serviceSearch" v-model="filter" :submit="submit" :label="$t('searchSingleMetrics')"></search>
+      </v-flex>
+      <v-flex lg4 sm6 xs12>
+        <v-card>
+          <v-card-text>
+            <mini-chart
+              title="Monthly Sales"
+              sub-title="10%"
+              icon="trending_up"
+              :data="dataset.monthVisit"
+              :chart-color="color.blue.base"
+              type="line"
+            >
+            </mini-chart>
+            <mini-chart
+              title="Monthly Sales"
+              sub-title="10%"
+              icon="trending_up"
+              :data="dataset.monthVisit"
+              :chart-color="color.blue.base"
+              type="line"
+            >
+            </mini-chart>
+          </v-card-text>
+        </v-card>
+      </v-flex>
+      <v-flex lg4 sm6 xs12>
+        <v-card>
+          <v-card-text>
+            <mini-chart
+              title="Monthly Sales"
+              sub-title="10%"
+              icon="trending_up"
+              :data="dataset.monthVisit"
+              :chart-color="color.blue.base"
+              type="line"
+            >
+            </mini-chart>
+            <mini-chart
+              title="Monthly Sales"
+              sub-title="10%"
+              icon="trending_up"
+              :data="dataset.monthVisit"
+              :chart-color="color.blue.base"
+              type="line"
+            >
+            </mini-chart>
+          </v-card-text>
+        </v-card>
+      </v-flex>
+      <v-flex lg4 sm6 xs12>
+        <v-card>
+          <v-card-text>
+            <mini-chart
+              title="Monthly Sales"
+              sub-title="10%"
+              icon="trending_up"
+              :data="dataset.monthVisit"
+              :chart-color="color.blue.base"
+              type="line"
+            >
+            </mini-chart>
+            <mini-chart
+              title="Monthly Sales"
+              sub-title="10%"
+              icon="trending_up"
+              :data="dataset.monthVisit"
+              :chart-color="color.blue.base"
+              type="line"
+            >
+            </mini-chart>
+          </v-card-text>
+        </v-card>
+      </v-flex>
+      <v-flex sm12>
+        <h3>{{$t('methodMetrics')}}</h3>
+      </v-flex>
+      <v-flex lg12 >
+        <v-tabs
+          class="elevation-1">
+          <v-tab>
+            {{$t('providers')}}
+          </v-tab>
+          <v-tab>
+            {{$t('consumers')}}
+          </v-tab>
+          <v-tab-item>
+            <v-data-table
+              class="elevation-1"
+              :headers="headers"
+              :items="providerDetails"
+            >
+              <template slot="items" slot-scope="props">
+                <td>{{props.item.service}}</td>
+                <td>{{props.item.method}}</td>
+                <td>{{props.item.qps}}</td>
+                <td>{{props.item.rt}}</td>
+                <td>{{props.item.successRate}}</td>
+              </template>
+            </v-data-table>
+          </v-tab-item>
+          <v-tab-item >
+            <v-data-table
+              class="elevation-1"
+              :headers="headers"
+              :items="consumerDetails"
+            >
+              <template slot="items" slot-scope="props">
+                <td>{{props.item.service}}</td>
+                <td>{{props.item.method}}</td>
+                <td>{{props.item.qps}}</td>
+                <td>{{props.item.rt}}</td>
+                <td>{{props.item.successRate}}</td>
+              </template>
+            </v-data-table>
+          </v-tab-item>
+        </v-tabs>
       </v-flex>
     </v-layout>
   </v-container>
 </template>
 
 <script>
+  import EChart from '@/util/echart'
+  import Material from 'vuetify/es5/util/colors'
+  import MiniChart from '@/components/public/MiniChart'
+  import Breadcrumb from '@/components/public/Breadcrumb'
+  import Search from '@/components/public/Search'
+  import {
+    monthVisitData,
+    campaignData,
+    locationData,
+    StackData,
+    SinData
+  } from '@/api/chart'
   export default {
-    name: 'ServiceMetrics'
+    name: 'ServiceMetrics',
+    components: {
+      MiniChart,
+      EChart,
+      Breadcrumb,
+      Search
+    },
+    data () {
+      return {
+        selectedTab: 'tab-1',
+        filter: '',
+        headers: [],
+        providerDetails: [
+          {
+            service: 'a.b.c.d',
+            method: 'aaaa~ICS',
+            qps: '0.58',
+            rt: '111',
+            successRate: '100%'
+          },
+          {
+            service: 'a.b.c.f',
+            method: 'bbbb~ICS',
+            qps: '0.87',
+            rt: '120',
+            successRate: '90%'
+          }
+
+        ],
+        consumerDetails: [],
+        option: null,
+        dataset: {
+          sinData: SinData,
+          monthVisit: monthVisitData,
+          campaign: campaignData,
+          location: locationData,
+          stackData: StackData
+        },
+        color: Material,
+        breads: [
+          {
+            text: 'metrics',
+            href: ''
+          }
+        ]
+
+      }
+    },
+    methods: {
+      submit: function () {
+      },
+      setHeaders: function () {
+        this.headers = [
+          {
+            text: this.$t('service'),
+            value: 'service'
+          },
+          {
+            text: this.$t('method'),
+            value: 'method'
+          },
+          {
+            text: this.$t('qps'),
+            value: 'qps'
+          },
+          {
+            text: this.$t('rt'),
+            value: 'rt'
+          },
+          {
+            text: this.$t('successRate'),
+            value: 'successRate'
+          }
+        ]
+      }
+    },
+    mounted: function () {
+      this.setHeaders()
+    }
   }
 </script>
 
diff --git a/dubbo-admin-ui/src/components/public/MiniChart.vue b/dubbo-admin-ui/src/components/public/MiniChart.vue
new file mode 100644
index 0000000..bc087e9
--- /dev/null
+++ b/dubbo-admin-ui/src/components/public/MiniChart.vue
@@ -0,0 +1,86 @@
+<!--
+  - Licensed to the Apache Software Foundation (ASF) under one or more
+  - contributor license agreements.  See the NOTICE file distributed with
+  - this work for additional information regarding copyright ownership.
+  - The ASF licenses this file to You under the Apache License, Version 2.0
+  - (the "License"); you may not use this file except in compliance with
+  - the License.  You may obtain a copy of the License at
+  -
+  -     http://www.apache.org/licenses/LICENSE-2.0
+  -
+  - Unless required by applicable law or agreed to in writing, software
+  - distributed under the License is distributed on an "AS IS" BASIS,
+  - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  - See the License for the specific language governing permissions and
+  - limitations under the License.
+  -->
+
+<template>
+      <div class="layout row ma-0 align-center justify-space-between">
+        <div class="text-box">
+          <div class="subheading pb-2">{{title}}</div>
+          <span class="grey--text">{{subTitle}} <v-icon small :color="iconColor">{{icon}}</v-icon></span>
+        </div>
+        <div class="chart">
+          <e-chart
+            :path-option="computeChartOption"
+            height="68px"
+            width="100%"
+          >
+          </e-chart>
+        </div>
+      </div>
+</template>
+
+<script>
+  import EChart from '@/util/echart'
+  export default {
+    components: {
+      EChart
+    },
+    props: {
+      title: String,
+      subTitle: String,
+      icon: String,
+      iconColor: {
+        type: String,
+        default: 'success'
+      },
+      type: String,
+      chartColor: String,
+      data: Array
+    },
+    data () {
+      return {
+        defaultOption: [
+          ['dataset.source', this.data],
+          ['xAxis.show', false],
+          ['yAxis.show', false],
+          ['color', [this.chartColor]]
+        ]
+      }
+    },
+
+    computed: {
+      computeChartOption () {
+        switch (this.type) {
+          case 'bar':
+            this.defaultOption.push(['series[0].type', 'bar'])
+            break
+          case 'area':
+            this.defaultOption.push(['series[0].type', 'line'])
+            this.defaultOption.push(['series[0].areaStyle', {}])
+            break
+          default:
+            break
+        }
+        return this.defaultOption
+      }
+    }
+
+  }
+</script>
+
+<style scoped>
+
+</style>
diff --git a/dubbo-admin-ui/src/lang/en.js b/dubbo-admin-ui/src/lang/en.js
index af39aff..405fa04 100644
--- a/dubbo-admin-ui/src/lang/en.js
+++ b/dubbo-admin-ui/src/lang/en.js
@@ -34,6 +34,9 @@ export default {
   version: 'Version',
   app: 'Application',
   ip: 'IP',
+  qps: 'qps',
+  rt: 'rt',
+  successRate: 'success rate',
   port: 'PORT',
   timeout: 'timeout(ms)',
   serialization: 'serialization',
@@ -72,11 +75,13 @@ export default {
   appNameHint: 'Application name the service belongs to',
   basicInfo: 'BasicInfo',
   metaData: 'MetaData',
+  methodMetrics: 'Method Statistics',
   searchDubboService: 'Search Dubbo Services or applications',
   serviceSearchHint: 'Service ID, org.apache.dubbo.demo.api.DemoService, * for all services',
   ipSearchHint: 'Find all services provided by the target server on the specified IP address',
   appSearchHint: 'Input an application name to find all services provided by one particular application, * for all',
   searchTagRule: 'Search Tag Rule by application name',
+  searchSingleMetrics: 'Search Metrics by IP',
   searchBalanceRule: 'Search Balancing Rule',
   noMetadataHint: 'There is no metadata available, please update to Dubbo2.7, or check your config center configuration in application.properties, please check ',
   parameterList: 'parameterList',
diff --git a/dubbo-admin-ui/src/lang/zh.js b/dubbo-admin-ui/src/lang/zh.js
index 9a03cfe..3f863a1 100644
--- a/dubbo-admin-ui/src/lang/zh.js
+++ b/dubbo-admin-ui/src/lang/zh.js
@@ -33,6 +33,9 @@ export default {
   version: '版本',
   app: '应用',
   ip: 'IP地址',
+  qps: 'qps',
+  rt: 'rt',
+  successRate: '成功率',
   serviceInfo: '服务信息',
   port: '端口',
   timeout: '超时(毫秒)',
@@ -72,11 +75,13 @@ export default {
   appNameHint: '服务所属的应用名称',
   basicInfo: '基础信息',
   metaData: '元数据',
+  methodMetrics: '服务方法统计',
   searchDubboService: '搜索Dubbo服务或应用',
   serviceSearchHint: '服务ID, org.apache.dubbo.demo.api.DemoService, * 代表所有服务',
   ipSearchHint: '在指定的IP地址上查找目标服务器提供的所有服务',
   appSearchHint: '输入应用名称以查找由一个特定应用提供的所有服务, * 代表所有',
   searchTagRule: '根据应用名搜索标签规则',
+  searchSingleMetrics: '输入IP搜索Metrics信息',
   searchBalanceRule: '搜索负载均衡规则',
   parameterList: '参数列表',
   returnType: '返回值',
diff --git a/dubbo-admin-ui/src/util/echart.js b/dubbo-admin-ui/src/util/echart.js
new file mode 100644
index 0000000..3660f21
--- /dev/null
+++ b/dubbo-admin-ui/src/util/echart.js
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * ECharts Vue Wrapper
+ * Michael Wang
+ */
+import colors from 'vuetify/es5/util/colors'
+import _object from 'lodash/object'
+
+const ECharts = window.echarts || undefined
+if (ECharts === undefined) {
+  console.error('ECharts is not defined')
+}
+// set color palette
+const colorPalette = []
+
+Object.entries(colors).forEach((item) => {
+  if (item[1].base) {
+    colorPalette.push(item[1].base)
+  }
+});
+
+(function () {
+  const throttle = function (type, name, obj) {
+    obj = obj || window
+    let running = false
+    let func = function () {
+      if (running) { return }
+      running = true
+      requestAnimationFrame(function () {
+        obj.dispatchEvent(new CustomEvent(name))
+        running = false
+      })
+    }
+    obj.addEventListener(type, func)
+  }
+  /* init - you can init any event */
+  throttle('resize', 'optimizedResize')
+})()
+export default {
+  name: 'v-echart',
+
+  render (h) {
+    const data = {
+      staticClass: 'v-chart',
+      style: this.canvasStyle,
+      ref: 'canvas',
+      on: this.$listeners
+    }
+    return h('div', data)
+  },
+
+  props: {
+    // args of  ECharts.init(dom, theme, opts)
+    width: { type: String, default: 'auto' },
+    height: { type: String, default: '400px' },
+    merged: {
+      type: Boolean,
+      default: true
+    },
+    // instace.setOption
+    pathOption: [Object, Array],
+    option: Object,
+    // general config
+    textStyle: Object,
+    title: Object,
+    legend: [Object, Array],
+    tooltip: Object,
+    grid: { type: [Object, Array] },
+    xAxis: [Object, Array],
+    yAxis: [Object, Array],
+    series: [Object, Array],
+    axisPointer: Object,
+    dataset: { type: [Object, Array], default () { return {} } }, // option.dataSet
+    colors: Array, // echarts.option.color
+    backgroundColor: [Object, String],
+    toolbox: { type: [Object, Array] },
+    // resize delay
+    widthChangeDelay: {
+      type: Number,
+      default: 450
+    }
+  },
+  data: () => ({
+    chartInstance: null,
+    clientWidth: null,
+    allowedOptions: [
+      'textStyle', 'title', 'legend', 'xAxis',
+      'yAxis', 'series', 'tooltip', 'axisPointer',
+      'grid', 'dataset', 'colors', 'backgroundColor'
+    ],
+    _defaultOption: {
+      tooltip: {
+        show: true
+      },
+      title: {
+        show: true,
+        textStyle: {
+          color: 'rgba(0, 0, 0 , .87)',
+          fontFamily: 'sans-serif'
+        }
+      },
+      grid: {
+        containLabel: true
+      },
+      xAxis: {
+        show: true,
+        type: 'category',
+        axisLine: {
+          lineStyle: {
+            color: 'rgba(0, 0, 0 , .54)',
+            type: 'dashed'
+          }
+        },
+        axisTick: {
+          show: true,
+          alignWithLabel: true,
+          lineStyle: {
+            show: true,
+            color: 'rgba(0, 0, 0 , .54)',
+            type: 'dashed'
+          }
+        },
+        axisLabel: {
+          show: false
+        }
+      },
+      yAxis: {
+        show: true,
+        type: 'value',
+        axisLine: {
+          lineStyle: {
+            color: 'rgba(0, 0, 0 , .54)',
+            type: 'dashed'
+          }
+        },
+        axisLabel: {
+          show: false
+        },
+        splitLine: {
+          lineStyle: {
+            type: 'dashed'
+          }
+        },
+        axisTick: {
+          show: true,
+          lineStyle: {
+            show: true,
+            color: 'rgba(0, 0, 0 , .54)',
+            type: 'dashed'
+          }
+        }
+      },
+      series: [{
+        type: 'line'
+      }]
+
+    }
+  }),
+  computed: {
+    canvasStyle () {
+      return {
+        width: this.width,
+        height: this.height
+      }
+    }
+  },
+  methods: {
+    init () {
+      // set
+      if (this.pathOption) {
+        this.pathOption.forEach((p) => {
+          _object.set(this.$data._defaultOption, p[0], p[1])
+        })
+      }
+      this.chartInstance = ECharts.init(this.$refs.canvas, 'material')
+      this.chartInstance.setOption(_object.merge(this.option, this.$data._defaultOption))
+      window.addEventListener('optimizedResize', (e) => {
+        setTimeout(_ => {
+          this.chartInstance.resize()
+        }, this.widthChangeDelay)
+      })
+    },
+
+    resize () {
+      this.chartInstance.resize()
+    },
+    clean () {
+      window.removeEventListener('resize', this.chartInstance.resize)
+      this.chartInstance.clear()
+    }
+  },
+  mounted () {
+    this.init()
+  },
+
+  beforeDestroy () {
+    this.clean()
+  }
+}