You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@skywalking.apache.org by ha...@apache.org on 2018/09/04 08:24:13 UTC

[incubator-skywalking-ui] branch 6.0.0/dev updated: Refactor Dashboard page

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

hanahmily pushed a commit to branch 6.0.0/dev
in repository https://gitbox.apache.org/repos/asf/incubator-skywalking-ui.git


The following commit(s) were added to refs/heads/6.0.0/dev by this push:
     new ab179d4  Refactor Dashboard page
ab179d4 is described below

commit ab179d44b065e2344cb2f5cc60c49f55d1766b2c
Author: Gao Hongtao <ha...@gmail.com>
AuthorDate: Tue Sep 4 16:23:33 2018 +0800

    Refactor Dashboard page
---
 .gitmodules                                    |   3 +
 .roadhogrc.mock.js                             |  32 +++++-
 src/services/graphql.js => mock/aggregation.js |  20 ++--
 mock/alarm.js                                  |   3 +
 mock/dashboard.js                              |  57 -----------
 mock/metadata.js                               |  52 ++--------
 src/services/graphql.js => mock/metric.js      |  23 +++--
 package-lock.json                              |  76 +++++++++++++-
 package.json                                   |   4 +-
 query-protocol                                 |   1 +
 src/models/dashboard.js                        |  56 ++++++-----
 src/routes/Dashboard/Dashboard.js              |  49 +++++----
 src/services/graphql.js                        |   7 ++
 src/utils/models.js                            | 132 ++++++++++++++++++++++++-
 14 files changed, 341 insertions(+), 174 deletions(-)

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..aa82721
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "query-protocol"]
+	path = query-protocol
+	url = https://github.com/apache/incubator-skywalking-query-protocol.git
diff --git a/.roadhogrc.mock.js b/.roadhogrc.mock.js
index c2455e7..52159cb 100644
--- a/.roadhogrc.mock.js
+++ b/.roadhogrc.mock.js
@@ -1,17 +1,43 @@
 import mockjs from 'mockjs';
+import fs from 'fs';
 import { delay } from 'roadhog-api-doc';
-import { getDashboard } from './mock/dashboard';
 import { getTopology } from './mock/topology';
 import { getAllApplication, getApplication } from './mock/application';
 import { searchServer, getServer } from './mock/server';
 import { searchService, getService } from './mock/service';
-import { getAlarm, getNoticeAlarm } from './mock/alarm';
+import { getAlarm, getNoticeAlarm, AlarmTrend } from './mock/alarm';
 import { getAllApplication as getAllApplicationForTrace, getTrace, getSpans } from './mock/trace'
+import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
+import { graphql } from 'graphql';
+import { ClusterBrief } from './mock/metadata';
+import { Thermodynamic } from './mock/metric';
+import { getTopN } from './mock/aggregation';
 
 const noMock = process.env.NO_MOCK === 'true';
 
