diff --git a/.nx/cache/18.3.4-nx.linux-x64-gnu.node b/.nx/cache/18.3.4-nx.linux-x64-gnu.node new file mode 100644 index 0000000000..29fb952f46 Binary files /dev/null and b/.nx/cache/18.3.4-nx.linux-x64-gnu.node differ diff --git a/.nx/cache/nx_files.nxt b/.nx/cache/nx_files.nxt new file mode 100644 index 0000000000..cd7fb4a75b Binary files /dev/null and b/.nx/cache/nx_files.nxt differ diff --git a/Dockerfile b/Dockerfile index bbfd8c31be..47b7ce6e05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ COPY . . RUN $(npm bin)/ng build --configuration=custom --build-optimizer ### STAGE 2: Setup ### -FROM nginx:1.26.0-alpine +FROM nginx:1.26.1-alpine ## Copy our default nginx config COPY nginx/default.conf /etc/nginx/conf.d/ diff --git a/package-lock.json b/package-lock.json index 5178d99626..165aab986b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@denbi/cloud-portal-webapp", - "version": "4.846.0", + "version": "4.847.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@denbi/cloud-portal-webapp", - "version": "4.846.0", + "version": "4.847.0", "dependencies": { "@angular-eslint/eslint-plugin": "^17.3.0", "@angular/animations": "17.3.10", @@ -77,7 +77,7 @@ "@angular/compiler-cli": "17.3.10", "@playwright/test": "1.43.1", "@types/jasmine": "5.1.4", - "@types/node": "20.12.12", + "@types/node": "20.12.14", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "async": "3.2.5", @@ -86,7 +86,7 @@ "eslint": "^8.57.0", "eslint-config-airbnb-base": "15.0.0", "eslint-plugin-import": "^2.29.0", - "eslint-plugin-jsdoc": "48.2.5", + "eslint-plugin-jsdoc": "48.2.7", "eslint-plugin-no-null": "latest", "eslint-plugin-prefer-arrow": "1.2.3", "exports-loader": "5.0.0", @@ -97,7 +97,7 @@ "karma": "6.4.3", "karma-chrome-launcher": "3.2.0", "less-loader": "12.2.0", - "lint-staged": "15.2.4", + "lint-staged": "15.2.5", "ngx-spec": "2.1.6", "npm-run-all2": "6.1.2", "prettier": "3.2.5", @@ -2848,9 +2848,9 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.0.tgz", - "integrity": "sha512-Q1CnsQrytI3TlCB1IVWXWeqUIPGVEKGaE7IbVdt13Nq/3i0JESAkQQERrfiQkmlpijl+++qyqPgaS31Bvc1jRQ==", + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz", + "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==", "dev": true, "license": "MIT", "dependencies": { @@ -4250,9 +4250,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "version": "20.12.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz", + "integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -6026,18 +6026,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/builtins": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", @@ -8505,20 +8493,19 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.2.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.5.tgz", - "integrity": "sha512-ZeTfKV474W1N9niWfawpwsXGu+ZoMXu4417eBROX31d7ZuOk8zyG66SO77DpJ2+A9Wa2scw/jRqBPnnQo7VbcQ==", + "version": "48.2.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.7.tgz", + "integrity": "sha512-fYj3roTnkFL9OFFTB129rico8lerC5G8Vp2ZW9SjO9RNWG0exVvI+i/Y8Bpm1ufjR0uvT38xtoab/U0Hp8Ybog==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.43.0", + "@es-joy/jsdoccomment": "~0.43.1", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.5.0", - "is-builtin-module": "^3.2.1", - "semver": "^7.6.1", + "semver": "^7.6.2", "spdx-expression-parse": "^4.0.0" }, "engines": { @@ -10745,21 +10732,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-callable": { "version": "1.2.7", "dev": true, @@ -11832,22 +11804,22 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "15.2.4", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.4.tgz", - "integrity": "sha512-3F9KRQIS2fVDGtCkBp4Bx0jswjX7zUcKx6OF0ZeY1prksUyKPRIIUqZhIUYAstJfvj6i48VFs4dwVIbCYwvTYQ==", + "version": "15.2.5", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.5.tgz", + "integrity": "sha512-j+DfX7W9YUvdzEZl3Rk47FhDF6xwDBV5wwsCPw6BwWZVPYJemusQmvb9bRsW23Sqsaa+vRloAWogbK4BUuU2zA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "5.3.0", - "commander": "12.1.0", - "debug": "4.3.4", - "execa": "8.0.1", - "lilconfig": "3.1.1", - "listr2": "8.2.1", - "micromatch": "4.0.6", - "pidtree": "0.6.0", - "string-argv": "0.3.2", - "yaml": "2.4.2" + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.4", + "execa": "~8.0.1", + "lilconfig": "~3.1.1", + "listr2": "~8.2.1", + "micromatch": "~4.0.7", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.4.2" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -12496,30 +12468,18 @@ } }, "node_modules/micromatch": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.6.tgz", - "integrity": "sha512-Y4Ypn3oujJYxJcMacVgcs92wofTHxp9FzfDpQON4msDefoC0lb3ETvQLOdLcbhSwU1bz8HrL/1sygfBIHudrkQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "license": "MIT", "dependencies": { "braces": "^3.0.3", - "picomatch": "^4.0.2" + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.3.tgz", @@ -17752,9 +17712,9 @@ } }, "node_modules/vite": { - "version": "5.2.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", - "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", + "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 370ad6001c..d18435cf6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@denbi/cloud-portal-webapp", - "version": "4.846.0", + "version": "4.847.0", "description": "de.NBI Cloud Portal", "scripts": { "ng": "ng serve", @@ -87,7 +87,7 @@ "@angular/compiler-cli": "17.3.10", "@playwright/test": "1.43.1", "@types/jasmine": "5.1.4", - "@types/node": "20.12.12", + "@types/node": "20.12.14", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "async": "3.2.5", @@ -96,7 +96,7 @@ "eslint": "^8.57.0", "eslint-config-airbnb-base": "15.0.0", "eslint-plugin-import": "^2.29.0", - "eslint-plugin-jsdoc": "48.2.5", + "eslint-plugin-jsdoc": "48.2.7", "eslint-plugin-no-null": "latest", "eslint-plugin-prefer-arrow": "1.2.3", "exports-loader": "5.0.0", @@ -107,7 +107,7 @@ "karma": "6.4.3", "karma-chrome-launcher": "3.2.0", "less-loader": "12.2.0", - "lint-staged": "15.2.4", + "lint-staged": "15.2.5", "ngx-spec": "2.1.6", "npm-run-all2": "6.1.2", "prettier": "3.2.5", @@ -124,7 +124,7 @@ }, "overrides": { "undici": "6.15.0", - "vite": "5.2.11" + "vite": "5.2.12" }, "lint-staged": { "*.{js,jsx,ts}": [ diff --git a/src/app/api-connector/applications.service.ts b/src/app/api-connector/applications.service.ts index bd283a951f..4987f0d22e 100644 --- a/src/app/api-connector/applications.service.ts +++ b/src/app/api-connector/applications.service.ts @@ -8,6 +8,7 @@ import { Application } from '../applications/application.model/application.model import { ApplicationLifetimeExtension } from '../applications/application_extension.model'; import { ApplicationModification } from '../applications/application_modification.model'; import { ApplicationCreditRequest } from '../applications/application_credit_request'; +import { User } from '../applications/application.model/user.model'; /** * Service which provides methods for creating application. @@ -44,6 +45,29 @@ export class ApplicationsService { ); } + getApplicationPI(application_id: string | number): Observable { + return this.http.get(`${ApiSettings.getApiBaseURL()}project_applications/${application_id}/pi/`, { + withCredentials: true, + }); + } + + getApplicationUser(application_id: string | number): Observable { + return this.http.get(`${ApiSettings.getApiBaseURL()}project_applications/${application_id}/user/`, { + withCredentials: true, + }); + } + + getLifetimeExtensionUser(lifetimeextension_id: string | number): Observable { + return this.http.get(`${ApiSettings.getApiBaseURL()}project_applications/lifetime/extensions/${lifetimeextension_id}/user/`, { + withCredentials: true, + }); + } + + getModificationUser(project_id: string | number): Observable { + return this.http.get(`${ApiSettings.getApiBaseURL()}project_applications/modifications/${project_id}/user/`, { + withCredentials: true, + }); + } getApplicationValidationByHash(hash: string): Observable { return this.http.get(`${ApiSettings.getApiBaseURL()}project_applications/validation/${hash}/`, { withCredentials: true, @@ -124,11 +148,11 @@ export class ApplicationsService { } /** - * Checks if some client has the resource available for an application. - * - * @param app_id - * @returns - */ + * Checks if some client has the resource available for an application. + * + * @param app_id + * @returns + */ getApplicationClientAvaiable(app_id: string): Observable { return this.http.get(`${ApiSettings.getApiBaseURL()}project_applications/${app_id}/clients/resource/`, { withCredentials: true, diff --git a/src/app/api-connector/credits.service.ts b/src/app/api-connector/credits.service.ts index 6b32aa46ed..39a96f0536 100644 --- a/src/app/api-connector/credits.service.ts +++ b/src/app/api-connector/credits.service.ts @@ -9,7 +9,7 @@ import { ResourceWeight, IResourceWeight } from '../credits-calculator/resource- /** * Service which delivers functions for services related to the credit service. */ -@Injectable() +@Injectable({ providedIn: 'root' }) export class CreditsService { constructor(private http: HttpClient) { this.http = http; diff --git a/src/app/api-connector/facility.service.ts b/src/app/api-connector/facility.service.ts index b4a03d766f..8c09bb0720 100644 --- a/src/app/api-connector/facility.service.ts +++ b/src/app/api-connector/facility.service.ts @@ -683,7 +683,7 @@ export class FacilityService { * @param application_id * @returns */ - declineFacilityApplication(facility: number, application_id: number): Observable { + declineFacilityApplication(facility: string|number, application_id: number|string): Observable { const params: HttpParams = new HttpParams().set('action', 'decline'); return this.http.post( @@ -760,13 +760,13 @@ export class FacilityService { }); } - approveTerminationByFM(groupId: number | string, facility: number): Observable { + approveTerminationByFM(groupId: number | string, facility: number|string): Observable { return this.http.delete(`${ApiSettings.getApiBaseURL()}computecenters/${facility}/projects/${groupId}/`, { withCredentials: true, }); } - declineTerminationByFM(groupId: number | string, facility: number): Observable { + declineTerminationByFM(groupId: number | string, facility: number|string): Observable { return this.http.get(`${ApiSettings.getApiBaseURL()}computecenters/${facility}/projects/${groupId}/`, { withCredentials: true, }); diff --git a/src/app/api-connector/flavor.service.ts b/src/app/api-connector/flavor.service.ts index b481cee2e0..daf58f10ad 100644 --- a/src/app/api-connector/flavor.service.ts +++ b/src/app/api-connector/flavor.service.ts @@ -9,7 +9,7 @@ import { FlavorType } from '../virtualmachines/virtualmachinemodels/flavorType'; /** * Service which provides methods for Flavors. */ -@Injectable() +@Injectable({ providedIn: 'root' }) export class FlavorService { 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 81c34a6c35..0122236ca0 100644 --- a/src/app/api-connector/group.service.ts +++ b/src/app/api-connector/group.service.ts @@ -75,7 +75,7 @@ export class GroupService { } } - assignGroupToResource(groupid: string, computecenter: string): Observable { + assignGroupToResource(groupid: number, computecenter: string): Observable { const params: HttpParams = new HttpParams().set('compute_center', computecenter); return this.http.post(`${ApiSettings.getApiBaseURL()}projects/${groupid}/resource/`, params, { diff --git a/src/app/applications/application-card/application-card.component.html b/src/app/applications/application-card/application-card.component.html new file mode 100644 index 0000000000..af0582ae14 --- /dev/null +++ b/src/app/applications/application-card/application-card.component.html @@ -0,0 +1,49 @@ + + + + + {{ application?.project_application_name }} + {{ application?.project_application_shortname }} + {{ application?.project_application_date_submitted }} + + @if (application?.project_application_user?.username) { + {{ application?.project_application_user?.username }} + } @else { + + + } + + + {{ application?.project_application_institute }} + + @if (voView) { + + + } @else if (facilityView) { + + } + + + + @if (!isCollapsed) { + + + } + + + diff --git a/src/app/applications/application-card/application-card.component.scss b/src/app/applications/application-card/application-card.component.scss new file mode 100644 index 0000000000..d505897736 --- /dev/null +++ b/src/app/applications/application-card/application-card.component.scss @@ -0,0 +1,3 @@ +.table-line { + display: contents; +} \ No newline at end of file diff --git a/src/app/applications/application-card/application-card.component.spec.ts b/src/app/applications/application-card/application-card.component.spec.ts new file mode 100644 index 0000000000..9460a0c08c --- /dev/null +++ b/src/app/applications/application-card/application-card.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ApplicationCardComponent } from './application-card.component'; + +describe('ApplicationCardComponent', () => { + let component: ApplicationCardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ApplicationCardComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ApplicationCardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/application-card/application-card.component.ts b/src/app/applications/application-card/application-card.component.ts new file mode 100644 index 0000000000..7d01029b74 --- /dev/null +++ b/src/app/applications/application-card/application-card.component.ts @@ -0,0 +1,383 @@ +import { + Component, EventEmitter, Input, OnInit, Output, +} from '@angular/core'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; +import { Subscription } from 'rxjs'; +import { HttpStatusCode } from '@angular/common/http'; +import { AbstractBaseClass, Application_States } from '../../shared/shared_modules/baseClass/abstract-base-class'; +import { ConfirmationActions } from '../../shared/modal/confirmation_actions'; +import { Application } from '../application.model/application.model'; +import { ApplicationTabStates } from '../../shared/enums/application-tab-states'; +import { ApplicationsService } from '../../api-connector/applications.service'; +import { NotificationModalComponent } from '../../shared/modal/notification-modal'; +import { is_vo } from '../../shared/globalvar'; +import { ComputecenterComponent } from '../../projectmanagement/computecenter.component'; +import { GroupService } from '../../api-connector/group.service'; +import { User } from '../application.model/user.model'; + +@Component({ + selector: 'app-application-card', + templateUrl: './application-card.component.html', + styleUrl: './application-card.component.scss', +}) +export class ApplicationCardComponent extends AbstractBaseClass implements OnInit { + private subscription: Subscription = new Subscription(); + + @Input() application: Application; + @Input() tabState: ApplicationTabStates = ApplicationTabStates.SUBMITTED; + @Input() computeCenters: ComputecenterComponent[] = []; + @Output() reloadNumbersTrigger: EventEmitter = new EventEmitter(); + @Output() removeApplicationTrigger: EventEmitter = new EventEmitter(); + @Input() facilityView: boolean = false; + @Input() voView: boolean = false; + + bsModalRef: BsModalRef; + is_vo_admin: boolean = false; + selectedComputeCenter: ComputecenterComponent; + + ngOnInit() { + this.is_vo_admin = is_vo; + this.getAndSetPiAndUserApplication(); + + } + + getAndSetPiAndUserApplication() { + if (!this.application.project_application_user) { + this.getAndSetApplicationUser(); + } + if (!this.application.project_application_pi) { + this.getAndSetApplicationPi(); + } + + } + + getAndSetApplicationPi() { + this.applicationsService.getApplicationPI(this.application.project_application_id).subscribe((pi: User) => { + this.application.project_application_pi = pi; + + }); + } + + getAndSetApplicationUser() { + this.applicationsService.getApplicationUser(this.application.project_application_id).subscribe((user: User) => { + this.application.project_application_user = user; + + }); + } + + constructor( + private applicationsService: ApplicationsService, + private modalService: BsModalService, + private groupService: GroupService, + ) { + super(); + } + + triggerRemoveApplication() { + + this.removeApplicationTrigger.emit(this.application.project_application_id); + } + + triggerReloadNumbers() { + this.reloadNumbersTrigger.emit(); + } + + getApplication(): void { + this.applicationsService.getApplication(this.application.project_application_id.toString()).subscribe( + (aj: Application): void => { + this.application = aj; + }, + (error: any): void => { + console.log(error); + }, + ); + } + + resetApplicationPI(): void { + this.applicationsService.resetPIValidation(this.application).subscribe((app: Application) => { + this.getApplication(); + }); + } + + switchCollaps() { + this.isCollapsed = !this.isCollapsed; + } + + showNotificationModal( + notificationModalTitle: string, + notificationModalMessage: string, + notificationModalType: string, + ) { + const initialState = { notificationModalTitle, notificationModalType, notificationModalMessage }; + if (this.bsModalRef) { + this.bsModalRef.hide(); + } + + this.bsModalRef = this.modalService.show(NotificationModalComponent, { initialState }); + this.bsModalRef.setClass('modal-lg'); + } + + createSimpleVmProjectGroup(): void { + this.showNotificationModal('Info', 'Creating Project...', 'info'); + + if (this.selectedComputeCenter && this.selectedComputeCenter.FacilityId) { + this.groupService.createGroupByApplication(this.application.project_application_id, this.selectedComputeCenter.FacilityId).subscribe( + (res: any): void => { + if (!res['client_available'] && !res['created']) { + this.showNotificationModal( + 'Failed', + `The client ${res['client_name']} has not the necessary resources left!`, + 'danger', + ); + } else { + this.showNotificationModal('Success', 'The project was created!', 'success'); + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + } + }, + (error: any): void => { + console.log(error); + const errorMessage = error && error.error === 'locked' + ? 'Project is locked and could not be created!' + : 'Project could not be created!'; + + this.showNotificationModal('Failed', errorMessage, 'danger'); + console.log(error); + }, + ); + } + } + + approveModificationRequest(): void { + this.applicationsService.approveModificationRequest(this.application.project_application_id).subscribe( + (res: Response): void => { + this.showNotificationModal('Success', 'The resource modification request was approved!', 'success'); + if (!this.application.project_application_openstack_project) { + if (res.status === HttpStatusCode.Accepted) { + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + + // this.all_applications.splice(this.all_applications.indexOf(application), 1); + } + } else { + this.getApplication(); + } + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Approval of resource modification failed!', 'danger'); + }, + ); + } + + declineModificationRequest(): void { + this.applicationsService.deleteModificationRequest(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Declined', 'The resource modification request was declined!', 'success'); + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + + this.getApplication(); + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Decline of resource modification failed!', 'danger'); + }, + ); + } + + deleteApplication(): void { + // const idx: number = this.all_applications.indexOf(application); + + this.subscription.add( + this.applicationsService.deleteApplication(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Success', 'The application has been successfully removed', 'success'); + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + + }, + (): void => { + this.updateNotificationModal('Failed', 'Application could not be removed!', true, 'danger'); + }, + ), + ); + } + + declineLifetimeExtension(): void { + this.applicationsService.deleteAdditionalLifetimeRequests(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Declined', 'The project extension was declined!', 'success'); + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Decline of project extension failed!', 'danger'); + }, + ); + } + + declineCreditExtension(): void { + this.applicationsService.deleteAdditionalCreditsRequests(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Declined', 'The credit extension request was declined!', 'success'); + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Decline of credit extension failed!', 'danger'); + }, + ); + } + + declineApplication(): void { + // const idx: number = this.all_applications.indexOf(app); + this.applicationsService.declineApplication(this.application.project_application_id).subscribe( + (): void => { + const message: string = 'The Application was declined.'; + + this.showNotificationModal('Success', message, 'success'); + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + + }, + (): void => { + this.showNotificationModal('Failed', 'Application could not be declined!', 'danger'); + }, + ); + } + + approveLifetimeExtension(): void { + this.applicationsService.approveAdditionalLifetime(this.application.project_application_id).subscribe( + (res: Response): void => { + if (this.application.project_application_openstack_project) { + this.getApplication(); + this.showNotificationModal('Success', 'The request has been sent to the facility manager.', 'success'); + } else { + this.showNotificationModal('Success', 'The project has been extended!', 'success'); + } + if (!this.application.project_application_openstack_project) { + if (res.status === HttpStatusCode.Accepted) { + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + + } + } + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Project lifetime could not be extendend!', 'danger'); + }, + ); + } + + approveCreditExtension(): void { + this.applicationsService.approveAdditionalCreditsRequest(this.application.project_application_id).subscribe( + (res: Response): void => { + this.showNotificationModal('Success', 'The credit extension request was approved!', 'success'); + if (!this.application.project_application_openstack_project) { + if (res.status === HttpStatusCode.Accepted) { + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + + } + } else { + + this.getApplication(); + } + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Approval of credit extension failed!', 'danger'); + }, + ); + } + + createOpenStackProjectGroup(): void { + this.groupService.createGroupOpenStack(this.application.project_application_id, this.selectedComputeCenter.FacilityId).subscribe( + (result: { [key: string]: string }): void => { + if (result['Error']) { + this.showNotificationModal('Failed', result['Error'], 'danger'); + } else { + this.showNotificationModal('Success', 'The project was assigned to the facility.', 'success'); + this.getApplication(); + // this.switchApproveLocked(false); + } + }, + (error: any): void => { + const errorMessage = error && error.error === 'locked' + ? 'Project is locked and could not be created!' + : 'Project could not be created!'; + + this.showNotificationModal('Failed', errorMessage, 'danger'); + console.log(error); + }, + ); + } + + /** + * Function to listen to modal results. + */ + subscribeToBsModalRef(): void { + this.subscription.add( + this.bsModalRef.content.event.subscribe((result: any) => { + let action = null; + if ('action' in result) { + action = result['action']; + } + if ('createSimpleVM' in result) { + this.createSimpleVmProjectGroup(); + } + if (action === ConfirmationActions.APPROVE_MODIFICATION) { + this.approveModificationRequest(); + } + if ('closed' in result) { + // this.switchApproveLocked(false); + } + if (action === ConfirmationActions.DECLINE_MODIFICATION) { + this.declineModificationRequest(); + } + if (action === ConfirmationActions.DELETE_APPLICATION) { + this.deleteApplication(); + } + if (action === ConfirmationActions.DECLINE_EXTENSION) { + this.declineLifetimeExtension(); + } + if (action === ConfirmationActions.DECLINE_CREDITS) { + this.declineCreditExtension(); + } + if (action === ConfirmationActions.DECLINE_APPLICATION) { + this.declineApplication(); + } + + if (action === ConfirmationActions.APPROVE_EXTENSION) { + this.approveLifetimeExtension(); + } + if (action === ConfirmationActions.RESET_PI) { + this.resetApplicationPI(); + } + if (action === ConfirmationActions.APPROVE_CREDITS) { + this.approveCreditExtension(); + } + if (action === ConfirmationActions.APPROVE_APPLICATION) { + if (this.application.project_application_openstack_project) { + this.createOpenStackProjectGroup(); + } + } + if (action === 'adjustedModificationRequest') { + // this.isLoaded = false; + // this.changeTabState(ApplicationTabStates.MODIFICATION_EXTENSION); + } + }), + ); + } + + isCollapsed: boolean = true; + protected readonly Application_States = Application_States; + protected readonly ConfirmationActions = ConfirmationActions; + protected readonly ApplicationTabStates = ApplicationTabStates; +} diff --git a/src/app/applications/application-detail/application-detail.component.html b/src/app/applications/application-detail/application-detail.component.html index b14f9851f7..1fc75bf549 100644 --- a/src/app/applications/application-detail/application-detail.component.html +++ b/src/app/applications/application-detail/application-detail.component.html @@ -1,71 +1,61 @@ diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index e772a2931a..ef5f794798 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -9,6 +9,7 @@ import { FacilityService } from '../../api-connector/facility.service'; 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'; /** * Class which displays the details of an application. @@ -96,13 +97,30 @@ export class ApplicationDetailComponent extends ApplicationBaseClassComponent im ngOnInit(): void { this.setTab(this.default_tab); - this.getMemberDetailsByElixirId(this.application); + this.getPi(); + this.getUser(); if (this.application.credits_allowed) { this.getCurrentCredits(); } this.is_vo_admin = is_vo; } + getUser() { + if (!this.application.project_application_user) { + this.applicationsService.getApplicationUser(this.application.project_application_id).subscribe((user: User) => { + this.application.project_application_user = user; + }); + } + } + + getPi() { + if (!this.application.project_application_pi.email) { + this.applicationsService.getApplicationPI(this.application.project_application_id).subscribe((pi: User) => { + this.application.project_application_pi = pi; + }); + } + } + getCurrentCredits(): void { this.creditsService .getCurrentCreditsOfProject(Number(this.application.project_application_perun_id.toString())) diff --git a/src/app/applications/application-detail/application-pi-detail/application-pi-detail.component.html b/src/app/applications/application-detail/application-pi-detail/application-pi-detail.component.html index f1f6e57857..61032abcb1 100644 --- a/src/app/applications/application-detail/application-pi-detail/application-pi-detail.component.html +++ b/src/app/applications/application-detail/application-pi-detail/application-pi-detail.component.html @@ -1,57 +1,98 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Approved by VO - {{ application?.approved_by_vo_manager }} -
Approved by FM - {{ application?.approved_by_facility_manager }} -
PI Name - {{ application?.project_application_pi?.username }} -
PI Email - {{ application?.project_application_pi?.email }} -
PI institue or workgroup link
PI Affiliations - {{ application?.project_application_pi?.user_affiliations }} -
User Affiliations{{ application?.project_application_user?.user_affiliations }}
User Name - {{ application?.project_application_user?.username }} -
User Email - {{ application?.project_application_user?.email }} -
Approved by VO + {{ application?.approved_by_vo_manager }} +
Approved by FM + {{ application?.approved_by_facility_manager }} +
PI Name + @if (application.project_application_pi?.username) { + {{ application?.project_application_pi?.username }} + @if (application?.project_application_pi?.pi_project_count_total > 0) { + + {{ application?.project_application_pi?.pi_project_count_total }} + + + } + } @else if (this.application.project_application_pi_approved) { + + + } +
PI Email + @if (application?.project_application_pi?.email) { + {{ application?.project_application_pi?.email }} + } @else { + + + } +
PI institue or workgroup link
PI Affiliations + @if (application?.project_application_pi?.user_affiliations) { + {{ application?.project_application_pi?.user_affiliations }} + } @else { + + + } +
User Affiliations + @if (application?.project_application_pi?.user_affiliations) { + {{ application?.project_application_pi?.user_affiliations }} + } @else { + + + } +
User Name + @if (application?.project_application_pi?.username) { + {{ application?.project_application_pi?.username }} + } @else { + + + } +
User Email + @if (application?.project_application_pi?.email) { + {{ application?.project_application_pi?.email }} + } @else { + + + } +
diff --git a/src/app/applications/application-detail/lifetime-extension-detail/lifetime-extension-detail.component.html b/src/app/applications/application-detail/lifetime-extension-detail/lifetime-extension-detail.component.html index 66592b48dd..4464d8082b 100644 --- a/src/app/applications/application-detail/lifetime-extension-detail/lifetime-extension-detail.component.html +++ b/src/app/applications/application-detail/lifetime-extension-detail/lifetime-extension-detail.component.html @@ -1,51 +1,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Lifetime Extension Requested from: - {{ application?.project_lifetime_request?.user?.username }} - - ( {{ application?.project_lifetime_request?.user?.email }} | - {{ application?.project_lifetime_request?.user?.elixir_id }} - ) -
Comment{{ application?.project_lifetime_request?.comment }}
Comment by Cloud Governance{{ application?.project_lifetime_request?.manager_comment }}
Extra Lifetime - {{ application?.project_lifetime_request?.extra_lifetime }} - month(s) - ending - {{ application?.project_lifetime_request?.new_end_date.length > 11 ? '' : 'on' }} - {{ application?.project_lifetime_request?.new_end_date }} - instead of - {{ application?.project_lifetime_request?.old_end_date }} -
Extra Credits - {{ application?.project_lifetime_request?.extra_credits }} - (Total: - {{ application?.totalLifetimeExtensionCredits }}) -
Date Submitted - {{ application?.project_lifetime_request?.date_submitted }} -
Lifetime Extension Requested from: + + @if (application?.project_lifetime_request?.user) { + {{ application?.project_lifetime_request?.user?.username }} + + ( {{ application?.project_lifetime_request?.user?.email }} | + {{ application?.project_lifetime_request?.user?.elixir_id }} + ) } + @else{ + + + } + +
Comment{{ application?.project_lifetime_request?.comment }}
Comment by Cloud Governance{{ application?.project_lifetime_request?.manager_comment }}
Extra Lifetime + {{ application?.project_lifetime_request?.extra_lifetime }} + month(s) - ending + {{ application?.project_lifetime_request?.new_end_date.length > 11 ? '' : 'on' }} + {{ application?.project_lifetime_request?.new_end_date }} + instead of + {{ application?.project_lifetime_request?.old_end_date }} +
Extra Credits + {{ application?.project_lifetime_request?.extra_credits }} + (Total: + {{ application?.totalLifetimeExtensionCredits }}) +
Date Submitted + {{ application?.project_lifetime_request?.date_submitted }} +
diff --git a/src/app/applications/application-detail/lifetime-extension-detail/lifetime-extension-detail.component.ts b/src/app/applications/application-detail/lifetime-extension-detail/lifetime-extension-detail.component.ts index a153963c9d..4998333c22 100644 --- a/src/app/applications/application-detail/lifetime-extension-detail/lifetime-extension-detail.component.ts +++ b/src/app/applications/application-detail/lifetime-extension-detail/lifetime-extension-detail.component.ts @@ -1,5 +1,7 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { Application } from '../../application.model/application.model'; +import { ApplicationBaseClassComponent } from '../../../shared/shared_modules/baseClass/application-base-class.component'; +import { User } from '../../application.model/user.model'; /** * Lifetime extension details. @@ -8,6 +10,20 @@ import { Application } from '../../application.model/application.model'; selector: 'app-lifetime-extension-detail', templateUrl: './lifetime-extension-detail.component.html', }) -export class LifetimeExtensionDetailComponent { - @Input() application: Application; +export class LifetimeExtensionDetailComponent extends ApplicationBaseClassComponent implements OnInit { + @Input() application: Application; + + ngOnInit() { + this.getRequestingUser(); + } + + getRequestingUser() { + if (this.application.project_lifetime_request && !this.application.project_lifetime_request.user) { + this.applicationsService.getLifetimeExtensionUser(this.application.project_application_id).subscribe((user: User) => { + + this.application.project_lifetime_request.user = user; + + }); + } + } } diff --git a/src/app/applications/application-detail/modification-detail/modification-detail.component.html b/src/app/applications/application-detail/modification-detail/modification-detail.component.html index 6f484c7117..c655ba62e2 100644 --- a/src/app/applications/application-detail/modification-detail/modification-detail.component.html +++ b/src/app/applications/application-detail/modification-detail/modification-detail.component.html @@ -1,98 +1,104 @@ - - - + + + - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - + >In development + Existing + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - - - + + + + - - - - - - - - + + + + + + + + - - - + + +
Modification Requested from:
Modification Requested from: - {{ application?.project_modification_request?.user?.username }} - ( {{ application?.project_modification_request?.user?.email }} | - {{ application?.project_modification_request?.user?.elixir_id }}) -
Comment{{ application?.project_modification_request?.comment }}
Service status + @if (application?.project_modification_request?.user) { + + {{ application?.project_modification_request?.user?.username }} + ( {{ application?.project_modification_request?.user?.email }} | + {{ application?.project_modification_request?.user?.elixir_id }})} + + @else{ + + + } +
Comment{{ application?.project_modification_request?.comment }}
Service status In development - Existing -
VMs - Requested: - {{ application?.project_modification_request?.vms_requested }} -
Flavor: {{ flavor.name }}{{ application?.project_modification_request | flavorCounter: flavor }}
Total Cores
VMs + Requested: + {{ application?.project_modification_request?.vms_requested }} +
Flavor: {{ flavor.name }}{{ application?.project_modification_request | flavorCounter: flavor }}
Total Cores - {{ application?.project_modification_request?.total_cores }} -
Total RAM{{ application?.project_modification_request?.total_ram }} GB
Total GPUs - {{ application?.project_modification_request?.total_gpu }} -
+ {{ application?.project_modification_request?.total_cores }} +
Total RAM{{ application?.project_modification_request?.total_ram }} GB
Total GPUs + {{ application?.project_modification_request?.total_gpu }} +
Extra Credits
Extra Credits - {{ application?.project_modification_request?.extra_credits }} - (Total: {{ application?.totalModificationRequestCredits }}) -
+ {{ application?.project_modification_request?.extra_credits }} + (Total: {{ application?.totalModificationRequestCredits }}) +
Storage Limit
Storage Limit - {{ application?.project_modification_request?.volume_limit }} - GB -
Volume Counter + {{ application?.project_modification_request?.volume_limit }} + GB +
Volume Counter - {{ application?.project_modification_request?.volume_counter }} - volumes -
ObjectStorage - {{ application?.project_modification_request?.object_storage }} - GB -
Date Submitted + {{ application?.project_modification_request?.volume_counter }} + volumes +
ObjectStorage + {{ application?.project_modification_request?.object_storage }} + GB +
Date Submitted - {{ application?.project_modification_request?.date_submitted }} -
+ {{ application?.project_modification_request?.date_submitted }} +
diff --git a/src/app/applications/application-detail/modification-detail/modification-detail.component.ts b/src/app/applications/application-detail/modification-detail/modification-detail.component.ts index 25f3e3cae7..fc3af5ad93 100644 --- a/src/app/applications/application-detail/modification-detail/modification-detail.component.ts +++ b/src/app/applications/application-detail/modification-detail/modification-detail.component.ts @@ -1,6 +1,8 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { Application } from '../../application.model/application.model'; import { Application_States } from '../../../shared/shared_modules/baseClass/abstract-base-class'; +import { ApplicationBaseClassComponent } from '../../../shared/shared_modules/baseClass/application-base-class.component'; +import { User } from '../../application.model/user.model'; /** * Application modification details. @@ -9,8 +11,23 @@ import { Application_States } from '../../../shared/shared_modules/baseClass/abs selector: 'app-modification-detail', templateUrl: './modification-detail.component.html', }) -export class ModificationDetailComponent { - @Input() application: Application; - @Input() is_vo_admin: boolean; - Application_States: typeof Application_States = Application_States; +export class ModificationDetailComponent extends ApplicationBaseClassComponent implements OnInit { + @Input() application: Application; + @Input() is_vo_admin: boolean; + Application_States: typeof Application_States = Application_States; + + ngOnInit() { + this.getRequestingUser(); + } + + getRequestingUser() { + if (this.application.project_modification_request && !this.application.project_modification_request.user) { + this.applicationsService.getModificationUser(this.application.project_application_id).subscribe((user: User) => { + + this.application.project_modification_request.user = user; + + }); + } + } + } diff --git a/src/app/applications/application-detail/resource-detail/resource-detail.component.html b/src/app/applications/application-detail/resource-detail/resource-detail.component.html index 1f62d36c18..11c78f76a6 100644 --- a/src/app/applications/application-detail/resource-detail/resource-detail.component.html +++ b/src/app/applications/application-detail/resource-detail/resource-detail.component.html @@ -1,55 +1,232 @@ +@if (application.project_modification_request) { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Modification
Modification Requested from: @if (application?.project_modification_request?.user) { + + {{ application?.project_modification_request?.user?.username }} + ( {{ application?.project_modification_request?.user?.email }} | + {{ application?.project_modification_request?.user?.elixir_id }}) + } @else { + + + } +
Comment{{ application?.project_modification_request?.comment }}
Service status + In development + Existing +
Date Submitted + {{ application?.project_modification_request?.date_submitted }} +
+} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + @if (application.project_modification_request) { + + + + } + + + + + + + @if (application.project_modification_request) { + + + + } + + + @for (flavorDiff of flavorDiffs; track flavorDiff.name) { + + + + @if (application.project_modification_request) { + + + + + } + + + } + + + + + + @if (application.project_modification_request) { + + + } + + + + + + @if (application.project_modification_request) { + + + } + + + + + @if (application.project_modification_request) { + + + } + + + + + + + @if (application.project_modification_request) { + + + } + + + + + + @if (application.project_modification_request) { + + + + } + + + + + + @if (application.project_modification_request) { + + + } + + + + + @if (application.project_modification_request) { + + + } + + + + + @if (application.project_modification_request) { + + + } + +
VMs - {{ application?.project_application_vms_requested }} -
Flavor: {{ flavor.name }}{{ flavor.counter }}
Total Cores{{ application?.project_application_total_cores }}
Total RAM{{ application?.project_application_total_ram }} GB
Total GPUs{{ application?.project_application_total_gpu }}
Initial Credits{{ application?.project_application_initial_credits }}
Used Credits{{ current_credits }}
Storage Limit{{ application?.project_application_volume_limit }} GB
Volume Counter{{ application?.project_application_volume_counter }} volumes
ObjectStorage{{ application?.project_application_object_storage }} GB
ResourceCurrentChangesModification
VMs + {{ application?.project_application_vms_requested }} + + + {{ application.project_modification_request.vms_requested === application.project_application_vms_requested ? '' : (application.project_modification_request.vms_requested > application.project_application_vms_requested ? '+' : '-') }} {{ Math.abs(application.project_modification_request.vms_requested - application.project_application_vms_requested) }} + + {{ application.project_modification_request.vms_requested }}
Flavor: {{ flavorDiff.name }}{{ flavorDiff.current }} + + {{ flavorDiff.diff === 0 ? '' : (flavorDiff.diff > 0 ? '+' : '-') }} + {{ flavorDiff.diff }} + + {{ flavorDiff.new }}
Total Cores{{ application?.project_application_total_cores }} + + {{ application.project_modification_request.total_cores === application.project_application_total_cores ? '' : (application.project_modification_request.total_cores > application.project_application_total_cores ? '+' : '-') }} + {{ Math.abs(application.project_modification_request.total_cores - application.project_application_total_cores) }} + + {{ application.project_modification_request.total_cores }}
Total RAM{{ application?.project_application_total_ram }} GB + + + + {{ application.project_modification_request.total_ram === application.project_application_total_ram ? '' : (application.project_modification_request.total_ram > application.project_application_total_ram ? '+' : '-') }} + {{ Math.abs(application.project_modification_request.total_ram - application.project_application_total_ram) }} + + {{ application.project_modification_request.total_ram }}
Total GPUs{{ application?.project_application_total_gpu }} + + {{ application.project_modification_request.total_gpu === application.project_application_total_gpu ? '' : (application.project_modification_request.total_gpu > application.project_application_total_gpu ? '+' : '-') }} + {{ Math.abs(application.project_modification_request.total_gpu - application.project_application_total_gpu) }} + + {{ application.project_modification_request.total_gpu }}
Initial Credits{{ application?.project_application_initial_credits }} + + + {{ application.project_modification_request.extra_credits }} + + {{ application.project_modification_request.extra_credits + application.project_application_initial_credits }}
Used Credits{{ current_credits }}
Storage Limit{{ application?.project_application_volume_limit }} GB + + {{ application.project_modification_request.volume_limit === application.project_application_volume_limit ? '' : (application.project_modification_request.volume_limit > application.project_application_volume_limit ? '+' : '-') }} + {{ Math.abs(application.project_modification_request.volume_limit - application.project_application_volume_limit) }} + + {{ application.project_modification_request.volume_limit }}
Volume Counter{{ application?.project_application_volume_counter }} volumes + + {{ application.project_modification_request.volume_counter === application.project_application_volume_counter ? '' : (application.project_modification_request.volume_counter > application.project_application_volume_counter ? '+' : '-') }} + {{ Math.abs(application.project_modification_request.volume_counter - application.project_application_volume_counter) }} + + {{ application.project_modification_request.volume_counter }}
ObjectStorage{{ application?.project_application_object_storage }} GB + + {{ application.project_modification_request.object_storage === application.project_application_object_storage ? '' : (application.project_modification_request.object_storage > application.project_application_object_storage ? '+' : '-') }} + {{ Math.abs(application.project_modification_request.object_storage - application.project_application_object_storage) }} + + {{ application.project_modification_request.object_storage }}
diff --git a/src/app/applications/application-detail/resource-detail/resource-detail.component.ts b/src/app/applications/application-detail/resource-detail/resource-detail.component.ts index 24b6c50192..5f9af57ec8 100644 --- a/src/app/applications/application-detail/resource-detail/resource-detail.component.ts +++ b/src/app/applications/application-detail/resource-detail/resource-detail.component.ts @@ -1,5 +1,16 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; +import { green } from 'audit-ci/dist/colors'; import { Application } from '../../application.model/application.model'; +import { User } from '../../application.model/user.model'; +import { ApplicationBaseClassComponent } from '../../../shared/shared_modules/baseClass/application-base-class.component'; +import { Flavor } from '../../../virtualmachines/virtualmachinemodels/flavor'; + +interface FlavorDiff { + name: string; + current: number; + diff: number; + new: number; +} /** * Application Resource Details. @@ -8,8 +19,61 @@ import { Application } from '../../application.model/application.model'; selector: 'app-resource-detail', templateUrl: './resource-detail.component.html', }) -export class ResourceDetailComponent { - @Input() application: Application; - @Input() is_vo_admin: boolean; - @Input() current_credits: number; +export class ResourceDetailComponent extends ApplicationBaseClassComponent implements OnInit { + @Input() application: Application; + @Input() is_vo_admin: boolean; + @Input() current_credits: number; + protected readonly Math = Math; + protected readonly green = green; + flavorDiffs: FlavorDiff[] = []; + + ngOnInit() { + this.getFlavorChanges(); + this.getModificationRequestingUser(); + } + + getModificationRequestingUser() { + if (this.application.project_modification_request && !this.application.project_modification_request.user) { + this.applicationsService.getModificationUser(this.application.project_application_id).subscribe((user: User) => { + + this.application.project_modification_request.user = user; + + }); + } + } + + getFlavorChanges() { + this.flavorDiffs = []; + + // Initialize flavorDiffs with current flavors + this.application.flavors.forEach((flavor: Flavor) => { + this.flavorDiffs.push({ + name: flavor.name, current: flavor.counter, diff: 0, new: 0, + }); + }); + + // Iterate over modification request flavors + if (this.application.project_modification_request) { + this.application.project_modification_request.flavors.forEach((modificationFlavor: Flavor) => { + const existingFlavorDiffIndex = this.flavorDiffs.findIndex(flavorDiff => flavorDiff.name === modificationFlavor.name); + + if (existingFlavorDiffIndex !== -1) { + // Flavor diff with same name exists + const existingFlavorDiff = this.flavorDiffs[existingFlavorDiffIndex]; + existingFlavorDiff.new = modificationFlavor.counter; + existingFlavorDiff.diff = existingFlavorDiff.new - existingFlavorDiff.current; + } else { + // Flavor diff with same name does not exist, add new flavor diff + this.flavorDiffs.push({ + name: modificationFlavor.name, + current: 0, // Set current as 0 as it's not present in the application flavors + diff: modificationFlavor.counter, + new: modificationFlavor.counter, + }); + } + }); + } + + } + } diff --git a/src/app/applications/application-facility-actions/application-facility-actions.component.html b/src/app/applications/application-facility-actions/application-facility-actions.component.html new file mode 100644 index 0000000000..3a2c083d9c --- /dev/null +++ b/src/app/applications/application-facility-actions/application-facility-actions.component.html @@ -0,0 +1,161 @@ + + +
+ @switch (tabState) { + + + @case (ApplicationTabStates.SUBMITTED) { + + @if (application | hasstatusinlist: Application_States.WAIT_FOR_CONFIRMATION) { + + } + @if (application | hasstatusinlist: Application_States.WAIT_FOR_CONFIRMATION) { + + + } + + + } + @case (ApplicationTabStates.LIFETIME_EXTENSION) { + + + + + + + } + @case (ApplicationTabStates.CREDITS_EXTENSION) { + + } + @case (ApplicationTabStates.MODIFICATION_EXTENSION) { + + + + + } + @case (ApplicationTabStates.TERMINATION_REQUEST) { + + + + + + } + } + + + + + + + +
+ diff --git a/src/app/applications/application-facility-actions/application-facility-actions.component.scss b/src/app/applications/application-facility-actions/application-facility-actions.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/applications/application-facility-actions/application-facility-actions.component.spec.ts b/src/app/applications/application-facility-actions/application-facility-actions.component.spec.ts new file mode 100644 index 0000000000..6c62c90e08 --- /dev/null +++ b/src/app/applications/application-facility-actions/application-facility-actions.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ApplicationFacilityActionsComponent } from './application-facility-actions.component'; + +describe('ApplicationFacilityActionsComponent', () => { + let component: ApplicationFacilityActionsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ApplicationFacilityActionsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ApplicationFacilityActionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/application-facility-actions/application-facility-actions.component.ts b/src/app/applications/application-facility-actions/application-facility-actions.component.ts new file mode 100644 index 0000000000..981ddbce05 --- /dev/null +++ b/src/app/applications/application-facility-actions/application-facility-actions.component.ts @@ -0,0 +1,273 @@ +import { + Component, EventEmitter, Input, Output, +} from '@angular/core'; + +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; +import { ConfirmationActions } from 'app/shared/modal/confirmation_actions'; +import { Subscription } from 'rxjs'; +import { Application } from '../application.model/application.model'; +import { ComputecenterComponent } from '../../projectmanagement/computecenter.component'; +import { ApplicationTabStates } from '../../shared/enums/application-tab-states'; +import { AbstractBaseClass, Application_States } from '../../shared/shared_modules/baseClass/abstract-base-class'; +import { FacilityService } from '../../api-connector/facility.service'; +import { NotificationModalComponent } from '../../shared/modal/notification-modal'; +import { ConfirmationModalComponent } from '../../shared/modal/confirmation-modal.component'; +import { ApplicationsService } from '../../api-connector/applications.service'; + +@Component({ + selector: 'app-application-facility-actions', + + templateUrl: './application-facility-actions.component.html', + styleUrl: './application-facility-actions.component.scss', +}) +export class ApplicationFacilityActionsComponent extends AbstractBaseClass { + private subscription: Subscription = new Subscription(); + + protected readonly ConfirmationActions = ConfirmationActions; + protected readonly ApplicationTabStates = ApplicationTabStates; + @Input() application: Application; + @Input() tabState: ApplicationTabStates = ApplicationTabStates.SUBMITTED; + @Input() computeCenters: ComputecenterComponent[] = []; + @Output() reloadNumbersTrigger: EventEmitter = new EventEmitter(); + @Output() removeApplicationTrigger: EventEmitter = new EventEmitter(); + isCollapsed: boolean = true; + bsModalRef: BsModalRef; + @Output() switchCollapseEvent: EventEmitter = new EventEmitter(); + + constructor(private facilityService: FacilityService, private modalService: BsModalService, private applicationsService: ApplicationsService) { + super(); + } + + switchCollaps() { + this.switchCollapseEvent.emit(); + } + + triggerRemoveApplication() { + + this.removeApplicationTrigger.emit(this.application.project_application_id); + } + + triggerReloadNumbers() { + this.reloadNumbersTrigger.emit(); + } + + declineApplication(): void { + this.showNotificationModal('Decline Application', 'Waiting..', 'info'); + + this.facilityService + .declineFacilityApplication( + this.application.project_application_compute_center.FacilityId, + this.application.project_application_id, + ) + .subscribe( + (): void => { + this.showNotificationModal('Success', 'Successfully declined the application.', 'success'); + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + + // this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); + }, + (): void => { + this.showNotificationModal('Failed', 'Failed to decline the application.', 'danger'); + }, + ); + } + + public approveExtension(): void { + this.applicationsService.approveAdditionalLifetime(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Success', 'Successfully approved extension!', 'success'); + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + }, + (): void => { + this.showNotificationModal('Failed', 'The approval of the extension request has failed.', 'danger'); + }, + ); + } + + /** + * Decline an extension request. + * + * @param application_id + */ + public declineExtension(): void { + this.applicationsService.declineAdditionalLifetime(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Success', 'Successfully declined extension!', 'success'); + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + }, + (): void => { + this.showNotificationModal('Failed', 'The decline of the extension request has failed.', 'danger'); + }, + ); + } + + showNotificationModal( + notificationModalTitle: string, + notificationModalMessage: string, + notificationModalType: string, + ) { + const initialState = { notificationModalTitle, notificationModalType, notificationModalMessage }; + if (this.bsModalRef) { + this.bsModalRef.hide(); + } + + this.bsModalRef = this.modalService.show(NotificationModalComponent, { initialState }); + this.bsModalRef.setClass('modal-lg'); + } + + approveApplication(): void { + + this.showNotificationModal('Approving Application', 'Waiting..', 'info'); + this.facilityService + .approveFacilityApplication(this.application.project_application_compute_center.FacilityId, this.application.project_application_id) + .subscribe( + (): void => { + this.showNotificationModal('Success', 'Successfully approved the application.', 'success'); + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + + // this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); + }, + (): void => { + this.showNotificationModal('Failed', 'Failed to approve the application.', 'danger'); + }, + ); + } + + showConfirmationModal(action: ConfirmationActions): void { + + const initialState = { + application: this.application, action, + }; + + this.bsModalRef = this.modalService.show(ConfirmationModalComponent, { initialState, class: 'modal-lg' }); + this.subscribeToBsModalRef(); + } + + approveModification(): void { + this.applicationsService.approveModificationRequest(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Success', 'Successfully approved modification!', 'success'); + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + }, + (): void => { + this.showNotificationModal('Failed', 'The approval of the modification request has failed.', 'danger'); + }, + ); + } + + declineModification(): void { + this.applicationsService.declineModificationRequest(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Success', 'Successfully declined modification!', 'success'); + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + }, + (): void => { + this.showNotificationModal('Failed', 'The decline of the modification request has failed.', 'danger'); + }, + ); + } + + approveTermination(): void { + this.facilityService + .approveTerminationByFM(this.application.project_application_perun_id, this.application.project_application_compute_center.FacilityId) + .subscribe( + (): void => { + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + this.showNotificationModal('Success', 'The project was terminated.', 'success'); + }, + (error: any): void => { + if (error['status'] === 409) { + this.showNotificationModal( + 'Failed', + `The project could not be terminated. Reason: ${error['error']['reason']} for ${error['error']['openstackid']}`, + + 'danger', + ); + } else { + this.showNotificationModal('Failed', 'The project could not be terminated.', 'danger'); + } + }, + ); + } + + declineTermination(): void { + this.facilityService + .declineTerminationByFM(this.application.project_application_perun_id, this.application.project_application_compute_center.FacilityId) + .subscribe( + (): void => { + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + this.showNotificationModal('Success', 'The termination of the project was declined.', 'success'); + }, + (error: any): void => { + if (error['status'] === 409) { + this.showNotificationModal( + 'Failed', + `The decline of the project was not successful. Reason: ${error['error']['reason']} for ${error['error']['openstackid']}`, + + 'danger', + ); + } else { + this.showNotificationModal('Failed', 'The decline of the project failed.', 'danger'); + } + }, + ); + } + + subscribeToBsModalRef(): void { + this.subscription.add( + this.bsModalRef.content.event.subscribe((event: any) => { + const action: ConfirmationActions = event.action; + switch (action) { + + case ConfirmationActions.APPROVE_APPLICATION: { + this.approveApplication(); + break; + } + case ConfirmationActions.DECLINE_APPLICATION: { + this.declineApplication(); + break; + } + case ConfirmationActions.DECLINE_EXTENSION: { + this.declineExtension(); + break; + } + case ConfirmationActions.APPROVE_EXTENSION: { + this.approveExtension(); + break; + } + case ConfirmationActions.DECLINE_MODIFICATION: { + this.declineModification(); + break; + } + case ConfirmationActions.APPROVE_MODIFICATION: { + this.approveModification(); + break; + } + case ConfirmationActions.APPROVE_TERMINATION: { + this.approveTermination(); + break; + } + case ConfirmationActions.DECLINE_TERMINATION: { + this.declineTermination(); + break; + } + } + + }), + ); + } + + ngOnInit() { + + } + + protected readonly Application_States = Application_States; +} diff --git a/src/app/applications/application-formular/application-formular.component.ts b/src/app/applications/application-formular/application-formular.component.ts index cfeee1f59b..62f161a73a 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 { } from '../../../links/links'; import { UserService } from '../../api-connector/user.service'; import { Userinfo } from '../../userinfo/userinfo.model'; +import { User } from '../application.model/user.model'; /** * Application formular component. @@ -170,6 +171,7 @@ export class ApplicationFormularComponent extends ApplicationBaseClassComponent if (this.application && !this.initiated_validation && this.is_validation) { this.openstack_project = this.application.project_application_openstack_project; this.simple_vm_project = !this.openstack_project; + this.application.project_application_pi = new User(); this.searchTermsInEdamTerms(); if (this.application.dissemination.someAllowed()) { diff --git a/src/app/applications/application-header/application-header.component.html b/src/app/applications/application-header/application-header.component.html new file mode 100644 index 0000000000..adc37ad21f --- /dev/null +++ b/src/app/applications/application-header/application-header.component.html @@ -0,0 +1 @@ +

application-header works!

diff --git a/src/app/applications/application-header/application-header.component.scss b/src/app/applications/application-header/application-header.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/applications/application-header/application-header.component.spec.ts b/src/app/applications/application-header/application-header.component.spec.ts new file mode 100644 index 0000000000..06c39a25c4 --- /dev/null +++ b/src/app/applications/application-header/application-header.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ApplicationHeaderComponent } from './application-header.component'; + +describe('ApplicationHeaderComponent', () => { + let component: ApplicationHeaderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ApplicationHeaderComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ApplicationHeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/application-header/application-header.component.ts b/src/app/applications/application-header/application-header.component.ts new file mode 100644 index 0000000000..408fec6e6d --- /dev/null +++ b/src/app/applications/application-header/application-header.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-application-header', + standalone: true, + imports: [], + templateUrl: './application-header.component.html', + styleUrl: './application-header.component.scss', +}) +export class ApplicationHeaderComponent {} diff --git a/src/app/applications/application-list/application-list.component.html b/src/app/applications/application-list/application-list.component.html new file mode 100644 index 0000000000..5e4d0de289 --- /dev/null +++ b/src/app/applications/application-list/application-list.component.html @@ -0,0 +1,32 @@ +
+
+ + + + + + + + + + + + + + + + @for (application of applications; track application.project_application_id) { + + + } + +
Project NameShort NameDate submittedUserInstituteCompute CenterActions
+
+
diff --git a/src/app/applications/application-list/application-list.component.scss b/src/app/applications/application-list/application-list.component.scss new file mode 100644 index 0000000000..d505897736 --- /dev/null +++ b/src/app/applications/application-list/application-list.component.scss @@ -0,0 +1,3 @@ +.table-line { + display: contents; +} \ No newline at end of file diff --git a/src/app/applications/application-list/application-list.component.spec.ts b/src/app/applications/application-list/application-list.component.spec.ts new file mode 100644 index 0000000000..4ee910f899 --- /dev/null +++ b/src/app/applications/application-list/application-list.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ApplicationListComponent } from './application-list.component'; + +describe('ApplicationListComponent', () => { + let component: ApplicationListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ApplicationListComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ApplicationListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/application-list/application-list.component.ts b/src/app/applications/application-list/application-list.component.ts new file mode 100644 index 0000000000..ae582b1ce3 --- /dev/null +++ b/src/app/applications/application-list/application-list.component.ts @@ -0,0 +1,48 @@ +import { + Component, EventEmitter, Input, OnInit, Output, +} from '@angular/core'; +import { Application_States } from '../../shared/shared_modules/baseClass/abstract-base-class'; + +import { Application } from '../application.model/application.model'; +import { ApplicationTabStates } from '../../shared/enums/application-tab-states'; +import { ComputecenterComponent } from '../../projectmanagement/computecenter.component'; +import { is_vo } from '../../shared/globalvar'; + +@Component({ + selector: 'app-application-list', + + templateUrl: './application-list.component.html', + styleUrl: './application-list.component.scss', +}) +export class ApplicationListComponent implements OnInit { + @Output() reloadNumbersTrigger: EventEmitter = new EventEmitter(); + + @Input() applications: Application[] = []; + @Input() tabState: ApplicationTabStates = ApplicationTabStates.SUBMITTED; + @Input() computeCenters: ComputecenterComponent[] = []; + @Input() facilityView:boolean = false; + @Input() voView:boolean = false; + + is_vo_admin: boolean = false; + + ngOnInit() { + this.is_vo_admin = is_vo; + + } + + triggerReloadNumbers() { + console.log('trigger reload 2'); + this.reloadNumbersTrigger.emit(); + } + + removeApplicationFromList(application_id: string | number) { + const idx: number = this.applications.findIndex((application: Application) => application.project_application_id === application_id); + + if (idx !== -1) { + console.log('remove index'); + this.applications.splice(idx, 1); + } + } + + protected readonly Application_States = Application_States; +} diff --git a/src/app/applications/application-vo-actions/application-vo-actions.component.html b/src/app/applications/application-vo-actions/application-vo-actions.component.html new file mode 100644 index 0000000000..c400edc050 --- /dev/null +++ b/src/app/applications/application-vo-actions/application-vo-actions.component.html @@ -0,0 +1,341 @@ +@if (is_vo_admin) { + + + @if ((application | hasstatusinlist: Application_States.SUBMITTED) || + (application | hasstatusinlist: Application_States.CONFIRMATION_DECLINED) || + ((application | hasstatusinlist: Application_States.WAIT_FOR_CONFIRMATION) && + !application?.project_application_compute_center?.FacilityId)) { + + } @else if (application?.project_application_compute_center?.Name) { + {{ application?.project_application_compute_center?.Name }} + } + + +
+ + + @if (application?.processing_vo_initials && is_vo_admin) { + + + } + @if (!application?.processing_vo_initials && is_vo_admin) { + + } + + + @switch (tabState) { + + @case (ApplicationTabStates.SUBMITTED) { + + @if ((application | hasstatusinlist: Application_States.SUBMITTED) && + !application?.project_application_openstack_project) { + + } + @if (application | hasstatusinlist: Application_States.SUBMITTED) { + + } + @if (application.project_application_pi_approved && + (application | hasstatusinlist: Application_States.SUBMITTED)) { + + } + @if ((application | hasstatusinlist: Application_States.CONFIRMATION_DECLINED) || + ((application | hasstatusinlist: Application_States.WAIT_FOR_CONFIRMATION) && + !application?.project_application_compute_center?.FacilityId)) { + + } + @if ((application | hasstatusinlist: Application_States.SUBMITTED) && + !(application | hasstatusinlist: Application_States.APPROVED)) { + + } + @if ((application | hasstatusinlist: Application_States.WAIT_FOR_CONFIRMATION) && + application?.project_application_compute_center?.FacilityId) { + + + } + @if ((application | hasstatusinlist: Application_States.SUBMITTED) && + application?.project_application_openstack_project) { + + } + + } + @case (ApplicationTabStates.LIFETIME_EXTENSION) { + @if ((application | hasstatusinlist: Application_States.LIFETIME_EXTENSION_REQUESTED) && + application?.project_lifetime_request) { + + + + + } + + } + @case (ApplicationTabStates.CREDITS_EXTENSION) { + + } + @case (ApplicationTabStates.MODIFICATION_EXTENSION) { + @if ((application | hasstatusinlist: Application_States.MODIFICATION_REQUESTED) && + application?.project_application_openstack_project && + application?.project_modification_request) { + + + } + + @if ((application | hasstatusinlist: Application_States.MODIFICATION_REQUESTED) && + !application?.project_application_openstack_project && + application?.project_modification_request) { + + + } + + @if ((application | hasstatusinlist: Application_States.MODIFICATION_REQUESTED) && + application?.project_modification_request) { + + } + @if ((application | hasstatusinlist: Application_States.MODIFICATION_REQUESTED) && + application?.project_modification_request) { + + } + + + } + @case (ApplicationTabStates.TERMINATION_REQUEST) { + + } + } + + + + + + + +
+ +} \ No newline at end of file diff --git a/src/app/applications/application-vo-actions/application-vo-actions.component.scss b/src/app/applications/application-vo-actions/application-vo-actions.component.scss new file mode 100644 index 0000000000..d505897736 --- /dev/null +++ b/src/app/applications/application-vo-actions/application-vo-actions.component.scss @@ -0,0 +1,3 @@ +.table-line { + display: contents; +} \ No newline at end of file diff --git a/src/app/applications/application-vo-actions/application-vo-actions.component.spec.ts b/src/app/applications/application-vo-actions/application-vo-actions.component.spec.ts new file mode 100644 index 0000000000..29b795233c --- /dev/null +++ b/src/app/applications/application-vo-actions/application-vo-actions.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ApplicationVoActionsComponent } from './application-vo-actions.component'; + +describe('ApplicationVoActionsComponent', () => { + let component: ApplicationVoActionsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ApplicationVoActionsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ApplicationVoActionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/application-vo-actions/application-vo-actions.component.ts b/src/app/applications/application-vo-actions/application-vo-actions.component.ts new file mode 100644 index 0000000000..7b1d8cf6a3 --- /dev/null +++ b/src/app/applications/application-vo-actions/application-vo-actions.component.ts @@ -0,0 +1,495 @@ +import { + Component, EventEmitter, Input, OnInit, Output, +} from '@angular/core'; +import { Subscription } from 'rxjs'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; +import { HttpStatusCode } from '@angular/common/http'; +import { ApplicationTabStates } from 'app/shared/enums/application-tab-states'; +import { ConfirmationActions } from 'app/shared/modal/confirmation_actions'; +import { AbstractBaseClass, Application_States } from '../../shared/shared_modules/baseClass/abstract-base-class'; + +import { Application } from '../application.model/application.model'; +import { ComputecenterComponent } from '../../projectmanagement/computecenter.component'; +import { is_vo } from '../../shared/globalvar'; +import { ApplicationsService } from '../../api-connector/applications.service'; +import { VoService } from '../../api-connector/vo.service'; +import { GroupService } from '../../api-connector/group.service'; +import { + AdjustLifetimeRequestComponent, +} from '../../projectmanagement/modals/adjust-lifetime/adjust-lifetime-request.component'; +import { + AdjustApplicationComponent, +} from '../../projectmanagement/modals/adjust-application/adjust-application.component'; +import { + ModificationRequestComponent, +} from '../../projectmanagement/modals/modification-request/modification-request.component'; +import { ConfirmationModalComponent } from '../../shared/modal/confirmation-modal.component'; +import { ClientLimitsComponent } from '../../vo_manager/clients/modals/client-limits..component'; +import { NotificationModalComponent } from '../../shared/modal/notification-modal'; + +@Component({ + selector: 'app-application-vo-actions', + templateUrl: './application-vo-actions.component.html', + styleUrl: './application-vo-actions.component.scss', +}) +export class ApplicationVoActionsComponent extends AbstractBaseClass implements OnInit { + private subscription: Subscription = new Subscription(); + + @Input() application: Application; + @Input() tabState: ApplicationTabStates = ApplicationTabStates.SUBMITTED; + @Input() computeCenters: ComputecenterComponent[] = []; + @Output() reloadNumbersTrigger: EventEmitter = new EventEmitter(); + @Output() removeApplicationTrigger: EventEmitter = new EventEmitter(); + @Output() switchCollapseEvent: EventEmitter = new EventEmitter(); + + bsModalRef: BsModalRef; + is_vo_admin: boolean = false; + selectedComputeCenter: ComputecenterComponent; + + ngOnInit() { + this.is_vo_admin = is_vo; + + } + + constructor( + private applicationsService: ApplicationsService, + private modalService: BsModalService, + private voService: VoService, + private groupService: GroupService, + private adjustLifeTimeExtensionModal: AdjustLifetimeRequestComponent, + private adjustApplicationModal: AdjustApplicationComponent, + ) { + super(); + } + + triggerRemoveApplication() { + + this.removeApplicationTrigger.emit(this.application.project_application_id); + } + + showAdjustLifetimeExtensionModal() { + this.adjustLifeTimeExtensionModal.showAdjustLifetimeExtensionModal(this.application).subscribe((eventSuccess: boolean) => { + if (eventSuccess) { + this.getApplication(); + this.showNotificationModal('Success', 'The lifetime of the extension request were adjusted successfully!', 'success'); + + } else { + this.showNotificationModal('Failed', 'The adjustment of the lifetime has failed!', 'danger'); + + } + }); + } + + showAdjustApplicationModal() { + this.adjustApplicationModal.showAdjustApplicationModal(this.application).subscribe((changed: boolean) => { + if (changed) { + this.getApplication(); + + this.showNotificationModal('Success', 'The resources of the application were adjusted successfully!', 'success'); + + } else { + this.showNotificationModal('Failed', 'The adjustment of the resources has failed!', 'danger'); + + } + }); + } + + showModificationAdjustmentModal() { + const initialState = { + project: this.application, + adjustment: true, + }; + + this.bsModalRef = this.modalService.show(ModificationRequestComponent, { + initialState, + class: 'modal-lg', + }); + this.subscribeToBsModalRef(); + // this.subscribeForExtensionResult(this.ExtensionRequestType.MODIFICATION); + } + + triggerReloadNumbers() { + this.reloadNumbersTrigger.emit(); + } + + setCurrentUserProcessingVoManager(application: Application): void { + if (this.is_vo_admin) { + this.voService.setCurrentUserProcessingVoManager(application.project_application_id).subscribe((res: any) => { + application.processing_vo_initials = res['processing_vo_initials']; + }); + } + } + + showConfirmationModal(action: ConfirmationActions): void { + let initialState = {}; + if (action === ConfirmationActions.APPROVE_APPLICATION) { + const application_center = !this.selectedComputeCenter.FacilityId; + initialState = { application: this.application, action, application_center }; + } else { + initialState = { + application: this.application, action, + }; + } + this.bsModalRef = this.modalService.show(ConfirmationModalComponent, { initialState, class: 'modal-lg' }); + this.subscribeToBsModalRef(); + } + + unsetProcessingVoManager(application: Application): void { + if (this.is_vo_admin) { + this.voService.unsetProcessingVoManager(application.project_application_id).subscribe(() => { + application.processing_vo_initials = null; + }); + } + } + + showClientsLimitsModal( + is_modification_request: boolean = false, + ): void { + let initialState = {}; + if (is_modification_request) { + initialState = { + compute_center_id: this.application.project_application_compute_center.FacilityId, + application: this.application, + is_modification_request, + }; + } else { + initialState = { + compute_center_id: this.selectedComputeCenter.FacilityId, + application: this.application, + is_modification_request, + }; + } + + this.bsModalRef = this.modalService.show(ClientLimitsComponent, { initialState }); + this.subscribeToBsModalRef(); + } + + removeApplicationFromFacilityConfirmation(): void { + this.groupService.removeGroupFromResource(this.application.project_application_perun_id.toString()).subscribe( + (): void => { + this.getApplication(); + this.showNotificationModal('Success', 'The application was removed from the compute center', 'success'); + }, + (): void => { + this.showNotificationModal('Failed', 'The application was removed from the compute center', 'danger'); + }, + ); + } + + getApplication(): void { + this.applicationsService.getApplication(this.application.project_application_id.toString()).subscribe( + (aj: Application): void => { + this.application = aj; + }, + (error: any): void => { + console.log(error); + }, + ); + } + + assignGroupToFacility(): void { + if (this.selectedComputeCenter) { + this.groupService.assignGroupToResource(this.application.project_application_perun_id, this.selectedComputeCenter.FacilityId).subscribe( + (): void => { + this.getApplication(); + + this.showNotificationModal('Success', 'The project was assigned to the facility.', 'success'); + }, + (error: object): void => { + console.log(error); + this.showNotificationModal('Failed', 'Project could not be created!', 'danger'); + }, + ); + } else { + this.showNotificationModal('Failed', 'You need to select an compute center!', 'danger'); + } + } + + resetApplicationPI(): void { + this.applicationsService.resetPIValidation(this.application).subscribe((app: Application) => { + this.getApplication(); + }); + } + + switchCollaps() { + this.switchCollapseEvent.emit(); + } + + showNotificationModal( + notificationModalTitle: string, + notificationModalMessage: string, + notificationModalType: string, + ) { + const initialState = { notificationModalTitle, notificationModalType, notificationModalMessage }; + if (this.bsModalRef) { + this.bsModalRef.hide(); + } + + this.bsModalRef = this.modalService.show(NotificationModalComponent, { initialState }); + this.bsModalRef.setClass('modal-lg'); + } + + createSimpleVmProjectGroup(): void { + this.showNotificationModal('Info', 'Creating Project...', 'info'); + + if (this.selectedComputeCenter && this.selectedComputeCenter.FacilityId) { + this.groupService.createGroupByApplication(this.application.project_application_id, this.selectedComputeCenter.FacilityId).subscribe( + (res: any): void => { + if (!res['client_available'] && !res['created']) { + this.showNotificationModal( + 'Failed', + `The client ${res['client_name']} has not the necessary resources left!`, + 'danger', + ); + } else { + this.showNotificationModal('Success', 'The project was created!', 'success'); + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + } + }, + (error: any): void => { + console.log(error); + const errorMessage = error && error.error === 'locked' + ? 'Project is locked and could not be created!' + : 'Project could not be created!'; + + this.showNotificationModal('Failed', errorMessage, 'danger'); + console.log(error); + }, + ); + } + } + + approveModificationRequest(): void { + this.applicationsService.approveModificationRequest(this.application.project_application_id).subscribe( + (res: Response): void => { + this.showNotificationModal('Success', 'The resource modification request was approved!', 'success'); + if (!this.application.project_application_openstack_project) { + if (res.status === HttpStatusCode.Accepted) { + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + + // this.all_applications.splice(this.all_applications.indexOf(application), 1); + } + } else { + this.getApplication(); + } + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Approval of resource modification failed!', 'danger'); + }, + ); + } + + declineModificationRequest(): void { + this.applicationsService.deleteModificationRequest(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Declined', 'The resource modification request was declined!', 'success'); + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + + this.getApplication(); + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Decline of resource modification failed!', 'danger'); + }, + ); + } + + deleteApplication(): void { + // const idx: number = this.all_applications.indexOf(application); + + this.subscription.add( + this.applicationsService.deleteApplication(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Success', 'The application has been successfully removed', 'success'); + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + + }, + (): void => { + this.updateNotificationModal('Failed', 'Application could not be removed!', true, 'danger'); + }, + ), + ); + } + + declineLifetimeExtension(): void { + this.applicationsService.deleteAdditionalLifetimeRequests(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Declined', 'The project extension was declined!', 'success'); + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Decline of project extension failed!', 'danger'); + }, + ); + } + + declineCreditExtension(): void { + this.applicationsService.deleteAdditionalCreditsRequests(this.application.project_application_id).subscribe( + (): void => { + this.showNotificationModal('Declined', 'The credit extension request was declined!', 'success'); + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Decline of credit extension failed!', 'danger'); + }, + ); + } + + declineApplication(): void { + // const idx: number = this.all_applications.indexOf(app); + this.applicationsService.declineApplication(this.application.project_application_id).subscribe( + (): void => { + const message: string = 'The Application was declined.'; + + this.showNotificationModal('Success', message, 'success'); + this.triggerRemoveApplication(); + this.triggerReloadNumbers(); + + }, + (): void => { + this.showNotificationModal('Failed', 'Application could not be declined!', 'danger'); + }, + ); + } + + approveLifetimeExtension(): void { + this.applicationsService.approveAdditionalLifetime(this.application.project_application_id).subscribe( + (res: Response): void => { + if (this.application.project_application_openstack_project) { + this.getApplication(); + this.showNotificationModal('Success', 'The request has been sent to the facility manager.', 'success'); + } else { + this.showNotificationModal('Success', 'The project has been extended!', 'success'); + } + if (!this.application.project_application_openstack_project) { + if (res.status === HttpStatusCode.Accepted) { + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + + } + } + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Project lifetime could not be extendend!', 'danger'); + }, + ); + } + + approveCreditExtension(): void { + this.applicationsService.approveAdditionalCreditsRequest(this.application.project_application_id).subscribe( + (res: Response): void => { + this.showNotificationModal('Success', 'The credit extension request was approved!', 'success'); + if (!this.application.project_application_openstack_project) { + if (res.status === HttpStatusCode.Accepted) { + this.triggerReloadNumbers(); + this.triggerRemoveApplication(); + + } + } else { + + this.getApplication(); + } + }, + (err: any): void => { + console.log('error', err.status); + this.showNotificationModal('Failed', 'Approval of credit extension failed!', 'danger'); + }, + ); + } + + createOpenStackProjectGroup(): void { + this.groupService.createGroupOpenStack(this.application.project_application_id, this.selectedComputeCenter.FacilityId).subscribe( + (result: { [key: string]: string }): void => { + if (result['Error']) { + this.showNotificationModal('Failed', result['Error'], 'danger'); + } else { + this.showNotificationModal('Success', 'The project was assigned to the facility.', 'success'); + this.getApplication(); + // this.switchApproveLocked(false); + } + }, + (error: any): void => { + const errorMessage = error && error.error === 'locked' + ? 'Project is locked and could not be created!' + : 'Project could not be created!'; + + this.showNotificationModal('Failed', errorMessage, 'danger'); + console.log(error); + }, + ); + } + + /** + * Function to listen to modal results. + */ + subscribeToBsModalRef(): void { + this.subscription.add( + this.bsModalRef.content.event.subscribe((result: any) => { + let action = null; + if ('action' in result) { + action = result['action']; + } + if ('createSimpleVM' in result) { + this.createSimpleVmProjectGroup(); + } + if (action === ConfirmationActions.APPROVE_MODIFICATION) { + this.approveModificationRequest(); + } + if ('closed' in result) { + // this.switchApproveLocked(false); + } + if (action === ConfirmationActions.DECLINE_MODIFICATION) { + this.declineModificationRequest(); + } + if (action === ConfirmationActions.DELETE_APPLICATION) { + this.deleteApplication(); + } + if (action === ConfirmationActions.DECLINE_EXTENSION) { + this.declineLifetimeExtension(); + } + if (action === ConfirmationActions.DECLINE_CREDITS) { + this.declineCreditExtension(); + } + if (action === ConfirmationActions.DECLINE_APPLICATION) { + this.declineApplication(); + } + + if (action === ConfirmationActions.APPROVE_EXTENSION) { + this.approveLifetimeExtension(); + } + if (action === ConfirmationActions.RESET_PI) { + this.resetApplicationPI(); + } + if (action === ConfirmationActions.APPROVE_CREDITS) { + this.approveCreditExtension(); + } + if (action === ConfirmationActions.APPROVE_APPLICATION) { + if (this.application.project_application_openstack_project) { + this.createOpenStackProjectGroup(); + } + } + if (action === 'adjustedModificationRequest') { + // this.isLoaded = false; + // this.changeTabState(ApplicationTabStates.MODIFICATION_EXTENSION); + } + }), + ); + } + + isCollapsed: boolean = true; + protected readonly Application_States = Application_States; + protected readonly ConfirmationActions = ConfirmationActions; + protected readonly ApplicationTabStates = ApplicationTabStates; +} diff --git a/src/app/applications/application.model/application.model.ts b/src/app/applications/application.model/application.model.ts index c07ce90a99..2577cbfc3b 100644 --- a/src/app/applications/application.model/application.model.ts +++ b/src/app/applications/application.model/application.model.ts @@ -55,7 +55,7 @@ export class Application { project_lifetime_request: ApplicationLifetimeExtension; project_modification_request: ApplicationModification; project_credit_request: ApplicationCreditRequest = null; - project_application_perun_id: number | string; + project_application_perun_id: number; project_application_total_cores: number = 0; project_application_total_ram: number = 0; project_application_initial_credits: number = 0; @@ -75,6 +75,7 @@ export class Application { dissemination: ApplicationDissemination; project_application_pi_approved: boolean; project_application_cloud_service: boolean; + project_application_kubernetes_access: boolean = false; project_application_cloud_service_develop: boolean; project_application_cloud_service_user_number: number; flavors: Flavor[] = []; diff --git a/src/app/applications/application.model/user.model.ts b/src/app/applications/application.model/user.model.ts index ab36d49c33..42fde49920 100644 --- a/src/app/applications/application.model/user.model.ts +++ b/src/app/applications/application.model/user.model.ts @@ -6,4 +6,5 @@ export class User { user_affiliations: string [] = []; elixir_id: string; email: string; + pi_project_count_total: number = 0; } diff --git a/src/app/applications/application_extension.model.ts b/src/app/applications/application_extension.model.ts index 3ec92cb6bf..0613974c9d 100644 --- a/src/app/applications/application_extension.model.ts +++ b/src/app/applications/application_extension.model.ts @@ -5,7 +5,7 @@ import { Application } from './application.model/application.model'; * Application Extension class. */ export class ApplicationLifetimeExtension { - Id: number; + id: number; project_application_id: number | string; extra_lifetime: number; diff --git a/src/app/applications/applications.component.html b/src/app/applications/applications.component.html index d1d818b6c4..4c91fe1a6a 100644 --- a/src/app/applications/applications.component.html +++ b/src/app/applications/applications.component.html @@ -6,55 +6,55 @@
@@ -62,1611 +62,235 @@ Loading applications/requests ...
- - -
+ + @if (all_applications.length <= 0 && !loading_applications) { +
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project NameShort NameDate submittedUserInstituteCompute CenterActions
- - {{ application?.project_application_name }}{{ application?.project_application_shortname }}{{ application?.project_application_date_submitted }} - {{ application?.project_application_user?.username }} - {{ application?.project_application_institute }} - - - {{ - application?.project_application_compute_center?.Name - }} - -
- - - - - - - - - - - - - -
-
- -
-
-
-
- -
- -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project NameShort NameModification submittedUserInstituteCompute CenterActions
- - {{ application?.project_application_name }}{{ application?.project_application_shortname }}{{ application?.project_modification_request?.date_submitted }} - {{ application?.project_application_user?.username }} - {{ application?.project_application_institute }} - {{ application?.project_application_compute_center?.Name }} - -
- - - - - - - - - - -
-
- -
-
-
-
- -
- -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project NameShort NameLifetime extension submittedUserInstituteCompute CenterActions
- - {{ application?.project_application_name }}{{ application?.project_application_shortname }}{{ application?.project_lifetime_request?.date_submitted }} - {{ application?.project_application_user?.username }} - {{ application?.project_application_institute }} - {{ - application?.project_application_compute_center?.Name - }} - -
- - - - - - - -
-
- -
-
-
-
- -
- -
- -
-
- - - - - - - - - - - - - - - - - - + + + +
Project NameShort NameCredits request submittedUserInstituteCompute CenterActions
+ } @else { + + } + + - - - - - - - - - -
+
+
+
+
- diff --git a/src/app/applications/applications.component.ts b/src/app/applications/applications.component.ts index 8c1450565a..4d2b052312 100644 --- a/src/app/applications/applications.component.ts +++ b/src/app/applications/applications.component.ts @@ -4,8 +4,6 @@ import { } from '@angular/core'; import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { Subscription } from 'rxjs'; -import { HttpStatusCode } from '@angular/common/http'; -import { FlavorTypeShortcuts } from 'app/shared/shared_modules/baseClass/flavor-type-shortcuts'; import { ApplicationsService } from '../api-connector/applications.service'; import { ApiSettings } from '../api-connector/api-settings.service'; import { Application } from './application.model/application.model'; @@ -22,19 +20,11 @@ import { is_vo } from '../shared/globalvar'; import { Application_States } from '../shared/shared_modules/baseClass/abstract-base-class'; import { FlavorType } from '../virtualmachines/virtualmachinemodels/flavorType'; import { CreditsService } from '../api-connector/credits.service'; -import { ClientLimitsComponent } from '../vo_manager/clients/modals/client-limits..component'; import { NotificationModalComponent } from '../shared/modal/notification-modal'; -import { ConfirmationModalComponent } from '../shared/modal/confirmation-modal.component'; -import { ModificationRequestComponent } from '../projectmanagement/modals/modification-request/modification-request.component'; import { ConfirmationActions } from '../shared/modal/confirmation_actions'; +import { ApplicationTabStates } from '../shared/enums/application-tab-states'; // eslint-disable-next-line no-shadow -enum TabStates { - 'SUBMITTED' = 0, - 'CREDITS_EXTENSION' = 3, - 'LIFETIME_EXTENSION' = 5, - 'MODIFICATION_EXTENSION' = 4, -} /** * Application Overview component. @@ -55,13 +45,11 @@ enum TabStates { }) export class ApplicationsComponent extends ApplicationBaseClassComponent implements OnInit, OnDestroy { title: string = 'Application Overview'; - tab_state: number = TabStates.SUBMITTED; - TabStates: typeof TabStates = TabStates; - selectedCenter: { [key: string]: string } = {}; + tab_state: number = ApplicationTabStates.SUBMITTED; + ApplicationTabStates: typeof ApplicationTabStates = ApplicationTabStates; ConfirmationActions = ConfirmationActions; loading_applications: boolean = false; - atLeastOneVM: boolean = false; bsModalRef: BsModalRef; subscription: Subscription = new Subscription(); @@ -72,7 +60,6 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme */ all_applications: Application[] = []; selectedApplication: Application; - adjustedApplication: Application; applications_history: Application[] = []; /** @@ -80,26 +67,12 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme */ notificationClientInfo: Client[] = []; - /** - * id of the extension status. - * - * @type {number} - */ - extension_status: number = 0; - /** - * id of Application set for deletion. - */ - numberOfExtensionRequests: number = 0; numberOfModificationRequests: number = 0; numberOfCreditRequests: number = 0; numberOfProjectApplications: number = 0; Application_States: typeof Application_States = Application_States; - reassignLocked: boolean = false; - assignLocked: boolean = false; - approveLocked: boolean = false; - /** * Constructor. * Loads all Applications if user is vo admin and all user_applications. @@ -108,20 +81,16 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme * @param userService * @param groupservice * @param modalService - * @param voService * @param facilityService * @param flavorService - * @param creditsService */ constructor( applicationsService: ApplicationsService, userService: UserService, private groupservice: GroupService, private modalService: BsModalService, - private voService: VoService, facilityService: FacilityService, private flavorService: FlavorService, - private creditsService: CreditsService, cdrRef: ChangeDetectorRef, ) { super(userService, applicationsService, facilityService, cdrRef); @@ -134,6 +103,8 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme ngOnInit(): void { this.is_vo_admin = is_vo; if (this.is_vo_admin) { + this.getApplicationNumbers(); + this.getSubmittedApplicationsAdmin(); this.getApplicationHistory(); this.getComputeCenters(); @@ -143,7 +114,6 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme this.flavorService.getListOfTypesAvailable().subscribe((availableTypes: FlavorType[]): void => { this.typeList = availableTypes; }); - this.getApplicationNumbers(); } else { this.isLoaded = true; } @@ -153,6 +123,7 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme * Getting the current numbers of all Application-Request types from the API */ getApplicationNumbers(): void { + console.log('rload numbers 3'); this.applicationsService.getExtensionRequestsCounter().subscribe((result: any): void => { this.numberOfCreditRequests = result['credits_extension_requests_all']; this.numberOfExtensionRequests = result['lifetime_extension_requests_all']; @@ -161,139 +132,6 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme }); } - /** - * Sets both the selected application and the adjusted application which serves as a model for the - * application adjustment of the VO - * @param application - */ - setApplicationToAdjust(application: Application): void { - this.selectedApplication = application; - this.adjustedApplication = new Application(application); - this.checkIfMinimumSelected(); - } - - onChangeFlavor(flavor: Flavor, value: number): void { - this.adjustedApplication.setFlavorInFlavors(flavor, value); - this.checkIfMinimumSelected(); - this.creditsService - .getCreditsForApplication(this.adjustedApplication.flavors, this.adjustedApplication.project_application_lifetime) - .toPromise() - .then((credits: number): void => { - this.adjustedApplication.project_application_initial_credits = credits; - }) - .catch((err: any): void => console.log(err)); - } - - public setFlavorInFlavors(flavor_param: Flavor, counter: number): void { - const idx: number = this.adjustedApplication.flavors.findIndex( - (fl: Flavor): boolean => fl.name === flavor_param.name, - ); - if (idx !== -1) { - if (counter > 0) { - this.adjustedApplication.flavors[idx].counter = counter; - } else { - this.adjustedApplication.flavors.splice(idx, 1); - } - } else if (counter > 0) { - const flavor: Flavor = flavor_param; - flavor.counter = counter; - this.adjustedApplication.flavors.push(flavor); - } - this.calculateRamCores(); - } - - checkIfMinimumSelected(): void { - let numberOfVMs: number = 0; - for (const fl of this.adjustedApplication.flavors) { - numberOfVMs += this.adjustedApplication.getFlavorCounter(fl); - } - this.atLeastOneVM = numberOfVMs > 0 || this.adjustedApplication.project_application_openstack_project; - } - - adjustApplication(): void { - this.applicationsService.adjustApplication(this.adjustedApplication).subscribe( - (adjustmentResult: Application): void => { - const index: number = this.all_applications.indexOf(this.selectedApplication); - this.all_applications[index] = new Application(adjustmentResult); - this.showNotificationModal('Success', 'The resources of the application were adjusted successfully!', 'success'); - }, - (): void => { - this.showNotificationModal('Failed', 'The adjustment of the resources has failed!', 'danger'); - }, - ); - } - - adjustLifetimeExtension(): void { - this.applicationsService.adjustLifetimeExtension(this.adjustedApplication.project_lifetime_request).subscribe( - (): void => { - this.showNotificationModal( - 'Success', - 'The lifetime of the extension request has been adjusted successfully!', - 'success', - ); - this.getApplicationsByTabState(); - }, - (): void => { - this.showNotificationModal('Failed', 'The adjustment of the lifetime extension has failed!', 'danger'); - }, - ); - } - - adjustModification(): void { - this.applicationsService.adjustModification(this.adjustedApplication.project_modification_request).subscribe( - (): void => { - this.showNotificationModal('Success', 'The modification request has been adjusted successfully!', 'success'); - }, - (): void => { - this.showNotificationModal('Failed', 'The adjustment of the modification request has failed!', 'danger'); - }, - ); - } - - /** - * Checks if the flavor is available for SimpleVM - * @param type - */ - checkIfTypeGotSimpleVmFlavor(type: FlavorType): boolean { - for (const flav of this.flavorList) { - if (flav.type.shortcut === type.shortcut && flav.simple_vm) { - return true; - } - } - - return false; - } - - checkIfTypeGotSimpleVMFlavorOrIsCustom(type: FlavorType): boolean { - for (const flav of this.flavorList) { - if ( - (flav.type.shortcut === type.shortcut && flav.simple_vm) - || type.shortcut === FlavorTypeShortcuts.CUSTOM_FLAVOR - ) { - return true; - } - } - - return false; - } - - /** - * Checks if the key given represents a flavor and if so returns the respective Flavor - * - * @param key the key which is checked - */ - isKeyFlavor(key: string): Flavor { - for (const fkey in this.flavorList) { - if (fkey in this.flavorList) { - if (this.flavorList[fkey].name === key.substring(20)) { - return this.flavorList[fkey]; - } - } - } - - return null; - } - changeTabState(state: number): void { if (!this.loading_applications) { this.tab_state = state; @@ -328,115 +166,22 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme } } - approveLifetimeExtension(application: Application): void { - this.applicationsService.approveAdditionalLifetime(application.project_application_id).subscribe( - (res: Response): void => { - if (application.project_application_openstack_project) { - const applicationToGet: Application = application; - applicationToGet.project_application_statuses = []; - this.getApplication(applicationToGet); - this.showNotificationModal('Success', 'The request has been sent to the facility manager.', 'success'); - } else { - this.showNotificationModal('Success', 'The project has been extended!', 'success'); - } - if (!application.project_application_openstack_project) { - if (res.status === HttpStatusCode.Accepted) { - this.getApplicationNumbers(); - this.all_applications.splice(this.all_applications.indexOf(application), 1); - } - } - }, - (err: any): void => { - console.log('error', err.status); - this.showNotificationModal('Failed', 'Project lifetime could not be extendend!', 'danger'); - }, - ); - } - - declineLifetimeExtension(application: Application): void { - this.applicationsService.deleteAdditionalLifetimeRequests(application.project_application_id).subscribe( - (): void => { - this.showNotificationModal('Declined', 'The project extension was declined!', 'success'); - this.all_applications.splice(this.all_applications.indexOf(application), 1); - this.getApplicationNumbers(); - }, - (err: any): void => { - console.log('error', err.status); - this.showNotificationModal('Failed', 'Decline of project extension failed!', 'danger'); - }, - ); - } - - approveModificationRequest(application: Application): void { - this.applicationsService.approveModificationRequest(application.project_application_id).subscribe( - (res: Response): void => { - this.showNotificationModal('Success', 'The resource modification request was approved!', 'success'); - if (!application.project_application_openstack_project) { - if (res.status === HttpStatusCode.Accepted) { - this.getApplicationNumbers(); - this.all_applications.splice(this.all_applications.indexOf(application), 1); - } - } else { - const applicationToGet: Application = application; - applicationToGet.project_application_statuses = []; - this.getApplication(applicationToGet); - } - }, - (err: any): void => { - console.log('error', err.status); - this.showNotificationModal('Failed', 'Approval of resource modification failed!', 'danger'); - }, - ); - } - - declineModificationRequest(application: Application): void { - this.applicationsService.deleteModificationRequest(application.project_application_id).subscribe( - (): void => { - this.showNotificationModal('Declined', 'The resource modification request was declined!', 'success'); - this.all_applications.splice(this.all_applications.indexOf(application), 1); - this.getApplicationNumbers(); - }, - (err: any): void => { - console.log('error', err.status); - this.showNotificationModal('Failed', 'Decline of resource modification failed!', 'danger'); - }, - ); - } - - approveCreditExtension(application: Application): void { - this.applicationsService.approveAdditionalCreditsRequest(application.project_application_id).subscribe( - (res: Response): void => { - this.showNotificationModal('Success', 'The credit extension request was approved!', 'success'); - if (!application.project_application_openstack_project) { - if (res.status === HttpStatusCode.Accepted) { - this.getApplicationNumbers(); - this.all_applications.splice(this.all_applications.indexOf(application), 1); - } - } else { - const applicationToGet: Application = application; - applicationToGet.project_application_statuses = []; - this.getApplication(applicationToGet); - } - }, - (err: any): void => { - console.log('error', err.status); - this.showNotificationModal('Failed', 'Approval of credit extension failed!', 'danger'); - }, - ); - } + setApplicationByLoadedApplication(applications: Application[]) { + this.all_applications = []; + if (applications.length === 0) { + this.isLoaded_userApplication = true; + } + for (const application of applications) { + const newApplication = new Application(application); - declineCreditExtension(application: Application): void { - this.applicationsService.deleteAdditionalCreditsRequests(application.project_application_id).subscribe( - (): void => { - this.showNotificationModal('Declined', 'The credit extension request was declined!', 'success'); - this.all_applications.splice(this.all_applications.indexOf(application), 1); - this.getApplicationNumbers(); - }, - (err: any): void => { - console.log('error', err.status); - this.showNotificationModal('Failed', 'Decline of credit extension failed!', 'danger'); - }, - ); + this.all_applications.push(newApplication); + } + this.isLoaded = true; + for (const app of this.all_applications) { + this.getFacilityProject(app); + } + this.sortApplicationsByTabState(); + this.loading_applications = false; } /** @@ -445,16 +190,7 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme getSubmittedApplicationsAdmin(): void { if (this.is_vo_admin) { this.applicationsService.getSubmittedApplications().subscribe((applications: Application[]): void => { - if (applications.length === 0) { - this.isLoaded_userApplication = true; - } - for (const application of applications) { - this.all_applications.push(new Application(application)); - } - this.isLoaded = true; - for (const app of this.all_applications) { - this.getFacilityProject(app); - } + this.setApplicationByLoadedApplication(applications); }); } } @@ -481,28 +217,28 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme */ sortApplicationsByTabState(): void { switch (this.tab_state) { - case TabStates.SUBMITTED: + case ApplicationTabStates.SUBMITTED: this.all_applications.sort( (a, b) => new Date(a.project_application_date_submitted).getTime() - new Date(b.project_application_date_submitted).getTime(), ); break; - case TabStates.LIFETIME_EXTENSION: + case ApplicationTabStates.LIFETIME_EXTENSION: this.all_applications.sort( (a, b) => new Date(a.project_lifetime_request.date_submitted).getTime() - new Date(b.project_lifetime_request.date_submitted).getTime(), ); break; - case TabStates.MODIFICATION_EXTENSION: + case ApplicationTabStates.MODIFICATION_EXTENSION: this.all_applications.sort( (a, b) => new Date(a.project_modification_request.date_submitted).getTime() - new Date(b.project_modification_request.date_submitted).getTime(), ); break; - case TabStates.CREDITS_EXTENSION: + case ApplicationTabStates.CREDITS_EXTENSION: this.all_applications.sort( (a, b) => new Date(a.project_credit_request.date_submitted).getTime() - new Date(b.project_credit_request.date_submitted).getTime(), @@ -516,19 +252,7 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme getSubmittedApplications(): void { this.applicationsService.getSubmittedApplications().subscribe((applications: Application[]): void => { - if (applications.length === 0) { - this.isLoaded_userApplication = true; - } - for (const application of applications) { - this.all_applications.push(new Application(application)); - } - for (const app of this.all_applications) { - this.getFacilityProject(app); - } - this.sortApplicationsByTabState(); - - this.isLoaded = true; - this.loading_applications = false; + this.setApplicationByLoadedApplication(applications); }); } @@ -554,19 +278,7 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme this.applicationsService .getLifetimeRequestedApplications() .subscribe((lifetime_applications: Application[]): void => { - if (lifetime_applications.length === 0) { - // bool here? - } - for (const lifetime_application of lifetime_applications) { - this.all_applications.push(new Application(lifetime_application)); - } - for (const app of this.all_applications) { - this.getFacilityProject(app); - } - this.sortApplicationsByTabState(); - - this.isLoaded = true; - this.loading_applications = false; + this.setApplicationByLoadedApplication(lifetime_applications); }); } @@ -574,19 +286,7 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme this.applicationsService .getModificationRequestedApplications() .subscribe((modification_applications: Application[]): void => { - if (modification_applications.length === 0) { - // bool here? - } - for (const modification_application of modification_applications) { - this.all_applications.push(new Application(modification_application)); - } - for (const app of this.all_applications) { - this.getFacilityProject(app); - } - this.sortApplicationsByTabState(); - - this.isLoaded = true; - this.loading_applications = false; + this.setApplicationByLoadedApplication(modification_applications); }); } @@ -594,106 +294,18 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme this.loading_applications = true; if (this.is_vo_admin) { this.clearApplicationLists(); - if (this.tab_state === TabStates.SUBMITTED) { + if (this.tab_state === ApplicationTabStates.SUBMITTED) { this.getSubmittedApplications(); - } else if (this.tab_state === TabStates.CREDITS_EXTENSION) { + } else if (this.tab_state === ApplicationTabStates.CREDITS_EXTENSION) { this.getCreditsExtensionRequests(); - } else if (this.tab_state === TabStates.LIFETIME_EXTENSION) { + } else if (this.tab_state === ApplicationTabStates.LIFETIME_EXTENSION) { this.getLifetimeExtensionRequests(); - } else if (this.tab_state === TabStates.MODIFICATION_EXTENSION) { + } else if (this.tab_state === ApplicationTabStates.MODIFICATION_EXTENSION) { this.getModificationRequests(); } } } - /** - * Get all Applications if user is admin. - */ - getAllApplications(): void { - if (this.is_vo_admin) { - this.applicationsService.getAllApplications().subscribe((applications: Application[]): void => { - if (applications.length === 0) { - this.isLoaded_userApplication = true; - } - for (const application of applications) { - this.all_applications.push(new Application(application)); - } - this.isLoaded = true; - for (const app of this.all_applications) { - this.getFacilityProject(app); - } - }); - } - } - - /** - * Updates an application with the actual values. - * - * @param application - */ - public getApplication(application: Application): void { - const index: number = this.all_applications.indexOf(application); - - this.applicationsService.getApplication(application.project_application_id.toString()).subscribe( - (aj: Application): void => { - const newApp: Application = aj; - this.all_applications[index] = newApp; - this.getFacilityProject(newApp); - }, - (error: any): void => { - console.log(error); - }, - ); - } - - /** - * Remove Application from facility , where it is for confirmation - * - * @param application the application - */ - removeApplicationFromFacilityConfirmation(application: Application): void { - this.groupservice.removeGroupFromResource(application.project_application_perun_id.toString()).subscribe( - (): void => { - this.getApplication(application); - this.switchReassignLocked(false); - this.showNotificationModal('Success', 'The application was removed from the compute center', 'success'); - }, - (): void => { - this.showNotificationModal('Failed', 'The application was removed from the compute center', 'danger'); - }, - ); - } - - /** - * Create a new Group in perun with the specific attributes. - * - * @param application - * @param compute_center - */ - public createOpenStackProjectGroup(application: Application, compute_center: string): void { - this.groupservice.createGroupOpenStack(application.project_application_id, compute_center).subscribe( - (result: { [key: string]: string }): void => { - if (result['Error']) { - this.showNotificationModal('Failed', result['Error'], 'danger'); - } else { - const applicationToGet: Application = application; - applicationToGet.project_application_statuses = []; - this.showNotificationModal('Success', 'The project was assigned to the facility.', 'success'); - this.getApplication(applicationToGet); - this.switchApproveLocked(false); - } - }, - (error: any): void => { - const errorMessage = error && error.error === 'locked' - ? 'Project is locked and could not be created!' - : 'Project could not be created!'; - - this.showNotificationModal('Failed', errorMessage, 'danger'); - console.log(error); - }, - ); - } - public resetNotificationModal(): void { this.notificationModalTitle = 'Notification'; this.notificationModalMessage = 'Please wait...'; @@ -702,36 +314,6 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme this.notificationClientInfo = []; } - /** - * Bugfix not scrollable site after closing modal - */ - removeModalOpen(): void { - document.body.classList.remove('modal-open'); - } - - showConfirmationModal(application: Application, action: ConfirmationActions): void { - let initialState = {}; - if (action === ConfirmationActions.APPROVE_APPLICATION) { - const application_center = this.selectedCenter[application.project_application_id]; - initialState = { application, action, application_center }; - } else { - initialState = { application, action }; - } - this.bsModalRef = this.modalService.show(ConfirmationModalComponent, { initialState, class: 'modal-lg' }); - this.subscribeToBsModalRef(); - } - - showClientsLimitsModal( - compute_center_id: string, - application: Application, - is_modification_request: boolean = false, - ): void { - const initialState = { compute_center_id, application, is_modification_request }; - - this.bsModalRef = this.modalService.show(ClientLimitsComponent, { initialState }); - this.subscribeToBsModalRef(); - } - showNotificationModal( notificationModalTitle: string, notificationModalMessage: string, @@ -745,279 +327,4 @@ export class ApplicationsComponent extends ApplicationBaseClassComponent impleme this.bsModalRef = this.modalService.show(NotificationModalComponent, { initialState }); this.bsModalRef.setClass('modal-lg'); } - - showModificationAdjustmentModal() { - this.adjustedApplication = this.selectedApplication; - const initialState = { - project: this.adjustedApplication, - adjustment: true, - }; - - this.bsModalRef = this.modalService.show(ModificationRequestComponent, { - initialState, - class: 'modal-lg', - }); - this.subscribeToBsModalRef(); - // this.subscribeForExtensionResult(this.ExtensionRequestType.MODIFICATION); - } - - /** - * Function to listen to modal results. - */ - subscribeToBsModalRef(): void { - this.subscription.add( - this.bsModalRef.content.event.subscribe((result: any) => { - let action = null; - if ('action' in result) { - action = result['action']; - } - if ('createSimpleVM' in result) { - this.createSimpleVmProjectGroup(result['application'], result['compute_center_id']); - } - if (action === ConfirmationActions.APPROVE_MODIFICATION) { - this.approveModificationRequest(result['application']); - } - if ('closed' in result) { - this.switchApproveLocked(false); - } - if (action === ConfirmationActions.DECLINE_MODIFICATION) { - this.declineModificationRequest(result['application']); - } - if (action === ConfirmationActions.DELETE_APPLICATION) { - this.deleteApplication(result['application']); - } - if (action === ConfirmationActions.DECLINE_EXTENSION) { - this.declineLifetimeExtension(result['application']); - } - if (action === ConfirmationActions.DECLINE_CREDITS) { - this.declineCreditExtension(result['application']); - } - if (action === ConfirmationActions.DECLINE_APPLICATION) { - this.declineApplication(result['application']); - } - - if (action === ConfirmationActions.APPROVE_EXTENSION) { - this.approveLifetimeExtension(result['application']); - } - if (action === ConfirmationActions.APPROVE_CREDITS) { - this.approveCreditExtension(result['application']); - } - if (action === ConfirmationActions.APPROVE_APPLICATION) { - const tmp_application: Application = result['application']; - if (tmp_application.project_application_openstack_project) { - this.createOpenStackProjectGroup(result['application'], result['selectedCenter']); - this.switchApproveLocked(true); - } - } - if (action === 'adjustedModificationRequest') { - this.isLoaded = false; - this.changeTabState(TabStates.MODIFICATION_EXTENSION); - } - }), - ); - } - - public createSimpleVmProjectGroup(app: Application, compute_center_id?: string): void { - this.showNotificationModal('Info', 'Creating Project...', 'info'); - - const application_id: string = app.project_application_id as string; - if (compute_center_id && compute_center_id !== 'undefined') { - this.groupservice.createGroupByApplication(application_id, compute_center_id).subscribe( - (res: any): void => { - if (!res['client_available'] && !res['created']) { - this.showNotificationModal( - 'Failed', - `The client ${res['client_name']} has not the necessary resources left!`, - 'danger', - ); - } else { - this.all_applications.splice(this.all_applications.indexOf(app), 1); - this.getApplicationNumbers(); - this.showNotificationModal('Success', 'The project was created!', 'success'); - this.switchApproveLocked(false); - // this.reloadApplicationList(application_id) - } - }, - (error: any): void => { - console.log(error); - const errorMessage = error && error.error === 'locked' - ? 'Project is locked and could not be created!' - : 'Project could not be created!'; - - this.showNotificationModal('Failed', errorMessage, 'danger'); - console.log(error); - }, - ); - } else { - if (this.computeCenters.length > 0) { - this.roundRobinCreateSimpleVmProjectGroup(app); - } else { - this.showNotificationModal('Failed', 'Project could not be created!', 'danger'); - this.approveLocked = false; - } - } - } - - deleteApplication(application: Application): void { - const idx: number = this.all_applications.indexOf(application); - - this.subscription.add( - this.applicationsService.deleteApplication(application.project_application_id).subscribe( - (): void => { - this.showNotificationModal('Success', 'The application has been successfully removed', 'success'); - this.all_applications.splice(idx, 1); - this.getApplicationNumbers(); - }, - (): void => { - this.updateNotificationModal('Failed', 'Application could not be removed!', true, 'danger'); - }, - ), - ); - } - - roundRobinCreateSimpleVmProjectGroup(application: Application): void { - const application_id: string = application.project_application_id as string; - this.groupservice.createGroupByApplication(application_id, undefined).subscribe( - (res: any): void => { - if (!res['client_available'] && !res['created']) { - this.showNotificationModal( - 'Failed', - 'Project could not be created as no clients with necessary resources are available.', - 'danger', - ); - this.switchApproveLocked(false); - } else { - this.showNotificationModal('Success', `The project was created in ${res['client']} !`, 'success'); - this.all_applications = []; - this.getSubmittedApplicationsAdmin(); - this.applicationsService.getExtensionRequestsCounter().subscribe((result: any): void => { - this.numberOfProjectApplications = result['applications_submitted_vo']; - }); - this.switchApproveLocked(false); - } - }, - (error: any): void => { - const errorMessage = error && error.error === 'locked' - ? 'Project is locked and could not be created!' - : 'Project could not be created!'; - - this.showNotificationModal('Failed', errorMessage, 'danger'); - console.log(error); - }, - ); - } - - resetApplicationPI(application: Application): void { - this.applicationsService.resetPIValidation(application).subscribe((app: Application) => { - this.all_applications[this.all_applications.indexOf(application)] = new Application(app); - }); - } - - assignGroupToFacility(group_id: string, application_id: string, compute_center: string): void { - if (compute_center !== 'undefined') { - this.groupservice.assignGroupToResource(group_id, compute_center).subscribe( - (): void => { - for (const app of this.all_applications) { - if (app.project_application_id.toString() === application_id.toString()) { - this.getApplication(app); - break; - } - } - this.switchAssignLocked(false); - this.showNotificationModal('Success', 'The project was assigned to the facility.', 'success'); - }, - (error: object): void => { - console.log(error); - this.showNotificationModal('Failed', 'Project could not be created!', 'danger'); - }, - ); - } else { - this.showNotificationModal('Failed', 'You need to select an compute center!', 'danger'); - } - } - - /** - * Decline an application. - * - * @param app - */ - - public declineApplication(app: Application): void { - const idx: number = this.all_applications.indexOf(app); - this.applicationsService.declineApplication(app.project_application_id).subscribe( - (): void => { - const message: string = 'The Application was declined.'; - - this.showNotificationModal('Success', message, 'success'); - this.all_applications.splice(idx, 1); - this.getApplicationNumbers(); - }, - (): void => { - this.showNotificationModal('Failed', 'Application could not be declined!', 'danger'); - this.changeTabState(this.tab_state); - }, - ); - } - - setCurrentUserProcessingVoManager(application: Application): void { - if (this.is_vo_admin) { - this.voService.setCurrentUserProcessingVoManager(application.project_application_id).subscribe((res: any) => { - application.processing_vo_initials = res['processing_vo_initials']; - }); - } - } - - unsetProcessingVoManager(application: Application): void { - if (this.is_vo_admin) { - this.voService.unsetProcessingVoManager(application.project_application_id).subscribe(() => { - application.processing_vo_initials = null; - }); - } - } - - switchReassignLocked(check: boolean): void { - this.reassignLocked = check; - } - - switchAssignLocked(check: boolean): void { - this.assignLocked = check; - } - - switchApproveLocked(check: boolean): void { - this.approveLocked = check; - } - - togglePersonalDataType(checked: boolean, data_type: string) { - switch (data_type) { - case 'person_related': { - if (!checked) { - this.adjustedApplication.project_application_no_personal_data = false; - this.adjustedApplication.project_application_nonsensitive_data = false; - this.adjustedApplication.project_application_sensitive_data = false; - } - break; - } - case 'no_personal_data': { - if (checked) { - this.adjustedApplication.project_application_nonsensitive_data = false; - this.adjustedApplication.project_application_sensitive_data = false; - } - break; - } - case 'nonsensitive': { - if (checked) { - this.adjustedApplication.project_application_no_personal_data = false; - } - break; - } - case 'sensitive': { - if (checked) { - this.adjustedApplication.project_application_no_personal_data = false; - } - break; - } - default: - break; - } - } } diff --git a/src/app/applications/applications.module.ts b/src/app/applications/applications.module.ts index 5dceea3243..15f3403727 100644 --- a/src/app/applications/applications.module.ts +++ b/src/app/applications/applications.module.ts @@ -6,7 +6,8 @@ import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { ModalModule } from 'ngx-bootstrap/modal'; import { NgSelectModule } from '@ng-select/ng-select'; -import { BadgeModule } from '@coreui/angular'; +import { BadgeModule, TableDirective } from '@coreui/angular'; +import { TooltipModule } from 'ngx-bootstrap/tooltip'; import { ApplicationsComponent } from './applications.component'; import { ApplicationsRoutingModule } from './applications-routing.module'; import { AddsimplevmComponent } from './addsimplevm.component'; @@ -22,10 +23,20 @@ import { InformationDetailComponent } from './application-detail/information-det import { AdjustmentDetailComponent } from './application-detail/adjustment-detail/adjustment-detail.component'; import { ResourceDetailComponent } from './application-detail/resource-detail/resource-detail.component'; import { ModificationDetailComponent } from './application-detail/modification-detail/modification-detail.component'; -import { CreditsExtensionDetailComponent } from './application-detail/credits-extension-detail/credits-extension-detail.component'; -import { LifetimeExtensionDetailComponent } from './application-detail/lifetime-extension-detail/lifetime-extension-detail.component'; +import { + CreditsExtensionDetailComponent, +} from './application-detail/credits-extension-detail/credits-extension-detail.component'; +import { + LifetimeExtensionDetailComponent, +} from './application-detail/lifetime-extension-detail/lifetime-extension-detail.component'; import { NewsModule } from '../news/news.module'; import { SharedModuleModule } from '../shared/shared_modules/shared-module.module'; +import { ApplicationListComponent } from './application-list/application-list.component'; +import { ApplicationCardComponent } from './application-card/application-card.component'; +import { ApplicationVoActionsComponent } from './application-vo-actions/application-vo-actions.component'; +import { + ApplicationFacilityActionsComponent, +} from './application-facility-actions/application-facility-actions.component'; /** * Applications Module. @@ -44,6 +55,8 @@ import { SharedModuleModule } from '../shared/shared_modules/shared-module.modul NgSelectModule, BadgeModule, SharedModuleModule, + TableDirective, + TooltipModule, ], declarations: [ ApplicationsComponent, @@ -60,7 +73,13 @@ import { SharedModuleModule } from '../shared/shared_modules/shared-module.modul CreditsExtensionDetailComponent, LifetimeExtensionDetailComponent, AdjustmentDetailComponent, + ApplicationListComponent, + ApplicationCardComponent, + ApplicationVoActionsComponent, + ApplicationFacilityActionsComponent, + ], - exports: [ApplicationDetailComponent], + exports: [ApplicationDetailComponent, ApplicationListComponent], }) -export class ApplicationsModule {} +export class ApplicationsModule { +} diff --git a/src/app/facility_manager/facility.application.component.html b/src/app/facility_manager/facility.application.component.html index 1a9c8a5bd8..149fdc9b90 100644 --- a/src/app/facility_manager/facility.application.component.html +++ b/src/app/facility_manager/facility.application.component.html @@ -88,772 +88,23 @@ Loading applications/requests ... - - -
- -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project NameShort NameDate submittedUserInstituteActions
- - {{ application?.project_application_name }}{{ application?.project_application_shortname }}{{ application?.project_application_date_submitted }} - {{ application?.project_application_user?.username }} - {{ application?.project_application_institute }} -
- - - - - -
-
- -
-
- - -
-
- -
+ + @if (allApplicationsToCheck.length <= 0 && !loadingApplications) { +
- -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project NameShort NameDate submittedUserInstituteActions
- - - - - - - - - - - - - - - - {{ application?.project_application_name }}{{ application?.project_application_shortname }}{{ application?.project_application_date_submitted }} - {{ application?.project_application_user?.username }} - {{ application?.project_application_institute }} -
- - - - - -
-
- -
-
- - -
-
- -
- -
- -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project NameShort NameDate submittedUserInstituteActions
- - - - - - - - - - - - - - - - {{ application?.project_application_name }}{{ application?.project_application_shortname }}{{ application?.project_application_date_submitted }} - {{ application?.project_application_user?.username }} - {{ application?.project_application_institute }} -
- - - - - -
-
- - -
-
- - -
-
- -
- -
- -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project NameShort NameDate submittedUserInstituteActions
- - - - - - - - - - - - - - - - {{ application?.project_application_name }}{{ application?.project_application_shortname }}{{ application?.project_application_date_submitted }} - {{ application?.project_application_user?.username }} - {{ application?.project_application_institute }} -
- - - - - -
-
- -
-
- - -
-
- -
- -
- -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project NameShort NameDate submittedUserInstituteActions
- - - - - - - - - - - - - - - - {{ application?.project_application_name }}{{ application?.project_application_shortname }}{{ application?.project_application_date_submitted }} - {{ application?.project_application_user?.username }} - {{ application?.project_application_institute }} -
- - - - - -
-
- -
-
- - -
-
+ } @else { + + }
@@ -918,10 +169,10 @@ Information + type="button" + class="btn btn-secondary"> + Remove Application + @@ -958,91 +209,6 @@ - - - - - -
{ - this.setApproveLocked(false); - this.updateNotificationModal('Success', 'Successfully approved extension!', true, 'success'); - this.allApplicationsToCheck.splice(this.allApplicationsToCheck.indexOf(app), 1); - this.numberOfExtensionRequests -= 1; - this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); - }, - (): void => { - this.setApproveLocked(false); - this.updateNotificationModal('Failed', 'The approval of the extension request has failed.', true, 'danger'); - }, - ); - } - - /** - * Decline an extension request. - * - * @param application_id - */ - public declineExtension(app: Application): void { - this.applicationsService.declineAdditionalLifetime(app.project_application_id).subscribe( - (): void => { - this.updateNotificationModal('Success', 'Successfully declined extension!', true, 'success'); - this.allApplicationsToCheck.splice(this.allApplicationsToCheck.indexOf(app), 1); - this.numberOfExtensionRequests -= 1; - this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); - }, - (): void => { - this.updateNotificationModal('Failed', 'The decline of the extension request has failed.', true, 'danger'); - }, - ); - } - - public approveModification(app: Application): void { - this.setApproveLocked(true); - this.applicationsService.approveModificationRequest(app.project_application_id).subscribe( - (): void => { - this.setApproveLocked(false); - this.updateNotificationModal('Success', 'Successfully approved modification!', true, 'success'); - this.allApplicationsToCheck.splice(this.allApplicationsToCheck.indexOf(app), 1); - this.numberOfModificationRequests -= 1; - this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); - }, - (): void => { - this.setApproveLocked(false); - this.updateNotificationModal('Failed', 'The approval of the modification request has failed.', true, 'danger'); - }, - ); - } - - public declineModification(app: Application): void { - this.applicationsService.declineModificationRequest(app.project_application_id).subscribe( - (): void => { - this.updateNotificationModal('Success', 'Successfully declined modification!', true, 'success'); - this.allApplicationsToCheck.splice(this.allApplicationsToCheck.indexOf(app), 1); - this.numberOfModificationRequests -= 1; - this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); - }, - (): void => { - this.updateNotificationModal('Failed', 'The decline of the modification request has failed.', true, 'danger'); - }, - ); - } - - public approveCreditRequest(app: Application): void { - this.setApproveLocked(true); - this.applicationsService.approveAdditionalCreditsRequest(app.project_application_id).subscribe( - (): void => { - this.setApproveLocked(false); - this.updateNotificationModal('Success', 'Successfully approved credit extension!', true, 'success'); - this.allApplicationsToCheck.splice(this.allApplicationsToCheck.indexOf(app), 1); - this.numberOfCreditRequests -= 1; - this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); - }, - (): void => { - this.setApproveLocked(false); - this.updateNotificationModal('Failed', 'The approval of the credit request has failed.', true, 'danger'); - }, - ); - } - - public declineCreditRequest(app: Application): void { - this.applicationsService.declineAdditionalCredits(app.project_application_id).subscribe( - (): void => { - this.updateNotificationModal('Success', 'Successfully declined credit extension!', true, 'success'); - this.allApplicationsToCheck.splice(this.allApplicationsToCheck.indexOf(app), 1); - this.numberOfCreditRequests -= 1; - this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); - }, - (): void => { - this.updateNotificationModal('Failed', 'The decline of the credit request has failed.', true, 'danger'); - }, - ); - } - - approveTermination(app: Application): void { - this.facilityService - .approveTerminationByFM(app.project_application_perun_id, this.selectedFacility['FacilityId']) - .subscribe( - (): void => { - this.allApplicationsToCheck.splice(this.allApplicationsToCheck.indexOf(app), 1); - this.numberOfTerminationRequests -= 1; - this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); - 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'); - } - }, - ); - } - - declineTermination(app: Application): void { - this.facilityService - .declineTerminationByFM(app.project_application_perun_id, this.selectedFacility['FacilityId']) - .subscribe( - (): void => { - this.allApplicationsToCheck.splice(this.allApplicationsToCheck.indexOf(app), 1); - this.numberOfTerminationRequests -= 1; - this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); - this.updateNotificationModal('Success', 'The termination of the project was declined.', true, 'success'); - }, - (error: any): void => { - if (error['status'] === 409) { - this.updateNotificationModal( - 'Failed', - `The decline of the project was not successful. Reason: ${error['error']['reason']} for ${error['error']['openstackid']}`, - true, - 'danger', - ); - } else { - this.updateNotificationModal('Failed', 'The decline of the project failed.', true, 'danger'); - } - }, - ); - } - - /** - * Approves an application. - * - * @param app: Application - */ - approveApplication(app: Application): void { - this.setApproveLocked(true); - - this.updateNotificationModal('Approving Application', 'Waiting..', true, 'info'); - this.facilityService - .approveFacilityApplication(this.selectedFacility['FacilityId'], app.project_application_id) - .subscribe( - (): void => { - this.setApproveLocked(false); - this.updateNotificationModal('Success', 'Successfully approved the application.', true, 'success'); - this.allApplicationsToCheck.splice(this.allApplicationsToCheck.indexOf(app), 1); - this.numberOfProjectApplications -= 1; - this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); - }, - (): void => { - this.setApproveLocked(false); - this.updateNotificationModal('Failed', 'Failed to approve the application.', true, 'danger'); - }, - ); - } - - /** - * Declines an Application. - * - * @param application_id - */ - declineApplication(app: Application): void { - this.updateNotificationModal('Decline Application', 'Waiting..', true, 'info'); - - this.facilityService - .declineFacilityApplication( - this.selectedFacility['FacilityId'], - parseInt(app.project_application_id.toString(), 10), - ) - .subscribe( - (): void => { - this.updateNotificationModal('Success', 'Successfully declined the application.', true, 'success'); - this.allApplicationsToCheck.splice(this.allApplicationsToCheck.indexOf(app), 1); - this.numberOfProjectApplications -= 1; - this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); - }, - (): void => { - this.updateNotificationModal('Failed', 'Failed to decline the application.', true, 'danger'); - }, - ); - } - /** * If the selected facility changes, reload the applicatins. * @@ -412,19 +212,23 @@ export class FacilityApplicationComponent extends ApplicationBaseClassComponent } } + getApplicationNumbers() { + this.facilityService + .getExtensionRequestsCounterFacility(this.selectedFacility['FacilityId']) + .subscribe((res: any): void => { + this.numberOfCreditRequests = res['credits_extension_requests']; + this.numberOfExtensionRequests = res['lifetime_extension_requests']; + this.numberOfModificationRequests = res['modification_requests']; + this.numberOfProjectApplications = res['applications_submitted']; + this.numberOfTerminationRequests = res['termination_requests']; + }); + } + ngOnInit(): void { this.facilityService.getManagerFacilities().subscribe((result: any): void => { this.managerFacilities = result; this.selectedFacility = this.managerFacilities[0]; - this.facilityService - .getExtensionRequestsCounterFacility(this.selectedFacility['FacilityId']) - .subscribe((res: any): void => { - this.numberOfCreditRequests = res['credits_extension_requests']; - this.numberOfExtensionRequests = res['lifetime_extension_requests']; - this.numberOfModificationRequests = res['modification_requests']; - this.numberOfProjectApplications = res['applications_submitted']; - this.numberOfTerminationRequests = res['termination_requests']; - }); + this.getApplicationNumbers(); this.changeTabState(TabStates.SUBMITTED); this.isLoaded = true; @@ -432,8 +236,4 @@ export class FacilityApplicationComponent extends ApplicationBaseClassComponent this.getAllApplicationsHistory(this.selectedFacility['FacilityId']); }); } - - setApproveLocked(locked: boolean): void { - this.approveLocked = locked; - } } diff --git a/src/app/projectmanagement/modals/adjust-application/adjust-application.component.html b/src/app/projectmanagement/modals/adjust-application/adjust-application.component.html new file mode 100644 index 0000000000..92f8b8ac77 --- /dev/null +++ b/src/app/projectmanagement/modals/adjust-application/adjust-application.component.html @@ -0,0 +1,482 @@ + + diff --git a/src/app/projectmanagement/modals/adjust-application/adjust-application.component.ts b/src/app/projectmanagement/modals/adjust-application/adjust-application.component.ts new file mode 100644 index 0000000000..f3662ee6c1 --- /dev/null +++ b/src/app/projectmanagement/modals/adjust-application/adjust-application.component.ts @@ -0,0 +1,192 @@ +import { + Component, EventEmitter, Injectable, OnInit, Output, +} from '@angular/core'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; +import { Application } from '../../../applications/application.model/application.model'; +import { CreditsService } from '../../../api-connector/credits.service'; +import { ApplicationsService } from '../../../api-connector/applications.service'; +import { Flavor } from '../../../virtualmachines/virtualmachinemodels/flavor'; +import { FlavorType } from '../../../virtualmachines/virtualmachinemodels/flavorType'; +import { FlavorService } from '../../../api-connector/flavor.service'; +import { FlavorTypeShortcuts } from '../../../shared/shared_modules/baseClass/flavor-type-shortcuts'; + +@Injectable({ providedIn: 'root' }) +@Component({ + selector: 'app-application', + templateUrl: './adjust-application.component.html', + providers: [ApplicationsService], +}) +export class AdjustApplicationComponent implements OnInit { + bsModalRef = BsModalRef; + loaded: boolean = false; + loadedFlavorTypes: boolean = false; + loadedFlavors: boolean = false; + modalId: number | string | undefined; + totalNumberOfCores: number = 0; + newFlavors: { + [id: string]: { + counter: number + flavor: Flavor + } + } = {}; + totalRAM: number = 0; + atLeastOneVM: boolean = false; + + totalGPU: number = 0; + typeList: FlavorType[]; + flavorList: Flavor[] = []; + + application: Application; + adjustedApplication: Application; + FlavorTypeShortcuts: typeof FlavorTypeShortcuts = FlavorTypeShortcuts; + + @Output() eventSuccess: EventEmitter = new EventEmitter(); + + constructor( + private modalService: BsModalService, + private applicationsService: ApplicationsService, + private creditsService: CreditsService, + private flavorService: FlavorService, + ) { + } + + ngOnInit() { + this.loaded = false; + this.loadedFlavorTypes = false; + this.loadedFlavors = false; + this.adjustedApplication = new Application(this.application); + this.getAvailableFlavorTypes(); + this.getAvailableFlavors(); + } + + getAvailableFlavors() { + this.flavorService.getListOfFlavorsAvailable(undefined, undefined, true).subscribe((flavList: Flavor[]): void => { + this.flavorList = flavList; + this.loadedFlavors = true; + this.loaded = this.loadedFlavorTypes && this.loadedFlavors; + + }); + } + + getAvailableFlavorTypes() { + this.flavorService.getListOfTypesAvailable().subscribe((availableTypes: FlavorType[]): void => { + this.typeList = availableTypes; + this.loadedFlavorTypes = true; + this.loaded = this.loadedFlavorTypes && this.loadedFlavors; + }); + } + + calculateRamCores(): void { + this.totalNumberOfCores = 0; + this.totalRAM = 0; + this.totalGPU = 0; + // tslint:disable-next-line:forin + for (const extensionFlavorsKey in this.newFlavors) { + const fl: any = this.newFlavors[extensionFlavorsKey]; + this.totalRAM += fl.flavor.ram_gib * fl.counter; + this.totalNumberOfCores += fl.flavor.vcpus * fl.counter; + this.totalGPU += fl.flavor.gpu * fl.counter; + } + } + + checkIfTypeGotSimpleVMFlavorOrIsCustom(type: FlavorType): boolean { + for (const flav of this.flavorList) { + if ( + (flav.type.shortcut === type.shortcut && flav.simple_vm) + || type.shortcut === FlavorTypeShortcuts.CUSTOM_FLAVOR + ) { + return true; + } + } + + return false; + } + + togglePersonalDataType(checked: boolean, data_type: string) { + switch (data_type) { + case 'person_related': { + if (!checked) { + this.adjustedApplication.project_application_no_personal_data = false; + this.adjustedApplication.project_application_nonsensitive_data = false; + this.adjustedApplication.project_application_sensitive_data = false; + } + break; + } + case 'no_personal_data': { + if (checked) { + this.adjustedApplication.project_application_nonsensitive_data = false; + this.adjustedApplication.project_application_sensitive_data = false; + } + break; + } + case 'nonsensitive': { + if (checked) { + this.adjustedApplication.project_application_no_personal_data = false; + } + break; + } + case 'sensitive': { + if (checked) { + this.adjustedApplication.project_application_no_personal_data = false; + } + break; + } + default: + break; + } + } + + hide(): void { + this.modalService.hide(this.modalId); + } + + checkIfMinimumSelected(): void { + let numberOfVMs: number = 0; + for (const fl of this.adjustedApplication.flavors) { + numberOfVMs += this.adjustedApplication.getFlavorCounter(fl); + } + this.atLeastOneVM = numberOfVMs > 0 || this.adjustedApplication.project_application_openstack_project; + } + + onChangeFlavor(flavor: Flavor, value: number): void { + this.adjustedApplication.setFlavorInFlavors(flavor, value); + this.checkIfMinimumSelected(); + this.creditsService + .getCreditsForApplication(this.adjustedApplication.flavors, this.adjustedApplication.project_application_lifetime) + .toPromise() + .then((credits: number): void => { + this.adjustedApplication.project_application_initial_credits = credits; + }) + .catch((err: any): void => console.log(err)); + } + + showAdjustApplicationModal(application: Application): EventEmitter { + const initialState = { + application, + }; + const bsModalRef: BsModalRef = this.modalService.show(AdjustApplicationComponent, { initialState }); + bsModalRef.setClass('modal-lg'); + this.modalId = bsModalRef.id; + + return bsModalRef.content.eventSuccess; + } + + adjustApplication(): void { + this.loaded = false; + this.applicationsService.adjustApplication(this.adjustedApplication).subscribe( + (): void => { + this.hide(); + + this.eventSuccess.emit(true); + + }, + (): void => { + this.hide(); + + this.eventSuccess.emit(false); + + }, + ); + } + +} diff --git a/src/app/projectmanagement/modals/adjust-lifetime/adjust-lifetime-request.component.html b/src/app/projectmanagement/modals/adjust-lifetime/adjust-lifetime-request.component.html new file mode 100644 index 0000000000..c4469c8380 --- /dev/null +++ b/src/app/projectmanagement/modals/adjust-lifetime/adjust-lifetime-request.component.html @@ -0,0 +1,120 @@ + + diff --git a/src/app/projectmanagement/modals/adjust-lifetime/adjust-lifetime-request.component.ts b/src/app/projectmanagement/modals/adjust-lifetime/adjust-lifetime-request.component.ts new file mode 100644 index 0000000000..5ceb390fab --- /dev/null +++ b/src/app/projectmanagement/modals/adjust-lifetime/adjust-lifetime-request.component.ts @@ -0,0 +1,68 @@ +import { + Component, EventEmitter, Injectable, OnInit, Output, +} from '@angular/core'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; +import { Application } from '../../../applications/application.model/application.model'; +import { ApplicationLifetimeExtension } from '../../../applications/application_extension.model'; + +import { ApplicationsService } from '../../../api-connector/applications.service'; + +@Injectable({ providedIn: 'root' }) +@Component({ + selector: 'app-adjust-lifetime-request', + templateUrl: './adjust-lifetime-request.component.html', + providers: [ApplicationsService], +}) +export class AdjustLifetimeRequestComponent implements OnInit { + bsModalRef = BsModalRef; + modalId: number | string | undefined; + loaded: boolean = false; + + application: Application; + adjustedApplicationLifetimeExtension: ApplicationLifetimeExtension; + @Output() eventSuccess: EventEmitter = new EventEmitter(); + + constructor( + private modalService: BsModalService, + private applicationsService: ApplicationsService, + ) { + } + + ngOnInit() { + this.loaded = false; + this.adjustedApplicationLifetimeExtension = new ApplicationLifetimeExtension(this.application.project_lifetime_request); + this.loaded = true; + } + + hide(): void { + + this.modalService.hide(this.modalId); + } + + showAdjustLifetimeExtensionModal(application: Application): EventEmitter { + const initialState = { + application, + }; + const bsModalRef: BsModalRef = this.modalService.show(AdjustLifetimeRequestComponent, { initialState }); + bsModalRef.setClass('modal-lg'); + this.modalId = bsModalRef.id; + + return bsModalRef.content.eventSuccess; + } + + adjustLifetimeExtension(): void { + this.loaded = false; + this.applicationsService.adjustLifetimeExtension(this.adjustedApplicationLifetimeExtension).subscribe((): void => { + this.hide(); + + this.eventSuccess.emit(true); + + }, (): void => { + this.hide(); + + this.eventSuccess.emit(false); + + }); + } + +} diff --git a/src/app/projectmanagement/projectmanagement.module.ts b/src/app/projectmanagement/projectmanagement.module.ts index 98811c3dc7..2b53063b0a 100644 --- a/src/app/projectmanagement/projectmanagement.module.ts +++ b/src/app/projectmanagement/projectmanagement.module.ts @@ -23,6 +23,8 @@ import { CreditsRequestComponent } from './modals/credits-request/credits-reques import { ExtensionEntryComponent } from './modals/testimonial/extension-entry.component'; import { ResultComponent } from './modals/result/result.component'; import { SharedModuleModule } from '../shared/shared_modules/shared-module.module'; +import { AdjustLifetimeRequestComponent } from './modals/adjust-lifetime/adjust-lifetime-request.component'; +import { AdjustApplicationComponent } from './modals/adjust-application/adjust-application.component'; /** * Projectmanagment module. @@ -55,6 +57,8 @@ import { SharedModuleModule } from '../shared/shared_modules/shared-module.modul ModificationRequestComponent, ResultComponent, ExtensionEntryComponent, + AdjustLifetimeRequestComponent, + AdjustApplicationComponent, ], exports: [ProjectOsDetailsComponent, ExtensionEntryComponent], }) diff --git a/src/app/shared/enums/application-tab-states.ts b/src/app/shared/enums/application-tab-states.ts new file mode 100644 index 0000000000..bdbd1b7ca5 --- /dev/null +++ b/src/app/shared/enums/application-tab-states.ts @@ -0,0 +1,7 @@ +export enum ApplicationTabStates { + 'SUBMITTED' = 0, + 'CREDITS_EXTENSION' = 1, + 'LIFETIME_EXTENSION' = 2, + 'MODIFICATION_EXTENSION' = 3, + 'TERMINATION_REQUEST' = 4, +} diff --git a/src/app/shared/modal/confirmation-modal.component.ts b/src/app/shared/modal/confirmation-modal.component.ts index 4bbd8d9801..9432f45f7b 100644 --- a/src/app/shared/modal/confirmation-modal.component.ts +++ b/src/app/shared/modal/confirmation-modal.component.ts @@ -34,6 +34,11 @@ export class ConfirmationModalComponent implements OnDestroy, OnInit { [ConfirmationActions.DECLINE_CREDITS]: { action: ConfirmationActions.DECLINE_CREDITS }, [ConfirmationActions.DECLINE_APPLICATION]: { action: ConfirmationActions.DECLINE_APPLICATION }, [ConfirmationActions.APPROVE_MODIFICATION]: { action: ConfirmationActions.APPROVE_MODIFICATION }, + [ConfirmationActions.DECLINE_TERMINATION]: { action: ConfirmationActions.DECLINE_TERMINATION }, + [ConfirmationActions.APPROVE_TERMINATION]: { action: ConfirmationActions.APPROVE_TERMINATION }, + + [ConfirmationActions.RESET_PI]: { action: ConfirmationActions.RESET_PI }, + [ConfirmationActions.APPROVE_EXTENSION]: { action: ConfirmationActions.APPROVE_EXTENSION }, [ConfirmationActions.DELETE_APPLICATION]: { action: ConfirmationActions.DELETE_APPLICATION }, @@ -76,6 +81,9 @@ export class ConfirmationModalComponent implements OnDestroy, OnInit { return 'Deletion'; case ConfirmationTypes.DECLINE: return 'Declination'; + case ConfirmationTypes.RESET_PI: + return 'Reset PI'; + default: break; } @@ -97,7 +105,7 @@ export class ConfirmationModalComponent implements OnDestroy, OnInit { message: 'Do you really want to delete the application?', }, [ConfirmationActions.DECLINE_APPLICATION]: { - title: 'Confirm declien of project application', + title: 'Confirm decline of project application', type: ConfirmationTypes.DECLINE, message: 'Do you really want to decline the project application', }, @@ -141,6 +149,21 @@ export class ConfirmationModalComponent implements OnDestroy, OnInit { type: ConfirmationTypes.ENABLE, message: 'Do you really want to enable the application', }, + [ConfirmationActions.RESET_PI]: { + title: 'Confirm reset pi', + type: ConfirmationTypes.RESET_PI, + message: 'Do you really want to reset the PI', + }, + [ConfirmationActions.APPROVE_TERMINATION]: { + title: 'Approve Termination', + type: ConfirmationTypes.APPROVE, + message: 'Do you really want to terminate this project', + }, + [ConfirmationActions.DECLINE_TERMINATION]: { + title: 'Decline Termination', + type: ConfirmationTypes.DECLINE, + message: 'Do you really want to decline the termination of this project', + }, }; const data = confirmationData[this.action]; diff --git a/src/app/shared/modal/confirmation_actions.ts b/src/app/shared/modal/confirmation_actions.ts index 91078958a9..aa14ac25ba 100644 --- a/src/app/shared/modal/confirmation_actions.ts +++ b/src/app/shared/modal/confirmation_actions.ts @@ -1,13 +1,16 @@ export enum ConfirmationActions { - DECLINE_MODIFICATION, - DECLINE_EXTENSION, - DECLINE_CREDITS, - DECLINE_APPLICATION, - APPROVE_MODIFICATION, - APPROVE_EXTENSION, - APPROVE_CREDITS, - APPROVE_APPLICATION, - DISABLE_APPLICATION, - ENABLE_APPLICATION, - DELETE_APPLICATION, + DECLINE_MODIFICATION, + DECLINE_EXTENSION, + DECLINE_CREDITS, + DECLINE_APPLICATION, + APPROVE_MODIFICATION, + APPROVE_EXTENSION, + APPROVE_CREDITS, + APPROVE_APPLICATION, + DISABLE_APPLICATION, + ENABLE_APPLICATION, + DELETE_APPLICATION, + RESET_PI, + APPROVE_TERMINATION, + DECLINE_TERMINATION } diff --git a/src/app/shared/modal/confirmation_types.ts b/src/app/shared/modal/confirmation_types.ts index fe21a25b67..88a97022d6 100644 --- a/src/app/shared/modal/confirmation_types.ts +++ b/src/app/shared/modal/confirmation_types.ts @@ -1,7 +1,8 @@ export enum ConfirmationTypes { - ENABLE, - DISABLE, - APPROVE, - DECLINE, - DELETE, + ENABLE, + DISABLE, + APPROVE, + DECLINE, + DELETE, + RESET_PI } diff --git a/src/app/shared/shared_modules/baseClass/application-base-class.component.ts b/src/app/shared/shared_modules/baseClass/application-base-class.component.ts index 4d488be035..48822f38cd 100644 --- a/src/app/shared/shared_modules/baseClass/application-base-class.component.ts +++ b/src/app/shared/shared_modules/baseClass/application-base-class.component.ts @@ -9,6 +9,7 @@ import { FlavorService } from '../../../api-connector/flavor.service'; import { FacilityService } from '../../../api-connector/facility.service'; import { UserService } from '../../../api-connector/user.service'; import { FlavorTypeShortcuts } from './flavor-type-shortcuts'; +import { User } from '../../../applications/application.model/user.model'; /** * Application base component.. @@ -20,54 +21,54 @@ import { FlavorTypeShortcuts } from './flavor-type-shortcuts'; }) export class ApplicationBaseClassComponent extends AbstractBaseClass { /** - * If all Applications are loaded, important for the loader. - * - * @type {boolean} - */ + * If all Applications are loaded, important for the loader. + * + * @type {boolean} + */ isLoaded: boolean = false; FlavorTypeShortcuts: typeof FlavorTypeShortcuts = FlavorTypeShortcuts; /** - * Selected Application. - */ + * Selected Application. + */ selectedApplication: Application; /** - * All available compute centers. - * - * @type {Array} - */ + * All available compute centers. + * + * @type {Array} + */ computeCenters: ComputecenterComponent[] = []; /** - * List of flavor types. - */ + * List of flavor types. + */ typeList: FlavorType[]; /** - * Total number of cores. - * - * @type {number} - */ + * Total number of cores. + * + * @type {number} + */ totalNumberOfCores: number = 0; /** - * Total number of ram. - * - * @type {number} - */ + * Total number of ram. + * + * @type {number} + */ totalRAM: number = 0; /** - * Total number of GPUs - */ + * Total number of GPUs + */ totalGPU: number = 0; newFlavors: { - [id: string]: { - counter: number - flavor: Flavor - } - } = {}; + [id: string]: { + counter: number + flavor: Flavor + } + } = {}; GPU_SHORTCUT = 'GPU'; HMF_SHORTCUT = 'HMF'; @@ -75,57 +76,57 @@ export class ApplicationBaseClassComponent extends AbstractBaseClass { extension_request: boolean = false; /** - * If shortname is valid. - * - * @type {boolean} - */ + * If shortname is valid. + * + * @type {boolean} + */ public wronginput: boolean = false; /** - * - */ + * + */ constantStrings: Object; /** - * List of flavors. - */ + * List of flavors. + */ flavorList: Flavor[] = []; extraResourceCommentRequired: boolean = false; /** - * If all userApplications are loaded, important for the loader. - * - * @type {boolean} - */ + * If all userApplications are loaded, important for the loader. + * + * @type {boolean} + */ isLoaded_userApplication: boolean = false; public project_application_pi_approved: boolean = false; /** - * Name of the project. - */ + * Name of the project. + */ public projectName: string; public project_application_report_allowed: boolean = false; /** - * Applications of the user viewing the Application overview. - * - * @type {Array} - */ + * Applications of the user viewing the Application overview. + * + * @type {Array} + */ user_applications: Application[] = []; constructor( - protected userService: UserService, - protected applicationsService: ApplicationsService, - protected facilityService: FacilityService, - private cdRef: ChangeDetectorRef, + protected userService: UserService, + protected applicationsService: ApplicationsService, + protected facilityService: FacilityService, + private cdRef: ChangeDetectorRef, ) { super(); } /** - * Gets all available compute centers and saves them in the computeCenters attribute. - */ + * Gets all available compute centers and saves them in the computeCenters attribute. + */ getComputeCenters(): void { this.facilityService.getComputeCenters().subscribe((result: [{ [key: string]: string }]): void => { for (const cc of result) { @@ -161,8 +162,8 @@ export class ApplicationBaseClassComponent extends AbstractBaseClass { if ( (entry?.flavor?.type?.shortcut.toUpperCase() === this.GPU_SHORTCUT - || entry?.flavor?.type?.shortcut.toUpperCase() === this.HMF_SHORTCUT) - && entry.counter > 0 + || entry?.flavor?.type?.shortcut.toUpperCase() === this.HMF_SHORTCUT) + && entry.counter > 0 ) { this.extraResourceCommentRequired = true; this.cdRef.detectChanges(); @@ -172,6 +173,48 @@ export class ApplicationBaseClassComponent extends AbstractBaseClass { } this.extraResourceCommentRequired = false; this.cdRef.detectChanges(); + + } + + getApplicationPi(application: Application) { + if (!application.project_application_pi) { + this.applicationsService.getApplicationPI(application.project_application_id).subscribe((pi: User) => { + + application.project_application_pi = pi; + + }); + } + + } + + getApplicationUser(application: Application) { + if (!application.project_application_user) { + this.applicationsService.getApplicationUser(application.project_application_id).subscribe((user: User) => { + + application.project_application_user = user; + + }); + } + } + + getExtensionUser(application: Application) { + if (application.project_lifetime_request && !application.project_lifetime_request.user) { + this.applicationsService.getLifetimeExtensionUser(application.project_application_id).subscribe((user: User) => { + + application.project_lifetime_request.user = user; + + }); + } + } + + getModificationUser(application: Application) { + if (application.project_modification_request && !application.project_modification_request.user) { + this.applicationsService.getLifetimeExtensionUser(application.project_application_id).subscribe((user: User) => { + + application.project_modification_request.user = user; + + }); + } } calculateRamCores(): void { @@ -188,11 +231,11 @@ export class ApplicationBaseClassComponent extends AbstractBaseClass { } /** - * Get details of member like name and email by elixir. - * - * @param application - * @param collapse_id - */ + * Get details of member like name and email by elixir. + * + * @param application + * @param collapse_id + */ public getMemberDetailsByElixirIdIfCollapsed(application: Application, collapse_id: string): void { if (!this.getCollapseStatus(collapse_id)) { this.getMemberDetailsByElixirId(application); @@ -213,11 +256,11 @@ export class ApplicationBaseClassComponent extends AbstractBaseClass { } /** - * Get status name by status id. - * - * @param id - * @returns - */ + * Get status name by status id. + * + * @param id + * @returns + */ public getStatusById(id: number): string { const dummy: string = 'Unknown'; if (Application_States_Strings[Application_States[id]]) { @@ -228,20 +271,20 @@ export class ApplicationBaseClassComponent extends AbstractBaseClass { } /** - * Sets the selected application. - * - * @param application - */ + * Sets the selected application. + * + * @param application + */ setSelectedApplication(application: Application): void { this.selectedApplication = application; } /** - * Uses the param types to safe the available FlavorTypes to the array typeList. - * Also it fills the array collapseList with booleans of value 'false' so all flavor-categories are shown in the application form. - * - * @param types array of all available FlavorTypes - */ + * Uses the param types to safe the available FlavorTypes to the array typeList. + * Also it fills the array collapseList with booleans of value 'false' so all flavor-categories are shown in the application form. + * + * @param types array of all available FlavorTypes + */ setListOfTypes(types: FlavorType[]): void { let index: number = -1; for (let i: number = 0; i < types.length; i += 1) { @@ -260,17 +303,17 @@ export class ApplicationBaseClassComponent extends AbstractBaseClass { } /** - * Check if short name is valid. - * - * @param shortname - */ + * Check if short name is valid. + * + * @param shortname + */ public checkShortname(shortname: string): void { this.wronginput = !/^[a-zA-Z0-9\s]*$/.test(shortname); } /** - * Fills the array constantStrings with values dependent of keys which are used to indicate inputs from the application-form - */ + * Fills the array constantStrings with values dependent of keys which are used to indicate inputs from the application-form + */ generateConstants(): void { this.constantStrings = []; this.constantStrings['project_application_shortname'] = 'Shortname: '; @@ -307,13 +350,13 @@ export class ApplicationBaseClassComponent extends AbstractBaseClass { } /** - * This function concatenates a given key combined with a given value to a string - * which is used on the confirmation-modal. - * - * @param key the key to access a string in the array constantStrings - * @param val the value that is concatenated with the string from the array and an optional addition (depending on the key) - * @returns the concatenated string for the confirmation-modal - */ + * This function concatenates a given key combined with a given value to a string + * which is used on the confirmation-modal. + * + * @param key the key to access a string in the array constantStrings + * @param val the value that is concatenated with the string from the array and an optional addition (depending on the key) + * @returns the concatenated string for the confirmation-modal + */ matchString(key: string, val: string): string { if (key in this.constantStrings) { switch (key) {