Support brokers and clusters page (#115)



* ### Motivation
Add related pages about namespace
Namespace contains a large number of policies,  In order to configure these policies, a page is added to update them.

### Modifications
* On the namespaceInfo page, include three tab => overview, topics and policies
* On the overview page, statistics can be seen and bundle can be configured.
* On the policies page, contains configuration of policies
* On the topics page, Including statistical information on all topics under this namespace

### Verifying this change
Add Unit Test For Backend

diff --git a/front-end/src/api/brokerStats.js b/front-end/src/api/brokerStats.js
index eb66f1d..1913b1e 100644
--- a/front-end/src/api/brokerStats.js
+++ b/front-end/src/api/brokerStats.js
@@ -13,11 +13,18 @@
  */
 import request from '@/utils/request'
 
-const BASE_URL_V2 = '/admin/v2'
+const SPRING_BASE_URL_V2 = '/pulsar-manager/admin/v2'
 
-export function fetchBrokerStats() {
+export function fetchBrokerStatsTopics(broker) {
   return request({
-    url: BASE_URL_V2 + `/broker-stats/topics`,
+    url: SPRING_BASE_URL_V2 + `/broker-stats/topics?broker=` + broker,
+    method: 'get'
+  })
+}
+
+export function fetchBrokerStatsMetrics(broker) {
+  return request({
+    url: SPRING_BASE_URL_V2 + `/broker-stats/metrics?broker=` + broker,
     method: 'get'
   })
 }
diff --git a/front-end/src/api/brokers.js b/front-end/src/api/brokers.js
index 75a6027..ddc26f1 100644
--- a/front-end/src/api/brokers.js
+++ b/front-end/src/api/brokers.js
@@ -15,8 +15,17 @@
 
 const BASE_URL_V2 = '/admin/v2'
 
+const SPRING_BASE_URL_V2 = '/pulsar-manager/admin/v2'
+
 export function fetchBrokers(cluster) {
   return request({
+    url: SPRING_BASE_URL_V2 + `/brokers/${cluster}`,
+    method: 'get'
+  })
+}
+
+export function fetchBrokersByDirectBroker(cluster) {
+  return request({
     url: BASE_URL_V2 + `/brokers/${cluster}`,
     method: 'get'
   })
diff --git a/front-end/src/api/clusters.js b/front-end/src/api/clusters.js
index b09db5a..ac6f59e 100644
--- a/front-end/src/api/clusters.js
+++ b/front-end/src/api/clusters.js
@@ -15,9 +15,11 @@
 
 const BASE_URL_V2 = '/admin/v2'
 
+const SPRING_BASE_URL_V2 = '/pulsar-manager/admin/v2'
+
 export function fetchClusters(query) {
   return request({
-    url: BASE_URL_V2 + '/clusters',
+    url: SPRING_BASE_URL_V2 + '/clusters',
     method: 'get',
     params: { query }
   })
diff --git a/front-end/src/api/isolationPolicies.js b/front-end/src/api/isolationPolicies.js
new file mode 100644
index 0000000..ca40afd
--- /dev/null
+++ b/front-end/src/api/isolationPolicies.js
@@ -0,0 +1,40 @@
+/*
+ * Licensed 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 request from '@/utils/request'
+
+const BASE_URL_V2 = '/admin/v2'
+
+export function fetchIsolationPolicies(cluster) {
+  return request({
+    url: BASE_URL_V2 + `/clusters/${cluster}/namespaceIsolationPolicies`,
+    method: 'get'
+  })
+}
+
+export function updateIsolationPolicies(cluster, policyName, data) {
+  return request({
+    headers: { 'Content-Type': 'application/json' },
+    url: BASE_URL_V2 + `/clusters/${cluster}/namespaceIsolationPolicies/${policyName}`,
+    method: 'post',
+    data
+  })
+}
+
+export function deleteIsolationPolicies(cluster, policyName) {
+  return request({
+    headers: { 'Content-Type': 'application/json' },
+    url: BASE_URL_V2 + `/clusters/${cluster}/namespaceIsolationPolicies/${policyName}`,
+    method: 'delete'
+  })
+}
diff --git a/front-end/src/router/index.js b/front-end/src/router/index.js
index be0a504..c109a80 100644
--- a/front-end/src/router/index.js
+++ b/front-end/src/router/index.js
@@ -109,6 +109,41 @@
         meta: { title: 'Clusters', noCache: true }
       },
       {
+        path: 'clusters/:cluster/cluster',
+        component: () => import('@/views/management/clusters/cluster'),
+        name: 'ClusterInfo',
+        meta: { title: 'ClusterInfo', noCache: true },
+        hidden: true
+      },
+      {
+        path: 'clusters/:cluster/:failureDomainName/failureDomainName',
+        component: () => import('@/views/management/clusters/failureDomain'),
+        name: 'FailureDomainInfo',
+        meta: { title: 'FailureDomainInfo', noCache: true },
+        hidden: true
+      },
+      {
+        path: 'clusters/:cluster/:namespaceIsolation/namespaceIsolationPolicy',
+        component: () => import('@/views/management/namespaceIsolations/namespaceIsolationPolicy'),
+        name: 'NamespaceIsolationPolicy',
+        meta: { title: 'NamespaceIsolationPolicy', noCache: true },
+        hidden: true
+      },
+      {
+        path: 'brokers',
+        component: () => import('@/views/management/brokers'),
+        name: 'Brokers',
+        meta: { title: 'Brokers', noCache: true },
+        hidden: true
+      },
+      {
+        path: 'brokers/:cluster/:broker/broker',
+        component: () => import('@/views/management/brokers/broker'),
+        name: 'BrokerInfo',
+        meta: { title: 'BrokerInfo', noCache: true },
+        hidden: true
+      },
+      {
         path: 'tenants',
         component: () => import('@/views/management/tenants/index'),
         name: 'Tenants',
@@ -176,12 +211,6 @@
         hidden: true
       },
       {
-        path: 'brokers',
-        component: () => import('@/views/management/brokers'),
-        name: 'Brokers',
-        meta: { title: 'Brokers', noCache: true }
-      },
-      {
         path: 'functions',
         component: () => import('@/views/management/functions'),
         name: 'Functions',
diff --git a/front-end/src/views/management/brokers/broker.vue b/front-end/src/views/management/brokers/broker.vue
new file mode 100644
index 0000000..e9340ce
--- /dev/null
+++ b/front-end/src/views/management/brokers/broker.vue
@@ -0,0 +1,244 @@
+<template>
+  <div class="app-container">
+    <div class="createPost-container">
+      <el-form :inline="true" :model="postForm" class="form-container">
+        <el-form-item class="postInfo-container-item" label="Cluster">
+          <el-select v-model="postForm.cluster" placeholder="select cluster" @change="getClusterList(postForm.cluster)">
+            <el-option v-for="(item,index) in clustersListOptions" :key="item+index" :label="item" :value="item"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item class="postInfo-container-item" label="Broker">
+          <el-select v-model="postForm.broker" placeholder="select broker" @change="getBrokersList(postForm.broker)">
+            <el-option v-for="(item,index) in brokersListOptions" :key="item+index" :label="item" :value="item"/>
+          </el-select>
+        </el-form-item>
+        <el-button type="primary" @click="handleHeartBeat">Heartbeat</el-button>
+        <el-button type="primary" @click="handleRuntimeConfig">Runtime Config</el-button>
+      </el-form>
+      <el-table
+        :data="brokerStats"
+        border
+        style="width: 100%">
+        <el-table-column prop="inMsg" label="In - msg/s"/>
+        <el-table-column prop="outMsg" label="Out - msg/s"/>
+        <el-table-column prop="inBytes" label="In - bytes/s"/>
+        <el-table-column prop="outBytes" label="Out - bytes/s"/>
+      </el-table>
+      <h4>Owned Namespaces</h4>
+      <el-table
+        :key="bundleTableKey"
+        :data="bundleList"
+        border
+        fit
+        highlight-current-row
+        style="width: 100%;">
+        <el-table-column label="Tenant" align="center" min-width="100px">
+          <template slot-scope="scope">
+            <span>{{ scope.row.tenant }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="Namespace" align="center" min-width="100px">
+          <template slot-scope="scope">
+            <span>{{ scope.row.namespace }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="Bundle" align="center" min-width="100px">
+          <template slot-scope="scope">
+            <span>{{ scope.row.bundle }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="Operations" align="center" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button size="medium" type="danger" @click="handleUnloadBundle(scope.row)">Unload</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <h4>Namespace Isolation Policies</h4>
+      <el-table
+        :data="isolationPolicyList"
+        border
+        fit
+        highlight-current-row
+        style="width: 100%;">
+        <el-table-column label="Isolation Policy" align="center" min-width="100px">
+          <template slot-scope="scope">
+            <span>{{ scope.row.isolationPolicy }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="Number of Primary Brokers" align="center" min-width="100px">
+          <template slot-scope="scope">
+            <span>{{ scope.row.primaryBrokers }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="Number of Secondary Brokers" align="center" min-width="100px">
+          <template slot-scope="scope">
+            <span>{{ scope.row.secondaryBrokers }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-dialog :visible.sync="dialogFormVisible">
+        <jsonEditor :value="jsonValue"/>
+      </el-dialog>
+    </div>
+  </div>
+</template>
+
+<script>
+import { fetchClusters } from '@/api/clusters'
+import { fetchBrokerStatsMetrics, fetchBrokerStatsTopics } from '@/api/brokerStats'
+import { fetchBrokersHealth } from '@/api/brokers'
+import { fetchIsolationPolicies } from '@/api/isolationPolicies'
+import { unloadBundle } from '@/api/namespaces'
+import { fetchBrokersRuntimeConfiguration } from '@/api/brokers'
+import jsonEditor from '@/components/JsonEditor'
+import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
+import MdInput from '@/components/MDinput'
+const defaultForm = {
+  cluster: '',
+  broker: ''
+}
+export default {
+  name: 'BrokerInfo',
+  components: {
+    Pagination,
+    MdInput,
+    jsonEditor
+  },
+  data() {
+    return {
+      postForm: Object.assign({}, defaultForm),
+      clustersListOptions: [],
+      brokersListOptions: [],
+      brokerStats: [],
+      bundleTableKey: 0,
+      bundleList: [],
+      isolationPolicyList: [],
+      isolationPolicyTableKey: 0,
+      dialogFormVisible: false,
+      jsonValue: {}
+    }
+  },
+  created() {
+    this.postForm.cluster = this.$route.params && this.$route.params.cluster
+    this.postForm.broker = this.$route.params && this.$route.params.broker
+    this.getBrokerInfo()
+    this.getBrokerStats()
+    this.getIsolationPolicy()
+  },
+  methods: {
+    getBrokerInfo() {
+      fetchBrokerStatsTopics(this.postForm.broker).then(response => {
+        if (!response.data) return
+        this.brokerStatsTopic = response.data
+        for (var tenantNamespace in this.brokerStatsTopic) {
+          var tn = tenantNamespace.split('/')
+          for (var bundle in this.brokerStatsTopic[tenantNamespace]) {
+            var ownedNamespace = {}
+            ownedNamespace['tenant'] = tn[0]
+            ownedNamespace['namespace'] = tn[1]
+            ownedNamespace['bundle'] = bundle
+            this.bundleList.push(ownedNamespace)
+          }
+        }
+      })
+    },
+    getBrokerStats() {
+      var throughputIn = 0
+      var throughputOut = 0
+      var bandwidthIn = 0
+      var bandwidthOut = 0
+      fetchBrokerStatsMetrics(this.postForm.broker).then(response => {
+        for (var m = 0; m < response.data.length; m++) {
+          if (response.data[m].dimensions.hasOwnProperty('namespace')) {
+            if (response.data[m].dimensions.namespace.split('/').length === 2) {
+              throughputIn += response.data[m].metrics.brk_in_tp_rate
+              throughputOut += response.data[m].metrics.brk_out_tp_rate
+              bandwidthIn += response.data[m].metrics.brk_in_rate
+              bandwidthOut += response.data[m].metrics.brk_out_rate
+            }
+          }
+        }
+        this.brokerStats.push({
+          'inBytes': throughputIn,
+          'outBytes': throughputOut,
+          'inMsg': bandwidthIn,
+          'outMsg': bandwidthOut
+        })
+      })
+    },
+    getClusterList() {
+      fetchClusters(this.listQuery).then(response => {})
+    },
+    getIsolationPolicy() {
+      fetchIsolationPolicies(this.postForm.cluster).then(res => {
+        var tempIsolationPolicy = []
+        for (var policy in res.data) {
+          for (var i in res.data[policy].primary) {
+            var regexPrimary = new RegExp(res.data[policy].primary[i])
+            if (regexPrimary.test(this.postFormbroker)) {
+              if (tempIsolationPolicy.indexOf(policy) < 0) {
+                tempIsolationPolicy.push(policy)
+              }
+            }
+          }
+          for (var j in res.data[policy].secondary) {
+            var regexSecondary = new RegExp(res.data[policy].secondary[j])
+            if (regexSecondary.test(this.postFormbroker)) {
+              if (tempIsolationPolicy.indexOf(policy) < 0) {
+                tempIsolationPolicy.push(policy)
+              }
+            }
+          }
+          if (tempIsolationPolicy.indexOf(policy) >= 0) {
+            this.isolationPolicyList.push({
+              'isolationPolicy': policy,
+              'primaryBrokers': res.data[policy].primary.length,
+              'secondaryBrokers': res.data[policy].secondary.length
+            })
+          }
+        }
+      })
+    },
+    getBrokersList(cluster) {
+    },
+    handleUnloadBundle(row) {
+      unloadBundle(row.tenant + '/' + row.namespace, row.bundle).then(response => {
+        this.$notify({
+          title: 'success',
+          message: 'Unload bundle success',
+          type: 'success',
+          duration: 3000
+        })
+      })
+    },
+    handleHeartBeat() {
+      fetchBrokersHealth().then(response => {
+        if (response.data === 'ok') {
+          this.$notify({
+            title: 'success',
+            message: 'Health Check success',
+            type: 'success',
+            duration: 3000
+          })
+        } else {
+          this.$notify({
+            title: 'error',
+            message: 'Health Check failed',
+            type: 'error',
+            duration: 3000
+          })
+        }
+      })
+    },
+    handleRuntimeConfig() {
+      fetchBrokersRuntimeConfiguration().then(response => {
+        this.dialogFormVisible = true
+        this.jsonValue = response.data
+      })
+    }
+  }
+}
+</script>
+
+<style>
+</style>
diff --git a/front-end/src/views/management/clusters/cluster.vue b/front-end/src/views/management/clusters/cluster.vue
new file mode 100644
index 0000000..943e661
--- /dev/null
+++ b/front-end/src/views/management/clusters/cluster.vue
@@ -0,0 +1,451 @@
+<template>
+  <div class="app-container">
+    <div class="createPost-container">
+      <el-form :inline="true" :model="postForm" class="form-container">
+        <el-form-item class="postInfo-container-item" label="Cluster">
+          <el-select v-model="postForm.cluster" placeholder="select cluster" @change="getClusterInfo()">
+            <el-option v-for="(item,index) in clustersListOptions" :key="item+index" :label="item" :value="item"/>
+          </el-select>
+        </el-form-item>
+      </el-form>
+    </div>
+    <el-tabs v-model="activeName" @tab-click="handleClick">
+      <el-tab-pane label="BROKERS" name="brokers">
+        <el-row :gutter="24">
+          <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 24}" :xl="{span: 24}" style="padding-right:8px;margin-bottom:30px;">
+            <el-table
+              :key="brokerTableKey"
+              :data="brokersList"
+              border
+              fit
+              highlight-current-row
+              style="width: 100%;">
+              <el-table-column label="Brokers" min-width="50px" align="center">
+                <template slot-scope="scope">
+                  <router-link :to="'/management/brokers/' + scope.row.cluster + '/' + scope.row.broker + '/broker'" class="link-type">
+                    <span>{{ scope.row.broker }}</span>
+                  </router-link>
+                </template>
+              </el-table-column>
+              <el-table-column label="Failure Domains" align="center" min-width="100px">
+                <template slot-scope="scope">
+                  <span>{{ scope.row.failureDomain }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="Owned Namespaces" align="center" min-width="100px">
+                <template slot-scope="scope">
+                  <span>{{ scope.row.ownedNamespaces }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="Throughput" align="center" min-width="100px">
+                <template slot-scope="scope">
+                  <span>In:{{ scope.row.throughputIn }}<br>Out:{{ scope.row.throughputOut }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="Bandwidth" align="center" min-width="100px">
+                <template slot-scope="scope">
+                  <span>In: {{ scope.row.bandwidthIn }}<br>Out: {{ scope.row.bandwidthOut }}</span>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-col>
+        </el-row>
+      </el-tab-pane>
+      <el-tab-pane label="ISOLATION-POLICIES" name="isolationPolicies">
+        <el-input v-model="isolationPolicyKey" placeholder="policy" style="width: 200px;" @keyup.enter.native="handlePolicyFilter"/>
+        <el-button type="primary" icon="el-icon-search" @click="handleFilter">Search</el-button>
+        <el-button type="primary" icon="el-icon-edit" @click="handleCreatePolicy">New Policy</el-button>
+        <el-row :gutter="24">
+          <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 24}" :xl="{span: 24}" style="padding-right:8px;margin-top:15px;">
+            <el-table
+              :key="isolationTableKey"
+              :data="isolationPoliciesList"
+              border
+              fit
+              highlight-current-row
+              style="width: 100%;">
+              <el-table-column label="Isolation Policy" min-width="50px" align="center">
+                <template slot-scope="scope">
+                  <router-link :to="'/management/clusters/' + scope.row.cluster + '/' + scope.row.isolationPolicy + '/namespaceIsolationPolicy'" class="link-type">
+                    <span>{{ scope.row.isolationPolicy }}</span>
+                  </router-link>
+                </template>
+              </el-table-column>
+              <el-table-column label="Number of Primary Brokers" align="center" min-width="100px">
+                <template slot-scope="scope">
+                  <span>{{ scope.row.numberOfPrimaryBrokers }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="Number of Secondary Brokers" align="center" min-width="100px">
+                <template slot-scope="scope">
+                  <span>{{ scope.row.numberOfSecondaryBrokers }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="Delete Policy" align="center" class-name="small-padding fixed-width">
+                <template slot-scope="scope">
+                  <el-button class="el-button el-button--primary el-button--medium" type="danger" @click="handleDeletePolicy(scope.row)">Delete
+                  </el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-col>
+        </el-row>
+      </el-tab-pane>
+      <el-tab-pane label="FAILURE-DOMAINS" name="failureDomains">
+        <el-input v-model="failureDomainsKey" placeholder="Failure Domains" style="width: 200px;" @keyup.enter.native="handleFailureDomainFilter"/>
+        <el-button type="primary" icon="el-icon-search" @click="handleFailureDomainFilter">Search</el-button>
+        <el-button type="primary" icon="el-icon-edit" @click="newFailureDomain">New FailureDomain</el-button>
+        <el-table
+          :data="faileDomainList"
+          border
+          style="width: 100%;margin-top:15px">
+          <el-table-column prop="domain" label="Domain">
+            <template slot-scope="scope">
+              <router-link :to="'/management/clusters/' + scope.row.cluster + '/' + scope.row.domain + '/failureDomainName'" class="link-type">
+                <span>{{ scope.row.domain }}</span>
+              </router-link>
+            </template>
+          </el-table-column>
+          <el-table-column prop="brokers" label="Brokers">
+            <template slot-scope="scope">
+              <span>{{ scope.row.brokers }}</span>
+            </template>
+          </el-table-column>
+          <!-- <el-table-column label="Delete FailureDomain" align="center" class-name="small-padding fixed-width">
+            <template slot-scope="scope">
+              <el-button class="el-button el-button--primary el-button--medium" type="danger" @click="handleDeleteFailureDomain(scope.row)">Delete</el-button>
+            </template>
+          </el-table-column> -->
+        </el-table>
+      </el-tab-pane>
+      <el-tab-pane label="CONFIG" name="config">
+        <el-form :inline="true" :model="form" :rules="rules">
+          <el-form-item prop="httpServiceUrl">
+            <span>Http Service Url</span>
+            <md-input
+              v-model="form.httpServiceUrl"
+              class="md-input-style"
+              name="serviceUrl"
+              placeholder="Please input httpServiceUrl"
+              @keyup.enter.native="handleServiceUrl">
+              http://
+            </md-input>
+          </el-form-item>
+          <el-form-item prop="httpsServiceUrl">
+            <span>Https Service Url</span>
+            <md-input
+              v-model="form.httpsServiceUrl"
+              class="md-input-style"
+              name="httpsServiceUrl"
+              placeholder="Please input httpsServiceUrl"
+              @keyup.enter.native="handleServiceUrl">
+              https://
+            </md-input>
+          </el-form-item>
+          <el-form-item prop="brokerServiceUrl">
+            <span>Broker Service Url</span>
+            <md-input
+              v-model="form.brokerServiceUrl"
+              class="md-input-style"
+              name="brokerServiceUrl"
+              placeholder="Please input brokerServiceUrl"
+              @keyup.enter.native="handleServiceUrl">
+              pulsar://
+            </md-input>
+          </el-form-item>
+          <el-form-item prop="brokersServiceUrl">
+            <span>Http Secure Service Url</span>
+            <md-input
+              v-model="form.brokersServiceUrl"
+              class="md-input-style"
+              name="brokersServiceUrl"
+              placeholder="Please input brokersServiceUrl"
+              @keyup.enter.native="handleServiceUrl">
+              pulsar+ssl://
+            </md-input>
+          </el-form-item>
+        </el-form>
+        <h4>Danager Zone</h4>
+        <hr class="danger-line">
+        <el-button type="danger" class="button" @click="handleDelete">Delete Cluster</el-button>
+      </el-tab-pane>
+    </el-tabs>
+    <el-dialog :visible.sync="dialogFormVisible" title="Create Failure Domain Name">
+      <el-form ref="temp" :rules="rules" :model="temp" label-position="left" label-width="150px" style="width: 400px; margin-left:50px;">
+        <div v-if="dialogStatus==='create'">
+          <div v-if="dialogStatus==='create'">
+            <el-form-item label="domainName" prop="domainName">
+              <el-input v-model="temp.domainName"/>
+            </el-form-item>
+            <el-form-item label="brokerList" prop="brokerList">
+              <el-select
+                v-model="temp.brokerValue"
+                style="width:254px;"
+                multiple
+                placeholder="Please Select Brokers">
+                <el-option v-for="item in brokerOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </div>
+        </div>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">{{ $t('table.cancel') }}</el-button>
+        <el-button type="primary" @click="handleCreateFailureDomain()">{{ $t('table.confirm') }}</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  fetchClusters,
+  updateCluster,
+  listClusterDomainName,
+  deleteCluster,
+  createClusterDomainName
+} from '@/api/clusters'
+import { fetchBrokerStatsMetrics } from '@/api/brokerStats'
+import { fetchBrokers, fetchBrokersByDirectBroker } from '@/api/brokers'
+import { fetchIsolationPolicies } from '@/api/isolationPolicies'
+import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
+import MdInput from '@/components/MDinput'
+const defaultForm = {
+  cluster: ''
+}
+export default {
+  name: 'ClusterInfo',
+  components: {
+    Pagination,
+    MdInput
+  },
+  data() {
+    return {
+      postForm: Object.assign({}, defaultForm),
+      localList: [],
+      listQuery: {
+        cluster: '',
+        page: 1,
+        limit: 10
+      },
+      activeName: 'brokers',
+      clustersListOptions: [],
+      form: {
+        serviceUrl: '',
+        httpsServiceUrl: '',
+        brokerServiceUrl: '',
+        brokersServiceUrl: ''
+      },
+      brokerOptions: [],
+      temp: {
+        domainName: '',
+        brokerValue: []
+      },
+      rules: {
+        domainName: [{ required: true, message: 'Please input Domain Name', trigger: 'change' }]
+      },
+      faileDomainList: [],
+      brokersList: [],
+      brokerTableKey: 0,
+      isolationTableKey: 0,
+      isolationPoliciesList: [],
+      isolationPolicyKey: '',
+      failureDomainsKey: '',
+      dialogFormVisible: false,
+      dialogStatus: ''
+    }
+  },
+  created() {
+    this.postForm.cluster = this.$route.params && this.$route.params.cluster
+    this.getClusterList()
+    this.getBrokerList()
+    if (this.$route.query && this.$route.query.tab) {
+      this.activeName = this.$route.query.tab
+      if (this.activeName === 'isolationPolicies') {
+        this.getNamespaceIsolationPolicy()
+      }
+    }
+    this.getFailureDomainsList()
+  },
+  methods: {
+    getClusterList() {
+      fetchClusters(this.listQuery).then(response => {
+        for (var i = 0; i < response.data.data.length; i++) {
+          this.clustersListOptions.push(response.data.data[i].cluster)
+          if (response.data.data[i].cluster === this.postForm.cluster) {
+            this.form.httpServiceUrl = response.data.data[i].serviceUrl
+            this.form.httpsServiceUrl = response.data.data[i].serviceUrlTls
+            this.form.brokerServiceUrl = response.data.data[i].brokerServiceUrl
+            this.form.brokersServiceUrl = response.data.data[i].brokerServiceUrlTls
+          }
+        }
+      })
+    },
+    getBrokerList() {
+      fetchBrokers(this.postForm.cluster).then(response => {
+        if (!response.data) return
+        var brokerInfo = {}
+        for (var i = 0; i < response.data.data.length; i++) {
+          var throughputIn = 0
+          var throughputOut = 0
+          var bandwidthIn = 0
+          var bandwidthOut = 0
+          var numberNamespaces = 0
+          brokerInfo['cluster'] = this.postForm.cluster
+          brokerInfo['broker'] = response.data.data[i].broker
+          brokerInfo['failureDomain'] = response.data.data[i].failureDomain.join(',')
+          fetchBrokerStatsMetrics(response.data.data[i].broker).then(res => {
+            for (var m = 0; m < res.data.length; m++) {
+              if (res.data[m].dimensions.hasOwnProperty('namespace')) {
+                if (res.data[m].dimensions.namespace.split('/').length === 2) {
+                  throughputIn += res.data[m].metrics.brk_in_tp_rate
+                  throughputOut += res.data[m].metrics.brk_out_tp_rate
+                  bandwidthIn += res.data[m].metrics.brk_in_rate
+                  bandwidthOut += res.data[m].metrics.brk_out_rate
+                  numberNamespaces += 1
+                }
+              }
+            }
+            brokerInfo['ownedNamespaces'] = numberNamespaces
+            brokerInfo['throughputIn'] = throughputIn
+            brokerInfo['throughputOut'] = throughputOut
+            brokerInfo['bandwidthOut'] = bandwidthOut
+            brokerInfo['bandwidthIn'] = bandwidthIn
+            this.brokersList.push(brokerInfo)
+          })
+        }
+      })
+    },
+    getNamespaceIsolationPolicy() {
+      fetchIsolationPolicies(this.postForm.cluster).then(response => {
+        if (!response.data) return
+        for (var key in response.data) {
+          this.isolationPoliciesList.push({
+            'cluster': this.postForm.cluster,
+            'isolationPolicy': key,
+            'numberOfPrimaryBrokers': response.data[key].primary.length,
+            'numberOfSecondaryBrokers': response.data[key].secondary.length
+          })
+        }
+      })
+    },
+    getClusterInfo() {
+      this.$router.push({ path: '/management/clusters/' + this.postForm.cluster + '/cluster?tab=config' })
+    },
+    handleClick(tab, event) {
+      if (tab.name === 'isolationPolicies') {
+        this.getNamespaceIsolationPolicy()
+      }
+    },
+    handleServiceUrl() {
+      var data = {
+        'serviceUrl': this.form.httpServiceUrl,
+        'serviceUrlTls': this.form.httpsServiceUrl,
+        'brokerServiceUrl': this.form.brokerServiceUrl,
+        'brokerServiceUrlTls': this.form.brokersServiceUrl
+      }
+      updateCluster(this.postForm.cluster, data).then(() => {
+        this.$notify({
+          title: 'success',
+          message: 'Update cluster success',
+          type: 'success',
+          duration: 2000
+        })
+      })
+    },
+    getFailureDomainsList() {
+      listClusterDomainName(this.postForm.cluster).then(response => {
+        if (!response.data) return
+        for (var key in response.data) {
+          this.faileDomainList.push({
+            'cluster': this.postForm.cluster,
+            'domain': key,
+            'brokers': response.data[key].brokers.length
+          })
+        }
+      })
+    },
+    getSelectBrokers() {
+      fetchBrokersByDirectBroker(this.postForm.cluster).then(response => {
+        for (var i = 0; i < response.data.length; i++) {
+          this.brokerOptions.push({
+            value: response.data[i],
+            label: response.data[i]
+          })
+        }
+      })
+    },
+    newFailureDomain() {
+      this.temp.domainName = ''
+      this.temp.brokerValue = []
+      this.brokerOptions = []
+      this.getSelectBrokers()
+      this.dialogFormVisible = true
+      this.dialogStatus = 'create'
+      this.$nextTick(() => {
+        this.$refs['temp'].clearValidate()
+      })
+    },
+    handleCreateFailureDomain() {
+      this.$refs['temp'].validate((valid) => {
+        if (valid) {
+          const data = {
+            'brokers': this.temp.brokerValue
+          }
+          createClusterDomainName(this.postForm.cluster, this.temp.domainName, data).then(response => {
+            this.$notify({
+              title: 'success',
+              message: 'Set Domain Name success',
+              type: 'success',
+              duration: 3000
+            })
+            this.getFailureDomainsList()
+            this.dialogFormVisible = false
+          })
+        }
+      })
+    },
+    handleCreatePolicy() {
+      this.$router.push({ path: '/management/clusters/' + this.postForm.cluster + '/create/namespaceIsolationPolicy?created=true' })
+    },
+    handleFilter() {
+    },
+    handleDelete() {
+      deleteCluster(this.postForm.cluster).then(response => {
+        this.$notify({
+          title: 'success',
+          message: 'delete success',
+          type: 'success',
+          duration: 2000
+        })
+        this.$router.push({ path: '/management/clusters' })
+      })
+    },
+    handleFailureDomainFilter() {
+    }
+    // handleDeletePolicy(row) {
+    //   deleteIsolationPolicies(this.postForm.cluster, row.isolationPolicy).then(response => {
+    //     this.$notify({
+    //       title: 'success',
+    //       message: 'Delete policy success',
+    //       type: 'success',
+    //       duration: 3000
+    //     })
+    //     this.$router.push({ path: '/management/clusters/' + this.postForm.cluster + '/cluster?tab=isolationPolicies' })
+    //   })
+    // }
+  }
+}
+</script>
+
+<style>
+.md-input-style {
+  width: 300px;
+  margin-top: 15px;
+}
+.danger-line {
+  background: red;
+  border: none;
+  height: 1px;
+}
+</style>
diff --git a/front-end/src/views/management/clusters/failureDomain.vue b/front-end/src/views/management/clusters/failureDomain.vue
new file mode 100644
index 0000000..a14251d
--- /dev/null
+++ b/front-end/src/views/management/clusters/failureDomain.vue
@@ -0,0 +1,158 @@
+<template>
+  <div class="app-container">
+    <div class="createPost-container">
+      <el-form :inline="true" :model="postForm" class="form-container">
+        <el-form-item class="postInfo-container-item" label="Cluster">
+          <el-select v-model="postForm.cluster" placeholder="select cluster" @change="getFailureDomainsList()">
+            <el-option v-for="(item,index) in clustersListOptions" :key="item+index" :label="item" :value="item"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item class="postInfo-container-item" label="Failure Domain">
+          <el-select v-model="postForm.failureDomainName" placeholder="select domain" @change="getFailureDomainInfo()">
+            <el-option v-for="(item,index) in failureDomainListOptions" :key="item+index" :label="item" :value="item"/>
+          </el-select>
+        </el-form-item>
+      </el-form>
+    </div>
+    <h3>FailureDomain</h3>
+    <h4>Brokers
+      <el-tooltip :content="brokersContent" class="item" effect="dark" placement="top">
+        <i class="el-icon-info"/>
+      </el-tooltip>
+    </h4>
+    <el-select
+      v-model="brokerValue"
+      style="width:500px;margin-top:20px"
+      multiple
+      placeholder="Please Select Brokers"
+      @change="handleSelectBrokers()">
+      <el-option v-for="item in brokerOptions" :key="item.value" :label="item.label" :value="item.value" />
+    </el-select>
+    <h4>Danager Zone</h4>
+    <hr class="danger-line">
+    <el-button type="danger" class="button" @click="handleDelete">Delete Failure Domain</el-button>
+  </div>
+</template>
+
+<script>
+// import MdInput from '@/components/MDinput'
+import {
+  fetchClusters,
+  getClusterDomainName,
+  updateClusterDomainName,
+  listClusterDomainName,
+  deleteClusterDomainName
+} from '@/api/clusters'
+import { fetchBrokersByDirectBroker } from '@/api/brokers'
+
+const defaultForm = {
+  cluster: '',
+  failureDomainName: ''
+}
+export default {
+  name: 'FailureDomainInfo',
+  // components: {
+  //   MdInput
+  // },
+  data() {
+    return {
+      postForm: Object.assign({}, defaultForm),
+      clustersListOptions: [],
+      brokersContent: 'This is BrokersContent',
+      brokerValue: [],
+      brokerOptions: [],
+      failureDomainListOptions: [],
+      firstInit: false
+    }
+  },
+  created() {
+    this.postForm.cluster = this.$route.params && this.$route.params.cluster
+    this.postForm.failureDomainName = this.$route.params && this.$route.params.failureDomainName
+    this.firstInit = true
+    this.getClusterList()
+    this.initBrokerValue()
+    this.initSelectBrokers()
+    this.getFailureDomainsList()
+  },
+  methods: {
+    initBrokerValue() {
+      getClusterDomainName(this.postForm.cluster, this.postForm.failureDomainName).then(response => {
+        if (!response.data) return
+        this.brokerValue = response.data.brokers
+      })
+    },
+    getClusterList() {
+      fetchClusters(this.listQuery).then(response => {
+        if (!response.data) return
+        for (var i = 0; i < response.data.data.length; i++) {
+          this.clustersListOptions.push(response.data.data[i].cluster)
+        }
+      })
+    },
+    getFailureDomainsList() {
+      listClusterDomainName(this.postForm.cluster).then(response => {
+        if (!response.data) return
+        if (this.firstInit) {
+          this.firstInit = false
+        } else {
+          this.postForm.failureDomainName = ''
+        }
+        this.failureDomainListOptions = []
+        for (var key in response.data) {
+          this.failureDomainListOptions.push(key)
+        }
+      })
+    },
+    initSelectBrokers() {
+      fetchBrokersByDirectBroker(this.postForm.cluster).then(response => {
+        for (var i = 0; i < response.data.length; i++) {
+          this.brokerOptions.push({
+            value: response.data[i],
+            label: response.data[i]
+          })
+        }
+      })
+    },
+    handleSelectBrokers() {
+      const data = {
+        'brokers': this.brokerValue
+      }
+      updateClusterDomainName(this.postForm.cluster, this.postForm.failureDomainName, data).then(response => {
+        this.$notify({
+          title: 'success',
+          message: 'Update brokers success',
+          type: 'success',
+          duration: 3000
+        })
+      })
+    },
+    getFailureDomainInfo() {
+      this.$router.push({ path: '/management/clusters/' + this.postForm.cluster + '/' + this.postForm.failureDomainName + '/failureDomainName' })
+    },
+    handleDelete() {
+      deleteClusterDomainName(this.postForm.cluster, this.postForm.failureDomainName).then(response => {
+        this.$notify({
+          title: 'success',
+          message: 'Delete Domain success',
+          type: 'success',
+          duration: 3000
+        })
+        this.$router.push({ path: '/management/clusters/' + this.postForm.cluster + '/cluster?tab=failureDomains' })
+      })
+    }
+  }
+}
+</script>
+
+<style>
+.split-line {
+  background: #e6e9f3;
+  border: none;
+  height: 1px;
+}
+.danger-line {
+  background: red;
+  border: none;
+  height: 1px;
+}
+</style>
diff --git a/front-end/src/views/management/clusters/index.vue b/front-end/src/views/management/clusters/index.vue
index c38b704..118a65a 100644
--- a/front-end/src/views/management/clusters/index.vue
+++ b/front-end/src/views/management/clusters/index.vue
@@ -4,23 +4,10 @@
       <el-input :placeholder="$t('table.cluster')" v-model="listQuery.cluster" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
       <el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.search') }}</el-button>
       <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">{{ $t('table.add') }}</el-button>
-      <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleUpdateClusterPeer">updatePeer</el-button>
-      <el-dropdown class="filter-item" style="margin-left: 10px;" @command="handleCommand">
-        <el-button type="primary">
-          domainName<i class="el-icon-arrow-down el-icon--right"/>
-        </el-button>
-        <el-dropdown-menu slot="dropdown">
-          <el-dropdown-item command="domain-name-list">list</el-dropdown-item>
-          <el-dropdown-item command="domain-name-get">get</el-dropdown-item>
-          <el-dropdown-item command="domain-name-delete">delete</el-dropdown-item>
-          <el-dropdown-item command="domain-name-update">update</el-dropdown-item>
-          <el-dropdown-item command="domain-name-create">create</el-dropdown-item>
-        </el-dropdown-menu>
-      </el-dropdown>
     </div>
 
-    <el-row :gutter="8">
-      <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
+    <el-row :gutter="24">
+      <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 24}" :xl="{span: 24}" style="padding-right:8px;margin-bottom:30px;">
         <el-table
           v-loading="listLoading"
           :key="tableKey"
@@ -28,45 +15,38 @@
           border
           fit
           highlight-current-row
-          style="width: 100%;"
-          @row-click="getCurrentRow">
+          style="width: 100%;">
           <el-table-column :label="$t('table.cluster')" min-width="150px" align="center">
             <template slot-scope="scope">
-              <span>{{ scope.row.cluster }}</span>
+              <router-link :to="'/management/clusters/' + scope.row.cluster + '/cluster?tab=config'" class="link-type">
+                <span>{{ scope.row.cluster }}</span>
+              </router-link>
             </template>
           </el-table-column>
-          <el-table-column :label="$t('table.config')" min-width="150px" align="center">
+          <el-table-column label="Brokers" min-width="150px" align="center">
             <template slot-scope="scope">
-              <span class="link-type" @click="handleGetConfig(scope.row)">config</span>
+              <span>{{ scope.row.brokers }}</span>
             </template>
           </el-table-column>
-          <el-table-column :label="$t('table.actions')" align="center" width="230" class-name="small-padding fixed-width">
+          <el-table-column label="Service Urls" min-width="150px" align="center">
             <template slot-scope="scope">
-              <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
-              <el-button type="danger" size="mini" @click="handleDelete(scope.row)">{{ $t('table.delete') }}</el-button>
+              <span>
+                data: {{ scope.row.brokerServiceUrl }}
+                <br>
+                admin: {{ scope.row.serviceUrl }}
+              </span>
             </template>
           </el-table-column>
         </el-table>
-
-        <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getClusters" />
-      </el-col>
-      <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="margin-bottom:30px;">
-        <jsonEditor :value="jsonValue"/>
       </el-col>
     </el-row>
-
     <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
-      <el-form ref="temp" :rules="rules" :model="temp" label-position="left" label-width="130px" style="width: 400px; margin-left:50px;">
+      <el-form ref="temp" :rules="rules" :model="temp" label-position="left" label-width="150px" style="width: 400px; margin-left:50px;">
         <div v-if="dialogStatus==='create'||dialogStatus==='update'">
           <div v-if="dialogStatus==='create'">
             <el-form-item :label="$t('table.cluster')" prop="cluster">
               <el-input v-model="temp.cluster"/>
             </el-form-item>
-          </div>
-          <div v-if="dialogStatus==='update'">
-            <el-form-item :label="$t('table.cluster')">
-              <span>{{ temp.cluster }}</span>
-            </el-form-item>
             <el-form-item :label="$t('table.serviceUrl')" prop="serviceUrl">
               <el-input v-model="temp.serviceUrl"/>
             </el-form-item>
@@ -76,50 +56,17 @@
             <el-form-item :label="$t('table.brokerServiceUrl')" prop="brokerServiceUrl">
               <el-input v-model="temp.brokerServiceUrl"/>
             </el-form-item>
-            <el-form-item label="brokerUrlTls" prop="brokerServiceUrlTls">
+            <el-form-item label="brokerServiceUrlTls" prop="brokerServiceUrlTls">
               <el-input v-model="temp.brokerServiceUrlTls"/>
             </el-form-item>
           </div>
         </div>
-        <div v-if="dialogStatus==='updatePeer'">
-          <el-form-item label="peerClusters" prop="peerClusters">
-            <el-drag-select v-model="temp.peerClusters" style="width:300px;" multiple placeholder="Please select">
-              <el-option v-for="item in peerClustersListOptions" :label="item.label" :value="item.value" :key="item.value" />
-            </el-drag-select>
-          </el-form-item>
-        </div>
-        <div v-if="dialogStatus==='domain-name-get'||dialogStatus==='domain-name-delete'">
-          <el-form-item label="domainNames" prop="domainNames">
-            <el-select v-model="temp.domainNames" style="width:300px;" placeholder="Please select">
-              <el-option v-for="(item,index) in domainNamesListOptions" :key="item+index" :label="item" :value="item"/>
-            </el-select>
-          </el-form-item>
-        </div>
-        <div v-if="dialogStatus==='domain-name-create'||dialogStatus==='domain-name-update'">
-          <div v-if="dialogStatus==='domain-name-create'">
-            <el-form-item label="domainName" prop="domainName">
-              <el-input v-model="temp.domainName"/>
-            </el-form-item>
-          </div>
-          <div v-if="dialogStatus==='domain-name-update'">
-            <el-form-item label="domainNames" prop="domainNames">
-              <el-select v-model="temp.domainNames" style="width:300px;" placeholder="Please select">
-                <el-option v-for="(item,index) in domainNamesListOptions" :key="item+index" :label="item" :value="item"/>
-              </el-select>
-            </el-form-item>
-          </div>
-          <el-form-item label="brokerList" prop="brokerList">
-            <el-input v-model="temp.brokerList"/>
-          </el-form-item>
-        </div>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button @click="dialogFormVisible = false">{{ $t('table.cancel') }}</el-button>
         <el-button type="primary" @click="handleOptions()">{{ $t('table.confirm') }}</el-button>
-        <!-- <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">{{ $t('table.confirm') }}</el-button> -->
       </div>
     </el-dialog>
-
   </div>
 </template>
 
@@ -127,23 +74,13 @@
 import {
   fetchClusters,
   putCluster,
-  deleteCluster,
-  updateCluster,
-  fetchClusterConfig,
-  updateClusterPeer,
-  listClusterDomainName,
-  getClusterDomainName,
-  createClusterDomainName,
-  deleteClusterDomainName,
-  updateClusterDomainName
-  // getClusterPeer
+  fetchClusterConfig
 } from '@/api/clusters'
 import waves from '@/directive/waves' // Waves directive
 import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
 import jsonEditor from '@/components/JsonEditor'
 import { validateEmpty } from '@/utils/validate'
 import ElDragSelect from '@/components/DragSelect' // base on element-ui
-import { trim } from '@/utils/index'
 
 export default {
   name: 'Clusters',
@@ -219,8 +156,13 @@
       } else {
         this.listLoading = true
         fetchClusters(this.listQuery).then(response => {
-          for (var i = 0; i < response.data.length; i++) {
-            this.localList.push({ 'cluster': response.data[i] })
+          for (var i = 0; i < response.data.data.length; i++) {
+            this.localList.push({
+              'cluster': response.data.data[i]['cluster'],
+              'brokers': response.data.data[i]['brokers'],
+              'serviceUrl': response.data.data[i]['serviceUrl'],
+              'brokerServiceUrl': response.data.data[i]['brokerServiceUrl']
+            })
           }
           this.total = this.localList.length
           this.list = this.localList.slice((this.listQuery.page - 1) * this.listQuery.limit, this.listQuery.limit * this.listQuery.page)
@@ -267,7 +209,9 @@
       this.temp = {
         cluster: '',
         serviceUrl: '',
-        brokerServiceUrl: ''
+        serviceUrlTls: '',
+        brokerServiceUrl: '',
+        brokerServiceUrlTls: ''
       }
     },
     handleCreate() {
@@ -280,8 +224,9 @@
     },
     createData() {
       putCluster(this.temp.cluster, this.temp).then(response => {
-        this.list.unshift(this.temp)
         this.dialogFormVisible = false
+        this.localList = []
+        this.getClusters()
         this.$notify({
           title: 'success',
           message: 'create success',
@@ -290,91 +235,6 @@
         })
       })
     },
-    handleUpdate(row) {
-      this.dialogStatus = 'update'
-      this.dialogFormVisible = true
-      this.$nextTick(() => {
-        this.$refs['temp'].clearValidate()
-      })
-      this.temp.cluster = row.cluster
-      fetchClusterConfig(row.cluster).then(response => {
-        this.temp.serviceUrl = response.data.serviceUrl
-        this.temp.serviceUrlTls = response.data.serviceUrlTls
-        this.temp.brokerServiceUrl = response.data.brokerServiceUrl
-        this.temp.brokerServiceUrlTls = response.data.brokerServiceUrlTls
-      })
-    },
-    updateData() {
-      updateCluster(this.temp.cluster, this.temp).then(() => {
-        for (const v of this.list) {
-          if (v.id === this.temp.id) {
-            const index = this.list.indexOf(v)
-            this.list.splice(index, 1, this.temp)
-            break
-          }
-        }
-        this.dialogFormVisible = false
-        this.$notify({
-          title: 'success',
-          message: 'update success',
-          type: 'success',
-          duration: 2000
-        })
-      })
-    },
-    handleDelete(row) {
-      deleteCluster(row.cluster).then(response => {
-        this.$notify({
-          title: 'success',
-          message: 'delete success',
-          type: 'success',
-          duration: 2000
-        })
-        const index = this.list.indexOf(row)
-        this.list.splice(index, 1)
-      })
-    },
-    formatJson(filterVal, jsonData) {
-      return jsonData.map(v => filterVal.map(j => {
-        return v[j]
-      }))
-    },
-    getCurrentRow(item) {
-      this.currentCluster = item.cluster
-    },
-    handleUpdateClusterPeer() {
-      if (this.currentCluster.length <= 0) {
-        this.$notify({
-          title: 'error',
-          message: 'Please select any cluster in table',
-          type: 'error',
-          duration: 2000
-        })
-        return
-      }
-      this.temp.peerClusters = []
-      this.peerClustersListOptions = []
-      fetchClusters(this.listQuery).then(response => {
-        for (var i = 0; i < response.data.length; i++) {
-          if (response.data[i] !== this.currentCluster) {
-            this.peerClustersListOptions.push({ 'label': response.data[i], 'value': response.data[i] })
-          }
-        }
-      })
-      this.dialogStatus = 'updatePeer'
-      this.dialogFormVisible = true
-    },
-    confirmUpdateClusterPeer() {
-      updateClusterPeer(this.currentCluster, this.temp.peerClusters).then(response => {
-        this.$notify({
-          title: 'success',
-          message: 'Update peer clusters success',
-          type: 'success',
-          duration: 2000
-        })
-        this.dialogFormVisible = false
-      })
-    },
     handleOptions() {
       this.$refs['temp'].validate((valid) => {
         if (valid) {
@@ -382,24 +242,6 @@
             case 'create':
               this.createData()
               break
-            case 'update':
-              this.updateData()
-              break
-            case 'updatePeer':
-              this.confirmUpdateClusterPeer()
-              break
-            case 'domain-name-get':
-              this.confirmGetDomainName()
-              break
-            case 'domain-name-create':
-              this.confirmDomainNameCreate()
-              break
-            case 'domain-name-delete':
-              this.confirmDomainNameDelete()
-              break
-            case 'domain-name-update':
-              this.confirmDomainNameUpdate()
-              break
           }
         }
       })
@@ -416,113 +258,10 @@
       }
       this.currentCommand = command
       switch (this.currentCommand) {
-        case 'domain-name-list':
-          this.confirmListDomainName()
-          break
-        case 'domain-name-get':
-          this.handleDomainNameGet()
-          break
         case 'domain-name-create':
           this.handleDomainNameCreate()
           break
-        case 'domain-name-update':
-          this.handleDomainNameUpdate()
-          break
-        case 'domain-name-delete':
-          this.handleDomainNameDelete()
-          break
       }
-    },
-    handleDomainNameGet() {
-      this.dialogStatus = 'domain-name-get'
-      this.dialogFormVisible = true
-      this.domainNamesListOptions = []
-      this.temp.domainNames = ''
-      listClusterDomainName(this.currentCluster).then(response => {
-        for (var key in response.data) {
-          this.domainNamesListOptions.push(key)
-        }
-      })
-    },
-    handleDomainNameCreate() {
-      this.dialogStatus = 'domain-name-create'
-      this.dialogFormVisible = true
-    },
-    handleDomainNameUpdate() {
-      this.dialogStatus = 'domain-name-update'
-      this.dialogFormVisible = true
-      this.domainNamesListOptions = []
-      this.temp.domainNames = ''
-      listClusterDomainName(this.currentCluster).then(response => {
-        for (var key in response.data) {
-          this.domainNamesListOptions.push(key)
-        }
-      })
-    },
-    handleDomainNameDelete() {
-      this.dialogStatus = 'domain-name-delete'
-      this.dialogFormVisible = true
-      this.domainNamesListOptions = []
-      this.temp.domainNames = ''
-      listClusterDomainName(this.currentCluster).then(response => {
-        for (var key in response.data) {
-          this.domainNamesListOptions.push(key)
-        }
-      })
-    },
-    confirmListDomainName() {
-      listClusterDomainName(this.currentCluster).then(response => {
-        this.jsonValue = response.data
-      })
-    },
-    confirmGetDomainName() {
-      getClusterDomainName(this.currentCluster, this.temp.domainNames).then(response => {
-        this.jsonValue = response.data
-        this.dialogFormVisible = false
-      })
-    },
-    confirmDomainNameCreate() {
-      const data = []
-      const brokerList = this.temp.brokerList.split(',')
-      for (var i = 0; i < brokerList.length; i++) {
-        data.push(trim(brokerList[i]))
-      }
-      createClusterDomainName(this.currentCluster, this.temp.domainName, { brokers: data }).then(response => {
-        this.$notify({
-          title: 'success',
-          message: 'create success',
-          type: 'success',
-          duration: 2000
-        })
-        this.dialogFormVisible = false
-      })
-    },
-    confirmDomainNameDelete() {
-      deleteClusterDomainName(this.currentCluster, this.temp.domainNames).then(response => {
-        this.$notify({
-          title: 'success',
-          message: 'delete success',
-          type: 'success',
-          duration: 2000
-        })
-        this.dialogFormVisible = false
-      })
-    },
-    confirmDomainNameUpdate() {
-      const data = []
-      const brokerList = this.temp.brokerList.split(',')
-      for (var i = 0; i < brokerList.length; i++) {
-        data.push(trim(brokerList[i]))
-      }
-      updateClusterDomainName(this.currentCluster, this.temp.domainNames, { brokers: data }).then(response => {
-        this.$notify({
-          title: 'success',
-          message: 'update success',
-          type: 'success',
-          duration: 2000
-        })
-        this.dialogFormVisible = false
-      })
     }
   }
 }
diff --git a/front-end/src/views/management/namespaceIsolations/namespaceIsolationPolicy.vue b/front-end/src/views/management/namespaceIsolations/namespaceIsolationPolicy.vue
new file mode 100644
index 0000000..30a9e8c
--- /dev/null
+++ b/front-end/src/views/management/namespaceIsolations/namespaceIsolationPolicy.vue
@@ -0,0 +1,467 @@
+<template>
+  <div class="app-container">
+    <div class="createPost-container">
+      <el-form :inline="true" :model="postForm" class="form-container">
+        <el-form-item class="postInfo-container-item" label="Cluster">
+          <el-select v-model="postForm.cluster" placeholder="select cluster" @change="getClusterList(postForm.cluster)">
+            <el-option v-for="(item,index) in clustersListOptions" :key="item+index" :label="item" :value="item"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item v-if="created===false" class="postInfo-container-item" label="Policy">
+          <el-select v-model="postForm.policy" placeholder="select policy" @change="(postForm.cluster)">
+            <el-option v-for="(item,index) in policiesListOptions" :key="item+index" :label="item" :value="item"/>
+          </el-select>
+        </el-form-item>
+      </el-form>
+    </div>
+    <h2>Namespace Isolation Policy</h2>
+    <hr class="split-line">
+    <div v-if="created===true">
+      <h3>Policy Name</h3>
+      <hr class="split-line">
+      <el-input
+        v-model="policyName"
+        placeholder="Please Input policy name"
+        clearable
+        style="width:300px"/>
+    </div>
+    <h3>Namespaces</h3>
+    <hr class="split-line">
+    <span>Selected Namespace (Regex)
+      <el-tooltip :content="namespaceContent" class="item" effect="dark" placement="top">
+        <i class="el-icon-info"/>
+      </el-tooltip>
+    </span>
+    <br>
+    <el-tag
+      v-for="(ntag, index) in namespaceDynamicTags"
+      :key="'namespace-' + index"
+      :disable-transitions="false"
+      closable
+      @close="handleCloseNamespace(ntag)">
+      {{ ntag }}
+    </el-tag>
+    <el-input
+      v-if="inputVisibleNamespace"
+      ref="saveTagInputNamespace"
+      v-model="inputValueNamespace"
+      class="input-new-tag"
+      size="small"
+      @keyup.enter.native="handleInputConfirmNamespace"/>
+    <el-button v-else class="button-new-tag" size="small" @click="showInputNamespace">+ Regex</el-button>
+    <h3>Primary Brokers</h3>
+    <hr class="split-line">
+    <span>Selected Brokers (Regex)
+      <el-tooltip :content="brokerContent" class="item" effect="dark" placement="top">
+        <i class="el-icon-info"/>
+      </el-tooltip>
+    </span>
+    <br>
+    <el-tag
+      v-for="(ptag, index) in primaryDynamicTags"
+      :key="'primary-' + index"
+      :disable-transitions="false"
+      closable
+      @close="handleClosePrimary(ptag)">
+      {{ ptag }}
+    </el-tag>
+    <el-input
+      v-if="inputVisiblePrimary"
+      ref="saveTagInputPrimary"
+      v-model="inputValuePrimary"
+      class="input-new-tag"
+      size="small"
+      @keyup.enter.native="handleInputConfirmPrimary"/>
+    <el-button v-else class="button-new-tag" size="small" @click="showInputPrimary">+ Regex</el-button>
+    <h3>Secondary Brokers</h3>
+    <hr class="split-line">
+    <span>Selected Brokers (Regex)
+      <el-tooltip :content="secondaryBrokersContent" class="item" effect="dark" placement="top">
+        <i class="el-icon-info"/>
+      </el-tooltip>
+    </span>
+    <br>
+    <el-tag
+      v-for="(stag, index) in secondaryDynamicTags"
+      :key="'secondary-' + index"
+      :disable-transitions="false"
+      closable
+      @close="handleCloseSecondary(stag)">
+      {{ stag }}
+    </el-tag>
+    <el-input
+      v-if="inputVisibleSecondary"
+      ref="saveTagInputSecondary"
+      v-model="inputValueSecondary"
+      class="input-new-tag"
+      size="small"
+      @keyup.enter.native="handleInputConfirmSecondary"/>
+    <el-button v-else class="button-new-tag" size="small" @click="showInputSecondary">+ Regex</el-button>
+    <h3>Auto Failover Policy</h3>
+    <hr class="split-line">
+    <span>Policy Type
+      <el-tooltip :content="policyTypeContent" class="item" effect="dark" placement="top">
+        <i class="el-icon-info"/>
+      </el-tooltip>
+    </span>
+    <br>
+    <el-select
+      v-model="autoFailoverPolicy"
+      placeholder="Please select Auto Failover Policy"
+      style="margin-top:20px;width:300px">
+      <el-option
+        v-for="item in autoFailoverPolicyOptions"
+        :key="item.value"
+        :label="item.label"
+        :value="item.value"/>
+    </el-select>
+    <el-form :inline="true" :model="form" :rules="rules">
+      <el-form-item prop="ensembelSize">
+        <span>Broker Usage Threshold (%)</span>
+        <el-tooltip :content="brokerUsageThresholdContent" class="item" effect="dark" placement="top">
+          <i class="el-icon-info"/>
+        </el-tooltip>
+        <md-input
+          v-model="form.brokerUsageThreshold"
+          class="md-input-style"
+          name="brokerUsageThreshold"
+          placeholder="Please input brokerUsageThreshold"
+          @keyup.enter.native="handleIsolationPolicy"/>
+      </el-form-item>
+      <el-form-item prop="writeQuorumSize">
+        <span>Minimal Available Brokers</span>
+        <el-tooltip :content="minimalAvailableBrokerContent" class="item" effect="dark" placement="top">
+          <i class="el-icon-info"/>
+        </el-tooltip>
+        <md-input
+          v-model="form.minimalAvailableBroker"
+          class="md-input-style"
+          name="minimalAvailableBroker"
+          placeholder="Please input minimalAvailableBroker"
+          @keyup.enter.native="handleIsolationPolicy"/>
+      </el-form-item>
+    </el-form>
+    <div v-if="created===true">
+      <h4>Create Zone</h4>
+      <hr class="split-line">
+      <el-button type="primary" class="button" @click="handleIsolationPolicy">Create Policy</el-button>
+    </div>
+    <div v-if="created===false">
+      <h4>Danager Zone</h4>
+      <hr class="danger-line">
+      <el-button type="danger" class="button" @click="handleDelete">Delete Policy</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import { fetchClusters } from '@/api/clusters'
+import { fetchIsolationPolicies, updateIsolationPolicies, deleteIsolationPolicies } from '@/api/isolationPolicies'
+import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
+import MdInput from '@/components/MDinput'
+const defaultForm = {
+  cluster: '',
+  policy: ''
+}
+export default {
+  name: 'NamespaceIsolationPolicy',
+  components: {
+    Pagination,
+    MdInput
+  },
+  data() {
+    return {
+      postForm: Object.assign({}, defaultForm),
+      clustersListOptions: [],
+      namespaceContent: 'This is namespaces Content',
+      brokerContent: 'This is broker content',
+      secondaryBrokersContent: 'This is secondary brokers content',
+      policyTypeContent: 'This is policy type content',
+      brokerUsageThresholdContent: 'This is brokerUsageThresholdContent',
+      minimalAvailableBrokerContent: 'This is minimalAvailableBrokerContent',
+      form: {
+        namespaces: '',
+        broker: '',
+        brokerUsageThreshold: '',
+        minimalAvailableBroker: ''
+      },
+      autoFailoverPolicy: 'min_available',
+      autoFailoverPolicyOptions: [],
+      rules: {
+      },
+      policiesListOptions: [],
+      primaryDynamicTags: [],
+      inputVisiblePrimary: false,
+      inputValuePrimary: '',
+      namespaceDynamicTags: [],
+      inputValueNamespace: '',
+      inputVisibleNamespace: false,
+      secondaryDynamicTags: [],
+      inputVisibleSecondary: false,
+      inputValueSecondary: '',
+      created: false,
+      policyName: ''
+    }
+  },
+  created() {
+    this.postForm.cluster = this.$route.params && this.$route.params.cluster
+    this.getClusterList()
+    if (this.$route.query && this.$route.query.created) {
+      this.created = true
+    } else {
+      this.postForm.policy = this.$route.params && this.$route.params.namespaceIsolation
+      this.getPoliciesList()
+    }
+  },
+  methods: {
+    getClusterList() {
+      fetchClusters().then(response => {
+        if (!response.data) return
+        for (var i = 0; i < response.data.data.length; i++) {
+          this.clustersListOptions.push(response.data.data[i].cluster)
+        }
+      })
+    },
+    getPoliciesList() {
+      fetchIsolationPolicies(this.postForm.cluster).then(response => {
+        if (!response.data) return
+        for (var key in response.data) {
+          this.policiesListOptions.push(key)
+          if (key === this.postForm.policy) {
+            this.namespaceDynamicTags = response.data[key].namespaces
+            this.primaryDynamicTags = response.data[key].primary
+            this.secondaryDynamicTags = response.data[key].secondary
+            this.autoFailoverPolicyOptions.push({
+              value: response.data[key].auto_failover_policy.policy_type,
+              label: response.data[key].auto_failover_policy.policy_type
+            })
+            this.form.minimalAvailableBroker = response.data[key].auto_failover_policy.parameters.min_limit
+            this.form.brokerUsageThreshold = response.data[key].auto_failover_policy.parameters.usage_threshold
+          }
+        }
+      })
+    },
+    handleIsolationPolicy() {
+      var policyName = this.postForm.policy
+      if (this.created) {
+        if (this.policyName.length <= 0) {
+          this.$notify({
+            title: 'error',
+            message: 'Policy Name cannot be empty',
+            type: 'error',
+            duration: 3000
+          })
+          return
+        }
+        if (this.namespaceDynamicTags.length <= 0) {
+          this.$notify({
+            title: 'error',
+            message: 'Namespace Regex cannot be empty',
+            type: 'error',
+            duration: 3000
+          })
+          return
+        }
+        if (this.primaryDynamicTags.length <= 0) {
+          this.$notify({
+            title: 'error',
+            message: 'Primary Broker Regex cannot be empty',
+            type: 'error',
+            duration: 3000
+          })
+          return
+        }
+        if (this.secondaryDynamicTags.length <= 0) {
+          this.$notify({
+            title: 'error',
+            message: 'Secondary Broker Regex cannot be empty',
+            type: 'error',
+            duration: 3000
+          })
+          return
+        }
+        if (this.form.minimalAvailableBroker <= 0) {
+          this.$notify({
+            title: 'error',
+            message: 'min_limit should greater than 0',
+            type: 'error',
+            duration: 3000
+          })
+          return
+        }
+        if (this.form.brokerUsageThreshold <= 0) {
+          this.$notify({
+            title: 'error',
+            message: 'usage_threshold should greater than 0',
+            type: 'error',
+            duration: 3000
+          })
+          return
+        }
+        policyName = this.policyName
+      }
+      const data = {
+        'namespaces': this.namespaceDynamicTags,
+        'primary': this.primaryDynamicTags,
+        'secondary': this.secondaryDynamicTags,
+        'auto_failover_policy': {
+          'policy_type': 0,
+          'parameters': {
+            'min_limit': this.form.minimalAvailableBroker,
+            'usage_threshold': this.form.brokerUsageThreshold
+          }
+        }
+      }
+      updateIsolationPolicies(this.postForm.cluster, policyName, data).then(response => {
+        this.$notify({
+          title: 'success',
+          message: 'Set policy success',
+          type: 'success',
+          duration: 3000
+        })
+        if (this.created) {
+          this.$router.push({ path: '/management/clusters/' + this.postForm.cluster + '/cluster?tab=isolationPolicies' })
+        }
+      })
+    },
+    handleClosePrimary(tag) {
+      this.primaryDynamicTags.splice(this.primaryDynamicTags.indexOf(tag), 1)
+      this.handleIsolationPolicy()
+    },
+    showInputPrimary() {
+      this.inputVisiblePrimary = true
+      this.$nextTick(_ => {
+        this.$refs.saveTagInputPrimary.$refs.input.focus()
+      })
+    },
+    handleInputConfirmPrimary() {
+      const inputValue = this.inputValuePrimary
+      if (this.primaryDynamicTags.indexOf(inputValue) > 0) {
+        this.$notify({
+          title: 'error',
+          message: 'This regex exist',
+          type: 'error',
+          duration: 3000
+        })
+        return
+      }
+      if (inputValue) {
+        this.primaryDynamicTags.push(inputValue)
+      }
+      this.inputVisiblePrimary = false
+      this.inputValuePrimary = ''
+      if (this.created) {
+        return
+      }
+      this.handleIsolationPolicy()
+    },
+    handleCloseNamespace(tag) {
+      this.namespaceDynamicTags.splice(this.namespaceDynamicTags.indexOf(tag), 1)
+      this.handleIsolationPolicy()
+    },
+    showInputNamespace() {
+      this.inputVisibleNamespace = true
+      this.$nextTick(_ => {
+        this.$refs.saveTagInputNamespace.$refs.input.focus()
+      })
+    },
+    handleInputConfirmNamespace() {
+      const inputValue = this.inputValueNamespace
+      if (this.namespaceDynamicTags.indexOf(inputValue) > 0) {
+        this.$notify({
+          title: 'error',
+          message: 'This regex exist',
+          type: 'success',
+          duration: 3000
+        })
+        return
+      }
+      if (inputValue) {
+        this.namespaceDynamicTags.push(inputValue)
+      }
+      this.inputVisibleNamespace = false
+      this.inputValueNamespace = ''
+      if (this.created) {
+        return
+      }
+      this.handleIsolationPolicy()
+    },
+    handleCloseSecondary(tag) {
+      this.secondaryDynamicTags.splice(this.secondaryDynamicTags.indexOf(tag), 1)
+      this.handleIsolationPolicy()
+    },
+    showInputSecondary() {
+      this.inputVisibleSecondary = true
+      this.$nextTick(_ => {
+        this.$refs.saveTagInputSecondary.$refs.input.focus()
+      })
+    },
+    handleInputConfirmSecondary() {
+      const inputValue = this.inputValueSecondary
+      if (this.secondaryDynamicTags.indexOf(inputValue) > 0) {
+        this.$notify({
+          title: 'error',
+          message: 'This regex exist',
+          type: 'error',
+          duration: 3000
+        })
+        return
+      }
+      if (inputValue) {
+        this.secondaryDynamicTags.push(inputValue)
+      }
+      this.inputVisibleSecondary = false
+      this.inputValueSecondary = ''
+      if (this.created) {
+        return
+      }
+      this.handleIsolationPolicy()
+    },
+    handleDelete() {
+      deleteIsolationPolicies(this.postForm.cluster, this.postForm.policy).then(response => {
+        this.$notify({
+          title: 'success',
+          message: 'Delete policy success',
+          type: 'success',
+          duration: 3000
+        })
+        this.$router.push({ path: '/management/clusters/' + this.postForm.cluster + '/cluster?tab=isolationPolicies' })
+      })
+    }
+  }
+}
+</script>
+
+<style>
+.split-line {
+  background: #e6e9f3;
+  border: none;
+  height: 1px;
+}
+.md-input-style {
+  width: 300px;
+  margin-top: 20px;
+}
+.danger-line {
+  background: red;
+  border: none;
+  height: 1px;
+}
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+.button-new-tag {
+  margin-top: 20px;
+  margin-left: 10px;
+  height: 32px;
+  line-height: 30px;
+  padding-top: 0;
+  padding-bottom: 0;
+}
+.input-new-tag {
+  width: 90px;
+  margin-top: 20px;
+  margin-left: 10px;
+  vertical-align: bottom;
+}
+</style>
diff --git a/front-end/src/views/management/namespaces/namespace.vue b/front-end/src/views/management/namespaces/namespace.vue
index a9b5ae5..7e60ba9 100644
--- a/front-end/src/views/management/namespaces/namespace.vue
+++ b/front-end/src/views/management/namespaces/namespace.vue
@@ -700,9 +700,8 @@
   deleteNamespace,
   clearBundleBacklog
 } from '@/api/namespaces'
-import { putTopic } from '@/api/topics'
-import { fetchBrokerStats } from '@/api/brokerStats'
-import { fetchTopicsByPulsarManager } from '@/api/topics'
+import { fetchBrokerStatsTopics } from '@/api/brokerStats'
+import { putTopic, fetchTopicsByPulsarManager } from '@/api/topics'
 import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
 import ElDragSelect from '@/components/DragSelect' // base on element-ui
 import MdInput from '@/components/MDinput'
@@ -950,7 +949,7 @@
             'topicLink': topicLink
           })
         }
-        fetchBrokerStats().then(res => {
+        fetchBrokerStatsTopics('').then(res => {
           if (!res.data) return
           this.brokerStats = res.data
           if (this.brokerStats.hasOwnProperty(this.tenantNamespace)) {
diff --git a/front-end/src/views/management/tenants/index.vue b/front-end/src/views/management/tenants/index.vue
index a3a0e83..253a58b 100644
--- a/front-end/src/views/management/tenants/index.vue
+++ b/front-end/src/views/management/tenants/index.vue
@@ -62,7 +62,22 @@
           </el-drag-select>
         </el-form-item>
         <el-form-item v-if="dialogStatus==='create'" label="Allowed Roles" prop="roles">
-          <el-input v-model="temp.adminRoles"/>
+          <el-tag
+            v-for="tag in dynamicRoles"
+            :key="tag"
+            :disable-transitions="false"
+            closable
+            @close="handleClose(tag)">
+            {{ tag }}
+          </el-tag>
+          <el-input
+            v-if="inputVisible"
+            ref="saveTagInput"
+            v-model="inputValue"
+            size="small"
+            class="input-new-tag"
+            @keyup.enter.native="handleInputConfirm"/>
+          <el-button v-else class="button-new-tag" size="small" @click="showInput">+ New Role</el-button>
         </el-form-item>
         <el-form-item v-if="dialogStatus==='update'" :label="$t('table.tenant')">
           <span>{{ temp.tenant }}</span>
@@ -154,7 +169,10 @@
       rules: {
         tenant: [{ required: true, message: 'Tenant is required', trigger: 'blur' }],
         clusters: [{ required: true, message: 'Cluster is required', trigger: 'blur' }]
-      }
+      },
+      dynamicRoles: [],
+      inputVisible: false,
+      inputValue: ''
     }
   },
   created() {
@@ -259,8 +277,9 @@
       this.temp.clusters = []
       this.clusterListOptions = []
       fetchClusters(this.listQuery).then(response => {
-        for (var i = 0; i < response.data.length; i++) {
-          this.clusterListOptions.push({ 'value': response.data[i], 'label': response.data[i] })
+        console.log(response.data)
+        for (var i = 0; i < response.data.data.length; i++) {
+          this.clusterListOptions.push({ 'value': response.data.data[i].cluster, 'label': response.data.data[i].cluster })
         }
       })
       this.$nextTick(() => {
@@ -271,10 +290,8 @@
       this.$refs['temp'].validate((valid) => {
         if (valid) {
           const data = {
-            allowedClusters: this.temp.clusters
-          }
-          if (this.temp.adminRoles.length > 0) {
-            data.adminRoles = this.temp.adminRoles.split(',')
+            allowedClusters: this.temp.clusters,
+            adminRoles: this.dynamicRoles
           }
           putTenant(this.temp.tenant, data).then((response) => {
             this.temp.adminRoles = 'empty'
@@ -358,6 +375,32 @@
         this.localList = []
         this.getTenants()
       })
+    },
+    handleClose(tag) {
+      this.dynamicRoles.splice(this.dynamicRoles.indexOf(tag), 1)
+    },
+    showInput() {
+      this.inputVisible = true
+      this.$nextTick(_ => {
+        this.$refs.saveTagInput.$refs.input.focus()
+      })
+    },
+    handleInputConfirm() {
+      const inputValue = this.inputValue
+      if (inputValue) {
+        if (this.dynamicRoles.indexOf(this.inputValue) >= 0) {
+          this.$notify({
+            title: 'error',
+            message: 'Role is exists',
+            type: 'error',
+            duration: 2000
+          })
+          return
+        }
+        this.dynamicRoles.push(inputValue)
+      }
+      this.inputVisible = false
+      this.inputValue = ''
     }
   }
 }
diff --git a/front-end/src/views/management/topics/topic.vue b/front-end/src/views/management/topics/topic.vue
index 622dd2d..3bec4e2 100644
--- a/front-end/src/views/management/topics/topic.vue
+++ b/front-end/src/views/management/topics/topic.vue
@@ -434,13 +434,11 @@
     if (this.$route.query && this.$route.query.tab) {
       this.activeName = this.$route.query.tab
     }
-    console.log(this.postForm.persistent)
     if (this.postForm.persistent === 'persistent') {
       this.nonPersistent = false
     } else if (this.postForm.persistent === 'non-persistent') {
       this.nonPersistent = true
     }
-    console.log(this.nonPersistent)
     this.topicName = this.postForm.persistent + '://' + this.tenantNamespaceTopic
     this.firstInit = true
     this.firstInitTopic = true
diff --git a/src/main/java/com/manager/pulsar/controller/BrokerStatsController.java b/src/main/java/com/manager/pulsar/controller/BrokerStatsController.java
new file mode 100644
index 0000000..2bbd0fb
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/controller/BrokerStatsController.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed 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.
+ */
+package com.manager.pulsar.controller;
+
+import com.manager.pulsar.service.BrokerStatsService;
+import com.manager.pulsar.service.BrokersService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.ws.rs.QueryParam;
+import java.util.Map;
+
+/**
+ * Broker Stats route foward
+ */
+@RequestMapping(value = "/pulsar-manager/admin/v2")
+@Api(description = "Support more flexible queries to brokerStats.")
+@Validated
+@RestController
+public class BrokerStatsController {
+
+    @Autowired
+    private BrokerStatsService brokerStatsService;
+
+    @ApiOperation(value = "Get the broker stats metrics")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "ok"),
+            @ApiResponse(code = 500, message = "Internal server error")
+    })
+    @RequestMapping(value = "/broker-stats/metrics", method =  RequestMethod.GET)
+    public ResponseEntity<String> getBrokerStatsMetrics(
+            @RequestParam() String broker) {
+        String result = brokerStatsService.forwarBrokerStatsMetrics(broker);
+        return ResponseEntity.ok(result);
+    }
+
+    @ApiOperation(value = "Get the broker stats topics")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "ok"),
+            @ApiResponse(code = 500, message = "Internal server error")
+    })
+    @RequestMapping(value = "/broker-stats/topics", method =  RequestMethod.GET)
+    public ResponseEntity<String> getBrokerStatsTopics(
+            @RequestParam() String broker) {
+        String result = brokerStatsService.forwardBrokerStatsTopics(broker);
+        return ResponseEntity.ok(result);
+    }
+
+}
diff --git a/src/main/java/com/manager/pulsar/controller/BrokersController.java b/src/main/java/com/manager/pulsar/controller/BrokersController.java
index ed55038..0b600a3 100644
--- a/src/main/java/com/manager/pulsar/controller/BrokersController.java
+++ b/src/main/java/com/manager/pulsar/controller/BrokersController.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.Maps;
 import com.manager.pulsar.entity.BrokerEntity;
 import com.manager.pulsar.entity.BrokersRepository;