+const resolvers = {
+  Query: {
+    getTopN
+  }
+}
+
+const schema = makeExecutableSchema({ typeDefs: [
+  fs.readFileSync('query-protocol/common.graphqls', 'utf8'),
+  fs.readFileSync('query-protocol/metadata.graphqls', 'utf8'),
+  fs.readFileSync('query-protocol/alarm.graphqls', 'utf8'),
+  fs.readFileSync('query-protocol/metric.graphqls', 'utf8'),
+  fs.readFileSync('query-protocol/aggregation.graphqls', 'utf8'),
+], resolvers });
+
+addMockFunctionsToSchema({ schema, mocks: {
+  ClusterBrief, Thermodynamic, AlarmTrend
+}, preserveResolvers: true });
+
 const proxy = {
-  'POST /api/dashboard': getDashboard,
+  'POST /api/graphql': (req, res) => {
+    const { query: source, variables: variableValues } = req.body;
+    graphql({ schema, source, variableValues }).then((result) => res.send(result));
+  },
   'POST /api/topology': getTopology,
   'POST /api/application/options': getAllApplication,
   'POST /api/application': getApplication,
diff --git a/src/services/graphql.js b/mock/aggregation.js
similarity index 64%
copy from src/services/graphql.js
copy to mock/aggregation.js
index 1f081a2..e03c16a 100644
--- a/src/services/graphql.js
+++ b/mock/aggregation.js
@@ -15,12 +15,16 @@
  * limitations under the License.
  */
 
+import mockjs from 'mockjs';
 
-import request from '../utils/request';
-
-export async function query(namespace, playload) {
-  return request(`/api/${namespace}`, {
-    method: 'POST',
-    body: playload,
-  });
-}
+export default {
+  getTopN: (obj, args, context, info) => {
+    let array = null;
+    if (args.condition.filterScope === 'ENDPOINT') {
+      array = mockjs.mock({ 'array|10': [{ 'id|+1': 1, name: '@url', 'value|200-1000': 1 }]});
+    } else if (args.condition.filterScope === 'SERVICE') {
+      array = mockjs.mock({ 'array|10': [{ 'id|+1': 1, name: '@name', 'value|100-10000': 1 }]});
+    }
+    return array.array;
+  },
+};
diff --git a/mock/alarm.js b/mock/alarm.js
index 150b7cb..2358bfe 100644
--- a/mock/alarm.js
+++ b/mock/alarm.js
@@ -19,6 +19,9 @@
 import mockjs from 'mockjs';
 
 export default {
+  AlarmTrend: () => mockjs.mock({
+    'numOfAlarm|60': ['@natural(0, 9999)'],
+  }),
   getNoticeAlarm(req, res) {
     return res.json(mockjs.mock(
       {
diff --git a/mock/dashboard.js b/mock/dashboard.js
deleted file mode 100644
index 0d58909..0000000
--- a/mock/dashboard.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * 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.
- */
-
-
-import mockjs from 'mockjs';
-
-export default {
-  getDashboard(req, res) {
-    res.json(mockjs.mock(
-      {
-        data: {
-          getClusterBrief: {
-            'numOfApplication|1-100': 1,
-            'numOfService|1-100': 1,
-            'numOfDatabase|1-100': 1,
-            'numOfCache|1-100': 1,
-            'numOfMQ|1-100': 1,
-          },
-          getAlarmTrend: {
-            'numOfAlarmRate|60': ['@natural(0, 9999)'],
-          },
-          getConjecturalApps: {
-            'apps|3-5': [{ 'name|1': ['Oracle', 'MySQL', 'ActiveMQ', 'Redis', 'Memcache', 'SQLServer'], num: '@natural(1, 20)' }],
-          },
-          getThermodynamic: {
-            nodes: () => {
-              const result = [];
-              for (let i = 0; i < 61; i += 1) {
-                for (let j = 0; j < 41; j += 1) {
-                  result.push([i, j, mockjs.Random.natural(0, 999)]);
-                }
-              }
-              return result;
-            },
-            responseTimeStep: 50,
-          },
-          'getTopNSlowService|10': [{ service: { 'key|+1': 1, label: '@url', 'applicationId|+1': 1, applicationName: '@name' }, 'value|200-1000': 1 }],
-          'getTopNApplicationThroughput|10': [{ 'key|+1': 1, label: '@name', 'value|100-10000': 1 }],
-        },
-      }
-    ));
-  },
-};
diff --git a/mock/metadata.js b/mock/metadata.js
index a16b833..cd1630d 100644
--- a/mock/metadata.js
+++ b/mock/metadata.js
@@ -18,48 +18,12 @@
 import mockjs from 'mockjs';
 
 export default {
-  getDashboard(req, res) {
-    res.json(
-      mockjs.mock({
-        data: {
-          getGlobalBrief: {
-            'numOfService|1-100': 1,
-            'numOfEndpoint|1-100': 1,
-            'numOfDatabase|1-100': 1,
-            'numOfCache|1-100': 1,
-            'numOfMQ|1-100': 1,
-          },
-          getAlarmTrend: {
-            'numOfAlarm|60': ['@natural(0, 9999)'],
-          },
-          getThermodynamic: {
-            nodes: () => {
-              const result = [];
-              for (let i = 0; i < 61; i += 1) {
-                for (let j = 0; j < 41; j += 1) {
-                  result.push([i, j, mockjs.Random.natural(0, 999)]);
-                }
-              }
-              return result;
-            },
-            axisYStep: 50,
-          },
-          'getTopNSlowService|10': [
-            {
-              service: {
-                'key|+1': 1,
-                label: '@url',
-                'applicationId|+1': 1,
-                applicationName: '@name',
-              },
-              'value|200-1000': 1,
-            },
-          ],
-          'getTopNApplicationThroughput|10': [
-            { 'key|+1': 1, label: '@name', 'value|100-10000': 1 },
-          ],
-        },
-      })
-    );
-  },
+  ClusterBrief: () => mockjs.mock({
+      'numOfService|1-100': 1,
+      'numOfEndpoint|1-100': 1,
+      'numOfDatabase|1-100': 1,
+      'numOfCache|1-100': 1,
+      'numOfMQ|1-100': 1,
+    })
+  ,
 };
diff --git a/src/services/graphql.js b/mock/metric.js
similarity index 70%
copy from src/services/graphql.js
copy to mock/metric.js
index 1f081a2..673cc63 100644
--- a/src/services/graphql.js
+++ b/mock/metric.js
@@ -15,12 +15,19 @@
  * limitations under the License.
  */
 
+import mockjs from 'mockjs';
 
-import request from '../utils/request';
-
-export async function query(namespace, playload) {
-  return request(`/api/${namespace}`, {
-    method: 'POST',
-    body: playload,
-  });
-}
+export default {
+  Thermodynamic: () => ({
+    nodes: () => {
+      const result = [];
+      for (let i = 0; i < 61; i += 1) {
+        for (let j = 0; j < 41; j += 1) {
+          result.push([i, j, mockjs.Random.natural(0, 999)]);
+        }
+      }
+      return result;
+    },
+    responseTimeStep: 50,
+  }),
+};
diff --git a/package-lock.json b/package-lock.json
index 381f6a4..3219e08 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3325,6 +3325,11 @@
         "any-observable": "^0.3.0"
       }
     },
+    "@types/graphql": {
+      "version": "0.12.6",
+      "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-0.12.6.tgz",
+      "integrity": "sha512-wXAVyLfkG1UMkKOdMijVWFky39+OD/41KftzqfX1Oejd0Gm6dOIKjCihSVECg6X7PHjftxXmfOKA/d1H79ZfvQ=="
+    },
     "@types/history": {
       "version": "4.7.0",
       "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.0.tgz",
@@ -4098,6 +4103,24 @@
         }
       }
     },
+    "apollo-link": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.2.tgz",
+      "integrity": "sha512-Uk/BC09dm61DZRDSu52nGq0nFhq7mcBPTjy5EEH1eunJndtCaNXQhQz/BjkI2NdrfGI+B+i5he6YSoRBhYizdw==",
+      "requires": {
+        "@types/graphql": "0.12.6",
+        "apollo-utilities": "^1.0.0",
+        "zen-observable-ts": "^0.8.9"
+      }
+    },
+    "apollo-utilities": {
+      "version": "1.0.20",
+      "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.20.tgz",
+      "integrity": "sha512-2M4BJCyX/9UXGJFoV4sTnVTZ4Q29aM18Z1avDrwvlCGGwoRTz50sGBAfTiWnUnnNQyPIIJEYElScw46DgIu0Rg==",
+      "requires": {
+        "fast-json-stable-stringify": "^2.0.0"
+      }
+    },
     "append-transform": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz",
@@ -7443,6 +7466,11 @@
       "integrity": "sha512-b5dDNQYdy2vW9WXUD8+RQlfoxvqztLLhDE+T7Gd37I5E8My7nJkKu6FmhdDeRWJ8B+yjZKuwjCta8pgi8kgSqA==",
       "dev": true
     },
