From 5bd7dc11e1d94769011aa5d400197a7a5386f5db Mon Sep 17 00:00:00 2001 From: lhzzforever Date: Wed, 8 Nov 2023 15:28:52 +0800 Subject: [PATCH 01/27] =?UTF-8?q?fix:=20=E5=8D=95=E7=8B=AC=E5=A4=84?= =?UTF-8?q?=E7=90=86staff=E8=A7=92=E8=89=B2=E5=9C=A8=E7=BB=84=E7=BB=87?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E9=80=89=E6=8B=A9=E5=99=A8=E7=9A=84=E4=BA=A4?= =?UTF-8?q?=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/dialog-infinite-list/index.vue | 5 +- frontend/src/store/modules/organization.js | 2 +- .../views/group/components/iam-add-member.vue | 250 ++++++++---------- .../add-member-boundary/index.vue | 197 ++++++++------ 4 files changed, 231 insertions(+), 223 deletions(-) diff --git a/frontend/src/components/dialog-infinite-list/index.vue b/frontend/src/components/dialog-infinite-list/index.vue index 0d54db143..61975f4e6 100644 --- a/frontend/src/components/dialog-infinite-list/index.vue +++ b/frontend/src/components/dialog-infinite-list/index.vue @@ -164,6 +164,9 @@ }, selectedNode () { return (payload) => { + if (payload.disabled) { + return true; + } if (this.hasSelectedDepartments.length || this.hasSelectedUsers.length) { payload.is_selected = this.hasSelectedDepartments.map( item => item.id.toString()).includes(payload.id.toString()) @@ -288,7 +291,7 @@ }, async handleNodeClick (node) { - const isDisabled = this.isDisabled || (this.getGroupAttributes && this.getGroupAttributes().source_from_role && node.type === 'depart'); + const isDisabled = node.disabled || this.isDisabled || (this.getGroupAttributes && this.getGroupAttributes().source_from_role && node.type === 'depart'); if (!isDisabled) { if (this.isStaff) { node.is_selected = !node.is_selected; diff --git a/frontend/src/store/modules/organization.js b/frontend/src/store/modules/organization.js index 22961cfd5..fdf49ac44 100644 --- a/frontend/src/store/modules/organization.js +++ b/frontend/src/store/modules/organization.js @@ -208,7 +208,7 @@ export default { * @return {Promise} promise 对象 */ getSubjectScopeCheck ({ commit, state, dispatch }, config) { - return http.post(`${AJAX_URL_PREFIX}/roles/subject_scope_check/`, config); + return http.post(`${AJAX_URL_PREFIX}/roles/subject_scope_check/?${+new Date()}`, config); } } }; diff --git a/frontend/src/views/group/components/iam-add-member.vue b/frontend/src/views/group/components/iam-add-member.vue index 983e815f4..f632980e2 100644 --- a/frontend/src/views/group/components/iam-add-member.vue +++ b/frontend/src/views/group/components/iam-add-member.vue @@ -648,6 +648,9 @@ return (payload) => { return ['depart', 'department'].includes(payload.type) ? payload.name : `${payload.username}(${payload.name})`; }; + }, + isStaff () { + return this.user.role === 'staff'; } }, watch: { @@ -833,7 +836,7 @@ } } }); - this.filterUserList = manualList.filter((item) => !this.filterDepartList.includes(item)); + this.filterUserList = manualList.filter((item) => !this.filterDepartList.includes(item) && item !== ''); }, handleClearManualUser () { @@ -841,6 +844,71 @@ this.manualInputError = false; }, + // 处理同步异步操作数据 + async formatSearchData (data, curData) { + if (data) { + const { users, departments } = data; + if (users && users.length) { + users.forEach((item) => { + item.type = 'user'; + }); + const result = await this.fetchSubjectScopeCheck(users, 'user'); + if (result && result.length) { + const hasSelectedUsers = [...this.hasSelectedUsers, ...this.hasSelectedManualUsers]; + const userTemp = result.filter((item) => { + return !hasSelectedUsers.map((subItem) => + `${subItem.username}&${subItem.name}`).includes(`${item.username}&${item.name}`); + }); + this.hasSelectedUsers.push(...userTemp); + this.hasSelectedManualUsers.push(...userTemp); + // 保存原有格式 + let formatStr = _.cloneDeep(this.manualValue); + const usernameList = result.map((item) => item.username); + usernameList.forEach((item) => { + // 处理既有部门又有用户且不连续相同类型的展示数据 + formatStr = formatStr + .replace(this.evil('/' + item + '(;\\n|\\s\\n|)/'), '') + .replace(/(\s*\r?\n\s*)+/g, '\n') + .replace(';;', ''); + // 处理复制全部用户不相连的两个不在授权范围内的用户存在空字符 + formatStr = formatStr + .split(/;|\n|\s/) + .filter((item) => item !== '' && item !== curData) + .join('\n'); + }); + // 处理只选择全部符合条件的用户,还存在特殊符号的情况 + if (formatStr === '\n' || formatStr === '\s' || formatStr === ';') { + formatStr = ''; + } + this.manualValue = _.cloneDeep(formatStr); + } + } + if (departments && departments.length) { + departments.forEach((item) => { + item.type = 'depart'; + }); + const result = await this.fetchSubjectScopeCheck(departments, 'depart'); + if (result && result.length) { + const hasSelectedDepartments = [...this.hasSelectedDepartments, ...this.hasSelectedManualDepartments]; + const departTemp = result.filter((item) => { + return !hasSelectedDepartments.map((subItem) => + subItem.id.toString()).includes(item.id.toString()); + }); + this.hasSelectedManualDepartments.push(...departTemp); + this.hasSelectedDepartments.push(...departTemp); + // 备份一份粘贴板里的内容,清除组织的数据,在过滤掉组织的数据 + let clipboardValue = _.cloneDeep(this.manualValue); + // 处理不相连的数据之间存在特殊符号的情况 + clipboardValue = clipboardValue + .split(/;|\n|\s/) + .filter((item) => item !== '' && item !== curData) + .join('\n'); + this.manualValue = _.cloneDeep(clipboardValue); + } + } + } + }, + async handleSearchOrgAndUser () { let manualInputValue = _.cloneDeep(this.manualValue.split(/;|\n|\s/)); manualInputValue = manualInputValue.filter((item) => item !== ''); @@ -850,80 +918,20 @@ is_exact: false }; try { - const { data } = await this.$store.dispatch('organization/getSearchOrganizations', params); - const { users, departments } = data; - if (users && users.length) { - users.forEach((item) => { - item.type = 'user'; - }); - const result = await this.fetchSubjectScopeCheck(users, 'user'); - if (result && result.length) { - const hasSelectedUsers = [...this.hasSelectedUsers, ...this.hasSelectedManualUsers]; - const userTemp = result.filter((item) => { - return !hasSelectedUsers.map((subItem) => - `${subItem.username}&${subItem.name}`).includes(`${item.username}&${item.name}`); - }); - this.hasSelectedUsers.push(...userTemp); - this.hasSelectedManualUsers.push(...userTemp); - // 保存原有格式 - let formatStr = _.cloneDeep(this.manualValue); - const usernameList = result.map((item) => item.username); - usernameList.forEach((item) => { - // 处理既有部门又有用户且不连续相同类型的展示数据 - formatStr = formatStr - .replace(this.evil('/' + item + '(;\\n|\\s\\n|)/'), '') - .replace(/(\s*\r?\n\s*)+/g, '\n') - .replace(';;', ''); - // 处理复制全部用户不相连的两个不在授权范围内的用户存在空字符 - formatStr = formatStr - .split(/;|\n|\s/) - .filter((item) => item !== '' && item !== manualInputValue[i]) - .join('\n'); - }); - // 处理只选择全部符合条件的用户,还存在特殊符号的情况 - if (formatStr === '\n' || formatStr === '\s' || formatStr === ';') { - formatStr = ''; - } - this.manualValue = _.cloneDeep(formatStr); - this.manualInputError = !!this.manualValue.length; - } else { - this.manualInputError = true; - } - } - if (departments && departments.length) { - departments.forEach((item) => { - item.type = 'depart'; + if (manualInputValue.length < 10) { + const { data } = await this.$store.dispatch('organization/getSearchOrganizations', params); + await this.formatSearchData(data, manualInputValue[i]); + } else { + this.$store.dispatch('organization/getSearchOrganizations', params).then(async ({ data }) => { + this.formatSearchData(data, manualInputValue[i]); }); - const result = await this.fetchSubjectScopeCheck(departments, 'depart'); - if (result && result.length) { - const hasSelectedDepartments = [...this.hasSelectedDepartments, ...this.hasSelectedManualDepartments]; - const departTemp = result.filter((item) => { - return !hasSelectedDepartments.map((subItem) => - subItem.id.toString()).includes(item.id.toString()); - }); - this.hasSelectedManualDepartments.push(...departTemp); - this.hasSelectedDepartments.push(...departTemp); - // 备份一份粘贴板里的内容,清除组织的数据,在过滤掉组织的数据 - let clipboardValue = _.cloneDeep(this.manualValue); - // 处理不相连的数据之间存在特殊符号的情况 - clipboardValue = clipboardValue - .split(/;|\n|\s/) - .filter((item) => item !== '' && item !== manualInputValue[i]) - .join('\n'); - this.manualValue = _.cloneDeep(clipboardValue); - this.manualInputError = !!this.manualValue.length; - } else { - this.manualInputError = true; - } } } catch (e) { console.error(e); this.messageAdvancedError(e); } } - if (!this.manualValue) { - this.manualInputError = false; - } + this.manualInputError = !!this.manualValue.length; }, async handleAddManualUser () { @@ -980,8 +988,16 @@ formatStr = ''; } this.manualValue = _.cloneDeep(formatStr); + if (this.isStaff) { + this.manualInputError = true; + return; + } this.formatOrgAndUser(); } else { + if (this.isStaff) { + this.manualInputError = true; + return; + } this.formatOrgAndUser(); } } catch (e) { @@ -994,13 +1010,16 @@ // 处理只复制部门或者部门和用户一起复制情况 async formatOrgAndUser () { - if (this.manualValue) { + if (this.manualValue && !this.isStaff) { // 校验查验失败的数据是不是属于部门 const departData = _.cloneDeep(this.manualValue.split(/;|\n|\s/)); const departGroups = this.filterDepartList.filter((item) => departData.includes(item)); - if (departGroups.length && this.getGroupAttributes) { - if (this.getGroupAttributes().source_from_role) { + if (departGroups.length) { + if (this.getGroupAttributes && this.getGroupAttributes().source_from_role) { this.messageWarn(this.$t(`m.common['管理员组不能添加部门']`), 3000); + this.manualTableListStorage = [...this.hasSelectedManualDepartments, ...this.hasSelectedManualUsers]; + this.manualTableList = _.cloneDeep(this.manualTableListStorage); + this.fetchManualTableData(); this.manualInputError = true; return; } @@ -1044,16 +1063,22 @@ .filter((item) => item !== '') .join('\n'); this.manualValue = _.cloneDeep(clipboardValue); - this.manualInputError = !!this.manualValue.length; + // this.manualInputError = !!this.manualValue.length; } else { - this.manualInputError = true; + if (this.isStaff) { + this.manualInputError = true; + return; + } + // this.manualInputError = true; } } else { - this.manualInputError = true; + if (this.isStaff) { + this.manualInputError = true; + return; + } } } - - if (this.manualInputError) { + if (this.manualValue && !this.isStaff) { await this.handleSearchOrgAndUser(); } this.manualTableListStorage = [...this.hasSelectedManualDepartments, ...this.hasSelectedManualUsers]; @@ -1137,7 +1162,6 @@ offset: 0 }; const res = await this.$store.dispatch('userGroup/getUserGroupMemberList', params); - this.defaultDepartments = res.data.results.filter((item) => item.type === 'department'); this.defaultUsers = res.data.results.filter((item) => item.type === 'user'); if (this.isRatingManager) { @@ -1153,12 +1177,12 @@ } }, - fetchCategoriesList () { + async fetchCategoriesList () { try { if (this.isRatingManager) { - this.fetchRoleSubjectScope(false, true); + await this.fetchRoleSubjectScope(false, true); } else { - this.fetchCategories(false, true); + await this.fetchCategories(false, true); } } catch (e) { console.error(e); @@ -1194,34 +1218,6 @@ child.async = child.child_count > 0 || child.member_count > 0; child.isNewMember = false; child.parentNodeId = ''; - // if (child.type === 'user') { - // child.username = child.id; - // } - - // if (this.hasSelectedDepartments.length > 0) { - // child.is_selected = this.hasSelectedDepartments.map(item => item.id).includes(child.id); - // } else { - // child.is_selected = false; - // } - - // if (this.hasSelectedUsers.length > 0) { - // child.is_selected = this.hasSelectedUsers.map(item => item.id).includes(child.id); - // } else { - // child.is_selected = false; - // } - - // if (this.defaultDepartments.length > 0 - // && this.defaultDepartments.map(item => item.id).includes(child.id.toString()) - // ) { - // child.is_selected = true; - // child.disabled = true; - // } - - // if (this.defaultUsers.length && this.defaultUsers.map(item => item.id).includes(child.id)) { - // child.is_selected = true; - // child.disabled = true; - // } - if (child.type === 'user') { child.username = child.id; if (this.hasSelectedUsers.length > 0) { @@ -1304,7 +1300,6 @@ child.isNewMember = false; child.parentNodeId = item.id; child.full_name = `${item.name}:${child.name}`; - if (this.hasSelectedDepartments.length) { child.is_selected = this.hasSelectedDepartments.map((item) => item.id).includes(child.id); } else { @@ -1385,33 +1380,16 @@ if (this.keyword === '') { return; } - - // if (this.searchedResult.length === 1) { - // if (this.searchedDepartment.length === 1) { - // this.hasSelectedDepartments.push(this.searchedDepartment[0]) - // } else { - // this.hasSelectedUsers.push(this.searchedUsers[0]) - // } - // this.keyword = '' - // this.searchedResult.splice(0, this.searchedResult.length, ...[]) - // this.searchedDepartment.splice(0, this.searchedDepartment.length, ...[]) - // this.searchedUsers.splice(0, this.searchedUsers.length, ...[]) - // return - // } - if (this.focusItemIndex !== -1) { this.$refs.searchedResultsRef.setCheckStatusByIndex(); return; } - this.treeList.splice(0, this.treeList.length, ...[]); this.isBeingSearch = true; this.treeLoading = true; - this.searchedResult.splice(0, this.searchedResult.length, ...[]); this.searchedDepartment.splice(0, this.searchedDepartment.length, ...[]); this.searchedUsers.splice(0, this.searchedUsers.length, ...[]); - const defaultDepartIds = [...this.defaultDepartments.map((item) => item.id)]; const defaultUserIds = [...this.defaultUsers.map((item) => item.id)]; const departIds = [...this.hasSelectedDepartments.map((item) => item.id)]; @@ -1514,9 +1492,7 @@ payload.expanded = false; return; } - const curIndex = this.treeList.findIndex((item) => item.id === payload.id); - if (curIndex === -1) { return; } @@ -1538,13 +1514,11 @@ child.isNewMember = false; child.parentNodeId = payload.id; child.full_name = `${payload.full_name}/${child.name}`; - if (this.hasSelectedDepartments.length > 0) { child.is_selected = this.hasSelectedDepartments.map((item) => item.id).includes(child.id); } else { child.is_selected = false; } - if ( this.defaultDepartments.length > 0 && this.defaultDepartments.map((item) => item.id).includes(child.id.toString()) @@ -1554,7 +1528,6 @@ } }); } - if (members.length > 0) { members.forEach((child, childIndex) => { child.visiable = payload.expanded; @@ -1572,7 +1545,6 @@ child.parentNodeId = payload.id; // child.full_name = `${payload.full_name}/${child.name}`; child.full_name = payload.full_name; - // parentNodeId + username 组合成id child.id = `${child.parentNodeId}${child.username}`; if (this.hasSelectedUsers.length > 0) { @@ -1596,17 +1568,12 @@ } }); } - const loadChildren = children.concat([...members]); - treeList.splice(curIndex + 1, 0, ...loadChildren); - this.treeList.splice(0, this.treeList.length, ...treeList); - if (!payload.children) { payload.children = []; } - payload.children.splice(0, payload.children.length, ...loadChildren); } catch (e) { console.error(e); @@ -1770,20 +1737,11 @@ }, async handleSkip () { - // bus.$emit('nav-change', { id: this.$store.getters.navCurRoleId }, 0); - // await this.$store.dispatch('role/updateCurrentRole', { id: 0 }); - // const routeData = this.$router.resolve({ path: `${this.$store.getters.navCurRoleId}/rating-manager-edit`, params: { id: this.$store.getters.navCurRoleId } }); const routeData = this.$router.resolve({ name: 'authorBoundaryEditFirstLevel', params: { id: this.$store.getters.curRoleId } }); window.open(routeData.href, '_blank'); - // this.$router.push({ - // name: 'gradingAdminEdit', - // params: { - // id: this.$store.getters.navCurRoleId - // } - // }); }, fetchSelectedGroups (type, payload, row) { diff --git a/frontend/src/views/my-manage-space/add-member-boundary/index.vue b/frontend/src/views/my-manage-space/add-member-boundary/index.vue index d5ad11790..039970a5a 100644 --- a/frontend/src/views/my-manage-space/add-member-boundary/index.vue +++ b/frontend/src/views/my-manage-space/add-member-boundary/index.vue @@ -473,6 +473,11 @@ manualAddLoading: false, manualInputError: false, manualValueBackup: [], + manualOrgList: [], + manualUserList: [], + filterUserList: [], + filterDepartList: [], + usernameList: [], isAll: false, isAllFlag: false, showLimit: false, @@ -606,6 +611,9 @@ return (payload) => { return ['depart', 'department'].includes(payload.type) ? payload.name : `${payload.username}(${payload.name})`; }; + }, + isStaff () { + return this.user.role === 'staff'; } }, watch: { @@ -831,6 +839,71 @@ this.manualInputError = false; }, + // 处理同步异步操作数据 + async formatSearchData (data, curData) { + if (data) { + const { users, departments } = data; + if (users && users.length) { + users.forEach((item) => { + item.type = 'user'; + }); + const result = await this.fetchSubjectScopeCheck(users, 'user'); + if (result && result.length) { + const hasSelectedUsers = [...this.hasSelectedUsers, ...this.hasSelectedManualUsers]; + const userTemp = result.filter((item) => { + return !hasSelectedUsers.map((subItem) => + `${subItem.username}&${subItem.name}`).includes(`${item.username}&${item.name}`); + }); + this.hasSelectedUsers.push(...userTemp); + this.hasSelectedManualUsers.push(...userTemp); + // 保存原有格式 + let formatStr = _.cloneDeep(this.manualValue); + const usernameList = result.map((item) => item.username); + usernameList.forEach((item) => { + // 处理既有部门又有用户且不连续相同类型的展示数据 + formatStr = formatStr + .replace(this.evil('/' + item + '(;\\n|\\s\\n|)/'), '') + .replace(/(\s*\r?\n\s*)+/g, '\n') + .replace(';;', ''); + // 处理复制全部用户不相连的两个不在授权范围内的用户存在空字符 + formatStr = formatStr + .split(/;|\n|\s/) + .filter((item) => item !== '' && item !== curData) + .join('\n'); + }); + // 处理只选择全部符合条件的用户,还存在特殊符号的情况 + if (formatStr === '\n' || formatStr === '\s' || formatStr === ';') { + formatStr = ''; + } + this.manualValue = _.cloneDeep(formatStr); + } + } + if (departments && departments.length) { + departments.forEach((item) => { + item.type = 'depart'; + }); + const result = await this.fetchSubjectScopeCheck(departments, 'depart'); + if (result && result.length) { + const hasSelectedDepartments = [...this.hasSelectedDepartments, ...this.hasSelectedManualDepartments]; + const departTemp = result.filter((item) => { + return !hasSelectedDepartments.map((subItem) => + subItem.id.toString()).includes(item.id.toString()); + }); + this.hasSelectedManualDepartments.push(...departTemp); + this.hasSelectedDepartments.push(...departTemp); + // 备份一份粘贴板里的内容,清除组织的数据,在过滤掉组织的数据 + let clipboardValue = _.cloneDeep(this.manualValue); + // 处理不相连的数据之间存在特殊符号的情况 + clipboardValue = clipboardValue + .split(/;|\n|\s/) + .filter((item) => item !== '' && item !== curData) + .join('\n'); + this.manualValue = _.cloneDeep(clipboardValue); + } + } + } + }, + async handleSearchOrgAndUser () { let manualInputValue = _.cloneDeep(this.manualValue.split(/;|\n|\s/)); manualInputValue = manualInputValue.filter((item) => item !== ''); @@ -840,80 +913,20 @@ is_exact: false }; try { - const { data } = await this.$store.dispatch('organization/getSearchOrganizations', params); - const { users, departments } = data; - if (users && users.length) { - users.forEach((item) => { - item.type = 'user'; - }); - const result = await this.fetchSubjectScopeCheck(users, 'user'); - if (result && result.length) { - const hasSelectedUsers = [...this.hasSelectedUsers, ...this.hasSelectedManualUsers]; - const userTemp = result.filter((item) => { - return !hasSelectedUsers.map((subItem) => - `${subItem.username}&${subItem.name}`).includes(`${item.username}&${item.name}`); - }); - this.hasSelectedUsers.push(...userTemp); - this.hasSelectedManualUsers.push(...userTemp); - // 保存原有格式 - let formatStr = _.cloneDeep(this.manualValue); - const usernameList = result.map((item) => item.username); - usernameList.forEach((item) => { - // 处理既有部门又有用户且不连续相同类型的展示数据 - formatStr = formatStr - .replace(this.evil('/' + item + '(;\\n|\\s\\n|)/'), '') - .replace(/(\s*\r?\n\s*)+/g, '\n') - .replace(';;', ''); - // 处理复制全部用户不相连的两个不在授权范围内的用户存在空字符 - formatStr = formatStr - .split(/;|\n|\s/) - .filter((item) => item !== '' && item !== manualInputValue[i]) - .join('\n'); - }); - // 处理只选择全部符合条件的用户,还存在特殊符号的情况 - if (formatStr === '\n' || formatStr === '\s' || formatStr === ';') { - formatStr = ''; - } - this.manualValue = _.cloneDeep(formatStr); - this.manualInputError = !!this.manualValue.length; - } else { - this.manualInputError = true; - } - } - if (departments && departments.length) { - departments.forEach((item) => { - item.type = 'depart'; + if (manualInputValue.length < 10) { + const { data } = await this.$store.dispatch('organization/getSearchOrganizations', params); + await this.formatSearchData(data, manualInputValue[i]); + } else { + this.$store.dispatch('organization/getSearchOrganizations', params).then(async ({ data }) => { + this.formatSearchData(data, manualInputValue[i]); }); - const result = await this.fetchSubjectScopeCheck(departments, 'depart'); - if (result && result.length) { - const hasSelectedDepartments = [...this.hasSelectedDepartments, ...this.hasSelectedManualDepartments]; - const departTemp = result.filter((item) => { - return !hasSelectedDepartments.map((subItem) => - subItem.id.toString()).includes(item.id.toString()); - }); - this.hasSelectedManualDepartments.push(...departTemp); - this.hasSelectedDepartments.push(...departTemp); - // 备份一份粘贴板里的内容,清除组织的数据,在过滤掉组织的数据 - let clipboardValue = _.cloneDeep(this.manualValue); - // 处理不相连的数据之间存在特殊符号的情况 - clipboardValue = clipboardValue - .split(/;|\n|\s/) - .filter((item) => item !== '' && item !== manualInputValue[i]) - .join('\n'); - this.manualValue = _.cloneDeep(clipboardValue); - this.manualInputError = !!this.manualValue.length; - } else { - this.manualInputError = true; - } } } catch (e) { console.error(e); this.messageAdvancedError(e); } } - if (!this.manualValue) { - this.manualInputError = false; - } + this.manualInputError = !!this.manualValue.length; }, async handleAddManualUser () { @@ -935,11 +948,28 @@ this.hasSelectedManualUsers.push(...temps); if (res.data.length) { this.usernameList = res.data.map((item) => item.username); + // 分号拼接 + // const templateArr = []; + // this.manualValueBackup = this.manualValueActual.split(';').filter(item => item !== ''); + // this.manualValueBackup.forEach(item => { + // const name = getUsername(item); + // if (!usernameList.includes(name)) { + // templateArr.push(item); + // } + // }); + // this.manualValue = templateArr.join(';'); + // 保存原有格式 let formatStr = _.cloneDeep(this.manualValue); this.usernameList.forEach((item) => { + // 去掉之前有查全局的写法, 如果username有多个重复的item, 比如shengjieliu03@shengjietest.com、shengjieliu05的时候/g就会有问题 + // formatStr = formatStr.replace(this.evil('/' + item + '(;\\n|\\s\\n|)/g'), ''); + + // 处理既有部门又有用户且不连续相同类型的展示数据 formatStr = formatStr .replace(this.evil('/' + item + '(;\\n|\\s\\n|)/'), '') + // .replace('\n\n', '\n') + // .replace('\s\s', '\s') .replace(/(\s*\r?\n\s*)+/g, '\n') .replace(';;', ''); // 处理复制全部用户不相连的两个不在授权范围内的用户存在空字符 @@ -953,8 +983,16 @@ formatStr = ''; } this.manualValue = _.cloneDeep(formatStr); + if (this.isStaff) { + this.manualInputError = true; + return; + } this.formatOrgAndUser(); } else { + if (this.isStaff) { + this.manualInputError = true; + return; + } this.formatOrgAndUser(); } } catch (e) { @@ -967,13 +1005,16 @@ // 处理只复制部门或者部门和用户一起复制情况 async formatOrgAndUser () { - if (this.manualValue) { + if (this.manualValue && !this.isStaff) { // 校验查验失败的数据是不是属于部门 const departData = _.cloneDeep(this.manualValue.split(/;|\n|\s/)); const departGroups = this.filterDepartList.filter((item) => departData.includes(item)); - if (departGroups.length && this.getGroupAttributes) { - if (this.getGroupAttributes().source_from_role) { + if (departGroups.length) { + if (this.getGroupAttributes && this.getGroupAttributes().source_from_role) { this.messageWarn(this.$t(`m.common['管理员组不能添加部门']`), 3000); + this.manualTableListStorage = [...this.hasSelectedManualDepartments, ...this.hasSelectedManualUsers]; + this.manualTableList = _.cloneDeep(this.manualTableListStorage); + this.fetchManualTableData(); this.manualInputError = true; return; } @@ -1017,16 +1058,22 @@ .filter((item) => item !== '') .join('\n'); this.manualValue = _.cloneDeep(clipboardValue); - this.manualInputError = !!this.manualValue.length; + // this.manualInputError = !!this.manualValue.length; } else { - this.manualInputError = true; + if (this.isStaff) { + this.manualInputError = true; + return; + } + // this.manualInputError = true; } } else { - this.manualInputError = true; + if (this.isStaff) { + this.manualInputError = true; + return; + } } } - - if (this.manualInputError) { + if (this.manualValue && !this.isStaff) { await this.handleSearchOrgAndUser(); } this.manualTableListStorage = [...this.hasSelectedManualDepartments, ...this.hasSelectedManualUsers]; From db9cbf36228ce8d75b08b1ebc278aff2d236be2e Mon Sep 17 00:00:00 2001 From: lhzzforever Date: Wed, 8 Nov 2023 15:29:05 +0800 Subject: [PATCH 02/27] =?UTF-8?q?fix:=20=E5=8D=95=E7=8B=AC=E5=A4=84?= =?UTF-8?q?=E7=90=86staff=E8=A7=92=E8=89=B2=E5=9C=A8=E7=BB=84=E7=BB=87?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E9=80=89=E6=8B=A9=E5=99=A8=E7=9A=84=E4=BA=A4?= =?UTF-8?q?=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/views/group/components/iam-add-member.vue | 2 +- .../src/views/my-manage-space/add-member-boundary/index.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/views/group/components/iam-add-member.vue b/frontend/src/views/group/components/iam-add-member.vue index f632980e2..5a550acb8 100644 --- a/frontend/src/views/group/components/iam-add-member.vue +++ b/frontend/src/views/group/components/iam-add-member.vue @@ -650,7 +650,7 @@ }; }, isStaff () { - return this.user.role === 'staff'; + return this.user.role.type === 'staff'; } }, watch: { diff --git a/frontend/src/views/my-manage-space/add-member-boundary/index.vue b/frontend/src/views/my-manage-space/add-member-boundary/index.vue index 039970a5a..52398ca57 100644 --- a/frontend/src/views/my-manage-space/add-member-boundary/index.vue +++ b/frontend/src/views/my-manage-space/add-member-boundary/index.vue @@ -613,7 +613,7 @@ }; }, isStaff () { - return this.user.role === 'staff'; + return this.user.role.type === 'staff'; } }, watch: { From 998c787b7bb97ee4cf0101da6681ee2f3e5449f5 Mon Sep 17 00:00:00 2001 From: lhzzforever Date: Wed, 8 Nov 2023 17:11:49 +0800 Subject: [PATCH 03/27] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E6=8A=A5=E9=94=99=E6=9C=AA=E6=B8=85=E7=A9=BA=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/views/group/components/iam-add-member.vue | 8 ++++---- .../views/my-manage-space/add-member-boundary/index.vue | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/src/views/group/components/iam-add-member.vue b/frontend/src/views/group/components/iam-add-member.vue index 5a550acb8..738726c71 100644 --- a/frontend/src/views/group/components/iam-add-member.vue +++ b/frontend/src/views/group/components/iam-add-member.vue @@ -989,13 +989,13 @@ } this.manualValue = _.cloneDeep(formatStr); if (this.isStaff) { - this.manualInputError = true; + this.manualInputError = !!this.manualValue; return; } this.formatOrgAndUser(); } else { if (this.isStaff) { - this.manualInputError = true; + this.manualInputError = !!this.manualValue; return; } this.formatOrgAndUser(); @@ -1066,14 +1066,14 @@ // this.manualInputError = !!this.manualValue.length; } else { if (this.isStaff) { - this.manualInputError = true; + this.manualInputError = !!this.manualValue; return; } // this.manualInputError = true; } } else { if (this.isStaff) { - this.manualInputError = true; + this.manualInputError = !!this.manualValue; return; } } diff --git a/frontend/src/views/my-manage-space/add-member-boundary/index.vue b/frontend/src/views/my-manage-space/add-member-boundary/index.vue index 52398ca57..993b9de9c 100644 --- a/frontend/src/views/my-manage-space/add-member-boundary/index.vue +++ b/frontend/src/views/my-manage-space/add-member-boundary/index.vue @@ -984,13 +984,13 @@ } this.manualValue = _.cloneDeep(formatStr); if (this.isStaff) { - this.manualInputError = true; + this.manualInputError = !!this.manualValue; return; } this.formatOrgAndUser(); } else { if (this.isStaff) { - this.manualInputError = true; + this.manualInputError = !!this.manualValue; return; } this.formatOrgAndUser(); @@ -1061,14 +1061,14 @@ // this.manualInputError = !!this.manualValue.length; } else { if (this.isStaff) { - this.manualInputError = true; + this.manualInputError = !!this.manualValue; return; } // this.manualInputError = true; } } else { if (this.isStaff) { - this.manualInputError = true; + this.manualInputError = !!this.manualValue; return; } } From 2655f1c1f48e9a66cc1c81d5b9c7855a26b6fbd4 Mon Sep 17 00:00:00 2001 From: Timmy Date: Thu, 9 Nov 2023 11:50:52 +0800 Subject: [PATCH 04/27] feat: add subject group search api (#2344) --- .../migrations/0014_auto_20231109_1046.py | 42 +++++++++ saas/backend/apps/subject/urls.py | 10 +++ saas/backend/apps/subject/views.py | 39 ++++++++ saas/backend/apps/user/views.py | 90 +++++++------------ saas/backend/biz/role.py | 8 ++ saas/config/default.py | 2 + saas/urls.py | 2 +- 7 files changed, 132 insertions(+), 61 deletions(-) create mode 100644 saas/backend/api/authorization/migrations/0014_auto_20231109_1046.py diff --git a/saas/backend/api/authorization/migrations/0014_auto_20231109_1046.py b/saas/backend/api/authorization/migrations/0014_auto_20231109_1046.py new file mode 100644 index 000000000..a00ba9d38 --- /dev/null +++ b/saas/backend/api/authorization/migrations/0014_auto_20231109_1046.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.16 on 2023-11-09 02:46 + +from django.db import migrations + +from backend.api.authorization.constants import AuthorizationAPIEnum +from backend.api.constants import ALLOW_ANY + + +def init_allow_list(apps, schema_editor): + """初始化授权API白名单""" + AuthAPIAllowListConfig = apps.get_model("authorization", "AuthAPIAllowListConfig") + # 查询已存在白名单,避免重复 + all_allow_list = AuthAPIAllowListConfig.objects.all() + allow_set = set([(a.type, a.system_id, a.object_id) for a in all_allow_list]) + # 新建关联实例授权API 白名单 + system_resource_types = { + "bk_cmdb": [ALLOW_ANY], + } + auth_api_allow_list_config = [] + for system_id, resource_types in system_resource_types.items(): + for resource_type_id in resource_types: + # 已存在,则直接忽略 + if (AuthorizationAPIEnum.CREATOR_AUTHORIZATION_INSTANCE.value, system_id, resource_type_id) in allow_set: + continue + auth_api_allow_list_config.append( + AuthAPIAllowListConfig( + type=AuthorizationAPIEnum.CREATOR_AUTHORIZATION_INSTANCE.value, + system_id=system_id, + object_id=resource_type_id, + ) + ) + if len(auth_api_allow_list_config) != 0: + AuthAPIAllowListConfig.objects.bulk_create(auth_api_allow_list_config) + + +class Migration(migrations.Migration): + + dependencies = [ + ('authorization', '0013_auto_20221101_1216'), + ] + + operations = [migrations.RunPython(init_allow_list)] diff --git a/saas/backend/apps/subject/urls.py b/saas/backend/apps/subject/urls.py index f59a63a72..d8f95de82 100644 --- a/saas/backend/apps/subject/urls.py +++ b/saas/backend/apps/subject/urls.py @@ -60,6 +60,16 @@ views.SubjectTemporaryPolicySystemViewSet.as_view({"get": "list"}), name="subject.temporary_policies_systems", ), + path( + "groups/search/", + views.SubjectGroupSearchViewSet.as_view({"get": "list"}), + name="subject.group_search", + ), + path( + "departments/-/groups/search/", + views.SubjectDepartmentGroupSearchViewSet.as_view({"get": "list"}), + name="subject.department.group_search", + ), ] ), ) diff --git a/saas/backend/apps/subject/views.py b/saas/backend/apps/subject/views.py index a4e7df7ba..0e57b42fb 100644 --- a/saas/backend/apps/subject/views.py +++ b/saas/backend/apps/subject/views.py @@ -17,8 +17,10 @@ from backend.account.serializers import AccountRoleSLZ from backend.apps.group.audit import GroupMemberDeleteAuditProvider from backend.apps.group.models import Group +from backend.apps.group.serializers import GroupSearchSLZ from backend.apps.policy.serializers import PolicyDeleteSLZ, PolicyPartDeleteSLZ, PolicySLZ, PolicySystemSLZ from backend.apps.user.serializers import GroupSLZ +from backend.apps.user.views import SubjectGroupSearchMixin from backend.audit.audit import audit_context_setter, view_audit_decorator from backend.biz.group import GroupBiz from backend.biz.policy import ConditionBean, PolicyOperationBiz, PolicyQueryBiz @@ -337,3 +339,40 @@ def list(self, request, *args, **kwargs): data = self.biz.list_temporary_system_counter_by_subject(subject) return Response([one.dict() for one in data]) + + +class SubjectGroupSearchViewSet(SubjectGroupSearchMixin): + @swagger_auto_schema( + operation_description="搜索subject用户组列表", + request_body=GroupSearchSLZ(label="用户组搜索"), + responses={status.HTTP_200_OK: SubjectGroupSLZ(label="用户组", many=True)}, + tags=["subject"], + ) + def search(self, request, *args, **kwargs): + return super().search(request, *args, **kwargs) + + def get_subject(self, request, kwargs): + subject = Subject(type=kwargs["subject_type"], id=kwargs["subject_id"]) + return subject + + +class SubjectDepartmentGroupSearchViewSet(SubjectGroupSearchMixin): + @swagger_auto_schema( + operation_description="搜索Subject部门用户组列表", + request_body=GroupSearchSLZ(label="用户组搜索"), + responses={status.HTTP_200_OK: SubjectGroupSLZ(label="用户组", many=True)}, + tags=["subject"], + ) + def search(self, request, *args, **kwargs): + return super().search(request, *args, **kwargs) + + def get_subject(self, request, kwargs): + subject = Subject(type=kwargs["subject_type"], id=kwargs["subject_id"]) + return subject + + def get_group_dict(self, subject: Subject): + groups = self.biz.list_all_user_department_group(subject) + return {one.id: one for one in groups} + + def get_page_result(self, group_dict, page): + return [group_dict[one.id] for one in page] diff --git a/saas/backend/apps/user/views.py b/saas/backend/apps/user/views.py index c46a69921..e1b7f4809 100644 --- a/saas/backend/apps/user/views.py +++ b/saas/backend/apps/user/views.py @@ -228,19 +228,13 @@ def list(self, request, *args, **kwargs): return Response([one.dict() for one in user_roles]) -class UserGroupSearchViewSet(mixins.ListModelMixin, GenericViewSet): +class SubjectGroupSearchMixin(mixins.ListModelMixin, GenericViewSet): queryset = Group.objects.all() serializer_class = GroupSLZ biz = GroupBiz() - @swagger_auto_schema( - operation_description="搜索用户用户组列表", - request_body=GroupSearchSLZ(label="用户组搜索"), - responses={status.HTTP_200_OK: SubjectGroupSLZ(label="用户组", many=True)}, - tags=["user"], - ) def search(self, request, *args, **kwargs): slz = GroupSearchSLZ(data=request.data) slz.is_valid(raise_exception=True) @@ -259,10 +253,10 @@ def search(self, request, *args, **kwargs): ) queryset = f.qs - subject = Subject.from_username(request.user.username) + subject = self.get_subject(request, kwargs) # 查询用户加入的所有用户组 - groups = list_all_subject_groups(subject.type, subject.id) - ids = sorted([int(g["id"]) for g in groups]) + group_dict = self.get_group_dict(subject) + ids = sorted(group_dict.keys()) if data["system_id"] and data["action_id"]: # 通过实例或操作查询用户组 @@ -281,77 +275,53 @@ def search(self, request, *args, **kwargs): page = self.paginate_queryset(queryset) if page is not None: - group_dict = {int(one["id"]): one for one in groups} - relations = [SubjectGroup(**group_dict[one.id]) for one in page] - results = self.biz._convert_to_subject_group_beans(relations) + results = self.get_page_result(group_dict, page) slz = GroupSLZ(instance=results, many=True) return Response({"count": queryset.count(), "results": slz.data}) return Response({"count": 0, "results": []}) + def get_subject(self, request, kwargs): + subject = Subject.from_username(request.user.username) + return subject -class UserDepartmentGroupSearchViewSet(mixins.ListModelMixin, GenericViewSet): + def get_group_dict(self, subject: Subject): + groups = list_all_subject_groups(subject.type, subject.id) + return {int(one["id"]): one for one in groups} - queryset = Group.objects.all() - serializer_class = GroupSLZ + def get_page_result(self, group_dict, page): + relations = [SubjectGroup(**group_dict[one.id]) for one in page] + return self.biz._convert_to_subject_group_beans(relations) - biz = GroupBiz() +class UserGroupSearchViewSet(SubjectGroupSearchMixin): @swagger_auto_schema( - operation_description="搜索用户部门用户组列表", + operation_description="搜索用户用户组列表", request_body=GroupSearchSLZ(label="用户组搜索"), responses={status.HTTP_200_OK: SubjectGroupSLZ(label="用户组", many=True)}, tags=["user"], ) def search(self, request, *args, **kwargs): - slz = GroupSearchSLZ(data=request.data) - slz.is_valid(raise_exception=True) + return super().search(request, *args, **kwargs) - data = slz.validated_data - # 筛选 - f = GroupFilter( - data={ - k: v - for k, v in data.items() - if k in ["id", "name", "description", "hidden"] - if isinstance(v, bool) or v - }, - queryset=self.get_queryset(), - ) - queryset = f.qs +class UserDepartmentGroupSearchViewSet(SubjectGroupSearchMixin): + @swagger_auto_schema( + operation_description="搜索用户部门用户组列表", + request_body=GroupSearchSLZ(label="用户组搜索"), + responses={status.HTTP_200_OK: SubjectGroupSLZ(label="用户组", many=True)}, + tags=["user"], + ) + def search(self, request, *args, **kwargs): + return super().search(request, *args, **kwargs) - subject = Subject.from_username(request.user.username) + def get_group_dict(self, subject: Subject): groups = self.biz.list_all_user_department_group(subject) + return {one.id: one for one in groups} - # 查询用户加入的所有用户组 - ids = sorted([g.id for g in groups]) - - if data["system_id"] and data["action_id"]: - # 通过实例或操作查询用户组 - data["permission_type"] = PermissionTypeEnum.RESOURCE_INSTANCE.value - data["limit"] = 1000 - subjects = QueryAuthorizedSubjects(data).query_by_resource_instance(subject_type="group") - subject_id_set = {int(s["id"]) for s in subjects} - - # 筛选同时有权限并且用户加入的用户组 - ids = [_id for _id in ids if _id in subject_id_set] - - if not ids: - return Response({"count": 0, "results": []}) - - queryset = queryset.filter(id__in=ids) - - page = self.paginate_queryset(queryset) - if page is not None: - group_dict = {one.id: one for one in groups} - relations = [group_dict[one.id] for one in page] - - slz = GroupSLZ(instance=relations, many=True) - return Response({"count": queryset.count(), "results": slz.data}) - - return Response({"count": 0, "results": []}) + def get_page_result(self, group_dict, page): + return [group_dict[one.id] for one in page] class UserPolicySearchViewSet(mixins.ListModelMixin, GenericViewSet): diff --git a/saas/backend/biz/role.py b/saas/backend/biz/role.py index a1352b31f..74b1b6e38 100644 --- a/saas/backend/biz/role.py +++ b/saas/backend/biz/role.py @@ -825,11 +825,19 @@ def _list_relation_role_id(self): return role_ids def _check_object(self, obj_type: str, obj_id: int) -> bool: + # 如果是超级管理员, 直接返回True + if self.role.type == RoleType.SUPER_MANAGER.value: + return True + return RoleRelatedObject.objects.filter( role_id__in=self._list_relation_role_id(), object_type=obj_type, object_id=obj_id ).exists() def _check_object_ids(self, obj_type: str, obj_ids: List[int]) -> bool: + # 如果是超级管理员, 直接返回True + if self.role.type == RoleType.SUPER_MANAGER.value: + return True + count = RoleRelatedObject.objects.filter( role_id__in=self._list_relation_role_id(), object_type=obj_type, object_id__in=obj_ids ).count() diff --git a/saas/config/default.py b/saas/config/default.py index 426fc3b2f..8b93cf764 100644 --- a/saas/config/default.py +++ b/saas/config/default.py @@ -171,6 +171,8 @@ "DEFAULT_AUTO_SCHEMA_CLASS": "backend.common.swagger.ResponseSwaggerAutoSchema", } +ENABLE_SWAGGER = env.bool("BKAPP_ENABLE_SWAGGER", default=False) + # CELERY 开关,使用时请改为 True,否则请保持为False。启动方式为以下两行命令: # worker: python manage.py celery worker -l info # beat: python manage.py celery beat -l info diff --git a/saas/urls.py b/saas/urls.py index c1488d555..f9607aecb 100644 --- a/saas/urls.py +++ b/saas/urls.py @@ -79,7 +79,7 @@ ] # add swagger api document -if settings.IS_LOCAL: +if settings.IS_LOCAL or settings.ENABLE_SWAGGER: urlpatterns += [ url(r"^swagger/$", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"), ] From 593db4717aee823fec231316e33d4fb7bbdf5742 Mon Sep 17 00:00:00 2001 From: Timmy Date: Thu, 9 Nov 2023 12:15:54 +0800 Subject: [PATCH 05/27] fix: fix staticfiles (#2345) --- saas/config/default.py | 1 + 1 file changed, 1 insertion(+) diff --git a/saas/config/default.py b/saas/config/default.py index 8b93cf764..69196c4b0 100644 --- a/saas/config/default.py +++ b/saas/config/default.py @@ -36,6 +36,7 @@ "django.contrib.sessions", "django.contrib.sites", "django.contrib.messages", + "django.contrib.staticfiles", "backend.account", "rest_framework", "django_filters", From 28b40eb32242cbba867bef3b410f7c0fa7725f3d Mon Sep 17 00:00:00 2001 From: Timmy Date: Thu, 9 Nov 2023 12:47:26 +0800 Subject: [PATCH 06/27] fix: fix swagger (#2346) --- saas/backend/apps/user/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/saas/backend/apps/user/serializers.py b/saas/backend/apps/user/serializers.py index 0bed199ad..3a01707f4 100644 --- a/saas/backend/apps/user/serializers.py +++ b/saas/backend/apps/user/serializers.py @@ -33,6 +33,9 @@ class GroupSLZ(SubjectGroupSLZ): role_members = serializers.SerializerMethodField() attributes = serializers.SerializerMethodField() + class Meta: + ref_name = "UserGroupSLZ" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.group_role_dict = None From 440197a6394075fe841cd93265b17987fe7bb7a2 Mon Sep 17 00:00:00 2001 From: lhzzforever Date: Thu, 9 Nov 2023 13:19:36 +0800 Subject: [PATCH 07/27] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=88=91?= =?UTF-8?q?=E7=9A=84=E6=9D=83=E9=99=90=E5=A4=9A=E4=B8=AA=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=89=A7=E8=A1=8C=E5=AF=BC=E8=87=B4=E4=BC=9A?= =?UTF-8?q?=E5=87=BA=E7=8E=B0=E9=97=AA=E7=8E=B0=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom-perm-system-policy/index.vue | 4 +- frontend/src/views/perm/custom-perm/index.vue | 39 +++++++++++++++---- frontend/src/views/perm/index.vue | 27 +++++++++++-- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/custom-perm-system-policy/index.vue b/frontend/src/components/custom-perm-system-policy/index.vue index e695d8bf7..da330c1dc 100644 --- a/frontend/src/components/custom-perm-system-policy/index.vue +++ b/frontend/src/components/custom-perm-system-policy/index.vue @@ -1,9 +1,9 @@ + diff --git a/frontend/src/views/user/components/group-perm.vue b/frontend/src/views/user/components/group-perm.vue index bbfac0852..8b46e5714 100644 --- a/frontend/src/views/user/components/group-perm.vue +++ b/frontend/src/views/user/components/group-perm.vue @@ -1,13 +1,17 @@ - + @@ -80,20 +88,27 @@ @@ -105,13 +120,15 @@ text :disabled="row.readonly" @click="handleAddMember(row)" - style="margin-right: 5px;" + style="margin-right: 5px" > {{ $t(`m.common['添加成员']`) }}
- -

