🎨 优化主机分组授权显示.
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
@grant="doGrant"
|
||||
@select-all="selectAll"
|
||||
@reverse="reverseSelect">
|
||||
<!-- 分组 -->
|
||||
<!-- 主机分组 -->
|
||||
<host-group-tree v-model:checked-keys="checkedGroups"
|
||||
ref="tree"
|
||||
outer-class="group-main-tree"
|
||||
@@ -15,8 +15,36 @@
|
||||
@set-loading="setLoading"
|
||||
@selected-node="(e) => selectedGroup = e"
|
||||
@on-selected="clickGroup" />
|
||||
<!-- 主机列表 -->
|
||||
<host-list class="group-main-hosts sticky-list" :group="selectedGroup" />
|
||||
<a-divider direction="vertical" />
|
||||
<!-- 主机表格 -->
|
||||
<a-table class="group-main-hosts"
|
||||
row-key="id"
|
||||
:sticky-header="true"
|
||||
:loading="loading"
|
||||
:columns="hostColumns"
|
||||
:data="selectedGroupHosts"
|
||||
:pagination="false"
|
||||
:bordered="false">
|
||||
<!-- 空状态 -->
|
||||
<template #empty>
|
||||
<a-empty style="margin: 32px 0;" description="当前分组内无主机" />
|
||||
</template>
|
||||
<!-- 主机类型 -->
|
||||
<template #type="{ record }">
|
||||
<a-tag class="flex-center" :color="getDictValue(hostTypeKey, record.type, 'color')">
|
||||
<!-- 系统类型图标 -->
|
||||
<component v-if="HostOsType[record.osType as keyof typeof HostOsType]"
|
||||
:is="HostOsType[record.osType as keyof typeof HostOsType].icon"
|
||||
class="os-icon" />
|
||||
<!-- 主机类型 -->
|
||||
<span>{{ getDictValue(hostTypeKey, record.type) }}</span>
|
||||
</a-tag>
|
||||
</template>
|
||||
<!-- 地址 -->
|
||||
<template #address="{ record }">
|
||||
{{ record.address }}:{{ record.port }}
|
||||
</template>
|
||||
</a-table>
|
||||
</grant-layout>
|
||||
</template>
|
||||
|
||||
@@ -28,15 +56,18 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TreeNodeData } from '@arco-design/web-vue';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
import type { AssetAuthorizedDataQueryRequest, AssetDataGrantRequest } from '@/api/asset/asset-data-grant';
|
||||
import { ref } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { getAuthorizedHostGroup, grantHostGroup } from '@/api/asset/asset-data-grant';
|
||||
import { useCacheStore } from '@/store';
|
||||
import { useCacheStore, useDictStore } from '@/store';
|
||||
import { flatNodeKeys } from '@/utils/tree';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { hostColumns } from '../types/table.columns';
|
||||
import { HostOsType, hostTypeKey } from '@/views/asset/host-list/types/const';
|
||||
import { getHostGroupRelList } from '@/api/asset/host-group';
|
||||
import HostGroupTree from '@/components/asset/host-group/tree/index.vue';
|
||||
import HostList from './host-list.vue';
|
||||
import GrantLayout from './grant-layout.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -44,12 +75,32 @@
|
||||
}>();
|
||||
|
||||
const cacheStore = useCacheStore();
|
||||
const { getDictValue } = useDictStore();
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
const tree = ref();
|
||||
const authorizedGroups = ref<Array<number>>([]);
|
||||
const checkedGroups = ref<Array<number>>([]);
|
||||
const selectedGroup = ref<TreeNodeData>({});
|
||||
const selectedGroupHosts = ref<Array<HostQueryResponse>>([]);
|
||||
|
||||
// 监听分组变化 加载组内数据
|
||||
watch(() => selectedGroup.value?.key, async (groupId) => {
|
||||
if (!groupId) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
// 加载组内数据
|
||||
const { data } = await getHostGroupRelList(groupId as number);
|
||||
const hosts = await cacheStore.loadHosts('');
|
||||
selectedGroupHosts.value = data.map(s => hosts.find(h => h.id === s) as HostQueryResponse)
|
||||
.filter(Boolean);
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
// 点击分组
|
||||
const clickGroup = (groups: Array<number>) => {
|
||||
@@ -117,12 +168,18 @@
|
||||
|
||||
<style lang="less" scoped>
|
||||
.group-main-tree {
|
||||
width: calc(60% - 16px);
|
||||
width: calc(100% - 496px);
|
||||
height: 100%;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.group-main-hosts {
|
||||
width: 40%;
|
||||
width: 480px;
|
||||
height: 100%;
|
||||
|
||||
.os-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
import type { AssetAuthorizedDataQueryRequest, AssetDataGrantRequest } from '@/api/asset/asset-data-grant';
|
||||
import type { HostIdentityQueryResponse } from '@/api/asset/host-identity';
|
||||
import type { HostKeyQueryResponse } from '@/api/asset/host-key';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, onActivated } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { useRowSelection } from '@/hooks/table';
|
||||
import { getAuthorizedHostIdentity, grantHostIdentity } from '@/api/asset/asset-data-grant';
|
||||
@@ -125,23 +125,23 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化身份数据
|
||||
onMounted(async () => {
|
||||
// 初始化主机身份
|
||||
const initIdentities = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 加载主机身份
|
||||
hostIdentities.value = await cacheStore.loadHostIdentities();
|
||||
// 加载主机密钥
|
||||
hostKeys.value = await cacheStore.loadHostKeys();
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化密钥数据
|
||||
onMounted(async () => {
|
||||
// 加载主机密钥
|
||||
hostKeys.value = await cacheStore.loadHostKeys();
|
||||
});
|
||||
// 初始化身份数据
|
||||
onMounted(initIdentities);
|
||||
onActivated(initIdentities);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
import type { TableData } from '@arco-design/web-vue/es/table/interface';
|
||||
import type { AssetAuthorizedDataQueryRequest, AssetDataGrantRequest } from '@/api/asset/asset-data-grant';
|
||||
import type { HostKeyQueryResponse } from '@/api/asset/host-key';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, onActivated } from 'vue';
|
||||
import { getAuthorizedHostKey, grantHostKey } from '@/api/asset/asset-data-grant';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { useRowSelection } from '@/hooks/table';
|
||||
@@ -101,8 +101,8 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化数据
|
||||
onMounted(async () => {
|
||||
// 初始化主机密钥
|
||||
const initKeys = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
hostKeys.value = await cacheStore.loadHostKeys();
|
||||
@@ -110,7 +110,11 @@
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化主机密钥
|
||||
onMounted(initKeys);
|
||||
onActivated(initKeys);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
<template>
|
||||
<a-list size="small"
|
||||
max-height="100%"
|
||||
:hoverable="true"
|
||||
:data="selectedGroupHosts"
|
||||
:loading="loading">
|
||||
<!-- 表头 -->
|
||||
<template #header>
|
||||
<span class="hosts-header-title">组内数据</span>
|
||||
<span class="span-blue">{{ group?.title }}</span>
|
||||
</template>
|
||||
<!-- 空数据 -->
|
||||
<template #empty>
|
||||
<span class="host-list-empty">当前分组未配置主机</span>
|
||||
</template>
|
||||
<!-- 数据 -->
|
||||
<template #item="{ item }">
|
||||
<a-tooltip :content="`${item.name} - ${item.address}`">
|
||||
<a-list-item>
|
||||
<icon-desktop class="host-list-icon" />
|
||||
<span>{{ `${item.name} - ` }}</span>
|
||||
<span class="span-blue">{{ item.address }}</span>
|
||||
</a-list-item>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'hostList'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TreeNodeData } from '@arco-design/web-vue';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { useCacheStore } from '@/store';
|
||||
import { ref, watch } from 'vue';
|
||||
import { getHostGroupRelList } from '@/api/asset/host-group';
|
||||
|
||||
const props = defineProps<Partial<{
|
||||
group: TreeNodeData;
|
||||
}>>();
|
||||
|
||||
const cacheStore = useCacheStore();
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
const selectedGroupHosts = ref<Array<HostQueryResponse>>([]);
|
||||
|
||||
// 监听分组变化 加载组内数据
|
||||
watch(() => props.group?.key, async (groupId) => {
|
||||
if (!groupId) {
|
||||
return;
|
||||
}
|
||||
// 加载组内数据
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data } = await getHostGroupRelList(groupId as number);
|
||||
const hosts = await cacheStore.loadHosts(undefined);
|
||||
selectedGroupHosts.value = data.map(s => hosts.find(h => h.id === s) as HostQueryResponse)
|
||||
.filter(Boolean);
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.hosts-header-title {
|
||||
&:after {
|
||||
content: '-';
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.host-list-empty {
|
||||
padding: 16px 24px;
|
||||
text-align: center;
|
||||
color: var(--color-text-2);
|
||||
display: block;
|
||||
}
|
||||
|
||||
:deep(.arco-scrollbar) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.arco-list-item-content) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-text-1);
|
||||
overflow: hidden;
|
||||
word-break: keep-all;
|
||||
white-space: pre;
|
||||
|
||||
.host-list-icon {
|
||||
font-size: 24px;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-white);
|
||||
background: rgb(var(--blue-6));
|
||||
margin-right: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -74,8 +74,14 @@ export const GrantTabs = [
|
||||
},
|
||||
];
|
||||
|
||||
// 主机类型 字典项
|
||||
export const hostTypeKey = 'hostType';
|
||||
|
||||
// 主机系统类型 字典项
|
||||
export const hostOsTypeKey = 'hostOsType';
|
||||
|
||||
// 身份类型 字典项
|
||||
export const identityTypeKey = 'hostIdentityType';
|
||||
|
||||
// 加载的字典值
|
||||
export const dictKeys = [identityTypeKey];
|
||||
export const dictKeys = [hostTypeKey, hostOsTypeKey, identityTypeKey];
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
|
||||
import { dateFormat } from '@/utils';
|
||||
|
||||
// 主机列
|
||||
export const hostColumns = [
|
||||
{
|
||||
title: 'id',
|
||||
dataIndex: 'id',
|
||||
slotName: 'id',
|
||||
width: 68,
|
||||
align: 'left',
|
||||
fixed: 'left',
|
||||
}, {
|
||||
title: '主机类型',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
}, {
|
||||
title: '主机名称',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
}, {
|
||||
title: '主机地址',
|
||||
dataIndex: 'address',
|
||||
slotName: 'address',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
] as TableColumnData[];
|
||||
|
||||
// 主机密钥列
|
||||
export const hostKeyColumns = [
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { TransferItem } from '@arco-design/web-vue/es/transfer/interface';
|
||||
import type { TreeNodeData } from '@arco-design/web-vue';
|
||||
import { onMounted, ref, watch, computed } from 'vue';
|
||||
import { onMounted, ref, watch, computed, onActivated } from 'vue';
|
||||
import { useCacheStore } from '@/store';
|
||||
import { getHostGroupRelList } from '@/api/asset/host-group';
|
||||
|
||||
@@ -111,8 +111,9 @@
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
cacheStore.loadHosts(undefined).then(hosts => {
|
||||
// 加载主机列表
|
||||
const loadHosts = () => {
|
||||
cacheStore.loadHosts('').then(hosts => {
|
||||
data.value = hosts.map(s => {
|
||||
return {
|
||||
value: String(s.id),
|
||||
@@ -121,7 +122,10 @@
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(loadHosts);
|
||||
onActivated(loadHosts);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@
|
||||
// 重新加载数据
|
||||
fetchCardData();
|
||||
// 清空缓存
|
||||
cacheStore.reset('host_SSH');
|
||||
cacheStore.reset('host_', 'host_SSH');
|
||||
};
|
||||
|
||||
defineExpose({ reload });
|
||||
|
||||
@@ -364,13 +364,11 @@
|
||||
// 重新加载数据
|
||||
fetchTableData();
|
||||
// 清空缓存
|
||||
cacheStore.reset('host_SSH');
|
||||
cacheStore.reset('host_', 'host_SSH');
|
||||
};
|
||||
|
||||
defineExpose({ reload });
|
||||
|
||||
defineExpose({ reload });
|
||||
|
||||
// 加载数据
|
||||
const doFetchTableData = async (request: HostQueryRequest) => {
|
||||
try {
|
||||
|
||||
@@ -30,7 +30,6 @@ export const HostOsType = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// 主机类型 字典项
|
||||
export const hostTypeKey = 'hostType';
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- 表格 -->
|
||||
<a-table row-key="id"
|
||||
:loading="loading"
|
||||
:columns="batchExecColumns"
|
||||
@@ -18,6 +19,10 @@
|
||||
:pagination="false"
|
||||
:bordered="false"
|
||||
:scroll="{ y: 258 }">
|
||||
<!-- 空状态 -->
|
||||
<template #empty>
|
||||
<a-empty style="margin-top: 42px;" description="暂无批量执行记录" />
|
||||
</template>
|
||||
<!-- 执行状态 -->
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getDictValue(execHostStatusKey, record.status, 'color')">
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- 表格 -->
|
||||
<a-table row-key="id"
|
||||
:loading="loading"
|
||||
:columns="terminalLogColumns"
|
||||
@@ -18,6 +19,10 @@
|
||||
:pagination="false"
|
||||
:bordered="false"
|
||||
:scroll="{ y: 258 }">
|
||||
<!-- 空状态 -->
|
||||
<template #empty>
|
||||
<a-empty style="margin-top: 42px;" description="暂无连接记录" />
|
||||
</template>
|
||||
<!-- 连接主机 -->
|
||||
<template #hostName="{ record }">
|
||||
<span class="table-cell-value" :title="record.hostName">
|
||||
|
||||
Reference in New Issue
Block a user