优化资产授权交互逻辑.

This commit is contained in:
lijiahang
2024-04-18 11:31:05 +08:00
parent 1034ba4896
commit fe4b87927e
26 changed files with 166 additions and 84 deletions

View File

@@ -5,8 +5,7 @@
:loading="loading" :loading="loading"
placeholder="请选择主机分组" placeholder="请选择主机分组"
:allow-clear="true" :allow-clear="true"
:allow-search="true"> :allow-search="true" />
</a-tree-select>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@@ -2,15 +2,17 @@
<a-scrollbar> <a-scrollbar>
<!-- 分组树 --> <!-- 分组树 -->
<a-tree v-if="treeData.length" <a-tree v-if="treeData.length"
v-model:checked-keys="checkedKeys"
ref="tree" ref="tree"
class="tree-container block-tree" class="tree-container block-tree"
v-model:checked-keys="checkedKeys" :class="[ editable ? 'editable-tree' : '' ]"
:blockNode="true" :blockNode="true"
:draggable="editable"
:data="treeData" :data="treeData"
:draggable="editable"
:checkable="checkable" :checkable="checkable"
:check-strictly="true" :check-strictly="true"
@drop="moveGroup"> @drop="moveGroup"
@select="(s) => emits('onSelected', s)">
<!-- 标题 --> <!-- 标题 -->
<template #title="node"> <template #title="node">
<!-- 修改名称输入框 --> <!-- 修改名称输入框 -->
@@ -97,13 +99,16 @@
import { isString } from '@/utils/is'; import { isString } from '@/utils/is';
import { useCacheStore } from '@/store'; import { useCacheStore } from '@/store';
const props = defineProps<Partial<{ const props = withDefaults(defineProps<Partial<{
loading: boolean; loading: boolean;
editable: boolean; editable: boolean;
checkable: boolean; checkable: boolean;
checkedKeys: Array<number>; checkedKeys: Array<number>;
}>>(); }>>(), {
const emits = defineEmits(['setLoading', 'selectedNode', 'update:checkedKeys']); editable: false,
checkable: false,
});
const emits = defineEmits(['setLoading', 'onSelected', 'selectedNode', 'update:checkedKeys']);
const cacheStore = useCacheStore(); const cacheStore = useCacheStore();
@@ -331,19 +336,9 @@
width: max-content; width: max-content;
user-select: none; user-select: none;
overflow: hidden; overflow: hidden;
}
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
padding-top: 25px;
color: var(--color-text-3);
}
:deep(.arco-tree-node-title) { :deep(.arco-tree-node-title) {
padding: 0 80px 0 0; padding: 0;
} }
.node-title-wrapper { .node-title-wrapper {
@@ -358,5 +353,21 @@
font-size: 12px; font-size: 12px;
color: rgb(var(--primary-6)); color: rgb(var(--primary-6));
} }
}
.editable-tree {
:deep(.arco-tree-node-title) {
padding: 0 80px 0 0;
}
}
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
padding-top: 25px;
color: var(--color-text-3);
}
</style> </style>

View File

@@ -19,7 +19,7 @@
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
const props = defineProps<Partial<{ const props = defineProps<Partial<{
modelValue: number modelValue: number;
}>>(); }>>();
const emits = defineEmits(['update:modelValue']); const emits = defineEmits(['update:modelValue']);

View File

@@ -39,7 +39,8 @@
allowCreate: boolean; allowCreate: boolean;
tagColor: Array<string>; tagColor: Array<string>;
}>>(), { }>>(), {
tagColor: () => [] allowCreate: false,
tagColor: () => [],
}); });
const emits = defineEmits(['update:modelValue', 'onLimited']); const emits = defineEmits(['update:modelValue', 'onLimited']);

View File

