Merge branch 'master' of https://github.com/ikun-Lg/dubbo-kubernetes
diff --git a/.github/workflows/dubboctl-ui-update.yaml b/.github/workflows/dubboctl-ui-update.yaml
index 07e154a..373a1e8 100644
--- a/.github/workflows/dubboctl-ui-update.yaml
+++ b/.github/workflows/dubboctl-ui-update.yaml
@@ -38,6 +38,29 @@
       - Makefile
 
 jobs:
+  lint:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ui-vue3
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Setup Node.js environment
+        uses: actions/setup-node@v4
+        with:
+          node-version-file: "ui-vue3/.nvmrc"
+
+      - name: Install dependencies
+        run: yarn --frozen-lockfile
+
+      - name: Lint
+        run: |
+          # disable eslint for now as the new ui is under heavy development
+          # when the new ui is ready, we can enable eslint again (please also update the .lintstagedrc.json file)
+          # yarn lint
+          yarn prettier-check
+
   update-ui:
     runs-on: ubuntu-latest
     if: github.repository == 'apache/dubbo-kubernetes'
diff --git a/.licenserc.yaml b/.licenserc.yaml
index 4d7dd0a..30350fc 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -81,6 +81,7 @@
     - '**/testdata/**'
     - '**/deploy.tpl'
     - '**/docs/**'
+    - '**/.husky/pre-commit'
     - '**/.nvmrc'
     - '**/**.txtar'
     - '**/**gen.go'
