refactor: 修改缓存加载逻辑.

This commit is contained in:
lijiahang
2023-12-04 14:35:18 +08:00
parent a22f30a8b4
commit f4b5ba168a
39 changed files with 278 additions and 332 deletions

View File

@@ -30,7 +30,7 @@ public @interface OperatorLog {
* - {@link org.springframework.web.bind.annotation.PathVariable}
* <p>
* 使用 @IgnoreParameter 可以忽略参数记录 {@link IgnoreParameter}
* 如果只需要忽略某个字段可以使用 @Desensitize(toEmpty = true) 标注
* 如果只需要忽略某个字段可以使用 {@code @Desensitize(toEmpty = true)} 标注
*/
boolean parameter() default true;

View File

@@ -45,23 +45,16 @@ public class CodeGenerators {
// .color("blue", "gray", "red", "green", "white")
// .valueUseFields()
// .build(),
Template.create("code_snippet", "代码片段", "snippet")
Template.create("command_template", "命令模板", "command")
.disableUnitTest()
.cache("code:snippet:{}", "代码片段")
.cache("command:template:list", "命令模板列表")
.expire(1, TimeUnit.DAYS)
.vue("asset", "snippet")
.enableCardView()
.enableDrawerForm()
.dict("codeSnippetType", "type")
.comment("代码片段类型")
.fields("COMMAND", "TEMPLATE")
.labels("命令", "模板")
.extra("icon", "icon-code-block", "icon-code")
.valueUseFields()
.dict("codeSnippetRender", "prepare_render")
.dict("commandTemplateRender", "prepare_render")
.comment("是否使用脚本渲染")
.fields("UN_RENDER", "RENDER")
.labels("渲染", "渲染")
.fields("UNUSED", "USED")
.labels("使用", "使用")
.values(0, 1)
.build(),
};

View File

@@ -1,5 +1,5 @@
### 查询当前用户已授权的主机分组及主机
GET {{baseUrl}}/asset/authorized-data/current-host-group
### 查询当前用户已授权的主机
GET {{baseUrl}}/asset/authorized-data/current-host
Authorization: {{token}}
### 查询当前用户已授权的主机秘钥

View File

@@ -4,7 +4,7 @@ import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.asset.entity.vo.AuthorizedHostGroupWrapperVO;
import com.orion.ops.module.asset.entity.vo.AuthorizedHostWrapperVO;
import com.orion.ops.module.asset.entity.vo.HostIdentityVO;
import com.orion.ops.module.asset.entity.vo.HostKeyVO;
import com.orion.ops.module.asset.service.AssetAuthorizedDataService;
@@ -38,9 +38,9 @@ public class AssetAuthorizedDataServiceController {
private AssetAuthorizedDataService assetAuthorizedDataService;
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/current-host-group")
@Operation(summary = "查询当前用户已授权的主机分组及主机")
public AuthorizedHostGroupWrapperVO getCurrentAuthorizedHostGroup() {
@GetMapping("/current-host")
@Operation(summary = "查询当前用户已授权的主机")
public AuthorizedHostWrapperVO getCurrentAuthorizedHostGroup() {
return assetAuthorizedDataService.getUserAuthorizedHostGroup(SecurityUtils.getLoginUserId());
}

View File

@@ -7,6 +7,8 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 已授权的主机分组 视图响应对象
@@ -20,7 +22,7 @@ import java.util.List;
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "AuthorizedHostGroupWrapperVO", description = "已授权的主机分组 视图响应对象")
public class AuthorizedHostGroupWrapperVO {
public class AuthorizedHostWrapperVO {
@Schema(description = "授权的主机分组")
private List<HostGroupTreeVO> groupTree;
@@ -28,4 +30,7 @@ public class AuthorizedHostGroupWrapperVO {
@Schema(description = "授权的主机列表")
private List<HostVO> hostList;
@Schema(description = "分组树节点映射 'groupId':hostIdList")
private Map<String, Set<Long>> treeNodes;
}

View File

@@ -44,7 +44,4 @@ public class HostGroupTreeVO implements TreeNode<HostGroupTreeVO>, Serializable
@Schema(description = "子节点")
private List<HostGroupTreeVO> children;
@Schema(description = "分组内主机")
private List<HostVO> hostList;
}

View File

@@ -1,7 +1,7 @@
package com.orion.ops.module.asset.service;
import com.orion.ops.module.asset.entity.request.asset.AssetAuthorizedDataQueryRequest;
import com.orion.ops.module.asset.entity.vo.AuthorizedHostGroupWrapperVO;
import com.orion.ops.module.asset.entity.vo.AuthorizedHostWrapperVO;
import com.orion.ops.module.asset.entity.vo.HostIdentityVO;
import com.orion.ops.module.asset.entity.vo.HostKeyVO;
import com.orion.ops.module.infra.enums.DataPermissionTypeEnum;
@@ -27,12 +27,12 @@ public interface AssetAuthorizedDataService {
List<Long> getAuthorizedDataRelId(DataPermissionTypeEnum type, AssetAuthorizedDataQueryRequest request);
/**
* 查询用户已授权的主机分组和主机
* 查询用户已授权的主机主机
*
* @param userId userId
* @return group
*/
AuthorizedHostGroupWrapperVO getUserAuthorizedHostGroup(Long userId);
AuthorizedHostWrapperVO getUserAuthorizedHostGroup(Long userId);
/**
* 查询用户已授权的主机秘钥

View File

@@ -74,7 +74,7 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
}
@Override
public AuthorizedHostGroupWrapperVO getUserAuthorizedHostGroup(Long userId) {
public AuthorizedHostWrapperVO getUserAuthorizedHostGroup(Long userId) {
if (systemUserApi.isAdminUser(userId)) {
// 管理员查询所有
return this.buildUserAuthorizedHostGroup(null);
@@ -83,7 +83,7 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
List<Long> authorizedIdList = dataPermissionApi.getUserAuthorizedRelIdList(DataPermissionTypeEnum.HOST_GROUP, userId);
if (authorizedIdList.isEmpty()) {
// 无数据
return AuthorizedHostGroupWrapperVO.builder()
return AuthorizedHostWrapperVO.builder()
.groupTree(Lists.empty())
.hostList(Lists.empty())
.build();
@@ -142,60 +142,103 @@ public class AssetAuthorizedDataServiceImpl implements AssetAuthorizedDataServic
* @param authorizedGroupIdList authorizedGroupIdList
* @return tree
*/
private AuthorizedHostGroupWrapperVO buildUserAuthorizedHostGroup(List<Long> authorizedGroupIdList) {
private AuthorizedHostWrapperVO buildUserAuthorizedHostGroup(List<Long> authorizedGroupIdList) {
final boolean allData = Lists.isEmpty(authorizedGroupIdList);
AuthorizedHostGroupWrapperVO wrapper = new AuthorizedHostGroupWrapperVO();
// 查询主机列表
List<HostVO> hosts = hostService.getHostListByCache();
Map<Long, HostVO> hostMap = hosts.stream()
.collect(Collectors.toMap(HostVO::getId, Function.identity(), Functions.right()));
// 查询分组引用
Map<Long, Set<Long>> groupRel = dataGroupRelApi.getGroupRelList(DataGroupTypeEnum.HOST);
AuthorizedHostWrapperVO wrapper = new AuthorizedHostWrapperVO();
// TODO async get 最近连接
// TODO async get 我的收藏
// 查询分组
List<DataGroupDTO> dataGroup = dataGroupApi.getDataGroupList(DataGroupTypeEnum.HOST);
// 过滤分组
// 查询分组引用
Map<Long, Set<Long>> dataGroupRel = dataGroupRelApi.getGroupRelList(DataGroupTypeEnum.HOST);
// 过滤已经授权的分组
if (!allData) {
// 构建已授权的分组
List<DataGroupDTO> relNodes = new ArrayList<>();
TreeUtils.getAllNodes(dataGroup, authorizedGroupIdList, relNodes);
dataGroup = new ArrayList<>(new HashSet<>(relNodes));
}
// 设置组内数据
// 设置主机分组树
wrapper.setGroupTree(this.getAuthorizedHostGroupTree(dataGroup));
// 设置主机分组下的主机
wrapper.setTreeNodes(this.getAuthorizedHostGroupNodes(allData, dataGroup, dataGroupRel, authorizedGroupIdList));
// 设置已授权的所有主机
wrapper.setHostList(this.getAuthorizedHostList(allData, dataGroup, dataGroupRel, authorizedGroupIdList));
// TODO set 最近连接
// TODO set 我的收藏
return wrapper;
}
/**
* 构建主机分组树
*
* @param dataGroup dataGroup
* @return tree
*/
private List<HostGroupTreeVO> getAuthorizedHostGroupTree(List<DataGroupDTO> dataGroup) {
List<HostGroupTreeVO> groupList = HostGroupConvert.MAPPER.toList(dataGroup);
groupList.stream()
// 因为可能父菜单没有授权 这里需要判断组
.filter(s -> allData || authorizedGroupIdList.contains(s.getId()))
.forEach(s -> {
List<HostVO> groupHosts = Lists.stream(groupRel.get(s.getId()))
.map(hostMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
s.setHostList(groupHosts);
});
// 构建主机树
HostGroupTreeVO rootNode = HostGroupTreeVO.builder()
.id(Const.ROOT_PARENT_ID)
.sort(Const.DEFAULT_SORT)
.build();
TreeUtils.buildGroupTree(rootNode, groupList);
wrapper.setGroupTree(rootNode.getChildren());
// 设置授权的主机
return rootNode.getChildren();
}
/**
* 获取主机分组树 主机节点映射
*
* @param allData allData
* @param dataGroup dataGroup
* @param dataGroupRel dataGroupRel
* @param authorizedGroupIdList authorizedGroupIdList
* @return hostGroupId:hostIdList
*/
private Map<String, Set<Long>> getAuthorizedHostGroupNodes(boolean allData,
List<DataGroupDTO> dataGroup,
Map<Long, Set<Long>> dataGroupRel,
List<Long> authorizedGroupIdList) {
Map<String, Set<Long>> result = new HashMap<>();
dataGroup.stream()
.map(DataGroupDTO::getId)
// 因为可能父菜单没有授权 这里需要判断分组权限
.filter(id -> allData || authorizedGroupIdList.contains(id))
.forEach(s -> result.put(String.valueOf(s), dataGroupRel.get(s)));
return result;
}
/**
* 查询已授权的所有主机
*
* @param allData allData
* @param dataGroup dataGroup
* @param dataGroupRel dataGroupRel
* @param authorizedGroupIdList authorizedGroupIdList
* @return hosts
*/
private List<HostVO> getAuthorizedHostList(boolean allData,
List<DataGroupDTO> dataGroup,
Map<Long, Set<Long>> dataGroupRel,
List<Long> authorizedGroupIdList) {
// 查询主机列表
List<HostVO> hosts = hostService.getHostListByCache();
// 全部数据直接返回
if (allData) {
// 设置全部数据
wrapper.setHostList(hosts);
} else {
// 仅设置已授权的数据
List<HostVO> groupHosts = groupList.stream()
.filter(s -> authorizedGroupIdList.contains(s.getId()))
.map(s -> groupRel.get(s.getId()))
.filter(Lists::isNoneEmpty)
.flatMap(Collection::stream)
.map(hostMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
wrapper.setHostList(groupHosts);
return hosts;
}
return wrapper;
Map<Long, HostVO> hostMap = hosts.stream()
.collect(Collectors.toMap(HostVO::getId, Function.identity(), Functions.right()));
// 仅设置已授权的数据
return dataGroup.stream()
.map(DataGroupDTO::getId)
.filter(authorizedGroupIdList::contains)
.map(dataGroupRel::get)
.filter(Lists::isNoneEmpty)
.flatMap(Collection::stream)
.map(hostMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}

View File

@@ -5,18 +5,19 @@ import type { HostIdentityQueryResponse } from './host-identity';
import axios from 'axios';
/**
* 已授权的主机分组 查询响应
* 已授权的主机 查询响应
*/
export interface AuthorizedHostGroupQueryResponse {
export interface AuthorizedHostQueryResponse {
groupTree: Array<HostGroupQueryResponse>;
hostList: Array<HostQueryResponse>;
treeNodes: Record<string, Array<number>>;
}
/**
* 查询当前用户已授权的主机分组
* 查询当前用户已授权的主机
*/
export function getCurrentAuthorizedHostGroup() {
return axios.get<AuthorizedHostGroupQueryResponse>('/asset/authorized-data/current-host-group');
export function getCurrentAuthorizedHost() {
return axios.get<AuthorizedHostQueryResponse>('/asset/authorized-data/current-host');
}
/**

View File

@@ -1,4 +1,3 @@
import type { HostQueryResponse } from './host';
import axios from 'axios';
/**
@@ -34,7 +33,6 @@ export interface HostGroupQueryResponse {
parentId: number;
title: string;
children: Array<HostGroupQueryResponse>;
hostList: Array<HostQueryResponse>;
}
/**

View File

@@ -1,6 +1,6 @@
import axios from 'axios';
export type TagType = 'HOST'
export type TagType = 'HOST' | string
/**
* tag 创建对象
@@ -29,5 +29,5 @@ export function createTag(request: TagCreateRequest) {
* 查询标签
*/
export function getTagList(type: TagType) {
return axios.get<TagQueryResponse>('/infra/tag/list', { params: { type } });
return axios.get<Array<TagQueryResponse>>('/infra/tag/list', { params: { type } });
}

View File

@@ -44,15 +44,10 @@
const treeData = ref<Array<TreeNodeData>>([]);
// 初始化选项
onBeforeMount(async () => {
if (cacheStore.hostGroups.length) {
treeData.value = cacheStore.hostGroups;
} else {
// 加载数据
const { data } = await getHostGroupTree();
treeData.value = data;
cacheStore.set('hostGroups', data);
}
onBeforeMount(() => {
cacheStore.loadHostGroups().then(s => {
treeData.value = s;
});
});
</script>

View File

@@ -5,7 +5,7 @@
ref="tree"
class="tree-container"
:blockNode="true"
:draggable="draggable"
:draggable="editable"
:data="treeData"
:checkable="checkable"
v-model:checked-keys="checkedKeys"
@@ -20,7 +20,6 @@
v-model="currName"
style="width: 138px;"
placeholder="名称"
autofocus
:max-length="32"
:disabled="node.loading"
@blur="() => saveNode(node.key)"
@@ -77,7 +76,7 @@
<!-- 无数据 -->
<div v-else-if="!loading" class="empty-container">
<span>暂无数据</span>
<span>点击上方 '<icon-plus />' 添加一个分组吧~</span>
<span v-if="editable">点击上方 '<icon-plus />' 添加一个分组吧~</span>
</div>
</a-scrollbar>
</template>
@@ -99,7 +98,7 @@
const props = defineProps({
loading: Boolean,
draggable: {
editable: {
type: Boolean,
default: true
},
@@ -313,20 +312,13 @@
// 加载数据
const fetchTreeData = async (force = false) => {
if (cacheStore.hostGroups.length && !force) {
// 缓存有数据并且非强制加载 直接从缓存中加载
treeData.value = cacheStore.hostGroups;
} else {
// 无数据/强制加载
try {
emits('loading', true);
const { data } = await getHostGroupTree();
treeData.value = data;
cacheStore.hostGroups = data;
} catch (e) {
} finally {
emits('loading', false);
}
try {
const groups = await cacheStore.loadHostGroups(force);
emits('loading', true);
treeData.value = groups;
} catch (e) {
} finally {
emits('loading', false);
}
// 未选择则选择首个
if (!tree.value?.getSelectedNodes()?.length && treeData.value.length) {

View File

@@ -40,11 +40,13 @@
// 初始化选项
onBeforeMount(() => {
optionData.value = cacheStore.hostIdentities.map(s => {
return {
label: `${s.name} (${s.username})`,
value: s.id,
};
cacheStore.loadHostIdentities().then(hostIdentities => {
optionData.value = hostIdentities.map(s => {
return {
label: `${s.name} (${s.username})`,
value: s.id,
};
});
});
});

View File

@@ -40,11 +40,13 @@
// 初始化选项
onBeforeMount(() => {
optionData.value = cacheStore.hostKeys.map(s => {
return {
label: s.name,
value: s.id,
};
cacheStore.loadHostKeys().then(hostKeys => {
optionData.value = hostKeys.map(s => {
return {
label: s.name,
value: s.id,
};
});
});
});
</script>

View File

@@ -2,6 +2,7 @@
<a-select v-model:model-value="value"
:placeholder="placeholder"
:options="optionData"
:loading="loading"
:limit="limit as number"
:allow-create="allowCreate"
@exceed-limit="() => { emits('onLimited', limit, `最多选择${limit}个tag`) }"
@@ -24,6 +25,7 @@
import { useCacheStore } from '@/store';
import { Message } from '@arco-design/web-vue';
import { createTag } from '@/api/meta/tag';
import useLoading from '@/hooks/loading';
const props = defineProps({
modelValue: Array as PropType<Array<number>>,
@@ -31,11 +33,11 @@
limit: Number,
type: String,
allowCreate: Boolean,
tagType: String,
});
const emits = defineEmits(['update:modelValue', 'onLimited']);
const { loading, setLoading } = useLoading();
const cacheStore = useCacheStore();
const value = computed<Array<number>>({
@@ -51,12 +53,13 @@
// 初始化选项
onBeforeMount(() => {
const tagCache = cacheStore[props.tagType as string] as Array<any>;
optionData.value = tagCache.map(s => {
return {
label: s.name,
value: s.id,
};
cacheStore.loadTags(props.type as string).then((tags) => {
optionData.value = tags.map(s => {
return {
label: s.name,
value: s.id,
};
});
});
});
@@ -78,12 +81,16 @@
tags.splice(i, 1);
return;
}
// 不存在则创建 tag
setLoading(true);
try {
// 创建 tag
tags[i] = await doCreateTag(tag);
} catch (e) {
// 失败删除
tags.splice(i, 1);
} finally {
setLoading(false);
}
}
};
@@ -95,8 +102,8 @@
type: props.type
} as unknown as TagCreateRequest);
// 插入缓存
const tagCache = cacheStore[props.tagType as string] as Array<any>;
tagCache.push({ id, name });
const tagCache = await cacheStore.loadTags(props.type as string);
tagCache && tagCache.push({ id, name });
// 插入 options
optionData.value.push({
label: name,

View File

@@ -41,18 +41,18 @@
},
set(e) {
if (typeof e === 'string') {
// 创建的值
emits('update:modelValue', undefined);
// 创建的值 这里必须为 null, 否则 table 的重置不生效
emits('update:modelValue', null);
emits('change', {
id: undefined,
id: null,
keyName: e
});
} else {
// 已有的值
emits('update:modelValue', e);
const find = cacheStore.dictKeys.find(s => s.id === e);
const find = optionData.value.find(s => s.value === e);
if (find) {
emits('change', find);
emits('change', find.origin);
}
}
}
@@ -61,11 +61,14 @@
// 初始化选项
onBeforeMount(() => {
optionData.value = cacheStore.dictKeys.map(s => {
return {
label: `${s.keyName} - ${s.description || ''}`,
value: s.id,
};
cacheStore.loadDictKeys().then(dictKeys => {
optionData.value = dictKeys.map(s => {
return {
label: `${s.keyName} - ${s.description || ''}`,
value: s.id,
origin: s
};
});
});
});

View File

@@ -67,7 +67,9 @@
unTriggerChange.value = true;
checkedKeys.value = keys;
if (!menuData.value.length) {
menuData.value = [...cacheStore.menus];
cacheStore.loadMenus().then(menus => {
menuData.value = [...menus];
});
}
};

View File

@@ -1,9 +1,9 @@
<template>
<a-tree-select v-model:model-value="value"
:data="treeData()"
:data="treeData"
:disabled="disabled"
:allow-search="true"
:filter-tree-node="filterTreeNode"
:filter-tree-node="titleFilter"
placeholder="请选择菜单" />
</template>
@@ -16,8 +16,9 @@
<script lang="ts" setup>
import type { TreeNodeData } from '@arco-design/web-vue';
import { useCacheStore } from '@/store';
import { computed } from 'vue';
import { MenuType } from '../types/const';
import { computed, onBeforeMount, ref } from 'vue';
import { MenuType } from '@/views/system/menu/types/const';
import { titleFilter } from '@/types/form';
const props = defineProps({
modelValue: Number,
@@ -26,6 +27,10 @@
const emits = defineEmits(['update:modelValue']);
const cacheStore = useCacheStore();
const treeData = ref<Array<TreeNodeData>>([]);
const value = computed({
get() {
return props.modelValue;
@@ -35,9 +40,7 @@
}
});
//
const cacheStore = useCacheStore();
const treeData = (): TreeNodeData[] => {
onBeforeMount(() => {
let render = (arr: any[]): TreeNodeData[] => {
return arr.map((s) => {
// function
@@ -57,17 +60,14 @@
return node;
}).filter(Boolean);
};
return [{
key: 0,
title: '根目录',
children: render([...cacheStore.menus])
}];
};
//
const filterTreeNode = (searchValue: string, nodeData: { title: string; }) => {
return nodeData.title.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
};
cacheStore.loadMenus().then(menus => {
treeData.value = [{
key: 0,
title: '根目录',
children: render([...menus])
}];
});
});
</script>

View File

@@ -1,24 +1,23 @@
import type { CacheState } from './types';
import type { AxiosResponse } from 'axios';
import type { TagType } from '@/api/meta/tag';
import { getTagList } from '@/api/meta/tag';
import { defineStore } from 'pinia';
import { getUserList } from '@/api/user/user';
import { getRoleList } from '@/api/user/role';
import { getDictKeyList } from '@/api/system/dict-key';
import { getHostKeyList } from '@/api/asset/host-key';
import { getHostIdentityList } from '@/api/asset/host-identity';
import { getHostList } from '@/api/asset/host';
import { getHostGroupTree } from '@/api/asset/host-group';
import { getMenuList } from '@/api/system/menu';
export type CacheType = 'users' | 'menus' | 'roles'
| 'host' | 'hostGroups' | 'hostTags' | 'hostKeys' | 'hostIdentities'
| 'host' | 'hostGroups' | 'hostKeys' | 'hostIdentities'
| 'dictKeys' | string
export default defineStore('cache', {
state: (): CacheState => ({
menus: [],
roles: [],
hosts: [],
hostGroups: [],
hostTags: [],
hostKeys: [],
hostIdentities: [],
dictKeys: [],
}),
state: (): CacheState => ({}),
getters: {},
@@ -41,9 +40,9 @@ export default defineStore('cache', {
},
// 加载数据
async load<T>(name: CacheType, loader: () => Promise<AxiosResponse<T>>, onErrorValue = []) {
async load<T>(name: CacheType, loader: () => Promise<AxiosResponse<T>>, force = false, onErrorValue = []) {
// 尝试直接从缓存中获取数据
if (this[name]) {
if (this[name] && !force) {
return this[name] as T;
}
// 加载数据
@@ -57,13 +56,48 @@ export default defineStore('cache', {
},
// 获取用户列表
async loadUsers() {
return await this.load('users', getUserList);
async loadUsers(force = false) {
return await this.load('users', getUserList, force);
},
// 获取角色列表
async loadRoles() {
return await this.load('roles', getRoleList);
async loadRoles(force = false) {
return await this.load('roles', getRoleList, force);
},
// 获取菜单列表
async loadMenus(force = false) {
return await this.load('menus', () => getMenuList({}), force);
},
// 获取主机分组列表
async loadHostGroups(force = false) {
return await this.load('hostGroups', getHostGroupTree, force);
},
// 获取主机列表
async loadHosts(force = false) {
return await this.load('hosts', getHostList, force);
},
// 获取主机秘钥列表
async loadHostKeys(force = false) {
return await this.load('hostKeys', getHostKeyList, force);
},
// 获取主机身份列表
async loadHostIdentities(force = false) {
return await this.load('hostIdentities', getHostIdentityList, force);
},
// 获取字典配置项列表
async loadDictKeys(force = false) {
return await this.load('dictKeys', getDictKeyList, force);
},
// 加载 tags
async loadTags(type: TagType, force = false) {
return await this.load(`${type}_Tags`, () => getTagList(type), force);
},
}

View File

@@ -1,7 +1,6 @@
import type { UserQueryResponse } from '@/api/user/user';
import type { MenuQueryResponse } from '@/api/system/menu';
import type { RoleQueryResponse } from '@/api/user/role';
import type { TagQueryResponse } from '@/api/meta/tag';
import type { HostQueryResponse } from '@/api/asset/host';
import type { HostGroupQueryResponse } from '@/api/asset/host-group';
import type { HostKeyQueryResponse } from '@/api/asset/host-key';
@@ -12,7 +11,6 @@ export interface CacheState {
users?: UserQueryResponse[];
menus?: MenuQueryResponse[];
roles?: RoleQueryResponse[];
hostTags?: TagQueryResponse[];
hosts?: HostQueryResponse[];
hostGroups?: HostGroupQueryResponse[];
hostKeys?: HostKeyQueryResponse[];

View File

@@ -2,3 +2,8 @@
export const labelFilter = (searchValue: string, option: { label: string; }) => {
return option.label.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
};
// 通过 title 进行过滤
export const titleFilter = (searchValue: string, option: { title: string; }) => {
return option.title.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
};

View File

@@ -31,7 +31,7 @@
<host-group-tree outer-class="group-main-tree"
:checkable="true"
:checked-keys="checkedGroups"
:draggable="false"
:editable="false"
:loading="loading"
@loading="setLoading"
@select-node="e => selectedGroup = e"

View File

@@ -30,7 +30,7 @@
<!-- 分组 -->
<host-group-tree outer-class="group-main-tree"
:checked-keys="checkedGroups"
:draggable="false"
:editable="false"
:loading="loading"
@loading="setLoading"
@select-node="e => selectedGroup = e"

View File

@@ -36,10 +36,8 @@
import type { PropType } from 'vue';
import useLoading from '@/hooks/loading';
import { useCacheStore } from '@/store';
import { onBeforeMount, ref, watch } from 'vue';
import { ref, watch } from 'vue';
import { getHostGroupRelList } from '@/api/asset/host-group';
import { getHostList } from '@/api/asset/host';
import { Message } from '@arco-design/web-vue';
const props = defineProps({
group: {
@@ -64,7 +62,8 @@
try {
setLoading(true);
const { data } = await getHostGroupRelList(groupId as number);
selectedGroupHosts.value = data.map(s => cacheStore.hosts.find(h => h.id === s) as HostQueryResponse)
const hosts = await cacheStore.loadHosts();
selectedGroupHosts.value = data.map(s => hosts.find(h => h.id === s) as HostQueryResponse)
.filter(Boolean);
} catch (e) {
} finally {
@@ -72,24 +71,6 @@
}
});
// 加载主机列表
const loadHostList = async () => {
try {
const { data } = await getHostList();
// 设置到缓存
cacheStore.set('hosts', data);
} catch (e) {
Message.error('主机列表加载失败');
}
};
onBeforeMount(async () => {
if (!cacheStore.hosts.length) {
// 加载用户列表
await loadHostList();
}
});
</script>
<style lang="less" scoped>

View File

@@ -1,5 +1,5 @@
<template>
<div class="layout-container" v-if="render">
<div class="layout-container">
<!-- 列表-表格 -->
<host-identity-table v-if="renderTable"
ref="table"
@@ -16,7 +16,7 @@
<host-identity-form-modal ref="modal"
@added="modalAddCallback"
@updated="modalUpdateCallback" />
<!-- 添加修改模态框 -->
<!-- 主机秘钥抽屉 -->
<host-key-form-drawer ref="keyDrawer" />
</div>
</template>
@@ -28,17 +28,13 @@
</script>
<script lang="ts" setup>
import { ref, computed, onUnmounted } from 'vue';
import { useAppStore, useCacheStore } from '@/store';
import HostIdentityCardList from './components/host-identity-card-list.vue';
import HostIdentityTable from './components/host-identity-table.vue';
import HostIdentityFormModal from './components/host-identity-form-modal.vue';
import HostKeyFormDrawer from '../host-key/components/host-key-form-drawer.vue';
import { getHostKeyList } from '@/api/asset/host-key';
import { ref, computed, onBeforeMount, onUnmounted } from 'vue';
import { useAppStore, useCacheStore } from '@/store';
import { Message } from '@arco-design/web-vue';
const render = ref(false);
const table = ref();
const card = ref();
const modal = ref();
@@ -66,22 +62,6 @@
}
};
// 获取主机秘钥列表
const fetchHostKeyList = async () => {
try {
const { data } = await getHostKeyList();
cacheStore.set('hostKeys', data);
} catch (e) {
Message.error('主机秘钥加载失败');
}
};
onBeforeMount(async () => {
// 加载主机秘钥
await fetchHostKeyList();
render.value = true;
});
// 卸载时清除 cache
onUnmounted(() => {
const cacheStore = useCacheStore();

View File

@@ -36,8 +36,6 @@
import { Message } from '@arco-design/web-vue';
import { getHostConfigAll } from '@/api/asset/host';
import { useCacheStore, useDictStore } from '@/store';
import { getHostKeyList } from '@/api/asset/host-key';
import { getHostIdentityList } from '@/api/asset/host-identity';
import { dictKeys as sshDictKeys } from './ssh/types/const';
import SshConfigForm from './ssh/ssh-config-form.vue';
@@ -77,32 +75,10 @@
defineExpose({ open });
// 加载主机秘钥
const fetchHostKeys = async () => {
try {
const { data } = await getHostKeyList();
cacheStore.set('hostKeys', data);
} catch (e) {
}
};
// 加载主机身份
const fetchHostIdentities = async () => {
try {
const { data } = await getHostIdentityList();
cacheStore.set('hostIdentities', data);
} catch (e) {
}
};
onBeforeMount(async () => {
// 加载字典值
const dictStore = useDictStore();
await dictStore.loadKeys([...sshDictKeys]);
// 加载主机秘钥
await fetchHostKeys();
// 加载主机身份
await fetchHostIdentities();
});
</script>

View File

@@ -52,9 +52,7 @@
import type { PropType } from 'vue';
import { onMounted, ref, watch, computed } from 'vue';
import { useCacheStore } from '@/store';
import { getHostGroupRelList, updateHostGroupRel } from '@/api/asset/host-group';
import { Message } from '@arco-design/web-vue';
import { getHostList } from '@/api/asset/host';
import { getHostGroupRelList } from '@/api/asset/host-group';
const props = defineProps({
modelValue: {
@@ -114,31 +112,15 @@
}
});
// 加载主机列表
const loadHostList = async () => {
emits('loading', true);
try {
const { data } = await getHostList();
// 设置到缓存
cacheStore.set('hosts', data);
} catch (e) {
Message.error('主机列表加载失败');
} finally {
emits('loading', false);
}
};
onMounted(async () => {
if (!cacheStore.hosts.length) {
// 加载主机列表
await loadHostList();
}
data.value = cacheStore.hosts.map(s => {
return {
value: String(s.id),
label: `${s.name}(${s.code})-${s.address}`,
disabled: false
};
onMounted(() => {
cacheStore.loadHosts().then(hosts => {
data.value = hosts.map(s => {
return {
value: String(s.id),
label: `${s.name}(${s.code})-${s.address}`,
disabled: false
};
});
});
});

View File

@@ -78,7 +78,6 @@
:allowCreate="false"
:limit="0"
type="HOST"
tag-type="hostTags"
placeholder="请选择主机标签" />
</a-form-item>
</a-form>

View File

@@ -43,7 +43,6 @@
:allowCreate="true"
:limit="5"
type="HOST"
tag-type="hostTags"
placeholder="请选择主机标签"
@onLimited="onLimitedTag" />
</a-form-item>

View File

@@ -32,7 +32,6 @@
:allowCreate="false"
:limit="0"
type="HOST"
tag-type="hostTags"
placeholder="请选择主机标签" />
</a-form-item>
</a-query-header>

View File

@@ -1,5 +1,5 @@
<template>
<div class="layout-container" v-if="render">
<div class="layout-container">
<!-- 列表-表格 -->
<host-table v-if="renderTable"
ref="table"
@@ -32,10 +32,8 @@
</script>
<script lang="ts" setup>
import { computed, ref, onBeforeMount, onUnmounted } from 'vue';
import { computed, ref, onUnmounted } from 'vue';
import { useAppStore, useCacheStore } from '@/store';
import { getTagList } from '@/api/meta/tag';
import { Message } from '@arco-design/web-vue';
import HostTable from './components/host-table.vue';
import HostCardList from './components/host-card-list.vue';
import HostFormModal from './components/host-form-modal.vue';
@@ -72,26 +70,9 @@
}
};
// 加载 tags
const loadTags = async () => {
try {
const { data } = await getTagList('HOST');
// 设置到缓存
cacheStore.set('hostTags', data);
} catch (e) {
Message.error('tag加载失败');
}
};
onBeforeMount(async () => {
// 加载 tags
await loadTags();
render.value = true;
});
// 卸载时清除 cache
onUnmounted(() => {
cacheStore.reset('hosts', 'hostTags', 'hostKeys', 'hostIdentities', 'hostGroups');
cacheStore.reset('hosts', 'hostKeys', 'hostIdentities', 'hostGroups', 'HOST_Tags');
});
</script>

View File

@@ -139,10 +139,11 @@
};
// 渲染表单
const renderForm = (record: any) => {
const renderForm = async (record: any) => {
formModel.value = Object.assign({}, record);
// schema
const find = record.keyId && cacheStore.dictKeys.find((item) => item.id === record.keyId);
const dictKeys = await cacheStore.loadDictKeys();
const find = record.keyId && dictKeys.find((item) => item.id === record.keyId);
keyExtraSchemas.value = (find && find.extraSchema && JSON.parse(find.extraSchema)) || [];
// 额外参数
extraValue.value = (formModel.value.extra && JSON.parse(formModel.value.extra)) || {};

View File

@@ -10,6 +10,7 @@
<a-form-item field="keyId" label="配置项" label-col-flex="50px">
<dict-key-selector v-model="formModel.keyId"
@change="changeKey"
allow-create
allow-clear />
</a-form-item>
<!-- 配置值 -->

View File

@@ -1,5 +1,5 @@
<template>
<div class="layout-container" v-if="render">
<div class="layout-container">
<!-- 列表-表格 -->
<dict-value-table ref="table"
@openAdd="() => modal.openAdd()"
@@ -27,14 +27,11 @@
import DictValueTable from './components/dict-value-table.vue';
import DictValueFormModal from './components/dict-value-form-modal.vue';
import HistoryValueModal from '@/components/meta/history/history-value-modal.vue';
import { ref, onBeforeMount, onUnmounted } from 'vue';
import { ref, onUnmounted } from 'vue';
import { historyType } from './types/const';
import { useCacheStore } from '@/store';
import { getDictKeyList } from '@/api/system/dict-key';
import { Message } from '@arco-design/web-vue';
import { rollbackDictValue } from '@/api/system/dict-value';
const render = ref(false);
const table = ref();
const modal = ref();
const history = ref();
@@ -55,23 +52,6 @@
await rollbackDictValue({ id, valueId });
};
// 加载字典配置项
const loadDictKeys = async () => {
try {
const { data } = await getDictKeyList();
// 设置到缓存
cacheStore.set('dictKeys', data);
} catch (e) {
Message.error('配置项加载失败');
}
};
onBeforeMount(async () => {
// 加载字典值
await loadDictKeys();
render.value = true;
});
// 卸载时清除 cache
onUnmounted(() => {
cacheStore.reset('dictKeys');

View File

@@ -125,12 +125,11 @@
import formRules from '../types/form.rules';
import { menuCacheKey, sortStep } from '../types/const';
import { menuVisibleKey, menuTypeKey, MenuType, MenuVisible, MenuCache } from '../types/const';
import IconPicker from '@sanqi377/arco-vue-icon-picker';
import MenuTreeSelector from './menu-tree-selector.vue';
import { createMenu, updateMenu } from '@/api/system/menu';
import { Message } from '@arco-design/web-vue';
import { useDictStore } from '@/store';
import IconPicker from '@sanqi377/arco-vue-icon-picker';
import MenuTreeSelector from '@/components/system/menu/selector/menu-tree-selector.vue';
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();

View File

@@ -263,7 +263,6 @@
setFetchLoading(true);
const { data } = await getMenuList(formModel);
tableRenderData.value = data as MenuQueryResponse[];
cacheStore.set('menus', tableRenderData.value);
} catch (e) {
} finally {
setFetchLoading(false);

View File

@@ -57,10 +57,8 @@
import useVisible from '@/hooks/visible';
import { getRoleMenuId, grantRoleMenu } from '@/api/user/role';
import { Message } from '@arco-design/web-vue';
import { useCacheStore } from '@/store';
import { getMenuList } from '@/api/system/menu';
import MenuGrantTable from '@/components/system/menu/grant/menu-grant-table.vue';
import { quickGrantMenuOperator } from '../types/const';
import MenuGrantTable from '@/components/system/menu/grant/menu-grant-table.vue';
const { visible, setVisible } = useVisible();
const { loading, setLoading } = useLoading();
@@ -74,13 +72,6 @@
setVisible(true);
try {
setLoading(true);
// 获取菜单列表
const cacheStore = useCacheStore();
if (!cacheStore.menus?.length) {
// 加载菜单
const { data: menuData } = await getMenuList({});
cacheStore.set('menus', menuData);
}
// 获取角色菜单
const { data: roleMenuIdList } = await getRoleMenuId(record.id);
table.value.init(roleMenuIdList);

View File

@@ -18,10 +18,11 @@ const columns = [
dataIndex: 'code',
slotName: 'code',
}, {
title: '角色状态',
title: '状态',
dataIndex: 'status',
slotName: 'status',
align: 'center',
width: 84,
}, {
title: '创建时间',
dataIndex: 'createTime',