Merge pull request #157 from jianyi-gronk/feature/ui/globalSearch

add global search
diff --git a/ui-vue3/src/api/mock/mockGlobalSearch.ts b/ui-vue3/src/api/mock/mockGlobalSearch.ts
new file mode 100644
index 0000000..bf729e2
--- /dev/null
+++ b/ui-vue3/src/api/mock/mockGlobalSearch.ts
@@ -0,0 +1,27 @@
+/*
+ * 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(/\/search\?searchType=\w+&keywords=\w*/, 'get', {
+  code: 200,
+  message: '成功',
+  data: {
+    find: true,
+    candidates: ['test1', 'test2', 'tset3']
+  }
+})
diff --git a/ui-vue3/src/api/service/globalSearch.ts b/ui-vue3/src/api/service/globalSearch.ts
new file mode 100644
index 0000000..dd2973f
--- /dev/null
+++ b/ui-vue3/src/api/service/globalSearch.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 globalSearch = (params: any): Promise<any> => {
+  return request({
+    url: '/search',
+    method: 'get',
+    params
+  })
+}
diff --git a/ui-vue3/src/base/i18n/en.ts b/ui-vue3/src/base/i18n/en.ts
index afac304..bd970a6 100644
--- a/ui-vue3/src/base/i18n/en.ts
+++ b/ui-vue3/src/base/i18n/en.ts
@@ -44,6 +44,7 @@
   app: 'Application',
   services: 'Services',
   application: 'Application',
+  instance: 'Instance',
   all: 'All',
   ip: 'IP',
   qps: 'qps',
@@ -265,7 +266,9 @@
   instances: 'Instances',
 
   backHome: 'Back Home',
-  noPageTip: 'Sorry, the page you visited does not exist.'
+  noPageTip: 'Sorry, the page you visited does not exist.',
+
+  globalSearchTip: 'Search ip, application, instance, service'
 }
 
 export default words
diff --git a/ui-vue3/src/base/i18n/zh.ts b/ui-vue3/src/base/i18n/zh.ts
index d694f84..1f0e96f 100644
--- a/ui-vue3/src/base/i18n/zh.ts
+++ b/ui-vue3/src/base/i18n/zh.ts
@@ -37,6 +37,7 @@
   providers: '提供者',
   consumers: '消费者',
   application: '应用',
+  instance: '实例',
   all: '全部',
   common: '通用',
 
@@ -263,7 +264,9 @@
   instances: '实例',
 
   backHome: '回到首页',
-  noPageTip: '抱歉,你访问的页面不存在'
+  noPageTip: '抱歉,你访问的页面不存在',
+
+  globalSearchTip: '搜索ip,应用,实例,服务'
 }
 
 export default words
diff --git a/ui-vue3/src/layout/header/layout_header.vue b/ui-vue3/src/layout/header/layout_header.vue
index 8d53f1d..dc169e3 100644
--- a/ui-vue3/src/layout/header/layout_header.vue
+++ b/ui-vue3/src/layout/header/layout_header.vue
@@ -16,7 +16,7 @@
 -->
 <template>
   <div class="__container_layout_header">
-    <a-layout-header class="header" style="">
+    <a-layout-header class="header">
       <a-row>
         <a-col :span="2">
           <menu-unfold-outlined
@@ -26,7 +26,26 @@
           />
           <menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
         </a-col>
-        <a-col :span="16"> </a-col>
+        <a-col :span="3"></a-col>
+        <a-col :span="10" class="search-group">
+          <a-input-group compact>
+            <a-select v-model:value="searchType" class="select-type">
+              <a-select-option v-for="option in searchTypeOptions" :value="option.value">{{
+                option.label
+              }}</a-select-option>
+            </a-select>
+            <a-auto-complete
+              v-model:value="keywords"
+              class="input-keywords"
+              :placeholder="$t('globalSearchTip')"
+              :options="candidates"
+              @select="onSelect"
+              @search="inputChange"
+            />
+            <a-button :icon="h(SearchOutlined)" class="search-icon" @click="inputChange"></a-button>
+          </a-input-group>
+        </a-col>
+        <a-col :span="3"></a-col>
         <a-col :span="2">
           <a-segmented v-model:value="locale" :options="i18nConfig.opts" />
         </a-col>
@@ -38,7 +57,6 @@
             shape="circle"
             useType="pure"
           ></color-picker>
