From 9b042b355337db32e0f4ff5385ab78e327784ff9 Mon Sep 17 00:00:00 2001
From: Artem Dychenko <s192441@student.pg.edu.pl>
Date: Sun, 23 Mar 2025 21:55:04 +0100
Subject: [PATCH] feat: managing statistics from settings menu

---
 .../dashboard-settings.component.html         |  35 +++---
 .../dashboard-settings.component.scss         |  16 ++-
 .../dashboard-settings.component.spec.ts      |  23 ----
 .../dashboard-settings.component.ts           |  56 ++++++++-
 .../dashboard-statistics.component.html       | 119 ++++++++++++------
 .../dashboard-statistics.component.scss       |   1 +
 .../dashboard-statistics.component.ts         |  38 +++++-
 .../slide-toggle/slide-toggle.component.scss  |   4 -
 .../slide-toggle/slide-toggle.component.ts    |   2 +-
 src/app/models/settings.ts                    |  29 +++++
 src/app/service/settings.service.ts           |  39 ++++++
 11 files changed, 263 insertions(+), 99 deletions(-)
 delete mode 100644 src/app/components/dashboard-settings/dashboard-settings.component.spec.ts
 create mode 100644 src/app/models/settings.ts
 create mode 100644 src/app/service/settings.service.ts

diff --git a/src/app/components/dashboard-settings/dashboard-settings.component.html b/src/app/components/dashboard-settings/dashboard-settings.component.html
index 3f7c6f8..bddf167 100644
--- a/src/app/components/dashboard-settings/dashboard-settings.component.html
+++ b/src/app/components/dashboard-settings/dashboard-settings.component.html
@@ -1,42 +1,41 @@
-<div class="dialog">
+<div class="dialog" [formGroup]="settingsForm">
   <h2 mat-dialog-title class="dialog__header">Settings</h2>
   <mat-dialog-content class="dialog__content">
-    <div class="dialog__content__statistics__columns">
+    <div formGroupName="statisticsColumns" class="dialog__content__statistics__columns">
       <div class="dialog__content__statistics__columns-title">
         Statistics columns
       </div>
       <div class="dialog__content__statistics__columns-content">
-        <app-slide-toggle>Total packets</app-slide-toggle>
-        <app-slide-toggle>Packets per seconds</app-slide-toggle>
-        <app-slide-toggle>Total bytes</app-slide-toggle>
-        <app-slide-toggle>Bytes per second</app-slide-toggle>
+        <mat-slide-toggle [hideIcon]="true" formControlName="showTotalPackets">Total packets</mat-slide-toggle>
+        <mat-slide-toggle [hideIcon]="true" formControlName="showPacketsPerSec">Packets per seconds</mat-slide-toggle>
+        <mat-slide-toggle [hideIcon]="true" formControlName="showTotalBytes">Total bytes</mat-slide-toggle>
+        <mat-slide-toggle [hideIcon]="true" formControlName="showBytesPerSec">Bytes per second</mat-slide-toggle>
       </div>
     </div>
-    <div class="dialog__content__statistics__rows">
+    <div formGroupName="statisticsRowsAndCharts" class="dialog__content__statistics__rows">
       <div class="dialog__content__statistics__rows-title">
         Statistics rows & charts
       </div>
       <div class="dialog__content__statistics__rows-content">
-        <app-slide-toggle>ETH</app-slide-toggle>
-        <app-slide-toggle>IPv4</app-slide-toggle>
-        <app-slide-toggle >IPv6</app-slide-toggle>
-        <app-slide-toggle >TCP</app-slide-toggle>
+        <mat-slide-toggle [hideIcon]="true" formControlName="showETH">ETH</mat-slide-toggle>
+        <mat-slide-toggle [hideIcon]="true" formControlName="showIPv4">IPv4</mat-slide-toggle>
+        <mat-slide-toggle [hideIcon]="true" formControlName="showIPv6">IPv6</mat-slide-toggle>
+        <mat-slide-toggle [hideIcon]="true" formControlName="showTCP">TCP</mat-slide-toggle>
       </div>
     </div>
-    <div class="dialog__content__statistics__ir">
+    <div formGroupName="statisticsIR" class="dialog__content__statistics__ir">
       <div class="dialog__content__statistics__ir-title">
         Information Rate
       </div>
       <div class="dialog__content__statistics__ir-content">
