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);
+ }
+}