diff --git a/docker-compose.yml b/docker-compose.yml index 0dd09cdb1db1c1dbb005b924b4523907b6fd4acd..6263ed376a7948acd14ca409aa57934daebe6e6c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,11 @@ services: miars-ng: build: . + network_mode: "host" volumes: - .:/app - node_modules:/app/node_modules - ports: - - "4200:4200" - command: ["npm", "run", "start", "--", "--host", "0.0.0.0", "--poll=2000"] + command: ["npm", "run", "start", "--", "--host", "0.0.0.0", "--poll=2000", "--proxy-config", "src/proxy.conf.json"] volumes: node_modules: diff --git a/src/app/api/configuration.api.ts b/src/app/api/configuration.api.ts index 45e99433bce50ca9788685402773a3d1cd337d28..e5c08bc2c323d8f74a7bacd9f77610ce00344fd9 100644 --- a/src/app/api/configuration.api.ts +++ b/src/app/api/configuration.api.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { map, mergeMap, Observable, tap } from 'rxjs'; +import { map, mergeMap, Observable } from 'rxjs'; import { Configuration, ConfigurationDto, @@ -9,7 +9,7 @@ import { } from '../models/configuration'; import { ApiResponse } from '../models/api-response'; -export const CONFIGURATION_API_URL = '/api/configuration'; +export const CONFIGURATION_API_URL = '/api/configurations'; @Injectable({ providedIn: 'root', @@ -47,11 +47,19 @@ export class ConfigurationApi { ); } - find(configName: string): Observable<Configuration> { + find(configId: string): Observable<Configuration> { return this.httpClient .get< ApiResponse<ConfigurationDto> - >(`${CONFIGURATION_API_URL}/${configName}`) + >(`${CONFIGURATION_API_URL}/${configId}`) + .pipe(map(dto => dtoToConfiguration(dto.data))); + } + + add(configuration: Configuration): Observable<Configuration> { + return this.httpClient + .post< + ApiResponse<ConfigurationDto> + >(CONFIGURATION_API_URL, configurationToDto(configuration)) .pipe(map(dto => dtoToConfiguration(dto.data))); } @@ -59,13 +67,13 @@ export class ConfigurationApi { return this.httpClient .put< ApiResponse<ConfigurationDto> - >(CONFIGURATION_API_URL, configurationToDto(configuration)) + >(`${CONFIGURATION_API_URL}/${configuration.id}`, configurationToDto(configuration)) .pipe(map(dto => dtoToConfiguration(dto.data))); } apply(configurationId: string): Observable<Configuration> { return this.httpClient - .put< + .post< ApiResponse<ConfigurationDto> >(`${CONFIGURATION_API_URL}/${configurationId}/apply`, {}) .pipe(map(dto => dtoToConfiguration(dto.data))); diff --git a/src/app/components/configuration/configuration.component.html b/src/app/components/configuration/configuration.component.html index 6c6e9ab6e887126be45eac4dd694739630150ba8..ef7e79f77b0db07fa3ecab86a069690a24d199ce 100644 --- a/src/app/components/configuration/configuration.component.html +++ b/src/app/components/configuration/configuration.component.html @@ -15,7 +15,7 @@ <mat-form-field> <mat-label>Configuration</mat-label> <mat-select - [value]="chosenConfiguration()" + [value]="chosenConfigurationId()" (valueChange)="onChangeConfiguration($event)"> @for (config of configurationOptions(); track $index) { <mat-option [value]="config.id">{{ config.name }}</mat-option> @@ -56,7 +56,7 @@ @case (ConfigurationPageMode.CREATE) { <app-configuration-form (cancel)="pageMode.set(ConfigurationPageMode.READ)" - (submit)="onSubmitConfiguration($event)" /> + (submit)="onAddConfiguration($event)" /> } @case (ConfigurationPageMode.EDIT) { <app-configuration-form @@ -70,23 +70,25 @@ <ng-container *ngTemplateOutlet="configurationSelect" /> <div class="configuration__header-actions"> - <button mat-fab extended (click)="onDeleteConfiguration()"> - <mat-icon>delete</mat-icon> - Delete - </button> - @if (!configuration()?.is_applied) { - <button mat-fab extended (click)="onApplyConfiguration()"> - <mat-icon>save</mat-icon> - Apply + @if (configuration()?.id) { + @if (!configuration()?.is_applied) { + <button mat-fab extended (click)="onDeleteConfiguration()"> + <mat-icon>delete</mat-icon> + Delete + </button> + <button mat-fab extended (click)="onApplyConfiguration()"> + <mat-icon>save</mat-icon> + Apply + </button> + } + <button + mat-fab + extended + (click)="pageMode.set(ConfigurationPageMode.EDIT)"> + <mat-icon>edit_square</mat-icon> + Edit </button> } - <button - mat-fab - extended - (click)="pageMode.set(ConfigurationPageMode.EDIT)"> - <mat-icon>edit_square</mat-icon> - Edit - </button> </div> </div> <div mac-source class="configuration__mac"> diff --git a/src/app/components/configuration/configuration.component.ts b/src/app/components/configuration/configuration.component.ts index 910833bff1914b10cf030a4b681c3ae0eed406db..b6dc83b6dc040a416017b4a6c603a92ce0246a8f 100644 --- a/src/app/components/configuration/configuration.component.ts +++ b/src/app/components/configuration/configuration.component.ts @@ -55,16 +55,16 @@ export class ConfigurationComponent implements OnInit { pageMode = signal<ConfigurationPageMode>(ConfigurationPageMode.READ); configurationOptions = signal<Partial<Configuration>[]>([]); - chosenConfiguration = signal<string | undefined>(undefined); + chosenConfigurationId = signal<string | undefined>(undefined); isLoading = signal<boolean>(true); configuration = signal<Configuration | undefined>(undefined); constructor() { effect(() => { this.isLoading.set(true); - const chosenConfiguration = this.chosenConfiguration(); - if (chosenConfiguration) { - this.configurationApi.find(chosenConfiguration).subscribe(config => { + const chosenConfigurationId = this.chosenConfigurationId(); + if (chosenConfigurationId) { + this.configurationApi.find(chosenConfigurationId).subscribe(config => { this.isLoading.set(false); this.configuration.set(config); }); @@ -77,13 +77,24 @@ export class ConfigurationComponent implements OnInit { } onDeleteConfiguration() { - this.configurationApi.delete(this.chosenConfiguration()!).subscribe(() => { + const configId = this.chosenConfigurationId(); + if (!configId) return; + + this.isLoading.set(true); + this.configurationApi.delete(configId).subscribe(() => { this.updateConfigurationOptions(); + this.isLoading.set(false); + + // no applied config -> set default view + this.configuration.set(undefined); }); } onApplyConfiguration() { - this.configurationApi.apply(this.chosenConfiguration()!).subscribe(() => { + const configId = this.chosenConfigurationId(); + if (!configId) return; + + this.configurationApi.apply(configId).subscribe(() => { this.fetchConfigurationOptions().subscribe(configurations => this.configurationOptions.set(configurations) ); @@ -91,13 +102,32 @@ export class ConfigurationComponent implements OnInit { } onChangeConfiguration(configId: string) { - this.chosenConfiguration.set(configId); + this.chosenConfigurationId.set(configId); + } + + onAddConfiguration(configuration: Configuration) { + this.isLoading.set(true); + + this.configurationApi.add(configuration).subscribe(config => { + this.configuration.set(config); + this.configurationOptions.update(configs => [...configs, config]); + this.chosenConfigurationId.set(config.id); + + this.isLoading.set(false); + this.pageMode.set(ConfigurationPageMode.READ); + }); } onSubmitConfiguration(configuration: Configuration) { - console.log('Submitting configuration', configuration); - this.configurationApi.save(configuration); - this.pageMode.set(ConfigurationPageMode.READ); + this.isLoading.set(true); + + this.configurationApi.save(configuration).subscribe(config => { + this.configuration.set(config); + this.updateConfigurationOptions(); + + this.isLoading.set(false); + this.pageMode.set(ConfigurationPageMode.READ); + }); } private fetchConfigurationOptions(): Observable<Partial<Configuration>[]> { @@ -120,7 +150,7 @@ export class ConfigurationComponent implements OnInit { this.fetchConfigurationOptions().subscribe(configurations => { const appliedConfiguration = configurations.find(c => c.is_applied); if (appliedConfiguration) { - this.chosenConfiguration.set(appliedConfiguration.id); + this.chosenConfigurationId.set(appliedConfiguration.id); } this.configurationOptions.set(configurations); }); diff --git a/src/app/interceptor/interceptor.ts b/src/app/interceptor/interceptor.ts index 6cab5d5c0d4153206b9e13b59f43d1ca76197f81..e7b0470ac378b57fa1b313951e7e5bb3a5d7e8f8 100644 --- a/src/app/interceptor/interceptor.ts +++ b/src/app/interceptor/interceptor.ts @@ -1,6 +1,3 @@ -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 framesCurrent from './mocks/frames-current.json'; import * as framesHistorical from './mocks/frames-historical.json'; import * as irCurrent from './mocks/ir-current.json'; @@ -11,18 +8,6 @@ import * as dashboardStats from './mocks/dashboard-statistics.json'; import * as dashboardStats2 from './mocks/dashboard-statistics-2.json'; export const urls = [ - { - url: '/api/configuration/1abc', - json: () => configuration1, - }, - { - url: '/api/configuration/2def', - json: () => configuration2, - }, - { - url: '/api/configuration', - json: () => configurationList, - }, { url: '/api/statistics/frames/current', json: () => framesCurrent, diff --git a/src/app/interceptor/mocks/configuration-1.json b/src/app/interceptor/mocks/configuration-1.json deleted file mode 100644 index 93c1d9b1f5575ffd6fdcbaf52e02e18506e2b6a7..0000000000000000000000000000000000000000 --- a/src/app/interceptor/mocks/configuration-1.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "data": { - "id": "1abc", - "name": "Custom", - "is_applied": true, - "mac_source": [ - "01:23:45:67:89:00", - "01:23:45:67:89:11", - "01:23:45:67:89:22", - "01:23:45:67:89:33", - "01:23:45:67:89:44", - "01:23:45:67:89:55", - "01:23:45:67:89:66", - "01:23:45:67:89:77" - ], - "mac_destination": ["fe:dc:ba:98:76:54"], - "frame_ranges": [ - [0, 63], - [128, 255], - [1024, 1518] - ], - "protocols": ["IPv4", "IPv6", "UDP"] - } -} diff --git a/src/app/interceptor/mocks/configuration-2.json b/src/app/interceptor/mocks/configuration-2.json deleted file mode 100644 index f047b8b4edb47f698b826edce66221969e05d2dd..0000000000000000000000000000000000000000 --- a/src/app/interceptor/mocks/configuration-2.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "data": { - "id": "2def", - "name": "Everything", - "is_applied": false, - "mac_source": [], - "mac_destination": [], - "frame_ranges": [], - "protocols": [] - } -} diff --git a/src/app/interceptor/mocks/configurations.json b/src/app/interceptor/mocks/configurations.json deleted file mode 100644 index e7de12535e42398db682bf55103fbbf5cc909593..0000000000000000000000000000000000000000 --- a/src/app/interceptor/mocks/configurations.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "data": [ - { - "id": "1abc", - "name": "Custom", - "is_applied": true, - "mac_source": [ - "01:23:45:67:89:00", - "01:23:45:67:89:11", - "01:23:45:67:89:22", - "01:23:45:67:89:33", - "01:23:45:67:89:44", - "01:23:45:67:89:55", - "01:23:45:67:89:66", - "01:23:45:67:89:77" - ], - "mac_destination": ["fe:dc:ba:98:76:54"], - "frame_ranges": [ - [0, 63], - [128, 255], - [1024, 1518] - ], - "protocols": ["IPv4", "IPv6", "UDP"] - }, - { - "id": "2def", - "name": "Everything", - "is_applied": false, - "mac_source": [], - "mac_destination": [], - "frame_ranges": [], - "protocols": [] - } - ] -} diff --git a/src/app/models/configuration.ts b/src/app/models/configuration.ts index 8424ba4169009534cb22bf058708fd0f8a4a592e..e3b631a9b159d3814ef2cbb4c19dfd937f3919df 100644 --- a/src/app/models/configuration.ts +++ b/src/app/models/configuration.ts @@ -1,6 +1,4 @@ -export type Configuration = ConfigurationDto; - -export interface ConfigurationDto { +export interface Configuration { id: string; name: string; is_applied: boolean; @@ -10,12 +8,30 @@ export interface ConfigurationDto { protocols: string[]; } +export interface ConfigurationDto { + id: string; + name: string; + is_applied: boolean; + source_mac: string[]; + destination_mac: string[]; + frame_ranges: [number, number][]; + protocols: string[]; +} + export function dtoToConfiguration(dto: ConfigurationDto): Configuration { - return dto; + return { + ...dto, + mac_source: dto.source_mac, + mac_destination: dto.destination_mac, + }; } export function configurationToDto( configuration: Configuration ): ConfigurationDto { - return configuration; + return { + ...configuration, + source_mac: configuration.mac_source, + destination_mac: configuration.mac_destination, + }; } diff --git a/src/proxy.conf.json b/src/proxy.conf.json new file mode 100644 index 0000000000000000000000000000000000000000..8c73ea947e837ba77692a2c97fafafd4431810a6 --- /dev/null +++ b/src/proxy.conf.json @@ -0,0 +1,10 @@ +{ + "/api": { + "target": "http://localhost:5000", + "secure": false, + "changeOrigin": true, + "pathRewrite": { + "^/api": "" + } + } +}