bigtop-manager-ui/src/pages/cluster-manage/cluster/host.vue (277 lines of code) (raw):
<!--
~ 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.
-->
<script setup lang="ts">
import { message, Modal, TableColumnType, TableProps } from 'ant-design-vue'
import { computed, onActivated, reactive, ref, useAttrs } from 'vue'
import { useI18n } from 'vue-i18n'
import { getHosts } from '@/api/hosts'
import * as hostApi from '@/api/hosts'
import { useRouter } from 'vue-router'
import useBaseTable from '@/composables/use-base-table'
import HostCreate from '@/pages/cluster-manage/hosts/create.vue'
import InstallDependencies from '@/pages/cluster-manage/hosts/install-dependencies.vue'
import type { FilterConfirmProps, FilterResetProps } from 'ant-design-vue/es/table/interface'
import type { GroupItem } from '@/components/common/button-group/types'
import type { HostVO } from '@/api/hosts/types'
import type { ClusterVO } from '@/api/cluster/types'
import type { HostReq } from '@/api/command/types'
type Key = string | number
interface TableState {
selectedRowKeys: Key[]
searchText: string
searchedColumn: keyof HostVO
}
const { t } = useI18n()
const router = useRouter()
const attrs = useAttrs() as ClusterVO
const searchInputRef = ref()
const hostCreateRef = ref<InstanceType<typeof HostCreate> | null>(null)
const installRef = ref<InstanceType<typeof InstallDependencies> | null>(null)
const hostStatus = ref(['INSTALLING', 'SUCCESS', 'FAILED', 'UNKNOWN'])
const state = reactive<TableState>({
searchText: '',
searchedColumn: '',
selectedRowKeys: []
})
const columns = computed((): TableColumnType<HostVO>[] => [
{
title: t('host.hostname'),
dataIndex: 'hostname',
key: 'hostname',
ellipsis: true,
customFilterDropdown: true,
onFilterDropdownOpenChange: (visible) => onFilterDropdownOpenChange(visible)
},
{
title: t('host.ip_address'),
dataIndex: 'ipv4',
key: 'ipv4',
ellipsis: true,
customFilterDropdown: true,
onFilterDropdownOpenChange: (visible) => onFilterDropdownOpenChange(visible)
},
{
title: t('common.os'),
dataIndex: 'os',
ellipsis: true
},
{
title: t('common.arch'),
dataIndex: 'arch',
ellipsis: true
},
{
title: t('host.component_count'),
dataIndex: 'componentNum',
ellipsis: true
},
{
title: t('common.status'),
dataIndex: 'status',
key: 'status',
width: '160px',
ellipsis: true,
filterMultiple: false,
filters: [
{
text: t('common.success'),
value: 1
},
{
text: t('common.failed'),
value: 2
},
{
text: t('common.unknown'),
value: 3
}
]
},
{
title: t('common.operation'),
key: 'operation',
width: '160px',
fixed: 'right'
}
])
const { loading, dataSource, filtersParams, paginationProps, onChange } = useBaseTable<HostVO>({
columns: columns.value,
rows: []
})
const operations = computed((): GroupItem[] => [
{
text: 'edit',
clickEvent: (_item, args) => handleEdit(args)
},
{
text: 'remove',
danger: true,
clickEvent: (_item, args) => deleteHost([args.id])
}
])
const onFilterDropdownOpenChange = (visible: boolean) => {
if (visible) {
setTimeout(() => {
searchInputRef.value.focus()
}, 100)
}
}
const onSelectChange = (selectedRowKeys: Key[]) => {
state.selectedRowKeys = selectedRowKeys
}
const handleSearch = (selectedKeys: Key[], confirm: (param?: FilterConfirmProps) => void, dataIndex: string) => {
confirm()
state.searchText = selectedKeys[0] as string
state.searchedColumn = dataIndex
}
const handleReset = (clearFilters: (param?: FilterResetProps) => void) => {
clearFilters({ confirm: true })
state.searchText = ''
}
const handleEdit = (row: HostVO) => {
const formatHost = { ...row, displayName: row.clusterDisplayName, clusterId: attrs?.id }
hostCreateRef.value?.handleOpen('EDIT', formatHost)
}
const bulkRemove = () => {
if (state.selectedRowKeys.length === 0) {
message.error(t('common.delete_empty'))
return
}
deleteHost(state.selectedRowKeys as number[])
}
const deleteHost = (ids: number[]) => {
Modal.confirm({
title: ids.length > 1 ? t('common.delete_msgs') : t('common.delete_msg'),
async onOk() {
try {
const data = await hostApi.removeHost({ ids })
if (data) {
message.success(t('common.delete_success'))
await getHostList(true)
}
} catch (error) {
console.log('error :>> ', error)
}
}
})
}
const viewHostDetail = (row: HostVO) => {
const { id: hostId } = row
router.push({ name: 'HostDetail', query: { hostId, clusterId: attrs.id } })
}
const addHost = () => {
hostCreateRef.value?.handleOpen('ADD', { clusterId: attrs.id })
}
const afterSetupHostConfig = async (type: 'ADD' | 'EDIT', item: HostReq) => {
type === 'ADD' ? installRef.value?.handleOpen(item) : await getHostList(true)
}
const getHostList = async (isReset = false) => {
loading.value = true
if (attrs.id == undefined || !paginationProps.value) {
loading.value = false
return
}
if (isReset) {
paginationProps.value.current = 1
}
try {
const res = await getHosts({ ...filtersParams.value, clusterId: attrs.id })
dataSource.value = res.content
paginationProps.value.total = res.total
loading.value = false
} catch (error) {
console.log('error :>> ', error)
} finally {
loading.value = false
}
}
const tableChange: TableProps['onChange'] = (pagination, filters, ...args) => {
onChange(pagination, filters, ...args)
getHostList()
}
onActivated(() => {
getHostList()
})
</script>
<template>
<div class="host">
<header>
<div class="header-title">{{ $t('host.host_list') }}</div>
<a-space :size="16">
<a-button type="primary" danger @click="bulkRemove">{{ $t('common.bulk_remove') }}</a-button>
<a-button type="primary" @click="addHost">{{ $t('cluster.add_host') }}</a-button>
</a-space>
</header>
<a-table
:loading="loading"
:data-source="dataSource"
:columns="columns"
:pagination="paginationProps"
:row-selection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }"
@change="tableChange"
>
<template #customFilterDropdown="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
<div class="search">
<a-input
ref="searchInputRef"
:placeholder="$t('common.enter_error', [column.title])"
:value="selectedKeys[0]"
@change="(e: any) => setSelectedKeys(e.target?.value ? [e.target?.value] : [])"
@press-enter="handleSearch(selectedKeys, confirm, column.dataIndex)"
/>
<div class="search-option">
<a-button size="small" @click="handleReset(clearFilters)">
{{ $t('common.reset') }}
</a-button>
<a-button type="primary" size="small" @click="handleSearch(selectedKeys, confirm, column.dataIndex)">
{{ $t('common.search') }}
</a-button>
</div>
</div>
</template>
<template #customFilterIcon="{ filtered, column }">
<svg-icon v-if="column.key != 'status'" :name="filtered ? 'search_activated' : 'search'" />
<svg-icon v-else :name="filtered ? 'filter_activated' : 'filter'" />
</template>
<template #bodyCell="{ record, column }">
<template v-if="column.key === 'hostname'">
<a-typography-link underline @click="viewHostDetail(record)"> {{ record.hostname }} </a-typography-link>
</template>
<template v-if="column.key === 'status'">
<svg-icon style="margin-left: 0" :name="hostStatus[record.status].toLowerCase()" />
<span>{{ $t(`common.${hostStatus[record.status].toLowerCase()}`) }}</span>
</template>
<template v-if="column.key === 'operation'">
<button-group
i18n="common"
:text-compact="true"
:space="24"
:groups="operations"
:payload="record"
group-shape="default"
group-type="link"
/>
</template>
</template>
</a-table>
<host-create ref="hostCreateRef" :api-edit-caller="true" @on-ok="afterSetupHostConfig" />
<install-dependencies ref="installRef" @on-install-success="getHostList(true)" />
</div>
</template>
<style lang="scss" scoped>
header {
margin-bottom: $space-md;
}
.search {
display: grid;
gap: $space-sm;
padding: $space-sm;
&-option {
width: 100%;
display: grid;
gap: $space-sm;
grid-template-columns: 1fr 1fr;
button {
width: 100%;
}
}
}
</style>