Support namespace page (#113)
* ### 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/build.gradle b/build.gradle
index d78f921..723e0e9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -71,6 +71,7 @@
compile group: 'com.github.pagehelper', name: 'pagehelper-spring-boot-starter', version: pageHelperVersion
compile group: 'org.mockito', name: 'mockito-core', version: mockitoVersion
compile group: 'com.google.guava', name: 'guava', version: guavaVersion
+ compile group: 'com.google.code.gson', name: 'gson', version: gsonVersion
compile group: 'org.apache.pulsar', name: 'pulsar-client', version: pulsarVersion
compile group: 'org.apache.pulsar', name: 'pulsar-common', version: pulsarVersion
compile group: 'io.springfox', name: 'springfox-swagger2', version: swagger2Version
diff --git a/front-end/src/api/brokerStats.js b/front-end/src/api/brokerStats.js
new file mode 100644
index 0000000..eb66f1d
--- /dev/null
+++ b/front-end/src/api/brokerStats.js
@@ -0,0 +1,23 @@
+/*
+ * 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 fetchBrokerStats() {
+ return request({
+ url: BASE_URL_V2 + `/broker-stats/topics`,
+ method: 'get'
+ })
+}
diff --git a/front-end/src/api/namespaces.js b/front-end/src/api/namespaces.js
index 25ab87b..e1c399a 100644
--- a/front-end/src/api/namespaces.js
+++ b/front-end/src/api/namespaces.js
@@ -13,11 +13,13 @@
*/
import request from '@/utils/request'
+const SPRING_BASE_URL_V2 = '/pulsar-manager/admin/v2'
+
const BASE_URL_V2 = '/admin/v2'
export function fetchNamespaces(tenant, query) {
return request({
- url: BASE_URL_V2 + `/namespaces/${tenant}`,
+ url: SPRING_BASE_URL_V2 + `/namespaces/${tenant}`,
method: 'get',
params: { query }
})
@@ -101,6 +103,14 @@
})
}
+export function getPersistence(tenantNamespace) {
+ return request({
+ headers: { 'Content-Type': 'application/json' },
+ url: BASE_URL_V2 + `/namespaces/${tenantNamespace}/persistence`,
+ method: 'get'
+ })
+}
+
export function setPersistence(tenantNamespace, data) {
return request({
headers: { 'Content-Type': 'application/json' },
@@ -145,6 +155,14 @@
})
}
+export function getRetention(tenantNamespace) {
+ return request({
+ headers: { 'Content-Type': 'application/json' },
+ url: BASE_URL_V2 + `/namespaces/${tenantNamespace}/retention`,
+ method: 'get'
+ })
+}
+
export function setRetention(tenantNamespace, data) {
return request({
headers: { 'Content-Type': 'application/json' },
@@ -188,6 +206,24 @@
})
}
+export function setSubscribeRate(tenantNamespace, data) {
+ return request({
+ headers: { 'Content-Type': 'application/json' },
+ url: BASE_URL_V2 + `/namespaces/${tenantNamespace}/subscribeRate`,
+ method: 'post',
+ data
+ })
+}
+
+export function setSubscriptionDispatchRate(tenantNamespace, data) {
+ return request({
+ headers: { 'Content-Type': 'application/json' },
+ url: BASE_URL_V2 + `/namespaces/${tenantNamespace}/subscriptionDispatchRate`,
+ method: 'post',
+ data
+ })
+}
+
export function clearBacklog(tenantNamespace) {
return request({
headers: { 'Content-Type': 'application/json' },
@@ -308,3 +344,12 @@
data
})
}
+
+export function setSchemaValidationEnforced(tenantNamespace, data) {
+ return request({
+ headers: { 'Content-Type': 'application/json' },
+ url: BASE_URL_V2 + `/namespaces/${tenantNamespace}/schemaValidationEnforced`,
+ method: 'post',
+ data
+ })
+}
diff --git a/front-end/src/api/topics.js b/front-end/src/api/topics.js
index 1a88ecc..caa6f59 100644
--- a/front-end/src/api/topics.js
+++ b/front-end/src/api/topics.js
@@ -13,6 +13,8 @@
*/
import request from '@/utils/request'
+const SPRING_BASE_URL_V2 = '/pulsar-manager/admin/v2'
+
const BASE_URL_V2 = '/admin/v2'
export function fetchTopics(tenant, namespace, query) {
@@ -23,6 +25,14 @@
})
}
+export function fetchTopicsByPulsarManager(tenant, namespace, query) {
+ return request({
+ url: SPRING_BASE_URL_V2 + `/topics/${tenant}/${namespace}`,
+ method: 'get',
+ params: { query }
+ })
+}
+
export function fetchPersistentPartitonsTopics(tenant, namespace) {
return request({
url: BASE_URL_V2 + `/persistent/${tenant}/${namespace}/partitioned`,
diff --git a/front-end/src/components/MDinput/index.vue b/front-end/src/components/MDinput/index.vue
new file mode 100644
index 0000000..eba7733
--- /dev/null
+++ b/front-end/src/components/MDinput/index.vue
@@ -0,0 +1,356 @@
+<template>
+ <div :class="computedClasses" class="material-input__component">
+ <div :class="{iconClass:icon}">
+ <i v-if="icon" :class="['el-icon-' + icon]" class="el-input__icon material-input__icon" />
+ <input
+ v-if="type === 'email'"
+ v-model="currentValue"
+ :name="name"
+ :placeholder="fillPlaceHolder"
+ :readonly="readonly"
+ :disabled="disabled"
+ :autocomplete="autoComplete"
+ :required="required"
+ type="email"
+ class="material-input"
+ @focus="handleMdFocus"
+ @blur="handleMdBlur"
+ @input="handleModelInput"
+ >
+ <input
+ v-if="type === 'url'"
+ v-model="currentValue"
+ :name="name"
+ :placeholder="fillPlaceHolder"
+ :readonly="readonly"
+ :disabled="disabled"
+ :autocomplete="autoComplete"
+ :required="required"
+ type="url"
+ class="material-input"
+ @focus="handleMdFocus"
+ @blur="handleMdBlur"
+ @input="handleModelInput"
+ >
+ <input
+ v-if="type === 'number'"
+ v-model="currentValue"
+ :name="name"
+ :placeholder="fillPlaceHolder"
+ :step="step"
+ :readonly="readonly"
+ :disabled="disabled"
+ :autocomplete="autoComplete"
+ :max="max"
+ :min="min"
+ :minlength="minlength"
+ :maxlength="maxlength"
+ :required="required"
+ type="number"
+ class="material-input"
+ @focus="handleMdFocus"
+ @blur="handleMdBlur"
+ @input="handleModelInput"
+ >
+ <input
+ v-if="type === 'password'"
+ v-model="currentValue"
+ :name="name"
+ :placeholder="fillPlaceHolder"
+ :readonly="readonly"
+ :disabled="disabled"
+ :autocomplete="autoComplete"
+ :max="max"
+ :min="min"
+ :required="required"
+ type="password"
+ class="material-input"
+ @focus="handleMdFocus"
+ @blur="handleMdBlur"
+ @input="handleModelInput"
+ >
+ <input
+ v-if="type === 'tel'"
+ v-model="currentValue"
+ :name="name"
+ :placeholder="fillPlaceHolder"
+ :readonly="readonly"
+ :disabled="disabled"
+ :autocomplete="autoComplete"
+ :required="required"
+ type="tel"
+ class="material-input"
+ @focus="handleMdFocus"
+ @blur="handleMdBlur"
+ @input="handleModelInput"
+ >
+ <input
+ v-if="type === 'text'"
+ v-model="currentValue"
+ :name="name"
+ :placeholder="fillPlaceHolder"
+ :readonly="readonly"
+ :disabled="disabled"
+ :autocomplete="autoComplete"
+ :minlength="minlength"
+ :maxlength="maxlength"
+ :required="required"
+ type="text"
+ class="material-input"
+ @focus="handleMdFocus"
+ @blur="handleMdBlur"
+ @input="handleModelInput"
+ >
+ <span class="material-input-bar" />
+ <label class="material-label">
+ <slot />
+ </label>
+ </div>
+ </div>
+</template>
+
+<script>
+// source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue
+export default {
+ name: 'MdInput',
+ props: {
+ /* eslint-disable */
+ icon: String,
+ name: String,
+ type: {
+ type: String,
+ default: 'text'
+ },
+ value: [String, Number],
+ placeholder: String,
+ readonly: Boolean,
+ disabled: Boolean,
+ min: String,
+ max: String,
+ step: String,
+ minlength: Number,
+ maxlength: Number,
+ required: {
+ type: Boolean,
+ default: true
+ },
+ autoComplete: {
+ type: String,
+ default: 'off'
+ },
+ validateEvent: {
+ type: Boolean,
+ default: true
+ }
+ },
+ data() {
+ return {
+ currentValue: this.value,
+ focus: false,
+ fillPlaceHolder: null
+ }
+ },
+ computed: {
+ computedClasses() {
+ return {
+ 'material--active': this.focus,
+ 'material--disabled': this.disabled,
+ 'material--raised': Boolean(this.focus || this.currentValue) // has value
+ }
+ }
+ },
+ watch: {
+ value(newValue) {
+ this.currentValue = newValue
+ }
+ },
+ methods: {
+ handleModelInput(event) {
+ const value = event.target.value
+ this.$emit('input', value)
+ if (this.$parent.$options.componentName === 'ElFormItem') {
+ if (this.validateEvent) {
+ this.$parent.$emit('el.form.change', [value])
+ }
+ }
+ this.$emit('change', value)
+ },
+ handleMdFocus(event) {
+ this.focus = true
+ this.$emit('focus', event)
+ if (this.placeholder && this.placeholder !== '') {
+ this.fillPlaceHolder = this.placeholder
+ }
+ },
+ handleMdBlur(event) {
+ this.focus = false
+ this.$emit('blur', event)
+ this.fillPlaceHolder = null
+ if (this.$parent.$options.componentName === 'ElFormItem') {
+ if (this.validateEvent) {
+ this.$parent.$emit('el.form.blur', [this.currentValue])
+ }
+ }
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+ // Fonts:
+ $font-size-base: 16px;
+ $font-size-small: 18px;
+ $font-size-smallest: 12px;
+ $font-weight-normal: normal;
+ $font-weight-bold: bold;
+ $apixel: 1px;
+ // Utils
+ $spacer: 12px;
+ $transition: 0.2s ease all;
+ $index: 0px;
+ $index-has-icon: 30px;
+ // Theme:
+ $color-white: white;
+ $color-grey: #9E9E9E;
+ $color-grey-light: #E0E0E0;
+ $color-blue: #2196F3;
+ $color-red: #F44336;
+ $color-black: black;
+ // Base clases:
+ %base-bar-pseudo {
+ content: '';
+ height: 1px;
+ width: 0;
+ bottom: 0;
+ position: absolute;
+ transition: $transition;
+ }
+ // Mixins:
+ @mixin slided-top() {
+ top: - ($font-size-base + $spacer);
+ left: 0;
+ font-size: $font-size-base;
+ font-weight: $font-weight-bold;
+ }
+ // Component:
+ .material-input__component {
+ margin-top: 36px;
+ position: relative;
+ * {
+ box-sizing: border-box;
+ }
+ .iconClass {
+ .material-input__icon {
+ position: absolute;
+ left: 0;
+ line-height: $font-size-base;
+ color: $color-blue;
+ top: $spacer;
+ width: $index-has-icon;
+ height: $font-size-base;
+ font-size: $font-size-base;
+ font-weight: $font-weight-normal;
+ pointer-events: none;
+ }
+ .material-label {
+ left: $index-has-icon;
+ }
+ .material-input {
+ text-indent: $index-has-icon;
+ }
+ }
+ .material-input {
+ font-size: $font-size-base;
+ padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
+ display: block;
+ width: 100%;
+ border: none;
+ line-height: 1;
+ border-radius: 0;
+ &:focus {
+ outline: none;
+ border: none;
+ border-bottom: 1px solid transparent; // fixes the height issue
+ }
+ }
+ .material-label {
+ font-weight: $font-weight-normal;
+ position: absolute;
+ pointer-events: none;
+ left: $index;
+ top: 0;
+ transition: $transition;
+ font-size: $font-size-small;
+ }
+ .material-input-bar {
+ position: relative;
+ display: block;
+ width: 100%;
+ &:before {
+ @extend %base-bar-pseudo;
+ left: 50%;
+ }
+ &:after {
+ @extend %base-bar-pseudo;
+ right: 50%;
+ }
+ }
+ // Disabled state:
+ &.material--disabled {
+ .material-input {
+ border-bottom-style: dashed;
+ }
+ }
+ // Raised state:
+ &.material--raised {
+ .material-label {
+ @include slided-top();
+ }
+ }
+ // Active state:
+ &.material--active {
+ .material-input-bar {
+ &:before,
+ &:after {
+ width: 50%;
+ }
+ }
+ }
+ }
+ .material-input__component {
+ background: $color-white;
+ .material-input {
+ background: none;
+ color: $color-black;
+ text-indent: $index;
+ border-bottom: 1px solid $color-grey-light;
+ }
+ .material-label {
+ color: $color-grey;
+ }
+ .material-input-bar {
+ &:before,
+ &:after {
+ background: $color-blue;
+ }
+ }
+ // Active state:
+ &.material--active {
+ .material-label {
+ color: $color-blue;
+ }
+ }
+ // Errors:
+ &.material--has-errors {
+ &.material--active .material-label {
+ color: $color-red;
+ }
+ .material-input-bar {
+ &:before,
+ &:after {
+ background: transparent;
+ }
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/front-end/src/router/index.js b/front-end/src/router/index.js
index 4e46ac1..ab638bb 100644
--- a/front-end/src/router/index.js
+++ b/front-end/src/router/index.js
@@ -153,10 +153,10 @@
hidden: true
},
{
- path: 'namespaces/:tenant/:namespace/policies',
- component: () => import('@/views/management/namespaces/policies'),
- name: 'NamespacesPolicies',
- meta: { title: 'NamespacesPolicies', noCache: true },
+ path: 'namespaces/:tenant/:namespace/namespace',
+ component: () => import('@/views/management/namespaces/namespace'),
+ name: 'NamespacesInfo',
+ meta: { title: 'NamespacesInfo', noCache: true },
hidden: true
},
{
diff --git a/front-end/src/views/management/namespaces/index.vue b/front-end/src/views/management/namespaces/index.vue
index 2e25b20..ad00189 100644
--- a/front-end/src/views/management/namespaces/index.vue
+++ b/front-end/src/views/management/namespaces/index.vue
@@ -1,53 +1,22 @@
<template>
<div class="app-container">
<div class="createPost-container">
- <el-form ref="postForm" :model="postForm" class="form-container">
- <div class="createPost-main-container">
- <el-row>
- <el-col :span="12">
- <div class="postInfo-container">
- <el-row>
- <el-col :span="8">
- <el-form-item class="postInfo-container-item">
- <el-select v-model="postForm.tenant" placeholder="select tenant" @change="getNamespacesList(postForm.tenant)">
- <el-option v-for="(item,index) in tenantsListOptions" :key="item+index" :label="item" :value="item"/>
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- </div>
- </el-col>
- </el-row>
- </div>
+ <el-form :inline="true" :model="postForm" class="form-container">
+ <el-form-item class="postInfo-container-item" label="Tenant">
+ <el-select v-model="postForm.tenant" placeholder="select tenant" @change="getNamespacesList(postForm.tenant)">
+ <el-option v-for="(item,index) in tenantsListOptions" :key="item+index" :label="item" :value="item"/>
+ </el-select>
+ </el-form-item>
</el-form>
</div>
<div class="filter-container">
<el-input :placeholder="$t('table.namespace')" v-model="listQuery.namespace" 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-dropdown @command="handleCommand">
- <el-button class="filter-item" style="margin-left: 10px;" type="primary">{{ $t('table.quotas') }}<i class="el-icon-arrow-down el-icon--right"/>
- </el-button>
- <el-dropdown-menu slot="dropdown">
- <el-dropdown-item command="quotas-get">get</el-dropdown-item>
- <el-dropdown-item command="quotas-set">set</el-dropdown-item>
- <el-dropdown-item command="quotas-reset">reset</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- <el-autocomplete
- v-model="postForm.otherOptions"
- :fetch-suggestions="querySearch"
- class="filter-item inline-input"
- style="margin-left: 10px; width:400px"
- placeholder="select options"
- clearable
- @select="moreListOptionsChange"
- />
</div>
-
<div>
- <el-row :gutter="8">
- <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 14}" :xl="{span: 14}" 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"
@@ -55,18 +24,15 @@
border
fit
highlight-current-row
- style="width: 100%;"
- @row-click="getCurrentRow">
+ style="width: 100%;">
<el-table-column :label="$t('table.namespace')" min-width="50px" align="center">
<template slot-scope="scope">
- <router-link :to="'/management/tenantNamespace/' + scope.row.namespace" class="link-type">
- <span>{{ scope.row.namespace }}</span>
- </router-link>
+ <span>{{ scope.row.namespace }}</span>
</template>
</el-table-column>
- <el-table-column :label="$t('table.stats')" min-width="30px" align="center">
+ <el-table-column label="topics" min-width="30px" align="center">
<template slot-scope="scope">
- <span class="link-type" @click="getNamespacePolicies(scope.row.namespace)">stats</span>
+ <span>{{ scope.row.topics }}</span>
</template>
</el-table-column>
<div v-if="monitorEnable">
@@ -78,17 +44,16 @@
</div>
<el-table-column :label="$t('table.actions')" align="center" width="150" class-name="small-padding fixed-width">
<template slot-scope="scope">
+ <router-link :to="'/management/namespaces/' + scope.row.tenant +'/' + scope.row.namespace + '/namespace'">
+ <el-button type="primary" size="mini">{{ $t('table.edit') }}</el-button>
+ </router-link>
<el-button v-if="scope.row.status!='deleted'" size="mini" type="danger" @click="handleDelete(scope.row)">{{ $t('table.delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getNamespaces" />
</el-col>
- <el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 10}" :xl="{span: 10}" style="margin-bottom:30px;">
- <jsonEditor :value="jsonValue"/>
- </el-col>
</el-row>
-
</div>
<el-dialog :visible.sync="dialogFormVisible">
@@ -98,292 +63,8 @@
<el-input v-model="temp.namespace" placeholder="Please input namespace"/>
</el-form-item>
</div>
- <div v-else-if="dialogStatus==='grant-permission'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item :label="$t('table.grant')" prop="grant">
- <el-drag-select v-model="temp.actions" style="width:300px;" multiple placeholder="Please select consumer or produce">
- <el-option v-for="item in actionsListOptions" :label="item.label" :value="item.value" :key="item.value" />
- </el-drag-select>
- </el-form-item>
- <el-form-item :label="$t('table.role')" prop="role">
- <el-input v-model="temp.role" style="width:300px;" />
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='revoke-permission'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item :label="$t('table.role')" prop="role">
- <el-input v-model="temp.role"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-clusters'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item :label="$t('table.clusters')" prop="clusters">
- <el-drag-select v-model="temp.clusters" style="width:330px;" multiple placeholder="Please select clusters">
- <el-option v-for="item in clusterListOptions" :label="item.label" :value="item.value" :key="item.value" />
- </el-drag-select>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-backlog-quota'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item :label="$t('table.limit')" prop="limit">
- <el-input v-model="temp.limit" placeholder="Please select limit"/>
- </el-form-item>
- <el-form-item :label="$t('table.policies')" prop="policy">
- <el-select v-model="temp.policy" placeholder="Please select polices">
- <el-option v-for="item in policiesListOptions" :label="item.label" :value="item.value" :key="item.value" />
- </el-select>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='remove-backlog-quota'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-persistence'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="ackQuorum" prop="ackQuorum">
- <el-input v-model="temp.ackQuorum"/>
- </el-form-item>
- <el-form-item label="ensemble" prop="ensemble">
- <el-input v-model="temp.ensemble"/>
- </el-form-item>
- <el-form-item label="writeQuorum" prop="writeQuorum">
- <el-input v-model="temp.writeQuorum"/>
- </el-form-item>
- <el-form-item label="deleteMaxRate" prop="deleteMaxRate">
- <el-input v-model="temp.deleteMaxRate"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-message-ttl'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="messageTTL" prop="messageTTL">
- <el-input v-model="temp.messageTTL"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-anti-affinity-group'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="group" prop="group">
- <el-input v-model="temp.group" placeholder="Please input group"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='delete-anti-affinity-group'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-deduplication'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="deduplication">
- <el-switch
- v-model="temp.deduplication"
- active-color="#13ce66"
- inactive-color="#ff4949"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-retention'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="retentionSize" prop="retentionSize">
- <el-input v-model="temp.retentionSize" placeholder="Please input retentionSize"/>
- </el-form-item>
- <el-form-item label="retentionTime" prop="retentionTime">
- <el-input v-model="temp.retentionTime" placeholder="Please input retentionTime"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-dispatch-rate'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="byteDispatchRate" prop="byteDispatchRate">
- <el-input v-model="temp.byteDispatchRate"/>
- </el-form-item>
- <el-form-item label="dispatchRatePeriod" prop="dispatchRatePeriod">
- <el-input v-model="temp.dispatchRatePeriod"/>
- </el-form-item>
- <el-form-item label="msgDispatchRate" prop="msgDispatchRate">
- <el-input v-model="temp.msgDispatchRate"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='unsubscribe'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="unsubBundle" prop="unsubBundle">
- <el-input v-model="temp.unsubBundle"/>
- </el-form-item>
- <el-form-item label="subName" prop="subName">
- <el-input v-model="temp.subName" placeholder="Please input subName"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-encryption-required'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="encryption">
- <el-switch
- v-model="temp.encryption"
- active-color="#13ce66"
- inactive-color="#ff4949"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-subscription-auth-mode'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="auth-mode" prop="subscriptionAuthMode">
- <el-select v-model="temp.subscriptionAuthMode" style="width:330px;" placeholder="Please select authMode">
- <el-option v-for="item in authModeListOptions" :label="item.label" :value="item.value" :key="item.value" />
- </el-select>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-max-producers-per-topic'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="Topic" prop="maxProducersPerTopic">
- <el-input v-model="temp.maxProducersPerTopic" placeholder="maxProducersPerTopic for a namespace"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-max-consumers-per-topic'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="Topic" prop="maxConsumersPerTopic">
- <el-input v-model="temp.maxConsumersPerTopic" placeholder="maxConsumersPerTopic for a namespace"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-max-consumers-per-subscription'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="subName" prop="maxConsumersPerSub">
- <el-input v-model="temp.maxConsumersPerSub" placeholder="Get maxConsumersPerSubscription for a namespace"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-compaction-threshold'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="threshold" prop="threshold">
- <el-input v-model="temp.threshold" placeholder="Set compactionThreshold for a namespace"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-offload-threshold'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="thresholdSize" prop="thresholdSize">
- <el-input v-model="temp.thresholdSize" placeholder="Set offloadThreshold for a namespace"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-offload-deletion-lag'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="deletionLag" prop="deletionLag">
- <el-input v-model="temp.deletionLag" placeholder="Get offloadDeletionLag, in minutes, for a namespace"/>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='clear-offload-deletion-lag'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='set-schema-autoupdate-strategy'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <el-form-item label="compatibility" prop="compatibility">
- <el-select v-model="temp.compatibility" style="width:330px;" placeholder="Please select">
- <el-option v-for="item in compatibilityListOptions" :label="item.label" :value="item.value" :key="item.value" />
- </el-select>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='clear-backlog'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='quotas-get'||dialogStatus==='quotas-reset'||dialogStatus==='unload'||dialogStatus==='split-bundle'">
- <div v-if="dialogStatus==='unload'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='split-bundle'">
- <el-form-item :label="$t('table.namespace')">
- <span>{{ currentNamespace }}</span>
- </el-form-item>
- <!-- <el-form-item label="splitBundle" prop="splitBundle">
- <el-input v-model="temp.splitBundle"/>
- </el-form-item> -->
- <el-form-item label="splitUnload">
- <el-switch
- v-model="temp.splitUnload"
- active-color="#13ce66"
- inactive-color="#ff4949"/>
- </el-form-item>
- </div>
- <el-form-item label="startBundle" prop="startBundle">
- <el-select v-model="temp.startBundle" style="width:330px;" placeholder="Please select startBundle" @focus="getBundleList()">
- <el-option v-for="(item,index) in startBundleListOptions" :key="item+index" :label="item" :value="item"/>
- </el-select>
- </el-form-item>
- <el-form-item label="stopBundle" prop="stopBundle">
- <el-select v-model="temp.stopBundle" style="width:330px;" placeholder="Please select stopBundle" @focus="getBundleList()">
- <el-option v-for="(item,index) in stopBundleListOptions" :key="item+index" :label="item" :value="item"/>
- </el-select>
- </el-form-item>
- </div>
- <div v-else-if="dialogStatus==='quotas-set'">
- <el-form-item label="startBundle" prop="setStartBundle">
- <el-select v-model="temp.setStartBundle" style="width:330px;" placeholder="Please select startBundle" @focus="getBundleList()">
- <el-option v-for="(item,index) in startBundleListOptions" :key="item+index" :label="item" :value="item"/>
- </el-select>
- </el-form-item>
- <el-form-item label="stopBundle" prop="setsStopBundle">
- <el-select v-model="temp.setStopBundle" style="width:330px;" placeholder="Please select stopBundle" @focus="getBundleList()">
- <el-option v-for="(item,index) in stopBundleListOptions" :key="item+index" :label="item" :value="item"/>
- </el-select>
- </el-form-item>
- <el-form-item label="dynamic">
- <el-switch
- v-model="temp.dynamic"
- active-color="#13ce66"
- inactive-color="#ff4949"/>
- </el-form-item>
- <el-form-item label="bandwidthIn" prop="bandwidthIn">
- <el-input v-model="temp.bandwidthIn" placeholder="expected inbound bandwidth (bytes/second)"/>
- </el-form-item>
- <el-form-item label="bandwidthOut" prop="bandwidthOut">
- <el-input v-model="temp.bandwidthOut" placeholder="expected outbound bandwidth (bytes/second)"/>
- </el-form-item>
- <el-form-item label="memory" prop="memory">
- <el-input v-model="temp.memory" placeholder="expected memory usage (Mbytes)"/>
- </el-form-item>
- <el-form-item label="msgRateIn" prop="msgRateIn">
- <el-input v-model="temp.msgRateIn" placeholder="expected incoming messages per second"/>
- </el-form-item>
- <el-form-item label="msgRateOut" prop="msgRateOut">
- <el-input v-model="temp.msgRateOut" placeholder="expected outgoing messages per second"/>
- </el-form-item>
+ <div v-if="dialogStatus==='delete'">
+ <h4>Are you sure you want to delete this namespace {{ temp.tenant }}/{{ temp.namespace }}?</h4>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
@@ -398,63 +79,24 @@
<script>
import {
fetchNamespaces,
- fetchNamespacePolicies,
putNamespace,
- deleteNamespace,
- grantPermissions,
- revokePermissions,
- setClusters,
- setBacklogQuota,
- removeBacklogQuota,
- setPersistence,
- setMessageTtl,
- setAntiAffinityGroup,
- deleteAntiAffinityGroup,
- setDeduplication,
- setRetention,
- unloadBundle,
- splitBundle,
- setDispatchRate,
- clearBacklog,
- unsubscribe,
- unsubscribeByBundle,
- setEncryptionRequired,
- setSubscriptionAuthMode,
- setMaxProducersPerTopic,
- setMaxConsumersPerTopic,
- setMaxConsumersPerSubscription,
- setCompactionThreshold,
- setOffloadThreshold,
- setOffloadDeletionLag,
- clearOffloadDeletionLag,
- setSchemaAutoupdateStrategy
+ deleteNamespace
} from '@/api/namespaces'
-import {
- getResourceQuotasByNamespace,
- getResourceQuotas,
- setResourceQuotas,
- setResourceQuotasByNamespace,
- removeResourceQuotasByNamespace
-} from '@/api/resource-quotas'
-import { grafanaSearch } from '@/api/grafana'
import { fetchTenants } from '@/api/tenants'
import { fetchClusters } 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 jsonEditor from '@/components/JsonEditor'
import { validateEmpty } from '@/utils/validate'
import ElDragSelect from '@/components/DragSelect' // base on element-ui
-
const defaultForm = {
tenant: '',
otherOptions: ''
}
-
export default {
name: 'Namespaces',
components: {
Pagination,
- jsonEditor,
ElDragSelect
},
directives: { waves },
@@ -465,8 +107,6 @@
clusters: [],
clusterListOptions: [],
tenantsListOptions: [],
- policiesListOptions: [],
- actionsListOptions: [],
authModeListOptions: [],
moreListOptions: [],
startBundleListOptions: [],
@@ -478,7 +118,6 @@
localList: [],
searchList: [],
total: 0,
- jsonValue: {},
listLoading: true,
policiesListLoading: false,
currentNamespace: '',
@@ -497,116 +136,34 @@
namespace: '',
limit: '',
actions: [],
- clusters: [],
- subscriptionAuthMode: '',
- policy: '',
- ackQuorum: 0,
- ensemble: 0,
- writeQuorum: 0,
- deleteMaxRate: 0.0,
- messageTTL: 0,
- group: '',
- deduplication: false,
- retentionSize: '',
- retentionTime: '',
- unloadBundle: '',
- splitBundle: '',
- splitUnload: false,
- byteDispatchRate: -1,
- dispatchRatePeriod: 1,
- msgDispatchRate: 1,
- clearBundle: '',
- clearForce: false,
- clearSub: '',
- unsubBundle: '',
- encryption: false,
- maxProducersPerTopic: 0,
- maxConsumersPerTopic: 0,
- maxConsumersPerSub: 0,
- threshold: 0,
- thresholdSize: -1,
- deletionLag: -1,
- compatibility: 0,
- startBundle: '0x00000000',
- stopBundle: '0x40000000',
- setStartBundle: '0x00000000',
- setStopBundle: '0x40000000',
- bandwidthIn: 0,
- bandwidthOut: 0,
- memory: '',
- msgRateIn: 0,
- msgRateOut: 0,
- dynamic: false,
- subName: ''
+ clusters: []
},
dialogFormVisible: false,
dialogStatus: '',
rules: {
namespace: [{ required: true, message: 'namespace is required', trigger: 'blur' }],
grant: [{ required: true, message: 'grant is required', trigger: 'blur' }],
- clusters: [{ required: true, message: 'clusters is required', trigger: 'blur' }],
- limit: [{ required: true, message: 'limit is required', trigger: 'blur' }],
- policy: [{ required: true, message: 'policy is required', trigger: 'blur' }],
- role: [{ required: true, message: 'role is required', trigger: 'blur' }],
- ackQuorum: [{ required: true, message: 'ackQuorum is required', trigger: 'blur' }],
- ensemble: [{ required: true, message: 'ensemble is required', trigger: 'blur' }],
- writeQuorum: [{ required: true, message: 'writeQuorum is required', trigger: 'blur' }],
- deleteMaxRate: [{ required: true, message: 'deleteMaxRate is required', trigger: 'blur' }],
- messageTTL: [{ required: true, message: 'messageTTL is required', trigger: 'blur' }],
- group: [{ required: true, message: 'group is required', trigger: 'blur' }],
- retentionSize: [{ required: true, message: 'retentionSize is required', trigger: 'blur' }],
- retentionTime: [{ required: true, message: 'retentionTime is required', trigger: 'blur' }],
- stopBundle: [{ required: true, message: 'stopBundle is required', trigger: 'blur' }],
- startBundle: [{ required: true, message: 'startBundle is required', trigger: 'blur' }],
- bandwidthIn: [{ required: true, message: 'bandwidthIn is required', trigger: 'blur' }],
- bandwidthOut: [{ required: true, message: 'bandwidthOut is required', trigger: 'blur' }],
- memory: [{ required: true, message: 'memory is required', trigger: 'blur' }],
- msgRateIn: [{ required: true, message: 'msgRateIn is required', trigger: 'blur' }],
- msgRateOut: [{ required: true, message: 'msgRateOut is required', trigger: 'blur' }],
- subName: [{ required: true, message: 'subName is required', trigger: 'blur' }],
- subscriptionAuthMode: [{ required: true, message: 'subscriptionAuthMode is required', trigger: 'blur' }],
- maxProducersPerTopic: [{ required: true, message: 'maxProducersPerTopic is required', trigger: 'blur' }],
- maxConsumersPerTopic: [{ required: true, message: 'maxConsumersPerTopic is required', trigger: 'blur' }],
- maxConsumersPerSub: [{ required: true, message: 'maxConsumersPerTopic is required', trigger: 'blur' }],
- threshold: [{ required: true, message: 'threshold is required', trigger: 'blur' }],
- thresholdSize: [{ required: true, message: 'thresholdSize is required', trigger: 'blur' }],
- deletionLag: [{ required: true, message: 'deletionLag is required', trigger: 'blur' }]
+ clusters: [{ required: true, message: 'clusters is required', trigger: 'blur' }]
}
}
},
created() {
+ this.tenant = this.$route.params && this.$route.params.tenant
if (process.env.GRAFANA_ENABLE) {
- this.getGrafanaSearch()
+ // this.getGrafanaSearch()
} else {
this.getNamespaces()
}
- this.tenant = this.$route.params && this.$route.params.tenant
+ this.postForm.tenant = this.tenant
this.getRemoteTenantsList()
},
mounted() {
- this.moreListOptions = this.loadAllOptions()
- this.actionsListOptions = [{ value: 'produce', label: 'produce' }, { value: 'consume', label: 'consume' }]
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] })
}
})
- this.policiesListOptions = [
- { value: 'producer_request_hold', label: 'producer_request_hold' },
- { value: 'producer_exception', label: 'producer_exception' },
- { value: 'consumer_backlog_eviction', label: 'consumer_backlog_eviction' }
- ]
- this.compatibilityListOptions = [
- { value: 0, label: 'AutoUpdateDisabled' },
- { value: 1, label: 'Backward' },
- { value: 2, label: 'Forward' },
- { value: 3, label: 'Full' }
- ]
- this.authModeListOptions = [
- { value: 0, label: 'None' },
- { value: 1, label: 'Prefix' }
- ]
},
methods: {
getNamespaces() {
@@ -620,12 +177,21 @@
this.tenant = 'public'
}
fetchNamespaces(this.tenant, this.listQuery).then(response => {
- for (var i = 0; i < response.data.length; i++) {
+ for (var i = 0; i < response.data.data.length; i++) {
if (this.monitorEnable) {
- const monitorUrl = process.env.GRAFANA_ADDRESS + this.grafanaUrl + '?refresh=1m&' + 'var-namespace=' + response.data[i]
- this.localList.push({ 'namespace': response.data[i], 'monitor': monitorUrl })
+ const monitorUrl = process.env.GRAFANA_ADDRESS + this.grafanaUrl + '?refresh=1m&' + 'var-namespace=' + response.data.data[i].namespace
+ this.localList.push({
+ 'tenant': this.tenant,
+ 'namespace': response.data.data[i].namespace,
+ 'topics': response.data.data[i].topics,
+ 'monitor': monitorUrl
+ })
} else {
- this.localList.push({ 'namespace': response.data[i] })
+ this.localList.push({
+ 'tenant': this.tenant,
+ 'namespace': response.data.data[i].namespace,
+ 'topics': response.data.data[i].topics
+ })
}
}
this.total = this.localList.length
@@ -654,16 +220,6 @@
}
this.listLoading = false
},
- getNamespacePolicies(namespace) {
- // this.policiesListLoading = true
- fetchNamespacePolicies(namespace).then(response => {
- this.jsonValue = response.data
- // Just to simulate the time of the request
- setTimeout(() => {
- this.policiesListLoading = false
- }, 1.5 * 1000)
- })
- },
handleFilter() {
this.getNamespaces()
},
@@ -672,47 +228,7 @@
namespace: '',
limit: '',
actions: [],
- clusters: [],
- subscriptionAuthMode: '',
- policy: '',
- ackQuorum: 0,
- ensemble: 0,
- writeQuorum: 0,
- deleteMaxRate: 0.0,
- messageTTL: 0,
- group: '',
- deduplication: false,
- retentionSize: '',
- retentionTime: '',
- unloadBundle: '',
- splitBundle: '',
- splitUnload: false,
- byteDispatchRate: -1,
- dispatchRatePeriod: 1,
- msgDispatchRate: 1,
- clearBundle: '',
- clearForce: false,
- clearSub: '',
- unsubBundle: '',
- encryption: false,
- maxProducersPerTopic: 0,
- maxConsumersPerTopic: 0,
- maxConsumersPerSub: 0,
- threshold: 0,
- thresholdSize: -1,
- deletionLag: -1,
- compatibility: 0,
- startBundle: '0x00000000',
- stopBundle: '0x40000000',
- setStartBundle: '0x00000000',
- setStopBundle: '0x40000000',
- bandwidthIn: 0,
- bandwidthOut: 0,
- memory: '',
- msgRateIn: 0,
- msgRateOut: 0,
- dynamic: false,
- subName: ''
+ clusters: []
}
},
handleCreate() {
@@ -734,13 +250,21 @@
})
},
handleDelete(row) {
- deleteNamespace(row.namespace).then((response) => {
+ this.dialogStatus = 'delete'
+ this.dialogFormVisible = true
+ this.temp.tenant = row.tenant
+ this.temp.namespace = row.namespace
+ },
+ deleteData() {
+ var tenantNamespace = this.temp.tenant + '/' + this.temp.namespace
+ deleteNamespace(tenantNamespace).then((response) => {
this.$notify({
title: 'success',
message: 'delete success',
type: 'success',
duration: 2000
})
+ this.dialogFormVisible = false
this.localList = []
this.getNamespaces()
})
@@ -748,8 +272,9 @@
getRemoteTenantsList() {
fetchTenants().then(response => {
if (!response.data) return
- this.tenantsListOptions = response.data
- console.log(this.tenantsListOptions)
+ for (var i = 0; i < response.data.total; i++) {
+ this.tenantsListOptions.push(response.data.data[i].tenant)
+ }
})
},
getNamespacesList(tenant) {
@@ -757,24 +282,6 @@
this.localList = []
this.getNamespaces()
},
- moreListOptionsChange(item) {
- if (this.currentNamespace.length <= 0) {
- this.$notify({
- title: 'error',
- message: 'Please select any one namespace in table',
- type: 'error',
- duration: 3000
- })
- this.postForm.otherOptions = ''
- return
- }
- this.dialogStatus = item.value
- this.dialogFormVisible = true
- this.postForm.otherOptions = ''
- this.$nextTick(() => {
- this.$refs['temp'].clearValidate()
- })
- },
querySearch(queryString, cb) {
var moreListOptions = this.moreListOptions
var results = moreListOptions.filter(this.createFilterOptions(queryString))
@@ -785,42 +292,6 @@
return (moreListOptions.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0)
}
},
- loadAllOptions() {
- const options = [
- { 'value': 'set-clusters' },
- { 'value': 'set-backlog-quota' },
- { 'value': 'remove-backlog-quota' },
- { 'value': 'set-persistence' },
- { 'value': 'set-message-ttl' },
- { 'value': 'set-anti-affinity-group' },
- { 'value': 'delete-anti-affinity-group' },
- { 'value': 'set-deduplication' },
- { 'value': 'set-retention' },
- { 'value': 'unload' },
- { 'value': 'split-bundle' },
- { 'value': 'set-dispatch-rate' },
- { 'value': 'clear-backlog' },
- { 'value': 'unsubscribe' },
- { 'value': 'set-encryption-required' },
- { 'value': 'set-subscription-auth-mode' },
- { 'value': 'set-max-producers-per-topic' },
- { 'value': 'set-max-consumers-per-topic' },
- { 'value': 'set-max-consumers-per-subscription' },
- { 'value': 'set-compaction-threshold' },
- { 'value': 'set-offload-threshold' },
- { 'value': 'set-offload-deletion-lag' },
- { 'value': 'clear-offload-deletion-lag' },
- { 'value': 'set-schema-autoupdate-strategy' }
- ]
- if (process.env.USE_TLS) {
- options.push({ 'value': 'grant-permission' })
- options.push({ 'value': 'revoke-permission' })
- }
- return options
- },
- getCurrentRow(item) {
- this.currentNamespace = item.namespace
- },
handleOptions() {
this.$refs['temp'].validate((valid) => {
if (valid) {
@@ -828,553 +299,9 @@
case 'create':
this.createNamespace()
break
- case 'grant-permission':
- this.confirmGrantPermission()
+ case 'delete':
+ this.deleteData()
break
- case 'revoke-permission':
- this.confirmRevokePermissions()
- break
- case 'set-clusters':
- this.confirmSetClusters()
- break
- case 'set-backlog-quota':
- this.confirmSetBacklogQuota()
- break
- case 'remove-backlog-quota':
- this.confirmRemoveBacklogQuota()
- break
- case 'set-persistence':
- this.confirmSetPersistence()
- break
- case 'set-message-ttl':
- this.confirmSetMessageTtl()
- break
- case 'set-anti-affinity-group':
- this.confirmSetAntiAffinityGroup()
- break
- case 'delete-anti-affinity-group':
- this.confirmDeleteAntiAffinityGroup()
- break
- case 'set-deduplication':
- this.confirmSetDeduplication()
- break
- case 'set-retention':
- this.confirmSetRetention()
- break
- case 'unload':
- this.confirmUnload()
- break
- case 'split-bundle':
- this.confirmSplitBundle()
- break
- case 'set-dispatch-rate':
- this.confirmSetDispatchRate()
- break
- case 'clear-backlog':
- this.confirmClearBacklog()
- break
- case 'unsubscribe':
- this.confirmUnsubscribe()
- break
- case 'set-encryption-required':
- this.confirmSetEncryptionRequired()
- break
- case 'set-subscription-auth-mode':
- this.confirmSetSubscriptionAuthMode()
- break
- case 'set-max-producers-per-topic':
- this.confirmSetMaxProducersPerTopic()
- break
- case 'set-max-consumers-per-topic':
- this.confirmSetMaxConsumersPerTopic()
- break
- case 'set-max-consumers-per-subscription':
- this.confirmSetMaxConsumersPerSubscription()
- break
- case 'set-compaction-threshold':
- this.confirmSetCompactionThreshold()
- break
- case 'set-offload-threshold':
- this.confirmSetOffloadThreshold()
- break
- case 'set-offload-deletion-lag':
- this.confirmSetOffloadDeletionLag()
- break
- case 'clear-offload-deletion-lag':
- this.confirmClearOffloadDeletionLag()
- break
- case 'set-schema-autoupdate-strategy':
- this.confirmSetSchemaAutoupdateStrategy()
- break
- case 'quotas-get':
- this.confirmQuotasGet()
- break
- case 'quotas-set':
- this.confirmQuotasSet()
- break
- case 'quotas-reset':
- this.confirmQuotasReset()
- break
- }
- }
- })
- },
- confirmGrantPermission() {
- grantPermissions(this.currentNamespace, this.temp.role, this.temp.actions).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Add success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmRevokePermissions() {
- revokePermissions(this.currentNamespace, this.temp.role).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Delete success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetClusters() {
- setClusters(this.currentNamespace, this.temp.clusters).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Add clusters success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetBacklogQuota() {
- setBacklogQuota(this.currentNamespace, this.temp).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Add Backlog Quota success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmRemoveBacklogQuota() {
- removeBacklogQuota(this.currentNamespace).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Delete Backlog Quota success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetPersistence() {
- const data = {
- 'bookkeeper_ack_quorum': this.temp.ackQuorum,
- 'bookkeeper_ensemble': this.temp.ensemble,
- 'bookkeeper_write_quorum': this.temp.writeQuorum,
- 'ml_mark_delete_max_rate': this.temp.deleteMaxRate
- }
- setPersistence(this.currentNamespace, data).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set persistence success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetMessageTtl() {
- setMessageTtl(this.currentNamespace, parseInt(this.temp.messageTTL)).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set messageTTL success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetAntiAffinityGroup() {
- setAntiAffinityGroup(this.currentNamespace, this.temp.group).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set AntiAffinityGroup success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmDeleteAntiAffinityGroup() {
- deleteAntiAffinityGroup(this.currentNamespace).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Delete AntiAffinityGroup success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetDeduplication() {
- setDeduplication(this.currentNamespace, this.temp.deduplication).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set deduplication success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetRetention() {
- const data = { 'size': this.temp.retentionSize, 'time': this.temp.retentionTime }
- setRetention(this.currentNamespace, data).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set Retention success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmUnload() {
- const bundle = this.temp.startBundle + '_' + this.temp.stopBundle
- unloadBundle(this.currentNamespace, bundle).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Unload success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSplitBundle() {
- // const data = { 'unload': this.temp.splitUnload }
- // problem split http 412
- // to do solve
- const bundle = this.temp.startBundle + '_' + this.temp.stopBundle
- splitBundle(this.currentNamespace, bundle, this.temp.splitUnload).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'splitBundle success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetDispatchRate() {
- const data = {
- 'dispatchThrottlingRateInByte': this.temp.byteDispatchRate,
- 'ratePeriodInSecond': this.temp.dispatchRatePeriod,
- 'dispatchThrottlingRateInMsg': this.temp.msgDispatchRate
- }
- setDispatchRate(this.currentNamespace, data).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'set DispatchRate success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmClearBacklog() {
- clearBacklog(this.currentNamespace).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'clearBacklog success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmUnsubscribe() {
- if (this.temp.unsubBundle.length > 0) {
- unsubscribeByBundle(this.currentNamespace, this.temp.unsubBundle, this.temp.unsubscribe).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'unsubscribe success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- } else {
- unsubscribe(this.currentNamespace, this.temp.subName).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'unsubscribe success for namespace',
- type: 'success',
- duration: 3000
- })
- })
- }
- },
- confirmSetEncryptionRequired() {
- setEncryptionRequired(this.currentNamespace, this.temp.encryption).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetSubscriptionAuthMode() {
- setSubscriptionAuthMode(this.currentNamespace, this.temp.subscriptionAuthMode).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetMaxProducersPerTopic() {
- setMaxProducersPerTopic(this.currentNamespace, this.temp.maxProducersPerTopic).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set max producers per topic success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetMaxConsumersPerTopic() {
- setMaxConsumersPerTopic(this.currentNamespace, this.temp.maxConsumersPerTopic).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set max consumers per topic success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetMaxConsumersPerSubscription() {
- setMaxConsumersPerSubscription(this.currentNamespace, this.temp.maxConsumersPerSub).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set max subscription per topic success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetCompactionThreshold() {
- setCompactionThreshold(this.currentNamespace, this.temp.threshold).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set threshold success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetOffloadThreshold() {
- setOffloadThreshold(this.currentNamespace, this.temp.thresholdSize).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set threshold success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetOffloadDeletionLag() {
- setOffloadDeletionLag(this.currentNamespace, this.temp.deletionLag).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set DeletionLag success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmClearOffloadDeletionLag() {
- clearOffloadDeletionLag(this.currentNamespace).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Clear DeletionLag success',
- type: 'success',
- duration: 3000
- })
- })
- },
- confirmSetSchemaAutoupdateStrategy() {
- // todo put method not allowed
- setSchemaAutoupdateStrategy(this.currentNamespace, this.temp.compatibility).then(response => {
- this.dialogFormVisible = false
- this.$notify({
- title: 'success',
- message: 'Set SchemaAutoupdateStrategy success',
- type: 'success',
- duration: 3000
- })
- })
- },
- handleCommand(command) {
- this.currentCommand = command
- switch (this.currentCommand) {
- case 'quotas-get':
- this.handleQuotasGet()
- break
- case 'quotas-set':
- this.handleQuotasSet()
- break
- case 'quotas-reset':
- this.handleQuotasReset()
- break
- }
- },
- handleQuotasGet() {
- if (this.currentNamespace.length > 0) {
- this.dialogStatus = 'quotas-get'
- this.dialogFormVisible = true
- this.$nextTick(() => {
- this.$refs['temp'].clearValidate()
- })
- } else {
- this.confirmQuotasGet()
- }
- },
- handleQuotasSet() {
- this.dialogStatus = 'quotas-set'
- this.dialogFormVisible = true
- this.$nextTick(() => {
- this.$refs['temp'].clearValidate()
- })
- },
- handleQuotasReset() {
- this.dialogStatus = 'quotas-reset'
- this.dialogFormVisible = true
- this.$nextTick(() => {
- this.$refs['temp'].clearValidate()
- })
- },
- confirmQuotasGet() {
- if (this.currentNamespace.length > 0) {
- const bundle = this.temp.startBundle + '_' + this.temp.stopBundle
- getResourceQuotasByNamespace(this.currentNamespace, bundle).then(response => {
- this.jsonValue = response.data
- this.dialogFormVisible = false
- return
- })
- return
- }
- getResourceQuotas().then(response => {
- this.jsonValue = response.data
- })
- },
- confirmQuotasSet() {
- const data = {
- 'msgRateIn': this.temp.msgRateIn,
- 'msgRateOut': this.temp.msgRateOut,
- 'bandwidthIn': this.temp.bandwidthIn,
- 'bandwidthOut': this.temp.bandwidthOut,
- 'memory': this.temp.memory,
- 'dynamic': this.temp.dynamic
- }
- if (this.currentNamespace.length > 0) {
- if (this.temp.setStartBundle.length <= 0 || this.temp.setStopBundle <= 0) {
- this.$notify({
- title: 'error',
- message: 'Please select startBundle and stopBundle',
- type: 'error',
- duration: 3000
- })
- return
- }
- const bundle = this.temp.setStartBundle + '_' + this.temp.setStopBundle
- setResourceQuotasByNamespace(this.currentNamespace, bundle, data).then(response => {
- this.$notify({
- title: 'success',
- message: 'Set resource quotas success for namespace',
- type: 'success',
- duration: 3000
- })
- this.dialogFormVisible = false
- })
- return
- }
- setResourceQuotas(data).then(response => {
- this.$notify({
- title: 'success',
- message: 'Set resource quotas success',
- type: 'success',
- duration: 3000
- })
- this.dialogFormVisible = false
- })
- },
- confirmQuotasReset() {
- if (this.currentNamespace.length <= 0) {
- this.$notify({
- title: 'error',
- message: 'Please select any one namespace in table',
- type: 'error',
- duration: 3000
- })
- return
- }
- const bundle = this.temp.startBundle + '_' + this.temp.stopBundle
- removeResourceQuotasByNamespace(this.currentNamespace, bundle).then(response => {
- this.$notify({
- title: 'success',
- message: 'Remove resource quotas success',
- type: 'success',
- duration: 3000
- })
- this.dialogFormVisible = false
- return
- })
- },
- getBundleList() {
- this.startBundleListOptions = []
- this.stopBundleListOptions = []
- if (this.currentNamespace.length <= 0) {
- this.$notify({
- title: 'error',
- message: 'Please select any one namespace in table',
- type: 'error',
- duration: 3000
- })
- return
- }
- fetchNamespacePolicies(this.currentNamespace).then(response => {
- this.startBundleListOptions = response.data.bundles.boundaries
- this.stopBundleListOptions = response.data.bundles.boundaries
- })
- },
- getGrafanaSearch() {
- grafanaSearch('', 'messaging').then(response => {
- for (var i = 0; i < response.data.length; i++) {
- if (response.data[i]['uri'] === 'db/messaging-metrics') {
- this.grafanaUrl = response.data[i]['url']
- this.monitorEnable = true
- this.getNamespaces()
}
}
})
diff --git a/front-end/src/views/management/namespaces/namespace.vue b/front-end/src/views/management/namespaces/namespace.vue
new file mode 100644
index 0000000..8d5e9b2
--- /dev/null
+++ b/front-end/src/views/management/namespaces/namespace.vue
@@ -0,0 +1,1475 @@
+<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="Tenant">
+ <el-select v-model="postForm.tenant" placeholder="select tenant" @change="getNamespacesList(postForm.tenant)">
+ <el-option v-for="(item,index) in tenantsListOptions" :key="item+index" :label="item" :value="item"/>
+ </el-select>
+ </el-form-item>
+ <el-form-item class="postInfo-container-item" label="Namespace">
+ <el-select v-model="postForm.namespace" placeholder="select namespace" @change="getNamespaceInfo(postForm.tenant, postForm.namespace)">
+ <el-option v-for="(item,index) in namespacesListOptions" :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="OVERVIEW" name="overview">
+ <el-table
+ :data="namespaceStats"
+ 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>Bundles</h4>
+ <hr class="split-line">
+ <div class="filter-container">
+ <!-- <el-input placeholder="Search Bundles" prefix-icon="el-icon-search" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilterBundle"/> -->
+ <!-- <el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilterBundle">{{ $t('table.search') }}</el-button> -->
+ <el-button class="filter-item" style="margin-left: 10px;" type="danger" icon="el-icon-delete" @click="handleUnloadAll">Unload All</el-button>
+ <el-button class="filter-item" style="margin-left: 10px;" type="danger" icon="el-icon-delete" @click="hanldeClearAllBacklog">Clear All Backlog</el-button>
+ </div>
+ <el-table
+ :key="tableKey"
+ :data="localList"
+ border
+ fit
+ highlight-current-row
+ style="width: 100%;">
+ <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="handleSplitBundle(scope.row)">Split</el-button>
+ <el-button size="medium" type="danger" @click="handleUnloadBundle(scope.row)">Unload</el-button>
+ <el-button size="medium" type="danger">Clear Backlog</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <!-- <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit"/> -->
+ </el-tab-pane>
+ <el-tab-pane label="TOPICS" name="topics">
+ <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="topicsListLoading"
+ :key="topicsTableKey"
+ :data="topicsList"
+ border
+ fit
+ highlight-current-row
+ style="width: 100%;">
+ <el-table-column label="Topic" min-width="50px" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.topic }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="Partitions" min-width="30px" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.partitions }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="Producers" min-width="30px" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.producers }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="Subscriptions" min-width="30px" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.subscriptions }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="In - msg/s" min-width="30px" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.inMsg }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="Out - msg/s" min-width="30px" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.outMsg }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="In - bytes/s" min-width="30px" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.inBytes }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="Out - bytes/s" min-width="30px" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.outBytes }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="Storage Size" min-width="30px" align="center">
+ <template slot-scope="scope">
+ <span>{{ scope.row.storageSize }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination v-show="topicsTotal>0" :total="topicsTotal" :page.sync="topicsListQuery.page" :limit.sync="topicsListQuery.limit" @pagination="getTopics" />
+ </el-col>
+ </el-row>
+ </el-tab-pane>
+ <el-tab-pane label="POLICIES" name="policies">
+ <h4>Namespace Name</h4>
+ <hr class="split-line">
+ <span>{{ tenantNamespace }}</span>
+ <h4>Clusters</h4>
+ <hr class="split-line">
+ <div class="component-item">
+ <div class="section-title">
+ <span>Replicated Clusters</span>
+ <el-tooltip :content="replicatedClustersContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ </div>
+ <el-select
+ v-model="replicationClustersValue"
+ style="width:500px;margin-top:20px"
+ multiple
+ placeholder="Please Select Cluster"
+ @change="handleReplicationsClusters()">
+ <el-option v-for="item in replicationClustersOptions" :key="item.value" :label="item.label" :value="item.value" />
+ </el-select>
+ </div>
+ <h4>Authorization
+ <el-tooltip :content="authorizationContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ </h4>
+ <hr class="split-line">
+ <el-form>
+ <el-tag
+ v-for="tag in dynamicTags"
+ :label="tag"
+ :key="tag"
+ :disable-transitions="false"
+ style="margin-top:20px"
+ class="role-el-tag">
+ <div>
+ <span> {{ tag }} </span>
+ </div>
+ <el-select
+ v-model="roleMap[tag]"
+ multiple
+ placeholder="Please Select Options"
+ style="width:300px;"
+ @change="handleChangeOptions()">
+ <el-option
+ v-for="item in roleMapOptions[tag]"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ style="width:300px"/>
+ </el-select>
+ <el-button @click.prevent="handleClose(tag)">åˆ é™¤</el-button>
+ </el-tag>
+ <el-form-item style="margin-top:30px">
+ <el-input
+ v-if="inputVisible"
+ ref="saveTagInput"
+ v-model="inputValue"
+ style="margin-right:10px;width:200px;vertical-align:top"
+ size="small"
+ class="input-new-tag"
+ @keyup.enter.native="handleInputConfirm"
+ @blur="handleInputConfirm"
+ />
+ <el-button @click="showInput()">Add Role</el-button>
+ <!-- <el-button @click="revokeAllRole()">Revoke All</el-button> -->
+ </el-form-item>
+ </el-form>
+ <h4>Subscription Authentication</h4>
+ <hr class="split-line">
+ <div class="section-title">
+ <span>Subscription Authentication Mode</span>
+ <el-tooltip :content="subscriptionAuthenticationModeContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ </div>
+ <el-select
+ v-model="subscriptionAuthenticationMode"
+ placeholder="Please select Authentication"
+ style="margin-top:20px;width:300px"
+ @change="handleSubscriptionAuthMode()">
+ <el-option
+ v-for="item in subscriptionAuthenticationModeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"/>
+ </el-select>
+ <h4>Storage</h4>
+ <hr class="split-line">
+ <div class="section-title">
+ <span>Replication Factor</span>
+ <el-tooltip :content="replicationFactorContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ </div>
+ <el-form :inline="true" :model="form" :rules="rules">
+ <el-form-item prop="ensembelSize">
+ <md-input
+ v-model="form.ensembleSize"
+ class="md-input-style"
+ name="ensembelSize"
+ placeholder="Please input Ensemble"
+ @keyup.enter.native="handlePersistence">
+ Ensemble Size
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="writeQuorumSize">
+ <md-input
+ v-model="form.writeQuorumSize"
+ class="md-input-style"
+ name="writeQuorumSize"
+ placeholder="Please input Write Quorum Size"
+ @keyup.enter.native="handlePersistence">
+ Write Quorum Size
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="readQuorumSize">
+ <md-input
+ v-model="form.readQuorumSize"
+ class="md-input-style"
+ name="readQuorumSize"
+ placeholder="Please input Read Quorum Size"
+ @keyup.enter.native="handlePersistence">
+ Read Quorum Size
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="markDeleteMaxRate">
+ <md-input
+ v-model="form.markDeleteMaxRate"
+ class="md-input-style"
+ name="markDeleteMaxRate"
+ placeholder="Please input Delete Max Rate"
+ @keyup.enter.native="handlePersistence">
+ Mark Delete Max Rate
+ </md-input>
+ </el-form-item>
+ </el-form>
+ <el-form :inline="true" :model="form" :rules="rules">
+ <el-form-item prop="backlogQuotasLimit">
+ <span>Backlog Quotas Limit</span>
+ <el-tooltip :content="backlogQuotasLimitContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.backlogQuotasLimit"
+ class="md-input-style"
+ name="backlogQuotasLimit"
+ placeholder="Please input Backlog Quotas Limit"
+ @keyup.enter.native="handleBacklogQuota">
+ Backlog Quotas Limit
+ </md-input>
+ </el-form-item>
+ <el-form-item style="width:300px">
+ <span>Backlog Retention Policy</span>
+ <el-tooltip :content="backlogRententionPolicyContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <br>
+ <el-select
+ v-model="form.backlogRententionPolicy"
+ placeholder="Please select backlog rentention policy"
+ style="margin-top:36px;width:400px"
+ @change="handleBacklogQuota()">
+ <el-option
+ v-for="item in backlogRententionPolicyOption"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"/>
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <el-form :inline="true" :model="form" :rules="rules">
+ <el-form-item prop="encryptionRequire">
+ <span>Encryption Require</span>
+ <el-tooltip :content="encryptionRequireContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <br>
+ <el-radio-group
+ v-model="form.encryptionRequireRadio"
+ size="medium"
+ style="margin-top:30px;width:300px"
+ @change="handleEncryption()">
+ <el-radio label="Enabled"/>
+ <el-radio label="Disabled"/>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item prop="deduplication">
+ <span>Deduplication</span>
+ <el-tooltip :content="deduplicationContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <br>
+ <el-radio-group
+ v-model="form.deduplicationRadio"
+ size="medium"
+ style="margin-top:30px;width:300px"
+ @change="handleDeduplication()">
+ <el-radio label="Enabled"/>
+ <el-radio label="Disabled"/>
+ </el-radio-group>
+ </el-form-item>
+ </el-form>
+ <h4>Schema</h4>
+ <hr class="split-line">
+ <el-form :inline="true" :model="form" :rules="rules">
+ <el-form-item prop="autoUpdateStrategy">
+ <span>AutoUpdate Strategy</span>
+ <el-tooltip :content="autoUpdateStrategyContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <br>
+ <el-select
+ v-model="form.autoUpdateStrategy"
+ placeholder="Please select backlog autoUpdate strategy"
+ style="margin-top:30px;width:300px"
+ @change="handleSchemaAutoUpdateStrategy()">
+ <el-option
+ v-for="item in autoUpdateStrategyOption"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"/>
+ </el-select>
+ </el-form-item>
+ <el-form-item prop="schemaValidationEnforced">
+ <span>Schema Validation Enforced</span>
+ <el-tooltip :content="schemaValidationEnforcedContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <br>
+ <el-radio-group
+ v-model="form.schemaValidationEnforcedRadio"
+ size="medium"
+ style="margin-top:39px;width:300px"
+ @change="handleSchemaValidationEnforced()">
+ <el-radio label="Enabled"/>
+ <el-radio label="Disabled"/>
+ </el-radio-group>
+ </el-form-item>
+ </el-form>
+ <h4>Cleanup Policy</h4>
+ <hr class="split-line">
+ <el-form :inline="true" :model="form" :rules="rules">
+ <el-form-item prop="messageTTL">
+ <span>Message TTL(Unit Second)</span>
+ <el-tooltip :content="messageTTLContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.messageTTL"
+ class="md-input-style"
+ name="messageTTL"
+ placeholder="Please input Backlog Quotas Limit"
+ @keyup.enter.native="handleMessageTTL">
+ Default 0
+ </md-input>
+ </el-form-item>
+ </el-form>
+ <el-form :inline="true" :model="form" :rules="rules">
+ <el-form-item prop="retentionSize">
+ <span>Retention Size</span>
+ <el-tooltip :content="retentionSizeContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.retentionSize"
+ class="md-input-style"
+ name="retentionSize"
+ placeholder="Please input retention size"
+ @keyup.enter.native="handleRetention">
+ retention size default 0
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="retentionTime">
+ <span>Retention Time(Unit Minutes)</span>
+ <el-tooltip :content="retentionTimeContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.retentionTime"
+ class="md-input-style"
+ name="retentionTime"
+ placeholder="Please input Retention Time"
+ @keyup.enter.native="handleRetention">
+ Default 0
+ </md-input>
+ </el-form-item>
+ </el-form>
+ <el-form :inline="true" :model="form" :rules="rules">
+ <el-form-item prop="compactionThreshold">
+ <span>Compaction Threshold</span>
+ <el-tooltip :content="compactionThresholdContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.compactionThreshold"
+ class="md-input-style"
+ name="compactionThreshold"
+ placeholder="Please input retention size"
+ @keyup.enter.native="handleCompactionThreshold">
+ Default 0
+ </md-input>
+ </el-form-item>
+ </el-form>
+ <el-form :inline="true" :model="form" :rules="rules">
+ <el-form-item prop="offloadThreshold">
+ <span>Offload Threshold</span>
+ <el-tooltip :content="offloadThresholdContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.offloadThreshold"
+ class="md-input-style"
+ name="offloadThreshold"
+ placeholder="Please input Offload Threshold"
+ @keyup.enter.native="handleOffloadThreshold">
+ Default -1
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="offloadDeletionLag">
+ <span>Offload Deletion Lag</span>
+ <el-tooltip :content="offloadDeletionLagContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.offloadDeletionLag"
+ class="md-input-style"
+ name="offloadDeletionLag"
+ placeholder="Please input Retention Time"
+ @keyup.enter.native="handleOffloadDeletionLag">
+ Default -1
+ </md-input>
+ </el-form-item>
+ </el-form>
+ <h4>Throttling</h4>
+ <hr class="split-line">
+ <el-form :inline="true" :model="form" :rules="rules">
+ <el-form-item prop="maxProducersPerTopic">
+ <span>Max Producers Per Topic</span>
+ <el-tooltip :content="maxProducersPerTopicContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.maxProducersPerTopic"
+ class="md-input-style"
+ name="maxProducersPerTopic"
+ placeholder="Please input max Producers"
+ @keyup.enter.native="handleMaxProducersPerTopic">
+ Default 0
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="maxConsumersPerTopic">
+ <span>Max Consumers Per Topic</span>
+ <el-tooltip :content="maxConsumersPerTopicContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.maxConsumersPerTopic"
+ class="md-input-style"
+ name="maxConsumersPerTopic"
+ placeholder="Please input max Consumers"
+ @keyup.enter.native="handleMaxConsumersPerTopic">
+ Default 0
+ </md-input>
+ </el-form-item>
+ </el-form>
+ <el-form :inline="true" :model="form" :rules="rules" @submit.native.prevent>
+ <el-form-item prop="maxConsumerPerSub">
+ <span>Max Consumers Per Subscription</span>
+ <el-tooltip :content="maxConsumerPerSubContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.maxConsumerPerSub"
+ class="md-input-style"
+ name="maxConsumerPerSub"
+ placeholder="Please input max Consumers"
+ @keyup.enter.native="handleMaxConsuemrsPerSubscription">
+ Default 0
+ </md-input>
+ </el-form-item>
+ </el-form>
+ <h4>Dispatch Rate</h4>
+ <hr class="split-line">
+ <span>Dispatch Rate Per Topic</span>
+ <el-tooltip :content="dispatchRatePerTopicContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <el-form :inline="true" :model="form" :rules="rules">
+ <el-form-item prop="dispatchRatePerTopicBytes">
+ <span>bytes/second</span>
+ <md-input
+ v-model="form.dispatchRatePerTopicBytes"
+ class="md-input-style"
+ name="dispatchRatePerTopicBytes"
+ placeholder="Please input dispatch rate"
+ @keyup.enter.native="handleDispatchRatePerTopic">
+ Default -1
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="dispatchRatePerTopicMessage">
+ <span>message/second</span>
+ <md-input
+ v-model="form.dispatchRatePerTopicMessage"
+ class="md-input-style"
+ name="dispatchRatePerTopicMessage"
+ placeholder="Please input dispatch rate"
+ @keyup.enter.native="handleDispatchRatePerTopic">
+ Default -1
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="dispatchRatePerTopicPeriod">
+ <span>period(Unit second)</span>
+ <md-input
+ v-model="form.dispatchRatePerTopicPeriod"
+ class="md-input-style"
+ name="dispatchRatePerTopicPeriod"
+ placeholder="Please input dispatch rate"
+ @keyup.enter.native="handleDispatchRatePerTopic">
+ Default -1
+ </md-input>
+ </el-form-item>
+ </el-form>
+ <span>Dispatch Rate Per Subscription</span>
+ <el-tooltip :content="dispatchRatePerSubContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <el-form :inline="true" :model="form" :rules="rules" @submit.native.prevent>
+ <el-form-item prop="dispatchRatePerSubBytes">
+ <span>bytes/second</span>
+ <md-input
+ v-model="form.dispatchRatePerSubBytes"
+ class="md-input-style"
+ name="dispatchRatePerSubBytes"
+ placeholder="Please input dispatch rate"
+ @keyup.enter.native="handleDispatchRatePerSub">
+ Default -1
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="dispatchRatePerSubMessage">
+ <span>message/second</span>
+ <md-input
+ v-model="form.dispatchRatePerSubMessage"
+ class="md-input-style"
+ name="dispatchRatePerSubMessage"
+ placeholder="Please input dispatch rate"
+ @keyup.enter.native="handleDispatchRatePerSub">
+ Default -1
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="dispatchRatePerSubPeriod">
+ <span>period(Unit Second)</span>
+ <md-input
+ v-model="form.dispatchRatePerSubPeriod"
+ class="md-input-style"
+ name="dispatchRatePerSubPeriod"
+ placeholder="Please input dispatch rate"
+ @keyup.enter.native="handleDispatchRatePerSub">
+ Default -1
+ </md-input>
+ </el-form-item>
+ </el-form>
+ <span>Subscribe Rate Per Consumer</span>
+ <el-tooltip :content="subscribeRatePerConsumerContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <el-form :inline="true" :model="form" :rules="rules" @submit.native.prevent>
+ <el-form-item prop="subscribeRatePerConsumerSub">
+ <span>message/second</span>
+ <md-input
+ v-model="form.subscribeRatePerConsumerSub"
+ class="md-input-style"
+ name="subscribeRatePerConsumerSub"
+ placeholder="Please input subscribe rate"
+ @keyup.enter.native="handleSubscribeRate">
+ Default -1
+ </md-input>
+ </el-form-item>
+ <el-form-item prop="subscribeRatePerConsumerPeriod">
+ <span>period(Unit Second)</span>
+ <md-input
+ v-model="form.subscribeRatePerConsumerPeriod"
+ class="md-input-style"
+ name="subscribeRatePerConsumerPeriod"
+ placeholder="Please input subscribe rate"
+ @keyup.enter.native="handleSubscribeRate">
+ Default -1
+ </md-input>
+ </el-form-item>
+ </el-form>
+ <h4>Anti Affinity</h4>
+ <hr class="split-line">
+ <el-form :inline="true" :model="form" :rules="rules" @submit.native.prevent>
+ <el-form-item prop="antiAffinityGroup">
+ <span>Anti Affinity Group</span>
+ <el-tooltip :content="antiAffinityGroupContent" class="item" effect="dark" placement="top">
+ <i class="el-icon-info"/>
+ </el-tooltip>
+ <md-input
+ v-model="form.antiAffinityGroup"
+ class="md-input-style"
+ name="antiAffinityGroup"
+ placeholder="Please input Anti Affinity Group"
+ @keyup.enter.native="handleAntiAffinityGroup"/>
+ </el-form-item>
+ </el-form>
+ <h4>Danager Zone</h4>
+ <hr class="danger-line">
+ <el-button type="danger" class="button" @click="deleteNamespace">Delete Namespace</el-button>
+ </el-tab-pane>
+ </el-tabs>
+ </div>
+</template>
+<script>
+import { fetchTenants } from '@/api/tenants'
+import {
+ fetchNamespaces,
+ fetchNamespacePolicies,
+ getPermissions,
+ getPersistence,
+ getRetention,
+ setClusters,
+ grantPermissions,
+ revokePermissions,
+ setSubscriptionAuthMode,
+ setPersistence,
+ setBacklogQuota,
+ setEncryptionRequired,
+ setDeduplication,
+ setSchemaAutoupdateStrategy,
+ setSchemaValidationEnforced,
+ setMessageTtl,
+ setRetention,
+ setCompactionThreshold,
+ setOffloadThreshold,
+ setOffloadDeletionLag,
+ setMaxProducersPerTopic,
+ setMaxConsumersPerTopic,
+ setMaxConsumersPerSubscription,
+ setDispatchRate,
+ setSubscriptionDispatchRate,
+ setSubscribeRate,
+ setAntiAffinityGroup,
+ splitBundle,
+ unloadBundle,
+ unload,
+ clearBacklog,
+ deleteNamespace
+} from '@/api/namespaces'
+import { fetchBrokerStats } from '@/api/brokerStats'
+import { 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'
+const defaultForm = {
+ tenant: '',
+ namespace: ''
+}
+export default {
+ name: 'NamespaceInfo',
+ components: {
+ ElDragSelect,
+ MdInput,
+ Pagination
+ },
+ data() {
+ // const validate = (rule, value, callback) => {
+ // if (value.length !== 6) {
+ // callback(new Error('请输入å…个å—符'))
+ // } else {
+ // callback()
+ // }
+ // }
+ return {
+ postForm: Object.assign({}, defaultForm),
+ tenantNamespace: '',
+ tenantsListOptions: [],
+ namespacesListOptions: [],
+ activeName: 'overview',
+ namespaceStats: [{
+ inMsg: 0,
+ outMsg: 0,
+ inBytes: 0,
+ outBytes: 0
+ }],
+ replicatedClustersContent: 'This is Allowed Clusters',
+ replicationClustersValue: [],
+ replicationClustersOptions: [],
+ dynamicValidateForm: {
+ domains: [{
+ value: ''
+ }],
+ email: ''
+ },
+ dynamicTags: [],
+ inputVisible: false,
+ inputValue: '',
+ roleValue: [],
+ roleMap: {},
+ roleMapOptions: {},
+ roleOptions: [{
+ value: 'consume',
+ label: 'consume'
+ }, {
+ value: 'produce',
+ label: 'produce'
+ }, {
+ value: 'functions',
+ label: 'functions'
+ }],
+ authorizationContent: 'This is AuthorizationContent',
+ subscriptionAuthenticationMode: '',
+ subscriptionAuthenticationModeContent: 'This is subscriptionAuthenticationMode',
+ subscriptionAuthenticationModeOptions: [{
+ value: 'None',
+ label: 'None'
+ }, {
+ value: 'Prefix',
+ label: 'Prefix'
+ }],
+ replicationFactorContent: 'This is replicationFactorContent',
+ form: {
+ ensembelSize: '',
+ writeQuorumSize: '',
+ readQuorumSize: '',
+ markDeleteMaxRate: '',
+ backlogQuotasLimit: '',
+ backlogRententionPolicy: '',
+ encryptionRequire: '',
+ deduplication: '',
+ autoUpdateStrategy: '',
+ messageTTL: '',
+ retentionTime: '',
+ encryptionRequireRadio: 'Disabled',
+ deduplicationRadio: 'Disabled',
+ schemaValidationEnforcedRadio: 'Disabled',
+ dispatchRatePerTopicBytes: '',
+ dispatchRatePerTopicMessage: '',
+ dispatchRatePerTopicPeriod: '',
+ dispatchRatePerSubBytes: '',
+ antiAffinityGroup: '',
+ dispatchRatePerSubMessage: '',
+ dispatchRatePerSubPeriod: '',
+ subscribeRatePerConsumerSub: '',
+ subscribeRatePerConsumerPeriod: '',
+ maxConsumerPerSub: ''
+ },
+ rules: {
+ // ensembelSize: [{ required: true, message: 'EnsembelSize is greater more than 0', trigger: 'blur' }]
+ },
+ backlogQuotasLimitContent: 'This is backlogQuotasLimitContent',
+ backlogRententionPolicyContent: 'This is backlogRententionPolicyContent',
+ backlogRententionPolicyRadio: 'consumer_backlog_eviction',
+ backlogRententionPolicyOption: [{
+ value: 'consumer_backlog_eviction',
+ label: 'consumer_backlog_eviction'
+ }, {
+ value: 'producer_exception',
+ label: 'producer_exception'
+ }, {
+ value: 'producer_request_hold',
+ lable: 'producer_request_hold'
+ }],
+ encryptionRequireContent: 'This is encryptionRequireContent',
+ encryptionRequireOption: [{
+ value: 'Enabled',
+ label: 'Enabled'
+ }, {
+ value: 'Disabled',
+ label: 'Disabled'
+ }],
+ deduplicationContent: 'This is deduplicationContent',
+ deduplication: '',
+ deduplicationOption: [{
+ value: 'Enabled',
+ label: 'Enabled'
+ }, {
+ value: 'Disabled',
+ label: 'Disabled'
+ }],
+ autoUpdateStrategyContent: 'This is schemaValidationEnforced',
+ autoUpdateStrategyOption: [{
+ value: 'Full',
+ label: 'Full'
+ }, {
+ value: 'Forward',
+ label: 'Forward'
+ }, {
+ value: 'Backward',
+ label: 'Backward'
+ }, {
+ value: 'AlwaysCompatible',
+ label: 'Always Compatible'
+ }, {
+ value: 'AutoUpdateDisabled',
+ label: 'AutoUpdateDisabled'
+ }, {
+ value: 'BackwardTransitive',
+ label: 'BackwardTransitive'
+ }, {
+ value: 'ForwardTransitive',
+ label: 'ForwardTransitive'
+ }, {
+ value: 'FullTransitive',
+ label: 'FullTransitive'
+ }],
+ schemaValidationEnforcedContent: 'This is schemaValidationEnforcedContent',
+ schemaValidationEnforced: '',
+ messageTTLContent: 'This is messageTTLContent',
+ retentionSizeContent: 'This is retentionSizeContent',
+ retentionTimeContent: 'This is retentionTimeContent',
+ compactionThresholdContent: 'This is compactionThresholdContent',
+ offloadThresholdContent: 'This is offloadThresholdContent',
+ offloadDeletionLagContent: 'This is offloadDeletionLagContent',
+ maxProducersPerTopicContent: 'This is maxProducersPerTopicContent',
+ maxConsumersPerTopicContent: 'This is maxConsumersPerTopicContent',
+ maxConsumerPerSubContent: 'This is maxConsumerPerSubContent',
+ dispatchRatePerTopicContent: 'This is dispatchRatePerTopicContent',
+ dispatchRatePerSubContent: 'This is dispatchRatePerSubContent',
+ subscribeRatePerConsumerContent: 'This is subscribeRatePerConsumerContent',
+ antiAffinityGroupContent: 'This is antiAffinityGroupContent',
+ tableKey: 0,
+ topicsListLoading: true,
+ topicsTableKey: 0,
+ brokerStats: null,
+ topics: {},
+ localList: [],
+ topicsList: [],
+ topicsTotal: 0,
+ // total: 0,
+ topicsListQuery: {
+ page: 1,
+ limit: 10
+ },
+ firstInit: false
+ }
+ },
+ created() {
+ this.postForm.tenant = this.$route.params && this.$route.params.tenant
+ this.postForm.namespace = this.$route.params && this.$route.params.namespace
+ this.tenantNamespace = this.postForm.tenant + '/' + this.postForm.namespace
+ this.firstInit = true
+ this.getRemoteTenantsList()
+ this.getNamespacesList(this.postForm.tenant)
+ this.getPolicies(this.tenantNamespace)
+ this.initPermissions(this.tenantNamespace)
+ this.initPersistence(this.tenantNamespace)
+ this.initRetention(this.tenantNamespace)
+ this.getStats()
+ },
+ methods: {
+ getStats() {
+ this.getTopics()
+ },
+ getTopics() {
+ fetchTopicsByPulsarManager(this.postForm.tenant, this.postForm.namespace).then(response => {
+ if (!response.data) return
+ for (var i in response.data.topics) {
+ this.topics[response.data.topics[i]['topic']] = i
+ this.topicsList.push({
+ 'topic': response.data.topics[i]['topic'],
+ 'partitions': response.data.topics[i]['partitions'],
+ 'producers': 0,
+ 'subscriptions': 0,
+ 'inMsg': 0,
+ 'outMsg': 0,
+ 'inBytes': 0,
+ 'outBytes': 0,
+ 'storageSize': 0
+ })
+ }
+ fetchBrokerStats().then(res => {
+ if (!res.data) return
+ this.brokerStats = res.data
+ if (this.brokerStats.hasOwnProperty(this.tenantNamespace)) {
+ for (var bundle in this.brokerStats[this.tenantNamespace]) {
+ for (var p in this.brokerStats[this.tenantNamespace][bundle]) {
+ for (var topic in this.brokerStats[this.tenantNamespace][bundle][p]) {
+ this.namespaceStats[0].inMsg += this.brokerStats[this.tenantNamespace][bundle][p][topic].msgRateIn
+ this.namespaceStats[0].outMsg += this.brokerStats[this.tenantNamespace][bundle][p][topic].msgRateOut
+ this.namespaceStats[0].inBytes += this.brokerStats[this.tenantNamespace][bundle][p][topic].msgThroughputIn
+ this.namespaceStats[0].outBytes += this.brokerStats[this.tenantNamespace][bundle][p][topic].msgThroughputOut
+ var topicName = topic.split('://')[1].split('/')[2]
+ var isPartition = false
+ if (topicName.indexOf('-partition-') > 0) {
+ topicName = topicName.split('-partition-')[0]
+ isPartition = true
+ }
+ if (this.topics.hasOwnProperty(topicName)) {
+ if (isPartition) {
+ this.topicsList[this.topics[topicName]]['producers'] += this.brokerStats[this.tenantNamespace][bundle][p][topic].producerCount
+ for (var psub in this.brokerStats[this.tenantNamespace][bundle][p][topic].subscriptions) {
+ this.topicsList[this.topics[topicName]]['subscriptions'] += Object.keys(this.brokerStats[this.tenantNamespace][bundle][p][topic].subscriptions[psub].consumers).length
+ }
+ this.topicsList[this.topics[topicName]]['inMsg'] += this.brokerStats[this.tenantNamespace][bundle][p][topic].msgRateIn
+ this.topicsList[this.topics[topicName]]['outMsg'] += this.brokerStats[this.tenantNamespace][bundle][p][topic].msgRateOut
+ this.topicsList[this.topics[topicName]]['inBytes'] += this.brokerStats[this.tenantNamespace][bundle][p][topic].msgThroughputIn
+ this.topicsList[this.topics[topicName]]['outBytes'] += this.brokerStats[this.tenantNamespace][bundle][p][topic].msgThroughputOut
+ this.topicsList[this.topics[topicName]]['storageSize'] += this.brokerStats[this.tenantNamespace][bundle][p][topic].storageSize
+ } else {
+ this.topicsList[this.topics[topicName]]['producers'] = this.brokerStats[this.tenantNamespace][bundle][p][topic].producerCount
+ for (var sub in this.brokerStats[this.tenantNamespace][bundle][p][topic].subscriptions) {
+ this.topicsList[this.topics[topicName]]['subscriptions'] = Object.keys(this.brokerStats[this.tenantNamespace][bundle][p][topic].subscriptions[sub].consumers).length
+ }
+ this.topicsList[this.topics[topicName]]['inMsg'] = this.brokerStats[this.tenantNamespace][bundle][p][topic].msgRateIn
+ this.topicsList[this.topics[topicName]]['outMsg'] = this.brokerStats[this.tenantNamespace][bundle][p][topic].msgRateOut
+ this.topicsList[this.topics[topicName]]['inBytes'] = this.brokerStats[this.tenantNamespace][bundle][p][topic].msgThroughputIn
+ this.topicsList[this.topics[topicName]]['outBytes'] = this.brokerStats[this.tenantNamespace][bundle][p][topic].msgThroughputOut
+ this.topicsList[this.topics[topicName]]['storageSize'] = this.brokerStats[this.tenantNamespace][bundle][p][topic].storageSize
+ }
+ }
+ }
+ }
+ }
+ }
+ })
+ this.topicsListLoading = false
+ })
+ },
+ getPolicies(tenantNamespace) {
+ fetchNamespacePolicies(tenantNamespace).then(response => {
+ if (!response.data) return
+ this.initPoliciesOptions(response.data)
+ })
+ },
+ initPersistence(tenantNamespace) {
+ getPersistence(tenantNamespace).then(response => {
+ if (!response.data) return
+ this.form.ensembleSize = String(response.data.bookkeeperEnsemble)
+ this.form.writeQuorumSize = String(response.data.bookkeeperWriteQuorum)
+ this.form.readQuorumSize = String(response.data.bookkeeperAckQuorum)
+ this.form.markDeleteMaxRate = String(response.data.managedLedgerMaxMarkDeleteRate)
+ })
+ },
+ initPermissions(tenantNamespace) {
+ getPermissions(tenantNamespace).then(response => {
+ if (!response.data) return
+ for (var key in response.data) {
+ this.roleMap[key] = response.data.key
+ this.roleMapOptions[key] = this.roleOptions
+ }
+ })
+ },
+ initRetention(tenantNamespace) {
+ getRetention(tenantNamespace).then(response => {
+ if (!response.data) return
+ this.form.retentionSize = String(response.data.retentionSizeInMB)
+ this.form.retentionTime = String(response.data.retentionTimeInMinutes)
+ })
+ },
+ initPoliciesOptions(policies) {
+ for (var i = 0; i < policies.replication_clusters.length; i++) {
+ this.replicationClustersOptions.push({
+ value: policies.replication_clusters[i],
+ label: policies.replication_clusters[i]
+ })
+ }
+ this.replicationClustersValue = policies.replication_clusters
+ this.subscriptionAuthenticationMode = policies.subscription_auth_mode
+ this.form.backlogQuotasLimit = String(policies.backlog_quota_map.destination_storage.limit)
+ this.form.backlogRententionPolicy = String(policies.backlog_quota_map.destination_storage.policy)
+ if (policies.encryption_required) {
+ this.form.encryptionRequireRadio = 'Enabled'
+ }
+ if (policies.hasOwnProperty('deduplicationRadio')) {
+ this.form.deduplicationRadio = policies.deduplicationRadio
+ }
+ this.form.autoUpdateStrategy = policies.schema_auto_update_compatibility_strategy
+ if (policies.schema_validation_enforced) {
+ this.form.schemaValidationEnforcedRadio = 'Enabled'
+ }
+ this.form.messageTTL = String(policies.message_ttl_in_seconds)
+ this.form.compactionThreshold = String(policies.compaction_threshold)
+ this.form.offloadThreshold = String(policies.offload_threshold)
+ if (policies.hasOwnProperty('offload_deletion_lag_ms')) {
+ this.form.offloadDeletionLag = String(policies.offload_deletion_lag_ms)
+ }
+ this.form.maxProducersPerTopic = String(policies.max_producers_per_topic)
+ this.form.maxConsumersPerTopic = String(policies.max_consumers_per_topic)
+ this.form.maxConsumerPerSub = String(policies.max_consumers_per_subscription)
+ for (var t in policies.topicDispatchRate) {
+ this.form.dispatchRatePerTopicMessage = String(policies.topicDispatchRate[t].dispatchThrottlingRateInMsg)
+ this.form.dispatchRatePerTopicBytes = String(policies.topicDispatchRate[t].dispatchThrottlingRateInByte)
+ this.form.dispatchRatePerTopicPeriod = String(policies.topicDispatchRate[t].ratePeriodInSecond)
+ }
+ for (var s in policies.subscriptionDispatchRate) {
+ this.form.dispatchRatePerSubBytes = String(policies.subscriptionDispatchRate[s].dispatchThrottlingRateInByte)
+ this.form.dispatchRatePerSubMessage = String(policies.subscriptionDispatchRate[s].dispatchThrottlingRateInMsg)
+ this.form.dispatchRatePerSubPeriod = String(policies.subscriptionDispatchRate[s].ratePeriodInSecond)
+ }
+ for (var c in policies.clusterSubscribeRate) {
+ this.form.subscribeRatePerConsumerSub = String(policies.clusterSubscribeRate[c].subscribeThrottlingRatePerConsumer)
+ this.form.subscribeRatePerConsumerPeriod = String(policies.clusterSubscribeRate[c].ratePeriodInSecond)
+ }
+ if (policies.hasOwnProperty('antiAffinityGroup')) {
+ this.form.antiAffinityGroup = policies.antiAffinityGroup
+ }
+ // this.listQuery.limit = policies.bundles.numBundles
+ // this.listQuery.page = 1
+ // this.total = policies.bundles.numBundles
+ for (var n = 0; n < policies.bundles.numBundles; n++) {
+ this.localList.push({ 'bundle': policies.bundles.boundaries[n] + '_' + policies.bundles.boundaries[n + 1] })
+ }
+ },
+ handleClick(tab, event) {
+ console.log(tab, event)
+ },
+ getRemoteTenantsList() {
+ fetchTenants().then(response => {
+ if (!response.data) return
+ for (var i = 0; i < response.data.total; i++) {
+ this.tenantsListOptions.push(response.data.data[i].tenant)
+ }
+ })
+ },
+ getNamespacesList(tenant) {
+ fetchNamespaces(tenant, this.query).then(response => {
+ let namespace = []
+ this.namespacesListOptions = []
+ if (this.firstInit) {
+ this.firstInit = false
+ } else {
+ this.postForm.namespace = ''
+ }
+ for (var i = 0; i < response.data.data.length; i++) {
+ namespace = response.data.data[i].namespace
+ this.namespacesListOptions.push(namespace)
+ }
+ })
+ },
+ getNamespaceInfo(tenant, namespace) {
+ this.$router.push({ path: '/management/namespaces/' + tenant + '/' + namespace + '/namespace' })
+ },
+ handleFilterBundle() {
+ },
+ submitForm(formName) {
+ this.$refs[formName].validate((valid) => {
+ if (valid) {
+ alert('submit!')
+ } else {
+ console.log('error submit!!')
+ return false
+ }
+ })
+ },
+ resetForm(formName) {
+ this.$refs[formName].resetFields()
+ },
+ // removeDomain(item) {
+ // var index = this.dynamicValidateForm.domains.indexOf(item)
+ // if (index !== -1) {
+ // this.dynamicValidateForm.domains.splice(index, 1)
+ // }
+ // },
+ // addDomain() {
+ // this.dynamicValidateForm.domains.push({
+ // value: '',
+ // key: Date.now()
+ // })
+ // },
+ handleClose(tag) {
+ this.dynamicTags.splice(this.dynamicTags.indexOf(tag), 1)
+ revokePermissions(this.tenantNamespace, tag).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Add success',
+ type: 'success',
+ duration: 3000
+ })
+ delete this.roleMap[tag]
+ delete this.roleMapOptions[tag]
+ })
+ },
+ showInput() {
+ this.inputVisible = true
+ this.$nextTick(_ => {
+ this.$refs.saveTagInput.$refs.input.focus()
+ })
+ },
+ handleInputConfirm() {
+ const inputValue = this.inputValue
+ if (inputValue) {
+ if (this.roleMap.hasOwnProperty(inputValue)) {
+ this.$message({
+ message: 'This role is exist',
+ type: 'error'
+ })
+ this.inputVisible = false
+ this.inputValue = ''
+ return
+ }
+ grantPermissions(this.currentNamespace, inputValue, this.roleMap[inputValue]).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Add success',
+ type: 'success',
+ duration: 3000
+ })
+ this.dynamicTags.push(inputValue)
+ this.roleMap[inputValue] = []
+ this.roleMapOptions[inputValue] = this.roleOptions
+ })
+ }
+ this.inputVisible = false
+ this.inputValue = ''
+ },
+ handleChangeOptions() {
+ console.log(this.roleMap)
+ this.$forceUpdate()
+ },
+ revokeAllRole() {
+ },
+ handlePersistence() {
+ const data = {
+ 'bookkeeperAckQuorum': parseInt(this.form.readQuorumSize),
+ 'bookkeeperEnsemble': parseInt(this.form.ensembleSize),
+ 'bookkeeperWriteQuorum': parseInt(this.form.writeQuorumSize),
+ 'managedLedgerMaxMarkDeleteRate': parseInt(this.form.markDeleteMaxRate)
+ }
+ setPersistence(this.tenantNamespace, data).then(response => {
+ this.dialogFormVisible = false
+ this.$notify({
+ title: 'success',
+ message: 'Set persistence success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleBacklogQuota() {
+ const data = {
+ 'limit': this.form.backlogQuotasLimit,
+ 'policy': this.form.backlogRententionPolicy
+ }
+ setBacklogQuota(this.tenantNamespace, data).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Add Backlog Quota success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleSplitBundle(row) {
+ splitBundle(this.tenantNamespace, row.bundle, false).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'splitBundle success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ this.localList = []
+ this.getPolicies(this.tenantNamespace)
+ })
+ },
+ handleUnloadAll() {
+ unload(this.tenantNamespace).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Unload success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ this.localList = []
+ this.getPolicies(this.tenantNamespace)
+ })
+ },
+ handleUnloadBundle(row) {
+ unloadBundle(this.tenantNamespace, row.bundle).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Unload success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ this.localList = []
+ this.getPolicies(this.tenantNamespace)
+ },
+ hanldeClearAllBacklog() {
+ clearBacklog(this.tenantNamespace).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'clearBacklog success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleReplicationsClusters() {
+ setClusters(this.tenantNamespace, this.replicationClustersValue).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Add clusters success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleSubscriptionAuthMode() {
+ var subAuthMode = 0
+ if (this.subscriptionAuthenticationMode === 'Prefix') {
+ subAuthMode = 1
+ }
+ setSubscriptionAuthMode(this.tenantNamespace, subAuthMode).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'success',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleEncryption() {
+ var encryptionRequire = false
+ if (this.form.encryptionRequireRadio === 'Enabled') {
+ encryptionRequire = true
+ }
+ setEncryptionRequired(this.tenantNamespace, encryptionRequire).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'success',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleDeduplication() {
+ var deduplication = false
+ if (this.form.deduplicationRadio === 'Enabled') {
+ deduplication = true
+ }
+ setDeduplication(this.tenantNamespace, deduplication).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set deduplication success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleSchemaAutoUpdateStrategy() {
+ console.log(this.form.autoUpdateStrategy)
+ var strategy = 3
+ if (this.form.autoUpdateStrategy === 'AutoUpdateDisabled') {
+ strategy = 0
+ } else if (this.form.autoUpdateStrategy === 'Backward') {
+ strategy = 1
+ } else if (this.form.autoUpdateStrategy === 'Forward') {
+ strategy = 2
+ } else if (this.form.autoUpdateStrategy === 'Full') {
+ strategy = 3
+ } else if (this.form.autoUpdateStrategy === 'AlwaysCompatible') {
+ strategy = 4
+ } else if (this.form.autoUpdateStrategy === 'BackwardTransitive') {
+ strategy = 5
+ } else if (this.form.autoUpdateStrategy === 'ForwardTransitive') {
+ strategy = 6
+ } else if (this.form.autoUpdateStrategy === 'FullTransitive') {
+ strategy = 7
+ }
+ setSchemaAutoupdateStrategy(this.tenantNamespace, strategy).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set SchemaAutoupdateStrategy success',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleSchemaValidationEnforced() {
+ var schemaValidation = false
+ if (this.form.schemaValidationEnforcedRadio === 'Enabled') {
+ schemaValidation = true
+ }
+ setSchemaValidationEnforced(this.tenantNamespace, schemaValidation).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set SchemaValidationEnforced success',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleMessageTTL() {
+ setMessageTtl(this.tenantNamespace, parseInt(this.form.messageTTL)).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set messageTTL success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleRetention() {
+ const data = { 'retentionSizeInMB': parseInt(this.form.retentionSize), 'retentionTimeInMinutes': parseInt(this.form.retentionTime) }
+ setRetention(this.tenantNamespace, data).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set Retention success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleCompactionThreshold() {
+ setCompactionThreshold(this.tenantNamespace, this.form.compactionThreshold).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set threshold success',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleOffloadThreshold() {
+ setOffloadThreshold(this.tenantNamespace, this.form.offloadThreshold).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set threshold success',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleOffloadDeletionLag() {
+ setOffloadDeletionLag(this.tenantNamespace, this.form.offloadDeletionLag).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set DeletionLag success',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleMaxProducersPerTopic() {
+ setMaxProducersPerTopic(this.tenantNamespace, this.form.maxProducersPerTopic).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set max producers per topic success',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleMaxConsumersPerTopic() {
+ setMaxConsumersPerTopic(this.tenantNamespace, this.form.maxConsumersPerTopic).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set max consumers per topic success',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleMaxConsuemrsPerSubscription() {
+ setMaxConsumersPerSubscription(this.tenantNamespace, this.form.maxConsumerPerSub).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set max subscription per topic success',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleDispatchRatePerTopic() {
+ const data = {
+ 'dispatchThrottlingRateInByte': this.form.dispatchRatePerTopicBytes,
+ 'ratePeriodInSecond': this.form.dispatchRatePerTopicPeriod,
+ 'dispatchThrottlingRateInMsg': this.form.dispatchRatePerTopicMessage
+ }
+ setDispatchRate(this.tenantNamespace, data).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'set DispatchRate success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleDispatchRatePerSub() {
+ const data = {
+ 'dispatchThrottlingRateInByte': this.form.dispatchRatePerSubBytes,
+ 'ratePeriodInSecond': this.form.dispatchRatePerSubPeriod,
+ 'dispatchThrottlingRateInMsg': this.form.dispatchRatePerSubMessage
+ }
+ setSubscriptionDispatchRate(this.tenantNamespace, data).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'set subscription dispatchRate success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleSubscribeRate() {
+ const data = {
+ 'subscribeThrottlingRatePerConsumer': this.form.subscribeRatePerConsumerSub,
+ 'ratePeriodInSecond': this.form.subscribeRatePerConsumerPeriod
+ }
+ setSubscribeRate(this.tenantNamespace, data).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'set subscription dispatchRate success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ handleAntiAffinityGroup() {
+ setAntiAffinityGroup(this.tenantNamespace, this.form.antiAffinityGroup).then(response => {
+ this.$notify({
+ title: 'success',
+ message: 'Set AntiAffinityGroup success for namespace',
+ type: 'success',
+ duration: 3000
+ })
+ })
+ },
+ deleteNamespace() {
+ deleteNamespace(this.tenantNamespace).then((response) => {
+ this.$notify({
+ title: 'success',
+ message: 'delete success',
+ type: 'success',
+ duration: 2000
+ })
+ this.$router.push({ path: '/management/namespaces/' + this.postForm.tenant })
+ })
+ }
+ }
+}
+</script>
+
+<style>
+.role-el-tag {
+ background-color: #fff !important;
+ border: none !important;
+ font-size: 16px !important;
+ color: black !important;
+}
+.split-line {
+ background: #e6e9f3;
+ border: none;
+ height: 1px;
+}
+.danger-line {
+ background: red;
+ border: none;
+ height: 1px;
+}
+.md-input-style {
+ width: 300px;
+ margin-top: 20px;
+}
+</style>
diff --git a/front-end/src/views/management/namespaces/policies.vue b/front-end/src/views/management/namespaces/policies.vue
deleted file mode 100644
index 8dd58c9..0000000
--- a/front-end/src/views/management/namespaces/policies.vue
+++ /dev/null
@@ -1,247 +0,0 @@
-<template>
- <div class="app-container">
-
- <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-table
- :key="tableKey"
- :data="list"
- border
- fit
- highlight-current-row
- style="width: 100%;">
- <el-table-column :label="$t('table.policies')" min-width="150px" align="center">
- <template slot-scope="scope">
- <span class="link-type" @click="getNamespacePolicie()">{{ scope.row.policies }}</span>
- </template>
- </el-table-column>
- <el-table-column :label="$t('table.description')" min-width="150px" align="center">
- <template slot-scope="scope">
- <span>{{ scope.row.description }}</span>
- </template>
- </el-table-column>
- </el-table>
- <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="pagePolicies" />
- </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"/>
- <template>
- <el-button type="primary" style="float: right; margin-top: 10px" @click="handleDelete()">{{ $t('table.update') }}
- </el-button>
- </template>
- </el-col>
- </el-row>
-
- <el-dialog :visible.sync="dialogFormVisible">
- <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="70px" style="width: 400px; margin-left:50px;">
- <el-form-item :label="$t('table.namespace')" prop="namespace">
- <el-input v-model="temp.namespace"/>
- </el-form-item>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="dialogFormVisible = false">{{ $t('table.cancel') }}</el-button>
- <el-button type="primary" @click="createData()">{{ $t('table.confirm') }}</el-button>
- </div>
- </el-dialog>
-
- </div>
-</template>
-
-<script>
-import { updateTenant } from '@/api/tenants'
-import waves from '@/directive/waves' // Waves directive
-import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
-import JsonEditor from '@/components/JsonEditor'
-
-export default {
- name: 'Namespaces',
- components: {
- Pagination,
- JsonEditor
- },
- directives: { waves },
- data() {
- return {
- tableKey: 0,
- jsonValue: {},
- tenant: '',
- namespace: '',
- list: null,
- policiesList: policiesList,
- total: 0,
- listQuery: {
- page: 1,
- limit: 10
- },
- temp: {
- namespace: ''
- },
- dialogFormVisible: false,
- dialogStatus: '',
- textMap: {
- update: 'Edit',
- create: 'Create'
- },
- rules: {
- namespace: [{ required: true, message: 'namespace is required', trigger: 'blur' }]
- }
- }
- },
- created() {
- this.tenant = this.$route.params && this.$route.params.tenant
- this.namespace = this.$route.params && this.$route.params.namespace
- this.pagePolicies()
- // this.getNamespaces()
- // this.getNamespacePolicies()
- },
- methods: {
- pagePolicies() {
- this.total = this.policiesList.length
- this.list = this.policiesList.slice((this.listQuery.page - 1) * 10, this.listQuery.page * 10)
- },
- handleUpdate(row) {
- this.temp = Object.assign({}, row) // copy obj
- this.dialogStatus = 'update'
- this.dialogFormVisible = true
- this.$nextTick(() => {
- this.$refs['dataForm'].clearValidate()
- })
- },
- getNamespacePolicie() {
-
- },
- updateData() {
- this.$refs['dataForm'].validate((valid) => {
- if (valid) {
- const tempData = Object.assign({}, this.temp)
- updateTenant(this.temp.tenant, tempData).then(() => {
- for (const v of this.list) {
- if (v.tenant === this.temp.tenant) {
- 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) {
- this.$notify({
- title: 'success',
- message: 'delete success',
- type: 'success',
- duration: 2000
- })
- const index = this.list.indexOf(row)
- this.list.splice(index, 1)
- }
- }
-}
-const policiesList = [
- {
- 'policies': 'maxConsumersPerSubscription',
- 'description': 'maxConsumersPerSubscription'
- },
- {
- 'policies': 'compactionThreshold',
- 'description': 'compactionThreshold'
- },
- {
- 'policies': 'antiAffinity',
- 'description': 'antiAffinity'
- },
- {
- 'policies': 'backlogQuota',
- 'description': 'backlogQuota'
- },
- {
- 'policies': 'clearBacklog',
- 'description': 'clearBacklog'
- },
- {
- 'policies': 'deduplication',
- 'description': 'deduplication'
- },
- {
- 'policies': 'dispatchRate',
- 'description': 'dispatchRate'
- },
- {
- 'policies': 'encryptionRequired',
- 'description': 'encryptionRequired'
- },
- {
- 'policies': 'maxConsumersPerTopic',
- 'description': 'maxConsumersPerTopic'
- },
- {
- 'policies': 'maxProducersPerTopic',
- 'description': 'maxProducersPerTopic'
- },
- {
- 'policies': 'messageTTL',
- 'description': 'messageTTL'
- },
- {
- 'policies': 'offloadDeletionLagMs',
- 'description': 'offloadDeletionLagMs'
- },
- {
- 'policies': 'offloadThreshold',
- 'description': 'offloadThreshold'
- },
- {
- 'policies': 'permissions',
- 'description': 'permissions'
- },
- {
- 'policies': 'persistence',
- 'description': 'persistence'
- },
- {
- 'policies': 'replication',
- 'description': 'replication'
- },
- {
- 'policies': 'retention',
- 'description': 'retention'
- },
- {
- 'policies': 'schemaAutoUpdateCompatibilityStrategy',
- 'description': 'schemaAutoUpdateCompatibilityStrategy'
- },
- {
- 'policies': 'subscribeRate',
- 'description': 'subscribeRate'
- },
- {
- 'policies': 'subscriptionAuthMode',
- 'description': 'subscriptionAuthMode'
- },
- {
- 'policies': 'subscriptionDispatchRate',
- 'description': 'subscriptionDispatchRate'
- },
- {
- 'policies': 'unload',
- 'description': 'unload'
- },
- {
- 'policies': 'unsubscribe',
- 'description': 'unsubscribe'
- },
- {
- 'policies': 'bundle',
- 'description': 'bundle'
- }
-]
-</script>
-
diff --git a/gradle.properties b/gradle.properties
index cc5f12a..90f3c91 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,8 +8,9 @@
pageHelperVersion=1.2.4
mockitoVersion=1.10.19
guavaVersion=21.0
-pulsarVersion=2.5.0-1b64a6e1f
+pulsarVersion=2.5.0-2cc34afc0
swagger2Version=2.9.2
swaggeruiVersion=2.9.2
apiMockitoVersion=1.7.1
-mockitoJunit4Version=1.7.1
\ No newline at end of file
+mockitoJunit4Version=1.7.1
+gsonVersion=2.8.2
diff --git a/src/main/java/com/manager/pulsar/controller/NamespacesController.java b/src/main/java/com/manager/pulsar/controller/NamespacesController.java
index 9ab75c9..f1abca5 100644
--- a/src/main/java/com/manager/pulsar/controller/NamespacesController.java
+++ b/src/main/java/com/manager/pulsar/controller/NamespacesController.java
@@ -17,6 +17,7 @@
import com.google.common.collect.Maps;
import com.manager.pulsar.entity.NamespaceEntity;
import com.manager.pulsar.entity.NamespacesRepository;
+import com.manager.pulsar.service.NamespacesService;
import io.swagger.annotations.*;
import org.hibernate.validator.constraints.Range;
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,6 +46,9 @@
@Autowired
private NamespacesRepository namespacesRepository;
+ @Autowired
+ private NamespacesService namespacesService;
+
@ApiOperation(value = "Get the list of existing namespaces, support paging, the default is 10 per page")
@ApiResponses({
@ApiResponse(code = 200, message = "ok"),
diff --git a/src/main/java/com/manager/pulsar/controller/TopicsController.java b/src/main/java/com/manager/pulsar/controller/TopicsController.java
new file mode 100644
index 0000000..d56b9ac
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/controller/TopicsController.java
@@ -0,0 +1,71 @@
+/**
+ * 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.TopicsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.hibernate.validator.constraints.Range;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PathVariable;
+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.validation.constraints.Min;
+import javax.validation.constraints.Size;
+import java.util.Map;
+
+/**
+ * Topics rest api class.
+ */
+@RequestMapping(value = "/pulsar-manager/admin/v2")
+@Api(description = "Support more flexible queries to namespaces.")
+@Validated
+@RestController
+public class TopicsController {
+
+ @Autowired
+ private TopicsService topicsService;
+
+ @ApiOperation(value = "Query topic info by tenant and namespace and topic")
+ @ApiResponses({
+ @ApiResponse(code = 200, message = "ok"),
+ @ApiResponse(code = 500, message = "Internal server error")
+ })
+ @RequestMapping(value = "/topics/{tenant}/{namespace}", method = RequestMethod.GET)
+ public Map<String, Object> getTopicsByTenantNamespace(
+ @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,
+ @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,
+ @ApiParam(value = "The name of tenant")
+ @Size(min = 1, max = 255)
+ @PathVariable String tenant,
+ @ApiParam(value = "The name of namespace")
+ @Size(min = 1, max = 255)
+ @PathVariable String namespace) {
+ Map<String, Object> result = topicsService.getTopicsList(pageNum, pageSize, tenant, namespace);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/manager/pulsar/service/NamespacesService.java b/src/main/java/com/manager/pulsar/service/NamespacesService.java
new file mode 100644
index 0000000..786de06
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/service/NamespacesService.java
@@ -0,0 +1,22 @@
+/**
+ * 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 NamespacesService {
+
+ Map<String, Object> getNamespaceList(Integer pageNum, Integer pageSize, String tenant);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/manager/pulsar/service/TopicsService.java b/src/main/java/com/manager/pulsar/service/TopicsService.java
new file mode 100644
index 0000000..7949ca6
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/service/TopicsService.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 TopicsService {
+
+ Map<String, Object> getTopicsList(Integer pageNum, Integer pageSize, String namespace, String tenant);
+}
\ No newline at end of file
diff --git a/src/main/java/com/manager/pulsar/service/impl/NamespacesServiceImpl.java b/src/main/java/com/manager/pulsar/service/impl/NamespacesServiceImpl.java
new file mode 100644
index 0000000..be74939
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/service/impl/NamespacesServiceImpl.java
@@ -0,0 +1,70 @@
+/**
+ * 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.NamespacesService;
+import com.manager.pulsar.service.TopicsService;
+import com.manager.pulsar.utils.HttpUtil;
+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 NamespacesServiceImpl implements NamespacesService {
+
+ @Value("${backend.directRequestBroker}")
+ private boolean directRequestBroker;
+
+ @Value("${backend.directRequestHost}")
+ private String directRequestHost;
+
+ @Autowired
+ private TopicsService topicsService;
+
+ public Map<String, Object> getNamespaceList(Integer pageNum, Integer pageSize, String tenant) {
+ Map<String, Object> namespacesMap = Maps.newHashMap();
+ List<Map<String, Object>> namespacesArray = 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/namespaces/" + tenant, header);
+ if (result != null) {
+ List<String> namespacesList = gson.fromJson(result, new TypeToken<List<String>>(){}.getType());
+ for (String tenantNamespace : namespacesList) {
+ String namespace = tenantNamespace.split("/")[1];
+ Map<String, Object> topicsEntity = Maps.newHashMap();
+ Map<String, Object> topics = topicsService.getTopicsList(
+ 0, 0, tenant, namespace);
+ topicsEntity.put("topics", topics.get("total"));
+ topicsEntity.put("namespace", namespace);
+ namespacesArray.add(topicsEntity);
+ }
+ namespacesMap.put("isPage", false);
+ namespacesMap.put("total", namespacesList.size());
+ namespacesMap.put("data", namespacesArray);
+ namespacesMap.put("pageNum", 1);
+ namespacesMap.put("pageSize", namespacesArray.size());
+ }
+ }
+ return namespacesMap;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/manager/pulsar/service/impl/TenantsServiceImpl.java b/src/main/java/com/manager/pulsar/service/impl/TenantsServiceImpl.java
index c9ebd2a..914f2b7 100644
--- a/src/main/java/com/manager/pulsar/service/impl/TenantsServiceImpl.java
+++ b/src/main/java/com/manager/pulsar/service/impl/TenantsServiceImpl.java
@@ -13,17 +13,14 @@
*/
package com.manager.pulsar.service.impl;
-import com.github.pagehelper.Page;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
-import com.manager.pulsar.entity.NamespaceEntity;
+import com.google.gson.Gson;
import com.manager.pulsar.entity.NamespacesRepository;
-import com.manager.pulsar.entity.TenantEntity;
import com.manager.pulsar.entity.TenantsRepository;
import com.manager.pulsar.service.TenantsService;
import com.manager.pulsar.utils.HttpUtil;
import org.apache.pulsar.common.policies.data.TenantInfo;
-import org.apache.pulsar.shade.com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
diff --git a/src/main/java/com/manager/pulsar/service/impl/TopicsServiceImpl.java b/src/main/java/com/manager/pulsar/service/impl/TopicsServiceImpl.java
new file mode 100644
index 0000000..a18f5b9
--- /dev/null
+++ b/src/main/java/com/manager/pulsar/service/impl/TopicsServiceImpl.java
@@ -0,0 +1,124 @@
+/**
+ * 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.TopicsService;
+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.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class TopicsServiceImpl implements TopicsService {
+
+ @Value("${backend.directRequestBroker}")
+ private boolean directRequestBroker;
+
+ @Value("${backend.directRequestHost}")
+ private String directRequestHost;
+
+ public static final String PARTITIONED_TOPIC_SUFFIX = "-partition-";
+
+ private boolean isPartitonedTopic(List<String> topics, String topic) {
+ if (topic.contains(PARTITIONED_TOPIC_SUFFIX)) {
+ String[] t = topic.split(PARTITIONED_TOPIC_SUFFIX);
+ if (topics != null && topics.contains(t[0])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Map<String, Object> getTopicsList(Integer pageNum, Integer pageSize, String tenant, String namespace) {
+ Map<String, Object> topicsMap = Maps.newHashMap();
+ List<Map<String, String>> persistentTopic = this.getTopicListByHttp(tenant, namespace, "persistent");
+ topicsMap.put("topics", persistentTopic);
+ topicsMap.put("isPage", false);
+ topicsMap.put("total", persistentTopic.size());
+ topicsMap.put("pageNum", 1);
+ topicsMap.put("pageSize", persistentTopic.size());
+ return topicsMap;
+ }
+
+ private List<Map<String, String>> getTopicListByHttp(String tenant, String namespace, String persistent) {
+ List<Map<String, String>> topicsArray = new ArrayList<>();
+ Map<String, String> header = Maps.newHashMap();
+ header.put("Content-Type", "application/json");
+ String prefix = "/admin/v2/" + persistent + "/" + tenant + "/" + namespace;
+ Gson gson = new Gson();
+ String partitonedUrl = directRequestHost + prefix + "/partitioned";
+ String partitonedTopic = HttpUtil.doGet(partitonedUrl, header);
+ List<String> partitionedTopicsList = Arrays.asList();
+ Map<String, List<String>> partitionedMap = Maps.newHashMap();
+ if (partitonedTopic != null) {
+ partitionedTopicsList = gson.fromJson(
+ partitonedTopic, new TypeToken<List<String>>(){}.getType());
+ for (String p : partitionedTopicsList) {
+ partitionedMap.put(this.getTopicName(p), new ArrayList<>());
+ }
+ }
+
+ String topicUrl = directRequestHost + prefix;
+ String topics = HttpUtil.doGet(topicUrl, header);
+ if (topics != null) {
+ List<String> topicsList = gson.fromJson(
+ topics, new TypeToken<List<String>>(){}.getType());
+ for (String topic: topicsList) {
+ String topicName = this.getTopicName(topic);
+ Map<String, String> topicEntity = Maps.newHashMap();
+ if (isPartitonedTopic(partitionedTopicsList, topic)) {
+ String[] name = topicName.split(PARTITIONED_TOPIC_SUFFIX);
+ partitionedMap.get(name[0]).add(topicName);
+ } else {
+ topicEntity.put("topic", topicName);
+ topicEntity.put("partitions", "0");
+ topicsArray.add(topicEntity);
+ }
+ }
+ }
+ if (partitionedTopicsList != null) {
+ for (String s : partitionedTopicsList) {
+ String topicName = this.getTopicName(s);
+ Map<String, String> topicEntity = Maps.newHashMap();
+ List<String> partitionedTopicList = partitionedMap.get(s);
+ if (partitionedTopicList != null && partitionedTopicList.size() > 0) {
+ topicEntity.put("topic", topicName);
+ topicEntity.put("partitions", String.valueOf(partitionedTopicList.size()));
+ } else {
+ topicEntity.put("topic", topicName);
+ String metadataTopicUrl = directRequestHost + prefix + "/" + topicName + "/partitions";
+ String metadataTopic = HttpUtil.doGet(metadataTopicUrl, header);
+ Map<String, Integer> metadata = gson.fromJson(
+ metadataTopic, new TypeToken<Map<String, Integer>>(){}.getType());
+ topicEntity.put("partitions", String.valueOf(metadata.get("partitions")));
+ }
+ topicsArray.add(topicEntity);
+ }
+ }
+ return topicsArray;
+ }
+
+ private String getTopicName(String topic) {
+ String tntPath = topic.split("://")[1];
+ String topicName = tntPath.split("/")[2];
+ return topicName;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/manager/pulsar/service/NamespacesServiceImplTest.java b/src/test/java/com/manager/pulsar/service/NamespacesServiceImplTest.java
new file mode 100644
index 0000000..55d6634
--- /dev/null
+++ b/src/test/java/com/manager/pulsar/service/NamespacesServiceImplTest.java
@@ -0,0 +1,60 @@
+/**
+ * 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 NamespacesServiceImplTest {
+
+ @Autowired
+ private NamespacesService namespacesService;
+
+ @Test
+ public void namespaceServiceImplTest() {
+ 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/namespaces/public", header))
+ .thenReturn("[\"public/default\"]");
+
+ PowerMockito.when(HttpUtil.doGet("http://localhost:8080/admin/v2/persistent/public/default", header))
+ .thenReturn("[\"persistent://public/default/test789\"]");
+ PowerMockito.when(HttpUtil.doGet(
+ "http://localhost:8080/admin/v2/persistent/public/default/partitioned", header))
+ .thenReturn("[]");
+ Map<String, Object> result = namespacesService.getNamespaceList(1, 1, "public");
+ Assert.assertEquals(result.get("total"), 1);
+ Assert.assertFalse((Boolean) result.get("isPage"));
+ Assert.assertEquals(result.get("data").toString(), "[{topics=1, namespace=default}]");
+ }
+}
diff --git a/src/test/java/com/manager/pulsar/service/TopicsServiceImplTest.java b/src/test/java/com/manager/pulsar/service/TopicsServiceImplTest.java
new file mode 100644
index 0000000..e6a0631
--- /dev/null
+++ b/src/test/java/com/manager/pulsar/service/TopicsServiceImplTest.java
@@ -0,0 +1,70 @@
+/**
+ * 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 TopicsServiceImplTest {
+
+ @Autowired
+ private TopicsService topicsService;
+
+ private final String topics = "[" +
+ "\"persistent://public/default/test789\"," +
+ "\"persistent://public/default/test900-partition-0\"," +
+ "\"persistent://public/default/test900-partition-1\"," +
+ "\"persistent://public/default/test900-partition-2\"]";
+
+ private final String partitionedTopics = "[\"persistent://public/default/test900\"]";
+
+ @Test
+ public void topicsServiceImplTest() {
+ 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/persistent/public/default", header))
+ .thenReturn(topics);
+ PowerMockito.when(HttpUtil.doGet(
+ "http://localhost:8080/admin/v2/persistent/public/default/partitioned", header))
+ .thenReturn(partitionedTopics);
+ PowerMockito.when(HttpUtil.doGet(
+ "http://localhost:8080/admin/v2/persistent/public/default/test900/partitions", header))
+ .thenReturn("{\"partitions\":3}");
+ Map<String, Object> topicsMap = topicsService.getTopicsList(
+ 1, 1, "public", "default");
+ Assert.assertEquals(topicsMap.get("total"), 2);
+ Assert.assertFalse((Boolean) topicsMap.get("isPage"));
+ Assert.assertEquals(topicsMap.get("topics").toString(),
+ "[{partitions=0, topic=test789}, {partitions=3, topic=test900}]");
+ }
+}