From a4b0e1cc4582d0710e237198d74511d9716ef0d7 Mon Sep 17 00:00:00 2001 From: Artem Dychenko <s192441@student.pg.edu.pl> Date: Fri, 28 Feb 2025 20:11:19 +0100 Subject: [PATCH 1/7] feat: add statistisc mock --- .../dashboard-statistics.component.html | 4 +++ src/app/interceptor/interceptor.ts | 5 +++ .../mocks/dashboard-statistics.json | 33 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 src/app/interceptor/mocks/dashboard-statistics.json diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.html b/src/app/components/dashboard-statistics/dashboard-statistics.component.html index 12e92e1..2eaf5d2 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.html +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.html @@ -1 +1,5 @@ + + + + <ngx-skeleton-loader count="10" /> diff --git a/src/app/interceptor/interceptor.ts b/src/app/interceptor/interceptor.ts index 2dfdedf..1b55567 100644 --- a/src/app/interceptor/interceptor.ts +++ b/src/app/interceptor/interceptor.ts @@ -1,6 +1,7 @@ import * as configurationList from './mocks/configurations.json'; import * as configuration1 from './mocks/configuration-1.json'; import * as configuration2 from './mocks/configuration-2.json'; +import * as dashboardStats from './mocks/dashboard-statistics.json'; export const urls = [ { @@ -15,4 +16,8 @@ export const urls = [ url: '/api/v1/configuration', json: configurationList, }, + { + url: '/api/v1/dashboard/statistics', + json: dashboardStats, + }, ]; diff --git a/src/app/interceptor/mocks/dashboard-statistics.json b/src/app/interceptor/mocks/dashboard-statistics.json new file mode 100644 index 0000000..073d63b --- /dev/null +++ b/src/app/interceptor/mocks/dashboard-statistics.json @@ -0,0 +1,33 @@ +{ + "data": { + "id": "statistics", + "total_time": "01:05:30", + "protocols": [ + { + "name": "ETH", + "total-packets": 1234567, + "total-bytes": 123456789 + }, + { + "name": "TCP", + "total-packets": 321312, + "total-bytes": 32143245 + }, + { + "name": "IPv4", + "total-packets": 3333, + "total-bytes": 777 + }, + { + "name": "IPv6", + "total-packets": 0, + "total-bytes": 0 + } + ], + "information-rate": { + "min": 1.23, + "max": 123456, + "current": 123.45 + } + } +} -- GitLab From ca05fa446a966c8b02a0929e37620cbf0b8bca7c Mon Sep 17 00:00:00 2001 From: Artem Dychenko <s192441@student.pg.edu.pl> Date: Sat, 1 Mar 2025 18:00:53 +0100 Subject: [PATCH 2/7] Implement dashboard statistics API part, add d. statistics model and make initial html --- src/app/api/dashboard.api.ts | 23 ++++++-- .../dashboard-statistics.component.html | 47 ++++++++++++++-- .../dashboard-statistics.component.ts | 47 ++++++++++++++-- .../mocks/dashboard-statistics.json | 2 +- src/app/models/dashboard-statistics.ts | 53 +++++++++++++++++++ 5 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 src/app/models/dashboard-statistics.ts diff --git a/src/app/api/dashboard.api.ts b/src/app/api/dashboard.api.ts index 3f08285..498536d 100644 --- a/src/app/api/dashboard.api.ts +++ b/src/app/api/dashboard.api.ts @@ -1,8 +1,25 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { map, Observable, tap } from 'rxjs'; +import { + DashboardStatistics, + DashboardStatisticsDto, + dashboardStatisticsToDto, + dtoToDashboardStatistics, +} from '../models/dashboard-statistics'; +import { ApiResponse } from '../models/api-response'; -export const DASHBOARD_API_URL = '/api/v1/dashboard'; +export const DASHBOARD_API_URL = '/api/v1/dashboard/statistics'; @Injectable({ providedIn: 'root', }) -export class DashboardApi {} +export class DashboardApi { + private readonly httpClient = inject(HttpClient); + + fetch(): Observable<DashboardStatistics> { + return this.httpClient + .get<ApiResponse<DashboardStatisticsDto>>(DASHBOARD_API_URL) + .pipe(map(dto => dtoToDashboardStatistics(dto.data))); + } +} diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.html b/src/app/components/dashboard-statistics/dashboard-statistics.component.html index 2eaf5d2..7da5152 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.html +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.html @@ -1,5 +1,42 @@ - - - - -<ngx-skeleton-loader count="10" /> +<div class="statistics"> + @if (isLoading()) { + <div class="statistics__loader"> + <div eth class="statistics__eth"> + <div class="statistics__eth-content"> + <ngx-skeleton-loader count="7" /> + </div> + </div> + <div protocols class="statistics__prot"> + <div class="statistics__prot-content"> + <ngx-skeleton-loader count="7" /> + </div> + </div> + <div ir> + <ngx-skeleton-loader count="10" /> + </div> + </div> + } + @else { + <div class="statistics__content"> + <p>Total time {{ dashboardStatistic()?.total_time }} </p> +<!-- @for (mac of configuration()?.mac_source; track $index) {--> +<!-- <app-pill>{{ mac }}</app-pill>--> +<!-- } @empty {--> +<!-- <app-pill>All addresses</app-pill>--> +<!-- }--> + <div eth class="statistics__eth"> + <div class="statistics__eth-content"> +<!-- <p>ETH Price: {{ statistics.ethPrice }} USD</p>--> + </div> + </div> + <div protocols class="statistics__prot"> + <div class="statistics__prot-content"> +<!-- <p>Number of active protocols: {{ statistics.protocols }}</p>--> + </div> + </div> + <div ir> +<!-- <p>Other data: {{ statistics.otherData }}</p>--> + </div> + </div> + } +</div> diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.ts b/src/app/components/dashboard-statistics/dashboard-statistics.component.ts index 4f88f14..56a83da 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.ts +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.ts @@ -1,10 +1,51 @@ -import { Component } from '@angular/core'; +import { + afterNextRender, + Component, + effect, + inject, + signal, +} from '@angular/core'; import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader'; +import { NgTemplateOutlet } from '@angular/common'; +import { DashboardApi } from '../../api/dashboard.api'; +import { map, Observable, tap } from 'rxjs'; +import { DashboardStatistics } from '../../models/dashboard-statistics'; +import { PillComponent } from '../pill/pill.component'; @Component({ selector: 'app-dashboard-statistics', - imports: [NgxSkeletonLoaderComponent], + imports: [NgxSkeletonLoaderComponent, NgTemplateOutlet, PillComponent], templateUrl: './dashboard-statistics.component.html', styleUrl: './dashboard-statistics.component.scss', }) -export class DashboardStatisticsComponent {} +export class DashboardStatisticsComponent { + dashboardApi = inject(DashboardApi); + + isLoading = signal<boolean>(false); + dashboardStatistic = signal<DashboardStatistics | undefined>(undefined); + + constructor() { + effect(() => { + this.isLoading.set(true); + + this.dashboardApi.fetch().subscribe(data => { + this.isLoading.set(false); + this.dashboardStatistic.set(data); + }); + }); + } + + private fetchConfigurationOptions(): Observable<DashboardStatistics> { + this.isLoading.set(true); + return this.dashboardApi.fetch().pipe( + map(dashboardStatistics => { + return { + total_time: dashboardStatistics.total_time, + protocols: dashboardStatistics.protocols, + information_rate: dashboardStatistics.information_rate, + }; + }), + tap(() => this.isLoading.set(false)) + ); + } +} diff --git a/src/app/interceptor/mocks/dashboard-statistics.json b/src/app/interceptor/mocks/dashboard-statistics.json index 073d63b..65334a5 100644 --- a/src/app/interceptor/mocks/dashboard-statistics.json +++ b/src/app/interceptor/mocks/dashboard-statistics.json @@ -1,7 +1,7 @@ { "data": { "id": "statistics", - "total_time": "01:05:30", + "total-time": "1740770761000", "protocols": [ { "name": "ETH", diff --git a/src/app/models/dashboard-statistics.ts b/src/app/models/dashboard-statistics.ts new file mode 100644 index 0000000..8d07d36 --- /dev/null +++ b/src/app/models/dashboard-statistics.ts @@ -0,0 +1,53 @@ +export type DashboardStatistics = DashboardStatisticsDto; + +export interface ProtocolStatistics { + name: string; + total_packets: number; + total_bytes: number; +} + +export interface InformationRate { + min: number; + max: number; + current: number; +} + +export interface DashboardStatisticsDto { + total_time: string; + protocols: ProtocolStatistics[]; + information_rate: InformationRate; +} + +export function dtoToDashboardStatistics(dto: any): DashboardStatistics { + return { + total_time: dto['total-time'], + protocols: dto.protocols.map((protocol: any) => ({ + name: protocol.name, + total_packets: protocol['total-packets'], + total_bytes: protocol['total-bytes'], + })), + information_rate: { + min: dto['information-rate'].min, + max: dto['information-rate'].max, + current: dto['information-rate'].current, + }, + }; +} + +export function dashboardStatisticsToDto( + dashboardStatistics: DashboardStatistics +): any { + return { + 'total-time': dashboardStatistics.total_time, + protocols: dashboardStatistics.protocols.map(protocol => ({ + name: protocol.name, + 'total-packets': protocol.total_packets, + 'total-bytes': protocol.total_bytes, + })), + 'information-rate': { + min: dashboardStatistics.information_rate.min, + max: dashboardStatistics.information_rate.max, + current: dashboardStatistics.information_rate.current, + }, + }; +} -- GitLab From 490122ecc212a7c19220821b54fff1316f395092 Mon Sep 17 00:00:00 2001 From: Artem Dychenko <s192441@student.pg.edu.pl> Date: Sat, 1 Mar 2025 18:08:06 +0100 Subject: [PATCH 3/7] mod: change dashboard statistics html --- .../dashboard-statistics.component.html | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.html b/src/app/components/dashboard-statistics/dashboard-statistics.component.html index 7da5152..c34d1b5 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.html +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.html @@ -1,6 +1,9 @@ <div class="statistics"> @if (isLoading()) { <div class="statistics__loader"> + <div time class="statistics__total-time"> + <ngx-skeleton-loader count="7" /> + </div> <div eth class="statistics__eth"> <div class="statistics__eth-content"> <ngx-skeleton-loader count="7" /> @@ -11,19 +14,18 @@ <ngx-skeleton-loader count="7" /> </div> </div> - <div ir> - <ngx-skeleton-loader count="10" /> + <div ir class="statistics__ir"> + <div class="statistics__ir-content"> + <ngx-skeleton-loader count="7" /> + </div> </div> </div> } @else { <div class="statistics__content"> - <p>Total time {{ dashboardStatistic()?.total_time }} </p> -<!-- @for (mac of configuration()?.mac_source; track $index) {--> -<!-- <app-pill>{{ mac }}</app-pill>--> -<!-- } @empty {--> -<!-- <app-pill>All addresses</app-pill>--> -<!-- }--> + <div time class="statistics__total-time"> + <p>Total time {{ dashboardStatistic()?.total_time }} </p> + </div> <div eth class="statistics__eth"> <div class="statistics__eth-content"> <!-- <p>ETH Price: {{ statistics.ethPrice }} USD</p>--> @@ -34,8 +36,10 @@ <!-- <p>Number of active protocols: {{ statistics.protocols }}</p>--> </div> </div> - <div ir> + <div ir class="statistics__ir"> + <div class="statistics__ir-content"> <!-- <p>Other data: {{ statistics.otherData }}</p>--> + </div> </div> </div> } -- GitLab From 7aeaf5510333f3b6640ef0e9b09324209e29dfc3 Mon Sep 17 00:00:00 2001 From: Artem Dychenko <s192441@student.pg.edu.pl> Date: Mon, 3 Mar 2025 22:50:15 +0100 Subject: [PATCH 4/7] feat: add all API info on html, add time pipe, fix total_time type in mock --- src/app/api/dashboard.api.ts | 2 +- .../dashboard-statistics.component.html | 37 +++++++- .../dashboard-statistics.component.scss | 34 +++++++ .../dashboard-statistics.component.ts | 93 +++++++++++++++---- .../mocks/dashboard-statistics.json | 2 +- src/app/models/dashboard-statistics.ts | 2 +- src/app/pipes/time.pipe.ts | 15 +++ 7 files changed, 161 insertions(+), 24 deletions(-) create mode 100644 src/app/pipes/time.pipe.ts diff --git a/src/app/api/dashboard.api.ts b/src/app/api/dashboard.api.ts index 498536d..c0378e5 100644 --- a/src/app/api/dashboard.api.ts +++ b/src/app/api/dashboard.api.ts @@ -17,7 +17,7 @@ export const DASHBOARD_API_URL = '/api/v1/dashboard/statistics'; export class DashboardApi { private readonly httpClient = inject(HttpClient); - fetch(): Observable<DashboardStatistics> { + fetchStatistics(): Observable<DashboardStatistics> { return this.httpClient .get<ApiResponse<DashboardStatisticsDto>>(DASHBOARD_API_URL) .pipe(map(dto => dtoToDashboardStatistics(dto.data))); diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.html b/src/app/components/dashboard-statistics/dashboard-statistics.component.html index c34d1b5..5a6fc81 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.html +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.html @@ -1,5 +1,6 @@ <div class="statistics"> - @if (isLoading()) { + @let statistics = dashboardStatistics | async; + @if (!statistics) { <div class="statistics__loader"> <div time class="statistics__total-time"> <ngx-skeleton-loader count="7" /> @@ -24,21 +25,47 @@ @else { <div class="statistics__content"> <div time class="statistics__total-time"> - <p>Total time {{ dashboardStatistic()?.total_time }} </p> + <p>Total time: {{ statistics.total_time | time }} </p> </div> <div eth class="statistics__eth"> <div class="statistics__eth-content"> -<!-- <p>ETH Price: {{ statistics.ethPrice }} USD</p>--> + <ng-container *ngIf="getProtocolETH() | async as protocol"> + {{ protocol.name }} + {{ protocol.total_packets }} + <ng-container *ngIf="getPacketsPerSecond(protocol.name) | async as packetsPerSecond"> + {{ packetsPerSecond }} + </ng-container> + {{protocol.total_bytes}} + <ng-container *ngIf="getBytesPerSecond(protocol.name) | async as bytesPerSecond"> + {{ bytesPerSecond }} + </ng-container> + </ng-container> </div> </div> <div protocols class="statistics__prot"> <div class="statistics__prot-content"> -<!-- <p>Number of active protocols: {{ statistics.protocols }}</p>--> + <div *ngFor="let protocol of getProtocolOthers() | async"> + {{ protocol.name }} + {{ protocol.total_packets }} + <ng-container *ngIf="getPacketsPerSecond(protocol.name) | async as packetsPerSecond"> + {{ packetsPerSecond }} + </ng-container> + {{protocol.total_bytes}} + <ng-container *ngIf="getBytesPerSecond(protocol.name) | async as bytesPerSecond"> + {{ bytesPerSecond }} + </ng-container> + </div> </div> </div> <div ir class="statistics__ir"> <div class="statistics__ir-content"> -<!-- <p>Other data: {{ statistics.otherData }}</p>--> + <ng-container *ngIf="getIR() | async as information_rate"> + Information Rate + {{information_rate.min}} + {{information_rate.max}} + {{information_rate.current}} + MB/s + </ng-container> </div> </div> </div> diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.scss b/src/app/components/dashboard-statistics/dashboard-statistics.component.scss index e69de29..a52ca0a 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.scss +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.scss @@ -0,0 +1,34 @@ +@use '../../../vars'; +@use 'sass:map'; + +:host { + height: 100%; + width: 100%; + + .statistics { + &content { + display: flex; + gap: map.get(vars.$spacing, 'xs'); + } + + &__eth { + display: flex; + justify-content: space-between; + + &-title { + color: map.get(vars.$grey, 100); + font-size: map.get(vars.$text, 'lg'); + } + + &-content { + width: calc(100% - 200px); + } + } + } + + // Configuration select + ::ng-deep .mdc-text-field--filled:not(.mdc-text-field--disabled) { + background-color: map.get(vars.$grey, 10); + border-radius: map.get(vars.$radius, 'xs'); + } +} diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.ts b/src/app/components/dashboard-statistics/dashboard-statistics.component.ts index 56a83da..34ccbc3 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.ts +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.ts @@ -6,15 +6,16 @@ import { signal, } from '@angular/core'; import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader'; -import { NgTemplateOutlet } from '@angular/common'; +import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; import { DashboardApi } from '../../api/dashboard.api'; -import { map, Observable, tap } from 'rxjs'; +import { interval, map, Observable, shareReplay, switchMap, tap } from 'rxjs'; import { DashboardStatistics } from '../../models/dashboard-statistics'; import { PillComponent } from '../pill/pill.component'; +import { TimePipe } from '../../pipes/time.pipe'; @Component({ selector: 'app-dashboard-statistics', - imports: [NgxSkeletonLoaderComponent, NgTemplateOutlet, PillComponent], + imports: [NgxSkeletonLoaderComponent, AsyncPipe, TimePipe, NgIf, NgForOf], templateUrl: './dashboard-statistics.component.html', styleUrl: './dashboard-statistics.component.scss', }) @@ -22,30 +23,90 @@ export class DashboardStatisticsComponent { dashboardApi = inject(DashboardApi); isLoading = signal<boolean>(false); - dashboardStatistic = signal<DashboardStatistics | undefined>(undefined); + + dashboardStatistics = interval(1000).pipe( + switchMap(() => this.dashboardApi.fetchStatistics()), + map(dashboardStatistics => ({ + ...dashboardStatistics, + total_time: new Date(dashboardStatistics.total_time * 1000), + })), + shareReplay(1) + ); constructor() { effect(() => { this.isLoading.set(true); - this.dashboardApi.fetch().subscribe(data => { + this.dashboardApi.fetchStatistics().subscribe(data => { this.isLoading.set(false); - this.dashboardStatistic.set(data); + this.dashboardApi.fetchStatistics().subscribe(() => { + this.isLoading.set(false); + }); }); }); } - private fetchConfigurationOptions(): Observable<DashboardStatistics> { - this.isLoading.set(true); - return this.dashboardApi.fetch().pipe( + getPacketsPerSecond(protocolName: string): Observable<number> { + return this.dashboardStatistics.pipe( + map(dashboardStatistics => { + const protocol = dashboardStatistics.protocols.find( + protocol => protocol.name === protocolName + ); + + if (!protocol) { + return 0; + } + + const totalPackets = protocol.total_packets; + const totalSeconds = dashboardStatistics.total_time.getTime() / 1000; + return totalPackets / totalSeconds; + }) + ); + } + + getBytesPerSecond(protocolName: string): Observable<number> { + return this.dashboardStatistics.pipe( + map(dashboardStatistics => { + const protocol = dashboardStatistics.protocols.find( + protocol => protocol.name === protocolName + ); + + if (!protocol) { + return 0; + } + + const totalBytes = protocol.total_bytes; + const totalSeconds = dashboardStatistics.total_time.getTime() / 1000; + return totalBytes / totalSeconds; + }) + ); + } + + getProtocolETH() { + return this.dashboardStatistics.pipe( + map(dashboardStatistics => { + return dashboardStatistics.protocols.find( + protocol => protocol.name === 'ETH' + ); + }) + ); + } + + getProtocolOthers() { + return this.dashboardStatistics.pipe( + map(dashboardStatistics => { + return dashboardStatistics.protocols.filter( + protocol => protocol.name !== 'ETH' + ); + }) + ); + } + + getIR() { + return this.dashboardStatistics.pipe( map(dashboardStatistics => { - return { - total_time: dashboardStatistics.total_time, - protocols: dashboardStatistics.protocols, - information_rate: dashboardStatistics.information_rate, - }; - }), - tap(() => this.isLoading.set(false)) + return dashboardStatistics.information_rate; + }) ); } } diff --git a/src/app/interceptor/mocks/dashboard-statistics.json b/src/app/interceptor/mocks/dashboard-statistics.json index 65334a5..f12cab4 100644 --- a/src/app/interceptor/mocks/dashboard-statistics.json +++ b/src/app/interceptor/mocks/dashboard-statistics.json @@ -1,7 +1,7 @@ { "data": { "id": "statistics", - "total-time": "1740770761000", + "total-time": 160401, "protocols": [ { "name": "ETH", diff --git a/src/app/models/dashboard-statistics.ts b/src/app/models/dashboard-statistics.ts index 8d07d36..2214b87 100644 --- a/src/app/models/dashboard-statistics.ts +++ b/src/app/models/dashboard-statistics.ts @@ -13,7 +13,7 @@ export interface InformationRate { } export interface DashboardStatisticsDto { - total_time: string; + total_time: number; protocols: ProtocolStatistics[]; information_rate: InformationRate; } diff --git a/src/app/pipes/time.pipe.ts b/src/app/pipes/time.pipe.ts new file mode 100644 index 0000000..3213400 --- /dev/null +++ b/src/app/pipes/time.pipe.ts @@ -0,0 +1,15 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'time', +}) +export class TimePipe implements PipeTransform { + transform(date: Date): string { + let totalSeconds = Math.floor(date.getTime() / 1000); + const hours = Math.floor(totalSeconds / 3600); + totalSeconds %= 3600; + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + } +} -- GitLab From 2879ee49a27378c263c426fceb51b5dde719aaa2 Mon Sep 17 00:00:00 2001 From: Artem Dychenko <s192441@student.pg.edu.pl> Date: Tue, 4 Mar 2025 14:37:57 +0100 Subject: [PATCH 5/7] finish emplement html and add styling --- .../dashboard-statistics.component.html | 124 ++++++++++-------- .../dashboard-statistics.component.scss | 96 +++++++++++--- .../dashboard-statistics.component.ts | 8 ++ .../mocks/dashboard-statistics.json | 6 +- 4 files changed, 157 insertions(+), 77 deletions(-) diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.html b/src/app/components/dashboard-statistics/dashboard-statistics.component.html index 5a6fc81..75ab2a0 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.html +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.html @@ -2,72 +2,84 @@ @let statistics = dashboardStatistics | async; @if (!statistics) { <div class="statistics__loader"> - <div time class="statistics__total-time"> - <ngx-skeleton-loader count="7" /> + <div class="statistics__loader-time"> + <ngx-skeleton-loader count="7" /> </div> - <div eth class="statistics__eth"> - <div class="statistics__eth-content"> - <ngx-skeleton-loader count="7" /> - </div> + <div class="statistics__loader-eth"> + <ngx-skeleton-loader count="7" /> </div> - <div protocols class="statistics__prot"> - <div class="statistics__prot-content"> - <ngx-skeleton-loader count="7" /> - </div> + <div class="statistics__loader-protocol"> + <ngx-skeleton-loader count="7" /> </div> - <div ir class="statistics__ir"> - <div class="statistics__ir-content"> - <ngx-skeleton-loader count="7" /> - </div> + <div class="statistics__loader-ir"> + <ngx-skeleton-loader count="7" /> </div> </div> } @else { - <div class="statistics__content"> - <div time class="statistics__total-time"> + <div class="statistics__table"> + <div class="statistics__table__time"> <p>Total time: {{ statistics.total_time | time }} </p> </div> - <div eth class="statistics__eth"> - <div class="statistics__eth-content"> - <ng-container *ngIf="getProtocolETH() | async as protocol"> - {{ protocol.name }} - {{ protocol.total_packets }} - <ng-container *ngIf="getPacketsPerSecond(protocol.name) | async as packetsPerSecond"> - {{ packetsPerSecond }} - </ng-container> - {{protocol.total_bytes}} - <ng-container *ngIf="getBytesPerSecond(protocol.name) | async as bytesPerSecond"> - {{ bytesPerSecond }} - </ng-container> + <table class="statistics__table__header"> + <thead class="statistics__table__header-title"> + <tr> + <th></th> + <th>Total packets</th> + <th>Packets per second</th> + <th>Total bytes</th> + <th>Bytes per second</th> + </tr> + </thead> + <tbody> + <tr *ngIf="getProtocolETH() | async as protocol" class="statistics__table__header-eth"> + <td>{{ protocol.name }}</td> + <td>{{ protocol.total_packets }}</td> + <td> + {{ (getPacketsPerSecond(protocol.name) | async) === 0 ? 0 : (getPacketsPerSecond(protocol.name) | async) }} + </td> + <td>{{ protocol.total_bytes }}</td> + <td> + {{ (getBytesPerSecond(protocol.name) | async) === 0 ? 0 : (getBytesPerSecond(protocol.name) | async) }} + </td> + </tr> + </tbody> + </table> + <table class="statistics__table__protocols"> + <tbody> + <ng-container *ngFor="let protocol of getProtocolOthers() | async"> + <tr> + <td>{{ protocol.name }}</td> + <td>{{ protocol.total_packets }}</td> + <td> + {{ (getPacketsPerSecond(protocol.name) | async) === 0 ? 0 : (getPacketsPerSecond(protocol.name) | async) }} + </td> + <td>{{ protocol.total_bytes }}</td> + <td> + {{ (getBytesPerSecond(protocol.name) | async) === 0 ? 0 : (getBytesPerSecond(protocol.name) | async) }} + </td> + </tr> </ng-container> - </div> - </div> - <div protocols class="statistics__prot"> - <div class="statistics__prot-content"> - <div *ngFor="let protocol of getProtocolOthers() | async"> - {{ protocol.name }} - {{ protocol.total_packets }} - <ng-container *ngIf="getPacketsPerSecond(protocol.name) | async as packetsPerSecond"> - {{ packetsPerSecond }} - </ng-container> - {{protocol.total_bytes}} - <ng-container *ngIf="getBytesPerSecond(protocol.name) | async as bytesPerSecond"> - {{ bytesPerSecond }} - </ng-container> - </div> - </div> - </div> - <div ir class="statistics__ir"> - <div class="statistics__ir-content"> - <ng-container *ngIf="getIR() | async as information_rate"> - Information Rate - {{information_rate.min}} - {{information_rate.max}} - {{information_rate.current}} - MB/s - </ng-container> - </div> - </div> + </tbody> + </table> + <table class="statistics__table__ir"> + <thead class="statistics__table__ir-header"> + <tr> + <th></th> + <th>Min</th> + <th>Max</th> + <th>Current</th> + </tr> + </thead> + <tbody> + <tr *ngIf="getIR() | async as information_rate" class="statistics__table_ir-content"> + <td>Information Rate</td> + <td>{{ information_rate.min }}</td> + <td>{{ information_rate.max }}</td> + <td>{{ information_rate.current }}</td> + </tr> + </tbody> + </table> </div> } </div> diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.scss b/src/app/components/dashboard-statistics/dashboard-statistics.component.scss index a52ca0a..b4f8a6c 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.scss +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.scss @@ -5,30 +5,90 @@ height: 100%; width: 100%; - .statistics { - &content { - display: flex; - gap: map.get(vars.$spacing, 'xs'); + ngx-skeleton-loader { + display: flex; + flex-direction: row; + gap: map.get(vars.$spacing, 'md'); + + ::ng-deep * { + height: 50px; } + } - &__eth { - display: flex; - justify-content: space-between; - &-title { - color: map.get(vars.$grey, 100); - font-size: map.get(vars.$text, 'lg'); + .statistics { + display: flex; + flex-direction: column; + justify-content: space-between; + font-weight: bold; + + &__table { + &__time { + font-size: map.get(vars.$text, 'md'); } - &-content { - width: calc(100% - 200px); + &__header { + width: 100%; + border-radius: map.get(vars.$radius, 'xs'); + background-color: map.get(vars.$grey, 0); + padding: map.get(vars.$spacing, 'none') map.get(vars.$spacing, 'xl'); + + &-title { + border-radius: map.get(vars.$radius, 'xs'); + background-color: map.get(vars.$grey, 0); + padding: map.get(vars.$spacing, 'none') map.get(vars.$spacing, 'xl'); + } + + &-eth td { + border-top: 1px solid map.get(vars.$grey, 30); + } + + th, td { + width: 20%; + padding: map.get(vars.$spacing, 'sm'); + text-align: left; + } + } - } - } - // Configuration select - ::ng-deep .mdc-text-field--filled:not(.mdc-text-field--disabled) { - background-color: map.get(vars.$grey, 10); - border-radius: map.get(vars.$radius, 'xs'); + &__protocols { + border-radius: map.get(vars.$radius, 'xs'); + background-color: map.get(vars.$grey, 0); + padding: map.get(vars.$spacing, 'none') map.get(vars.$spacing, 'xl'); + + margin-top: map.get(vars.$text, 'lg'); + width: 100%; + + + th, td { + width: 20%; + padding: map.get(vars.$spacing, 'sm'); + text-align: left; + } + + tr:not(:last-child) td { + border-bottom: 1px solid map.get(vars.$grey, 30); + } + } + + &__ir { + border-radius: map.get(vars.$radius, 'xs'); + background-color: map.get(vars.$grey, 0); + padding: map.get(vars.$spacing, 'none') map.get(vars.$spacing, 'xl'); + + margin-top: map.get(vars.$spacing, 'lg'); + width: 100%; + + tbody td { + border-top: 1px solid map.get(vars.$grey, 30); + } + + th, td { + width: 25%; + padding: map.get(vars.$spacing, 'sm'); + text-align: left; + } + } + } } } diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.ts b/src/app/components/dashboard-statistics/dashboard-statistics.component.ts index 34ccbc3..99bf83c 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.ts +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.ts @@ -57,6 +57,10 @@ export class DashboardStatisticsComponent { return 0; } + if (protocol.total_packets == 0) { + return 0; + } + const totalPackets = protocol.total_packets; const totalSeconds = dashboardStatistics.total_time.getTime() / 1000; return totalPackets / totalSeconds; @@ -75,6 +79,10 @@ export class DashboardStatisticsComponent { return 0; } + if (protocol.total_bytes == 0) { + return 0; + } + const totalBytes = protocol.total_bytes; const totalSeconds = dashboardStatistics.total_time.getTime() / 1000; return totalBytes / totalSeconds; diff --git a/src/app/interceptor/mocks/dashboard-statistics.json b/src/app/interceptor/mocks/dashboard-statistics.json index f12cab4..b05d511 100644 --- a/src/app/interceptor/mocks/dashboard-statistics.json +++ b/src/app/interceptor/mocks/dashboard-statistics.json @@ -25,9 +25,9 @@ } ], "information-rate": { - "min": 1.23, - "max": 123456, - "current": 123.45 + "min": 0, + "max": 0, + "current": 0 } } } -- GitLab From 2255bb8944e3882f81a94dd50039b15517299042 Mon Sep 17 00:00:00 2001 From: Artem Dychenko <s192441@student.pg.edu.pl> Date: Tue, 4 Mar 2025 14:41:16 +0100 Subject: [PATCH 6/7] make some minor changes --- src/app/api/dashboard.api.ts | 1 - .../dashboard-statistics/dashboard-statistics.component.scss | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/app/api/dashboard.api.ts b/src/app/api/dashboard.api.ts index c0378e5..435ea30 100644 --- a/src/app/api/dashboard.api.ts +++ b/src/app/api/dashboard.api.ts @@ -4,7 +4,6 @@ import { map, Observable, tap } from 'rxjs'; import { DashboardStatistics, DashboardStatisticsDto, - dashboardStatisticsToDto, dtoToDashboardStatistics, } from '../models/dashboard-statistics'; import { ApiResponse } from '../models/api-response'; diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.scss b/src/app/components/dashboard-statistics/dashboard-statistics.component.scss index b4f8a6c..d47873a 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.scss +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.scss @@ -15,7 +15,6 @@ } } - .statistics { display: flex; flex-direction: column; @@ -59,7 +58,6 @@ margin-top: map.get(vars.$text, 'lg'); width: 100%; - th, td { width: 20%; padding: map.get(vars.$spacing, 'sm'); -- GitLab From 515e18653f5b6e9a54a5167403ed926e32dc6bdf Mon Sep 17 00:00:00 2001 From: Artem Dychenko <s192441@student.pg.edu.pl> Date: Thu, 6 Mar 2025 20:42:51 +0100 Subject: [PATCH 7/7] add review changes and add decimal pipe --- src/app/api/dashboard.api.ts | 2 +- .../dashboard-statistics.component.html | 46 ++++---- .../dashboard-statistics.component.scss | 7 +- .../dashboard-statistics.component.ts | 102 +++--------------- .../dashboard/dashboard.component.scss | 4 + src/app/interceptor/interceptor.ts | 2 +- src/app/models/dashboard-statistics.ts | 42 ++++---- src/app/pipes/decimal.pipe.ts | 16 +++ 8 files changed, 75 insertions(+), 146 deletions(-) create mode 100644 src/app/pipes/decimal.pipe.ts diff --git a/src/app/api/dashboard.api.ts b/src/app/api/dashboard.api.ts index 435ea30..e81df04 100644 --- a/src/app/api/dashboard.api.ts +++ b/src/app/api/dashboard.api.ts @@ -8,7 +8,7 @@ import { } from '../models/dashboard-statistics'; import { ApiResponse } from '../models/api-response'; -export const DASHBOARD_API_URL = '/api/v1/dashboard/statistics'; +export const DASHBOARD_API_URL = '/api/statistics'; @Injectable({ providedIn: 'root', diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.html b/src/app/components/dashboard-statistics/dashboard-statistics.component.html index 75ab2a0..bd5d0df 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.html +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.html @@ -2,18 +2,9 @@ @let statistics = dashboardStatistics | async; @if (!statistics) { <div class="statistics__loader"> - <div class="statistics__loader-time"> + @for (_ of [].constructor(4); track $index) { <ngx-skeleton-loader count="7" /> - </div> - <div class="statistics__loader-eth"> - <ngx-skeleton-loader count="7" /> - </div> - <div class="statistics__loader-protocol"> - <ngx-skeleton-loader count="7" /> - </div> - <div class="statistics__loader-ir"> - <ngx-skeleton-loader count="7" /> - </div> + } </div> } @else { @@ -32,34 +23,35 @@ </tr> </thead> <tbody> - <tr *ngIf="getProtocolETH() | async as protocol" class="statistics__table__header-eth"> - <td>{{ protocol.name }}</td> - <td>{{ protocol.total_packets }}</td> + @let protocolEth = getETHStatistics(statistics.protocols); + <tr class="statistics__table__header-eth"> + <td>{{ protocolEth.name }}</td> + <td>{{ protocolEth.total_packets | decimal }}</td> <td> - {{ (getPacketsPerSecond(protocol.name) | async) === 0 ? 0 : (getPacketsPerSecond(protocol.name) | async) }} + {{ getPerSecond(protocolEth.total_packets, statistics.total_time) | decimal }} </td> - <td>{{ protocol.total_bytes }}</td> + <td>{{ protocolEth.total_bytes | decimal }}</td> <td> - {{ (getBytesPerSecond(protocol.name) | async) === 0 ? 0 : (getBytesPerSecond(protocol.name) | async) }} + {{ getPerSecond(protocolEth.total_bytes, statistics.total_time) | decimal }} </td> </tr> </tbody> </table> <table class="statistics__table__protocols"> <tbody> - <ng-container *ngFor="let protocol of getProtocolOthers() | async"> + @for (protocol of getProtocols(statistics.protocols); track protocol.name) { <tr> <td>{{ protocol.name }}</td> - <td>{{ protocol.total_packets }}</td> + <td>{{ protocol.total_packets | decimal }}</td> <td> - {{ (getPacketsPerSecond(protocol.name) | async) === 0 ? 0 : (getPacketsPerSecond(protocol.name) | async) }} + {{ getPerSecond(protocol.total_packets, statistics.total_time) | decimal }} </td> - <td>{{ protocol.total_bytes }}</td> + <td>{{ protocol.total_bytes | decimal }}</td> <td> - {{ (getBytesPerSecond(protocol.name) | async) === 0 ? 0 : (getBytesPerSecond(protocol.name) | async) }} + {{ getPerSecond(protocol.total_bytes, statistics.total_time) | decimal }} </td> </tr> - </ng-container> + } </tbody> </table> <table class="statistics__table__ir"> @@ -72,11 +64,11 @@ </tr> </thead> <tbody> - <tr *ngIf="getIR() | async as information_rate" class="statistics__table_ir-content"> + <tr class="statistics__table_ir-content"> <td>Information Rate</td> - <td>{{ information_rate.min }}</td> - <td>{{ information_rate.max }}</td> - <td>{{ information_rate.current }}</td> + <td>{{ statistics.information_rate.min | decimal }}</td> + <td>{{ statistics.information_rate.max | decimal }}</td> + <td>{{ statistics.information_rate.current | decimal }}</td> </tr> </tbody> </table> diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.scss b/src/app/components/dashboard-statistics/dashboard-statistics.component.scss index d47873a..e11b5db 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.scss +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.scss @@ -2,14 +2,10 @@ @use 'sass:map'; :host { - height: 100%; - width: 100%; - ngx-skeleton-loader { display: flex; flex-direction: row; gap: map.get(vars.$spacing, 'md'); - ::ng-deep * { height: 50px; } @@ -19,7 +15,8 @@ display: flex; flex-direction: column; justify-content: space-between; - font-weight: bold; + font-weight: 500; + font-size: map.get(vars.$text, 'lg'); &__table { &__time { diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.ts b/src/app/components/dashboard-statistics/dashboard-statistics.component.ts index 99bf83c..d9040ee 100644 --- a/src/app/components/dashboard-statistics/dashboard-statistics.component.ts +++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.ts @@ -6,115 +6,41 @@ import { signal, } from '@angular/core'; import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader'; -import { AsyncPipe, NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; +import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; import { DashboardApi } from '../../api/dashboard.api'; import { interval, map, Observable, shareReplay, switchMap, tap } from 'rxjs'; -import { DashboardStatistics } from '../../models/dashboard-statistics'; +import { + DashboardStatistics, + ProtocolStatistics, +} from '../../models/dashboard-statistics'; import { PillComponent } from '../pill/pill.component'; import { TimePipe } from '../../pipes/time.pipe'; +import { DecimalPipe } from '../../pipes/decimal.pipe'; @Component({ selector: 'app-dashboard-statistics', - imports: [NgxSkeletonLoaderComponent, AsyncPipe, TimePipe, NgIf, NgForOf], + imports: [NgxSkeletonLoaderComponent, AsyncPipe, TimePipe, DecimalPipe], templateUrl: './dashboard-statistics.component.html', styleUrl: './dashboard-statistics.component.scss', }) export class DashboardStatisticsComponent { dashboardApi = inject(DashboardApi); - isLoading = signal<boolean>(false); - dashboardStatistics = interval(1000).pipe( switchMap(() => this.dashboardApi.fetchStatistics()), - map(dashboardStatistics => ({ - ...dashboardStatistics, - total_time: new Date(dashboardStatistics.total_time * 1000), - })), shareReplay(1) ); - constructor() { - effect(() => { - this.isLoading.set(true); - - this.dashboardApi.fetchStatistics().subscribe(data => { - this.isLoading.set(false); - this.dashboardApi.fetchStatistics().subscribe(() => { - this.isLoading.set(false); - }); - }); - }); - } - - getPacketsPerSecond(protocolName: string): Observable<number> { - return this.dashboardStatistics.pipe( - map(dashboardStatistics => { - const protocol = dashboardStatistics.protocols.find( - protocol => protocol.name === protocolName - ); - - if (!protocol) { - return 0; - } - - if (protocol.total_packets == 0) { - return 0; - } - - const totalPackets = protocol.total_packets; - const totalSeconds = dashboardStatistics.total_time.getTime() / 1000; - return totalPackets / totalSeconds; - }) - ); - } - - getBytesPerSecond(protocolName: string): Observable<number> { - return this.dashboardStatistics.pipe( - map(dashboardStatistics => { - const protocol = dashboardStatistics.protocols.find( - protocol => protocol.name === protocolName - ); - - if (!protocol) { - return 0; - } - - if (protocol.total_bytes == 0) { - return 0; - } - - const totalBytes = protocol.total_bytes; - const totalSeconds = dashboardStatistics.total_time.getTime() / 1000; - return totalBytes / totalSeconds; - }) - ); - } - - getProtocolETH() { - return this.dashboardStatistics.pipe( - map(dashboardStatistics => { - return dashboardStatistics.protocols.find( - protocol => protocol.name === 'ETH' - ); - }) - ); + getPerSecond(value: number, time: Date): number { + const totalSeconds = time.getTime() / 1000; + return value / totalSeconds; } - getProtocolOthers() { - return this.dashboardStatistics.pipe( - map(dashboardStatistics => { - return dashboardStatistics.protocols.filter( - protocol => protocol.name !== 'ETH' - ); - }) - ); + getETHStatistics(protocols: ProtocolStatistics[]): ProtocolStatistics { + return protocols.find(protocol => protocol.name === 'ETH')!; } - getIR() { - return this.dashboardStatistics.pipe( - map(dashboardStatistics => { - return dashboardStatistics.information_rate; - }) - ); + getProtocols(protocols: ProtocolStatistics[]): ProtocolStatistics[] { + return protocols.filter(protocol => protocol.name !== 'ETH'); } } diff --git a/src/app/components/dashboard/dashboard.component.scss b/src/app/components/dashboard/dashboard.component.scss index 1938814..d11c2b9 100644 --- a/src/app/components/dashboard/dashboard.component.scss +++ b/src/app/components/dashboard/dashboard.component.scss @@ -29,6 +29,10 @@ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); border-bottom: 1px solid map.get(vars.$grey, 30); + &-title { + font-size: map.get(vars.$text, 'lg'); + } + &:last-child { border-bottom: none; } diff --git a/src/app/interceptor/interceptor.ts b/src/app/interceptor/interceptor.ts index 1b55567..8e088ac 100644 --- a/src/app/interceptor/interceptor.ts +++ b/src/app/interceptor/interceptor.ts @@ -17,7 +17,7 @@ export const urls = [ json: configurationList, }, { - url: '/api/v1/dashboard/statistics', + url: '/api/statistics', json: dashboardStats, }, ]; diff --git a/src/app/models/dashboard-statistics.ts b/src/app/models/dashboard-statistics.ts index 2214b87..3a8befc 100644 --- a/src/app/models/dashboard-statistics.ts +++ b/src/app/models/dashboard-statistics.ts @@ -1,11 +1,15 @@ -export type DashboardStatistics = DashboardStatisticsDto; - export interface ProtocolStatistics { name: string; total_packets: number; total_bytes: number; } +export interface ProtocolStatisticsDto { + name: string; + 'total-packets': number; + 'total-bytes': number; +} + export interface InformationRate { min: number; max: number; @@ -13,15 +17,23 @@ export interface InformationRate { } export interface DashboardStatisticsDto { - total_time: number; + 'total-time': number; + protocols: ProtocolStatisticsDto[]; + 'information-rate': InformationRate; +} + +export interface DashboardStatistics { + total_time: Date; protocols: ProtocolStatistics[]; information_rate: InformationRate; } -export function dtoToDashboardStatistics(dto: any): DashboardStatistics { +export function dtoToDashboardStatistics( + dto: DashboardStatisticsDto +): DashboardStatistics { return { - total_time: dto['total-time'], - protocols: dto.protocols.map((protocol: any) => ({ + total_time: new Date(dto['total-time'] * 1000), + protocols: dto.protocols.map((protocol: ProtocolStatisticsDto) => ({ name: protocol.name, total_packets: protocol['total-packets'], total_bytes: protocol['total-bytes'], @@ -33,21 +45,3 @@ export function dtoToDashboardStatistics(dto: any): DashboardStatistics { }, }; } - -export function dashboardStatisticsToDto( - dashboardStatistics: DashboardStatistics -): any { - return { - 'total-time': dashboardStatistics.total_time, - protocols: dashboardStatistics.protocols.map(protocol => ({ - name: protocol.name, - 'total-packets': protocol.total_packets, - 'total-bytes': protocol.total_bytes, - })), - 'information-rate': { - min: dashboardStatistics.information_rate.min, - max: dashboardStatistics.information_rate.max, - current: dashboardStatistics.information_rate.current, - }, - }; -} diff --git a/src/app/pipes/decimal.pipe.ts b/src/app/pipes/decimal.pipe.ts new file mode 100644 index 0000000..1d9a6e1 --- /dev/null +++ b/src/app/pipes/decimal.pipe.ts @@ -0,0 +1,16 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'decimal', +}) +export class DecimalPipe implements PipeTransform { + transform(value: number | string, decimalPlaces: number = 2): string { + if (value == null || isNaN(Number(value))) { + return ''; + } + + let num = Number(value).toFixed(decimalPlaces); + + return num.replace(/\B(?=(\d{3})+(?!\d))/g, ' '); + } +} -- GitLab