+    "deprecated-decorator": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz",
+      "integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc="
+    },
     "des.js": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
@@ -9058,8 +9086,7 @@
     "fast-json-stable-stringify": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
-      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
-      "dev": true
+      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
     },
     "fast-levenshtein": {
       "version": "2.0.6",
@@ -10426,6 +10453,33 @@
         "lodash": "^4.11.1"
       }
     },
+    "graphql": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.0.tgz",
+      "integrity": "sha512-HGVcnO6B25YZcSt6ZsH6/N+XkYuPA7yMqJmlJ4JWxWlS4Tr8SHI56R1Ocs8Eor7V7joEZPRXPDH8RRdll1w44Q==",
+      "requires": {
+        "iterall": "^1.2.2"
+      }
+    },
+    "graphql-tools": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-3.1.1.tgz",
+      "integrity": "sha512-yHvPkweUB0+Q/GWH5wIG60bpt8CTwBklCSzQdEHmRUgAdEQKxw+9B7zB3dG7wB3Ym7M7lfrS4Ej+jtDZfA2UXg==",
+      "requires": {
+        "apollo-link": "^1.2.2",
+        "apollo-utilities": "^1.0.1",
+        "deprecated-decorator": "^0.1.6",
+        "iterall": "^1.1.3",
+        "uuid": "^3.1.0"
+      },
+      "dependencies": {
+        "uuid": {
+          "version": "3.3.2",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+          "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+        }
+      }
+    },
     "growly": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
