Merge pull request #258 from jianyi-gronk/feat/ui/complete_service
improve service pages
diff --git a/ui-vue3/src/api/mock/mockService.ts b/ui-vue3/src/api/mock/mockService.ts
index c10e0cd..2383bcd 100644
--- a/ui-vue3/src/api/mock/mockService.ts
+++ b/ui-vue3/src/api/mock/mockService.ts
@@ -28,56 +28,192 @@
data: [
{
serviceName: 'org.apache.dubbo.samples.UserService',
- interfaceNum: 4,
+ versionGroup: [
+ {
+ version: '1.0.0',
+ group: 'group1'
+ },
+ {
+ version: '1.0.0',
+ group: null
+ },
+ {
+ version: null,
+ group: 'group1'
+ },
+ {
+ version: null,
+ group: null
+ }
+ ],
avgQPS: 6,
avgRT: '194ms',
requestTotal: 200
},
{
serviceName: 'org.apache.dubbo.samples.OrderService',
- interfaceNum: 12,
+ versionGroup: [
+ {
+ version: '1.0.0',
+ group: 'group1'
+ },
+ {
+ version: '1.0.0',
+ group: null
+ },
+ {
+ version: null,
+ group: 'group1'
+ },
+ {
+ version: null,
+ group: null
+ }
+ ],
avgQPS: 13,
avgRT: '189ms',
requestTotal: 164
},
{
serviceName: 'org.apache.dubbo.samples.DetailService',
- interfaceNum: 14,
+ versionGroup: [
+ {
+ version: '1.0.0',
+ group: 'group1'
+ },
+ {
+ version: '1.0.0',
+ group: null
+ },
+ {
+ version: null,
+ group: 'group1'
+ },
+ {
+ version: null,
+ group: null
+ }
+ ],
avgQPS: 0.5,
avgRT: '268ms',
requestTotal: 1324
},
{
serviceName: 'org.apache.dubbo.samples.PayService',
- interfaceNum: 8,
+ versionGroup: [
+ {
+ version: '1.0.0',
+ group: 'group1'
+ },
+ {
+ version: '1.0.0',
+ group: null
+ },
+ {
+ version: null,
+ group: 'group1'
+ },
+ {
+ version: null,
+ group: null
+ }
+ ],
avgQPS: 9,
avgRT: '346ms',
requestTotal: 189
},
{
serviceName: 'org.apache.dubbo.samples.CommentService',
- interfaceNum: 9,
+ versionGroup: [
+ {
+ version: '1.0.0',
+ group: 'group1'
+ },
+ {
+ version: '1.0.0',
+ group: null
+ },
+ {
+ version: null,
+ group: 'group1'
+ },
+ {
+ version: null,
+ group: null
+ }
+ ],
avgQPS: 8,
avgRT: '936ms',
requestTotal: 200
},
{
serviceName: 'org.apache.dubbo.samples.RepayService',
- interfaceNum: 16,
+ versionGroup: [
+ {
+ version: '1.0.0',
+ group: 'group1'
+ },
+ {
+ version: '1.0.0',
+ group: null
+ },
+ {
+ version: null,
+ group: 'group1'
+ },
+ {
+ version: null,
+ group: null
+ }
+ ],
avgQPS: 17,
avgRT: '240ms',
requestTotal: 146
},
{
serviceName: 'org.apche.dubbo.samples.TransportService',
- interfaceNum: 5,
+ versionGroup: [
+ {
+ version: '1.0.0',
+ group: 'group1'
+ },
+ {
+ version: '1.0.0',
+ group: null
+ },
+ {
+ version: null,
+ group: 'group1'
+ },
+ {
+ version: null,
+ group: null
+ }
+ ],
avgQPS: 43,
avgRT: '89ms',
requestTotal: 367
},
{
serviceName: 'org.apche.dubbo.samples.DistributionService',
- interfaceNum: 5,
+ versionGroup: [
+ {
+ version: '1.0.0',
+ group: 'group1'
+ },
+ {
+ version: '1.0.0',
+ group: null
+ },
+ {
+ version: null,
+ group: 'group1'
+ },
+ {
+ version: null,
+ group: null
+ }
+ ],
avgQPS: 4,
avgRT: '78ms',
requestTotal: 145
diff --git a/ui-vue3/src/api/mock/mockServiceDistribution.ts b/ui-vue3/src/api/mock/mockServiceDistribution.ts
index 4a88fa4..23b74aa 100644
--- a/ui-vue3/src/api/mock/mockServiceDistribution.ts
+++ b/ui-vue3/src/api/mock/mockServiceDistribution.ts
@@ -28,67 +28,101 @@
{
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'
- ]
+ instanceName: 'shop-order0',
+ rpcPort: '172.168.45.89:20888',
+ timeout: '1000ms',
+ retryNum: '2',
+ label: 'region=beijing'
},
{
applicationName: 'shop-order',
instanceNum: 15,
- instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+ instanceName: 'shop-order1',
+ rpcPort: '172.168.45.24:20888',
+ timeout: '500ms',
+ retryNum: '1',
+ label: 'region=wuhan'
},
{
applicationName: 'shop-user',
instanceNum: 12,
- instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+ instanceName: 'shop-order2',
+ rpcPort: '172.161.23.89:20888',
+ timeout: '200ms',
+ retryNum: '1',
+ label: 'region=shanghai'
},
{
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'
- ]
+ instanceName: 'shop-order3',
+ rpcPort: '172.168.45.89:12423',
+ timeout: '2000ms',
+ retryNum: '2',
+ label: 'region=hangzhou'
},
{
applicationName: 'shop-order',
instanceNum: 15,
- instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+ instanceName: 'shop-order4',
+ rpcPort: '172.168.45.89:20888',
+ timeout: '100ms',
+ retryNum: '0',
+ label: 'region=wuxi'
},
{
applicationName: 'shop-user',
instanceNum: 12,
- instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+ instanceName: 'shop-order5',
+ rpcPort: '172.168.45.89:20888',
+ timeout: '1000ms',
+ retryNum: '2',
+ label: 'region=beijing'
},
{
applicationName: 'shop-order',
instanceNum: 15,
- instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+ instanceName: 'shop-order6',
+ rpcPort: '172.168.45.89:20888',
+ timeout: '1000ms',
+ retryNum: '2',
+ label: 'region=ningbo'
},
{
applicationName: 'shop-user',
instanceNum: 12,
- instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+ instanceName: 'shop-order7',
+ rpcPort: '172.168.45.89:20888',
+ timeout: '1000ms',
+ retryNum: '2',
+ label: 'region=shenzhen'
},
{
applicationName: 'shop-user',
instanceNum: 12,
- instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+ instanceName: 'shop-order8',
+ rpcPort: '172.168.45.89:20888',
+ timeout: '1000ms',
+ retryNum: '2',
+ label: 'region=guangzhou'
},
{
applicationName: 'shop-order',
instanceNum: 15,
- instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+ instanceName: 'shop-order9',
+ rpcPort: '172.168.45.89:20888',
+ timeout: '1000ms',
+ retryNum: '2',
+ label: 'region=nanjing'
},
{
applicationName: 'shop-user',
instanceNum: 12,
- instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+ instanceName: 'shop-order10',
+ rpcPort: '172.168.45.89:20888',
+ timeout: '1000ms',
+ retryNum: '2',
+ label: 'region=beijing'
}
]
}
diff --git a/ui-vue3/src/base/i18n/en.ts b/ui-vue3/src/base/i18n/en.ts
index 03deca7..697a47f 100644
--- a/ui-vue3/src/base/i18n/en.ts
+++ b/ui-vue3/src/base/i18n/en.ts
@@ -61,6 +61,9 @@
labels: 'Labels',
startTime_k8s: 'StartTime(k8s)'
},
+ serviceDomain: {
+ name: 'Name'
+ },
appServiceTimeout: 'Adjusting the timeout for application service provision',
enableAppInstanceLogs: 'Enable access logs for all instances of this application',
appServiceLoadBalance: 'Adjusting the load balancing strategy for application service provision',
@@ -99,6 +102,10 @@
instance: 'Instance',
resourceDetails: 'Resource Details',
service: 'Service',
+ versionGroup: 'Version & Group',
+ avgQPS: 'last 1min QPS',
+ avgRT: 'last 1min RT',
+ requestTotal: 'last 1min request total',
serviceSearch: 'Search Service',
serviceGovernance: 'Routing Rule',
trafficManagement: 'Traffic Management',
@@ -391,6 +398,7 @@
debug: 'Debug',
distribution: 'Distribution',
tracing: 'Tracing',
+ sceneConfig: 'Scene Config',
provideService: 'Provide Service',
dependentService: 'Dependent Service',
diff --git a/ui-vue3/src/base/i18n/zh.ts b/ui-vue3/src/base/i18n/zh.ts
index 8428a8c..6215763 100644
--- a/ui-vue3/src/base/i18n/zh.ts
+++ b/ui-vue3/src/base/i18n/zh.ts
@@ -61,7 +61,14 @@
creationTime_k8s: '创建时间(k8s)',
startTime_k8s: '启动时间(k8s)'
},
+ serviceDomain: {
+ name: '服务名'
+ },
service: '服务',
+ versionGroup: '版本&分组',
+ avgQPS: '近1min QPS',
+ avgRT: '近1min RT',
+ requestTotal: '近1min 请求总量',
serviceSearch: '服务查询',
serviceGovernance: '路由规则',
trafficManagement: '流量管控',
@@ -363,6 +370,7 @@
distribution: '分布',
monitor: '监控',
tracing: '链路追踪',
+ sceneConfig: '场景配置',
event: '事件',
provideService: '提供服务',
diff --git a/ui-vue3/src/router/defaultRoutes.ts b/ui-vue3/src/router/defaultRoutes.ts
index d67c200..d3ebc54 100644
--- a/ui-vue3/src/router/defaultRoutes.ts
+++ b/ui-vue3/src/router/defaultRoutes.ts
@@ -20,6 +20,7 @@
import LayoutTab from '../layout/tab/layout_tab.vue'
import _ from 'lodash'
import AppTabHeaderSlot from '@/views/resources/applications/slots/AppTabHeaderSlot.vue'
+import ServiceTabHeaderSlot from '@/views/resources/services/slots/ServiceTabHeaderSlot.vue'
export declare type RouteRecordType = RouteRecordRaw & {
key?: string
@@ -208,7 +209,10 @@
redirect: 'search',
component: LayoutTab,
meta: {
- tab_parent: true
+ tab_parent: true,
+ slots: {
+ header: ServiceTabHeaderSlot
+ }
},
children: [
{
@@ -220,7 +224,7 @@
}
},
{
- path: '/detail/:serviceName',
+ path: '/detail/:pathId',
name: 'detail',
component: () => import('../views/resources/services/tabs/detail.vue'),
meta: {
@@ -228,7 +232,7 @@
}
},
{
- path: '/debug/:serviceName',
+ path: '/debug/:pathId',
name: 'debug',
component: () => import('../views/resources/services/tabs/debug.vue'),
meta: {
@@ -236,7 +240,7 @@
}
},
{
- path: '/distribution/:serviceName',
+ path: '/distribution/:pathId',
name: 'distribution',
component: () => import('../views/resources/services/tabs/distribution.vue'),
meta: {
@@ -244,7 +248,7 @@
}
},
{
- path: '/monitor/:serviceName',
+ path: '/monitor/:pathId',
name: 'monitor',
component: () => import('../views/resources/services/tabs/monitor.vue'),
meta: {
@@ -252,7 +256,7 @@
}
},
{
- path: '/tracing/:serviceName',
+ path: '/tracing/:pathId',
name: 'tracing',
component: () => import('../views/resources/services/tabs/tracing.vue'),
meta: {
@@ -260,7 +264,15 @@
}
},
{
- path: '/event/:serviceName',
+ path: '/sceneConfig/:pathId',
+ name: 'sceneConfig',
+ component: () => import('../views/resources/services/tabs/sceneConfig.vue'),
+ meta: {
+ tab: true
+ }
+ },
+ {
+ path: '/event/:pathId',
name: 'event',
component: () => import('../views/resources/services/tabs/event.vue'),
meta: {
diff --git a/ui-vue3/src/utils/SearchUtil.ts b/ui-vue3/src/utils/SearchUtil.ts
index aeb39ec..e6f0fcb 100644
--- a/ui-vue3/src/utils/SearchUtil.ts
+++ b/ui-vue3/src/utils/SearchUtil.ts
@@ -41,6 +41,7 @@
]
searchApi: Function
result: any
+ handleResult?: Function
tableStyle: any
table: {
loading?: boolean
@@ -57,7 +58,8 @@
searchApi: any,
columns: TableColumnsType | any,
paged?: any | undefined,
- noPaged?: boolean
+ noPaged?: boolean,
+ handleResult?: Function
) {
this.params = query
this.noPaged = noPaged
@@ -72,16 +74,17 @@
this.paged = { ...this.paged, ...paged }
}
this.searchApi = searchApi
- this.onSearch()
+ handleResult && this.onSearch(handleResult)
}
- async onSearch() {
+ async onSearch(handleResult: Function) {
this.table.loading = true
setTimeout(() => {
this.table.loading = false
}, 5000)
- let res = (await this.searchApi(this.queryForm || {})).data
- this.result = res.data
+ const res = (await this.searchApi(this.queryForm || {})).data
+ this.result = handleResult ? handleResult(res.data) : res.data
+ console.log(this.result)
this.paged.total = res.total
this.table.loading = false
}
diff --git a/ui-vue3/src/views/resources/services/search.vue b/ui-vue3/src/views/resources/services/search.vue
index 73ef847..64db7ad 100644
--- a/ui-vue3/src/views/resources/services/search.vue
+++ b/ui-vue3/src/views/resources/services/search.vue
@@ -19,7 +19,23 @@
<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>
+ <span class="service-link" @click="viewDetail(text)">
+ <b>
+ <Icon style="margin-bottom: -2px" icon="material-symbols:attach-file-rounded"></Icon>
+ {{ text }}
+ </b>
+ </span>
+ </template>
+ <template v-else-if="column.dataIndex === 'versionGroupSelect'">
+ <a-select v-model:value="text.versionGroupValue" :bordered="false" style="width: 80%">
+ <a-select-option
+ v-for="(item, index) in text.versionGroupArr"
+ :value="item"
+ :key="index"
+ >
+ {{ item }}
+ </a-select-option>
+ </a-select>
</template>
</template>
</search-table>
@@ -33,69 +49,99 @@
import { SearchDomain } from '@/utils/SearchUtil'
import SearchTable from '@/components/SearchTable.vue'
import { PROVIDE_INJECT_KEY } from '@/base/enums/ProvideInject'
+import { PRIMARY_COLOR } from '@/base/constants'
+import { Icon } from '@iconify/vue'
+let __null = PRIMARY_COLOR
const router = useRouter()
const columns = [
{
- title: '服务',
+ title: 'service',
+ key: 'service',
dataIndex: 'serviceName',
- key: 'serviceName',
sorter: true,
width: '30%'
},
{
- title: '接口数',
- dataIndex: 'interfaceNum',
- key: 'interfaceNum',
- sorter: true,
- width: '10%'
+ title: 'versionGroup',
+ key: 'versionGroup',
+ dataIndex: 'versionGroupSelect',
+ width: '25%'
},
{
- title: '近 1min QPS',
- dataIndex: 'avgQPS',
+ title: 'avgQPS',
key: 'avgQPS',
+ dataIndex: 'avgQPS',
sorter: true,
width: '15%'
},
{
- title: '近 1min RT',
- dataIndex: 'avgRT',
+ title: 'avgRT',
key: 'avgRT',
+ dataIndex: 'avgRT',
sorter: true,
width: '15%'
},
{
- title: '近 1min 请求总量',
- dataIndex: 'requestTotal',
+ title: 'requestTotal',
key: 'requestTotal',
+ dataIndex: 'requestTotal',
sorter: true,
width: '15%'
}
]
+const handleResult = (result: any) => {
+ return result.map((service) => {
+ service.versionGroupSelect = {}
+ service.versionGroupSelect.versionGroupArr = service.versionGroup.map((item: any) => {
+ return (item.versionGroup =
+ (item.version ? 'version: ' + item.version + ', ' : '') +
+ (item.group ? 'group: ' + item.group : '') || '无')
+ })
+ service.versionGroupSelect.versionGroupValue = service.versionGroupSelect.versionGroupArr[0]
+ return service
+ })
+}
+
const searchDomain = reactive(
new SearchDomain(
[
{
- label: '服务名',
+ label: 'serviceName',
param: 'serviceName',
- placeholder: '请输入',
+ placeholder: 'typeAppName',
style: {
width: '200px'
}
}
],
searchService,
- columns
+ columns,
+ undefined,
+ undefined,
+ handleResult
)
)
-searchDomain.onSearch()
+searchDomain.onSearch(handleResult)
const viewDetail = (serviceName: string) => {
- router.push({ name: 'detail', params: { serviceName } })
+ router.push({ name: 'detail', params: { pathId: serviceName } })
}
provide(PROVIDE_INJECT_KEY.SEARCH_DOMAIN, searchDomain)
</script>
-<style lang="less" scoped></style>
+<style lang="less" scoped>
+.__container_services_index {
+ .service-link {
+ padding: 4px 10px 4px 4px;
+ border-radius: 4px;
+ color: v-bind('PRIMARY_COLOR');
+ &:hover {
+ cursor: pointer;
+ background: rgba(133, 131, 131, 0.13);
+ }
+ }
+}
+</style>
diff --git a/ui-vue3/src/views/resources/services/slots/ServiceTabHeaderSlot.vue b/ui-vue3/src/views/resources/services/slots/ServiceTabHeaderSlot.vue
new file mode 100644
index 0000000..2962a4e
--- /dev/null
+++ b/ui-vue3/src/views/resources/services/slots/ServiceTabHeaderSlot.vue
@@ -0,0 +1,46 @@
+<!--
+ ~ 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.
+-->
+<template>
+ <!-- example like blow-->
+ <div class="__container_ServiceTabHeaderSlot">
+ <span class="header-desc">{{ $t('serviceDomain.name') }}: {{ route.params?.pathId }}</span>
+ <!-- <a-select
+ v-model:value="versionGroupSelect.versionGroupValue"
+ :bordered="false"
+ style="width: 80%"
+ >
+ <a-select-option v-for="(item, index) in versionGroupSelect.versionGroupArr" :value="item" :key="index">
+ {{ item }}
+ </a-select-option>
+ </a-select> -->
+ </div>
+</template>
+
+<script setup lang="ts">
+import { inject } from 'vue'
+import { useRoute } from 'vue-router'
+import { PROVIDE_INJECT_KEY } from '@/base/enums/ProvideInject'
+const route = useRoute()
+</script>
+<style lang="less" scoped>
+.__container_ServiceTabHeaderSlot {
+ .header-desc {
+ line-height: 30px;
+ vertical-align: center;
+ }
+}
+</style>
diff --git a/ui-vue3/src/views/resources/services/tabs/debug.vue b/ui-vue3/src/views/resources/services/tabs/debug.vue
index 6e67003..796349f 100644
--- a/ui-vue3/src/views/resources/services/tabs/debug.vue
+++ b/ui-vue3/src/views/resources/services/tabs/debug.vue
@@ -19,20 +19,12 @@
<div class="tabs-title">方法列表</div>
<a-tabs v-model:activeKey="activeKey" tab-position="left" :tabBarStyle="{ width: '200px' }">
<a-tab-pane v-for="tabName in methodTabs" :key="tabName" :tab="`${tabName}`">
- <a-descriptions :column="4" layout="vertical">
- <a-descriptions-item label="接口" :span="2">
+ <a-descriptions :column="2" layout="vertical">
+ <a-descriptions-item label="接口">
<p class="description-item-content">
org.apache.dubbo.samples.UserService:getPermissions
</p>
</a-descriptions-item>
- <a-descriptions-item label="版本&分组">
- <a-select
- v-model:value="versionAndGroup"
- size="large"
- :options="versionAndGroupOptions"
- class="description-item-content"
- ></a-select>
- </a-descriptions-item>
<a-descriptions-item label="指定生产者">
<a-select
v-model:value="versionAndGroup"
@@ -41,16 +33,16 @@
class="description-item-content"
></a-select>
</a-descriptions-item>
- <a-descriptions-item label="入参类型" :span="2">
+ <a-descriptions-item label="入参类型">
<a-tree block-node :tree-data="enterParamType" class="description-item-content" />
</a-descriptions-item>
- <a-descriptions-item label="出参类型" :span="2">
+ <a-descriptions-item label="出参类型">
<a-tree block-node :tree-data="outputParamType" class="description-item-content" />
</a-descriptions-item>
- <a-descriptions-item label="请求" :span="2">
+ <a-descriptions-item label="请求">
<monaco-editor editorId="requestEditor" width="90%" height="300px" />
</a-descriptions-item>
- <a-descriptions-item label="响应" :span="2">
+ <a-descriptions-item label="响应">
<monaco-editor editorId="responseEditor" width="90%" height="300px" />
</a-descriptions-item>
</a-descriptions>
@@ -152,7 +144,7 @@
}
.description-item-content {
margin-left: 20px;
- width: 90%;
+ width: 80%;
}
}
</style>
diff --git a/ui-vue3/src/views/resources/services/tabs/detail.vue b/ui-vue3/src/views/resources/services/tabs/detail.vue
index 9420d9c..4c8452b 100644
--- a/ui-vue3/src/views/resources/services/tabs/detail.vue
+++ b/ui-vue3/src/views/resources/services/tabs/detail.vue
@@ -16,46 +16,55 @@
-->
<template>
<div class="__container_services_tabs_detail">
- <a-flex>
- <a-descriptions class="description-column" :column="1" layout="vertical">
- <a-descriptions-item label="服务名">
- <p class="description-item-content">{{ serviceDetail.serviceName }}</p>
- </a-descriptions-item>
- <a-descriptions-item label="服务版本&分组">
- <div class="description-item-content">
- <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">{{ serviceDetail.protocol }}</p>
- </a-descriptions-item>
- <a-descriptions-item label="延迟注册时间">
- <p class="description-item-content">{{ serviceDetail.delay }}</p>
- </a-descriptions-item>
- <a-descriptions-item label="超时时间">
- <p class="description-item-content">{{ serviceDetail.timeOut }}</p>
- </a-descriptions-item>
- <a-descriptions-item label="重试次数">
- <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">{{ serviceDetail.requestTotal }}</p>
- </a-descriptions-item>
- <a-descriptions-item label="平均RT(1min)">
- <p class="description-item-content">{{ serviceDetail.avgRT }}</p>
- </a-descriptions-item>
- <a-descriptions-item label="平均qps(1min)">
- <p class="description-item-content">{{ serviceDetail.avgQPS }}</p>
- </a-descriptions-item>
- <a-descriptions-item label="是否过时">
- <p class="description-item-content">{{ serviceDetail.obsolete }}</p>
- </a-descriptions-item>
- </a-descriptions>
- </a-flex>
+ <a-card-grid>
+ <a-card title="生产者详情">
+ <a-flex>
+ <a-descriptions class="description-column" :column="1" layout="vertical">
+ <a-descriptions-item label="超时时间">
+ <p class="description-item-content">{{ serviceDetail.timeOut }}</p>
+ </a-descriptions-item>
+ <a-descriptions-item label="重试次数">
+ <p class="description-item-content">{{ serviceDetail.retry }}</p>
+ </a-descriptions-item>
+ <a-descriptions-item label="是否过时">
+ <p class="description-item-content">{{ serviceDetail.obsolete }}</p>
+ </a-descriptions-item>
+ </a-descriptions>
+ <a-descriptions class="description-column" :column="1" layout="vertical">
+ <a-descriptions-item label="RPC协议">
+ <p class="description-item-content">{{ serviceDetail.protocol }}</p>
+ </a-descriptions-item>
+ <a-descriptions-item label="延迟注册时间">
+ <p class="description-item-content">{{ serviceDetail.delay }}</p>
+ </a-descriptions-item>
+ </a-descriptions>
+ </a-flex>
+ </a-card>
+ <a-card title="消费者详情" style="margin-top: 10px">
+ <a-flex>
+ <a-descriptions class="description-column" :column="1" layout="vertical">
+ <a-descriptions-item label="超时时间">
+ <p class="description-item-content">{{ serviceDetail.timeOut }}</p>
+ </a-descriptions-item>
+ <a-descriptions-item label="重试次数">
+ <p class="description-item-content">{{ serviceDetail.retry }}</p>
+ </a-descriptions-item>
+ <a-descriptions-item label="是否过时">
+ <p class="description-item-content">{{ serviceDetail.obsolete }}</p>
+ </a-descriptions-item>
+ </a-descriptions>
+ <a-descriptions class="description-column" :column="1" layout="vertical">
+ <a-descriptions-item label="RPC协议">
+ <p class="description-item-content">{{ serviceDetail.protocol }}</p>
+ </a-descriptions-item>
+ <a-descriptions-item label="延迟注册时间">
+ <p class="description-item-content">{{ serviceDetail.delay }}</p>
+ </a-descriptions-item>
+ </a-descriptions>
+ </a-flex>
+ </a-card>
+ </a-card-grid>
+ <a-flex> </a-flex>
</div>
</template>
diff --git a/ui-vue3/src/views/resources/services/tabs/distribution.vue b/ui-vue3/src/views/resources/services/tabs/distribution.vue
index 4b985a2..ef7a335 100644
--- a/ui-vue3/src/views/resources/services/tabs/distribution.vue
+++ b/ui-vue3/src/views/resources/services/tabs/distribution.vue
@@ -18,30 +18,17 @@
<div class="__container_services_tabs_distribution">
<a-flex vertical>
<a-flex class="service-filter">
- <a-flex>
- <div>
- <span>版本&分组:</span>
- <a-select
- 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="debounceSearch"
- enter-button
- />
- </a-flex>
- <div>
- <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>
- </div>
+ <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>
+ <a-input-search
+ v-model:value="searchValue"
+ placeholder="搜索应用,ip,支持前缀搜索"
+ class="service-filter-input"
+ @search="debounceSearch"
+ enter-button
+ />
</a-flex>
<a-table
:columns="tableColumns"
@@ -51,13 +38,18 @@
>
<template #bodyCell="{ column, text }">
<template v-if="column.dataIndex === 'applicationName'">
- <a-button type="link">{{ text }}</a-button>
+ <span class="app-link" @click="viewDetail(text)">
+ <b>
+ <Icon
+ style="margin-bottom: -2px"
+ icon="material-symbols:attach-file-rounded"
+ ></Icon>
+ {{ text }}
+ </b>
+ </span>
</template>
- <template v-if="column.dataIndex === 'instanceIP'">
- <a-flex justify="">
- <a-button v-for="ip in text.slice(0, 3)" :key="ip" type="link">{{ ip }}</a-button>
- <a-button v-if="text.length > 3" type="link">更多</a-button>
- </a-flex>
+ <template v-if="column.dataIndex === 'label'">
+ <a-tag :color="PRIMARY_COLOR">{{ text }}</a-tag>
</template>
</template>
</a-table>
@@ -70,7 +62,10 @@
import { ref, reactive, getCurrentInstance } from 'vue'
import { getServiceDistribution } from '@/api/service/service'
import { debounce } from 'lodash'
+import { PRIMARY_COLOR } from '@/base/constants'
+import { Icon } from '@iconify/vue'
+let __null = PRIMARY_COLOR
const {
appContext: {
config: { globalProperties }
@@ -103,19 +98,51 @@
{
title: '应用名',
dataIndex: 'applicationName',
- width: '25%',
- sorter: true
+ width: '20%',
+ customCell: (_, index) => {
+ if (index === 0) {
+ return { rowSpan: tableData.value.length }
+ } else {
+ return { rowSpan: 0 }
+ }
+ }
},
{
title: '实例数',
dataIndex: 'instanceNum',
- width: '25%',
- sorter: true
+ width: '15%',
+ customCell: (_, index) => {
+ if (index === 0) {
+ return { rowSpan: tableData.value.length }
+ } else {
+ return { rowSpan: 0 }
+ }
+ }
},
{
- title: '实例ip',
- dataIndex: 'instanceIP',
- width: '50%'
+ title: '实例名',
+ dataIndex: 'instanceName',
+ width: '15%'
+ },
+ {
+ title: 'RPC端口',
+ dataIndex: 'rpcPort',
+ width: '15%'
+ },
+ {
+ title: '超时时间',
+ dataIndex: 'timeout',
+ width: '10%'
+ },
+ {
+ title: '重试次数',
+ dataIndex: 'retryNum',
+ width: '10%'
+ },
+ {
+ title: '标签',
+ dataIndex: 'label',
+ width: '15%'
}
]
@@ -138,10 +165,10 @@
globalProperties.$t('searchDomain.unit')
}
</script>
+
<style lang="less" scoped>
.__container_services_tabs_distribution {
.service-filter {
- justify-content: space-between;
margin-bottom: 20px;
.service-filter-select {
margin-left: 10px;
@@ -152,5 +179,14 @@
width: 300px;
}
}
+ .app-link {
+ padding: 4px 10px 4px 4px;
+ border-radius: 4px;
+ color: v-bind('PRIMARY_COLOR');
+ &:hover {
+ cursor: pointer;
+ background: rgba(133, 131, 131, 0.13);
+ }
+ }
}
</style>
diff --git a/ui-vue3/src/views/resources/services/tabs/event.vue b/ui-vue3/src/views/resources/services/tabs/event.vue
index d9a86c0..11a5eaa 100644
--- a/ui-vue3/src/views/resources/services/tabs/event.vue
+++ b/ui-vue3/src/views/resources/services/tabs/event.vue
@@ -15,8 +15,52 @@
~ limitations under the License.
-->
<template>
- <div class="__container_services_tabs_event">事件todo</div>
+ <div class="__container_services_tabs_event">
+ <a-timeline class="timeline">
+ <a-timeline-item v-for="(item, index) in eventData" :key="index">
+ <a-tag class="time" :color="PRIMARY_COLOR">{{ item.time }}</a-tag>
+ <span class="description">{{ item.description }}</span>
+ </a-timeline-item>
+ </a-timeline>
+ </div>
</template>
-<script setup lang="ts"></script>
-<style lang="less" scoped></style>
+<script setup lang="ts">
+import { PRIMARY_COLOR } from '@/base/constants'
+
+let __null = PRIMARY_COLOR
+const eventData = [
+ {
+ time: '2022-01-01',
+ description: 'description'
+ },
+ {
+ time: '2022-01-02',
+ description: 'description'
+ },
+ {
+ time: '2022-01-03',
+ description: 'description'
+ },
+ {
+ time: '2022-01-04',
+ description: 'description'
+ },
+ {
+ time: '2022-01-05',
+ description: 'description'
+ }
+]
+</script>
+
+<style lang="less" scoped>
+.__container_services_tabs_event {
+ display: flex;
+ justify-content: center;
+ .timeline {
+ .description {
+ font-size: 16px;
+ }
+ }
+}
+</style>
diff --git a/ui-vue3/src/views/resources/services/tabs/paramRoute.vue b/ui-vue3/src/views/resources/services/tabs/paramRoute.vue
new file mode 100644
index 0000000..6bcfce3
--- /dev/null
+++ b/ui-vue3/src/views/resources/services/tabs/paramRoute.vue
@@ -0,0 +1,198 @@
+<!--
+ ~ 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.
+-->
+<template>
+ <div class="__container_services_tabs_param_route">
+ <a-card :bordered="false" style="width: 1000px">
+ <template #title>
+ <a-flex justify="space-between">
+ <span>路由</span>
+ <a-flex class="handle-form">
+ <EditOutlined class="edit-icon" />
+ <DeleteOutlined class="edit-icon" />
+ </a-flex>
+ </a-flex>
+ </template>
+ <a-form :labelCol="{ span: 3 }">
+ <a-form-item label="选择方法">
+ <a-select v-model:value="method.value" style="width: 120px">
+ <a-select-option v-for="(item, index) in method.selectArr" :value="item" :key="index">
+ {{ item }}
+ </a-select-option>
+ </a-select>
+ </a-form-item>
+ <a-form-item label="指定方法参数">
+ <a-table
+ :columns="functionParamsColumn"
+ :data-source="props.paramRouteForm.functionParams"
+ :pagination="false"
+ >
+ <template #bodyCell="{ column, index: idx }">
+ <template v-if="column.dataIndex === 'param'">
+ <a-input v-model:value="functionParamsEdit[idx].param" />
+ </template>
+ <template v-if="column.dataIndex === 'relation'">
+ <a-input v-model:value="functionParamsEdit[idx].relation" />
+ </template>
+ <template v-if="column.dataIndex === 'value'">
+ <a-input v-model:value="functionParamsEdit[idx].value" />
+ </template>
+ <template v-if="column.dataIndex === 'handle'">
+ <a-flex justify="space-between">
+ <PlusOutlined
+ class="edit-icon"
+ @click="emit('addRow', 'functionParams', props.index, idx)"
+ />
+ <MinusOutlined
+ class="edit-icon"
+ @click="emit('deleteRow', 'functionParams', props.index, idx)"
+ />
+ </a-flex>
+ </template>
+ </template>
+ </a-table>
+ </a-form-item>
+ <a-form-item label="路由目的地">
+ <a-table
+ :columns="destinationColumn"
+ :data-source="props.paramRouteForm.destination"
+ :pagination="false"
+ >
+ <template #bodyCell="{ column, index: idx }">
+ <template v-if="column.dataIndex === 'label'">
+ <a-input v-model:value="destinationEdit[idx].label" />
+ </template>
+ <template v-if="column.dataIndex === 'relation'">
+ <a-input v-model:value="destinationEdit[idx].relation" />
+ </template>
+ <template v-if="column.dataIndex === 'value'">
+ <a-input v-model:value="destinationEdit[idx].value" />
+ </template>
+ <template v-if="column.dataIndex === 'weight'">
+ <a-input v-model:value="destinationEdit[idx].weight" />
+ </template>
+ <template v-if="column.dataIndex === 'handle'">
+ <a-flex justify="space-between">
+ <PlusOutlined
+ class="edit-icon"
+ @click="emit('addRow', 'destination', props.index, idx)"
+ />
+ <MinusOutlined
+ class="edit-icon"
+ @click="emit('deleteRow', 'destination', props.index, idx)"
+ />
+ </a-flex>
+ </template>
+ </template>
+ </a-table>
+ </a-form-item>
+ </a-form>
+ </a-card>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { EditOutlined, DeleteOutlined, PlusOutlined, MinusOutlined } from '@ant-design/icons-vue'
+import { ref } from 'vue'
+
+const props = defineProps<{
+ paramRouteForm: {
+ type: Object
+ default: {}
+ }
+ index: {
+ type: Number
+ }
+}>()
+
+console.log('props', props.paramRouteForm)
+
+const method = ref(JSON.parse(JSON.stringify(props.paramRouteForm.method)))
+const functionParamsEdit = ref(JSON.parse(JSON.stringify(props.paramRouteForm.functionParams)))
+const destinationEdit = ref(JSON.parse(JSON.stringify(props.paramRouteForm.destination)))
+
+const functionParamsColumn = [
+ {
+ title: '参数索引',
+ key: 'param',
+ dataIndex: 'param',
+ width: '30%'
+ },
+ {
+ title: '关系',
+ key: 'relation',
+ dataIndex: 'relation',
+ width: '30%'
+ },
+ {
+ title: '值',
+ key: 'value',
+ dataIndex: 'value',
+ width: '30%'
+ },
+ {
+ title: '操作',
+ key: 'handle',
+ dataIndex: 'handle',
+ width: '10%'
+ }
+]
+
+const destinationColumn = [
+ {
+ title: '标签',
+ key: 'label',
+ dataIndex: 'label',
+ width: '25%'
+ },
+ {
+ title: '关系',
+ key: 'relation',
+ dataIndex: 'relation',
+ width: '25%'
+ },
+ {
+ title: '值',
+ key: 'value',
+ dataIndex: 'value',
+ width: '25%'
+ },
+ {
+ title: '权重',
+ key: 'weight',
+ dataIndex: 'weight',
+ width: '15%'
+ },
+ {
+ title: '操作',
+ key: 'handle',
+ dataIndex: 'handle',
+ width: '10%'
+ }
+]
+</script>
+
+<style lang="less" scoped>
+.__container_services_tabs_param_route {
+ .handle-form {
+ width: 50px;
+ justify-content: space-between;
+ }
+ .edit-icon {
+ font-size: 18px;
+ }
+}
+</style>
diff --git a/ui-vue3/src/views/resources/services/tabs/sceneConfig.vue b/ui-vue3/src/views/resources/services/tabs/sceneConfig.vue
new file mode 100644
index 0000000..2fc5d3e
--- /dev/null
+++ b/ui-vue3/src/views/resources/services/tabs/sceneConfig.vue
@@ -0,0 +1,146 @@
+<!--
+ ~ 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.
+-->
+<template>
+ <div class="__container_services_tabs_scene_config">
+ <a-tabs v-model:activeKey="activeKey" :tab-position="'left'" animated>
+ <a-tab-pane key="timeout" tab="超时时间">
+ <a-descriptions layout="vertical">
+ <a-descriptions-item label="超时时间">
+ <a-flex v-if="!editForm.timeout.isEdit">
+ <span class="item-content">1000ms</span>
+ <EditOutlined @click="showEdit('timeout')" class="item-icon" />
+ </a-flex>
+ <a-flex v-else align="center">
+ <a-input v-model:value="editForm.timeout.value" class="item-input" />
+ <span style="margin-left: 5px">ms</span>
+ <CheckOutlined @click="hideEdit('timeout')" class="item-icon" />
+ <CloseOutlined @click="hideEdit('timeout')" class="item-icon" />
+ </a-flex>
+ </a-descriptions-item>
+ </a-descriptions>
+ </a-tab-pane>
+ <a-tab-pane key="retryNum" tab="重试次数">
+ <a-descriptions layout="vertical">
+ <a-descriptions-item label="重试次数">
+ <a-flex v-if="!editForm.retryNum.isEdit">
+ <span class="item-content">1000次</span>
+ <EditOutlined @click="showEdit('retryNum')" class="item-icon" />
+ </a-flex>
+ <a-flex v-else align="center">
+ <a-input v-model:value="editForm.retryNum.value" class="item-input" />
+ <span style="margin-left: 5px">次</span>
+ <CheckOutlined @click="hideEdit('retryNum')" class="item-icon" />
+ <CloseOutlined @click="hideEdit('retryNum')" class="item-icon" />
+ </a-flex>
+ </a-descriptions-item>
+ </a-descriptions>
+ </a-tab-pane>
+ <a-tab-pane key="sameArea" tab="同区域优先">
+ <a-descriptions layout="vertical">
+ <a-descriptions-item label="同区域优先">
+ <a-radio-group v-model:value="editForm.sameArea.value" button-style="solid">
+ <a-radio-button value="close">关闭</a-radio-button>
+ <a-radio-button value="open">开启</a-radio-button>
+ </a-radio-group>
+ </a-descriptions-item>
+ </a-descriptions>
+ </a-tab-pane>
+ <a-tab-pane key="paramRoute" tab="参数路由">
+ <paramRoute
+ v-for="(item, index) in paramRouteForms"
+ :key="index"
+ :paramRouteForm="item"
+ :index="index"
+ @addRow="() => {}"
+ @deleteRow="() => {}"
+ />
+ <a-button type="primary" style="margin-top: 20px">增加路由</a-button>
+ </a-tab-pane>
+ </a-tabs>
+ </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue'
+import { EditOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons-vue'
+import paramRoute from './paramRoute.vue'
+
+const editForm = reactive({
+ timeout: {
+ isEdit: false,
+ value: ''
+ },
+ retryNum: {
+ isEdit: false,
+ value: ''
+ },
+ sameArea: {
+ value: 'close'
+ },
+ paramRoute: {
+ isEdit: false,
+ value: {}
+ }
+})
+
+const activeKey = ref('timeout')
+const showEdit = (param: string) => {
+ editForm[param].isEdit = true
+}
+const hideEdit = (param: string) => {
+ editForm[param].isEdit = false
+}
+
+const paramRouteForms = [
+ {
+ method: {
+ value: 'getUserInfo',
+ selectArr: ['getUserInfo', 'register', 'login']
+ },
+ functionParams: [
+ {
+ param: '',
+ relation: '',
+ value: ''
+ }
+ ],
+ destination: [
+ {
+ label: '',
+ relation: '',
+ value: '',
+ weight: ''
+ }
+ ]
+ }
+]
+</script>
+
+<style lang="less" scoped>
+.__container_services_tabs_scene_config {
+ .item-content {
+ margin-right: 20px;
+ }
+ .item-input {
+ width: 200px;
+ }
+ .item-icon {
+ margin-left: 15px;
+ font-size: 18px;
+ }
+}
+</style>