+import com.manager.pulsar.service.BrokersService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -47,6 +48,9 @@
 public class BrokersController {
 
     @Autowired
+    private BrokersService brokersService;
+
+    @Autowired
     private BrokersRepository brokersRepository;
 
     @ApiOperation(value = "Get the list of existing brokers, support paging, the default is 10 per page")
@@ -54,35 +58,33 @@
             @ApiResponse(code = 200, message = "ok"),
             @ApiResponse(code = 500, message = "Internal server error")
     })
-    @RequestMapping(value = "/brokers", method =  RequestMethod.GET)
+    @RequestMapping(value = "/brokers/{cluster}", method =  RequestMethod.GET)
     public ResponseEntity<Map<String, Object>> getBrokers(
             @ApiParam(value = "page_num", defaultValue = "1", example = "1")
             @RequestParam(name = "page_num", defaultValue = "1")
             @Min(value = 1, message = "page_num is incorrect, should be greater than 0.")
-                    Integer pageNum,
+            Integer pageNum,
             @ApiParam(value = "page_size", defaultValue = "10", example = "10")
             @RequestParam(name="page_size", defaultValue = "10")
             @Range(min = 1, max = 1000, message = "page_size is incorrect, should be greater than 0 and less than 1000.")
-                    Integer pageSize) {
-        Page<BrokerEntity> brokersEntityPage = brokersRepository.getBrokersList(pageNum, pageSize);
-        Map<String, Object> result = Maps.newHashMap();
-        result.put("total", brokersEntityPage.getTotal());
-        result.put("data", brokersEntityPage);
+            Integer pageSize,
+            @PathVariable String cluster) {
+        Map<String, Object> result = brokersService.getBrokersList(pageNum, pageSize, cluster);
         return ResponseEntity.ok(result);
     }
