diff --git a/src/frontend/devops-manage/src/components/project-user-selector.vue b/src/frontend/devops-manage/src/components/project-user-selector.vue index 0e22c16c869e..511b403027a2 100644 --- a/src/frontend/devops-manage/src/components/project-user-selector.vue +++ b/src/frontend/devops-manage/src/components/project-user-selector.vue @@ -3,7 +3,7 @@ ref="tagInputRef" class="manage-user-selector" clearable - :placeholder="t('输入授权人,选中回车进行校验')" + :placeholder="t('输入交接人,选中回车进行有效性校验')" :search-key="searchKeyArr" save-key="id" display-key="displayName" diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-group-tab.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-group-tab.vue new file mode 100644 index 000000000000..4ae49954db7b --- /dev/null +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-group-tab.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-tab-table.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-tab-table.vue new file mode 100644 index 000000000000..a21ae94148e6 --- /dev/null +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-tab-table.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-tab.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-tab.vue index 5007f17fe2f0..476d9814c319 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-tab.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-tab.vue @@ -57,7 +57,11 @@ 'manage-icon manage-icon-right-shape': !item.activeFlag, 'shape-icon': true, }" /> - + {{item.resourceTypeName}} ({{ item.resourceType }}) {{item.count}}

@@ -101,40 +105,13 @@ import { useI18n } from 'vue-i18n'; import { defineProps, defineEmits, computed } from 'vue'; import userGroupTable from '@/store/userGroupTable'; import TabTable from './tab-table.vue'; -import pipelineIcon from '../../../svg/color-logo-pipeline.svg'; -import codelibIcon from '../../../svg/color-logo-codelib.svg'; -import codeccIcon from '../../../svg/color-logo-codecc.svg'; -import environmentIcon from '../../../svg/color-logo-environment.svg'; -import experienceIcon from '../../../svg/color-logo-experience.svg'; -import qualityIcon from '../../../svg/color-logo-quality.svg'; -import ticketIcon from '../../../svg/color-logo-ticket.svg'; -import turboIcon from '../../../svg/color-logo-turbo.svg'; +import userDetailGroupTable from '@/store/userDetailGroupTable'; const { t } = useI18n(); const groupTableStore = userGroupTable(); const projectTable = computed(() => props.sourceList.find(item => item.resourceType == 'project')); const sourceTable= computed(() => props.sourceList.filter(item => item.resourceType != 'project')); - -const getServiceIcon = (type) => { - const iconMap = { - 'pipeline': pipelineIcon, - 'pipeline_group': pipelineIcon, - 'repertory': codelibIcon, - 'credential': ticketIcon, - 'cert': ticketIcon, - 'environment': environmentIcon, - 'env_node': pipelineIcon, - 'codecc_task': codeccIcon, - 'codecc_rule_set': codeccIcon, - 'codecc_ignore_type': codeccIcon, - 'experience_task': experienceIcon, - 'experience_group': experienceIcon, - 'rule': qualityIcon, - 'quality_group': qualityIcon, - 'pipeline_template': pipelineIcon, - } - return iconMap[type] -} +const detailGroupTable = userDetailGroupTable(); const props = defineProps({ isShowOperation: { diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue index 8ad3a587c8e6..031c7125d1ac 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue @@ -269,119 +269,139 @@ + diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/tab-table.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/tab-table.vue index 48ef5807468d..ca8a041b0a50 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/tab-table.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/tab-table.vue @@ -37,7 +37,7 @@ {{ row.resourceName }} @@ -49,7 +49,7 @@
{{ row.unableMessage }} @@ -162,6 +162,12 @@ import { timeFormatter } from '@/common/util.ts' import useManageAside from "@/store/manageAside"; import { storeToRefs } from 'pinia'; +const LINKABLE_RESOURCE_TYPES = ['codecc_task', 'pipeline', 'pipeline_group']; +const URL_TEMPLATES = { + pipeline: (projectId, row) => `${location.origin}/console/pipeline/${projectId}/${row.resourceCode}/history/permission/?groupId=${row.groupId}`, + pipeline_group: (projectId, row) => `${location.origin}/console/pipeline/${projectId}/list/listAuth/${row.resourceCode}/${row.resourceName}?groupId=${row.groupId}`, + codecc_task: (projectId, row) => `${location.origin}/console/codecc/${projectId}/task/${row.resourceCode}/settings/authority?groupId=${row.groupId}` +}; const props = defineProps({ isShowOperation: { type: Boolean, @@ -207,19 +213,18 @@ const refTable = ref(null); const curSelectedData = ref([]); const isCurrentAll = ref(false); const selectedResourceCode = computed(() => isCurrentAll.value ? tableList.value.map(i => i.resourceCode) : curSelectedData.value.map(i => i.resourceCode)); -const resourceType = computed(() => props.resourceType); -const groupTotal = computed(() => props.groupTotal); const remainingCount = computed(()=> props.groupTotal - props.data.length); const TOOLTIPS_CONTENT = { - UNIQUE_MANAGER: t('唯一管理员,不可移出。请添加新的管理员后再移出'), - UNIQUE_OWNER: t('唯一拥有者,不可移出。请添加新的拥有者后再移出'), + UNIQUE_MANAGER: t('唯一管理员,不可直接移出。请交接或停用项目'), + UNIQUE_OWNER: t('唯一拥有者,不可直接移出。请交接或删除资源'), TEMPLATE: t('通过用户组加入,不可直接移出。如需调整,请编辑用户组') } const projectId = computed(() => route.params?.projectCode || route.query?.projectCode); const tableList = computed(() => props.data.map(item => ({ ...item, unableMessage: getUnableMessage(item), - isExpired: item.expiredAt < Date.now() && item.removeMemberButtonControl === 'OTHER' + isExpired: item.expiredAt < Date.now() && item.removeMemberButtonControl === 'OTHER', + isLinkable: LINKABLE_RESOURCE_TYPES.includes(item.resourceType) })) ); const border = ['row', 'outer']; @@ -266,7 +271,7 @@ function getUnableMessage(row){ */ function handleSelectAll({checked}) { if (checked) { - emit('getSelectList', tableList.value, resourceType.value); + emit('getSelectList', tableList.value, props.resourceType); curSelectedData.value = tableList.value; isCurrentAll.value = false; } else { @@ -278,7 +283,7 @@ function handleSelectAll({checked}) { */ function handleSelectionChange() { const selectionList = refTable.value.getSelection(); - emit('getSelectList', selectionList, resourceType.value); + emit('getSelectList', selectionList, props.resourceType); curSelectedData.value = selectionList; isCurrentAll.value = props.data.length === selectionList }; @@ -290,7 +295,7 @@ function handleSelectAllData() { if (selectLength != props.data.length) { refTable.value.toggleAllSelection(); } - emit('handleSelectAllData', resourceType.value) + emit('handleSelectAllData', props.resourceType) isCurrentAll.value = true; } /** @@ -299,55 +304,49 @@ function handleSelectAllData() { function handleClear() { refTable.value.clearSelection(); isCurrentAll.value = false; - emit('handleClear', resourceType.value); + curSelectedData.value = []; + emit('handleClear', props.resourceType); } /** * 续期按钮点击 * @param row 行数据 */ function handleRenewal(row) { - emit('handleRenewal', row, resourceType.value, refTable.value); + emit('handleRenewal', row, props.resourceType, refTable.value); } /** * 移交按钮点击 * @param row 行数据 */ function handleHandOver(row, index) { - emit('handleHandOver', row, resourceType.value, index); + emit('handleHandOver', row, props.resourceType, index); } /** * 移出按钮点击 * @param row 行数据 */ function handleRemove(row, index) { - emit('handleRemove', row, resourceType.value, index); + emit('handleRemove', row, props.resourceType, index); } /** * 加载更多 */ function handleLoadMore() { - emit('handleLoadMore', resourceType.value); + emit('handleLoadMore', props.resourceType); } function pageLimitChange(limit) { - emit('pageLimitChange',limit, resourceType.value); + emit('pageLimitChange',limit, props.resourceType); } function pageValueChange(value) { - emit('pageValueChange',value, resourceType.value); + emit('pageValueChange',value, props.resourceType); } function handleToResourcePage (row) { - if (!(['codecc_task', 'pipeline', 'pipeline_group'].includes(row.resourceType))) return - switch (row.resourceType) { - case 'pipeline': - window.open(`${location.origin}/console/pipeline/${projectId.value}/${row.resourceCode}/history/permission/?groupId=${row.groupId}`) - return - case 'pipeline_group': - window.open(`${location.origin}/console/pipeline/${projectId.value}/list/listAuth/${row.resourceCode}/${row.resourceName}?groupId=${row.groupId}`) - return - case 'codecc_task': - window.open(`${location.origin}/console/codecc/${projectId.value}/task/${row.resourceCode}/settings/authority?groupId=${row.groupId}`) - return + if (!row.isLinkable) return + const url = URL_TEMPLATES[row.resourceType]?.(projectId.value, row); + if (url) { + window.open(url); } } diff --git a/src/frontend/devops-manage/src/http/api.ts b/src/frontend/devops-manage/src/http/api.ts index c2bcaeca4f4f..d1f445e56842 100644 --- a/src/frontend/devops-manage/src/http/api.ts +++ b/src/frontend/devops-manage/src/http/api.ts @@ -373,5 +373,23 @@ export default { */ getIsDirectRemove(projectId: string, groupId: number, params: any) { return http.DELETE(`${IAM_PERFIX}/member/${projectId}/single/${groupId}/${OPERATE_CHANNEL}/remove`, params); - } + }, + /** + * 获取资源授权管理数量 + */ + getResourceType2CountOfHandover(params: any) { + return http.post(`${USER_PERFIX}/auth/handover/getResourceType2CountOfHandover`, params); + }, + /** + * 获取交接单中授权相关 + */ + listAuthorizationsOfHandover(params: any) { + return http.post(`${USER_PERFIX}/auth/handover/listAuthorizationsOfHandover`, params); + }, + /** + * 获取交接单中用户组相关 + */ + listGroupsOfHandover(params: any) { + return http.post(`${USER_PERFIX}/auth/handover/listGroupsOfHandover`, params); + }, }; diff --git a/src/frontend/devops-manage/src/store/userDetailGroupTable.ts b/src/frontend/devops-manage/src/store/userDetailGroupTable.ts new file mode 100644 index 000000000000..468fcb4dd15b --- /dev/null +++ b/src/frontend/devops-manage/src/store/userDetailGroupTable.ts @@ -0,0 +1,239 @@ +import http from '@/http/api'; +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { deepEqual } from "@/utils/util.js"; +import pipelineIcon from '@/components/user-group/svg/color-logo-pipeline.svg'; +import codelibIcon from '@/components/user-group/svg/color-logo-codelib.svg'; +import codeccIcon from '@/components/user-group/svg/color-logo-codecc.svg'; +import environmentIcon from '@/components/user-group/svg/color-logo-environment.svg'; +import experienceIcon from '@/components/user-group/svg/color-logo-experience.svg'; +import qualityIcon from '@/components/user-group/svg/color-logo-quality.svg'; +import ticketIcon from '@/components/user-group/svg/color-logo-ticket.svg'; +import turboIcon from '@/components/user-group/svg/color-logo-turbo.svg'; + +export enum HandoverType { + AUTHORIZATION = 'AUTHORIZATION', + GROUP = 'GROUP' +} +interface GroupTableType { + groupName?: String; + iamGroupId?: Number; + projectCode?: String; + resourceCode: String; + resourceName: String; + handoverType?: HandoverType; + handoverFrom?: String; + groupDesc?: String; + +}; +interface Pagination { + limit: number; + current: number; + count: number; +} + +interface SourceType { + activeFlag?: boolean; + count?: number; + pagination: Pagination; + resourceType: string; + resourceTypeName?: string; + tableData: GroupTableType[]; + tableLoading?: boolean; + type: HandoverType; +} + +interface CollapseListType { + resourceType: string; + resourceTypeName: string; + count: number; + type: HandoverType; +} + +interface DetailParams { + projectCode?: String; + resourceType?: String, + batchOperateType?: String; + previewConditionReq?: String; + queryChannel?: String; + page?: Number, + pageSize?: Number, + flowNo?: String +} + +export default defineStore('userDetailGroupTable', () => { + const isLoading = ref(true); + const detailSourceList = ref([]); + const collapseList = ref([]); + let currentRequestId = 0; + const detailParams = ref(); + + /** + * 获取资源授权管理数量 + */ + async function getCollapseList() { + try { + const params = detailParams.value; + const res = await http.getResourceType2CountOfHandover(params); + collapseList.value = res; + detailSourceList.value = collapseList.value.map(item => ({ + ...item, + tableLoading: false, + pagination: { count: 0, current: 1, limit: 10 }, + tableData: [], + })); + } catch (error) { + console.log(error); + } + } + + async function fetchListData(fetchFunction: (item: DetailParams) => Promise, item: SourceType) { + if (!collapseList.value.some(collapseItem => collapseItem.resourceType === item.resourceType)) { + return {}; + } + try { + const params: DetailParams = { + ...detailParams.value, + resourceType: item.resourceType, + page: item.pagination.current, + pageSize: item.pagination.limit, + }; + return await fetchFunction(params); + } catch (error) { + console.log(error); + return {}; + } + } + /** + * 获取交接单中授权相关 + * @param resourceType 资源类型 + */ + async function getAuthorizationsList(item: SourceType) { + return fetchListData(http.listAuthorizationsOfHandover, item); + } + /** + * 获取交接单中用户组相关 + * @param resourceType 资源类型 + */ + async function getGroupList(item: SourceType) { + return fetchListData(http.listGroupsOfHandover, item); + } + /** + * 获取页面数据 + */ + async function fetchDetailList(projectIdParam: DetailParams) { + // 初始化数据 + detailSourceList.value = []; + + detailParams.value = projectIdParam; + const requestId = ++currentRequestId; + + // 加载权限管理数量 + await getCollapseList(); + + try { + isLoading.value = true; + + const [authorizationItem, userGroupItem] = [ + detailSourceList.value.find(item => item.type === HandoverType.AUTHORIZATION), + detailSourceList.value.find(item => item.type === HandoverType.GROUP) + ]; + // 同时获取授权列表和用户组列表 + const [authorizationData, userGroupData] = await Promise.all([ + authorizationItem ? getAuthorizationsList(authorizationItem) : Promise.resolve(null), + userGroupItem ? getGroupList(userGroupItem) : Promise.resolve(null) + ]); + + if (currentRequestId === requestId) { + detailSourceList.value.forEach(item => { + if (authorizationData && Array.isArray(authorizationData.records) && deepEqual(item, authorizationItem)) { + item.tableData = authorizationData.records; + item.activeFlag = true; + } else if (userGroupData && Array.isArray(userGroupData.records) && deepEqual(item, userGroupItem)) { + item.tableData = userGroupData.records; + item.activeFlag = true; + } + item.pagination.count = item.count ?? 0; + }); + } + } catch (error) { + console.log(error); + } finally { + isLoading.value = false; + } + } + + async function handlePaginationChange(fetchFunction: (item: SourceType) => Promise, item: SourceType) { + try { + item.tableLoading = true; + const res = await fetchFunction(item); + item.tableData = res.records; + } catch (error) { + console.log(error); + } finally { + item.tableLoading = false; + } + } + /** + * 折叠面板调用接口获取表格数据 + */ + async function detailCollapseClick(resourceType: string, flag: HandoverType) { + const item = detailSourceList.value.find(item => item.resourceType === resourceType && item.type === flag); + if (!item || !item.count || item.tableData.length) return; + + item.pagination.current = 1; + await handlePaginationChange(flag === HandoverType.GROUP ? getGroupList : getAuthorizationsList, item); + } + /** + * 切换表格每页显示条数时 + */ + async function detailPageLimitChange(limit: number, resourceType: string, flag: HandoverType) { + const item = detailSourceList.value.find(item => item.resourceType === resourceType && item.type === flag); + if (item) { + item.pagination.limit = limit; + await handlePaginationChange(flag === HandoverType.GROUP ? getGroupList : getAuthorizationsList, item); + } + } + /** + * 切换表格分页时 + */ + async function detailPageValueChange(value: number, resourceType: string, flag: HandoverType) { + const item = detailSourceList.value.find(item => item.resourceType === resourceType && item.type === flag); + if (item) { + item.pagination.current = value; + await handlePaginationChange(flag === HandoverType.GROUP ? getGroupList : getAuthorizationsList, item); + } + } + + function getServiceIcon (type: string) { + const iconMap = { + 'pipeline': pipelineIcon, + 'pipeline_group': pipelineIcon, + 'repertory': codelibIcon, + 'credential': ticketIcon, + 'cert': ticketIcon, + 'environment': environmentIcon, + 'env_node': pipelineIcon, + 'codecc_task': codeccIcon, + 'codecc_rule_set': codeccIcon, + 'codecc_ignore_type': codeccIcon, + 'experience_task': experienceIcon, + 'experience_group': experienceIcon, + 'rule': qualityIcon, + 'quality_group': qualityIcon, + 'pipeline_template': pipelineIcon, + } + return iconMap[type] + } + + return { + isLoading, + detailSourceList, + collapseList, + fetchDetailList, + detailCollapseClick, + detailPageLimitChange, + detailPageValueChange, + getServiceIcon, + }; +}); diff --git a/src/frontend/devops-manage/src/store/userGroupTable.ts b/src/frontend/devops-manage/src/store/userGroupTable.ts index 282f80a4e6f6..d75dcf96e613 100644 --- a/src/frontend/devops-manage/src/store/userGroupTable.ts +++ b/src/frontend/devops-manage/src/store/userGroupTable.ts @@ -277,7 +277,7 @@ export default defineStore('userGroupTable', () => { count: sourceItem.isAll ? sourceItem.count! : tableData.length }, ...sourceItem, - tableData: tableData.slice(0,11), + tableData: tableData, count: sourceItem.isAll ? sourceItem.count! : tableData.length, ...(!sourceItem.isAll && { groupIds: tableData.map(item => ({ id: item.groupId, memberType: item.memberType })) }), ...(!sourceItem.isAll && { isRemotePagination: false }), diff --git a/src/frontend/devops-manage/src/utils/util.js b/src/frontend/devops-manage/src/utils/util.js index ce554a051685..4876c6c4d0c6 100644 --- a/src/frontend/devops-manage/src/utils/util.js +++ b/src/frontend/devops-manage/src/utils/util.js @@ -15,3 +15,25 @@ export function convertTime (ms) { return `${time.getFullYear()}-${prezero(time.getMonth() + 1)}-${prezero(time.getDate())} ${prezero(time.getHours())}:${prezero(time.getMinutes())}:${prezero(time.getSeconds())}` } +export function deepEqual(obj1, obj2) { + if (obj1 === obj2) return true; + + if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 === null || obj2 === null) { + return false; + } + + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) return false; + + const keys2Set = new Set(keys2); + + for (const key of keys1) { + if (!keys2Set.has(key) || !deepEqual(obj1[key], obj2[key])) { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/src/frontend/devops-permission/src/components/permission-manage/detail-group-tab.vue b/src/frontend/devops-permission/src/components/permission-manage/detail-group-tab.vue index a0154e8d9f9f..da82083309e3 100644 --- a/src/frontend/devops-permission/src/components/permission-manage/detail-group-tab.vue +++ b/src/frontend/devops-permission/src/components/permission-manage/detail-group-tab.vue @@ -18,16 +18,12 @@ >