diff --git a/src/app/api/dashboard.api.ts b/src/app/api/dashboard.api.ts index a6090f096ae1c510c2a8cbe46d26573a7edf1a68..3168c8b979db259d18df8d314db390a7804a6023 100644 --- a/src/app/api/dashboard.api.ts +++ b/src/app/api/dashboard.api.ts @@ -67,10 +67,13 @@ export class DashboardApi { .pipe(map(response => response.data)); } - fetchProtocolHistory(interval: number): Observable<ChartProtocol[]> { + fetchProtocolHistory( + protocolName: string, + interval: number + ): Observable<ChartProtocol[]> { return this.httpClient .get<ApiResponse<ChartProtocol[]>>( - `${DASHBOARD_API_URL}/protocols/historical`, + `${DASHBOARD_API_URL}/${protocolName}/historical`, { params: { interval: interval.toString(), @@ -80,9 +83,11 @@ export class DashboardApi { .pipe(map(response => response.data)); } - fetchProtocol(): Observable<ChartProtocol> { + fetchProtocol(protocolName: string): Observable<ChartProtocol> { return this.httpClient - .get<ApiResponse<ChartProtocol>>(`${DASHBOARD_API_URL}/protocols/current`) + .get< + ApiResponse<ChartProtocol> + >(`${DASHBOARD_API_URL}/${protocolName}/current`) .pipe(map(response => response.data)); } } diff --git a/src/app/components/dashboard-chart-protocol/dashboard-chart-protocol.component.html b/src/app/components/dashboard-chart-protocol/dashboard-chart-protocol.component.html index d0159e3ed05bea3d3b25a39cd4510b5c559dab1a..ce1f83fe6455c4803838efe6afa49606b4d2fa1a 100644 --- a/src/app/components/dashboard-chart-protocol/dashboard-chart-protocol.component.html +++ b/src/app/components/dashboard-chart-protocol/dashboard-chart-protocol.component.html @@ -1 +1,20 @@ -<p-chart type="line" [data]="data" /> +<div class="dashboard-charts__interval"> + <mat-form-field> + <mat-label>Interval</mat-label> + <input + matInput + type="number" + [ngModel]="recordsInterval()" + (ngModelChange)="recordsInterval.set($event)" /> + </mat-form-field> + <div class="dashboard-charts__interval-slider"> + <mat-slider [max]="100" [min]="1" [discrete]="true"> + <input + matSliderThumb + [ngModel]="recordsInterval()" + (ngModelChange)="recordsInterval.set($event)" /> + </mat-slider> + </div> +</div> + +<p-chart type="line" [data]="data()" [options]="options" /> diff --git a/src/app/components/dashboard-chart-protocol/dashboard-chart-protocol.component.ts b/src/app/components/dashboard-chart-protocol/dashboard-chart-protocol.component.ts index e9586f87ef26cd65e640671baf43be3d7fc74da5..39c14d3f6e42caa2ef402b63d8bc6487e2b647d0 100644 --- a/src/app/components/dashboard-chart-protocol/dashboard-chart-protocol.component.ts +++ b/src/app/components/dashboard-chart-protocol/dashboard-chart-protocol.component.ts @@ -1,47 +1,127 @@ -import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + computed, + effect, + inject, + input, + signal, +} from '@angular/core'; import { UIChart } from 'primeng/chart'; +import { MatFormField, MatLabel } from '@angular/material/form-field'; +import { MatInput } from '@angular/material/input'; +import { MatSlider, MatSliderThumb } from '@angular/material/slider'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { DashboardApi } from '../../api/dashboard.api'; +import { ChartInformationRate } from '../../models/chart-information-rate'; +import { interval, shareReplay, Subject, switchMap, takeUntil } from 'rxjs'; +import { ChartProtocol } from '../../models/chart-protocol'; @Component({ selector: 'app-dashboard-chart-protocol', - imports: [UIChart], + imports: [ + UIChart, + MatFormField, + MatInput, + MatLabel, + MatSlider, + MatSliderThumb, + ReactiveFormsModule, + FormsModule, + ], templateUrl: './dashboard-chart-protocol.component.html', styleUrl: './dashboard-chart-protocol.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class DashboardChartProtocolComponent { - protocol = input.required<string>(); - data: any; - - ngOnInit() { - const documentStyle = getComputedStyle(document.documentElement); - - this.data = { - labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + readonly #dashboardApi = inject(DashboardApi); + protocolName = input.required<string>(); + recordsLimit = input<number>(20); + recordsInterval = signal<number>(1); + protocolHistory = signal<ChartProtocol[]>([]); + data = computed(() => { + const protocols = this.protocolHistory(); + if (!protocols) return; + console.log(protocols); + return { + labels: this.getFrameLabels().map(date => date.toLocaleTimeString()), datasets: [ { - label: 'First Dataset', - data: [65, 59, 80, 81, 56, 55, 40], - fill: false, + label: 'Bytes', + data: protocols.map(protocol => protocol.bytes), + yAxisId: 'y', tension: 0.4, - borderColor: documentStyle.getPropertyValue('--p-cyan-500'), + borderColor: '#4464e3', }, + // @todo: fix Y1 axis { - label: 'Second Dataset', - data: [28, 48, 40, 19, 86, 27, 90], - fill: false, - borderDash: [5, 5], + label: 'Packets', + data: protocols.map(protocol => protocol.packets), + yAxisId: 'y1', tension: 0.4, - borderColor: documentStyle.getPropertyValue('--p-orange-500'), - }, - { - label: 'Third Dataset', - data: [12, 51, 62, 33, 21, 62, 45], - fill: true, - borderColor: documentStyle.getPropertyValue('--p-gray-500'), - tension: 0.4, - backgroundColor: 'rgba(107, 114, 128, 0.2)', + borderColor: '#61aa48', }, ], }; + }); + + options = { + animation: { + duration: 0, + }, + stacked: false, + scales: { + y: { + type: 'linear', + display: true, + position: 'left', + }, + y1: { + type: 'linear', + display: true, + position: 'right', + grid: { + drawOnChartArea: false, + }, + }, + }, + }; + + private stopFetching = new Subject(); + + constructor() { + effect(() => { + this.stopFetching.next(undefined); + + this.#dashboardApi + .fetchProtocolHistory(this.protocolName(), this.recordsInterval()) + .subscribe(response => this.protocolHistory.set(response)); + + interval(this.recordsInterval() * 1000) + .pipe( + takeUntil(this.stopFetching), + switchMap(() => + this.#dashboardApi.fetchProtocol(this.protocolName()) + ), + shareReplay(1) + ) + .subscribe(ir => { + this.protocolHistory.update(protocols => { + if (protocols.length < this.recordsLimit()) + return [...protocols, ir]; + return [...protocols.splice(1), ir]; + }); + }); + }); + } + + private getFrameLabels(): Date[] { + const labelsNumber = this.protocolHistory().length; + const now = new Date().getTime(); + return Array.from( + { length: labelsNumber }, + (_, index) => + new Date(now - (labelsNumber - index) * 1000 * this.recordsInterval()) + ); } } diff --git a/src/app/components/dashboard-charts/dashboard-charts.component.html b/src/app/components/dashboard-charts/dashboard-charts.component.html index 3ce65654f20ae36f6ed4c7f21c573af3e61e6583..9434f6470bf1e5e63db55a41bb2d363dbf717d82 100644 --- a/src/app/components/dashboard-charts/dashboard-charts.component.html +++ b/src/app/components/dashboard-charts/dashboard-charts.component.html @@ -7,7 +7,7 @@ </mat-tab> @for (protocol of protocols(); track $index) { <mat-tab [label]="protocol"> - <app-dashboard-chart-protocol [protocol]="protocol" /> + <app-dashboard-chart-protocol [protocolName]="protocol" /> </mat-tab> } </mat-tab-group> diff --git a/src/app/interceptor/mock-interceptor.ts b/src/app/interceptor/mock-interceptor.ts index e2da97046e10dd9cd22cfea3b11b28728f9bceb3..3947067ee8bdb98b2b2ffe06e085eff0bf1aeda2 100644 --- a/src/app/interceptor/mock-interceptor.ts +++ b/src/app/interceptor/mock-interceptor.ts @@ -4,6 +4,7 @@ import { urls } from './interceptor'; import { ChartFrames } from '../models/chart-frames'; import { DASHBOARD_API_URL } from '../api/dashboard.api'; import { ChartInformationRate } from '../models/chart-information-rate'; +import { ChartProtocol } from '../models/chart-protocol'; export const MockInterceptor: HttpInterceptorFn = (req, next) => { const { url, method } = req; @@ -15,6 +16,8 @@ export const MockInterceptor: HttpInterceptorFn = (req, next) => { body = getRandomFrameRecord(); } else if (url === DASHBOARD_API_URL + '/information-rate/current') { body = getRandomInformationRateRecord(); + } else if (url === DASHBOARD_API_URL + '/ipv4/current') { + body = getRandomProtocolRecord(); } if (url.includes(element.url)) { @@ -43,3 +46,13 @@ function getRandomInformationRateRecord(): { data: ChartInformationRate } { }, }; } + +function getRandomProtocolRecord(): { data: ChartProtocol } { + const packets = Math.floor(Math.random() * 1000) + 30; + return { + data: { + packets: packets, + bytes: packets * Math.floor(Math.random() * 512), + }, + }; +}