diff --git a/apps/admin-gui/src/app/shared/components/dialogs/add-member-dialog/add-member-dialog.component.html b/apps/admin-gui/src/app/shared/components/dialogs/add-member-dialog/add-member-dialog.component.html index b71a93033..35665514f 100644 --- a/apps/admin-gui/src/app/shared/components/dialogs/add-member-dialog/add-member-dialog.component.html +++ b/apps/admin-gui/src/app/shared/components/dialogs/add-member-dialog/add-member-dialog.component.html @@ -22,7 +22,7 @@

{{'DIALOGS.ADD_MEMBERS.TITLE' | translate}}

-
+
{{'DIALOGS.ADD_MEMBERS.NO_USERS_FOUND' | translate}} diff --git a/apps/admin-gui/src/app/shared/components/dialogs/add-member-dialog/add-member-dialog.component.ts b/apps/admin-gui/src/app/shared/components/dialogs/add-member-dialog/add-member-dialog.component.ts index e7da05454..1931e49b4 100644 --- a/apps/admin-gui/src/app/shared/components/dialogs/add-member-dialog/add-member-dialog.component.ts +++ b/apps/admin-gui/src/app/shared/components/dialogs/add-member-dialog/add-member-dialog.component.ts @@ -1,16 +1,19 @@ -import {Component, Inject, OnInit} from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; -import {TranslateService} from '@ngx-translate/core'; -import {NotificatorService} from '../../../../core/services/common/notificator.service'; -import {ActivatedRoute, Router} from '@angular/router'; -import {SelectionModel} from '@angular/cdk/collections'; -import { MembersService, RegistrarService, VoService } from '@perun-web-apps/perun/services'; -import { MemberCandidate } from '@perun-web-apps/perun/models'; +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { TranslateService } from '@ngx-translate/core'; +import { NotificatorService } from '../../../../core/services/common/notificator.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { SelectionModel } from '@angular/cdk/collections'; +import { GroupService, MembersService, RegistrarService, VoService } from '@perun-web-apps/perun/services'; +import { Group, MemberCandidate } from '@perun-web-apps/perun/models'; import { Urns } from '@perun-web-apps/perun/urns'; export interface AddMemberDialogData { - voId: number; + voId?: number; + entityId: number; + group?: Group; theme: string; + type: 'vo' | 'group'; } @Component({ @@ -24,6 +27,7 @@ export class AddMemberDialogComponent implements OnInit { private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private data: AddMemberDialogData, private memberService: MembersService, + private groupService: GroupService, private voService: VoService, private registrarService: RegistrarService, private translate: TranslateService, @@ -58,23 +62,40 @@ export class AddMemberDialogComponent implements OnInit { onAdd(): void { this.processing = true; // TODO Adds only one member at the time. In the future there would be need to add more - this.memberService.addMember(this.data.voId, this.selection.selected[0].richUser.id).subscribe(() => { - this.translate.get('DIALOGS.ADD_MEMBERS.SUCCESS').subscribe(() => { - this.notificator.showSuccess(this.successMessage); - this.dialogRef.close(); - this.processing = false; - }); - }); + const selectedMemberCandidate = this.selection.selected[0]; + + if (this.data.type === 'vo') { + if (!!selectedMemberCandidate.richUser) { + this.addUserToVo(selectedMemberCandidate); + } else { + this.addCandidateToVo(selectedMemberCandidate); + } + } else if (this.data.type === 'group') { + if (!!selectedMemberCandidate.member) { + this.addMemberToGroup(selectedMemberCandidate); + } + else if (!!selectedMemberCandidate.richUser) { + this.addUserToGroup(selectedMemberCandidate); + } + else if (!!selectedMemberCandidate.candidate) { + this.addCandidateToGroup(selectedMemberCandidate); + } + } } onInvite(): void { + // TODO Was not tested properly. Need to be tested on devel. - this.registrarService.sendInvitationToExistingUser(this.selection.selected[0].richUser.id, this.data.voId).subscribe(() => { - this.translate.get('DIALOGS.ADD_MEMBERS.SUCCESS_INVITE').subscribe(() => { - this.notificator.showSuccess(this.successInviteMessage); - this.dialogRef.close(); - }); - }); + if (this.data.type === 'vo') { + this.registrarService.sendInvitationToExistingUser(this.selection.selected[0].richUser.id, this.data.entityId).subscribe(() => { + this.translate.get('DIALOGS.ADD_MEMBERS.SUCCESS_INVITE').subscribe(() => { + this.notificator.showSuccess(this.successInviteMessage); + this.dialogRef.close(); + }); + }); + } else if (this.data.type === 'group') { + //TODO + } } onSearchByString() { @@ -83,7 +104,10 @@ export class AddMemberDialogComponent implements OnInit { this.selection.clear(); // TODO properly test it on devel when possible. - this.voService.getCompleteCandidates(this.data.voId, + + this.voService.getCompleteCandidates( + this.data.entityId, + this.data.type, [Urns.USER_DEF_ORGANIZATION, Urns.USER_DEF_PREFERRED_MAIL], this.searchString).subscribe( members => { this.members = members; @@ -98,4 +122,47 @@ export class AddMemberDialogComponent implements OnInit { this.theme = this.data.theme; } + private addUserToVo(selectedMemberCandidate: MemberCandidate) { + this.memberService.createMember(this.data.entityId, selectedMemberCandidate.richUser.id).subscribe(() => { + this.onAddSuccess(); + }, () => this.onError()); + } + + private addCandidateToVo(selectedMemberCandidate: MemberCandidate) { + this.memberService.createMemberForCandidate( + this.data.entityId, selectedMemberCandidate.candidate).subscribe(() => { + this.onAddSuccess(); + }, () => this.onError()); + } + + private addUserToGroup(selectedMemberCandidate: MemberCandidate) { + this.memberService.createMemberWithGroups( + this.data.voId, selectedMemberCandidate.richUser.id, [this.data.group]).subscribe(() => { + this.onAddSuccess(); + }, () => this.onError()); + } + + private addMemberToGroup(selectedMemberCandidate: MemberCandidate) { + this.groupService.addMembers(this.data.entityId, [selectedMemberCandidate.member.id]).subscribe(() => { + this.onAddSuccess(); + }, () => this.onError()); + } + + private addCandidateToGroup(selectedMemberCandidate: MemberCandidate) { + this.memberService.createMemberForCandidateWithGroups( + this.data.voId, selectedMemberCandidate.candidate, [this.data.group]).subscribe(() => { + this.onAddSuccess(); + }, () => this.onError()); + } + + private onAddSuccess() { + this.translate.get('DIALOGS.ADD_MEMBERS.SUCCESS').subscribe(() => { + this.notificator.showSuccess(this.successMessage); + this.dialogRef.close(); + }); + } + + private onError() { + this.processing = false; + } } diff --git a/apps/admin-gui/src/app/shared/components/dialogs/remove-members-dialog/remove-members-dialog.component.html b/apps/admin-gui/src/app/shared/components/dialogs/remove-members-dialog/remove-members-dialog.component.html index cf9b673c6..a2f1b6379 100644 --- a/apps/admin-gui/src/app/shared/components/dialogs/remove-members-dialog/remove-members-dialog.component.html +++ b/apps/admin-gui/src/app/shared/components/dialogs/remove-members-dialog/remove-members-dialog.component.html @@ -1,7 +1,7 @@