-
           <a-popover>
             <template #content>reset the theme</template>
             <Icon
@@ -63,7 +81,8 @@
 
 <script setup lang="ts">
 import { MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined } from '@ant-design/icons-vue'
-import { inject, ref, watch } from 'vue'
+import type { ComponentInternalInstance } from 'vue'
+import { inject, ref, reactive, watch, h, getCurrentInstance, computed } from 'vue'
 import { PROVIDE_INJECT_KEY } from '@/base/enums/ProvideInject'
 import { changeLanguage, localeConfig } from '@/base/i18n'
 import {
@@ -74,6 +93,16 @@
 } from '@/base/constants'
 import devTool from '@/utils/DevToolUtil'
 import { Icon } from '@iconify/vue'
+import { SearchOutlined } from '@ant-design/icons-vue'
+import { globalSearch } from '@/api/service/globalSearch'
+import { debounce } from 'lodash'
+import type { SelectOption } from '@/types/common.ts'
+
+const {
+  appContext: {
+    config: { globalProperties }
+  }
+} = <ComponentInternalInstance>getCurrentInstance()
 
 let __null = PRIMARY_COLOR
 const collapsed = inject(PROVIDE_INJECT_KEY.COLLAPSED)
@@ -93,12 +122,70 @@
 watch(locale, (value) => {
   changeLanguage(value)
 })
+
+const searchTypeOptions = reactive([
+  {
+    label: 'IP',
+    value: 'ip'
+  },
+  {
+    label: computed(() => globalProperties.$t('application')),
+    value: 'appName'
+  },
+  {
+    label: computed(() => globalProperties.$t('instance')),
+    value: 'instanceName'
+  },
+  {
+    label: computed(() => globalProperties.$t('service')),
+    value: 'serviceName'
+  }
+])
+const searchType = ref(searchTypeOptions[0].value)
+
+const keywords = ref('')
+
+const onSearch = async () => {
+  let { data } = await globalSearch({
+    searchType: searchType.value,
+    keywords: keywords.value
+  })
+  if (data.find) {
+    for (let i = 0; i < data.candidates.length; i++) {
+      candidates.value[i] = {
+        label: data.candidates[i],
+        value: data.candidates[i]
+      }
+    }
+  } else {
+    candidates.value = []
+  }
+}
+
+const candidates = ref<Array<SelectOption>>([])
+
+const inputChange = debounce(onSearch, 300)
+
+const onSelect = () => {}
 </script>
 <style lang="less" scoped>
 .__container_layout_header {
   .header {
     background: v-bind('PRIMARY_COLOR');
     padding: 0;
+    .search-group {
+      display: flex;
+      align-items: center;
+      .select-type {
+        width: 120px;
+      }
+      .input-keywords {
+        width: calc(100% - 152px);
+      }
+      .search-icon {
+        width: 32px;
+      }
+    }
   }
 
   .trigger {
diff --git a/ui-vue3/src/main.ts b/ui-vue3/src/main.ts
index 736014e..0262b2c 100644
--- a/ui-vue3/src/main.ts
+++ b/ui-vue3/src/main.ts
@@ -24,6 +24,7 @@
 import './api/mock/mockServer'
 import './api/mock/mockCluster'
 import './api/mock/mockVersion'
+import './api/mock/mockGlobalSearch'
 
 import Vue3ColorPicker from 'vue3-colorpicker'
 import 'vue3-colorpicker/style.css'
diff --git a/ui-vue3/src/types/common.ts b/ui-vue3/src/types/common.ts
new file mode 100644
index 0000000..38864a5
--- /dev/null
+++ b/ui-vue3/src/types/common.ts
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+export type SelectOption = {
+  label: string
+  value: string
+}
diff --git a/ui-vue3/src/views/home/index.vue b/ui-vue3/src/views/home/index.vue
index 21a8c3e..54d0a23 100644
--- a/ui-vue3/src/views/home/index.vue
+++ b/ui-vue3/src/views/home/index.vue
@@ -58,7 +58,7 @@
         metricsMetadata.info.prometheus
       }}</a-descriptions-item>
       <a-descriptions-item label="Remark">empty</a-descriptions-item>
-      <a-descriptions-item label="rules" :span="4">
+      <a-descriptions-item label="rules">
         <a-tag :color="PRIMARY_COLOR" v-for="v in metricsMetadata.info.rules">{{ v }}</a-tag>
       </a-descriptions-item>
     </a-descriptions>