-
-    @ApiOperation(value = "Query broker info")
-    @ApiResponses({
-            @ApiResponse(code = 200, message = "ok"),
-            @ApiResponse(code = 500, message = "Internal server error")
-    })
-    @RequestMapping(value = "/brokers/{broker}", method = RequestMethod.GET)
-    public ResponseEntity<Optional<BrokerEntity>> getBroker(
-            @ApiParam(value = "The name of broker")
-            @Size(min = 1, max = 255)
-            @PathVariable String broker) {
-        Optional<BrokerEntity> brokersEntity = brokersRepository.findByBroker(broker);
-        return ResponseEntity.ok(brokersEntity);
-    }
+//
+//    @ApiOperation(value = "Query broker info")
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "ok"),
+//            @ApiResponse(code = 500, message = "Internal server error")
+//    })
+//    @RequestMapping(value = "/brokers/{broker}", method = RequestMethod.GET)
+//    public ResponseEntity<Optional<BrokerEntity>> getBroker(
+//            @ApiParam(value = "The name of broker")
+//            @Size(min = 1, max = 255)
+//            @PathVariable String broker) {
+//        Optional<BrokerEntity> brokersEntity = brokersRepository.findByBroker(broker);
+//        return ResponseEntity.ok(brokersEntity);
+//    }
 
 }