{{ $t(`m.permApply['请选择申请期限']`) }}

+ +

+ {{ $t(`m.permApply['请选择申请期限']`) }} +

+ theme="primary" + @click="handleBatchUserGroupSubmit" + data-test-id="group_btn_create" + > {{ $t(`m.common['提交']`) }} + theme="default" + @click="handleBatchUserGroupCancel" + data-test-id="group_btn_create" + > {{ $t(`m.common['取消']`) }}
@@ -225,13 +267,15 @@ :sub-title="deleteDialogConf.msg" @on-after-leave="afterLeaveDelete" @on-cancel="cancelDelete" - @on-sumbit="confirmDelete" /> + @on-sumbit="confirmDelete" + /> + @animation-end="handleAnimationEnd" + /> import _ from 'lodash'; import { mapGetters } from 'vuex'; + import { bus } from '@/common/bus'; import { PERMANENT_TIMESTAMP } from '@/common/constants'; import { formatCodeData, getWindowHeight } from '@/common/util'; import DeleteDialog from '@/components/iam-confirm-dialog/index.vue'; @@ -288,6 +333,35 @@ default: () => { return {}; } + }, + personalGroupList: { + type: Array, + default: () => [] + }, + emptyData: { + type: Object, + default: () => { + return { + type: '', + text: '', + tip: '', + tipType: '' + }; + } + }, + curSearchParams: { + type: Object + }, + isSearchPerm: { + type: Boolean, + default: false + }, + checkGroupList: { + type: Array, + default: () => [] + }, + totalCount: { + type: Number } }, data () { @@ -338,13 +412,13 @@ count: 0, limit: 10 }, - emptyData: { + emptyDialogData: { type: '', text: '', tip: '', tipType: '' }, - emptyDialogData: { + groupPermEmptyData: { type: '', text: '', tip: '', @@ -369,45 +443,74 @@ }; }, computed: { - ...mapGetters(['user']), - curSelectIds () { - return this.currentSelectList.map(item => item.id); - }, - curSelectMemberIds () { - return this.currentSelectGroupList.map(item => item.id); - }, - tableHeight () { - return getWindowHeight() - 290; - }, - isAdminGroup () { - return (payload) => { - if (payload) { - const { attributes, role_members } = payload; - if (attributes && attributes.source_from_role && role_members.length === 1) { - return true; - } - return false; + ...mapGetters(['user', 'externalSystemId', 'mainContentLoading']), + curSelectIds () { + return this.currentSelectList.map((item) => item.id); + }, + curSelectMemberIds () { + return this.currentSelectGroupList.map((item) => item.id); + }, + tableHeight () { + return getWindowHeight() - 290; + }, + isAdminGroup () { + return (payload) => { + if (payload) { + const { attributes, role_members } = payload; + if (attributes && attributes.source_from_role && role_members.length === 1) { + return true; } - }; - }, - curRole () { - return this.user.role.type; - }, - isRatingManager () { - return ['rating_manager', 'subset_manager'].includes(this.curRole); - }, - isBatchDisabled () { - if (this.currentSelectGroupList.length) { - const isDisabled = this.currentSelectGroupList.every(item => item.readonly); - return isDisabled; - } else { - return true; + return false; } + }; + }, + curRole () { + return this.user.role.type; + }, + isRatingManager () { + return ['rating_manager', 'subset_manager'].includes(this.curRole); + }, + isBatchDisabled () { + if (this.currentSelectGroupList.length) { + const isDisabled = this.currentSelectGroupList.every((item) => item.readonly); + return isDisabled; + } else { + return true; } + } }, watch: { 'pagination.current' (value) { this.currentBackup = value; + }, + personalGroupList: { + handler (v) { + if (this.pageConf.current === 1) { + this.pageConf = Object.assign(this.pageConf, { count: this.totalCount }); + this.curPageData = [...v]; + return; + } + this.resetPagination(this.pageConf.limit); + }, + immediate: true + }, + emptyData: { + handler (value) { + this.groupPermEmptyData = Object.assign({}, value); + }, + immediate: true + }, + checkGroupList: { + handler (value) { + const list = value.filter( + (item) => + !this.currentSelectGroupList + .map((v) => v.id.toString()) + .includes(item.id.toString()) + ); + this.currentSelectGroupList = [...this.currentSelectGroupList, ...list]; + }, + immediate: true } }, async created () { @@ -430,24 +533,50 @@ * fetchPermGroups */ async fetchPermGroups (isTableLoading = false, isPageLoading = false) { - this.tableLoading = isTableLoading; - this.pageLoading = isPageLoading; - const { type } = this.data; try { - const { code, data } = await this.$store.dispatch('perm/getPermGroups', { - subjectType: type === 'user' ? type : 'department', - subjectId: type === 'user' ? this.data.username : this.data.id, - limit: this.pageConf.limit, - offset: this.pageConf.current + this.pageLoading = isPageLoading; + let url = ''; + let params = {}; + const { current, limit } = this.pageConf; + if (!this.mainContentLoading) { + this.tableLoading = isTableLoading; + } + if (this.isSearchPerm) { + url = 'perm/getUserGroupSearch'; + params = { + ...this.curSearchParams, + limit, + offset: limit * (current - 1) + }; + } else { + url = 'perm/getPermGroups'; + params = { + limit: this.pageConf.limit, + offset: this.pageConf.current + }; + } + if (this.externalSystemId) { + params.system_id = this.externalSystemId; + params.hidden = false; + } + const { id, type, username } = this.data; + const { code, data } = await this.$store.dispatch(url, { + ...params, + ...{ + subjectType: type === 'user' ? type : 'department', + subjectId: type === 'user' ? username : id + } }); - const currentSelectGroupList = this.currentSelectGroupList.map(item => item.id.toString()); + const currentSelectGroupList = this.currentSelectGroupList.map((item) => + item.id.toString() + ); this.pageConf.count = data.count || 0; this.dataList.splice(0, this.dataList.length, ...(data.results || [])); this.curPageData = [...this.dataList]; this.$nextTick(() => { - this.curPageData.forEach(item => { + this.curPageData.forEach((item) => { if (item.role_members && item.role_members.length) { - item.role_members = item.role_members.map(v => { + item.role_members = item.role_members.map((v) => { return { username: v, readonly: false @@ -455,23 +584,28 @@ }); } if (currentSelectGroupList.includes(item.id.toString())) { - this.$refs.groupPermTableRef && this.$refs.groupPermTableRef.toggleRowSelection(item, true); + this.$refs.groupPermTableRef + && this.$refs.groupPermTableRef.toggleRowSelection(item, true); } }); if (this.currentSelectGroupList.length < 1) { this.$refs.groupPermTableRef && this.$refs.groupPermTableRef.clearSelection(); } }); - this.emptyData = formatCodeData(code, this.emptyData, data.results.length === 0); + this.groupPermEmptyData = formatCodeData(code, this.emptyData, data.results.length === 0); } catch (e) { this.$emit('toggle-loading', false); console.error(e); const { code } = e; - this.emptyData = formatCodeData(code, this.emptyData); + this.currentSelectGroupList = []; + this.groupPermEmptyData = formatCodeData(code, this.emptyData); this.messageAdvancedError(e); } finally { this.tableLoading = false; this.pageLoading = false; + if (this.isSearchPerm) { + bus.$emit('on-perm-tab-count', { active: 'GroupPerm', count: this.pageConf.count }); + } } }, @@ -516,7 +650,11 @@ showQuitTemplates (row) { this.deleteDialogConf.visiable = true; this.deleteDialogConf.row = Object.assign({}, row); - this.deleteDialogConf.msg = `${this.$t(`m.common['退出']`)}${this.$t(`m.common['【']`)}${row.name}${this.$t(`m.common['】']`)}${this.$t(`m.common[',']`)}${this.$t(`m.info['将不再继承该组的权限']`)}${this.$t(`m.common['。']`)}`; + this.deleteDialogConf.msg = `${this.$t(`m.common['退出']`)}${this.$t( + `m.common['【']` + )}${row.name}${this.$t(`m.common['】']`)}${this.$t(`m.common[',']`)}${this.$t( + `m.info['将不再继承该组的权限']` + )}${this.$t(`m.common['。']`)}`; }, async confirmDelete () { @@ -556,9 +694,7 @@ await this.fetchUserGroupList(); }, - handleAfterEditLeave () { - - }, + handleAfterEditLeave () {}, handleSearch (payload, result) { this.currentSelectList = []; @@ -567,6 +703,15 @@ this.fetchUserGroupList(true); }, + fetchSelectedGroupCount () { + this.$nextTick(() => { + const selectionCount = document.getElementsByClassName('bk-page-selection-count'); + if (this.$refs.groupPermTableRef && selectionCount) { + selectionCount[0].children[0].innerHTML = this.currentSelectGroupList.length; + } + }); + }, + fetchSelectedGroups (type, payload, row) { const typeMap = { multiple: () => { @@ -578,24 +723,15 @@ (item) => item.id.toString() !== row.id.toString() ); } - this.$nextTick(() => { - const selectionCount = document.getElementsByClassName('bk-page-selection-count'); - if (this.$refs.groupPermTableRef && selectionCount) { - selectionCount[0].children[0].innerHTML = this.currentSelectGroupList.length; - } - }); + this.fetchSelectedGroupCount(); }, all: () => { const tableList = _.cloneDeep(this.curPageData); - const selectGroups = this.currentSelectGroupList.filter(item => - !tableList.map(v => v.id.toString()).includes(item.id.toString())); + const selectGroups = this.currentSelectGroupList.filter( + (item) => !tableList.map((v) => v.id.toString()).includes(item.id.toString()) + ); this.currentSelectGroupList = [...selectGroups, ...payload]; - this.$nextTick(() => { - const selectionCount = document.getElementsByClassName('bk-page-selection-count'); - if (this.$refs.groupPermTableRef && selectionCount) { - selectionCount[0].children[0].innerHTML = this.currentSelectGroupList.length; - } - }); + this.fetchSelectedGroupCount(); } }; return typeMap[type](); @@ -619,12 +755,17 @@ quit: () => { this.isShowDeleteDialog = true; this.delActionDialogTitle = this.$t(`m.dialog['确认批量退出所选的用户组吗?']`); - const adminGroups = this.currentSelectGroupList.filter(item => - item.attributes && item.attributes.source_from_role - && item.role_members.length === 1 && item.department_id === 0 + const adminGroups = this.currentSelectGroupList.filter( + (item) => + item.attributes + && item.attributes.source_from_role + && item.role_members.length === 1 + && item.department_id === 0 ); if (adminGroups.length) { - this.delActionDialogTip = this.$t(`m.perm['存在用户组不可退出(唯一管理员不能退出)']`); + this.delActionDialogTip = this.$t( + `m.perm['存在用户组不可退出(唯一管理员不能退出)']` + ); this.delActionList = adminGroups; } } @@ -645,10 +786,17 @@ }, handleBatchAddMember () { - const hasDisabledData = this.currentSelectGroupList.filter(item => item.readonly); + const hasDisabledData = this.currentSelectGroupList.filter((item) => item.readonly); if (hasDisabledData.length) { - const disabledNames = hasDisabledData.map(item => item.name); - this.messageWarn(this.$t(`m.info['用户组为只读用户组不能添加成员']`, { value: `${this.$t(`m.common['【']`)}${disabledNames}${this.$t(`m.common['】']`)}` }), 3000); + const disabledNames = hasDisabledData.map((item) => item.name); + this.messageWarn( + this.$t(`m.info['用户组为只读用户组不能添加成员']`, { + value: `${this.$t(`m.common['【']`)}${disabledNames}${this.$t( + `m.common['】']` + )}` + }), + 3000 + ); return; } this.isBatch = true; @@ -677,11 +825,20 @@ async handleSubmitAdd (payload) { const { users, departments, expiredAt } = payload; // 判断批量选择的用户组里是否包含管理员组 - const hasAdminGroups = this.currentSelectGroupList.filter(item => - item.attributes && item.attributes.source_from_role && departments.length > 0); + const hasAdminGroups = this.currentSelectGroupList.filter( + (item) => + item.attributes && item.attributes.source_from_role && departments.length > 0 + ); if (hasAdminGroups.length) { - const adminGroupNames = hasAdminGroups.map(item => item.name).join(); - this.messageWarn(this.$t(`m.info['用户组为管理员组,不能添加部门']`, { value: `${this.$t(`m.common['【']`)}${adminGroupNames}${this.$t(`m.common['】']`)}` }), 3000); + const adminGroupNames = hasAdminGroups.map((item) => item.name).join(); + this.messageWarn( + this.$t(`m.info['用户组为管理员组,不能添加部门']`, { + value: `${this.$t(`m.common['【']`)}${adminGroupNames}${this.$t( + `m.common['】']` + )}` + }), + 3000 + ); return; } let expired = payload.policy_expired_at; @@ -743,10 +900,15 @@ }, async handleSubmitDelete () { - const selectGroups = this.currentSelectGroupList.filter(item => - !this.delActionList.map(v => v.id.toString()).includes(item.id.toString())); + const selectGroups = this.currentSelectGroupList.filter( + (item) => + !this.delActionList.map((v) => v.id.toString()).includes(item.id.toString()) + ); if (!selectGroups.length) { - this.messageWarn(this.$t(`m.perm['当前勾选项都为不可退出的用户组(唯一管理员不能退出)']`), 3000); + this.messageWarn( + this.$t(`m.perm['当前勾选项都为不可退出的用户组(唯一管理员不能退出)']`), + 3000 + ); return; } const { id, username, type } = this.data; @@ -784,17 +946,29 @@ await this.fetchUserGroupList(true); }, + handleEmptyClear () { + this.searchParams = {}; + this.searchValue = []; + this.groupPermEmptyData.tipType = ''; + this.pageConf = Object.assign(this.pageConf, { current: 1, limit: 10 }); + this.$emit('on-clear'); + }, + async handleEmptyRefresh () { this.pageConf = Object.assign(this.pageConf, { current: 1, limit: 10 }); await this.fetchPermGroups(false, true); + this.$emit('on-refresh'); }, resetPagination () { - this.pagination = Object.assign({}, { - limit: 10, - current: 1, - count: 0 - }); + this.pagination = Object.assign( + {}, + { + limit: 10, + current: 1, + count: 0 + } + ); }, pageChange (page) { @@ -819,7 +993,7 @@ if (payload !== PERMANENT_TIMESTAMP && payload) { const nowTimestamp = +new Date() / 1000; const tempArr = String(nowTimestamp).split(''); - const dotIndex = tempArr.findIndex(item => item === '.'); + const dotIndex = tempArr.findIndex((item) => item === '.'); const nowSecond = parseInt(tempArr.splice(0, dotIndex).join(''), 10); this.expiredAtUse = payload + nowSecond; return; @@ -839,15 +1013,22 @@ async fetchUserGroupList () { this.tableDialogLoading = true; const params = { - ...this.searchParams, - limit: this.pagination.limit, - offset: this.pagination.limit * (this.pagination.current - 1) + ...this.searchParams, + limit: this.pagination.limit, + offset: this.pagination.limit * (this.pagination.current - 1) }; try { - const { code, data } = await this.$store.dispatch('userGroup/getUserGroupList', params); + const { code, data } = await this.$store.dispatch( + 'userGroup/getUserGroupList', + params + ); this.pagination.count = data.count || 0; this.tableList.splice(0, this.tableList.length, ...(data.results || [])); - this.emptyDialogData = formatCodeData(code, this.emptyDialogData, this.tableList.length === 0); + this.emptyDialogData = formatCodeData( + code, + this.emptyDialogData, + this.tableList.length === 0 + ); } catch (e) { console.error(e); const { code } = e; @@ -871,7 +1052,7 @@ handleExpiredAt () { const nowTimestamp = +new Date() / 1000; const tempArr = String(nowTimestamp).split(''); - const dotIndex = tempArr.findIndex(item => item === '.'); + const dotIndex = tempArr.findIndex((item) => item === '.'); const nowSecond = parseInt(tempArr.splice(0, dotIndex).join(''), 10); const expiredAt = this.expiredAtUse + nowSecond; return expiredAt; @@ -926,30 +1107,30 @@ diff --git a/frontend/src/views/user/components/perm-table-edit.vue b/frontend/src/views/user/components/perm-table-edit.vue index 353e71851..c35972604 100644 --- a/frontend/src/views/user/components/perm-table-edit.vue +++ b/frontend/src/views/user/components/perm-table-edit.vue @@ -152,6 +152,7 @@ import DeleteActionDialog from '@/views/group/components/delete-related-action-dialog.vue'; import { formatCodeData } from '@/common/util'; import { mapGetters } from 'vuex'; + import { bus } from '@/common/bus'; export default { name: '', @@ -188,6 +189,13 @@ tipType: '' }; } + }, + curSearchParams: { + type: Object + }, + isSearchPerm: { + type: Boolean, + default: false } }, data () { @@ -271,23 +279,6 @@ } }, methods: { - /** - * fetchData - */ - async fetchData (params) { - try { - const { code, data } = await this.$store.dispatch('perm/getPersonalPolicy', { ...params }); - this.tableList = data && data.map(item => new PermPolicy(item)); - this.policyEmptyData = formatCodeData(code, this.policyEmptyData, data.length === 0); - } catch (e) { - console.error(e); - this.policyEmptyData = formatCodeData(e.code, this.policyEmptyData); - this.messageAdvancedError(e); - } finally { - this.initRequestQueue.shift(); - } - }, - /** * 获取系统对应的自定义操作 * @@ -312,6 +303,50 @@ } }, + async fetchData (params) { + const { subjectId, subjectType } = params; + try { + let url = ''; + let queryParams = {}; + if (this.isSearchPerm) { + url = 'perm/getPoliciesSearch'; + queryParams = { + ...this.searchParams, + ...{ + subjectId, + subjectType + } + }; + if (!queryParams.system_id) { + return; + } + } else { + url = 'perm/getPersonalPolicy'; + queryParams = { + ...params + }; + } + const { code, data } = await this.$store.dispatch(url, queryParams); + if (data.length) { + this.tableList = data.map(item => { + const relatedEnvironments = this.linearActionList.find(sub => sub.id === item.id); + item.related_environments = relatedEnvironments ? relatedEnvironments.related_environments : []; + return new PermPolicy(item); + }); + } + this.policyEmptyData = formatCodeData(code, this.policyEmptyData, data.length === 0); + } catch (e) { + console.error(e); + this.policyEmptyData = formatCodeData(e.code, this.policyEmptyData); + this.messageAdvancedError(e); + } finally { + this.initRequestQueue.shift(); + if (this.isSearchPerm) { + bus.$emit('on-perm-tab-count', { active: 'CustomPerm', count: this.tableList.length || 0 }); + } + } + }, + handleActionLinearData () { const linearActions = []; this.originalCustomTmplList.forEach((item, index) => { diff --git a/frontend/src/views/user/components/render-user.vue b/frontend/src/views/user/components/render-user.vue index bba8ede2c..aa798a5ff 100644 --- a/frontend/src/views/user/components/render-user.vue +++ b/frontend/src/views/user/components/render-user.vue @@ -4,27 +4,75 @@ {{ curData.username }} ({{ curData.name }}) +
+ +
+ ext-cls="iam-user-tab-cls" + :active.sync="active" + :key="tabKey" + > + :key="index" + @tab-change="handleTabChange" + > + - + :data="curData" + :personal-group-list="personalGroupList" + :system-list="systemList" + :tep-system-list="teporarySystemList" + :department-group-list="departmentGroupList" + :empty-data="curEmptyData" + :cur-search-params="curSearchParams" + :cur-search-pagination="curSearchPagination" + :is-search-perm="isSearchPerm" + :check-group-list="panels[0].selectList" + @refresh="fetchData" + @on-select-group="handleSelectGroup" + @on-clear="handleEmptyClear" + @on-refresh="handleEmptyRefresh" + />
+ + diff --git a/frontend/src/views/user/index.css b/frontend/src/views/user/index.css index 7e82d3642..417654b0f 100644 --- a/frontend/src/views/user/index.css +++ b/frontend/src/views/user/index.css @@ -298,7 +298,8 @@ .drag-line { position: absolute; left: 280px; - height: calc(100vh - 61px); + /* height: calc(100vh - 61px); */ + height: 100%; width: 1px; background: #dcdee5; z-index: 1500; From 49aa2fd7a715973e9f1a2e5846dabf7a9f8f1e65 Mon Sep 17 00:00:00 2001 From: Timmy Date: Fri, 10 Nov 2023 17:25:59 +0800 Subject: [PATCH 15/27] fix: fix user policy search (#2357) --- saas/backend/apps/user/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saas/backend/apps/user/views.py b/saas/backend/apps/user/views.py index d1fd1fc2b..1f303a285 100644 --- a/saas/backend/apps/user/views.py +++ b/saas/backend/apps/user/views.py @@ -383,6 +383,6 @@ def search(self, request, *args, **kwargs): # no action_id policy return Response([]) - def get_subject(self, request): + def get_subject(self, request, kwargs): subject = Subject.from_username(request.user.username) return subject From d3f2b27883d48a0227c0155a2d5b89fbfc4c874e Mon Sep 17 00:00:00 2001 From: lhzzforever Date: Fri, 10 Nov 2023 20:09:34 +0800 Subject: [PATCH 16/27] =?UTF-8?q?feature:=20=E5=AF=B9=E6=8E=A5=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=A8=A1=E5=9D=97=E5=9B=9B=E7=BA=A7=E8=81=94=E5=8A=A8?= =?UTF-8?q?=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/store/modules/perm.js | 4 +- .../perm/department-group-perm/index.vue | 2 +- frontend/src/views/perm/group-perm/index.vue | 2 - .../src/views/user/components/custom-perm.vue | 38 ++-- .../user/components/department-group-perm.vue | 188 ++++++++++-------- .../src/views/user/components/group-perm.vue | 2 +- .../views/user/components/perm-table-edit.vue | 2 +- .../src/views/user/components/render-user.vue | 122 ++++++++---- 8 files changed, 209 insertions(+), 151 deletions(-) diff --git a/frontend/src/store/modules/perm.js b/frontend/src/store/modules/perm.js index 3eea31596..0ede424fd 100644 --- a/frontend/src/store/modules/perm.js +++ b/frontend/src/store/modules/perm.js @@ -773,7 +773,7 @@ export default { * * @return {Promise} promise 对象 */ - getPoliciesPermSearch ({ commit, state, dispatch }, params, config) { + getPersonalPolicySearch ({ commit, state, dispatch }, params, config) { delete params.offset; delete params.limit; delete params.id; @@ -781,7 +781,7 @@ export default { delete params.name; return http.post( `${AJAX_URL_PREFIX}/subjects/${params.subjectType}/${params.subjectId}/policies/search/`, - {}, + params, config ); } diff --git a/frontend/src/views/perm/department-group-perm/index.vue b/frontend/src/views/perm/department-group-perm/index.vue index ed84d1367..114407496 100644 --- a/frontend/src/views/perm/department-group-perm/index.vue +++ b/frontend/src/views/perm/department-group-perm/index.vue @@ -174,7 +174,7 @@ }, watch: { departmentGroupList: { - async handler (v) { + handler (v) { if (this.isSearchPerm) { this.pageConf = Object.assign(this.pageConf, { current: 1, limit: 10, count: this.totalCount }); } diff --git a/frontend/src/views/perm/group-perm/index.vue b/frontend/src/views/perm/group-perm/index.vue index 753bbb4f1..abc7ad5eb 100644 --- a/frontend/src/views/perm/group-perm/index.vue +++ b/frontend/src/views/perm/group-perm/index.vue @@ -20,13 +20,11 @@ v-bkloading="{ isLoading: tableLoading, opacity: 1 }" > - - -