-        <app-slide-toggle class="custom" >Min value</app-slide-toggle>
-        <app-slide-toggle >Max value</app-slide-toggle>
-        <app-slide-toggle >Current value</app-slide-toggle>
-
+        <mat-slide-toggle [hideIcon]="true" formControlName="showMinValue">Min value</mat-slide-toggle>
+        <mat-slide-toggle [hideIcon]="true" formControlName="showMaxValue">Max value</mat-slide-toggle>
+        <mat-slide-toggle [hideIcon]="true" formControlName="showCurrentValue">Current value</mat-slide-toggle>
       </div>
     </div>
   </mat-dialog-content>
   <mat-dialog-actions class="dialog__actions">
-    <app-button secondary class="cancel-button">CANCEL</app-button>
-    <app-button class="save-button">SAVE</app-button>
+    <app-button secondary class="cancel-button" (click)="onCancel()">CANCEL</app-button>
+    <app-button class="save-button" (click)="onSubmit()">SAVE</app-button>
   </mat-dialog-actions>
 </div>
diff --git a/src/app/components/dashboard-settings/dashboard-settings.component.scss b/src/app/components/dashboard-settings/dashboard-settings.component.scss
index ac32f2e..712cfb4 100644
--- a/src/app/components/dashboard-settings/dashboard-settings.component.scss
+++ b/src/app/components/dashboard-settings/dashboard-settings.component.scss
@@ -29,10 +29,8 @@
         }
       }
     }
-    ::ng-deep app-slide-toggle {
-      mat-slide-toggle {
+    ::ng-deep mat-slide-toggle {
         width: 25% !important;
-      }
     }
   }
   &__actions {
@@ -47,12 +45,22 @@
         width: 450px;
       }
       margin: map.get(vars.$spacing, 'md') 0 0 0;
-
     }
   }
 }
 
 
+::ng-deep .mat-mdc-slide-toggle.mat-mdc-slide-toggle-checked:not(.mat-disabled) .mdc-switch__shadow {
+  background-color: map.get(vars.$grey, 10);
+}
+
+::ng-deep .mat-mdc-slide-toggle.mat-mdc-slide-toggle-checked:not(.mat-disabled) .mdc-switch__track::after {
+  background-color: vars.$textPrimary !important;
+}
+
+::ng-deep .mdc-switch__track::before {
+  background-color: map.get(vars.$grey, 30) !important;
+}
 
 
 
diff --git a/src/app/components/dashboard-settings/dashboard-settings.component.spec.ts b/src/app/components/dashboard-settings/dashboard-settings.component.spec.ts
deleted file mode 100644
index 5a25de3..0000000
--- a/src/app/components/dashboard-settings/dashboard-settings.component.spec.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { DashboardSettingsComponent } from './dashboard-settings.component';
-
-describe('DashboardSettingsComponent', () => {
-  let component: DashboardSettingsComponent;
-  let fixture: ComponentFixture<DashboardSettingsComponent>;
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [DashboardSettingsComponent]
-    })
-    .compileComponents();
-
-    fixture = TestBed.createComponent(DashboardSettingsComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-});
diff --git a/src/app/components/dashboard-settings/dashboard-settings.component.ts b/src/app/components/dashboard-settings/dashboard-settings.component.ts
index f3df6d3..dfff137 100644
--- a/src/app/components/dashboard-settings/dashboard-settings.component.ts
+++ b/src/app/components/dashboard-settings/dashboard-settings.component.ts
@@ -1,10 +1,17 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
+import {
+  ChangeDetectionStrategy,
+  Component,
+  inject,
+  input,
+  OnInit,
+} from '@angular/core';
 import { MatDialogModule } from '@angular/material/dialog';
 import { ToggleSwitchModule } from 'primeng/toggleswitch';
-import { FormsModule } from '@angular/forms';
+import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { MatButtonModule } from '@angular/material/button';
 import { ButtonComponent } from '../button/button.component';
-import { SlideToggleComponent } from '../slide-toggle/slide-toggle.component';
+import { SettingsService } from '../../service/settings.service';
+import { MatSlideToggle } from '@angular/material/slide-toggle';
 
 @Component({
   changeDetection: ChangeDetectionStrategy.OnPush,
@@ -14,12 +21,49 @@ import { SlideToggleComponent } from '../slide-toggle/slide-toggle.component';
     FormsModule,
     MatButtonModule,
     ButtonComponent,
-    SlideToggleComponent,
+    ReactiveFormsModule,
+    MatSlideToggle,
   ],
   selector: 'app-dashboard-settings',
   styleUrl: './dashboard-settings.component.scss',
   templateUrl: './dashboard-settings.component.html',
 })
