优化资产授权交互逻辑.

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"
placeholder="请选择主机分组"
:allow-clear="true"
:allow-search="true">
</a-tree-select>
:allow-search="true" />
</template>
<script lang="ts">

View File

@@ -2,15 +2,17 @@
<a-scrollbar>
<!-- 分组树 -->
<a-tree v-if="treeData.length"
v-model:checked-keys="checkedKeys"
ref="tree"
class="tree-container block-tree"
v-model:checked-keys="checkedKeys"
:class="[ editable ? 'editable-tree' : '' ]"
:blockNode="true"
:draggable="editable"
:data="treeData"
:draggable="editable"
:checkable="checkable"
:check-strictly="true"
@drop="moveGroup">
@drop="moveGroup"
@select="(s) => emits('onSelected', s)">
<!-- 标题 -->
<template #title="node">
<!-- 修改名称输入框 -->
@@ -40,8 +42,8 @@
<span v-else
class="node-title-wrapper"
@click="() => emits('selectedNode', node)">
{{ node.title }}
</span>
{{ node.title }}
</span>
</template>
<!-- 操作图标 -->
<template #drag-icon="{ node }">
@@ -51,8 +53,8 @@
class="tree-icon"
title="重命名"
@click="rename(node.title, node.key)">
<icon-edit />
</span>
<icon-edit />
</span>
<!-- 删除 -->
<a-popconfirm content="确认删除这条记录吗?"
position="left"
@@ -69,8 +71,8 @@
class="tree-icon"
title="新增"
@click="addChildren(node)">
<icon-plus />
</span>
<icon-plus />
</span>
</a-space>
</template>
</a-tree>
@@ -97,13 +99,16 @@
import { isString } from '@/utils/is';
import { useCacheStore } from '@/store';
const props = defineProps<Partial<{
const props = withDefaults(defineProps<Partial<{
loading: boolean;
editable: boolean;
checkable: boolean;
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();
@@ -331,6 +336,29 @@
width: max-content;
user-select: none;
overflow: hidden;
:deep(.arco-tree-node-title) {
padding: 0;
}
.node-title-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
padding-left: 8px;
}
.tree-icon {
font-size: 12px;
color: rgb(var(--primary-6));
}
}
.editable-tree {
:deep(.arco-tree-node-title) {
padding: 0 80px 0 0;
}
}
.empty-container {
@@ -342,21 +370,4 @@
color: var(--color-text-3);
}
:deep(.arco-tree-node-title) {
padding: 0 80px 0 0;
}
.node-title-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
padding-left: 8px;
}
.tree-icon {
font-size: 12px;
color: rgb(var(--primary-6));
}
</style>

View File

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

View File

@@ -4,7 +4,7 @@
:title="title"
:width="960"
:top="80"
:body-style="{padding: '0 8px'}"
:body-style="{ padding: '0 8px' }"
:align-center="false"
:draggable="true"
:mask-closable="false"

View File

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

View File

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

View File

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

View File

@@ -2,15 +2,19 @@
<grant-layout :type="type"
:loading="loading"
@fetch="fetchAuthorizedData"
@grant="doGrant">
@grant="doGrant"
@select-all="selectAll"
@reverse="reverseSelect">
<!-- 分组 -->
<host-group-tree v-model:checked-keys="checkedGroups"
ref="tree"
outer-class="group-main-tree"
:checkable="true"
:editable="false"
:loading="loading"
@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" />
</grant-layout>
@@ -28,6 +32,8 @@
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
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 HostGroupTree from '@/components/asset/host-group/tree/index.vue';
import HostList from './host-list.vue';
@@ -37,12 +43,27 @@
type: string;
}>();
const cacheStore = useCacheStore();
const { loading, setLoading } = useLoading();
const tree = ref();
const authorizedGroups = ref<Array<number>>([]);
const checkedGroups = ref<Array<number>>([]);
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) => {
setLoading(true);
@@ -74,6 +95,24 @@
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>
<style lang="less" scoped>

View File

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

View File

@@ -2,7 +2,9 @@
<grant-layout :type="type"
:loading="loading"
@fetch="fetchAuthorizedData"
@grant="doGrant">
@grant="doGrant"
@select-all="selectAll"
@reverse="reverseSelect">
<!-- 主机秘钥表格 -->
<a-table row-key="id"
class="host-key-main-table"
@@ -78,6 +80,17 @@
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 index = selectedKeys.value.indexOf(id);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@
:content="action.content">
<div v-if="action.visible !== false"
class="terminal-sidebar-icon-wrapper"
:class="[wrapperClass]">
:class="[ wrapperClass ]">
<div class="terminal-sidebar-icon"
:class="[
iconClass,

View File

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

View File

@@ -23,18 +23,18 @@
</script>
<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 { 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 HostListView from './host-list-view.vue';
import HostSettingModal from '../setting/extra/host-setting-modal.vue';
const props = defineProps<{
hosts: AuthorizedHostQueryResponse,
filterValue: string,
newConnectionType: string
hosts: AuthorizedHostQueryResponse;
filterValue: string;
newConnectionType: string;
}>();
const hostList = ref<Array<HostQueryResponse>>([]);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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