feat: 主机列表视图.
This commit is contained in:
@@ -16,9 +16,9 @@
|
|||||||
</a-scrollbar>
|
</a-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
<!-- 主机列表 -->
|
<!-- 主机列表 -->
|
||||||
<div class="host-list">
|
<host-list class="host-list"
|
||||||
<host-list :hosts="hosts" />
|
:hostList="hosts.hostList"
|
||||||
</div>
|
empty-value="当前分组内无授权主机!" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="list-view-container">
|
<!-- 主机列表 -->
|
||||||
<!-- 主机列表 -->
|
<host-list class="list-view-container"
|
||||||
<host-list :hosts="hosts" />
|
:hostList="hostList"
|
||||||
</div>
|
:empty-value="emptyValue" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -12,11 +12,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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';
|
import HostList from './host-list.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
hosts: AuthorizedHostQueryResponse
|
hostList: Array<HostQueryResponse>,
|
||||||
|
emptyValue: string
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,117 +1,116 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-list ref="listRel"
|
<div class="hosts-list-container">
|
||||||
size="large"
|
<a-list size="large"
|
||||||
max-height="100%"
|
max-height="100%"
|
||||||
:hoverable="true"
|
:hoverable="true"
|
||||||
:data="hosts.hostList">
|
:data="hostList">
|
||||||
<!-- 空数据 -->
|
<!-- 空数据 -->
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<a-empty>
|
<a-empty>
|
||||||
<template #image>
|
<template #image>
|
||||||
<icon-desktop />
|
<icon-desktop />
|
||||||
</template>
|
</template>
|
||||||
当前分组内无授权主机!
|
{{ emptyValue }}
|
||||||
</a-empty>
|
</a-empty>
|
||||||
</template>
|
</template>
|
||||||
<!-- 数据 -->
|
<!-- 数据 -->
|
||||||
<template #item="{ item }">
|
<template #item="{ item }">
|
||||||
<a-list-item class="host-item-wrapper" @click="openTerminal(item)">
|
<a-list-item class="host-item-wrapper" @click="openTerminal(item)">
|
||||||
<div class="host-item">
|
<div class="host-item">
|
||||||
<!-- 左侧图标-名称 -->
|
<!-- 左侧图标-名称 -->
|
||||||
<div class="flex-center host-item-left">
|
<div class="flex-center host-item-left">
|
||||||
<!-- 图标 -->
|
<!-- 图标 -->
|
||||||
<span class="host-item-left-icon">
|
<span class="host-item-left-icon">
|
||||||
<icon-desktop />
|
<icon-desktop />
|
||||||
</span>
|
</span>
|
||||||
<!-- 名称 -->
|
<!-- 名称 -->
|
||||||
<a-tooltip position="top"
|
<a-tooltip position="top"
|
||||||
:mini="true"
|
:mini="true"
|
||||||
content-class="terminal-tooltip-content"
|
content-class="terminal-tooltip-content"
|
||||||
arrow-class="terminal-tooltip-content"
|
arrow-class="terminal-tooltip-content"
|
||||||
:content="`${item.name} (${item.code})`">
|
:content="`${item.name} (${item.code})`">
|
||||||
<span class="host-item-text host-item-left-name">
|
<span class="host-item-text host-item-left-name">
|
||||||
{{ `${item.name} (${item.code})` }}
|
{{ `${item.name} (${item.code})` }}
|
||||||
</span>
|
</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<!-- 中间ip -->
|
<!-- 中间ip -->
|
||||||
<div class="flex-center host-item-center">
|
<div class="flex-center host-item-center">
|
||||||
<!-- ip -->
|
<!-- ip -->
|
||||||
<a-tooltip position="top"
|
<a-tooltip position="top"
|
||||||
:mini="true"
|
:mini="true"
|
||||||
content-class="terminal-tooltip-content"
|
content-class="terminal-tooltip-content"
|
||||||
arrow-class="terminal-tooltip-content"
|
arrow-class="terminal-tooltip-content"
|
||||||
:content="item.address">
|
:content="item.address">
|
||||||
<span class="host-item-text host-item-center-address">
|
<span class="host-item-text host-item-center-address">
|
||||||
{{ item.address }}
|
{{ item.address }}
|
||||||
</span>
|
</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>
|
</a-tooltip>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</a-list-item>
|
||||||
</a-list-item>
|
</template>
|
||||||
</template>
|
</a-list>
|
||||||
</a-list>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -121,20 +120,18 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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 useFavorite from '@/hooks/favorite';
|
||||||
import { ref } from 'vue';
|
|
||||||
import { dataColor } from '@/utils';
|
import { dataColor } from '@/utils';
|
||||||
import { tagColor } from '@/views/asset/host-list/types/const';
|
import { tagColor } from '@/views/asset/host-list/types/const';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
hosts: AuthorizedHostQueryResponse
|
hostList: Array<HostQueryResponse>,
|
||||||
|
emptyValue: string
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { toggle: toggleFavorite, loading: favoriteLoading } = useFavorite('HOST');
|
const { toggle: toggleFavorite, loading: favoriteLoading } = useFavorite('HOST');
|
||||||
|
|
||||||
const listRel = ref();
|
|
||||||
|
|
||||||
// 打开终端
|
// 打开终端
|
||||||
const openTerminal = (item: any) => {
|
const openTerminal = (item: any) => {
|
||||||
console.log('ter', item);
|
console.log('ter', item);
|
||||||
|
|||||||
@@ -12,10 +12,24 @@
|
|||||||
:options="toOptions(NewConnectionTypeKey)"
|
:options="toOptions(NewConnectionTypeKey)"
|
||||||
@change="changeConnectionType" />
|
@change="changeConnectionType" />
|
||||||
<!-- 过滤 -->
|
<!-- 过滤 -->
|
||||||
<a-input-search v-model="filterValue"
|
<a-auto-complete v-model="filterValue"
|
||||||
class="host-filter"
|
class="host-filter"
|
||||||
placeholder="输入名称/编码/IP @标签"
|
placeholder="输入名称/编码/IP @标签"
|
||||||
:allow-clear="true" />
|
: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>
|
||||||
<!-- 授权主机 -->
|
<!-- 授权主机 -->
|
||||||
<div class="terminal-setting-block" style="margin: 0;">
|
<div class="terminal-setting-block" style="margin: 0;">
|
||||||
@@ -46,15 +60,20 @@
|
|||||||
<div v-else class="host-view-container">
|
<div v-else class="host-view-container">
|
||||||
<!-- 分组视图列表 -->
|
<!-- 分组视图列表 -->
|
||||||
<host-group-view v-if="NewConnectionType.GROUP === newConnectionType"
|
<host-group-view v-if="NewConnectionType.GROUP === newConnectionType"
|
||||||
:hosts="hosts" />
|
:hosts="hosts"
|
||||||
|
:filter-value="filterValue" />
|
||||||
<!-- 列表视图 -->
|
<!-- 列表视图 -->
|
||||||
<host-list-view v-if="NewConnectionType.LIST === newConnectionType"
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,37 +88,108 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { SelectOptionData } from '@arco-design/web-vue';
|
||||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
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 { 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 { NewConnectionType, NewConnectionTypeKey } from '../../types/terminal.const';
|
||||||
import useLoading from '@/hooks/loading';
|
import useLoading from '@/hooks/loading';
|
||||||
import { useDictStore } from '@/store';
|
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 HostGroupView from './host-group-view.vue';
|
||||||
import HostListView from './host-list-view.vue';
|
import HostListView from './host-list-view.vue';
|
||||||
|
|
||||||
const { loading, setLoading } = useLoading();
|
const { loading, setLoading } = useLoading();
|
||||||
const { toOptions } = useDictStore();
|
const { toOptions } = useDictStore();
|
||||||
|
|
||||||
const newConnectionType = ref(NewConnectionType.GROUP);
|
const newConnectionType = ref(NewConnectionType.LIST);
|
||||||
const filterValue = ref();
|
const filterValue = ref();
|
||||||
|
const filterOptions = ref<Array<SelectOptionData>>([]);
|
||||||
const hosts = ref<AuthorizedHostQueryResponse>({} as AuthorizedHostQueryResponse);
|
const hosts = ref<AuthorizedHostQueryResponse>({} as AuthorizedHostQueryResponse);
|
||||||
|
const hostList = ref<Array<HostQueryResponse>>([]);
|
||||||
|
|
||||||
// 修改连接类型
|
// 修改连接类型
|
||||||
const changeConnectionType = () => {
|
const changeConnectionType = () => {
|
||||||
// FIXME 持久化
|
// 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 {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { data } = await getCurrentAuthorizedHost();
|
const { data } = await getCurrentAuthorizedHost();
|
||||||
hosts.value = data;
|
hosts.value = data;
|
||||||
|
// 初始化过滤项
|
||||||
|
initFilterOptions();
|
||||||
|
// 处理数据
|
||||||
|
shuffleHosts();
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// 加载主机信息
|
||||||
|
onBeforeMount(init);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -108,7 +198,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.host-filter {
|
:deep(.host-filter) {
|
||||||
width: 36%;
|
width: 36%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
background: var(--color-bg-header);
|
background: var(--color-bg-header);
|
||||||
|
position: relative;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-main {
|
&-main {
|
||||||
|
|||||||
Reference in New Issue
Block a user