You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by si...@apache.org on 2019/12/25 15:54:05 UTC
[pulsar-manager] branch master updated: Support sub and unsub (#240)
This is an automated email from the ASF dual-hosted git repository.
sijie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-manager.git
The following commit(s) were added to refs/heads/master by this push:
new 1a74888 Support sub and unsub (#240)
1a74888 is described below
commit 1a748880f8135c8efe26fa4a5cbc4772bd8abd60
Author: tuteng <gu...@apache.org>
AuthorDate: Wed Dec 25 23:53:56 2019 +0800
Support sub and unsub (#240)
Fix #167
### Motivation
Add operation for sub and unsub
### Modifications
* Add sub and unsub operations
* Support topic and partition topic
### Verifying this change
Local test pass
---
front-end/src/api/subscriptions.js | 22 +++++
front-end/src/lang/en.js | 9 +-
front-end/src/lang/zh.js | 9 +-
front-end/src/utils/request.js | 2 +
.../views/management/topics/partitionedTopic.vue | 98 +++++++++++++++++++---
front-end/src/views/management/topics/topic.vue | 71 +++++++++++++++-
6 files changed, 193 insertions(+), 18 deletions(-)
diff --git a/front-end/src/api/subscriptions.js b/front-end/src/api/subscriptions.js
index e2f5b56..5a002f2 100644
--- a/front-end/src/api/subscriptions.js
+++ b/front-end/src/api/subscriptions.js
@@ -29,9 +29,31 @@ export function putSubscription(tenant, namespace, topic, subscription) {
})
}
+export function putSubscriptionOnCluster(cluster, persistent, tenantNamespaceTopic, subscription) {
+ return request({
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-pulsar-cluster': cluster
+ },
+ url: BASE_URL_V2 + `/${persistent}/${tenantNamespaceTopic}/subscription/${subscription}`,
+ method: 'put'
+ })
+}
+
export function deleteSubscription(tenant, namespace, topic, subscription) {
return request({
url: BASE_URL_V2 + `/persistent/${tenant}/${namespace}/${topic}/subscription/${subscription}`,
method: 'delete'
})
}
+
+export function deleteSubscriptionOnCluster(cluster, persistent, tenantNamespaceTopic, subscription) {
+ return request({
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-pulsar-cluster': cluster
+ },
+ url: BASE_URL_V2 + `/${persistent}/${tenantNamespaceTopic}/subscription/${subscription}`,
+ method: 'delete'
+ })
+}
diff --git a/front-end/src/lang/en.js b/front-end/src/lang/en.js
index 69d1327..8264b31 100644
--- a/front-end/src/lang/en.js
+++ b/front-end/src/lang/en.js
@@ -557,7 +557,14 @@ export default {
storage: 'STORAGE',
storageSize: 'Storage Size',
entries: 'Entries',
- segments: 'Segments'
+ segments: 'Segments',
+ newSub: 'New Subscription',
+ sub: 'Subscribe',
+ unsub: 'Unsubscribe',
+ subNotification: 'Please input correct sub name',
+ createSubSuccess: 'Create subscription successfully',
+ deleteSubSuccess: 'Delete subscription successfully',
+ deleteSubConfirm: 'Do you want to delete this subscription?'
},
segment: {
label: 'Segment',
diff --git a/front-end/src/lang/zh.js b/front-end/src/lang/zh.js
index 0c1e983..d09b9b3 100644
--- a/front-end/src/lang/zh.js
+++ b/front-end/src/lang/zh.js
@@ -557,7 +557,14 @@ export default {
storage: 'STORAGE',
storageSize: 'Storage Size',
entries: 'Entries',
- segments: 'Segments'
+ segments: 'Segments',
+ newSub: 'New Subscription',
+ sub: 'Subscribe',
+ unsub: 'Unsubscribe',
+ subNotification: 'Please input correct sub name',
+ createSubSuccess: 'Create subscription successfully',
+ deleteSubSuccess: 'Delete subscription successfully',
+ deleteSubConfirm: 'Do you want to delete this subscription?'
},
segment: {
label: 'Segment',
diff --git a/front-end/src/utils/request.js b/front-end/src/utils/request.js
index 92cc701..07dcca3 100644
--- a/front-end/src/utils/request.js
+++ b/front-end/src/utils/request.js
@@ -75,6 +75,8 @@ service.interceptors.response.use(
})
return
}
+ } else if (error.response.data.hasOwnProperty('reason')) {
+ message = error.response.data.reason
} else {
message = error.response.data
if (message.indexOf('Trying to subscribe with incompatible') >= 0) {
diff --git a/front-end/src/views/management/topics/partitionedTopic.vue b/front-end/src/views/management/topics/partitionedTopic.vue
index 7ad9e58..1601773 100644
--- a/front-end/src/views/management/topics/partitionedTopic.vue
+++ b/front-end/src/views/management/topics/partitionedTopic.vue
@@ -56,6 +56,13 @@
<el-table-column :label="$t('common.outBytes')" prop="outBytes"/>
</el-table>
<h4>{{ $t('topic.subscription.subscriptions') }}</h4>
+ <el-button
+ class="filter-item"
+ type="success"
+ style="margin-bottom: 15px"
+ @click="handleCreateSub">
+ New Sub
+ </el-button>
<el-row :gutter="24">
<el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 24}" :xl="{span: 24}">
<el-table
@@ -128,6 +135,9 @@
<el-dropdown-item :command="{'action': 'clear', 'subscription': scope.row.subscription }">
{{ $t('topic.subscription.clear') }}
</el-dropdown-item>
+ <el-dropdown-item :command="{'action': 'unsub', 'subscription': scope.row.subscription }">
+ {{ $t('topic.subscription.unsub') }}
+ </el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
@@ -268,6 +278,12 @@
<span>{{ $t('topic.subscription.clearMessageConfirm') }}</span>
</el-form-item>
</el-form-item>
+ <el-form-item v-if="dialogStatus==='createSub'">
+ <el-input v-model="currentSubscription" placeholder="Please input sub name"/>
+ </el-form-item>
+ <el-form-item v-if="dialogStatus==='unsub'">
+ <h4>{{ $t('topic.subscription.deleteSubConfirm') }}</h4>
+ </el-form-item>
<el-form-item>
<el-button type="primary" @click="handleOptions()">{{ $t('table.confirm') }}</el-button>
<el-button @click="dialogFormVisible=false">{{ $t('table.cancel') }}</el-button>
@@ -294,6 +310,7 @@ import { fetchTopicsByPulsarManager } from '@/api/topics'
import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
import { formatBytes } from '@/utils/index'
import { numberFormatter } from '@/filters/index'
+import { putSubscriptionOnCluster, deleteSubscriptionOnCluster } from '@/api/subscriptions'
const defaultForm = {
persistent: '',
@@ -347,7 +364,9 @@ export default {
delete: this.$i18n.t('topic.deleteTopic'),
expire: this.$i18n.t('topic.subscription.msgExpired'),
clear: this.$i18n.t('topic.subscription.clearMessage'),
- reset: this.$i18n.t('topic.subscription.resetByTime')
+ reset: this.$i18n.t('topic.subscription.resetByTime'),
+ createSub: this.$i18n.t('topic.subscription.sub'),
+ deleteSub: this.$i18n.t('topic.subscription.unsub')
},
dialogFormVisible: false,
dialogStatus: '',
@@ -414,6 +433,7 @@ export default {
initTopicStats() {
fetchPartitionTopicStats(this.postForm.persistent, this.tenantNamespaceTopic, true).then(response => {
if (!response.data) return
+ this.partitionTopicStats = []
this.partitionTopicStats.push({
inMsg: numberFormatter(response.data.msgRateIn, 2),
outMsg: numberFormatter(response.data.msgRateOut, 2),
@@ -422,22 +442,30 @@ export default {
})
var prefix = this.postForm.persistent + '://' + this.tenantNamespaceTopic
var tempPartitionsList = Object.keys(response.data.partitions)
+ this.partitionsList = []
for (var i = 0; i < tempPartitionsList.length; i++) {
var key = prefix + '-partition-' + i
- var partition = this.postForm.topic + '-partition-' + i
- this.partitionsList.push({
- 'partition': partition,
- 'producers': response.data.partitions[key].publishers.length,
- 'subscriptions': Object.keys(response.data.partitions[key].subscriptions).length,
- 'inMsg': numberFormatter(response.data.partitions[key].msgRateIn, 2),
- 'outMsg': numberFormatter(response.data.partitions[key].msgRateOut, 2),
- 'inBytes': formatBytes(response.data.partitions[key].msgThroughputIn),
- 'outBytes': formatBytes(response.data.partitions[key].msgThroughputOut),
- 'storageSize': formatBytes(response.data.partitions[key].storageSize, 0),
- 'partitionTopicLink': '/management/topics/' + this.postForm.persistent + '/' + this.tenantNamespaceTopic + '-partition-' + i + '/topic'
- })
+ if (response.data.partitions.hasOwnProperty(key)) {
+ var partition = this.postForm.topic + '-partition-' + i
+ var publishers = 0
+ if (response.data.partitions[key].hasOwnProperty('publishers')) {
+ publishers = response.data.partitions[key].publishers.length
+ }
+ this.partitionsList.push({
+ 'partition': partition,
+ 'producers': publishers,
+ 'subscriptions': Object.keys(response.data.partitions[key].subscriptions).length,
+ 'inMsg': numberFormatter(response.data.partitions[key].msgRateIn, 2),
+ 'outMsg': numberFormatter(response.data.partitions[key].msgRateOut, 2),
+ 'inBytes': formatBytes(response.data.partitions[key].msgThroughputIn),
+ 'outBytes': formatBytes(response.data.partitions[key].msgThroughputOut),
+ 'storageSize': formatBytes(response.data.partitions[key].storageSize, 0),
+ 'partitionTopicLink': '/management/topics/' + this.postForm.persistent + '/' + this.tenantNamespaceTopic + '-partition-' + i + '/topic'
+ })
+ }
}
var index = 0
+ this.subscriptionsList = []
for (var s in response.data.subscriptions) {
index += 1
var type = 'Exclusive'
@@ -630,6 +658,12 @@ export default {
case 'clear':
this.clearAllSubMessage()
break
+ case 'createSub':
+ this.createSub()
+ break
+ case 'unsub':
+ this.deleteSub()
+ break
}
}
})
@@ -687,6 +721,44 @@ export default {
this.dialogFormVisible = false
this.getPartitionTopicInfo()
})
+ },
+ handleCreateSub() {
+ this.currentSubscription = ''
+ this.dialogStatus = 'createSub'
+ this.dialogFormVisible = true
+ },
+ createSub() {
+ if (this.currentSubscription.length <= 0) {
+ this.$notify({
+ title: 'error',
+ message: this.$i18n.t('topic.subscription.subNotification'),
+ type: 'error',
+ duration: 3000
+ })
+ return
+ }
+ putSubscriptionOnCluster(this.getCurrentCluster(), this.postForm.persistent, this.tenantNamespaceTopic, this.currentSubscription).then(response => {
+ this.$notify({
+ title: 'success',
+ message: this.$i18n.t('topic.subscription.createSubSuccess'),
+ type: 'success',
+ duration: 3000
+ })
+ this.initTopicStats()
+ this.dialogFormVisible = false
+ })
+ },
+ deleteSub() {
+ deleteSubscriptionOnCluster(this.getCurrentCluster(), this.postForm.persistent, this.tenantNamespaceTopic, this.currentSubscription).then(response => {
+ this.$notify({
+ title: 'success',
+ message: this.$i18n.t('topic.subscription.deleteSubSuccess'),
+ type: 'success',
+ duration: 3000
+ })
+ this.initTopicStats()
+ this.dialogFormVisible = false
+ })
}
}
}
diff --git a/front-end/src/views/management/topics/topic.vue b/front-end/src/views/management/topics/topic.vue
index 11f6fe7..43a0b3a 100644
--- a/front-end/src/views/management/topics/topic.vue
+++ b/front-end/src/views/management/topics/topic.vue
@@ -264,6 +264,13 @@
</el-col>
</el-row>
<h4>{{ $t('topic.subscription.subscriptions') }}</h4>
+ <el-button
+ class="filter-item"
+ type="success"
+ style="margin-bottom: 15px"
+ @click="handleCreateSub">
+ {{ $t('topic.subscription.newSub') }}
+ </el-button>
<el-row :gutter="24">
<el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 24}" :xl="{span: 24}">
<el-table
@@ -304,7 +311,7 @@
<el-table-column :label="$t('topic.subscription.backlog')" min-width="30px" align="center">
<template slot-scope="scope">
<span>{{ scope.row.backlog }}</span>
- <el-dropdown>
+ <el-dropdown @command="handleCommand(scope.row.subscription)">
<span class="el-dropdown-link"><i class="el-icon-more"/></span>
<el-dropdown-menu slot="dropdown">
<router-link :to="scope.row.subscriptionLink + '?topTab=backlogOperation&leftTab=skip'" class="link-type">
@@ -319,6 +326,7 @@
<router-link :to="scope.row.subscriptionLink + '?topTab=backlogOperation&leftTab=reset'" class="link-type">
<el-dropdown-item command="reset">{{ $t('topic.subscription.reset') }}</el-dropdown-item>
</router-link>
+ <el-dropdown-item command="unsub">{{ $t('topic.subscription.unsub') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
@@ -502,8 +510,16 @@
<el-form-item v-if="dialogStatus==='delete'">
<h4>{{ $t('topic.deleteTopicMessage') }}</h4>
</el-form-item>
+ <el-form-item v-if="dialogStatus==='createSub'">
+ <el-input v-model="subName" placeholder="Please input sub name"/>
+ </el-form-item>
+ <el-form-item v-if="dialogStatus==='deleteSub'">
+ <h4>{{ $t('topic.subscription.deleteSubConfirm') }}</h4>
+ </el-form-item>
<el-form-item>
- <el-button type="primary" @click="deleteTopic">{{ $t('table.confirm') }}</el-button>
+ <el-button v-if="dialogStatus==='delete'" type="primary" @click="deleteTopic">{{ $t('table.confirm') }}</el-button>
+ <el-button v-if="dialogStatus==='createSub'" type="primary" @click="createSub">{{ $t('table.confirm') }}</el-button>
+ <el-button v-if="dialogStatus==='deleteSub'" type="primary" @click="deleteSub">{{ $t('table.confirm') }}</el-button>
<el-button @click="dialogFormVisible=false">{{ $t('table.cancel') }}</el-button>
</el-form-item>
</el-form>
@@ -534,6 +550,7 @@ import {
import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
import { formatBytes } from '@/utils/index'
import { numberFormatter } from '@/filters/index'
+import { putSubscriptionOnCluster, deleteSubscriptionOnCluster } from '@/api/subscriptions'
const defaultForm = {
persistent: '',
@@ -612,8 +629,11 @@ export default {
currentTabName: '',
nonPersistent: false,
textMap: {
- delete: this.$i18n.t('topic.deleteTopic')
+ delete: this.$i18n.t('topic.deleteTopic'),
+ createSub: this.$i18n.t('topic.subscription.sub'),
+ deleteSub: this.$i18n.t('topic.subscription.unsub')
},
+ subName: '',
dialogFormVisible: false,
dialogStatus: '',
topicPartitions: {},
@@ -727,6 +747,7 @@ export default {
initTopicStats() {
fetchTopicStats(this.postForm.persistent, this.getFullTopic()).then(response => {
if (!response.data) return
+ this.topicStats = []
this.topicStats.push({
inMsg: numberFormatter(response.data.msgRateIn, 2),
outMsg: numberFormatter(response.data.msgRateOut, 2),
@@ -744,6 +765,7 @@ export default {
'since': response.data.publishers[i].connectedSince
})
}
+ this.subscriptionsList = []
for (var s in response.data.subscriptions) {
var type = ''
if (response.data.subscriptions[s].hasOwnProperty('type')) {
@@ -1025,6 +1047,49 @@ export default {
})
this.$router.push({ path: '/management/namespaces/' + this.postForm.tenant + '/' + this.postForm.namespace + '/namespace?tab=topics' })
})
+ },
+ handleCreateSub() {
+ this.subName = ''
+ this.dialogStatus = 'createSub'
+ this.dialogFormVisible = true
+ },
+ createSub() {
+ if (this.subName.length <= 0) {
+ this.$notify({
+ title: 'error',
+ message: this.$i18n.t('topic.subscription.subNotification'),
+ type: 'error',
+ duration: 3000
+ })
+ return
+ }
+ putSubscriptionOnCluster(this.getCurrentCluster(), this.postForm.persistent, this.getFullTopic(), this.subName).then(response => {
+ this.$notify({
+ title: 'success',
+ message: this.$i18n.t('topic.subscription.createSubSuccess'),
+ type: 'success',
+ duration: 3000
+ })
+ this.initTopicStats()
+ this.dialogFormVisible = false
+ })
+ },
+ handleCommand(subName) {
+ this.subName = subName
+ this.dialogStatus = 'deleteSub'
+ this.dialogFormVisible = true
+ },
+ deleteSub() {
+ deleteSubscriptionOnCluster(this.getCurrentCluster(), this.postForm.persistent, this.getFullTopic(), this.subName).then(response => {
+ this.$notify({
+ title: 'success',
+ message: this.$i18n.t('topic.subscription.deleteSubSuccess'),
+ type: 'success',
+ duration: 3000
+ })
+ this.initTopicStats()
+ this.dialogFormVisible = false
+ })
}
}
}