{{'DIALOGS.REMOVE_MEMBERS.TITLE' | translate}}

- {{'DIALOGS.REMOVE_MEMBERS.DESCRIPTION' | translate}} + {{(!!data.groupId ? 'DIALOGS.REMOVE_MEMBERS.DESCRIPTION_GROUP' : 'DIALOGS.REMOVE_MEMBERS.DESCRIPTION') | translate}}

@@ -36,6 +36,6 @@

{{'DIALOGS.REMOVE_MEMBERS.TITLE' | translate}}

class="ml-2" color="warn" (click)="onSubmit()"> - {{'DIALOGS.DELETE_GROUP.DELETE' | translate}} + {{(!!data.groupId ? 'DIALOGS.REMOVE_MEMBERS.REMOVE_GROUP' : 'DIALOGS.REMOVE_MEMBERS.REMOVE') | translate}}
diff --git a/apps/admin-gui/src/app/shared/components/dialogs/remove-members-dialog/remove-members-dialog.component.ts b/apps/admin-gui/src/app/shared/components/dialogs/remove-members-dialog/remove-members-dialog.component.ts index 3c8a5917d..f84328667 100644 --- a/apps/admin-gui/src/app/shared/components/dialogs/remove-members-dialog/remove-members-dialog.component.ts +++ b/apps/admin-gui/src/app/shared/components/dialogs/remove-members-dialog/remove-members-dialog.component.ts @@ -3,10 +3,11 @@ import {MAT_DIALOG_DATA, MatDialogRef, MatTableDataSource} from '@angular/materi import {NotificatorService} from '../../../../core/services/common/notificator.service'; import {TranslateService} from '@ngx-translate/core'; import { RichMember } from '@perun-web-apps/perun/models'; -import { MembersService } from '@perun-web-apps/perun/services'; +import { GroupService, MembersService } from '@perun-web-apps/perun/services'; export interface RemoveMembersDialogData { members: RichMember[]; + groupId?: number; } @Component({ selector: 'app-remove-members-dialog', @@ -19,6 +20,7 @@ export class RemoveMembersDialogComponent implements OnInit { public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: RemoveMembersDialogData, private membersService: MembersService, + private groupService: GroupService, private notificator: NotificatorService, private translate: TranslateService ) { } @@ -38,14 +40,25 @@ export class RemoveMembersDialogComponent implements OnInit { onSubmit() { this.loading = true; - this.membersService.deleteMembers(this.data.members.map(m => m.id)).subscribe(() => { - this.translate.get('DIALOGS.REMOVE_MEMBERS.SUCCESS').subscribe(successMessage => { - this.notificator.showSuccess(successMessage); - this.dialogRef.close(true); - this.loading = false; - }); - }, () => { - this.loading = false; - }); + if (!!this.data.groupId) { + this.groupService.removeMembers(this.data.groupId, this.data.members.map(m => m.id)) + .subscribe(() => this.onSuccess(), () => this.onError()); + } else { + this.membersService.deleteMembers(this.data.members.map(m => m.id)) + .subscribe(() => this.onSuccess(), () => this.onError()); + } + } + + onSuccess() { + const message = !!this.data.groupId ? + this.translate.instant('DIALOGS.REMOVE_MEMBERS.SUCCESS_GROUP'): + this.translate.instant('DIALOGS.REMOVE_MEMBERS.SUCCESS'); + this.notificator.showSuccess(message); + this.dialogRef.close(true); + this.loading = false; + } + + onError() { + this.loading = false; } } diff --git a/apps/admin-gui/src/app/shared/pipes/member-candidate-email.pipe.ts b/apps/admin-gui/src/app/shared/pipes/member-candidate-email.pipe.ts index b03487ace..1a4a74a38 100644 --- a/apps/admin-gui/src/app/shared/pipes/member-candidate-email.pipe.ts +++ b/apps/admin-gui/src/app/shared/pipes/member-candidate-email.pipe.ts @@ -8,7 +8,7 @@ import { parseEmail, parseUserEmail } from '@perun-web-apps/perun/utils'; export class MemberCandidateEmailPipe implements PipeTransform { transform(value: MemberCandidate): any { - return value.member ? parseEmail(value.member) : parseUserEmail(value.richUser); + return !!value.member && !!value.member.memberAttributes ? parseEmail(value.member) : parseUserEmail(value.richUser); } } diff --git a/apps/admin-gui/src/app/vos/pages/group-detail-page/group-members/group-members.component.ts b/apps/admin-gui/src/app/vos/pages/group-detail-page/group-members/group-members.component.ts index 36f8206bd..c5c337e0e 100644 --- a/apps/admin-gui/src/app/vos/pages/group-detail-page/group-members/group-members.component.ts +++ b/apps/admin-gui/src/app/vos/pages/group-detail-page/group-members/group-members.component.ts @@ -4,6 +4,9 @@ import {SelectionModel} from '@angular/cdk/collections'; import { GroupService, MembersService } from '@perun-web-apps/perun/services'; import { Group, RichMember } from '@perun-web-apps/perun/models'; import { Urns } from '@perun-web-apps/perun/urns'; +import { AddMemberDialogComponent } from '../../../../shared/components/dialogs/add-member-dialog/add-member-dialog.component'; +import { MatDialog } from '@angular/material'; +import { RemoveMembersDialogComponent } from '../../../../shared/components/dialogs/remove-members-dialog/remove-members-dialog.component'; @Component({ selector: 'app-group-members', @@ -17,10 +20,13 @@ export class GroupMembersComponent implements OnInit { // used for router animation @HostBinding('class.router-component') true; - constructor(private membersService: MembersService, - private groupService: GroupService, - protected route: ActivatedRoute, - protected router: Router) { } + constructor( + private membersService: MembersService, + private groupService: GroupService, + protected route: ActivatedRoute, + protected router: Router, + private dialog: MatDialog + ) { } group: Group; @@ -66,7 +72,22 @@ export class GroupMembersComponent implements OnInit { } onAddMember() { + const dialogRef = this.dialog.open(AddMemberDialogComponent, { + width: '1000px', + data: { + voId: this.group.voId, + group: this.group, + entityId: this.group.id, + theme: 'group-theme', + type: 'group', + } + }); + dialogRef.afterClosed().subscribe(() => { + if (this.firstSearchDone) { + this.refreshTable(); + } + }); } onKeyInput(event: KeyboardEvent) { @@ -76,6 +97,19 @@ export class GroupMembersComponent implements OnInit { } onRemoveMembers() { + const dialogRef = this.dialog.open(RemoveMembersDialogComponent, { + width: '450px', + data: { + groupId: this.group.id, + members: this.selection.selected + } + }); + + dialogRef.afterClosed().subscribe(wereMembersDeleted => { + if (wereMembersDeleted) { + this.refreshTable(); + } + }); } refreshTable() { diff --git a/apps/admin-gui/src/app/vos/pages/vo-detail-page/vo-members/vo-members.component.ts b/apps/admin-gui/src/app/vos/pages/vo-detail-page/vo-members/vo-members.component.ts index f2e893ca8..8abc0964d 100644 --- a/apps/admin-gui/src/app/vos/pages/vo-detail-page/vo-members/vo-members.component.ts +++ b/apps/admin-gui/src/app/vos/pages/vo-detail-page/vo-members/vo-members.component.ts @@ -92,7 +92,11 @@ export class VoMembersComponent implements OnInit { onAddMember() { const dialogRef = this.dialog.open(AddMemberDialogComponent, { width: '1000px', - data: {voId: this.vo.id, theme: 'vo-theme'} + data: { + entityId: this.vo.id, + theme: 'vo-theme', + type: 'vo' + } }); dialogRef.afterClosed().subscribe(() => { diff --git a/apps/admin-gui/src/assets/i18n/en.json b/apps/admin-gui/src/assets/i18n/en.json index 1fa235dc3..bc26a73cf 100644 --- a/apps/admin-gui/src/assets/i18n/en.json +++ b/apps/admin-gui/src/assets/i18n/en.json @@ -601,8 +601,12 @@ "REMOVE_MEMBERS": { "TITLE": "Confirm removal", "DESCRIPTION": "Following members will be removed from VO and their settings will be lost.\n\nYou can consider changing their status to \"DISABLED\", which will prevent them from accessing VO resources.", + "DESCRIPTION_GROUP": "Following members will be removed from group. They will lose access to resources provided by this group.", "ASK": "Do you want to proceed?", - "SUCCESS": "Selected members were deleted" + "SUCCESS": "Selected members were deleted", + "SUCCESS_GROUP": "Selected members were removed", + "REMOVE": "Delete", + "REMOVE_GROUP": "Remove" }, "REMOVE_RESOURCES": { "TITLE": "Confirm removal", diff --git a/libs/perun/services/src/lib/group.service.ts b/libs/perun/services/src/lib/group.service.ts index b853b2a92..7d605b7cf 100644 --- a/libs/perun/services/src/lib/group.service.ts +++ b/libs/perun/services/src/lib/group.service.ts @@ -78,4 +78,18 @@ export class GroupService { getVoOfGroup(id: number, showNotificationOnError = true): Observable { return this.apiService.get(`json/groupsManager/getVo?group=${id}`, new HttpParams(), showNotificationOnError); } + + addMembers(group: number, members: number[], showNotificationOnError: boolean = true): Observable { + return this.apiService.post('json/groupsManager/addMembers', { + group: group, + members: members + }, showNotificationOnError); + } + + removeMembers(group: number, members: number[], showNotificationOnError: boolean = true): Observable { + return this.apiService.post('json/groupsManager/removeMembers', { + group: group, + members: members + }, showNotificationOnError); + } } diff --git a/libs/perun/services/src/lib/members.service.ts b/libs/perun/services/src/lib/members.service.ts index faefa7d30..4b9f2499e 100644 --- a/libs/perun/services/src/lib/members.service.ts +++ b/libs/perun/services/src/lib/members.service.ts @@ -3,7 +3,7 @@ import {Observable} from 'rxjs'; import {HttpParams} from '@angular/common/http'; import { PERUN_API_SERVICE } from '@perun-web-apps/perun/tokens'; import { PerunApiService } from './perun-api-service'; -import { Member, RichMember } from '@perun-web-apps/perun/models'; +import { Candidate, Group, Member, RichMember } from '@perun-web-apps/perun/models'; @Injectable({ providedIn: 'root' @@ -76,13 +76,50 @@ export class MembersService { }, showNotificationOnError); } - addMember(voId: number, userId: number, showNotificationOnError = true): Observable { + createMember(voId: number, userId: number, showNotificationOnError = true): Observable { return this.apiService.post('json/membersManager/createMember', { vo: voId, user: userId }, showNotificationOnError); } + createMemberWithGroups( + voId: number, + userId: number, + groups: Group[], + showNotificationOnError = true + ): Observable { + return this.apiService.post('json/membersManager/createMember', { + vo: voId, + user: userId, + groups: groups + }, showNotificationOnError); + } + + createMemberForCandidateWithGroups( + voId: number, + candidate: Candidate, + groups: Group[], + showNotificationOnError = true + ): Observable { + return this.apiService.post('json/membersManager/createMember', { + vo: voId, + candidate: candidate, + groups: groups + }, showNotificationOnError); + } + + createMemberForCandidate( + voId: number, + candidate: Candidate, + showNotificationOnError = true + ): Observable { + return this.apiService.post('json/membersManager/createMember', { + vo: voId, + candidate: candidate + }, showNotificationOnError); + } + getMemberByUser(voId: number, userId: number, showNotificationOnError = true): Observable { return this.apiService.post('json/membersManager/getMemberByUser', { 'vo': voId, diff --git a/libs/perun/services/src/lib/vo.service.ts b/libs/perun/services/src/lib/vo.service.ts index ef9feebe1..94acf4fc3 100644 --- a/libs/perun/services/src/lib/vo.service.ts +++ b/libs/perun/services/src/lib/vo.service.ts @@ -26,13 +26,15 @@ export class VoService { return this.apiService.get(`json/vosManager/getVoById?id=${id}`, new HttpParams(), showNotificationOnError); } - getCompleteCandidates(voId: number, attrNames: string[], searchString: string, + getCompleteCandidates(id: number, entity : 'group' | 'vo', attrNames: string[], searchString: string, showNotificationOnError = true): Observable { - return this.apiService.post('json/vosManager/getCompleteCandidates', { - vo: voId, + const payload = { attrNames: attrNames, searchString: searchString - }, showNotificationOnError); + }; + payload[entity] = id; + + return this.apiService.post('json/vosManager/getCompleteCandidates', payload, showNotificationOnError); } removeVo(voId:number, force:boolean, showNotificationOnError = true): Observable {