@@ -1,12 +1,8 @@
<template> <template>
<div ref="editorContainer" <div ref="editorContainer"
class="editor-wrapper" class="editor-wrapper"
:class="[ :class="[ !!containerClass ? containerClass : '' ]"
!!containerClass ? containerClass : '' :style="{ ...containerStyle }" />
]"
:style="{
...containerStyle
}" />
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@@ -27,15 +27,30 @@
</template> </template>
</span> </span>
</a-alert> </a-alert>
<!-- 操作按钮 -->
<a-space>
<!-- 全选 -->
<a-button @click="emits('selectAll')">
全选
<template #icon>
<icon-select-all />
</template>
</a-button>
<!-- 反选 -->
<a-button @click="emits('reverse')">
反选
<template #icon>
<icon-list />
</template>
</a-button>
<!-- 授权 --> <!-- 授权 -->
<a-button class="grant-button" <a-button type="primary" @click="doGrant">
type="primary"
@click="doGrant">
授权 授权
<template #icon> <template #icon>
<icon-safe /> <icon-safe />
</template> </template>
</a-button> </a-button>
</a-space>
</div> </div>
<!-- 主体部分 --> <!-- 主体部分 -->
<div class="data-main"> <div class="data-main">
@@ -63,7 +78,7 @@
type: string; type: string;
loading: boolean; loading: boolean;
}>(); }>();
const emits = defineEmits(['fetch', 'grant']); const emits = defineEmits(['fetch', 'grant', 'selectAll', 'reverse']);
const subjectId = ref(); const subjectId = ref();
const currentSubject = ref(); const currentSubject = ref();
@@ -115,16 +130,13 @@
.alert-wrapper { .alert-wrapper {
padding: 4px 16px; padding: 4px 16px;
margin-right: 12px;
.alert-message { .alert-message {
display: block; display: block;
height: 22px; height: 22px;
} }
} }
.grant-button {
margin-left: 16px;
}
} }
.data-main { .data-main {

View File

@@ -2,15 +2,19 @@
<grant-layout :type="type" <grant-layout :type="type"
:loading="loading" :loading="loading"
@fetch="fetchAuthorizedData" @fetch="fetchAuthorizedData"
@grant="doGrant"> @grant="doGrant"
@select-all="selectAll"
@reverse="reverseSelect">
<!-- 分组 --> <!-- 分组 -->
<host-group-tree v-model:checked-keys="checkedGroups" <host-group-tree v-model:checked-keys="checkedGroups"
ref="tree"
outer-class="group-main-tree" outer-class="group-main-tree"
:checkable="true" :checkable="true"
:editable="false" :editable="false"
:loading="loading" :loading="loading"
@set-loading="setLoading" @set-loading="setLoading"
@selected-node="(e) => selectedGroup = e" /> @selected-node="(e) => selectedGroup = e"
@on-selected="clickGroup" />
<!-- 主机列表 --> <!-- 主机列表 -->
<host-list class="group-main-hosts sticky-list" :group="selectedGroup" /> <host-list class="group-main-hosts sticky-list" :group="selectedGroup" />
</grant-layout> </grant-layout>
@@ -28,6 +32,8 @@
import { ref } from 'vue'; import { ref } from 'vue';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { getAuthorizedHostGroup, grantHostGroup } from '@/api/asset/asset-data-grant'; import { getAuthorizedHostGroup, grantHostGroup } from '@/api/asset/asset-data-grant';
import { useCacheStore } from '@/store';
import { flatNodeKeys } from '@/utils/tree';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import HostGroupTree from '@/components/asset/host-group/tree/index.vue'; import HostGroupTree from '@/components/asset/host-group/tree/index.vue';
import HostList from './host-list.vue'; import HostList from './host-list.vue';
@@ -37,12 +43,27 @@
type: string; type: string;
}>(); }>();
const cacheStore = useCacheStore();
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
const tree = ref();
const authorizedGroups = ref<Array<number>>([]); const authorizedGroups = ref<Array<number>>([]);
const checkedGroups = ref<Array<number>>([]); const checkedGroups = ref<Array<number>>([]);
const selectedGroup = ref<TreeNodeData>({}); const selectedGroup = ref<TreeNodeData>({});
// 点击分组
const clickGroup = (groups: Array<number>) => {
if (groups && groups.length) {
const group = groups[0];
const index = checkedGroups.value.findIndex((s) => s === group);
if (index === -1) {
checkedGroups.value.push(group);
} else {
checkedGroups.value.splice(index, 1);
}
}
};
// 获取授权列表 // 获取授权列表
const fetchAuthorizedData = async (request: AssetAuthorizedDataQueryRequest) => { const fetchAuthorizedData = async (request: AssetAuthorizedDataQueryRequest) => {
setLoading(true); setLoading(true);
@@ -74,6 +95,24 @@
await fetchAuthorizedData(request); await fetchAuthorizedData(request);
}; };
// 全选
const selectAll = async () => {
// 从缓存中查询全部分组
const groups = await cacheStore.loadHostGroups();
const groupKeys: number[] = [];
flatNodeKeys(groups, groupKeys);
checkedGroups.value = groupKeys;
};
// 反选
const reverseSelect = async () => {
// 从缓存中查询全部分组
const groups = await cacheStore.loadHostGroups();
const groupKeys: number[] = [];
flatNodeKeys(groups, groupKeys);
checkedGroups.value = groupKeys.filter(s => !checkedGroups.value.includes(s));
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -2,7 +2,9 @@
<grant-layout :type="type" <grant-layout :type="type"
:loading="loading" :loading="loading"
@fetch="fetchAuthorizedData" @fetch="fetchAuthorizedData"
@grant="doGrant"> @grant="doGrant"
@select-all="selectAll"
@reverse="reverseSelect">
<!-- 主机身份表格 --> <!-- 主机身份表格 -->
<a-table row-key="id" <a-table row-key="id"
class="host-identity-main-table" class="host-identity-main-table"
@@ -102,6 +104,17 @@
await fetchAuthorizedData(request); await fetchAuthorizedData(request);
}; };
// 全选
const selectAll = () => {
selectedKeys.value = hostIdentities.value.map(s => s.id);
};
// 反选
const reverseSelect = () => {
selectedKeys.value = hostIdentities.value.map(s => s.id)
.filter(s => !selectedKeys.value.includes(s));
};
// 点击行 // 点击行
const clickRow = ({ id }: TableData) => { const clickRow = ({ id }: TableData) => {
const index = selectedKeys.value.indexOf(id); const index = selectedKeys.value.indexOf(id);
@@ -112,7 +125,7 @@
} }
}; };
// 初始化数据 // 初始化身份数据
onMounted(async () => { onMounted(async () => {
setLoading(true); setLoading(true);
try { try {
@@ -124,7 +137,7 @@
} }
}); });
// 初始化数据 // 初始化秘钥数据
onMounted(async () => { onMounted(async () => {
// 加载主机秘钥 // 加载主机秘钥
hostKeys.value = await cacheStore.loadHostKeys(); hostKeys.value = await cacheStore.loadHostKeys();

View File

@@ -2,7 +2,9 @@
<grant-layout :type="type" <grant-layout :type="type"
:loading="loading" :loading="loading"
@fetch="fetchAuthorizedData" @fetch="fetchAuthorizedData"
@grant="doGrant"> @grant="doGrant"
@select-all="selectAll"
@reverse="reverseSelect">
<!-- 主机秘钥表格 --> <!-- 主机秘钥表格 -->
<a-table row-key="id" <a-table row-key="id"
class="host-key-main-table" class="host-key-main-table"
@@ -78,6 +80,17 @@
await fetchAuthorizedData(request); await fetchAuthorizedData(request);
}; };
// 全选
const selectAll = () => {
selectedKeys.value = hostKeys.value.map(s => s.id);
};
// 反选
const reverseSelect = () => {
selectedKeys.value = hostKeys.value.map(s => s.id)
.filter(s => !selectedKeys.value.includes(s));
};
// 点击行 // 点击行
const clickRow = ({ id }: TableData) => { const clickRow = ({ id }: TableData) => {
const index = selectedKeys.value.indexOf(id); const index = selectedKeys.value.indexOf(id);

View File

@@ -42,6 +42,7 @@
<host-group-tree outer-class="tree-card-main" <host-group-tree outer-class="tree-card-main"
ref="tree" ref="tree"
:loading="loading" :loading="loading"
:editable="true"
@set-loading="setLoading" @set-loading="setLoading"
@selected-node="selectGroup" /> @selected-node="selectGroup" />
</div> </div>

View File

@@ -74,7 +74,6 @@
<!-- 主机标签 --> <!-- 主机标签 -->
<a-form-item field="tags" label="主机标签"> <a-form-item field="tags" label="主机标签">
<tag-multi-selector v-model="formModel.tags" <tag-multi-selector v-model="formModel.tags"
:allowCreate="false"
:limit="0" :limit="0"
type="HOST" type="HOST"
:tagColor="tagColor" :tagColor="tagColor"

View File

@@ -29,7 +29,6 @@
<a-form-item field="tags" label="主机标签"> <a-form-item field="tags" label="主机标签">
<tag-multi-selector v-model="formModel.tags" <tag-multi-selector v-model="formModel.tags"
ref="tagSelector" ref="tagSelector"
:allowCreate="false"
:limit="0" :limit="0"
type="HOST" type="HOST"
:tagColor="tagColor" :tagColor="tagColor"

View File

@@ -31,7 +31,7 @@
import CommandSnippetListItem from './command-snippet-list-item.vue'; import CommandSnippetListItem from './command-snippet-list-item.vue';
defineProps<{ defineProps<{
snippet: CommandSnippetWrapperResponse snippet: CommandSnippetWrapperResponse;
}>(); }>();
// 计算总量 // 计算总量

View File

@@ -121,7 +121,7 @@
import { openUpdateSnippetKey, removeSnippetKey } from '../types/const'; import { openUpdateSnippetKey, removeSnippetKey } from '../types/const';
const props = defineProps<{ const props = defineProps<{
item: CommandSnippetQueryResponse item: CommandSnippetQueryResponse;
}>(); }>();
const { getAndCheckCurrentSshSession } = useTerminalStore(); const { getAndCheckCurrentSshSession } = useTerminalStore();

View File

@@ -28,9 +28,9 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { HostQueryResponse } from '@/api/asset/host';
import type { HostGroupQueryResponse } from '@/api/asset/host-group';
import { computed } from 'vue'; import { computed } from 'vue';
import { HostQueryResponse } from '@/api/asset/host';
import { HostGroupQueryResponse } from '@/api/asset/host-group';
import HostListView from './host-list-view.vue'; import HostListView from './host-list-view.vue';
const props = defineProps<{ const props = defineProps<{

View File

@@ -23,18 +23,18 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
import type { HostQueryResponse } from '@/api/asset/host';
import { computed, onMounted, provide, ref, watch } from 'vue'; import { computed, onMounted, provide, ref, watch } from 'vue';
import { NewConnectionType, openSettingModalKey } from '../../types/terminal.const'; import { NewConnectionType, openSettingModalKey } from '../../types/terminal.const';
import { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
import { HostQueryResponse } from '@/api/asset/host';
import HostGroupView from './host-group-view.vue'; import HostGroupView from './host-group-view.vue';
import HostListView from './host-list-view.vue'; import HostListView from './host-list-view.vue';
import HostSettingModal from '../setting/extra/host-setting-modal.vue'; import HostSettingModal from '../setting/extra/host-setting-modal.vue';
const props = defineProps<{ const props = defineProps<{
hosts: AuthorizedHostQueryResponse, hosts: AuthorizedHostQueryResponse;
filterValue: string, filterValue: string;
newConnectionType: string newConnectionType: string;
}>(); }>();
const hostList = ref<Array<HostQueryResponse>>([]); const hostList = ref<Array<HostQueryResponse>>([]);

View File

@@ -3,8 +3,7 @@
ref="formRef" ref="formRef"
label-align="right" label-align="right"
:label-col-props="{ span: 6 }" :label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }" :wrapper-col-props="{ span: 18 }">
:rules="{}">
<!-- 验证方式 --> <!-- 验证方式 -->
<a-form-item field="authType" label="验证方式"> <a-form-item field="authType" label="验证方式">
<a-radio-group type="button" <a-radio-group type="button"

View File

@@ -80,7 +80,7 @@
defineProps<{ defineProps<{
title: string; title: string;
type: number; type: number;
items: Array<TerminalShortcutKeyEditable> items: Array<TerminalShortcutKeyEditable>;
}>(); }>();
const emits = defineEmits(['setEditable', 'clearEditable', 'updateEnabled']); const emits = defineEmits(['setEditable', 'clearEditable', 'updateEnabled']);

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="terminal-example" ref="terminal"></div> <div class="terminal-example" ref="terminal"/>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -14,7 +14,7 @@
import { onMounted, onUnmounted, ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
const props = defineProps<{ const props = defineProps<{
schema: TerminalThemeSchema | Record<string, any> schema: TerminalThemeSchema | Record<string, any>;
}>(); }>();
const terminal = ref(); const terminal = ref();

View File

@@ -79,7 +79,7 @@
import SftpUploadModal from './sftp-upload-modal.vue'; import SftpUploadModal from './sftp-upload-modal.vue';
const props = defineProps<{ const props = defineProps<{
tab: TerminalTabItem tab: TerminalTabItem;
}>(); }>();
const { preference, sessionManager, transferManager } = useTerminalStore(); const { preference, sessionManager, transferManager } = useTerminalStore();

View File

@@ -79,7 +79,7 @@
import XtermSearchModal from '@/components/xtrem/search-modal/index.vue'; import XtermSearchModal from '@/components/xtrem/search-modal/index.vue';
const props = defineProps<{ const props = defineProps<{
tab: TerminalTabItem tab: TerminalTabItem;
}>(); }>();
const { getDictValue } = useDictStore(); const { getDictValue } = useDictStore();

View File

@@ -60,8 +60,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { UserQueryResponse, LoginHistoryQueryResponse } from '@/api/user/user'; import type { UserQueryResponse, LoginHistoryQueryResponse } from '@/api/user/user';
import useLoading from '@/hooks/loading';
import { ref, onBeforeMount } from 'vue'; import { ref, onBeforeMount } from 'vue';
import useLoading from '@/hooks/loading';
import { ResultStatus } from '../types/const'; import { ResultStatus } from '../types/const';
import { getCurrentLoginHistory } from '@/api/user/mine'; import { getCurrentLoginHistory } from '@/api/user/mine';
import { getLoginHistory } from '@/api/user/user'; import { getLoginHistory } from '@/api/user/user';

View File

@@ -49,8 +49,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { UserUpdateRequest, UserQueryResponse } from '@/api/user/user'; import type { UserUpdateRequest, UserQueryResponse } from '@/api/user/user';
import useLoading from '@/hooks/loading';
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import useLoading from '@/hooks/loading';
import formRules from '../../user/types/form.rules'; import formRules from '../../user/types/form.rules';
import { useUserStore } from '@/store'; import { useUserStore } from '@/store';
import { getCurrentUser, updateCurrentUser } from '@/api/user/mine'; import { getCurrentUser, updateCurrentUser } from '@/api/user/mine';

View File

@@ -68,14 +68,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { UserQueryResponse } from '@/api/user/user'; import type { UserQueryResponse } from '@/api/user/user';
import type { UserSessionQueryResponse } from '@/api/user/user'; import type { UserSessionQueryResponse } from '@/api/user/user';
import useLoading from '@/hooks/loading';
import { ref, onBeforeMount } from 'vue'; import { ref, onBeforeMount } from 'vue';
import { getUserSessionList, offlineUserSession } from '@/api/user/user';
import { getCurrentUserSessionList, offlineCurrentUserSession } from '@/api/user/mine'; import { getCurrentUserSessionList, offlineCurrentUserSession } from '@/api/user/mine';
import { dateFormat } from '@/utils'; import { dateFormat } from '@/utils';
import { isMobile } from '@/utils/is'; import { isMobile } from '@/utils/is';
import { Message } from '@arco-design/web-vue'; import useLoading from '@/hooks/loading';
import usePermission from '@/hooks/permission'; import usePermission from '@/hooks/permission';
import { getUserSessionList, offlineUserSession } from '@/api/user/user'; import { Message } from '@arco-design/web-vue';
const props = defineProps<{ const props = defineProps<{
user?: UserQueryResponse; user?: UserQueryResponse;