feat: 主机别名.

This commit is contained in:
lijiahangmax
2023-12-19 01:06:09 +08:00
parent bbb56d63bf
commit 934c59cf96
9 changed files with 206 additions and 66 deletions

View File

@@ -28,6 +28,7 @@ public class HostAliasUpdateRequest implements Serializable {
@Schema(description = "id") @Schema(description = "id")
private Long id; private Long id;
@NotNull
@Size(max = 32) @Size(max = 32)
@Schema(description = "别名") @Schema(description = "别名")
private String name; private String name;

View File

@@ -61,4 +61,7 @@ public class HostVO implements Serializable {
@Schema(description = "分组 id") @Schema(description = "分组 id")
private Set<Long> groupIdList; private Set<Long> groupIdList;
@Schema(description = "别名")
private String alias;
} }

View File

@@ -2,6 +2,7 @@ package com.orion.ops.module.asset.service.impl;
import com.orion.lang.function.Functions; import com.orion.lang.function.Functions;
import com.orion.lang.utils.collect.Lists; import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.collect.Maps;
import com.orion.ops.framework.common.constant.Const; import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.framework.common.utils.TreeUtils; import com.orion.ops.framework.common.utils.TreeUtils;
import com.orion.ops.framework.common.utils.Valid; import com.orion.ops.framework.common.utils.Valid;
@@ -15,10 +16,7 @@ import com.orion.ops.module.asset.service.HostService;
import com.orion.ops.module.infra.api.*; import com.orion.ops.module.infra.api.*;
import com.orion.ops.module.infra.entity.dto.data.DataGroupDTO; import com.orion.ops.module.infra.entity.dto.data.DataGroupDTO;
import com.orion.ops.module.infra.entity.dto.tag.TagDTO; import com.orion.ops.module.infra.entity.dto.tag.TagDTO;
import com.orion.ops.module.infra.enums.DataGroupTypeEnum; import com.orion.ops.module.infra.enums.*;
import com.orion.ops.module.infra.enums.DataPermissionTypeEnum;
import com.orion.ops.module.infra.enums.FavoriteTypeEnum;
import com.orion.ops.module.infra.enums.TagTypeEnum;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -67,6 +65,9 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
@Resource @Resource
private TagRelApi tagRelApi; private TagRelApi tagRelApi;
@Resource
private DataAliasApi dataAliasApi;
@Override @Override
public List<Long> getAuthorizedDataRelId(DataPermissionTypeEnum type, AssetAuthorizedDataQueryRequest request) { public List<Long> getAuthorizedDataRelId(DataPermissionTypeEnum type, AssetAuthorizedDataQueryRequest request) {
Long userId = request.getUserId(); Long userId = request.getUserId();
@@ -157,6 +158,8 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
AuthorizedHostWrapperVO wrapper = new AuthorizedHostWrapperVO(); AuthorizedHostWrapperVO wrapper = new AuthorizedHostWrapperVO();
// 查询我的收藏 // 查询我的收藏
Future<List<Long>> favoriteResult = favoriteApi.getFavoriteRelIdListAsync(FavoriteTypeEnum.HOST, userId); Future<List<Long>> favoriteResult = favoriteApi.getFavoriteRelIdListAsync(FavoriteTypeEnum.HOST, userId);
// 查询数据别名
Future<Map<Long, String>> dataAliasResult = dataAliasApi.getDataAliasAsync(userId, DataAliasTypeEnum.HOST);
// 查询分组 // 查询分组
List<DataGroupDTO> dataGroup = dataGroupApi.getDataGroupList(DataGroupTypeEnum.HOST); List<DataGroupDTO> dataGroup = dataGroupApi.getDataGroupList(DataGroupTypeEnum.HOST);
// 查询分组引用 // 查询分组引用
@@ -181,7 +184,9 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
dataGroupRel, dataGroupRel,
authorizedGroupIdList)); authorizedGroupIdList));
// 设置主机拓展信息 // 设置主机拓展信息
this.getAuthorizedHostExtra(wrapper.getHostList(), favoriteResult.get()); this.getAuthorizedHostExtra(wrapper.getHostList(),
favoriteResult.get(),
dataAliasResult.get());
return wrapper; return wrapper;
} }
@@ -263,9 +268,11 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
* *
* @param hosts hosts * @param hosts hosts
* @param favorite favorite * @param favorite favorite
* @param aliasMap aliasMap
*/ */
private void getAuthorizedHostExtra(List<HostVO> hosts, private void getAuthorizedHostExtra(List<HostVO> hosts,
List<Long> favorite) { List<Long> favorite,
Map<Long, String> aliasMap) {
if (Lists.isEmpty(hosts)) { if (Lists.isEmpty(hosts)) {
return; return;
} }
@@ -281,6 +288,10 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
for (int i = 0; i < hosts.size(); i++) { for (int i = 0; i < hosts.size(); i++) {
hosts.get(i).setTags(tags.get(i)); hosts.get(i).setTags(tags.get(i));
} }
// 设置主机别名
if (!Maps.isEmpty(aliasMap)) {
hosts.forEach(s -> s.setAlias(aliasMap.get(s.getId())));
}
} }
} }

