blob: b3ea6fe57656abac5818f1ad65ba9900e5f02398 [file] [log] [blame]
<!--
~ 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_layout_header">
<a-layout-header class="header">
<a-flex style="height: 100%" justify="space-between" align="center">
<a-flex>
<menu-unfold-outlined
v-if="collapsed"
class="trigger"
@click="() => (collapsed = !collapsed)"
/>
<menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
</a-flex>
<div></div>
<a-flex :gap="20">
<a-input-group @keyup.enter="globalSearch" class="search-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="globalSearch"
></a-button>
</a-input-group>
<a-form layout="inline">
<a-form-item class="mesh-select-item" :label="$t('registryCenter')" inline>
<a-select
class="mesh-select"
:value="meshStore.mesh"
:options="
meshes.map((x: any) => {
return {
value: x.name,
label: x.name
}
})
"
@change="changeMesh"
></a-select>
</a-form-item>
</a-form>
</a-flex>
<div></div>
<a-flex align="center" gap="middle">
<a-flex align="center">
<a-segmented v-model:value="locale" :options="i18nConfig.opts" />
</a-flex>
<a-flex align="center">
<color-picker
:pureColor="PRIMARY_COLOR"
@pureColorChange="changeTheme"
format="hex6"
shape="circle"
useType="pure"
></color-picker>
<a-popover>
<template #content>reset the theme</template>
<Icon
class="reset-icon"
icon="material-symbols:reset-tv-outline"
@click="resetTheme"
></Icon>
</a-popover>
</a-flex>
<a-flex align="center">
<a-dropdown>
<a-avatar @click="">
<template #icon>
<UserOutlined />
</template>
</a-avatar>
<template #overlay>
<a-menu>
<a-menu-item @click="logoutHandle">
<a href="javascript:;">logout</a>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<span class="username">{{ authState?.userinfo?.username }}</span>
</a-flex>
</a-flex>
</a-flex>
</a-layout-header>
</div>
</template>
<script setup lang="ts">
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
SearchOutlined,
UserOutlined
} from '@ant-design/icons-vue'
import { type ComponentInternalInstance, onMounted } from 'vue'
import { computed, getCurrentInstance, h, inject, nextTick, reactive, ref, watch } from 'vue'
import { PROVIDE_INJECT_KEY } from '@/base/enums/ProvideInject'
import { changeLanguage, localeConfig } from '@/base/i18n'
import {
LOCAL_STORAGE_THEME,
PRIMARY_COLOR,
PRIMARY_COLOR_DEFAULT,
PRIMARY_COLOR_R
} from '@/base/constants'
import { Icon } from '@iconify/vue'
import { debounce } from 'lodash'
import type { SelectOption } from '@/types/common.ts'
import { searchApplications } from '@/api/service/app'
import { searchInstances } from '@/api/service/instance'
import { searchService } from '@/api/service/service'
import { useRoute, useRouter } from 'vue-router'
import { getAuthState, removeAuthState } from '@/utils/AuthUtil'
import { logout } from '@/api/service/login'
import { useMeshStore } from '@/stores/mesh'
import { meshesSearch } from '@/api/service/globalSearch'
const {
appContext: {
config: { globalProperties }
}
} = <ComponentInternalInstance>getCurrentInstance()
let __null = PRIMARY_COLOR
let __null_r = PRIMARY_COLOR_R
const collapsed = inject(PROVIDE_INJECT_KEY.COLLAPSED)
const i18nConfig = <typeof localeConfig>inject(PROVIDE_INJECT_KEY.LOCALE)
let locale = ref(localeConfig.locale)
function changeTheme(val: string) {
localStorage.setItem(LOCAL_STORAGE_THEME, val)
PRIMARY_COLOR.value = val
}
function resetTheme(val: string) {
localStorage.removeItem(LOCAL_STORAGE_THEME)
PRIMARY_COLOR.value = PRIMARY_COLOR_DEFAULT
}
function logoutHandle() {
removeAuthState()
logout().then(() => {
router.replace(`/login?redirect=${route.path}`)
})
}
const meshes = ref([])
const meshStore = useMeshStore()
const refreshCurrentRoute: any = inject(PROVIDE_INJECT_KEY.LAYOUT_ROUTE_KEY)
const changeMesh = (value: any) => {
meshStore.mesh = value
refreshCurrentRoute()
}
onMounted(async () => {
const { data } = await meshesSearch()
meshes.value = data
})
const authState = getAuthState()
watch(locale, (value) => {
changeLanguage(value)
})
const searchTypeOptions = reactive([
// Temporarily hidden, awaiting improvement
// {
// label: 'IP',
// value: 'ip'
// },
{
label: computed(() => globalProperties.$t('application')),
value: 'applications'
},
{
label: computed(() => globalProperties.$t('instance')),
value: 'instances'
},
{
label: computed(() => globalProperties.$t('service')),
value: 'services'
}
])
const searchType = ref(searchTypeOptions[0].value)
const keywords = ref('')
const onSearch = async () => {
const params = {
keywords: keywords.value
}
// Public search processing function
const globalSearch = async (searchFunc: Function, labelKey: string) => {
const {
data: { list }
} = await searchFunc(params)
// Using map instead of forEach is more concise.
candidates.value = list.map((item: any) => ({
label: item[labelKey],
value: item[labelKey]
}))
}
// Various types of search functions
const globalSearchByIp = () => {
// The IP search logic is undefined and left blank.
}
switch (searchType.value) {
case 'ip':
globalSearchByIp()
break
case 'applications':
await globalSearch(searchApplications, 'appName')
break
case 'instances':
await globalSearch(searchInstances, 'name')
break
case 'services':
await globalSearch(searchService, 'serviceName')
break
default:
break
}
}
// Listen for changes in searchType and trigger a search.
watch(searchType, async (newType) => {
await onSearch() // When a change is detected, re-call the search function.
})
const candidates = ref<Array<SelectOption>>([])
const inputChange = debounce(onSearch, 300)
const router = useRouter()
const route = useRoute()
const globalSearch = () => {
router.replace(`/resources/${searchType.value}/list?query=${keywords.value}`)
}
const onSelect = () => {}
</script>
<style lang="less" scoped>
.__container_layout_header {
.header {
background: v-bind('PRIMARY_COLOR');
padding: 0;
.mesh-select {
min-width: 100px;
}
.mesh-select-item {
:deep(label) {
color: v-bind('PRIMARY_COLOR_R');
}
}
.search-group {
display: flex;
align-items: center;
.select-type {
width: 120px;
}
.input-keywords {
&:hover {
border-color: #d9d9d9;
:deep(.ant-select-selector) {
border-color: #d9d9d9;
}
}
width: calc(20vw);
:deep(.ant-select-selector) {
border-color: #d9d9d9;
box-shadow: none;
&:hover {
border-color: #d9d9d9;
box-shadow: none;
}
}
}
.search-icon {
width: 32px;
}
}
}
.trigger {
font-size: 20px;
margin-left: 20px;
color: white;
}
.username {
color: white;
padding: 5px;
}
.reset-icon {
font-size: 25px;
color: white;
//margin-bottom: -9px;
}
}
</style>