-export class DashboardSettingsComponent {
-  isChecked: Boolean = true;
+export class DashboardSettingsComponent implements OnInit {
+  private formBuilder = inject(FormBuilder);
+
+  settingsForm = this.formBuilder.group({
+    statisticsColumns: this.formBuilder.group({
+      showTotalPackets: [true],
+      showPacketsPerSec: [true],
+      showTotalBytes: [true],
+      showBytesPerSec: [true],
+    }),
+    statisticsRowsAndCharts: this.formBuilder.group({
+      showETH: [true],
+      showIPv4: [true],
+      showIPv6: [true],
+      showTCP: [true],
+    }),
+    statisticsIR: this.formBuilder.group({
+      showMinValue: [true],
+      showMaxValue: [true],
+      showCurrentValue: [true],
+    }),
+  });
+
+  private settingsService = inject(SettingsService);
+
+  ngOnInit() {
+    const currentSettings = this.settingsForm.value;
+    this.settingsForm.patchValue(currentSettings);
+  }
+
+  onSubmit() {
+    this.settingsService.updateSettings(this.settingsForm.value);
+  }
+
+  onCancel() {
+    const currentSettings = this.settingsService.getSettings();
+    this.settingsForm.patchValue(currentSettings);
+  }
 }
diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.html b/src/app/components/dashboard-statistics/dashboard-statistics.component.html
index bd5d0df..f40e325 100644
--- a/src/app/components/dashboard-statistics/dashboard-statistics.component.html
+++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.html
@@ -6,70 +6,111 @@
         <ngx-skeleton-loader count="7" />
       }
     </div>