diff --git a/ui-vue3/.husky/pre-commit b/ui-vue3/.husky/pre-commit
new file mode 100644
index 0000000..36af219
--- /dev/null
+++ b/ui-vue3/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npx lint-staged
diff --git a/ui-vue3/.lintstagedrc.json b/ui-vue3/.lintstagedrc.json
new file mode 100644
index 0000000..adec306
--- /dev/null
+++ b/ui-vue3/.lintstagedrc.json
@@ -0,0 +1,6 @@
+{
+  "*.{vue,ts,json}": [
+    // "lint:fix",
+    "prettier-format"
+  ]
+}
diff --git a/ui-vue3/package.json b/ui-vue3/package.json
index 2a3778d..322cec6 100644
--- a/ui-vue3/package.json
+++ b/ui-vue3/package.json
@@ -6,13 +6,15 @@
   "scripts": {
     "dev": "vite",
     "check:i18n": "node --loader ts-node/esm src/base/i18n/sortI18n.ts",
-    "build": "prettier --write src/ && vite build",
     "preview": "vite preview",
     "test:unit": "vitest",
-    "build-only": "vite build",
+    "build": "vite build",
     "type-check": "vue-tsc --build --force",
-    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
-    "format": "prettier --write src/"
+    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
+    "lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
+    "prepare": "husky install",
+    "prettier-format": "prettier --write src/",
+    "prettier-check": "prettier --check src/"
   },
   "resolutions": {
     "jackspeak": "2.1.1"
@@ -28,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",
@@ -54,6 +57,7 @@
     "eslint": "^8.49.0",
     "eslint-plugin-cypress": "^2.15.1",
     "eslint-plugin-vue": "^9.17.0",
+    "husky": "^9.0.6",
     "jsdom": "^23.0.1",
     "npm-run-all2": "^6.1.1",
     "prettier": "^3.0.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 e3fdb6d..0553d2d 100644
--- a/ui-vue3/src/base/i18n/zh.ts
+++ b/ui-vue3/src/base/i18n/zh.ts
@@ -18,50 +18,6 @@
 import type { I18nType } from './type.ts'
 
 const words: I18nType = {
-  instanceDomain: {
-    details: '详情',
-    monitor: '监控',
-    linkTracking: '链路追踪',
-    configuration: '配置',
-    event: '事件'
-  },
-  appServiceTimeout: '调整应用提供服务的超时时间',
-  enableAppInstanceLogs: '开启该应用所有实例的访问日志',
-  appServiceLoadBalance: '调整应用提供服务的负载均衡策略',
-  appServiceRetries: '调整应用提供服务的重试次数',
-  appServiceNegativeClusteringMethod: '调整应用提供服务的负集群方式',
-  executionLog: '执行日志',
-  clusterApproach: '集群方式',
-  retryCount: '重试次数',
-  event: '事件',
-  configuration: '配置',
-  linkTracking: '链路追踪',
-  monitor: '监控',
-  details: '详情',
-  creationTime_k8s: '创建时间(k8s)',
-  dubboPort: 'Dubbo端口',
-  whichApplication: '所属应用',
-  registerTime: '注册时间',
-  startTime_k8s: '启动时间(k8s)',
-  registerStates: '注册状态',
-  deployState: '部署状态',
-  registerCluster: '注册集群',
-  owningWorkload_k8s: '所属工作负载(k8s)',
-  creationTime: '创建时间',
-  nodeIP: '所在节点IP',
-  healthExamination: '健康检查',
-  instanceImage_k8s: '镜像(k8s)',
-  instanceLabel: '实例标签',
-  instanceDetail: '实例详情',
-  CPU: 'CPU',
-  state: '状态',
-  memory: '内存',
-  node: '节点',
-  labels: '标签',
-  instanceIP: '实例IP',
-  instanceName: '实例名称',
-  instance: '实例',
-  resourceDetails: '资源详情',
   service: '服务',
   serviceSearch: '服务查询',
   serviceGovernance: '路由规则',
@@ -81,6 +37,7 @@
   providers: '提供者',
   consumers: '消费者',
   application: '应用',
+  instance: '实例',
   all: '全部',
   common: '通用',
 
@@ -319,14 +276,18 @@
   backHome: '回到首页',
   noPageTip: '抱歉,你访问的页面不存在',
   globalSearchTip: '搜索ip,应用,实例,服务',
+
   placeholder: {
     typeAppName: '请输入应用名,支持前缀搜索',
     typeDefault: '请输入'
   },
   none: '无',
+  details: '详情',
   debug: '调试',
   distribution: '分布',
+  monitor: '监控',
   tracing: '链路追踪',
+  event: '事件',
 
   provideService: '提供服务',
   dependentService: '依赖服务',
diff --git a/ui-vue3/src/components/editor/MonacoEditor.ts b/ui-vue3/src/components/editor/MonacoEditor.ts
new file mode 100644
index 0000000..bfe9478
--- /dev/null
+++ b/ui-vue3/src/components/editor/MonacoEditor.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 * as monaco from 'monaco-editor'
+
+export default function useMonaco(language = 'json') {
+  let monacoEditor: monaco.editor.IStandaloneCodeEditor | null = null
+  let initReadOnly = false
+
+  const updateVal = async (val: string) => {
+    monacoEditor?.setValue(val)
+    setTimeout(async () => {
+      initReadOnly && monacoEditor?.updateOptions({ readOnly: false })
+      await monacoEditor?.getAction('editor.action.formatDocument')?.run()
+      initReadOnly && monacoEditor?.updateOptions({ readOnly: true })
+    }, 100)
+  }
+
+  const createEditor = (
+    el: HTMLElement | null,
+    editorOption: monaco.editor.IStandaloneEditorConstructionOptions = {}
+  ) => {
+    if (monacoEditor) {
+      return
+    }
+    initReadOnly = !!editorOption.readOnly
+    monacoEditor =
+      el &&
+      monaco.editor.create(el, {
+        language,
+        minimap: { enabled: false },
+        theme: 'vs-light',
+        multiCursorModifier: 'ctrlCmd',
+        tabSize: 2,
+        automaticLayout: true, // 自适应宽高
+        ...editorOption
+      })
+    return monacoEditor
+  }
+  const onFormatDoc = () => {
+    monacoEditor?.getAction('editor.action.formatDocument')?.run()
+  }
+  return {
+    updateVal,
+    getEditor: () => monacoEditor,
+    createEditor,
+    onFormatDoc
+  }
+}
diff --git a/ui-vue3/src/components/editor/MonacoEditor.vue b/ui-vue3/src/components/editor/MonacoEditor.vue
new file mode 100644
index 0000000..0dfee0e
--- /dev/null
+++ b/ui-vue3/src/components/editor/MonacoEditor.vue
@@ -0,0 +1,102 @@
+<!--
+  ~ 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
+    :id="editorId"
+    :style="{
+      width: typeof width === 'number' ? width + 'px' : width,
+      height: typeof height === 'number' ? height + 'px' : height
+    }"
+  ></div>
+</template>
+
+<script lang="ts" setup>
+import { defineEmits, onMounted, watch } from 'vue'
+import useMonaco from './MonacoEditor'
+
+const emit = defineEmits(['update:modelValue', 'blur'])
+
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: ''
+  },
+  width: {
+    type: [String, Number],
+    default: '100%'
+  },
+  height: {
+    type: [String, Number],
+    default: '100%'
+  },
+  theme: {
+    type: String,
+    default: 'vs-light'
+  },
+  language: {
+    type: String,
+    default: 'json'
+  },
+  editorId: {
+    type: String,
+    default: 'editor'
+  },
+  editorOptions: {
+    type: Object,
+    default: () => ({})
+  },
+  readonly: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const { createEditor, updateVal, getEditor, onFormatDoc } = useMonaco(props.language)
+
+const updateMonacoVal = (_val?: string) => {
+  const val = _val || props.modelValue
+  updateVal(val)
+}
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    val !== getEditor()?.getValue() && updateMonacoVal(val)
+  }
+)
+
+onMounted(() => {
+  const editor = createEditor(document.querySelector(`#${props.editorId}`), {
+    theme: props.theme,
+    readOnly: props.readonly,
+    ...props.editorOptions
+  })
+  updateMonacoVal()
+  if (editor) {
+    editor.onDidChangeModelContent(() => {
+      emit('update:modelValue', editor.getValue())
+    })
+    editor.onDidBlurEditorText(() => {
+      emit('blur')
+    })
+  }
+})
+
+defineExpose({
+  onFormatDoc
+})
+</script>
diff --git a/ui-vue3/src/layout/tab/layout_tab.vue b/ui-vue3/src/layout/tab/layout_tab.vue
index 7901718..e6a4df5 100644
--- a/ui-vue3/src/layout/tab/layout_tab.vue
+++ b/ui-vue3/src/layout/tab/layout_tab.vue
@@ -15,35 +15,36 @@
   ~ limitations under the License.
 -->
 <template>
