From 5c5cdfc5871cf4361b8824bb8cd7515b1b184ed1 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Thu, 2 Oct 2025 14:24:09 +0300 Subject: [PATCH 01/10] display NaN in landing page metrics when api is not responding --- src/app/landing/landing.component.ts | 36 +++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/app/landing/landing.component.ts b/src/app/landing/landing.component.ts index 1cc25fd..76815d0 100644 --- a/src/app/landing/landing.component.ts +++ b/src/app/landing/landing.component.ts @@ -60,19 +60,19 @@ export class LandingComponent implements OnInit { registeredUsers: this.generalMetricsService.getRegisteredIndividuals().pipe( catchError(error => { console.error('Failed to load registered users', error); - return scheduled([{ registeredIndividuals: this.registeredUsers }], asyncScheduler); + return scheduled([{ registeredIndividuals: NaN }], asyncScheduler); }) ), publishedServiceSpecs: this.generalMetricsService.getPublishedServiceSpecifications().pipe( catchError(error => { console.error('Failed to load published specs', error); - return scheduled([{ publishedServiceSpecifications: this.publishedServiceSpecs }], asyncScheduler); + return scheduled([{ publishedServiceSpecifications: NaN }], asyncScheduler); }) ), registeredResourceSpecs: this.generalMetricsService.getRegisteredResourceSpecifications().pipe( catchError(error => { console.error('Failed to load resource specs', error); - return scheduled([{ registeredResourceSpecifications: this.registeredResourceSpecs }], asyncScheduler); + return scheduled([{ registeredResourceSpecifications: NaN }], asyncScheduler); }) ) }).subscribe( @@ -157,6 +157,7 @@ export class LandingComponent implements OnInit { // } startAnimation() { + const stepTime = 50; this.intervalId = setInterval(() => { @@ -172,6 +173,35 @@ export class LandingComponent implements OnInit { } } }, stepTime); + + } + + infiniteLoopAnimation() { + + const stepTime = 50; + let time = 0; + + const maxValues: { [key: string]: number } = {}; + + for (const key in this.animatedCounts) { + maxValues[key] = Math.floor(Math.random() * 500) + 10; + } + + this.intervalId = setInterval(() => { + time += 0.1; + + for (const key in this.animatedCounts) { + const k = key as keyof typeof this.animatedCounts; + const maxValue = maxValues[key]; + + const triangleWave = 2 * Math.abs((time % 2) - 1) - 1; + const normalizedTriangleWave = (triangleWave + 1) / 2; + const apiNotRespondingValue = normalizedTriangleWave * maxValue; + + this.animatedCounts[k] = Math.round(apiNotRespondingValue); + } + }, stepTime); + } ngOnDestroy() { -- GitLab From 46a40f410a5cc510005eb1823b75c132050aaad0 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Wed, 8 Oct 2025 11:44:38 +0300 Subject: [PATCH 02/10] showcase different types of counters when apis are not responding --- src/app/app.module.ts | 36 ++++++++++++++------------ src/app/landing/landing.component.html | 11 +++++--- src/app/landing/landing.component.scss | 2 ++ src/app/landing/landing.component.ts | 16 +++++++++--- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 90e14c2..b183014 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -88,6 +88,7 @@ import { DoughnutchartComponent } from './landing/metrics/doughnutchart/doughnut import { StatusCardComponent } from './landing/metrics/statuscard/statuscard.component'; import { PortalCardComponent } from './landing/portalcard/portalcard.component'; import { SkeletonComponent } from './landing/metrics/skeleton.component'; +import { CdkTableModule } from "@angular/cdk/table"; registerLocaleData(enGB); export function initializeApp(bootstrap: BootstrapService) { @@ -116,23 +117,24 @@ export function initializeAppTheme(bootstrap: BootstrapService) { SkeletonComponent, ], imports: [ - BrowserModule, - AppRoutingModule, - HttpClientModule, - BrowserAnimationsModule, - CollapseModule.forRoot(), - BsDropdownModule.forRoot(), - FormsModule, - ReactiveFormsModule, - NgProgressModule, - NgProgressHttpModule, - MatInputModule, - MatDialogModule, - MatCheckboxModule, - OAuthModule.forRoot(), - ToastrModule.forRoot({ progressBar: true, preventDuplicates: true }), - NgChartsModule - ], + BrowserModule, + AppRoutingModule, + HttpClientModule, + BrowserAnimationsModule, + CollapseModule.forRoot(), + BsDropdownModule.forRoot(), + FormsModule, + ReactiveFormsModule, + NgProgressModule, + NgProgressHttpModule, + MatInputModule, + MatDialogModule, + MatCheckboxModule, + OAuthModule.forRoot(), + ToastrModule.forRoot({ progressBar: true, preventDuplicates: true }), + NgChartsModule, + CdkTableModule +], providers: [ AppService, AuthService, diff --git a/src/app/landing/landing.component.html b/src/app/landing/landing.component.html index 4247534..249b14e 100644 --- a/src/app/landing/landing.component.html +++ b/src/app/landing/landing.component.html @@ -46,7 +46,7 @@
-
{{animatedCounts.registeredUsers}}
+
{{isNaN(animatedCounts.registeredUsers) ? targetCounts.registeredUsers: animatedCounts.registeredUsers}}
Registered Users
@@ -61,7 +61,7 @@
-
{{animatedCounts.publishedServiceSpecs}}
+
{{isNaN(animatedCounts.publishedServiceSpecs) ? ' ': animatedCounts.publishedServiceSpecs}}
Published Service Specifications
@@ -77,7 +77,12 @@
-
{{animatedCounts.registeredResourceSpecs}}
+
+
+
+ +
{{animatedCounts.registeredResourceSpecs}}
+
Registered Resource Specifications
diff --git a/src/app/landing/landing.component.scss b/src/app/landing/landing.component.scss index 944ec75..4234036 100644 --- a/src/app/landing/landing.component.scss +++ b/src/app/landing/landing.component.scss @@ -95,6 +95,8 @@ .card-title { padding-top: 1.5rem; transition: all 0.3s ease-in-out; + display: flex; + justify-content: center; } diff --git a/src/app/landing/landing.component.ts b/src/app/landing/landing.component.ts index 76815d0..9dbe215 100644 --- a/src/app/landing/landing.component.ts +++ b/src/app/landing/landing.component.ts @@ -23,16 +23,16 @@ export class LandingComponent implements OnInit { config: IAppConfig loggedIn: boolean - // Video section variables private player: Player; videoLoaded: boolean = false; + @ViewChild('videoRef', { static: true }) videoElement!: ElementRef; // Metrics section variables - registeredUsers = 9999; - publishedServiceSpecs = 9999; - registeredResourceSpecs = 9999; + // registeredUsers = 9999; + // publishedServiceSpecs = 9999; + // registeredResourceSpecs = 9999; animatedCounts = { registeredUsers: 0, publishedServiceSpecs: 0, @@ -84,6 +84,9 @@ export class LandingComponent implements OnInit { ) } + isNaN(value: any): boolean { + return isNaN(value); + } initializePlayer() { this.player = new Player(this.videoElement.nativeElement, { @@ -166,6 +169,11 @@ export class LandingComponent implements OnInit { const current = this.animatedCounts[k]; const target = this.targetCounts[k]; + if (isNaN(target)) { + this.animatedCounts[k] = NaN; + continue; + } + const delta = target - current; if (Math.abs(delta) > 0) { -- GitLab From b53db583fb83480db39330499c505c3db3bde046 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Tue, 14 Oct 2025 12:51:20 +0300 Subject: [PATCH 03/10] skeleton loader when waiting for the api response --- src/app/landing/landing.component.html | 20 ++++++++++---- src/app/landing/landing.component.ts | 36 +++++++++++++++++--------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/app/landing/landing.component.html b/src/app/landing/landing.component.html index 249b14e..f2e0f28 100644 --- a/src/app/landing/landing.component.html +++ b/src/app/landing/landing.component.html @@ -46,7 +46,12 @@
-
{{isNaN(animatedCounts.registeredUsers) ? targetCounts.registeredUsers: animatedCounts.registeredUsers}}
+
+
+
+ +
{{isNaN(animatedCounts.registeredUsers) ? 'NaN': animatedCounts.registeredUsers}}
+
Registered Users
@@ -61,7 +66,12 @@
-
{{isNaN(animatedCounts.publishedServiceSpecs) ? ' ': animatedCounts.publishedServiceSpecs}}
+
+
+
+ +
{{isNaN(animatedCounts.publishedServiceSpecs) ? ' ': animatedCounts.publishedServiceSpecs}}
+
Published Service Specifications
@@ -77,10 +87,10 @@
-
-
+
+
- +
{{animatedCounts.registeredResourceSpecs}}
Registered Resource Specifications
diff --git a/src/app/landing/landing.component.ts b/src/app/landing/landing.component.ts index 9dbe215..b3e8555 100644 --- a/src/app/landing/landing.component.ts +++ b/src/app/landing/landing.component.ts @@ -5,7 +5,7 @@ import { OAuthService } from 'angular-oauth2-oidc'; import { AuthService } from 'src/app/shared/services/auth.service'; import { GeneralMetricsApiService } from '../openApis/openSliceMetrics/services'; import { asyncScheduler, forkJoin, scheduled } from 'rxjs'; -import { catchError } from 'rxjs/operators'; +import { catchError, delay } from 'rxjs/operators'; import Player from '@vimeo/player'; @Component({ @@ -26,6 +26,7 @@ export class LandingComponent implements OnInit { // Video section variables private player: Player; videoLoaded: boolean = false; + isLoading: boolean = true; @ViewChild('videoRef', { static: true }) videoElement!: ElementRef; @@ -34,14 +35,14 @@ export class LandingComponent implements OnInit { // publishedServiceSpecs = 9999; // registeredResourceSpecs = 9999; animatedCounts = { - registeredUsers: 0, - publishedServiceSpecs: 0, - registeredResourceSpecs: 0 + registeredUsers: NaN, + publishedServiceSpecs: NaN, + registeredResourceSpecs: NaN }; targetCounts = { - registeredUsers: 0, - publishedServiceSpecs: 0, - registeredResourceSpecs: 0 + registeredUsers: NaN, + publishedServiceSpecs: NaN, + registeredResourceSpecs: NaN }; private intervalId: any; @@ -53,9 +54,7 @@ export class LandingComponent implements OnInit { this.initializePlayer() - - // Initialize animated counts - this.startAnimation(); + forkJoin({ registeredUsers: this.generalMetricsService.getRegisteredIndividuals().pipe( catchError(error => { @@ -75,13 +74,20 @@ export class LandingComponent implements OnInit { return scheduled([{ registeredResourceSpecifications: NaN }], asyncScheduler); }) ) - }).subscribe( + }) + .pipe(delay(1000)) + .subscribe( results => { + this.targetCounts.registeredUsers = results.registeredUsers.registeredIndividuals; this.targetCounts.publishedServiceSpecs = results.publishedServiceSpecs.publishedServiceSpecifications; this.targetCounts.registeredResourceSpecs = results.registeredResourceSpecs.registeredResourceSpecifications; + this.isLoading = false; + // Initialize animated counts + this.startAnimation(); + } - ) + ); } isNaN(value: any): boolean { @@ -163,6 +169,12 @@ export class LandingComponent implements OnInit { const stepTime = 50; + this.animatedCounts = { + registeredUsers: 0, + publishedServiceSpecs: 0, + registeredResourceSpecs: 0 + } + this.intervalId = setInterval(() => { for (const key in this.animatedCounts) { const k = key as keyof typeof this.animatedCounts; -- GitLab From 74d86deb7cc9dd0ee0d1f61c3e55f0735d1210f0 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Wed, 15 Oct 2025 14:12:26 +0300 Subject: [PATCH 04/10] bug fixes --- src/app/landing/landing.component.html | 2 +- src/app/landing/landing.component.ts | 79 ++++++++------------------ 2 files changed, 25 insertions(+), 56 deletions(-) diff --git a/src/app/landing/landing.component.html b/src/app/landing/landing.component.html index f2e0f28..15f04aa 100644 --- a/src/app/landing/landing.component.html +++ b/src/app/landing/landing.component.html @@ -47,7 +47,7 @@
-
+
{{isNaN(animatedCounts.registeredUsers) ? 'NaN': animatedCounts.registeredUsers}}
diff --git a/src/app/landing/landing.component.ts b/src/app/landing/landing.component.ts index b3e8555..d69e49a 100644 --- a/src/app/landing/landing.component.ts +++ b/src/app/landing/landing.component.ts @@ -31,9 +31,6 @@ export class LandingComponent implements OnInit { @ViewChild('videoRef', { static: true }) videoElement!: ElementRef; // Metrics section variables - // registeredUsers = 9999; - // publishedServiceSpecs = 9999; - // registeredResourceSpecs = 9999; animatedCounts = { registeredUsers: NaN, publishedServiceSpecs: NaN, @@ -50,11 +47,11 @@ export class LandingComponent implements OnInit { this.config = this.appService.config this.authService.isAuthenticated$.subscribe( isAuthenticated => this.loggedIn = isAuthenticated - ) + ) this.initializePlayer() - + forkJoin({ registeredUsers: this.generalMetricsService.getRegisteredIndividuals().pipe( catchError(error => { @@ -75,19 +72,19 @@ export class LandingComponent implements OnInit { }) ) }) - .pipe(delay(1000)) - .subscribe( - results => { - - this.targetCounts.registeredUsers = results.registeredUsers.registeredIndividuals; - this.targetCounts.publishedServiceSpecs = results.publishedServiceSpecs.publishedServiceSpecifications; - this.targetCounts.registeredResourceSpecs = results.registeredResourceSpecs.registeredResourceSpecifications; - this.isLoading = false; - // Initialize animated counts - this.startAnimation(); + .pipe(delay(500)) + .subscribe( + results => { - } - ); + this.targetCounts.registeredUsers = results.registeredUsers.registeredIndividuals; + this.targetCounts.publishedServiceSpecs = results.publishedServiceSpecs.publishedServiceSpecifications; + this.targetCounts.registeredResourceSpecs = results.registeredResourceSpecs.registeredResourceSpecifications; + this.isLoading = false; + // Initialize animated counts + this.startAnimation(); + + } + ); } isNaN(value: any): boolean { @@ -103,8 +100,8 @@ export class LandingComponent implements OnInit { muted: true, playsinline: true, // For mobile devices }); - - this.player.ready().then(() => { + + this.player.ready().then(() => { this.player.on('play', () => { this.videoLoaded = true; }); @@ -119,7 +116,7 @@ export class LandingComponent implements OnInit { // console.error('Player not initialized'); // return; // } - + // try { // if (this.isPlaying) { // await this.player.pause(); @@ -149,7 +146,7 @@ export class LandingComponent implements OnInit { // toggleFullscreen() { // const elem = this.videoElement.nativeElement.parentElement; - + // if (!document.fullscreenElement) { // if (elem?.requestFullscreen) { // elem.requestFullscreen(); @@ -166,14 +163,14 @@ export class LandingComponent implements OnInit { // } startAnimation() { - const stepTime = 50; + // Initialize counts to NaN if the target is NaN (api didn't respond), otherwise start from 0 and animate the count this.animatedCounts = { - registeredUsers: 0, - publishedServiceSpecs: 0, - registeredResourceSpecs: 0 - } + registeredUsers: isNaN(this.targetCounts.registeredUsers) ? NaN : 0, + publishedServiceSpecs: isNaN(this.targetCounts.publishedServiceSpecs) ? NaN : 0, + registeredResourceSpecs: isNaN(this.targetCounts.registeredResourceSpecs) ? NaN : 0 + }; this.intervalId = setInterval(() => { for (const key in this.animatedCounts) { @@ -196,34 +193,6 @@ export class LandingComponent implements OnInit { } - infiniteLoopAnimation() { - - const stepTime = 50; - let time = 0; - - const maxValues: { [key: string]: number } = {}; - - for (const key in this.animatedCounts) { - maxValues[key] = Math.floor(Math.random() * 500) + 10; - } - - this.intervalId = setInterval(() => { - time += 0.1; - - for (const key in this.animatedCounts) { - const k = key as keyof typeof this.animatedCounts; - const maxValue = maxValues[key]; - - const triangleWave = 2 * Math.abs((time % 2) - 1) - 1; - const normalizedTriangleWave = (triangleWave + 1) / 2; - const apiNotRespondingValue = normalizedTriangleWave * maxValue; - - this.animatedCounts[k] = Math.round(apiNotRespondingValue); - } - }, stepTime); - - } - ngOnDestroy() { if (this.intervalId) { clearInterval(this.intervalId); @@ -234,5 +203,5 @@ export class LandingComponent implements OnInit { this.authService.login() } - + } -- GitLab From a5971b1ed211fcff6a5155fe29415afc74b1f37b Mon Sep 17 00:00:00 2001 From: tzanatos Date: Thu, 16 Oct 2025 11:24:03 +0300 Subject: [PATCH 05/10] fixed skeleton height --- src/app/landing/landing.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/landing/landing.component.html b/src/app/landing/landing.component.html index 15f04aa..60ce40b 100644 --- a/src/app/landing/landing.component.html +++ b/src/app/landing/landing.component.html @@ -47,7 +47,7 @@
-
+
{{isNaN(animatedCounts.registeredUsers) ? 'NaN': animatedCounts.registeredUsers}}
@@ -67,7 +67,7 @@
-
+
{{isNaN(animatedCounts.publishedServiceSpecs) ? ' ': animatedCounts.publishedServiceSpecs}}
@@ -88,7 +88,7 @@
-
+
{{animatedCounts.registeredResourceSpecs}}
-- GitLab From 62b3bf17d038ef29118e5aba05bc357d4de0c0a1 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Thu, 16 Oct 2025 11:31:36 +0300 Subject: [PATCH 06/10] removed the delay rxjs operator --- src/app/landing/landing.component.html | 10 ++++++---- src/app/landing/landing.component.ts | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/app/landing/landing.component.html b/src/app/landing/landing.component.html index 60ce40b..639a491 100644 --- a/src/app/landing/landing.component.html +++ b/src/app/landing/landing.component.html @@ -46,11 +46,12 @@
-
+
-
{{isNaN(animatedCounts.registeredUsers) ? 'NaN': animatedCounts.registeredUsers}}
+ +
{{animatedCounts.registeredUsers}}
Registered Users
@@ -66,11 +67,12 @@
-
+
-
{{isNaN(animatedCounts.publishedServiceSpecs) ? ' ': animatedCounts.publishedServiceSpecs}}
+ +
{{animatedCounts.publishedServiceSpecs}}
Published Service Specifications
diff --git a/src/app/landing/landing.component.ts b/src/app/landing/landing.component.ts index d69e49a..5ecfc72 100644 --- a/src/app/landing/landing.component.ts +++ b/src/app/landing/landing.component.ts @@ -5,7 +5,7 @@ import { OAuthService } from 'angular-oauth2-oidc'; import { AuthService } from 'src/app/shared/services/auth.service'; import { GeneralMetricsApiService } from '../openApis/openSliceMetrics/services'; import { asyncScheduler, forkJoin, scheduled } from 'rxjs'; -import { catchError, delay } from 'rxjs/operators'; +import { catchError } from 'rxjs/operators'; import Player from '@vimeo/player'; @Component({ @@ -72,7 +72,7 @@ export class LandingComponent implements OnInit { }) ) }) - .pipe(delay(500)) + // .pipe(delay(500)) .subscribe( results => { -- GitLab From 8e4af021516467b280e750fb516ef6e176d6fb15 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Mon, 3 Nov 2025 10:10:10 +0200 Subject: [PATCH 07/10] fixed skeleton to metrics page analytics --- .../doughnutchart.component.scss | 10 ++++---- src/app/landing/metrics/metrics.component.ts | 24 +++++++++---------- .../statuscard/statuscard.component.html | 20 +++++++++------- .../statuscard/statuscard.component.scss | 4 ++++ .../statuscard/statuscard.component.ts | 4 ++++ 5 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/app/landing/metrics/doughnutchart/doughnutchart.component.scss b/src/app/landing/metrics/doughnutchart/doughnutchart.component.scss index 9bc9618..d531267 100644 --- a/src/app/landing/metrics/doughnutchart/doughnutchart.component.scss +++ b/src/app/landing/metrics/doughnutchart/doughnutchart.component.scss @@ -18,11 +18,11 @@ .pie-chart { // height: calc(20vh - 10px); - // width: calc(20vh - 10px); - // max-height: 170px; - // max-width: 170px; - min-height: 150px; - min-width: 150px; + width: calc(20vh - 20px); + max-height: 170px; + max-width: 170px; + min-height: 80px; + min-width: 80px; } .chart-info { diff --git a/src/app/landing/metrics/metrics.component.ts b/src/app/landing/metrics/metrics.component.ts index 29d214f..744b5dd 100644 --- a/src/app/landing/metrics/metrics.component.ts +++ b/src/app/landing/metrics/metrics.component.ts @@ -169,73 +169,73 @@ export class MetricsComponent implements OnInit { registeredMANO: this.nfvMetricsService.getRegisteredManoProviders().pipe( catchError(error => { console.error('Failed to load registered MANO providers', error); - return scheduled([{ registeredManoProviders: this.registeredMANO }], asyncScheduler); + return scheduled([{ registeredManoProviders: NaN }], asyncScheduler); }) ), registeredNSDs: this.nfvMetricsService.getRegisteredNsds().pipe( catchError(error => { console.error('Failed to load registered NSDs', error); - return scheduled([{ registeredNSDs: this.registeredNSDs }], asyncScheduler); + return scheduled([{ registeredNSDs: NaN }], asyncScheduler); }) ), registeredVNFs: this.nfvMetricsService.getRegisteredVnfs().pipe( catchError(error => { console.error('Failed to load registered VNFs', error); - return scheduled([{ registeredVNFs: this.registeredVNFs }], asyncScheduler); + return scheduled([{ registeredVNFs: NaN }], asyncScheduler); }) ), registeredUsers: this.generalMetricsService.getRegisteredIndividuals().pipe( catchError(error => { console.error('Failed to load registered users', error); - return scheduled([{ registeredIndividuals: this.registeredUsers }], asyncScheduler); + return scheduled([{ registeredIndividuals: NaN }], asyncScheduler); }) ), publishedServiceSpecs: this.generalMetricsService.getPublishedServiceSpecifications().pipe( catchError(error => { console.error('Failed to load published specs', error); - return scheduled([{ publishedServiceSpecifications: this.publishedServiceSpecs }], asyncScheduler); + return scheduled([{ publishedServiceSpecifications: NaN }], asyncScheduler); }) ), registeredResourceSpecs: this.generalMetricsService.getRegisteredResourceSpecifications().pipe( catchError(error => { console.error('Failed to load resource specs', error); - return scheduled([{ registeredResourceSpecifications: this.registeredResourceSpecs }], asyncScheduler); + return scheduled([{ registeredResourceSpecifications: NaN }], asyncScheduler); }) ), totalCreatedServices: this.serviceMetricsService.getTotalServices().pipe( catchError(error => { console.error('Failed to load total services', error); - return scheduled([{ totalServices: this.totalCreatedServices }], asyncScheduler); + return scheduled([{ totalServices: NaN }], asyncScheduler); }) ), activeServices: this.serviceMetricsService.getTotalServices({ state: 'ACTIVE' }).pipe( catchError(error => { console.error('Failed to load active services', error); - return scheduled([{ totalServices: this.activeServices }], asyncScheduler); + return scheduled([{ totalServices: NaN }], asyncScheduler); }) ), totalResources: this.resourceMetricsService.getTotalResources().pipe( catchError(error => { console.error('Failed to load total resources', error); - return scheduled([{ totalResources: this.totalResources }], asyncScheduler); + return scheduled([{ totalResources: NaN }], asyncScheduler); }) ), availableResources: this.resourceMetricsService.getTotalResources({ state: 'AVAILABLE' }).pipe( catchError(error => { console.error('Failed to load available resources', error); - return scheduled([{ totalResources: this.availableResources }], asyncScheduler); + return scheduled([{ totalResources: NaN }], asyncScheduler); }) ), activeServiceOrders: this.serviceOrderMetricsService.getTotalActiveServiceOrders().pipe( catchError(error => { console.error('Failed to load active service orders', error); - return scheduled([{ activeServiceOrders: this.activeServiceOrders }], asyncScheduler); + return scheduled([{ activeServiceOrders: NaN }], asyncScheduler); }) ), totalCompletedOrders: this.serviceOrderMetricsService.getTotalServiceOrders({ state: 'COMPLETED' }).pipe( catchError(error => { console.error('Failed to load completed service orders', error); - return scheduled([{ totalServiceOrders: this.totalCompletedOrders }], asyncScheduler); + return scheduled([{ totalServiceOrders: NaN }], asyncScheduler); }) ), serviceOrdersTotal15days: this.serviceOrderMetricsService.getServiceOrdersGroupedByDay({ starttime, endtime }).pipe( diff --git a/src/app/landing/metrics/statuscard/statuscard.component.html b/src/app/landing/metrics/statuscard/statuscard.component.html index bdfc877..cef8038 100644 --- a/src/app/landing/metrics/statuscard/statuscard.component.html +++ b/src/app/landing/metrics/statuscard/statuscard.component.html @@ -21,10 +21,8 @@
-
- -
-
+ +
@@ -49,13 +47,17 @@
-
{{statusStats}}
+
+
+ +
+ +
{{statusStats}}
+
+
-
- -
-

{{statusTitle}}

+

{{statusTitle}}

diff --git a/src/app/landing/metrics/statuscard/statuscard.component.scss b/src/app/landing/metrics/statuscard/statuscard.component.scss index 3a55ea1..e08e37a 100644 --- a/src/app/landing/metrics/statuscard/statuscard.component.scss +++ b/src/app/landing/metrics/statuscard/statuscard.component.scss @@ -50,6 +50,10 @@ font-size: 0.9rem; } +.status-stats { + font-weight: 700; +} + .portal-button { color: #0078AE; } diff --git a/src/app/landing/metrics/statuscard/statuscard.component.ts b/src/app/landing/metrics/statuscard/statuscard.component.ts index a716203..eecabe9 100644 --- a/src/app/landing/metrics/statuscard/statuscard.component.ts +++ b/src/app/landing/metrics/statuscard/statuscard.component.ts @@ -19,5 +19,9 @@ export class StatusCardComponent implements OnInit { ngOnInit(): void { } + + isNaN(value: any): boolean { + return isNaN(value); + } } \ No newline at end of file -- GitLab From 950fa968dd8ff7a4e43bbf10ab8393f5800c3bf8 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Thu, 20 Nov 2025 13:31:25 +0200 Subject: [PATCH 08/10] chart skeleton fixes --- .../doughnutchart.component.html | 14 +++---- .../doughnutchart/doughnutchart.component.ts | 4 ++ .../landing/metrics/metrics.component.html | 4 +- src/app/landing/metrics/metrics.component.ts | 39 +++++++++++-------- 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/app/landing/metrics/doughnutchart/doughnutchart.component.html b/src/app/landing/metrics/doughnutchart/doughnutchart.component.html index 2eaed51..d08cc83 100644 --- a/src/app/landing/metrics/doughnutchart/doughnutchart.component.html +++ b/src/app/landing/metrics/doughnutchart/doughnutchart.component.html @@ -1,27 +1,27 @@
-
+
-
+
{{noDataMessage}}
-
-
+
-
+
{{chartTitle}}
-
+
-
+
  • Service Orders Last 15 Days
    -
    +
    @@ -78,7 +78,7 @@
    No Service Orders found for the last 15 days
    -
    diff --git a/src/app/landing/metrics/metrics.component.ts b/src/app/landing/metrics/metrics.component.ts index 744b5dd..5aa7516 100644 --- a/src/app/landing/metrics/metrics.component.ts +++ b/src/app/landing/metrics/metrics.component.ts @@ -37,23 +37,23 @@ export class MetricsComponent implements OnInit { config: IAppConfig loggedIn: boolean - registeredUsers = 9999; - activeServices = 9999; - registeredNSDs = 9999; - registeredVNFs = 9999; - registeredMANO = 9999; - registeredResourceSpecs = 9999; - publishedServiceSpecs = 9999; - availableResources = 9999; - activeServiceOrders = 9999; - publishedProductOfferings = 9999; - totalCreatedServices = 9999; - totalCompletedOrders = 9999; - totalResources = 9999; - serviceOrdersTotal15days = 9999; - serviceOrdersTotalMonth = 9999; - servicesTotalMonth = 9999; - resourcesTotalMonth = 9999; + registeredUsers = NaN; + activeServices = NaN; + registeredNSDs = NaN; + registeredVNFs = NaN; + registeredMANO = NaN; + registeredResourceSpecs = NaN; + publishedServiceSpecs = NaN; + availableResources = NaN; + activeServiceOrders = NaN; + publishedProductOfferings = NaN; + totalCreatedServices = NaN; + totalCompletedOrders = NaN; + totalResources = NaN; + serviceOrdersTotal15days = NaN; + serviceOrdersTotalMonth = NaN; + servicesTotalMonth = NaN; + resourcesTotalMonth = NaN; serviceOrdersByState = []; servicesByState: ServicesGroupByStateItem[]; @@ -344,6 +344,8 @@ export class MetricsComponent implements OnInit { this.resourcesTotalMonth = results.resourcesTotalMonth.resources.total; + console.log(this.serviceOrdersTotal15days); + this.serviceOrdersByDay = results.serviceOrdersGroupedByDay.serviceOrders.aggregations.groupByDay; this.barchartData.labels = this.serviceOrdersByDay.map(data => new Date(data.key).toLocaleDateString('en-GB', { day: 'numeric', month: 'numeric' }) @@ -373,6 +375,9 @@ export class MetricsComponent implements OnInit { this.authService.login() } + isNaN(value: any): boolean { + return isNaN(value); + } chartOptions: ChartConfiguration<'doughnut'>['options'] = { responsive: true, -- GitLab From 6ebd6c8bc3881bf4fffab899c656a47ff90d74d5 Mon Sep 17 00:00:00 2001 From: tzanatos Date: Fri, 21 Nov 2025 11:28:41 +0200 Subject: [PATCH 09/10] added toastr service in metrics page --- src/app/landing/metrics/metrics.component.ts | 72 +++++++++++++------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/src/app/landing/metrics/metrics.component.ts b/src/app/landing/metrics/metrics.component.ts index 5aa7516..8b6ac88 100644 --- a/src/app/landing/metrics/metrics.component.ts +++ b/src/app/landing/metrics/metrics.component.ts @@ -15,6 +15,8 @@ import { ResourcesGroupByStateItem, ServiceOrdersGroupByDayItem, ServicesGroupB import { asyncScheduler, forkJoin, scheduled } from 'rxjs'; import { catchError } from 'rxjs/internal/operators/catchError'; import * as moment from 'moment'; +import { ToastrService } from 'ngx-toastr'; + @Component({ selector: 'app-landing', @@ -30,9 +32,11 @@ export class MetricsComponent implements OnInit { private serviceMetricsService: ServiceMetricsApiService, private resourceMetricsService: ResourceMetricsApiService, private serviceOrderMetricsService: ServiceOrderMetricsApiService, - private nfvMetricsService: NfvMetricsApiService + private nfvMetricsService: NfvMetricsApiService, + private toast: ToastrService ) { } + apiError: boolean = false; isLoading: boolean = true; config: IAppConfig loggedIn: boolean @@ -168,85 +172,99 @@ export class MetricsComponent implements OnInit { forkJoin({ registeredMANO: this.nfvMetricsService.getRegisteredManoProviders().pipe( catchError(error => { - console.error('Failed to load registered MANO providers', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ registeredManoProviders: NaN }], asyncScheduler); }) ), registeredNSDs: this.nfvMetricsService.getRegisteredNsds().pipe( catchError(error => { - console.error('Failed to load registered NSDs', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ registeredNSDs: NaN }], asyncScheduler); }) ), registeredVNFs: this.nfvMetricsService.getRegisteredVnfs().pipe( catchError(error => { - console.error('Failed to load registered VNFs', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ registeredVNFs: NaN }], asyncScheduler); }) ), registeredUsers: this.generalMetricsService.getRegisteredIndividuals().pipe( catchError(error => { - console.error('Failed to load registered users', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ registeredIndividuals: NaN }], asyncScheduler); }) ), publishedServiceSpecs: this.generalMetricsService.getPublishedServiceSpecifications().pipe( catchError(error => { - console.error('Failed to load published specs', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ publishedServiceSpecifications: NaN }], asyncScheduler); }) ), registeredResourceSpecs: this.generalMetricsService.getRegisteredResourceSpecifications().pipe( catchError(error => { - console.error('Failed to load resource specs', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ registeredResourceSpecifications: NaN }], asyncScheduler); }) ), totalCreatedServices: this.serviceMetricsService.getTotalServices().pipe( catchError(error => { - console.error('Failed to load total services', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ totalServices: NaN }], asyncScheduler); }) ), activeServices: this.serviceMetricsService.getTotalServices({ state: 'ACTIVE' }).pipe( catchError(error => { - console.error('Failed to load active services', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ totalServices: NaN }], asyncScheduler); }) ), totalResources: this.resourceMetricsService.getTotalResources().pipe( catchError(error => { - console.error('Failed to load total resources', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ totalResources: NaN }], asyncScheduler); }) ), availableResources: this.resourceMetricsService.getTotalResources({ state: 'AVAILABLE' }).pipe( catchError(error => { - console.error('Failed to load available resources', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ totalResources: NaN }], asyncScheduler); }) ), activeServiceOrders: this.serviceOrderMetricsService.getTotalActiveServiceOrders().pipe( catchError(error => { - console.error('Failed to load active service orders', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ activeServiceOrders: NaN }], asyncScheduler); }) ), totalCompletedOrders: this.serviceOrderMetricsService.getTotalServiceOrders({ state: 'COMPLETED' }).pipe( catchError(error => { - console.error('Failed to load completed service orders', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ totalServiceOrders: NaN }], asyncScheduler); }) ), serviceOrdersTotal15days: this.serviceOrderMetricsService.getServiceOrdersGroupedByDay({ starttime, endtime }).pipe( catchError(error => { - console.error('Failed to load service orders by day', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ serviceOrders:{ total: this.serviceOrdersTotal15days } }], asyncScheduler); }) ), serviceOrdersGroupedByDay: this.serviceOrderMetricsService.getServiceOrdersGroupedByDay({ starttime, endtime }).pipe( catchError(error => { - console.error('Failed to load service orders by day', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); const mockGroupByDay = this.barChartLabels.map(label => { const [day, month] = label.split('/').map(Number); const isoDate = new Date(Date.UTC(new Date().getFullYear(), month - 1, day)).toISOString(); @@ -266,13 +284,15 @@ export class MetricsComponent implements OnInit { ), serviceOrdersTotalMonth: this.serviceOrderMetricsService.getServiceOrdersGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - console.error('Failed to load service orders by state', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ serviceOrders:{ total: this.serviceOrdersTotalMonth } }], asyncScheduler); }) ), serviceOrdersGroupedByState: this.serviceOrderMetricsService.getServiceOrdersGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - console.error('Failed to load service orders by state', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ serviceOrders: { aggregations: { @@ -287,13 +307,15 @@ export class MetricsComponent implements OnInit { ), resourcesTotalMonth: this.resourceMetricsService.getResourcesGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - console.error('Failed to load resources by state', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ resources:{ total: this.resourcesTotalMonth } }], asyncScheduler); }) ), resourcesGroupedByState: this.resourceMetricsService.getResourcesGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - console.error('Failed to load resources by state', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ resources: { aggregations: { @@ -308,13 +330,15 @@ export class MetricsComponent implements OnInit { ), servicesTotalMonth: this.serviceMetricsService.getServicesGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - console.error('Failed to load services by state', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ services:{ total: this.servicesTotalMonth } }], asyncScheduler); }) ), servicesGroupedByState: this.serviceMetricsService.getServicesGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - console.error('Failed to load services by state', error); + this.apiError = true; + this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ services: { aggregations: { @@ -344,8 +368,6 @@ export class MetricsComponent implements OnInit { this.resourcesTotalMonth = results.resourcesTotalMonth.resources.total; - console.log(this.serviceOrdersTotal15days); - this.serviceOrdersByDay = results.serviceOrdersGroupedByDay.serviceOrders.aggregations.groupByDay; this.barchartData.labels = this.serviceOrdersByDay.map(data => new Date(data.key).toLocaleDateString('en-GB', { day: 'numeric', month: 'numeric' }) @@ -367,6 +389,10 @@ export class MetricsComponent implements OnInit { .filter(item => chartLabelsServices.includes(item.key)); this.servicesData.datasets[0].data = this.servicesByState.map(data => data.count); this.isLoading = false; + + if(!this.apiError) { + this.toast.success("All data loaded successfully"); + } }); } -- GitLab From e0187018359493665ebade0ad39311c74e902dff Mon Sep 17 00:00:00 2001 From: Kostis Trantzas Date: Fri, 21 Nov 2025 16:33:23 +0200 Subject: [PATCH 10/10] removing success toastr --- src/app/landing/metrics/metrics.component.ts | 36 ++++---------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/src/app/landing/metrics/metrics.component.ts b/src/app/landing/metrics/metrics.component.ts index 8b6ac88..180c86f 100644 --- a/src/app/landing/metrics/metrics.component.ts +++ b/src/app/landing/metrics/metrics.component.ts @@ -36,7 +36,6 @@ export class MetricsComponent implements OnInit { private toast: ToastrService ) { } - apiError: boolean = false; isLoading: boolean = true; config: IAppConfig loggedIn: boolean @@ -122,8 +121,8 @@ export class MetricsComponent implements OnInit { datasets: [ { data: [], - backgroundColor: ['#428bca', '#42ca74', '#c5ca42' ], - hoverBackgroundColor: ['#235e91', '#24a34a', '#8d911f'], + backgroundColor: ['#fd8f00', '#28a745', '#dc3545' ], + hoverBackgroundColor: ['#d27700', '#1e873d', '#941822'], hoverBorderColor: 'white' } ], @@ -135,8 +134,8 @@ export class MetricsComponent implements OnInit { datasets: [ { data: [], - backgroundColor: ['#428bca', '#42ca74', '#c5ca42', '#ca42c1', '#d91424'], - hoverBackgroundColor: ['#235e91', '#24a34a', '#8d911f', '#871880', '#941822'], + backgroundColor: ['#428bca', '#cab642ff', '#fd8f00', '#28a745', '#dc3545'], + hoverBackgroundColor: ['#235e91', '#8d911f', '#d27700', '#1e873d', '#941822'], hoverBorderColor: 'white', } ], @@ -149,8 +148,8 @@ export class MetricsComponent implements OnInit { datasets: [ { data: [], - backgroundColor: ['#428bca', '#c5ca42', '#42ca74', '#d91424'], - hoverBackgroundColor: ['#235e91', '#8d911f', '#24a34a', '#941822'], + backgroundColor: ['#fd8f00', '#a2a3a4', '#28a745', '#d91424'], + hoverBackgroundColor: ['#d27700', '#818181', '#1e873d', '#941822'], hoverBorderColor: 'white' } ] @@ -172,98 +171,84 @@ export class MetricsComponent implements OnInit { forkJoin({ registeredMANO: this.nfvMetricsService.getRegisteredManoProviders().pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ registeredManoProviders: NaN }], asyncScheduler); }) ), registeredNSDs: this.nfvMetricsService.getRegisteredNsds().pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ registeredNSDs: NaN }], asyncScheduler); }) ), registeredVNFs: this.nfvMetricsService.getRegisteredVnfs().pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ registeredVNFs: NaN }], asyncScheduler); }) ), registeredUsers: this.generalMetricsService.getRegisteredIndividuals().pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ registeredIndividuals: NaN }], asyncScheduler); }) ), publishedServiceSpecs: this.generalMetricsService.getPublishedServiceSpecifications().pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ publishedServiceSpecifications: NaN }], asyncScheduler); }) ), registeredResourceSpecs: this.generalMetricsService.getRegisteredResourceSpecifications().pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ registeredResourceSpecifications: NaN }], asyncScheduler); }) ), totalCreatedServices: this.serviceMetricsService.getTotalServices().pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ totalServices: NaN }], asyncScheduler); }) ), activeServices: this.serviceMetricsService.getTotalServices({ state: 'ACTIVE' }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ totalServices: NaN }], asyncScheduler); }) ), totalResources: this.resourceMetricsService.getTotalResources().pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ totalResources: NaN }], asyncScheduler); }) ), availableResources: this.resourceMetricsService.getTotalResources({ state: 'AVAILABLE' }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ totalResources: NaN }], asyncScheduler); }) ), activeServiceOrders: this.serviceOrderMetricsService.getTotalActiveServiceOrders().pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ activeServiceOrders: NaN }], asyncScheduler); }) ), totalCompletedOrders: this.serviceOrderMetricsService.getTotalServiceOrders({ state: 'COMPLETED' }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ totalServiceOrders: NaN }], asyncScheduler); }) ), serviceOrdersTotal15days: this.serviceOrderMetricsService.getServiceOrdersGroupedByDay({ starttime, endtime }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ serviceOrders:{ total: this.serviceOrdersTotal15days } }], asyncScheduler); }) ), serviceOrdersGroupedByDay: this.serviceOrderMetricsService.getServiceOrdersGroupedByDay({ starttime, endtime }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); const mockGroupByDay = this.barChartLabels.map(label => { const [day, month] = label.split('/').map(Number); @@ -284,14 +269,12 @@ export class MetricsComponent implements OnInit { ), serviceOrdersTotalMonth: this.serviceOrderMetricsService.getServiceOrdersGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ serviceOrders:{ total: this.serviceOrdersTotalMonth } }], asyncScheduler); }) ), serviceOrdersGroupedByState: this.serviceOrderMetricsService.getServiceOrdersGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ serviceOrders: { @@ -307,14 +290,12 @@ export class MetricsComponent implements OnInit { ), resourcesTotalMonth: this.resourceMetricsService.getResourcesGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ resources:{ total: this.resourcesTotalMonth } }], asyncScheduler); }) ), resourcesGroupedByState: this.resourceMetricsService.getResourcesGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ resources: { @@ -330,14 +311,12 @@ export class MetricsComponent implements OnInit { ), servicesTotalMonth: this.serviceMetricsService.getServicesGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ services:{ total: this.servicesTotalMonth } }], asyncScheduler); }) ), servicesGroupedByState: this.serviceMetricsService.getServicesGroupedByState({ starttime: starttimeMonth, endtime }).pipe( catchError(error => { - this.apiError = true; this.toast.error('API services are not responding. Please verify service health.'); return scheduled([{ services: { @@ -390,9 +369,6 @@ export class MetricsComponent implements OnInit { this.servicesData.datasets[0].data = this.servicesByState.map(data => data.count); this.isLoading = false; - if(!this.apiError) { - this.toast.success("All data loaded successfully"); - } }); } -- GitLab