diff --git a/src/main/java/com/manager/pulsar/controller/ClustersController.java b/src/main/java/com/manager/pulsar/controller/ClustersController.java
index 57bb16d..fa53782 100644
--- a/src/main/java/com/manager/pulsar/controller/ClustersController.java
+++ b/src/main/java/com/manager/pulsar/controller/ClustersController.java
@@ -13,10 +13,9 @@
  */
 package com.manager.pulsar.controller;
 
-import com.github.pagehelper.Page;
-import com.google.common.collect.Maps;
 import com.manager.pulsar.entity.ClusterEntity;
 import com.manager.pulsar.entity.ClustersRepository;
+import com.manager.pulsar.service.ClustersService;
 import io.swagger.annotations.*;
 import org.hibernate.validator.constraints.Range;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -41,6 +40,9 @@
     @Autowired
     private ClustersRepository clustersRepository;
 
+    @Autowired
+    private ClustersService clusterService;
+
     @ApiOperation(value = "Get the list of existing clusters, support paging, the default is 10 per page")
     @ApiResponses({
             @ApiResponse(code = 200, message = "ok"),
@@ -56,10 +58,8 @@
             @RequestParam(name="page_size", defaultValue = "10")
             @Range(min = 1, max = 1000, message = "page_size is incorrect, should be greater than 0 and less than 1000.")
                     Integer pageSize) {
-        Map<String, Object> result = Maps.newHashMap();
-        Page<ClusterEntity> clustersEntityPage = clustersRepository.getClustersList(pageNum, pageSize);
-        result.put("total", clustersEntityPage.getTotal());
-        result.put("data", clustersEntityPage);
+        Map<String, Object> result = clusterService.getClustersList(pageNum, pageSize);
+
         return ResponseEntity.ok(result);
     }
 
diff --git a/src/main/java/com/manager/pulsar/controller/NamespacesController.java b/src/main/java/com/manager/pulsar/controller/NamespacesController.java
index 605d9e1..7dd3dda 100644
--- a/src/main/java/com/manager/pulsar/controller/NamespacesController.java
+++ b/src/main/java/com/manager/pulsar/controller/NamespacesController.java
@@ -84,7 +84,7 @@
             @ApiParam(value = "page_num", defaultValue = "1", example = "1")
             @RequestParam(name = "page_num", defaultValue = "1")
             @Min(value = 1, message = "page_num is incorrect, should be greater than 0.")
-            Integer pageNum,
+                    Integer pageNum,
             @ApiParam(value = "page_size", defaultValue = "10", example = "10")
             @RequestParam(name="page_size", defaultValue = "10")
             @Range(min = 1, max = 1000, message = "page_size is incorrect, should be greater than 0 and less than 1000.")
diff --git a/src/main/java/com/manager/pulsar/service/BrokerStatsService.java b/src/main/java/com/manager/pulsar/service/BrokerStatsService.java
new file mode 100644
index 0000000..445d1a4
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/service/BrokerStatsService.java
@@ -0,0 +1,21 @@
+/**
+ * Licensed 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.
+ */
+package com.manager.pulsar.service;
+
+public interface BrokerStatsService {
+
+    String forwarBrokerStatsMetrics(String broker);
+
+    String forwardBrokerStatsTopics(String broker);
+}
diff --git a/src/main/java/com/manager/pulsar/service/BrokersService.java b/src/main/java/com/manager/pulsar/service/BrokersService.java
new file mode 100644
index 0000000..397f1ef
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/service/BrokersService.java
@@ -0,0 +1,21 @@
+/**
+ * Licensed 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.
+ */
+package com.manager.pulsar.service;
+
+import java.util.Map;
+
+public interface BrokersService {
+
+    Map<String, Object> getBrokersList(Integer pageNum, Integer pageSize, String cluster);
+}
diff --git a/src/main/java/com/manager/pulsar/service/ClustersService.java b/src/main/java/com/manager/pulsar/service/ClustersService.java
new file mode 100644
index 0000000..35f5565
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/service/ClustersService.java
@@ -0,0 +1,20 @@
+/**
+ * Licensed 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.
+ */
+package com.manager.pulsar.service;
+
+import java.util.Map;
+
+public interface ClustersService {
+    Map<String, Object> getClustersList(Integer pageNum, Integer pageSize);
+}
diff --git a/src/main/java/com/manager/pulsar/service/impl/BrokerStatsServiceImpl.java b/src/main/java/com/manager/pulsar/service/impl/BrokerStatsServiceImpl.java
new file mode 100644
index 0000000..856a8fd
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/service/impl/BrokerStatsServiceImpl.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed 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.
+ */
+package com.manager.pulsar.service.impl;
+
+import com.manager.pulsar.service.BrokerStatsService;
+import com.manager.pulsar.utils.HttpUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class BrokerStatsServiceImpl implements BrokerStatsService {
+
+    @Value("${backend.directRequestHost}")
+    private String directRequestHost;
+
+    private static final Map<String, String> header = new HashMap<String, String>(){{
+        put("Content-Type","application/json");
+    }};
+
+    public String forwarBrokerStatsMetrics(String broker) {
+
+        broker = checkBroker(broker);
+        return HttpUtil.doGet(broker + "/admin/v2/broker-stats/metrics", header);
+    }
+
+    public String forwardBrokerStatsTopics(String broker) {
+
+        broker = checkBroker(broker);
+        return HttpUtil.doGet(broker + "/admin/v2/broker-stats/topics", header);
+    }
+
+    private String checkBroker(String broker) {
+        if (broker == null || broker.length() <= 0) {
+            broker = directRequestHost;
+        }
+
+        if (!broker.startsWith("http")) {
+            broker = "http://" + broker;
+        }
+        return broker;
+    }
+}
diff --git a/src/main/java/com/manager/pulsar/service/impl/BrokersServiceImpl.java b/src/main/java/com/manager/pulsar/service/impl/BrokersServiceImpl.java
new file mode 100644
index 0000000..2c96265
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/service/impl/BrokersServiceImpl.java
@@ -0,0 +1,81 @@
+/**
+ * Licensed 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.
+ */
+package com.manager.pulsar.service.impl;
+
+import com.google.common.collect.Maps;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.manager.pulsar.service.BrokersService;
+import com.manager.pulsar.utils.HttpUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class BrokersServiceImpl implements BrokersService {
+
+    @Value("${backend.directRequestBroker}")
+    private boolean directRequestBroker;
+
+    @Value("${backend.directRequestHost}")
+    private String directRequestHost;
+
+    public Map<String, Object> getBrokersList(Integer pageNum, Integer pageSize, String cluster) {
+        Map<String, Object> brokersMap = Maps.newHashMap();
+        List<Map<String, Object>> brokersArray = new ArrayList<>();
+        if (directRequestBroker) {
+            Gson gson = new Gson();
+            Map<String, String> header = Maps.newHashMap();
+            header.put("Content-Type", "application/json");
+            String failureDomainsResult = HttpUtil.doGet(
+                    directRequestHost + "/admin/v2/clusters/" + cluster + "/failureDomains", header);
+            Map<String, Map<String, List<String>>> failureDomains = gson.fromJson(
+                    failureDomainsResult, new TypeToken<Map<String, Map<String, List<String>>>>() {}.getType());
+            String result = HttpUtil.doGet(directRequestHost + "/admin/v2/brokers/" + cluster, header);
+            List<String> brokersList = gson.fromJson(result, new TypeToken<List<String>>() {}.getType());
+            for (String broker: brokersList) {
+                Map<String, Object> brokerEntity = Maps.newHashMap();
+                List<String> failureDomain = this.getFailureDomain(broker, failureDomains);
+                brokerEntity.put("broker", broker);
+                brokerEntity.put("failureDomain", failureDomain);
+                brokersArray.add(brokerEntity);
+            }
+            brokersMap.put("isPage", false);
+            brokersMap.put("total", brokersArray.size());
+            brokersMap.put("data", brokersArray);
+            brokersMap.put("pageNum", 1);
+            brokersMap.put("pageSize", brokersArray.size());
+        }
+        return brokersMap;
+    }
+
+    private List<String> getFailureDomain(String broker, Map<String, Map<String, List<String>>> failureDomains) {
+        List<String> failureDomainsList = new ArrayList<>();
+        Map<String, String> header = Maps.newHashMap();
+        header.put("Content-Type", "application/json");
+        for (String failureDomain: failureDomains.keySet()) {
+            Map<String, List<String>> domains = failureDomains.get(failureDomain);
+            for (String domain: domains.keySet()) {
+                List<String> domainList = domains.get(domain);
+                if (domainList.contains(broker)) {
+                    failureDomainsList.add(failureDomain);
+                }
+            }
+        }
+        return failureDomainsList;
+    }
+}
diff --git a/src/main/java/com/manager/pulsar/service/impl/ClustersServiceImpl.java b/src/main/java/com/manager/pulsar/service/impl/ClustersServiceImpl.java
new file mode 100644
index 0000000..cdf7d83
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/service/impl/ClustersServiceImpl.java
@@ -0,0 +1,73 @@
+/**
+ * Licensed 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.
+ */
+package com.manager.pulsar.service.impl;
+
+import com.google.common.collect.Maps;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.manager.pulsar.service.BrokersService;
+import com.manager.pulsar.service.ClustersService;
+import com.manager.pulsar.utils.HttpUtil;
+import org.apache.pulsar.common.policies.data.ClusterData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class ClustersServiceImpl implements ClustersService {
+
+    @Value("${backend.directRequestBroker}")
+    private boolean directRequestBroker;
+
+    @Value("${backend.directRequestHost}")
+    private String directRequestHost;
+
+    @Autowired
+    private BrokersService brokersService;
+
+    public Map<String, Object> getClustersList(Integer pageNum, Integer pageSize) {
+        Map<String, Object> clustersMap = Maps.newHashMap();
+        List<Map<String, Object>> clustersArray = new ArrayList<>();
+        if (directRequestBroker) {
+            Gson gson = new Gson();
+            Map<String, String> header = Maps.newHashMap();
+            header.put("Content-Type", "application/json");
+            String result = HttpUtil.doGet(directRequestHost + "/admin/v2/clusters", header);
+            List<String> clustersList = gson.fromJson(result, new TypeToken<List<String>>(){}.getType());
+            for (String cluster: clustersList) {
+                Map<String, Object> clusterEntity = Maps.newHashMap();
+                Map<String, Object> brokers = brokersService.getBrokersList(1, 1, cluster);
+                clusterEntity.put("brokers", brokers.get("total"));
+                clusterEntity.put("cluster", cluster);
+                String clusterInfo = HttpUtil.doGet(directRequestHost + "/admin/v2/clusters/" + cluster, header);
+                ClusterData clusterData = gson.fromJson(clusterInfo, ClusterData.class);
+                clusterEntity.put("serviceUrl", clusterData.getServiceUrl());
+                clusterEntity.put("serviceUrlTls", clusterData.getServiceUrlTls());
+                clusterEntity.put("brokerServiceUrl", clusterData.getBrokerServiceUrl());
+                clusterEntity.put("brokerServiceUrlTls", clusterData.getBrokerServiceUrlTls());
+                clustersArray.add(clusterEntity);
+            }
+            clustersMap.put("isPage", false);
+            clustersMap.put("total", clustersArray.size());
+            clustersMap.put("data", clustersArray);
+            clustersMap.put("pageNum", 1);
+            clustersMap.put("pageSize", clustersArray.size());
+        }
+        return clustersMap;
+    }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 11b1a76..d745811 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -57,3 +57,6 @@
 
 backend.directRequestBroker=true
 backend.directRequestHost=http://localhost:8080
+
+pulsar-manager.account=pulsar
+pulsar-manager.password=pulsar
diff --git a/src/test/java/com/manager/pulsar/service/BrokersServiceImplTest.java b/src/test/java/com/manager/pulsar/service/BrokersServiceImplTest.java
new file mode 100644
index 0000000..4f7e487
--- /dev/null
+++ b/src/test/java/com/manager/pulsar/service/BrokersServiceImplTest.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed 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.
+ */
+package com.manager.pulsar.service;
+
+
+import com.google.common.collect.Maps;
+import com.manager.pulsar.utils.HttpUtil;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Map;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(SpringRunner.class)
+@PowerMockIgnore( {"javax.management.*", "javax.net.ssl.*"})
+@PrepareForTest(HttpUtil.class)
+@SpringBootTest
+public class BrokersServiceImplTest {
+
+    @Autowired
+    private BrokersService brokersService;
+
+    @Test
+    public void brokersServiceTest() {
+        PowerMockito.mockStatic(HttpUtil.class);
+        Map<String, String> header = Maps.newHashMap();
+        header.put("Content-Type", "application/json");
+        PowerMockito.when(HttpUtil.doGet("http://localhost:8080/admin/v2/clusters/standalone/failureDomains", header))
+                .thenReturn("{\"test\":{\"brokers\":[\"tengdeMBP:8080\"]}}");
+
+        PowerMockito.when(HttpUtil.doGet("http://localhost:8080/admin/v2/brokers/standalone", header))
+                .thenReturn("[\"tengdeMBP:8080\"]");
+        Map<String, Object> result = brokersService.getBrokersList(1, 1, "standalone");
+        Assert.assertEquals(result.get("total"), 1);
+        Assert.assertEquals(result.get("data").toString(), "[{failureDomain=[test], broker=tengdeMBP:8080}]");
+        Assert.assertEquals(result.get("pageSize"), 1);
+    }
+}
diff --git a/src/test/java/com/manager/pulsar/service/ClustersServiceImplTest.java b/src/test/java/com/manager/pulsar/service/ClustersServiceImplTest.java
new file mode 100644
index 0000000..3657597
--- /dev/null
+++ b/src/test/java/com/manager/pulsar/service/ClustersServiceImplTest.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed 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.
+ */
+package com.manager.pulsar.service;
+
+import com.google.common.collect.Maps;
+import com.manager.pulsar.utils.HttpUtil;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Map;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(SpringRunner.class)
+@PowerMockIgnore( {"javax.management.*", "javax.net.ssl.*"})
+@PrepareForTest(HttpUtil.class)
+@SpringBootTest
+public class ClustersServiceImplTest {
+
+    @Autowired
+    private ClustersService clustersService;
+
+    @Test
+    public void clusterServiceImplTest() {
+        PowerMockito.mockStatic(HttpUtil.class);
+        Map<String, String> header = Maps.newHashMap();
+        header.put("Content-Type", "application/json");
+        PowerMockito.when(HttpUtil.doGet("http://localhost:8080/admin/v2/clusters", header))
+                .thenReturn("[\"standalone\"]");
+
+        PowerMockito.when(HttpUtil.doGet("http://localhost:8080/admin/v2/clusters/standalone", header))
+                .thenReturn("{\n" +
+                        "\"serviceUrl\" : \"http://tengdeMBP:8080\",\n" +
+                        "\"brokerServiceUrl\" : \"pulsar://tengdeMBP:6650\"\n" +
+                        "}");
+        PowerMockito.when(HttpUtil.doGet("http://localhost:8080/admin/v2/clusters/standalone/failureDomains", header))
+                .thenReturn("{\"test\":{\"brokers\":[\"tengdeMBP:8080\"]}}");
+
+        PowerMockito.when(HttpUtil.doGet("http://localhost:8080/admin/v2/brokers/standalone", header))
+                .thenReturn("[\"tengdeMBP:8080\"]");
+        Map<String, Object> result = clustersService.getClustersList(1, 1);
+        Assert.assertEquals(result.get("data").toString(),
+                "[{cluster=standalone, serviceUrlTls=null, brokers=1, serviceUrl=http://tengdeMBP:8080, " +
+                        "brokerServiceUrlTls=null, brokerServiceUrl=pulsar://tengdeMBP:6650}]");
+        Assert.assertEquals(result.get("total"), 1);
+        Assert.assertEquals(result.get("pageSize"), 1);
+    }
+}