-  <div class="__container_router_tab_index" :key="key">
-    <a-tabs
-      v-if="tabRoute.meta.tab"
-      @change="router.push({ name: activeKey || '' })"
-      v-model:activeKey="activeKey"
-    >
-      <a-tab-pane :key="v.name" v-for="v in tabRouters">
-        <template #tab>
+  <div class="__container_router_tab_index">
+    <div :key="key">
+      <a-tabs
+          v-if="tabRoute.meta.tab"
+          @change="router.push({ name: activeKey || '' })"
+          v-model:activeKey="activeKey"
+      >
+        <a-tab-pane :key="v.name" v-for="v in tabRouters">
+          <template #tab>
           <span>
             <Icon style="margin-bottom: -2px" :icon="v.meta.icon"></Icon>
             {{ $t(v.name) }}
           </span>
-        </template>
-        <router-view :key="key" />
-      </a-tab-pane>
-    </a-tabs>
-    <router-view v-if="!tabRoute.meta.tab" />
+          </template>
+        </a-tab-pane>
+      </a-tabs>
+      <router-view/>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue'
-import { Icon } from '@iconify/vue'
-import { useRoute, useRouter } from 'vue-router'
+import {computed, ref} from 'vue'
+import {Icon} from '@iconify/vue'
+import {useRoute, useRouter} from 'vue-router'
 import _ from 'lodash'
-import { PRIMARY_COLOR } from '@/base/constants'
-import type { RouterMeta } from '@/router/RouterMeta'
+
 const router = useRouter()
 const tabRoute = useRoute()
+let meta: any = tabRoute.meta
 const tabRouters = computed(() => {
   let meta: any = tabRoute.meta
   return meta?.parent?.children?.filter((x: any): any => x.meta.tab)
@@ -60,6 +61,5 @@
     transitionFlag.value = true
   }, 500)
 })
-console.log(tabRoute)
 </script>
 <style lang="less" scoped></style>
diff --git a/ui-vue3/src/router/defaultRoutes.ts b/ui-vue3/src/router/defaultRoutes.ts
index 3bb6460..d0af19f 100644
--- a/ui-vue3/src/router/defaultRoutes.ts
+++ b/ui-vue3/src/router/defaultRoutes.ts
@@ -85,6 +85,14 @@
                 meta: {
                   tab: true
                 }
+              },
+              {
+                path: '/detail3/:pathId',
+                name: 'application-tab3',
+                component: () => import('../views/resources/applications/tabs/tab3.vue'),
+                meta: {
+                  tab: true
+                }
               }
             ]
           },
@@ -151,7 +159,7 @@
             path: '/services',
             name: 'services',
             redirect: 'search',
-            component: () => import('../views/resources/services/index.vue'),
+            component: LayoutTab,
             meta: {
               tab_parent: true
             },
diff --git a/ui-vue3/src/views/resources/applications/index.vue b/ui-vue3/src/views/resources/applications/index.vue
index d85ae20..b8328e3 100644
--- a/ui-vue3/src/views/resources/applications/index.vue
+++ b/ui-vue3/src/views/resources/applications/index.vue
@@ -96,8 +96,10 @@
 )
 
 onMounted(() => {
+  searchDomain.tableStyle = {
+    scrollY: '40vh'
+  }
   searchDomain.onSearch()
-  console.log(searchDomain.result)
 })
 
 provide(PROVIDE_INJECT_KEY.SEARCH_DOMAIN, searchDomain)