@@ -11986,6 +12040,11 @@
         "handlebars": "^4.0.11"
       }
     },
+    "iterall": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz",
+      "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA=="
+    },
     "jest-changed-files": {
       "version": "22.4.3",
       "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-22.4.3.tgz",
@@ -24100,6 +24159,19 @@
       "requires": {
         "fd-slicer": "~1.0.1"
       }
+    },
+    "zen-observable": {
+      "version": "0.8.9",
+      "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.9.tgz",
+      "integrity": "sha512-Y9kPzjGvIZ5jchSlqlCpBW3I82zBBL4z+ulXDRVA1NwsKzjt5kwAi+gOYIy0htNkfuehGZZtP5mRXHRV6TjDWw=="
+    },
+    "zen-observable-ts": {
+      "version": "0.8.9",
+      "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.9.tgz",
+      "integrity": "sha512-KJz2O8FxbAdAU5CSc8qZ1K2WYEJb1HxS6XDRF+hOJ1rOYcg6eTMmS9xYHCXzqZZzKw6BbXWyF4UpwSsBQnHJeA==",
+      "requires": {
+        "zen-observable": "^0.8.0"
+      }
     }
   }
 }
diff --git a/package.json b/package.json
index 7cd61eb..43a00f0 100755
--- a/package.json
+++ b/package.json
@@ -33,8 +33,8 @@
   "dependencies": {
     "@antv/data-set": "^0.8.0",
     "@babel/polyfill": "^7.0.0-beta.36",
-    "antd": "^3.8.0",
     "ant-design-pro": "^1.4.4",
+    "antd": "^3.8.0",
     "bizcharts": "^3.1.10",
     "bizcharts-plugin-slider": "^2.0.1",
     "classnames": "^2.2.5",
@@ -45,6 +45,8 @@
     "dva": "^2.2.3",
     "dva-loading": "^2.0.3",
     "enquire-js": "^0.2.1",
+    "graphql": "^14.0.0",
+    "graphql-tools": "^3.1.1",
     "lodash": "^4.17.10",
     "lodash-decorators": "^6.0.0",
     "moment": "^2.19.3",
diff --git a/query-protocol b/query-protocol
new file mode 160000
index 0000000..ebbeb72
--- /dev/null
+++ b/query-protocol
@@ -0,0 +1 @@
+Subproject commit ebbeb721d95bba8ee63bc2e706e877ee3a56433e
diff --git a/src/models/dashboard.js b/src/models/dashboard.js
index b5d14ee..01f2771 100644
--- a/src/models/dashboard.js
+++ b/src/models/dashboard.js
@@ -16,57 +16,65 @@
  */
 
 
-import { generateModal } from '../utils/models';
+import { base } from '../utils/models';
 
-export default generateModal({
+export default base({
   namespace: 'dashboard',
   state: {
-    getClusterBrief: {
-      numOfApplication: 0,
+    getGlobalBrief: {
       numOfService: 0,
+      numOfEndpoint: 0,
       numOfDatabase: 0,
       numOfCache: 0,
       numOfMQ: 0,
     },
     getAlarmTrend: {
-      numOfAlarmRate: [],
+      numOfAlarm: [],
     },
     getThermodynamic: {
       nodes: [],
       responseTimeStep: 0,
     },
-    getTopNSlowService: [],
-    getTopNApplicationThroughput: [],
+    getTopNSlowEndpoint: [],
+    getTopNServiceThroughput: [],
   },
   dataQuery: `
     query Dashboard($duration: Duration!) {
-      getClusterBrief(duration: $duration) {
-        numOfApplication
+      getGlobalBrief(duration: $duration) {
         numOfService
+        numOfEndpoint
         numOfDatabase
         numOfCache
         numOfMQ
       }
       getAlarmTrend(duration: $duration) {
-        numOfAlarmRate
+        numOfAlarm
       }
-      getThermodynamic(duration: $duration, type: ALL) {
+      getThermodynamic(duration: $duration, metric: {
+        name: "Endpoint_avg"
+      }) {
         nodes
-        responseTimeStep
+        responseTimeStep: axisYStep
       }
-      getTopNSlowService(duration: $duration, topN: 10) {
-        service {
-          key: id
-          label: name
-          applicationId
-          applicationName
-        }
-        value: avgResponseTime
+      getTopNSlowEndpoint: getTopN(duration: $duration, condition: {
+        name: "slowEndpoint",
+        topN: 10,
+        order: DES,
+        filterScope: ENDPOINT
+      }) {
+        key: id
+        label: name
+        value
       }
-      getTopNApplicationThroughput(duration: $duration, topN: 10) {
-        key: applicationId
-        label: applicationCode
-        value: cpm
+      getTopNServiceThroughput: getTopN(duration: $duration, condition: {
+        name: "serviceThroughput",
+        topN: 10,
+        order: DES,
+        filterScope: SERVICE
+      }) {
+        key: id
+        label: name
+        value
       }
     }
   `,
diff --git a/src/routes/Dashboard/Dashboard.js b/src/routes/Dashboard/Dashboard.js
index 1032431..57e3ee0 100644
--- a/src/routes/Dashboard/Dashboard.js
+++ b/src/routes/Dashboard/Dashboard.js
@@ -50,50 +50,50 @@ export default class Dashboard extends PureComponent {
 
   render() {
     const { data } = this.props.dashboard;
-    const { numOfAlarmRate } = data.getAlarmTrend;
+    const { numOfAlarm } = data.getAlarmTrend;
     const accuracy = 100;
     let visitData = [];
     let avg = 0;
     let max = 0;
     let min = 0;
-    if (numOfAlarmRate && numOfAlarmRate.length > 0) {
-      visitData = axis(this.props.duration, numOfAlarmRate, ({ x, y }) => ({ x, y: y / accuracy }));
-      avg = avgTimeSeries(numOfAlarmRate) / accuracy;
-      max = numOfAlarmRate.reduce((acc, curr) => { return acc < curr ? curr : acc; }) / accuracy;
-      min = numOfAlarmRate.reduce((acc, curr) => { return acc > curr ? curr : acc; }) / accuracy;
+    if (numOfAlarm && numOfAlarm.length > 0) {
+      visitData = axis(this.props.duration, numOfAlarm, ({ x, y }) => ({ x, y: y / accuracy }));
+      avg = avgTimeSeries(numOfAlarm) / accuracy;
+      max = numOfAlarm.reduce((acc, curr) => { return acc < curr ? curr : acc; }) / accuracy;
+      min = numOfAlarm.reduce((acc, curr) => { return acc > curr ? curr : acc; }) / accuracy;
     }
     return (
       <Panel globalVariables={this.props.globalVariables} onChange={this.handleDurationChange}>
         <Row gutter={8}>
           <Col xs={24} sm={24} md={12} lg={6} xl={6}>
             <ChartCard
-              title="App"
-              action={this.renderAction('Show application details', '/monitor/application')}
-              avatar={<img style={{ width: 56, height: 56 }} src="img/icon/app.png" alt="app" />}
-              total={data.getClusterBrief.numOfApplication}
+              title="Service"
+              action={this.renderAction('Show service details', '/monitor/service')}
+              avatar={<img style={{ width: 56, height: 56 }} src="img/icon/app.png" alt="service" />}
+              total={data.getGlobalBrief.numOfService}
             />
           </Col>
           <Col xs={24} sm={24} md={12} lg={6} xl={6}>
             <ChartCard
-              title="Service"
-              action={this.renderAction('Show service details', '/monitor/service')}
-              avatar={<img style={{ width: 56, height: 56 }} src="img/icon/service.png" alt="service" />}
-              total={data.getClusterBrief.numOfService}
+              title="Endpoint"
+              action={this.renderAction('Show endpoint details', '/monitor/endpoint')}
+              avatar={<img style={{ width: 56, height: 56 }} src="img/icon/service.png" alt="endpoint" />}
+              total={data.getGlobalBrief.numOfEndpoint}
             />
           </Col>
           <Col xs={24} sm={24} md={12} lg={6} xl={6}>
             <ChartCard
               title="DB & Cache"
               avatar={<img style={{ width: 56, height: 56 }} src="img/icon/database.png" alt="database" />}
-              total={data.getClusterBrief.numOfDatabase
-                + data.getClusterBrief.numOfCache}
+              total={data.getGlobalBrief.numOfDatabase
+                + data.getGlobalBrief.numOfCache}
             />
           </Col>
           <Col xs={24} sm={24} md={12} lg={6} xl={6}>
             <ChartCard
               title="MQ"
               avatar={<img style={{ width: 56, height: 56 }} src="img/icon/mq.png" alt="mq" />}
-              total={data.getClusterBrief.numOfMQ}
+              total={data.getGlobalBrief.numOfMQ}
             />
           </Col>
         </Row>
@@ -147,31 +147,28 @@ export default class Dashboard extends PureComponent {
         <Row gutter={8}>
           <Col xs={24} sm={24} md={24} lg={16} xl={16} style={{ marginTop: 8 }}>
             <Card
-              title="Slow Service"
+              title="Slow Endpoint"
               bordered={false}
               bodyStyle={{ padding: '0px 10px' }}
             >
               <RankList
-                data={data.getTopNSlowService.map(_ => ({ ..._.service, value: _.value }))}
+                data={data.getTopNSlowEndpoint}
                 renderValue={_ => `${_.value} ms`}
-                onClick={(key, item) => redirect(this.props.history, '/monitor/service', { key,
-                    label: item.label,
-                    applicationId: item.applicationId,
-                    applicationName: item.applicationName })}
+                onClick={(key, item) => redirect(this.props.history, '/monitor/endpoint', { key })}
               />
             </Card>
           </Col>
           <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 8 }}>
             <Card
-              title="Application Throughput"
+              title="Service Throughput"
               bordered={false}
               bodyStyle={{ padding: '0px 10px' }}
             >
               <RankList
-                data={data.getTopNApplicationThroughput}
+                data={data.getTopNServiceThroughput}
                 renderValue={_ => `${_.value} cpm`}
                 color="#965fe466"
-                onClick={(key, item) => redirect(this.props.history, '/monitor/application', { key, label: item.label })}
+                onClick={(key, item) => redirect(this.props.history, '/monitor/service', { key })}
               />
             </Card>
           </Col>
diff --git a/src/services/graphql.js b/src/services/graphql.js
index 1f081a2..663b296 100644
--- a/src/services/graphql.js
+++ b/src/services/graphql.js
@@ -24,3 +24,10 @@ export async function query(namespace, playload) {
     body: playload,
   });
 }
+
+export async function exec(playload) {
+  return request(`/api/graphql`, {
+    method: 'POST',
+    body: playload,
+  });
+}
diff --git a/src/utils/models.js b/src/utils/models.js
index 16a32c6..e88a90b 100644
--- a/src/utils/models.js
+++ b/src/utils/models.js
@@ -16,7 +16,7 @@
  */
 
 
-import { query as queryService } from '../services/graphql';
+import { query as queryService, exec } from '../services/graphql';
 
 export function saveOptionsInState(defaultOption, preState, { payload: allOptions }) {
   if (!allOptions) {
@@ -202,3 +202,133 @@ export function generateModal({ namespace, dataQuery, optionsQuery, defaultOptio
     },
   };
 }
+
+export function base({ namespace, dataQuery, optionsQuery, defaultOption, state = {},
+  varState = {}, effects = {}, reducers = {}, subscriptions = {} }) {
+  return {
+    namespace,
+    state: {
+      variables: {
+        values: {},
+        labels: {},
+        options: {},
+        ...varState,
+      },
+      data: state,
+    },
+    effects: {
+      *initOptions({ payload }, { call, put }) {
+        const { variables, reducer = undefined } = payload;
+        const response = yield call(exec, { variables, query: optionsQuery });
+        if (reducer) {
+          yield put({
+            type: reducer,
+            payload: response.data,
+          });
+        } else {
+          yield put({
+            type: 'saveOptions',
+            payload: response.data,
+          });
+        }
+      },
+      *fetchData({ payload }, { call, put }) {
+        const { variables, reducer = undefined } = payload;
+        const response = yield call(exec, { variables, query: dataQuery });
+        if (!response.data) {
+          return;
+        }
+        if (reducer) {
+          yield put({
+            type: reducer,
+            payload: response.data,
+          });
+        } else {
+          yield put({
+            type: 'saveData',
+            payload: response.data,
+          });
+        }
+      },
+      ...effects,
+    },
+    reducers: {
+      saveOptions(preState, action) {
+        const raw = saveOptionsInState(defaultOption, preState, action);
+        return raw;
+      },
+      save(preState, { payload: { variables: { values = {}, options = {}, labels = {} },
+        data = {} } }) {
+        const { variables: { values: preValues, options: preOptions, labels: preLabels },
+          data: preData } = preState;
+        return {
+          variables: {
+            values: {
+              ...preValues,
+              ...values,
+            },
+            options: {
+              ...preOptions,
+              ...options,
+            },
+            labels: {
+              ...preLabels,
+              ...labels,
+            },
+          },
+          data: {
+            ...preData,
+            ...data,
+          },
+        };
+      },
+      saveData(preState, { payload }) {
+        const { data } = preState;
+        return {
+          ...preState,
+          data: {
+            ...data,
+            ...payload,
+          },
+        };
+      },
+      saveVariables(preState, { payload: { values: variableValues, labels = {} } }) {
+        const { variables: preVariables } = preState;
+        const { values: preValues, lables: preLabels } = preVariables;
+        return {
+          ...preState,
+          variables: {
+            ...preVariables,
+            values: {
+              ...preValues,
+              ...variableValues,
+            },
+            labels: {
+              ...preLabels,
+              ...labels,
+            },
+          },
+        };
+      },
+      initVariables(preState, { payload: { values: variableValues, labels = {} } }) {
+        const { variables: preVariables } = preState;
+        return {
+          ...preState,
+          variables: {
+            ...preVariables,
+            values: {
+              ...variableValues,
+            },
+            labels: {
+              ...labels,
+            },
+          },
+        };
+      },
+      ...reducers,
+    },
+    subscriptions: {
+      ...subscriptions,
+    },
+  };
+}