diff --git a/src/app/facility_manager/facilitymanager.module.ts b/src/app/facility_manager/facilitymanager.module.ts index 7097df402b..3ff7a9f8d8 100644 --- a/src/app/facility_manager/facilitymanager.module.ts +++ b/src/app/facility_manager/facilitymanager.module.ts @@ -6,7 +6,9 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; import { NgbPaginationModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; import { NgSelectModule } from '@ng-select/ng-select'; -import { AlertComponent, BadgeComponent } from '@coreui/angular'; +import { + AlertComponent, BadgeComponent, ButtonDirective, InputGroupComponent, +} from '@coreui/angular'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import { FacilityProjectsOverviewComponent } from '../facility_manager/facilityprojectsoverview.component'; import { ImageTagComponent } from '../facility_manager/imagetags.component'; @@ -48,6 +50,8 @@ import { SharedModuleModule } from '../shared/shared_modules/shared-module.modul AlertComponent, BadgeComponent, TooltipModule, + InputGroupComponent, + ButtonDirective, ], declarations: [ FacilityProjectsOverviewComponent, diff --git a/src/app/facility_manager/facilityprojectsoverview.component.html b/src/app/facility_manager/facilityprojectsoverview.component.html index 4145c0989c..cd9cc581dd 100644 --- a/src/app/facility_manager/facilityprojectsoverview.component.html +++ b/src/app/facility_manager/facilityprojectsoverview.component.html @@ -1,766 +1,755 @@
-
- -
-
- -
+
+ +
+
+ +
-
- +
+ - + -
-
-
- -
-
- -
-
- - -
-
- -
-
- -
-
-
-
Projects
+
+
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+
+
Projects
-
-
- - +
+
+ + -
- +
+ - + - -
+ +
- -
+ +
-
-
- -
- -
- -
- +
+
+ +
+ +
+ +
+ + + + + - Loading... + Loading... -
-
- - -
+
+
+ + +
-
- - -
+
+ + +
-
- - -
-
-
+
+ + +
+
+
+
- -
- -
-
-
- - - - - + [checked]="sortProjectService.filterStatusList | inList: application_states.WAIT_FOR_TERMINATION_FM" + /> + {{ + application_states[application_states.WAIT_FOR_TERMINATION_FM] + }} + + + +
SelectType
+ + + + - - - + + + - - - - + + + + - - - - - - @if (projectsLoaded) { - - - + + + + + + @if (projectsLoaded) { + + + - - - - + + + - - - - + + + + + - - - } @else { - - - - } - -
SelectType - Project ID - - Project Name - Long - Name - Project IDProject NameLong NameStatus - Ram - - Cores - - GPUs - StatusRamCoresGPUsDescriptionActions
- - - - DescriptionActions
+ + + + - > - - - - - - + + > + + + + + + {{ project_states[project_states.ACTIVE] }} + *ngIf="project | hasstatusinlist: project_states.ACTIVE" + style="margin-left: 5px" + class="badge bg-success" + >{{ project_states[project_states.ACTIVE] }} - {{ lifetime_states[lifetime_states.EXPIRED] }} + style="margin-left: 5px" + class="badge bg-danger" + >{{ lifetime_states[lifetime_states.EXPIRED] }} - {{ lifetime_states[lifetime_states.EXPIRES_SOON] }} + {{ lifetime_states[lifetime_states.EXPIRES_SOON] }} - WAIT FOR CONFIRMATION + WAIT FOR CONFIRMATION - WAIT FOR TERMINATION BY FM + WAIT FOR TERMINATION BY FM - - - - > - - - > - - - - + + + > + + + > + + + + - -
- -
-
-
-
-
- -
+ + + + + } @else { + + + + + + } + + +
+
+
+
+ +
+ +
+ + + + + + + diff --git a/src/app/facility_manager/facilityprojectsoverview.component.ts b/src/app/facility_manager/facilityprojectsoverview.component.ts index fd7b9d25e0..ddb38f765f 100644 --- a/src/app/facility_manager/facilityprojectsoverview.component.ts +++ b/src/app/facility_manager/facilityprojectsoverview.component.ts @@ -32,130 +32,131 @@ import { CsvMailTemplateModel } from '../shared/classes/csvMailTemplate.model'; providers: [FacilityService, UserService, GroupService, ApiSettings, NewsService, ProjectSortService], }) export class FacilityProjectsOverviewComponent extends AbstractBaseClass implements OnInit { - @Input() voRegistrationLink: string = environment.voRegistrationLink; - - title: string = 'Projects Overview'; - filter: string; - - membersLoaded: boolean = false; - public memberFilter: string = ''; - filteredMembers: object[] = []; - selectedMember: object[] = []; - facility_members: object[] = []; - - filterChanged: Subject = new Subject(); - isLoaded: boolean = false; - projects: Application[] = []; - projectsCopy: Application[] = []; - show_openstack_projects: boolean = true; - show_simple_vm_projects: boolean = true; - details_loaded: boolean = false; - selectedEmailProjects: Application[] = []; - bsModalRef: BsModalRef; - userElixirSearchPI: boolean = true; - userElixirSearchAdmin: boolean = true; - userElixirSearchMember: boolean = true; - userElixirIdFilter: string; - - projectsLoaded: boolean = false; - - /** - * Approved group status. - * - * @type {number} - */ - STATUS_APPROVED: number = 2; - - selectedProjectType: string = 'ALL'; - - // modal variables for User list - public selectedProjectForSearch: Application; - public usersModalProjectMembers: ProjectMember[] = []; - allFacilityMembers: object[] = []; - public usersModalProjectID: number; - public usersModalProjectName: string; - public selectedProject: Application; - public userSearchValue: string; - - public emailSubject: string; - public emailText: string; - public emailStatus: number = 0; - public emailReply: string = ''; - public sendNews: boolean; - public alternative_emailText: string = ''; - public news_tags: string[] = []; - FILTER_DEBOUNCE_TIME: number = 500; - - public managerFacilities: [string, number][] = []; - public selectedFacility: [string, number]; - projects_filtered: Application[] = []; - facilitySupportMails: string = ''; - supportMailEditing: boolean = false; - PREDEFINED_TAGS: string[] = ['downtime', 'openstack', 'simplevm', 'maintenance', 'update']; - - @ViewChildren(NgbdSortableHeaderDirective) headers: QueryList; - - applictions$: Observable; - total$: Observable; - - constructor( - private groupService: GroupService, - private facilityService: FacilityService, - private newsService: NewsService, - public sortProjectService: ProjectSortService, - private modalService: BsModalService, - private emailService: EmailService, - ) { - 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': + @Input() voRegistrationLink: string = environment.voRegistrationLink; + + title: string = 'Projects Overview'; + filter: string; + + membersLoaded: boolean = false; + public memberFilter: string = ''; + filteredMembers: object[] = []; + selectedMember: object[] = []; + facility_members: object[] = []; + + filterChanged: Subject = new Subject(); + isLoaded: boolean = false; + projects: Application[] = []; + projectsCopy: Application[] = []; + show_openstack_projects: boolean = true; + show_simple_vm_projects: boolean = true; + details_loaded: boolean = false; + selectedEmailProjects: Application[] = []; + bsModalRef: BsModalRef; + userElixirSearchPI: boolean = true; + userElixirSearchAdmin: boolean = true; + userElixirSearchMember: boolean = true; + userElixirIdFilter: string; + + projectsLoaded: boolean = false; + + /** + * Approved group status. + * + * @type {number} + */ + STATUS_APPROVED: number = 2; + + selectedProjectType: string = 'ALL'; + + // modal variables for User list + public selectedProjectForSearch: Application; + public usersModalProjectMembers: ProjectMember[] = []; + allFacilityMembers: object[] = []; + public usersModalProjectID: number; + public usersModalProjectName: string; + public selectedProject: Application; + public userSearchValue: string; + validElixirIdFilter: boolean = false; + + public emailSubject: string; + public emailText: string; + public emailStatus: number = 0; + public emailReply: string = ''; + public sendNews: boolean; + public alternative_emailText: string = ''; + public news_tags: string[] = []; + FILTER_DEBOUNCE_TIME: number = 500; + + public managerFacilities: [string, number][] = []; + public selectedFacility: [string, number]; + projects_filtered: Application[] = []; + facilitySupportMails: string = ''; + supportMailEditing: boolean = false; + PREDEFINED_TAGS: string[] = ['downtime', 'openstack', 'simplevm', 'maintenance', 'update']; + + @ViewChildren(NgbdSortableHeaderDirective) headers: QueryList; + + applictions$: Observable; + total$: Observable; + + constructor( + private groupService: GroupService, + private facilityService: FacilityService, + private newsService: NewsService, + public sortProjectService: ProjectSortService, + private modalService: BsModalService, + private emailService: EmailService, + ) { + 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': + this.emailSubject = `[${this.selectedFacility['Facility']}]`; + break; + case 'OVP': + this.emailSubject = `[${this.selectedFacility['Facility']}: OpenStack]`; + break; + case 'SVP': + this.emailSubject = `[${this.selectedFacility['Facility']}: SimpleVm]`; + break; + case 'USER': + this.emailSubject = `[${this.selectedFacility['Facility']}: Specific Members]`; + break; + default: + // eslint-disable-next-line no-case-declarations + const pro: Application = this.projects.find( + (project: Application): boolean => project.project_application_perun_id.toString() === this.selectedProjectType.toString(), + ); + if (pro) { + this.emailSubject = `[${this.selectedFacility['Facility']}: ${pro.project_application_shortname}]`; + } else { this.emailSubject = `[${this.selectedFacility['Facility']}]`; - break; - case 'OVP': - this.emailSubject = `[${this.selectedFacility['Facility']}: OpenStack]`; - break; - case 'SVP': - this.emailSubject = `[${this.selectedFacility['Facility']}: SimpleVm]`; - break; - case 'USER': - this.emailSubject = `[${this.selectedFacility['Facility']}: Specific Members]`; - break; - default: - // eslint-disable-next-line no-case-declarations - const pro: Application = this.projects.find( - (project: Application): boolean => project.project_application_perun_id.toString() === this.selectedProjectType.toString(), - ); - if (pro) { - this.emailSubject = `[${this.selectedFacility['Facility']}: ${pro.project_application_shortname}]`; - } else { - this.emailSubject = `[${this.selectedFacility['Facility']}]`; - } - break; - } + } + break; } + } - ngOnInit(): void { - this.facilityService.getManagerFacilities().subscribe((result: any): void => { - this.managerFacilities = result; - this.selectedFacility = this.managerFacilities[0]; - this.emailSubject = `[${this.selectedFacility['Facility']}]`; - this.getFacilityProjects(this.managerFacilities[0]['FacilityId']); - this.title = `${this.title}:${this.selectedFacility['Facility']}`; - }); - this.sendNews = true; + ngOnInit(): void { + this.facilityService.getManagerFacilities().subscribe((result: any): void => { + this.managerFacilities = result; + this.selectedFacility = this.managerFacilities[0]; + this.emailSubject = `[${this.selectedFacility['Facility']}]`; + this.getFacilityProjects(this.managerFacilities[0]['FacilityId']); + this.title = `${this.title}:${this.selectedFacility['Facility']}`; + }); + this.sendNews = true; - /** needs refactoring in case we introduce tags to wagtail + /** needs refactoring in case we introduce tags to wagtail * this.newsService.getAvailableTagsFromWordPress().subscribe((tags: WordPressTag[]): void => { if (!(('code' in tags) && tags['code'] === 'wp-die')) { if (tags) { @@ -163,383 +164,397 @@ export class FacilityProjectsOverviewComponent extends AbstractBaseClass impleme } } }); * */ + } + + onCsvFileSelected(event): void { + const inputElement = event.target as HTMLInputElement; + if (inputElement.files && inputElement.files.length > 0) { + this.emailService.sendCsvTemplate(inputElement.files[0]).subscribe( + (csvTemplate: CsvMailTemplateModel) => { + this.openProjectMailsModal(inputElement.files[0], csvTemplate); + }, + (error: CsvMailTemplateModel) => { + console.log(error['error']); + this.openProjectMailsModal(inputElement.files[0], error['error']); + }, + ); } + } - onCsvFileSelected(event): void { - const inputElement = event.target as HTMLInputElement; - if (inputElement.files && inputElement.files.length > 0) { - this.emailService.sendCsvTemplate(inputElement.files[0]).subscribe( - (csvTemplate: CsvMailTemplateModel) => { - this.openProjectMailsModal(inputElement.files[0], csvTemplate); - }, - (error: CsvMailTemplateModel) => { - console.log(error['error']); - this.openProjectMailsModal(inputElement.files[0], error['error']); - }, - ); + onSort({ column, direction }: SortEvent) { + // resetting other headers + this.headers.forEach(header => { + if (header.appSortable !== column) { + header.direction = ''; } + }); + + this.sortProjectService.sortColumn = column; + this.sortProjectService.sortDirection = direction; + } + + searchForUserInFacility(searchString: string): void { + this.facilityService.getFilteredMembersOfFacility(searchString); + } + + filterMembers(bare_searchString: string): void { + this.filteredMembers = []; + const searchString: string = bare_searchString.toLowerCase(); + + this.allFacilityMembers.forEach((member: object): void => { + if ( + member['elixirId'].toLowerCase().includes(searchString) + || member['email'].toLowerCase().includes(searchString) + || member['firstName'].toLowerCase().includes(searchString) + || member['lastName'].toLowerCase().includes(searchString) + ) { + this.filteredMembers.push(member); + } + }); + } + + checkValidElixirIdFilter(): void { + this.validElixirIdFilter = this.userElixirIdFilter && this.userElixirIdFilter.includes('@elixir-europe.org'); + if (!this.validElixirIdFilter) { + this.sortProjectService.applications = this.projectsCopy; + this.applictions$ = this.sortProjectService.applications$; + this.total$ = this.sortProjectService.total$; + this.projectsLoaded = true; } + } - onSort({ column, direction }: SortEvent) { - // resetting other headers - this.headers.forEach(header => { - if (header.appSortable !== column) { - header.direction = ''; - } - }); - - this.sortProjectService.sortColumn = column; - this.sortProjectService.sortDirection = direction; - } + getProjectsByMemberElixirId(): void { + // tslint:disable-next-line:max-line-length + this.userElixirIdFilter = this.userElixirIdFilter.trim(); + if (this.userElixirIdFilter && this.userElixirIdFilter.includes('@elixir-europe.org')) { + this.projectsLoaded = false; - searchForUserInFacility(searchString: string): void { - this.facilityService.getFilteredMembersOfFacility(searchString); + this.facilityService + .getFacilityGroupsByMemberElixirId( + this.selectedFacility['FacilityId'], + this.userElixirIdFilter, + this.userElixirSearchPI, + this.userElixirSearchAdmin, + this.userElixirSearchMember, + ) + .subscribe((applications: Application[]): void => { + this.projects = applications; + for (const group of applications) { + if (group.project_application_lifetime > 0) { + group.lifetime_reached = this.lifeTimeReached(group.lifetime_days, group.DaysRunning); + } + } + this.sortProjectService.applications = this.projects; + this.applictions$ = this.sortProjectService.applications$; + this.total$ = this.sortProjectService.total$; + this.projectsLoaded = true; + }); + } else { + this.sortProjectService.applications = this.projectsCopy; + this.applictions$ = this.sortProjectService.applications$; + this.total$ = this.sortProjectService.total$; + this.projectsLoaded = true; } - - filterMembers(bare_searchString: string): void { - this.filteredMembers = []; - const searchString: string = bare_searchString.toLowerCase(); - - this.allFacilityMembers.forEach((member: object): void => { - if ( - member['elixirId'].toLowerCase().includes(searchString) - || member['email'].toLowerCase().includes(searchString) - || member['firstName'].toLowerCase().includes(searchString) - || member['lastName'].toLowerCase().includes(searchString) - ) { - this.filteredMembers.push(member); - } + } + + /** + * Gets projects and sets email subject prefix when selected facility changes. + */ + onChangeSelectedFacility(): void { + this.isLoaded = false; + this.getFacilityProjects(this.selectedFacility['FacilityId']); + this.emailSubject = `[${this.selectedFacility['Facility']}]`; + } + + showMembersModal(application: Application): void { + const initialState = { + projectId: application.project_application_perun_id, + projectName: application.project_application_shortname, + facilityId: this.selectedFacility['FacilityId'], + }; + + this.bsModalRef = this.modalService.show(MembersListModalComponent, { initialState, class: 'modal-lg' }); + } + + getProjectLifetime(): void { + this.details_loaded = true; + } + + selectAllFilteredProjects(): void { + this.selectedEmailProjects = []; + + // get all the applications + this.applictions$.pipe(take(1)).subscribe(applications => { + // set the selected state of all projects to true + applications.forEach(application => { + application.is_project_selected = true; + this.toggleSelectedEmailApplication(application, application.is_project_selected); }); - } - - getProjectsByMemberElixirId(): void { - // tslint:disable-next-line:max-line-length - this.userElixirIdFilter = this.userElixirIdFilter.trim(); - if (this.userElixirIdFilter && this.userElixirIdFilter.includes('@elixir-europe.org')) { - this.projectsLoaded = false; - - this.facilityService - .getFacilityGroupsByMemberElixirId(this.selectedFacility['FacilityId'], this.userElixirIdFilter, this.userElixirSearchPI, this.userElixirSearchAdmin, this.userElixirSearchMember) - .subscribe((applications: Application[]): void => { - this.projects = applications; - for (const group of applications) { - if (group.project_application_lifetime > 0) { - group.lifetime_reached = this.lifeTimeReached(group.lifetime_days, group.DaysRunning); - } + }); + } + + unselectAll(): void { + this.sortProjectService.applications.forEach((pr: Application) => { + pr.is_project_selected = false; + this.toggleSelectedEmailApplication(pr, pr.is_project_selected); + }); + // this.selectedEmailProjects = []; // clear the selectedEmailProjects list + } + + unselectAllFilteredProjects(): void { + // get all the applications + this.applictions$.pipe(take(1)).subscribe(applications => { + // set the selected state of all projects to false + applications.forEach(application => { + application.is_project_selected = false; + this.toggleSelectedEmailApplication(application, application.is_project_selected); + }); + }); + } - } - this.sortProjectService.applications = this.projects; - this.applictions$ = this.sortProjectService.applications$; - this.total$ = this.sortProjectService.total$; - this.projectsLoaded = true; + toggleSelectedEmailApplication(application: Application, isChecked: boolean): void { + const index = this.selectedEmailProjects.indexOf(application); - }); - } else { - this.sortProjectService.applications = this.projectsCopy; - this.applictions$ = this.sortProjectService.applications$; - this.total$ = this.sortProjectService.total$; - this.projectsLoaded = true; + if (isChecked) { + // checkbox was checked + if (index === -1) { + // application is not in the list, so add it + this.selectedEmailProjects.push(application); } + } else { + // checkbox was unchecked + // application is in the list, so remove it + this.selectedEmailProjects.splice(index, 1); } - - /** - * Gets projects and sets email subject prefix when selected facility changes. - */ - onChangeSelectedFacility(): void { - this.isLoaded = false; - this.getFacilityProjects(this.selectedFacility['FacilityId']); - this.emailSubject = `[${this.selectedFacility['Facility']}]`; + } + + /** + * Returns the name of the project with the id of the selectedProjectType + */ + getProjectNameBySelectedProjectTypeAsId(): string { + const id: string = this.selectedProjectType; + if (!id) { + return 'NOT_FOUND'; } - - showMembersModal(application: Application): void { - const initialState = { - projectId: application.project_application_perun_id, - projectName: application.project_application_shortname, - facilityId: this.selectedFacility['FacilityId'], - }; - - this.bsModalRef = this.modalService.show(MembersListModalComponent, { initialState, class: 'modal-lg' }); + const project: Application = this.projects.find( + (element: Application): boolean => element.project_application_perun_id.toString() === id.toString(), + ); + if (project) { + return project.project_application_shortname; } - getProjectLifetime(): void { - this.details_loaded = true; - } + return 'NOT_FOUND'; + } - selectAllFilteredProjects(): void { - this.selectedEmailProjects = []; + getFacilityProjects(facility: string): void { + this.projects = []; + this.projectsLoaded = false; - // get all the applications - this.applictions$.pipe(take(1)).subscribe(applications => { - // set the selected state of all projects to true - applications.forEach(application => { - application.is_project_selected = true; - this.toggleSelectedEmailApplication(application, application.is_project_selected); - }); - }); - } + // tslint:disable-next-line:max-line-length + this.facilityService + .getFacilityAllowedGroupsWithDetailsAndSpecificStatus(facility, this.STATUS_APPROVED) + .subscribe((applications: Application[]): void => { + for (const group of applications) { + if (group.project_application_lifetime > 0) { + group.lifetime_reached = this.lifeTimeReached(group.lifetime_days, group.DaysRunning); + } + this.projects.push(group); + } + this.projectsCopy = this.projects; + this.sortProjectService.applications = this.projects; + this.applictions$ = this.sortProjectService.applications$; + this.total$ = this.sortProjectService.total$; + this.projectsLoaded = true; - unselectAll(): void { - this.sortProjectService.applications.forEach((pr: Application) => { - pr.is_project_selected = false; - this.toggleSelectedEmailApplication(pr, pr.is_project_selected); + this.isLoaded = true; }); - // this.selectedEmailProjects = []; // clear the selectedEmailProjects list - } - - unselectAllFilteredProjects(): void { - // get all the applications - this.applictions$.pipe(take(1)).subscribe(applications => { - // set the selected state of all projects to false - applications.forEach(application => { - application.is_project_selected = false; - this.toggleSelectedEmailApplication(application, application.is_project_selected); - }); + this.facilityService.getAllMembersOfFacility(facility, this.STATUS_APPROVED).subscribe( + (result: any[]): void => { + this.membersLoaded = true; + this.allFacilityMembers = result; + }, + (error: any): void => { + console.log(error); + this.membersLoaded = false; + }, + ); + } + + /** + * Adds or deletes tags from the list of tags to add to the news when the corresponding checkbox gets clicked. + * + * @param tag the tag which gets added/deleted. + */ + + /** + * Sends an email to users and also posts it as a news in WordPress via newsManager if selected. + * + * @param facility the facility of the users which shall be informed + * @param subject the subject as a string + * @param message the message as a string + * @param reply the reply-address + * @param send boolean if it should be sent to WordPress + * @param alternative_news_text the news text for WordPress, in case it shall be different from the original text + * @param selectedMember the specific member the mail is sent to in case one specific member is chosen + */ + sendMailToFacility( + facility: string, + subject: string, + message: string, + reply?: string, + send?: any, + alternative_news_text?: string, + ): void { + this.emailStatus = 0; + if (this.selectedProjectType === 'USER') { + const tempMailList: string[] = []; + // tslint:disable-next-line:no-for-each-push + this.selectedMember.forEach((member: object): void => { + tempMailList.push(member['email']); }); + this.selectedProjectType = tempMailList.join(','); } - - toggleSelectedEmailApplication(application: Application, isChecked: boolean): void { - const index = this.selectedEmailProjects.indexOf(application); - - if (isChecked) { - // checkbox was checked - if (index === -1) { - // application is not in the list, so add it - this.selectedEmailProjects.push(application); - } - } else { - // checkbox was unchecked - // application is in the list, so remove it - this.selectedEmailProjects.splice(index, 1); - } + if (reply) { + reply = reply.trim(); } - /** - * Returns the name of the project with the id of the selectedProjectType - */ - getProjectNameBySelectedProjectTypeAsId(): string { - const id: string = this.selectedProjectType; - if (!id) { - return 'NOT_FOUND'; - } - const project: Application = this.projects.find( - (element: Application): boolean => element.project_application_perun_id.toString() === id.toString(), - ); - if (project) { - return project.project_application_shortname; - } - - return 'NOT_FOUND'; - } - - getFacilityProjects(facility: string): void { - this.projects = []; - this.projectsLoaded = false; - - // tslint:disable-next-line:max-line-length - this.facilityService - .getFacilityAllowedGroupsWithDetailsAndSpecificStatus(facility, this.STATUS_APPROVED) - .subscribe((applications: Application[]): void => { - for (const group of applications) { - if (group.project_application_lifetime > 0) { - group.lifetime_reached = this.lifeTimeReached(group.lifetime_days, group.DaysRunning); - } - this.projects.push(group); + this.facilityService + .sendMailToFacility( + facility, + encodeURIComponent(subject), + encodeURIComponent(message), + this.selectedProjectType, + encodeURIComponent(reply), + send, + encodeURIComponent(alternative_news_text), + this.news_tags.join(), + ) + .subscribe( + (result: any): void => { + if (result.status === 201) { + this.emailStatus = 1; + } else { + this.emailStatus = 2; } - this.projectsCopy = this.projects; - this.sortProjectService.applications = this.projects; - this.applictions$ = this.sortProjectService.applications$; - this.total$ = this.sortProjectService.total$; - this.projectsLoaded = true; - - this.isLoaded = true; - }); - this.facilityService.getAllMembersOfFacility(facility, this.STATUS_APPROVED).subscribe( - (result: any[]): void => { - this.membersLoaded = true; - this.allFacilityMembers = result; }, - (error: any): void => { - console.log(error); - this.membersLoaded = false; + (): void => { + this.emailStatus = 2; + }, + (): void => { + this.filteredMembers = []; + this.selectedProjectType = 'ALL'; + this.emailReply = ''; + this.selectedMember = []; + this.memberFilter = ''; }, ); - } + } - /** - * Adds or deletes tags from the list of tags to add to the news when the corresponding checkbox gets clicked. - * - * @param tag the tag which gets added/deleted. - */ - - /** - * Sends an email to users and also posts it as a news in WordPress via newsManager if selected. - * - * @param facility the facility of the users which shall be informed - * @param subject the subject as a string - * @param message the message as a string - * @param reply the reply-address - * @param send boolean if it should be sent to WordPress - * @param alternative_news_text the news text for WordPress, in case it shall be different from the original text - * @param selectedMember the specific member the mail is sent to in case one specific member is chosen - */ - sendMailToFacility( - facility: string, - subject: string, - message: string, - reply?: string, - send?: any, - alternative_news_text?: string, - ): void { - this.emailStatus = 0; - if (this.selectedProjectType === 'USER') { - const tempMailList: string[] = []; - // tslint:disable-next-line:no-for-each-push - this.selectedMember.forEach((member: object): void => { - tempMailList.push(member['email']); - }); - this.selectedProjectType = tempMailList.join(','); - } - if (reply) { - reply = reply.trim(); - } + /** + * Sets the member selected in the mail modal as the member to send the mail to. + * + * @param member the selected member + */ - this.facilityService - .sendMailToFacility( - facility, - encodeURIComponent(subject), - encodeURIComponent(message), - this.selectedProjectType, - encodeURIComponent(reply), - send, - encodeURIComponent(alternative_news_text), - this.news_tags.join(), - ) - .subscribe( - (result: any): void => { - if (result.status === 201) { - this.emailStatus = 1; - } else { - this.emailStatus = 2; - } - }, - (): void => { - this.emailStatus = 2; - }, - (): void => { - this.filteredMembers = []; - this.selectedProjectType = 'ALL'; - this.emailReply = ''; - this.selectedMember = []; - this.memberFilter = ''; - }, - ); + setSelectedUserForMail(member: object): void { + if (!this.selectedMember.includes(member)) { + this.selectedMember.push(member); } + } - /** - * Sets the member selected in the mail modal as the member to send the mail to. - * - * @param member the selected member - */ - - setSelectedUserForMail(member: object): void { - if (!this.selectedMember.includes(member)) { - this.selectedMember.push(member); - } + removeSelectedUserForMail(member: object): void { + const index: number = this.selectedMember.indexOf(member); + if (index > -1) { + this.selectedMember.splice(index, 1); } - - removeSelectedUserForMail(member: object): void { - const index: number = this.selectedMember.indexOf(member); - if (index > -1) { - this.selectedMember.splice(index, 1); + } + + getMembersOfTheProject(projectid: number, projectname: string): void { + this.facilityService + .getFacilityGroupRichMembers(projectid, this.selectedFacility['FacilityId']) + .subscribe((members: ProjectMember[]): void => { + this.usersModalProjectID = projectid; + this.usersModalProjectName = projectname; + this.usersModalProjectMembers = members; + }); + } + + public resetEmailModal(): void { + this.selectedProjectType = 'ALL'; + this.emailSubject = `[${this.selectedFacility['Facility']}]`; + this.emailText = null; + this.emailReply = null; + this.emailStatus = 0; + this.sendNews = true; + this.alternative_emailText = ''; + this.news_tags = []; + this.selectedMember = []; + } + + getFacilitySupportMails(): void { + this.facilityService.getSupportMails(this.selectedFacility['FacilityId']).subscribe((result: any) => { + this.facilitySupportMails = result['body']; + if (this.facilitySupportMails === '' || this.facilitySupportMails === null) { + this.facilitySupportMails = 'example@mail1.com, example@mail2.com'; } - } + }); + } - getMembersOfTheProject(projectid: number, projectname: string): void { - this.facilityService - .getFacilityGroupRichMembers(projectid, this.selectedFacility['FacilityId']) - .subscribe((members: ProjectMember[]): void => { - this.usersModalProjectID = projectid; - this.usersModalProjectName = projectname; - this.usersModalProjectMembers = members; - }); - } - - public resetEmailModal(): void { - this.selectedProjectType = 'ALL'; - this.emailSubject = `[${this.selectedFacility['Facility']}]`; - this.emailText = null; - this.emailReply = null; - this.emailStatus = 0; - this.sendNews = true; - this.alternative_emailText = ''; - this.news_tags = []; - this.selectedMember = []; - } + openProjectMailsModal(csvFile: File = null, csvTemplate: CsvMailTemplateModel = null): void { + let initialState = {}; - getFacilitySupportMails(): void { - this.facilityService.getSupportMails(this.selectedFacility['FacilityId']).subscribe((result: any) => { - this.facilitySupportMails = result['body']; - if (this.facilitySupportMails === '' || this.facilitySupportMails === null) { - this.facilitySupportMails = 'example@mail1.com, example@mail2.com'; - } - }); + if (csvFile) { + initialState = { + selectedProjects: csvTemplate.valid_projects, + csvFile, + csvMailTemplate: csvTemplate, + }; + } else { + initialState = { selectedProjects: this.selectedEmailProjects }; } + this.bsModalRef = this.modalService.show(ProjectEmailModalComponent, { initialState, class: 'modal-lg' }); + this.bsModalRef.content.event.subscribe((sent_successfully: boolean) => { + if (sent_successfully) { + const initialStateNotification = { + notificationModalTitle: 'Success', + notificationModalType: 'success', + notificationModalMessage: 'Mails were successfully sent', + }; - openProjectMailsModal(csvFile: File = null, csvTemplate: CsvMailTemplateModel = null): void { - let initialState = {}; - - if (csvFile) { - initialState = { - selectedProjects: csvTemplate.valid_projects, - csvFile, - csvMailTemplate: csvTemplate, + this.modalService.show(NotificationModalComponent, { initialState: initialStateNotification }); + } else { + const initialStateNotification = { + notificationModalTitle: 'Failed', + notificationModalType: 'danger', + notificationModalMessage: 'Failed to send mails!', }; + this.modalService.show(NotificationModalComponent, { initialState: initialStateNotification }); + } + }); + } + + setFacilitySupportMails(supportMails: string): void { + const facilityId = this.selectedFacility['FacilityId']; + this.facilityService.setSupportMails(facilityId, supportMails).subscribe((result: any): void => { + if (result.ok) { + this.updateNotificationModal( + 'Facility support mails changed', + 'You successfully changed the facility support mails.', + true, + 'success', + ); } else { - initialState = { selectedProjects: this.selectedEmailProjects }; + this.updateNotificationModal( + "Couldn't change facility support mails", + 'An error occurred while trying to change the facility support mails.', + true, + 'danger', + ); } - this.bsModalRef = this.modalService.show(ProjectEmailModalComponent, { initialState, class: 'modal-lg' }); - this.bsModalRef.content.event.subscribe((sent_successfully: boolean) => { - if (sent_successfully) { - const initialStateNotification = { - notificationModalTitle: 'Success', - notificationModalType: 'success', - notificationModalMessage: 'Mails were successfully sent', - }; - - this.modalService.show(NotificationModalComponent, { initialState: initialStateNotification }); - } else { - const initialStateNotification = { - notificationModalTitle: 'Failed', - notificationModalType: 'danger', - notificationModalMessage: 'Failed to send mails!', - }; - this.modalService.show(NotificationModalComponent, { initialState: initialStateNotification }); - } - }); - } - - setFacilitySupportMails(supportMails: string): void { - const facilityId = this.selectedFacility['FacilityId']; - this.facilityService.setSupportMails(facilityId, supportMails).subscribe((result: any): void => { - if (result.ok) { - this.updateNotificationModal( - 'Facility support mails changed', - 'You successfully changed the facility support mails.', - true, - 'success', - ); - } else { - this.updateNotificationModal( - 'Couldn\'t change facility support mails', - 'An error occurred while trying to change the facility support mails.', - true, - 'danger', - ); - } - }); - } + }); + } - toggleSupportMailEditing(): void { - this.supportMailEditing = !this.supportMailEditing; - } + toggleSupportMailEditing(): void { + this.supportMailEditing = !this.supportMailEditing; + } } diff --git a/src/app/vo_manager/VoManager.module.ts b/src/app/vo_manager/VoManager.module.ts index ec169b77fa..36123f5286 100644 --- a/src/app/vo_manager/VoManager.module.ts +++ b/src/app/vo_manager/VoManager.module.ts @@ -6,7 +6,7 @@ import { CommonModule } from '@angular/common'; import { ModalModule } from 'ngx-bootstrap/modal'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NgbPaginationModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap'; -import { BadgeComponent } from '@coreui/angular'; +import { BadgeComponent, ButtonDirective, InputGroupComponent } from '@coreui/angular'; import { VoManagerRoutingModule } from './VoManager-routing.module'; import { VoOverviewComponent } from './VoOverviewComponent'; import { VoGuardService } from './vo-guard.service'; @@ -43,6 +43,8 @@ import { SharedModuleModule } from '../shared/shared_modules/shared-module.modul TimepickerComponent, SharedModuleModule, BadgeComponent, + InputGroupComponent, + ButtonDirective, ], declarations: [ VoOverviewComponent, diff --git a/src/app/vo_manager/VoOverviewComponent.ts b/src/app/vo_manager/VoOverviewComponent.ts index b3d73c0a50..d7ee2544a3 100644 --- a/src/app/vo_manager/VoOverviewComponent.ts +++ b/src/app/vo_manager/VoOverviewComponent.ts @@ -41,653 +41,669 @@ export class VoOverviewComponent extends AbstractBaseClass implements OnInit, On public emailText: string; public emailStatus: number = 0; - @ViewChild('notificationModal') notificationModal: ModalDirective; - public emailHeader: string; - public emailVerify: string; - public emailType: number; - public emailAdminsOnly: boolean = false; - public expiredTemplated: boolean = false; - - public removalDate: Date = new Date(); - public selectedProject: Application; - selectedEmailProjects: Application[] = []; - computecenters: ComputecenterComponent[] = []; - bsModalRef: BsModalRef; - subscription: Subscription = new Subscription(); - protected readonly ConfirmationActions = ConfirmationActions; - userElixirSearchPI: boolean = true; - userElixirSearchAdmin: boolean = true; - userElixirSearchMember: boolean = true; - projectsLoaded: boolean = false; - show_openstack_projects: boolean = true; - show_simple_vm_projects: boolean = true; - show_simple_vm: boolean = true; - show_openstack: boolean = true; - - tsvTaskRunning: boolean = false; - numberOfTsvs: number = 0; - checkTSVTimer: ReturnType; - checkTSVTimeout: number = 10000; - projectsCopy: Application[] = []; - - selectedProjectType: string = 'ALL'; - selectedFacility: string | number = 'ALL'; - userElixirIdFilter: string; - - public newsletterSubscriptionCounter: number; - member_id: number; - projects: Application[] = []; - - // modal variables for User list - public usersModalProjectMembers: ProjectMember[] = []; - public usersModalProjectID: number; - public usersModalProjectName: string; - public managerFacilities: [string, number][]; - - projectMailTemplates: string[] = []; - @ViewChildren(NgbdSortableHeaderDirective) headers: QueryList; - - applictions$: Observable; - total$: Observable; - - // public selectedFacility: [string, number]; - - constructor( - private fullLayout: FullLayoutComponent, - private sanitizer: DomSanitizer, - private voService: VoService, - private groupservice: GroupService, - private facilityService: FacilityService, - public sortProjectService: ProjectSortService, - private modalService: BsModalService, - private emailService: EmailService, - ) { - super(); - } - - ngOnInit(): void { - this.getVoProjects(); - this.getComputeCenters(); - this.voService.getNewsletterSubscriptionCounter().subscribe((result: IResponseTemplate): void => { - this.newsletterSubscriptionCounter = result.value as number; - }); - this.getTSVInformation(); - } - - ngOnDestroy() { - this.subscription.unsubscribe(); - } - - onCsvFileSelected(event): void { - const inputElement = event.target as HTMLInputElement; - if (inputElement.files && inputElement.files.length > 0) { - this.emailService.sendCsvTemplate(inputElement.files[0]).subscribe( - (csvTemplate: CsvMailTemplateModel) => { - this.openProjectMailsModal(inputElement.files[0], csvTemplate); - }, - (error: CsvMailTemplateModel) => { - console.log(error['error']); - this.openProjectMailsModal(inputElement.files[0], error['error']); - }, - ); - } + @ViewChild('notificationModal') notificationModal: ModalDirective; + public emailHeader: string; + public emailVerify: string; + public emailType: number; + public emailAdminsOnly: boolean = false; + public expiredTemplated: boolean = false; + + public removalDate: Date = new Date(); + public selectedProject: Application; + selectedEmailProjects: Application[] = []; + computecenters: ComputecenterComponent[] = []; + bsModalRef: BsModalRef; + subscription: Subscription = new Subscription(); + protected readonly ConfirmationActions = ConfirmationActions; + userElixirSearchPI: boolean = true; + userElixirSearchAdmin: boolean = true; + userElixirSearchMember: boolean = true; + projectsLoaded: boolean = false; + show_openstack_projects: boolean = true; + show_simple_vm_projects: boolean = true; + show_simple_vm: boolean = true; + show_openstack: boolean = true; + + validElixirIdFilter: boolean = false; + tsvTaskRunning: boolean = false; + numberOfTsvs: number = 0; + checkTSVTimer: ReturnType; + checkTSVTimeout: number = 10000; + projectsCopy: Application[] = []; + + selectedProjectType: string = 'ALL'; + selectedFacility: string | number = 'ALL'; + userElixirIdFilter: string; + + public newsletterSubscriptionCounter: number; + member_id: number; + projects: Application[] = []; + + // modal variables for User list + public usersModalProjectMembers: ProjectMember[] = []; + public usersModalProjectID: number; + public usersModalProjectName: string; + public managerFacilities: [string, number][]; + + projectMailTemplates: string[] = []; + @ViewChildren(NgbdSortableHeaderDirective) headers: QueryList; + + applictions$: Observable; + total$: Observable; + + // public selectedFacility: [string, number]; + + constructor( + private fullLayout: FullLayoutComponent, + private sanitizer: DomSanitizer, + private voService: VoService, + private groupservice: GroupService, + private facilityService: FacilityService, + public sortProjectService: ProjectSortService, + private modalService: BsModalService, + private emailService: EmailService, + ) { + super(); + } + + ngOnInit(): void { + this.getVoProjects(); + this.getComputeCenters(); + this.voService.getNewsletterSubscriptionCounter().subscribe((result: IResponseTemplate): void => { + this.newsletterSubscriptionCounter = result.value as number; + }); + this.getTSVInformation(); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + + onCsvFileSelected(event): void { + const inputElement = event.target as HTMLInputElement; + if (inputElement.files && inputElement.files.length > 0) { + this.emailService.sendCsvTemplate(inputElement.files[0]).subscribe( + (csvTemplate: CsvMailTemplateModel) => { + this.openProjectMailsModal(inputElement.files[0], csvTemplate); + }, + (error: CsvMailTemplateModel) => { + console.log(error['error']); + this.openProjectMailsModal(inputElement.files[0], error['error']); + }, + ); } + } - getTSVInformation(timeout: number = this.checkTSVTimeout): void { - this.stopCheckTSVTimer(); - this.subscription.add( - this.voService.getTsvInformation().subscribe( - (result: any): void => { - this.tsvTaskRunning = result[0]; - this.numberOfTsvs = result[1]; - if (result[0] !== true) { - this.stopCheckTSVTimer(); - } else { - this.checkTSVTimer = setTimeout((): void => { - this.getTSVInformation(); - }, timeout); - } - }, - () => { - this.tsvTaskRunning = true; - this.numberOfTsvs = 0; + getTSVInformation(timeout: number = this.checkTSVTimeout): void { + this.stopCheckTSVTimer(); + this.subscription.add( + this.voService.getTsvInformation().subscribe( + (result: any): void => { + this.tsvTaskRunning = result[0]; + this.numberOfTsvs = result[1]; + if (result[0] !== true) { + this.stopCheckTSVTimer(); + } else { this.checkTSVTimer = setTimeout((): void => { this.getTSVInformation(); }, timeout); - }, - ), - ); - } - - stopCheckTSVTimer(): void { - if (this.checkTSVTimer) { - clearTimeout(this.checkTSVTimer); - } - } - - selectAllFilteredProjects(): void { - this.selectedEmailProjects = []; - - // get all the applications - this.applictions$.pipe(take(1)).subscribe(applications => { - // set the selected state of all projects to true - applications.forEach(application => { - application.is_project_selected = true; - this.toggleSelectedEmailApplication(application, application.is_project_selected); - }); - }); - } - - showConfirmationModal(application: Application, action: ConfirmationActions): void { - const initialState = { application, action }; - console.log(initialState); - - this.bsModalRef = this.modalService.show(ConfirmationModalComponent, { initialState, class: 'modal-lg' }); - this.subscribeToBsModalRef(); - } - - showMembersModal(application: Application): void { - const initialState = { - projectId: application.project_application_perun_id, - projectName: application.project_application_shortname, - }; - - this.bsModalRef = this.modalService.show(MembersListModalComponent, { initialState, class: 'modal-lg' }); - } - - subscribeToBsModalRef(): void { - this.subscription.add( - this.bsModalRef.content.event.subscribe((result: any) => { - let action = null; - if ('action' in result) { - action = result['action']; } + }, + () => { + this.tsvTaskRunning = true; + this.numberOfTsvs = 0; + this.checkTSVTimer = setTimeout((): void => { + this.getTSVInformation(); + }, timeout); + }, + ), + ); + } - if (ConfirmationActions.ENABLE_APPLICATION === action) { - this.enableProject(result['application']); - } - if (ConfirmationActions.DISABLE_APPLICATION === action) { - this.disableProject(result['application']); - } - }), - ); + stopCheckTSVTimer(): void { + if (this.checkTSVTimer) { + clearTimeout(this.checkTSVTimer); } + } - unselectAll(): void { - this.sortProjectService.applications.forEach((pr: Application) => { - pr.is_project_selected = false; - this.toggleSelectedEmailApplication(pr, pr.is_project_selected); - }); - // this.selectedEmailProjects = []; // clear the selectedEmailProjects list - } + selectAllFilteredProjects(): void { + this.selectedEmailProjects = []; - unselectAllFilteredProjects(): void { - // get all the applications - this.applictions$.pipe(take(1)).subscribe(applications => { - // set the selected state of all projects to false - applications.forEach(application => { - application.is_project_selected = false; - this.toggleSelectedEmailApplication(application, application.is_project_selected); - }); + // get all the applications + this.applictions$.pipe(take(1)).subscribe(applications => { + // set the selected state of all projects to true + applications.forEach(application => { + application.is_project_selected = true; + this.toggleSelectedEmailApplication(application, application.is_project_selected); }); - } - - toggleSelectedEmailApplication(application: Application, isChecked: boolean): void { - const index = this.selectedEmailProjects.indexOf(application); - - if (isChecked) { - // checkbox was checked - if (index === -1) { - // application is not in the list, so add it - this.selectedEmailProjects.push(application); + }); + } + + showConfirmationModal(application: Application, action: ConfirmationActions): void { + const initialState = { application, action }; + console.log(initialState); + + this.bsModalRef = this.modalService.show(ConfirmationModalComponent, { initialState, class: 'modal-lg' }); + this.subscribeToBsModalRef(); + } + + showMembersModal(application: Application): void { + const initialState = { + projectId: application.project_application_perun_id, + projectName: application.project_application_shortname, + }; + + this.bsModalRef = this.modalService.show(MembersListModalComponent, { initialState, class: 'modal-lg' }); + } + + subscribeToBsModalRef(): void { + this.subscription.add( + this.bsModalRef.content.event.subscribe((result: any) => { + let action = null; + if ('action' in result) { + action = result['action']; } - } else { - // checkbox was unchecked - // application is in the list, so remove it - this.selectedEmailProjects.splice(index, 1); - } - } - openProjectMailsModal(csvFile: File = null, csvTemplate: CsvMailTemplateModel = null): void { - let initialState = {}; - - if (csvFile) { - initialState = { - selectedProjects: csvTemplate.valid_projects, - csvFile, - csvMailTemplate: csvTemplate, - }; - } else { - initialState = { selectedProjects: this.selectedEmailProjects }; - } - - this.bsModalRef = this.modalService.show(ProjectEmailModalComponent, { initialState, class: 'modal-lg' }); - this.bsModalRef.content.event.subscribe((sent_successfully: boolean) => { - if (sent_successfully) { - this.updateNotificationModal('Success', 'Mails were successfully sent', true, 'success'); - } else { - this.updateNotificationModal('Failed', 'Failed to send mails!', true, 'danger'); + if (ConfirmationActions.ENABLE_APPLICATION === action) { + this.enableProject(result['application']); } - this.notificationModal.show(); - }); - } - - disableProject(project: Application): void { - this.voService.setDisabledProject(project.project_application_perun_id).subscribe((upd_app: Application) => { - const idx = this.projects.indexOf(project); - this.projects[idx] = upd_app; - this.sortProjectService.applications = this.projects; + if (ConfirmationActions.DISABLE_APPLICATION === action) { + this.disableProject(result['application']); + } + }), + ); + } + + unselectAll(): void { + this.sortProjectService.applications.forEach((pr: Application) => { + pr.is_project_selected = false; + this.toggleSelectedEmailApplication(pr, pr.is_project_selected); + }); + // this.selectedEmailProjects = []; // clear the selectedEmailProjects list + } + + unselectAllFilteredProjects(): void { + // get all the applications + this.applictions$.pipe(take(1)).subscribe(applications => { + // set the selected state of all projects to false + applications.forEach(application => { + application.is_project_selected = false; + this.toggleSelectedEmailApplication(application, application.is_project_selected); }); - } - - getProjectsByMemberElixirId(): void { - // tslint:disable-next-line:max-line-length - this.userElixirIdFilter = this.userElixirIdFilter.trim(); - if (this.userElixirIdFilter && this.userElixirIdFilter.includes('@elixir-europe.org')) { - this.projectsLoaded = false; - - this.voService - .getGroupsByMemberElixirId(this.userElixirIdFilter, this.userElixirSearchPI, this.userElixirSearchAdmin, this.userElixirSearchMember) - .subscribe((applications: Application[]): void => { - this.projects = applications; - for (const group of applications) { - if (group.project_application_lifetime > 0) { - group.lifetime_reached = this.lifeTimeReached(group.lifetime_days, group.DaysRunning); - } + }); + } - } - this.sortProjectService.applications = this.projects; - this.applictions$ = this.sortProjectService.applications$; - this.total$ = this.sortProjectService.total$; - this.projectsLoaded = true; + toggleSelectedEmailApplication(application: Application, isChecked: boolean): void { + const index = this.selectedEmailProjects.indexOf(application); - }); - } else { - this.sortProjectService.applications = this.projectsCopy; - this.applictions$ = this.sortProjectService.applications$; - this.total$ = this.sortProjectService.total$; - this.projectsLoaded = true; + if (isChecked) { + // checkbox was checked + if (index === -1) { + // application is not in the list, so add it + this.selectedEmailProjects.push(application); } + } else { + // checkbox was unchecked + // application is in the list, so remove it + this.selectedEmailProjects.splice(index, 1); } + } - enableProject(project: Application): void { - this.voService.unsetDisabledProject(project.project_application_perun_id).subscribe((upd_app: Application) => { - const idx = this.projects.indexOf(project); - this.projects[idx] = upd_app; - this.sortProjectService.applications = this.projects; - }); - } - - onSort({ column, direction }: SortEvent) { - // resetting other headers - this.headers.forEach(header => { - if (header.appSortable !== column) { - header.direction = ''; - } - }); - - this.sortProjectService.sortColumn = column; - this.sortProjectService.sortDirection = direction; - } - - getApplicationInfos(): void { - this.voService.getVoProjectResourcesTimeframes().subscribe(); + openProjectMailsModal(csvFile: File = null, csvTemplate: CsvMailTemplateModel = null): void { + let initialState = {}; - this.voService.getVoProjectCounter().subscribe(); - this.voService.getVoProjectDates().subscribe(); + if (csvFile) { + initialState = { + selectedProjects: csvTemplate.valid_projects, + csvFile, + csvMailTemplate: csvTemplate, + }; + } else { + initialState = { selectedProjects: this.selectedEmailProjects }; } - sendEmail(subject: string, message: string, reply?: string): void { - if (reply) { - reply = reply.trim(); - } - switch (this.emailType) { - case 0: { - this.sendMailToVo( - subject, - message, - this.selectedFacility.toString(), - this.selectedProjectType, - this.emailAdminsOnly, - this.expiredTemplated, - this.removalDate, - reply, - ); - break; - } - case 1: { - this.sendNewsletterToVo(subject, message, this.selectedProjectType, this.emailAdminsOnly, reply); - break; - } - default: + this.bsModalRef = this.modalService.show(ProjectEmailModalComponent, { initialState, class: 'modal-lg' }); + this.bsModalRef.content.event.subscribe((sent_successfully: boolean) => { + if (sent_successfully) { + this.updateNotificationModal('Success', 'Mails were successfully sent', true, 'success'); + } else { + this.updateNotificationModal('Failed', 'Failed to send mails!', true, 'danger'); } - } - - sendTestBug(): void { - this.voService.sendTestError().subscribe(); - } - - sendNewsletterToVo( - subject: string, - message: string, - selectedProjectType: string, - adminsOnly: boolean, - reply?: string, - ): void { - this.voService - .sendNewsletterToVo( - encodeURIComponent(subject), - encodeURIComponent(message), - selectedProjectType, - adminsOnly, - encodeURIComponent(reply), - ) - .subscribe((result: IResponseTemplate): void => { - if ((result.value as boolean) === true) { - this.emailStatus = 1; - } else { - this.emailStatus = 2; - } - }); - } + this.notificationModal.show(); + }); + } + + disableProject(project: Application): void { + this.voService.setDisabledProject(project.project_application_perun_id).subscribe((upd_app: Application) => { + const idx = this.projects.indexOf(project); + this.projects[idx] = upd_app; + this.sortProjectService.applications = this.projects; + }); + } + + checkValidElixirIdFilter(): void { + this.validElixirIdFilter = this.userElixirIdFilter && this.userElixirIdFilter.includes('@elixir-europe.org'); + if (!this.validElixirIdFilter) { + this.sortProjectService.applications = this.projectsCopy; + this.applictions$ = this.sortProjectService.applications$; + this.total$ = this.sortProjectService.total$; + this.projectsLoaded = true; + } + } + + getProjectsByMemberElixirId(): void { + // tslint:disable-next-line:max-line-length + this.userElixirIdFilter = this.userElixirIdFilter.trim(); + if (this.userElixirIdFilter && this.userElixirIdFilter.includes('@elixir-europe.org')) { + this.projectsLoaded = false; - sendMailToVo( - subject: string, - message: string, - facility: string, - type: string, - adminsOnly: boolean, - expiredTemplate: boolean, - removalDate: Date, - reply?: string, - ): void { this.voService - .sendMailToVo( - encodeURIComponent(subject), - encodeURIComponent(message), - facility, - type, - adminsOnly, - expiredTemplate, - removalDate, - encodeURIComponent(reply), + .getGroupsByMemberElixirId( + this.userElixirIdFilter, + this.userElixirSearchPI, + this.userElixirSearchAdmin, + this.userElixirSearchMember, ) - .subscribe((result: IResponseTemplate): void => { - if ((result.value as boolean) === true) { - this.emailStatus = 1; - } else { - this.emailStatus = 2; + .subscribe((applications: Application[]): void => { + this.projects = applications; + for (const group of applications) { + if (group.project_application_lifetime > 0) { + group.lifetime_reached = this.lifeTimeReached(group.lifetime_days, group.DaysRunning); + } } - this.selectedProjectType = 'ALL'; - this.selectedFacility = 'ALL'; + this.sortProjectService.applications = this.projects; + this.applictions$ = this.sortProjectService.applications$; + this.total$ = this.sortProjectService.total$; + this.projectsLoaded = true; }); - } - - dayChanged(date: { year: number; month: number; day: number }): void { - this.removalDate.setDate(date.day); - this.removalDate.setMonth(date.month - 1); - this.removalDate.setFullYear(date.year); - } - - setEmailType(type: number): void { - this.emailType = type; - switch (this.emailType) { - case 0: { - this.emailHeader = 'Send email to selected members of the VO'; - break; + } else { + this.sortProjectService.applications = this.projectsCopy; + this.applictions$ = this.sortProjectService.applications$; + this.total$ = this.sortProjectService.total$; + this.projectsLoaded = true; + } + } + + enableProject(project: Application): void { + this.voService.unsetDisabledProject(project.project_application_perun_id).subscribe((upd_app: Application) => { + const idx = this.projects.indexOf(project); + this.projects[idx] = upd_app; + this.sortProjectService.applications = this.projects; + }); + } + + onSort({ column, direction }: SortEvent) { + // resetting other headers + this.headers.forEach(header => { + if (header.appSortable !== column) { + header.direction = ''; + } + }); + + this.sortProjectService.sortColumn = column; + this.sortProjectService.sortDirection = direction; + } + + getApplicationInfos(): void { + this.voService.getVoProjectResourcesTimeframes().subscribe(); + + this.voService.getVoProjectCounter().subscribe(); + this.voService.getVoProjectDates().subscribe(); + } + + sendEmail(subject: string, message: string, reply?: string): void { + if (reply) { + reply = reply.trim(); + } + switch (this.emailType) { + case 0: { + this.sendMailToVo( + subject, + message, + this.selectedFacility.toString(), + this.selectedProjectType, + this.emailAdminsOnly, + this.expiredTemplated, + this.removalDate, + reply, + ); + break; + } + case 1: { + this.sendNewsletterToVo(subject, message, this.selectedProjectType, this.emailAdminsOnly, reply); + break; + } + default: + } + } + + sendTestBug(): void { + this.voService.sendTestError().subscribe(); + } + + sendNewsletterToVo( + subject: string, + message: string, + selectedProjectType: string, + adminsOnly: boolean, + reply?: string, + ): void { + this.voService + .sendNewsletterToVo( + encodeURIComponent(subject), + encodeURIComponent(message), + selectedProjectType, + adminsOnly, + encodeURIComponent(reply), + ) + .subscribe((result: IResponseTemplate): void => { + if ((result.value as boolean) === true) { + this.emailStatus = 1; + } else { + this.emailStatus = 2; } - case 1: { - this.emailHeader = 'Send newsletter to VO'; - break; + }); + } + + sendMailToVo( + subject: string, + message: string, + facility: string, + type: string, + adminsOnly: boolean, + expiredTemplate: boolean, + removalDate: Date, + reply?: string, + ): void { + this.voService + .sendMailToVo( + encodeURIComponent(subject), + encodeURIComponent(message), + facility, + type, + adminsOnly, + expiredTemplate, + removalDate, + encodeURIComponent(reply), + ) + .subscribe((result: IResponseTemplate): void => { + if ((result.value as boolean) === true) { + this.emailStatus = 1; + } else { + this.emailStatus = 2; } - default: + this.selectedProjectType = 'ALL'; + this.selectedFacility = 'ALL'; + }); + } + + dayChanged(date: { year: number; month: number; day: number }): void { + this.removalDate.setDate(date.day); + this.removalDate.setMonth(date.month - 1); + this.removalDate.setFullYear(date.year); + } + + setEmailType(type: number): void { + this.emailType = type; + switch (this.emailType) { + case 0: { + this.emailHeader = 'Send email to selected members of the VO'; + break; } - this.emailVerify = 'Are you sure you want to send this newsletter to all members of the de.NBI VO?'; + case 1: { + this.emailHeader = 'Send newsletter to VO'; + break; + } + default: } + this.emailVerify = 'Are you sure you want to send this newsletter to all members of the de.NBI VO?'; + } - getFacilityName(): string { - if (this.selectedFacility === 'ALL') { + getFacilityName(): string { + if (this.selectedFacility === 'ALL') { + return 'of the de.NBI VO'; + } else { + const temp_cc = this.computecenters.find(cc => cc.FacilityId === this.selectedFacility); + if (temp_cc === undefined) { return 'of the de.NBI VO'; } else { - const temp_cc = this.computecenters.find(cc => cc.FacilityId === this.selectedFacility); - if (temp_cc === undefined) { - return 'of the de.NBI VO'; - } else { - return `of the facility "${temp_cc.Name}"`; - } + return `of the facility "${temp_cc.Name}"`; } } - - getMailConfinementByProjectType(): string { - switch (this.selectedProjectType) { - case 'ALL_GM': - return 'of all active projects'; - case 'EXP': - return 'of all expired projects'; - case 'SVP': - return 'of all SimpleVM projects'; - case 'OVP': - return 'of all OpenStack projects'; - case 'WSH': - return 'of all Workshops'; - default: - return ''; - } - } - - adjustVerifyText(): void { - switch (this.emailType) { - case 0: { - this.emailVerify = `Are you sure you want to send this email to all ${ - this.emailAdminsOnly ? ' group administrators' : 'members' - } ${this.getMailConfinementByProjectType()} ${this.getFacilityName()} ?`; - break; - } - case 1: { - this.emailVerify = `Are you sure you want to send this newsletter to all members ${this.getMailConfinementByProjectType()} ${this.getFacilityName()} ?`; - break; - } - default: - this.emailVerify = 'Are you sure you want to send this?'; + } + + getMailConfinementByProjectType(): string { + switch (this.selectedProjectType) { + case 'ALL_GM': + return 'of all active projects'; + case 'EXP': + return 'of all expired projects'; + case 'SVP': + return 'of all SimpleVM projects'; + case 'OVP': + return 'of all OpenStack projects'; + case 'WSH': + return 'of all Workshops'; + default: + return ''; + } + } + + adjustVerifyText(): void { + switch (this.emailType) { + case 0: { + this.emailVerify = `Are you sure you want to send this email to all ${ + this.emailAdminsOnly ? ' group administrators' : 'members' + } ${this.getMailConfinementByProjectType()} ${this.getFacilityName()} ?`; + break; } - if (this.selectedProjectType !== 'EXP') { - this.expiredTemplated = false; + case 1: { + this.emailVerify = `Are you sure you want to send this newsletter to all members ${this.getMailConfinementByProjectType()} ${this.getFacilityName()} ?`; + break; } + default: + this.emailVerify = 'Are you sure you want to send this?'; } - - getVoProjects(): void { - this.projects = []; - this.voService.getAllGroupsWithDetails().subscribe((applications: Application[]): void => { - for (const application of applications) { - if (application.project_application_lifetime > 0) { - application.lifetime_reached = this.lifeTimeReached(application.lifetime_days, application.DaysRunning); - } - this.projects.push(application); - } - this.sortProjectService.applications = this.projects; - this.applictions$ = this.sortProjectService.applications$; - this.total$ = this.sortProjectService.total$; - this.projectsLoaded = true; - }); - } - - resetEmailModal(): void { - this.emailHeader = null; - this.emailSubject = null; - this.emailText = null; - this.emailType = null; - this.emailVerify = null; - this.emailReply = ''; - this.emailStatus = 0; - this.emailAdminsOnly = false; + if (this.selectedProjectType !== 'EXP') { + this.expiredTemplated = false; } + } - public resetNotificationModal(): void { - this.notificationModalTitle = 'Notification'; - this.notificationModalMessage = 'Please wait...'; - this.notificationModalIsClosable = false; - this.notificationModalType = 'info'; - } - - /** - * Get all computecenters. - */ - getComputeCenters(): void { - this.facilityService.getComputeCenters().subscribe((result: any): void => { - for (const cc of result) { - const compute_center: ComputecenterComponent = new ComputecenterComponent( - cc['compute_center_facility_id'], - cc['compute_center_name'], - cc['compute_center_login'], - cc['compute_center_support_mail'], - ); - this.computecenters.push(compute_center); + getVoProjects(): void { + this.projects = []; + this.voService.getAllGroupsWithDetails().subscribe((applications: Application[]): void => { + for (const application of applications) { + if (application.project_application_lifetime > 0) { + application.lifetime_reached = this.lifeTimeReached(application.lifetime_days, application.DaysRunning); } - }); - } - - /** - * Bugfix not scrollable site after closing modal - */ - removeModalOpen(): void { - document.body.classList.remove('modal-open'); - } - - public terminateProject(): void { - this.voService.terminateProject(this.selectedProject.project_application_perun_id).subscribe( - (): void => { - const indexAll: number = this.projects.indexOf(this.selectedProject, 0); - if (!this.selectedProject.project_application_openstack_project) { - this.projects.splice(indexAll, 1); - this.sortProjectService.applications = this.projects; - } else { - this.getProjectStatus(this.projects[indexAll]); - } - this.fullLayout.getGroupsEnumeration(); - if (this.selectedProject.project_application_openstack_project) { - this.updateNotificationModal( - 'Success', - 'The request to terminate the project was forwarded to the facility manager.', - true, - 'success', - ); - } else { - this.updateNotificationModal('Success', 'The project was terminated.', true, 'success'); - } - }, - (error: any): void => { - if (error['status'] === 409) { - this.updateNotificationModal( - 'Failed', - `The project could not be terminated. Reason: ${error['error']['reason']} for ${error['error']['openstackid']}`, - true, - 'danger', - ); - } else { - this.updateNotificationModal('Failed', 'The project could not be terminated.', true, 'danger'); - } - }, - ); - } - - getProjectStatus(project: Application): void { - this.voService.getProjectStatus(project.project_application_perun_id).subscribe((res: any): void => { - project.project_application_statuses = res['status']; - }); - } - - suspendProject(project: Application): void { - this.voService.removeResourceFromGroup(project.project_application_perun_id).subscribe( - (): void => { - this.updateNotificationModal('Success', 'The project got suspended successfully', true, 'success'); - this.getProjectStatus(project); - project.project_application_compute_center = null; - }, - (): void => { - this.updateNotificationModal('Failed', 'The status change was not successful.', true, 'danger'); - }, - ); - } - - resumeProject(project: Application): void { - this.voService.resumeProject(project.project_application_perun_id).subscribe( - (): void => { - this.updateNotificationModal('Success', 'The project got resumed successfully', true, 'success'); - this.getProjectStatus(project); - }, - (): void => { - this.updateNotificationModal('Failed', 'The status change was not successful.', true, 'danger'); - }, - ); - } - - declineTermination(project: Application): void { - this.voService.declineTermination(project.project_application_perun_id).subscribe( - (): void => { - this.updateNotificationModal('Success', 'The termination was successfully declined', true, 'success'); - const indexAll: number = this.projects.indexOf(project, 0); + this.projects.push(application); + } + this.projectsCopy = this.projects; + + this.sortProjectService.applications = this.projects; + this.applictions$ = this.sortProjectService.applications$; + this.total$ = this.sortProjectService.total$; + this.projectsLoaded = true; + }); + } + + resetEmailModal(): void { + this.emailHeader = null; + this.emailSubject = null; + this.emailText = null; + this.emailType = null; + this.emailVerify = null; + this.emailReply = ''; + this.emailStatus = 0; + this.emailAdminsOnly = false; + } + + public resetNotificationModal(): void { + this.notificationModalTitle = 'Notification'; + this.notificationModalMessage = 'Please wait...'; + this.notificationModalIsClosable = false; + this.notificationModalType = 'info'; + } + + /** + * Get all computecenters. + */ + getComputeCenters(): void { + this.facilityService.getComputeCenters().subscribe((result: any): void => { + for (const cc of result) { + const compute_center: ComputecenterComponent = new ComputecenterComponent( + cc['compute_center_facility_id'], + cc['compute_center_name'], + cc['compute_center_login'], + cc['compute_center_support_mail'], + ); + this.computecenters.push(compute_center); + } + }); + } + + /** + * Bugfix not scrollable site after closing modal + */ + removeModalOpen(): void { + document.body.classList.remove('modal-open'); + } + + public terminateProject(): void { + this.voService.terminateProject(this.selectedProject.project_application_perun_id).subscribe( + (): void => { + const indexAll: number = this.projects.indexOf(this.selectedProject, 0); + if (!this.selectedProject.project_application_openstack_project) { + this.projects.splice(indexAll, 1); + this.sortProjectService.applications = this.projects; + } else { this.getProjectStatus(this.projects[indexAll]); - }, - (): void => { - this.updateNotificationModal('Failed', 'The status change was not successful.', true, 'danger'); - }, - ); - } - - setProtected(project: Application, set: boolean): void { - this.voService.setProtected(project.project_application_perun_id, set).subscribe( - (result: any): void => { + } + this.fullLayout.getGroupsEnumeration(); + if (this.selectedProject.project_application_openstack_project) { this.updateNotificationModal( 'Success', - result['result'] === 'set' - ? 'The project was successfully set as protected.' - : 'The status "Protected" was removed successfully', + 'The request to terminate the project was forwarded to the facility manager.', true, 'success', ); - const indexAll: number = this.projects.indexOf(project, 0); - this.getProjectStatus(this.projects[indexAll]); - }, - (error: any): void => { - if (error['status'] === 500) { - this.updateNotificationModal('Failed', 'The status change was not successful.', true, 'danger'); - } - }, - ); - } - - getMembersOfTheProject(projectid: number, projectname: string): void { - this.voService.getVoGroupRichMembers(projectid).subscribe((members: ProjectMember[]): void => { - this.usersModalProjectID = projectid; - this.usersModalProjectName = projectname; - this.usersModalProjectMembers = members; - }); - } - - showMembersOfTheProject(projectid: number, projectname: string): void { - this.getMembersOfTheProject(projectid, projectname); - } - - initiateTsvExport(): void { - this.tsvTaskRunning = true; - this.voService.getAllProjectsForTsvExport().subscribe((): void => { - this.getTSVInformation(); - }); - } - - downloadCurrentTSV(): void { - this.voService.downloadProjectsTsv().subscribe( - (result): void => { - const blobn = new Blob([result], { - type: 'text/tsv', - }); + } else { + this.updateNotificationModal('Success', 'The project was terminated.', true, 'success'); + } + }, + (error: any): void => { + if (error['status'] === 409) { + this.updateNotificationModal( + 'Failed', + `The project could not be terminated. Reason: ${error['error']['reason']} for ${error['error']['openstackid']}`, + true, + 'danger', + ); + } else { + this.updateNotificationModal('Failed', 'The project could not be terminated.', true, 'danger'); + } + }, + ); + } + + getProjectStatus(project: Application): void { + this.voService.getProjectStatus(project.project_application_perun_id).subscribe((res: any): void => { + project.project_application_statuses = res['status']; + }); + } + + suspendProject(project: Application): void { + this.voService.removeResourceFromGroup(project.project_application_perun_id).subscribe( + (): void => { + this.updateNotificationModal('Success', 'The project got suspended successfully', true, 'success'); + this.getProjectStatus(project); + project.project_application_compute_center = null; + }, + (): void => { + this.updateNotificationModal('Failed', 'The status change was not successful.', true, 'danger'); + }, + ); + } + + resumeProject(project: Application): void { + this.voService.resumeProject(project.project_application_perun_id).subscribe( + (): void => { + this.updateNotificationModal('Success', 'The project got resumed successfully', true, 'success'); + this.getProjectStatus(project); + }, + (): void => { + this.updateNotificationModal('Failed', 'The status change was not successful.', true, 'danger'); + }, + ); + } + + declineTermination(project: Application): void { + this.voService.declineTermination(project.project_application_perun_id).subscribe( + (): void => { + this.updateNotificationModal('Success', 'The termination was successfully declined', true, 'success'); + const indexAll: number = this.projects.indexOf(project, 0); + this.getProjectStatus(this.projects[indexAll]); + }, + (): void => { + this.updateNotificationModal('Failed', 'The status change was not successful.', true, 'danger'); + }, + ); + } + + setProtected(project: Application, set: boolean): void { + this.voService.setProtected(project.project_application_perun_id, set).subscribe( + (result: any): void => { + this.updateNotificationModal( + 'Success', + result['result'] === 'set' + ? 'The project was successfully set as protected.' + : 'The status "Protected" was removed successfully', + true, + 'success', + ); + const indexAll: number = this.projects.indexOf(project, 0); + this.getProjectStatus(this.projects[indexAll]); + }, + (error: any): void => { + if (error['status'] === 500) { + this.updateNotificationModal('Failed', 'The status change was not successful.', true, 'danger'); + } + }, + ); + } + + getMembersOfTheProject(projectid: number, projectname: string): void { + this.voService.getVoGroupRichMembers(projectid).subscribe((members: ProjectMember[]): void => { + this.usersModalProjectID = projectid; + this.usersModalProjectName = projectname; + this.usersModalProjectMembers = members; + }); + } + + showMembersOfTheProject(projectid: number, projectname: string): void { + this.getMembersOfTheProject(projectid, projectname); + } + + initiateTsvExport(): void { + this.tsvTaskRunning = true; + this.voService.getAllProjectsForTsvExport().subscribe((): void => { + this.getTSVInformation(); + }); + } + + downloadCurrentTSV(): void { + this.voService.downloadProjectsTsv().subscribe( + (result): void => { + const blobn = new Blob([result], { + type: 'text/tsv', + }); - const dateTime = new Date(); - FileSaver.saveAs(blobn, `projects-${dateTime.getDate()}-${dateTime.getMonth()}-${dateTime.getFullYear()}.tsv`); - }, - (err: any) => { - console.log(`No such file found! - ${err.toString()}`); - }, - ); - } + const dateTime = new Date(); + FileSaver.saveAs(blobn, `projects-${dateTime.getDate()}-${dateTime.getMonth()}-${dateTime.getFullYear()}.tsv`); + }, + (err: any) => { + console.log(`No such file found! - ${err.toString()}`); + }, + ); + } } diff --git a/src/app/vo_manager/voOverview.component.html b/src/app/vo_manager/voOverview.component.html index cae2288c8c..4c60dd0b33 100644 --- a/src/app/vo_manager/voOverview.component.html +++ b/src/app/vo_manager/voOverview.component.html @@ -1,255 +1,263 @@
-
- +
+ -
- +
+ - + - - - - + + + + - + -
-
Projects
+
+
Projects
-
-
- +
+
+ -
- +
+ - + - -
+ +
- -
+ +
-
-
- -
- -
- Loading... -
-
- - -
-
-
-
- -
-
-
-
- -
- +
+
+ +
+ +
+ Loading... +
+
+ + +
+
+
+
+ +
+
+
+
+ +
+ + + + + - Loading... + Loading... -
-
- - -
+
+
+ + +
-
- - -
+
+ + +
-
- - -
-
-
-
-
+
+ + +
+
+
+
+
- -
-
- -
-
- -
-
- -
-
- -
- -
- -
-
- -
+
+ -
-
- -
-
- -
-
- - - - - +
+
+ {{ + application_states[application_states.WAIT_FOR_CONFIRMATION] + }} + + +
+ +
+
+ +
+ +
SelectTypeFacility
+ + + + - - - - - - - + - - - - - - @if (projectsLoaded) { + + + + + + + - - - - - - + + + + + + @if (projectsLoaded) { + + + + + + - - - - - + + + + + - + - - - } @else { - - - - } - -
SelectTypeProject ID - Project Name - DetailsStatusRam - Cores - GPUs - FacilityCredits - Actions
Project ID + Project Name + DetailsStatusRamCoresGPUs
- - - - - - - > - - - - + Credits + Actions
+ + + + + + + > + + + + - - - {{ project_states[project_states.ACTIVE] }} - - {{ application_states[application_states.DISABLED] }} - - {{ lifetime_states[lifetime_states.EXPIRED] }} - - {{ lifetime_states[lifetime_states.EXPIRES_SOON] }} - {{ project_states[project_states.SUSPENDED] }} - - WAIT FOR CONFIRMATION - - TERMINATION REQUESTED - - WAIT FOR TERMINATION BY FM - - PROTECTED - - - - - > - - - > - - + + + {{ project_states[project_states.ACTIVE] }} + + {{ application_states[application_states.DISABLED] }} + + {{ lifetime_states[lifetime_states.EXPIRED] }} + + {{ lifetime_states[lifetime_states.EXPIRES_SOON] }} + {{ project_states[project_states.SUSPENDED] }} + + WAIT FOR CONFIRMATION + + TERMINATION REQUESTED + + WAIT FOR TERMINATION BY FM + + PROTECTED + + + + + > + + + > + + - > - - / - > - - + > + + / + > + + -
- +
+
+ - - - - - - - - - -
-
- -
-
-
-
-
- -
-
+ + + + + +
+ + + } @else { + + + + + + } + + +
+
+
+
+ +
+
- + - - + + - - + + - - + + - + - - + + + + - + - - + + - - + + +