diff --git a/src/app/api-connector/applications.service.ts b/src/app/api-connector/applications.service.ts index 7e739f440d..c004fca888 100644 --- a/src/app/api-connector/applications.service.ts +++ b/src/app/api-connector/applications.service.ts @@ -13,7 +13,9 @@ import { User } from '../applications/application.model/user.model' /** * Service which provides methods for creating application. */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class ApplicationsService { constructor(private http: HttpClient) { this.http = http diff --git a/src/app/api-connector/client.service.ts b/src/app/api-connector/client.service.ts index aa730fea7e..4e4ccadd14 100644 --- a/src/app/api-connector/client.service.ts +++ b/src/app/api-connector/client.service.ts @@ -9,7 +9,9 @@ import { IResponseTemplate } from './response-template' /** * Service which provides client methods. */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class ClientService { clientURL: string = `${ApiSettings.getApiBaseURL()}clients/` diff --git a/src/app/api-connector/facility.service.ts b/src/app/api-connector/facility.service.ts index 817c452240..ac35ddb7cd 100644 --- a/src/app/api-connector/facility.service.ts +++ b/src/app/api-connector/facility.service.ts @@ -14,7 +14,9 @@ import { GeneralStorageFactor } from '../facility_manager/resources/general-stor /** * Service which provides methods for the facilities. */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class FacilityService { constructor(private http: HttpClient) { this.http = http diff --git a/src/app/api-connector/group.service.ts b/src/app/api-connector/group.service.ts index 7670c1fbf8..ac55aacd00 100644 --- a/src/app/api-connector/group.service.ts +++ b/src/app/api-connector/group.service.ts @@ -12,7 +12,9 @@ import { ProjectMember } from '../projectmanagement/project_member.model' /** * Service which provides Group methods. */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class GroupService { constructor(private http: HttpClient) { this.http = http diff --git a/src/app/api-connector/image.service.ts b/src/app/api-connector/image.service.ts index 950802dc76..29ab844203 100644 --- a/src/app/api-connector/image.service.ts +++ b/src/app/api-connector/image.service.ts @@ -9,7 +9,9 @@ import { BlockedImageTag, BlockedImageTagResenv, ImageLogo, ImageMode, ImageTag /** * Service which provides image methods. */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class ImageService { constructor(private http: HttpClient) {} diff --git a/src/app/api-connector/key.service.ts b/src/app/api-connector/key.service.ts index 54099f17cd..d5c7f88985 100644 --- a/src/app/api-connector/key.service.ts +++ b/src/app/api-connector/key.service.ts @@ -8,7 +8,9 @@ import { BlacklistedResponse } from './response-interfaces' /** * Service which provides public key methods. */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class KeyService { constructor(private http: HttpClient) {} diff --git a/src/app/api-connector/news.service.ts b/src/app/api-connector/news.service.ts index 2272870151..00ca52782a 100644 --- a/src/app/api-connector/news.service.ts +++ b/src/app/api-connector/news.service.ts @@ -10,7 +10,9 @@ import { News } from '../news/news.model' /** * Service which provides methods for the facilities. */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class NewsService { constructor(private http: HttpClient) { this.http = http diff --git a/src/app/api-connector/numbers.service.ts b/src/app/api-connector/numbers.service.ts index dca3e48c5f..6683e2dcf3 100644 --- a/src/app/api-connector/numbers.service.ts +++ b/src/app/api-connector/numbers.service.ts @@ -6,7 +6,9 @@ import { ApiSettings } from './api-settings.service' /** * Class to get numbers from the api for graphs */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class NumbersService { constructor(private http: HttpClient) { this.http = http diff --git a/src/app/api-connector/token-interceptor.ts b/src/app/api-connector/token-interceptor.ts index e13a8f2546..da5876fe41 100644 --- a/src/app/api-connector/token-interceptor.ts +++ b/src/app/api-connector/token-interceptor.ts @@ -7,7 +7,9 @@ import { catchError } from 'rxjs/operators' /** * Interceptor which inserts withCredentials and csrf header */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class TokenInterceptor implements HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler): Observable> { const skipIntercept: boolean = req.headers.has('skip') diff --git a/src/app/api-connector/user.service.ts b/src/app/api-connector/user.service.ts index ea8be8a59b..70f4032194 100644 --- a/src/app/api-connector/user.service.ts +++ b/src/app/api-connector/user.service.ts @@ -9,7 +9,9 @@ import { Userinfo } from '../userinfo/userinfo.model' /** * Service which provides user methods. */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class UserService { constructor(private http: HttpClient) { this.http = http diff --git a/src/app/api-connector/vo.service.ts b/src/app/api-connector/vo.service.ts index 967a6cbf9f..e2207644fb 100644 --- a/src/app/api-connector/vo.service.ts +++ b/src/app/api-connector/vo.service.ts @@ -12,7 +12,9 @@ import { MaintenanceTimeFrame } from '../vo_manager/maintenance/maintenanceTimeF /** * Service which provides vo methods. */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class VoService { constructor(private http: HttpClient) {} diff --git a/src/app/api-connector/workshop.service.ts b/src/app/api-connector/workshop.service.ts index a1f5ad83b9..3fcd49b5ea 100644 --- a/src/app/api-connector/workshop.service.ts +++ b/src/app/api-connector/workshop.service.ts @@ -7,7 +7,9 @@ import { Workshop } from '../virtualmachines/workshop/workshop.model' import { WorkshopVM } from '../virtualmachines/workshop/workshop-vm.model' import { WorkshopTimeFrame } from '../virtualmachines/workshop/workshopTimeFrame.model' -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class WorkshopService { constructor(private http: HttpClient) {} diff --git a/src/app/app.component.html b/src/app/app.component.html index e3aae6657a..0680b43f9c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,40 +1 @@ - - - diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 4a948187a2..c53a34a654 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,4 @@ -import { AfterViewInit, ApplicationRef, Component, OnInit, ViewChild } from '@angular/core' -import { ModalDirective } from 'ngx-bootstrap/modal' +import { AfterViewInit, ApplicationRef, Component, OnInit } from '@angular/core' import { VoService } from './api-connector/vo.service' import { TitleService } from './title.service' @@ -17,8 +16,6 @@ export class AppComponent implements AfterViewInit, OnInit { 'A new update is available. Please reload the site to use the new version of the portal.' notificationModalType: string = 'info' - @ViewChild('notificationModal', { static: true }) modal: ModalDirective - constructor( private appRef: ApplicationRef, private titleService: TitleService diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index bd8dbcdf06..82aa0e1d2e 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -8,6 +8,7 @@ import { is_vo } from '../../shared/globalvar' import { CreditsService } from '../../api-connector/credits.service' import { Application_States } from '../../shared/shared_modules/baseClass/abstract-base-class' import { User } from '../application.model/user.model' +import { NotificationModalComponent } from '../../shared/modal/notification-modal' /** * Class which displays the details of an application. @@ -97,11 +98,10 @@ export class ApplicationDetailComponent extends ApplicationBaseClassComponent im applicationsService: ApplicationsService, userService: UserService, facilityService: FacilityService, - creditsService: CreditsService, - cdrRef: ChangeDetectorRef + cdrRef: ChangeDetectorRef, + notificationModal: NotificationModalComponent ) { - super(userService, applicationsService, facilityService, cdrRef) - this.creditsService = creditsService + super(userService, applicationsService, facilityService, notificationModal, cdrRef) } ngOnInit(): void { diff --git a/src/app/applications/application-formular/application-formular.component.ts b/src/app/applications/application-formular/application-formular.component.ts index 4842e14079..8beeb64cd6 100644 --- a/src/app/applications/application-formular/application-formular.component.ts +++ b/src/app/applications/application-formular/application-formular.component.ts @@ -32,6 +32,7 @@ import { import { UserService } from '../../api-connector/user.service' import { Userinfo } from '../../userinfo/userinfo.model' import { User } from '../application.model/user.model' +import { NotificationModalComponent } from '../../shared/modal/notification-modal' /** * Application formular component. @@ -107,9 +108,10 @@ export class ApplicationFormularComponent extends ApplicationBaseClassComponent private fullLayout: FullLayoutComponent, userService: UserService, applicationsService: ApplicationsService, - cdrRef: ChangeDetectorRef + cdrRef: ChangeDetectorRef, + notificationModal: NotificationModalComponent ) { - super(userService, applicationsService, null, cdrRef) + super(userService, applicationsService, null, notificationModal, cdrRef) } ngOnInit(): void { diff --git a/src/app/applications/applications.component.ts b/src/app/applications/applications.component.ts index 2c58f8e304..9f57309492 100644 --- a/src/app/applications/applications.component.ts +++ b/src/app/applications/applications.component.ts @@ -87,9 +87,10 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme private modalService: BsModalService, facilityService: FacilityService, private flavorService: FlavorService, - cdrRef: ChangeDetectorRef + cdrRef: ChangeDetectorRef, + notificationModal: NotificationModalComponent ) { - super(userService, applicationsService, facilityService, cdrRef) + super(userService, applicationsService, facilityService, notificationModal, cdrRef) } ngOnDestroy() { diff --git a/src/app/facility_manager/facility.application.component.html b/src/app/facility_manager/facility.application.component.html index 4de29ac3f9..fc7410eef4 100644 --- a/src/app/facility_manager/facility.application.component.html +++ b/src/app/facility_manager/facility.application.component.html @@ -1,258 +1,205 @@
-
- -
+
+ +
-
- -
Applications for review
-
- - + - + - + - + -
+ id="tab_state_button_termination_requests" + > + Termination Requests + {{ numberOfTerminationRequests }} + +
-
-
- Loading applications/requests ... -
-
+
+
+ Loading applications/requests ... +
+
- - @if (!loadingApplications) { + + @if (!loadingApplications) { + + } + +
- - } - - +
+
Application history
+
+
+
+
+ +
-
-
Application history
-
-
-
-
- -
- -
-
- - - - - - - - - - - - - - - - - - - - - - + + + +
Project NameShort NameDate submitted (d/m/y)InstituteStatusActions
- - {{ application?.project_application_name }}{{ application?.project_application_shortname }}{{ application?.project_application_date_submitted }}{{ application?.project_application_institute }} - +
+
+ + + + + + + + + + + + + + + + + + + + + + - - - - - - -
Project NameShort NameDate submitted (d/m/y)InstituteStatusActions
+ + {{ application?.project_application_name }}{{ application?.project_application_shortname }}{{ application?.project_application_date_submitted }}{{ application?.project_application_institute }} + {{ getStatusById(st) }} - - - -
- + -
+
-
- Loading... -
-
-
-
-
- - - - - +
+
+
+
+
- diff --git a/src/app/facility_manager/facility.application.component.ts b/src/app/facility_manager/facility.application.component.ts index 6aa9ba49a2..185936d247 100644 --- a/src/app/facility_manager/facility.application.component.ts +++ b/src/app/facility_manager/facility.application.component.ts @@ -8,6 +8,7 @@ import { Application } from '../applications/application.model/application.model import { Application_States } from '../shared/shared_modules/baseClass/abstract-base-class' import { ApplicationsService } from '../api-connector/applications.service' import { ApplicationBaseClassComponent } from '../shared/shared_modules/baseClass/application-base-class.component' +import { NotificationModalComponent } from '../shared/modal/notification-modal' enum TabStates { 'SUBMITTED' = 0, @@ -72,9 +73,10 @@ export class FacilityApplicationComponent extends ApplicationBaseClassComponent userService: UserService, facilityService: FacilityService, applicationsService: ApplicationsService, - cdrRef: ChangeDetectorRef + cdrRef: ChangeDetectorRef, + notificationModal: NotificationModalComponent ) { - super(userService, applicationsService, facilityService, cdrRef) + super(userService, applicationsService, facilityService, notificationModal, cdrRef) } getFacilityApplicationById(application: Application): void { diff --git a/src/app/facility_manager/facilityprojectsoverview.component.html b/src/app/facility_manager/facilityprojectsoverview.component.html index d7feed64bd..219787d642 100644 --- a/src/app/facility_manager/facilityprojectsoverview.component.html +++ b/src/app/facility_manager/facilityprojectsoverview.component.html @@ -598,7 +598,7 @@ - - - - - - diff --git a/src/app/facility_manager/facilityprojectsoverview.component.ts b/src/app/facility_manager/facilityprojectsoverview.component.ts index b2c559922d..1cd8fbde9d 100644 --- a/src/app/facility_manager/facilityprojectsoverview.component.ts +++ b/src/app/facility_manager/facilityprojectsoverview.component.ts @@ -21,6 +21,7 @@ import { MembersListModalComponent } from '../shared/modal/members/members-list- import { EmailService } from '../api-connector/email.service' import { CsvMailTemplateModel } from '../shared/classes/csvMailTemplate.model' import { ProjectCsvTemplatedEmailModalComponent } from '../shared/modal/email/project-csv-templated-email-modal/project-csv-templated-email-modal.component' +import { NotificationModalComponent } from '../shared/modal/notification-modal' /** * Facility Project overview component. @@ -97,24 +98,15 @@ export class FacilityProjectsOverviewComponent extends AbstractBaseClass impleme total$: Observable constructor( - private groupService: GroupService, private facilityService: FacilityService, - private newsService: NewsService, public sortProjectService: ProjectSortService, private modalService: BsModalService, - private emailService: EmailService + private emailService: EmailService, + private notificationModal: NotificationModalComponent ) { super() } - switchShowSimpleVmProjects(): void { - this.show_simple_vm_projects = !this.show_simple_vm_projects - } - - switchOpenStackVmProjects(): void { - this.show_openstack_projects = !this.show_openstack_projects - } - setEmailSubject(): void { switch (this.selectedProjectType) { case 'ALL': @@ -178,11 +170,13 @@ export class FacilityProjectsOverviewComponent extends AbstractBaseClass impleme ) } } + openProjectCSVMailModal(): void { console.log('show') this.bsModalRef = this.modalService.show(ProjectCsvTemplatedEmailModalComponent, { class: 'modal-lg' }) } + onSort({ column, direction }: SortEvent) { // resetting other headers this.headers.forEach(header => { @@ -517,18 +511,14 @@ export class FacilityProjectsOverviewComponent extends AbstractBaseClass impleme const facilityId = this.selectedFacility['FacilityId'] this.facilityService.setSupportMails(facilityId, supportMails).subscribe((result: any): void => { if (result.ok) { - this.updateNotificationModal( + this.notificationModal.showSuccessFullNotificationModal( 'Facility support mails changed', - 'You successfully changed the facility support mails.', - true, - 'success' + 'You successfully changed the facility support mails.' ) } else { - this.updateNotificationModal( + this.notificationModal.showDangerNotificationModal( "Couldn't change facility support mails", - 'An error occurred while trying to change the facility support mails.', - true, - 'danger' + 'An error occurred while trying to change the facility support mails.' ) } }) diff --git a/src/app/layouts/full-layout.component.ts b/src/app/layouts/full-layout.component.ts index 2808448169..8d475b7dd8 100644 --- a/src/app/layouts/full-layout.component.ts +++ b/src/app/layouts/full-layout.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, OnInit } from '@angular/core' +import { ChangeDetectorRef, Component, Injectable, OnInit } from '@angular/core' import moment from 'moment' import { ApiSettings } from '../api-connector/api-settings.service' import { ClientService } from '../api-connector/client.service' @@ -21,6 +21,9 @@ import { UserInfoComponent } from '../userinfo/userinfo.component' /** * FullLayout component. */ +@Injectable({ + providedIn: 'root' +}) @Component({ selector: 'app-dashboard', templateUrl: './full-layout.component.html', @@ -74,7 +77,7 @@ export class FullLayoutComponent extends ApplicationBaseClassComponent implement applicationsService: ApplicationsService, private cd: ChangeDetectorRef ) { - super(userService, applicationsService, facilityService, cd) + super(userService, applicationsService, facilityService, null, cd) } componentAdded(component: any): void { diff --git a/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.html b/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.html new file mode 100644 index 0000000000..1d39841ede --- /dev/null +++ b/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.html @@ -0,0 +1,94 @@ + + diff --git a/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.scss b/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.spec.ts b/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.spec.ts new file mode 100644 index 0000000000..f9d9319998 --- /dev/null +++ b/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { AddUserModalComponent } from './add-user-modal.component' + +describe('AddUserModalComponent', () => { + let component: AddUserModalComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AddUserModalComponent] + }).compileComponents() + + fixture = TestBed.createComponent(AddUserModalComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.ts b/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.ts new file mode 100644 index 0000000000..a3213a3a24 --- /dev/null +++ b/src/app/projectmanagement/modals/add-user-modal/add-user-modal.component.ts @@ -0,0 +1,34 @@ +import { Component, EventEmitter, Injectable } from '@angular/core' +import { CLOUD_PORTAL_REGISTER_LINK, WIKI_MEMBER_MANAGEMENT } from '../../../../links/links' +import { BsModalService } from 'ngx-bootstrap/modal' +import { AbstractBaseModalComponent } from '../../../shared/modal/abstract-base-modal/abstract-base-modal.component' +import { Application } from '../../../applications/application.model/application.model' + +@Injectable({ + providedIn: 'root' +}) +@Component({ + selector: 'app-add-user-modal', + templateUrl: './add-user-modal.component.html', + styleUrl: './add-user-modal.component.scss' +}) +export class AddUserModalComponent extends AbstractBaseModalComponent { + invitation_link: string + application: Application + + constructor(protected modalService: BsModalService) { + super(modalService) + } + + showAddUserModalComponent(application: Application, invitation_link: string): EventEmitter { + const initialState = { + invitation_link, + application + } + + return this.showBaseModal(AddUserModalComponent, initialState) + } + + protected readonly CLOUD_PORTAL_REGISTER_LINK = CLOUD_PORTAL_REGISTER_LINK + protected readonly WIKI_MEMBER_MANAGEMENT = WIKI_MEMBER_MANAGEMENT +} diff --git a/src/app/projectmanagement/modals/delete-member-application-modal/delete-application-modal.component.html b/src/app/projectmanagement/modals/delete-member-application-modal/delete-application-modal.component.html new file mode 100644 index 0000000000..363c237baf --- /dev/null +++ b/src/app/projectmanagement/modals/delete-member-application-modal/delete-application-modal.component.html @@ -0,0 +1,19 @@ + + diff --git a/src/app/projectmanagement/modals/delete-member-application-modal/delete-application-modal.component.ts b/src/app/projectmanagement/modals/delete-member-application-modal/delete-application-modal.component.ts new file mode 100644 index 0000000000..15f3058b2e --- /dev/null +++ b/src/app/projectmanagement/modals/delete-member-application-modal/delete-application-modal.component.ts @@ -0,0 +1,55 @@ +import { Component, EventEmitter, Injectable } from '@angular/core' +import { BsModalService } from 'ngx-bootstrap/modal' +import { AbstractBaseModalComponent } from '../../../shared/modal/abstract-base-modal/abstract-base-modal.component' +import { Application } from '../../../applications/application.model/application.model' + +import { FullLayoutComponent } from '../../../layouts/full-layout.component' +import { NotificationModalComponent } from '../../../shared/modal/notification-modal' +import { ApplicationsService } from '../../../api-connector/applications.service' + +@Injectable({ + providedIn: 'root' +}) +@Component({ + selector: 'app-delete-member-application-modal', + + templateUrl: './delete-application-modal.component.html' +}) +export class DeleteApplicationModal extends AbstractBaseModalComponent { + application: Application + + constructor( + protected modalService: BsModalService, + private notificationModalComponent: NotificationModalComponent, + private applicationsService: ApplicationsService, + private fullLayout: FullLayoutComponent + ) { + super(modalService) + } + + showDeleteApplicationModal(application: Application): EventEmitter { + const initialState = { + application + } + + return this.showBaseModal(DeleteApplicationModal, initialState) + } + + async deleteApplication(): Promise { + await this.hide() + + this.applicationsService.deleteApplication(this.application.project_application_id).subscribe( + (): void => { + this.notificationModalComponent.showSuccessFullNotificationModal( + 'Success', + 'The application has been successfully removed', + 'userinfo' + ) + this.fullLayout.getGroupsEnumeration() + }, + (): void => { + this.notificationModalComponent.showDangerNotificationModal('Failed', 'Application could not be removed!') + } + ) + } +} diff --git a/src/app/projectmanagement/modals/leave-project/leave-project.component.html b/src/app/projectmanagement/modals/leave-project/leave-project.component.html new file mode 100644 index 0000000000..4c675bfe9a --- /dev/null +++ b/src/app/projectmanagement/modals/leave-project/leave-project.component.html @@ -0,0 +1,17 @@ + + diff --git a/src/app/projectmanagement/modals/leave-project/leave-project.component.scss b/src/app/projectmanagement/modals/leave-project/leave-project.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/projectmanagement/modals/leave-project/leave-project.component.spec.ts b/src/app/projectmanagement/modals/leave-project/leave-project.component.spec.ts new file mode 100644 index 0000000000..2f0fd94ed0 --- /dev/null +++ b/src/app/projectmanagement/modals/leave-project/leave-project.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { LeaveProjectComponent } from './leave-project.component' + +describe('LeaveProjectComponent', () => { + let component: LeaveProjectComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LeaveProjectComponent] + }).compileComponents() + + fixture = TestBed.createComponent(LeaveProjectComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/projectmanagement/modals/leave-project/leave-project.component.ts b/src/app/projectmanagement/modals/leave-project/leave-project.component.ts new file mode 100644 index 0000000000..f5363a4090 --- /dev/null +++ b/src/app/projectmanagement/modals/leave-project/leave-project.component.ts @@ -0,0 +1,106 @@ +import { Component, EventEmitter, Injectable } from '@angular/core' +import { BsModalService } from 'ngx-bootstrap/modal' +import { Application } from '../../../applications/application.model/application.model' +import { GroupService } from '../../../api-connector/group.service' +import { FullLayoutComponent } from '../../../layouts/full-layout.component' +import { NotificationModalComponent } from '../../../shared/modal/notification-modal' +import { AbstractBaseModalComponent } from '../../../shared/modal/abstract-base-modal/abstract-base-modal.component' +import { Userinfo } from '../../../userinfo/userinfo.model' +import { CLOUD_PORTAL_SUPPORT_MAIL } from '../../../../links/links' + +@Injectable({ + providedIn: 'root' +}) +@Component({ + selector: 'app-leave-project', + templateUrl: './leave-project.component.html', + styleUrl: './leave-project.component.scss' +}) +export class LeaveProjectComponent extends AbstractBaseModalComponent { + application: Application + userinfo: Userinfo + CLOUD_PORTAL_SUPPORT_MAIL: string = CLOUD_PORTAL_SUPPORT_MAIL + + constructor( + protected modalService: BsModalService, + private groupService: GroupService, + private fullLayout: FullLayoutComponent, + private notificationModalComponent: NotificationModalComponent + ) { + super(modalService) + } + + showLeaveProjectModal(application: Application, userinfo: Userinfo): EventEmitter { + const initialState = { + application, + userinfo + } + + return this.showBaseModal(LeaveProjectComponent, initialState) + } + + async leaveProject(): Promise { + console.log('leave project') + console.log(this.modalId) + await this.hide() + + if (this.application.project_application_pi.elixir_id === this.userinfo.ElixirId) { + console.log('is pi') + this.notificationModalComponent.showDangerNotificationModal( + 'Denied', + `You cannot leave projects as PI. Please contact ${CLOUD_PORTAL_SUPPORT_MAIL} for further steps.` + ) + } else { + console.log('leave group') + + this.groupService + .leaveGroup( + this.application.project_application_perun_id, + this.userinfo?.MemberId, + this.application.project_application_compute_center.FacilityId + ) + .subscribe( + (result: any) => { + switch (result.status) { + case 200: + this.fullLayout.getGroupsEnumeration() + + this.notificationModalComponent.showSuccessFullNotificationModal( + 'Success', + `You were removed from the project ${this.application.project_application_shortname}`, + 'userinfo' + ) + + break + case 403: + this.notificationModalComponent.showDangerNotificationModal( + 'Unauthorized', + 'You are not authorized to leave this project.' + ) + + break + case 500: + this.notificationModalComponent.showDangerNotificationModal( + 'Server Error', + 'A server error occurred while trying to leave the project. Please try again later.' + ) + + break + default: + this.notificationModalComponent.showDangerNotificationModal( + 'Failed', + `Failed to leave the project ${this.application.project_application_shortname}!` + ) + } + }, + error => { + console.error('Error:', error) + this.notificationModalComponent.showDangerNotificationModal( + 'Failed', + `Failed to leave the project ${this.application.project_application_shortname}!` + ) + } + ) + } + } +} diff --git a/src/app/projectmanagement/modals/termination-request/termination-request.component.html b/src/app/projectmanagement/modals/termination-request/termination-request.component.html new file mode 100644 index 0000000000..b6bdb0d1d4 --- /dev/null +++ b/src/app/projectmanagement/modals/termination-request/termination-request.component.html @@ -0,0 +1,53 @@ + + diff --git a/src/app/projectmanagement/modals/termination-request/termination-request.component.scss b/src/app/projectmanagement/modals/termination-request/termination-request.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/projectmanagement/modals/termination-request/termination-request.component.spec.ts b/src/app/projectmanagement/modals/termination-request/termination-request.component.spec.ts new file mode 100644 index 0000000000..896c4635a4 --- /dev/null +++ b/src/app/projectmanagement/modals/termination-request/termination-request.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { TerminationRequestComponent } from './termination-request.component' + +describe('TerminationRequestComponent', () => { + let component: TerminationRequestComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TerminationRequestComponent] + }).compileComponents() + + fixture = TestBed.createComponent(TerminationRequestComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/projectmanagement/modals/termination-request/termination-request.component.ts b/src/app/projectmanagement/modals/termination-request/termination-request.component.ts new file mode 100644 index 0000000000..f912d7559f --- /dev/null +++ b/src/app/projectmanagement/modals/termination-request/termination-request.component.ts @@ -0,0 +1,48 @@ +import { Component, EventEmitter, Injectable } from '@angular/core' +import { BsModalService } from 'ngx-bootstrap/modal' + +import { Application } from '../../../applications/application.model/application.model' +import { GroupService } from '../../../api-connector/group.service' +import { FullLayoutComponent } from '../../../layouts/full-layout.component' +import { NotificationModalComponent } from '../../../shared/modal/notification-modal' +import { AbstractBaseModalComponent } from '../../../shared/modal/abstract-base-modal/abstract-base-modal.component' + +@Injectable({ + providedIn: 'root' +}) +@Component({ + selector: 'app-termination-request', + templateUrl: './termination-request.component.html', + styleUrl: './termination-request.component.scss' +}) +export class TerminationRequestComponent extends AbstractBaseModalComponent { + application: Application + terminate_confirmation_given: boolean = false + + constructor( + protected modalService: BsModalService, + private groupService: GroupService, + private fullLayout: FullLayoutComponent, + private notificationModalComponent: NotificationModalComponent + ) { + super(modalService) + } + + showTerminationRequestModal(application: Application): EventEmitter { + const initialState = { + application + } + + return this.showBaseModal(TerminationRequestComponent, initialState) + } + + async requestProjectTermination(): Promise { + await this.hide() + this.notificationModalComponent.showInfoNotificationModal('Waiting', 'Termination request will be submitted...') + this.groupService.requestProjectTermination(this.application.project_application_perun_id).subscribe((): void => { + this.fullLayout.getGroupsEnumeration() + this.notificationModalComponent.showSuccessFullNotificationModal('Success', 'Termination was requested!') + this.event.emit() + }) + } +} diff --git a/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.html b/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.html new file mode 100644 index 0000000000..4c0fd9a244 --- /dev/null +++ b/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.html @@ -0,0 +1,73 @@ + diff --git a/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.scss b/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.spec.ts b/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.spec.ts new file mode 100644 index 0000000000..72809fc543 --- /dev/null +++ b/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { UserApplicationsModalComponent } from './user-applications-modal.component' + +describe('UserApplicationsModalComponent', () => { + let component: UserApplicationsModalComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UserApplicationsModalComponent] + }).compileComponents() + + fixture = TestBed.createComponent(UserApplicationsModalComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.ts b/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.ts new file mode 100644 index 0000000000..7d2974b271 --- /dev/null +++ b/src/app/projectmanagement/modals/user-applications-modal/user-applications-modal.component.ts @@ -0,0 +1,121 @@ +import { Component, EventEmitter, Injectable, OnInit } from '@angular/core' +import { AbstractBaseModalComponent } from '../../../shared/modal/abstract-base-modal/abstract-base-modal.component' +import { BsModalService } from 'ngx-bootstrap/modal' +import { ProjectMemberApplication } from '../../project_member_application' +import moment from 'moment/moment' +import { Application } from '../../../applications/application.model/application.model' +import { GroupService } from '../../../api-connector/group.service' + +@Injectable({ + providedIn: 'root' +}) +@Component({ + selector: 'app-user-applications-modal', + templateUrl: './user-applications-modal.component.html', + styleUrl: './user-applications-modal.component.scss' +}) +export class UserApplicationsModalComponent extends AbstractBaseModalComponent implements OnInit { + application: Application + memberApplicationsLoaded: boolean + application_action_done: boolean + application_action_success: boolean + application_action_error_message: string + application_action: string + application_member_name: string + + constructor( + protected modalService: BsModalService, + private groupService: GroupService + ) { + super(modalService) + } + + ngOnInit() { + this.getUserProjectApplications() + } + + showAddUserApplicationModal(application: Application): EventEmitter { + const initialState = { + application + } + + return this.showBaseModal(UserApplicationsModalComponent, initialState) + } + + getUserProjectApplications(): void { + if (this.application.isApproved() && this.application.project_application_perun_id) { + this.memberApplicationsLoaded = false + this.groupService + .getGroupApplications(this.application.project_application_perun_id) + .subscribe((applications: any): void => { + const newProjectApplications: ProjectMemberApplication[] = [] + if (applications.length === 0) { + this.application.project_application_member_applications = [] + + this.memberApplicationsLoaded = true + } + for (const application of applications) { + const dateApplicationCreated: moment.Moment = moment(application['createdAt'], 'YYYY-MM-DD HH:mm:ss.SSS') + const membername: string = application['displayName'] + + const newMemberApplication: ProjectMemberApplication = new ProjectMemberApplication( + application['id'], + membername, + `${dateApplicationCreated.date()}.${dateApplicationCreated.month() + 1}.${dateApplicationCreated.year()}` + ) + newProjectApplications.push(newMemberApplication) + this.application.project_application_member_applications = newProjectApplications + this.memberApplicationsLoaded = true + } + }) + } + } + + approveMemberApplication(application: number, membername: string): void { + this.memberApplicationsLoaded = false + this.application_action_done = false + this.groupService + .approveGroupApplication(Number(this.application.project_application_perun_id), application) + .subscribe((tmp_application: any): void => { + if (tmp_application['state'] === 'APPROVED') { + this.application_action_success = true + } else if (tmp_application['message']) { + this.application_action_success = false + + this.application_action_error_message = tmp_application['message'] + } else { + this.application_action_success = false + } + + this.application_action = 'approved' + this.application_member_name = membername + this.application_action_done = true + this.getUserProjectApplications() + this.event.emit() + }) + } + + rejectMemberApplication(application: number, membername: string): void { + this.application_action_done = false + this.groupService + .rejectGroupApplication(Number(this.application.project_application_perun_id), application) + .subscribe((tmp_application: any): void => { + this.application.project_application_member_applications = [] + + if (tmp_application['state'] === 'REJECTED') { + this.application_action_success = true + } else if (tmp_application['message']) { + this.application_action_success = false + + this.application_action_error_message = tmp_application['message'] + } else { + this.application_action_success = false + } + this.application_action = 'rejected' + this.application_member_name = membername + this.application_action_done = true + this.getUserProjectApplications() + this.event.emit() + }) + } +} diff --git a/src/app/projectmanagement/overview.component.html b/src/app/projectmanagement/overview.component.html index d7d2ed2877..28b7660c5e 100644 --- a/src/app/projectmanagement/overview.component.html +++ b/src/app/projectmanagement/overview.component.html @@ -81,7 +81,7 @@

@@ -1000,7 +1005,7 @@

- - diff --git a/src/app/projectmanagement/overview.component.ts b/src/app/projectmanagement/overview.component.ts index 53cac11bce..ac5ab404ba 100644 --- a/src/app/projectmanagement/overview.component.ts +++ b/src/app/projectmanagement/overview.component.ts @@ -1,13 +1,4 @@ -import { - ChangeDetectorRef, - Component, - ElementRef, - OnDestroy, - OnInit, - Renderer2, - ViewChild, - Inject -} from '@angular/core' +import { ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, ViewChild, Inject } from '@angular/core' import moment from 'moment' import { forkJoin, Observable, Subscription } from 'rxjs' import { ActivatedRoute, Router } from '@angular/router' @@ -39,13 +30,10 @@ import { PUBLICATIONS_LINK, SIMPLE_VM_LINK, STATUS_LINK, - WIKI_MEMBER_MANAGEMENT, WIKI_PUBLICATIONS, - KUBERNETES_LINK, - CLOUD_PORTAL_REGISTER_LINK + KUBERNETES_LINK } from '../../links/links' import { Doi } from '../applications/doi/doi' -import { ApiSettings } from '../api-connector/api-settings.service' import { Application_States, ExtensionRequestType } from '../shared/shared_modules/baseClass/abstract-base-class' import { ProjectMember } from './project_member.model' import { ModificationRequestComponent } from './modals/modification-request/modification-request.component' @@ -54,31 +42,27 @@ import { CreditsRequestComponent } from './modals/credits-request/credits-reques import { ExtensionEntryComponent } from './modals/testimonial/extension-entry.component' import { WITHDRAWAL_TYPES, WithdrawModalComponent } from './modals/withdraw/withdraw-modal.component' import { ApplicationRequestType } from '../shared/enums/application-request-type' +import { TerminationRequestComponent } from './modals/termination-request/termination-request.component' +import { ViewPublicKeyComponent } from '../shared/modal/view-public-key/view-public-key.component' +import { LeaveProjectComponent } from './modals/leave-project/leave-project.component' +import { NotificationModalComponent } from '../shared/modal/notification-modal' +import { DeleteApplicationModal } from './modals/delete-member-application-modal/delete-application-modal.component' +import { AddUserModalComponent } from './modals/add-user-modal/add-user-modal.component' +import { UserApplicationsModalComponent } from './modals/user-applications-modal/user-applications-modal.component' /** * Projectoverview component. */ @Component({ selector: 'app-project-overview', - templateUrl: 'overview.component.html', - providers: [ - FlavorService, - ApplicationsService, - FacilityService, - UserService, - GroupService, - ApiSettings, - CreditsService - ] + templateUrl: 'overview.component.html' }) export class OverviewComponent extends ApplicationBaseClassComponent implements OnInit, OnDestroy { bsModalRef: BsModalRef modificationRequestDisabled: boolean = false lifetimeExtensionDisabled: boolean = false creditsExtensionDisabled: boolean = false - voRegistrationLink: string = environment.voRegistrationLink vo_name: string = environment.voName - WIKI_MEMBER_MANAGEMENT: string = WIKI_MEMBER_MANAGEMENT WIKI_PUBLICATIONS: string = WIKI_PUBLICATIONS CREDITS_WIKI: string = CREDITS_WIKI CLOUD_PORTAL_SUPPORT_MAIL: string = CLOUD_PORTAL_SUPPORT_MAIL @@ -90,33 +74,22 @@ export class OverviewComponent extends ApplicationBaseClassComponent implements STATUS_LINK: string = STATUS_LINK NEW_SVM_PORTAL_LINK: string = NEW_SVM_PORTAL_LINK @ViewChild('creditsChart') creditsCanvas: ElementRef - @ViewChild('publicKeyModal') publicKeyModal: any - @ViewChild('terminateModal') terminateModal: any - publicKeyToShow: string = '' - publicKeyMemberName: string = '' project_id: string application_id: string credits: number = 0 errorMessage: string - terminate_confirmation_given: boolean = false showInformationCollapse: boolean = false newDoi: string doiError: string remove_members_clicked: boolean dois: Doi[] disabledDoiInput: boolean = false - invitation_link: string - CLOUD_PORTAL_REGISTER_LINK = CLOUD_PORTAL_REGISTER_LINK + project_application: Application - application_action: string = '' - application_member_name: string = '' - application_action_done: boolean = false - application_action_success: boolean - application_action_error_message: boolean + loaded: boolean = true userinfo: Userinfo allSet: boolean = false - renderer: Renderer2 supportMails: string[] = [] toggleLocked: boolean = false resourceDataLoaded: boolean = false @@ -154,10 +127,17 @@ export class OverviewComponent extends ApplicationBaseClassComponent implements private fullLayout: FullLayoutComponent, private router: Router, private creditsService: CreditsService, + private terminationRequestComponent: TerminationRequestComponent, + private viewPublicKeyComponent: ViewPublicKeyComponent, + private leaveProjectComponent: LeaveProjectComponent, + private deleteApplicationModal: DeleteApplicationModal, + private addUserModalComponent: AddUserModalComponent, + private userApplicationsModalComponent: UserApplicationsModalComponent, + notificationModal: NotificationModalComponent, @Inject(DOCUMENT) private document: Document, cdrRef: ChangeDetectorRef ) { - super(userService, applicationsService, facilityService, cdrRef) + super(userService, applicationsService, facilityService, notificationModal, cdrRef) } calculateProgressBar(numberToRoundUp: number): string { @@ -382,12 +362,37 @@ export class OverviewComponent extends ApplicationBaseClassComponent implements } else if (event.showExtension) { this.showLifetimeExtensionModal() } else if (event.showTermination) { - this.terminateModal.show() + this.showTerminationModal() } }) ) } + showUserApplicationModal(): void { + this.userApplicationsModalComponent.showAddUserApplicationModal(this.project_application).subscribe(() => { + this.getMembersOfTheProject() + }) + } + + showAddMemberModal(): void { + const invitationLink: string = this.getAddUserInvitationLink() + this.addUserModalComponent.showAddUserModalComponent(this.project_application, invitationLink) + } + + showDeleteApplicationModal(): void { + this.deleteApplicationModal.showDeleteApplicationModal(this.project_application) + } + + showLeaveTerminationModal(): void { + this.leaveProjectComponent.showLeaveProjectModal(this.project_application, this.userinfo).subscribe() + } + + showTerminationModal(): void { + this.terminationRequestComponent.showTerminationRequestModal(this.project_application).subscribe(() => { + this.getApplication() + }) + } + showLifetimeExtensionModal(): void { if (this.lifetimeExtensionDisabled) { return @@ -535,33 +540,6 @@ export class OverviewComponent extends ApplicationBaseClassComponent implements } } - approveMemberApplication(application: number, membername: string): void { - this.loaded = false - this.application_action_done = false - this.subscription.add( - this.groupService - .approveGroupApplication(Number(this.project_application.project_application_perun_id), application) - .subscribe((tmp_application: any): void => { - if (tmp_application['state'] === 'APPROVED') { - this.application_action_success = true - } else if (tmp_application['message']) { - this.application_action_success = false - - this.application_action_error_message = tmp_application['message'] - } else { - this.application_action_success = false - } - - this.application_action = 'approved' - this.application_member_name = membername - this.application_action_done = true - this.getUserProjectApplications() - this.getMembersOfTheProject() - this.loaded = true - }) - ) - } - /** * If the application is an openstack application, the requested/approved resources will be set for maximum VMs. * For SimpleVM also the VMs in use are set. @@ -667,47 +645,6 @@ export class OverviewComponent extends ApplicationBaseClassComponent implements } } - requestProjectTermination(): void { - this.updateNotificationModal('Waiting', 'Termination request will be submitted...', true, 'info') - - this.subscription.add( - this.groupService - .requestProjectTermination(this.project_application.project_application_perun_id) - .subscribe((): void => { - this.fullLayout.getGroupsEnumeration() - this.getApplication() - this.updateNotificationModal('Success', 'Termination was requested!', true, 'success') - }) - ) - } - - rejectMemberApplication(application: number, membername: string): void { - this.loaded = false - this.application_action_done = false - this.subscription.add( - this.groupService - .rejectGroupApplication(Number(this.project_application.project_application_perun_id), application) - .subscribe((tmp_application: any): void => { - this.project_application.project_application_member_applications = [] - - if (tmp_application['state'] === 'REJECTED') { - this.application_action_success = true - } else if (tmp_application['message']) { - this.application_action_success = false - - this.application_action_error_message = tmp_application['message'] - } else { - this.application_action_success = false - } - this.application_action = 'rejected' - this.application_member_name = membername - this.application_action_done = true - this.getUserProjectApplications() - this.loaded = true - }) - ) - } - /** * Get all user applications for a project. */ @@ -854,9 +791,8 @@ export class OverviewComponent extends ApplicationBaseClassComponent implements this.allSet = false } - setAddUserInvitationLink(): void { - const project_reg: string = `https://signup.aai.lifescience-ri.eu/fed/registrar/?vo=${this.vo_name}&group=${this.project_application.project_application_shortname}` - this.invitation_link = project_reg + getAddUserInvitationLink(): string { + return `https://signup.aai.lifescience-ri.eu/fed/registrar/?vo=${this.vo_name}&group=${this.project_application.project_application_shortname}` } copyToClipboard(text: string): void { @@ -952,74 +888,8 @@ export class OverviewComponent extends ApplicationBaseClassComponent implements ) } - /** - * Leave a project - * - * @param memberid of the member - * @param projectname of the project - */ - public leaveProject(memberid: number, projectname: string): void { - if (this.project_application.project_application_pi.elixir_id === this.userinfo.ElixirId) { - this.updateNotificationModal( - 'Denied', - `You cannot leave projects as PI. Please contact ${CLOUD_PORTAL_SUPPORT_MAIL} for further steps.`, - true, - 'danger' - ) - } else { - this.subscription.add( - this.groupService - .leaveGroup( - this.project_application.project_application_perun_id, - memberid, - this.project_application.project_application_compute_center.FacilityId - ) - .subscribe( - (result: any): void => { - if (result.status === 200) { - this.updateNotificationModal( - 'Success', - `You were removed from the project ${projectname}`, - true, - 'success' - ) - void this.router.navigate(['/userinfo']) - this.fullLayout.getGroupsEnumeration() - } else { - this.updateNotificationModal('Failed', `Failed to leave the project ${projectname}!`, true, 'danger') - } - }, - (): void => { - this.updateNotificationModal('Failed', `Failed to leave the project ${projectname}!`, true, 'danger') - } - ) - ) - } - } - showPublicKeyModal(member: ProjectMember): void { - this.publicKeyToShow = member.publicKey - this.publicKeyMemberName = `${member.firstName} ${member.lastName}` - this.publicKeyModal.show() - } - - /** - * Delete an application. - */ - public deleteApplication(): void { - this.subscription.add( - this.applicationsService.deleteApplication(this.project_application.project_application_id).subscribe( - (): void => { - this.updateNotificationModal('Success', 'The application has been successfully removed', true, 'success') - this.fullLayout.getGroupsEnumeration() - - void this.router.navigate(['/userinfo']) - }, - (): void => { - this.updateNotificationModal('Failed', 'Application could not be removed!', true, 'danger') - } - ) - ) + this.viewPublicKeyComponent.showViewPublicKeyModal(`${member.firstName} ${member.lastName}`, member.publicKey) } protected readonly ApplicationRequestType = ApplicationRequestType diff --git a/src/app/projectmanagement/project-os-details/project-os-details.component.html b/src/app/projectmanagement/project-os-details/project-os-details.component.html index f0e2787763..b8282b4432 100644 --- a/src/app/projectmanagement/project-os-details/project-os-details.component.html +++ b/src/app/projectmanagement/project-os-details/project-os-details.component.html @@ -1,7 +1,10 @@
-
+@if (show_error) { + +} +
diff --git a/src/app/projectmanagement/project-os-details/project-os-details.component.ts b/src/app/projectmanagement/project-os-details/project-os-details.component.ts index 4d47104194..2a9749a2ef 100644 --- a/src/app/projectmanagement/project-os-details/project-os-details.component.ts +++ b/src/app/projectmanagement/project-os-details/project-os-details.component.ts @@ -20,6 +20,7 @@ export class ProjectOsDetailsComponent implements OnInit, OnChanges { selectedProjectVolumes: Volume[] = [] selectedProjectSnapshots: SnapshotModel[] = [] details_loaded: boolean = false + show_error: boolean = false constructor(private groupService: GroupService) { this.groupService = groupService @@ -33,6 +34,7 @@ export class ProjectOsDetailsComponent implements OnInit, OnChanges { ngOnInit(): void { this.details_loaded = false + this.show_error = false this.getProjectDetails() } @@ -40,11 +42,17 @@ export class ProjectOsDetailsComponent implements OnInit, OnChanges { if (!this.project.project_application_perun_id || this.project.project_application_openstack_project) { return } - this.groupService.getProjectOSDetails(this.project.project_application_perun_id).subscribe((res: any): void => { - this.selectedProjectVms = res['vms'] - this.selectedProjectVolumes = res['volumes'] - this.selectedProjectSnapshots = res['snapshots'] - this.details_loaded = true - }) + this.groupService.getProjectOSDetails(this.project.project_application_perun_id).subscribe( + (res: any): void => { + this.selectedProjectVms = res['vms'] + this.selectedProjectVolumes = res['volumes'] + this.selectedProjectSnapshots = res['snapshots'] + this.details_loaded = true + }, + () => { + this.details_loaded = true + this.show_error = true + } + ) } } diff --git a/src/app/projectmanagement/projectmanagement.module.ts b/src/app/projectmanagement/projectmanagement.module.ts index 64baca103e..334eb5d3a1 100644 --- a/src/app/projectmanagement/projectmanagement.module.ts +++ b/src/app/projectmanagement/projectmanagement.module.ts @@ -25,6 +25,12 @@ import { SharedModuleModule } from '../shared/shared_modules/shared-module.modul import { AdjustLifetimeRequestComponent } from './modals/adjust-lifetime/adjust-lifetime-request.component' import { AdjustApplicationComponent } from './modals/adjust-application/adjust-application.component' import { WithdrawModalComponent } from './modals/withdraw/withdraw-modal.component' +import { TerminationRequestComponent } from './modals/termination-request/termination-request.component' +import { LeaveProjectComponent } from './modals/leave-project/leave-project.component' +import { DeleteApplicationModal } from './modals/delete-member-application-modal/delete-application-modal.component' +import { AddUserModalComponent } from './modals/add-user-modal/add-user-modal.component' +import { UserApplicationsModalComponent } from './modals/user-applications-modal/user-applications-modal.component' +import { ClipboardModule } from 'ngx-clipboard' /** * Projectmanagment module. @@ -45,7 +51,8 @@ import { WithdrawModalComponent } from './modals/withdraw/withdraw-modal.compone NgSelectModule, NgbModule, BadgeModule, - SharedModuleModule + SharedModuleModule, + ClipboardModule ], declarations: [ OverviewComponent, @@ -58,7 +65,12 @@ import { WithdrawModalComponent } from './modals/withdraw/withdraw-modal.compone ExtensionEntryComponent, AdjustLifetimeRequestComponent, AdjustApplicationComponent, - WithdrawModalComponent + WithdrawModalComponent, + TerminationRequestComponent, + LeaveProjectComponent, + DeleteApplicationModal, + AddUserModalComponent, + UserApplicationsModalComponent ], exports: [ProjectOsDetailsComponent, ExtensionEntryComponent] }) diff --git a/src/app/shared/modal/abstract-base-modal/abstract-base-modal.component.ts b/src/app/shared/modal/abstract-base-modal/abstract-base-modal.component.ts new file mode 100644 index 0000000000..d7f152af0a --- /dev/null +++ b/src/app/shared/modal/abstract-base-modal/abstract-base-modal.component.ts @@ -0,0 +1,37 @@ +import { Component, EventEmitter, Output } from '@angular/core' +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal' + +@Component({ template: '' }) +export abstract class AbstractBaseModalComponent { + modalId: number | string | undefined + @Output() event: EventEmitter = new EventEmitter() + bsModalRef: BsModalRef + + constructor(protected modalService: BsModalService) { + this.modalService.onHidden.subscribe(() => { + this.modalId = undefined // Reset modalId when any modal is closed + }) + } + + async hide(): Promise { + console.log('close modal abstract') + this.modalService.hide() + + //Fix when calling hide and show form within a modal -- if it is called directly after another the new modal won't open + await this.sleep(200) + } + + private async sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) + } + + showBaseModal(modalType: any, initialState?: any): EventEmitter { + const bsModalRef: BsModalRef = this.modalService.show(modalType, { initialState }) + this.bsModalRef = bsModalRef + bsModalRef.setClass('modal-lg') + this.modalId = bsModalRef.id + console.log(`new id ${this.modalId}`) + + return bsModalRef.content.event + } +} diff --git a/src/app/shared/modal/confirmation-modal.component.html b/src/app/shared/modal/confirmation-modal.component.html index 7674b92e22..d841e611f8 100644 --- a/src/app/shared/modal/confirmation-modal.component.html +++ b/src/app/shared/modal/confirmation-modal.component.html @@ -1,12 +1,6 @@ @@ -1102,144 +961,4 @@ - - - - - - - - diff --git a/tests/page_objects/vo_overview.po.ts b/tests/page_objects/vo_overview.po.ts index 508f1f50a2..24eaae5caa 100644 --- a/tests/page_objects/vo_overview.po.ts +++ b/tests/page_objects/vo_overview.po.ts @@ -1,115 +1,116 @@ -import { expect, Page } from '@playwright/test'; -import { Util } from '../util'; +import { expect, Page } from '@playwright/test' +import { Util } from '../util' /** * Vo Overview Page. */ export class VoOverviewPage { - private VO_OVERVIEW_URL: string = '/#/vo-manager/overview'; - private FILTER_PROJECT_NAME_INPUT: string = 'filter_project_name'; - private SHOW_TERMINATE_PREFIX: string = 'show_terminate_'; - private TERMINATE_PROJECT_BTN: string = 'approve_terminate_project_btn'; - private NOTIFICATION_MESSAGE: string = 'notification_message'; - private CLOSE_NOTIFICATION_BTN: string = 'close_notification_modal_btn'; - private PROJECT_TERMINATED_MESSAGE: string = 'The project was terminated.'; - private PROJECT_TERMINATION_FORWARDED_TO_FACILITY: string = 'The request to terminate the project was forwarded to the facility manager.'; - - private TERMINATE_BUTTON_TEXT: string = 'Terminate Project'; - private NOTIFICATION_MODAL_TITLE: string = 'notification_modal_title'; - private SUCCESS: string = 'Success'; - private SITE_LOADER: string = 'site-loader'; - private SPINNER: string = 'spinner'; - - readonly page: Page; - readonly baseURL: string; + private VO_OVERVIEW_URL: string = '/#/vo-manager/overview' + private FILTER_PROJECT_NAME_INPUT: string = 'filter_project_name' + private SHOW_TERMINATE_PREFIX: string = 'show_terminate_' + private TERMINATE_PROJECT_BTN: string = 'approve_terminate_project_btn' + private NOTIFICATION_MESSAGE: string = 'notification_modal_message' + private NOTIFICATION_MESSAGE_TYPE: string = 'notification_message_type' + + private CLOSE_NOTIFICATION_BTN: string = 'close_notification_modal_btn' + private PROJECT_TERMINATED_MESSAGE: string = 'The project was terminated.' + private PROJECT_TERMINATION_FORWARDED_TO_FACILITY: string = + 'The request to terminate the project was forwarded to the facility manager.' + + private TERMINATE_BUTTON_TEXT: string = 'Terminate Project' + private NOTIFICATION_MODAL_TITLE: string = 'notification_modal_title' + private SUCCESS: string = 'Success' + private SITE_LOADER: string = 'site-loader' + private SPINNER: string = 'spinner' + + readonly page: Page + readonly baseURL: string constructor(page: Page, baseURL) { - this.page = page; - this.baseURL = baseURL; + this.page = page + this.baseURL = baseURL } async goto() { - console.log('Goto vo manager overview Page'); - await this.page.goto(this.baseURL + this.VO_OVERVIEW_URL, { waitUntil: 'networkidle' }); - console.log(this.page.url()); + console.log('Goto vo manager overview Page') + await this.page.goto(this.baseURL + this.VO_OVERVIEW_URL, { waitUntil: 'networkidle' }) + console.log(this.page.url()) - expect(this.page.url()).toContain(this.VO_OVERVIEW_URL); - await this.page.waitForSelector(Util.by_data_test_id_str(this.SITE_LOADER), { state: 'hidden' }); + expect(this.page.url()).toContain(this.VO_OVERVIEW_URL) + await this.page.waitForSelector(Util.by_data_test_id_str(this.SITE_LOADER), { state: 'hidden' }) try { - await this.page.waitForSelector(Util.by_data_test_id_str(this.SPINNER), { state: 'visible', timeout: 5000 }); + await this.page.waitForSelector(Util.by_data_test_id_str(this.SPINNER), { state: 'visible', timeout: 5000 }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { - console.log('Spinner not visible within 5 seconds, continuing...'); + console.log('Spinner not visible within 5 seconds, continuing...') } - await this.page.waitForSelector(Util.by_data_test_id_str(this.SPINNER), { state: 'hidden' }); + await this.page.waitForSelector(Util.by_data_test_id_str(this.SPINNER), { state: 'hidden' }) // Add a 3-second wait at the end - await this.page.waitForTimeout(3000); + await this.page.waitForTimeout(3000) } async filterForProjects(filter: string): Promise { - console.log(`Filter for ${filter} Projects`); - await this.page.type(Util.by_data_test_id_str(this.FILTER_PROJECT_NAME_INPUT), filter); + console.log(`Filter for ${filter} Projects`) + await this.page.type(Util.by_data_test_id_str(this.FILTER_PROJECT_NAME_INPUT), filter) } async terminateOpenStackProjects(project_name: string): Promise { - console.log(`Terminate all openstack projects with name ${project_name}`); - await this.goto(); - await this.filterForProjects(project_name); + console.log(`Terminate all openstack projects with name ${project_name}`) + await this.goto() + await this.filterForProjects(project_name) let project_count: number = await this.page .locator(Util.by_data_test_id_str_prefix(this.SHOW_TERMINATE_PREFIX + Util.OPENSTACK_APPLICATION_NAME)) - .count(); - console.log(`Terminating ${project_count} openstack projects with name ${project_name}`); - // eslint-disable-next-line no-plusplus + .count() + console.log(`Terminating ${project_count} openstack projects with name ${project_name}`) + while (project_count > 0) { - // eslint-disable-next-line no-await-in-loop await this.page .locator(Util.by_data_test_id_str_prefix(this.SHOW_TERMINATE_PREFIX + Util.OPENSTACK_APPLICATION_NAME)) .first() - .click(); - // eslint-disable-next-line no-await-in-loop - await this.page.locator(Util.by_data_test_id_str(this.TERMINATE_PROJECT_BTN)).first().click(); + .click() + + await this.page.locator(Util.by_data_test_id_str(this.TERMINATE_PROJECT_BTN)).first().click() - // eslint-disable-next-line no-await-in-loop - await expect(this.page.locator(Util.by_data_test_id_str(this.NOTIFICATION_MESSAGE))).toHaveClass(/alert-success/); - // eslint-disable-next-line no-await-in-loop - await this.page.locator(Util.by_data_test_id_str(this.CLOSE_NOTIFICATION_BTN)).click(); - await this.page.waitForTimeout(1500); + await expect(this.page.locator(Util.by_data_test_id_str(this.NOTIFICATION_MESSAGE_TYPE))).toHaveClass( + /alert-success/ + ) + + await this.page.locator(Util.by_data_test_id_str(this.CLOSE_NOTIFICATION_BTN)).click() + await this.page.waitForTimeout(1500) project_count = await this.page .locator(Util.by_data_test_id_str_prefix(this.SHOW_TERMINATE_PREFIX + Util.OPENSTACK_APPLICATION_NAME)) - .count(); + .count() } - } async terminateSimpleVMProjects(project_name: string): Promise { - console.log(`Terminate all simplevm projects with name ${project_name}`); - await this.goto(); - await this.filterForProjects(project_name); - await this.page.waitForTimeout(7500); + console.log(`Terminate all simplevm projects with name ${project_name}`) + await this.goto() + await this.filterForProjects(project_name) + await this.page.waitForTimeout(7500) const project_count: number = await this.page .locator(Util.by_data_test_id_str_prefix(this.SHOW_TERMINATE_PREFIX + Util.SIMPLE_VM_APPLICATION_NAME)) - .count(); - console.log(`Terminating ${project_count} simplevm projects with name ${project_name}`); - // eslint-disable-next-line no-plusplus + .count() + console.log(`Terminating ${project_count} simplevm projects with name ${project_name}`) + for (let i = 0; i < project_count; i++) { - // eslint-disable-next-line no-await-in-loop await this.page .locator(Util.by_data_test_id_str_prefix(this.SHOW_TERMINATE_PREFIX + Util.SIMPLE_VM_APPLICATION_NAME)) .first() - .click(); - // eslint-disable-next-line no-await-in-loop - await this.page.locator(Util.by_data_test_id_str(this.TERMINATE_PROJECT_BTN)).first().click(); + .click() + + await this.page.locator(Util.by_data_test_id_str(this.TERMINATE_PROJECT_BTN)).first().click() - // eslint-disable-next-line no-await-in-loop await this.page.waitForSelector( - `data-test-id=${this.NOTIFICATION_MESSAGE} >> text=${this.PROJECT_TERMINATED_MESSAGE}`, - ); - // eslint-disable-next-line no-await-in-loop - await this.page.locator(Util.by_data_test_id_str(this.CLOSE_NOTIFICATION_BTN)).click(); + `data-test-id=${this.NOTIFICATION_MESSAGE} >> text=${this.PROJECT_TERMINATED_MESSAGE}` + ) + + await this.page.locator(Util.by_data_test_id_str(this.CLOSE_NOTIFICATION_BTN)).click() } } }
-