From 6842cab307133193b117436198f53ffde2a02dfb Mon Sep 17 00:00:00 2001 From: Marco Martorana <74554879+marcomartorana@users.noreply.github.com> Date: Mon, 27 Sep 2021 17:48:24 +0200 Subject: [PATCH] Add Dashboard functionalities, Add Translation for new pages, Change on Auth/services to get Roles (due to Spring needed) --- Dockerfile | 3 +- adapt-config-json.sh | 3 +- package-lock.json | 41 +++ package.json | 5 + .../components/header/header.component.html | 34 ++- src/app/app.component.ts | 27 +- src/app/app.module.ts | 182 ++++++------- src/app/auth/oidc/oidc.ts | 4 + src/app/auth/services/auth.guard.ts | 3 +- .../services/oidc-user-information.service.ts | 15 +- src/app/auth/services/token.interceptor.ts | 27 +- .../datalet-iframe.component.html | 2 +- .../dataset/dataset.component.ts | 1 + .../services/data-cataglogue-api.service.ts | 1 - src/app/pages/home/home.component.html | 123 ++++++++- src/app/pages/pages-menu.ts | 28 ++ src/app/pages/pages-routing.module.ts | 7 + src/app/pages/pages.component.ts | 45 +++- src/app/pages/pages.module.ts | 5 +- src/assets/config-template.json | 7 + src/assets/config.json | 7 + src/assets/i18n/en.json | 242 ++++++++++++++++++ src/polyfills.ts | 2 + 23 files changed, 682 insertions(+), 132 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5086028b..a1dbf744 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,5 +31,6 @@ ENV DASHBOARD_BASE_URL=http://localhost:4200 \ BIKE_ANALYSIS_API_BASE_URL=http://localhost:8000/bikeAnalysis \ IDRA_BASE_URL=http://localhost:8080 \ DATALET_BASE_URL=http://localhost/deep/deep-components/creator.html \ - TRAFFICSIMSERVER_API_BASE_URL=http://localhost:8081 + TRAFFICSIMSERVER_API_BASE_URL=http://localhost:8081 \ + DASHBOARD_CTRL_API_BASE_URL=http://localhost:8085 \ No newline at end of file diff --git a/adapt-config-json.sh b/adapt-config-json.sh index b88ad860..bdf02864 100644 --- a/adapt-config-json.sh +++ b/adapt-config-json.sh @@ -14,4 +14,5 @@ sed -i -e "s|__TRAFFIC_PREDICTION_API_BASE_URL__|$TRAFFIC_PREDICTION_API_BASE_UR sed -i -e "s|__BIKE_ANALYSIS_API_BASE_URL__|$BIKE_ANALYSIS_API_BASE_URL|g" $FILE sed -i -e "s|__IDRA_BASE_URL__|$IDRA_BASE_URL|g" $FILE sed -i -e "s|__DATALET_BASE_URL__|$DATALET_BASE_URL|g" $FILE -sed -i -e "s|__TRAFFICSIMSERVER_API_BASE_URL__|$TRAFFICSIMSERVER_API_BASE_URL|g" $FILE \ No newline at end of file +sed -i -e "s|__TRAFFICSIMSERVER_API_BASE_URL__|$TRAFFICSIMSERVER_API_BASE_URL|g" $FILE +sed -i -e "s|__DASHBOARD_CTRL_API_BASE_URL__|$DASHBOARD_CTRL_API_BASE_URL|g" $FILE \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 01611bfd..d1d7d230 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2645,6 +2645,14 @@ "schema-utils": "^2.7.0" } }, + "@kolkov/angular-editor": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@kolkov/angular-editor/-/angular-editor-1.2.0.tgz", + "integrity": "sha512-uAXsYxK62rOcgGJZdT3Q8iqJZ1k9BJchmg23+Iwy+oTtaqdzqkjl1Sgeka1uXQRWEUqFFxjxn7CpU8mNLCmeGQ==", + "requires": { + "tslib": "^2.0.0" + } + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -4989,6 +4997,14 @@ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, + "angular-gridster2": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/angular-gridster2/-/angular-gridster2-11.2.0.tgz", + "integrity": "sha512-9+4Qt2/mPKAzGhhzp2Ag++WQbvr3Ry3dbfHG1XI596jpKh1ZtYvTLrh0Fn6jJEq248F1VN0xob9K9PzTH4P/2A==", + "requires": { + "tslib": "^2.0.0" + } + }, "angular2-chartjs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/angular2-chartjs/-/angular2-chartjs-0.4.1.tgz", @@ -11039,6 +11055,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -15377,6 +15398,21 @@ } } }, + "ngx-image-cropper": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/ngx-image-cropper/-/ngx-image-cropper-4.0.1.tgz", + "integrity": "sha512-Bl6VG7+dXQDfso5YAJ/9Dbq5Bt1xFsLM0fcyPcPyWt9qm40fofZGGP8Kg03d8/FdmdsoTbuLLHNh4r3YF2RtIw==", + "requires": { + "tslib": "^1.10.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "ngx-markdown": { "version": "11.1.3", "resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-11.1.3.tgz", @@ -19364,6 +19400,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "resize-observer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/resize-observer/-/resize-observer-1.0.2.tgz", + "integrity": "sha512-X0lHFNsxItpBRIRsdwOTkl/VguTaLGx7Gz9xoTGix9ObBN3jRYq9J/rSIuYDrey8AdU3IkfgIMpCeVSEW1QS0Q==" + }, "resolve": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", diff --git a/package.json b/package.json index bb29500a..e4e59323 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,11 @@ "@types/leaflet.markercluster": "^1.4.4", "@types/leaflet": "1.2.3", "@types/leaflet.heat": "0.2.0", + "angular-gridster2": "^11.2.0", + "@kolkov/angular-editor": "^1.2.0", + "hammerjs": "^2.0.8", + "ngx-image-cropper": "^4.0.1", + "resize-observer": "^1.0.2", "angular2-chartjs": "0.4.1", "apexcharts": "^3.27.1", "bootstrap": "4.3.1", diff --git a/src/app/@theme/components/header/header.component.html b/src/app/@theme/components/header/header.component.html index 4df83937..0d8d3384 100644 --- a/src/app/@theme/components/header/header.component.html +++ b/src/app/@theme/components/header/header.component.html @@ -25,13 +25,35 @@ matRipple [matRippleUnbounded]="true" [matRippleCentered]="true"> - <nb-user [nbContextMenu]="userMenu" - nbContextMenuTag="user-menu" - [onlyPicture]="userPictureOnly" - [name]="(user?.name!=undefined)?user?.name:user?.preferred_username" - [title]="user?.roles.join(' | ')" - [picture]="'assets/images/default_user.png'"> + + <!-- + <nb-user [nbContextMenu]="userMenu" + nbContextMenuTag="user-menu" + [onlyPicture]="userPictureOnly" + [name]="(user?.name!=undefined)?user?.name:user?.preferred_username" + [title]="user?.roles.join(' | ')" + [picture]="'assets/images/default_user.png'"> + </nb-user> + --> + + <nb-user *ngIf="user.roles!=undefined" + [nbContextMenu]="userMenu" + nbContextMenuTag="user-menu" + [onlyPicture]="userPictureOnly" + [name]="(user?.name!=undefined)?user?.name:user?.preferred_username" + [title]="user?.roles.join(' | ')" + [picture]="'assets/images/default_user.png'"> </nb-user> + + <nb-user *ngIf="user.realm_access!=undefined && user.realm_access.roles != undefined" + [nbContextMenu]="userMenu" + nbContextMenuTag="user-menu" + [onlyPicture]="userPictureOnly" + [name]="(user?.name!=undefined)?user?.name:user?.preferred_username" + [title]="user?.realm_access.roles.join(' | ')" + [picture]="'assets/images/default_user.png'"> + </nb-user> + </nb-action> </nb-actions> </div> diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6d1ccf66..2063bef2 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -8,9 +8,10 @@ import { AnalyticsService } from './@core/utils/analytics.service'; import { SeoService } from './@core/utils/seo.service'; import { v4 as uuidv4 } from 'uuid'; -import { NbAuthJWTToken, NbAuthOAuth2JWTToken, NbAuthOAuth2Token, NbAuthService, NbAuthStrategy, NbOAuth2AuthStrategy, NbOAuth2ClientAuthMethod, NbOAuth2ResponseType } from '@nebular/auth'; +import { NbAuthJWTToken, NbAuthOAuth2JWTToken, NbAuthOAuth2Token, NbAuthService, NbAuthStrategy, NbOAuth2AuthStrategy, NbOAuth2ClientAuthMethod, NbOAuth2GrantType, NbOAuth2ResponseType } from '@nebular/auth'; import { ConfigService } from '@ngx-config/core'; import { OidcJWTToken } from './auth/oidc/oidc'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'ngx-app', @@ -19,11 +20,13 @@ import { OidcJWTToken } from './auth/oidc/oidc'; export class AppComponent implements OnInit { - constructor(private analytics: AnalyticsService, - private seoService: SeoService, - private configService: ConfigService, - authService: NbAuthService, // force construction of the auth service - oauthStrategy: NbOAuth2AuthStrategy) { + constructor( + private analytics: AnalyticsService, + private seoService: SeoService, + private translate: TranslateService, + private configService: ConfigService, + authService: NbAuthService, // force construction of the auth service + oauthStrategy: NbOAuth2AuthStrategy) { oauthStrategy.setOptions({ name: configService.getSettings("authProfile"), @@ -49,14 +52,20 @@ export class AppComponent implements OnInit { failure: null, // stay on the same page }, refresh: { - // endpoint: 'token', - // grantType: NbOAuth2GrantType.REFRESH_TOKEN, + endpoint: '/token', + grantType: NbOAuth2GrantType.REFRESH_TOKEN, + class: OidcJWTToken } - }) + }); + + //MMA SET DEFAULT LANGUAGE + let defaultLanguage = configService.getSettings("defaultLanguage"); + translate.setDefaultLang(defaultLanguage); } ngOnInit(): void { this.analytics.trackPageViews(); this.seoService.trackCanonicalChanges(); } + } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1196dfff..e8d59942 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -3,95 +3,95 @@ * Copyright Akveo. All Rights Reserved. * Licensed under the MIT License. See License.txt in the project root for license information. */ -import { BrowserModule } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { NgModule } from '@angular/core'; -import { HttpClientModule, HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http'; -import { ConfigModule, ConfigLoader } from '@ngx-config/core'; -import { ConfigHttpLoader } from '@ngx-config/http-loader'; -import { TranslateHttpLoader } from "@ngx-translate/http-loader"; -import { CoreModule } from './@core/core.module'; -import { ThemeModule } from './@theme/theme.module'; -import { AppComponent } from './app.component'; -import { AppRoutingModule } from './app-routing.module'; -import { - NbDatepickerModule, - NbDialogModule, - NbMenuModule, - NbSidebarModule, - NbToastrModule, - NbWindowModule, - NbTimepickerModule, -} from '@nebular/theme'; -import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; -import { NbRoleProvider, NbSecurityModule } from '@nebular/security'; -import { TokenInterceptor } from './auth/services/token.interceptor'; -import { AuthGuard } from './auth/services/auth.guard'; -import { AuthLogoutComponent } from './auth/logout/auth-logout.component'; -import { MarkdownModule } from 'ngx-markdown'; + import { BrowserModule } from '@angular/platform-browser'; + import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + import { NgModule } from '@angular/core'; + import { HttpClientModule, HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http'; + import { ConfigModule, ConfigLoader } from '@ngx-config/core'; + import { ConfigHttpLoader } from '@ngx-config/http-loader'; + import { TranslateHttpLoader } from "@ngx-translate/http-loader"; + import { CoreModule } from './@core/core.module'; + import { ThemeModule } from './@theme/theme.module'; + import { AppComponent } from './app.component'; + import { AppRoutingModule } from './app-routing.module'; + import { + NbDatepickerModule, + NbDialogModule, + NbMenuModule, + NbSidebarModule, + NbToastrModule, + NbWindowModule, + NbTimepickerModule, + } from '@nebular/theme'; + import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; + import { NbSecurityModule } from '@nebular/security'; + import { TokenInterceptor } from './auth/services/token.interceptor'; + import { AuthGuard } from './auth/services/auth.guard'; + import { AuthLogoutComponent } from './auth/logout/auth-logout.component'; + import { MarkdownModule } from 'ngx-markdown'; + + export function configFactory(http: HttpClient): ConfigLoader { + return new ConfigHttpLoader(http, './assets/config.json'); + } + + export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); + } + + @NgModule({ + declarations: [AppComponent, AuthLogoutComponent], + imports: [ + BrowserModule, + BrowserAnimationsModule, + HttpClientModule, + AppRoutingModule, + NbSidebarModule.forRoot(), + NbMenuModule.forRoot(), + NbDatepickerModule.forRoot(), + NbTimepickerModule.forRoot(), + NbDialogModule.forRoot(), + NbWindowModule.forRoot(), + NbToastrModule.forRoot(), + CoreModule.forRoot(), + ThemeModule.forRoot(), + MarkdownModule.forRoot(), + ConfigModule.forRoot({ + provide: ConfigLoader, + useFactory: configFactory, + deps: [HttpClient] + }), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: createTranslateLoader, + deps: [HttpClient], + }, + }), - -export function configFactory(http: HttpClient): ConfigLoader { - return new ConfigHttpLoader(http, './assets/config.json'); -} - -export function createTranslateLoader(http: HttpClient) { - return new TranslateHttpLoader(http, './assets/i18n/', '.json'); -} - -@NgModule({ - declarations: [AppComponent, AuthLogoutComponent], - imports: [ - BrowserModule, - BrowserAnimationsModule, - HttpClientModule, - AppRoutingModule, - NbSidebarModule.forRoot(), - NbMenuModule.forRoot(), - NbDatepickerModule.forRoot(), - NbTimepickerModule.forRoot(), - NbDialogModule.forRoot(), - NbWindowModule.forRoot(), - NbToastrModule.forRoot(), - CoreModule.forRoot(), - ThemeModule.forRoot(), - MarkdownModule.forRoot(), - ConfigModule.forRoot({ - provide: ConfigLoader, - useFactory: configFactory, - deps: [HttpClient] - }), - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useFactory: createTranslateLoader, - deps: [HttpClient], - }, - }), - - NbSecurityModule.forRoot({ - accessControl: { - ADMIN: { - view: '*' - }, - MANAGER: { - view: ['external-app', 'maps', 'messina', 'home', 'about', 'charts', 'lorem-ipsum', 'simulation-wizard'] - }, - CITIZEN: { - view: ['home', 'about', 'ui-features','catalogues', 'simulation-wizard'] - } - }, - }) - ], - providers: [ - AuthGuard, - { - provide: HTTP_INTERCEPTORS, - useClass: TokenInterceptor, - multi: true - } - ], - bootstrap: [AppComponent], -}) -export class AppModule { -} + NbSecurityModule.forRoot({ + accessControl: { + ADMIN: { + view: '*' + }, + MANAGER: { + view: ['external-app', 'maps', 'messina', 'home', 'about', 'charts', 'catalogues', 'simulation-wizard', 'traffic simulation', 'tecnalia', 'dashboard-management'] + }, + CITIZEN: { + view: ['home', 'about', 'ui-features','catalogues', 'simulation-wizard', 'traffic simulation', 'tecnalia'] + } + }, + }) + ], + providers: [ + AuthGuard, + { + provide: HTTP_INTERCEPTORS, + useClass: TokenInterceptor, + multi: true + } + ], + bootstrap: [AppComponent], + }) + export class AppModule { + } + \ No newline at end of file diff --git a/src/app/auth/oidc/oidc.ts b/src/app/auth/oidc/oidc.ts index 7598356c..570befbe 100644 --- a/src/app/auth/oidc/oidc.ts +++ b/src/app/auth/oidc/oidc.ts @@ -14,8 +14,12 @@ export interface UserClaims { sub: string; updated_at: string; roles: string[]; + realm_access: RealmAccess; } +export interface RealmAccess { + roles: string[]; +} export interface OidcToken { user: UserClaims; diff --git a/src/app/auth/services/auth.guard.ts b/src/app/auth/services/auth.guard.ts index 8fbe1f44..713728b4 100644 --- a/src/app/auth/services/auth.guard.ts +++ b/src/app/auth/services/auth.guard.ts @@ -18,7 +18,8 @@ export class AuthGuard implements CanActivate { if (!this.configService.getSettings('enableAuthentication')) { return true; } else { - return this.authService.isAuthenticated() + //return this.authService.isAuthenticated() + return this.authService.isAuthenticatedOrRefresh() .pipe( tap(authenticated => { if (!authenticated) { diff --git a/src/app/auth/services/oidc-user-information.service.ts b/src/app/auth/services/oidc-user-information.service.ts index 53a1f8c7..d530159b 100644 --- a/src/app/auth/services/oidc-user-information.service.ts +++ b/src/app/auth/services/oidc-user-information.service.ts @@ -29,14 +29,25 @@ export class OidcUserInformationService { } getRole(): Observable<string[]> { - return this.user ? observableOf(this.user.roles.map(role => role.toUpperCase())) : observableOf(['CITIZEN']); + //console.log("## DEMO GetRole, this.user =>" + this.user); + //return observableOf(['ADMIN']); + //return this.user ? observableOf(this.user.roles.map(role => role.toUpperCase())) : observableOf(['CITIZEN']); + return this.user + ? ( + this.user.roles != undefined + ? observableOf(this.user.roles.map(role => role.toUpperCase())) + : (this.user.realm_access != undefined && this.user.realm_access.roles != undefined + ? observableOf(this.user.realm_access.roles.map(role => role.toUpperCase())) + : observableOf(['CITIZEN']) + ) + ) + : observableOf(['CITIZEN']); } getUser(): Observable<UserClaims> { return observableOf(this.user); } - private publishUser(user: any) { this.user$.next(user) } diff --git a/src/app/auth/services/token.interceptor.ts b/src/app/auth/services/token.interceptor.ts index ae1ddb56..c9afc416 100644 --- a/src/app/auth/services/token.interceptor.ts +++ b/src/app/auth/services/token.interceptor.ts @@ -8,6 +8,7 @@ import { import { Observable } from 'rxjs'; import { NbAuthOAuth2JWTToken, NbAuthOAuth2Token, NbAuthService } from '@nebular/auth'; import { ConfigService } from '@ngx-config/core'; +import { switchMap, tap } from 'rxjs/operators'; @Injectable() export class TokenInterceptor implements HttpInterceptor { @@ -31,14 +32,26 @@ export class TokenInterceptor implements HttpInterceptor { let newHeaders = req.headers; - if(this.config.getSettings('enableAuthentication')){ - this.auth.getToken().subscribe((x: NbAuthOAuth2JWTToken) => this.token = x); - if (this.token.getPayload() != null) { - newHeaders = newHeaders.append('Authorization', 'Bearer ' + this.token.getPayload().access_token); - } + if(this.config.getSettings('enableAuthentication')) { + return this.auth.isAuthenticatedOrRefresh().pipe( + tap(authenticated=>{ + // console.log("tap authenticated => "+authenticated); + }), + + switchMap((authenticated)=>{ + this.auth.getToken().subscribe((x: NbAuthOAuth2JWTToken) => this.token = x); + let newHeaders = req.headers; + if (this.token.getPayload() != null) { + newHeaders = newHeaders.append('Authorization', 'Bearer ' + this.token.getPayload().access_token); + } + const authReq = req.clone({ headers: newHeaders }); + return next.handle(authReq); + }) + ) + } else { + const authReq = req.clone({ headers: newHeaders }); + return next.handle(authReq); } - const authReq = req.clone({ headers: newHeaders }); - return next.handle(authReq); } } diff --git a/src/app/pages/data-catalogue/datalet-iframe/datalet-iframe.component.html b/src/app/pages/data-catalogue/datalet-iframe/datalet-iframe.component.html index 2309c568..57600b9b 100644 --- a/src/app/pages/data-catalogue/datalet-iframe/datalet-iframe.component.html +++ b/src/app/pages/data-catalogue/datalet-iframe/datalet-iframe.component.html @@ -1,5 +1,5 @@ <nb-card> <nb-card-body> - <iframe class="responsive-iframe" frameBorder="0" [src]="iframeUrl | safe"></iframe> + <iframe class="responsive-iframe" frameBorder="0" [src]="iframeUrl | safe"></iframe> </nb-card-body> </nb-card> \ No newline at end of file diff --git a/src/app/pages/data-catalogue/dataset/dataset.component.ts b/src/app/pages/data-catalogue/dataset/dataset.component.ts index f0c0e86a..384e209c 100644 --- a/src/app/pages/data-catalogue/dataset/dataset.component.ts +++ b/src/app/pages/data-catalogue/dataset/dataset.component.ts @@ -199,6 +199,7 @@ export class DatasetComponent implements OnInit { } openExistingDatalet(distribution:DCATDistribution){ + this.dialogService.open(ShowDataletsComponent, { context: { distributionID: distribution.id, diff --git a/src/app/pages/data-catalogue/services/data-cataglogue-api.service.ts b/src/app/pages/data-catalogue/services/data-cataglogue-api.service.ts index 58912ffe..2610f531 100644 --- a/src/app/pages/data-catalogue/services/data-cataglogue-api.service.ts +++ b/src/app/pages/data-catalogue/services/data-cataglogue-api.service.ts @@ -16,7 +16,6 @@ import { SearchResult } from '../model/search-result'; providedIn: 'root' }) export class DataCataglogueAPIService { - private apiEndpoint; constructor(private config:ConfigService,private http:HttpClient) { diff --git a/src/app/pages/home/home.component.html b/src/app/pages/home/home.component.html index 7e39c846..6e956ae0 100644 --- a/src/app/pages/home/home.component.html +++ b/src/app/pages/home/home.component.html @@ -78,19 +78,132 @@ <div *ngSwitchCase="'MESSINA'"> - {{pilotName}} + <section class="jumbotron text-center"> + <div class="container"> + <h1 class="jumbotron-heading">Urbanite Project</h1> + <p class="lead text-muted"> + <i><b>A platform that will assist with decision-making in urban transportation and transformation.</b></i> + </p> + <p class="lead text-muted"> + In recent years, with the population and size of cities increasing exponentially, transportation has + become even more important. The need for more efficient mobility solutions has been met with a variety of + new concepts like sharing vehicles, electric scooters for rent, and even disruptive start-ups like Uber + or Cabify, which are causing a stir with previously used models. However, such new models are putting + public administrations in a challenging situation. The EU-funded URBANITE project aims to develop a + solution that collects and analyses data, that combined with AI, can assist public administrations in + policy-related decisions concerning urban transformation caused by these new transportation and business models. + URBANITE focuses also on social aspects, by analyzing stakeholders’ trust in technologies aiding decision-making. + </p> + + <nb-card> + <nb-card-header>{{pilotName}}</nb-card-header> + <nb-card-body> + <img class="img-fluid" src="/assets/images/messina_07.png" alt="Photo of Messina"> + </nb-card-body> + <nb-card-footer class="d-flex justify-content-center"> + <nb-icon icon="info" status="info" nbTooltip="Details" (click)="openDialog('MESSINA')"></nb-icon> + </nb-card-footer> + </nb-card> + + </div> + </section> </div> <div *ngSwitchCase="'BILBAO'"> - {{pilotName}} + <section class="jumbotron text-center"> + <div class="container"> + <h1 class="jumbotron-heading">Urbanite Project</h1> + <p class="lead text-muted"> + <i><b>A platform that will assist with decision-making in urban transportation and transformation.</b></i> + </p> + <p class="lead text-muted"> + In recent years, with the population and size of cities increasing exponentially, transportation has + become even more important. The need for more efficient mobility solutions has been met with a variety of + new concepts like sharing vehicles, electric scooters for rent, and even disruptive start-ups like Uber + or Cabify, which are causing a stir with previously used models. However, such new models are putting + public administrations in a challenging situation. The EU-funded URBANITE project aims to develop a + solution that collects and analyses data, that combined with AI, can assist public administrations in + policy-related decisions concerning urban transformation caused by these new transportation and business models. + URBANITE focuses also on social aspects, by analyzing stakeholders’ trust in technologies aiding decision-making. + </p> + + <nb-card> + <nb-card-header>{{pilotName}}</nb-card-header> + <nb-card-body> + <img class="img-fluid" src="/assets/images/bilbao_02.jpeg" alt="Photo of Bilbao"> + </nb-card-body> + <nb-card-footer class="d-flex justify-content-center"> + <nb-icon icon="info" status="info" nbTooltip="Details" (click)="openDialog('BILBAO')"></nb-icon> + </nb-card-footer> + </nb-card> + + </div> + </section> </div> <div *ngSwitchCase="'HELSINKI'"> - {{pilotName}} + <section class="jumbotron text-center"> + <div class="container"> + <h1 class="jumbotron-heading">Urbanite Project</h1> + <p class="lead text-muted"> + <i><b>A platform that will assist with decision-making in urban transportation and transformation.</b></i> + </p> + <p class="lead text-muted"> + In recent years, with the population and size of cities increasing exponentially, transportation has + become even more important. The need for more efficient mobility solutions has been met with a variety of + new concepts like sharing vehicles, electric scooters for rent, and even disruptive start-ups like Uber + or Cabify, which are causing a stir with previously used models. However, such new models are putting + public administrations in a challenging situation. The EU-funded URBANITE project aims to develop a + solution that collects and analyses data, that combined with AI, can assist public administrations in + policy-related decisions concerning urban transformation caused by these new transportation and business models. + URBANITE focuses also on social aspects, by analyzing stakeholders’ trust in technologies aiding decision-making. + </p> + + <nb-card> + <nb-card-header>{{pilotName}}</nb-card-header> + <nb-card-body> + <img class="img-fluid" src="/assets/images/helsinki_06.jpg" alt="Photo of Helsinki"> + </nb-card-body> + <nb-card-footer class="d-flex justify-content-center"> + <nb-icon icon="info" status="info" nbTooltip="Details" (click)="openDialog('HELSINKI')"></nb-icon> + </nb-card-footer> + </nb-card> + + </div> + </section> </div> - <div *ngSwitchCase="'AMSTERDAM'"> - {{pilotName}} + <div *ngSwitchCase="'AMSTERDAM'"> + <section class="jumbotron text-center"> + <div class="container"> + <h1 class="jumbotron-heading">Urbanite Project</h1> + <p class="lead text-muted"> + <i><b>A platform that will assist with decision-making in urban transportation and transformation.</b></i> + </p> + <p class="lead text-muted"> + In recent years, with the population and size of cities increasing exponentially, transportation has + become even more important. The need for more efficient mobility solutions has been met with a variety of + new concepts like sharing vehicles, electric scooters for rent, and even disruptive start-ups like Uber + or Cabify, which are causing a stir with previously used models. However, such new models are putting + public administrations in a challenging situation. The EU-funded URBANITE project aims to develop a + solution that collects and analyses data, that combined with AI, can assist public administrations in + policy-related decisions concerning urban transformation caused by these new transportation and business models. + URBANITE focuses also on social aspects, by analyzing stakeholders’ trust in technologies aiding decision-making. + </p> + + <nb-card> + <nb-card-header>{{pilotName}}</nb-card-header> + <nb-card-body> + <img class="img-fluid" src="/assets/images/am3.jpg" alt="Photo of Amsterdam"> + </nb-card-body> + <nb-card-footer class="d-flex justify-content-center"> + <nb-icon icon="info" status="info" nbTooltip="Details" (click)="openDialog('AMSTERDAM')"></nb-icon> + </nb-card-footer> + </nb-card> + + </div> + </section> + </div> </div> diff --git a/src/app/pages/pages-menu.ts b/src/app/pages/pages-menu.ts index 6a8554e0..a1715330 100644 --- a/src/app/pages/pages-menu.ts +++ b/src/app/pages/pages-menu.ts @@ -99,6 +99,34 @@ export const MENU_ITEMS: NbMenuItem[] = [ } ], }, + + { + title: 'Dashboard Section', + icon: 'color-palette-outline', + data:{ + name:"dashboard-management" + }, + children: [ + { + title: 'Manage Dashboard Pages', + link: '/pages/dashboard-management/manage-dashboard-pages', + }, + { + title: 'Manage Menu Blocks', + link: '/pages/dashboard-management/manage-menu-blocks', + }, + { + title: 'Clone Dashboard', + link: '/pages/dashboard-management/dashboard-clone-wizard', + } + /*, + { + title: 'Manage Dashboard Target', + link: '/pages/dashboard-management/manage-dashboard-target', + }*/ + ], + }, + { title: 'Maps', icon: 'map-outline', diff --git a/src/app/pages/pages-routing.module.ts b/src/app/pages/pages-routing.module.ts index b4878a97..a79a0cd2 100644 --- a/src/app/pages/pages-routing.module.ts +++ b/src/app/pages/pages-routing.module.ts @@ -43,6 +43,13 @@ const routes: Routes = [{ loadChildren: () => import('./messina/messina.module') .then(m => m.MessinaModule), }, + + { + path: 'dashboard-management', + loadChildren: () => import('./dashboard-management/dashboard-management.module') + .then(m => m.DashboardManagementModule), + }, + { path: 'charts', loadChildren: () => import('./charts/charts.module') diff --git a/src/app/pages/pages.component.ts b/src/app/pages/pages.component.ts index 8c5d6072..524d862a 100644 --- a/src/app/pages/pages.component.ts +++ b/src/app/pages/pages.component.ts @@ -2,6 +2,9 @@ import { Component, OnInit } from '@angular/core'; import { NbAccessChecker } from '@nebular/security'; import { NbMenuItem } from '@nebular/theme'; import { ConfigService } from '@ngx-config/core'; +import { IMenuBlock } from '../model/menu-block.model'; +import { MenuBlockCrudService } from './dashboard-management/services/menu-block-crud.service'; +import { MenuMapperService } from './dashboard-management/services/menu-mapper.service'; import { MENU_ITEMS } from './pages-menu'; @Component({ @@ -15,15 +18,18 @@ import { MENU_ITEMS } from './pages-menu'; `, }) export class PagesComponent implements OnInit { - menu = MENU_ITEMS; userRoles: string[]; demoEnabled=false; extPages=[]; default_pilot="URBANITE"; + constructor( private accessChecker: NbAccessChecker, - private configService: ConfigService) { + private configService: ConfigService, + private menuBlockService: MenuBlockCrudService, + private menuMapperService: MenuMapperService + ) { } ngOnInit() { @@ -44,9 +50,34 @@ export class PagesComponent implements OnInit { } }) - //if (this.configService.getSettings('enableAuthentication')) { + /** JMA - Add SHARED Menu to the Dashboard */ + this.menuBlockService.getAllShared().subscribe((response: Array<IMenuBlock>) => { + // console.log('block response: ', JSON.stringify(response)); + if (response !== undefined && response !== null){ + try{ + this.menu = this.menu.concat(response.map(item => this.menuMapperService.mapBlockToNbMenuItem(item))) ; + } catch(e) { + console.log("# Error in menu content loading due to " + e); + } + } + // console.log("# Menu SHARED => "+this.menu); + }); + + /** JMA - Add PERSONAL Menu to the Dashboard */ + this.menuBlockService.getAllPersonal().subscribe((response: Array<IMenuBlock>) => { + // console.log('block response: ', JSON.stringify(response)); + if (response !== undefined && response !== null){ + try{ + this.menu = this.menu.concat(response.map(item => this.menuMapperService.mapBlockToNbMenuItem(item))) ; + } catch(e) { + console.log("# Error in menu content loading due to " + e); + } + } + // console.log("# Menu PESONAL => "+this.menu); + }); + + this.authMenuItems(); - //} } authMenuItems() { @@ -76,10 +107,11 @@ export class PagesComponent implements OnInit { } /** - * MMA - This is useful to enable a specific menuItem only for a specific Pilot. + * MMA - Useful to enable a specific menuItem only for a specific Pilot. + * NOTE: "URBANITE" is visible for all pilots */ if(menuItem.data && menuItem.data['pilot']!=undefined){ - menuItem.hidden = !(menuItem.data['pilot']==this.default_pilot); + menuItem.hidden = menuItem.data['pilot'] != this.default_pilot && this.default_pilot != 'URBANITE'; } if (!menuItem.hidden && menuItem.children != null) { @@ -88,4 +120,5 @@ export class PagesComponent implements OnInit { }); } } + } diff --git a/src/app/pages/pages.module.ts b/src/app/pages/pages.module.ts index ea319597..89b8fc17 100644 --- a/src/app/pages/pages.module.ts +++ b/src/app/pages/pages.module.ts @@ -1,10 +1,11 @@ import { NgModule } from '@angular/core'; import { NbMenuModule, NbDatepickerModule } from '@nebular/theme'; - import { ThemeModule } from '../@theme/theme.module'; import { PagesComponent } from './pages.component'; import { PagesRoutingModule } from './pages-routing.module'; import { MiscellaneousModule } from './miscellaneous/miscellaneous.module'; +import { SharedModule } from './shared/shared.module'; +import { TranslateModule } from '@ngx-translate/core'; @NgModule({ @@ -12,7 +13,9 @@ import { MiscellaneousModule } from './miscellaneous/miscellaneous.module'; PagesRoutingModule, ThemeModule, NbMenuModule, + SharedModule, MiscellaneousModule, + TranslateModule, ], declarations: [ PagesComponent diff --git a/src/assets/config-template.json b/src/assets/config-template.json index c047874f..ea37dcf0 100644 --- a/src/assets/config-template.json +++ b/src/assets/config-template.json @@ -1,4 +1,5 @@ { + "defaultLanguage":"en", "dashboardBaseURL":"__DASHBOARD_BASE_URL__", "enableAuthentication":true, "authProfile": "oidc", @@ -30,6 +31,12 @@ "api_base_url":"https://urbanite-node1.comune.messina.it", "token":"mcOUnnIyupQJzbitPX7Q2MnyqrVQUkmo" }, + "dashboard-controller":{ + "api_base_url":"__DASHBOARD_CTRL_API_BASE_URL__", + "useIDM4Target":true, + "enable_delay":true, + "delay": 1400 + }, "jsi":{ "traffic_sim_server":"__TRAFFICSIMSERVER_API_BASE_URL__" } diff --git a/src/assets/config.json b/src/assets/config.json index e78bb740..3c8d8101 100644 --- a/src/assets/config.json +++ b/src/assets/config.json @@ -1,4 +1,5 @@ { + "defaultLanguage":"en", "dashboardBaseURL":"http://localhost:4200", "enableAuthentication":true, "authProfile": "oidc", @@ -30,6 +31,12 @@ "api_base_url":"https://urbanite-node1.comune.messina.it", "token":"mcOUnnIyupQJzbitPX7Q2MnyqrVQUkmo" }, + "dashboard-controller":{ + "api_base_url":"http://localhost:8085", + "useIDM4Target":true, + "enable_delay":true, + "delay": 1400 + }, "jsi":{ "traffic_sim_server":"http://localhost:8081" } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index e69de29b..07e3a146 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -0,0 +1,242 @@ +{ + "homepage": { + "homepage": "Homepage", + "welcome": "Welcome " + }, + "general": { + "success": "Success", + "error": "Error", + "warning": "Warning", + "copy": "Copy", + "manage": "Manage", + "add": "Add", + "save": "Save", + "back": "Back", + "cancel": "Cancel", + "edit": "Edit", + "drag": "Drag", + "delete": "Delete", + "created": "created", + "updated": "updated", + "reset": "Ripristina", + "actions": "Actions", + "close": "Close", + "username": "Username", + "log_in": "Login", + "log_out": "Logout", + "page": "Page", + "dashboard": "Dashboard", + "component": "Component", + "targets": "Targets", + "menu_items": "Menu Items", + "menu_blocks": "Menu Blocks", + "manage_targets": "Manage Targets", + "editContent":"Edit Content", + "deleteConfirm": "Do you want delete the selected item?", + "yes": "Yes", + "no": "No", + "mandatory_field": "Mandatory field", + "catch_error": "Error loading the results, please try again.", + "operation_failed": "Operation failed", + "datacatalogue_error": "Error in retrieving data from Data Catalogue. Please contact admin" + }, + "dashboardPage":{ + "title": "Manage Dashboard Pages", + "created": "A new Dashboard Page has been succesfully created", + "updated": "The Dashboard Page has been succesfully updated", + "deleted": "The Dashboard Page has been deleted", + "succesfully_cloned": "The Dashboard Page has been sucessfully cloned", + "notFound": "No Dashboard Pages found", + "createLabel": "Create a new Dashboard Page", + "createOrEditLabel": "Create or edit a Dashboard Page", + "id": "ID", + "name": "Name", + "description": "Description", + "content": "Content", + "note": "Note", + "shared": "Published", + "createdDate": "Created Date", + "createdBy": "Created By", + "dashboardComponent": "Dashboard Component", + "menuItem": "Menu Item", + "target": "Target", + "margin": "Margin", + "drag_items": "Drag items", + "outer_margin": "Outer Margin", + "push_items": "Push Items", + "disable_push_on_drag": "Disable Push On Drag", + "swap_items": "Swap Items", + "shared_info": "Field to enable(true) or disable(false) the Dashboard publishing.", + "margin_description": "Spacing between the components", + "outer_margin_description": "Spacing around the components", + "push_items_description": "Push items when resizing and dragging", + "disable_push_on_drag_description": "Disable push on drag", + "swap_items_description": "Swap items when dragging", + "not_mandatory_field": "This field in only for SHARED Dashboard", + "checkbox_label":"Flags", + "margin_label":"Margin", + "back_modal": "Are you sure you want to go back without saving the dashboard?", + "catch_error": "Error loading the dashboard page, please try again.", + "wait_for_components": "Please wait for components to load.", + "visualization_catch_error": "Error in Dashboard page content during visualization. Please contact admin o try again." + }, + "dashboardComponent": { + "list": "Dashboard Components", + "title": "Dashboard Component", + "id": "ID", + "componentType": "Component Type", + "componentName": "Component Name", + "componentcontent": "Componentcontent", + "componentPosition": "Component Position", + "choose_file": "Choose file", + "createdDate": "Created Date", + "createdBy": "Created By", + "dashboardPage": "Dashboard Page", + "properties":"Component properties", + "selectComponentType":"Select a component type", + "deleteConfirm":"Are you sure to delete component?", + "fill_text": "Enter text...", + "fill_image": "Paste image url or base 64 image string...", + "fill_chart": "Select datalet...", + "fill_iframe": "Enter iframe content...", + "fill_map": "Enter GeoJson URL of the map...", + "fill_map_title": "Enter Title", + "fill_weather_title": "Enter Title", + "fill_weather_subtitle": "Enter Subtitle", + "fill_statistics_title": "Enter Title", + "fill_statistics_subtitle": "Enter Subtitle", + "fill_statistics_value": "Enter Value", + "fill_statistics_icon": "Enter Icon", + "select_datalet": "Select a datalet...", + "select_aspect_ratio": "Select aspect ratio", + "example_map_url": "http://localhost:4200/assets/map/2020-March-heatgeo-5minutes.json", + "label_geoJsonurl": "GeoJSON URL", + "label_title": "Title", + "label_subtitle": "Subtitle", + "label_value": "Value", + "label_icon": "Icon", + "label_content": "Content", + "label_datalet": "Datalet", + "label_image_url": "Image URL", + "label_upload_image": "Upload Image", + "upload_from_file": "Upload from file", + "upload_from_url/base64": "Upload from url/base64", + "catch_error": "Error loading the dashboard component, please try again." + }, + "target": { + "list": "Targets", + "title": "Target", + "notFound": "No Targets found", + "createLabel": "Create a new Target", + "createOrEditLabel": "Create or edit a Target", + "created": "Target has been succesfully created", + "updated": "Target has been succesfully updated", + "deleted": "Target has been succesfully deleted", + "deleteConfirm": "Are you sure you want to delete Target ?", + "id": "ID", + "type": "Type", + "name": "Name", + "note": "Note", + "value": "Value", + "fill_person":"Select person first!", + "fill_group":"Select group first!", + "fill_role":"Select role first!", + "createdDate": "Created Date", + "createdBy": "Created By", + "dashboardPage": "Dashboard Page", + "select_person":"Select Available Person", + "select_group":"Select Available Group", + "select_role":"Select Available Role", + "add_button_info": "Select a Person and/or a Role to share with him the Dashboard", + "add_button_info_disabled": "Disabled Section. To enable 'Targets' the Dashboard have to be of type SHARED" + }, + "menuBlock": { + "list": "Menu Blocks", + "title": "Menu Block", + "created": "Menu Block has been succesfully created", + "updated": "Menu Block has been succesfully updated", + "deleted": "Menu Block has been succesfully deleted", + "deleteConfirm": "Are you sure you want to delete Menu Block?", + "id": "ID", + "code": "Code", + "label": "Label", + "order": "Order", + "type": "Type", + "createdDate": "Created Date", + "createdBy": "Created By", + "menuItem": "Menu Item", + "select_menu_block":"Select a menu block", + "select_menu_block_info": "Selecting a SHARED block the Dashboard will be of type SHARED, otherwise the Dashboard will be of type PERSONAL.", + "shared": "Shared", + "delete_fk_error": "Delete failed cause this menu block is linked to more menu items.", + "type_info": "SHARED is for sharing the menu and the dashboard linked, PERSONAL is only for private visibility (not sharing)." + }, + "menuItem": { + "list": "Menu Items", + "title": "Menu Item", + "created": "Menu Item has been succesfully created", + "updated": "Menu Item has been succesfully updated", + "deleted": "Menu Item has been succesfully deleted", + "deleteConfirm": "Are you sure you want to delete Menu Item?", + "id": "ID", + "code": "Code", + "label": "Label", + "order": "Order", + "type": "Type", + "createdDate": "Created Date", + "createdBy": "Created By", + "dashboardPage": "Dashboard Page", + "menuBlock": "Menu Block", + "menu_item_label":"Menu Item label", + "add_menu_item_label": "Add the label of MenuItem", + "add_button_info": "Select a menu block and then write the menu item label to add a link to the Dashboard", + "copy_info": "Copy the Dashboard name into menu item label (to generate the link Name)" + }, + "dashboardClone":{ + "step_one": "First step", + "step_two": "Second step", + "step_three": "Third step", + "question_one": "Which Dashboard do you want to clone?", + "question_two": "Which Dashboard want to create?", + "question_three": "Do you want to proceed with cloning?", + "question_targets": "Do you want to clone targets?", + "action_clone": "Clone", + "action_prev": "Prev", + "action_next": "Next", + "action_preview": "Preview", + "clone_targets": "Clone the targets", + "summary_one": "Selected dashboard name:", + "summary_two": "Created dashboard name:", + "summary_three": "Created dashboard targets:", + "select_dashboard": "Select a dashboard", + "add_dashboard_name":"Dashboard name", + "info": "Click on the Preview button to visualize it", + "source_dashboard": "Source Dashboard", + "parameter_error": "Error: missing 1 or more parameters", + "cloned_dashboard_name": "Cloned Dashboard name", + "clone_missing_parameter": "Clone Missing Parameters" + }, + "ComponentType": { + "null": "", + "TEXT": "TEXT", + "IMAGE": "IMAGE", + "CHART": "CHART", + "IFRAME": "IFRAME", + "STATISTICS": "SUMMARY", + "MAP": "MAP", + "WEATHER": "WEATHER EXAMPLE" + }, + "MenuType": { + "null": "", + "SHARED": "SHARED", + "PERSONAL": "PERSONAL", + "OTHER": "EMPTY TYPE" + }, + "TargetType": { + "null": "", + "PERSON": "PERSON", + "GROUP": "GROUP", + "ROLE": "ROLE" + } + +} \ No newline at end of file diff --git a/src/polyfills.ts b/src/polyfills.ts index 4ed2f714..1505190e 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -55,3 +55,5 @@ import 'core-js/es7/object'; if (typeof SVGElement.prototype.contains === 'undefined') { SVGElement.prototype.contains = HTMLDivElement.prototype.contains; } + +import 'hammerjs/hammer'; -- GitLab