Merge pull request #178 from jianyi-gronk/feature/ui/improveServicePage
Improve service pages
diff --git a/ui-vue3/package.json b/ui-vue3/package.json
index c1ab225..322cec6 100644
--- a/ui-vue3/package.json
+++ b/ui-vue3/package.json
@@ -30,6 +30,7 @@
"less": "^4.2.0",
"lodash": "^4.17.21",
"mockjs": "^1.1.0",
+ "monaco-editor": "^0.45.0",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinyin-pro": "^3.19.3",
diff --git a/ui-vue3/src/api/mock/mockServiceDetail.ts b/ui-vue3/src/api/mock/mockServiceDetail.ts
new file mode 100644
index 0000000..438cec3
--- /dev/null
+++ b/ui-vue3/src/api/mock/mockServiceDetail.ts
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Mock from 'mockjs'
+
+Mock.mock('/mock/service/detail', 'get', {
+ code: 200,
+ message: 'success',
+ data: {
+ total: 8,
+ curPage: 1,
+ pageSize: 1,
+ data: {
+ serviceName: 'org.apache.dubbo.samples.UserService',
+ versionGroup: ['version=v1', 'version=2.0,group=group1'],
+ protocol: 'triple',
+ delay: '3000ms',
+ timeOut: '3000ms',
+ retry: 3,
+ requestTotal: 1384,
+ avgRT: '96ms',
+ avgQPS: 12,
+ obsolete: false
+ }
+ }
+})
diff --git a/ui-vue3/src/api/mock/mockServiceDistribution.ts b/ui-vue3/src/api/mock/mockServiceDistribution.ts
new file mode 100644
index 0000000..4a88fa4
--- /dev/null
+++ b/ui-vue3/src/api/mock/mockServiceDistribution.ts
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Mock from 'mockjs'
+
+Mock.mock('/mock/service/distribution', 'get', {
+ code: 200,
+ message: 'success',
+ data: {
+ total: 8,
+ curPage: 1,
+ pageSize: 1,
+ data: [
+ {
+ applicationName: 'shop-order',
+ instanceNum: 15,
+ instanceIP: [
+ '192.168.32.28:8697',
+ '192.168.32.26:20880',
+ '192.168.32.24:28080',
+ '192.168.32.22:20880'
+ ]
+ },
+ {
+ applicationName: 'shop-order',
+ instanceNum: 15,
+ instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+ },
+ {
+ applicationName: 'shop-user',
+ instanceNum: 12,
+ instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+ },
+ {
+ applicationName: 'shop-order',
+ instanceNum: 15,
+ instanceIP: [
+ '192.168.32.28:8697',
+ '192.168.32.26:20880',
+ '192.168.32.24:28080',
+ '192.168.32.22:20880'
+ ]
+ },
+ {
+ applicationName: 'shop-order',
+ instanceNum: 15,
+ instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+ },
+ {
+ applicationName: 'shop-user',
+ instanceNum: 12,
+ instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+ },
+ {
+ applicationName: 'shop-order',
+ instanceNum: 15,
+ instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+ },
+ {
+ applicationName: 'shop-user',
+ instanceNum: 12,
+ instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+ },
+ {
+ applicationName: 'shop-user',
+ instanceNum: 12,
+ instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+ },
+ {
+ applicationName: 'shop-order',
+ instanceNum: 15,
+ instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+ },
+ {
+ applicationName: 'shop-user',
+ instanceNum: 12,
+ instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+ }
+ ]
+ }
+})
diff --git a/ui-vue3/src/api/service/serviceDetail.ts b/ui-vue3/src/api/service/serviceDetail.ts
new file mode 100644
index 0000000..3fe9a27
--- /dev/null
+++ b/ui-vue3/src/api/service/serviceDetail.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import request from '@/base/http/request'
+
+export const getServiceDetail = (params: any): Promise<any> => {
+ return request({
+ url: '/service/detail',
+ method: 'get',
+ params
+ })
+}
diff --git a/ui-vue3/src/api/service/serviceDistribution.ts b/ui-vue3/src/api/service/serviceDistribution.ts
new file mode 100644
index 0000000..61dbb52
--- /dev/null
+++ b/ui-vue3/src/api/service/serviceDistribution.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import request from '@/base/http/request'
+
+export const getServiceDistribution = (params: any): Promise<any> => {
+ return request({
+ url: '/service/distribution',
+ method: 'get',
+ params
+ })
+}
diff --git a/ui-vue3/src/base/i18n/zh.ts b/ui-vue3/src/base/i18n/zh.ts
index de115e2..0553d2d 100644
--- a/ui-vue3/src/base/i18n/zh.ts
+++ b/ui-vue3/src/base/i18n/zh.ts
@@ -277,7 +277,6 @@
noPageTip: '抱歉,你访问的页面不存在',
globalSearchTip: '搜索ip,应用,实例,服务',
- globalSearchTip: '搜索ip,应用,实例,服务',
placeholder: {
typeAppName: '请输入应用名,支持前缀搜索',
typeDefault: '请输入'
diff --git a/ui-vue3/src/views/resources/services/search.vue b/ui-vue3/src/views/resources/services/search.vue
index 9d36ebf..73ef847 100644
--- a/ui-vue3/src/views/resources/services/search.vue
+++ b/ui-vue3/src/views/resources/services/search.vue
@@ -16,112 +16,86 @@
-->
<template>
<div class="__container_services_index">
- <a-flex vertical>
- <a-flex class="service-filter">
- <a-input-search
- v-model:value="serviceName"
- placeholder="请输入"
- class="service-name-input"
- @search="debounceSearch"
- enter-button
- />
- </a-flex>
- <a-table
- :columns="columns"
- :data-source="dataSource"
- :pagination="pagination"
- :scroll="{ y: '55vh' }"
- >
- <template #bodyCell="{ column, text }">
- <template v-if="column.dataIndex === 'serviceName'">
- <a-button type="link" @click="viewDetail(text)">{{ text }}</a-button>
- </template>
+ <search-table :search-domain="searchDomain">
+ <template #bodyCell="{ column, text }">
+ <template v-if="column.dataIndex === 'serviceName'">
+ <a-button type="link" @click="viewDetail(text)">{{ text }}</a-button>
</template>
- </a-table>
- </a-flex>
+ </template>
+ </search-table>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
-import type { ComponentInternalInstance } from 'vue'
-import { ref, getCurrentInstance } from 'vue'
+import { reactive, provide } from 'vue'
import { searchService } from '@/api/service/service.ts'
-import { debounce } from 'lodash'
-
-const {
- appContext: {
- config: { globalProperties }
- }
-} = <ComponentInternalInstance>getCurrentInstance()
-
-const serviceName = ref('')
+import { SearchDomain } from '@/utils/SearchUtil'
+import SearchTable from '@/components/SearchTable.vue'
+import { PROVIDE_INJECT_KEY } from '@/base/enums/ProvideInject'
const router = useRouter()
const columns = [
{
title: '服务',
dataIndex: 'serviceName',
+ key: 'serviceName',
sorter: true,
width: '30%'
},
{
title: '接口数',
dataIndex: 'interfaceNum',
+ key: 'interfaceNum',
sorter: true,
width: '10%'
},
{
title: '近 1min QPS',
dataIndex: 'avgQPS',
+ key: 'avgQPS',
sorter: true,
width: '15%'
},
{
title: '近 1min RT',
dataIndex: 'avgRT',
+ key: 'avgRT',
sorter: true,
width: '15%'
},
{
title: '近 1min 请求总量',
dataIndex: 'requestTotal',
+ key: 'requestTotal',
sorter: true,
width: '15%'
}
]
-const dataSource = ref([])
+const searchDomain = reactive(
+ new SearchDomain(
+ [
+ {
+ label: '服务名',
+ param: 'serviceName',
+ placeholder: '请输入',
+ style: {
+ width: '200px'
+ }
+ }
+ ],
+ searchService,
+ columns
+ )
+)
-const onSearch = async () => {
- let { data } = await searchService({})
- dataSource.value = data.data
-}
-
-onSearch()
-
-const debounceSearch = debounce(onSearch, 300)
+searchDomain.onSearch()
const viewDetail = (serviceName: string) => {
router.push({ name: 'detail', params: { serviceName } })
}
-const pagination = {
- showTotal: (v: any) =>
- globalProperties.$t('searchDomain.total') +
- ': ' +
- v +
- ' ' +
- globalProperties.$t('searchDomain.unit')
-}
+provide(PROVIDE_INJECT_KEY.SEARCH_DOMAIN, searchDomain)
</script>
-<style lang="less" scoped>
-.__container_services_index {
- .service-filter {
- margin-bottom: 20px;
- .service-name-input {
- width: 500px;
- }
- }
-}
-</style>
+<style lang="less" scoped></style>
diff --git a/ui-vue3/src/views/resources/services/tabs/debug.vue b/ui-vue3/src/views/resources/services/tabs/debug.vue
index 5535074..7c911d6 100644
--- a/ui-vue3/src/views/resources/services/tabs/debug.vue
+++ b/ui-vue3/src/views/resources/services/tabs/debug.vue
@@ -48,20 +48,10 @@
<a-tree block-node :tree-data="outputParamType" class="description-item-content" />
</a-descriptions-item>
<a-descriptions-item label="请求" :span="2">
- <a-textarea
- v-model="requestValue"
- placeholder="请输入"
- :rows="4"
- class="description-item-content"
- />
+ <div ref="requestEditor" class="editor"></div>
</a-descriptions-item>
<a-descriptions-item label="响应" :span="2">
- <a-textarea
- v-model="responseValue"
- placeholder="请输入"
- :rows="4"
- class="description-item-content"
- />
+ <div ref="responseEditor" class="editor"></div>
</a-descriptions-item>
</a-descriptions>
<a-button type="primary">发送请求</a-button>
@@ -71,7 +61,8 @@
</template>
<script setup lang="ts">
-import { ref, reactive } from 'vue'
+import { ref, reactive, onMounted } from 'vue'
+import * as monaco from 'monaco-editor'
const methodTabs = reactive([
'login',
@@ -151,8 +142,24 @@
}
]
-const requestValue = ref('')
-const responseValue = ref('')
+const requestEditor = ref(null)
+const responseEditor = ref(null)
+
+const createEditor = (element: HTMLElement) => {
+ monaco.editor.create(element, {
+ value: '',
+ language: 'json',
+ theme: 'vs-dark',
+ automaticLayout: true,
+ fontSize: 14,
+ lineNumbers: 'off',
+ roundedSelection: true
+ })
+}
+onMounted(() => {
+ createEditor(requestEditor.value![0])
+ createEditor(responseEditor.value![0])
+})
</script>
<style lang="less" scoped>
.__container_services_tabs_debug {
@@ -165,5 +172,9 @@
margin-left: 20px;
width: 90%;
}
+ .editor {
+ width: 90%;
+ height: 300px;
+ }
}
</style>
diff --git a/ui-vue3/src/views/resources/services/tabs/detail.vue b/ui-vue3/src/views/resources/services/tabs/detail.vue
index 1a26063..fe0751d 100644
--- a/ui-vue3/src/views/resources/services/tabs/detail.vue
+++ b/ui-vue3/src/views/resources/services/tabs/detail.vue
@@ -19,46 +19,59 @@
<a-flex>
<a-descriptions class="description-column" :column="1" layout="vertical">
<a-descriptions-item label="服务名">
- <p class="description-item-content">org.apache.dubbo.samples.UserService</p>
+ <p class="description-item-content">{{ serviceDetail.serviceName }}</p>
</a-descriptions-item>
<a-descriptions-item label="服务版本&分组">
<div class="description-item-content">
- <a-tag color="cyan">version=v1</a-tag>
- <a-tag color="cyan">version=2.0,group=group1</a-tag>
+ <a-tag color="cyan" v-for="item in serviceDetail.versionGroup" :key="item">{{
+ item
+ }}</a-tag>
</div>
</a-descriptions-item>
<a-descriptions-item label="RPC协议">
- <p class="description-item-content">triple</p>
+ <p class="description-item-content">{{ serviceDetail.protocol }}</p>
</a-descriptions-item>
<a-descriptions-item label="延迟注册时间">
- <p class="description-item-content">3000ms</p>
+ <p class="description-item-content">{{ serviceDetail.delay }}</p>
</a-descriptions-item>
<a-descriptions-item label="超时时间">
- <p class="description-item-content">3000ms</p>
+ <p class="description-item-content">{{ serviceDetail.timeOut }}</p>
</a-descriptions-item>
<a-descriptions-item label="重试次数">
- <p class="description-item-content">3</p>
+ <p class="description-item-content">{{ serviceDetail.retry }}</p>
</a-descriptions-item>
</a-descriptions>
<a-descriptions class="description-column" :column="1" layout="vertical">
<a-descriptions-item label="请求总量(1min)">
- <p class="description-item-content">1384</p>
+ <p class="description-item-content">{{ serviceDetail.requestTotal }}</p>
</a-descriptions-item>
<a-descriptions-item label="平均RT(1min)">
- <p class="description-item-content">96ms</p>
+ <p class="description-item-content">{{ serviceDetail.avgRT }}</p>
</a-descriptions-item>
<a-descriptions-item label="平均qps(1min)">
- <p class="description-item-content">12</p>
+ <p class="description-item-content">{{ serviceDetail.avgQPS }}</p>
</a-descriptions-item>
<a-descriptions-item label="是否过时">
- <p class="description-item-content">false</p>
+ <p class="description-item-content">{{ serviceDetail.obsolete }}</p>
</a-descriptions-item>
</a-descriptions>
</a-flex>
</div>
</template>
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { ref } from 'vue'
+import { getServiceDetail } from '@/api/service/serviceDetail'
+
+const serviceDetail = ref({})
+const onSearch = async () => {
+ const { data } = await getServiceDetail()
+ serviceDetail.value = data.data
+}
+
+onSearch()
+</script>
+
<style lang="less" scoped>
.__container_services_tabs_detail {
.description-item-content {
diff --git a/ui-vue3/src/views/resources/services/tabs/distribution.vue b/ui-vue3/src/views/resources/services/tabs/distribution.vue
index 9a402e6..796f77f 100644
--- a/ui-vue3/src/views/resources/services/tabs/distribution.vue
+++ b/ui-vue3/src/views/resources/services/tabs/distribution.vue
@@ -25,18 +25,19 @@
v-model:value="versionAndGroup"
:options="versionAndGroupOptions"
class="service-filter-select"
+ @change="debounceSearch"
></a-select>
</div>
<a-input-search
v-model:value="searchValue"
placeholder="搜索应用,ip,支持前缀搜索"
class="service-filter-input"
- @search="() => {}"
+ @search="debounceSearch"
enter-button
/>
</a-flex>
<div>
- <a-radio-group v-model:value="type" button-style="solid">
+ <a-radio-group v-model:value="type" button-style="solid" @click="debounceSearch">
<a-radio-button value="producer">生产者</a-radio-button>
<a-radio-button value="consumer">消费者</a-radio-button>
</a-radio-group>
@@ -67,6 +68,8 @@
<script setup lang="ts">
import type { ComponentInternalInstance } from 'vue'
import { ref, reactive, getCurrentInstance } from 'vue'
+import { getServiceDistribution } from '@/api/service/serviceDistribution'
+import { debounce } from 'lodash'
const {
appContext: {
@@ -116,73 +119,15 @@
}
]
-const tableData = reactive([
- {
- applicationName: 'shop-order',
- instanceNum: 15,
- instanceIP: [
- '192.168.32.28:8697',
- '192.168.32.26:20880',
- '192.168.32.24:28080',
- '192.168.32.22:20880'
- ]
- },
- {
- applicationName: 'shop-order',
- instanceNum: 15,
- instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
- },
- {
- applicationName: 'shop-user',
- instanceNum: 12,
- instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
- },
- {
- applicationName: 'shop-order',
- instanceNum: 15,
- instanceIP: [
- '192.168.32.28:8697',
- '192.168.32.26:20880',
- '192.168.32.24:28080',
- '192.168.32.22:20880'
- ]
- },
- {
- applicationName: 'shop-order',
- instanceNum: 15,
- instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
- },
- {
- applicationName: 'shop-user',
- instanceNum: 12,
- instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
- },
- {
- applicationName: 'shop-order',
- instanceNum: 15,
- instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
- },
- {
- applicationName: 'shop-user',
- instanceNum: 12,
- instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
- },
- {
- applicationName: 'shop-user',
- instanceNum: 12,
- instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
- },
- {
- applicationName: 'shop-order',
- instanceNum: 15,
- instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
- },
- {
- applicationName: 'shop-user',
- instanceNum: 12,
- instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
- }
-])
+const tableData = ref([])
+
+const onSearch = async () => {
+ let { data } = await getServiceDistribution({})
+ tableData.value = data.data
+}
+onSearch()
+
+const debounceSearch = debounce(onSearch, 300)
const pagination = {
showTotal: (v: any) =>
diff --git a/ui-vue3/vite.config.ts b/ui-vue3/vite.config.ts
index 9f66e50..499c805 100644
--- a/ui-vue3/vite.config.ts
+++ b/ui-vue3/vite.config.ts
@@ -42,7 +42,8 @@
],
resolve: {
alias: {
- '@': fileURLToPath(new URL('./src', import.meta.url))
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
+ 'monaco-editor': 'monaco-editor/esm/vs/editor/editor.api.js'
},
// ignore suffix
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
diff --git a/ui-vue3/yarn.lock b/ui-vue3/yarn.lock
index 12b3784..01450d8 100644
--- a/ui-vue3/yarn.lock
+++ b/ui-vue3/yarn.lock
@@ -4134,6 +4134,11 @@
dependencies:
commander "*"
+monaco-editor@^0.45.0:
+ version "0.45.0"
+ resolved "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.45.0.tgz#6939123a6254aea9fea2d647697f846306dd4448"
+ integrity sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==
+
ms@2.1.2:
version "2.1.2"
resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"