Selaa lähdekoodia

user role & group & accounts

MaxKey 3 vuotta sitten
vanhempi
commit
3cabc8a790
34 muutettua tiedostoa jossa 985 lisäystä ja 165 poistoa
  1. 2 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/GroupMemberMapper.java
  2. 2 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/RoleMemberMapper.java
  3. 29 0
      maxkey-persistence/src/main/java/org/maxkey/persistence/service/GroupMemberService.java
  4. 32 1
      maxkey-persistence/src/main/java/org/maxkey/persistence/service/RoleMemberService.java
  5. 22 0
      maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/GroupMemberMapper.xml
  6. 23 0
      maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/RoleMemberMapper.xml
  7. 2 1
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/access.module.ts
  8. 4 5
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/group-members.component.html
  9. 49 21
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/group-members.component.ts
  10. 56 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/member-groups-editer/member-groups-editer.component.html
  11. 0 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/member-groups-editer/member-groups-editer.component.less
  12. 25 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/member-groups-editer/member-groups-editer.component.spec.ts
  13. 170 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/member-groups-editer/member-groups-editer.component.ts
  14. 62 83
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/accounts/account-editer/account-editer.component.html
  15. 5 7
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/accounts/account-editer/account-editer.component.spec.ts
  16. 13 4
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/accounts/account-editer/account-editer.component.ts
  17. 11 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/accounts/accounts.component.ts
  18. 2 1
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/permissions.module.ts
  19. 56 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/member-roles-editer/member-roles-editer.component.html
  20. 0 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/member-roles-editer/member-roles-editer.component.less
  21. 25 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/member-roles-editer/member-roles-editer.component.spec.ts
  22. 169 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/member-roles-editer/member-roles-editer.component.ts
  23. 4 5
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/role-members.component.html
  24. 50 21
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/role-members.component.ts
  25. 12 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/users/users.component.html
  26. 13 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/users/users.component.ts
  27. 14 4
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/service/group-members.service.ts
  28. 14 5
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/service/role-members.service.ts
  29. 6 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/app/service/users.service.ts
  30. 3 0
      maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/en-US.json
  31. 4 1
      maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-CN.json
  32. 49 4
      maxkey-webs/maxkey-web-mgt/src/main/java/org/maxkey/web/access/contorller/GroupMemberController.java
  33. 7 0
      maxkey-webs/maxkey-web-mgt/src/main/java/org/maxkey/web/contorller/UserInfoController.java
  34. 50 2
      maxkey-webs/maxkey-web-mgt/src/main/java/org/maxkey/web/permissions/contorller/RoleMemberController.java

+ 2 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/GroupMemberMapper.java