diff --git a/ui-vue3/src/views/resources/applications/tabs/tab1.vue b/ui-vue3/src/views/resources/applications/tabs/tab1.vue
index be04898..1b67290 100644
--- a/ui-vue3/src/views/resources/applications/tabs/tab1.vue
+++ b/ui-vue3/src/views/resources/applications/tabs/tab1.vue
@@ -15,8 +15,15 @@
   ~ limitations under the License.
 -->
 <template>
-  <div class="__container_tabDemo">tab1</div>
+  <div class="__container_tabDemo1">tab1</div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+
+import {onMounted} from "vue";
+
+onMounted(()=>{
+  console.log(111)
+})
+</script>
 <style lang="less" scoped></style>
diff --git a/ui-vue3/src/views/resources/applications/tabs/tab2.vue b/ui-vue3/src/views/resources/applications/tabs/tab2.vue
index 55c136f..c20afd7 100644
--- a/ui-vue3/src/views/resources/applications/tabs/tab2.vue
+++ b/ui-vue3/src/views/resources/applications/tabs/tab2.vue
@@ -15,8 +15,15 @@
   ~ limitations under the License.
 -->
 <template>
-  <div class="__container_tabDemo">tab2</div>
+  <div class="__container_tabDemo2">tab2</div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import {onMounted} from "vue";
+
+onMounted(()=>{
+  console.log(222)
+})
+
+</script>
 <style lang="less" scoped></style>
diff --git a/ui-vue3/src/views/resources/applications/tabs/tab3.vue b/ui-vue3/src/views/resources/applications/tabs/tab3.vue
new file mode 100644
index 0000000..9d496cb
--- /dev/null
+++ b/ui-vue3/src/views/resources/applications/tabs/tab3.vue
@@ -0,0 +1,29 @@
+<!--
+  ~ 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_tabDemo3">tab3</div>
+</template>
+
+<script setup lang="ts">
+import {onMounted} from "vue";
+
+onMounted(()=>{
+  console.log(333)
+})
+
+</script>
+<style lang="less" scoped></style>
diff --git a/ui-vue3/src/views/resources/services/search.vue b/ui-vue3/src/views/resources/services/search.vue
index 9d36ebf..64f62e9 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 { searchService } from '@/api/service/service.ts'
-import { debounce } from 'lodash'
-
-const {
-  appContext: {
-    config: { globalProperties }
-  }
-} = <ComponentInternalInstance>getCurrentInstance()
-
-const serviceName = ref('')
+import { reactive, provide } from 'vue'
+import { searchService } from '@/api/service/service'
+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..e022898 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"
-            />
+            <monaco-editor editorId="requestEditor" width="90%" height="300px" />
           </a-descriptions-item>
           <a-descriptions-item label="响应" :span="2">
-            <a-textarea
-              v-model="responseValue"
-              placeholder="请输入"
-              :rows="4"
-              class="description-item-content"
-            />
+            <monaco-editor editorId="responseEditor" width="90%" height="300px" />
           </a-descriptions-item>
         </a-descriptions>
         <a-button type="primary">发送请求</a-button>
@@ -71,7 +61,9 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive } from 'vue'
+import {reactive, ref} from 'vue'
+import MonacoEditor from '@/components/editor/MonacoEditor.vue';
+
 
 const methodTabs = reactive([
   'login',
@@ -150,10 +142,8 @@
     ]
   }
 ]
-
-const requestValue = ref('')
-const responseValue = ref('')
 </script>
+
 <style lang="less" scoped>
 .__container_services_tabs_debug {
   width: 100%;
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 fbecaf6..01450d8 100644
--- a/ui-vue3/yarn.lock
+++ b/ui-vue3/yarn.lock
@@ -3371,6 +3371,11 @@
   resolved "https://registry.npmmirror.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
   integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
 
+husky@^9.0.6:
+  version "9.0.6"
+  resolved "https://registry.npmmirror.com/husky/-/husky-9.0.6.tgz#cee0245d60480b12279cf492ec6cfc1aeb7fa759"
+  integrity sha512-EEuw/rfTiMjOfuL7pGO/i9otg1u36TXxqjIA6D9qxVjd/UXoDOsLor/BSFf5hTK50shwzCU3aVVwdXDp/lp7RA==
+
 iconv-lite@0.6, iconv-lite@0.6.3, iconv-lite@^0.6.3:
   version "0.6.3"
   resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
@@ -4129,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"