-  }
-  @else {
+  } @else {
     <div class="statistics__table">
       <div class="statistics__table__time">
         <p>Total time: {{ statistics.total_time | time }} </p>
       </div>
-      <table class="statistics__table__header">
-        <thead class="statistics__table__header-title">
+      @if (settings?.statisticsRowsAndCharts?.showETH) {
+        <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>
+            @if (settings?.statisticsColumns?.showTotalPackets) {
+              <th>Total packets</th>
+            }
+            @if (settings?.statisticsColumns?.showPacketsPerSec) {
+              <th>Packets per second</th>
+            }
+            @if (settings?.statisticsColumns?.showTotalBytes) {
+              <th>Total bytes</th>
+            }
+            @if (settings?.statisticsColumns?.showBytesPerSec) {
+              <th>Bytes per second</th>
+            }
           </tr>
-        </thead>
-        <tbody>
-          @let protocolEth = getETHStatistics(statistics.protocols);
+          </thead>
+          <tbody>
+            @let protocolEth = getETHStatistics(statistics.protocols);
           <tr class="statistics__table__header-eth">
             <td>{{ protocolEth.name }}</td>
-            <td>{{ protocolEth.total_packets | decimal }}</td>
-            <td>
-              {{ getPerSecond(protocolEth.total_packets, statistics.total_time) | decimal }}
-            </td>
-            <td>{{ protocolEth.total_bytes | decimal }}</td>
-            <td>
-              {{ getPerSecond(protocolEth.total_bytes, statistics.total_time) | decimal }}
-            </td>
-          </tr>
-        </tbody>
-      </table>
-      <table class="statistics__table__protocols">
-        <tbody>
-        @for (protocol of getProtocols(statistics.protocols); track protocol.name) {
-            <tr>
-              <td>{{ protocol.name }}</td>
-              <td>{{ protocol.total_packets | decimal }}</td>
+            @if (settings?.statisticsColumns?.showTotalPackets) {
+              <td>{{ protocolEth.total_packets | decimal }}</td>
+            }
+            @if (settings?.statisticsColumns?.showPacketsPerSec) {
               <td>
-                {{ getPerSecond(protocol.total_packets, statistics.total_time) | decimal }}
+                {{ getPerSecond(protocolEth.total_packets, statistics.total_time) | decimal }}
               </td>
-              <td>{{ protocol.total_bytes | decimal }}</td>
+            }
+            @if (settings?.statisticsColumns?.showTotalBytes) {
+              <td>{{ protocolEth.total_bytes | decimal }}</td>
+            }
+            @if (settings?.statisticsColumns?.showBytesPerSec) {
               <td>
-                {{ getPerSecond(protocol.total_bytes, statistics.total_time) | decimal }}
+                {{ getPerSecond(protocolEth.total_bytes, statistics.total_time) | decimal }}
               </td>
-            </tr>
-        }
+            }
+          </tr>
+          </tbody>
+        </table>
+      }
+      <table class="statistics__table__protocols">
+        <tbody>
+          @for (protocol of getProtocols(statistics.protocols); track protocol.name) {
+            @if (settings?.statisticsRowsAndCharts?.showIPv4 && protocol.name === 'IPv4' ||
+            settings?.statisticsRowsAndCharts?.showIPv6 && protocol.name === 'IPv6' ||
+            settings?.statisticsRowsAndCharts?.showTCP && protocol.name === 'TCP') {
+              <tr>
+                <td>{{ protocol.name }}</td>
+                @if (settings?.statisticsColumns?.showTotalPackets) {
+                  <td>{{ protocol.total_packets | decimal }}</td>
+                }
+                @if (settings?.statisticsColumns?.showPacketsPerSec) {
+                  <td>
+                    {{ getPerSecond(protocol.total_packets, statistics.total_time) | decimal }}
+                  </td>
+                }
+                @if (settings?.statisticsColumns?.showTotalBytes) {
+                  <td>{{ protocol.total_bytes | decimal }}</td>
+                }
+                @if (settings?.statisticsColumns?.showBytesPerSec) {
+                  <td>
+                    {{ getPerSecond(protocol.total_bytes, statistics.total_time) | decimal }}
+                  </td>
+                }
+              </tr>
+            }
+          }
         </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>
+          @if (settings?.statisticsIR?.showMinValue) {
+            <th>Min</th>
+          }
+          @if (settings?.statisticsIR?.showMaxValue) {
+            <th>Max</th>
+          }
+          @if (settings?.statisticsIR?.showCurrentValue) {
+            <th>Current</th>
+          }
         </tr>
         </thead>
         <tbody>
-          <tr class="statistics__table_ir-content">
-            <td>Information Rate</td>
+        <tr class="statistics__table_ir-content">
+          <td>Information Rate</td>
+          @if (settings?.statisticsIR?.showMinValue) {
             <td>{{ statistics.information_rate.min | decimal }}</td>
+          }
+          @if (settings?.statisticsIR?.showMaxValue) {
             <td>{{ statistics.information_rate.max | decimal }}</td>
+          }
+          @if (settings?.statisticsIR?.showCurrentValue) {
             <td>{{ statistics.information_rate.current | decimal }}</td>
-          </tr>
+          }
+        </tr>
         </tbody>
       </table>
     </div>
diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.scss b/src/app/components/dashboard-statistics/dashboard-statistics.component.scss
index e11b5db..1814cde 100644
--- a/src/app/components/dashboard-statistics/dashboard-statistics.component.scss
+++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.scss
@@ -74,6 +74,7 @@
         margin-top: map.get(vars.$spacing, 'lg');
         width: 100%;
 
+
         tbody td {
           border-top: 1px solid map.get(vars.$grey, 30);
         }
diff --git a/src/app/components/dashboard-statistics/dashboard-statistics.component.ts b/src/app/components/dashboard-statistics/dashboard-statistics.component.ts
index 2b6f628..46f5e53 100644
--- a/src/app/components/dashboard-statistics/dashboard-statistics.component.ts
+++ b/src/app/components/dashboard-statistics/dashboard-statistics.component.ts
@@ -1,27 +1,57 @@
-import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
+import {
+  ChangeDetectionStrategy,
+  Component,
+  inject,
+  Input,
+  OnInit,
+} from '@angular/core';
 import { NgxSkeletonLoaderComponent } from 'ngx-skeleton-loader';
-import { AsyncPipe } from '@angular/common';
+import { AsyncPipe, NgIf } from '@angular/common';
 import { DashboardApi } from '../../api/dashboard.api';
+
 import { interval, shareReplay, switchMap } from 'rxjs';
 import { ProtocolStatistics } from '../../models/dashboard-statistics';
 import { TimePipe } from '../../pipes/time.pipe';
 import { DecimalPipe } from '../../pipes/decimal.pipe';
+import { FormGroup, ReactiveFormsModule } from '@angular/forms';
+import { SettingsService } from '../../service/settings.service';
 
 @Component({
   selector: 'app-dashboard-statistics',
-  imports: [NgxSkeletonLoaderComponent, AsyncPipe, TimePipe, DecimalPipe],
+  imports: [
+    NgxSkeletonLoaderComponent,
+    AsyncPipe,
+    TimePipe,
+    DecimalPipe,
+    ReactiveFormsModule,
+    NgIf,
+  ],
   templateUrl: './dashboard-statistics.component.html',
   styleUrl: './dashboard-statistics.component.scss',
   changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class DashboardStatisticsComponent {
+export class DashboardStatisticsComponent implements OnInit {
   dashboardApi = inject(DashboardApi);
+  settings: any;
+
+  private settingsService = inject(SettingsService);
+
+  ngOnInit() {
+    this.settingsService.settings$.subscribe(settings => {
+      this.settings = settings;
+      console.log('Settings received in Dashboard:', this.settings);
+    });
+  }
 
   dashboardStatistics = interval(1000).pipe(
     switchMap(() => this.dashboardApi.fetchStatistics()),
     shareReplay(1)
   );
 
+  get showETHSetting() {
+    return this.settings.value.statisticsRowsAndCharts.showETH;
+  }
+
   getPerSecond(value: number, time: Date): number {
     const totalSeconds = time.getTime() / 1000;
     return value / totalSeconds;
diff --git a/src/app/components/slide-toggle/slide-toggle.component.scss b/src/app/components/slide-toggle/slide-toggle.component.scss
index 88ed7b5..2ef9c9c 100644
--- a/src/app/components/slide-toggle/slide-toggle.component.scss
+++ b/src/app/components/slide-toggle/slide-toggle.component.scss
@@ -13,7 +13,3 @@
   background-color: map.get(vars.$grey, 30) !important;
 }
 
-//.mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-thumb:before {
-//  content: "" !important;
-//  font: normal normal normal 14px/1 FontAwesome !important;
-//}
diff --git a/src/app/components/slide-toggle/slide-toggle.component.ts b/src/app/components/slide-toggle/slide-toggle.component.ts
index 05f96f8..72ff668 100644
--- a/src/app/components/slide-toggle/slide-toggle.component.ts
+++ b/src/app/components/slide-toggle/slide-toggle.component.ts
@@ -7,4 +7,4 @@ import { MatSlideToggle } from '@angular/material/slide-toggle';
   templateUrl: './slide-toggle.component.html',
   styleUrl: './slide-toggle.component.scss',
 })
-export class SlideToggleComponent {}
+export class SlideToggleComponent extends MatSlideToggle {}
diff --git a/src/app/models/settings.ts b/src/app/models/settings.ts
new file mode 100644
index 0000000..94d2134
--- /dev/null
+++ b/src/app/models/settings.ts
@@ -0,0 +1,29 @@
+export type Settings = SettingsDto;
+
+export interface SettingsDto {
+  statisticsColumns: {
+    showTotalPackets: true;
+    showPacketsPerSec: true;
+    showTotalBytes: true;
+    showBytesPerSec: true;
+  };
+  statisticsRowsAndCharts: {
+    showETH: true;
+    showIPv4: true;
+    showIPv6: true;
+    showTCP: true;
+  };
+  statisticsIR: {
+    showMinValue: true;
+    showMaxValue: true;
+    showCurrentValue: true;
+  };
+}
+
+export function dtoToSettings(dto: SettingsDto): Settings {
+  return dto;
+}
+
+export function settingsToDto(settings: Settings): SettingsDto {
+  return settings;
+}
diff --git a/src/app/service/settings.service.ts b/src/app/service/settings.service.ts
new file mode 100644
index 0000000..fb59cb2
--- /dev/null
+++ b/src/app/service/settings.service.ts
@@ -0,0 +1,39 @@
+import { Injectable, OnInit } from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
+import { Settings } from '../models/settings';
+
+@Injectable({
+  providedIn: 'root',
+})
+export class SettingsService implements OnInit {
+  private settingsSubject = new BehaviorSubject<any>({
+    statisticsColumns: {
+      showTotalPackets: true,
+      showPacketsPerSec: true,
+      showTotalBytes: true,
+      showBytesPerSec: true,
+    },
+    statisticsRowsAndCharts: {
+      showETH: true,
+      showIPv4: true,
+      showIPv6: true,
+      showTCP: true,
+    },
+    statisticsIR: {
+      showMinValue: true,
+      showMaxValue: true,
+      showCurrentValue: true,
+    },
+  });
+  settings$ = this.settingsSubject.asObservable();
+
+  ngOnInit() {}
+
+  updateSettings(settings: any) {
+    this.settingsSubject.next(settings);
+  }
+
+  getSettings() {
+    return this.settingsSubject.value;
+  }
+}
-- 
GitLab