diff --git a/build.gradle b/build.gradle index b0f93a8b25f6963e6821448de89bd40a1cb6ccc0..508d65aa095ac10dfd5fe282e30b242f8b5820a8 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id "org.sonarqube" version "3.2.0" } -version = '1.6.5' +version = '1.7.0' task buildGUI(type: Exec) { println 'Building using Angular CLI' diff --git a/package-lock.json b/package-lock.json index 2158b8b4014ac3f0dbe34ebdc01a4c2d4d563d8d..881b777c79885b41a9fc397b3d9c20efa1751599 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nmaas-portal", - "version": "1.6.5", + "version": "1.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nmaas-portal", - "version": "1.6.5", + "version": "1.7.0", "license": "Apache 2.0", "dependencies": { "@angular/animations": "17.3.12", @@ -35,7 +35,7 @@ "jquery": "^3.6.0", "lodash": "^4.17.21", "ng-event-source": "^1.0.14", - "ng-recaptcha": "^13.0.0", + "ng-recaptcha": "^13.2.1", "ng-terminal": "^6.3.0", "ngx-pagination": "^6.0.3", "ngx-webstorage-service": "^5.0.0", diff --git a/package.json b/package.json index 09a8e774d77de12a925911a29ee16690f7e7194c..9402af9537167709179f216f116de9a9ef4cbefa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nmaas-portal", - "version": "1.6.5", + "version": "1.7.0", "license": "Apache 2.0", "angular-cli": {}, "scripts": { @@ -40,7 +40,7 @@ "jquery": "^3.6.0", "lodash": "^4.17.21", "ng-event-source": "^1.0.14", - "ng-recaptcha": "^13.0.0", + "ng-recaptcha": "^13.2.1", "ng-terminal": "^6.3.0", "ngx-pagination": "^6.0.3", "ngx-webstorage-service": "^5.0.0", diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 28a607296fcb770e6d847693cc44b139be7170cb..c9f6f599a90484886cf179ce005644d846eec145 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,12 +1,10 @@ /* tslint:disable:no-unused-variable */ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { AppComponent } from './app.component'; -import { RouterTestingModule} from '@angular/router/testing'; +import {TestBed, waitForAsync} from '@angular/core/testing'; +import {AppComponent} from './app.component'; +import {RouterTestingModule} from '@angular/router/testing'; import {AppConfigService, ConfigurationService} from './service'; -import {HttpClient, HttpHandler} from '@angular/common/http'; -import {TranslateService, TranslateModule, TranslateLoader, MissingTranslationHandler} from '@ngx-translate/core'; -import {TranslateFakeLoader} from '@ngx-translate/core'; +import {MissingTranslationHandler, TranslateFakeLoader, TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core'; import {Observable, of} from 'rxjs'; import {Configuration} from './model/configuration'; import {CustomMissingTranslationService} from './i18n/custommissingtranslation.service'; @@ -14,75 +12,104 @@ import {AuthService} from './auth/auth.service'; import {JwtHelperService, JwtModule} from '@auth0/angular-jwt'; import {ServiceUnavailableService} from './service-unavailable/service-unavailable.service'; import {SharedModule} from './shared'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; class MockConfigurationService { - protected uri: string; + protected uri: string; - constructor() { - this.uri = 'http://localhost/api'; - } + constructor() { + this.uri = 'http://localhost/api'; + } - public getApiUrl(): string { - return 'http://localhost/api'; - } + public getApiUrl(): string { + return 'http://localhost/api'; + } - public getConfiguration(): Observable<Configuration> { - return of<Configuration>(); - } + public getConfiguration(): Observable<Configuration> { + return of<Configuration>(); + } - public updateConfiguration(configuration: Configuration): Observable<any> { - return of<Configuration>(); - } + public updateConfiguration(configuration: Configuration): Observable<any> { + return of<Configuration>(); + } } +class MockAppConfigService { + config: any; + + constructor() { } + + public load() { + } + + public getApiUrl(): string { + return ''; + } + + public getNmaasGlobalDomainId(): number { + return 0; + } + + public getHttpTimeout(): number { + return 10000; + } + + public getShowGitInfo(): boolean { + return false; + } + + public getShowChangelog(): boolean { + return false; + } + } + class MockServiceUnavailableService { - public isServiceAvailable: boolean; + public isServiceAvailable: boolean; - constructor() { - this.isServiceAvailable = true; - } + constructor() { + this.isServiceAvailable = true; + } } describe('App: NmaasPortal', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ - AppComponent - ], - imports: [ - RouterTestingModule, - TranslateModule.forRoot({ - missingTranslationHandler: {provide: MissingTranslationHandler, useClass: CustomMissingTranslationService}, - loader: { - provide: TranslateLoader, - useClass: TranslateFakeLoader - } - }), - JwtModule.forRoot({ - config: { - tokenGetter: () => { - return ''; + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + imports: [ + HttpClientTestingModule, + RouterTestingModule, + TranslateModule.forRoot({ + missingTranslationHandler: {provide: MissingTranslationHandler, useClass: CustomMissingTranslationService}, + loader: { + provide: TranslateLoader, + useClass: TranslateFakeLoader + } + }), + JwtModule.forRoot({ + config: { + tokenGetter: () => { + return ''; + } } - } - }), - SharedModule - ], - providers: [ - {provide: AppConfigService, useClass: MockConfigurationService}, - HttpClient, - HttpHandler, - ConfigurationService, - TranslateService, - AuthService, - JwtHelperService, - {provide: ServiceUnavailableService, useClass: MockServiceUnavailableService} - ] + }), + SharedModule + ], + providers: [ + {provide: AppConfigService, useClass: MockAppConfigService}, + {provide: ConfigurationService, useClass: MockConfigurationService}, + TranslateService, + AuthService, + JwtHelperService, + {provide: ServiceUnavailableService, useClass: MockServiceUnavailableService} + ] + }); }); - }); - it('should create the app', waitForAsync(() => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app).toBeTruthy(); - })); + it('should create the app', waitForAsync(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + })); }); diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 610c10ccf5c6a61d2610e4be2b3fdefe9e51fcde..31a968fec3b241c1d54e3257898a92a6e781fe71 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -6,6 +6,7 @@ import { WelcomeRoutes } from './welcome/welcome.routes'; import {ServiceUnavailableRoutes} from './service-unavailable/service-unavailable.routes'; import {PageNotFoundComponent} from './shared/page-not-found/page-not-found.component'; import {LoginSuccessComponent} from './auth/login-success/login-success.component'; +import {LinkAccountComponent} from './welcome/link-account/link-account.component'; const appRoutes: Routes = [ ...WelcomeRoutes, @@ -13,6 +14,7 @@ const appRoutes: Routes = [ ...ServiceUnavailableRoutes, { path: 'notfound', component: PageNotFoundComponent }, { path: 'login-success', component: LoginSuccessComponent }, + { path: 'login-linking', component: LinkAccountComponent}, { path: '**', redirectTo: '/welcome' }, ]; diff --git a/src/app/appmarket/admin/clusters/details/clusterdetails.component.html b/src/app/appmarket/admin/clusters/details/clusterdetails.component.html index b5730cb4b9e56571b219092a7e57e10fe0a5bc8e..bed274174116378cf2e63aabfa68b5bfaec37345 100644 --- a/src/app/appmarket/admin/clusters/details/clusterdetails.component.html +++ b/src/app/appmarket/admin/clusters/details/clusterdetails.component.html @@ -1 +1 @@ -<nmaas-clusterdetails class="col-sm-12 col-sm-12 col-md-12" [cluster]="cluster" [error]="error" [mode]="getCurrentMode()" [allowedModes]="[ComponentMode.VIEW, ComponentMode.CREATE]" (onSave)="onSave($event)" (onDelete)="onDelete($event)"></nmaas-clusterdetails> +<nmaas-clusterdetails class="col-sm-12 col-sm-12 col-md-12" [cluster]="cluster" [error]="error" [mode]="getCurrentMode()" [allowedModes]="[ComponentMode.VIEW, ComponentMode.CREATE]"></nmaas-clusterdetails> diff --git a/src/app/appmarket/admin/clusters/details/clusterdetails.component.ts b/src/app/appmarket/admin/clusters/details/clusterdetails.component.ts index 248a6943e8c8662468fd62f1dad8c0833908ba1d..d9990a60a4ed7bf7effaebbf62dce13b1bcd63c4 100644 --- a/src/app/appmarket/admin/clusters/details/clusterdetails.component.ts +++ b/src/app/appmarket/admin/clusters/details/clusterdetails.component.ts @@ -29,21 +29,4 @@ export class ClusterDetailsComponent extends BaseComponent implements OnInit { }); } - public onSave($event) { - const upCluster: Cluster = $event; - if (!upCluster) { - return; - } - if (this.isInMode(ComponentMode.CREATE)) { - this.clusterService.add(upCluster) - .subscribe(() => this.router.navigateByUrl('/admin/clusters'), err => this.error = err.message); - } else { - this.clusterService.update(upCluster) - .subscribe(() => this.router.navigateByUrl('/admin/clusters'), err => this.error = err.message); - } - } - - public onDelete($event): void { - this.clusterService.remove($event).subscribe(() => this.router.navigate(['/admin/clusters/'])); - } } diff --git a/src/app/appmarket/admin/configuration/details/configurationdetails.component.html b/src/app/appmarket/admin/configuration/details/configurationdetails.component.html index 92aafcd35ac0b0fe8ffbd4b30836afee1c5b256e..30c84ce7fe97321197b2faa778088dd1a7e6df4f 100644 --- a/src/app/appmarket/admin/configuration/details/configurationdetails.component.html +++ b/src/app/appmarket/admin/configuration/details/configurationdetails.component.html @@ -131,6 +131,39 @@ </div> </div> + <div class="form-group"> + <label for="bulkDeploymentQueueRefresh" + class="col-sm-3 control-label">{{'PORTAL_CONFIGURATION.BULK_DEPLOYMENT_QUEUE_REFRESH' | translate}}</label> + <div class="col-sm-9 pd-top-7"> + <div class="input-width"> + <input class="form-control" type="number" id="bulkDeploymentQueueRefresh" name="bulkDeploymentQueueRefresh" + [(ngModel)]="this.configuration.bulkDeploymentQueueRefresh"> + </div> + + </div> + </div> + <div class="form-group"> + <label for="bulkDeploymentTimeThreshold" + class="col-sm-3 control-label">{{'PORTAL_CONFIGURATION.BULK_DEPLOYMENT_THRESHOLD' | translate}}</label> + <div class="col-sm-9 pd-top-7"> + <div class="input-width"> + <input class="form-control" type="number" id="bulkDeploymentTimeThreshold" name="bulkDeploymentTimeThreshold" + [(ngModel)]="this.configuration.bulkDeploymentTimeThreshold"> + </div> + + </div> + </div> + <div class="form-group"> + <label for="deploymentPrefix" + class="col-sm-3 control-label">{{'PORTAL_CONFIGURATION.DEPLOYMENT_PREFIX' | translate}}</label> + <div class="col-sm-9 pd-top-7"> + <div class="input-width"> + <input class="form-control" type="text" id="deploymentPrefix" name="deploymentPrefix" + [(ngModel)]="this.configuration.deploymentPrefix"> + </div> + + </div> + </div> <div class="flex justify-content-end"> <button class="btn btn-primary" type="submit">{{ 'PORTAL_CONFIGURATION.SUBMIT_BUTTON' | translate }}</button> diff --git a/src/app/appmarket/appdetails/appdetails.component.html b/src/app/appmarket/appdetails/appdetails.component.html index 4ba9674e295eeb8528aeb7426f08f1dbeea2cf49..bb2bbfff4d2c1ff653f1c8dc6942bb1ef5bc55eb 100644 --- a/src/app/appmarket/appdetails/appdetails.component.html +++ b/src/app/appmarket/appdetails/appdetails.component.html @@ -43,7 +43,7 @@ <div class="row" *ngIf="versionVisible"> <div class="col-xs-12 col-sm-6 col-md-6 col-lg-6" *ngIf="activeVersions"> <a *ngFor="let version of activeVersions" class="tag-button"> - v.{{version}} + v{{version}} </a> </div> </div> diff --git a/src/app/appmarket/appinstance/appinstance/appinstance.component.html b/src/app/appmarket/appinstance/appinstance/appinstance.component.html index 330151db9ebe9fe0c3741dec40f339e289153372..201b7698f8ab91f85596ecee8465ed82aa0aa563 100644 --- a/src/app/appmarket/appinstance/appinstance/appinstance.component.html +++ b/src/app/appmarket/appinstance/appinstance/appinstance.component.html @@ -95,7 +95,7 @@ <ul class="dropdown-menu"> <ng-container *ngIf="getStateAsEnum(appInstanceStatus?.state) === AppInstanceState.RUNNING"> <li> - <a role="button" (click)="this.accessMethodsModal.show()"> + <a role="button" (click)="openAccessMethodsModal()"> {{'APP_INSTANCE.APP_ACCESS_METHODS' | translate}} </a> </li> diff --git a/src/app/appmarket/appinstance/appinstance/appinstance.component.spec.ts b/src/app/appmarket/appinstance/appinstance/appinstance.component.spec.ts index 742069adfd21e9f305a3b14ceb8ccbbdcdf4c0ae..449459e4f4b881d437f763ae6433b6b45ad4b7b7 100644 --- a/src/app/appmarket/appinstance/appinstance/appinstance.component.spec.ts +++ b/src/app/appmarket/appinstance/appinstance/appinstance.component.spec.ts @@ -13,7 +13,6 @@ import {NgxPaginationModule} from 'ngx-pagination'; import {AppRestartModalComponent} from '../modals/app-restart-modal'; import {AppAbortModalComponent} from '../modals/app-abort-modal'; import {RouterTestingModule} from '@angular/router/testing'; -import {StorageServiceModule} from 'ngx-webstorage-service'; import {AppInstanceState, User} from '../../../model'; import {Role} from '../../../model/userrole'; import {ServiceAccessMethodType} from '../../../model/service-access-method'; @@ -272,7 +271,6 @@ describe('Component: AppInstance', () => { PipesModule, FormioModule, RouterTestingModule, - StorageServiceModule, JwtModule.forRoot({}), TranslateModule.forRoot({ loader: { diff --git a/src/app/appmarket/appinstance/appinstance/appinstance.component.ts b/src/app/appmarket/appinstance/appinstance/appinstance.component.ts index c4ce87cd4c01e91f4f95949c30428da0c5ec570d..543a563ff26a4089231b8e225fbcc4512120f17c 100644 --- a/src/app/appmarket/appinstance/appinstance/appinstance.component.ts +++ b/src/app/appmarket/appinstance/appinstance/appinstance.component.ts @@ -164,8 +164,6 @@ export class AppInstanceComponent implements OnInit, OnDestroy { this.configurationTemplate = this.getTemplate(appInstance.configWizardTemplate.template); this.app = appInstance.application; - this.updateAppInstancePodNames(); - this.submission.data.configuration = JSON.parse(appInstance.configuration); if (this.appInstance.configUpdateWizardTemplate != null) { @@ -645,4 +643,13 @@ export class AppInstanceComponent implements OnInit, OnDestroy { } + public openAccessMethodsModal(): void { + this.appInstanceService.getDeploymentParameters(this.appInstanceId).subscribe( + deployParams => { + this.deployParametersSubject.next(deployParams) + this.accessMethodsModal.show() ; + }) + + } + } diff --git a/src/app/appmarket/appmanagement/app-create-wizard/app-create-wizard.component.html b/src/app/appmarket/appmanagement/app-create-wizard/app-create-wizard.component.html index 72fd4db5ee3d8ae828654970a253fc669cc7b7ca..3771d215733c6678848010cdfda44529fe087dd0 100644 --- a/src/app/appmarket/appmanagement/app-create-wizard/app-create-wizard.component.html +++ b/src/app/appmarket/appmanagement/app-create-wizard/app-create-wizard.component.html @@ -536,6 +536,7 @@ <form-builder *ngIf="formDisplayChange && applicationDTO.application.configWizardTemplate.template" [form]="applicationDTO.application.configWizardTemplate.template" (change)="setConfigTemplate($event)"> </form-builder> + </p-tabPanel> <p-tabPanel header="{{'APPS_WIZARD.RAW_JSON' | translate}}"> diff --git a/src/app/appmarket/appmanagement/app-create-wizard/app-create-wizard.component.ts b/src/app/appmarket/appmanagement/app-create-wizard/app-create-wizard.component.ts index e9e30b6449f6c4154ed1f3e679754a5d17c26636..90f3e447abb68f69f83746904b7a43dc3712885e 100644 --- a/src/app/appmarket/appmanagement/app-create-wizard/app-create-wizard.component.ts +++ b/src/app/appmarket/appmanagement/app-create-wizard/app-create-wizard.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'; +import {Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'; import {ConfigWizardTemplate} from '../../../model'; import {MenuItem, SelectItem} from 'primeng/api'; import {AppImagesService, AppsService, TagService} from '../../../service'; @@ -29,7 +29,7 @@ import {ApplicationBase} from '../../../model/application-base'; styleUrls: ['./app-create-wizard.component.css'] }) -export class AppCreateWizardComponent extends BaseComponent implements OnInit { +export class AppCreateWizardComponent extends BaseComponent implements OnInit, OnDestroy { @ViewChild(ModalComponent, {static: true}) public modal: ModalComponent; @@ -59,6 +59,9 @@ export class AppCreateWizardComponent extends BaseComponent implements OnInit { public languages: SelectItem[] = []; public formDisplayChange = true; + public template : any; + public translateUpdate: any; + // properties for global parameters deploy validation // in future extensions pack this into single object public deployParamKeyValidator: ValidatorFn = noParameterTypeInControlValueValidator(); @@ -97,6 +100,7 @@ export class AppCreateWizardComponent extends BaseComponent implements OnInit { })); this.getParametersTypes().forEach(val => this.deployParameter.push({label: val.replace('_', ' '), value: val})); this.steps = this.getSteps(); + this.updateStepsTranslation(); this.route.params.subscribe(params => { if (params['id'] == null) { this.createNewWizard(); @@ -120,6 +124,25 @@ export class AppCreateWizardComponent extends BaseComponent implements OnInit { }); } + private updateStepsTranslation() { + this.translateUpdate = setInterval(() => { + if(this.translate.instant('APPS_WIZARD.GENERAL_INFO_STEP') !== null) { + this.steps = this.getSteps(); + this.stopTranslationUpdate(); + } + }, 200); + } + + private stopTranslationUpdate() { + clearInterval(this.translateUpdate); + this.translateUpdate = null; + } + + ngOnDestroy(): void { + clearInterval(this.translateUpdate); + this.translateUpdate = null; + } + public getSteps(): any { if (this.isInMode(ComponentMode.CREATE)) { return [ @@ -206,7 +229,8 @@ export class AppCreateWizardComponent extends BaseComponent implements OnInit { this.configFileTemplates.push(new ConfigFileTemplate()); this.applicationDTO.application.configWizardTemplate = new ConfigWizardTemplate(); this.applicationDTO.application.configWizardTemplate.template = this.configTemplateService.getConfigTemplate(); - } + }; + public nextStep(): void { this.activeStepIndex += 1; @@ -325,10 +349,15 @@ export class AppCreateWizardComponent extends BaseComponent implements OnInit { } public setConfigTemplate(event): void { - if (!this.applicationDTO.application.configWizardTemplate) { - this.applicationDTO.application.configWizardTemplate = new ConfigWizardTemplate(); + console.log(event) + if(event.type === "addComponent" || event.type === "saveComponent") { + console.log(event); + this.template = event.form; + this.applicationDTO.application.configWizardTemplate.template = null; + this.applicationDTO.application.configWizardTemplate.template = Object.assign({}, this.template); + console.log('Wizard saved',this.applicationDTO.application.configWizardTemplate.template) } - this.applicationDTO.application.configWizardTemplate.template = event.form; + } public setUpdateConfigTemplate(event): void { @@ -539,7 +568,7 @@ export class AppCreateWizardComponent extends BaseComponent implements OnInit { if (this.applicationDTO.application.appConfigurationSpec.configFileRepositoryRequired) { this.removeDefaultElement(); } else { - this.addDefaultElement(); + // this.addDefaultElement(); this.removeElementsFromUpdateConfig(); } } diff --git a/src/app/appmarket/appmanagement/app-management.routes.ts b/src/app/appmarket/appmanagement/app-management.routes.ts index 3a38b22f99228ccae091e7ab302d6140d07ace95..5b7f58eb2b73e13b934ae3e02a10e43d5cc20201 100644 --- a/src/app/appmarket/appmanagement/app-management.routes.ts +++ b/src/app/appmarket/appmanagement/app-management.routes.ts @@ -54,7 +54,7 @@ export const AppManagementRoutes: Route[] = [ path: 'admin/apps/bulks', component: BulkAppListComponent, canActivate: [AuthGuard, RoleGuard], - data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']} + data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']} }, { path: 'admin/apps/bulks/new', component: AppnavigatorComponent, @@ -68,6 +68,6 @@ export const AppManagementRoutes: Route[] = [ path: 'admin/apps/bulks/:id', component: BulkViewComponent, canActivate: [AuthGuard, RoleGuard], - data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER' ]} + data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER' ]} }, ]; diff --git a/src/app/appmarket/appmanagement/app-version-create-wizard/app-version-create-wizard.component.ts b/src/app/appmarket/appmanagement/app-version-create-wizard/app-version-create-wizard.component.ts index b964ab330bbac1b623b4390ecba870329d4b1e95..d4f6b6c6b18513c382a8d9605ca5a20a943246a5 100644 --- a/src/app/appmarket/appmanagement/app-version-create-wizard/app-version-create-wizard.component.ts +++ b/src/app/appmarket/appmanagement/app-version-create-wizard/app-version-create-wizard.component.ts @@ -1,25 +1,25 @@ -import {Component, OnInit, ViewChild} from '@angular/core'; -import {BaseComponent} from '../../../shared/common/basecomponent/base.component'; -import {ModalComponent} from '../../../shared'; -import {ConfigWizardTemplate} from '../../../model'; -import {ConfigFileTemplate} from '../../../model/configfiletemplate'; -import {AppImagesService, AppsService} from '../../../service'; -import {ActivatedRoute, Router} from '@angular/router'; -import {ConfigTemplateService} from '../../../service/configtemplate.service'; -import {ParameterType} from '../../../model/parametertype'; -import {KubernetesTemplate} from '../../../model/kubernetes-template'; -import {TranslateService} from '@ngx-translate/core'; -import {DomSanitizer} from '@angular/platform-browser'; -import {ApplicationState} from '../../../model/application-state'; -import {KubernetesChart} from '../../../model/kuberneteschart'; -import {AppStorageVolume} from '../../../model/app-storage-volume'; -import {parseServiceStorageVolumeType, ServiceStorageVolumeType} from '../../../model/service-storage-volume'; -import {AppAccessMethod} from '../../../model/app-access-method'; -import {parseServiceAccessMethodType, ServiceAccessMethodType} from '../../../model/service-access-method'; -import {AbstractControl, ValidatorFn} from '@angular/forms'; -import {MultiSelect} from 'primeng/multiselect'; -import {MenuItem, SelectItem} from 'primeng/api'; -import {ApplicationDTO} from '../../../model/application-dto'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { BaseComponent } from '../../../shared/common/basecomponent/base.component'; +import { ModalComponent } from '../../../shared'; +import { ConfigWizardTemplate } from '../../../model'; +import { ConfigFileTemplate } from '../../../model/configfiletemplate'; +import { AppImagesService, AppsService } from '../../../service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ConfigTemplateService } from '../../../service/configtemplate.service'; +import { ParameterType } from '../../../model/parametertype'; +import { KubernetesTemplate } from '../../../model/kubernetes-template'; +import { TranslateService } from '@ngx-translate/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ApplicationState } from '../../../model/application-state'; +import { KubernetesChart } from '../../../model/kuberneteschart'; +import { AppStorageVolume } from '../../../model/app-storage-volume'; +import { parseServiceStorageVolumeType, ServiceStorageVolumeType } from '../../../model/service-storage-volume'; +import { AppAccessMethod } from '../../../model/app-access-method'; +import { parseServiceAccessMethodType, ServiceAccessMethodType } from '../../../model/service-access-method'; +import { AbstractControl, ValidatorFn } from '@angular/forms'; +import { MultiSelect } from 'primeng/multiselect'; +import { MenuItem, SelectItem } from 'primeng/api'; +import { ApplicationDTO } from '../../../model/application-dto'; import { ApplicationVersion } from '../../../model/application-version'; import * as semver from 'semver'; import { Application } from '../../../model/application'; @@ -34,7 +34,7 @@ export function noParameterTypeInControlValueValidator(): ValidatorFn { } const notValid = labels.filter(val => control.value.includes(val)).length === 0; console.log('checking: ', control.value, 'valid: ', !notValid); - return notValid ? {'noParameterTypeInControlValue': {value: control.value}} : null; + return notValid ? { 'noParameterTypeInControlValue': { value: control.value } } : null; }; } @@ -43,10 +43,10 @@ export function noParameterTypeInControlValueValidator(): ValidatorFn { templateUrl: './app-version-create-wizard.component.html', styleUrls: ['./app-version-create-wizard.component.css'] }) -export class AppVersionCreateWizardComponent extends BaseComponent implements OnInit { +export class AppVersionCreateWizardComponent extends BaseComponent implements OnInit, OnDestroy { - @ViewChild(ModalComponent, {static: true}) + @ViewChild(ModalComponent, { static: true }) public modal: ModalComponent; @ViewChild('tagsMultiSelect') @@ -68,7 +68,9 @@ export class AppVersionCreateWizardComponent extends BaseComponent implements On public logo: any[] = []; public screenshots: any[] = []; public applicationVersions: ApplicationVersion[] = []; - public selectedVersion : any ; + public selectedVersion: any; + public template: any; + public translateUpdate: any; // properties for global parameters deploy validation // in future extensions pack this into single object @@ -83,12 +85,12 @@ export class AppVersionCreateWizardComponent extends BaseComponent implements On }; constructor(public appsService: AppsService, - public route: ActivatedRoute, - public translate: TranslateService, - public dom: DomSanitizer, - public configTemplateService: ConfigTemplateService, - public router: Router, - public appImagesService: AppImagesService) { + public route: ActivatedRoute, + public translate: TranslateService, + public dom: DomSanitizer, + public configTemplateService: ConfigTemplateService, + public router: Router, + public appImagesService: AppImagesService) { super(); } @@ -97,14 +99,21 @@ export class AppVersionCreateWizardComponent extends BaseComponent implements On this.modal.setModalType('success'); this.modal.setStatusOfIcons(false); this.mode = this.getMode(this.route); - this.getParametersTypes().forEach(val => this.deployParameter.push({label: val.replace('_', ' '), value: val})); - this.steps = [ - {label: this.translate.instant('APPS_WIZARD.GENERAL_INFO_STEP')}, - {label: this.translate.instant('APPS_WIZARD.BASIC_APP_INFO_STEP')}, - {label: this.translate.instant('APPS_WIZARD.APP_DEPLOYMENT_SPEC_STEP')}, - {label: this.translate.instant('APPS_WIZARD.CONFIG_TEMPLATES_STEP')}, - {label: this.translate.instant('APPS_WIZARD.SHORT_REVIEW_STEP')} - ]; + this.getParametersTypes().forEach(val => this.deployParameter.push({ label: val.replace('_', ' '), value: val })); + //trick to avoid using this.translate.onChange cuz its not working on current angular without changing language + this.translateUpdate = setInterval(() => { + if(this.translate.instant('APPS_WIZARD.GENERAL_INFO_STEP') !== null) { + this.steps = [ + { label: this.translate.instant('APPS_WIZARD.GENERAL_INFO_STEP') }, + { label: this.translate.instant('APPS_WIZARD.BASIC_APP_INFO_STEP') }, + { label: this.translate.instant('APPS_WIZARD.APP_DEPLOYMENT_SPEC_STEP') }, + { label: this.translate.instant('APPS_WIZARD.CONFIG_TEMPLATES_STEP') }, + { label: this.translate.instant('APPS_WIZARD.SHORT_REVIEW_STEP') } + ]; + this.stopTranslationUpdate(); + } + }, 200); + this.route.params.subscribe(params => { const appName = params['name'] const appId = params['id'] @@ -137,6 +146,16 @@ export class AppVersionCreateWizardComponent extends BaseComponent implements On }); } + private stopTranslationUpdate() { + clearInterval(this.translateUpdate); + this.translateUpdate = null; + } + + ngOnDestroy(): void { + clearInterval(this.translateUpdate); + this.translateUpdate = null; + } + public appVersionCompare(a: ApplicationVersion, b: ApplicationVersion): number { // defaults version that cannot be parsed to `0.0.0` return semver.compare(semver.coerce(b.version) || '0.0.0', semver.coerce(a.version) || '0.0.0') @@ -238,10 +257,13 @@ export class AppVersionCreateWizardComponent extends BaseComponent implements On } public setConfigTemplate(event): void { - if (!this.applicationDTO.application.configWizardTemplate) { - this.applicationDTO.application.configWizardTemplate = new ConfigWizardTemplate(); + if (event.type === "addComponent" || event.type === "saveComponent") { + console.log(event); + this.template = event.form; + this.applicationDTO.application.configWizardTemplate.template = null; + this.applicationDTO.application.configWizardTemplate.template = Object.assign({}, this.template); + console.log('Wizard saved', this.applicationDTO.application.configWizardTemplate.template) } - this.applicationDTO.application.configWizardTemplate.template = event.form; } public setUpdateConfigTemplate(event): void { @@ -494,7 +516,7 @@ export class AppVersionCreateWizardComponent extends BaseComponent implements On } private convertToProperImageFile(file: any) { - const result: any = new File([file], 'uploaded file', {type: file.type}); + const result: any = new File([file], 'uploaded file', { type: file.type }); result.objectURL = this.dom.bypassSecurityTrustUrl(URL.createObjectURL(result)); return result; } @@ -545,7 +567,7 @@ export class AppVersionCreateWizardComponent extends BaseComponent implements On } public onVersionSelect(event: any) { - console.log("Slected version ",event ) + console.log("Slected version ", event) this.appsService.getApplication(event.value.appVersionId).subscribe(data => { console.log(data); this.applicationDTO.application = data; diff --git a/src/app/appmarket/appmanagement/json-edit/json-edit.component.ts b/src/app/appmarket/appmanagement/json-edit/json-edit.component.ts index e92e8eaf211d700684c6b23b62d3b405d147f609..0cbc671cfff9fc0fb5c8262285c57b1f0ae9b77a 100644 --- a/src/app/appmarket/appmanagement/json-edit/json-edit.component.ts +++ b/src/app/appmarket/appmanagement/json-edit/json-edit.component.ts @@ -28,7 +28,6 @@ export class JsonEditComponent { @Input() set object(obj: any) { - console.log('setting value') const contentString = JSON.stringify(obj, null, 2); if (this.content && contentString !== this.content.value) { this.content.setValue(contentString) diff --git a/src/app/appmarket/appmarket.module.ts b/src/app/appmarket/appmarket.module.ts index 8bd9ce86288da65d981ac9fe9af4363df7b433b3..1539264532ea45f31fb58cb059887338c5cecf57 100644 --- a/src/app/appmarket/appmarket.module.ts +++ b/src/app/appmarket/appmarket.module.ts @@ -21,7 +21,6 @@ import {ClustersModule} from './admin/clusters/clusters.module'; import {ClusterService} from '../service/cluster.service'; import {ConfigurationModule} from './admin/configuration/configuration.module'; import {MonitorModule} from './admin/monitor/monitor.module'; -import {StorageServiceModule} from 'ngx-webstorage-service'; import {TranslateModule} from '@ngx-translate/core'; import {HttpClientModule} from '@angular/common/http'; import {BrowserModule} from '@angular/platform-browser'; @@ -48,6 +47,12 @@ import {NgxPaginationModule} from 'ngx-pagination'; import {InputTextModule} from 'primeng/inputtext'; import {BulkSearchPipe} from './bulkDeployment/bulk-list/bulk-search.pipe'; import {CheckboxModule} from 'primeng/checkbox'; +import { InputSwitchModule } from 'primeng/inputswitch'; +import { OverlayPanelModule } from 'primeng/overlaypanel'; +import { SidebarModule } from 'primeng/sidebar'; +import { ProgressBarModule } from 'primeng/progressbar'; + + @NgModule({ declarations: [ @@ -66,7 +71,6 @@ import {CheckboxModule} from 'primeng/checkbox'; ], imports: [ FormsModule, - StorageServiceModule, CommonModule, RouterModule, SharedModule, @@ -94,6 +98,10 @@ import {CheckboxModule} from 'primeng/checkbox'; InputTextModule, TooltipModule, CheckboxModule, + InputSwitchModule, + OverlayPanelModule, + SidebarModule, + ProgressBarModule ], exports: [ AppMarketComponent, diff --git a/src/app/appmarket/bulkDeployment/appdeployment.service.ts b/src/app/appmarket/bulkDeployment/appdeployment.service.ts index 0d4d728735e3e38d36eec79bcae0b6827d6b9ed2..f660f0531c4ff76f1c92041d070d6205ced673fe 100644 --- a/src/app/appmarket/bulkDeployment/appdeployment.service.ts +++ b/src/app/appmarket/bulkDeployment/appdeployment.service.ts @@ -5,6 +5,7 @@ import {AppConfigService} from '../../service'; import {BulkResponse} from '../../model/bulk-response'; import {Observable} from 'rxjs'; import {BulkDeployment} from '../../model/bulk-deployment'; +import { BulkQueueDetails } from '../../model/bulk-queue-details'; @Injectable({ providedIn: 'root' @@ -71,20 +72,24 @@ export class AppdeploymentService { return this.http.post<BulkDeployment>(this.getUrl() + 'domains', formParams); } - public getBulksDomainDeployments(): Observable<BulkDeployment[]> { - return this.http.get<BulkDeployment[]>(this.getUrl() + 'domains'); + public getBulksDomainDeployments(showDeleted: boolean = false): Observable<BulkDeployment[]> { + const formParams = new HttpParams().append('deleted', showDeleted); + + return this.http.get<BulkDeployment[]>(this.getUrl() + 'domains', {params:formParams}); } public getBulksDomainDeploymentsOwner(): Observable<BulkDeployment[]> { - return this.http.get<BulkDeployment[]>(this.getUrl() + 'domains/vl'); + return this.http.get<BulkDeployment[]>(this.getUrl() + 'domains/group'); } - public getBulksAppDeployments(): Observable<BulkDeployment[]> { - return this.http.get<BulkDeployment[]>(this.getUrl() + 'apps'); + public getBulksAppDeployments(showDeleted: boolean = false): Observable<BulkDeployment[]> { + const formParams = new HttpParams().append('deleted', showDeleted); + + return this.http.get<BulkDeployment[]>(this.getUrl() + 'apps', {params:formParams}); } public getBulksAppDeploymentsOwner(): Observable<BulkDeployment[]> { - return this.http.get<BulkDeployment[]>(this.getUrl() + 'apps/vl'); + return this.http.get<BulkDeployment[]>(this.getUrl() + 'apps/group'); } public getBulkDeployment(id: number): Observable<BulkDeployment> { @@ -104,4 +109,9 @@ export class AppdeploymentService { return this.http.delete(this.getUrl() +`${id}`, { params }); } + public getQueueDetails(id: number) : Observable<BulkQueueDetails> { + return this.http.get<BulkQueueDetails>(this.getUrl() + `queue/${id}`) + } + + } diff --git a/src/app/appmarket/bulkDeployment/bulk-app-list/bulk-app-list.component.html b/src/app/appmarket/bulkDeployment/bulk-app-list/bulk-app-list.component.html index e34eee5938d37ec4330b3cd1f1ebdc2d9659707f..340557d5220556e32498ffcac2549231bde94c23 100644 --- a/src/app/appmarket/bulkDeployment/bulk-app-list/bulk-app-list.component.html +++ b/src/app/appmarket/bulkDeployment/bulk-app-list/bulk-app-list.component.html @@ -1,2 +1,2 @@ -<app-bulk-list [header]="'BULK.APP.HEADER'" [bulks]="bulks" [mode]="mode"></app-bulk-list> +<app-bulk-list [header]="'BULK.APP.HEADER'" [bulks]="bulks" [mode]="mode" (refresh)="onRefresh($event)"></app-bulk-list> diff --git a/src/app/appmarket/bulkDeployment/bulk-app-list/bulk-app-list.component.ts b/src/app/appmarket/bulkDeployment/bulk-app-list/bulk-app-list.component.ts index 9256cfa4ea2a99172b68b96471a1312b92bb206d..7b89c6899bce5d8dbe07c971fd8601093a27d2fc 100644 --- a/src/app/appmarket/bulkDeployment/bulk-app-list/bulk-app-list.component.ts +++ b/src/app/appmarket/bulkDeployment/bulk-app-list/bulk-app-list.component.ts @@ -20,16 +20,20 @@ export class BulkAppListComponent implements OnInit { } ngOnInit(): void { - if (this.authService.getRoles().find(value => value === 'ROLE_VL_MANAGER') !== undefined) { + this.onRefresh(); + } + + onRefresh(showDeleted = false) : void { + if (this.authService.getRoles().find(value => value === 'ROLE_GROUP_MANAGER') !== undefined) { this.deployService.getBulksAppDeploymentsOwner().subscribe(data => { data = data.sort((a, b) => new Date(b.creationDate).getTime() - new Date(a.creationDate).getTime()) this.bulks = data }); } else { - this.deployService.getBulksAppDeployments().subscribe(data => { + this.deployService.getBulksAppDeployments(showDeleted).subscribe(data => { data = data.sort((a, b) => new Date(b.creationDate).getTime() - new Date(a.creationDate).getTime()) this.bulks = data }); } } -} + } diff --git a/src/app/appmarket/bulkDeployment/bulk-domain-list/bulk-domain-list.component.ts b/src/app/appmarket/bulkDeployment/bulk-domain-list/bulk-domain-list.component.ts index 696e86a9f8ccc30a4615d7e13af798cd423e4651..cfd2fe6751732bde31b6ef9e5553bb53b20e1a5f 100644 --- a/src/app/appmarket/bulkDeployment/bulk-domain-list/bulk-domain-list.component.ts +++ b/src/app/appmarket/bulkDeployment/bulk-domain-list/bulk-domain-list.component.ts @@ -20,7 +20,7 @@ export class BulkDomainListComponent implements OnInit { } ngOnInit(): void { - if (this.authService.getRoles().find(value => value === 'ROLE_VL_MANAGER') !== undefined) { + if (this.authService.getRoles().find(value => value === 'ROLE_GROUP_MANAGER') !== undefined) { this.deployService.getBulksDomainDeploymentsOwner().subscribe(data => { data = data.sort((a, b) => new Date(b.creationDate).getTime() - new Date(a.creationDate).getTime()) this.bulks = data diff --git a/src/app/appmarket/bulkDeployment/bulk-list/bulk-list.component.html b/src/app/appmarket/bulkDeployment/bulk-list/bulk-list.component.html index 04d746b77637cdfbe1ba345029160948e7452ea0..d974dc0a1a2154e5679a4c39d6efd633d8839c0a 100644 --- a/src/app/appmarket/bulkDeployment/bulk-list/bulk-list.component.html +++ b/src/app/appmarket/bulkDeployment/bulk-list/bulk-list.component.html @@ -7,7 +7,40 @@ <div *ngIf="mode=== bulkTypeApp"> <button class="btn btn-primary" [routerLink]="['/admin/apps/bulks/new']">New deployment</button> </div> + <div class="flex"> + <div *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']" class="flex align-items-center mr-6"> + + <p-button + type="button" + class="mr-2" + (onClick)="sidebarVisible4 = true" + label="Deployments Queue" + severity="secondary" + ></p-button> + <p-sidebar [(visible)]="sidebarVisible4" position="left" styleClass="w-30rem"> + <h3>Deployments Queue</h3> + <div class="flex flex-column"> + <div class="flex grid"> + <label class="col-10" for="jobInQueue">Jobs in queue</label> + <span class="col-2 " id="jobInQueue">{{queueDetails?.jobInQueue}}</span> + </div> + <div class="flex grid"> + <label class="col-10" for="jobInProcess">In progress</label> + <span class="col-2 " id="jobInProcess">{{queueDetails?.jobInProcess}}</span> + </div> + <div class="flex grid"> + <label class="col-10" for="jobInProcessId">Current proccessing bulk id</label> + <span class="col-2" id="jobInProcessId">{{queueDetails?.jobInProcessId}}</span> + </div> + </div> + </p-sidebar> + + </div> + <div *roles="['ROLE_SYSTEM_ADMIN']" class="flex align-items-center mr-6 pt-2"> + <label *ngIf="mode=== bulkTypeApp" class="mr-2" for="showDeleted">Show all</label> + <p-inputSwitch *ngIf="mode=== bulkTypeApp" id="showDeleted" (onChange)="refreshBulks()" [(ngModel)]="showDeleted" ngDefaultControl/> + </div> <div class="flex align-items-center mr-1">{{ 'BULK.LIST.PER_PAGE' | translate }}:</div> <span id="selectionItems" class="dropdown" style="vertical-align: middle; display: inline-block; margin-right: 1rem;"> @@ -102,7 +135,7 @@ <li *ngIf="mode === bulkTypeApp && bulk?.state !== 'REMOVED'"> <a (click)="getAppBulkDetails(bulk?.id)"> {{"BULK.APP.DOWNLOAD_CSV" | translate}}</a> </li> - <li *ngIf="mode === bulkTypeApp && bulk?.state !== 'REMOVED'"> + <li *ngIf="mode === bulkTypeApp && !(bulk?.state === 'REMOVED' || bulk?.deleted )"> <a (click)="modal.show(); removeBulkId=bulk?.id">{{ 'BULK.LIST.REMOVE' | translate }}</a> </li> </ul> diff --git a/src/app/appmarket/bulkDeployment/bulk-list/bulk-list.component.ts b/src/app/appmarket/bulkDeployment/bulk-list/bulk-list.component.ts index 6bc9e1ad97b4d5b90c287272922fbffc891a4615..4883a6bb95b8301576a8479a0d1aac8524bd0cb3 100644 --- a/src/app/appmarket/bulkDeployment/bulk-list/bulk-list.component.ts +++ b/src/app/appmarket/bulkDeployment/bulk-list/bulk-list.component.ts @@ -1,17 +1,20 @@ -import {Component, Input, QueryList, ViewChild, ViewChildren} from '@angular/core'; +import {Component, EventEmitter, Input, OnDestroy, OnInit, Output, QueryList, ViewChild, ViewChildren} from '@angular/core'; import {BulkDeployment} from '../../../model/bulk-deployment'; import {BulkType} from '../../../model/bulk-response'; import {SortableHeaderDirective} from '../../../service/sort-domain.directive'; import {ModalComponent} from '../../../shared'; import {AppdeploymentService} from '../appdeployment.service'; import {DomSanitizer} from '@angular/platform-browser'; +import { BulkQueueDetails } from '../../../model/bulk-queue-details'; +import { map, timer } from 'rxjs'; +import { ConfigurationService } from '../../../service'; @Component({ selector: 'app-bulk-list', templateUrl: './bulk-list.component.html', styleUrls: ['./bulk-list.component.css'] }) -export class BulkListComponent { +export class BulkListComponent implements OnDestroy, OnInit { public static BULK_ENTRY_DETAIL_KEY_APP_INSTANCE_NO = 'appInstanceNo'; public static BULK_ENTRY_DETAIL_KEY_APP_INSTANCE_NAME = 'appName'; @@ -32,6 +35,10 @@ export class BulkListComponent { @ViewChild(ModalComponent, {static: true}) public readonly modal: ModalComponent; + @Output() + public refresh: EventEmitter<boolean> = new EventEmitter<boolean>(); + + public showDeleted = false; public readonly bulkTypeDomain = BulkType.DOMAIN; public readonly bulkTypeApp = BulkType.APPLICATION; @@ -44,8 +51,24 @@ export class BulkListComponent { public removeAll = false; public removeBulkId = 0; + public queueDetails : BulkQueueDetails; + + public refreshQueue = undefined; + public sidebarVisible4 = false; + + public configRefresh = 60; + + constructor(private appDeploy: AppdeploymentService, - private sanitizer: DomSanitizer) { + private sanitizer: DomSanitizer, + private configService: ConfigurationService) { + } + + public ngOnInit(): void { + this.configService.getConfiguration().subscribe(conf => { + this.configRefresh = conf.bulkDeploymentQueueRefresh; + this.update(); + }) } public getApplicationName(details: Map<string, string>) { @@ -151,8 +174,41 @@ export class BulkListComponent { public removeBulk(): void { this.appDeploy.removeBulkDeployment(this.removeBulkId, this.removeAll).subscribe(_ => { - console.log("Bulk removed") + this.refreshBulks(); + this.modal.hide(); }) this.removeAll = false; } + + public refreshBulks(): void { + this.refresh.emit(this.showDeleted); + } + + public getQueueDetails(): void { + this.appDeploy.getQueueDetails(0).subscribe(queue => { + console.log(queue); + this.queueDetails = queue; + }) + } + + public update() { + this.refreshQueue = timer(0, this.refreshQueue * 1000).pipe(map(() => { + this.getQueueDetails(); + })).subscribe() + } + + public getQueryNumber() { + return this.queueDetails?.jobInQueue + this.queueDetails?.jobInProcess; + } + + + public ngOnDestroy() { + if (this.refreshQueue !== undefined) { + this.refreshQueue.unsubscribe(); + } + } + + public open() { + + } } diff --git a/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.css b/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.css index 415a4d52c3f56b52ae0540dc65b55bf5b1c3d599..66d99ef9dc8e1b044db0c92653980615c5db3ea5 100644 --- a/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.css +++ b/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.css @@ -34,4 +34,13 @@ white-space: nowrap !important; } +:host ::ng-deep .job-done-bar { + .p-progressbar-value { + border: 0 none; + margin: 0; + background:green; + } +} + + diff --git a/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.html b/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.html index c40a1ff6969dfe5322906d7e116981f228ef913a..9c870590051db4025f7b3d0dffbaeee499a71785 100644 --- a/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.html +++ b/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.html @@ -62,7 +62,7 @@ <td>{{getDomainId(response)}}</td> <td>{{getDomainName(response)}}</td> <td>{{getDomainCodeName(response)}}</td> - <td style="width: 5%" class="text-right"> + <td style="width: 5%" class="text-right" *ngIf="bulk.state !== 'REMOVED'"> <i *ngIf="response.type === 'DOMAIN'" class="pi pi-search" style="font-size: 1.8rem; cursor: pointer" [routerLink]="['/admin/domains/view/', response?.details['domainId']]"></i> <i *ngIf="response.type === 'USER'" class="pi pi-search" style="font-size: 1.8rem; cursor: pointer" [routerLink]="['/admin/users/view', response?.details['userId']]"></i> <!-- <span class="dropdown"> @@ -160,6 +160,15 @@ </div> </div> + <div class="" style="padding-bottom: 5rem;"> + <label for="id" class="col-sm-2 control-label text-right mt-2">{{ 'BULK.LIST.APP_NAME' | translate }}</label> + <div class="col-sm-10"> + <input type="text" class="form-control" id="id" name="id" [disabled]="true" + [(ngModel)]="bulk.details['appName']" #name="ngModel"> + </div> + + </div> + <div class="" style="padding-bottom: 5rem"> <label for="creator" class="col-sm-2 control-label text-right mt-2">{{ 'BULK.LIST.CREATOR' | translate }}</label> @@ -187,21 +196,31 @@ </div> </div> - <div class="" style="padding-bottom: 5rem;"> - <label for="id" class="col-sm-2 control-label text-right mt-2">{{ 'BULK.LIST.APP_NAME' | translate }}</label> + <div class="" style="padding-bottom: 5rem"> + <label for="completionDate" + class="col-sm-2 control-label text-right mt-2">{{ 'BULK.LIST.COMPLETION_DATE' | translate }}</label> <div class="col-sm-10"> - <input type="text" class="form-control" id="id" name="id" [disabled]="true" - [(ngModel)]="bulk.details['appName']" #name="ngModel"> + <input type="text" class="form-control" id="completionDate" name="completionDate" [disabled]="true" + placeholder="{{completionDate}}"> </div> - </div> - <div class="flex justify-content-end" style="padding-right: 1.5rem"> + + + <div *ngIf="bulk.state !== 'REMOVED'" class="flex justify-content-end" style="padding-right: 1.5rem"> <button class="btn btn-primary mr-2" (click)="refreshStates()">{{'BULK.APP.REFRESH' | translate}}</button> - <button class="btn btn-primary" (click)="getAppBulkDetails(this.bulkId)">{{'BULK.APP.DOWNLOAD_CSV' | translate}}</button> + <button *ngIf="bulk.state !== 'FAILED'" class="btn btn-primary" (click)="getAppBulkDetails(this.bulkId)">{{'BULK.APP.DOWNLOAD_CSV' | translate}}</button> </div> - <div class="panel panel-default" style="margin-top: 3rem"> + <div class="mt-4"> + <p-progressBar [mode]="progressBarMode"[value]="progressBarValue" id="progressBarr" [style]="{ height: '18px' }" [styleClass]="jobDone ? 'job-done-bar' : 'normal-bar'"> + <ng-template pTemplate="content" let-value> + <span>{{queueDetails?.jobDone}} <span *ngIf="queueDetails?.jobDone !== bulk.entries.length">(+{{queueDetails?.jobInProcess}})</span> / {{bulk.entries.length}}</span> + </ng-template> + </p-progressBar> + + </div> + <div class="panel panel-default" style="margin-top: 1rem"> <div class="panel-heading"> <div style="display: flex; justify-content: start; align-items: center"> @@ -235,8 +254,8 @@ <td>{{getAppInstanceId(response)}}</td> <td>{{getAppInstanceName(response)}}</td> <td>{{getDomainCodeName(response)}}</td> - <td style="width: 5%" class="text-right"> - <i class="pi pi-search" style="font-size: 1.8rem; cursor: pointer" [routerLink]="['/instances/', response?.details['appInstanceId']]"></i> + <td style="width: 5%" class="text-right" > + <i *ngIf="response?.details['appInstanceId'] !== undefined" class="pi pi-search" style="font-size: 1.8rem; cursor: pointer" [routerLink]="['/instances/', response?.details['appInstanceId']]"></i> <!-- <span *ngIf="response?.details['appInstanceId'] !== undefined" class="dropdown"> <a style="display: inline-block" class="dropdown-toggle " aria-expanded="false" aria-haspopup="true" data-toggle="dropdown" href="#" role="button"> diff --git a/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.spec.ts b/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.spec.ts index 33287d7e82506504fe8fc2bf9a4100084a49ff39..2c9c55fa673bd9e1bafe4db2fc2e97d50359fd76 100644 --- a/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.spec.ts +++ b/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.spec.ts @@ -3,6 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BulkViewComponent } from './bulk-view.component'; import {HttpClientTestingModule} from '@angular/common/http/testing'; import {RouterModule} from '@angular/router'; +import { DatePipe } from '@angular/common'; describe('BulkViewComponent', () => { let component: BulkViewComponent; @@ -14,6 +15,9 @@ describe('BulkViewComponent', () => { imports: [ HttpClientTestingModule, RouterModule.forRoot([]), + ], + providers: [ + DatePipe ] }) .compileComponents(); diff --git a/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.ts b/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.ts index 5895c0b6ba5531de6c627b1026c321333073a0df..4b0c59f9115bcae87a18f76b5ea7c109e3c5f9c9 100644 --- a/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.ts +++ b/src/app/appmarket/bulkDeployment/bulk-view/bulk-view.component.ts @@ -5,7 +5,9 @@ import {ActivatedRoute, Router} from '@angular/router'; import {BulkResponse, BulkType} from '../../../model/bulk-response'; import {timer} from 'rxjs'; import {map} from 'rxjs/operators'; -import {AppImagesService} from '../../../service'; +import {AppImagesService, ConfigurationService} from '../../../service'; +import { BulkQueueDetails } from '../../../model/bulk-queue-details'; +import { DatePipe } from '@angular/common'; @Component({ selector: 'app-bulk-view', @@ -20,21 +22,40 @@ export class BulkViewComponent implements OnInit, OnDestroy { public refresh = undefined; + public progressBarMode ; + public progressBarValue ; + + public queueDetails :BulkQueueDetails; + + public jobDone = false; + public completionDate = ""; + + public configRefresh = 60; + constructor(public deployService: AppdeploymentService, private route: ActivatedRoute, private router: Router, public appImagesService: AppImagesService, + private datePipe: DatePipe, + private configService: ConfigurationService ) { } ngOnInit(): void { + this.configService.getConfiguration().subscribe(conf => { + this.configRefresh = conf.bulkDeploymentQueueRefresh; + }) + this.route.params.subscribe(params => { if (params['id'] !== undefined) { this.bulkId = +params['id']; this.deployService.getBulkDeployment(this.bulkId).subscribe( (bulk) => { this.bulk = bulk; + this.sortByInstanceId(); this.bulkType = bulk.type; + this.getQueueDetails(); + this.setCompletionDate(bulk); if (this.bulkType === BulkType.APPLICATION) { this.update(); } @@ -88,10 +109,24 @@ export class BulkViewComponent implements OnInit, OnDestroy { } public update() { - this.refresh = timer(0, 20000).pipe(map(() => { + this.refresh = timer(0, this.configRefresh * 1000).pipe(map(() => { this.deployService.getBulkDeployment(this.bulk.id).subscribe(bulk => { this.bulk = bulk; + this.sortByInstanceId(); + this.setCompletionDate(bulk); + if(bulk.state === 'REMOVED') this.refresh.unsubscribe(); + if(bulk.state === 'PROCESSING' && this.queueDetails.jobInProcessId === bulk.id) { + this.progressBarMode = "determinate" + this.setBarValue(); + } else if(bulk.state === 'PROCESSING') { + this.setBarValue(); + this.progressBarMode = "indeterminate" + } else { + this.progressBarMode = "determinate" + this.setBarValue(); + } }) + })).subscribe() } @@ -103,7 +138,7 @@ export class BulkViewComponent implements OnInit, OnDestroy { public getAppBulkDetails(id: number) { this.deployService.getAppBulkDetails(id).subscribe( (data: Blob) => { - console.warn(data) + console.log(data) const blob = new Blob([data], { type: 'text/csv' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); @@ -119,7 +154,43 @@ export class BulkViewComponent implements OnInit, OnDestroy { public refreshStates() { this.deployService.refreshStatesInBulkDeployment(this.bulkId).subscribe( deply => { this.bulk = deply; - console.log("Updated states of bulks") + this.sortByInstanceId(); + this.setCompletionDate(deply); + this.getQueueDetails(); }) } + + public setCompletionDate( deployment: BulkDeployment) { + if(this.bulk.completionDate !== undefined && this.bulk.completionDate !== null && deployment.state === 'COMPLETED') { + this.completionDate = this.datePipe.transform(this.bulk.completionDate,'dd-MM-yyyy HH:mm' ) + } else { + this.completionDate = " - " + } + } + + public setBarValue() { + this.getQueueDetails(); + } + + public getQueueDetails(): void { + this.deployService.getQueueDetails(this.bulkId).subscribe(queue => { + this.queueDetails = queue; + if(queue.jobDone === this.bulk.entries.length) { + this.progressBarValue = 100; + this.jobDone = true; + this.progressBarMode = "determinate" + } else if(queue.jobDone === 0) { + this.progressBarMode = "indeterminate" + }else { + this.progressBarMode = "determinate" + this.progressBarValue = queue.jobDone * 100 / this.bulk.entries.length; + this.jobDone = false; + } + + }) + } + + public sortByInstanceId() { + this.bulk.entries.sort((a,b) => this.getAppInstanceId(a) < this.getAppInstanceId(b) ? 1 : -1); + } } diff --git a/src/app/appmarket/domains/domain-group-view/domain-group-view.component.html b/src/app/appmarket/domains/domain-group-view/domain-group-view.component.html index c42594752a704d430b01c6c597c099788a2ccb41..884fd53dc9108fdcc1b1e2a57db7b608a576f303 100644 --- a/src/app/appmarket/domains/domain-group-view/domain-group-view.component.html +++ b/src/app/appmarket/domains/domain-group-view/domain-group-view.component.html @@ -42,7 +42,7 @@ <div *roles="['ROLE_SYSTEM_ADMIN']" style="display: flex; justify-content: end"> <button type="button" class="btn btn-secondary" (click)="showModalUser()">{{'DOMAINS.GROUP.ADD_USERS' | translate}}</button> </div> - <div *roles="['ROLE_VL_MANAGER']" style="display: flex; justify-content: end"> + <div *roles="['ROLE_GROUP_MANAGER']" style="display: flex; justify-content: end"> <button type="button" class="btn btn-warning" (click)="removeMyAccess()">{{'DOMAINS.GROUP.DELETE_MYSELF' | translate}}</button> </div> <table class="table table-hover table-condensed" aria-describedby="Domains in Group table" style="margin-top: 2rem"> diff --git a/src/app/appmarket/domains/domain-groups/domain-groups.component.html b/src/app/appmarket/domains/domain-groups/domain-groups.component.html index 53ae0e2083a29f2718b9b1cbd78c80e2039d1fe5..00e87a76702570d97b6ef962c5a6a0b83893f8b1 100644 --- a/src/app/appmarket/domains/domain-groups/domain-groups.component.html +++ b/src/app/appmarket/domains/domain-groups/domain-groups.component.html @@ -2,7 +2,7 @@ <h3>{{'DOMAINS.LIST.GROUPS' | translate}}</h3> <div class="flex space-between"> <div class="flex"> - <a *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']" [routerLink]="['/admin/domains/groups/add']" class="btn btn-primary" + <a *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']" [routerLink]="['/admin/domains/groups/add']" class="btn btn-primary" role="button">{{'DOMAINS.ADD_BUTTON' | translate}}</a> </div> <div class="flex"> diff --git a/src/app/appmarket/domains/domains.routes.ts b/src/app/appmarket/domains/domains.routes.ts index 12fb5a90bfaa0a6e3363b6e89855cebc7bbc4f9f..c99b22f037941a04df1c9e17f75d39a52ef6f085 100644 --- a/src/app/appmarket/domains/domains.routes.ts +++ b/src/app/appmarket/domains/domains.routes.ts @@ -13,7 +13,7 @@ import { DomainAnnotationsComponent } from './domain-annotations/domain-annotati export const DomainsRoutes: Route[] = [ { path: 'admin/domains', component: DomainsListComponent, canActivate: [AuthGuard, RoleGuard], - data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_DOMAIN_ADMIN', 'ROLE_OPERATOR', 'ROLE_VL_DOMAIN_ADMIN', 'ROLE_VL_MANAGER']} + data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_DOMAIN_ADMIN', 'ROLE_OPERATOR', 'ROLE_GROUP_DOMAIN_ADMIN', 'ROLE_GROUP_MANAGER']} }, { path: 'admin/domains/add', component: DomainComponent, canActivate: [AuthGuard, RoleGuard], @@ -25,7 +25,7 @@ export const DomainsRoutes: Route[] = [ }, { path: 'admin/domains/view/:id', component: DomainComponent, canActivate: [AuthGuard, RoleGuard], - data: {mode: ComponentMode.VIEW, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_DOMAIN_ADMIN', 'ROLE_OPERATOR', 'ROLE_VL_DOMAIN_ADMIN']} + data: {mode: ComponentMode.VIEW, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_DOMAIN_ADMIN', 'ROLE_OPERATOR', 'ROLE_GROUP_DOMAIN_ADMIN']} }, { path: 'admin/domains/edit/:id', component: DomainComponent, canActivate: [AuthGuard, RoleGuard], @@ -33,25 +33,25 @@ export const DomainsRoutes: Route[] = [ }, { path: 'admin/domains/groups', component: DomainGroupsComponent, canActivate: [AuthGuard, RoleGuard], - data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']} + data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']} }, { path: 'admin/domains/groups/add', component: DomainGroupViewComponent, canActivate: [AuthGuard, RoleGuard], - data: {mode: ComponentMode.CREATE, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']} + data: {mode: ComponentMode.CREATE, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']} }, { path: 'admin/domains/groups/:id', component: DomainGroupViewComponent, canActivate: [AuthGuard, RoleGuard], - data: {mode: ComponentMode.VIEW, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']} + data: {mode: ComponentMode.VIEW, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']} }, { path: 'admin/domains/bulks/new', component: DomainuploadComponent, - data: {mode: ComponentMode.VIEW, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']}}, + data: {mode: ComponentMode.VIEW, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']}}, { path: 'admin/domains/bulks', component: BulkDomainListComponent, canActivate: [AuthGuard, RoleGuard], - data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']} + data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']} }, { path: 'admin/domains/bulks/:id', component: BulkViewComponent, canActivate: [AuthGuard, RoleGuard], - data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']} + data: {roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']} } ]; diff --git a/src/app/appmarket/domains/list/domainslist.component.ts b/src/app/appmarket/domains/list/domainslist.component.ts index de210023a06690a4cff6b3001fff0c4f714fea93..b6256b314fd0729197b3c9cf8861033cd4ff4bd0 100644 --- a/src/app/appmarket/domains/list/domainslist.component.ts +++ b/src/app/appmarket/domains/list/domainslist.component.ts @@ -62,7 +62,7 @@ export class DomainsListComponent implements OnInit { map((domains) => domains.filter((domain) => domain.id !== this.domainService.getGlobalDomainId()))); } else { return this.domainService.getMyDomains().pipe( - map((domains) => domains.filter((domain) => this.authService.hasDomainRole(domain.id, Role[Role.ROLE_DOMAIN_ADMIN]) || this.authService.hasDomainRole(domain.id, Role[Role.ROLE_VL_DOMAIN_ADMIN])))); + map((domains) => domains.filter((domain) => this.authService.hasDomainRole(domain.id, Role[Role.ROLE_DOMAIN_ADMIN]) || this.authService.hasDomainRole(domain.id, Role[Role.ROLE_GROUP_DOMAIN_ADMIN])))); } } diff --git a/src/app/appmarket/users/list/userslist.component.html b/src/app/appmarket/users/list/userslist.component.html index 6d9333ad9f77cb06d0cc389dc13401150af6c16e..76236c39205a2e4fe779f033f276151a3b12b0e8 100644 --- a/src/app/appmarket/users/list/userslist.component.html +++ b/src/app/appmarket/users/list/userslist.component.html @@ -11,7 +11,7 @@ </div> <div class="col-sm-12" *ngIf="domainMode"> - <div *roles="['ROLE_DOMAIN_ADMIN', 'ROLE_VL_MANAGER']"> + <div *roles="['ROLE_DOMAIN_ADMIN', 'ROLE_GROUP_MANAGER']"> <nmaas-userslist *ngIf="!isInAddToDomainMode" [users]="allUsers" [allowedModes]="[ComponentMode.VIEW, ComponentMode.DELETE]" [domainMode]="true" (onUserRoleChange)="onUserRoleChange($event)" (onView)="onUserView($event)" (onModeChange)="onModeChange($event)" (onDelete)="onUserDelete($event)" (onRemoveFromDomain)="onRemoveRole($event)"> </nmaas-userslist> diff --git a/src/app/appmarket/users/list/userslist.component.ts b/src/app/appmarket/users/list/userslist.component.ts index 3bb01ecd9dd280bf7bfc623d7a34e26f2d3e1727..dbcbc5545d6dd437cc30d86fa55c74823a1e8807 100644 --- a/src/app/appmarket/users/list/userslist.component.ts +++ b/src/app/appmarket/users/list/userslist.component.ts @@ -57,7 +57,7 @@ export class UsersListComponent implements OnInit { users = this.userService.getDomainUsersAsAdmin(this.domainId); } else if (this.authService.hasRole(Role[Role.ROLE_SYSTEM_ADMIN])) { users = this.userService.getAll(this.domainId); - } else if (this.domainId != null && (this.authService.hasDomainRole(this.domainId, Role[Role.ROLE_DOMAIN_ADMIN]) || this.authService.hasDomainRole(this.domainId, Role[Role.ROLE_VL_DOMAIN_ADMIN]))) { + } else if (this.domainId != null && (this.authService.hasDomainRole(this.domainId, Role[Role.ROLE_DOMAIN_ADMIN]) || this.authService.hasDomainRole(this.domainId, Role[Role.ROLE_GROUP_DOMAIN_ADMIN]))) { this.domainMode = true; users = this.userService.getAll(this.domainId); } else { diff --git a/src/app/appmarket/users/userdetails/userdetails.component.html b/src/app/appmarket/users/userdetails/userdetails.component.html index 6baf5888a19174411af0589f1fcfed73cbcb78e1..f728a79ce158c48bcbe6db89c05d4452d03a47c3 100644 --- a/src/app/appmarket/users/userdetails/userdetails.component.html +++ b/src/app/appmarket/users/userdetails/userdetails.component.html @@ -7,5 +7,6 @@ <div> <nmaas-userdetails [user]="user" [(userDetailsMode)]="userDetailsMode" [allowedModes]="[ComponentMode.VIEW, ComponentMode.EDIT]" [(errorMessage)]="errorMessage" (onSave)="onSave($event)" (refresh)="onRefresh()"></nmaas-userdetails> <nmaas-userprivileges [allowedModes]="[ComponentMode.VIEW, ComponentMode.CREATE]" [user]="user"></nmaas-userprivileges> + <nmaas-ssh-keys *roles="['ROLE_SYSTEM_ADMIN']" [userMode]="true" [userId]="user?.id"></nmaas-ssh-keys> </div> </div> diff --git a/src/app/appmarket/users/users.routes.ts b/src/app/appmarket/users/users.routes.ts index abe74e43673b587888d86cf984e1d54252e1e2fa..82be539df3e5deed04c5375cb9d52fabc5602bc8 100644 --- a/src/app/appmarket/users/users.routes.ts +++ b/src/app/appmarket/users/users.routes.ts @@ -10,5 +10,5 @@ export const UsersRoutes: Route[] = [ { path: 'admin/users/view/:id', component: UserDetailsComponent, canActivate: [AuthGuard, RoleGuard], data: {mode: ComponentMode.VIEW, roles: ['ROLE_SYSTEM_ADMIN']} }, { path: 'domain/users', component: UsersListComponent, canActivate: [AuthGuard, RoleGuard], - data: {roles: ['ROLE_DOMAIN_ADMIN', 'ROLE_VL_MANAGER', 'ROLE_VL_MANAGER']}}, + data: {roles: ['ROLE_DOMAIN_ADMIN', 'ROLE_GROUP_MANAGER', 'ROLE_GROUP_MANAGER']}}, ]; diff --git a/src/app/auth/auth.guard.ts b/src/app/auth/auth.guard.ts index aea400e0a3ccf6885624238b496fcbd5186d9e2e..a0578db6eb36a04c7c1458465f54678545a43810 100644 --- a/src/app/auth/auth.guard.ts +++ b/src/app/auth/auth.guard.ts @@ -2,6 +2,7 @@ import {Injectable} from '@angular/core'; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import {AuthService} from './auth.service'; import {ConfigurationService} from '../service'; +import { debounceTime } from 'rxjs'; @Injectable() export class AuthGuard { @@ -11,13 +12,7 @@ export class AuthGuard { public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { if (this.auth.isLogged()) { - this.maintenanceService.getConfiguration().subscribe(value => { - if (!this.auth.hasRole('ROLE_SYSTEM_ADMIN') && value.maintenance) { - this.auth.logout(); - this.router.navigate(['/welcome/login']); - return false; - } - }); + if(this.auth.hasRole('ROLE_INCOMPLETE') && route.url.toString() !== 'complete') { this.router.navigate(['/complete']); return false; diff --git a/src/app/auth/auth.service.spec.ts b/src/app/auth/auth.service.spec.ts index 6b0bb367feea3e0729c83c0209409e8d8d32dace..170ece28132bc78a75fd54ad8338c48914b82ea4 100644 --- a/src/app/auth/auth.service.spec.ts +++ b/src/app/auth/auth.service.spec.ts @@ -1,16 +1,20 @@ /* tslint:disable:no-unused-variable */ -import { TestBed, waitForAsync } from '@angular/core/testing'; +import {TestBed, waitForAsync} from '@angular/core/testing'; import {AuthService} from './auth.service'; -import {AppConfigService} from '../service'; +import {AppConfigService, ConfigurationService} from '../service'; import {JwtHelperService} from '@auth0/angular-jwt'; -import {HttpClientTestingModule} from '@angular/common/http/testing'; -import {Role} from '../model/userrole'; +import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; +import {Role, UserRole} from '../model/userrole'; +import {ProfileService} from '../service/profile.service'; +import {Observable, of} from 'rxjs'; +import {Configuration} from '../model/configuration'; describe('Service: Auth', () => { let authService: AuthService; let appConfigServiceSpy: jasmine.SpyObj<AppConfigService>; let jwtHelperServiceSpy: jasmine.SpyObj<JwtHelperService>; - + let maintenanceServiceSpy: jasmine.SpyObj<ConfigurationService>; + let httpMock: HttpTestingController; let store: any = {}; beforeEach(waitForAsync(() => { @@ -18,34 +22,74 @@ describe('Service: Auth', () => { config: { apiUrl: 'http://api.url', tokenName: 'token', - } + }, + getTestInstanceModalKey: () => 'testModalKey' }; - const jwtSpy = jasmine.createSpyObj('JwtHelperService', ['decodeToken', 'isTokenExpired']); jwtSpy.decodeToken.and.returnValue({ language: 'pl', sub: 'test-user', - scopes: [{authority: '1:' + Role[Role.ROLE_SYSTEM_ADMIN]}, {authority: '2:' + Role[Role.ROLE_USER]}] + global_role: ['ROLE_SYSTEM_ADMIN'], + roles: [`ROLE_USER`] }); jwtSpy.isTokenExpired.and.callFake((arg: string): boolean => { return arg !== 'valid'; }); + maintenanceServiceSpy = jasmine.createSpyObj('maintenanceService', ['getConfiguration']); + maintenanceServiceSpy.getConfiguration.and.returnValue(of()) + + class MockConfigurationService { + protected uri: string; + + constructor() { + this.uri = 'http://localhost/api'; + } + + public getApiUrl(): string { + return 'http://localhost/api'; + } + + public getConfiguration(): Observable<Configuration> { + return of<Configuration>(); + } + + public updateConfiguration(configuration: Configuration): Observable<any> { + return of<Configuration>(); + } + } + + + const userRole = new UserRole(); + userRole.role = Role.ROLE_SYSTEM_ADMIN; + userRole.domainName = 'test'; + userRole.domainId = 1; + const userRole2 = new UserRole(); + userRole2.role = Role.ROLE_USER; + userRole2.domainName = 'test2'; + userRole2.domainId = 2; + const profileServiceStub = jasmine.createSpyObj('ProfileService', ['getRoles']); + profileServiceStub.getRoles.and.returnValue(of([userRole, userRole2])) + TestBed.configureTestingModule({ imports: [ - HttpClientTestingModule + HttpClientTestingModule, ], providers: [ AuthService, {provide: AppConfigService, useValue: appConfigServiceStub}, {provide: JwtHelperService, useValue: jwtSpy}, + {provide: ProfileService, useValue: profileServiceStub}, + {provide: ConfigurationService, useClass: MockConfigurationService} ], }); + httpMock = TestBed.inject(HttpTestingController) authService = TestBed.get(AuthService); + authService.profile = [userRole, userRole2] appConfigServiceSpy = TestBed.get(AppConfigService); jwtHelperServiceSpy = TestBed.get(JwtHelperService); - // spyOn(appConfigServiceSpy, 'getTestInstanceModalKey').and.returnValue("test-instance-modal-key"); + // maintenanceServiceSpy = TestBed.get(ConfigurationService); // local store mock store = {token: 'valid'}; @@ -61,6 +105,10 @@ describe('Service: Auth', () => { }); })); + afterEach(() => { + httpMock.verify(); + store = {}; + }); it('should create service', () => { expect(authService).toBeTruthy(); @@ -99,7 +147,7 @@ describe('Service: Auth', () => { }); it('should return domains from roles', () => { - const result = authService.getDomains(); + const result = authService.getDomains(); expect(result).toContain(1); expect(result).toContain(2); store = {token: null}; @@ -137,8 +185,11 @@ describe('Service: Auth', () => { }); it('should remove token on logout', () => { + store['oidc-token'] = 'some-oidc-token'; authService.logout(); expect(store['token']).not.toBeDefined(); + const req = httpMock.expectOne('http://api.url/oidc/logout/some-oidc-token'); + req.flush({}); }); it('should be logged in when token is present and valid', () => { @@ -154,4 +205,58 @@ describe('Service: Auth', () => { expect(r).toEqual(false); }); + it('should store token and oidc token in localStorage', () => { + authService.storeToken('abc123'); + expect(store['token']).toEqual('abc123'); + + authService.storeOidcToken('oidc456'); + expect(store['oidc-token']).toEqual('oidc456'); + }); + + it('should remove roles from localStorage', () => { + store['rolesToken'] = 'some_roles'; + authService.removeRoles(); + expect(store['rolesToken']).toBeUndefined(); + }); + + it('should load and parse roles from localStorage', () => { + const roles = [{domainId: 1, role: Role.ROLE_USER, domainName: 'x'}]; + store['rolesToken'] = JSON.stringify(roles); + + const result = authService.loadRoles(); + expect(result.length).toEqual(1); + expect(result[0].role).toEqual(Role.ROLE_USER); + }); + + it('should assign loaded roles to profile', () => { + const roles = [{domainId: 2, role: Role.ROLE_DOMAIN_ADMIN, domainName: 'x'}]; + store['rolesToken'] = JSON.stringify(roles); + authService.loadAndSaveRoles(); + expect(authService.profile[0].role).toEqual(Role.ROLE_DOMAIN_ADMIN); + }); + it('should stringify and store roles', () => { + const roles = [new UserRole()]; + roles[0].domainId = 1; + roles[0].role = Role.ROLE_USER; + roles[0].domainName = 'dom1'; + + authService.storeRoles(roles); + expect(store['rolesToken']).toContain('ROLE_USER'); + }); + it('should get global role from token', () => { + const result = authService.getGlobalRole(); + expect(result).toContain('ROLE_SYSTEM_ADMIN'); + }); + + it('should handle login error with catchError', waitForAsync(() => { + authService.login('user', 'pass').subscribe({ + next: () => fail('Expected error'), + error: (err) => { + expect(err.status).toEqual(401); + } + }); + + const req = httpMock.expectOne('http://api.url/auth/basic/login'); + req.flush({ message: 'Invalid credentials' }, { status: 401, statusText: 'Unauthorized' }); + })); }); diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts index ca9cd4baf71a4e61c5ebc46832ca7e0d4d821875..6156c8e479cfd788ed7808556017f30cb57bc40f 100644 --- a/src/app/auth/auth.service.ts +++ b/src/app/auth/auth.service.ts @@ -1,17 +1,18 @@ -import {BehaviorSubject, Observable, Subject, throwError as observableThrowError} from 'rxjs'; +import {BehaviorSubject, Observable, of, Subject, throwError as observableThrowError} from 'rxjs'; import {catchError, debounceTime, map} from 'rxjs/operators'; import {Injectable} from '@angular/core'; -import {AppConfigService} from '../service'; +import {AppConfigService, ConfigurationService} from '../service'; import {JwtHelperService} from '@auth0/angular-jwt'; import {HttpClient, HttpHeaders} from '@angular/common/http'; -import {Authority} from '../model'; +import {ProfileService} from '../service/profile.service'; +import {Role, UserRole} from '../model/userrole'; -export class DomainRoles { - constructor(private domainId: number, private roles: string[] = []) { - } - public getDomainId(): number { - return this.domainId; +export class DomainRoles { + constructor( + private domainId: number, + private roles: string[] = [] + ) { } public getRoles(): string[] { @@ -28,25 +29,101 @@ export class AuthService { public loginUsingSsoService: boolean; private readonly isLoggedInSubject: Subject<boolean> = new BehaviorSubject<boolean>(false); + public profile: UserRole[] + + private rolesTabelName = 'rolesToken' + + private refresh: any; + + private maintenance: boolean = false; constructor(private http: HttpClient, private appConfig: AppConfigService, - private jwtHelper: JwtHelperService) { + private jwtHelper: JwtHelperService, + private profileService: ProfileService, + private maintenanceService: ConfigurationService) { + this.loadAndSaveRoles(); + this.loadUser() + this.getConfigurationToCheckMaintenance(); + } + + public loadUser(): void { + + this.profileService.getRoles().subscribe(roles => { + this.profile = roles + this.storeRoles(roles) + }) + } + + public refreshUserRoles(): void { + this.refresh = setInterval(() => { + if (this.isLogged()) { + this.loadUser(); + } + }, 60000); + } + + private getConfigurationToCheckMaintenance() { + this.maintenanceService.getConfiguration().subscribe(value => { + if (value !== undefined && value !== null && value.maintenance) { + console.warn('Maintenance is on. Disabled login.') + this.isLoggedInSubject.next(false); + this.logout(); + this.maintenance = true; + return false; + } + }); } + //TODO make this static again and serive this feature in other way public storeToken(token: string): void { localStorage.setItem(this.appConfig.config.tokenName, token); } + public storeRoles(roles: UserRole[]): void { + const rolesString = JSON.stringify(roles); + localStorage.setItem(this.rolesTabelName, rolesString); + } + + public loadAndSaveRoles() { + this.profile = this.loadRoles(); + } + + public loadRoles(): UserRole[] { + const rolesString = localStorage.getItem(this.rolesTabelName); + if (!rolesString) { + return null; + } + + const parsed = JSON.parse(rolesString); + return parsed.map((item: any) => Object.assign(new UserRole(), item)); + } + + public removeRoles(): void { + localStorage.removeItem(this.rolesTabelName) + } + + public storeOidcToken(token: string): void { + localStorage.setItem('oidc-token', token); + } + private getToken(): string { return localStorage.getItem(this.appConfig.config.tokenName) } + private getOidcToken(): string { + return localStorage.getItem('oidc-token') + } + private removeToken(): void { localStorage.removeItem(this.appConfig.config.tokenName); } + private removeOidcToken(): void { + localStorage.removeItem('oidc-token'); + } + public getSelectedLanguage(): string { if (localStorage.getItem('lang') != null) { return localStorage.getItem('lang') @@ -59,62 +136,60 @@ export class AuthService { return (token ? this.jwtHelper.decodeToken(token).sub : null); } - public hasRole(name: string): boolean { + public getPreferredUsername(): string { const token = this.getToken(); - const authorities: Authority[] = this.jwtHelper.decodeToken(token).scopes; - for (let i = 0; i < authorities.length; i++) { - if (authorities[i].authority.indexOf(name) > -1) { + return (token ? this.jwtHelper.decodeToken(token).preferred_username : null); + } + + public hasRole(name: string): boolean { + + const roles = this.getRoles() + + for (const role of roles) { + if (role === name) { return true; } } return false; + } public hasDomainRole(domainId: number, name: string): boolean { - const token = this.getToken(); - const authorities: Authority[] = this.jwtHelper.decodeToken(token).scopes; - for (let i = 0; i < authorities.length; i++) { - if (authorities[i].authority.indexOf(domainId + ':' + name) > -1) { - return true; + let result = false; + const domainRoles: Map<number, DomainRoles> = this.getDomainRoles(); + for (const [mapDomainId, domainRolesValue] of domainRoles) { + if (mapDomainId === domainId) { + domainRolesValue.getRoles().forEach(role => { + if (role === name) { + result = true; + } + }) } } - return false; + return result; } - public getDomainRoles(): Map<number, DomainRoles> { - const drMap: Map<number, DomainRoles> = new Map<number, DomainRoles>(); - + public getGlobalRole(): string[] { const token = this.getToken(); if (token == null) { - return drMap; + return null; } + return this.jwtHelper.decodeToken(token).global_role; + } - const authorities: Authority[] = this.jwtHelper.decodeToken(token).scopes; - if (authorities == null) { - return drMap; - } + public getDomainRoles(): Map<number, DomainRoles> { + const domainRolesMap: Map<number, DomainRoles> = new Map<number, DomainRoles>(); - for (let index = 0; index < authorities.length; index++) { - if (authorities[index].authority === undefined) { - continue; - } + const domains: number[] = this.getDomains(); + for (const domain of domains) { + const roles: string[] = this.profile + .filter(userRole => userRole.domainId === domain) + .map(userRole => Role[userRole.role]) - const domainRole: string[] = authorities[index].authority.split(':', 2); - if (domainRole.length !== 2) { - continue; - } - const domainId: number = Number.parseInt(domainRole[0], 10); - const role: string = domainRole[1]; + domainRolesMap.set(domain, new DomainRoles(domain, roles)); - let dr: DomainRoles; - if (!drMap.has(domainId)) { - drMap.set(domainId, new DomainRoles(domainId, [])); - } - dr = drMap.get(domainId); - dr.getRoles().push(role); } - - return drMap; + return domainRolesMap; } public getRoles(): string[] { @@ -124,56 +199,35 @@ export class AuthService { if (token == null) { return roles; } + const domainRoles: string[] = this.jwtHelper.decodeToken(token).roles; + const globalRole: string[] = this.jwtHelper.decodeToken(token).global_role; - const authorities: Authority[] = this.jwtHelper.decodeToken(token).scopes; - for (let index = 0; index < authorities.length; index++) { - if (authorities[index].authority === undefined) { - continue; - } + roles.push(globalRole[0]); - const domainRole: string[] = authorities[index].authority.split(':', 2); - if (domainRole.length !== 2) { - continue; - } - const role: string = domainRole[1]; - if (roles.indexOf(role) === -1) { - roles.push(role); - } + for (const role of domainRoles) { + + roles.push(role); } + return roles; } public getDomains(): number[] { - const domains: number[] = []; - - const token = this.getToken(); - if (token == null) { - return domains; - } - - const authorities: Authority[] = this.jwtHelper.decodeToken(token).scopes; - - for (let index = 0; index < authorities.length; index++) { - if (authorities[index].authority === undefined) { - continue; + if (this.isLogged()) { + if (this.profile !== undefined && this.profile !== null) { + return this.getDomainIds(); + } else { + return []; } - const domainIdStr: string[] = authorities[index].authority.split(':', 1); - if (domainIdStr.length === 0) { - continue; - } - const domainId: number = Number.parseInt(domainIdStr[0], 10); - if (domains.indexOf(domainId) === -1) { - domains.push(domainId); - } } - return domains; + return []; + } public getDomainsWithRole(name: string): number[] { const domainsWithRole: number[] = []; - const domains: number[] = this.getDomains(); domains.forEach((domainId) => { if (this.hasDomainRole(domainId, name)) { @@ -184,10 +238,60 @@ export class AuthService { return domainsWithRole; } + public oidcLinkingLogin(oidcToken: string, + email: string, + password: string, + uuid: string, + firstName: string, + lastName: string) { + const headers = new HttpHeaders({'Content-Type': 'application/json', 'Accept': 'application/json'}); + + return this.http.post(this.appConfig.config.apiUrl + '/oidc/link', + JSON.stringify( + { + 'oidcToken': oidcToken, + 'email': email, + 'password': password, + 'uuid': uuid, + 'firstName': firstName, + 'lastName': lastName, + } + ), + {headers: headers}).pipe( + debounceTime(1000), + map((res: Response) => { + const token = res && res['token']; + const oidcToken = res && res['oidcToken']; + if (token && oidcToken) { + this.storeToken(token); + this.storeOidcToken(oidcToken); + this.loginUsingSsoService = false; + this.isLoggedInSubject.next(true); + this.profileService.getRoles().subscribe(profile => { + this.profile = profile + this.storeRoles(profile); + return true; + }) + } else { + this.isLoggedInSubject.next(false); + return false; + } + } + ), + ) + } + public login(username: string, password: string): Observable<boolean> { // hack so test instance modal is shown onl after login localStorage.setItem(this.appConfig.getTestInstanceModalKey(), 'True'); + if (this.maintenance) { + this.isLoggedInSubject.next(false); + console.warn('Maintenance is on. Disabled login.') + //add toast here + return of(false); + } + const headers = new HttpHeaders({'Content-Type': 'application/json', 'Accept': 'application/json'}); return this.http.post(this.appConfig.config.apiUrl + '/auth/basic/login', JSON.stringify({'username': username, 'password': password}), {headers: headers}).pipe( @@ -206,7 +310,11 @@ export class AuthService { console.debug('AUTH | DomainRoles: ' + this.getDomainRoles()); this.loginUsingSsoService = false; this.isLoggedInSubject.next(true); - return true; + this.profileService.getRoles().subscribe(profile => { + this.profile = profile + this.storeRoles(profile); + return true; + }) } else { // return false to indicate failed login this.isLoggedInSubject.next(false); @@ -226,48 +334,26 @@ export class AuthService { })); } - public propagateSSOLogin(userid: string): Observable<boolean> { - console.debug('propagateSSOLogin'); - console.debug('propagateSSOLogin ' + this.appConfig.config.apiUrl); - console.debug('propagateSSOLogin ' + this.appConfig.config.apiUrl + '/auth/sso/login'); - console.debug('propagateSSOLogin ' + userid); - // hack so test instance modal is shown onl after login - localStorage.setItem(this.appConfig.getTestInstanceModalKey(), 'True'); - - const headers = new HttpHeaders({'Content-Type': 'application/json', 'Accept': 'application/json'}); - return this.http.post(this.appConfig.config.apiUrl + '/auth/sso/login', - JSON.stringify({'userid': userid}), {headers: headers}).pipe( - debounceTime(10000), - map((response: Response) => { - console.debug('SSO login response: ' + response); - // login successful if there's a jwt token in the response - const token = response && response['token']; - - if (token) { - this.storeToken(token); - console.debug('SSO AUTH | User: ' + this.getUsername()); - console.debug('SSO AUTH | Domains: ' + this.getDomains()); - console.debug('SSO AUTH | Roles: ' + this.getRoles()); - console.debug('SSO AUTH | DomainRoles: ' + this.getDomainRoles()); - this.loginUsingSsoService = true; - this.isLoggedInSubject.next(true); - return true; - } else { - // return false to indicate failed login - this.isLoggedInSubject.next(false); - return false; - } - }), - catchError((error) => { - console.error('SSO login error: ' + error.error['message']); - return observableThrowError(error); - })); + public logout(): void { + const oidcToken = this.getOidcToken(); + this.refresh = null; + if (oidcToken === null) { + this.removeToken(); + this.isLoggedInSubject.next(false); + localStorage.removeItem('_expiredTime'); + } else { + this.removeToken(); + this.removeOidcToken(); + this.isLoggedInSubject.next(false); + localStorage.removeItem('_expiredTime'); + this.http.get(this.appConfig.config.apiUrl + '/oidc/logout/' + oidcToken).subscribe(() => { + }) + } } - public logout(): void { - this.removeToken(); - this.isLoggedInSubject.next(false); - localStorage.removeItem('_expiredTime'); + public oidcLogout(oidcToken: string): void { + this.http.get(this.appConfig.config.apiUrl + '/oidc/logout/' + oidcToken).subscribe(() => { + }) } public isLogged(): boolean { @@ -278,10 +364,9 @@ export class AuthService { return (token ? !this.jwtHelper.isTokenExpired(token) : false); } - get isLoggedIn$(): Observable<boolean> { - return this.isLoggedInSubject.pipe( - debounceTime(100), // use debounceTime to aggregate multiple emissions https://rxjs.dev/api/operators/debounceTime - ); + + public getDomainIds(): number[] { + return Array.from(new Set(this.profile.map(ur => ur.domainId))); } } diff --git a/src/app/auth/login-success/login-success.component.ts b/src/app/auth/login-success/login-success.component.ts index d84fe015ab346f24da5bd1a2e1743b6ade19eca9..e7fec74d4dcaa6d40ec0da867a432314d09eb307 100644 --- a/src/app/auth/login-success/login-success.component.ts +++ b/src/app/auth/login-success/login-success.component.ts @@ -1,5 +1,5 @@ import {Component, OnInit} from '@angular/core'; -import {ActivatedRoute} from '@angular/router'; +import {ActivatedRoute, Router} from '@angular/router'; import {AuthService} from '../auth.service'; @Component({ @@ -8,19 +8,25 @@ import {AuthService} from '../auth.service'; styleUrls: ['./login-success.component.css'] }) export class LoginSuccessComponent implements OnInit { - constructor(private route: ActivatedRoute, - private authService: AuthService) { + constructor(private readonly router: Router, + private readonly route: ActivatedRoute, + private readonly authService: AuthService) { } ngOnInit(): void { - // Pobieranie tokena z parametrów URL this.route.queryParams.subscribe(params => { const token = params['token']; const refreshToken = params['refresh_token']; + const oidcToken = params['oidc_token']; if (token) { this.authService.storeToken(token); } - }); + if (refreshToken) { + this.authService.storeOidcToken(oidcToken); + } + this.router.navigate(['/']) + }) + } } diff --git a/src/app/model/bulk-deployment.ts b/src/app/model/bulk-deployment.ts index bf8828bb54fb0fa2dfdbfb0ad7cfc50f725979c9..1af669ecbe0bd3e05068993accf1dd17854d90e4 100644 --- a/src/app/model/bulk-deployment.ts +++ b/src/app/model/bulk-deployment.ts @@ -10,6 +10,8 @@ export class BulkDeployment { public type: BulkType; public details: Map<string, string>; public parallelDeploymentsLimit: number; + public deleted: boolean; + public completionDate: Date; } export enum BulkDeploymentState { diff --git a/src/app/model/bulk-queue-details.ts b/src/app/model/bulk-queue-details.ts new file mode 100644 index 0000000000000000000000000000000000000000..a86da4af02b6c0151783ddbe7733b54db57d5877 --- /dev/null +++ b/src/app/model/bulk-queue-details.ts @@ -0,0 +1,8 @@ +export class BulkQueueDetails { + + public jobInProcess: number; + public jobInProcessId: number; + public jobInQueue: number; + public jobDone: number; + public bulkJobInQueue: number; +} \ No newline at end of file diff --git a/src/app/model/configuration.ts b/src/app/model/configuration.ts index 3bffd6e8f90421fa78c8e722ece5d6de9b8aeb2d..40896c07bfc2b1439633aef88f05cb1660beceec 100644 --- a/src/app/model/configuration.ts +++ b/src/app/model/configuration.ts @@ -11,4 +11,7 @@ export class Configuration { public appInstanceFailureEmailList: string[] = []; public bulkDeploymentJobCron: string; public parallelDeploymentsLimit: number; + public bulkDeploymentQueueRefresh: number; + public deploymentPrefix: string; + public bulkDeploymentTimeThreshold: number; } diff --git a/src/app/model/userrole.ts b/src/app/model/userrole.ts index ee06e84b3828a14565b91448cae2c595b73fda3a..acf7ac2f0228e50b8220b4e1812e47fd4377a473 100644 --- a/src/app/model/userrole.ts +++ b/src/app/model/userrole.ts @@ -1,14 +1,14 @@ export enum Role { - ROLE_SYSTEM_ADMIN, - ROLE_DOMAIN_ADMIN, - ROLE_OPERATOR, - ROLE_TOOL_MANAGER, - ROLE_USER, - ROLE_GUEST, - ROLE_INCOMPLETE, - ROLE_NOT_ACCEPTED, - ROLE_VL_MANAGER, - ROLE_VL_DOMAIN_ADMIN + ROLE_SYSTEM_ADMIN = 'ROLE_SYSTEM_ADMIN', + ROLE_DOMAIN_ADMIN = 'ROLE_DOMAIN_ADMIN', + ROLE_OPERATOR = "ROLE_OPERATOR", + ROLE_TOOL_MANAGER = "ROLE_TOOL_MANAGER", + ROLE_USER = "ROLE_USER", + ROLE_GUEST = "ROLE_GUEST", + ROLE_INCOMPLETE = "ROLE_INCOMPLETE", + ROLE_NOT_ACCEPTED = "ROLE_NOT_ACCEPTED", + ROLE_GROUP_MANAGER = "ROLE_GROUP_MANAGER", + ROLE_GROUP_DOMAIN_ADMIN = "ROLE_GROUP_DOMAIN_ADMIN", } export function RoleAware(constructor: Function) { diff --git a/src/app/service/appconfig.service.ts b/src/app/service/appconfig.service.ts index c9988bcc13d5245f923bc152e29f7618cc774ac7..df61388c90ac79ee006e8922fb695a032bae7ed8 100644 --- a/src/app/service/appconfig.service.ts +++ b/src/app/service/appconfig.service.ts @@ -25,10 +25,7 @@ export class AppConfigService { } public getOidcUrl(): string { - if (this.config == null) { - return 'http://localhost:9000/oauth2/authorization/my-oidc'; - } - return this.config.oidcUrl; + return this.config.apiUrl + '/oauth2/authorization/my-oidc'; } public getApiUrl(): string { @@ -67,6 +64,7 @@ export class AppConfigService { } public getSiteKey(): string { + console.log("Site key:", this.config.captchaKey) if (this.config == null) { return ''; } diff --git a/src/app/service/cluster.service.ts b/src/app/service/cluster.service.ts index dace40815e83787a5897843b9d7206c153ca2686..4830699766480719290f664be76a554b8c85725d 100644 --- a/src/app/service/cluster.service.ts +++ b/src/app/service/cluster.service.ts @@ -17,22 +17,11 @@ export class ClusterService extends GenericDataService { constructor(http: HttpClient, appConfig: AppConfigService) { super(http, appConfig); - this.url = this.appConfig.getApiUrl() + '/management/kubernetes/'; + this.url = this.appConfig.getApiUrl() + '/management/kubernetes'; } public getCluster(): Observable<Cluster> { return this.get<Cluster>(this.url); } - public add(cluster: Cluster): Observable<any> { - return this.post(this.url, cluster); - } - - public update(cluster: Cluster): Observable<any> { - return this.put(this.url + cluster.id, cluster); - } - - public remove(clusterId: number): Observable<any> { - return this.http.delete(this.url + clusterId); - } } diff --git a/src/app/service/configuration.service.ts b/src/app/service/configuration.service.ts index 9a247125cd18b4bd8f7cb2bb0e74e19cc9e62d10..9d93b4018f6ead3cfc3eb71c3db60417323b71bd 100644 --- a/src/app/service/configuration.service.ts +++ b/src/app/service/configuration.service.ts @@ -22,7 +22,7 @@ export class ConfigurationService extends GenericDataService{ } public updateConfiguration(configuration: Configuration): Observable<any>{ - return this.put(this.uri + configuration.id, configuration); + return this.put(this.uri + "/"+ configuration.id, configuration); } } diff --git a/src/app/service/domain.service.ts b/src/app/service/domain.service.ts index ead7327158be7cadb18056da214ba9224f67a410..7ed0257a4aca76390edd525a84274cb6667c150d 100644 --- a/src/app/service/domain.service.ts +++ b/src/app/service/domain.service.ts @@ -117,7 +117,7 @@ export class DomainService extends GenericDataService { } public updateDomainGroupManagers(managers: User[], id: number): Observable<DomainGroup> { - return this.put(this.url + 'group/members/' + id, managers); + return this.put(this.url + '/group/members/' + id, managers); } public getAnnotations(): Observable<DomainAnnotation[]> { diff --git a/src/app/service/profile.service.ts b/src/app/service/profile.service.ts index 0c745763a950266d06cfa37e2f574eeb336a2cc8..ee77d26c2101c874456c1d6ebd1e868aa0cc68d7 100644 --- a/src/app/service/profile.service.ts +++ b/src/app/service/profile.service.ts @@ -4,6 +4,7 @@ import {AppConfigService} from './appconfig.service'; import {Observable} from 'rxjs'; import {User} from '../model'; import {HttpClient} from '@angular/common/http'; +import { UserRole } from '../model/userrole'; @Injectable({ @@ -19,6 +20,10 @@ export class ProfileService extends GenericDataService { return this.http.get<User>(this.getProfileUrl() + 'user') } + public getRoles(): Observable<UserRole[]> { + return this.http.get<UserRole[]>(this.getProfileUrl() + 'user/roles') + } + protected getProfileUrl(): string { return this.appConfig.getApiUrl() + '/profile/' } diff --git a/src/app/service/shell-client.service.ts b/src/app/service/shell-client.service.ts index 7a1cb49aef95fb181afe999a4bfaa17bc8599141..7d920a07bae5f470a5fc97ea19b286993a4b1ecf 100644 --- a/src/app/service/shell-client.service.ts +++ b/src/app/service/shell-client.service.ts @@ -18,11 +18,11 @@ export class ShellClientService { public initConnection(id: number, pod: string): Observable<string> { // @ts-ignore - return this.http.post<string>(this.appConfig.getApiUrl() + '/shell/' + id + '/init/' + pod, {}, {responseType: 'text'}); + return this.http.post<string>(this.appConfig.getApiUrl() + '/pods/shell/' + id + '/init/' + pod, {}, {responseType: 'text'}); } public sendCommand(sessionId: string, command: Object = {}): Observable<any> { - return this.http.post(this.appConfig.getApiUrl() + '/shell/' + sessionId + '/command', command); + return this.http.post(this.appConfig.getApiUrl() + '/pods/shell/' + sessionId + '/command', command); } /** @@ -30,7 +30,7 @@ export class ShellClientService { */ public closeConnection(sessionId: string) { this.closeEventStream() - this.http.delete(this.appConfig.getApiUrl() + '/shell/' + sessionId).subscribe( + this.http.delete(this.appConfig.getApiUrl() + '/pods/shell/' + sessionId).subscribe( () => console.log('session completed: ', sessionId), error => console.error('error completing session', error)) } @@ -42,7 +42,7 @@ export class ShellClientService { public getServerSentEvent(sessionId: string): Observable<OnMessageEvent> { return new Observable<OnMessageEvent>(observableEvents => { - const events = this._sseService.getEventSource(this.appConfig.getApiUrl() + '/shell/' + sessionId); + const events = this._sseService.getEventSource(this.appConfig.getApiUrl() + '/pods/shell/' + sessionId); events.onopen = onopenEvent => { this._zone.run(() => { @@ -67,7 +67,7 @@ export class ShellClientService { } public getPossiblePods(id: number): Observable<PodInfo[]> { - return this.http.get<PodInfo[]>(this.appConfig.getApiUrl() + '/shell/' + id + '/podnames'); + return this.http.get<PodInfo[]>(this.appConfig.getApiUrl() + '/pods/shell/' + id + '/podnames'); } } diff --git a/src/app/service/sshkey.service.ts b/src/app/service/sshkey.service.ts index f6b47b7245f782a9190f9bae5b6d1347cd398bdf..32424f472417e56ad29d71c83ff42cb5959f862b 100644 --- a/src/app/service/sshkey.service.ts +++ b/src/app/service/sshkey.service.ts @@ -26,4 +26,17 @@ export class SSHKeyService extends GenericDataService { public invalidate(id: number): Observable<any> { return this.delete(this.appConfig.getApiUrl() + '/user/keys/' + id); } + + public getAllByUserId(userId: number): Observable<SSHKeyView[]> { + return this.get<SSHKeyView[]>(this.appConfig.getApiUrl() + '/user/keys/view/' + userId); + } + + public createKeyForUser(request: SSHKeyRequest, userId: number ): Observable<any> { + return this.put<SSHKeyRequest, any>(this.appConfig.getApiUrl() + '/user/keys/view/' + userId, request); + } + + public invalidateUserKey(keyId: number, userId: number): Observable<any> { + return this.delete(this.appConfig.getApiUrl() + '/user/keys/view/' + userId + "/" + keyId); + } + } diff --git a/src/app/service/sso.service.spec.ts b/src/app/service/sso.service.spec.ts deleted file mode 100644 index 447e1159701776ff09a20d63fefdee219f341f06..0000000000000000000000000000000000000000 --- a/src/app/service/sso.service.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { TestBed, inject } from '@angular/core/testing'; - -import { SSOService } from './sso.service'; -import {Observable, of} from 'rxjs'; -import {Configuration} from '../model/configuration'; -import {HttpClient, HttpHandler} from '@angular/common/http'; -import {AppConfigService} from './appconfig.service'; - -class MockConfigurationService { - protected uri: string; - - constructor() { - this.uri = 'http://localhost/api'; - } - - public getApiUrl(): string { - return 'http://localhost/api'; - } - - public getConfiguration(): Observable<Configuration> { - return of<Configuration>(); - } - - public updateConfiguration(configuration: Configuration): Observable<any> { - return of<Configuration>(); - } -} - -describe('SSOService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [SSOService, HttpHandler, HttpClient, {provide: AppConfigService, useClass: MockConfigurationService}] - }); - }); - - it('should be created', inject([SSOService], (service: SSOService) => { - expect(service).toBeTruthy(); - })); -}); diff --git a/src/app/service/sso.service.ts b/src/app/service/sso.service.ts deleted file mode 100644 index c3ff73bfa55445414fc44f6c923af9607c6dc61b..0000000000000000000000000000000000000000 --- a/src/app/service/sso.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Injectable } from '@angular/core'; -import {GenericDataService} from './genericdata.service'; -import {HttpClient} from '@angular/common/http'; -import {AppConfigService} from './appconfig.service'; -import {Observable} from 'rxjs'; -import {SSOConfig} from '../model/sso'; - -@Injectable() -export class SSOService extends GenericDataService { - - protected url: string; - - constructor(http: HttpClient, appConfig: AppConfigService) { - super(http, appConfig); - this.url = this.appConfig.getApiUrl() + '/auth/sso'; - } - - public getOne(): Observable<SSOConfig> { - return this.get<SSOConfig>(this.url); - } - -} diff --git a/src/app/service/user.service.ts b/src/app/service/user.service.ts index bfdf43c7362644120cee3c2a4ac7dfac4e6c3494..8393eb431889e4f88d5c70304a12104b3a1634d7 100644 --- a/src/app/service/user.service.ts +++ b/src/app/service/user.service.ts @@ -20,19 +20,19 @@ export class UserService extends GenericDataService { public getAll(domainId?: number): Observable<User[]> { return this.get<User[]>(domainId === undefined || domainId === this.domainService.getGlobalDomainId() ? - this.getUsersUrl() : this.getDomainUsersUrl(domainId)); + this.getUsersUrlWithoutDash() : this.getDomainUsersUrlWithoutDash(domainId)); } public getOne(userId: number, domainId?: number): Observable<User> { - return this.get<User>((domainId === undefined ? this.getUsersUrl() : this.getDomainUsersUrl(domainId)) + '/' + userId); + return this.get<User>((domainId === undefined ? this.getUsersUrl() : this.getDomainUsersUrl(domainId)) + userId); } public deleteOne(userId: number, domainId?: number): Observable<any> { - return this.delete<any>((domainId === undefined ? this.getUsersUrl() : this.getDomainUsersUrl(domainId)) + '/' + userId); + return this.delete<any>((domainId === undefined ? this.getUsersUrl() : this.getDomainUsersUrl(domainId)) + userId); } public getRoles(userId: number, domainId?: number): Observable<UserRole[]> { - return this.get<UserRole[]>((domainId === undefined ? this.getUsersUrl() : this.getDomainUsersUrl(domainId)) + '/' + userId + '/roles'); + return this.get<UserRole[]>((domainId === undefined ? this.getUsersUrl() : this.getDomainUsersUrl(domainId)) + userId + '/roles'); } public updateUser(userId: number, user: User): Observable<any> { @@ -53,7 +53,7 @@ export class UserService extends GenericDataService { } public addRole(userId: number, role: Role, domainId?: number): Observable<any> { - const url: string = (domainId === undefined ? this.getUsersUrl() : this.getDomainUsersUrl(domainId)) + '/' + userId + '/roles'; + const url: string = (domainId === undefined ? this.getUsersUrl() : this.getDomainUsersUrl(domainId)) + userId + '/roles'; const targetDomainId: number = (domainId === undefined ? this.appConfig.getNmaasGlobalDomainId() : domainId); return this.post<UserRole, UserRole>(url, new UserRole(targetDomainId, undefined, role)); @@ -87,7 +87,16 @@ export class UserService extends GenericDataService { return this.appConfig.getApiUrl() + '/users/'; } + protected getUsersUrlWithoutDash(): string { + return this.appConfig.getApiUrl() + '/users'; + } + + protected getDomainUsersUrl(domainId: number): string { + return this.appConfig.getApiUrl() + '/domains/' + domainId + '/users/'; + } + + protected getDomainUsersUrlWithoutDash(domainId: number): string { return this.appConfig.getApiUrl() + '/domains/' + domainId + '/users'; } @@ -96,7 +105,7 @@ export class UserService extends GenericDataService { } protected getUserAcceptanceUrl(): string { - return this.appConfig.getApiUrl() + '/users/terms/'; + return this.appConfig.getApiUrl() + '/users/terms'; } protected getEnableOrDisableUsersUrl(userId: number, enabled: boolean): string { diff --git a/src/app/shared/about/about.component.css b/src/app/shared/about/about.component.css index cbf25d83e53161e2bfe9524f48abbe82c72e31e3..4a9f3de4150444470b0d301c1752aa16ef708229 100644 --- a/src/app/shared/about/about.component.css +++ b/src/app/shared/about/about.component.css @@ -16,3 +16,11 @@ padding-left: 15px; padding-right:15px; } +.position{ + display: flex; +} +@media (max-width: 991px){ + .position{ + flex-direction: column-reverse; + } +} diff --git a/src/app/shared/about/about.component.html b/src/app/shared/about/about.component.html index 624cbd8f01ae0b577f99c2acbb0643eb82124ec8..df6d0eb479e590e7436abc90906638ecbe4fb3d0 100644 --- a/src/app/shared/about/about.component.html +++ b/src/app/shared/about/about.component.html @@ -1,4 +1,4 @@ -<div class="col-md-offset-1 col-md-10 col-lg-offset-1 col-lg-10 row" style="padding-bottom: 80px; padding-top: 80px;"> +<div class="col-md-offset-1 col-md-10 col-lg-offset-1 col-lg-10 row position" style="padding-bottom: 80px; padding-top: 80px;"> <div class="col-md-6"> <div class="" style="padding-bottom: 15px;"> <h2>{{ 'ABOUT.CHANGELOG_TITLE' | translate }}</h2> @@ -41,16 +41,16 @@ <div class="col-md-6"> <div class="" style="padding-bottom: 15px;"> <h2>{{ 'ABOUT.CONTACT_TITLE' | translate }}</h2> - <span>{{ 'ABOUT.CONTACT_DESC' | translate}}</span> - <p>Join the mailing list: </p> + <p>Join the mailing list of your interest: </p> <ul> <li> - <p><a href="https://lists.geant.org/sympa/info/nmaas-announce">low frequency nmaas news</a> - Mailing list for nmaas-related announcements sent out by the nmaas Team members</p> + <p><a href="https://lists.geant.org/sympa/info/nmaas-announce">nmaas-announce</a> - for nmaas-related announcements sent out by the nmaas Team members,</p> </li> <li> - <p><a href="https://lists.geant.org/sympa/info/nmaas-users ">users discussion about nmaas</a> - Mailing list for message exchange between nmaas users and the nmaas Team</p> + <p><a href="https://lists.geant.org/sympa/info/nmaas-users">nmaas-users</a> - for discussions and message exchange between nmaas users and the nmaas Team.</p> </li> </ul> + <span>{{ 'ABOUT.CONTACT_DESC' | translate}}</span> </div> <div class="panel panel-default"> <div class="panel-body"> diff --git a/src/app/shared/common/domainfilter/domainfilter.component.ts b/src/app/shared/common/domainfilter/domainfilter.component.ts index bbef4bda7a01842696ec6d3ac4369463704dfd5e..48472f0247fb83250824874f96a70d87648e2385 100644 --- a/src/app/shared/common/domainfilter/domainfilter.component.ts +++ b/src/app/shared/common/domainfilter/domainfilter.component.ts @@ -106,6 +106,7 @@ export class DomainFilterComponent implements OnInit { private sortDomains(): void { const globalDomainId = this.domainService.getGlobalDomainId(); + console.log(this.domains); this.domains = this.domains.pipe( map( domains => { @@ -123,10 +124,13 @@ export class DomainFilterComponent implements OnInit { domains.unshift(defaultDomain) } this.domainsLocal = domains; + this.filteredDomainsSub.next(this.domainsLocal); return domains } ) ) + console.log(this.domainsLocal); + } public changeDomain(domainId: number, domainName: string) { diff --git a/src/app/shared/contact/contact.component.ts b/src/app/shared/contact/contact.component.ts index 732955b972b12bf62d045cb8aa57edfbc34ba737..2ccb5d3e20301d266483167f47290518c7b79a2b 100644 --- a/src/app/shared/contact/contact.component.ts +++ b/src/app/shared/contact/contact.component.ts @@ -125,8 +125,11 @@ export class ContactComponent implements OnInit { private sendMail(data: any): Observable<void> { // submit captcha request return this.recaptchaV3Service.execute('contactForm').pipe( - catchError(_ => of('')), // in case of captcha error return empty token + catchError(error => { + console.error(error); + return of(error)}), // in case of captcha error return empty token map((token) => { + console.log(token) const result = {token, mail: new Mail()} // create mail object result.mail.otherAttributes = data; // set properties and mail attributes result.mail.otherAttributes.subType = this.formType.key; diff --git a/src/app/shared/footer/footer.component.html b/src/app/shared/footer/footer.component.html index bbce48b72740f03e6b38520708430af57b838036..58f81ac5538eeb5823019387969a37198e0e38e4 100644 --- a/src/app/shared/footer/footer.component.html +++ b/src/app/shared/footer/footer.component.html @@ -4,7 +4,7 @@ <div class="col-sm-2"> <!-- nmaas Logo optionally --> <a href="https://www.geant.org/"> - <img alt="Geant Logo" src="/assets/images/geant-logo.png" width="200" class="image-link"/> + <img alt="GÉANT Logo" src="/assets/images/geant-logo.png" width="200" class="image-link"/> </a> </div> <div class="col-sm-3"> diff --git a/src/app/shared/navbar/navbar.component.css b/src/app/shared/navbar/navbar.component.css index 5978cfb11627290845a547fca380052b4cb72606..55bdd7192499c31c22991e19beac919fb0bbf82d 100644 --- a/src/app/shared/navbar/navbar.component.css +++ b/src/app/shared/navbar/navbar.component.css @@ -26,11 +26,6 @@ .lang-circle-icon{ height: 26px; } -.navbar-left{ - padding-top: 6px; - display: flex; - align-items: center; -} .navbar-right{ display:flex; align-items: center; @@ -48,7 +43,6 @@ } .navbar-left,.navbar-right { float: none !important; - display:block; } .navbar-toggle { display: block; @@ -203,18 +197,16 @@ color: #414F6B; } .navbar-default .navbar-nav>li>a{ - color: #414F6B; - padding:10px; + color: #6D788E; } .navbar-default .navbar-nav>.active>a{ border-radius: 5px; color: #142548; - padding:10px; + /*font-weight: bold;*/ background-color: #D1D1D1; } .navbar-default .navbar-nav>.open>a{ border-radius: 5px; color: #142548; - padding:10px; background-color: #D1D1D1; } diff --git a/src/app/shared/navbar/navbar.component.html b/src/app/shared/navbar/navbar.component.html index b75127c4b75f76f275941006c1910aa93457bac3..cead22d4afd206aea61c47f15b9c44bff96b8767 100644 --- a/src/app/shared/navbar/navbar.component.html +++ b/src/app/shared/navbar/navbar.component.html @@ -1,7 +1,8 @@ <nav class="navbar navbar-default" id="navbar" role="navigation" style="margin:0"> <div class="container-fluid"> <div class="navbar-header"> - <a routerLink="/"><img alt="Geant" src="assets/images/logo-small.png" style="margin: 8px; padding:4px; height:35px"></a> + <a routerLink="/"><img alt="GÉANT" src="assets/images/logo-small.png" + style="margin: 8px; padding:4px; height:35px"></a> <button class="navbar-toggle" data-target="#navbarCollapse" data-toggle="collapse" type="button"> <span class="sr-only">Toggle Navigation</span> <em class="fas fa-bars"></em> @@ -20,22 +21,22 @@ </ul> <ul *ngIf="authService.isLogged()" class="nav navbar-nav navbar-right"> <li *ngIf="showClock" class=""> - <div class="navbar-logout">{{'NAVBAR.EXPIRED_TIME' | translate}}: {{time}}</div> + <div class="navbar-logout">{{ 'NAVBAR.EXPIRED_TIME' | translate }}: {{ time }}</div> </li> <li *ngIf="showClock" class="divider-vertical"></li> <li *ngIf="checkUserRole()" class="drop-domain"> <nmaas-domain-filter class="drop-domain"></nmaas-domain-filter> </li> - <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']" class="divider-vertical"></li> - <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']" [routerLinkActiveOptions]="{exact:true}" + <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']" class="divider-vertical"></li> + <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']" [routerLinkActiveOptions]="{exact:true}" [routerLinkActive]="['active']" class="dropdown"> <a aria-expanded="false" aria-haspopup="true" class="dropdown-toggle" data-toggle="dropdown" - role="button">{{'NAVBAR.ADVANCED' | translate}}<strong class="caret"></strong></a> + role="button">{{ 'NAVBAR.ADVANCED' | translate }}<strong class="caret"></strong></a> <ul class="dropdown-menu"> - <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']"> + <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']"> <a [routerLink]="['/admin/domains/bulks']">{{ 'BULK.DOMAIN.HEADER' | translate }}</a> </li> - <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']"> + <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']"> <a [routerLink]="['/admin/apps/bulks']">{{ 'BULK.APP.HEADER' | translate }}</a> </li> </ul> @@ -44,15 +45,15 @@ <li *roles="['ROLE_SYSTEM_ADMIN']" [routerLinkActiveOptions]="{exact:true}" [routerLinkActive]="['active']" class="dropdown"> <a aria-expanded="false" aria-haspopup="true" class="dropdown-toggle" data-toggle="dropdown" - role="button">{{'NAVBAR.NOTIFICATION' | translate}}<strong class="caret"></strong></a> + role="button">{{ 'NAVBAR.NOTIFICATION' | translate }}<strong class="caret"></strong></a> <ul class="dropdown-menu"> <li *roles="['ROLE_SYSTEM_ADMIN']"> - <span (click)="showNotificationModal()">{{'NAVBAR.ALL_USERS' | translate}}</span> + <span (click)="showNotificationModal()">{{ 'NAVBAR.ALL_USERS' | translate }}</span> </li> </ul> </li> <li *roles="['ROLE_SYSTEM_ADMIN']" class="divider-vertical"></li> - <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_OPERATOR', 'ROLE_DOMAIN_ADMIN', 'ROLE_TOOL_MANAGER', 'ROLE_VL_MANAGER']" + <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_OPERATOR', 'ROLE_DOMAIN_ADMIN', 'ROLE_TOOL_MANAGER', 'ROLE_GROUP_MANAGER']" [routerLinkActiveOptions]="{exact:true}" [routerLinkActive]="['active']" class="dropdown"> <a aria-expanded="false" aria-haspopup="true" class="dropdown-toggle" data-toggle="dropdown" @@ -66,20 +67,21 @@ <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_TOOL_MANAGER']"><a [routerLink]="['/admin/apps']">{{ 'NAVBAR.MARKET' | translate }}</a> </li> - <li *roles="['ROLE_DOMAIN_ADMIN', 'ROLE_SYSTEM_ADMIN', 'ROLE_OPERATOR', 'ROLE_VL_MANAGER', 'ROLE_VL_DOMAIN_ADMIN']"><a - [routerLink]="['/admin/domains']">{{ 'NAVBAR.DOMAINS' | translate }}</a> + <li *roles="['ROLE_DOMAIN_ADMIN', 'ROLE_SYSTEM_ADMIN', 'ROLE_OPERATOR', 'ROLE_GROUP_MANAGER', 'ROLE_GROUP_DOMAIN_ADMIN']"> + <a + [routerLink]="['/admin/domains']">{{ 'NAVBAR.DOMAINS' | translate }}</a> </li> - <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_VL_MANAGER']"> + <li *roles="['ROLE_SYSTEM_ADMIN', 'ROLE_GROUP_MANAGER']"> <a [routerLink]="['/admin/domains/groups']">{{ 'NAVBAR.DOMAIN_GROUPS' | translate }}</a> </li> <li *roles="['ROLE_SYSTEM_ADMIN']"><a [routerLink]="['/admin/users']">{{ 'NAVBAR.USERS' | translate }}</a> </li> - <li *roles="['ROLE_DOMAIN_ADMIN', 'ROLE_VL_DOMAIN_ADMIN']"><a + <li *roles="['ROLE_DOMAIN_ADMIN', 'ROLE_GROUP_DOMAIN_ADMIN']"><a [routerLink]="['/domain/users']">{{ 'NAVBAR.DOMAIN_USERS' | translate }}</a> </li> <li *roles="['ROLE_SYSTEM_ADMIN']"><a - [routerLink]="['/admin/languages']">{{'NAVBAR.LANGUAGES' | translate }}</a> + [routerLink]="['/admin/languages']">{{ 'NAVBAR.LANGUAGES' | translate }}</a> </li> <li *roles="['ROLE_SYSTEM_ADMIN']" class="dropdown-divider"></li> @@ -94,7 +96,7 @@ <a aria-expanded="false" aria-haspopup="true" class="dropdown-toggle" data-toggle="dropdown" href="#" role="button"> <span class="glyphicon glyphicon-user"></span> - {{authService.getUsername()}} <strong class="caret"></strong></a> + {{ authService.getPreferredUsername() }} <strong class="caret"></strong></a> <ul class="dropdown-menu"> <li [routerLinkActiveOptions]="{exact:true}" [routerLinkActive]="['active']"><a [routerLink]="['/profile']">{{ 'NAVBAR.PROFILE' | translate }}</a></li> @@ -108,12 +110,16 @@ <ul *ngIf="!authService.isLogged()" class="nav navbar-nav pull-right-lg" id="navbar-main-not-logged"> <li> - <button *ngIf="router.url == '/welcome' || router.url.startsWith('/welcome/login') || router.url == '/welcome/registration'" data-parent="#accordion" - class="btn navbar-btn accordion-group" data-target="#login-panel" data-toggle="collapse" [routerLink]="['welcome/login']"> + <button *ngIf="router.url == '/welcome' || router.url.startsWith('/welcome/login') || router.url == '/welcome/registration'" + data-parent="#accordion" + class="btn navbar-btn accordion-group" data-target="#login-panel" data-toggle="collapse" + [routerLink]="['welcome/login']"> {{ 'WELCOME.LOGIN' | translate }} </button> - <button *ngIf="router.url == '/welcome' || router.url.startsWith('/welcome/login') || router.url == '/welcome/registration'" data-parent="#accordion" - class="btn navbar-btn accordion-group" data-target="#register-panel" data-toggle="collapse" [routerLink]="['welcome/registration']"> + <button *ngIf="router.url == '/welcome' || router.url.startsWith('/welcome/login') || router.url == '/welcome/registration'" + data-parent="#accordion" + class="btn navbar-btn accordion-group" data-target="#register-panel" data-toggle="collapse" + [routerLink]="['welcome/registration']"> {{ 'WELCOME.REGISTER' | translate }} </button> </li> @@ -136,7 +142,7 @@ <a (click)="useLanguage(lang)"> <img alt="lang flag" class="lang-circle-icon" src="assets/images/country/{{lang}}_circle.png"/> - <span>{{lang.toUpperCase()}}</span> + <span>{{ lang.toUpperCase() }}</span> </a> </li> </ul> diff --git a/src/app/shared/navbar/navbar.component.spec.ts b/src/app/shared/navbar/navbar.component.spec.ts index 7f81b7e65f719b681db4b423c85550e84c0a7e0d..fe0dc2256e63c906623f7fd69c28c13ff2513064 100644 --- a/src/app/shared/navbar/navbar.component.spec.ts +++ b/src/app/shared/navbar/navbar.component.spec.ts @@ -49,11 +49,12 @@ describe('NavbarComponent_Shared', () => { const mockLanguageService = jasmine.createSpyObj(['getEnabledLanguages', 'shouldUpdate']); mockLanguageService.getEnabledLanguages.and.returnValue(of(['en', 'fr', 'pl'])); mockLanguageService.shouldUpdate.and.returnValue(false); - const mockAuthService = jasmine.createSpyObj(['isLogged', 'hasRole', 'getDomains', 'getRoles']); + const mockAuthService = jasmine.createSpyObj(['isLogged', 'hasRole', 'getDomains', 'getRoles', 'loadUser', 'refreshUserRoles']); mockAuthService.isLogged.and.returnValue(false); mockAuthService.hasRole.and.returnValue(false); mockAuthService.getDomains.and.returnValue([]); mockAuthService.getRoles.and.returnValue([]); + mockAuthService.refreshUserRoles.and.returnValue(); const mockUserDataService = jasmine.createSpyObj(['selectedDomainId']) mockUserDataService.selectedDomainId.and.returnValue(1) diff --git a/src/app/shared/navbar/navbar.component.ts b/src/app/shared/navbar/navbar.component.ts index 394fcdfe84fa6c6fb988684ebc71df8b9a90ff0e..ee1d343013ae4f642475899fd2a5d4d329e45d19 100644 --- a/src/app/shared/navbar/navbar.component.ts +++ b/src/app/shared/navbar/navbar.component.ts @@ -56,15 +56,15 @@ export class NavbarComponent implements OnInit { ngOnInit() { this.isServiceAvailable = this.serviceAvailability.isServiceAvailable; this.getSupportedLanguages(); + this.authService.refreshUserRoles(); if (this.authService.isLogged()) { - if (this.authService.hasRole('ROLE_SYSTEM_ADMIN')) { this.refresh = interval(5000).subscribe(next => { if (this.languageService.shouldUpdate()) { this.getSupportedLanguages(); this.languageService.setUpdateRequiredFlag(false); } }); - } + // } } this.intervalId = setInterval(() => { if (this.authService.isLogged()) { @@ -88,10 +88,12 @@ export class NavbarComponent implements OnInit { } public checkUserRole(): boolean { - return this.authService.getDomains().filter(value => value !== this.domainService.getGlobalDomainId()).length > 0 + if (this.authService.isLogged()) { + return this.authService.getDomains().filter(value => value !== this.domainService.getGlobalDomainId()).length > 0 || this.authService.getRoles().filter(value => value !== 'ROLE_INCOMPLETE') .filter(value => value !== 'ROLE_GUEST') .length > 0; + } } public showNotificationModal(): void { @@ -99,7 +101,7 @@ export class NavbarComponent implements OnInit { } public isOnlyGuestInGlobalDomain(): boolean { - const globalDomainRoles = this.authService.getDomainRoles().get(this.domainService.getGlobalDomainId()).getRoles() + const globalDomainRoles = this.authService.getGlobalRole() return globalDomainRoles // does have any role in global domain (not undefined) && globalDomainRoles.length === 1 // only one role in global domain && globalDomainRoles[0] === 'ROLE_GUEST' // this single role is ROLE_GUEST diff --git a/src/app/shared/page-not-found/page-not-found.component.css b/src/app/shared/page-not-found/page-not-found.component.css index 81255a0b4ffd0b6f3f6e72de771581796a4648da..d1bc030c6435a8816481bec18f3bc499207c5439 100644 --- a/src/app/shared/page-not-found/page-not-found.component.css +++ b/src/app/shared/page-not-found/page-not-found.component.css @@ -1,5 +1,5 @@ .container-fluid { - min-height: 100vh; + min-height: 55vh; padding: 25px; background-repeat: no-repeat; background-color: rgb(255, 255, 255); diff --git a/src/app/shared/page-not-found/page-not-found.component.html b/src/app/shared/page-not-found/page-not-found.component.html index 64b11d2695c7b7e0c9db9c59d82fe8b5b026bc11..48dfe9e1d3a6916e20f9c7da4f431064c0012576 100644 --- a/src/app/shared/page-not-found/page-not-found.component.html +++ b/src/app/shared/page-not-found/page-not-found.component.html @@ -3,7 +3,7 @@ <img src="assets/images/error/404.gif" width="60%"> </div> <div class="row"> - <h1>Oops! Something went wrong.</h1> + <h1>Oops! Something went wrong (404).</h1> <p>Our robot couldn't find this page. Try a different link or head back to the homepage.</p> <button class="btn btn-primary" type="button" (click)="redirect()">Go back</button> </div> diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 2eb8101c668d54d301660d87298f6adc819d93e8..a6243e0198db0195b3b444eb4e99116ce403afeb 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -37,7 +37,7 @@ import {PasswordStrengthMeterComponent} from 'angular-password-strength-meter'; import {AboutComponent} from './about/about.component'; import {ChangelogComponent} from './changelog/changelog.component'; import {NotificationService} from '../service/notification.service'; -import {RECAPTCHA_V3_SITE_KEY, RecaptchaV3Module} from 'ng-recaptcha'; +import {RECAPTCHA_V3_SITE_KEY, RecaptchaModule, RecaptchaV3Module} from 'ng-recaptcha'; import {SingleCommentComponent} from './comments/single-comment/single-comment.component'; import {TranslateStateModule} from './translate-state/translate-state.module'; import {MinLengthDirective} from '../directive/min-length.directive'; @@ -60,6 +60,11 @@ import {SortableHeaderDirective} from '../service/sort-domain.directive'; import {InputTextModule} from 'primeng/inputtext'; import { DomainNamespaceAnnotationsComponent } from './domain-namespace-annotations/domain-namespace-annotations.component'; import { provideZxvbnServiceForPSM } from 'angular-password-strength-meter/zxcvbn'; +import { AccessTokensComponent } from './users/access-token/access-tokens.component'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { ButtonModule } from 'primeng/button'; +import { BrowserModule } from '@angular/platform-browser'; @NgModule({ @@ -70,7 +75,6 @@ import { provideZxvbnServiceForPSM } from 'angular-password-strength-meter/zxcv ServicesModule, RouterModule, ReactiveFormsModule, - RecaptchaV3Module, PasswordStrengthMeterComponent, TranslateModule.forChild(), NgxPaginationModule, @@ -79,6 +83,10 @@ import { provideZxvbnServiceForPSM } from 'angular-password-strength-meter/zxcv DropdownModule, InputTextModule, FormioModule, + InputGroupModule, + InputGroupAddonModule, + ButtonModule, + RecaptchaV3Module ], declarations: [ RateComponent, @@ -123,7 +131,8 @@ import { provideZxvbnServiceForPSM } from 'angular-password-strength-meter/zxcv ContactComponent, PreferencesComponent, SortableHeaderDirective, - DomainNamespaceAnnotationsComponent + DomainNamespaceAnnotationsComponent, + AccessTokensComponent ], providers: [ PasswordValidator, @@ -174,7 +183,8 @@ import { provideZxvbnServiceForPSM } from 'angular-password-strength-meter/zxcv ModalProvideSshKeyComponent, PreferencesComponent, SortableHeaderDirective, - DomainNamespaceAnnotationsComponent + DomainNamespaceAnnotationsComponent, + AccessTokensComponent ] }) export class SharedModule { diff --git a/src/app/shared/users/access-token/access-token.ts b/src/app/shared/users/access-token/access-token.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e0eb1e41bbaf717eedf8e5b4d1982b298f960b3 --- /dev/null +++ b/src/app/shared/users/access-token/access-token.ts @@ -0,0 +1,7 @@ +export class AccessToken { + public id: number + public name: string + public userId: number + public tokenValue: string + public valid: boolean +} \ No newline at end of file diff --git a/src/app/shared/users/access-token/access-tokens.component.html b/src/app/shared/users/access-token/access-tokens.component.html new file mode 100644 index 0000000000000000000000000000000000000000..9800061a0e11a4ba58e277a6b890c392c71b1b15 --- /dev/null +++ b/src/app/shared/users/access-token/access-tokens.component.html @@ -0,0 +1,80 @@ +<div style="margin-bottom: 15px;" class="panel panel-default"> + <div class="panel-heading">{{'TOKENS.HEADER' | translate}}</div> + <div class="panel-body"> + <table class="table table-hover" aria-describedby="User access tokens table"> + <thead> + <tr> + <th scope="col">{{'TOKENS.TABLE.ID' | translate}}</th> + <th scope="col">{{'TOKENS.TABLE.NAME' | translate}}</th> + <th scope="col">{{'TOKENS.TABLE.VALUE' | translate}}</th> + <th scope="col">{{'TOKENS.TABLE.VALID' | translate}}</th> + <th scope="col">{{'TOKENS.TABLE.ACTIONS' | translate}}</th> + </tr> + </thead> + <tbody> + <tr *ngFor="let token of tokensList"> + <td>{{token.id}}</td> + <td>{{token.name}}</td> + <td>{{token.tokenValue}}</td> + <td>{{token.valid}}</td> + <td *ngIf="token.valid"> + <button type="button" class="btn btn-danger" + (click)="invalidate(token.id)">{{'TOKENS.BUTTON_INVALIDATE' | translate}}</button> + </td> + <td *ngIf="!token.valid"> + <button type="button" class="btn btn-danger" + (click)="deleteToken(token.id)">{{'TOKENS.BUTTON_DELETE' | translate}}</button> + </td> + </tr> + <tr *ngIf="tokensList.length === 0"> + <td colspan="3" style="text-align: center">{{'TOKENS.NO_TOKENS' | translate}}</td> + </tr> + </tbody> + </table> + <div> + <button type="button" class="btn btn-success" + (click)="modal.show()">{{'TOKENS.NEW_TOKEN' | translate}}</button> + + </div> + </div> +</div> + +<nmaas-modal styleModal="info"> + <div class="nmaas-modal-header">{{'TOKENS.MODAL.HEADER' | translate}}</div> + <div class="nmaas-modal-body" style="height: 60%; max-height: 80vh;overflow-y: auto;"> + <form *ngIf="!showCopyToken" [formGroup]="requestForm" (ngSubmit)="createNewToken()"> + <div class="form-group"> + <label class="control-label" for="new-token-name"> + {{'TOKENS.MODAL.NAME' | translate}}: + </label> + <input id="new-token-name" type="text" class="form-control" formControlName="name"> + </div> + <div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert alert-danger"> + <div *ngIf="name.errors.required">{{'SSH_KEYS.MODAL.ERROR.NAME_REQUIRED' | translate}}</div> + <div *ngIf="name.errors.minlength">{{'SSH_KEYS.MODAL.ERROR.NAME_MINLENGTH' | translate}}</div> + <div *ngIf="name.errors.maxlength">{{'SSH_KEYS.MODAL.ERROR.NAME_MAXLENGTH' | translate}}</div> + <div *ngIf="name.errors.notUnique">{{name.errors.message}}</div> + + </div> + <input type="submit" class="btn btn-success" value="{{'SSH_KEYS.MODAL.BUTTON_ADD' | translate}}" + [disabled]="!requestForm.valid"> + <button type="button" class="btn btn-primary pull-right" + (click)="modal.hide()">{{'SSH_KEYS.MODAL.BUTTON_CANCEL' | translate}}</button> + + </form> + <div *ngIf="showCopyToken"> + <span class="text-bold"> + {{"TOKENS.MODAL.TOKEN_COPY" | translate}} + </span> + <div> + <p-inputGroup> + <input pInputText [(ngModel)]="newToken.tokenValue" readonly="true" placeholder="Token to copy" /> + <button class="btn btn-secondary" type="button" (click)="copyToClipboard()">{{"TOKENS.MODAL.COPY" | translate}}</button> + </p-inputGroup> + </div> + <div class="mt-6 flex "> + <button class="btn btn-primary" type="button" (click)="ConfirmAndClose()" >{{"TOKENS.MODAL.TOKEN_SAVED" | translate}}</button> + </div> + </div> + </div> +</nmaas-modal> \ No newline at end of file diff --git a/src/app/shared/users/access-token/access-tokens.component.spec.ts b/src/app/shared/users/access-token/access-tokens.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..98e26efb7ba9c778fb53e550ba27eec87ff1677f --- /dev/null +++ b/src/app/shared/users/access-token/access-tokens.component.spec.ts @@ -0,0 +1,38 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {AccessTokensComponent} from './access-tokens.component'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {TranslateFakeLoader, TranslateLoader, TranslateModule} from '@ngx-translate/core'; +import {ReactiveFormsModule} from '@angular/forms'; +import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core'; + +describe('AccessTokensComponent', () => { + let component: AccessTokensComponent; + let fixture: ComponentFixture<AccessTokensComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AccessTokensComponent], + imports: [ + HttpClientTestingModule, + ReactiveFormsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateFakeLoader + } + }), + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA], + }) + .compileComponents(); + + fixture = TestBed.createComponent(AccessTokensComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src/app/shared/users/access-token/access-tokens.component.ts b/src/app/shared/users/access-token/access-tokens.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..d3123d0cb97493131055103be59bf0058edb79c9 --- /dev/null +++ b/src/app/shared/users/access-token/access-tokens.component.ts @@ -0,0 +1,100 @@ + +import {Component, OnInit, ViewChild} from '@angular/core'; +import {Observable} from 'rxjs'; +import {AccessToken} from './access-token'; +import {AccessTokenService} from './access-tokens.service'; +import {ModalComponent} from '../../modal'; +import {UntypedFormBuilder, UntypedFormGroup, Validators} from '@angular/forms'; + +@Component({ + selector: 'app-access-tokens', + templateUrl: './access-tokens.component.html', + styleUrls: [] +}) +export class AccessTokensComponent implements OnInit { + + public tokens: Observable<AccessToken[]> = undefined; + public tokensList: AccessToken[] = []; + + public requestForm: UntypedFormGroup = undefined; + + public newTokenName = ''; + + public showCopyToken = false; + public newToken :AccessToken; + + @ViewChild(ModalComponent, {static: true}) + public readonly modal: ModalComponent; + + constructor(private tokenService: AccessTokenService, + private formBuilder: UntypedFormBuilder) { + } + + ngOnInit() { + this.requestForm = this.formBuilder.group({ + name: ['', [Validators.required, Validators.minLength(1), Validators.maxLength(16)]], + }) + + this.tokens = this.tokenService.getAll(); + this.getData(); + } + + getData() { + this.tokensList = []; + this.tokens.subscribe( + data => this.tokensList.push(...data), + error => console.error(error) + ) + } + + invalidate(id: number) { + this.tokenService.invalidate(id).subscribe( + (_) => this.getData(), + error => console.error(error) + ); + } + + deleteToken(id: number) { + this.tokenService.deleteToken(id).subscribe( + (_) => this.getData(), + error => console.error(error.err) + ); + } + + public createNewToken() { + this.tokenService.createToken(this.requestForm.value.name.trim()).subscribe({ + next: val => { + this.requestForm.reset(); + this.showCopyToken = true; + this.newToken = val; + }, + error: err => { + console.warn(err.error) + this.requestForm.controls['name'].setErrors({notUnique: true, message: err.error}); + console.log(this.requestForm) + } + }) + } + + public copyToClipboard() { + if (this.newToken.tokenValue) { + navigator.clipboard.writeText(this.newToken.tokenValue).then(() => { + console.log('Copied to clipbord'); + }, (err) => { + console.error('Some errors accoured: ', err); + }); + } + } + + public ConfirmAndClose() { + this.showCopyToken = false; + this.newToken = null; + this.getData(); + this.modal.hide(); + + } + + get name() { + return this.requestForm.get('name'); + } +} \ No newline at end of file diff --git a/src/app/shared/users/access-token/access-tokens.service.ts b/src/app/shared/users/access-token/access-tokens.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3c8dec74292a256c24e33a3bade736e7928ec5b --- /dev/null +++ b/src/app/shared/users/access-token/access-tokens.service.ts @@ -0,0 +1,36 @@ +import {Observable} from 'rxjs'; +import {AccessToken} from './access-token'; +import {Injectable} from '@angular/core'; +import {GenericDataService} from '../../../service/genericdata.service'; +import {HttpClient} from '@angular/common/http'; +import {AppConfigService} from '../../../service'; + +@Injectable({ + providedIn: 'root' +}) +export class AccessTokenService extends GenericDataService { + + constructor(http: HttpClient, appConfig: AppConfigService) { + super(http, appConfig); + } + + public getAll(): Observable<AccessToken[]> { + return this.http.get<AccessToken[]>(this.getUrl()) + } + + public invalidate(id: number): Observable<void> { + return this.http.put<void>(`${this.getUrl()}/${id}`, '') + } + + public deleteToken(id: number): Observable<void> { + return this.http.put<void>(`${this.getUrl()}/delete/${id}`, '') + } + + public createToken(tokenName: string): Observable<AccessToken> { + return this.http.post<AccessToken>(this.getUrl(), tokenName) + } + + private getUrl(): string { + return this.appConfig.getApiUrl() + '/tokens'; + } +} \ No newline at end of file diff --git a/src/app/shared/users/list/userslist.component.html b/src/app/shared/users/list/userslist.component.html index dcae8ac082f4203c553b5edad904b31c99eabb05..ab667693d70a8c9238af517b063e3cea502bbca0 100644 --- a/src/app/shared/users/list/userslist.component.html +++ b/src/app/shared/users/list/userslist.component.html @@ -3,7 +3,7 @@ {{ 'USERS.TITLE' | translate }}</h3> <div class="flex space-between"> <div class="flex"> - <button *ngIf="authService.hasDomainRole(domainId, 'ROLE_DOMAIN_ADMIN') || authService.hasDomainRole(domainId, 'ROLE_VL_DOMAIN_ADMIN')" + <button *ngIf="authService.hasDomainRole(domainId, 'ROLE_DOMAIN_ADMIN') || authService.hasDomainRole(domainId, 'ROLE_GROUP_DOMAIN_ADMIN')" class="btn btn-primary" (click)="changeMode()"> <span *ngIf="isModeAllowed(ComponentMode.DELETE)">{{'USERS.ADD_TO_DOMAIN_BUTTON' | translate}}</span> <span *ngIf="isModeAllowed(ComponentMode.EDIT)">{{'USERS.GO_BACK_BUTTON' | translate}}</span> diff --git a/src/app/shared/users/new-ssh-key/new-ssh-key.component.ts b/src/app/shared/users/new-ssh-key/new-ssh-key.component.ts index 619104369b0f33a17d8f96fb4aaa925f0d1a573a..191b25ae717054a9c62fa618cadd1568f2fb31db 100644 --- a/src/app/shared/users/new-ssh-key/new-ssh-key.component.ts +++ b/src/app/shared/users/new-ssh-key/new-ssh-key.component.ts @@ -1,4 +1,4 @@ -import {Component, EventEmitter, OnInit, Output, ViewChild} from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; import {SSHKeyService} from '../../../service/sshkey.service'; import {SSHKeyRequest} from '../../../model/sshkey-request'; import {UntypedFormBuilder, UntypedFormGroup, Validators} from '@angular/forms'; @@ -17,6 +17,12 @@ export class NewSshKeyComponent implements OnInit { @Output() public out: EventEmitter<any> = new EventEmitter<any>(); + @Input() + public userMode = false; + + @Input() + public userId : number; + public error: string = undefined; public requestForm: UntypedFormGroup = undefined; @@ -38,17 +44,34 @@ export class NewSshKeyComponent implements OnInit { console.log(this.requestForm); request.name = this.requestForm.value.name.trim(); request.key = this.requestForm.value.key.trim(); + if(this.userMode) { + if(this.userId !== null) { + this.keyService.createKeyForUser(request, this.userId).subscribe( + data => { + this.error = undefined; + this.requestForm.reset(); + this.modal.hide(); + this.out.emit(); + }, + error => { + this.error = error.message; + } + ); + } + } else { // profile view this.keyService.createKey(request).subscribe( - data => { - this.error = undefined; - this.requestForm.reset(); - this.modal.hide(); - this.out.emit(); - }, - error => { - this.error = error.message; - } - ); + data => { + this.error = undefined; + this.requestForm.reset(); + this.modal.hide(); + this.out.emit(); + }, + error => { + this.error = error.message; + } + ); + } + } get name() { diff --git a/src/app/shared/users/privileges/userprivileges.component.ts b/src/app/shared/users/privileges/userprivileges.component.ts index a79b06dc31f7548cedea9b393c72cc847efbe11c..5c7e8d99875729dbfc43be8c7bab9b8e6920bd83 100644 --- a/src/app/shared/users/privileges/userprivileges.component.ts +++ b/src/app/shared/users/privileges/userprivileges.component.ts @@ -57,7 +57,7 @@ export class UserPrivilegesComponent extends BaseComponent implements OnInit { if (this.authService.hasRole(Role[Role.ROLE_SYSTEM_ADMIN]) && Number(this.newPrivilegeForm.get('domainId').value) === this.domainService.getGlobalDomainId()) { // admin (global) role set - roles = [Role.ROLE_OPERATOR, Role.ROLE_TOOL_MANAGER, Role.ROLE_SYSTEM_ADMIN, Role.ROLE_VL_MANAGER]; + roles = [Role.ROLE_OPERATOR, Role.ROLE_TOOL_MANAGER, Role.ROLE_SYSTEM_ADMIN, Role.ROLE_GROUP_MANAGER]; roles = this.filterRoles(roles, this.newPrivilegeForm.get('domainId').value); } else if (this.newPrivilegeForm.get('domainId').value != null) { // default (domain) role set diff --git a/src/app/shared/users/ssh-keys/ssh-keys.component.html b/src/app/shared/users/ssh-keys/ssh-keys.component.html index efb4cb027d9813f2b99c014a6a7894b6ea6e829d..e9025bddb5cd7796a569b04f68bb8cd26b94c772 100644 --- a/src/app/shared/users/ssh-keys/ssh-keys.component.html +++ b/src/app/shared/users/ssh-keys/ssh-keys.component.html @@ -1,8 +1,8 @@ -<div style="margin-bottom: 120px;" class="panel panel-default"> +<div style="margin-bottom: 15px;" class="panel panel-default"> <div class="panel-heading">{{'SSH_KEYS.HEADER' | translate}}</div> <div class="panel-body"> <div class="flex justify-content-end mb-4"> - <app-new-ssh-key (out)="getData()"></app-new-ssh-key> + <app-new-ssh-key [userMode]="userMode" [userId]="userId" (out)="getData()"></app-new-ssh-key> </div> <table class="table table-hover" aria-describedby="User ssh keys table"> <thead> diff --git a/src/app/shared/users/ssh-keys/ssh-keys.component.ts b/src/app/shared/users/ssh-keys/ssh-keys.component.ts index 8bee8e6e677c36590e4fcd6a4b9a71725f0ec2c9..7def6034efd3074b784ac160800ff63b92dc70c5 100644 --- a/src/app/shared/users/ssh-keys/ssh-keys.component.ts +++ b/src/app/shared/users/ssh-keys/ssh-keys.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit} from '@angular/core'; +import {Component, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core'; import {SSHKeyService} from '../../../service/sshkey.service'; import {Observable} from 'rxjs'; import {SSHKeyView} from '../../../model/sshkey-view'; @@ -8,16 +8,37 @@ import {SSHKeyView} from '../../../model/sshkey-view'; templateUrl: './ssh-keys.component.html', styleUrls: ['./ssh-keys.component.css'] }) -export class SshKeysComponent implements OnInit { +export class SshKeysComponent implements OnInit, OnChanges { public keys: Observable<SSHKeyView[]> = undefined; public keysList: SSHKeyView[] = []; + @Input() + public userMode = false; + + @Input() + public userId : number; + constructor(private keyService: SSHKeyService) { } ngOnInit() { - this.keys = this.keyService.getAll(); - this.getData(); + if(this.userMode) { + if(this.userId !== null) { + this.keys = this.keyService.getAllByUserId(this.userId); + this.getData(); + } + } else { // profile view + this.keys = this.keyService.getAll(); + this.getData(); + } + + } + + ngOnChanges(changes: SimpleChanges) { + if (changes['userId']) { + this.ngOnInit(); + console.log('Nowa wartość userId:', this.userId); + } } getData() { @@ -33,15 +54,28 @@ export class SshKeysComponent implements OnInit { } invalidate(id: number) { - this.keyService.invalidate(id).subscribe( - data => { - console.log('invalidating ssh key id: ' + id + ' success'); - this.getData(); - }, - error => { - console.error(error); - } - ); + if(this.userMode) { + this.keyService.invalidateUserKey(id, this.userId).subscribe( + data => { + console.log('invalidating ssh key id: ' + id + ' success'); + this.getData(); + }, + error => { + console.error(error); + } + ); + } else { + this.keyService.invalidate(id).subscribe( + data => { + console.log('invalidating ssh key id: ' + id + ' success'); + this.getData(); + }, + error => { + console.error(error); + } + ); + } + } } diff --git a/src/app/welcome/complete/complete.component.html b/src/app/welcome/complete/complete.component.html index 46cef36fa39df40e49ed75893af4cca1c04f3485..64acd24a4363e8f96d10fafc70258bc9b3eaea3c 100644 --- a/src/app/welcome/complete/complete.component.html +++ b/src/app/welcome/complete/complete.component.html @@ -6,7 +6,7 @@ <div class="row"> <div class="col-xs-3"></div> <div class="text-center col-xs-6"> - <img alt="Geant logo" src="assets/images/geant-logo-small.png"> + <img alt="GÉANT logo" src="assets/images/geant-logo-small.png"> </div> <div class="dropdown text-right drop-lang-div col-xs-3"> <a style="display: inline-block" class="dropdown-toggle" aria-expanded="false" aria-haspopup="true" diff --git a/src/app/welcome/link-account/link-account.component.css b/src/app/welcome/link-account/link-account.component.css new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/src/app/welcome/link-account/link-account.component.css @@ -0,0 +1 @@ + diff --git a/src/app/welcome/link-account/link-account.component.html b/src/app/welcome/link-account/link-account.component.html new file mode 100644 index 0000000000000000000000000000000000000000..2d75205238a0c5f83811c5aa3ebcb229b4d79d89 --- /dev/null +++ b/src/app/welcome/link-account/link-account.component.html @@ -0,0 +1,51 @@ +<div style="display: flex; justify-content: center;"> + <div style=" +margin-top: 50px; + width: 60% +" class="panel panel-default"> + <div class="panel-heading">{{ 'ACCOUNT_LINKING.HEADER' | translate }}</div> + <div class="panel-body"> + <form *ngIf="user" + class="form-horizontal" #userDetailsForm="ngForm"> + <div> + <p> + {{ 'ACCOUNT_LINKING.INFO' | translate }} + </p> + </div> + <div class="form-group"> + <label class="col-sm-2 control-label">{{ 'USER_DETAILS.FIRST_NAME' | translate }}</label> + <div class="col-sm-10"> + <p class="form-control-static">{{ user.firstname }}</p> + </div> + </div> + + <div class="form-group"> + <label class="col-sm-2 control-label">{{ 'USER_DETAILS.LAST_NAME' | translate }}</label> + <div class="col-sm-10"> + <p class="form-control-static">{{ user.lastname }}</p> + </div> + </div> + + <div class="form-group"> + <label class="col-sm-2 control-label">{{ 'USER_DETAILS.EMAIL' | translate }}</label> + <div class="col-sm-10"> + <p class="form-control-static">{{ user.email }}</p> + </div> + </div> + + <div class="form-group"> + <label for="password" class="col-sm-2 control-label">{{ 'PASSWORD.PASSWORD' | translate }}</label> + <div class="col-sm-10"> + <input type="password" class="form-control" id="password" + name="password" [(ngModel)]="password"> + </div> + </div> + <button type="submit" class="btn btn-primary" + (click)="submit()">{{ 'ACCOUNT_LINKING.CONFIRM' | translate }} + </button> + <div *ngIf="error" class="alert alert-danger" style="margin-top: 20px">{{error}}</div> + </form> + <br> + </div> + </div> +</div> diff --git a/src/app/welcome/link-account/link-account.component.spec.ts b/src/app/welcome/link-account/link-account.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b0a9f98c9b851dbd8f044e900df4b02595ca2ca --- /dev/null +++ b/src/app/welcome/link-account/link-account.component.spec.ts @@ -0,0 +1,57 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {LinkAccountComponent} from './link-account.component'; +import {ActivatedRoute} from '@angular/router'; +import {of} from 'rxjs'; +import {AuthService} from '../../auth/auth.service'; +import {TranslateFakeLoader, TranslateLoader, TranslateModule} from '@ngx-translate/core'; + +describe('LinkAccountComponent', () => { + let component: LinkAccountComponent; + let fixture: ComponentFixture<LinkAccountComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [LinkAccountComponent], + imports: [TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateFakeLoader + } + })], + providers: [ + { + provide: ActivatedRoute, + useValue: { + queryParams: of({ + oidc_token: 'mocked.jwt.token' + }), + snapshot: { + paramMap: { + get: () => null + } + } + } + }, + { + provide: AuthService, + useValue: { + isLogged: () => true, + oidcLogout: jasmine.createSpy('oidcLogout'), + oidcLinkingLogin: jasmine.createSpy('oidcLinkingLogin').and.returnValue(of({})) + } + }, + + ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LinkAccountComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/welcome/link-account/link-account.component.ts b/src/app/welcome/link-account/link-account.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb60e030f051c169a57b0f1a72394101cee6157d --- /dev/null +++ b/src/app/welcome/link-account/link-account.component.ts @@ -0,0 +1,84 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {User} from '../../model'; +import {ActivatedRoute, Router} from '@angular/router'; +import jwtDecode from 'jwt-decode'; +import {AuthService} from '../../auth/auth.service'; +import {TranslateService} from '@ngx-translate/core'; + + +@Component({ + selector: 'app-link-account', + templateUrl: './link-account.component.html', + styleUrl: './link-account.component.css' +}) +export class LinkAccountComponent implements OnInit, OnDestroy { + public user: User; + private token: string; + public password: string; + public error: string; + + constructor( + private readonly route: ActivatedRoute, + private readonly authService: AuthService, + private readonly router: Router, + private translate: TranslateService, + ) { + } + + ngOnDestroy() { + if (!this.authService.isLogged()) { + this.authService.oidcLogout(this.token) + } + } + + ngOnInit() { + this.route.queryParams.subscribe(param => { + this.token = param['oidc_token']; + const decoded: TokenPayload = jwtDecode<TokenPayload>(this.token); + this.user = new User(); + this.user.username = decoded.sub; + this.user.firstname = decoded.given_name; + this.user.lastname = decoded.family_name; + this.user.email = decoded.email; + }) + + } + + public submit(): void { + this.authService.oidcLinkingLogin( + this.token, + this.user.email, + this.password, + this.user.username, + this.user.firstname, + this.user.lastname, + ).subscribe( + () => { + this.router.navigate(['/']); + }, + err => { + this.error = this.translate.instant(this.getMessage(err)); + } + ) + } + private getMessage(err: any): string { + switch (err['status']) { + case 401: + return 'LOGIN.LOGIN_FAILURE_MESSAGE'; + case 406: + return 'LOGIN.APPLICATION_UNDER_MAINTENANCE_MESSAGE'; + case 409: + return 'GENERIC_MESSAGE.UNAVAILABLE_MESSAGE'; + default: + return 'GENERIC_MESSAGE.UNAVAILABLE_MESSAGE'; + } + } +} + + +interface TokenPayload { + sub: string; + email: string; + given_name: string; + family_name: string; +} diff --git a/src/app/welcome/login/login.component.html b/src/app/welcome/login/login.component.html index 7084ea8ee9fea6a7682da1aea6576fc41164ee15..af758ef732c5a46c8e251d7f66b454f6ae62ec88 100644 --- a/src/app/welcome/login/login.component.html +++ b/src/app/welcome/login/login.component.html @@ -26,20 +26,13 @@ </fieldset> </form> <div class="form-group"> - <button type="submit" (click)="triggerSSO()" class="btn btn-primary btn-block" + <button type="submit" (click)="triggerOIDC()" class="btn btn-primary btn-block" [disabled]="!this.configuration?.ssoLoginAllowed || loading || ssoLoading"> {{ 'LOGIN.LOGIN_WITH' | translate }} </button> <img alt="sso" *ngIf="ssoLoading" src="data:"/> <div *ngIf="ssoError" class="alert alert-danger">{{ssoError}}</div> </div> -<div class="form-group"> - <button type="submit" (click)="triggerOIDC()" class="btn btn-primary btn-block"> - {{ 'LOGIN.LOGIN_WITH' | translate }} - </button> - <img alt="sso" *ngIf="ssoLoading" src="data:"/> - <div *ngIf="ssoError" class="alert alert-danger">{{ssoError}}</div> -</div> <div class="form-group" style="text-align: center"> <a (click)="resetPassword = !resetPassword">{{ 'RESET_PASSWORD.FORGOT_PASSWORD_BUTTON' | translate }}</a> </div> diff --git a/src/app/welcome/login/login.component.spec.ts b/src/app/welcome/login/login.component.spec.ts index 35e39bc620879877c64f1d11636786124b0439f8..f5ebbcf5ea4bef842108fda08b35ae7bdc1dd7d4 100644 --- a/src/app/welcome/login/login.component.spec.ts +++ b/src/app/welcome/login/login.component.spec.ts @@ -7,7 +7,6 @@ import {TranslateFakeLoader, TranslateLoader, TranslateModule} from '@ngx-transl import {ModalComponent} from '../../shared/modal'; import {AuthService} from '../../auth/auth.service'; import {ConfigurationService, UserService} from '../../service'; -import {SSOService} from '../../service/sso.service'; import createSpyObj = jasmine.createSpyObj; import {of} from 'rxjs'; import { HttpClientTestingModule } from '@angular/common/http/testing'; @@ -40,7 +39,6 @@ describe('Component: Login', () => { providers: [ {provide: AuthService, useValue: {}}, {provide: ConfigurationService, useValue: configServiceSpy}, - {provide: SSOService, useValue: {}}, {provide: UserService, useValue: {}}, ], }).compileComponents(); diff --git a/src/app/welcome/login/login.component.ts b/src/app/welcome/login/login.component.ts index dbfcc78194a47a0585edbfd2d35d4b8ee52d32ff..0a4481b66e12318164be2249a30b512a6dfcf0c9 100644 --- a/src/app/welcome/login/login.component.ts +++ b/src/app/welcome/login/login.component.ts @@ -4,7 +4,6 @@ import {Router} from '@angular/router'; import {AuthService} from '../../auth/auth.service'; import {AppConfigService, ConfigurationService, UserService} from '../../service'; import {Configuration} from '../../model/configuration'; -import {SSOService} from '../../service/sso.service'; import {SSOConfig} from '../../model/sso'; import {UntypedFormBuilder, UntypedFormGroup, Validators} from '@angular/forms'; import {ModalComponent} from '../../shared/modal'; @@ -34,7 +33,6 @@ export class LoginComponent implements OnInit { constructor(private router: Router, private auth: AuthService, private configService: ConfigurationService, - private ssoService: SSOService, private fb: UntypedFormBuilder, private userService: UserService, private translate: TranslateService, @@ -47,12 +45,6 @@ export class LoginComponent implements OnInit { ngOnInit() { this.configService.getConfiguration().subscribe(config => { this.configuration = config; - if (config.ssoLoginAllowed) { - this.ssoService.getOne().subscribe(sso => { - this.ssoConfig = sso; - this.checkSSO(); - }); - } }); } @@ -72,42 +64,13 @@ export class LoginComponent implements OnInit { ); } + // only for use in linking accounts public triggerOIDC() { - window.location.href = this.appConfig.getOidcUrl(); - } - - public checkSSO() { - const params = this.router.parseUrl(this.router.url).queryParams; - - if ('ssoUserId' in params) { - // Got auth data, send to api - this.ssoLoading = true; - this.ssoError = ''; - this.auth.propagateSSOLogin(params.ssoUserId).subscribe( - result => { - if (result === true) { - this.ssoLoading = false; - this.translate.setDefaultLang(this.auth.getSelectedLanguage()); - this.translate.use(this.auth.getSelectedLanguage()); - this.router.navigate(['/']); - } else { - this.ssoError = 'Failed to propagate SSO user id'; - this.ssoLoading = false; - } - }, - err => { - this.ssoError = this.translate.instant(this.getMessage(err)); - this.ssoLoading = false; - } - ); + if (!this.configuration.maintenance) { + window.location.href = this.appConfig.getOidcUrl(); } } - public triggerSSO() { - const url = window.location.href.replace(/ssoUserId=.+/, ''); - window.location.href = this.ssoConfig.loginUrl + '?return=' + url; - } - public sendResetNotification() { if (this.resetPasswordForm.valid) { this.userService.resetPasswordNotification(this.resetPasswordForm.controls['email'].value).subscribe( diff --git a/src/app/welcome/logout/logout.component.spec.ts b/src/app/welcome/logout/logout.component.spec.ts index e2e7d4c106d3cb949a4e25bd17b26566d346b828..8db6bfeb9cc5fdfee95e2aea5e4e64266bb7f9db 100644 --- a/src/app/welcome/logout/logout.component.spec.ts +++ b/src/app/welcome/logout/logout.component.spec.ts @@ -6,7 +6,6 @@ import createSpyObj = jasmine.createSpyObj; import {of} from 'rxjs'; import {AuthService} from '../../auth/auth.service'; import {ConfigurationService} from '../../service'; -import {SSOService} from '../../service/sso.service'; describe('LogoutComponent', () => { let component: LogoutComponent; @@ -29,7 +28,6 @@ describe('LogoutComponent', () => { providers: [ {provide: AuthService, useValue: authServiceSpy}, {provide: ConfigurationService, useValue: configServiceSpy}, - {provide: SSOService, useValue: {}} ] }) .compileComponents(); diff --git a/src/app/welcome/logout/logout.component.ts b/src/app/welcome/logout/logout.component.ts index fb9c9e8263c2d0187a1cbcb3dd76bcee4bd1e3b1..4eaaa50f7df58c9ae99dccb68665c21597a652c5 100644 --- a/src/app/welcome/logout/logout.component.ts +++ b/src/app/welcome/logout/logout.component.ts @@ -3,7 +3,6 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from '../../auth/auth.service'; import {ConfigurationService} from '../../service'; -import {SSOService} from '../../service/sso.service'; @Component({ @@ -15,21 +14,12 @@ export class LogoutComponent implements OnInit { constructor(private router: Router, private auth: AuthService, - private configService: ConfigurationService, - private ssoService: SSOService) { } + private configService: ConfigurationService) { } ngOnInit() { this.auth.logout(); this.configService.getConfiguration().subscribe(config => { - if (config.ssoLoginAllowed && this.auth.loginUsingSsoService) { - const url = window.location.origin; - this.ssoService.getOne().subscribe(sso => { - // Shibboleth SP uses parameter 'target' instead of 'return' - window.location.href = sso.logoutUrl + '?return=' + url; - }); - } else { this.router.navigate(['/welcome']); - } }); } diff --git a/src/app/welcome/passwordreset/password-reset.component.html b/src/app/welcome/passwordreset/password-reset.component.html index c62301d65a9f27622c178c45b8c299fa04ca760a..8c60eb985068cb262ef92924dabd30b0ca432958 100644 --- a/src/app/welcome/passwordreset/password-reset.component.html +++ b/src/app/welcome/passwordreset/password-reset.component.html @@ -4,7 +4,7 @@ <div class="panel panel-default login-vertical-offset"> <div class="panel-heading"> <div class="text-center"> - <img alt="Geant logo" src="assets/images/geant-logo-small.png"> + <img alt="GÉANT logo" src="assets/images/geant-logo-small.png"> </div> </div> <div class="panel-body" *ngIf="user"> diff --git a/src/app/welcome/passwordreset/password-reset.component.ts b/src/app/welcome/passwordreset/password-reset.component.ts index 3694303c9c9f26926dba0b28bdb272d824c56160..755353da38a280b7147aab06bece2072de01f587 100644 --- a/src/app/welcome/passwordreset/password-reset.component.ts +++ b/src/app/welcome/passwordreset/password-reset.component.ts @@ -58,8 +58,11 @@ export class PasswordResetComponent implements OnInit { public resetPassword() { if (this.form.valid) { this.recaptchaV3Service.execute('password_reset').pipe( - catchError(_ => of('')), // in case of captcha error return empty token + catchError(error => { + console.error(error); + return of(error)}), // in case of captcha error return empty tokenin case of captcha error return empty token ).subscribe((captchaToken) => { + console.log(captchaToken) this.showLoading = true; this.passwordReset.password = this.form.controls['newPassword'].value; this.passwordReset.token = this.token; diff --git a/src/app/welcome/profile/profile.component.html b/src/app/welcome/profile/profile.component.html index f869b826e6e2744d74e6edf32beee6c2566419a6..f8c17f7e5690bf61858490dcb21e28b1035f820b 100644 --- a/src/app/welcome/profile/profile.component.html +++ b/src/app/welcome/profile/profile.component.html @@ -34,5 +34,6 @@ [allowedModes]="[ComponentMode.PROFILVIEW, ComponentMode.EDIT]"> </nmaas-userprivileges> <nmaas-ssh-keys></nmaas-ssh-keys> + <app-access-tokens></app-access-tokens> </div> </div> diff --git a/src/app/welcome/terms-acceptance/terms-acceptance.component.html b/src/app/welcome/terms-acceptance/terms-acceptance.component.html index ce70113e91c73008691ffb22a2181a56b6a206c0..3605f9376128d984c7d9a38596cbdb918d9373bd 100644 --- a/src/app/welcome/terms-acceptance/terms-acceptance.component.html +++ b/src/app/welcome/terms-acceptance/terms-acceptance.component.html @@ -2,7 +2,7 @@ <div class="row panel panel-default login-vertical-offset col-lg-offset-4 col-lg-4 col-md-4 col-md-offset-4 col-sm-6 col-sm-offset-3 col-xs-12"> <div class="panel-heading"> <div class="text-center"> - <img alt="Geant logo" src="assets/images/geant-logo-small.png"> + <img alt="GÉANT logo" src="assets/images/geant-logo-small.png"> </div> </div> <div class="panel-body"> @@ -47,4 +47,4 @@ </div> </nmaas-modal> <modal-info-terms></modal-info-terms> -<modal-info-policy></modal-info-policy> \ No newline at end of file +<modal-info-policy></modal-info-policy> diff --git a/src/app/welcome/welcome.module.ts b/src/app/welcome/welcome.module.ts index 1e0ecde52c243ae5ed4004c6dce60bbe39f8c075..e212c4f059d959724211aa423e911d38b087f067 100644 --- a/src/app/welcome/welcome.module.ts +++ b/src/app/welcome/welcome.module.ts @@ -17,43 +17,44 @@ import {CompleteComponent} from './complete/complete.component'; import {ContentDisplayService} from '../service/content-display.service'; import {TermsAcceptanceComponent} from './terms-acceptance/terms-acceptance.component'; import {TranslateModule} from '@ngx-translate/core'; -import {SSOService} from '../service/sso.service'; import {PasswordResetComponent} from './passwordreset/password-reset.component'; import {PasswordStrengthMeterComponent} from 'angular-password-strength-meter'; import {PolicySubpageComponent} from './policy-subpage/policy-subpage.component'; +import {LinkAccountComponent} from './link-account/link-account.component'; @NgModule({ - declarations: [ - WelcomeComponent, - LoginComponent, - LogoutComponent, - RegistrationComponent, - ProfileComponent, - CompleteComponent, - TermsAcceptanceComponent, - PasswordResetComponent, - PolicySubpageComponent - ], - imports: [ - FormsModule, - ReactiveFormsModule, - CommonModule, - RouterModule, - SharedModule, - PipesModule, - AppMarketModule, - PasswordStrengthMeterComponent, - TranslateModule.forChild() - ], - exports: [ - WelcomeComponent - ], - providers: [ - RegistrationService, - UserService, - ChangelogService, - ContentDisplayService, - SSOService - ] + declarations: [ + WelcomeComponent, + LoginComponent, + LogoutComponent, + RegistrationComponent, + ProfileComponent, + CompleteComponent, + TermsAcceptanceComponent, + PasswordResetComponent, + PolicySubpageComponent, + LinkAccountComponent + ], + imports: [ + FormsModule, + ReactiveFormsModule, + CommonModule, + RouterModule, + SharedModule, + PipesModule, + AppMarketModule, + PasswordStrengthMeterComponent, + TranslateModule.forChild() + ], + exports: [ + WelcomeComponent + ], + providers: [ + RegistrationService, + UserService, + ChangelogService, + ContentDisplayService + ] }) -export class WelcomeModule {} +export class WelcomeModule { +} diff --git a/src/assets/formio/config-template.json b/src/assets/formio/config-template.json index 4f01f8c274ec539a2bf97f85f4d91142e72a2bb8..fe79f6dc84e9f22f76f9de4c119e722d51c99ebe 100644 --- a/src/assets/formio/config-template.json +++ b/src/assets/formio/config-template.json @@ -15,14 +15,7 @@ "input": true, "tab": 0, "key": "configuration", - "components": [ - { - "type": "htmlelement", - "input": false, - "content": "<p>All required configuration should be applied using graphical interface</p>", - "tab": 0 - } - ] + "components": [] } ] }, diff --git a/src/config.json b/src/config.json index cf304ab51e9b9163bb4f10cb3b444323cf2bd941..3b4e008b3f93a80b48bfe1b62a590d6efd354817 100644 --- a/src/config.json +++ b/src/config.json @@ -1,6 +1,5 @@ { "apiUrl": "http://localhost:9000/api", - "oidcUrl": "http://localhost:9000/oauth2/authorization/my-oidc", "tokenName": "token", "nmaas": { "globalDomainId": 1 diff --git a/src/styles.css b/src/styles.css index c8a1e4ce5ec367c50bb1b26cf662af0a84ff3270..b3eaa02ccce2b1b94b757865e1477ef33b3061c6 100644 --- a/src/styles.css +++ b/src/styles.css @@ -92,3 +92,7 @@ .btn-text:hover{ background: #EBEEF5; } + +.text-bold { + font-weight: 700;; +}