review code.

This commit is contained in:
lijiahang
2023-12-01 17:29:42 +08:00
parent 351669a4f3
commit e8d99cb263
21 changed files with 235 additions and 132 deletions

View File

@@ -21,7 +21,7 @@ export interface AssetAuthorizedDataQueryRequest {
* 主机分组授权
*/
export function grantHostGroup(request: AssetDataGrantRequest) {
return axios.put('/asset/host-group/grant-host-group', request);
return axios.put('/asset/data-grant/grant-host-group', request);
}
/**

View File

@@ -4,12 +4,10 @@
<div class="tab-bar-box">
<div class="tab-bar-scroll">
<div class="tags-wrap">
<TabItem
v-for="(tag, index) in tagList"
:key="tag.fullPath"
:index="index"
:item-data="tag"
/>
<TabItem v-for="(tag, index) in tagList"
:key="tag.fullPath"
:index="index"
:item-data="tag" />
</div>
</div>
<div class="tag-bar-operation"></div>
@@ -48,7 +46,7 @@
listenerRouteChange((route: RouteLocationNormalized) => {
if (
!route.meta.noAffix &&
!tagList.value.some((tag) => tag.fullPath === route.fullPath)
!tagList.value.some((tag) => tag.path === route.path)
) {
// 固定并且没有此 tab 则添加
tabBarStore.addTab(routerToTag(route), route.meta?.ignoreCache as unknown as boolean);

View File

@@ -1,18 +1,15 @@
<template>
<a-dropdown
trigger="contextMenu"
:popup-max-height="false"
@select="actionSelect">
<span
class="arco-tag arco-tag-size-medium arco-tag-checked"
:class="{ 'link-activated': itemData?.fullPath === $route.fullPath }"
@click="goto(itemData as TagProps)">
<a-dropdown trigger="contextMenu"
:popup-max-height="false"
@select="actionSelect">
<span class="arco-tag arco-tag-size-medium arco-tag-checked"
:class="{ 'link-activated': itemData?.path === $route.path }"
@click="goto(itemData as TagProps)">
<span class="tag-link">
{{ itemData.title }}
</span>
<span
class="arco-icon-hover arco-tag-icon-hover arco-icon-hover-size-medium arco-tag-close-btn"
@click.stop="tagClose(itemData as TagProps, index)">
<span class="arco-icon-hover arco-tag-icon-hover arco-icon-hover-size-medium arco-tag-close-btn"
@click.stop="tagClose(itemData as TagProps, index)">
<icon-close />
</span>
</span>
@@ -91,7 +88,7 @@
});
const disabledReload = computed(() => {
return props.itemData.fullPath !== route.fullPath;
return props.itemData.path !== route.path;
});
const disabledCurrent = computed(() => {
@@ -106,22 +103,22 @@
return props.index === tagList.value.length - 1;
});
/**
* 关闭 tag
*/
// 关闭 tag
const tagClose = (tag: TagProps, idx: number) => {
tabBarStore.deleteTab(idx, tag);
if (props.itemData.fullPath === route.fullPath) {
if (props.itemData.path === route.path) {
// 获取队列的前一个 tab
const latest = tagList.value[idx - 1];
router.push({ name: latest.name });
}
};
// 获取当前路由索引
const findCurrentRouteIndex = () => {
return tagList.value.findIndex((el) => el.fullPath === route.fullPath);
return tagList.value.findIndex((el) => el.path === route.path);
};
// 选择操作
const actionSelect = async (value: any) => {
const { itemData, index } = props;
const copyTagList = [...tagList.value];

View File

@@ -2,11 +2,9 @@
<router-view v-slot="{ Component, route }">
<transition name="fade" mode="out-in" appear>
<!-- 渲染组件 -->
<component
:is="Component"
v-if="route.meta.ignoreCache"
:key="route.fullPath"
/>
<component :is="Component"
v-if="route.meta.ignoreCache"
:key="route.fullPath" />
<!-- 渲染缓存组件 -->
<keep-alive v-else :include="cacheList">
<component :is="Component" :key="route.fullPath" />

View File

@@ -35,6 +35,7 @@ export const STATUS_ROUTER_LIST = [
export const DEFAULT_TAB = {
title: '工作台',
name: DEFAULT_ROUTE_NAME,
path: DEFAULT_ROUTE_FULL_PATH,
fullPath: DEFAULT_ROUTE_FULL_PATH,
};
@@ -42,10 +43,11 @@ export const DEFAULT_TAB = {
* router 转 tag
*/
export const routerToTag = (route: RouteLocationNormalized): TagProps => {
const { name, meta, fullPath, query } = route;
const { name, meta, path, fullPath, query } = route;
return {
title: meta.locale || '',
name: String(name),
path,
fullPath,
query,
ignoreCache: meta.ignoreCache,

View File

@@ -1,6 +1,7 @@
export interface TagProps {
title: string;
name: string;
path: string;
fullPath: string;
query?: any;
ignoreCache?: boolean;

View File

@@ -6,3 +6,9 @@ export const TablePageSizeOptions = [10, 20, 30, 50, 100];
// 卡片视图分页数配置
export const CardPageSizeOptions = [12, 18, 36, 48, 96];
// 通用启用状态
export const EnabledStatus = {
DISABLED: 0,
ENABLED: 1
};

View File

@@ -1,14 +1,16 @@
<template>
<a-spin :loading="loading" class="grant-container">
<!-- 角色列表 -->
<router-roles v-model="roleId" @change="fetchAuthorizedGroup" />
<router-roles outer-class="roles-router-wrapper"
v-model="roleId"
@change="fetchAuthorizedGroup" />
<!-- 分组列表 -->
<div class="group-container">
<!-- 顶部 -->
<div class="group-header">
<!-- 提示信息 -->
<a-alert class="alert-wrapper" :show-icon="false">
<span v-if="currentRole">
<span v-if="currentRole" class="alert-message">
当前选择的角色为 <span class="span-blue mr4">{{ currentRole?.text }}</span>
<span class="span-blue ml4" v-if="currentRole.code === AdminRoleCode">管理员拥有全部权限, 无需配置</span>
</span>
@@ -112,6 +114,12 @@
height: 100%;
display: flex;
padding: 0 12px 12px 0;
position: absolute;
.roles-router-wrapper {
margin-right: 16px;
border-right: 1px var(--color-neutral-3) solid;
}
.group-container {
position: relative;
@@ -126,6 +134,11 @@
.alert-wrapper {
padding: 4px 16px;
.alert-message {
display: block;
height: 22px;
}
}
.grant-button {

View File

@@ -1,14 +1,16 @@
<template>
<a-spin :loading="loading" class="grant-container">
<!-- 用户列表 -->
<router-users v-model="userId" @change="fetchAuthorizedGroup" />
<router-users outer-class="users-router-wrapper"
v-model="userId"
@change="fetchAuthorizedGroup" />
<!-- 分组列表 -->
<div class="group-container">
<!-- 顶部 -->
<div class="group-header">
<!-- 提示信息 -->
<a-alert class="alert-wrapper" :show-icon="false">
<span v-if="currentUser">
<span v-if="currentUser" class="alert-message">
当前选择的用户为 <span class="span-blue mr4">{{ currentUser?.text }}</span>
<span class="ml4">若当前选择的用户角色包含管理员则无需配置 (管理员拥有全部权限)</span>
</span>
@@ -110,6 +112,12 @@
height: 100%;
display: flex;
padding: 0 12px 12px 0;
position: absolute;
.users-router-wrapper {
margin-right: 16px;
border-right: 1px var(--color-neutral-3) solid;
}
.group-container {
position: relative;
@@ -124,6 +132,11 @@
.alert-wrapper {
padding: 4px 16px;
.alert-message {
display: block;
height: 22px;
}
}
.grant-button {

View File

@@ -66,13 +66,6 @@
const { data } = await getHostGroupRelList(groupId as number);
selectedGroupHosts.value = data.map(s => cacheStore.hosts.find(h => h.id === s) as HostQueryResponse)
.filter(Boolean);
for (let i = 1800; i < 2000; i++) {
selectedGroupHosts.value.push({
id: i,
name: i + '',
code: i + '',
} as any);
}
} catch (e) {
} finally {
setLoading(false);

View File

@@ -1,18 +1,24 @@
<template>
<div class="role-container">
<!-- 角色列表 -->
<tab-router v-if="rolesRouter.length"
class="role-router"
v-model="value"
:items="rolesRouter"
@change="(key, item) => emits('change', key, item)" />
<!-- 暂无数据 -->
<a-empty v-else class="role-empty">
<div slot="description">
暂无角色数据
</div>
</a-empty>
</div>
<a-scrollbar>
<div class="role-container">
<!-- 角色列表 -->
<tab-router v-if="rolesRouter.length"
class="role-router"
v-model="value"
:items="rolesRouter"
@change="(key, item) => emits('change', key, item)" />
<!-- 加载中 -->
<a-skeleton v-else-if="loading" class="skeleton-wrapper">
<a-skeleton-line :rows="4" />
</a-skeleton>
<!-- 暂无数据 -->
<a-empty v-else class="role-empty">
<div slot="description">
暂无角色数据
</div>
</a-empty>
</div>
</a-scrollbar>
</template>
<script lang="ts">
@@ -27,6 +33,7 @@
import { useCacheStore } from '@/store';
import { getRoleList } from '@/api/user/role';
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
const props = defineProps({
modelValue: Number
@@ -34,6 +41,7 @@
const emits = defineEmits(['update:modelValue', 'change']);
const { loading, setLoading } = useLoading();
const cacheStore = useCacheStore();
const rolesRouter = ref<Array<TabRouterItem>>([]);
@@ -49,12 +57,15 @@
// 加载角色列表
const loadRoleList = async () => {
setLoading(true);
try {
const { data } = await getRoleList();
// 设置到缓存
cacheStore.set('roles', data);
} catch (e) {
Message.error('角色列表加载失败');
} finally {
setLoading(false);
}
};
@@ -75,17 +86,27 @@
</script>
<style lang="less" scoped>
@width: 198px;
@height: 198px;
:deep(.arco-scrollbar-container) {
height: 100%;
overflow-y: auto;
}
.role-container {
margin-right: 16px;
overflow: hidden;
.role-router {
height: 100%;
min-width: max-content;
border-right: 1px var(--color-neutral-3) solid;
min-width: @width;
width: max-content;
}
.role-empty {
width: 198px;
.skeleton-wrapper, .role-empty {
width: @width;
height: 200px;
padding: 8px;
}
}
</style>

View File

@@ -1,18 +1,24 @@
<template>
<div class="user-container">
<!-- 用户列表 -->
<tab-router v-if="usersRouter.length"
class="user-router"
v-model="value"
:items="usersRouter"
@change="(key, item) => emits('change', key, item)" />
<!-- 暂无数据 -->
<a-empty v-else class="user-empty">
<div slot="description">
暂无用户数据
</div>
</a-empty>
</div>
<a-scrollbar>
<div class="user-container">
<!-- 用户列表 -->
<tab-router v-if="usersRouter.length"
class="user-router"
v-model="value"
:items="usersRouter"
@change="(key, item) => emits('change', key, item)" />
<!-- 加载中 -->
<a-skeleton v-else-if="loading" class="skeleton-wrapper">
<a-skeleton-line :rows="4" />
</a-skeleton>
<!-- 暂无数据 -->
<a-empty v-else class="user-empty">
<div slot="description">
暂无用户数据
</div>
</a-empty>
</div>
</a-scrollbar>
</template>
<script lang="ts">
@@ -27,6 +33,7 @@
import { useCacheStore } from '@/store';
import { getUserList } from '@/api/user/user';
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
const props = defineProps({
modelValue: Number
@@ -34,6 +41,7 @@
const emits = defineEmits(['update:modelValue', 'change']);
const { loading, setLoading } = useLoading();
const cacheStore = useCacheStore();
const usersRouter = ref<Array<TabRouterItem>>([]);
@@ -49,12 +57,15 @@
// 加载用户列表
const loadUserList = async () => {
setLoading(true);
try {
const { data } = await getUserList();
// 设置到缓存
cacheStore.set('users', data);
} catch (e) {
Message.error('用户列表加载失败');
} finally {
setLoading(false);
}
};
@@ -74,17 +85,27 @@
</script>
<style lang="less" scoped>
@width: 198px;
@height: 198px;
:deep(.arco-scrollbar-container) {
height: 100%;
overflow-y: auto;
}
.user-container {
margin-right: 16px;
overflow: hidden;
.user-router {
height: 100%;
min-width: max-content;
border-right: 1px var(--color-neutral-3) solid;
min-width: @width;
width: max-content;
}
.user-empty {
width: 198px;
.skeleton-wrapper, .user-empty {
width: @width;
height: @height;
padding: 8px;
}
}
</style>

View File

@@ -1,18 +1,18 @@
<template>
<div class="view-container">
<a-tabs class="tabs-container simple-card"
<a-tabs v-model:active-key="activeKey"
class="tabs-container simple-card"
size="large"
:destroy-on-hide="true"
:justify="true"
:lazy-load="true">
<!-- 主机分组授权(角色) -->
<a-tab-pane :key="1"
<a-tab-pane :key="GrantKey.HOST_GROUP_ROLE"
v-permission="['asset:host-group:grant']"
title="主机分组授权(角色)">
<host-group-role-grant />
</a-tab-pane>
<!-- 主机分组授权(用户) -->
<a-tab-pane :key="2"
<a-tab-pane :key="GrantKey.HOST_GROUP_USER"
v-permission="['asset:host-group:grant']"
title="主机分组授权(用户)">
<host-group-user-grant />
@@ -28,33 +28,31 @@
</script>
<script lang="ts" setup>
import { ref, onBeforeMount, onUnmounted } from 'vue';
import { onBeforeMount, onUnmounted, ref } from 'vue';
import { useCacheStore } from '@/store';
import { Message } from '@arco-design/web-vue';
import { getUserList } from '@/api/user/user';
import { getRoleList } from '@/api/user/role';
import HostGroupRoleGrant from './components/host-group-role-grant.vue';
import HostGroupUserGrant from './components/host-group-user-grant.vue';
import { GrantKey } from './types/const';
import { useRoute } from 'vue-router';
const render = ref(false);
const route = useRoute();
const cacheStore = useCacheStore();
// 加载用户列表
const loadUserList = async () => {
try {
const { data } = await getUserList();
// 设置到缓存
cacheStore.set('users', data);
} catch (e) {
Message.error('用户列表加载失败');
}
};
const activeKey = ref();
// 卸载时清除 cache
onUnmounted(() => {
cacheStore.reset('users', 'roles', 'hosts', 'hostGroups');
});
// 跳转到指定页
onBeforeMount(() => {
const key = route.query.key;
if (key) {
activeKey.value = Number(key);
}
});
</script>
<style lang="less" scoped>
@@ -71,6 +69,12 @@
height: 100%;
}
:deep(.arco-tabs-pane) {
width: 100%;
height: 100%;
position: relative;
}
:deep(.arco-tabs-tab-title) {
user-select: none;
}

View File

@@ -1,5 +1,10 @@
// 创建前缀
export const createGroupGroupPrefix = 'create-';
// 路由
export const GrantRouteName = 'assetGrant';
// 根id
export const rootId = 0;
// 授权 key
export const GrantKey = {
// 主机分组-角色
HOST_GROUP_ROLE: 1,
// 主机分组-用户
HOST_GROUP_USER: 2
};

View File

@@ -35,9 +35,10 @@
import useLoading from '@/hooks/loading';
import { Message } from '@arco-design/web-vue';
import { getHostConfigAll } from '@/api/asset/host';
import { useCacheStore } from '@/store';
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';
const { visible, setVisible } = useVisible();
@@ -95,6 +96,9 @@
};
onBeforeMount(async () => {
// 加载字典值
const dictStore = useDictStore();
await dictStore.loadKeys([...sshDictKeys]);
// 加载主机秘钥
await fetchHostKeys();
// 加载主机身份

View File

@@ -1,6 +1,6 @@
<template>
<a-card class="general-card"
:body-style="{ padding: config.status === 1 ? '' : '0' }">
:body-style="{ padding: config.status === EnabledStatus.ENABLED ? '' : '0' }">
<!-- 标题 -->
<template #title>
<div class="config-title-wrapper">
@@ -140,7 +140,7 @@
<script lang="ts" setup>
import type { FieldRule } from '@arco-design/web-vue';
import type { HostSshConfig } from './types/const';
import { ref, watch } from 'vue';
import { reactive, ref, watch } from 'vue';
import { updateHostConfigStatus, updateHostConfig } from '@/api/asset/host';
import { authTypeKey, AuthType } from './types/const';
import rules from './types/form.rules';
@@ -149,6 +149,7 @@
import { useDictStore } from '@/store';
import HostKeySelector from '@/components/asset/host-key/host-key-selector.vue';
import HostIdentitySelector from '@/components/asset/host-identity/host-identity-selector.vue';
import { EnabledStatus } from '@/types/const';
const { loading, setLoading } = useLoading();
const { toOptions } = useDictStore();
@@ -159,7 +160,7 @@
const emits = defineEmits(['submitted']);
const config = ref({
const config = reactive({
status: undefined,
version: undefined,
});
@@ -182,8 +183,8 @@
// 监听数据变化
watch(() => props.content, (v: any) => {
config.value.status = v?.status;
config.value.version = v?.version;
config.status = v?.status;
config.version = v?.version;
resetConfig();
});
@@ -221,9 +222,9 @@
return updateHostConfigStatus({
id: props?.content?.id,
status: e,
version: config.value.version
version: config.version
}).then(({ data }) => {
config.value.version = data;
config.version = data;
setLoading(false);
return true;
}).catch(() => {
@@ -250,10 +251,10 @@
setLoading(true);
const { data } = await updateHostConfig({
id: props?.content?.id,
version: config.value.version,
version: config.version,
config: JSON.stringify(formModel.value)
});
config.value.version = data;
config.version = data;
setLoading(false);
Message.success('修改成功');
// 回调 props

View File

@@ -26,3 +26,6 @@ export const AuthType = {
// 主机验证方式 字典项
export const authTypeKey = 'hostAuthTypeType';
// 加载的字典值
export const dictKeys = ['hostAuthTypeType'];

View File

@@ -8,7 +8,8 @@
:pagination="pagination"
:card-layout-cols="cardColLayout"
:filter-count="filterCount"
:handle-visible="{ disableAdd: true }"
:add-permission="['asset:host:create']"
@add="emits('openAdd')"
@reset="reset"
@search="fetchCardData"
@page-change="fetchCardData">
@@ -16,22 +17,31 @@
<template #leftHandle>
<!-- 主机分组 -->
<a-button v-permission="['asset:host-group:update']"
class="click-icon-wrapper card-header-icon-wrapper"
type="primary"
title="分组配置"
class="card-header-icon-wrapper"
@click="emits('openHostGroup')">
主机分组
<template #icon>
<icon-layers />
</template>
</a-button>
<!-- 创建 -->
<div v-permission="['asset:host:create']"
class="click-icon-wrapper card-header-icon-wrapper"
title="创建"
@click="emits('openAdd')">
<icon-plus />
</div>
<!-- 角色授权 -->
<a-button v-permission="['asset:host-group:grant']"
class="card-header-icon-wrapper"
@click="$router.push({ name: GrantRouteName, query: { key: GrantKey.HOST_GROUP_ROLE }})">
角色授权
<template #icon>
<icon-user-group />
</template>
</a-button>
<!-- 用户授权 -->
<a-button v-permission="['asset:host-group:grant']"
class="card-header-icon-wrapper"
@click="$router.push({ name: GrantRouteName, query: { key: GrantKey.HOST_GROUP_USER }})">
用户授权
<template #icon>
<icon-user />
</template>
</a-button>
</template>
<!-- 过滤条件 -->
<template #filterContent>
@@ -173,6 +183,7 @@
import { tagColor } from '../types/const';
import TagMultiSelector from '@/components/meta/tag/tag-multi-selector.vue';
import useCopy from '@/hooks/copy';
import { GrantKey, GrantRouteName } from '@/views/asset/grant/types/const';
const emits = defineEmits(['openAdd', 'openUpdate', 'openUpdateConfig', 'openHostGroup']);

View File

@@ -71,6 +71,24 @@
<icon-layers />
</template>
</a-button>
<!-- 角色授权 -->
<a-button type="primary"
v-permission="['asset:host-group:grant']"
@click="$router.push({ name: GrantRouteName, query: { key: GrantKey.HOST_GROUP_ROLE }})">
角色授权
<template #icon>
<icon-user-group />
</template>
</a-button>
<!-- 用户授权 -->
<a-button type="primary"
v-permission="['asset:host-group:grant']"
@click="$router.push({ name: GrantRouteName, query: { key: GrantKey.HOST_GROUP_USER }})">
用户授权
<template #icon>
<icon-user />
</template>
</a-button>
<!-- 新增 -->
<a-button type="primary"
v-permission="['asset:host:create']"
@@ -183,6 +201,7 @@
import useFavorite from '@/hooks/favorite';
import { dataColor } from '@/utils';
import TagMultiSelector from '@/components/meta/tag/tag-multi-selector.vue';
import { GrantKey, GrantRouteName } from '@/views/asset/grant/types/const';
const tagSelector = ref();
const tableRenderData = ref<HostQueryResponse[]>([]);

View File

@@ -33,9 +33,8 @@
<script lang="ts" setup>
import { computed, ref, onBeforeMount, onUnmounted } from 'vue';
import { useAppStore, useCacheStore, useDictStore } from '@/store';
import { useAppStore, useCacheStore } from '@/store';
import { getTagList } from '@/api/meta/tag';
import { dictKeys } from './types/const';
import { Message } from '@arco-design/web-vue';
import HostTable from './components/host-table.vue';
import HostCardList from './components/host-card-list.vue';
@@ -87,9 +86,6 @@
onBeforeMount(async () => {
// 加载 tags
await loadTags();
// 加载字典值
const dictStore = useDictStore();
await dictStore.loadKeys(dictKeys);
render.value = true;
});

View File

@@ -15,6 +15,3 @@ export const tagColor = [
'pinkpurple',
'magenta'
];
// 加载的字典值
export const dictKeys = ['hostAuthTypeType'];