View File

@@ -34,6 +34,7 @@ public class DataAliasUpdateDTO implements Serializable {
@Schema(description = "数据id") @Schema(description = "数据id")
private Long relId; private Long relId;
@NotNull
@Size(max = 32) @Size(max = 32)
@Schema(description = "别名") @Schema(description = "别名")
private String alias; private String alias;

View File

@@ -17,6 +17,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -83,6 +84,7 @@ public class DataAliasServiceImpl implements DataAliasService {
.eq(DataAliasDO::getType, type) .eq(DataAliasDO::getType, type)
.then() .then()
.stream() .stream()
.filter(s -> Objects.nonNull(s.getAlias()))
.collect(Collectors.toMap( .collect(Collectors.toMap(
s -> String.valueOf(s.getRelId()), s -> String.valueOf(s.getRelId()),
DataAliasDO::getAlias, DataAliasDO::getAlias,

View File

@@ -46,8 +46,13 @@ export interface HostQueryResponse extends TableData {
creator: string; creator: string;
updater: string; updater: string;
favorite: boolean; favorite: boolean;
alias: string;
tags: Array<{ id: number, name: string }>; tags: Array<{ id: number, name: string }>;
groupIdList: Array<number>; groupIdList: Array<number>;
editable: boolean;
loading: boolean;
modCount: number;
} }
/** /**
@@ -72,6 +77,14 @@ export interface HostConfigQueryResponse {
config: Record<string, any>; config: Record<string, any>;
} }
/**
* 主机别名更新请求
*/
export interface HostAliasUpdateRequest {
id?: number;
name?: string;
}
/** /**
* 创建主机 * 创建主机
*/ */
@@ -114,6 +127,13 @@ export function deleteHost(id: number) {
return axios.delete('/asset/host/delete', { params: { id } }); return axios.delete('/asset/host/delete', { params: { id } });
} }
/**
* 修改主机别名
*/
export function updateHostAlias(request: HostAliasUpdateRequest) {
return axios.put('/asset/host/update-alias', request);
}
/** /**
* 查询主机配置 * 查询主机配置
*/ */

View File

@@ -17,14 +17,14 @@
<template v-if="node.editable"> <template v-if="node.editable">
<a-input size="mini" <a-input size="mini"
ref="renameInput" ref="renameInput"
v-model="currName" v-model="node.title"
style="width: 138px;" style="width: 138px;"
placeholder="名称" placeholder="名称"
:max-length="32" :max-length="32"
:disabled="node.loading" :disabled="node.loading"
@blur="() => saveNode(node.key)" @blur="saveNode(node)"
@pressEnter="() => saveNode(node.key)" @pressEnter="saveNode(node)"
@change="() => saveNode(node.key)"> @change="saveNode(node)">
<template #suffix> <template #suffix>
<!-- 加载中 --> <!-- 加载中 -->
<icon-loading v-if="node.loading" /> <icon-loading v-if="node.loading" />
@@ -32,7 +32,7 @@
<icon-check v-else <icon-check v-else
class="pointer" class="pointer"
title="保存" title="保存"
@click="() => saveNode(node.key)" /> @click="saveNode(node)" />
</template> </template>
</a-input> </a-input>
</template> </template>
@@ -116,9 +116,7 @@
const cacheStore = useCacheStore(); const cacheStore = useCacheStore();
const tree = ref(); const tree = ref();
const modCount = ref(0);
const renameInput = ref(); const renameInput = ref();
const currName = ref();
const treeData = ref<Array<TreeNodeData>>([]); const treeData = ref<Array<TreeNodeData>>([]);
const checkedKeys = computed<Array<number>>({ const checkedKeys = computed<Array<number>>({
@@ -140,7 +138,6 @@
if (!node) { if (!node) {
return; return;
} }
currName.value = title;
node.editable = true; node.editable = true;
nextTick(() => { nextTick(() => {
renameInput.value?.focus(); renameInput.value?.focus();
@@ -178,14 +175,13 @@
const addRootNode = () => { const addRootNode = () => {
const newKey = `${createGroupGroupPrefix}${Date.now()}`; const newKey = `${createGroupGroupPrefix}${Date.now()}`;
treeData.value.push({ treeData.value.push({
title: '新分组', title: '',
key: newKey key: newKey
}); });
// 编辑子节点 // 编辑子节点
const newNode = findNode<TreeNodeData>(newKey, treeData.value); const newNode = findNode<TreeNodeData>(newKey, treeData.value);
if (newNode) { if (newNode) {
newNode.editable = true; newNode.editable = true;
currName.value = '';
nextTick(() => { nextTick(() => {
renameInput.value?.focus(); renameInput.value?.focus();
}); });
@@ -197,7 +193,7 @@
const newKey = `${createGroupGroupPrefix}${Date.now()}`; const newKey = `${createGroupGroupPrefix}${Date.now()}`;
const children = parentNode.children || []; const children = parentNode.children || [];
children.push({ children.push({
title: '新分组', title: '',
key: newKey key: newKey
}); });
parentNode.children = children; parentNode.children = children;
@@ -209,7 +205,6 @@
const newNode = findNode<TreeNodeData>(newKey, treeData.value); const newNode = findNode<TreeNodeData>(newKey, treeData.value);
if (newNode) { if (newNode) {
newNode.editable = true; newNode.editable = true;
currName.value = '';
nextTick(() => { nextTick(() => {
renameInput.value?.focus(); renameInput.value?.focus();
}); });
@@ -217,18 +212,16 @@
}); });
}; };
// FIXME 测试
// 保存节点 // 保存节点
const saveNode = async (key: string | number) => { const saveNode = async (node: TreeNodeData) => {
modCount.value++; const key = node.key
if (modCount.value !== 1) { const newTitle = node.title
node.modCount = (node.modCount || 0) + 1;
if (node.modCount != 1) {
return; return;
} }
// 寻找节点 if (newTitle) {
const node = findNode<TreeNodeData>(key, treeData.value);
if (!node) {
return;
}
if (currName.value) {
node.loading = true; node.loading = true;
try { try {
// 创建节点 // 创建节点
@@ -240,17 +233,16 @@
// 创建 // 创建
const { data } = await createHostGroup({ const { data } = await createHostGroup({
parentId: parent.key as number, parentId: parent.key as number,
name: currName.value name: newTitle
}); });
node.key = data; node.key = data;
} else { } else {
// 重命名节点 // 重命名节点
await updateHostGroupName({ await updateHostGroupName({
id: key as unknown as number, id: key as unknown as number,
name: currName.value name: newTitle
}); });
} }
node.title = currName.value;
node.editable = false; node.editable = false;
} catch (e) { } catch (e) {
// 重复 重新聚焦 // 重复 重新聚焦
@@ -282,7 +274,7 @@
} }
node.editable = false; node.editable = false;
} }
modCount.value = 0; node.modCount = 0;
}; };
// 移动分组 // 移动分组

View File

@@ -86,11 +86,12 @@
} }
}); });
// FIXME 省略和tooltip
// 渲染 label // 渲染 label
const renderLabel = (label: string) => { const renderLabel = (label: string) => {
const last = label.lastIndexOf('-'); const last = label.lastIndexOf('-');
const prefix = label.substring(0, last); const prefix = label.substring(0, last - 1);
const ip = label.substring(last + 1, label.length); const ip = label.substring(last + 2, label.length);
return `${prefix} - <span class="span-blue">${ip}</span>`; return `${prefix} - <span class="span-blue">${ip}</span>`;
}; };
@@ -117,7 +118,7 @@
data.value = hosts.map(s => { data.value = hosts.map(s => {
return { return {
value: String(s.id), value: String(s.id),
label: `${s.name}(${s.code})-${s.address}`, label: `${s.name} (${s.code}) - ${s.address}`,
disabled: false disabled: false
}; };
}); });

View File

@@ -21,18 +21,62 @@
<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" <span class="host-item-left-name">
:mini="true" <!-- 名称文本 -->
content-class="terminal-tooltip-content" <template v-if="!item.editable">
arrow-class="terminal-tooltip-content" <!-- 文本 -->
:content="`${item.name} (${item.code})`"> <a-tooltip position="top"
<span class="host-item-text host-item-left-name"> :mini="true"
{{ `${item.name} (${item.code})` }} content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
:content="item.alias || `${item.name} (${item.code})`">
<span class="host-item-text host-item-left-name-text">
<template v-if="item.alias">
{{ item.alias }}
</template>
<template v-else>
{{ `${item.name} (${item.code})` }}
</template>
</span>
</a-tooltip>
<!-- 修改别名 -->
<a-tooltip position="top"
:mini="true"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
content="修改别名">
<icon-edit class="host-item-left-name-edit"
@click.stop="clickEditAlias(item)" />
</a-tooltip>
</template>
<!-- 名称输入框 -->
<template v-else>
<a-input v-model="item.alias"
ref="aliasNameInput"
class="host-item-left-name-input"
:max-length="32"
:disabled="item.loading"
size="mini"
placeholder="主机别名"
@blur="saveAlias(item)"
@pressEnter="saveAlias(item)"
@change="saveAlias(item)"
@click.stop>
<template #suffix>
<!-- 加载中 -->
<icon-loading v-if="item.loading" />
<!-- 保存 -->
<icon-check v-else
class="pointer"
title="保存"
@click="saveAlias(item)" />
</template>
</a-input>
</template>
</span> </span>
</a-tooltip>
</div> </div>
<!-- 中间ip --> <!-- 中间ip -->
<div class="flex-center host-item-center"> <div class="flex-center host-item-center">
@@ -123,6 +167,8 @@
import useFavorite from '@/hooks/favorite'; import useFavorite from '@/hooks/favorite';
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';
import { ref, nextTick } from 'vue';
import { updateHostAlias } from '@/api/asset/host';
const props = defineProps<{ const props = defineProps<{
hostList: Array<HostQueryResponse>, hostList: Array<HostQueryResponse>,
@@ -131,18 +177,52 @@
const { toggle: toggleFavorite, loading: favoriteLoading } = useFavorite('HOST'); const { toggle: toggleFavorite, loading: favoriteLoading } = useFavorite('HOST');
const aliasNameInput = ref();
// 点击修改别名
const clickEditAlias = (item: HostQueryResponse) => {
item.editable = true;
if (!item.alias) {
item.alias = `${item.name} (${item.code})`;
}
nextTick(() => {
aliasNameInput.value?.focus();
});
};
// 保存别名
const saveAlias = async (item: HostQueryResponse) => {
item.loading = true;
item.modCount = (item.modCount || 0) + 1;
if (item.modCount != 1) {
return;
}
try {
// 修改别名
await updateHostAlias({
id: item.id,
name: item.alias || ''
});
item.editable = false;
} catch (e) {
} finally {
item.loading = false;
item.modCount = 0;
}
};
// 打开终端 // 打开终端
const openTerminal = (item: any) => { const openTerminal = (item: HostQueryResponse) => {
console.log('ter', item); console.log('ter', item);
}; };
// 打开配置 // 打开配置
const openSetting = (item: any) => { const openSetting = (item: HostQueryResponse) => {
console.log('set', item); console.log('set', item);
}; };
// 设置收藏 // 设置收藏
const setFavorite = async (item: any) => { const setFavorite = async (item: HostQueryResponse) => {
if (favoriteLoading.value) { if (favoriteLoading.value) {
return; return;
} }
@@ -184,11 +264,11 @@
.host-item { .host-item {
width: 100%; width: 100%;
padding: 0 18px; height: @host-item-height;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: @host-item-height; position: relative;
&-text { &-text {
display: inline-block; display: inline-block;
@@ -197,10 +277,27 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
&:hover {
.host-item-left-name-edit {
display: inline;
}
.host-item-right-tags {
display: none;
}
.host-item-right-actions {
display: flex;
}
}
} }
.host-item-left { .host-item-left {
width: 35%; width: 35%;
height: 100%;
padding-left: 18px;
position: absolute;
&-icon { &-icon {
width: 32px; width: 32px;
@@ -216,12 +313,36 @@
} }
&-name { &-name {
max-width: calc(100% - 32px - 12px - 8px); // 100% - icon-width - icon-margin-right
width: calc(100% - 42px);
display: flex;
align-items: center;
&-text {
// 100% - edit-margin-left - edit-font-size
max-width: calc(100% - 18px);
}
&-edit {
display: none;
margin-left: 4px;
cursor: pointer;
color: rgb(var(--blue-6));
font-size: 14px;
}
&-input {
width: 80%;
}
} }
} }
.host-item-center { .host-item-center {
width: 25%; width: 25%;
height: 100%;
left: 35%;
padding: 0 8px;
position: absolute;
&-address { &-address {
max-width: 100%; max-width: 100%;
@@ -231,34 +352,22 @@
.host-item-right { .host-item-right {
width: 40%; width: 40%;
height: 100%; height: 100%;
left: 60%;
padding-right: 18px;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
position: relative; position: absolute;
&-tags { &-tags {
// 必须设置 最外层用的是 min-width
position: absolute;
width: 100%; width: 100%;
} }
&-actions { &-actions {
position: absolute;
display: none; display: none;
width: 100%; width: 100%;
justify-content: flex-end; justify-content: flex-end;
} }
} }
&:hover {
.host-item-right-tags {
display: none;
}
.host-item-right-actions {
display: flex;
}
}
} }
.favorite { .favorite {