@@ -38,6 +38,8 @@ public  interface GroupMemberMapper extends IJpaBaseMapper<GroupMember> {
 	public List<GroupMember> memberNotInGroup(GroupMember entity);
 	public List<GroupMember> groupMemberInGroup(GroupMember entity);
 	
+	public List<Groups> groupsNoMember(GroupMember entity);
+	
 	public int addDynamicGroupMember(Groups dynamicGroup);
 	
 	public int deleteDynamicGroupMember(Groups dynamicGroup);

+ 2 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/mapper/RoleMemberMapper.java

@@ -37,6 +37,8 @@ public  interface RoleMemberMapper extends IJpaBaseMapper<RoleMember> {
 	public List<RoleMember> memberNotInRole(RoleMember entity);
 	public List<RoleMember> roleMemberInRole(RoleMember entity);
 	
+	public List<Roles> rolesNoMember(RoleMember entity);
+	
     public int addDynamicRoleMember(Roles dynamicRole);
 
     public int deleteDynamicRoleMember(Roles dynamicRole);

+ 29 - 0
maxkey-persistence/src/main/java/org/maxkey/persistence/service/GroupMemberService.java

@@ -20,14 +20,18 @@ package org.maxkey.persistence.service;
 import java.util.List;
 
 import org.apache.mybatis.jpa.persistence.JpaBaseService;
+import org.apache.mybatis.jpa.persistence.JpaPageResults;
 import org.maxkey.entity.GroupMember;
 import org.maxkey.entity.Groups;
 import org.maxkey.entity.UserInfo;
 import org.maxkey.persistence.mapper.GroupMemberMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Repository;
 
 @Repository
 public class GroupMemberService  extends JpaBaseService<GroupMember>{
+	final static Logger _logger = LoggerFactory.getLogger(GroupMemberService.class);
 	
 	public GroupMemberService() {
 		super(GroupMemberMapper.class);
@@ -57,4 +61,29 @@ public class GroupMemberService  extends JpaBaseService<GroupMember>{
 		return getMapper().queryMemberByGroupId(groupId);
 	}
 	
+	
+	public JpaPageResults<Groups> groupsNoMember(GroupMember entity) {
+		entity.setPageResultSelectUUID(entity.generateId());
+		entity.setStartRow(calculateStartRow(entity.getPageNumber() ,entity.getPageSize()));
+		
+		entity.setPageable(true);
+		List<Groups> resultslist = null;
+		try {
+			resultslist = getMapper().groupsNoMember(entity);
+		} catch (Exception e) {
+			_logger.error("queryPageResults Exception " , e);
+		}
+		entity.setPageable(false);
+		Integer totalPage = resultslist.size();
+		
+		Integer totalCount = 0;
+		if(entity.getPageNumber() == 1 && totalPage < entity.getPageSize()) {
+			totalCount = totalPage;
+		}else {
+			totalCount = parseCount(getMapper().queryPageResultsCount(entity));
+		}
+		
+		return new JpaPageResults<Groups>(entity.getPageNumber(),entity.getPageSize(),totalPage,totalCount,resultslist);
+	}
+	
 }

+ 32 - 1
maxkey-persistence/src/main/java/org/maxkey/persistence/service/RoleMemberService.java

@@ -1,5 +1,5 @@
 /*
- * Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
+ * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,15 +17,22 @@
 
 package org.maxkey.persistence.service;
 
+import java.util.List;
+
 import org.apache.mybatis.jpa.persistence.JpaBaseService;
+import org.apache.mybatis.jpa.persistence.JpaPageResults;
 import org.maxkey.entity.RoleMember;
 import org.maxkey.entity.Roles;
 import org.maxkey.persistence.mapper.RoleMemberMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Repository;
 
 @Repository
 public class RoleMemberService  extends JpaBaseService<RoleMember>{
 	
+	final static Logger _logger = LoggerFactory.getLogger(RoleMemberService.class);
+	
 	public RoleMemberService() {
 		super(RoleMemberMapper.class);
 	}
@@ -50,4 +57,28 @@ public class RoleMemberService  extends JpaBaseService<RoleMember>{
     public int deleteByRoleId(String roleId) {
         return getMapper().deleteByRoleId(roleId);
     }
+    
+	public JpaPageResults<Roles> rolesNoMember(RoleMember entity) {
+		entity.setPageResultSelectUUID(entity.generateId());
+		entity.setStartRow(calculateStartRow(entity.getPageNumber() ,entity.getPageSize()));
+		
+		entity.setPageable(true);
+		List<Roles> resultslist = null;
+		try {
+			resultslist = getMapper().rolesNoMember(entity);
+		} catch (Exception e) {
+			_logger.error("queryPageResults Exception " , e);
+		}
+		entity.setPageable(false);
+		Integer totalPage = resultslist.size();
+		
+		Integer totalCount = 0;
+		if(entity.getPageNumber() == 1 && totalPage < entity.getPageSize()) {
+			totalCount = totalPage;
+		}else {
+			totalCount = parseCount(getMapper().queryPageResultsCount(entity));
+		}
+		
+		return new JpaPageResults<Roles>(entity.getPageNumber(),entity.getPageSize(),totalPage,totalCount,resultslist);
+	}
 }

+ 22 - 0
maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/GroupMemberMapper.xml

@@ -139,6 +139,28 @@
 		</if>
 	</select>
 	
+	<select id="groupsNoMember" parameterType="GroupMember" resultType="Groups">
+        select  distinct
+            g.*
+        from
+            mxk_groups g
+        where
+            g.id not in(
+                select 
+                    gm.groupid 
+                from mxk_group_member gm,mxk_userinfo u
+                where gm.memberid = u.id
+                <if test="username != null and username != ''">
+		           and u.username = #{username}
+		        </if>
+		        <if test="memberId != null and memberId != ''">
+                   and gm.memberid = #{memberid}
+                </if>
+            )
+        <if test="groupName != null and groupName != ''">
+            and g.name = #{groupName}
+        </if>
+    </select>
 	
 	<!-- GROUP_MEMBER  Group Member-->
 	<select id="groupMemberInGroup" parameterType="GroupMember" resultType="Groups">

+ 23 - 0
maxkey-persistence/src/main/resources/org/maxkey/persistence/mapper/xml/mysql/RoleMemberMapper.xml

@@ -142,6 +142,29 @@
 	</select>
 	
 	
+	<select id="rolesNoMember" parameterType="RoleMember" resultType="Roles">
+        select  distinct
+            r.*
+        from
+            mxk_roles r
+        where
+            r.id not in(
+                select 
+                    rm.roleid 
+                from mxk_role_member rm,mxk_userinfo u
+                where rm.memberid = u.id
+                <if test="username != null and username != ''">
+                   and u.username = #{username}
+                </if>
+                <if test="memberId != null and memberId != ''">
+                   and rm.memberid = #{memberid}
+                </if>
+            )
+        <if test="roleName != null and roleName != ''">
+            and r.name = #{roleName}
+        </if>
+    </select>
+    
 	<!-- ROLE_MEMBER  Roles Member-->
 	<select id="roleMemberInRole" parameterType="RoleMember" resultType="Roles">
 		select	distinct

+ 2 - 1
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/access.module.ts

@@ -29,6 +29,7 @@ import { SessionsComponent } from './sessions/sessions.component';
 import { SelectGroupsComponent } from './groups/select-groups/select-groups.component';
 import { GroupMembersEditerComponent } from './group-members/group-members-editer/group-members-editer.component';
 import { PrivilegesEditerComponent } from './privileges/privileges-editer/privileges-editer.component';
+import { MemberGroupsEditerComponent } from './group-members/member-groups-editer/member-groups-editer.component';
 
 const routes: Routes = [
   {
@@ -52,7 +53,7 @@ const routes: Routes = [
 const COMPONENTS = [SessionsComponent, GroupsComponent, GroupMembersComponent, PrivilegesComponent, GroupEditerComponent];
 
 @NgModule({
-  declarations: [...COMPONENTS, PrivilegesComponent, SelectGroupsComponent, GroupMembersEditerComponent, PrivilegesEditerComponent],
+  declarations: [...COMPONENTS, PrivilegesComponent, SelectGroupsComponent, GroupMembersEditerComponent, PrivilegesEditerComponent, MemberGroupsEditerComponent],
   imports: [NzIconModule, SharedModule, CommonModule, RouterModule.forChild(routes)],
   exports: [RouterModule]
 })

+ 4 - 5
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/group-members.component.html

@@ -3,7 +3,7 @@
 <nz-card [nzBordered]="false">
   <form nz-form [nzLayout]="'inline'" (ngSubmit)="onSearch()" class="search__form">
     <div nz-row [nzGutter]="{ xs: 8, sm: 8, md: 24, lg: 24, xl: 48, xxl: 48 }">
-      <div nz-col nzMd="10" nzSm="24">
+      <div nz-col nzMd="8" nzSm="24">
         <nz-form-item>
           <nz-form-label nzFor="name">{{ 'mxk.groups.name' | i18n }}</nz-form-label>
           <nz-form-control>
@@ -18,7 +18,7 @@
           </nz-form-control>
         </nz-form-item>
       </div>
-      <div nz-col nzMd="10" nzSm="24">
+      <div nz-col nzMd="8" nzSm="24">
         <nz-form-item>
           <nz-form-label nzFor="name">{{ 'mxk.users.username' | i18n }}</nz-form-label>
           <nz-form-control>
@@ -27,11 +27,10 @@
           </nz-form-control>
         </nz-form-item>
       </div>
-      <div nz-col [nzSpan]="query.expandForm ? 24 : 4" [class.text-right]="query.expandForm">
+      <div nz-col [nzSpan]="query.expandForm ? 24 : 8" [class.text-right]="query.expandForm">
         <button nz-button type="submit" [nzType]="'primary'" [nzLoading]="query.submitLoading">{{ 'mxk.text.query' |
           i18n }}</button>
-        <button nz-button type="reset" (click)="onReset()" class="mx-sm" style="display: none">{{ 'mxk.text.reset' |
-          i18n }}</button>
+        <button nz-button type="reset" (click)="onReset()" class="mx-sm">{{ 'mxk.text.reset' | i18n }}</button>
         <button nz-button (click)="query.expandForm = !query.expandForm" class="mx-sm" style="display: none">
           {{ query.expandForm ? ('mxk.text.collapse' | i18n) : ('mxk.text.expand' | i18n) }}</button>
       </div>

+ 49 - 21
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/group-members.component.ts

@@ -1,22 +1,22 @@
 /*
  * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
 
 import { ChangeDetectionStrategy, ViewContainerRef, ChangeDetectorRef, Component, OnInit } from '@angular/core';
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
 import { _HttpClient } from '@delon/theme';
 import { format, addDays } from 'date-fns';
 import { NzSafeAny } from 'ng-zorro-antd/core/types';
@@ -30,6 +30,7 @@ import { GroupMembersService } from '../../../service/group-members.service';
 import { set2String } from '../../../shared/index';
 import { SelectGroupsComponent } from '../groups/select-groups/select-groups.component';
 import { GroupMembersEditerComponent } from './group-members-editer/group-members-editer.component';
+import { MemberGroupsEditerComponent } from './member-groups-editer/member-groups-editer.component';
 
 @Component({
   selector: 'app-group-members',
@@ -99,10 +100,14 @@ export class GroupMembersComponent implements OnInit {
     private viewContainerRef: ViewContainerRef,
     private fb: FormBuilder,
     private msg: NzMessageService,
+    private route: ActivatedRoute,
     private cdr: ChangeDetectorRef
   ) { }
 
   ngOnInit(): void {
+    if (this.route.snapshot.queryParams['username']) {
+      this.query.params.username = this.route.snapshot.queryParams['username'];
+    }
     this.query.tableInitialize = false;
   }
 
@@ -118,7 +123,12 @@ export class GroupMembersComponent implements OnInit {
     this.fetch();
   }
 
-  onReset(): void { }
+  onReset(): void {
+    this.query.params.username = '';
+    this.query.params.groupId = '';
+    this.query.params.name = '';
+    this.fetch();
+  }
 
   onBatchDelete(e: MouseEvent): void {
     e.preventDefault();
@@ -135,22 +145,40 @@ export class GroupMembersComponent implements OnInit {
 
   onAdd(e: MouseEvent): void {
     e.preventDefault();
-    const modal = this.modalService.create({
-      nzContent: GroupMembersEditerComponent,
-      nzViewContainerRef: this.viewContainerRef,
-      nzComponentParams: {
-        isEdit: false,
-        groupId: this.query.params.groupId
-      },
-      nzWidth: 700,
-      nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
-    });
-    // Return a result when closed
-    modal.afterClose.subscribe(result => {
-      if (result.refresh) {
-        this.fetch();
-      }
-    });
+    if (this.query.params.username != '') {
+      const modal = this.modalService.create({
+        nzContent: MemberGroupsEditerComponent,
+        nzViewContainerRef: this.viewContainerRef,
+        nzComponentParams: {
+          username: this.query.params.username
+        },
+        nzWidth: 700,
+        nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
+      });
+      // Return a result when closed
+      modal.afterClose.subscribe(result => {
+        if (result.refresh) {
+          this.fetch();
+        }
+      });
+    } else if (this.query.params.groupId != '') {
+      const modal = this.modalService.create({
+        nzContent: GroupMembersEditerComponent,
+        nzViewContainerRef: this.viewContainerRef,
+        nzComponentParams: {
+          isEdit: false,
+          groupId: this.query.params.groupId
+        },
+        nzWidth: 700,
+        nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
+      });
+      // Return a result when closed
+      modal.afterClose.subscribe(result => {
+        if (result.refresh) {
+          this.fetch();
+        }
+      });
+    }
   }
 
   onSelect(e: MouseEvent): void {

+ 56 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/member-groups-editer/member-groups-editer.component.html

@@ -0,0 +1,56 @@
+<div *nzModalTitle> {{ 'mxk.text.add' | i18n }} </div>
+<nz-card [nzBordered]="false">
+    <form nz-form [nzLayout]="'inline'" (ngSubmit)="onSearch()" class="search__form">
+        <div nz-row [nzGutter]="{ xs: 8, sm: 8, md: 8, lg: 24, xl: 48, xxl: 48 }">
+            <div nz-col nzMd="14" nzSm="24">
+                <nz-form-item>
+                    <nz-form-label nzFor="name">{{ 'mxk.groups.name' | i18n }}</nz-form-label>
+                    <nz-form-control>
+                        <input nz-input [(ngModel)]="query.params.groupName" [ngModelOptions]="{ standalone: true }"
+                            name="groupName" placeholder="" id="groupName" />
+                    </nz-form-control>
+                </nz-form-item>
+            </div>
+
+            <div nz-col [nzSpan]="query.expandForm ? 24 : 10" [class.text-right]="query.expandForm">
+                <button nz-button type="submit" [nzType]="'primary'" [nzLoading]="query.submitLoading">{{
+                    'mxk.text.query' | i18n }}</button>
+                <button nz-button type="reset" (click)="onReset()" class="mx-sm" style="display: none">{{
+                    'mxk.text.reset' | i18n }}</button>
+                <button nz-button (click)="query.expandForm = !query.expandForm" class="mx-sm" style="display: none">
+                    {{ query.expandForm ? ('mxk.text.collapse' | i18n) : ('mxk.text.expand' | i18n) }}</button>
+                <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.confirm' | i18n }}</button>
+            </div>
+        </div>
+    </form>
+
+    <nz-table #dynamicTable nzTableLayout="auto" nzBordered nzShowSizeChanger [nzData]="query.results.rows"
+        [nzFrontPagination]="false" [nzTotal]="query.results.records" [nzPageSizeOptions]="query.params.pageSizeOptions"
+        [nzPageSize]="query.params.pageSize" [nzPageIndex]="query.params.pageNumber"
+        [nzLoading]="this.query.tableLoading" (nzQueryParams)="onQueryParamsChange($event)" nzWidth="100%">
+        <thead>
+            <tr>
+                <th></th>
+                <th nzAlign="center" style="display: none">Id</th>
+                <th nzAlign="center">{{ 'mxk.groups.name' | i18n }}</th>
+                <th nzAlign="center">{{ 'mxk.groups.dynamic' | i18n }}</th>
+            </tr>
+        </thead>
+        <tbody>
+            <tr *ngFor="let data of query.results.rows">
+                <td [nzChecked]="query.tableCheckedId.has(data.id)" [nzDisabled]="data.disabled"
+                    (nzCheckedChange)="onTableItemChecked(data.id, $event)"></td>
+                <td nzAlign="left" style="display: none">
+                    <span>{{ data.id }}</span>
+                </td>
+                <td nzAlign="left"> {{ data.name }}</td>
+                <td nzAlign="center"> <i *ngIf="data.dynamic == 1" nz-icon nzType="check-circle" nzTheme="fill"
+                        style="color: green"></i></td>
+            </tr>
+        </tbody>
+    </nz-table>
+</nz-card>
+<div *nzModalFooter style="display: none">
+    <button nz-button nzType="default" (click)="onClose($event)">{{ 'mxk.text.close' | i18n }}</button>
+    <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.submit' | i18n }}</button>
+</div>

+ 0 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/member-groups-editer/member-groups-editer.component.less


+ 25 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/member-groups-editer/member-groups-editer.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MemberGroupsEditerComponent } from './member-groups-editer.component';
+
+describe('MemberGroupsEditerComponent', () => {
+  let component: MemberGroupsEditerComponent;
+  let fixture: ComponentFixture<MemberGroupsEditerComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ MemberGroupsEditerComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MemberGroupsEditerComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 170 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/access/group-members/member-groups-editer/member-groups-editer.component.ts

@@ -0,0 +1,170 @@
+import { ChangeDetectionStrategy, ViewContainerRef, ChangeDetectorRef, Component, OnInit, Input } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { _HttpClient } from '@delon/theme';
+import { format, addDays } from 'date-fns';
+import { NzSafeAny } from 'ng-zorro-antd/core/types';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal';
+import { NzTableQueryParams } from 'ng-zorro-antd/table';
+
+import { GroupMembersService } from '../../../../service/group-members.service';
+
+@Component({
+  selector: 'app-member-groups-editer',
+  templateUrl: './member-groups-editer.component.html',
+  styleUrls: ['./member-groups-editer.component.less']
+})
+export class MemberGroupsEditerComponent implements OnInit {
+  @Input() username?: String;
+  query: {
+    params: {
+      groupName: String;
+      username: String;
+      protocol: String;
+      startDate: String;
+      endDate: String;
+      startDatePicker: Date;
+      endDatePicker: Date;
+      pageSize: number;
+      pageNumber: number;
+      pageSizeOptions: number[];
+    };
+    results: {
+      records: number;
+      rows: NzSafeAny[];
+    };
+    expandForm: Boolean;
+    submitLoading: boolean;
+    tableLoading: boolean;
+    tableCheckedId: Set<String>;
+    indeterminate: boolean;
+    checked: boolean;
+  } = {
+      params: {
+        groupName: '',
+        username: '',
+        protocol: '',
+        startDate: '',
+        endDate: '',
+        startDatePicker: addDays(new Date(), -30),
+        endDatePicker: new Date(),
+        pageSize: 5,
+        pageNumber: 1,
+        pageSizeOptions: [5, 15, 50]
+      },
+      results: {
+        records: 0,
+        rows: []
+      },
+      expandForm: false,
+      submitLoading: false,
+      tableLoading: false,
+      tableCheckedId: new Set<String>(),
+      indeterminate: false,
+      checked: false
+    };
+
+  constructor(
+    private modalRef: NzModalRef,
+    private groupMembersService: GroupMembersService,
+    private viewContainerRef: ViewContainerRef,
+    private fb: FormBuilder,
+    private msg: NzMessageService,
+    private cdr: ChangeDetectorRef
+  ) { }
+
+  ngOnInit(): void {
+    if (this.username) {
+      this.query.params.username = this.username;
+    }
+    this.fetch();
+  }
+
+  onQueryParamsChange(tableQueryParams: NzTableQueryParams): void {
+    this.query.params.pageNumber = tableQueryParams.pageIndex;
+    this.query.params.pageSize = tableQueryParams.pageSize;
+    this.fetch();
+  }
+
+  onSearch(): void {
+    this.fetch();
+  }
+
+  onReset(): void { }
+
+  fetch(): void {
+    this.query.submitLoading = true;
+    this.query.tableLoading = true;
+    this.query.indeterminate = false;
+    this.query.checked = true;
+    this.query.tableCheckedId.clear();
+    if (this.query.expandForm) {
+      this.query.params.endDate = format(this.query.params.endDatePicker, 'yyyy-MM-dd HH:mm:ss');
+      this.query.params.startDate = format(this.query.params.startDatePicker, 'yyyy-MM-dd HH:mm:ss');
+    } else {
+      this.query.params.endDate = '';
+      this.query.params.startDate = '';
+    }
+    this.groupMembersService.groupsNoMember(this.query.params).subscribe(res => {
+      this.query.results = res.data;
+      this.query.submitLoading = false;
+      this.query.tableLoading = false;
+      this.cdr.detectChanges();
+    });
+  }
+
+  updateTableCheckedSet(id: String, checked: boolean): void {
+    if (checked) {
+      this.query.tableCheckedId.add(id);
+    } else {
+      this.query.tableCheckedId.delete(id);
+    }
+  }
+
+  refreshTableCheckedStatus(): void {
+    const listOfEnabledData = this.query.results.rows.filter(({ disabled }) => !disabled);
+    this.query.checked = listOfEnabledData.every(({ id }) => this.query.tableCheckedId.has(id));
+    this.query.indeterminate = listOfEnabledData.some(({ id }) => this.query.tableCheckedId.has(id)) && !this.query.checked;
+  }
+
+  onTableItemChecked(id: String, checked: boolean): void {
+    //this.onTableAllChecked(false);
+    this.updateTableCheckedSet(id, checked);
+    this.refreshTableCheckedStatus();
+  }
+
+  onTableAllChecked(checked: boolean): void {
+    this.query.results.rows.filter(({ disabled }) => !disabled).forEach(({ id }) => this.updateTableCheckedSet(id, checked));
+    this.refreshTableCheckedStatus();
+  }
+
+  onSubmit(e: MouseEvent): void {
+    e.preventDefault();
+    const listOfEnabledData = this.query.results.rows.filter(({ disabled }) => !disabled);
+    let selectedData = listOfEnabledData.filter(({ id, name }) => {
+      return this.query.tableCheckedId.has(id);
+    });
+    let groupIds = '';
+    let groupNames = '';
+    for (let i = 0; i < selectedData.length; i++) {
+      groupIds = `${groupIds},${selectedData[i].id}`;
+      groupNames = `${groupNames},${selectedData[i].name}`;
+    }
+    this.groupMembersService.addMember2Groups({ username: this.username, groupId: groupIds, groupName: groupNames }).subscribe(res => {
+      this.query.results = res.data;
+      this.query.submitLoading = false;
+      this.query.tableLoading = false;
+      if (res.code == 0) {
+        this.msg.success(`提交成功`);
+        this.fetch();
+      } else {
+        this.msg.success(`提交失败`);
+      }
+      this.cdr.detectChanges();
+    });
+  }
+
+  onClose(e: MouseEvent): void {
+    e.preventDefault();
+  }
+}

+ 62 - 83
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/accounts/account-editer/account-editer.component.html

@@ -4,140 +4,119 @@
     <nz-form-item style="display: none">
       <nz-form-label [nzMd]="6" nzFor="id">{{ 'mxk.text.id' | i18n }}</nz-form-label>
       <nz-form-control [nzMd]="18" nzErrorTip="The input is not valid id!">
-        <input [(ngModel)]="form.model.id" disabled="{{ isEdit }}" [ngModelOptions]="{ standalone: true }" nz-input name="id" id="id" />
+        <input [(ngModel)]="form.model.id" disabled="{{ isEdit }}" [ngModelOptions]="{ standalone: true }" nz-input
+          name="id" id="id" />
       </nz-form-control>
     </nz-form-item>
     <nz-form-item style="display: none">
-      <nz-form-label [nzSm]="6" [nzXs]="24" readonly nzRequired nzFor="userId">{{ 'mxk.users.id' | i18n }}</nz-form-label>
+      <nz-form-label [nzSm]="6" [nzXs]="24" readonly nzRequired nzFor="userId">{{ 'mxk.users.id' | i18n }}
+      </nz-form-label>
       <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid minLength!">
-        <input [(ngModel)]="form.model.userId" [ngModelOptions]="{ standalone: true }" nz-input name="userId" id="userId" />
+        <input [(ngModel)]="form.model.userId" [ngModelOptions]="{ standalone: true }" nz-input name="userId"
+          id="userId" />
       </nz-form-control>
     </nz-form-item>
 
-    <nz-form-item>
-      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="displayName">{{ 'mxk.users.displayName' | i18n }}</nz-form-label>
+    <nz-form-item *ngIf="!isEdit && username == ''">
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="displayName">{{ 'mxk.users.displayName' | i18n }}
+      </nz-form-label>
       <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid displayName!">
         <nz-input-group nzSearch [nzAddOnAfter]="suffixButton">
-          <input
-            [(ngModel)]="form.model.displayName"
-            readonly
-            [ngModelOptions]="{ standalone: true }"
-            nz-input
-            name="displayName"
-            id="displayName"
-          />
+          <input [(ngModel)]="form.model.displayName" readonly [ngModelOptions]="{ standalone: true }" nz-input
+            name="displayName" id="displayName" />
         </nz-input-group>
         <ng-template #suffixButton>
-          <button nz-button nzType="primary" nzSearch (click)="onSelectUser($event)">{{ 'mxk.text.select' | i18n }}</button>
+          <button nz-button nzType="primary" nzSearch (click)="onSelectUser($event)">{{ 'mxk.text.select' | i18n
+            }}</button>
         </ng-template>
       </nz-form-control>
     </nz-form-item>
+    <nz-form-item *ngIf="isEdit || username !== ''">
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="displayName">{{ 'mxk.users.displayName' | i18n }}
+      </nz-form-label>
+      <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid displayName!">
+        <nz-input-group>
+          <input [(ngModel)]="form.model.displayName" disabled [ngModelOptions]="{ standalone: true }" nz-input
+            name="displayName" id="displayName" />
+        </nz-input-group>
+      </nz-form-control>
+    </nz-form-item>
     <nz-form-item>
-      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="username">{{ 'mxk.users.username' | i18n }}</nz-form-label>
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="username">{{ 'mxk.users.username' | i18n }}
+      </nz-form-label>
       <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid username!">
-        <input
-          [(ngModel)]="form.model.username"
-          disabled="true"
-          [ngModelOptions]="{ standalone: true }"
-          nz-input
-          name="username"
-          id="username"
-        />
+        <input [(ngModel)]="form.model.username" disabled="true" [ngModelOptions]="{ standalone: true }" nz-input
+          name="username" id="username" />
       </nz-form-control>
     </nz-form-item>
     <nz-form-item style="display: none">
-      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="strategyId">{{ 'mxk.accountsstrategy.id' | i18n }}</nz-form-label>
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="strategyId">{{ 'mxk.accountsstrategy.id' | i18n }}
+      </nz-form-label>
       <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid strategyId!">
-        <input
-          [(ngModel)]="form.model.strategyId"
-          readonly
-          [ngModelOptions]="{ standalone: true }"
-          nz-input
-          name="strategyId"
-          id="strategyId"
-        />
+        <input [(ngModel)]="form.model.strategyId" readonly [ngModelOptions]="{ standalone: true }" nz-input
+          name="strategyId" id="strategyId" />
       </nz-form-control>
     </nz-form-item>
     <nz-form-item>
-      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="strategyName">{{ 'mxk.accountsstrategy.name' | i18n }}</nz-form-label>
-      <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid strategyName!">
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="strategyName">{{ 'mxk.accountsstrategy.name' | i18n }}
+      </nz-form-label>
+      <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+        nzErrorTip="The input is not valid strategyName!">
         <nz-input-group nzSearch [nzAddOnAfter]="suffixStrategyButton">
-          <input
-            [(ngModel)]="form.model.strategyName"
-            [ngModelOptions]="{ standalone: true }"
-            nz-input
-            readonly
-            name="strategyName"
-            id="strategyName"
-          />
+          <input [(ngModel)]="form.model.strategyName" [ngModelOptions]="{ standalone: true }" nz-input readonly
+            name="strategyName" id="strategyName" />
         </nz-input-group>
         <ng-template #suffixStrategyButton>
-          <button nz-button nzType="primary" nzSearch (click)="onSelectStrategy($event)">{{ 'mxk.text.select' | i18n }}</button>
+          <button nz-button nzType="primary" nzSearch (click)="onSelectStrategy($event)">{{ 'mxk.text.select' | i18n
+            }}</button>
         </ng-template>
       </nz-form-control>
     </nz-form-item>
     <nz-form-item style="display: none">
       <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="appId">{{ 'mxk.apps.id' | i18n }}</nz-form-label>
       <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid appId!">
-        <input [(ngModel)]="form.model.appId" readonly [ngModelOptions]="{ standalone: true }" nz-input name="appId" id="appId" />
+        <input [(ngModel)]="form.model.appId" readonly [ngModelOptions]="{ standalone: true }" nz-input name="appId"
+          id="appId" />
       </nz-form-control>
     </nz-form-item>
     <nz-form-item>
       <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="appName">{{ 'mxk.apps.name' | i18n }}</nz-form-label>
       <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid appName!">
-        <input
-          [(ngModel)]="form.model.appName"
-          disabled="true"
-          [ngModelOptions]="{ standalone: true }"
-          nz-input
-          name="appName"
-          id="appName"
-        />
+        <input [(ngModel)]="form.model.appName" disabled="true" [ngModelOptions]="{ standalone: true }" nz-input
+          name="appName" id="appName" />
       </nz-form-control>
     </nz-form-item>
     <nz-form-item>
-      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="relatedUsername">{{ 'mxk.accounts.relatedUsername' | i18n }}</nz-form-label>
-      <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid relatedUsername!">
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="relatedUsername">{{ 'mxk.accounts.relatedUsername' | i18n
+        }}</nz-form-label>
+      <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+        nzErrorTip="The input is not valid relatedUsername!">
         <nz-input-group nzSearch [nzAddOnAfter]="suffixUserButton">
-          <input
-            [(ngModel)]="form.model.relatedUsername"
-            [ngModelOptions]="{ standalone: true }"
-            nz-input
-            disabled="{{ isEdit }}"
-            name="relatedUsername"
-            id="relatedUsername"
-          />
+          <input [(ngModel)]="form.model.relatedUsername" [ngModelOptions]="{ standalone: true }" nz-input
+            disabled="{{ isEdit }}" name="relatedUsername" id="relatedUsername" />
         </nz-input-group>
         <ng-template #suffixUserButton>
-          <button
-            nz-button
-            nzType="primary"
-            style="{{ isEdit ? 'display:none' : 'display:block' }}"
-            nzSearch
-            (click)="onGenerate($event)"
-            >{{ 'mxk.text.generate' | i18n }}</button
-          >
+          <button nz-button nzType="primary" style="{{ isEdit ? 'display:none' : 'display:block' }}" nzSearch
+            (click)="onGenerate($event)">{{ 'mxk.text.generate' | i18n }}</button>
         </ng-template>
       </nz-form-control>
     </nz-form-item>
     <nz-form-item>
-      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="relatedPassword">{{ 'mxk.accounts.relatedPassword' | i18n }}</nz-form-label>
-      <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid relatedPassword!">
+      <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="relatedPassword">{{ 'mxk.accounts.relatedPassword' | i18n
+        }}</nz-form-label>
+      <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+        nzErrorTip="The input is not valid relatedPassword!">
         <nz-input-group nzSearch [nzSuffix]="suffixTemplate" [nzAddOnAfter]="suffixPasswordButton">
-          <input
-            [(ngModel)]="form.model.relatedPassword"
-            [ngModelOptions]="{ standalone: true }"
-            nz-input
-            [type]="passwordVisible ? 'text' : 'password'"
-            name="relatedPassword"
-            id="relatedPassword"
-          />
+          <input [(ngModel)]="form.model.relatedPassword" [ngModelOptions]="{ standalone: true }" nz-input
+            [type]="passwordVisible ? 'text' : 'password'" name="relatedPassword" id="relatedPassword" />
         </nz-input-group>
         <ng-template #suffixTemplate>
-          <i nz-icon [nzType]="passwordVisible ? 'eye-invisible' : 'eye'" (click)="passwordVisible = !passwordVisible"></i>
+          <i nz-icon [nzType]="passwordVisible ? 'eye-invisible' : 'eye'"
+            (click)="passwordVisible = !passwordVisible"></i>
         </ng-template>
         <ng-template #suffixPasswordButton>
-          <button nz-button nzType="primary" nzSearch (click)="onPassword($event)">{{ 'mxk.text.generate' | i18n }}</button>
+          <button nz-button nzType="primary" nzSearch (click)="onPassword($event)">{{ 'mxk.text.generate' | i18n
+            }}</button>
         </ng-template>
       </nz-form-control>
     </nz-form-item>
@@ -147,4 +126,4 @@
 <div *nzModalFooter>
   <button nz-button nzType="default" (click)="onClose($event)">{{ 'mxk.text.close' | i18n }}</button>
   <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.submit' | i18n }}</button>
-</div>
+</div>

+ 5 - 7
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/accounts/account-editer/account-editer.component.spec.ts

@@ -1,19 +1,18 @@
 /*
  * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
 
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
@@ -25,9 +24,8 @@ describe('AccountEditerComponent', () => {
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      declarations: [ AccountEditerComponent ]
-    })
-    .compileComponents();
+      declarations: [AccountEditerComponent]
+    }).compileComponents();
   });
 
   beforeEach(() => {

+ 13 - 4
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/accounts/account-editer/account-editer.component.ts

@@ -1,19 +1,18 @@
 /*
  * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
 
 import { Component, ChangeDetectorRef, ViewContainerRef, Input, OnInit } from '@angular/core';
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@@ -42,6 +41,7 @@ import { SelectUserComponent } from '../../users/select-user/select-user.compone
 })
 export class AccountEditerComponent implements OnInit {
   @Input() id?: String;
+  @Input() username?: String;
   @Input() isEdit?: boolean;
 
   passwordVisible = false;
@@ -74,6 +74,15 @@ export class AccountEditerComponent implements OnInit {
         this.cdr.detectChanges();
       });
     }
+
+    if (this.username) {
+      this.usersService.getByUsername(`${this.username}`).subscribe(res => {
+        this.form.model.userId = res.data.id;
+        this.form.model.username = res.data.username;
+        this.form.model.displayName = res.data.displayName;
+        this.cdr.detectChanges();
+      });
+    }
   }
 
   onSelectUser(e: MouseEvent): void {

+ 11 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/accounts/accounts.component.ts

@@ -16,6 +16,7 @@
 
 import { ChangeDetectionStrategy, ViewContainerRef, ChangeDetectorRef, Component, OnInit } from '@angular/core';
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
 import { _HttpClient } from '@delon/theme';
 import { format, addDays } from 'date-fns';
 import { NzSafeAny } from 'ng-zorro-antd/core/types';
@@ -34,6 +35,7 @@ import { AccountEditerComponent } from './account-editer/account-editer.componen
   styleUrls: ['./accounts.component.less']
 })
 export class AccountsComponent implements OnInit {
+  userId: String = '';
   query: {
     params: {
       username: String;
@@ -90,10 +92,18 @@ export class AccountsComponent implements OnInit {
     private accountsService: AccountsService,
     private fb: FormBuilder,
     private msg: NzMessageService,
+    private route: ActivatedRoute,
     private cdr: ChangeDetectorRef
   ) { }
 
   ngOnInit(): void {
+    if (this.route.snapshot.queryParams['username']) {
+      this.query.params.username = this.route.snapshot.queryParams['username'];
+    }
+    if (this.route.snapshot.queryParams['userId']) {
+      this.userId = this.route.snapshot.queryParams['userId'];
+    }
+
     this.fetch();
   }
 
@@ -149,6 +159,7 @@ export class AccountsComponent implements OnInit {
       nzViewContainerRef: this.viewContainerRef,
       nzComponentParams: {
         isEdit: false,
+        username: this.query.params.username,
         id: ''
       },
       nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))

+ 2 - 1
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/permissions.module.ts

@@ -29,6 +29,7 @@ import { RolesComponent } from './roles/roles.component';
 import { ResourceEditerComponent } from './resources/resource-editer/resource-editer.component';
 import { SelectRolesComponent } from './roles/select-roles/select-roles.component';
 import { RoleMembersEditerComponent } from './role-members/role-members-editer/role-members-editer.component';
+import { MemberRolesEditerComponent } from './role-members/member-roles-editer/member-roles-editer.component';
 const routes: Routes = [
   {
     path: 'roles',
@@ -51,7 +52,7 @@ const routes: Routes = [
 const COMPONENTS = [RolesComponent];
 
 @NgModule({
-  declarations: [...COMPONENTS, RoleEditerComponent, RoleMembersComponent, ResourcesComponent, PrivilegesComponent, ResourceEditerComponent, SelectRolesComponent, RoleMembersEditerComponent],
+  declarations: [...COMPONENTS, RoleEditerComponent, RoleMembersComponent, ResourcesComponent, PrivilegesComponent, ResourceEditerComponent, SelectRolesComponent, RoleMembersEditerComponent, MemberRolesEditerComponent],
   imports: [NzIconModule, SharedModule, CommonModule, RouterModule.forChild(routes)],
   exports: [RouterModule]
 })

+ 56 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/member-roles-editer/member-roles-editer.component.html

@@ -0,0 +1,56 @@
+<div *nzModalTitle> {{ 'mxk.text.add' | i18n }} </div>
+<nz-card [nzBordered]="false">
+    <form nz-form [nzLayout]="'inline'" (ngSubmit)="onSearch()" class="search__form">
+        <div nz-row [nzGutter]="{ xs: 8, sm: 8, md: 8, lg: 24, xl: 48, xxl: 48 }">
+            <div nz-col nzMd="14" nzSm="24">
+                <nz-form-item>
+                    <nz-form-label nzFor="name">{{ 'mxk.roles.name' | i18n }}</nz-form-label>
+                    <nz-form-control>
+                        <input nz-input [(ngModel)]="query.params.roleName" [ngModelOptions]="{ standalone: true }"
+                            name="roleName" placeholder="" id="roleName" />
+                    </nz-form-control>
+                </nz-form-item>
+            </div>
+
+            <div nz-col [nzSpan]="query.expandForm ? 24 : 10" [class.text-right]="query.expandForm">
+                <button nz-button type="submit" [nzType]="'primary'" [nzLoading]="query.submitLoading">{{
+                    'mxk.text.query' | i18n }}</button>
+                <button nz-button type="reset" (click)="onReset()" class="mx-sm" style="display: none">{{
+                    'mxk.text.reset' | i18n }}</button>
+                <button nz-button (click)="query.expandForm = !query.expandForm" class="mx-sm" style="display: none">
+                    {{ query.expandForm ? ('mxk.text.collapse' | i18n) : ('mxk.text.expand' | i18n) }}</button>
+                <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.confirm' | i18n }}</button>
+            </div>
+        </div>
+    </form>
+
+    <nz-table #dynamicTable nzTableLayout="auto" nzBordered nzShowSizeChanger [nzData]="query.results.rows"
+        [nzFrontPagination]="false" [nzTotal]="query.results.records" [nzPageSizeOptions]="query.params.pageSizeOptions"
+        [nzPageSize]="query.params.pageSize" [nzPageIndex]="query.params.pageNumber"
+        [nzLoading]="this.query.tableLoading" (nzQueryParams)="onQueryParamsChange($event)" nzWidth="100%">
+        <thead>
+            <tr>
+                <th></th>
+                <th nzAlign="center" style="display: none">Id</th>
+                <th nzAlign="center">{{ 'mxk.roles.name' | i18n }}</th>
+                <th nzAlign="center">{{ 'mxk.roles.dynamic' | i18n }}</th>
+            </tr>
+        </thead>
+        <tbody>
+            <tr *ngFor="let data of query.results.rows">
+                <td [nzChecked]="query.tableCheckedId.has(data.id)" [nzDisabled]="data.disabled"
+                    (nzCheckedChange)="onTableItemChecked(data.id, $event)"></td>
+                <td nzAlign="left" style="display: none">
+                    <span>{{ data.id }}</span>
+                </td>
+                <td nzAlign="left"> {{ data.name }}</td>
+                <td nzAlign="center"> <i *ngIf="data.dynamic == 1" nz-icon nzType="check-circle" nzTheme="fill"
+                        style="color: green"></i></td>
+            </tr>
+        </tbody>
+    </nz-table>
+</nz-card>
+<div *nzModalFooter style="display: none">
+    <button nz-button nzType="default" (click)="onClose($event)">{{ 'mxk.text.close' | i18n }}</button>
+    <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.submit' | i18n }}</button>
+</div>

+ 0 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/member-roles-editer/member-roles-editer.component.less


+ 25 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/member-roles-editer/member-roles-editer.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MemberRolesEditerComponent } from './member-roles-editer.component';
+
+describe('MemberRolesEditerComponent', () => {
+  let component: MemberRolesEditerComponent;
+  let fixture: ComponentFixture<MemberRolesEditerComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ MemberRolesEditerComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MemberRolesEditerComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 169 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/member-roles-editer/member-roles-editer.component.ts

@@ -0,0 +1,169 @@
+import { ChangeDetectionStrategy, ViewContainerRef, ChangeDetectorRef, Component, OnInit, Input } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { _HttpClient } from '@delon/theme';
+import { format, addDays } from 'date-fns';
+import { NzSafeAny } from 'ng-zorro-antd/core/types';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal';
+import { NzTableQueryParams } from 'ng-zorro-antd/table';
+
+import { RoleMembersService } from '../../../../service/role-members.service';
+@Component({
+  selector: 'app-member-roles-editer',
+  templateUrl: './member-roles-editer.component.html',
+  styleUrls: ['./member-roles-editer.component.less']
+})
+export class MemberRolesEditerComponent implements OnInit {
+  @Input() username?: String;
+  query: {
+    params: {
+      roleName: String;
+      username: String;
+      protocol: String;
+      startDate: String;
+      endDate: String;
+      startDatePicker: Date;
+      endDatePicker: Date;
+      pageSize: number;
+      pageNumber: number;
+      pageSizeOptions: number[];
+    };
+    results: {
+      records: number;
+      rows: NzSafeAny[];
+    };
+    expandForm: Boolean;
+    submitLoading: boolean;
+    tableLoading: boolean;
+    tableCheckedId: Set<String>;
+    indeterminate: boolean;
+    checked: boolean;
+  } = {
+      params: {
+        roleName: '',
+        username: '',
+        protocol: '',
+        startDate: '',
+        endDate: '',
+        startDatePicker: addDays(new Date(), -30),
+        endDatePicker: new Date(),
+        pageSize: 5,
+        pageNumber: 1,
+        pageSizeOptions: [5, 15, 50]
+      },
+      results: {
+        records: 0,
+        rows: []
+      },
+      expandForm: false,
+      submitLoading: false,
+      tableLoading: false,
+      tableCheckedId: new Set<String>(),
+      indeterminate: false,
+      checked: false
+    };
+
+  constructor(
+    private modalRef: NzModalRef,
+    private roleMembersService: RoleMembersService,
+    private viewContainerRef: ViewContainerRef,
+    private fb: FormBuilder,
+    private msg: NzMessageService,
+    private cdr: ChangeDetectorRef
+  ) { }
+
+  ngOnInit(): void {
+    if (this.username) {
+      this.query.params.username = this.username;
+    }
+    this.fetch();
+  }
+
+  onQueryParamsChange(tableQueryParams: NzTableQueryParams): void {
+    this.query.params.pageNumber = tableQueryParams.pageIndex;
+    this.query.params.pageSize = tableQueryParams.pageSize;
+    this.fetch();
+  }
+
+  onSearch(): void {
+    this.fetch();
+  }
+
+  onReset(): void { }
+
+  fetch(): void {
+    this.query.submitLoading = true;
+    this.query.tableLoading = true;
+    this.query.indeterminate = false;
+    this.query.checked = true;
+    this.query.tableCheckedId.clear();
+    if (this.query.expandForm) {
+      this.query.params.endDate = format(this.query.params.endDatePicker, 'yyyy-MM-dd HH:mm:ss');
+      this.query.params.startDate = format(this.query.params.startDatePicker, 'yyyy-MM-dd HH:mm:ss');
+    } else {
+      this.query.params.endDate = '';
+      this.query.params.startDate = '';
+    }
+    this.roleMembersService.rolesNoMember(this.query.params).subscribe(res => {
+      this.query.results = res.data;
+      this.query.submitLoading = false;
+      this.query.tableLoading = false;
+      this.cdr.detectChanges();
+    });
+  }
+
+  updateTableCheckedSet(id: String, checked: boolean): void {
+    if (checked) {
+      this.query.tableCheckedId.add(id);
+    } else {
+      this.query.tableCheckedId.delete(id);
+    }
+  }
+
+  refreshTableCheckedStatus(): void {
+    const listOfEnabledData = this.query.results.rows.filter(({ disabled }) => !disabled);
+    this.query.checked = listOfEnabledData.every(({ id }) => this.query.tableCheckedId.has(id));
+    this.query.indeterminate = listOfEnabledData.some(({ id }) => this.query.tableCheckedId.has(id)) && !this.query.checked;
+  }
+
+  onTableItemChecked(id: String, checked: boolean): void {
+    //this.onTableAllChecked(false);
+    this.updateTableCheckedSet(id, checked);
+    this.refreshTableCheckedStatus();
+  }
+
+  onTableAllChecked(checked: boolean): void {
+    this.query.results.rows.filter(({ disabled }) => !disabled).forEach(({ id }) => this.updateTableCheckedSet(id, checked));
+    this.refreshTableCheckedStatus();
+  }
+
+  onSubmit(e: MouseEvent): void {
+    e.preventDefault();
+    const listOfEnabledData = this.query.results.rows.filter(({ disabled }) => !disabled);
+    let selectedData = listOfEnabledData.filter(({ id, name }) => {
+      return this.query.tableCheckedId.has(id);
+    });
+    let roleIds = '';
+    let roleNames = '';
+    for (let i = 0; i < selectedData.length; i++) {
+      roleIds = `${roleIds},${selectedData[i].id}`;
+      roleNames = `${roleNames},${selectedData[i].name}`;
+    }
+    this.roleMembersService.addMember2Roles({ username: this.username, roleId: roleIds, roleName: roleNames }).subscribe(res => {
+      this.query.results = res.data;
+      this.query.submitLoading = false;
+      this.query.tableLoading = false;
+      if (res.code == 0) {
+        this.msg.success(`提交成功`);
+        this.fetch();
+      } else {
+        this.msg.success(`提交失败`);
+      }
+      this.cdr.detectChanges();
+    });
+  }
+
+  onClose(e: MouseEvent): void {
+    e.preventDefault();
+  }
+}

+ 4 - 5
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/role-members.component.html

@@ -3,7 +3,7 @@
 <nz-card [nzBordered]="false">
   <form nz-form [nzLayout]="'inline'" (ngSubmit)="onSearch()" class="search__form">
     <div nz-row [nzGutter]="{ xs: 8, sm: 8, md: 24, lg: 24, xl: 48, xxl: 48 }">
-      <div nz-col nzMd="10" nzSm="24">
+      <div nz-col nzMd="8" nzSm="24">
         <nz-form-item>
           <nz-form-label nzFor="name">{{ 'mxk.roles.name' | i18n }}</nz-form-label>
           <nz-form-control>
@@ -18,7 +18,7 @@
           </nz-form-control>
         </nz-form-item>
       </div>
-      <div nz-col nzMd="10" nzSm="24">
+      <div nz-col nzMd="8" nzSm="24">
         <nz-form-item>
           <nz-form-label nzFor="name">{{ 'mxk.users.username' | i18n }}</nz-form-label>
           <nz-form-control>
@@ -27,11 +27,10 @@
           </nz-form-control>
         </nz-form-item>
       </div>
-      <div nz-col [nzSpan]="query.expandForm ? 24 : 4" [class.text-right]="query.expandForm">
+      <div nz-col [nzSpan]="query.expandForm ? 24 : 8" [class.text-right]="query.expandForm">
         <button nz-button type="submit" [nzType]="'primary'" [nzLoading]="query.submitLoading">{{ 'mxk.text.query' |
           i18n }}</button>
-        <button nz-button type="reset" (click)="onReset()" class="mx-sm" style="display: none">{{ 'mxk.text.reset' |
-          i18n }}</button>
+        <button nz-button type="reset" (click)="onReset()" class="mx-sm">{{ 'mxk.text.reset' | i18n }}</button>
         <button nz-button (click)="query.expandForm = !query.expandForm" class="mx-sm" style="display: none">
           {{ query.expandForm ? ('mxk.text.collapse' | i18n) : ('mxk.text.expand' | i18n) }}</button>
       </div>

+ 50 - 21
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/permissions/role-members/role-members.component.ts

@@ -1,22 +1,22 @@
 /*
  * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
 
 import { ChangeDetectionStrategy, ViewContainerRef, ChangeDetectorRef, Component, OnInit } from '@angular/core';
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
 import { _HttpClient } from '@delon/theme';
 import { format, addDays } from 'date-fns';
 import { NzSafeAny } from 'ng-zorro-antd/core/types';
@@ -29,6 +29,7 @@ import { NzFormatEmitEvent, NzTreeNode, NzTreeNodeOptions } from 'ng-zorro-antd/
 import { RoleMembersService } from '../../../service/role-members.service';
 import { set2String } from '../../../shared/index';
 import { SelectRolesComponent } from '../roles/select-roles/select-roles.component';
+import { MemberRolesEditerComponent } from './member-roles-editer/member-roles-editer.component';
 import { RoleMembersEditerComponent } from './role-members-editer/role-members-editer.component';
 
 @Component({
@@ -101,10 +102,14 @@ export class RoleMembersComponent implements OnInit {
     private viewContainerRef: ViewContainerRef,
     private fb: FormBuilder,
     private msg: NzMessageService,
+    private route: ActivatedRoute,
     private cdr: ChangeDetectorRef
   ) { }
 
   ngOnInit(): void {
+    if (this.route.snapshot.queryParams['username']) {
+      this.query.params.username = this.route.snapshot.queryParams['username'];
+    }
     this.query.tableInitialize = false;
   }
 
@@ -120,7 +125,13 @@ export class RoleMembersComponent implements OnInit {
     this.fetch();
   }
 
-  onReset(): void { }
+  onReset(): void {
+    this.query.params.username = '';
+    this.query.params.name = '';
+    this.query.params.roleId = '';
+    this.query.params.roleName = '';
+    this.fetch();
+  }
 
   onBatchDelete(e: MouseEvent): void {
     e.preventDefault();
@@ -137,22 +148,40 @@ export class RoleMembersComponent implements OnInit {
 
   onAdd(e: MouseEvent): void {
     e.preventDefault();
-    const modal = this.modalService.create({
-      nzContent: RoleMembersEditerComponent,
-      nzViewContainerRef: this.viewContainerRef,
-      nzComponentParams: {
-        isEdit: false,
-        roleId: this.query.params.roleId
-      },
-      nzWidth: 700,
-      nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
-    });
-    // Return a result when closed
-    modal.afterClose.subscribe(result => {
-      if (result.refresh) {
-        this.fetch();
-      }
-    });
+    if (this.query.params.username != '') {
+      const modal = this.modalService.create({
+        nzContent: MemberRolesEditerComponent,
+        nzViewContainerRef: this.viewContainerRef,
+        nzComponentParams: {
+          username: this.query.params.username
+        },
+        nzWidth: 700,
+        nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
+      });
+      // Return a result when closed
+      modal.afterClose.subscribe(result => {
+        if (result.refresh) {
+          this.fetch();
+        }
+      });
+    } else if (this.query.params.roleId != '') {
+      const modal = this.modalService.create({
+        nzContent: RoleMembersEditerComponent,
+        nzViewContainerRef: this.viewContainerRef,
+        nzComponentParams: {
+          isEdit: false,
+          roleId: this.query.params.roleId
+        },
+        nzWidth: 700,
+        nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000))
+      });
+      // Return a result when closed
+      modal.afterClose.subscribe(result => {
+        if (result.refresh) {
+          this.fetch();
+        }
+      });
+    }
   }
 
   onSelect(e: MouseEvent): void {

+ 12 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/users/users.component.html

@@ -110,6 +110,18 @@
                 </button>
                 <nz-dropdown-menu #menuMoreAction="nzDropdownMenu">
                   <ul nz-menu>
+                    <li nz-menu-item *ngIf="data.status == 1"
+                      (click)="onNavToUrl($event, data.id, data.username, 'accounts')">{{
+                      'mxk.text.accounts' | i18n
+                      }}</li>
+                    <li nz-menu-item *ngIf="data.status == 1"
+                      (click)="onNavToUrl($event, data.id, data.username, 'groups')">{{
+                      'mxk.text.groups' | i18n
+                      }}</li>
+                    <li nz-menu-item *ngIf="data.status == 1"
+                      (click)="onNavToUrl($event, data.id, data.username, 'roles')">{{
+                      'mxk.text.roles' | i18n
+                      }}</li>
                     <li nz-menu-item *ngIf="data.status == 1" (click)="changePasswordById($event, data.id)">{{
                       'mxk.text.changepassword' | i18n
                       }}</li>

+ 13 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/users/users.component.ts

@@ -16,6 +16,7 @@
 
 import { ChangeDetectionStrategy, ViewContainerRef, ChangeDetectorRef, Component, OnInit } from '@angular/core';
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
 import { _HttpClient } from '@delon/theme';
 import { format, addDays } from 'date-fns';
 import { NzSafeAny } from 'ng-zorro-antd/core/types';
@@ -94,6 +95,7 @@ export class UsersComponent implements OnInit {
     private orgsService: OrganizationsService,
     private fb: FormBuilder,
     private msg: NzMessageService,
+    private router: Router,
     private cdr: ChangeDetectorRef
   ) { }
 
@@ -213,6 +215,17 @@ export class UsersComponent implements OnInit {
     });
   }
 
+  onNavToUrl(e: MouseEvent, userId: String, username: String, navType: String) {
+    e.preventDefault();
+    if (navType === 'accounts') {
+      this.router.navigateByUrl(`/accounts?username=${username}&userId=${userId}`);
+    } else if (navType === 'groups') {
+      this.router.navigateByUrl(`/access/groupmembers?username=${username}&userId=${userId}`);
+    } else if (navType === 'roles') {
+      this.router.navigateByUrl(`/permissions/rolemembers?username=${username}&userId=${userId}`);
+    }
+  }
+
   onUpdateStatus(e: MouseEvent, userId: String, status: number): void {
     e.preventDefault();
     this.usersService.updateStatus({ id: userId, status: status }).subscribe(res => {

+ 14 - 4
maxkey-web-frontend/maxkey-web-mgt-app/src/app/service/group-members.service.ts

@@ -1,19 +1,18 @@
 /*
  * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
 
 import { HttpClient } from '@angular/common/http';
 import { Injectable } from '@angular/core';
@@ -22,6 +21,7 @@ import { Observable } from 'rxjs';
 
 import { GroupMembers } from '../entity/GroupMembers';
 import { Message } from '../entity/Message';
+import { PageResults } from '../entity/PageResults';
 import { BaseService } from './base.service';
 
 @Injectable({
@@ -33,4 +33,14 @@ export class GroupMembersService extends BaseService<GroupMembers> {
     this.server.urls.member = '/memberInGroup';
     this.server.urls.memberOut = '/memberNotInGroup';
   }
+
+  groupsNoMember(params: NzSafeAny): Observable<Message<PageResults>> {
+    return this.http.get<Message<PageResults>>(`${this.server.urls.base}/groupsNoMember`, {
+      params: this.parseParams(params)
+    });
+  }
+
+  addMember2Groups(body: any): Observable<Message<PageResults>> {
+    return this.http.post<Message<PageResults>>(`${`${this.server.urls.base}/addMember2Groups`}`, body);
+  }
 }

+ 14 - 5
maxkey-web-frontend/maxkey-web-mgt-app/src/app/service/role-members.service.ts

@@ -1,19 +1,18 @@
 /*
  * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
 
 import { HttpClient } from '@angular/common/http';
 import { Injectable } from '@angular/core';
@@ -21,9 +20,9 @@ import { NzSafeAny } from 'ng-zorro-antd/core/types';
 import { Observable } from 'rxjs';
 
 import { Message } from '../entity/Message';
+import { PageResults } from '../entity/PageResults';
 import { RoleMembers } from '../entity/RoleMembers';
 import { BaseService } from './base.service';
-
 @Injectable({
   providedIn: 'root'
 })
@@ -33,4 +32,14 @@ export class RoleMembersService extends BaseService<RoleMembers> {
     this.server.urls.member = '/memberInRole';
     this.server.urls.memberOut = '/memberNotInRole';
   }
+
+  rolesNoMember(params: NzSafeAny): Observable<Message<PageResults>> {
+    return this.http.get<Message<PageResults>>(`${this.server.urls.base}/rolesNoMember`, {
+      params: this.parseParams(params)
+    });
+  }
+
+  addMember2Roles(body: any): Observable<Message<PageResults>> {
+    return this.http.post<Message<PageResults>>(`${`${this.server.urls.base}/addMember2Roles`}`, body);
+  }
 }

+ 6 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/service/users.service.ts

@@ -37,6 +37,12 @@ export class UsersService extends BaseService<Users> {
     });
   }
 
+  getByUsername(params: NzSafeAny): Observable<Message<Users>> {
+    return this.http.get<Message<Users>>(`${this.server.urls.base}/getByUsername/${params}`, {
+      params: this.parseParams(params)
+    });
+  }
+
   updateStatus(params: NzSafeAny): Observable<Message<Users>> {
     return this.http.get<Message<Users>>(`${this.server.urls.base}/updateStatus`, {
       params: this.parseParams(params)

+ 3 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/en-US.json

@@ -652,6 +652,9 @@
       "disable":"Disable",
       "lock":"Lock",
       "unlock":"UnLock",
+      "accounts":"Accounts",
+      "roles":"Roles",
+      "groups":"Groups",
       "moreaction":"More",
       "submit":"Submit",
       "generate":"Generate",

+ 4 - 1
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-CN.json

@@ -551,7 +551,7 @@
     "password":{
       "id": "用户编码",
       "displayName": "姓名",
-      "username": "账号",
+      "username": "登录账号",
       "oldPassword": "当前密码",
       "password": "新密码",
       "confirmPassword": "确认密码",
@@ -651,6 +651,9 @@
       "disable":"禁用",
       "lock":"锁定",
       "unlock":"解锁",
+      "accounts":"账号",
+      "roles":"角色",
+      "groups":"用户组",
       "submit":"提交",
       "moreaction":"更多",
       "generate":"生成",

+ 49 - 4
maxkey-webs/maxkey-web-mgt/src/main/java/org/maxkey/web/access/contorller/GroupMemberController.java

@@ -1,5 +1,5 @@
 /*
- * Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
+ * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,15 +20,17 @@ package org.maxkey.web.access.contorller;
 import org.apache.mybatis.jpa.persistence.JpaPageResults;
 import org.maxkey.authn.annotation.CurrentUser;
 import org.maxkey.entity.GroupMember;
+import org.maxkey.entity.Groups;
 import org.maxkey.entity.Message;
 import org.maxkey.entity.UserInfo;
 import org.maxkey.persistence.service.GroupMemberService;
 import org.maxkey.persistence.service.GroupsService;
+import org.maxkey.persistence.service.UserInfoService;
+import org.maxkey.util.StringUtils;
 import org.maxkey.web.WebContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
@@ -45,13 +47,14 @@ public class GroupMemberController {
 	final static Logger _logger = LoggerFactory.getLogger(GroupMemberController.class);
 	
 	@Autowired
-	@Qualifier("groupMemberService")
 	GroupMemberService groupMemberService;
 
 	@Autowired
-	@Qualifier("groupsService")
 	GroupsService groupsService;
 	
+	@Autowired
+	UserInfoService userInfoService;
+	
 	@RequestMapping(value = { "/fetch" }, produces = {MediaType.APPLICATION_JSON_VALUE})
 	@ResponseBody
 	public ResponseEntity<?> fetch(
@@ -82,6 +85,13 @@ public class GroupMemberController {
 				groupMemberService.queryPageResults("memberNotInGroup",groupMember)).buildResponse();
 	}
 	
+	@RequestMapping(value = { "/groupsNoMember" })
+	@ResponseBody
+	public ResponseEntity<?> groupsNoMember(@ModelAttribute  GroupMember groupMember,@CurrentUser UserInfo currentUser) {
+		groupMember.setInstId(currentUser.getInstId());
+		return new Message<JpaPageResults<Groups>>(
+				groupMemberService.groupsNoMember(groupMember)).buildResponse();
+	}
 	
 	@RequestMapping(value = {"/add"})
 	@ResponseBody
@@ -118,6 +128,41 @@ public class GroupMemberController {
 		return new Message<GroupMember>(Message.FAIL).buildResponse();
 	}
 	
+	
+	@RequestMapping(value = {"/addMember2Groups"})
+	@ResponseBody
+	public ResponseEntity<?> addMember2Groups(@RequestBody GroupMember groupMember,@CurrentUser UserInfo currentUser) {
+		if (groupMember == null || StringUtils.isBlank(groupMember.getUsername())) {
+			return new Message<GroupMember>(Message.FAIL).buildResponse();
+		}
+		UserInfo userInfo = userInfoService.findByUsername(groupMember.getUsername());
+		
+		boolean result = true;
+		String groupIds = groupMember.getGroupId();
+		String groupNames = groupMember.getGroupName();
+		if (groupIds != null && userInfo != null) {
+			String[] arrGroupIds = groupIds.split(",");
+			String[] arrGroupNames = groupNames.split(",");
+			
+			for (int i = 0; i < arrGroupIds.length; i++) {
+				GroupMember newGroupMember = 
+						new GroupMember(
+							arrGroupIds[i],
+							arrGroupNames[i], 
+							userInfo.getId(), 
+							userInfo.getDisplayName(),
+							"USER",
+							currentUser.getInstId());
+				newGroupMember.setId(WebContext.genId());
+				result = groupMemberService.insert(newGroupMember);
+			}
+			if(result) {
+				return new Message<GroupMember>(Message.SUCCESS).buildResponse();
+			}
+		}
+		return new Message<GroupMember>(Message.FAIL).buildResponse();
+	}
+	
 	@ResponseBody
 	@RequestMapping(value={"/delete"}, produces = {MediaType.APPLICATION_JSON_VALUE})
 	public ResponseEntity<?> delete(@RequestParam("ids") String ids,@CurrentUser UserInfo currentUser) {

+ 7 - 0
maxkey-webs/maxkey-web-mgt/src/main/java/org/maxkey/web/contorller/UserInfoController.java

@@ -107,6 +107,13 @@ public class UserInfoController {
 		return new Message<UserInfo>(userInfo).buildResponse();
 	}
 	
+	@RequestMapping(value = { "/getByUsername/{username}" }, produces = {MediaType.APPLICATION_JSON_VALUE})
+	public ResponseEntity<?> getByUsername(@PathVariable("username") String username) {
+		UserInfo userInfo=userInfoService.findByUsername(username);
+		userInfo.trans();
+		return new Message<UserInfo>(userInfo).buildResponse();
+	}
+	
 	@ResponseBody
 	@RequestMapping(value={"/add"}, produces = {MediaType.APPLICATION_JSON_VALUE})
 	public ResponseEntity<?> insert(@RequestBody UserInfo userInfo,@CurrentUser UserInfo currentUser) {

+ 50 - 2
maxkey-webs/maxkey-web-mgt/src/main/java/org/maxkey/web/permissions/contorller/RoleMemberController.java

@@ -21,9 +21,12 @@ import org.apache.mybatis.jpa.persistence.JpaPageResults;
 import org.maxkey.authn.annotation.CurrentUser;
 import org.maxkey.entity.Message;
 import org.maxkey.entity.RoleMember;
+import org.maxkey.entity.Roles;
 import org.maxkey.entity.UserInfo;
 import org.maxkey.persistence.service.RoleMemberService;
 import org.maxkey.persistence.service.RolesService;
+import org.maxkey.persistence.service.UserInfoService;
+import org.maxkey.util.StringUtils;
 import org.maxkey.web.WebContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -49,6 +52,9 @@ public class RoleMemberController {
 	@Autowired
 	RolesService rolesService;
 	
+	@Autowired
+	UserInfoService userInfoService;
+	
 	@RequestMapping(value = { "/fetch" }, produces = {MediaType.APPLICATION_JSON_VALUE})
 	@ResponseBody
 	public ResponseEntity<?> fetch(
@@ -85,7 +91,7 @@ public class RoleMemberController {
 		if (roleMember == null || roleMember.getRoleId() == null) {
 			return new Message<RoleMember>(Message.FAIL).buildResponse();
 		}
-		String groupId = roleMember.getRoleId();
+		String roleId = roleMember.getRoleId();
 		
 		boolean result = true;
 		String memberIds = roleMember.getMemberId();
@@ -97,7 +103,7 @@ public class RoleMemberController {
 			for (int i = 0; i < arrMemberIds.length; i++) {
 				RoleMember newRoleMember = 
 						new RoleMember(
-								groupId,
+								roleId,
 								roleMember.getRoleName(), 
 								arrMemberIds[i], 
 								arrMemberNames[i],
@@ -113,6 +119,48 @@ public class RoleMemberController {
 		return new Message<RoleMember>(Message.FAIL).buildResponse();
 	}
 	
+	@RequestMapping(value = { "/rolesNoMember" })
+	@ResponseBody
+	public ResponseEntity<?> rolesNoMember(@ModelAttribute  RoleMember roleMember,@CurrentUser UserInfo currentUser) {
+		roleMember.setInstId(currentUser.getInstId());
+		return new Message<JpaPageResults<Roles>>(
+				roleMemberService.rolesNoMember(roleMember)).buildResponse();
+	}
+	
+	@RequestMapping(value = {"/addMember2Roles"})
+	@ResponseBody
+	public ResponseEntity<?> addMember2Roles(@RequestBody RoleMember roleMember,@CurrentUser UserInfo currentUser) {
+		if (roleMember == null || StringUtils.isBlank(roleMember.getUsername())) {
+			return new Message<RoleMember>(Message.FAIL).buildResponse();
+		}
+		UserInfo userInfo = userInfoService.findByUsername(roleMember.getUsername());
+		
+		boolean result = true;
+		String roleIds = roleMember.getRoleId();
+		String roleNames = roleMember.getRoleName();
+		if (roleIds != null) {
+			String[] arrRoleIds = roleIds.split(",");
+			String[] arrRoleNames = roleNames.split(",");
+			
+			for (int i = 0; i < arrRoleIds.length; i++) {
+				RoleMember newRoleMember = 
+						new RoleMember(
+								arrRoleIds[i],
+								arrRoleNames[i], 
+								userInfo.getId(), 
+								userInfo.getDisplayName(),
+								"USER",
+								currentUser.getInstId());
+				newRoleMember.setId(WebContext.genId());
+				result = roleMemberService.insert(newRoleMember);
+			}
+			if(result) {
+				return new Message<RoleMember>(Message.SUCCESS).buildResponse();
+			}
+		}
+		return new Message<RoleMember>(Message.FAIL).buildResponse();
+	}
+	
 	@ResponseBody
 	@RequestMapping(value={"/delete"}, produces = {MediaType.APPLICATION_JSON_VALUE})
 	public ResponseEntity<?> delete(@RequestParam("ids") String ids,@CurrentUser UserInfo currentUser) {