feat: 主机列表视图.
This commit is contained in:
@@ -16,9 +16,9 @@
|
||||
</a-scrollbar>
|
||||
</div>
|
||||
<!-- 主机列表 -->
|
||||
<div class="host-list">
|
||||
<host-list :hosts="hosts" />
|
||||
</div>
|
||||
<host-list class="host-list"
|
||||
:hostList="hosts.hostList"
|
||||
empty-value="当前分组内无授权主机!" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="list-view-container">
|
||||
<!-- 主机列表 -->
|
||||
<host-list :hosts="hosts" />
|
||||
</div>
|
||||
<!-- 主机列表 -->
|
||||
<host-list class="list-view-container"
|
||||
:hostList="hostList"
|
||||
:empty-value="emptyValue" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -12,11 +12,12 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
import HostList from './host-list.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
hosts: AuthorizedHostQueryResponse
|
||||
hostList: Array<HostQueryResponse>,
|
||||
emptyValue: string
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,117 +1,116 @@
|
||||
<template>
|
||||
<a-list ref="listRel"
|
||||
size="large"
|
||||
max-height="100%"
|
||||
:hoverable="true"
|
||||
:data="hosts.hostList">
|
||||
<!-- 空数据 -->
|
||||
<template #empty>
|
||||
<a-empty>
|
||||
<template #image>
|
||||
<icon-desktop />
|
||||
</template>
|
||||
当前分组内无授权主机!
|
||||
</a-empty>
|
||||
</template>
|
||||
<!-- 数据 -->
|
||||
<template #item="{ item }">
|
||||
<a-list-item class="host-item-wrapper" @click="openTerminal(item)">
|
||||
<div class="host-item">
|
||||
<!-- 左侧图标-名称 -->
|
||||
<div class="flex-center host-item-left">
|
||||
<!-- 图标 -->
|
||||
<span class="host-item-left-icon">
|
||||
<div class="hosts-list-container">
|
||||
<a-list size="large"
|
||||
max-height="100%"
|
||||
:hoverable="true"
|
||||
:data="hostList">
|
||||
<!-- 空数据 -->
|
||||
<template #empty>
|
||||
<a-empty>
|
||||
<template #image>
|
||||
<icon-desktop />
|
||||
</template>
|
||||
{{ emptyValue }}
|
||||
</a-empty>
|
||||
</template>
|
||||
<!-- 数据 -->
|
||||
<template #item="{ item }">
|
||||
<a-list-item class="host-item-wrapper" @click="openTerminal(item)">
|
||||
<div class="host-item">
|
||||
<!-- 左侧图标-名称 -->
|
||||
<div class="flex-center host-item-left">
|
||||
<!-- 图标 -->
|
||||
<span class="host-item-left-icon">
|
||||
<icon-desktop />
|
||||
</span>
|
||||
<!-- 名称 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
:content="`${item.name} (${item.code})`">
|
||||
<!-- 名称 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
:content="`${item.name} (${item.code})`">
|
||||
<span class="host-item-text host-item-left-name">
|
||||
{{ `${item.name} (${item.code})` }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<!-- 中间ip -->
|
||||
<div class="flex-center host-item-center">
|
||||
<!-- ip -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
:content="item.address">
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<!-- 中间ip -->
|
||||
<div class="flex-center host-item-center">
|
||||
<!-- ip -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
:content="item.address">
|
||||
<span class="host-item-text host-item-center-address">
|
||||
{{ item.address }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<!-- 右侧tag-操作 -->
|
||||
<div class="flex-center host-item-right">
|
||||
<!-- tags -->
|
||||
<div class="host-item-right-tags">
|
||||
<template v-if="item.tags?.length">
|
||||
<a-tag v-for="(tag, i) in item.tags"
|
||||
class="host-item-text"
|
||||
:key="tag.id"
|
||||
:style="{
|
||||
maxWidth: `calc(${100 / item.tags.length}% - ${i !== item.tags.length - 1 ? '8px' : '0px'})`,
|
||||
marginRight: `${i !== item.tags.length - 1 ? '8px' : '0'}`,
|
||||
}"
|
||||
:color="dataColor(tag.name, tagColor)">
|
||||
{{ tag.name }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 操作 -->
|
||||
<div class="host-item-right-actions">
|
||||
<!-- 连接主机 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:popup-container="listRel?.$el?.parent"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="连接主机">
|
||||
<div class="terminal-sidebar-icon-wrapper">
|
||||
<div class="terminal-sidebar-icon" @click.stop="openTerminal(item)">
|
||||
<icon-thunderbolt />
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<!-- 连接设置 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:popup-container="listRel?.$el?.parent"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="连接设置">
|
||||
<div class="terminal-sidebar-icon-wrapper">
|
||||
<div class="terminal-sidebar-icon" @click.stop="openSetting(item)">
|
||||
<icon-settings />
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<!-- 收藏 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:popup-container="listRel?.$el?.parent"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="收藏">
|
||||
<div class="terminal-sidebar-icon-wrapper">
|
||||
<div class="terminal-sidebar-icon" @click.stop="setFavorite(item)">
|
||||
<icon-star-fill class="favorite" v-if="item.favorite" />
|
||||
<icon-star v-else />
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<!-- 右侧tag-操作 -->
|
||||
<div class="flex-center host-item-right">
|
||||
<!-- tags -->
|
||||
<div class="host-item-right-tags">
|
||||
<template v-if="item.tags?.length">
|
||||
<a-tag v-for="(tag, i) in item.tags"
|
||||
class="host-item-text"
|
||||
:key="tag.id"
|
||||
:style="{
|
||||
maxWidth: `calc(${100 / item.tags.length}% - ${i !== item.tags.length - 1 ? '8px' : '0px'})`,
|
||||
marginRight: `${i !== item.tags.length - 1 ? '8px' : '0'}`,
|
||||
}"
|
||||
:color="dataColor(tag.name, tagColor)">
|
||||
{{ tag.name }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 操作 -->
|
||||
<div class="host-item-right-actions">
|
||||
<!-- 连接主机 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:popup-visible="true"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="连接主机">
|
||||
<div class="terminal-sidebar-icon-wrapper">
|
||||
<div class="terminal-sidebar-icon" @click.stop="openTerminal(item)">
|
||||
<icon-thunderbolt />
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<!-- 连接设置 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="连接设置">
|
||||
<div class="terminal-sidebar-icon-wrapper">
|
||||
<div class="terminal-sidebar-icon" @click.stop="openSetting(item)">
|
||||
<icon-settings />
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<!-- 收藏 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="收藏">
|
||||
<div class="terminal-sidebar-icon-wrapper">
|
||||
<div class="terminal-sidebar-icon" @click.stop="setFavorite(item)">
|
||||
<icon-star-fill class="favorite" v-if="item.favorite" />
|
||||
<icon-star v-else />
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -121,20 +120,18 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
import useFavorite from '@/hooks/favorite';
|
||||
import { ref } from 'vue';
|
||||
import { dataColor } from '@/utils';
|
||||
import { tagColor } from '@/views/asset/host-list/types/const';
|
||||
|
||||
const props = defineProps<{
|
||||
hosts: AuthorizedHostQueryResponse
|
||||
hostList: Array<HostQueryResponse>,
|
||||
emptyValue: string
|
||||
}>();
|
||||
|
||||
const { toggle: toggleFavorite, loading: favoriteLoading } = useFavorite('HOST');
|
||||
|
||||
const listRel = ref();
|
||||
|
||||
// 打开终端
|
||||
const openTerminal = (item: any) => {
|
||||
console.log('ter', item);
|
||||
|
||||
@@ -12,10 +12,24 @@
|
||||
:options="toOptions(NewConnectionTypeKey)"
|
||||
@change="changeConnectionType" />
|
||||
<!-- 过滤 -->
|
||||
<a-input-search v-model="filterValue"
|
||||
class="host-filter"
|
||||
placeholder="输入名称/编码/IP @标签"
|
||||
:allow-clear="true" />
|
||||
<a-auto-complete v-model="filterValue"
|
||||
class="host-filter"
|
||||
placeholder="输入名称/编码/IP @标签"
|
||||
:allow-clear="true"
|
||||
:data="filterOptions"
|
||||
:filter-option="searchFilter"
|
||||
@change="shuffleHosts">
|
||||
<template #option="{ data: { raw: { value, isTag} } }">
|
||||
<!-- tag -->
|
||||
<a-tag v-if="isTag" :color="dataColor(value, tagColor)">
|
||||
{{ value }}
|
||||
</a-tag>
|
||||
<!-- 文本 -->
|
||||
<template v-else>
|
||||
{{ value }}
|
||||
</template>
|
||||
</template>
|
||||
</a-auto-complete>
|
||||
</div>
|
||||
<!-- 授权主机 -->
|
||||
<div class="terminal-setting-block" style="margin: 0;">
|
||||
@@ -46,15 +60,20 @@
|
||||
<div v-else class="host-view-container">
|
||||
<!-- 分组视图列表 -->
|
||||
<host-group-view v-if="NewConnectionType.GROUP === newConnectionType"
|
||||
:hosts="hosts" />
|
||||
:hosts="hosts"
|
||||
:filter-value="filterValue" />
|
||||
<!-- 列表视图 -->
|
||||
<host-list-view v-if="NewConnectionType.LIST === newConnectionType"
|
||||
:hosts="hosts" />
|
||||
|
||||
:host-list="hostList"
|
||||
empty-value="无授权主机!" />
|
||||
<!-- 我的收藏 -->
|
||||
|
||||
<host-list-view v-if="NewConnectionType.FAVORITE === newConnectionType"
|
||||
:host-list="[]"
|
||||
empty-value="无收藏记录, 快去点击主机右侧的⭐进行收藏吧!" />
|
||||
<!-- 最近连接 -->
|
||||
|
||||
<host-list-view v-if="NewConnectionType.LATEST === newConnectionType"
|
||||
:host-list="[]"
|
||||
empty-value="暂无连接记录, 快去体验吧!" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,37 +88,108 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { SelectOptionData } from '@arco-design/web-vue';
|
||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data';
|
||||
import { onBeforeMount, ref } from 'vue';
|
||||
import { onBeforeMount, ref, watch } from 'vue';
|
||||
import { NewConnectionType, NewConnectionTypeKey } from '../../types/terminal.const';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { useDictStore } from '@/store';
|
||||
import { dataColor } from '@/utils';
|
||||
import { tagColor } from '@/views/asset/host-list/types/const';
|
||||
import HostGroupView from './host-group-view.vue';
|
||||
import HostListView from './host-list-view.vue';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
const { toOptions } = useDictStore();
|
||||
|
||||
const newConnectionType = ref(NewConnectionType.GROUP);
|
||||
const newConnectionType = ref(NewConnectionType.LIST);
|
||||
const filterValue = ref();
|
||||
const filterOptions = ref<Array<SelectOptionData>>([]);
|
||||
const hosts = ref<AuthorizedHostQueryResponse>({} as AuthorizedHostQueryResponse);
|
||||
const hostList = ref<Array<HostQueryResponse>>([]);
|
||||
|
||||
// 修改连接类型
|
||||
const changeConnectionType = () => {
|
||||
// FIXME 持久化
|
||||
};
|
||||
|
||||
// 加载主机信息
|
||||
onBeforeMount(async () => {
|
||||
// 过滤输入
|
||||
const searchFilter = (searchValue: string, option: SelectOptionData) => {
|
||||
if (searchValue.startsWith('@')) {
|
||||
// tag 过滤
|
||||
return option.isTag && (option.value as string).toLowerCase().startsWith(searchValue.substring(1, searchValue.length).toLowerCase());
|
||||
} else {
|
||||
// 文本过滤
|
||||
return !option.isTag && (option.value as string).toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
|
||||
}
|
||||
};
|
||||
|
||||
// 数据处理
|
||||
const shuffleHosts = () => {
|
||||
let list = [...hosts.value?.hostList];
|
||||
// 过滤
|
||||
if (filterValue.value) {
|
||||
console.log(filterValue.value);
|
||||
}
|
||||
// 排序
|
||||
hostList.value = list?.sort((o1, o2) => {
|
||||
if (o1.favorite || o2.favorite) {
|
||||
if (o1.favorite && o2.favorite) {
|
||||
return o2.id < o1.id ? 1 : -1;
|
||||
}
|
||||
return o2.favorite ? 1 : -1;
|
||||
} else {
|
||||
return o2.id < o1.id ? 1 : -1;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化过滤器项
|
||||
const initFilterOptions = () => {
|
||||
// 添加 tags
|
||||
const tagNames = hosts.value.hostList?.map(s => s.tags)
|
||||
.filter(s => s?.length)
|
||||
.flat(1)
|
||||
.sort((o1, o2) => o1.id - o2.id)
|
||||
.map(s => s.name);
|
||||
[...new Set(tagNames)].map(value => {
|
||||
return { value: value, isTag: true };
|
||||
}).forEach(s => filterOptions.value.push(s));
|
||||
// 添加主机信息
|
||||
const hostMeta = hosts.value.hostList?.map(s => {
|
||||
return [s.name, s.code, s.address];
|
||||
}).flat(1);
|
||||
[...new Set(hostMeta)].map(value => {
|
||||
return { value };
|
||||
}).forEach(s => filterOptions.value.push(s));
|
||||
// // 添加主机信息
|
||||
// hosts.value.hostList?.map(s => {
|
||||
// return `${s.name} (${s.code}) - ${s.address}`;
|
||||
// }).map(value => {
|
||||
// return { value };
|
||||
// }).forEach(s => filterOptions.value.push(s));
|
||||
};
|
||||
|
||||
// 初始化
|
||||
const init = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data } = await getCurrentAuthorizedHost();
|
||||
hosts.value = data;
|
||||
// 初始化过滤项
|
||||
initFilterOptions();
|
||||
// 处理数据
|
||||
shuffleHosts();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 加载主机信息
|
||||
onBeforeMount(init);
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -108,7 +198,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.host-filter {
|
||||
:deep(.host-filter) {
|
||||
width: 36%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,8 @@
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: var(--color-bg-header);
|
||||
position: relative;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
&-main {
|
||||
|
||||
Reference in New Issue
Block a user