feat: 主机分组授权.
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@arco-design/web-vue": "^2.44.7",
|
||||
"@arco-design/web-vue": "^2.53.0",
|
||||
"@dangojs/a-query-header": "^0.0.31",
|
||||
"@sanqi377/arco-vue-icon-picker": "^1.0.7",
|
||||
"@vueuse/core": "^9.3.0",
|
||||
|
||||
14
orion-ops-ui/pnpm-lock.yaml
generated
14
orion-ops-ui/pnpm-lock.yaml
generated
@@ -11,11 +11,11 @@ overrides:
|
||||
|
||||
dependencies:
|
||||
'@arco-design/web-vue':
|
||||
specifier: ^2.44.7
|
||||
version: 2.51.1(vue@3.3.4)
|
||||
specifier: ^2.53.0
|
||||
version: 2.53.0(vue@3.3.4)
|
||||
'@dangojs/a-query-header':
|
||||
specifier: ^0.0.31
|
||||
version: 0.0.31(@arco-design/web-vue@2.51.1)(@dangojs/digitforce-ui-utils@0.0.9)(lodash@4.17.21)(vue@3.3.4)
|
||||
version: 0.0.31(@arco-design/web-vue@2.53.0)(@dangojs/digitforce-ui-utils@0.0.9)(lodash@4.17.21)(vue@3.3.4)
|
||||
'@sanqi377/arco-vue-icon-picker':
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(vue@3.3.4)
|
||||
@@ -224,8 +224,8 @@ packages:
|
||||
color: 3.2.1
|
||||
dev: false
|
||||
|
||||
/@arco-design/web-vue@2.51.1(vue@3.3.4):
|
||||
resolution: {integrity: sha512-YsNF1hrl700rshZPrJ7haNEN6pc3dQwpLbYzpE49jrLdF0mALGU9aP2A8q0FNPH2Rt4rxvxkw1o0gsL7my8H+Q==}
|
||||
/@arco-design/web-vue@2.53.0(vue@3.3.4):
|
||||
resolution: {integrity: sha512-IQtxtIJNmH038EdMecAdeLVayr4r+Txnk51RIVAz28PpdEdjf4J0wnLyirDb41YB0fV7eubZOFNodq5uTYqPTA==}
|
||||
peerDependencies:
|
||||
vue: ^3.1.0
|
||||
dependencies:
|
||||
@@ -720,7 +720,7 @@ packages:
|
||||
postcss-selector-parser: 6.0.13
|
||||
dev: true
|
||||
|
||||
/@dangojs/a-query-header@0.0.31(@arco-design/web-vue@2.51.1)(@dangojs/digitforce-ui-utils@0.0.9)(lodash@4.17.21)(vue@3.3.4):
|
||||
/@dangojs/a-query-header@0.0.31(@arco-design/web-vue@2.53.0)(@dangojs/digitforce-ui-utils@0.0.9)(lodash@4.17.21)(vue@3.3.4):
|
||||
resolution: {integrity: sha512-hXLAU8oIQ1ULDY+qXKoKstqo3/m4HTjXcxNaFbPatTVMHwox4tBAIc5Nox1crvlWkNagUyk7PpnQuzLH73j9AQ==}
|
||||
peerDependencies:
|
||||
'@arco-design/web-vue': ^2.47.1
|
||||
@@ -728,7 +728,7 @@ packages:
|
||||
lodash: ^4.17.21
|
||||
vue: ^3.3.2
|
||||
dependencies:
|
||||
'@arco-design/web-vue': 2.51.1(vue@3.3.4)
|
||||
'@arco-design/web-vue': 2.53.0(vue@3.3.4)
|
||||
'@dangojs/digitforce-ui-utils': 0.0.9
|
||||
lodash: 4.17.21
|
||||
vue: 3.3.4
|
||||
|
||||
@@ -90,6 +90,10 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.arco-trigger-menu {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.arco-dropdown-option-content {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<!-- FIXME -->
|
||||
<!-- LOGO -->
|
||||
<img alt="logo"
|
||||
draggable="false"
|
||||
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/dfdba5317c0c20ce20e64fac803d52bc.svg~tplv-49unhts6dw-image.image" />
|
||||
<!-- 标头 -->
|
||||
<a-typography-title :heading="5"
|
||||
@@ -161,8 +162,9 @@
|
||||
<li>
|
||||
<a-dropdown trigger="click">
|
||||
<!-- 头像 -->
|
||||
<a-avatar :size="32"
|
||||
:style="{ cursor: 'pointer', backgroundColor: '#3370ff' }">
|
||||
<a-avatar draggable="false"
|
||||
:size="32"
|
||||
:style="{ cursor: 'pointer', backgroundColor: '#3370ff', userSelect: 'none' }">
|
||||
{{ nickname }}
|
||||
</a-avatar>
|
||||
<template #content>
|
||||
@@ -316,6 +318,7 @@
|
||||
}
|
||||
|
||||
.left-side {
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
@@ -374,5 +377,4 @@
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -37,7 +37,14 @@
|
||||
<TabBar v-if="appStore.tabBar" />
|
||||
<!-- 页面 -->
|
||||
<a-layout-content>
|
||||
<PageLayout />
|
||||
<!-- 水印 -->
|
||||
<a-watermark :grayscale="true"
|
||||
:alpha=".6"
|
||||
:z-index="9999"
|
||||
style="width: 100%; height: 100%;"
|
||||
:content="userStore.username || ''">
|
||||
<PageLayout />
|
||||
</a-watermark>
|
||||
</a-layout-content>
|
||||
<!-- 页脚 -->
|
||||
<Footer v-if="footer" />
|
||||
@@ -49,7 +56,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, provide, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useAppStore } from '@/store';
|
||||
import { useAppStore, useUserStore } from '@/store';
|
||||
import Menu from '@/components/system/menu/tree/index.vue';
|
||||
import NavBar from '@/components/app/navbar/index.vue';
|
||||
import Footer from '@/components/app/footer/index.vue';
|
||||
@@ -60,6 +67,7 @@
|
||||
|
||||
const isInit = ref(false);
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
useResponsive(true);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<!-- 提示信息 -->
|
||||
<a-alert class="alert-wrapper" :show-icon="false">
|
||||
<span v-if="currentRole">
|
||||
当前选择的角色为 <span class="span-blue mr4">{{ currentRole.text }}</span>
|
||||
当前选择的角色为 <span class="span-blue mr4">{{ currentRole?.text }}</span>
|
||||
<span class="span-blue ml4" v-if="currentRole.code === AdminRoleCode">管理员拥有全部权限, 无需配置</span>
|
||||
</span>
|
||||
</a-alert>
|
||||
@@ -41,23 +41,12 @@
|
||||
:draggable="false"
|
||||
:loading="loading"
|
||||
@loading="setLoading"
|
||||
@select-node="fetchGroupHost"
|
||||
@select-node="e => selectedGroup = e"
|
||||
@update:checked-keys="updateCheckedGroups" />
|
||||
</div>
|
||||
<!-- 主机列表 -->
|
||||
<div class="group-main-hosts">
|
||||
<a-list size="smail" :title="'组内数据'" :split="false">
|
||||
<a-list-item v-for="idx in 4" :key="idx">
|
||||
<a-list-item-meta
|
||||
title="host"
|
||||
>
|
||||
<template #avatar>
|
||||
<icon-desktop />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
<host-list class="group-main-hosts"
|
||||
:group="selectedGroup" />
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
@@ -71,13 +60,15 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TabRouterItem } from '@/components/view/tab-router/types';
|
||||
import type { TreeNodeData } from '@arco-design/web-vue';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { useCacheStore } from '@/store';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { getAuthorizedHostGroup, grantHostGroup } from '@/api/asset/host-group';
|
||||
import { AdminRoleCode } from '@/types/const';
|
||||
import HostGroupTree from './host-group-tree.vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import HostGroupTree from './host-group-tree.vue';
|
||||
import HostList from './host-list.vue';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
const cacheStore = useCacheStore();
|
||||
@@ -87,6 +78,7 @@
|
||||
const rolesRouter = ref<Array<TabRouterItem>>([]);
|
||||
const authorizedGroups = ref<Array<number>>([]);
|
||||
const checkedGroups = ref<Array<number>>([]);
|
||||
const selectedGroup = ref<TreeNodeData>({});
|
||||
|
||||
// 监听角色变更 获取授权列表
|
||||
watch(roleId, async (value) => {
|
||||
@@ -107,10 +99,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 加载组内数据
|
||||
const fetchGroupHost = () => {
|
||||
};
|
||||
|
||||
// 选择分组
|
||||
const updateCheckedGroups = (e: Array<number>) => {
|
||||
checkedGroups.value = e;
|
||||
@@ -141,6 +129,7 @@
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -173,7 +162,7 @@
|
||||
align-items: center;
|
||||
|
||||
.alert-wrapper {
|
||||
height: 32px;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
@@ -185,28 +174,19 @@
|
||||
display: flex;
|
||||
|
||||
&-tree {
|
||||
width: calc(100% - 240px - 16px);
|
||||
width: calc(60% - 16px);
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&-hosts {
|
||||
width: 240px;
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
:deep(.tab-item) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
:deep(.arco-avatar-image) {
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #FFF;
|
||||
background: rgb(var(--blue-6));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,55 @@
|
||||
<template>
|
||||
<div>
|
||||
host-group-view-user-grant
|
||||
</div>
|
||||
<a-spin :loading="loading" class="grant-container">
|
||||
<!-- 左侧用户列表 -->
|
||||
<div class="user-container">
|
||||
<!-- 用户列表 -->
|
||||
<tab-router v-if="usersRouter.length"
|
||||
class="user-router"
|
||||
v-model="userId"
|
||||
:items="usersRouter" />
|
||||
<!-- 暂无数据 -->
|
||||
<a-empty v-else class="user-empty">
|
||||
<div slot="description">
|
||||
暂无用户数据
|
||||
</div>
|
||||
</a-empty>
|
||||
</div>
|
||||
<!-- 右侧菜单列表 -->
|
||||
<div class="group-container">
|
||||
<!-- 顶部 -->
|
||||
<div class="group-header">
|
||||
<!-- 提示信息 -->
|
||||
<a-alert class="alert-wrapper" :show-icon="false">
|
||||
<span v-if="currentUser">
|
||||
当前选择的用户为 <span class="span-blue mr4">{{ currentUser?.text }}</span>
|
||||
<span class="ml4">若当前选择的用户角色包含管理员则无需配置 (管理员拥有全部权限)</span>
|
||||
</span>
|
||||
</a-alert>
|
||||
<!-- 保存 -->
|
||||
<a-button class="save-button"
|
||||
type="primary"
|
||||
@click="save">
|
||||
保存
|
||||
</a-button>
|
||||
</div>
|
||||
<!-- 主题部分 -->
|
||||
<div class="group-main">
|
||||
<!-- 菜单 -->
|
||||
<div class="group-main-tree">
|
||||
<host-group-tree :checkable="true"
|
||||
:checked-keys="checkedGroups"
|
||||
:draggable="false"
|
||||
:loading="loading"
|
||||
@loading="setLoading"
|
||||
@select-node="e => selectedGroup = e"
|
||||
@update:checked-keys="updateCheckedGroups" />
|
||||
</div>
|
||||
<!-- 主机列表 -->
|
||||
<host-list class="group-main-hosts"
|
||||
:group="selectedGroup" />
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -11,9 +59,133 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TabRouterItem } from '@/components/view/tab-router/types';
|
||||
import type { TreeNodeData } from '@arco-design/web-vue';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { useCacheStore } from '@/store';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { getAuthorizedHostGroup, grantHostGroup } from '@/api/asset/host-group';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import HostGroupTree from './host-group-tree.vue';
|
||||
import HostList from './host-list.vue';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
const cacheStore = useCacheStore();
|
||||
|
||||
const userId = ref();
|
||||
const currentUser = ref();
|
||||
const usersRouter = ref<Array<TabRouterItem>>([]);
|
||||
const authorizedGroups = ref<Array<number>>([]);
|
||||
const checkedGroups = ref<Array<number>>([]);
|
||||
const selectedGroup = ref<TreeNodeData>({});
|
||||
|
||||
// 监听用户变更 获取授权列表
|
||||
watch(userId, async (value) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
currentUser.value = usersRouter.value.find(s => s.key === value);
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await getAuthorizedHostGroup({
|
||||
userId: userId.value
|
||||
});
|
||||
authorizedGroups.value = data;
|
||||
checkedGroups.value = data;
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
// 选择分组
|
||||
const updateCheckedGroups = (e: Array<number>) => {
|
||||
checkedGroups.value = e;
|
||||
};
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await grantHostGroup({
|
||||
userId: userId.value,
|
||||
groupIdList: checkedGroups.value
|
||||
});
|
||||
Message.success('保存成功');
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载主机
|
||||
onMounted(() => {
|
||||
usersRouter.value = cacheStore.users.map(s => {
|
||||
return {
|
||||
key: s.id,
|
||||
text: `${s.nickname} (${s.username})`
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.grant-container {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
|
||||
.user-container {
|
||||
margin-right: 16px;
|
||||
|
||||
.user-router {
|
||||
height: 100%;
|
||||
min-width: max-content;
|
||||
border-right: 1px var(--color-neutral-3) solid;
|
||||
}
|
||||
|
||||
.user-empty {
|
||||
width: 198px;
|
||||
}
|
||||
}
|
||||
|
||||
.group-container {
|
||||
width: 100%;
|
||||
|
||||
.group-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
align-items: center;
|
||||
|
||||
.alert-wrapper {
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
width: 60px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.group-main {
|
||||
display: flex;
|
||||
|
||||
&-tree {
|
||||
width: calc(60% - 16px);
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&-hosts {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.tab-item) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
113
orion-ops-ui/src/views/asset/host-group/components/host-list.vue
Normal file
113
orion-ops-ui/src/views/asset/host-group/components/host-list.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<a-list size="small"
|
||||
:hoverable="true"
|
||||
:data="selectedGroupHosts"
|
||||
:loading="loading">
|
||||
<!-- 表头 -->
|
||||
<template #header>
|
||||
<span class="hosts-header-title">组内数据</span>
|
||||
<span class="span-blue">{{ props.group?.title }}</span>
|
||||
</template>
|
||||
<!-- 空数据 -->
|
||||
<template #empty>
|
||||
<span class="host-list-empty">当前分组未配置主机</span>
|
||||
</template>
|
||||
<!-- 数据 -->
|
||||
<template #item="{ item }">
|
||||
<a-list-item :title="`${item.name}(${item.code}) - ${ item.address}`">
|
||||
<icon-desktop class="host-list-icon" />
|
||||
<span>{{ `${item.name}(${item.code}) - ` }}</span>
|
||||
<span class="span-blue">{{ item.address }}</span>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'host-list'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TreeNodeData } from '@arco-design/web-vue';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
import type { PropType } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { useCacheStore } from '@/store';
|
||||
import { ref, watch } from 'vue';
|
||||
import { getHostGroupRelList } from '@/api/asset/host-group';
|
||||
|
||||
const props = defineProps({
|
||||
group: {
|
||||
type: Object as PropType<TreeNodeData>,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
selectedGroupHosts.value = data.map(s => cacheStore.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: 24px;
|
||||
text-align: center;
|
||||
color: var(--color-text-2);
|
||||
display: block;
|
||||
}
|
||||
|
||||
:deep(.arco-list-wrapper .arco-list-spin) {
|
||||
height: unset;
|
||||
}
|
||||
|
||||
: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: #FFF;
|
||||
background: rgb(var(--blue-6));
|
||||
margin-right: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -161,7 +161,7 @@
|
||||
align-items: center;
|
||||
|
||||
.alert-wrapper {
|
||||
height: 32px;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
|
||||
@@ -129,6 +129,10 @@
|
||||
background: #FFF;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-tab-title){
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-content) {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user