From 963f5d4ce05e2f6c768e005cd5456987834e6c7f Mon Sep 17 00:00:00 2001 From: August Andersen Date: Fri, 12 Nov 2021 11:02:53 +0100 Subject: [PATCH 01/37] Added message to prompt when attempting to delete application with SigFox device * Added message and functionality to check and to prompt when attempting to delete application with SigFox device --- .../applications-table.component.ts | 50 +++++++++++++++++-- src/assets/i18n/da.json | 1 + 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/app/applications/applications-list/applications-table/applications-table.component.ts b/src/app/applications/applications-list/applications-table/applications-table.component.ts index 4da38304..4286f5c9 100644 --- a/src/app/applications/applications-list/applications-table/applications-table.component.ts +++ b/src/app/applications/applications-list/applications-table/applications-table.component.ts @@ -1,4 +1,10 @@ -import { Component, ViewChild, AfterViewInit, Input, OnInit } from '@angular/core'; +import { + Component, + ViewChild, + AfterViewInit, + Input, + OnInit, +} from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { Router } from '@angular/router'; @@ -10,6 +16,7 @@ import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dia import { MeService } from '@shared/services/me.service'; import { merge, Observable, of as observableOf } from 'rxjs'; import { catchError, map, startWith, switchMap } from 'rxjs/operators'; +import { DeviceType } from '@shared/enums/device-type'; /** * @title Table retrieving data through HTTP @@ -40,7 +47,7 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit { private router: Router, private meService: MeService, private deleteDialogService: DeleteDialogService - ) { } + ) {} ngOnInit() { this.canEdit = this.meService.canWriteInTargetOrganization(); @@ -88,6 +95,11 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit { deleteApplication(id: number) { let message: string; + + if (this.canBeDeleted(id)) { + return; + } + if (this.applicationHasDevices(id)) { message = this.translate.instant('APPLICATION.DELETE-HAS-DEVICES-PROMPT'); } @@ -110,10 +122,42 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit { } applicationHasDevices(id: number): boolean { - const applicationToDelete = this.data?.find(app => app.id === id); + const applicationToDelete = this.data?.find((app) => app.id === id); return applicationToDelete && applicationToDelete.iotDevices.length > 0; } + applicationHasSigFoxDevices(id: number): boolean { + const applicationToDelete = this.data?.find((app) => app.id === id); + const checkForSigfox = applicationToDelete.iotDevices.find((device) => { + return device.type === DeviceType.SIGFOX; + }); + if (checkForSigfox) { + return true; + } else return false; + } + + canBeDeleted(id: number): boolean { + let message: string; + + if (this.applicationHasSigFoxDevices(id)) { + message = this.translate.instant( + 'APPLICATION.DELETE-HAS-SIGFOX-DEVICES-PROMPT' + ); + this.deleteDialogService + .showSimpleDialog( + message, + false, + true, + false, + this.translate.instant('APPLICATION.DELETE') + ) + .subscribe(); + return true; + } else { + return false; + } + } + navigateToEditPage(applicationId: string) { this.router.navigate(['applications', 'edit-application', applicationId]); } diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 96c8b754..10f666f3 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -81,6 +81,7 @@ "SAVE": "Gem applikation", "DELETE": "Slet applikation", "DELETE-HAS-DEVICES-PROMPT": "Der er knyttet IoT-enheder til denne applikation. Disse vil også blive slettet. Slet alligevel?", + "DELETE-HAS-SIGFOX-DEVICES-PROMPT": "Applikationen kan ikke slettes, da der er knyttet Sigfox enheder til den", "NAME": "Applikationens navn", "DESCRIPTION": "Applikationens beskrivelse", "ATTACHED-IOT": "Tilknyttede IoT enheder", From 456672f7b4565177a8ebda74339581a86a0408b7 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Tue, 16 Nov 2021 14:16:43 +0100 Subject: [PATCH 02/37] Made the first additions to multicast in frontend. Will try to connect to backend next. --- .../application-detail.component.html | 1 + .../applications-routing.module.ts | 14 +- src/app/applications/applications.module.ts | 57 ++++---- .../datatarget-edit.component.ts | 6 +- .../datatarget-table.component.html | 2 +- .../multicast-detail.component.html | 24 ++++ .../multicast-detail.component.scss | 0 .../multicast-detail.component.spec.ts | 25 ++++ .../multicast-detail.component.ts | 98 +++++++++++++ .../multicast-edit.component.html | 97 +++++++++++++ .../multicast-edit.component.scss | 0 .../multicast-edit.component.spec.ts | 25 ++++ .../multicast-edit.component.ts | 134 ++++++++++++++++++ .../multicast-list.component.html | 13 ++ .../multicast-list.component.scss | 0 .../multicast-list.component.spec.ts | 25 ++++ .../multicast-list.component.ts | 47 ++++++ .../multicast-table.component.html | 53 +++++++ .../multicast-table.component.scss | 0 .../multicast-table.component.spec.ts | 25 ++++ .../multicast-table.component.ts | 120 ++++++++++++++++ .../applications/multicast/multicast.model.ts | 20 +++ .../multicast/multicast.module.ts | 43 ++++++ .../multicast/multicast.service.spec.ts | 16 +++ .../multicast/multicast.service.ts | 49 +++++++ src/app/shared/enums/multicast-type.ts | 4 + src/assets/i18n/da.json | 49 ++++++- 27 files changed, 912 insertions(+), 35 deletions(-) create mode 100644 src/app/applications/multicast/multicast-detail/multicast-detail.component.html create mode 100644 src/app/applications/multicast/multicast-detail/multicast-detail.component.scss create mode 100644 src/app/applications/multicast/multicast-detail/multicast-detail.component.spec.ts create mode 100644 src/app/applications/multicast/multicast-detail/multicast-detail.component.ts create mode 100644 src/app/applications/multicast/multicast-edit/multicast-edit.component.html create mode 100644 src/app/applications/multicast/multicast-edit/multicast-edit.component.scss create mode 100644 src/app/applications/multicast/multicast-edit/multicast-edit.component.spec.ts create mode 100644 src/app/applications/multicast/multicast-edit/multicast-edit.component.ts create mode 100644 src/app/applications/multicast/multicast-list/multicast-list.component.html create mode 100644 src/app/applications/multicast/multicast-list/multicast-list.component.scss create mode 100644 src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts create mode 100644 src/app/applications/multicast/multicast-list/multicast-list.component.ts create mode 100644 src/app/applications/multicast/multicast-table/multicast-table.component.html create mode 100644 src/app/applications/multicast/multicast-table/multicast-table.component.scss create mode 100644 src/app/applications/multicast/multicast-table/multicast-table.component.spec.ts create mode 100644 src/app/applications/multicast/multicast-table/multicast-table.component.ts create mode 100644 src/app/applications/multicast/multicast.model.ts create mode 100644 src/app/applications/multicast/multicast.module.ts create mode 100644 src/app/applications/multicast/multicast.service.spec.ts create mode 100644 src/app/applications/multicast/multicast.service.ts create mode 100644 src/app/shared/enums/multicast-type.ts diff --git a/src/app/applications/application-detail/application-detail.component.html b/src/app/applications/application-detail/application-detail.component.html index 1eac588d..647d2c1c 100644 --- a/src/app/applications/application-detail/application-detail.component.html +++ b/src/app/applications/application-detail/application-detail.component.html @@ -1,5 +1,6 @@
diff --git a/src/app/applications/applications-routing.module.ts b/src/app/applications/applications-routing.module.ts index f563dfab..d48876b1 100644 --- a/src/app/applications/applications-routing.module.ts +++ b/src/app/applications/applications-routing.module.ts @@ -10,6 +10,9 @@ import { DatatargetEditComponent } from './datatarget/datatarget-edit/datatarget import { DatatargetListComponent } from './datatarget/datatarget-list/datatarget-list.component'; import { DatatargetDetailComponent } from './datatarget/datatarget-detail/datatarget-detail.component'; import { BulkImportComponent } from './bulk-import/bulk-import.component'; +import { MulticastListComponent } from './multicast/multicast-list/multicast-list.component'; +import { MulticastEditComponent } from './multicast/multicast-edit/multicast-edit.component'; +import { MulticastDetailComponent } from './multicast/multicast-detail/multicast-detail.component'; const applicationRoutes: Routes = [ @@ -37,7 +40,16 @@ const applicationRoutes: Routes = [ ] }, - { path: 'bulk-import', component: BulkImportComponent } + { + path: 'multicast-list/:name', + children: [ + { path: '', component: MulticastListComponent }, + { path: 'multicast-edit', component: MulticastEditComponent}, + { path: 'multicast-edit/:multicastId', component: MulticastEditComponent }, + { path: 'multicast/:multicastId', component: MulticastDetailComponent } + ] + }, + { path: 'bulk-import', component: BulkImportComponent }, ], }, diff --git a/src/app/applications/applications.module.ts b/src/app/applications/applications.module.ts index 69f3114c..acc30053 100644 --- a/src/app/applications/applications.module.ts +++ b/src/app/applications/applications.module.ts @@ -17,34 +17,35 @@ import { NGMaterialModule } from '@shared/Modules/materiale.module'; import { BulkImportComponent } from './bulk-import/bulk-import.component'; import { PipesModule } from '@shared/pipes/pipes.module'; import { ApplicationsTableComponent } from './applications-list/applications-table/applications-table.component'; - +import { MulticastModule } from './multicast/multicast.module'; @NgModule({ - declarations: [ - ApplicationsComponent, - ApplicationDetailComponent, - ApplicationEditComponent, - ApplicationsListComponent, - ApplicationsTableComponent, - BulkImportComponent, - ], - exports: [ - ApplicaitonsRoutingModule, - ApplicationsComponent, - ApplicationsTableComponent, - ], - imports: [ - CommonModule, - RouterModule, - TranslateModule, - IotDevicesModule, - DatatargetModule, - DirectivesModule, - FormModule, - SharedModule, - FontAwesomeModule, - NGMaterialModule, - PipesModule, - ], + declarations: [ + ApplicationsComponent, + ApplicationDetailComponent, + ApplicationEditComponent, + ApplicationsListComponent, + ApplicationsTableComponent, + BulkImportComponent, + ], + exports: [ + ApplicaitonsRoutingModule, + ApplicationsComponent, + ApplicationsTableComponent, + ], + imports: [ + CommonModule, + RouterModule, + TranslateModule, + IotDevicesModule, + DatatargetModule, + DirectivesModule, + FormModule, + SharedModule, + FontAwesomeModule, + NGMaterialModule, + PipesModule, + MulticastModule, + ], }) -export class ApplicationsModule { } +export class ApplicationsModule {} diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts index 968df9e2..568dfb77 100644 --- a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts @@ -44,7 +44,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { public formFailedSubmit = false; public datatargetid: number; private applicationId: number; - private applicationNane: string; + private applicationName: string; public application: Application; public devices: IotDevice[]; public payloadDecoders = []; @@ -94,7 +94,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { this.datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); this.applicationId = +this.route.snapshot.paramMap.get('id'); - this.applicationNane = this.route.snapshot.paramMap.get('name'); + this.applicationName = this.route.snapshot.paramMap.get('name'); if (this.datatargetid !== 0) { this.getDatatarget(this.datatargetid); this.getPayloadDeviceDatatarget(this.datatargetid); @@ -288,7 +288,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { } routeToDatatargets(): void { - this.router.navigate(['applications',this.applicationId.toString(),'datatarget-list', this.applicationNane]) + this.router.navigate(['applications',this.applicationId.toString(),'datatarget-list', this.applicationName]) } onCoordinateKey(event: any) { diff --git a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html index 43de08b9..c839d784 100644 --- a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html +++ b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html @@ -15,7 +15,7 @@ - + {{ 'DATATARGET-TABLE.TYPE' | translate }} diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.html b/src/app/applications/multicast/multicast-detail/multicast-detail.component.html new file mode 100644 index 00000000..a8aede80 --- /dev/null +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.html @@ -0,0 +1,24 @@ +
+ +
+
+
+
+

{{ 'MULTICAST.DETAILS' | translate }}

+ + +

{{ 'MULTICAST.GROUPNAME' | translate }}{{multicast.groupName | translate }}

+

{{ 'MULTICAST.ADDRESS' | translate }}{{multicast.address | translate}}

+

{{ 'MULTICAST.NETWORK-KEY' | translate }}{{multicast.networkSessionKey | translate}}

+

{{ 'MULTICAST.APPLICATION-KEY' | translate }}{{multicast.applicationSessionKey | translate}}

+

{{ 'MULTICAST.FRAMECOUNTER' | translate }}{{multicast.frameCounter}}

+

{{ 'MULTICAST.DATARATE' | translate }}{{multicast.dataRate}}

+

{{ 'MULTICAST.FREQUENCY' | translate }}{{multicast.frequency}}

+

{{ 'MULTICAST.GROUPTYPE' | translate }}{{multicast.groupType}}

+ +
+
+
+
+
\ No newline at end of file diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.scss b/src/app/applications/multicast/multicast-detail/multicast-detail.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.spec.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.spec.ts new file mode 100644 index 00000000..20b2cf8e --- /dev/null +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MulticastDetailComponent } from './multicast-detail.component'; + +describe('MulticastDetailComponent', () => { + let component: MulticastDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MulticastDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MulticastDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts new file mode 100644 index 00000000..9e480e8d --- /dev/null +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts @@ -0,0 +1,98 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { BackButton } from '@shared/models/back-button.model'; +import { DropdownButton } from '@shared/models/dropdown-button.model'; +import { Subscription } from 'rxjs'; +import { Multicast } from '../multicast.model'; +import { Location } from '@angular/common'; +import { MulticastService } from '../multicast.service'; + +@Component({ + selector: 'app-multicast-detail', + templateUrl: './multicast-detail.component.html', + styleUrls: ['./multicast-detail.component.scss'], +}) +export class MulticastDetailComponent implements OnInit { + public multicastSubscription: Subscription; + public multicast: Multicast; + public backButton: BackButton = { label: '', routerLink: '/multicast-list' }; + private deleteDialogSubscription: Subscription; + public dropdownButton: DropdownButton; + private applicationName: string; + private applicationId: number; + + constructor( + private route: ActivatedRoute, + private deleteDialogService: DeleteDialogService, + private location: Location, + private multicastService: MulticastService, + public translate: TranslateService + ) {} + + ngOnInit(): void { + const id: number = +this.route.snapshot.paramMap.get('multicastId'); + this.applicationName = this.route.snapshot.paramMap.get('name'); + if (id) { + this.getMulticast(id); + this.dropdownButton = { + label: '', + editRouterLink: '../../multicast-edit/' + id, + isErasable: true, + }; + this.applicationId = +this.route.snapshot.paramMap.get('id'); + } + this.translate + .get(['GEN.BACK', 'MULTICAST-TABLE-ROW.SHOW-OPTIONS']) + .subscribe((translations) => { + this.backButton.label = translations['GEN.BACK']; + this.dropdownButton.label = + translations['MULTICAST-TABLE-ROW.SHOW-OPTIONS']; + }); + } + + getMulticast(id: number) { + this.multicastService.get(id).subscribe((multicast: Multicast) => { + this.multicast = multicast; + this.setBackButton(this.applicationId); + }); + } + + private setBackButton(applicationId: number) { + this.backButton.routerLink = [ + 'applications', + applicationId.toString(), + 'multicast-list', + this.applicationName, + ]; + } + + //only if classB can be used + // canShowPeriodicity(): boolean { + // if (this.multicast.groupType === MulticastType.ClassB) { + // return true; + // } else return false; + // } + + onDeleteDatatarget() { + this.deleteDialogSubscription = this.deleteDialogService + .showSimpleDialog() + .subscribe((response) => { + if (response) { + this.multicastService + .delete(this.multicast.id) + .subscribe((response) => {}); + this.location.back(); + } else { + console.log(response); + } + }); + } + + ngOnDestroy(): void { + if (this.deleteDialogSubscription) { + this.deleteDialogSubscription.unsubscribe(); + } + } +} diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html new file mode 100644 index 00000000..10554638 --- /dev/null +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html @@ -0,0 +1,97 @@ + + +
+
+
    +
  • + {{error | translate}} +
  • +
+
+ + +
+
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ * + +
+ +
+ + + + {{multicastType}} + + +
+ + + +
+ + +
+ + +
+
\ No newline at end of file diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.scss b/src/app/applications/multicast/multicast-edit/multicast-edit.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.spec.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.spec.ts new file mode 100644 index 00000000..84638528 --- /dev/null +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MulticastEditComponent } from './multicast-edit.component'; + +describe('MulticastEditComponent', () => { + let component: MulticastEditComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MulticastEditComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MulticastEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts new file mode 100644 index 00000000..7bf3b22a --- /dev/null +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -0,0 +1,134 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, Input, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { MulticastType } from '@shared/enums/multicast-type'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; +import { Subscription } from 'rxjs'; +import { Multicast } from '../multicast.model'; +import { MulticastService } from '../multicast.service'; + +@Component({ + selector: 'app-multicast-edit', + templateUrl: './multicast-edit.component.html', + styleUrls: ['./multicast-edit.component.scss'], +}) +export class MulticastEditComponent implements OnInit { + public title: string; + public multicastId: number; + public errorMessages: any; + private multicastSubscription: Subscription; + public errorFields: string[]; + @Input() submitButton: string; + public backButtonTitle: string; + public multicast: Multicast = new Multicast(); + private counter: number; + private applicationId: number; + private applicationName: string; + public formFailedSubmit: boolean = false; + public multicastTypes: string[] = Object.values(MulticastType); + public periodicities: number[] = [2, 4, 8, 16, 32, 64, 128]; + + constructor( + public translate: TranslateService, + private route: ActivatedRoute, + private router: Router, + public multicastService: MulticastService, + public errorMessageService: ErrorMessageService, + public scrollToTopService: ScrollToTopService + ) {} + + ngOnInit(): void { + this.translate + .get([ + 'FORM.CREATE-NEW-MULTICAST', + 'FORM.EDIT-MULTICAST', + 'MULTICAST.SAVE', + 'NAV.MULTICAST', + ]) + .subscribe((translations) => { + const multicastId = +this.route.snapshot.paramMap.get('multicastId'); + if (multicastId !== 0) { + this.title = translations['FORM.EDIT-MULTICAST']; + } else { + this.title = translations['FORM.CREATE-NEW-MULTICAST']; + } + this.submitButton = translations['MULTICAST.SAVE']; + this.backButtonTitle = translations['NAV.MULTICAST']; + }); + + this.multicastId = +this.route.snapshot.paramMap.get('multicastId'); + this.applicationId = +this.route.snapshot.paramMap.get('id'); + this.applicationName = this.route.snapshot.paramMap.get('name'); + + if (this.multicastId !== 0) { + this.getMulticast(this.multicastId); + } + } + onSubmit(): void { + this.counter = 0; + if (this.multicastId) { + this.updateMulticast(); + } else { + this.createMulticast(); + } + } + + getMulticast(id: number) { + this.multicastSubscription = this.multicastService + .get(id) + .subscribe((response: Multicast) => { + this.multicast = response; + }); + } + + //only if classB can be used + // showPeriodicity(): boolean { + // if (this.multicast.groupType === MulticastType.ClassB) { + // return true; + // } else return false; + // } + + updateMulticast(): void { + this.multicastService.update(this.multicast).subscribe( + (response) => { + console.log(response); + this.router.navigateByUrl('/applications'); + }, + (error: HttpErrorResponse) => { + this.handleError(error); + } + ); + } + createMulticast(): void { + this.multicastService.create(this.multicast).subscribe( + (response) => { + console.log(response); + this.router.navigateByUrl('/multicast-list'); + }, + (error: HttpErrorResponse) => { + this.handleError(error); + } + ); + } + routeBack(): void { + this.router.navigate([ + 'applications', + this.applicationId.toString(), + 'multicast-list', + this.applicationName, + ]); + } + handleError(error: HttpErrorResponse) { + const errors = this.errorMessageService.handleErrorMessageWithFields(error); + this.errorFields = errors.errorFields; + this.errorMessages = errors.errorMessages; + this.scrollToTopService.scrollToTop(); + } + ngOnDestroy(): void { + if (this.multicastSubscription) { + this.multicastSubscription.unsubscribe(); + } + } +} diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.html b/src/app/applications/multicast/multicast-list/multicast-list.component.html new file mode 100644 index 00000000..6ff143d4 --- /dev/null +++ b/src/app/applications/multicast/multicast-list/multicast-list.component.html @@ -0,0 +1,13 @@ + + +
+
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.scss b/src/app/applications/multicast/multicast-list/multicast-list.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts b/src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts new file mode 100644 index 00000000..353f2cb9 --- /dev/null +++ b/src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MulticastListComponent } from './multicast-list.component'; + +describe('MulticastListComponent', () => { + let component: MulticastListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MulticastListComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MulticastListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.ts b/src/app/applications/multicast/multicast-list/multicast-list.component.ts new file mode 100644 index 00000000..a4ad955c --- /dev/null +++ b/src/app/applications/multicast/multicast-list/multicast-list.component.ts @@ -0,0 +1,47 @@ +import { Component, OnInit } from '@angular/core'; +import { Title } from '@angular/platform-browser'; +import { ActivatedRoute } from '@angular/router'; +import { environment } from '@environments/environment'; +import { TranslateService } from '@ngx-translate/core'; +import { BackButton } from '@shared/models/back-button.model'; + +@Component({ + selector: 'app-multicast-list', + templateUrl: './multicast-list.component.html', + styleUrls: ['./multicast-list.component.scss'], +}) +export class MulticastListComponent implements OnInit { + public pageLimit = environment.tablePageSize; + public title: string; + public backButton: BackButton = { label: '', routerLink: '' }; + private applicationId: string; + + constructor( + public translate: TranslateService, + private titleService: Title, + private route: ActivatedRoute + ) { + translate.use('da'); + } + + ngOnInit(): void { + const applicationName: string = this.route.snapshot.paramMap.get('name'); + this.applicationId = this.route.snapshot.paramMap.get('id'); + this.translate + .get(['NAV.MULTICAST', 'NAV.APPLICATIONS', 'TITLE.MULTICAST']) + .subscribe((translate) => { + this.title = translate['NAV.MULTICAST'] + ' - ' + applicationName; + this.backButton.label = translate['NAV.APPLICATIONS']; + this.titleService.setTitle(translate['TITLE.MULTICAST']); + }); + this.setBackButton(); + } + + setBackButton() { + this.backButton.routerLink = ['applications', this.applicationId]; + } + + updatePageLimit(limit: any) { + console.log(limit); + } +} diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.html b/src/app/applications/multicast/multicast-table/multicast-table.component.html new file mode 100644 index 00000000..adfb5208 --- /dev/null +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.html @@ -0,0 +1,53 @@ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+ {{ 'MULTICAST-TABLE.NAME' | translate }} + + {{element.groupName}} + + {{ 'MULTICAST-TABLE.TYPE' | translate }} + + {{element.groupType | translate}} + +
+ + +
\ No newline at end of file diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.scss b/src/app/applications/multicast/multicast-table/multicast-table.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.spec.ts b/src/app/applications/multicast/multicast-table/multicast-table.component.spec.ts new file mode 100644 index 00000000..4a46e62b --- /dev/null +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MulticastTableComponent } from './multicast-table.component'; + +describe('MulticastTableComponent', () => { + let component: MulticastTableComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MulticastTableComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MulticastTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.ts b/src/app/applications/multicast/multicast-table/multicast-table.component.ts new file mode 100644 index 00000000..ff0786ce --- /dev/null +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.ts @@ -0,0 +1,120 @@ +import { + AfterViewInit, + Component, + Input, + OnDestroy, + OnInit, + ViewChild, +} from '@angular/core'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { ActivatedRoute } from '@angular/router'; +import { environment } from '@environments/environment'; +import { TranslateService } from '@ngx-translate/core'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { MulticastType } from '@shared/enums/multicast-type'; +import { tableSorter } from '@shared/helpers/table-sorting.helper'; +import { MeService } from '@shared/services/me.service'; +import { Subscription } from 'rxjs'; +import { Multicast, MulticastData } from '../multicast.model'; +import { MulticastService } from '../multicast.service'; + +@Component({ + selector: 'app-multicast-table', + templateUrl: './multicast-table.component.html', + styleUrls: ['./multicast-table.component.scss'], +}) +export class MulticastTableComponent + implements OnInit, AfterViewInit, OnDestroy { + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + displayedColumns: string[] = ['groupName', 'groupType', 'menu']; + dataSource = new MatTableDataSource(); + multicasts: Multicast[]; + resultsLength = 0; + public canEdit = false; + @Input() isLoadingResults: boolean = true; + public pageSize = environment.tablePageSize; + + @Input() pageLimit: number; + public pageOffset = 0; + public pageTotal: number; + public applicationId: number; + + private multicastSubscription: Subscription; + private deleteDialogSubscription: Subscription; + + constructor( + private route: ActivatedRoute, + private deleteDialogService: DeleteDialogService, + private multicastService: MulticastService, + private meService: MeService, + public translate: TranslateService + ) { + translate.use('da'); + } + + ngOnInit(): void { + this.applicationId = +Number( + this.route.parent.parent.snapshot.paramMap.get('id') + ); + console.log(this.applicationId); + this.getMulticast(); + this.canEdit = this.meService.canWriteInTargetOrganization(); + } + + ngAfterViewInit() { + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + } + + getMulticast(): void { + const appId: number = this.applicationId; + if (appId) { + this.multicastSubscription = this.multicastService + .getByApplicationId( + this.pageLimit, + this.pageOffset * this.pageLimit, + appId + ) + .subscribe((multicasts: MulticastData) => { + this.multicasts = multicasts.data; + this.dataSource = new MatTableDataSource(this.multicasts); + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + this.dataSource.sortingDataAccessor = tableSorter; + this.isLoadingResults = false; + if (this.pageLimit) { + this.pageTotal = Math.ceil(multicasts.count / this.pageLimit); + } + }); + } + } + + deleteMulticast(element: any) { + this.deleteDialogSubscription = this.deleteDialogService + .showSimpleDialog() + .subscribe((response) => { + if (response) { + this.multicastService.delete(element.id).subscribe((response) => { + if (response.ok && response.body.affected > 0) { + this.getMulticast(); + } + }); + } else { + console.log(response); + } + }); + } + + ngOnDestroy() { + // prevent memory leak by unsubscribing + if (this.multicastSubscription) { + this.multicastSubscription.unsubscribe(); + } + if (this.deleteDialogSubscription) { + this.deleteDialogSubscription.unsubscribe(); + } + } +} diff --git a/src/app/applications/multicast/multicast.model.ts b/src/app/applications/multicast/multicast.model.ts new file mode 100644 index 00000000..0f374bcf --- /dev/null +++ b/src/app/applications/multicast/multicast.model.ts @@ -0,0 +1,20 @@ +import { MulticastType } from '@shared/enums/multicast-type'; + +export class Multicast { + id: number; + groupName: string; + address: string; + networkSessionKey: string; + applicationSessionKey: string; + frameCounter: number = 0; + dataRate: number = 0; + frequency: number = 0; + groupType: MulticastType; + // periodicity: number; -> only if classB is gonna be used +} + +export class MulticastData { + data: Multicast[]; + ok?: boolean; + count?: number; +} \ No newline at end of file diff --git a/src/app/applications/multicast/multicast.module.ts b/src/app/applications/multicast/multicast.module.ts new file mode 100644 index 00000000..7f38d76b --- /dev/null +++ b/src/app/applications/multicast/multicast.module.ts @@ -0,0 +1,43 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { MulticastListComponent } from './multicast-list/multicast-list.component'; +import { MulticastDetailComponent } from './multicast-detail/multicast-detail.component'; +import { MulticastEditComponent } from './multicast-edit/multicast-edit.component'; +import { MulticastTableComponent } from './multicast-table/multicast-table.component'; +import { DatatargetModule } from '@applications/datatarget/datatarget.module'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { NGMaterialModule } from '@shared/Modules/materiale.module'; +import { FormModule } from '@shared/components/forms/form.module'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { PipesModule } from '@shared/pipes/pipes.module'; + +@NgModule({ + declarations: [ + MulticastListComponent, + MulticastDetailComponent, + MulticastEditComponent, + MulticastTableComponent, + ], + imports: [ + CommonModule, + RouterModule, + TranslateModule, + FormModule, + NGMaterialModule, + FontAwesomeModule, + ReactiveFormsModule, + FormsModule, + SharedModule, + PipesModule, + ], + exports: [ + MulticastListComponent, + MulticastDetailComponent, + MulticastEditComponent, + MulticastTableComponent, + ], +}) +export class MulticastModule {} diff --git a/src/app/applications/multicast/multicast.service.spec.ts b/src/app/applications/multicast/multicast.service.spec.ts new file mode 100644 index 00000000..c86a61c3 --- /dev/null +++ b/src/app/applications/multicast/multicast.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { MulticastService } from './multicast.service'; + +describe('MulticastService', () => { + let service: MulticastService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(MulticastService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/applications/multicast/multicast.service.ts b/src/app/applications/multicast/multicast.service.ts new file mode 100644 index 00000000..4b083571 --- /dev/null +++ b/src/app/applications/multicast/multicast.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@angular/core'; +import { RestService } from '@shared/services/rest.service'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Multicast, MulticastData } from './multicast.model'; + +@Injectable({ + providedIn: 'root', +}) +export class MulticastService { + constructor(private restService: RestService) {} + + private multicastURL = 'multicast'; + getByApplicationId( + limit: number, + offset: number, + applicationId: number + ): Observable { + const body = { + limit, + offset, + applicationId, + // sort: sort, + // orderOn: orderOn, + // todo tilføj når iot-314 er tilføjet + }; + return this.restService.get(this.multicastURL, body); + } + get(id: number): Observable { + return this.restService.get(this.multicastURL, {}, id).pipe( + map((response: Multicast) => { + return response; + }) + ); + } + delete(id: number) { + return this.restService.delete(this.multicastURL, id); + } + update(multicast: Multicast): Observable { + return this.restService.put(this.multicastURL, multicast, multicast.id, { + observe: 'response', + }); + } + create(multicast: Multicast): Observable { + return this.restService.post(this.multicastURL, multicast, { + observe: 'response', + }); + } +} diff --git a/src/app/shared/enums/multicast-type.ts b/src/app/shared/enums/multicast-type.ts new file mode 100644 index 00000000..c4d895cf --- /dev/null +++ b/src/app/shared/enums/multicast-type.ts @@ -0,0 +1,4 @@ +export enum MulticastType { + // ClassB = 'Class-B', + ClassC = 'Class-C' +} diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 96c8b754..1bc2965d 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -17,6 +17,7 @@ "ALL-IOT-DEVICES": "Alle IoT enheder", "LORA-GATEWAYS": "LoRaWAN gateways", "DATATARGET": "Datatarget", + "MULTICAST": "Multicast", "MY-DATATARGET": "Tilbage", "DATATARGET-APPLIKATION": "for applikation:", "PROFILES": "LoRaWAN profiler", @@ -81,10 +82,12 @@ "SAVE": "Gem applikation", "DELETE": "Slet applikation", "DELETE-HAS-DEVICES-PROMPT": "Der er knyttet IoT-enheder til denne applikation. Disse vil også blive slettet. Slet alligevel?", + "DELETE-HAS-SIGFOX-DEVICES-PROMPT": "Applikationen kan ikke slettes, da der er knyttet Sigfox enheder til den", "NAME": "Applikationens navn", "DESCRIPTION": "Applikationens beskrivelse", "ATTACHED-IOT": "Tilknyttede IoT enheder", "DATATARGET-SHOW": "Tilknyttede data targets", + "MULTICAST-SHOW": "Tilknyttede multicast grupper", "IMPORT-CSV": "Bulk import af IoT enheder", "BULK": { "TEMPLATE": { @@ -153,6 +156,19 @@ "OPENDATA-DK": "OpenDataDK", "NO-OPENDATA-DK": "Der er ikke oprettet nogen datadeling med Open Data DK endnu" }, + "MULTICAST": { + "SAVE": "Gem multicast", + "DETAILS": "Detaljer", + "GROUPNAME": "Gruppe navn", + "ADDRESS": "Adresse", + "NETWORK-KEY": "Network session key", + "APPLICATION-KEY": "Network application key", + "FRAMECOUNTER": "Frame counter", + "DATARATE": "Data rate", + "FREQUENCY": "Frekvens (Hz)", + "GROUPTYPE": "Gruppe type", + "PERIODICITY": "Periodicitet" + }, "OPENDATADK": { "QUESTION": { "GIVE-OPENDATADK-NAME": "Datasæt titel", @@ -240,6 +256,10 @@ "NAME": "Navn", "TYPE": "Type" }, + "MULTICAST-TABLE":{ + "NAME": "Gruppenavn", + "TYPE": "Gruppetype" + }, "IOT-DEVICE-TYPES": { "GENERIC_HTTP": "Generisk HTTP", "LORAWAN": "LoRaWAN", @@ -264,7 +284,13 @@ }, "DATATARGET-TABLE-ROW": { "DELETE": "Slet datatarget", - "EDIT": "Redigér" + "EDIT": "Redigér", + "SHOW-OPTIONS": "Håndter datatarget" + }, + "MULTICAST-TABLE-ROW": { + "DELETE": "Slet multicast", + "EDIT": "Redigér", + "SHOW-OPTIONS": "Håndter multicast" }, "PAYLOAD-DECODER": { "DELETE-FAILED": "Slet fejlede", @@ -364,7 +390,9 @@ "CREATE-NEW-LORA-GATEWAY": "Opret ny LoRaWAN gateway", "CREATE-NEW-DEVICE-MODEL": "Opret ny device model", "CREATE-NEW-DATATARGET": "Opret nyt datatarget", + "CREATE-NEW-MULTICAST": "Opret nyt multicast", "EDIT-DATATARGET": "Redigér datatarget", + "EDIT-MULTICAST": "Redigér multicast", "CREATE-NEW-IOT-DEVICE": "Tilføj en IoT enhed", "EDIT-NEW-GATEWAY": "Redigér LoRaWAN gateway", "EDIT-NEW-APPLICATION": "Redigér applikation", @@ -426,6 +454,22 @@ "GIVE-PAYLOADDECODER-PAYLOAD-INVALID-JSON": "Det angivne JSON var ikke gyldigt i feltet payload", "GIVE-ORGANISATION-NAME": "Navngiv organisation", "GIVE-ORGANISATION-NAME-PLACEHOLDER": "F.eks. 'Aarhus Kommune'", + "GIVE-MULTICAST-NAME":"Navngiv multicast", + "GIVE-MULTICAST-ADDRESS": "Angiv multicast adressen", + "GIVE-MULTICAST-NETWORK-KEY": "Angiv multicast network session key", + "GIVE-MULTICAST-APPLICATION-KEY": "Angiv multicast application session key", + "GIVE-MULTICAST-FRAMECOUNTER": "Angiv frame counter", + "GIVE-MULTICAST-DATARATE": "Angiv data rate", + "GIVE-MULTICAST-FREQUENCY": "Angiv frekvens (Hz)", + "GIVE-MULTICAST-GROUP-TYPE": "Angiv multicast gruppe type", + "GIVE-MULTICAST-PERIODICITY": "Angiv multicast ping-slot periodicitet", + "GIVE-MULTICAST-GROUPTYPE": "Angiv multicast gruppe typen", + "MULTICAST-NAME-PLACEHOLDER": "Multicastens navn", + "MULTICAST-ADDRESS-PLACEHOLDER": "Multicast adressen", + "MULTICAST-NETWORK-KEY-PLACEHOLDER": "Multicastens network session key", + "MULTICAST-APPLICATION-KEY-PLACEHOLDER": "Multicastens application session key", + "MULTICAST-GROUPTYPE-PLACEHOLDER": "Vælg multicast gruppe typen", + "MULTICAST-PERIODICITY-PLACEHOLDER": "Vælg Class-B ping periodiciteten", "OTAAAPPLICATIONKEY": "OTAA application key (AppKey)", "OTAAAPPLICATIONKEY-PLACEHOLDER": "Indtast OTAA application key", "DEVADDR": "Device adress", @@ -697,7 +741,7 @@ "PINGSLOTFREQ": "Class-B ping-slot frequency", "SUPPORTSCLASSC_ACTIVATE": "Device supports Class-C", "CLASSCTIMEOUT": "Class C confirmed downlink timeout", - "CANCEL": "Anuller", + "CANCEL": "Annuller", "SAVE": "Gem", "OTAA-ABP": "Join (OTAA / ABP)", "MACVERSION_PLACEHOLDER": "1.0.0", @@ -821,6 +865,7 @@ "PERMISSION": "OS2IoT - Brugergrupper", "ORGANIZATION": "OS2IoT - Organisationer", "DATATARGET": "OS2IoT - Datatarget", + "MULTICAST": "OS2IoT - Multicast", "BULKIMPORT": "OS2IoT - Bulk import", "IOTDEVICE": "OS2IoT - IoT enhed", "FRONTPAGE": "OS2IoT - Forside" From f73d376deaa9729b1b4cc9540b371b141c5d8ecd Mon Sep 17 00:00:00 2001 From: August Andersen Date: Wed, 17 Nov 2021 16:22:05 +0100 Subject: [PATCH 03/37] Minor changes to make it work with backend. --- .../multicast-edit.component.html | 14 +++++------ .../multicast-edit.component.ts | 25 +++++++++++++++++-- .../applications/multicast/multicast.model.ts | 1 + 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html index 10554638..62f53aea 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html @@ -28,19 +28,19 @@
- * + * + class="form-control" id="networkSessionKey" name="networkSessionKey" [(ngModel)]="multicast.networkSessionKey" + [ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('networkSessionKey'), 'is-valid' : formFailedSubmit && !errorFields.includes('networkSessionKey')}">
- * + * + class="form-control" id="appliciationSessionKey" name="applicationSessionKey" [(ngModel)]="multicast.applicationSessionKey" + [ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('applicationSessionKey'), 'is-valid' : formFailedSubmit && !errorFields.includes('applicationSessionKey')}">
@@ -68,7 +68,7 @@
- + * diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts index 7bf3b22a..ba0740c8 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -91,24 +91,40 @@ export class MulticastEditComponent implements OnInit { // } updateMulticast(): void { + this.resetErrors(); + this.multicast.applicationId = this.applicationId; this.multicastService.update(this.multicast).subscribe( (response) => { console.log(response); - this.router.navigateByUrl('/applications'); + this.router.navigate([ + 'applications', + this.applicationId.toString(), + 'multicast-list', + this.applicationName, + ]); }, (error: HttpErrorResponse) => { this.handleError(error); + this.formFailedSubmit = true; } ); } createMulticast(): void { + this.resetErrors(); + this.multicast.applicationId = this.applicationId; this.multicastService.create(this.multicast).subscribe( (response) => { console.log(response); - this.router.navigateByUrl('/multicast-list'); + this.router.navigate([ + 'applications', + this.applicationId.toString(), + 'multicast-list', + this.applicationName, + ]); }, (error: HttpErrorResponse) => { this.handleError(error); + this.formFailedSubmit = true; } ); } @@ -120,6 +136,11 @@ export class MulticastEditComponent implements OnInit { this.applicationName, ]); } + private resetErrors() { + this.errorFields = []; + this.errorMessages = undefined; + this.formFailedSubmit = false; + } handleError(error: HttpErrorResponse) { const errors = this.errorMessageService.handleErrorMessageWithFields(error); this.errorFields = errors.errorFields; diff --git a/src/app/applications/multicast/multicast.model.ts b/src/app/applications/multicast/multicast.model.ts index 0f374bcf..69976706 100644 --- a/src/app/applications/multicast/multicast.model.ts +++ b/src/app/applications/multicast/multicast.model.ts @@ -2,6 +2,7 @@ import { MulticastType } from '@shared/enums/multicast-type'; export class Multicast { id: number; + applicationId: number; groupName: string; address: string; networkSessionKey: string; From 4b11b86c05887fcf05ba9fe31841a2d6db23f365 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Fri, 19 Nov 2021 11:25:22 +0100 Subject: [PATCH 04/37] PR Changes. Also made the check for sigfox devices in detail page, which i missed before. --- .../application-detail.component.ts | 24 +++++- .../applications-table.component.ts | 73 +++++++------------ 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index 009f3a63..77a56571 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -5,6 +5,7 @@ import { Application } from '@applications/application.model'; import { ApplicationService } from '@applications/application.service'; import { TranslateService } from '@ngx-translate/core'; import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { DeviceType } from '@shared/enums/device-type'; import { BackButton } from '@shared/models/back-button.model'; import { DropdownButton } from '@shared/models/dropdown-button.model'; import { MeService } from '@shared/services/me.service'; @@ -59,12 +60,22 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { } onDeleteApplication() { + let message: string; - if (this.applicationHasDevices()) { - message = this.translate.instant('APPLICATION.DELETE-HAS-DEVICES-PROMPT'); + let showAccept: boolean = true; + const hasSigfoxDevices: boolean = this.applicationHasSigFoxDevices(); + + if (hasSigfoxDevices) { + message = this.translate.instant( + 'APPLICATION.DELETE-HAS-SIGFOX-DEVICES-PROMPT' + ); + showAccept = false; + + } else if (this.applicationHasDevices()) { + message = this.translate.instant('APPLICATION.DELETE-HAS-DEVICES-PROMPT'); } - this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog(message).subscribe( + this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog(message, showAccept).subscribe( (response) => { if (response) { this.applicationService.deleteApplication(this.application.id).subscribe((response) => { @@ -86,6 +97,13 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { return this.application.iotDevices?.length > 0; } + applicationHasSigFoxDevices(): boolean { + const sigfoxDevice = this.application.iotDevices.find((device) => { + return device.type === DeviceType.SIGFOX; + }); + return sigfoxDevice !== undefined; + } + bindApplication(id: number): void { this.applicationsSubscription = this.applicationService.getApplication(id).subscribe((application) => { this.application = application; diff --git a/src/app/applications/applications-list/applications-table/applications-table.component.ts b/src/app/applications/applications-list/applications-table/applications-table.component.ts index 4286f5c9..bfdb8b3f 100644 --- a/src/app/applications/applications-list/applications-table/applications-table.component.ts +++ b/src/app/applications/applications-list/applications-table/applications-table.component.ts @@ -95,30 +95,37 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit { deleteApplication(id: number) { let message: string; + let showAccept: boolean = true; + const hasSigfoxDevices: boolean = this.applicationHasSigFoxDevices(id); - if (this.canBeDeleted(id)) { - return; - } - - if (this.applicationHasDevices(id)) { + if (hasSigfoxDevices) { + message = this.translate.instant( + 'APPLICATION.DELETE-HAS-SIGFOX-DEVICES-PROMPT' + ); + showAccept = false; + } else if (this.applicationHasDevices(id)) { message = this.translate.instant('APPLICATION.DELETE-HAS-DEVICES-PROMPT'); } - this.deleteDialogService.showSimpleDialog(message).subscribe((response) => { - if (response) { - this.applicationService.deleteApplication(id).subscribe((response) => { - if (response.ok && response.body.affected > 0) { - this.paginator.page.emit({ - pageIndex: this.paginator.pageIndex, - pageSize: this.paginator.pageSize, - length: this.resultsLength, + this.deleteDialogService + .showSimpleDialog(message, showAccept) + .subscribe((response) => { + if (response) { + this.applicationService + .deleteApplication(id) + .subscribe((response) => { + if (response.ok && response.body.affected > 0) { + this.paginator.page.emit({ + pageIndex: this.paginator.pageIndex, + pageSize: this.paginator.pageSize, + length: this.resultsLength, + }); + } else { + this.errorMessage = response?.error?.message; + } }); - } else { - this.errorMessage = response?.error?.message; - } - }); - } - }); + } + }); } applicationHasDevices(id: number): boolean { @@ -128,34 +135,10 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit { applicationHasSigFoxDevices(id: number): boolean { const applicationToDelete = this.data?.find((app) => app.id === id); - const checkForSigfox = applicationToDelete.iotDevices.find((device) => { + const sigfoxDevice = applicationToDelete.iotDevices.find((device) => { return device.type === DeviceType.SIGFOX; }); - if (checkForSigfox) { - return true; - } else return false; - } - - canBeDeleted(id: number): boolean { - let message: string; - - if (this.applicationHasSigFoxDevices(id)) { - message = this.translate.instant( - 'APPLICATION.DELETE-HAS-SIGFOX-DEVICES-PROMPT' - ); - this.deleteDialogService - .showSimpleDialog( - message, - false, - true, - false, - this.translate.instant('APPLICATION.DELETE') - ) - .subscribe(); - return true; - } else { - return false; - } + return sigfoxDevice !== undefined; } navigateToEditPage(applicationId: string) { From 6d8c9896b02222604d2d2a9f6174fb57cc147161 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Fri, 19 Nov 2021 12:53:38 +0100 Subject: [PATCH 05/37] Minor changes so detail page will show how updated/created the multicast --- .../datatarget/datatarget.service.ts | 2 +- .../multicast/multicast-response.model.ts | 22 +++++++ .../applications/multicast/multicast.model.ts | 14 ++-- .../multicast/multicast.service.ts | 64 ++++++++++++++++--- 4 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 src/app/applications/multicast/multicast-response.model.ts diff --git a/src/app/applications/datatarget/datatarget.service.ts b/src/app/applications/datatarget/datatarget.service.ts index 72abdb98..74e3688c 100644 --- a/src/app/applications/datatarget/datatarget.service.ts +++ b/src/app/applications/datatarget/datatarget.service.ts @@ -66,7 +66,7 @@ export class DatatargetService { return datatarget; } ) - );; + ); } delete(id: number) { diff --git a/src/app/applications/multicast/multicast-response.model.ts b/src/app/applications/multicast/multicast-response.model.ts new file mode 100644 index 00000000..1b80fbd8 --- /dev/null +++ b/src/app/applications/multicast/multicast-response.model.ts @@ -0,0 +1,22 @@ +import { Application } from '@applications/application.model'; +import { MulticastType } from '@shared/enums/multicast-type'; + +export class MulticastResponse { + id: number; + application: Application; + groupName: string; + address: string; + networkSessionKey: string; + applicationSessionKey: string; + frameCounter: number = 0; + dataRate: number = 0; + frequency: number = 0; + groupType: MulticastType; + // periodicity: number; -> only if classB is gonna be used + createdAt: string; + updatedAt: string; + createdBy: number; + updatedBy: number; + createdByName: string; + updatedByName: string; +} \ No newline at end of file diff --git a/src/app/applications/multicast/multicast.model.ts b/src/app/applications/multicast/multicast.model.ts index 69976706..ec5566e4 100644 --- a/src/app/applications/multicast/multicast.model.ts +++ b/src/app/applications/multicast/multicast.model.ts @@ -12,10 +12,16 @@ export class Multicast { frequency: number = 0; groupType: MulticastType; // periodicity: number; -> only if classB is gonna be used + createdAt: string; + updatedAt: string; + createdBy: number; + updatedBy: number; + createdByName: string; + updatedByName: string; } export class MulticastData { - data: Multicast[]; - ok?: boolean; - count?: number; -} \ No newline at end of file + data: Multicast[]; + ok?: boolean; + count?: number; +} diff --git a/src/app/applications/multicast/multicast.service.ts b/src/app/applications/multicast/multicast.service.ts index 4b083571..44ec3ebb 100644 --- a/src/app/applications/multicast/multicast.service.ts +++ b/src/app/applications/multicast/multicast.service.ts @@ -1,14 +1,19 @@ import { Injectable } from '@angular/core'; +import { UserMinimalService } from '@app/admin/users/user-minimal.service'; import { RestService } from '@shared/services/rest.service'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; +import { MulticastResponse } from './multicast-response.model'; import { Multicast, MulticastData } from './multicast.model'; @Injectable({ providedIn: 'root', }) export class MulticastService { - constructor(private restService: RestService) {} + constructor( + private restService: RestService, + private userMinimalService: UserMinimalService + ) {} private multicastURL = 'multicast'; getByApplicationId( @@ -28,22 +33,61 @@ export class MulticastService { } get(id: number): Observable { return this.restService.get(this.multicastURL, {}, id).pipe( - map((response: Multicast) => { - return response; - }) + map( + (response: MulticastResponse) => { + const datatarget = this.mapToMulticast(response); + return datatarget; + } + ) ); } delete(id: number) { return this.restService.delete(this.multicastURL, id); } update(multicast: Multicast): Observable { - return this.restService.put(this.multicastURL, multicast, multicast.id, { - observe: 'response', - }); + return this.restService.put(this.multicastURL, multicast, multicast.id, { observe: 'response' }).pipe( + map( + (response: MulticastResponse) => { + const datatarget = this.mapToMulticast(response); + return datatarget; + } + ) + ); } create(multicast: Multicast): Observable { - return this.restService.post(this.multicastURL, multicast, { - observe: 'response', - }); + return this.restService.post(this.multicastURL, multicast).pipe( + map( + (response: MulticastResponse) => { + const datatarget = this.mapToMulticast(response); + return datatarget; + } + ) + ); + } + + private mapToMulticast(multicastResponse: MulticastResponse): Multicast { + const model: Multicast = { + id: multicastResponse.id, + groupName: multicastResponse.groupName, + groupType: multicastResponse.groupType, + address: multicastResponse.address, + applicationSessionKey: multicastResponse.applicationSessionKey, + dataRate: multicastResponse.dataRate, + frameCounter: multicastResponse.frameCounter, + frequency: multicastResponse.frequency, + networkSessionKey: multicastResponse.networkSessionKey, + applicationId: multicastResponse.application.id, + createdAt: multicastResponse.createdAt, + updatedAt: multicastResponse.updatedAt, + createdBy: multicastResponse.createdBy, + updatedBy: multicastResponse.updatedBy, + createdByName: this.userMinimalService.getUserNameFrom( + multicastResponse.createdBy + ), + updatedByName: this.userMinimalService.getUserNameFrom( + multicastResponse.updatedBy + ), + }; + return model; } } From 0ca152605bde25be7fbea66498deb61505975d2d Mon Sep 17 00:00:00 2001 From: August Andersen Date: Mon, 22 Nov 2021 11:23:52 +0100 Subject: [PATCH 06/37] PR Changes + formatting --- .../application-detail.component.ts | 178 ++++++++---------- .../applications-table.component.ts | 29 +-- .../delete-dialog/delete-dialog.service.ts | 79 ++++++-- 3 files changed, 144 insertions(+), 142 deletions(-) diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index 77a56571..bd2c124d 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -1,4 +1,10 @@ -import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; +import { + Component, + EventEmitter, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { Application } from '@applications/application.model'; @@ -12,110 +18,92 @@ import { MeService } from '@shared/services/me.service'; import { Subscription } from 'rxjs'; @Component({ - selector: 'app-application', - templateUrl: './application-detail.component.html', - styleUrls: ['./application-detail.component.scss'], + selector: 'app-application', + templateUrl: './application-detail.component.html', + styleUrls: ['./application-detail.component.scss'], }) export class ApplicationDetailComponent implements OnInit, OnDestroy { - @Output() deleteApplication = new EventEmitter(); - public applicationsSubscription: Subscription; - private deleteDialogSubscription: Subscription; - public application: Application; - public backButton: BackButton = { label: '', routerLink: '/applications' }; - public id: number; - public dropdownButton: DropdownButton; - public errorMessage: string; - public canEdit = false; + @Output() deleteApplication = new EventEmitter(); + public applicationsSubscription: Subscription; + public application: Application; + public backButton: BackButton = { label: '', routerLink: '/applications' }; + public id: number; + public dropdownButton: DropdownButton; + public errorMessage: string; + public canEdit = false; - constructor( - private applicationService: ApplicationService, - private route: ActivatedRoute, - public translate: TranslateService, - public router: Router, - private meService: MeService, - private titleService: Title, - private deleteDialogService: DeleteDialogService - ) { } + constructor( + private applicationService: ApplicationService, + private route: ActivatedRoute, + public translate: TranslateService, + public router: Router, + private meService: MeService, + private titleService: Title, + private deleteDialogService: DeleteDialogService + ) {} - ngOnInit(): void { - this.id = +this.route.snapshot.paramMap.get('id'); - if (this.id) { - this.bindApplication(this.id); - this.dropdownButton = { - label: '', - editRouterLink: '../../edit-application/' + this.id, - isErasable: true, - }; + ngOnInit(): void { + this.id = +this.route.snapshot.paramMap.get('id'); + if (this.id) { + this.bindApplication(this.id); + this.dropdownButton = { + label: '', + editRouterLink: '../../edit-application/' + this.id, + isErasable: true, + }; - console.log(this.id); - } - - this.translate.get(['NAV.APPLICATIONS', 'APPLICATION-TABLE-ROW.SHOW-OPTIONS', 'TITLE.APPLICATION']) - .subscribe(translations => { - this.backButton.label = translations['NAV.APPLICATIONS']; - this.dropdownButton.label = translations['APPLICATION-TABLE-ROW.SHOW-OPTIONS']; - this.titleService.setTitle(translations['TITLE.APPLICATION']); - }); - this.canEdit = this.meService.canWriteInTargetOrganization(); + console.log(this.id); } - onDeleteApplication() { - - let message: string; - let showAccept: boolean = true; - const hasSigfoxDevices: boolean = this.applicationHasSigFoxDevices(); - - if (hasSigfoxDevices) { - message = this.translate.instant( - 'APPLICATION.DELETE-HAS-SIGFOX-DEVICES-PROMPT' - ); - showAccept = false; + this.translate + .get([ + 'NAV.APPLICATIONS', + 'APPLICATION-TABLE-ROW.SHOW-OPTIONS', + 'TITLE.APPLICATION', + ]) + .subscribe((translations) => { + this.backButton.label = translations['NAV.APPLICATIONS']; + this.dropdownButton.label = + translations['APPLICATION-TABLE-ROW.SHOW-OPTIONS']; + this.titleService.setTitle(translations['TITLE.APPLICATION']); + }); + this.canEdit = this.meService.canWriteInTargetOrganization(); + } - } else if (this.applicationHasDevices()) { - message = this.translate.instant('APPLICATION.DELETE-HAS-DEVICES-PROMPT'); + onDeleteApplication() { + this.deleteDialogService + .showApplicationDialog(this.application) + .subscribe((response) => { + if (response) { + this.applicationService + .deleteApplication(this.application.id) + .subscribe((response) => { + if (response.ok && response.body.affected > 0) { + console.log( + 'delete application with id:' + this.application.id.toString() + ); + this.router.navigate(['applications']); + } else { + this.errorMessage = response?.error?.message; + } + }); + } else { + console.log(response); } + }); + } - this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog(message, showAccept).subscribe( - (response) => { - if (response) { - this.applicationService.deleteApplication(this.application.id).subscribe((response) => { - if (response.ok && response.body.affected > 0) { - console.log('delete application with id:' + this.application.id.toString()); - this.router.navigate(['applications']); - } else { - this.errorMessage = response?.error?.message; - } - }); - } else { - console.log(response); - } - } - ); - } - - applicationHasDevices(): boolean { - return this.application.iotDevices?.length > 0; - } - - applicationHasSigFoxDevices(): boolean { - const sigfoxDevice = this.application.iotDevices.find((device) => { - return device.type === DeviceType.SIGFOX; - }); - return sigfoxDevice !== undefined; - } + bindApplication(id: number): void { + this.applicationsSubscription = this.applicationService + .getApplication(id) + .subscribe((application) => { + this.application = application; + }); + } - bindApplication(id: number): void { - this.applicationsSubscription = this.applicationService.getApplication(id).subscribe((application) => { - this.application = application; - }); - } - - ngOnDestroy() { - if (this.applicationsSubscription) { - this.applicationsSubscription.unsubscribe(); - } - if (this.deleteDialogSubscription) { - this.deleteDialogSubscription.unsubscribe(); - } + ngOnDestroy() { + if (this.applicationsSubscription) { + this.applicationsSubscription.unsubscribe(); } + } } diff --git a/src/app/applications/applications-list/applications-table/applications-table.component.ts b/src/app/applications/applications-list/applications-table/applications-table.component.ts index bfdb8b3f..a459055d 100644 --- a/src/app/applications/applications-list/applications-table/applications-table.component.ts +++ b/src/app/applications/applications-list/applications-table/applications-table.component.ts @@ -94,21 +94,10 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit { } deleteApplication(id: number) { - let message: string; - let showAccept: boolean = true; - const hasSigfoxDevices: boolean = this.applicationHasSigFoxDevices(id); - - if (hasSigfoxDevices) { - message = this.translate.instant( - 'APPLICATION.DELETE-HAS-SIGFOX-DEVICES-PROMPT' - ); - showAccept = false; - } else if (this.applicationHasDevices(id)) { - message = this.translate.instant('APPLICATION.DELETE-HAS-DEVICES-PROMPT'); - } + const applicationToDelete = this.data?.find((app) => app.id === id); this.deleteDialogService - .showSimpleDialog(message, showAccept) + .showApplicationDialog(applicationToDelete) .subscribe((response) => { if (response) { this.applicationService @@ -127,20 +116,6 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit { } }); } - - applicationHasDevices(id: number): boolean { - const applicationToDelete = this.data?.find((app) => app.id === id); - return applicationToDelete && applicationToDelete.iotDevices.length > 0; - } - - applicationHasSigFoxDevices(id: number): boolean { - const applicationToDelete = this.data?.find((app) => app.id === id); - const sigfoxDevice = applicationToDelete.iotDevices.find((device) => { - return device.type === DeviceType.SIGFOX; - }); - return sigfoxDevice !== undefined; - } - navigateToEditPage(applicationId: string) { this.router.navigate(['applications', 'edit-application', applicationId]); } diff --git a/src/app/shared/components/delete-dialog/delete-dialog.service.ts b/src/app/shared/components/delete-dialog/delete-dialog.service.ts index 6bb51bcc..90a6257a 100644 --- a/src/app/shared/components/delete-dialog/delete-dialog.service.ts +++ b/src/app/shared/components/delete-dialog/delete-dialog.service.ts @@ -1,33 +1,72 @@ import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { DeleteDialogComponent } from './delete-dialog.component'; -import { Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; +import { Application } from '@applications/application.model'; +import { DeviceType } from '@shared/enums/device-type'; +import { TranslateService } from '@ngx-translate/core'; @Injectable({ providedIn: 'root', }) export class DeleteDialogService { - + private deleteDialogSubscription: Subscription; constructor( - private dialog: MatDialog - ) { } + private dialog: MatDialog, + public translate: TranslateService, + ) {} + + showSimpleDialog( + message?: string, + showAccept = true, + showCancel = true, + showOk = false, + infoTitle = '' + ): Observable { + return new Observable((observer) => { + const dialog = this.dialog.open(DeleteDialogComponent, { + data: { + infoTitle, + showOk, + showAccept, + showCancel, + message: message ? message : 'Er du sikker på at du vil slette?', + }, + }); + + dialog.afterClosed().subscribe((result) => { + observer.next(result); + }); + }); + } - showSimpleDialog(message?: string, showAccept = true, showCancel = true, showOk = false, infoTitle = ''): Observable { - return new Observable( - (observer) => { - const dialog = this.dialog.open(DeleteDialogComponent, { - data: { - infoTitle, - showOk, - showAccept, - showCancel, - message: message ? message : 'Er du sikker på at du vil slette?' - } - }); + showApplicationDialog(application: Application): Observable { + let message: string; + let showAccept: boolean = true; + const hasSigfoxDevices: boolean = this.applicationHasSigFoxDevices( + application + ); - dialog.afterClosed().subscribe((result) => { - observer.next(result); - }); + if (hasSigfoxDevices) { + message = this.translate.instant( + 'APPLICATION.DELETE-HAS-SIGFOX-DEVICES-PROMPT' + ); + showAccept = false; + } else if (this.applicationHasDevices(application)) { + message = this.translate.instant( + 'APPLICATION.DELETE-HAS-DEVICES-PROMPT' + ); } - ); + return this.showSimpleDialog(message, showAccept); + } + + applicationHasDevices(application: Application): boolean { + return application.iotDevices?.length > 0; + } + + applicationHasSigFoxDevices(application: Application): boolean { + const sigfoxDevice = application.iotDevices.find((device) => { + return device.type === DeviceType.SIGFOX; + }); + return sigfoxDevice !== undefined; } } From 16038ff6c64b8857a368d492685f8106a559298a Mon Sep 17 00:00:00 2001 From: August Andersen Date: Tue, 23 Nov 2021 14:16:00 +0100 Subject: [PATCH 07/37] Moved two buttons, multicast and datatargets, to a tab bar in application details. --- .../application-detail.component.html | 81 ++++-- .../application-detail.component.ts | 6 + .../applications-routing.module.ts | 30 +- .../datatarget-detail.component.ts | 2 +- .../datatarget-edit.component.ts | 7 +- .../datatarget-list.component.spec.ts | 40 +-- .../datatarget-list.component.ts | 84 +++--- .../datatarget-table.component.ts | 2 +- .../datatarget/datatarget.module.ts | 3 - .../multicast-detail.component.ts | 27 +- .../multicast-edit.component.html | 267 +++++++++++++----- .../multicast-edit.component.ts | 46 +-- .../multicast-list.component.html | 24 +- .../multicast-list.component.spec.ts | 40 +-- .../multicast-list.component.ts | 84 +++--- .../multicast-table.component.ts | 26 +- .../multicast/multicast.module.ts | 4 - .../payload-decoder-edit.component.ts | 4 +- src/app/shared/services/save-snack.service.ts | 17 -- src/app/shared/services/snack.service.ts | 39 +++ src/assets/i18n/da.json | 11 +- 21 files changed, 532 insertions(+), 312 deletions(-) delete mode 100644 src/app/shared/services/save-snack.service.ts create mode 100644 src/app/shared/services/snack.service.ts diff --git a/src/app/applications/application-detail/application-detail.component.html b/src/app/applications/application-detail/application-detail.component.html index 647d2c1c..d044ddea 100644 --- a/src/app/applications/application-detail/application-detail.component.html +++ b/src/app/applications/application-detail/application-detail.component.html @@ -1,8 +1,7 @@
+ [addDetailDowndown]="true" [dropDownButton]="dropdownButton" (deleteSelectedInDropdown)="onDeleteApplication()" + [pageLimit]="pageLimit" (updatePageLimit)="updatePageLimit($event)">
@@ -19,22 +18,68 @@

Detaljer

-
-
-
-
- - + + +
+
+
+
+
+ + +
+ + + + + +
+
- - - - -
-
-
+ + +
+
+
+
+
+ + +
+ + + + + +
+
+
+
+
+ +
+
+
+
+
+ + +
+ + + + + +
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index 009f3a63..168f6ac8 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -3,6 +3,7 @@ import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { Application } from '@applications/application.model'; import { ApplicationService } from '@applications/application.service'; +import { environment } from '@environments/environment'; import { TranslateService } from '@ngx-translate/core'; import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; import { BackButton } from '@shared/models/back-button.model'; @@ -22,6 +23,7 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { public application: Application; public backButton: BackButton = { label: '', routerLink: '/applications' }; public id: number; + public pageLimit = environment.tablePageSize; public dropdownButton: DropdownButton; public errorMessage: string; public canEdit = false; @@ -86,6 +88,7 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { return this.application.iotDevices?.length > 0; } + bindApplication(id: number): void { this.applicationsSubscription = this.applicationService.getApplication(id).subscribe((application) => { this.application = application; @@ -100,4 +103,7 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { this.deleteDialogSubscription.unsubscribe(); } } + updatePageLimit(limit: any) { + console.log(limit); + } } diff --git a/src/app/applications/applications-routing.module.ts b/src/app/applications/applications-routing.module.ts index d48876b1..d5dcd3eb 100644 --- a/src/app/applications/applications-routing.module.ts +++ b/src/app/applications/applications-routing.module.ts @@ -7,10 +7,8 @@ import { ApplicationsComponent } from './applications.component'; import { IoTDeviceDetailComponent } from './iot-devices/iot-device-detail/iot-device-detail.component'; import { IotDeviceEditComponent } from './iot-devices/iot-device-edit/iot-device-edit.component'; import { DatatargetEditComponent } from './datatarget/datatarget-edit/datatarget-edit.component'; -import { DatatargetListComponent } from './datatarget/datatarget-list/datatarget-list.component'; import { DatatargetDetailComponent } from './datatarget/datatarget-detail/datatarget-detail.component'; import { BulkImportComponent } from './bulk-import/bulk-import.component'; -import { MulticastListComponent } from './multicast/multicast-list/multicast-list.component'; import { MulticastEditComponent } from './multicast/multicast-edit/multicast-edit.component'; import { MulticastDetailComponent } from './multicast/multicast-detail/multicast-detail.component'; @@ -29,26 +27,16 @@ const applicationRoutes: Routes = [ { path: '', component: ApplicationDetailComponent }, { path: 'new-iot-device', component: IotDeviceEditComponent, }, { path: 'iot-device-edit/:deviceId', component: IotDeviceEditComponent, }, - { path: 'iot-device/:deviceId', component: IoTDeviceDetailComponent, }, - { - path: 'datatarget-list/:name', - children: [ - { path: '', component: DatatargetListComponent }, - { path: 'datatarget-edit', component: DatatargetEditComponent }, - { path: 'datatarget-edit/:datatargetId', component: DatatargetEditComponent }, - { path: 'datatarget/:datatargetId', component: DatatargetDetailComponent } - ] + { path: 'iot-device/:deviceId', component: IoTDeviceDetailComponent, }, - }, - { - path: 'multicast-list/:name', - children: [ - { path: '', component: MulticastListComponent }, - { path: 'multicast-edit', component: MulticastEditComponent}, - { path: 'multicast-edit/:multicastId', component: MulticastEditComponent }, - { path: 'multicast/:multicastId', component: MulticastDetailComponent } - ] - }, + { path: 'datatarget-edit', component: DatatargetEditComponent }, + { path: 'datatarget-edit/:datatargetId', component: DatatargetEditComponent }, + { path: 'datatarget/:datatargetId', component: DatatargetDetailComponent }, + + { path: 'multicast-edit', component: MulticastEditComponent}, + { path: 'multicast-edit/:multicastId', component: MulticastEditComponent }, + { path: 'multicast/:multicastId', component: MulticastDetailComponent }, + { path: 'bulk-import', component: BulkImportComponent }, ], }, diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts index 7af25889..34aa9507 100644 --- a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts @@ -65,7 +65,7 @@ export class DatatargetDetailComponent implements OnInit, OnDestroy { } private setBackButton(applicationId: number) { - this.backButton.routerLink = ['applications', applicationId.toString(), 'datatarget-list', this.applicationName ] + this.backButton.routerLink = ['applications', applicationId.toString()] } onDeleteDatatarget() { diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts index 568dfb77..fa1f81f9 100644 --- a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts @@ -11,7 +11,7 @@ import { DatatargetService } from '../datatarget.service'; import { ApplicationService } from '@applications/application.service'; import { PayloadDecoderService } from '@payload-decoder/payload-decoder.service'; import { PayloadDeviceDatatargetService } from '@payload-decoder/payload-device-datatarget.service'; -import { SaveSnackService } from '@shared/services/save-snack.service'; +import { SnackService } from '@shared/services/snack.service'; import { MatDialog } from '@angular/material/dialog'; import { HttpErrorResponse } from '@angular/common/http'; import { PayloadDecoderMappedResponse } from '@payload-decoder/payload-decoder.model'; @@ -63,7 +63,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { private applicationService: ApplicationService, private payloadDecoderService: PayloadDecoderService, private payloadDeviceDataTargetService: PayloadDeviceDatatargetService, - private saveSnackService: SaveSnackService, + private saveSnackService: SnackService, private dialog: MatDialog, private errorMessageService: ErrorMessageService, private opendatadkService: OpendatadkService, @@ -237,6 +237,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { this.datatarget.openDataDkDataset.acceptTerms = true; } this.showSavedSnack(); + this.routeToDatatargets(); }, (error: HttpErrorResponse) => { this.checkDataTargetModelOpendatadkdatasaet(); @@ -288,7 +289,7 @@ export class DatatargetEditComponent implements OnInit, OnDestroy { } routeToDatatargets(): void { - this.router.navigate(['applications',this.applicationId.toString(),'datatarget-list', this.applicationName]) + this.router.navigate(['applications',this.applicationId.toString()]) } onCoordinateKey(event: any) { diff --git a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts index 7b515efa..a3455dce 100644 --- a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts +++ b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts @@ -1,25 +1,25 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +// import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { DatatargetListComponent } from './datatarget-list.component'; +// import { DatatargetListComponent } from './datatarget-list.component'; -describe('DatatargetListComponent', () => { - let component: DatatargetListComponent; - let fixture: ComponentFixture; +// describe('DatatargetListComponent', () => { +// let component: DatatargetListComponent; +// let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ DatatargetListComponent ] - }) - .compileComponents(); - })); +// beforeEach(async(() => { +// TestBed.configureTestingModule({ +// declarations: [ DatatargetListComponent ] +// }) +// .compileComponents(); +// })); - beforeEach(() => { - fixture = TestBed.createComponent(DatatargetListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); +// beforeEach(() => { +// fixture = TestBed.createComponent(DatatargetListComponent); +// component = fixture.componentInstance; +// fixture.detectChanges(); +// }); - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); +// it('should create', () => { +// expect(component).toBeTruthy(); +// }); +// }); diff --git a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts index 2f5236cd..cf6ce46d 100644 --- a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts +++ b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts @@ -1,49 +1,49 @@ -import { Component, OnInit, Input } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; -import { ActivatedRoute } from '@angular/router'; -import { Datatarget } from '../datatarget.model'; -import { BackButton } from '@shared/models/back-button.model'; -import { environment } from '@environments/environment'; -import { Title } from '@angular/platform-browser'; +// import { Component, OnInit, Input } from '@angular/core'; +// import { TranslateService } from '@ngx-translate/core'; +// import { ActivatedRoute } from '@angular/router'; +// import { Datatarget } from '../datatarget.model'; +// import { BackButton } from '@shared/models/back-button.model'; +// import { environment } from '@environments/environment'; +// import { Title } from '@angular/platform-browser'; -@Component({ - selector: 'a[app-datatarget-list]', - templateUrl: './datatarget-list.component.html', - styleUrls: ['./datatarget-list.component.scss'] -}) -export class DatatargetListComponent implements OnInit { +// @Component({ +// selector: 'a[app-datatarget-list]', +// templateUrl: './datatarget-list.component.html', +// styleUrls: ['./datatarget-list.component.scss'] +// }) +// export class DatatargetListComponent implements OnInit { - public pageLimit = environment.tablePageSize; - public title: string; - public backButton: BackButton = { label: '', routerLink: ''}; - public datatarget: Datatarget; - private applikationId: string; +// public pageLimit = environment.tablePageSize; +// public title: string; +// public backButton: BackButton = { label: '', routerLink: ''}; +// public datatarget: Datatarget; +// private applikationId: string; - constructor( - public translate: TranslateService, - private titleService: Title, - private route: ActivatedRoute) { - translate.use('da'); - } +// constructor( +// public translate: TranslateService, +// private titleService: Title, +// private route: ActivatedRoute) { +// translate.use('da'); +// } - ngOnInit(): void { - const applikationName: string = this.route.snapshot.paramMap.get('name'); - this.applikationId = this.route.snapshot.paramMap.get('id'); - this.translate.get(["NAV.DATATARGET", "NAV.APPLICATIONS", "TITLE.DATATARGET"]) - .subscribe((translate) => { - this.title = translate['NAV.DATATARGET'] + ' - ' + applikationName; - this.backButton.label = translate['NAV.APPLICATIONS']; - this.titleService.setTitle(translate['TITLE.DATATARGET']); - }); - this.setBackButton() - } +// ngOnInit(): void { +// const applikationName: string = this.route.snapshot.paramMap.get('name'); +// this.applikationId = this.route.snapshot.paramMap.get('id'); +// this.translate.get(["NAV.DATATARGET", "NAV.APPLICATIONS", "TITLE.DATATARGET"]) +// .subscribe((translate) => { +// this.title = translate['NAV.DATATARGET'] + ' - ' + applikationName; +// this.backButton.label = translate['NAV.APPLICATIONS']; +// this.titleService.setTitle(translate['TITLE.DATATARGET']); +// }); +// this.setBackButton() +// } - setBackButton() { - this.backButton.routerLink = ['applications', this.applikationId]; - } +// setBackButton() { +// this.backButton.routerLink = ['applications', this.applikationId]; +// } - updatePageLimit(limit: any) { - console.log(limit); - } -} +// updatePageLimit(limit: any) { +// console.log(limit); +// } +// } diff --git a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts index c3bbc0df..fa4eb35d 100644 --- a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts +++ b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts @@ -46,7 +46,7 @@ export class DatatargetTableComponent implements OnInit, AfterViewInit, OnDestro } ngOnInit(): void { - this.applicationId = +Number(this.route.parent.parent.snapshot.paramMap.get('id')); + this.applicationId = +Number(this.route.parent.snapshot.paramMap.get('id')); console.log(this.applicationId); this.getDatatarget(); this.canEdit = this.meService.canWriteInTargetOrganization() diff --git a/src/app/applications/datatarget/datatarget.module.ts b/src/app/applications/datatarget/datatarget.module.ts index 58bd4078..46be0a33 100644 --- a/src/app/applications/datatarget/datatarget.module.ts +++ b/src/app/applications/datatarget/datatarget.module.ts @@ -1,7 +1,6 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { DatatargetTableComponent } from './datatarget-table/datatarget-table.component'; -import { DatatargetListComponent } from './datatarget-list/datatarget-list.component'; import { DatatargetEditComponent } from './datatarget-edit/datatarget-edit.component'; import { DatatargetDetailComponent } from './datatarget-detail/datatarget-detail.component'; import { TranslateModule } from '@ngx-translate/core'; @@ -19,7 +18,6 @@ import { PipesModule } from '@shared/pipes/pipes.module'; @NgModule({ declarations: [ DatatargetTableComponent, - DatatargetListComponent, DatatargetEditComponent, DatatargetDetailComponent, OpendatadkComponent, @@ -39,7 +37,6 @@ import { PipesModule } from '@shared/pipes/pipes.module'; ], exports: [ DatatargetTableComponent, - DatatargetListComponent, DatatargetEditComponent, DatatargetDetailComponent, NGMaterialModule diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts index 9e480e8d..3676f02f 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts @@ -8,6 +8,7 @@ import { Subscription } from 'rxjs'; import { Multicast } from '../multicast.model'; import { Location } from '@angular/common'; import { MulticastService } from '../multicast.service'; +import { SnackService } from '@shared/services/snack.service'; @Component({ selector: 'app-multicast-detail', @@ -28,7 +29,8 @@ export class MulticastDetailComponent implements OnInit { private deleteDialogService: DeleteDialogService, private location: Location, private multicastService: MulticastService, - public translate: TranslateService + public translate: TranslateService, + public snackService: SnackService ) {} ngOnInit(): void { @@ -62,9 +64,7 @@ export class MulticastDetailComponent implements OnInit { private setBackButton(applicationId: number) { this.backButton.routerLink = [ 'applications', - applicationId.toString(), - 'multicast-list', - this.applicationName, + applicationId.toString() ]; } @@ -82,14 +82,29 @@ export class MulticastDetailComponent implements OnInit { if (response) { this.multicastService .delete(this.multicast.id) - .subscribe((response) => {}); - this.location.back(); + .subscribe((response) => { + if (response.status !== 0) { + this.showDeletedSnack(); + this.location.back(); + } + else{ + this.showFailSnack(); + } + }); } else { console.log(response); } }); } + showDeletedSnack(): void { + this.snackService.showDeletedSnack(); + } + + showFailSnack(): void{ + this.snackService.showFailSnack(); + } + ngOnDestroy(): void { if (this.deleteDialogSubscription) { this.deleteDialogSubscription.unsubscribe(); diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html index 62f53aea..95dca867 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html @@ -1,84 +1,196 @@
-
-
    -
  • - {{error | translate}} -
  • -
-
+
+
    +
  • + {{ error | translate }} +
  • +
+
+
+
+ * + +
-
-
- * - -
- -
- * - -
+
+ * + +
-
- * - -
+
+ * + +
-
- * - -
+
+ * + +
-
- * - -
+
+ * + +
-
- * - -
+
+ * + +
-
- * - -
+
+ * + +
-
- * - - - {{multicastType}} - - -
+
+ * + + + {{ multicastType }} + + +
- - + -
- +
-
- - -
-
\ No newline at end of file +
+ + +
+ diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts index ba0740c8..435f3e7a 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -4,10 +4,12 @@ import { ActivatedRoute, Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { MulticastType } from '@shared/enums/multicast-type'; import { ErrorMessageService } from '@shared/error-message.service'; +import { SnackService } from '@shared/services/snack.service'; import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; import { Subscription } from 'rxjs'; import { Multicast } from '../multicast.model'; import { MulticastService } from '../multicast.service'; +import { THIS_EXPR } from '@angular/compiler/src/output/output_ast'; @Component({ selector: 'app-multicast-edit', @@ -36,7 +38,8 @@ export class MulticastEditComponent implements OnInit { private router: Router, public multicastService: MulticastService, public errorMessageService: ErrorMessageService, - public scrollToTopService: ScrollToTopService + public scrollToTopService: ScrollToTopService, + public snackService: SnackService ) {} ngOnInit(): void { @@ -95,13 +98,8 @@ export class MulticastEditComponent implements OnInit { this.multicast.applicationId = this.applicationId; this.multicastService.update(this.multicast).subscribe( (response) => { - console.log(response); - this.router.navigate([ - 'applications', - this.applicationId.toString(), - 'multicast-list', - this.applicationName, - ]); + this.showUpdatedSnack(); + this.routeBack(); }, (error: HttpErrorResponse) => { this.handleError(error); @@ -114,13 +112,8 @@ export class MulticastEditComponent implements OnInit { this.multicast.applicationId = this.applicationId; this.multicastService.create(this.multicast).subscribe( (response) => { - console.log(response); - this.router.navigate([ - 'applications', - this.applicationId.toString(), - 'multicast-list', - this.applicationName, - ]); + this.showSavedSnack(); + this.routeBack(); }, (error: HttpErrorResponse) => { this.handleError(error); @@ -129,12 +122,23 @@ export class MulticastEditComponent implements OnInit { ); } routeBack(): void { - this.router.navigate([ - 'applications', - this.applicationId.toString(), - 'multicast-list', - this.applicationName, - ]); + this.router.navigate(['applications', this.applicationId.toString()]); + } + showSavedSnack() { + this.snackService.showSavedSnack(); + } + showUpdatedSnack() { + this.snackService.showUpdatedSnack(); + } + keyPressAlphaNumeric(event) { + var inp = String.fromCharCode(event.keyCode); + + if (/[a-zA-Z0-9]/.test(inp)) { + return true; + } else { + event.preventDefault(); + return false; + } } private resetErrors() { this.errorFields = []; diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.html b/src/app/applications/multicast/multicast-list/multicast-list.component.html index 6ff143d4..52e442c4 100644 --- a/src/app/applications/multicast/multicast-list/multicast-list.component.html +++ b/src/app/applications/multicast/multicast-list/multicast-list.component.html @@ -1,13 +1,19 @@ - +
-
-
-
- -
+
+
+
+
-
\ No newline at end of file +
+
diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts b/src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts index 353f2cb9..ab2d625e 100644 --- a/src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts +++ b/src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts @@ -1,25 +1,25 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +// import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MulticastListComponent } from './multicast-list.component'; +// import { MulticastListComponent } from './multicast-list.component'; -describe('MulticastListComponent', () => { - let component: MulticastListComponent; - let fixture: ComponentFixture; +// describe('MulticastListComponent', () => { +// let component: MulticastListComponent; +// let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ MulticastListComponent ] - }) - .compileComponents(); - })); +// beforeEach(async(() => { +// TestBed.configureTestingModule({ +// declarations: [ MulticastListComponent ] +// }) +// .compileComponents(); +// })); - beforeEach(() => { - fixture = TestBed.createComponent(MulticastListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); +// beforeEach(() => { +// fixture = TestBed.createComponent(MulticastListComponent); +// component = fixture.componentInstance; +// fixture.detectChanges(); +// }); - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); +// it('should create', () => { +// expect(component).toBeTruthy(); +// }); +// }); diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.ts b/src/app/applications/multicast/multicast-list/multicast-list.component.ts index a4ad955c..b0b338b5 100644 --- a/src/app/applications/multicast/multicast-list/multicast-list.component.ts +++ b/src/app/applications/multicast/multicast-list/multicast-list.component.ts @@ -1,47 +1,47 @@ -import { Component, OnInit } from '@angular/core'; -import { Title } from '@angular/platform-browser'; -import { ActivatedRoute } from '@angular/router'; -import { environment } from '@environments/environment'; -import { TranslateService } from '@ngx-translate/core'; -import { BackButton } from '@shared/models/back-button.model'; +// import { Component, OnInit } from '@angular/core'; +// import { Title } from '@angular/platform-browser'; +// import { ActivatedRoute } from '@angular/router'; +// import { environment } from '@environments/environment'; +// import { TranslateService } from '@ngx-translate/core'; +// import { BackButton } from '@shared/models/back-button.model'; -@Component({ - selector: 'app-multicast-list', - templateUrl: './multicast-list.component.html', - styleUrls: ['./multicast-list.component.scss'], -}) -export class MulticastListComponent implements OnInit { - public pageLimit = environment.tablePageSize; - public title: string; - public backButton: BackButton = { label: '', routerLink: '' }; - private applicationId: string; +// @Component({ +// selector: 'app-multicast-list', +// templateUrl: './multicast-list.component.html', +// styleUrls: ['./multicast-list.component.scss'], +// }) +// export class MulticastListComponent implements OnInit { +// public pageLimit = environment.tablePageSize; +// public title: string; +// public backButton: BackButton = { label: '', routerLink: '' }; +// private applicationId: string; - constructor( - public translate: TranslateService, - private titleService: Title, - private route: ActivatedRoute - ) { - translate.use('da'); - } +// constructor( +// public translate: TranslateService, +// private titleService: Title, +// private route: ActivatedRoute +// ) { +// translate.use('da'); +// } - ngOnInit(): void { - const applicationName: string = this.route.snapshot.paramMap.get('name'); - this.applicationId = this.route.snapshot.paramMap.get('id'); - this.translate - .get(['NAV.MULTICAST', 'NAV.APPLICATIONS', 'TITLE.MULTICAST']) - .subscribe((translate) => { - this.title = translate['NAV.MULTICAST'] + ' - ' + applicationName; - this.backButton.label = translate['NAV.APPLICATIONS']; - this.titleService.setTitle(translate['TITLE.MULTICAST']); - }); - this.setBackButton(); - } +// ngOnInit(): void { +// const applicationName: string = this.route.snapshot.paramMap.get('name'); +// this.applicationId = this.route.snapshot.paramMap.get('id'); +// this.translate +// .get(['NAV.MULTICAST', 'NAV.APPLICATIONS', 'TITLE.MULTICAST']) +// .subscribe((translate) => { +// this.title = translate['NAV.MULTICAST'] + ' - ' + applicationName; +// this.backButton.label = translate['NAV.APPLICATIONS']; +// this.titleService.setTitle(translate['TITLE.MULTICAST']); +// }); +// this.setBackButton(); +// } - setBackButton() { - this.backButton.routerLink = ['applications', this.applicationId]; - } +// setBackButton() { +// this.backButton.routerLink = ['applications', this.applicationId]; +// } - updatePageLimit(limit: any) { - console.log(limit); - } -} +// updatePageLimit(limit: any) { +// console.log(limit); +// } +// } diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.ts b/src/app/applications/multicast/multicast-table/multicast-table.component.ts index ff0786ce..8e557d0d 100644 --- a/src/app/applications/multicast/multicast-table/multicast-table.component.ts +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.ts @@ -16,7 +16,9 @@ import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dia import { MulticastType } from '@shared/enums/multicast-type'; import { tableSorter } from '@shared/helpers/table-sorting.helper'; import { MeService } from '@shared/services/me.service'; +import { SnackService } from '@shared/services/snack.service'; import { Subscription } from 'rxjs'; +import { multicast } from 'rxjs/operators'; import { Multicast, MulticastData } from '../multicast.model'; import { MulticastService } from '../multicast.service'; @@ -50,16 +52,14 @@ export class MulticastTableComponent private deleteDialogService: DeleteDialogService, private multicastService: MulticastService, private meService: MeService, - public translate: TranslateService + public translate: TranslateService, + public snackService: SnackService ) { translate.use('da'); } ngOnInit(): void { - this.applicationId = +Number( - this.route.parent.parent.snapshot.paramMap.get('id') - ); - console.log(this.applicationId); + this.applicationId = +Number(this.route.parent.snapshot.paramMap.get('id')); this.getMulticast(); this.canEdit = this.meService.canWriteInTargetOrganization(); } @@ -79,6 +79,7 @@ export class MulticastTableComponent appId ) .subscribe((multicasts: MulticastData) => { + console.log(multicasts); this.multicasts = multicasts.data; this.dataSource = new MatTableDataSource(this.multicasts); this.dataSource.paginator = this.paginator; @@ -88,6 +89,9 @@ export class MulticastTableComponent if (this.pageLimit) { this.pageTotal = Math.ceil(multicasts.count / this.pageLimit); } + if (multicasts.ok === false) { + this.showLoadFailSnack(); + } }); } } @@ -100,6 +104,9 @@ export class MulticastTableComponent this.multicastService.delete(element.id).subscribe((response) => { if (response.ok && response.body.affected > 0) { this.getMulticast(); + this.showDeletedSnack(); + } else { + this.showFailSnack(); } }); } else { @@ -108,6 +115,15 @@ export class MulticastTableComponent }); } + showLoadFailSnack() { + this.snackService.showLoadFailSnack(); + } + showFailSnack() { + this.snackService.showFailSnack(); + } + showDeletedSnack() { + this.snackService.showDeletedSnack(); + } ngOnDestroy() { // prevent memory leak by unsubscribing if (this.multicastSubscription) { diff --git a/src/app/applications/multicast/multicast.module.ts b/src/app/applications/multicast/multicast.module.ts index 7f38d76b..690e3daa 100644 --- a/src/app/applications/multicast/multicast.module.ts +++ b/src/app/applications/multicast/multicast.module.ts @@ -1,11 +1,9 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; -import { MulticastListComponent } from './multicast-list/multicast-list.component'; import { MulticastDetailComponent } from './multicast-detail/multicast-detail.component'; import { MulticastEditComponent } from './multicast-edit/multicast-edit.component'; import { MulticastTableComponent } from './multicast-table/multicast-table.component'; -import { DatatargetModule } from '@applications/datatarget/datatarget.module'; import { RouterModule } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { NGMaterialModule } from '@shared/Modules/materiale.module'; @@ -16,7 +14,6 @@ import { PipesModule } from '@shared/pipes/pipes.module'; @NgModule({ declarations: [ - MulticastListComponent, MulticastDetailComponent, MulticastEditComponent, MulticastTableComponent, @@ -34,7 +31,6 @@ import { PipesModule } from '@shared/pipes/pipes.module'; PipesModule, ], exports: [ - MulticastListComponent, MulticastDetailComponent, MulticastEditComponent, MulticastTableComponent, diff --git a/src/app/payload-decoder/payload-decoder-edit/payload-decoder-edit.component.ts b/src/app/payload-decoder/payload-decoder-edit/payload-decoder-edit.component.ts index cf7462e0..e4bb105b 100644 --- a/src/app/payload-decoder/payload-decoder-edit/payload-decoder-edit.component.ts +++ b/src/app/payload-decoder/payload-decoder-edit/payload-decoder-edit.component.ts @@ -18,7 +18,7 @@ import { DeviceModelService } from '@app/device-model/device-model.service'; import { faExchangeAlt } from '@fortawesome/free-solid-svg-icons'; import { TestPayloadDecoder } from '@payload-decoder/test-payload-decoder.model'; import { TestPayloadDecoderService } from '@payload-decoder/test-payload-decoder.service'; -import { SaveSnackService } from '@shared/services/save-snack.service'; +import { SnackService } from '@shared/services/snack.service'; import { ErrorMessageService } from '@shared/error-message.service'; import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; import { environment } from '@environments/environment'; @@ -75,7 +75,7 @@ export class PayloadDecoderEditComponent implements OnInit { private sharedVariableService: SharedVariableService, private iotDeviceService: IoTDeviceService, private deviceModelService: DeviceModelService, - private saveSnackService: SaveSnackService, + private saveSnackService: SnackService, private errorMessageService: ErrorMessageService, private scrollToTopService: ScrollToTopService, ) { } diff --git a/src/app/shared/services/save-snack.service.ts b/src/app/shared/services/save-snack.service.ts deleted file mode 100644 index fcee59d7..00000000 --- a/src/app/shared/services/save-snack.service.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Injectable } from '@angular/core'; -import { MatSnackBar } from '@angular/material/snack-bar'; - - -@Injectable({ - providedIn: 'root', -}) -export class SaveSnackService { - constructor( - private snackBar: MatSnackBar) { } - - public showSavedSnack() { - this.snackBar.open('Gemt Succesfuldt', 'Luk', { - duration: 10000, - }); - } -} diff --git a/src/app/shared/services/snack.service.ts b/src/app/shared/services/snack.service.ts new file mode 100644 index 00000000..6eb98829 --- /dev/null +++ b/src/app/shared/services/snack.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { TranslateService } from '@ngx-translate/core'; + +@Injectable({ + providedIn: 'root', +}) +export class SnackService { + constructor( + private snackBar: MatSnackBar, + public translate: TranslateService + ) {} + + public showSavedSnack() { + this.snackBar.open(this.translate.instant('SNACK.SAVE'), 'Luk', { + duration: 10000, + }); + } + public showDeletedSnack() { + this.snackBar.open(this.translate.instant('SNACK.DELETE'), 'Luk', { + duration: 10000, + }); + } + public showUpdatedSnack() { + this.snackBar.open(this.translate.instant('SNACK.UPDATE'), 'Luk', { + duration: 10000, + }); + } + public showFailSnack() { + this.snackBar.open(this.translate.instant('SNACK.FAIL'), 'Luk', { + duration: 10000, + }); + } + public showLoadFailSnack() { + this.snackBar.open(this.translate.instant('SNACK.LOADFAIL'), 'Luk', { + duration: 10000, + }); + } +} diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 1bc2965d..9dcdb9f6 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -46,6 +46,13 @@ "DELETE": "Slet" } }, + "SNACK": { + "SAVE": "Gemt succesfuldt", + "DELETE": "Slettet succesfuldt", + "UPDATE": "Opdateret succesfuldt", + "FAIL": "Fejl - aktion ikke fuldført", + "LOADFAIL": "Fejl - kunne ikke loade" + }, "SEARCH": { "ICON": "", "TYPE": "Type", @@ -87,8 +94,10 @@ "DESCRIPTION": "Applikationens beskrivelse", "ATTACHED-IOT": "Tilknyttede IoT enheder", "DATATARGET-SHOW": "Tilknyttede data targets", - "MULTICAST-SHOW": "Tilknyttede multicast grupper", + "MULTICAST-SHOW": "Tilknyttede multicast", + "MULTICAST-GROUPS": "Multicast-grupper", "IMPORT-CSV": "Bulk import af IoT enheder", + "IOT-DEVICES": "IoT-enheder", "BULK": { "TEMPLATE": { "GENERIC": "Generic HTTP sample", From 473c76346bf91bbf997652f42e761b0085032572 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Wed, 24 Nov 2021 09:11:33 +0100 Subject: [PATCH 08/37] minor typo fix --- src/app/applications/multicast/multicast.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/applications/multicast/multicast.service.ts b/src/app/applications/multicast/multicast.service.ts index 44ec3ebb..a07c6f7c 100644 --- a/src/app/applications/multicast/multicast.service.ts +++ b/src/app/applications/multicast/multicast.service.ts @@ -58,8 +58,8 @@ export class MulticastService { return this.restService.post(this.multicastURL, multicast).pipe( map( (response: MulticastResponse) => { - const datatarget = this.mapToMulticast(response); - return datatarget; + const multicast = this.mapToMulticast(response); + return multicast; } ) ); From 7fffca21bd2a9a11d978b7b5ea0f99bcc8f272f2 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Thu, 25 Nov 2021 16:11:45 +0100 Subject: [PATCH 09/37] Finalized CRUD in frontend. Removed list maps from datatarget and multicast because of change in requirements - now the multicast and datatarget is in a tab bar in the application detail page. --- .../datatarget-list.component.html | 13 --- .../datatarget-list.component.scss | 0 .../datatarget-list.component.spec.ts | 25 ------ .../datatarget-list.component.ts | 49 ----------- .../multicast-detail.component.html | 16 ++-- .../multicast-detail.component.ts | 11 +-- .../multicast-edit.component.html | 82 +++++++++---------- .../multicast-edit.component.ts | 38 ++++----- .../multicast-list.component.html | 19 ----- .../multicast-list.component.scss | 0 .../multicast-list.component.spec.ts | 25 ------ .../multicast-list.component.ts | 47 ----------- .../multicast/multicast-response.model.ts | 2 +- .../multicast-table.component.html | 10 +-- .../multicast-table.component.ts | 44 +++++----- .../applications/multicast/multicast.model.ts | 16 ++-- .../multicast/multicast.module.ts | 1 - .../multicast/multicast.service.ts | 71 ++++++++-------- src/app/shared/enums/multicast-type.ts | 2 +- 19 files changed, 139 insertions(+), 332 deletions(-) delete mode 100644 src/app/applications/datatarget/datatarget-list/datatarget-list.component.html delete mode 100644 src/app/applications/datatarget/datatarget-list/datatarget-list.component.scss delete mode 100644 src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts delete mode 100644 src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts delete mode 100644 src/app/applications/multicast/multicast-list/multicast-list.component.html delete mode 100644 src/app/applications/multicast/multicast-list/multicast-list.component.scss delete mode 100644 src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts delete mode 100644 src/app/applications/multicast/multicast-list/multicast-list.component.ts diff --git a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.html b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.html deleted file mode 100644 index bad1cea6..00000000 --- a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - -
-
-
-
- -
-
-
-
\ No newline at end of file diff --git a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.scss b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts deleted file mode 100644 index a3455dce..00000000 --- a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -// import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -// import { DatatargetListComponent } from './datatarget-list.component'; - -// describe('DatatargetListComponent', () => { -// let component: DatatargetListComponent; -// let fixture: ComponentFixture; - -// beforeEach(async(() => { -// TestBed.configureTestingModule({ -// declarations: [ DatatargetListComponent ] -// }) -// .compileComponents(); -// })); - -// beforeEach(() => { -// fixture = TestBed.createComponent(DatatargetListComponent); -// component = fixture.componentInstance; -// fixture.detectChanges(); -// }); - -// it('should create', () => { -// expect(component).toBeTruthy(); -// }); -// }); diff --git a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts b/src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts deleted file mode 100644 index cf6ce46d..00000000 --- a/src/app/applications/datatarget/datatarget-list/datatarget-list.component.ts +++ /dev/null @@ -1,49 +0,0 @@ -// import { Component, OnInit, Input } from '@angular/core'; -// import { TranslateService } from '@ngx-translate/core'; -// import { ActivatedRoute } from '@angular/router'; -// import { Datatarget } from '../datatarget.model'; -// import { BackButton } from '@shared/models/back-button.model'; -// import { environment } from '@environments/environment'; -// import { Title } from '@angular/platform-browser'; - - -// @Component({ -// selector: 'a[app-datatarget-list]', -// templateUrl: './datatarget-list.component.html', -// styleUrls: ['./datatarget-list.component.scss'] -// }) -// export class DatatargetListComponent implements OnInit { - -// public pageLimit = environment.tablePageSize; -// public title: string; -// public backButton: BackButton = { label: '', routerLink: ''}; -// public datatarget: Datatarget; -// private applikationId: string; - -// constructor( -// public translate: TranslateService, -// private titleService: Title, -// private route: ActivatedRoute) { -// translate.use('da'); -// } - -// ngOnInit(): void { -// const applikationName: string = this.route.snapshot.paramMap.get('name'); -// this.applikationId = this.route.snapshot.paramMap.get('id'); -// this.translate.get(["NAV.DATATARGET", "NAV.APPLICATIONS", "TITLE.DATATARGET"]) -// .subscribe((translate) => { -// this.title = translate['NAV.DATATARGET'] + ' - ' + applikationName; -// this.backButton.label = translate['NAV.APPLICATIONS']; -// this.titleService.setTitle(translate['TITLE.DATATARGET']); -// }); -// this.setBackButton() -// } - -// setBackButton() { -// this.backButton.routerLink = ['applications', this.applikationId]; -// } - -// updatePageLimit(limit: any) { -// console.log(limit); -// } -// } diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.html b/src/app/applications/multicast/multicast-detail/multicast-detail.component.html index a8aede80..65c4a16a 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.html +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.html @@ -1,6 +1,6 @@
- +
@@ -8,12 +8,12 @@

{{ 'MULTICAST.DETAILS' | translate }}

-

{{ 'MULTICAST.GROUPNAME' | translate }}{{multicast.groupName | translate }}

-

{{ 'MULTICAST.ADDRESS' | translate }}{{multicast.address | translate}}

-

{{ 'MULTICAST.NETWORK-KEY' | translate }}{{multicast.networkSessionKey | translate}}

-

{{ 'MULTICAST.APPLICATION-KEY' | translate }}{{multicast.applicationSessionKey | translate}}

-

{{ 'MULTICAST.FRAMECOUNTER' | translate }}{{multicast.frameCounter}}

-

{{ 'MULTICAST.DATARATE' | translate }}{{multicast.dataRate}}

+

{{ 'MULTICAST.GROUPNAME' | translate }}{{multicast.name | translate }}

+

{{ 'MULTICAST.ADDRESS' | translate }}{{multicast.mcAddr | translate}}

+

{{ 'MULTICAST.NETWORK-KEY' | translate }}{{multicast.mcNwkSKey | translate}}

+

{{ 'MULTICAST.APPLICATION-KEY' | translate }}{{multicast.mcAppSKey | translate}}

+

{{ 'MULTICAST.FRAMECOUNTER' | translate }}{{multicast.fCnt}}

+

{{ 'MULTICAST.DATARATE' | translate }}{{multicast.dr}}

{{ 'MULTICAST.FREQUENCY' | translate }}{{multicast.frequency}}

{{ 'MULTICAST.GROUPTYPE' | translate }}{{multicast.groupType}}

diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts index 3676f02f..f074882f 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts @@ -16,12 +16,10 @@ import { SnackService } from '@shared/services/snack.service'; styleUrls: ['./multicast-detail.component.scss'], }) export class MulticastDetailComponent implements OnInit { - public multicastSubscription: Subscription; public multicast: Multicast; public backButton: BackButton = { label: '', routerLink: '/multicast-list' }; private deleteDialogSubscription: Subscription; public dropdownButton: DropdownButton; - private applicationName: string; private applicationId: number; constructor( @@ -34,8 +32,7 @@ export class MulticastDetailComponent implements OnInit { ) {} ngOnInit(): void { - const id: number = +this.route.snapshot.paramMap.get('multicastId'); - this.applicationName = this.route.snapshot.paramMap.get('name'); + const id: string = this.route.snapshot.paramMap.get('multicastId'); // the multicastId is a string, created by chirpstack. if (id) { this.getMulticast(id); this.dropdownButton = { @@ -54,7 +51,7 @@ export class MulticastDetailComponent implements OnInit { }); } - getMulticast(id: number) { + getMulticast(id: string) { this.multicastService.get(id).subscribe((multicast: Multicast) => { this.multicast = multicast; this.setBackButton(this.applicationId); @@ -75,13 +72,13 @@ export class MulticastDetailComponent implements OnInit { // } else return false; // } - onDeleteDatatarget() { + onDeleteMulticast() { this.deleteDialogSubscription = this.deleteDialogService .showSimpleDialog() .subscribe((response) => { if (response) { this.multicastService - .delete(this.multicast.id) + .delete(this.multicast.multicastId) .subscribe((response) => { if (response.status !== 0) { this.showDeletedSnack(); diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html index 95dca867..168c3e48 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html @@ -11,28 +11,28 @@
-
-
-
-
-
-
diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts index 435f3e7a..bfa0e314 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -9,7 +9,6 @@ import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; import { Subscription } from 'rxjs'; import { Multicast } from '../multicast.model'; import { MulticastService } from '../multicast.service'; -import { THIS_EXPR } from '@angular/compiler/src/output/output_ast'; @Component({ selector: 'app-multicast-edit', @@ -18,16 +17,14 @@ import { THIS_EXPR } from '@angular/compiler/src/output/output_ast'; }) export class MulticastEditComponent implements OnInit { public title: string; - public multicastId: number; + public multicastId: string; public errorMessages: any; private multicastSubscription: Subscription; public errorFields: string[]; @Input() submitButton: string; public backButtonTitle: string; public multicast: Multicast = new Multicast(); - private counter: number; private applicationId: number; - private applicationName: string; public formFailedSubmit: boolean = false; public multicastTypes: string[] = Object.values(MulticastType); public periodicities: number[] = [2, 4, 8, 16, 32, 64, 128]; @@ -51,8 +48,10 @@ export class MulticastEditComponent implements OnInit { 'NAV.MULTICAST', ]) .subscribe((translations) => { - const multicastId = +this.route.snapshot.paramMap.get('multicastId'); - if (multicastId !== 0) { + this.multicastId = this.route.snapshot.paramMap.get('multicastId'); // the multicastId is a string, created by chirpstack. Used when in edit when update. + this.applicationId = +this.route.snapshot.paramMap.get('id'); + + if (this.multicastId !== null) { this.title = translations['FORM.EDIT-MULTICAST']; } else { this.title = translations['FORM.CREATE-NEW-MULTICAST']; @@ -61,28 +60,23 @@ export class MulticastEditComponent implements OnInit { this.backButtonTitle = translations['NAV.MULTICAST']; }); - this.multicastId = +this.route.snapshot.paramMap.get('multicastId'); - this.applicationId = +this.route.snapshot.paramMap.get('id'); - this.applicationName = this.route.snapshot.paramMap.get('name'); - - if (this.multicastId !== 0) { + if (this.multicastId !== null) { // If edit is pressed, then get the specific multicast. this.getMulticast(this.multicastId); } } onSubmit(): void { - this.counter = 0; - if (this.multicastId) { + if (this.multicastId) { // if already created, only update this.updateMulticast(); - } else { + } else { // else create new this.createMulticast(); } } - getMulticast(id: number) { + getMulticast(id: string) { this.multicastSubscription = this.multicastService .get(id) .subscribe((response: Multicast) => { - this.multicast = response; + this.multicast = response; // gets the multicast and set's local multicast. Used when update. }); } @@ -95,9 +89,9 @@ export class MulticastEditComponent implements OnInit { updateMulticast(): void { this.resetErrors(); - this.multicast.applicationId = this.applicationId; + this.multicast.applicationID = this.applicationId; this.multicastService.update(this.multicast).subscribe( - (response) => { + () => { this.showUpdatedSnack(); this.routeBack(); }, @@ -109,9 +103,9 @@ export class MulticastEditComponent implements OnInit { } createMulticast(): void { this.resetErrors(); - this.multicast.applicationId = this.applicationId; + this.multicast.applicationID = this.applicationId; this.multicastService.create(this.multicast).subscribe( - (response) => { + () => { this.showSavedSnack(); this.routeBack(); }, @@ -130,10 +124,10 @@ export class MulticastEditComponent implements OnInit { showUpdatedSnack() { this.snackService.showUpdatedSnack(); } - keyPressAlphaNumeric(event) { + keyPressHexadecimal(event) { // make sure only hexadecimal can be typed in input with adresses. var inp = String.fromCharCode(event.keyCode); - if (/[a-zA-Z0-9]/.test(inp)) { + if (/[a-fA-F0-9]/.test(inp)) { return true; } else { event.preventDefault(); diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.html b/src/app/applications/multicast/multicast-list/multicast-list.component.html deleted file mode 100644 index 52e442c4..00000000 --- a/src/app/applications/multicast/multicast-list/multicast-list.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - -
-
-
-
- -
-
-
-
diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.scss b/src/app/applications/multicast/multicast-list/multicast-list.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts b/src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts deleted file mode 100644 index ab2d625e..00000000 --- a/src/app/applications/multicast/multicast-list/multicast-list.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -// import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -// import { MulticastListComponent } from './multicast-list.component'; - -// describe('MulticastListComponent', () => { -// let component: MulticastListComponent; -// let fixture: ComponentFixture; - -// beforeEach(async(() => { -// TestBed.configureTestingModule({ -// declarations: [ MulticastListComponent ] -// }) -// .compileComponents(); -// })); - -// beforeEach(() => { -// fixture = TestBed.createComponent(MulticastListComponent); -// component = fixture.componentInstance; -// fixture.detectChanges(); -// }); - -// it('should create', () => { -// expect(component).toBeTruthy(); -// }); -// }); diff --git a/src/app/applications/multicast/multicast-list/multicast-list.component.ts b/src/app/applications/multicast/multicast-list/multicast-list.component.ts deleted file mode 100644 index b0b338b5..00000000 --- a/src/app/applications/multicast/multicast-list/multicast-list.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -// import { Component, OnInit } from '@angular/core'; -// import { Title } from '@angular/platform-browser'; -// import { ActivatedRoute } from '@angular/router'; -// import { environment } from '@environments/environment'; -// import { TranslateService } from '@ngx-translate/core'; -// import { BackButton } from '@shared/models/back-button.model'; - -// @Component({ -// selector: 'app-multicast-list', -// templateUrl: './multicast-list.component.html', -// styleUrls: ['./multicast-list.component.scss'], -// }) -// export class MulticastListComponent implements OnInit { -// public pageLimit = environment.tablePageSize; -// public title: string; -// public backButton: BackButton = { label: '', routerLink: '' }; -// private applicationId: string; - -// constructor( -// public translate: TranslateService, -// private titleService: Title, -// private route: ActivatedRoute -// ) { -// translate.use('da'); -// } - -// ngOnInit(): void { -// const applicationName: string = this.route.snapshot.paramMap.get('name'); -// this.applicationId = this.route.snapshot.paramMap.get('id'); -// this.translate -// .get(['NAV.MULTICAST', 'NAV.APPLICATIONS', 'TITLE.MULTICAST']) -// .subscribe((translate) => { -// this.title = translate['NAV.MULTICAST'] + ' - ' + applicationName; -// this.backButton.label = translate['NAV.APPLICATIONS']; -// this.titleService.setTitle(translate['TITLE.MULTICAST']); -// }); -// this.setBackButton(); -// } - -// setBackButton() { -// this.backButton.routerLink = ['applications', this.applicationId]; -// } - -// updatePageLimit(limit: any) { -// console.log(limit); -// } -// } diff --git a/src/app/applications/multicast/multicast-response.model.ts b/src/app/applications/multicast/multicast-response.model.ts index 1b80fbd8..5a2b8aa3 100644 --- a/src/app/applications/multicast/multicast-response.model.ts +++ b/src/app/applications/multicast/multicast-response.model.ts @@ -2,7 +2,7 @@ import { Application } from '@applications/application.model'; import { MulticastType } from '@shared/enums/multicast-type'; export class MulticastResponse { - id: number; + multicastId: string; application: Application; groupName: string; address: string; diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.html b/src/app/applications/multicast/multicast-table/multicast-table.component.html index adfb5208..37169681 100644 --- a/src/app/applications/multicast/multicast-table/multicast-table.component.html +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.html @@ -10,7 +10,7 @@ {{ 'MULTICAST-TABLE.NAME' | translate }} - {{element.groupName}} @@ -24,17 +24,17 @@ {{element.groupType | translate}} - + - \ No newline at end of file + diff --git a/src/app/navbar/organisation-dropdown/organisation-dropdown.component.ts b/src/app/navbar/organisation-dropdown/organisation-dropdown.component.ts index 3d73055b..97364003 100644 --- a/src/app/navbar/organisation-dropdown/organisation-dropdown.component.ts +++ b/src/app/navbar/organisation-dropdown/organisation-dropdown.component.ts @@ -3,7 +3,7 @@ import { Router } from '@angular/router'; import { Organisation } from '@app/admin/organisation/organisation.model'; import { PermissionType } from '@app/admin/permission/permission.model'; import { UserResponse } from '@app/admin/users/user.model'; -import { faExchangeAlt, faLayerGroup, faUsers, faIdBadge, faToolbox, faBurn } from '@fortawesome/free-solid-svg-icons'; +import { faExchangeAlt, faLayerGroup, faUsers, faIdBadge, faToolbox, faBurn, faKey } from '@fortawesome/free-solid-svg-icons'; import { TranslateService } from '@ngx-translate/core'; import { SharedVariableService } from '@shared/shared-variable/shared-variable.service'; @@ -24,6 +24,7 @@ export class OrganisationDropdownComponent implements OnInit { faIdBadge = faIdBadge; faToolbox = faToolbox; faBurn = faBurn; + faKey = faKey; constructor( diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 96c8b754..d39792d8 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -34,7 +34,8 @@ "DEVICE-MODEL": "Device model", "BACK": "Tilbage", "LOGOUT": "Log ud", - "HELP": "Hjælp" + "HELP": "Hjælp", + "API-KEY": "API nøgler" }, "TOPBAR":{ "SEARCH": { @@ -150,7 +151,7 @@ "AUTHORIZATIONHEADER": "Authorization header", "NO-AUTHORIZATIONHEADER": "Ingen Authorization header angivet", "ADD-TO-OPENDATADK": "Send data til OpenDataDK", - "OPENDATA-DK": "OpenDataDK", + "OPENDATA-DK": "OpenDataDK", "NO-OPENDATA-DK": "Der er ikke oprettet nogen datadeling med Open Data DK endnu" }, "OPENDATADK": { From cb783e39689d45a9fb730f2fec69dffab4022fd7 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Mon, 29 Nov 2021 10:17:47 +0100 Subject: [PATCH 11/37] changed division in db so the frontend will receive a different promise. Mostly just changed property names because of this change. --- .../multicast-detail.component.ts | 16 +++++------- .../multicast-edit.component.ts | 23 ++++++++++------- .../multicast/multicast-response.model.ts | 23 ++++++++++------- .../multicast-table.component.html | 10 ++++---- .../multicast-table.component.ts | 2 +- .../applications/multicast/multicast.model.ts | 2 +- .../multicast/multicast.service.ts | 25 ++++++++++--------- 7 files changed, 54 insertions(+), 47 deletions(-) diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts index f074882f..4a6651ff 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts @@ -32,7 +32,7 @@ export class MulticastDetailComponent implements OnInit { ) {} ngOnInit(): void { - const id: string = this.route.snapshot.paramMap.get('multicastId'); // the multicastId is a string, created by chirpstack. + const id: number = +this.route.snapshot.paramMap.get('multicastId'); if (id) { this.getMulticast(id); this.dropdownButton = { @@ -51,7 +51,7 @@ export class MulticastDetailComponent implements OnInit { }); } - getMulticast(id: string) { + getMulticast(id: number) { this.multicastService.get(id).subscribe((multicast: Multicast) => { this.multicast = multicast; this.setBackButton(this.applicationId); @@ -59,10 +59,7 @@ export class MulticastDetailComponent implements OnInit { } private setBackButton(applicationId: number) { - this.backButton.routerLink = [ - 'applications', - applicationId.toString() - ]; + this.backButton.routerLink = ['applications', applicationId.toString()]; } //only if classB can be used @@ -78,13 +75,12 @@ export class MulticastDetailComponent implements OnInit { .subscribe((response) => { if (response) { this.multicastService - .delete(this.multicast.multicastId) + .delete(this.multicast.id) .subscribe((response) => { if (response.status !== 0) { this.showDeletedSnack(); this.location.back(); - } - else{ + } else { this.showFailSnack(); } }); @@ -98,7 +94,7 @@ export class MulticastDetailComponent implements OnInit { this.snackService.showDeletedSnack(); } - showFailSnack(): void{ + showFailSnack(): void { this.snackService.showFailSnack(); } diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts index bfa0e314..9a26bdce 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -17,7 +17,7 @@ import { MulticastService } from '../multicast.service'; }) export class MulticastEditComponent implements OnInit { public title: string; - public multicastId: string; + public multicastId: number; public errorMessages: any; private multicastSubscription: Subscription; public errorFields: string[]; @@ -46,33 +46,37 @@ export class MulticastEditComponent implements OnInit { 'FORM.EDIT-MULTICAST', 'MULTICAST.SAVE', 'NAV.MULTICAST', + 'GEN.BACK', ]) .subscribe((translations) => { - this.multicastId = this.route.snapshot.paramMap.get('multicastId'); // the multicastId is a string, created by chirpstack. Used when in edit when update. + this.multicastId = +this.route.snapshot.paramMap.get('multicastId'); this.applicationId = +this.route.snapshot.paramMap.get('id'); - if (this.multicastId !== null) { + if (this.multicastId) { this.title = translations['FORM.EDIT-MULTICAST']; } else { this.title = translations['FORM.CREATE-NEW-MULTICAST']; } this.submitButton = translations['MULTICAST.SAVE']; - this.backButtonTitle = translations['NAV.MULTICAST']; + this.backButtonTitle = translations['GEN.BACK']; }); - if (this.multicastId !== null) { // If edit is pressed, then get the specific multicast. + if (this.multicastId) { + // If edit is pressed, then get the specific multicast. this.getMulticast(this.multicastId); } } onSubmit(): void { - if (this.multicastId) { // if already created, only update + if (this.multicastId) { + // if already created, only update this.updateMulticast(); - } else { // else create new + } else { + // else create new this.createMulticast(); } } - getMulticast(id: string) { + getMulticast(id: number) { this.multicastSubscription = this.multicastService .get(id) .subscribe((response: Multicast) => { @@ -124,7 +128,8 @@ export class MulticastEditComponent implements OnInit { showUpdatedSnack() { this.snackService.showUpdatedSnack(); } - keyPressHexadecimal(event) { // make sure only hexadecimal can be typed in input with adresses. + keyPressHexadecimal(event) { + // make sure only hexadecimal can be typed in input with adresses. var inp = String.fromCharCode(event.keyCode); if (/[a-fA-F0-9]/.test(inp)) { diff --git a/src/app/applications/multicast/multicast-response.model.ts b/src/app/applications/multicast/multicast-response.model.ts index 5a2b8aa3..47c34d7a 100644 --- a/src/app/applications/multicast/multicast-response.model.ts +++ b/src/app/applications/multicast/multicast-response.model.ts @@ -2,16 +2,10 @@ import { Application } from '@applications/application.model'; import { MulticastType } from '@shared/enums/multicast-type'; export class MulticastResponse { - multicastId: string; + id: number; application: Application; groupName: string; - address: string; - networkSessionKey: string; - applicationSessionKey: string; - frameCounter: number = 0; - dataRate: number = 0; - frequency: number = 0; - groupType: MulticastType; + lorawanMulticastDefinition: LorawanMulticastDefinition; // periodicity: number; -> only if classB is gonna be used createdAt: string; updatedAt: string; @@ -19,4 +13,15 @@ export class MulticastResponse { updatedBy: number; createdByName: string; updatedByName: string; -} \ No newline at end of file +} + +export class LorawanMulticastDefinition { + address: string; + networkSessionKey: string; + applicationSessionKey: string; + frameCounter: number = 0; + dataRate: number = 0; + frequency: number = 0; + groupType: MulticastType; + chirpstackGroupId: string; +} diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.html b/src/app/applications/multicast/multicast-table/multicast-table.component.html index 37169681..f7f5892a 100644 --- a/src/app/applications/multicast/multicast-table/multicast-table.component.html +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.html @@ -10,7 +10,7 @@ {{ 'MULTICAST-TABLE.NAME' | translate }} - {{element.groupName}} @@ -21,7 +21,7 @@ {{ 'MULTICAST-TABLE.TYPE' | translate }} - {{element.groupType | translate}} + {{element.lorawanMulticastDefinition.groupType | translate}} @@ -29,12 +29,12 @@ -->
+
+ + +
+ + {{ + 'QUESTION.MULTICAST.SELECT-DEVICES' | translate + }} + + + + + + {{ + device.name + }} + + +
+
+ +
+ diff --git a/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts b/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts index c4926db9..1de65ecc 100644 --- a/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts +++ b/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts @@ -1,15 +1,81 @@ +import { Location } from '@angular/common'; import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { PermissionResponse } from '@app/admin/permission/permission.model'; +import { TranslateService } from '@ngx-translate/core'; +import { BackButton } from '@shared/models/back-button.model'; +import { ApiKeyRequest } from '../api-key.model'; +import { ApiKeyService } from '../api-key.service'; @Component({ selector: 'app-api-key-edit', templateUrl: './api-key-edit.component.html', - styleUrls: ['./api-key-edit.component.scss'] + styleUrls: ['./api-key-edit.component.scss'], }) export class ApiKeyEditComponent implements OnInit { + apiKeyRequest = new ApiKeyRequest(); + public backButton: BackButton = { + label: '', + routerLink: ['admin', 'api-key'], + }; + public title = ''; + public submitButton = ''; + private apiKeyId: number; + public errorMessage: string; + public errorMessages: string[]; + public errorFields: string[]; + public formFailedSubmit = false; + public isEditMode = false; + public permissions: PermissionResponse[] = []; - constructor() { } + constructor( + private translate: TranslateService, + private route: ActivatedRoute, + private location: Location, + private apiKeyService: ApiKeyService + ) { + translate.use('da'); + } ngOnInit(): void { + this.translate + .get(['NAV.API-KEY', 'FORM.EDIT-API-KEY', 'API-KEY.EDIT.SAVE']) + .subscribe((translations) => { + this.backButton.label = translations['NAV.API-KEY']; + this.title = translations['FORM.EDIT-API-KEY']; + this.submitButton = translations['API-KEY.EDIT.SAVE']; + }); + + this.apiKeyId = +this.route.snapshot.paramMap.get('api-key-id'); + + if (this.apiKeyId > 0) { + // TODO: Fetch current api key + this.isEditMode = true; + } + } + + onSubmit(): void { + if (this.apiKeyId) { + this.update(); + } else { + this.create(); + } + } + + private create(): void { + // TODO: CREATE + } + + private update(): void { + // TODO: UPDATE + this.routeBack(); } + public compare(o1: any, o2: any): boolean { + return o1 === o2; + } + + routeBack(): void { + this.location.back(); + } } diff --git a/src/app/admin/api-key/api-key-list/api-key-list.component.html b/src/app/admin/api-key/api-key-list/api-key-list.component.html index e696a106..0e5f1308 100644 --- a/src/app/admin/api-key/api-key-list/api-key-list.component.html +++ b/src/app/admin/api-key/api-key-list/api-key-list.component.html @@ -4,7 +4,7 @@
- +
diff --git a/src/app/admin/api-key/api-key-list/api-key-list.component.ts b/src/app/admin/api-key/api-key-list/api-key-list.component.ts index 84781d31..f422eac1 100644 --- a/src/app/admin/api-key/api-key-list/api-key-list.component.ts +++ b/src/app/admin/api-key/api-key-list/api-key-list.component.ts @@ -1,15 +1,28 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; +import { Title } from '@angular/platform-browser'; +import { TranslateService } from '@ngx-translate/core'; +import { SharedVariableService } from '@shared/shared-variable/shared-variable.service'; @Component({ selector: 'app-api-key-list', templateUrl: './api-key-list.component.html', - styleUrls: ['./api-key-list.component.scss'] + styleUrls: ['./api-key-list.component.scss'], }) export class ApiKeyListComponent implements OnInit { + @Input() organisationId: number; - constructor() { } + constructor( + public translate: TranslateService, + private titleService: Title, + private globalService: SharedVariableService + ) { + translate.use('da'); + } ngOnInit(): void { + this.translate.get(['TITLE.API-KEY']).subscribe((translations) => { + this.titleService.setTitle(translations['TITLE.API-KEY']); + }); + this.organisationId = this.globalService.getSelectedOrganisationId(); } - } diff --git a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html index 554c63e7..e2769aae 100644 --- a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html +++ b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html @@ -1 +1,98 @@ -

api-key-table works!

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ 'API-KEY.NAME' | translate }} + + + + {{ element.name }} + + + {{ 'API-KEY.PERMISSIONS' | translate }} + +
+ {{ element.users.length }} +
+ {{ 'NoUsersAdded' | translate }} +
+ {{ 'API-KEY.KEY' | translate }} + + {{ element.key }} + + +
+ + +
diff --git a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.ts b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.ts index 30853efb..c8ef2dd5 100644 --- a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.ts +++ b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.ts @@ -1,15 +1,122 @@ -import { Component, OnInit } from '@angular/core'; +import { AfterViewInit, Component, Input, ViewChild } from '@angular/core'; +import { MatPaginator, PageEvent } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { Router } from '@angular/router'; +import { environment } from '@environments/environment'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { MeService } from '@shared/services/me.service'; +import { merge, Observable, of } from 'rxjs'; +import { catchError, map, startWith, switchMap } from 'rxjs/operators'; +import { ApiKeyGetManyResponse, ApiKeyResponse } from '../../api-key.model'; +import { ApiKeyService } from '../../api-key.service'; @Component({ selector: 'app-api-key-table', templateUrl: './api-key-table.component.html', - styleUrls: ['./api-key-table.component.scss'] + styleUrls: ['./api-key-table.component.scss'], }) -export class ApiKeyTableComponent implements OnInit { +export class ApiKeyTableComponent implements AfterViewInit { + @Input() organisationId: number; + displayedColumns: string[] = ['name', 'permissions', 'key', 'menu']; + data: ApiKeyResponse[] = [ + // { id: 1, name: 'Abcd', key: 'sdafsegjhkjtrewr34252t$25!', permissions: [] }, + // { + // id: 2, + // name: 'DoofApi', + // key: 'jasdhjlw8o3-4u2qeuqnwodasd-521529f', + // permissions: [], + // }, + ]; + isLoadingResults = true; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + resultsLength = 0; + public pageSize = environment.tablePageSize; - constructor() { } + constructor( + private meService: MeService, + private router: Router, + private apiKeyService: ApiKeyService, + private deleteDialogService: DeleteDialogService + ) {} - ngOnInit(): void { + ngAfterViewInit(): void { + // If the user changes the sort order, reset back to the first page. + this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0)); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + startWith({}), + switchMap(() => { + this.isLoadingResults = true; + return this.getApiKeysByOrganisationId( + this.sort.active, + this.sort.direction + ); + }), + map((data) => { + // Flip flag to show that loading has finished. + this.isLoadingResults = false; + this.resultsLength = data.count; + + return data.data; + }), + catchError(() => { + this.isLoadingResults = false; + return of([]); + }) + ) + .subscribe((data) => (this.data = data)); + } + + getApiKeysByOrganisationId( + orderByColumn: string, + orderByDirection: string + ): Observable { + return this.apiKeyService.getApiKeys( + this.paginator.pageSize, + this.paginator.pageIndex * this.paginator.pageSize, + orderByColumn, + orderByDirection, + null, + this.organisationId + ); + } + + canAccess(element: ApiKeyResponse) { + // if (element.type === PermissionType.GlobalAdmin) { + // return this.meService.hasGlobalAdmin(); + // } + // return element.permissions?.some( + // (p) => + // p.organization && + // this.meService.hasAdminAccessInTargetOrganization(p.organization.id) + // ); + return this.meService.hasAdminAccessInTargetOrganization( + this.organisationId + ); } + routeToPermissions(element: any) { + this.router.navigate(['admin/api-key', element.id]); + } + + deleteApiKey(id: number) { + this.deleteDialogService.showSimpleDialog().subscribe((response) => { + if (response) { + this.apiKeyService.delete(id).subscribe((response) => { + if (response.ok && response.body.affected > 0) { + this.refresh(); + } + }); + } + }); + } + + private refresh() { + const pageEvent = new PageEvent(); + pageEvent.pageIndex = this.paginator.pageIndex; + pageEvent.pageSize = this.paginator.pageSize; + this.paginator.page.emit(pageEvent); + } } diff --git a/src/app/admin/api-key/api-key.model.ts b/src/app/admin/api-key/api-key.model.ts new file mode 100644 index 00000000..050a799b --- /dev/null +++ b/src/app/admin/api-key/api-key.model.ts @@ -0,0 +1,23 @@ +import { PermissionResponse } from '../permission/permission.model'; + +export class ApiKeyRequest { + id: number; + name: string; + permissions?: PermissionResponse[]; +} + +export interface ApiKeyResponse { + id: number; + name: string; + key: string; + permissions?: PermissionResponse[]; + createdBy: number; + updatedBy: number; + createdByName: string; + updatedByName: string; +} + +export interface ApiKeyGetManyResponse { + data: ApiKeyResponse[]; + count: number; +} diff --git a/src/app/admin/api-key/api-key.service.ts b/src/app/admin/api-key/api-key.service.ts new file mode 100644 index 00000000..77db1c4c --- /dev/null +++ b/src/app/admin/api-key/api-key.service.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@angular/core'; +import { RestService } from '@shared/services/rest.service'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { UserMinimalService } from '../users/user-minimal.service'; +import { + ApiKeyGetManyResponse, + ApiKeyRequest, + ApiKeyResponse, +} from './api-key.model'; + +@Injectable({ + providedIn: 'root', +}) +export class ApiKeyService { + endpoint = 'api-key'; + constructor( + private restService: RestService, + private userMinimalService: UserMinimalService + ) {} + + create(body: ApiKeyRequest): Observable { + return this.restService.post(this.endpoint, body, { + observe: 'response', + }); + } + + update(body: ApiKeyRequest, id: number): Observable { + return this.restService.put(this.endpoint, body, id, { + observe: 'response', + }); + } + + get(id: number): Observable { + return this.restService.get(this.endpoint, {}, id).pipe( + map((response: ApiKeyResponse) => { + response.createdByName = this.userMinimalService.getUserNameFrom( + response.createdBy + ); + response.updatedByName = this.userMinimalService.getUserNameFrom( + response.updatedBy + ); + return response; + }) + ); + } + + getApiKeys( + limit: number = 1000, + offset: number = 0, + orderByColumn?: string, + orderByDirection?: string, + userId?: number, + organisationId?: number + ): Observable { + if (userId) { + return this.restService.get(this.endpoint, { + limit, + offset, + orderOn: orderByColumn, + sort: orderByDirection, + userId, + }); + } else if (organisationId) { + return this.restService.get(this.endpoint, { + limit, + offset, + orderOn: orderByColumn, + sort: orderByDirection, + organisationId, + }); + } else { + return this.restService.get(this.endpoint, { + limit, + offset, + orderOn: orderByColumn, + sort: orderByDirection, + }); + } + } + + delete(id: number) { + return this.restService.delete(this.endpoint, id); + } +} diff --git a/src/app/admin/organisation/organisation.model.ts b/src/app/admin/organisation/organisation.model.ts index d3961220..f07c0a52 100644 --- a/src/app/admin/organisation/organisation.model.ts +++ b/src/app/admin/organisation/organisation.model.ts @@ -1,5 +1,6 @@ import { Application } from '@applications/application.model'; import { PayloadDecoder } from '../../payload-decoder/payload-decoder.model'; +import { PermissionResponse } from '../permission/permission.model'; export class Organisation { id?: number; @@ -18,8 +19,7 @@ export interface OrganisationResponse { payloadDecoders: PayloadDecoder[]; applications: Application[]; - // TODO: This. - permissions: any[]; + permissions: PermissionResponse[]; } export interface OrganisationGetManyResponse { @@ -30,4 +30,4 @@ export interface OrganisationGetManyResponse { export interface OrganisationGetMinimalResponse { data: Organisation[]; count: number; -} \ No newline at end of file +} diff --git a/src/app/admin/permission/permission-list/permission-list.component.ts b/src/app/admin/permission/permission-list/permission-list.component.ts index cec1432d..7559e47a 100644 --- a/src/app/admin/permission/permission-list/permission-list.component.ts +++ b/src/app/admin/permission/permission-list/permission-list.component.ts @@ -49,7 +49,6 @@ export class PermissionListComponent implements OnInit, OnChanges { } deletePermission(id: number) { - console.log("list") this.permissionService.deletePermission(id).subscribe((response) => { if (response.ok && response.body.affected > 0) { this.getPermissions(); diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index d39792d8..5919fe40 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -383,7 +383,8 @@ "EDIT-SIGFOX-GROUPS": "Redigér Sigfox grupper", "EDIT-SIGFOX-GROUP": "Redigér Sigfox gruppe", "EDIT-USERS": "Redigér bruger", - "EDIT-DEVICE-PROFILE": "Redigér device profil" + "EDIT-DEVICE-PROFILE": "Redigér device profil", + "EDIT-API-KEY": "Redigér API nøgle" }, "QUESTION": { "CREATE-IOT-DEVICE": "IoT-enhed", @@ -507,6 +508,9 @@ "SELECT-ENERGYLIMITATIONCLASS": "Vælg energibegrænsningsklassen (energyLimitationClass)", "SELECT-SUPPORTEDPROTOCOL": "Vælg understøttede protokoller (supportedProtocol)", "FIWARE-LINK-TEXT": "Denne data model er adopteret fra Fiware og følger ETSI standarden" + }, + "PERMISSION": { + "SELECT-PERMISSION": "Vælg brugergruppe" } }, "QUESTION-LORA-GATEWAY": { @@ -824,7 +828,8 @@ "DATATARGET": "OS2IoT - Datatarget", "BULKIMPORT": "OS2IoT - Bulk import", "IOTDEVICE": "OS2IoT - IoT enhed", - "FRONTPAGE": "OS2IoT - Forside" + "FRONTPAGE": "OS2IoT - Forside", + "API-KEY": "OS2IoT - API nøgler" }, "PAGINATOR": { @@ -842,5 +847,23 @@ "Forbidden resource": "Du har ikke rettigheder til at foretage denne handling", "GENERIC_HTTP": "Generisk HTTP", "LORAWAN": "LoRaWAN", - "SIGFOX": "Sigfox" + "SIGFOX": "Sigfox", + "API-KEY": { + "NAME": "Navn", + "PERMISSIONS": "Brugergrupper", + "KEY": "Nøgle", + "DETAIL": {}, + "EDIT": { + "NAME": "Indtast nøglens navn", + "NAME-PLACEHOLDER": "Indtast nøglens navn", + "CANCEL": "Annuller", + "SAVE": "Gem nøgle", + "CREATE-NEW-API-KEY": "Opret nøgle" + }, + "TABLE-ROW": { + "EDIT": "Redigér", + "DELETE": "Slet" + } + }, + "NoUsersAdded": "Ingen brugergrupper er tilføjet" } diff --git a/tsconfig.json b/tsconfig.json index fc6b17bc..7eecb11c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,5 +30,6 @@ "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true + // "strictTemplates": true // Highly recommended for type-safety in html. Apply if there's ever time to fix the 30+ errors that appear } } From e25152c3ce9884ab7f53de581c255a1b7f11725f Mon Sep 17 00:00:00 2001 From: August Andersen Date: Wed, 1 Dec 2021 11:46:34 +0100 Subject: [PATCH 16/37] removed 2 console logs --- .../multicast/multicast-edit/multicast-edit.component.ts | 1 - .../form-body-application/form-body-application.component.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts index aa97f6f8..7dcdfac4 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -87,7 +87,6 @@ export class MulticastEditComponent implements OnInit { .get(id) .subscribe((response: Multicast) => { this.multicast = response; // gets the multicast and set's local multicast. Used when update. - console.log(this.multicast.iotDevices); }); } diff --git a/src/app/shared/components/forms/form-body-application/form-body-application.component.ts b/src/app/shared/components/forms/form-body-application/form-body-application.component.ts index f1eb19a5..556e82d4 100644 --- a/src/app/shared/components/forms/form-body-application/form-body-application.component.ts +++ b/src/app/shared/components/forms/form-body-application/form-body-application.component.ts @@ -89,8 +89,7 @@ export class FormBodyApplicationComponent implements OnInit, OnDestroy { this.applicationService .createApplication(this.application) .subscribe( - (response) => { - console.log(response); + () => { this.router.navigateByUrl('/applications'); }, (error: HttpErrorResponse) => { From ff1ecf9414b24a6f1209a59d4d158c9bdf624c97 Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Wed, 1 Dec 2021 17:37:32 +0100 Subject: [PATCH 17/37] Api key management works --- .../api-key-edit/api-key-edit.component.html | 1 + .../api-key-edit/api-key-edit.component.ts | 84 +++++++++++++++---- .../api-key-list/api-key-list.component.html | 16 ++-- .../api-key-table.component.html | 21 ++--- .../api-key-table/api-key-table.component.ts | 25 ++---- .../organisation-dropdown.component.html | 14 ++-- src/app/shared/pipes/pipes.module.ts | 3 + .../unique-permission-organizations.pipe.ts | 19 +++++ src/assets/i18n/da.json | 4 +- tslint.json | 2 +- 10 files changed, 130 insertions(+), 59 deletions(-) create mode 100644 src/app/shared/pipes/unique-permission-organizations.pipe.ts diff --git a/src/app/admin/api-key/api-key-edit/api-key-edit.component.html b/src/app/admin/api-key/api-key-edit/api-key-edit.component.html index 8c477680..268d6972 100644 --- a/src/app/admin/api-key/api-key-edit/api-key-edit.component.html +++ b/src/app/admin/api-key/api-key-edit/api-key-edit.component.html @@ -43,6 +43,7 @@ name="permissions" [compareWith]="compare" [(ngModel)]="apiKeyRequest.permissions" + [multiple]="true" > { @@ -46,35 +56,79 @@ export class ApiKeyEditComponent implements OnInit { this.submitButton = translations['API-KEY.EDIT.SAVE']; }); - this.apiKeyId = +this.route.snapshot.paramMap.get('api-key-id'); + this.id = +this.route.snapshot.paramMap.get('api-key-id'); + this.organizationId = this.sharedVariableService.getSelectedOrganisationId(); - if (this.apiKeyId > 0) { - // TODO: Fetch current api key - this.isEditMode = true; - } + // if (this.id > 0) { + // // TODO: Fetch current api key + // this.getApiKey(this.id); + // this.isEditMode = true; + // } + } + + // private getApiKey(id: number) { + // this.apiKeyService.get(id).subscribe( + // (response) => { + // console.log(response); + // this.apiKeyRequest.id = response.id; + // this.apiKeyRequest.name = response.name; + // this.apiKeyRequest.permissions = response.permissions; + // }, + // (error: HttpErrorResponse) => { + // this.showError(error); + // } + // ); + // } + + private getPermissions() { + this.permissionService + .getPermissions( + undefined, + undefined, + undefined, + undefined, + undefined, + this.organizationId + ) + .subscribe( + (permissions) => { + this.permissions = permissions.data.filter( + (x) => x.organization?.id === this.organizationId + ); + }, + (error: HttpErrorResponse) => { + this.showError(error); + } + ); } onSubmit(): void { - if (this.apiKeyId) { - this.update(); - } else { - this.create(); - } + this.id ? this.update() : this.create(); } private create(): void { - // TODO: CREATE + this.apiKeyService.create(this.apiKeyRequest).subscribe( + () => this.routeBack(), + (err) => this.showError(err) + ); } private update(): void { - // TODO: UPDATE - this.routeBack(); + this.apiKeyService + .update(this.apiKeyRequest, this.id) + .subscribe(this.routeBack, this.showError); } public compare(o1: any, o2: any): boolean { return o1 === o2; } + private showError(err: HttpErrorResponse) { + const result = this.errorMessageService.handleErrorMessageWithFields(err); + this.errorFields = result.errorFields; + this.errorMessages = result.errorMessages; + } + routeBack(): void { this.location.back(); } diff --git a/src/app/admin/api-key/api-key-list/api-key-list.component.html b/src/app/admin/api-key/api-key-list/api-key-list.component.html index 0e5f1308..8ecf306e 100644 --- a/src/app/admin/api-key/api-key-list/api-key-list.component.html +++ b/src/app/admin/api-key/api-key-list/api-key-list.component.html @@ -1,11 +1,17 @@ - +
-
-
- -
+
+
+
+
diff --git a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html index e2769aae..92109f40 100644 --- a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html +++ b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html @@ -16,14 +16,7 @@ {{ 'API-KEY.NAME' | translate }} - - {{ element.name }} - @@ -33,9 +26,12 @@ {{ 'API-KEY.PERMISSIONS' | translate }} -
- {{ element.users.length }} -
+ + + {{ pm.name }} +
+
+
{{ 'NoUsersAdded' | translate }} @@ -68,13 +64,14 @@ class="dropdown-menu dropdown-menu--table" attr.aria-labelledby="tableRowDropdown-{{ element.id }}" > - -
+ diff --git a/src/app/shared/pipes/pipes.module.ts b/src/app/shared/pipes/pipes.module.ts index a3e26445..cd9aa58a 100644 --- a/src/app/shared/pipes/pipes.module.ts +++ b/src/app/shared/pipes/pipes.module.ts @@ -5,6 +5,7 @@ import { ActiveDeactivePipe } from './activeDeactive.pipe'; import { isGlobalAdminPipe } from './is-global-admin.pipe'; import { CreatedUpdatedByPipe } from './created-updated-by.pipe'; import { CustomDatePipe, CustomTableDatePipe } from './custom-date.pipe'; +import { UniquePermissionOrganizationsPipe } from './unique-permission-organizations.pipe'; @NgModule({ declarations: [ @@ -14,6 +15,7 @@ import { CustomDatePipe, CustomTableDatePipe } from './custom-date.pipe'; CustomDatePipe, CustomTableDatePipe, CreatedUpdatedByPipe, + UniquePermissionOrganizationsPipe, ], imports: [ CommonModule @@ -25,6 +27,7 @@ import { CustomDatePipe, CustomTableDatePipe } from './custom-date.pipe'; CustomDatePipe, CustomTableDatePipe, CreatedUpdatedByPipe, + UniquePermissionOrganizationsPipe, ] }) export class PipesModule { } diff --git a/src/app/shared/pipes/unique-permission-organizations.pipe.ts b/src/app/shared/pipes/unique-permission-organizations.pipe.ts new file mode 100644 index 00000000..5e646d71 --- /dev/null +++ b/src/app/shared/pipes/unique-permission-organizations.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { PermissionResponse } from '@app/admin/permission/permission.model'; + +@Pipe({ + name: 'uniquePermissionOrganizations', +}) +export class UniquePermissionOrganizationsPipe implements PipeTransform { + transform( + value: PermissionResponse[], + ..._: unknown[] + ): PermissionResponse[] { + // Ensure that no element appears twice. Orders it so that the duplicate (second match) takes priority + const uniqueArr = Array.from( + new Map(value.map((item) => [item.organization?.id, item])).values() + ); + + return uniqueArr; + } +} diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 5919fe40..fd0e912f 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -850,15 +850,17 @@ "SIGFOX": "Sigfox", "API-KEY": { "NAME": "Navn", + "ORGANIZATION": "Organisation", "PERMISSIONS": "Brugergrupper", "KEY": "Nøgle", + "CREATE-NEW-API-KEY": "Opret ny nøgle", "DETAIL": {}, "EDIT": { "NAME": "Indtast nøglens navn", "NAME-PLACEHOLDER": "Indtast nøglens navn", "CANCEL": "Annuller", "SAVE": "Gem nøgle", - "CREATE-NEW-API-KEY": "Opret nøgle" + "CREATE-API-KEY": "Opret nøgle" }, "TABLE-ROW": { "EDIT": "Redigér", diff --git a/tslint.json b/tslint.json index 4f4dff57..b35a3d4e 100644 --- a/tslint.json +++ b/tslint.json @@ -129,7 +129,7 @@ "use-lifecycle-interface": true, "use-pipe-transform-interface": true, "variable-name": { - "options": ["ban-keywords", "check-format", "allow-pascal-case"] + "options": ["ban-keywords", "check-format", "allow-pascal-case", "allow-leading-underscore"] }, "whitespace": { "options": [ From a778291493091b0c102fe8cc61d6d236f8589abf Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Wed, 1 Dec 2021 17:43:03 +0100 Subject: [PATCH 18/37] Cleanup after api key --- .../api-key-edit/api-key-edit.component.ts | 31 +--------------- .../api-key-table.component.html | 35 ++++--------------- src/app/admin/api-key/api-key.service.ts | 6 ---- 3 files changed, 7 insertions(+), 65 deletions(-) diff --git a/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts b/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts index 9a209db2..015f7852 100644 --- a/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts +++ b/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts @@ -24,12 +24,10 @@ export class ApiKeyEditComponent implements OnInit { }; public title = ''; public submitButton = ''; - private id: number; public errorMessage: string; public errorMessages: string[]; public errorFields: string[]; public formFailedSubmit = false; - public isEditMode = false; public permissions: PermissionResponse[] = []; private organizationId: number; @@ -56,30 +54,9 @@ export class ApiKeyEditComponent implements OnInit { this.submitButton = translations['API-KEY.EDIT.SAVE']; }); - this.id = +this.route.snapshot.paramMap.get('api-key-id'); this.organizationId = this.sharedVariableService.getSelectedOrganisationId(); - - // if (this.id > 0) { - // // TODO: Fetch current api key - // this.getApiKey(this.id); - // this.isEditMode = true; - // } } - // private getApiKey(id: number) { - // this.apiKeyService.get(id).subscribe( - // (response) => { - // console.log(response); - // this.apiKeyRequest.id = response.id; - // this.apiKeyRequest.name = response.name; - // this.apiKeyRequest.permissions = response.permissions; - // }, - // (error: HttpErrorResponse) => { - // this.showError(error); - // } - // ); - // } - private getPermissions() { this.permissionService .getPermissions( @@ -103,7 +80,7 @@ export class ApiKeyEditComponent implements OnInit { } onSubmit(): void { - this.id ? this.update() : this.create(); + this.create(); } private create(): void { @@ -113,12 +90,6 @@ export class ApiKeyEditComponent implements OnInit { ); } - private update(): void { - this.apiKeyService - .update(this.apiKeyRequest, this.id) - .subscribe(this.routeBack, this.showError); - } - public compare(o1: any, o2: any): boolean { return o1 === o2; } diff --git a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html index 92109f40..e52194d0 100644 --- a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html +++ b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html @@ -50,35 +50,12 @@ - + {{ 'API-KEY.TABLE-ROW.DELETE' | translate }} + diff --git a/src/app/admin/api-key/api-key.service.ts b/src/app/admin/api-key/api-key.service.ts index 77db1c4c..91a6dc2c 100644 --- a/src/app/admin/api-key/api-key.service.ts +++ b/src/app/admin/api-key/api-key.service.ts @@ -25,12 +25,6 @@ export class ApiKeyService { }); } - update(body: ApiKeyRequest, id: number): Observable { - return this.restService.put(this.endpoint, body, id, { - observe: 'response', - }); - } - get(id: number): Observable { return this.restService.get(this.endpoint, {}, id).pipe( map((response: ApiKeyResponse) => { From 2635888debb264575063eba54cf337943af5788b Mon Sep 17 00:00:00 2001 From: August Andersen Date: Thu, 2 Dec 2021 10:45:29 +0100 Subject: [PATCH 19/37] Message to multicast. Made the form and service operations to send to backend, which will then handle the request to chirpstack --- .../iot-devices/downlink.model.ts | 4 +- .../multicast-detail.component.html | 180 +++++++++++------- .../multicast-detail.component.ts | 101 +++++++++- .../multicast-edit.component.ts | 4 +- .../multicast/multicast.service.ts | 1 + src/app/shared/services/downlink.service.ts | 12 ++ src/app/shared/services/snack.service.ts | 15 +- src/assets/i18n/da.json | 14 +- 8 files changed, 253 insertions(+), 78 deletions(-) diff --git a/src/app/applications/iot-devices/downlink.model.ts b/src/app/applications/iot-devices/downlink.model.ts index 6c5f1b8b..7ff3acf3 100644 --- a/src/app/applications/iot-devices/downlink.model.ts +++ b/src/app/applications/iot-devices/downlink.model.ts @@ -1,6 +1,6 @@ export class Downlink { data: string; - port = 0; - confirmedDownlink = false; + port: number = 0; + confirmedDownlink? = false; } diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.html b/src/app/applications/multicast/multicast-detail/multicast-detail.component.html index 4478d6af..388bac0e 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.html +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.html @@ -7,75 +7,127 @@ [dropDownButton]="dropdownButton" (deleteSelectedInDropdown)="onDeleteMulticast()" >
-
-
-
-
-

{{ 'MULTICAST.DETAILS' | translate }}

- - -

- {{ 'MULTICAST.GROUPNAME' | translate }}{{ multicast.name | translate }} -

-

- {{ 'MULTICAST.ADDRESS' | translate }}{{ multicast.mcAddr | translate }} -

-

- {{ 'MULTICAST.NETWORK-KEY' | translate }}{{ multicast.mcNwkSKey | translate }} -

-

- {{ 'MULTICAST.APPLICATION-KEY' | translate }}{{ multicast.mcAppSKey | translate }} -

-

- {{ 'MULTICAST.FRAMECOUNTER' | translate }}{{ multicast.fCnt }} -

-

- {{ 'MULTICAST.DATARATE' | translate }}{{ multicast.dr }} -

-

- {{ 'MULTICAST.FREQUENCY' | translate }}{{ multicast.frequency }} -

+
+
+
+

{{ 'MULTICAST.DETAILS' | translate }}

+ + +

+ {{ 'MULTICAST.GROUPNAME' | translate }}{{ multicast.name | translate }} +

+

+ {{ 'MULTICAST.ADDRESS' | translate }}{{ multicast.mcAddr | translate }} +

+

+ {{ 'MULTICAST.NETWORK-KEY' | translate }}{{ multicast.mcNwkSKey | translate }} +

+

+ {{ 'MULTICAST.APPLICATION-KEY' | translate }}{{ multicast.mcAppSKey | translate }} +

+

+ {{ 'MULTICAST.FRAMECOUNTER' | translate }}{{ multicast.fCnt }} +

+

+ {{ 'MULTICAST.DATARATE' | translate }}{{ multicast.dr }} +

+

+ {{ 'MULTICAST.FREQUENCY' | translate }}{{ multicast.frequency }} +

+

+ {{ 'MULTICAST.GROUPTYPE' | translate }}{{ multicast.groupType }} +

+ +
+
+ +
+
+

{{ 'APPLICATION.ATTACHED-IOT' | translate }}

+

- {{ 'MULTICAST.GROUPTYPE' | translate }}{{ multicast.groupType }} + {{ 'MULTICAST.IOTDEVICE' | translate }}: + + , + {{ device.name }} +

-
-
- -
-
-
-
-

{{ 'APPLICATION.ATTACHED-IOT' | translate }}

-
-

- {{ 'DATATARGET.IOTDEVICE' | translate }}: - - , - {{ device.name }} - -

-
+
+
+

Downlink

+
+
    +
  • + {{ error | translate }} +
  • +
+
+
+ + +
+
+ + +
+
+
diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts index 4a6651ff..bd5d3a2f 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; @@ -9,6 +9,13 @@ import { Multicast } from '../multicast.model'; import { Location } from '@angular/common'; import { MulticastService } from '../multicast.service'; import { SnackService } from '@shared/services/snack.service'; +import { Downlink } from '@applications/iot-devices/downlink.model'; +import { DownlinkService } from '@shared/services/downlink.service'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { MatDialog } from '@angular/material/dialog'; +import { DownlinkDialogComponent } from '@applications/iot-devices/iot-device-detail/downlink/downlink-dialog/downlink-dialog.component'; +import { MatSnackBar } from '@angular/material/snack-bar'; @Component({ selector: 'app-multicast-detail', @@ -20,18 +27,27 @@ export class MulticastDetailComponent implements OnInit { public backButton: BackButton = { label: '', routerLink: '/multicast-list' }; private deleteDialogSubscription: Subscription; public dropdownButton: DropdownButton; + + public formFailedSubmit: boolean = false; private applicationId: number; + public downlink = new Downlink(); + @Input() errorMessages: string[]; constructor( private route: ActivatedRoute, + private snackBar: MatSnackBar, + public dialog: MatDialog, private deleteDialogService: DeleteDialogService, private location: Location, private multicastService: MulticastService, public translate: TranslateService, - public snackService: SnackService + public snackService: SnackService, + public downlinkService: DownlinkService, + private errorMessageService: ErrorMessageService ) {} ngOnInit(): void { + this.errorMessages = []; const id: number = +this.route.snapshot.paramMap.get('multicastId'); if (id) { this.getMulticast(id); @@ -97,6 +113,87 @@ export class MulticastDetailComponent implements OnInit { showFailSnack(): void { this.snackService.showFailSnack(); } + showQueueSnack(): void { + this.snackService.showInQueueSnack(); + } + keyPressHexadecimal(event) { + // make sure only hexadecimal can be typed in input with adresses. + var inp = String.fromCharCode(event.keyCode); + + if (/[a-fA-F0-9]/.test(inp)) { + return true; + } else { + event.preventDefault(); + return false; + } + } + private handleError(error: HttpErrorResponse) { + const errors = this.errorMessageService.handleErrorMessageWithFields(error); + this.errorMessages = errors.errorFields; + this.errorMessages = errors.errorMessages; + } + + clickDownlink() { + if (this.validateHex(this.downlink.data)) { + this.downlinkService.multicastGet(this.multicast.id).subscribe( + (response: any) => { + console.log(response) + if (response.totalCount > 0) { + this.openDownlinkDialog(); + } else { + this.startDownlink(); + } + }, + (error) => { + this.handleError(error); + console.log(error); + } + ); + } + } + openDownlinkDialog() { + const dialog = this.dialog.open(DownlinkDialogComponent, {}); + + dialog.afterClosed().subscribe((result) => { + if (result === true) { + this.startDownlink(); + console.log(`Dialog result: ${result}`); + } + }); + } + + private startDownlink() { + this.errorMessages = []; + this.downlinkService + .multicastPost(this.downlink, this.multicast.id) + .subscribe( + (response) => { + this.showQueueSnack(); + }, + (error) => { + this.handleError(error); + } + ); + } + + private validateHex(input: string): boolean { + const isHexinput = /^[a-fA-F\d]+$/.test(input); + let validator = false; + if (isHexinput) { + validator = true; + } else { + console.log('test'); + this.addToErrorMessage('MULTICAST.DOWNLINK.NO-PORT-OR-PAYLOAD'); + validator = false; + } + return validator; + } + + addToErrorMessage(text: string) { + this.translate.get([text]).subscribe((translations) => { + this.errorMessages.push(translations[text]); + }); + } ngOnDestroy(): void { if (this.deleteDialogSubscription) { diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts index 7dcdfac4..16e95613 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -30,7 +30,7 @@ export class MulticastEditComponent implements OnInit { private applicationId: number; public formFailedSubmit: boolean = false; public multicastTypes: string[] = Object.values(MulticastType); - public periodicities: number[] = [2, 4, 8, 16, 32, 64, 128]; + // public periodicities: number[] = [2, 4, 8, 16, 32, 64, 128]; // used for classB if it has to be used in the future constructor( public translate: TranslateService, @@ -152,7 +152,7 @@ export class MulticastEditComponent implements OnInit { showSavedSnack() { this.snackService.showSavedSnack(); } - showFailSnack(){ + showFailSnack() { this.snackService.showFailSnack(); } showUpdatedSnack() { diff --git a/src/app/applications/multicast/multicast.service.ts b/src/app/applications/multicast/multicast.service.ts index c2dc0c3a..33b1e316 100644 --- a/src/app/applications/multicast/multicast.service.ts +++ b/src/app/applications/multicast/multicast.service.ts @@ -88,6 +88,7 @@ export class MulticastService { updatedByName: this.userMinimalService.getUserNameFrom( multicastResponse.updatedBy ), + }; return model; } diff --git a/src/app/shared/services/downlink.service.ts b/src/app/shared/services/downlink.service.ts index 0715afbe..e39a3ca2 100644 --- a/src/app/shared/services/downlink.service.ts +++ b/src/app/shared/services/downlink.service.ts @@ -10,6 +10,8 @@ export class DownlinkService { private IOTDEVICEURL = 'iot-device/'; private DOWNLINKURL = 'downlink'; + private MULTICASTURL = 'multicast/'; + private DOWNLINKMULTICASTURL = 'downlink-multicast'; constructor(private restService: RestService) { } @@ -22,4 +24,14 @@ export class DownlinkService { const url = this.IOTDEVICEURL + deviceId + '/' + this.DOWNLINKURL; return this.restService.post(url, downlink, params); } + + + public multicastGet(multicastId: number, params = {}): Observable { + const url = this.MULTICASTURL + multicastId + '/' + this.DOWNLINKMULTICASTURL; + return this.restService.get(url, params); + } + public multicastPost(downlink: Downlink, multicastId: number, params = {}): Observable { + const url = this.MULTICASTURL + multicastId + '/' + this.DOWNLINKMULTICASTURL; + return this.restService.post(url, downlink, params); + } } diff --git a/src/app/shared/services/snack.service.ts b/src/app/shared/services/snack.service.ts index 6eb98829..d5831815 100644 --- a/src/app/shared/services/snack.service.ts +++ b/src/app/shared/services/snack.service.ts @@ -12,27 +12,32 @@ export class SnackService { ) {} public showSavedSnack() { - this.snackBar.open(this.translate.instant('SNACK.SAVE'), 'Luk', { + this.snackBar.open(this.translate.instant('SNACK.SAVE'), this.translate.instant('SNACK.CLOSE'), { duration: 10000, }); } public showDeletedSnack() { - this.snackBar.open(this.translate.instant('SNACK.DELETE'), 'Luk', { + this.snackBar.open(this.translate.instant('SNACK.DELETE'), this.translate.instant('SNACK.CLOSE'), { duration: 10000, }); } public showUpdatedSnack() { - this.snackBar.open(this.translate.instant('SNACK.UPDATE'), 'Luk', { + this.snackBar.open(this.translate.instant('SNACK.UPDATE'), this.translate.instant('SNACK.CLOSE'), { duration: 10000, }); } public showFailSnack() { - this.snackBar.open(this.translate.instant('SNACK.FAIL'), 'Luk', { + this.snackBar.open(this.translate.instant('SNACK.FAIL'), this.translate.instant('SNACK.CLOSE'), { duration: 10000, }); } public showLoadFailSnack() { - this.snackBar.open(this.translate.instant('SNACK.LOADFAIL'), 'Luk', { + this.snackBar.open(this.translate.instant('SNACK.LOADFAIL'), this.translate.instant('SNACK.CLOSE'), { + duration: 10000, + }); + } + public showInQueueSnack() { + this.snackBar.open(this.translate.instant('SNACK.QUEUE'), this.translate.instant('SNACK.CLOSE'), { duration: 10000, }); } diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 3964842a..ced5514c 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -51,7 +51,9 @@ "DELETE": "Slettet succesfuldt", "UPDATE": "Opdateret succesfuldt", "FAIL": "Fejl - aktion ikke fuldført", - "LOADFAIL": "Fejl - kunne ikke loade" + "LOADFAIL": "Fejl - kunne ikke loade", + "QUEUE": "Element sat i kø", + "CLOSE": "Luk" }, "SEARCH": { "ICON": "", @@ -177,7 +179,12 @@ "FREQUENCY": "Frekvens (Hz)", "GROUPTYPE": "Gruppe type", "PERIODICITY": "Periodicitet", - "IOTDEVICE": "IoT enhed(er)" + "IOTDEVICE": "IoT enhed(er)", + "DOWNLINK": { + "PORT": "Angiv den ønskede port", + "PAYLOAD": "Angiv det ønskede payload", + "START": "Sæt downlink i kø" + } }, "OPENDATADK": { "QUESTION": { @@ -516,7 +523,8 @@ "MULTICAST": { "SELECT-DEVICES": "Vælg enheder", "SELECTALLDEVICES": "Vælg alle", - "DESELECTALLDEVICES": "Fravælg alle" + "DESELECTALLDEVICES": "Fravælg alle", + "NO-PORT-OR-PAYLOAD": "Angiv en port og en payload" }, "SIGFOX": { "TITLE": "Sigfox specifikke felter", From 5f88b689cca0a98764fe4b1efff29ede13e7c692 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Mon, 6 Dec 2021 12:03:48 +0100 Subject: [PATCH 20/37] Changed details page. Not splitted up in basic details, and lorawan details. --- .../multicast-detail/multicast-detail.component.html | 6 ++++-- .../multicast-detail/multicast-detail.component.scss | 3 +++ src/app/applications/multicast/multicast-response.model.ts | 2 +- src/assets/i18n/da.json | 7 +++++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.html b/src/app/applications/multicast/multicast-detail/multicast-detail.component.html index 388bac0e..919ff162 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.html +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.html @@ -10,13 +10,15 @@
-

{{ 'MULTICAST.DETAILS' | translate }}

+

{{ 'MULTICAST.BASIC-DETAILS' | translate }}

-

{{ 'MULTICAST.GROUPNAME' | translate }}{{ multicast.name | translate }}

+ +

{{ 'MULTICAST.LORAWAN-DETAILS' | translate }}

+

{{ 'MULTICAST.ADDRESS' | translate }}{{ multicast.mcAddr | translate }} diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.scss b/src/app/applications/multicast/multicast-detail/multicast-detail.component.scss index e69de29b..ea3f31bc 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.scss +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.scss @@ -0,0 +1,3 @@ +.loraDetails{ + margin-top: 15px; +} \ No newline at end of file diff --git a/src/app/applications/multicast/multicast-response.model.ts b/src/app/applications/multicast/multicast-response.model.ts index 1d09c4bb..72c8558c 100644 --- a/src/app/applications/multicast/multicast-response.model.ts +++ b/src/app/applications/multicast/multicast-response.model.ts @@ -25,5 +25,5 @@ export class LorawanMulticastDefinition { dataRate: number = 0; frequency: number = 0; groupType: MulticastType; - chirpstackGroupId: string; + chirpstackGroupId?: string; } diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index ced5514c..a824b1e8 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -169,7 +169,8 @@ }, "MULTICAST": { "SAVE": "Gem multicast", - "DETAILS": "Detaljer", + "BASIC-DETAILS": "Basale detaljer", + "LORAWAN-DETAILS": "LoRaWAN detailjer", "GROUPNAME": "Gruppe navn", "ADDRESS": "Adresse", "NETWORK-KEY": "Network session key", @@ -698,7 +699,9 @@ "DELETE-NOT-ALLOWED-HAS-SIGFOX-DEVICE": "Applikation kan ikke blive slettet, da den indeholder en eller flere Sigfox enheder.", "DELETE-NOT-ALLOWED-HAS-LORAWAN-DEVICE": "Service profilen kan ikke blive slettet, da den er i brug af en eller flere LoRaWAN enheder.", "OTAA-INFO-MISSING": "OTAA nøgle mangler eller er ikke gyldig.", - "ABP-INFO-MISSING": "ABP nøgle mangler eller er ikke gyldig." + "ABP-INFO-MISSING": "ABP nøgle mangler eller er ikke gyldig.", + "DIFFERENT-SERVICE-PROFILE": "Dine devices har forskellige service profiles. De skal have den samme service profile!", + "WRONG-SERVICE-PROFILE": "Dine devices har forkert service profile. Vælg devices som har samme service profile som din multicast." }, "PROFILES": { "NAME": "LoRaWAN profiler", From 5780eaea252fae2ab79633d80bf62f60f2e61ea2 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Tue, 14 Dec 2021 14:54:35 +0100 Subject: [PATCH 21/37] PR changes plus search function for devices in multicast --- .../iot-devices/downlink.model.ts | 7 +- .../multicast-detail.component.spec.ts | 25 ------ .../multicast-detail.component.ts | 54 ++++--------- .../multicast-edit.component.html | 18 ++++- .../multicast-edit.component.scss | 5 ++ .../multicast-edit.component.spec.ts | 25 ------ .../multicast-edit.component.ts | 80 +++++++++++++------ .../multicast/multicast-response.model.ts | 6 +- .../multicast-table.component.html | 2 +- .../multicast-table.component.spec.ts | 25 ------ .../multicast-table.component.ts | 54 +++++-------- .../multicast/multicast.module.ts | 3 +- .../multicast/multicast.service.spec.ts | 16 ---- .../multicast/multicast.service.ts | 51 ++++++------ src/app/shared/constants/regex-constants.ts | 10 +++ src/app/shared/enums/multicast-type.ts | 4 +- src/app/shared/pipes/filter-devices.pipe.ts | 26 ++++++ src/app/shared/pipes/pipes.module.ts | 41 +++++----- src/app/shared/services/downlink.service.ts | 23 ++---- src/assets/i18n/da.json | 6 +- 20 files changed, 215 insertions(+), 266 deletions(-) delete mode 100644 src/app/applications/multicast/multicast-detail/multicast-detail.component.spec.ts delete mode 100644 src/app/applications/multicast/multicast-edit/multicast-edit.component.spec.ts delete mode 100644 src/app/applications/multicast/multicast-table/multicast-table.component.spec.ts delete mode 100644 src/app/applications/multicast/multicast.service.spec.ts create mode 100644 src/app/shared/constants/regex-constants.ts create mode 100644 src/app/shared/pipes/filter-devices.pipe.ts diff --git a/src/app/applications/iot-devices/downlink.model.ts b/src/app/applications/iot-devices/downlink.model.ts index 7ff3acf3..b5ed5035 100644 --- a/src/app/applications/iot-devices/downlink.model.ts +++ b/src/app/applications/iot-devices/downlink.model.ts @@ -1,6 +1,5 @@ - export class Downlink { - data: string; - port: number = 0; - confirmedDownlink? = false; + data: string; + port = 0; + confirmedDownlink? = false; } diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.spec.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.spec.ts deleted file mode 100644 index 20b2cf8e..00000000 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MulticastDetailComponent } from './multicast-detail.component'; - -describe('MulticastDetailComponent', () => { - let component: MulticastDetailComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ MulticastDetailComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(MulticastDetailComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts index bd5d3a2f..3bbcccd0 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts @@ -10,12 +10,11 @@ import { Location } from '@angular/common'; import { MulticastService } from '../multicast.service'; import { SnackService } from '@shared/services/snack.service'; import { Downlink } from '@applications/iot-devices/downlink.model'; -import { DownlinkService } from '@shared/services/downlink.service'; import { HttpErrorResponse } from '@angular/common/http'; import { ErrorMessageService } from '@shared/error-message.service'; import { MatDialog } from '@angular/material/dialog'; import { DownlinkDialogComponent } from '@applications/iot-devices/iot-device-detail/downlink/downlink-dialog/downlink-dialog.component'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { keyPressedHex } from '@shared/constants/regex-constants'; @Component({ selector: 'app-multicast-detail', @@ -27,7 +26,6 @@ export class MulticastDetailComponent implements OnInit { public backButton: BackButton = { label: '', routerLink: '/multicast-list' }; private deleteDialogSubscription: Subscription; public dropdownButton: DropdownButton; - public formFailedSubmit: boolean = false; private applicationId: number; public downlink = new Downlink(); @@ -35,14 +33,12 @@ export class MulticastDetailComponent implements OnInit { constructor( private route: ActivatedRoute, - private snackBar: MatSnackBar, - public dialog: MatDialog, + private dialog: MatDialog, private deleteDialogService: DeleteDialogService, private location: Location, private multicastService: MulticastService, - public translate: TranslateService, - public snackService: SnackService, - public downlinkService: DownlinkService, + private translate: TranslateService, + private snackService: SnackService, private errorMessageService: ErrorMessageService ) {} @@ -78,6 +74,7 @@ export class MulticastDetailComponent implements OnInit { this.backButton.routerLink = ['applications', applicationId.toString()]; } + // Class-B: //only if classB can be used // canShowPeriodicity(): boolean { // if (this.multicast.groupType === MulticastType.ClassB) { @@ -101,7 +98,6 @@ export class MulticastDetailComponent implements OnInit { } }); } else { - console.log(response); } }); } @@ -118,15 +114,9 @@ export class MulticastDetailComponent implements OnInit { } keyPressHexadecimal(event) { // make sure only hexadecimal can be typed in input with adresses. - var inp = String.fromCharCode(event.keyCode); - - if (/[a-fA-F0-9]/.test(inp)) { - return true; - } else { - event.preventDefault(); - return false; - } + keyPressedHex(event); } + private handleError(error: HttpErrorResponse) { const errors = this.errorMessageService.handleErrorMessageWithFields(error); this.errorMessages = errors.errorFields; @@ -135,20 +125,15 @@ export class MulticastDetailComponent implements OnInit { clickDownlink() { if (this.validateHex(this.downlink.data)) { - this.downlinkService.multicastGet(this.multicast.id).subscribe( - (response: any) => { - console.log(response) + this.multicastService + .multicastGet(this.multicast.id) + .subscribe((response: any) => { if (response.totalCount > 0) { this.openDownlinkDialog(); } else { this.startDownlink(); } - }, - (error) => { - this.handleError(error); - console.log(error); - } - ); + }); } } openDownlinkDialog() { @@ -157,17 +142,16 @@ export class MulticastDetailComponent implements OnInit { dialog.afterClosed().subscribe((result) => { if (result === true) { this.startDownlink(); - console.log(`Dialog result: ${result}`); } }); } private startDownlink() { this.errorMessages = []; - this.downlinkService + this.multicastService .multicastPost(this.downlink, this.multicast.id) .subscribe( - (response) => { + () => { this.showQueueSnack(); }, (error) => { @@ -178,15 +162,13 @@ export class MulticastDetailComponent implements OnInit { private validateHex(input: string): boolean { const isHexinput = /^[a-fA-F\d]+$/.test(input); - let validator = false; + if (isHexinput) { - validator = true; + return true; } else { - console.log('test'); this.addToErrorMessage('MULTICAST.DOWNLINK.NO-PORT-OR-PAYLOAD'); - validator = false; + return false; } - return validator; } addToErrorMessage(text: string) { @@ -196,8 +178,6 @@ export class MulticastDetailComponent implements OnInit { } ngOnDestroy(): void { - if (this.deleteDialogSubscription) { - this.deleteDialogSubscription.unsubscribe(); - } + this.deleteDialogSubscription?.unsubscribe(); } } diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html index 91945156..42e456dc 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html @@ -196,7 +196,7 @@

-->
-
+
@@ -211,7 +211,12 @@ name="devices" [compareWith]="compare" [(ngModel)]="multicast.iotDevices" + panelClass="overflow-x-hidden" > + + + - {{ - device.name - }} + {{ device.name }}
diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.scss b/src/app/applications/multicast/multicast-edit/multicast-edit.component.scss index e69de29b..74d588da 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.scss +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.scss @@ -0,0 +1,5 @@ +.onlyLorawan { + font-weight: bold; + font-size: 13px; + margin-left: 10px; +} diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.spec.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.spec.ts deleted file mode 100644 index 84638528..00000000 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MulticastEditComponent } from './multicast-edit.component'; - -describe('MulticastEditComponent', () => { - let component: MulticastEditComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ MulticastEditComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(MulticastEditComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts index 16e95613..f0715c58 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -6,11 +6,14 @@ import { MulticastType } from '@shared/enums/multicast-type'; import { ErrorMessageService } from '@shared/error-message.service'; import { SnackService } from '@shared/services/snack.service'; import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; -import { Subscription } from 'rxjs'; +import { ReplaySubject, Subject, Subscription } from 'rxjs'; import { Multicast } from '../multicast.model'; import { MulticastService } from '../multicast.service'; import { IotDevice } from '@applications/iot-devices/iot-device.model'; import { ApplicationService } from '@applications/application.service'; +import { keyPressedHex } from '@shared/constants/regex-constants'; +import { FormControl } from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-multicast-edit', @@ -22,25 +25,30 @@ export class MulticastEditComponent implements OnInit { public multicastId: number; public errorMessages: any; private multicastSubscription: Subscription; + public searchDevices: FormControl = new FormControl(); public errorFields: string[]; - public iotDevices: IotDevice[]; + public iotDevices: IotDevice[] = []; @Input() submitButton: string; public backButtonTitle: string; public multicast: Multicast = new Multicast(); private applicationId: number; public formFailedSubmit: boolean = false; public multicastTypes: string[] = Object.values(MulticastType); - // public periodicities: number[] = [2, 4, 8, 16, 32, 64, 128]; // used for classB if it has to be used in the future + // Class-B: { public periodicities: number[] = [2, 4, 8, 16, 32, 64, 128]; // used for classB if it has to be used in the future } + public deviceFilterCtrl: FormControl = new FormControl(); + public filteredDevicesMulti: ReplaySubject = new ReplaySubject< + IotDevice[] + >(1); constructor( - public translate: TranslateService, + private translate: TranslateService, private route: ActivatedRoute, private router: Router, - public multicastService: MulticastService, - public errorMessageService: ErrorMessageService, - public scrollToTopService: ScrollToTopService, - public snackService: SnackService, - public applicationService: ApplicationService + private multicastService: MulticastService, + private errorMessageService: ErrorMessageService, + private scrollToTopService: ScrollToTopService, + private snackService: SnackService, + private applicationService: ApplicationService ) {} ngOnInit(): void { @@ -71,7 +79,32 @@ export class MulticastEditComponent implements OnInit { // If edit is pressed, then get the specific multicast. this.getMulticast(this.multicastId); } + + this.deviceFilterCtrl.valueChanges + .pipe(takeUntil(this._onDestroy)) + .subscribe(() => { + this.filterDevicesMulti(); + }); + } + + private filterDevicesMulti() { + if (!this.iotDevices) { + return; + } + // get the search keyword + let search = this.deviceFilterCtrl?.value?.trim(); + if (!search) { + this.filteredDevicesMulti.next(this.iotDevices.slice()); + return; + } else { + search = search.toLowerCase(); + } + const filtered = this.iotDevices.filter((device) => { + return device.name.toLocaleLowerCase().indexOf(search) > -1; + }); + this.filteredDevicesMulti.next(filtered); } + onSubmit(): void { if (this.multicastId) { // if already created, only update @@ -91,9 +124,10 @@ export class MulticastEditComponent implements OnInit { } getApplication(id: number) { - this.applicationService - .getApplication(id) - .subscribe((application) => (this.iotDevices = application.iotDevices)); + this.applicationService.getApplication(id).subscribe((application) => { + this.iotDevices = application.iotDevices; + this.filteredDevicesMulti.next(this.iotDevices.slice()); + }); } //only if classB can be used @@ -135,8 +169,11 @@ export class MulticastEditComponent implements OnInit { } ); } - public compare(o1: any, o2: any): boolean { - return o1 && o2 && o1.id == o2.id; + public compare( + o1: IotDevice | undefined, + o2: IotDevice | undefined + ): boolean { + return o1?.id === o2?.id; } selectAll() { @@ -159,15 +196,7 @@ export class MulticastEditComponent implements OnInit { this.snackService.showUpdatedSnack(); } keyPressHexadecimal(event) { - // make sure only hexadecimal can be typed in input with adresses. - var inp = String.fromCharCode(event.keyCode); - - if (/[a-fA-F0-9]/.test(inp)) { - return true; - } else { - event.preventDefault(); - return false; - } + keyPressedHex(event); } private resetErrors() { this.errorFields = []; @@ -181,8 +210,7 @@ export class MulticastEditComponent implements OnInit { this.scrollToTopService.scrollToTop(); } ngOnDestroy(): void { - if (this.multicastSubscription) { - this.multicastSubscription.unsubscribe(); - } + this.multicastSubscription?.unsubscribe(); } + private _onDestroy = new Subject(); } diff --git a/src/app/applications/multicast/multicast-response.model.ts b/src/app/applications/multicast/multicast-response.model.ts index 72c8558c..16d385f6 100644 --- a/src/app/applications/multicast/multicast-response.model.ts +++ b/src/app/applications/multicast/multicast-response.model.ts @@ -21,9 +21,9 @@ export class LorawanMulticastDefinition { address: string; networkSessionKey: string; applicationSessionKey: string; - frameCounter: number = 0; - dataRate: number = 0; - frequency: number = 0; + frameCounter = 0; + dataRate = 0; + frequency = 0; groupType: MulticastType; chirpstackGroupId?: string; } diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.html b/src/app/applications/multicast/multicast-table/multicast-table.component.html index f7f5892a..babb247a 100644 --- a/src/app/applications/multicast/multicast-table/multicast-table.component.html +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.html @@ -21,7 +21,7 @@ {{ 'MULTICAST-TABLE.TYPE' | translate }} - {{element.lorawanMulticastDefinition.groupType | translate}} + {{element.lorawanMulticastDefinition.groupType}} diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.spec.ts b/src/app/applications/multicast/multicast-table/multicast-table.component.spec.ts deleted file mode 100644 index 4a46e62b..00000000 --- a/src/app/applications/multicast/multicast-table/multicast-table.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MulticastTableComponent } from './multicast-table.component'; - -describe('MulticastTableComponent', () => { - let component: MulticastTableComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ MulticastTableComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(MulticastTableComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.ts b/src/app/applications/multicast/multicast-table/multicast-table.component.ts index 908c2849..7fc714c2 100644 --- a/src/app/applications/multicast/multicast-table/multicast-table.component.ts +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.ts @@ -32,7 +32,7 @@ export class MulticastTableComponent displayedColumns: string[] = ['groupName', 'groupType', 'menu']; dataSource = new MatTableDataSource(); multicasts: Multicast[]; - resultsLength = 0 + resultsLength = 0; public canEdit = false; @Input() isLoadingResults: boolean = true; public pageSize = environment.tablePageSize; @@ -57,7 +57,7 @@ export class MulticastTableComponent ngOnInit(): void { this.applicationId = +Number(this.route.parent.snapshot.paramMap.get('id')); - this.getMulticasts(); // loads the multicasts + this.getMulticasts(); this.canEdit = this.meService.canWriteInTargetOrganization(); } @@ -70,7 +70,6 @@ export class MulticastTableComponent if (this.applicationId) { this.multicastSubscription = this.multicastService .getMulticastsByApplicationId( - // gets multicasts from db by applicationId this.pageLimit, this.pageOffset * this.pageLimit, this.applicationId @@ -86,7 +85,8 @@ export class MulticastTableComponent this.pageTotal = Math.ceil(multicasts.count / this.pageLimit); } if (multicasts.ok === false) { - this.showLoadFailSnack(); // if not possible to load the multicasts, show error. + // ok is only defined when it's an error. + this.snackService.showLoadFailSnack(); } }); } @@ -96,39 +96,25 @@ export class MulticastTableComponent this.deleteDialogSubscription = this.deleteDialogService .showSimpleDialog() .subscribe((response) => { - if (response) { // if user presses "yes, delete", then delete the multicast. - this.multicastService - .delete(multicast.id) - .subscribe((response) => { - if (response.ok && response.body.affected > 0) { // if deleted succesfully, get the new array of multicasts and show a succesful snack. - this.getMulticasts(); - this.showDeletedSnack(); - } else { - this.showFailSnack(); - } - }); - } else { - console.log(response); + if (response) { + // if user presses "yes, delete", then delete the multicast. + this.multicastService.delete(multicast.id).subscribe((response) => { + if (response.ok && response.body.affected > 0) { + // if deleted succesfully, get the new array of multicasts and show a succesful snack. + this.getMulticasts(); + this.snackService.showDeletedSnack(); + } else { + this.snackService.showFailSnack(); + } + }); } }); } - - showLoadFailSnack() { - this.snackService.showLoadFailSnack(); - } - showFailSnack() { - this.snackService.showFailSnack(); - } - showDeletedSnack() { - this.snackService.showDeletedSnack(); - } - ngOnDestroy() { // inspired by datatarget + ngOnDestroy() { // prevent memory leak by unsubscribing - if (this.multicastSubscription) { - this.multicastSubscription.unsubscribe(); - } - if (this.deleteDialogSubscription) { - this.deleteDialogSubscription.unsubscribe(); - } + + this.multicastSubscription?.unsubscribe(); + + this.deleteDialogSubscription?.unsubscribe(); } } diff --git a/src/app/applications/multicast/multicast.module.ts b/src/app/applications/multicast/multicast.module.ts index cdc2b9d8..e56d5271 100644 --- a/src/app/applications/multicast/multicast.module.ts +++ b/src/app/applications/multicast/multicast.module.ts @@ -7,10 +7,10 @@ import { MulticastTableComponent } from './multicast-table/multicast-table.compo import { RouterModule } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { NGMaterialModule } from '@shared/Modules/materiale.module'; -import { FormModule } from '@shared/components/forms/form.module'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { PipesModule } from '@shared/pipes/pipes.module'; +import { MatSelectSearchModule } from '@shared/components/mat-select-search/mat-select-search.module'; @NgModule({ declarations: [ @@ -28,6 +28,7 @@ import { PipesModule } from '@shared/pipes/pipes.module'; FormsModule, SharedModule, PipesModule, + MatSelectSearchModule, ], exports: [ MulticastDetailComponent, diff --git a/src/app/applications/multicast/multicast.service.spec.ts b/src/app/applications/multicast/multicast.service.spec.ts deleted file mode 100644 index c86a61c3..00000000 --- a/src/app/applications/multicast/multicast.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { MulticastService } from './multicast.service'; - -describe('MulticastService', () => { - let service: MulticastService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(MulticastService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/src/app/applications/multicast/multicast.service.ts b/src/app/applications/multicast/multicast.service.ts index 33b1e316..f769e24a 100644 --- a/src/app/applications/multicast/multicast.service.ts +++ b/src/app/applications/multicast/multicast.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { UserMinimalService } from '@app/admin/users/user-minimal.service'; +import { Downlink } from '@applications/iot-devices/downlink.model'; import { RestService } from '@shared/services/rest.service'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -15,7 +16,10 @@ export class MulticastService { private userMinimalService: UserMinimalService ) {} - private multicastURL = 'multicast'; // api endpoint + private multicastURL = 'multicast'; + private multicastDownlinkURL = 'multicast/'; + private DOWNLINKMULTICASTURL = 'downlink-multicast'; + getMulticastsByApplicationId( limit: number, offset: number, @@ -26,42 +30,27 @@ export class MulticastService { offset, applicationId, }; - return this.restService.get(this.multicastURL, body); // get's the multicasts from specific application by the url and the body with applicationId. + return this.restService.get(this.multicastURL, body); } + get(id: number): Observable { - // Get's a single multicast by id. return this.restService.get(this.multicastURL, {}, id).pipe( + // bind "this" correctly by creating a new lambda function map((response: MulticastResponse) => { - const multicast = this.mapToMulticast(response); // maps the response from backend. + const multicast = this.mapToMulticast(response); return multicast; }) ); } + delete(id: number) { - // deletes a chosen multicast by id return this.restService.delete(this.multicastURL, id); } update(multicast: Multicast): Observable { - // updates the chosen multicast by id - return this.restService - .put(this.multicastURL, multicast, multicast.id) - .pipe - // map((response: MulticastResponse) => { - // const multicast = this.mapToMulticast(response); - // return multicast; - // }) - (); + return this.restService.put(this.multicastURL, multicast, multicast.id); } create(multicast: Multicast): Observable { - // creates a new multicast - return this.restService - .post(this.multicastURL, multicast) - .pipe - // map((response: MulticastResponse) => { - // const multicast = this.mapToMulticast(response); - // return multicast; - // }) - (); + return this.restService.post(this.multicastURL, multicast); } private mapToMulticast(multicastResponse: MulticastResponse): Multicast { @@ -88,8 +77,22 @@ export class MulticastService { updatedByName: this.userMinimalService.getUserNameFrom( multicastResponse.updatedBy ), - }; return model; } + + public multicastGet(multicastId: number, params = {}): Observable { + const url = + this.multicastDownlinkURL + multicastId + '/' + this.DOWNLINKMULTICASTURL; + return this.restService.get(url, params); + } + public multicastPost( + downlink: Downlink, + multicastId: number, + params = {} + ): Observable { + const url = + this.multicastDownlinkURL + multicastId + '/' + this.DOWNLINKMULTICASTURL; + return this.restService.post(url, downlink, params); + } } diff --git a/src/app/shared/constants/regex-constants.ts b/src/app/shared/constants/regex-constants.ts new file mode 100644 index 00000000..ef7a76a5 --- /dev/null +++ b/src/app/shared/constants/regex-constants.ts @@ -0,0 +1,10 @@ +export function keyPressedHex(event) { + var inp = String.fromCharCode(event.keyCode); + + if (/[a-fA-F0-9]/.test(inp)) { + return true; + } else { + event.preventDefault(); + return false; + } +} diff --git a/src/app/shared/enums/multicast-type.ts b/src/app/shared/enums/multicast-type.ts index f078b06c..acb0283d 100644 --- a/src/app/shared/enums/multicast-type.ts +++ b/src/app/shared/enums/multicast-type.ts @@ -1,4 +1,4 @@ export enum MulticastType { - // ClassB = 'Class-B', - ClassC = 'CLASS_C' + // Class-B: {ClassB = 'Class-B'}, + ClassC = 'CLASS_C', } diff --git a/src/app/shared/pipes/filter-devices.pipe.ts b/src/app/shared/pipes/filter-devices.pipe.ts new file mode 100644 index 00000000..47edb0bf --- /dev/null +++ b/src/app/shared/pipes/filter-devices.pipe.ts @@ -0,0 +1,26 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { IotDevice } from '@applications/iot-devices/iot-device.model'; +import { DeviceType } from '@shared/enums/device-type'; + +@Pipe({ + name: 'filterDevices', +}) +export class FilterDevicesPipe implements PipeTransform { + transform(value: IotDevice[] | undefined, ..._: unknown[]): IotDevice[] { + // Filter devices so only LoRaWAN devices will be shown. + const lorawanDevices: IotDevice[] = []; + + if (!value) { + return lorawanDevices; + } + value.forEach((device) => { + if (device.type === DeviceType.LORAWAN) { + lorawanDevices.push(device); + } + }); + + lorawanDevices.sort((a, b) => a.name.localeCompare(b.name)); + + return lorawanDevices; + } +} diff --git a/src/app/shared/pipes/pipes.module.ts b/src/app/shared/pipes/pipes.module.ts index a3e26445..c5aacc28 100644 --- a/src/app/shared/pipes/pipes.module.ts +++ b/src/app/shared/pipes/pipes.module.ts @@ -5,26 +5,27 @@ import { ActiveDeactivePipe } from './activeDeactive.pipe'; import { isGlobalAdminPipe } from './is-global-admin.pipe'; import { CreatedUpdatedByPipe } from './created-updated-by.pipe'; import { CustomDatePipe, CustomTableDatePipe } from './custom-date.pipe'; +import { FilterDevicesPipe } from './filter-devices.pipe'; @NgModule({ - declarations: [ - isGlobalAdminPipe, - ActiveDeactivePipe, - YesNoPipe, - CustomDatePipe, - CustomTableDatePipe, - CreatedUpdatedByPipe, - ], - imports: [ - CommonModule - ], - exports: [ - isGlobalAdminPipe, - ActiveDeactivePipe, - YesNoPipe, - CustomDatePipe, - CustomTableDatePipe, - CreatedUpdatedByPipe, - ] + declarations: [ + isGlobalAdminPipe, + ActiveDeactivePipe, + YesNoPipe, + CustomDatePipe, + CustomTableDatePipe, + CreatedUpdatedByPipe, + FilterDevicesPipe, + ], + imports: [CommonModule], + exports: [ + isGlobalAdminPipe, + ActiveDeactivePipe, + YesNoPipe, + CustomDatePipe, + CustomTableDatePipe, + CreatedUpdatedByPipe, + FilterDevicesPipe, + ], }) -export class PipesModule { } +export class PipesModule {} diff --git a/src/app/shared/services/downlink.service.ts b/src/app/shared/services/downlink.service.ts index e39a3ca2..b031e1bb 100644 --- a/src/app/shared/services/downlink.service.ts +++ b/src/app/shared/services/downlink.service.ts @@ -4,34 +4,25 @@ import { Observable } from 'rxjs'; import { Downlink } from '@applications/iot-devices/downlink.model'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class DownlinkService { - private IOTDEVICEURL = 'iot-device/'; private DOWNLINKURL = 'downlink'; - private MULTICASTURL = 'multicast/'; - private DOWNLINKMULTICASTURL = 'downlink-multicast'; - constructor(private restService: RestService) { } + constructor(private restService: RestService) {} public get(deviceId: number, params = {}): Observable { const url = this.IOTDEVICEURL + deviceId + '/' + this.DOWNLINKURL; return this.restService.get(url, params); } - public post(downlink: Downlink, deviceId: number, params = {}): Observable { + public post( + downlink: Downlink, + deviceId: number, + params = {} + ): Observable { const url = this.IOTDEVICEURL + deviceId + '/' + this.DOWNLINKURL; return this.restService.post(url, downlink, params); } - - - public multicastGet(multicastId: number, params = {}): Observable { - const url = this.MULTICASTURL + multicastId + '/' + this.DOWNLINKMULTICASTURL; - return this.restService.get(url, params); - } - public multicastPost(downlink: Downlink, multicastId: number, params = {}): Observable { - const url = this.MULTICASTURL + multicastId + '/' + this.DOWNLINKMULTICASTURL; - return this.restService.post(url, downlink, params); - } } diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index a824b1e8..2e6c0614 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -91,7 +91,6 @@ "SAVE": "Gem applikation", "DELETE": "Slet applikation", "DELETE-HAS-DEVICES-PROMPT": "Der er knyttet IoT-enheder til denne applikation. Disse vil også blive slettet. Slet alligevel?", - "DELETE-HAS-SIGFOX-DEVICES-PROMPT": "Applikationen kan ikke slettes, da der er knyttet Sigfox enheder til den", "NAME": "Applikationens navn", "DESCRIPTION": "Applikationens beskrivelse", "ATTACHED-IOT": "Tilknyttede IoT enheder", @@ -180,7 +179,7 @@ "FREQUENCY": "Frekvens (Hz)", "GROUPTYPE": "Gruppe type", "PERIODICITY": "Periodicitet", - "IOTDEVICE": "IoT enhed(er)", + "IOTDEVICE": "IoT enheder", "DOWNLINK": { "PORT": "Angiv den ønskede port", "PAYLOAD": "Angiv det ønskede payload", @@ -525,7 +524,8 @@ "SELECT-DEVICES": "Vælg enheder", "SELECTALLDEVICES": "Vælg alle", "DESELECTALLDEVICES": "Fravælg alle", - "NO-PORT-OR-PAYLOAD": "Angiv en port og en payload" + "NO-PORT-OR-PAYLOAD": "Angiv en port og en payload", + "ONLY-LORAWAN": "* På nuværende tidspunkt er det kun muligt at tilknytte LoRaWAN devices." }, "SIGFOX": { "TITLE": "Sigfox specifikke felter", From e859ab15acd91f44aeb114ede8f2221a8377dadd Mon Sep 17 00:00:00 2001 From: August Andersen Date: Wed, 15 Dec 2021 10:39:24 +0100 Subject: [PATCH 22/37] PR changes - fixed pagination for multicast --- .../application-detail.component.html | 5 +- .../application-detail.component.ts | 3 - .../iot-devices-table.component.ts | 1 - .../multicast-table.component.html | 2 +- .../multicast-table.component.ts | 80 +++++++++++-------- .../multicast/multicast.service.ts | 4 + 6 files changed, 54 insertions(+), 41 deletions(-) diff --git a/src/app/applications/application-detail/application-detail.component.html b/src/app/applications/application-detail/application-detail.component.html index d044ddea..ff4a356a 100644 --- a/src/app/applications/application-detail/application-detail.component.html +++ b/src/app/applications/application-detail/application-detail.component.html @@ -1,7 +1,6 @@
+ [addDetailDowndown]="true" [dropDownButton]="dropdownButton" (deleteSelectedInDropdown)="onDeleteApplication()">
@@ -54,7 +53,7 @@

Detaljer

- +
diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index 168f6ac8..75eafee4 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -103,7 +103,4 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { this.deleteDialogSubscription.unsubscribe(); } } - updatePageLimit(limit: any) { - console.log(limit); - } } diff --git a/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.ts b/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.ts index ac550bb7..c2ece359 100644 --- a/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.ts +++ b/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.ts @@ -20,7 +20,6 @@ import { DeleteDialogComponent } from '@shared/components/delete-dialog/delete-d import { DeviceType } from '@shared/enums/device-type'; import { MatDialog } from '@angular/material/dialog'; import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; -import { ReceivedMessageMetadata } from '@shared/models/received-message-metadata.model'; import { environment } from '@environments/environment'; import { startWith, switchMap, map, catchError } from 'rxjs/operators'; import { MeService } from '@shared/services/me.service'; diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.html b/src/app/applications/multicast/multicast-table/multicast-table.component.html index babb247a..2b35a5f4 100644 --- a/src/app/applications/multicast/multicast-table/multicast-table.component.html +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.html @@ -2,7 +2,7 @@
- +
diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.ts b/src/app/applications/multicast/multicast-table/multicast-table.component.ts index 7fc714c2..b3fd804d 100644 --- a/src/app/applications/multicast/multicast-table/multicast-table.component.ts +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.ts @@ -8,15 +8,14 @@ import { } from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; -import { MatTableDataSource } from '@angular/material/table'; import { ActivatedRoute } from '@angular/router'; import { environment } from '@environments/environment'; import { TranslateService } from '@ngx-translate/core'; import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; -import { tableSorter } from '@shared/helpers/table-sorting.helper'; import { MeService } from '@shared/services/me.service'; import { SnackService } from '@shared/services/snack.service'; -import { Subscription } from 'rxjs'; +import { merge, Observable, Subscription, of as observableOf } from 'rxjs'; +import { catchError, map, startWith, switchMap } from 'rxjs/operators'; import { Multicast, MulticastData } from '../multicast.model'; import { MulticastService } from '../multicast.service'; @@ -30,15 +29,12 @@ export class MulticastTableComponent @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; displayedColumns: string[] = ['groupName', 'groupType', 'menu']; - dataSource = new MatTableDataSource(); - multicasts: Multicast[]; + multicasts: Multicast[] = []; resultsLength = 0; public canEdit = false; @Input() isLoadingResults: boolean = true; public pageSize = environment.tablePageSize; - @Input() pageLimit: number; public pageOffset = 0; - public pageTotal: number; public applicationId: number; private multicastSubscription: Subscription; @@ -57,38 +53,52 @@ export class MulticastTableComponent ngOnInit(): void { this.applicationId = +Number(this.route.parent.snapshot.paramMap.get('id')); - this.getMulticasts(); this.canEdit = this.meService.canWriteInTargetOrganization(); } ngAfterViewInit() { - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; + // If the user changes the sort order, reset back to the first page. + this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0)); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + startWith({}), + switchMap(() => { + this.isLoadingResults = true; + const multicasts = this.getMulticasts( + this.sort.active, + this.sort.direction + ); + //TODO::: Snack here + return multicasts; + }), + map((data) => { + // Flip flag to show that loading has finished. + this.isLoadingResults = false; + this.resultsLength = data.count; + + return data.data; + }), + catchError(() => { + this.isLoadingResults = false; + return observableOf([]); + }) + ) + .subscribe((data) => (this.multicasts = data)); } - getMulticasts(): void { + getMulticasts( + orderByColumn: string, + orderByDirection: string + ): Observable { if (this.applicationId) { - this.multicastSubscription = this.multicastService - .getMulticastsByApplicationId( - this.pageLimit, - this.pageOffset * this.pageLimit, - this.applicationId - ) - .subscribe((multicasts: MulticastData) => { - this.multicasts = multicasts.data; - this.dataSource = new MatTableDataSource(this.multicasts); // these lines of code is inspired/taken from datatarget. - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; - this.dataSource.sortingDataAccessor = tableSorter; - this.isLoadingResults = false; - if (this.pageLimit) { - this.pageTotal = Math.ceil(multicasts.count / this.pageLimit); - } - if (multicasts.ok === false) { - // ok is only defined when it's an error. - this.snackService.showLoadFailSnack(); - } - }); + return this.multicastService.getMulticastsByApplicationId( + this.paginator.pageSize, + this.paginator.pageIndex * this.paginator.pageSize, + orderByDirection, + orderByColumn, + this.applicationId + ); } } @@ -101,7 +111,11 @@ export class MulticastTableComponent this.multicastService.delete(multicast.id).subscribe((response) => { if (response.ok && response.body.affected > 0) { // if deleted succesfully, get the new array of multicasts and show a succesful snack. - this.getMulticasts(); + this.paginator.page.emit({ + pageIndex: this.paginator.pageIndex, + pageSize: this.paginator.pageSize, + length: this.resultsLength, + }); this.snackService.showDeletedSnack(); } else { this.snackService.showFailSnack(); diff --git a/src/app/applications/multicast/multicast.service.ts b/src/app/applications/multicast/multicast.service.ts index f769e24a..9a674d0c 100644 --- a/src/app/applications/multicast/multicast.service.ts +++ b/src/app/applications/multicast/multicast.service.ts @@ -23,11 +23,15 @@ export class MulticastService { getMulticastsByApplicationId( limit: number, offset: number, + sort: string, + orderOn: string, applicationId: number ): Observable { const body = { limit, offset, + sort, + orderOn, applicationId, }; return this.restService.get(this.multicastURL, body); From 144a650aacbac314c5e49ee382d36001e758d7ed Mon Sep 17 00:00:00 2001 From: August Andersen Date: Thu, 16 Dec 2021 13:39:06 +0100 Subject: [PATCH 23/37] Minor snack changes and check if devices is included before sending downlink --- .../multicast-detail.component.ts | 40 ++++++++----------- .../multicast-edit.component.ts | 17 ++------ .../multicast-table.component.ts | 4 +- src/app/shared/services/snack.service.ts | 5 +++ src/assets/i18n/da.json | 3 +- 5 files changed, 31 insertions(+), 38 deletions(-) diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts index 3bbcccd0..9aeb3cc1 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts @@ -91,10 +91,10 @@ export class MulticastDetailComponent implements OnInit { .delete(this.multicast.id) .subscribe((response) => { if (response.status !== 0) { - this.showDeletedSnack(); + this.snackService.showDeletedSnack(); this.location.back(); } else { - this.showFailSnack(); + this.snackService.showFailSnack(); } }); } else { @@ -102,16 +102,6 @@ export class MulticastDetailComponent implements OnInit { }); } - showDeletedSnack(): void { - this.snackService.showDeletedSnack(); - } - - showFailSnack(): void { - this.snackService.showFailSnack(); - } - showQueueSnack(): void { - this.snackService.showInQueueSnack(); - } keyPressHexadecimal(event) { // make sure only hexadecimal can be typed in input with adresses. keyPressedHex(event); @@ -124,16 +114,20 @@ export class MulticastDetailComponent implements OnInit { } clickDownlink() { - if (this.validateHex(this.downlink.data)) { - this.multicastService - .multicastGet(this.multicast.id) - .subscribe((response: any) => { - if (response.totalCount > 0) { - this.openDownlinkDialog(); - } else { - this.startDownlink(); - } - }); + if (this.multicast.iotDevices.length > 0) { + if (this.validateHex(this.downlink.data)) { + this.multicastService + .multicastGet(this.multicast.id) + .subscribe((response: any) => { + if (response.totalCount > 0) { + this.openDownlinkDialog(); + } else { + this.startDownlink(); + } + }); + } + } else { + this.snackService.showSendDownlinkFailNoDevices(); } } openDownlinkDialog() { @@ -152,7 +146,7 @@ export class MulticastDetailComponent implements OnInit { .multicastPost(this.downlink, this.multicast.id) .subscribe( () => { - this.showQueueSnack(); + this.snackService.showInQueueSnack(); }, (error) => { this.handleError(error); diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts index f0715c58..56e5014f 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -143,11 +143,11 @@ export class MulticastEditComponent implements OnInit { this.multicastService.update(this.multicast).subscribe( () => { - this.showUpdatedSnack(); + this.snackService.showUpdatedSnack(); this.routeBack(); }, (error: HttpErrorResponse) => { - this.showFailSnack(); + this.snackService.showFailSnack(); this.handleError(error); this.formFailedSubmit = true; } @@ -159,11 +159,11 @@ export class MulticastEditComponent implements OnInit { this.multicastService.create(this.multicast).subscribe( () => { - this.showSavedSnack(); + this.snackService.showSavedSnack(); this.routeBack(); }, (error: HttpErrorResponse) => { - this.showFailSnack(); + this.snackService.showFailSnack(); this.handleError(error); this.formFailedSubmit = true; } @@ -186,15 +186,6 @@ export class MulticastEditComponent implements OnInit { routeBack(): void { this.router.navigate(['applications', this.applicationId.toString()]); } - showSavedSnack() { - this.snackService.showSavedSnack(); - } - showFailSnack() { - this.snackService.showFailSnack(); - } - showUpdatedSnack() { - this.snackService.showUpdatedSnack(); - } keyPressHexadecimal(event) { keyPressedHex(event); } diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.ts b/src/app/applications/multicast/multicast-table/multicast-table.component.ts index b3fd804d..1a685a65 100644 --- a/src/app/applications/multicast/multicast-table/multicast-table.component.ts +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.ts @@ -69,11 +69,13 @@ export class MulticastTableComponent this.sort.active, this.sort.direction ); - //TODO::: Snack here return multicasts; }), map((data) => { // Flip flag to show that loading has finished. + if (data.ok === false) { + this.snackService.showLoadFailSnack(); + } this.isLoadingResults = false; this.resultsLength = data.count; diff --git a/src/app/shared/services/snack.service.ts b/src/app/shared/services/snack.service.ts index d5831815..16cd3b80 100644 --- a/src/app/shared/services/snack.service.ts +++ b/src/app/shared/services/snack.service.ts @@ -41,4 +41,9 @@ export class SnackService { duration: 10000, }); } + public showSendDownlinkFailNoDevices(){ + this.snackBar.open(this.translate.instant('SNACK.NODEVICES'), this.translate.instant('SNACK.CLOSE'), { + duration: 10000, + }); + } } diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 2e6c0614..a13050c4 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -53,7 +53,8 @@ "FAIL": "Fejl - aktion ikke fuldført", "LOADFAIL": "Fejl - kunne ikke loade", "QUEUE": "Element sat i kø", - "CLOSE": "Luk" + "CLOSE": "Luk", + "NODEVICES": "Kan ikke sende downlink - der er ingen devices!" }, "SEARCH": { "ICON": "", From 44de7a5e0d819c851400bcdcbf4684024ccbfdf3 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Wed, 22 Dec 2021 15:16:54 +0100 Subject: [PATCH 24/37] PR Changes --- .../multicast-detail.component.ts | 14 ++++++++------ .../multicast-edit/multicast-edit.component.ts | 14 +++++++------- .../multicast-table.component.ts | 5 +---- .../applications/multicast/multicast.service.ts | 4 ++-- src/app/shared/services/downlink.service.ts | 17 ++++++++++++++++- src/app/shared/services/snack.service.ts | 5 ----- 6 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts index 9aeb3cc1..a2a43859 100644 --- a/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts +++ b/src/app/applications/multicast/multicast-detail/multicast-detail.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; @@ -15,18 +15,19 @@ import { ErrorMessageService } from '@shared/error-message.service'; import { MatDialog } from '@angular/material/dialog'; import { DownlinkDialogComponent } from '@applications/iot-devices/iot-device-detail/downlink/downlink-dialog/downlink-dialog.component'; import { keyPressedHex } from '@shared/constants/regex-constants'; +import { DownlinkService } from '@shared/services/downlink.service'; @Component({ selector: 'app-multicast-detail', templateUrl: './multicast-detail.component.html', styleUrls: ['./multicast-detail.component.scss'], }) -export class MulticastDetailComponent implements OnInit { +export class MulticastDetailComponent implements OnInit, OnDestroy { public multicast: Multicast; public backButton: BackButton = { label: '', routerLink: '/multicast-list' }; private deleteDialogSubscription: Subscription; public dropdownButton: DropdownButton; - public formFailedSubmit: boolean = false; + public formFailedSubmit = false; private applicationId: number; public downlink = new Downlink(); @Input() errorMessages: string[]; @@ -39,7 +40,8 @@ export class MulticastDetailComponent implements OnInit { private multicastService: MulticastService, private translate: TranslateService, private snackService: SnackService, - private errorMessageService: ErrorMessageService + private errorMessageService: ErrorMessageService, + private downlinkService: DownlinkService ) {} ngOnInit(): void { @@ -75,7 +77,7 @@ export class MulticastDetailComponent implements OnInit { } // Class-B: - //only if classB can be used + // only if classB can be used // canShowPeriodicity(): boolean { // if (this.multicast.groupType === MulticastType.ClassB) { // return true; @@ -127,7 +129,7 @@ export class MulticastDetailComponent implements OnInit { }); } } else { - this.snackService.showSendDownlinkFailNoDevices(); + this.downlinkService.showSendDownlinkFailNoDevices(); } } openDownlinkDialog() { diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts index 56e5014f..4bdde2f0 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { MulticastType } from '@shared/enums/multicast-type'; @@ -20,10 +20,10 @@ import { takeUntil } from 'rxjs/operators'; templateUrl: './multicast-edit.component.html', styleUrls: ['./multicast-edit.component.scss'], }) -export class MulticastEditComponent implements OnInit { +export class MulticastEditComponent implements OnInit, OnDestroy { public title: string; public multicastId: number; - public errorMessages: any; + public errorMessages: unknown; private multicastSubscription: Subscription; public searchDevices: FormControl = new FormControl(); public errorFields: string[]; @@ -32,7 +32,8 @@ export class MulticastEditComponent implements OnInit { public backButtonTitle: string; public multicast: Multicast = new Multicast(); private applicationId: number; - public formFailedSubmit: boolean = false; + private onDestroy = new Subject(); + public formFailedSubmit = false; public multicastTypes: string[] = Object.values(MulticastType); // Class-B: { public periodicities: number[] = [2, 4, 8, 16, 32, 64, 128]; // used for classB if it has to be used in the future } public deviceFilterCtrl: FormControl = new FormControl(); @@ -81,7 +82,7 @@ export class MulticastEditComponent implements OnInit { } this.deviceFilterCtrl.valueChanges - .pipe(takeUntil(this._onDestroy)) + .pipe(takeUntil(this.onDestroy)) .subscribe(() => { this.filterDevicesMulti(); }); @@ -130,7 +131,7 @@ export class MulticastEditComponent implements OnInit { }); } - //only if classB can be used + // only if classB can be used // showPeriodicity(): boolean { // if (this.multicast.groupType === MulticastType.ClassB) { // return true; @@ -203,5 +204,4 @@ export class MulticastEditComponent implements OnInit { ngOnDestroy(): void { this.multicastSubscription?.unsubscribe(); } - private _onDestroy = new Subject(); } diff --git a/src/app/applications/multicast/multicast-table/multicast-table.component.ts b/src/app/applications/multicast/multicast-table/multicast-table.component.ts index 1a685a65..54225d5c 100644 --- a/src/app/applications/multicast/multicast-table/multicast-table.component.ts +++ b/src/app/applications/multicast/multicast-table/multicast-table.component.ts @@ -32,7 +32,7 @@ export class MulticastTableComponent multicasts: Multicast[] = []; resultsLength = 0; public canEdit = false; - @Input() isLoadingResults: boolean = true; + @Input() isLoadingResults = true; public pageSize = environment.tablePageSize; public pageOffset = 0; public applicationId: number; @@ -127,10 +127,7 @@ export class MulticastTableComponent }); } ngOnDestroy() { - // prevent memory leak by unsubscribing - this.multicastSubscription?.unsubscribe(); - this.deleteDialogSubscription?.unsubscribe(); } } diff --git a/src/app/applications/multicast/multicast.service.ts b/src/app/applications/multicast/multicast.service.ts index 9a674d0c..8dd061de 100644 --- a/src/app/applications/multicast/multicast.service.ts +++ b/src/app/applications/multicast/multicast.service.ts @@ -85,7 +85,7 @@ export class MulticastService { return model; } - public multicastGet(multicastId: number, params = {}): Observable { + public multicastGet(multicastId: number, params = {}): Observable { const url = this.multicastDownlinkURL + multicastId + '/' + this.DOWNLINKMULTICASTURL; return this.restService.get(url, params); @@ -94,7 +94,7 @@ export class MulticastService { downlink: Downlink, multicastId: number, params = {} - ): Observable { + ): Observable { const url = this.multicastDownlinkURL + multicastId + '/' + this.DOWNLINKMULTICASTURL; return this.restService.post(url, downlink, params); diff --git a/src/app/shared/services/downlink.service.ts b/src/app/shared/services/downlink.service.ts index b031e1bb..edbf8cfe 100644 --- a/src/app/shared/services/downlink.service.ts +++ b/src/app/shared/services/downlink.service.ts @@ -2,6 +2,8 @@ import { Injectable } from '@angular/core'; import { RestService } from './rest.service'; import { Observable } from 'rxjs'; import { Downlink } from '@applications/iot-devices/downlink.model'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { TranslateService } from '@ngx-translate/core'; @Injectable({ providedIn: 'root', @@ -10,7 +12,11 @@ export class DownlinkService { private IOTDEVICEURL = 'iot-device/'; private DOWNLINKURL = 'downlink'; - constructor(private restService: RestService) {} + constructor( + private restService: RestService, + private snackBar: MatSnackBar, + public translate: TranslateService + ) {} public get(deviceId: number, params = {}): Observable { const url = this.IOTDEVICEURL + deviceId + '/' + this.DOWNLINKURL; @@ -25,4 +31,13 @@ export class DownlinkService { const url = this.IOTDEVICEURL + deviceId + '/' + this.DOWNLINKURL; return this.restService.post(url, downlink, params); } + public showSendDownlinkFailNoDevices() { + this.snackBar.open( + this.translate.instant('SNACK.NODEVICES'), + this.translate.instant('SNACK.CLOSE'), + { + duration: 10000, + } + ); + } } diff --git a/src/app/shared/services/snack.service.ts b/src/app/shared/services/snack.service.ts index 16cd3b80..d5831815 100644 --- a/src/app/shared/services/snack.service.ts +++ b/src/app/shared/services/snack.service.ts @@ -41,9 +41,4 @@ export class SnackService { duration: 10000, }); } - public showSendDownlinkFailNoDevices(){ - this.snackBar.open(this.translate.instant('SNACK.NODEVICES'), this.translate.instant('SNACK.CLOSE'), { - duration: 10000, - }); - } } From 7112f40489785ca7a8b6cd2dc696794e5f9a1e15 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Thu, 6 Jan 2022 14:50:30 +0100 Subject: [PATCH 25/37] merge conflicting --- .../application-detail.component.ts | 173 ++++++++++-------- 1 file changed, 95 insertions(+), 78 deletions(-) diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index 75eafee4..d8121c56 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -1,4 +1,10 @@ -import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; +import { + Component, + EventEmitter, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { Application } from '@applications/application.model'; @@ -12,95 +18,106 @@ import { MeService } from '@shared/services/me.service'; import { Subscription } from 'rxjs'; @Component({ - selector: 'app-application', - templateUrl: './application-detail.component.html', - styleUrls: ['./application-detail.component.scss'], + selector: 'app-application', + templateUrl: './application-detail.component.html', + styleUrls: ['./application-detail.component.scss'], }) export class ApplicationDetailComponent implements OnInit, OnDestroy { - @Output() deleteApplication = new EventEmitter(); - public applicationsSubscription: Subscription; - private deleteDialogSubscription: Subscription; - public application: Application; - public backButton: BackButton = { label: '', routerLink: '/applications' }; - public id: number; - public pageLimit = environment.tablePageSize; - public dropdownButton: DropdownButton; - public errorMessage: string; - public canEdit = false; + @Output() deleteApplication = new EventEmitter(); + public applicationsSubscription: Subscription; + private deleteDialogSubscription: Subscription; + public application: Application; + public backButton: BackButton = { label: '', routerLink: '/applications' }; + public id: number; + public pageLimit = environment.tablePageSize; + public dropdownButton: DropdownButton; + public errorMessage: string; + public canEdit = false; - constructor( - private applicationService: ApplicationService, - private route: ActivatedRoute, - public translate: TranslateService, - public router: Router, - private meService: MeService, - private titleService: Title, - private deleteDialogService: DeleteDialogService - ) { } + constructor( + private applicationService: ApplicationService, + private route: ActivatedRoute, + public translate: TranslateService, + public router: Router, + private meService: MeService, + private titleService: Title, + private deleteDialogService: DeleteDialogService + ) {} - ngOnInit(): void { - this.id = +this.route.snapshot.paramMap.get('id'); - if (this.id) { - this.bindApplication(this.id); - this.dropdownButton = { - label: '', - editRouterLink: '../../edit-application/' + this.id, - isErasable: true, - }; + ngOnInit(): void { + this.id = +this.route.snapshot.paramMap.get('id'); + if (this.id) { + this.bindApplication(this.id); + this.dropdownButton = { + label: '', + editRouterLink: '../../edit-application/' + this.id, + isErasable: true, + }; - console.log(this.id); - } + console.log(this.id); + } - this.translate.get(['NAV.APPLICATIONS', 'APPLICATION-TABLE-ROW.SHOW-OPTIONS', 'TITLE.APPLICATION']) - .subscribe(translations => { - this.backButton.label = translations['NAV.APPLICATIONS']; - this.dropdownButton.label = translations['APPLICATION-TABLE-ROW.SHOW-OPTIONS']; - this.titleService.setTitle(translations['TITLE.APPLICATION']); - }); - this.canEdit = this.meService.canWriteInTargetOrganization(); + this.translate + .get([ + 'NAV.APPLICATIONS', + 'APPLICATION-TABLE-ROW.SHOW-OPTIONS', + 'TITLE.APPLICATION', + ]) + .subscribe((translations) => { + this.backButton.label = translations['NAV.APPLICATIONS']; + this.dropdownButton.label = + translations['APPLICATION-TABLE-ROW.SHOW-OPTIONS']; + this.titleService.setTitle(translations['TITLE.APPLICATION']); + }); + this.canEdit = this.meService.canWriteInTargetOrganization(); + } + + onDeleteApplication() { + let message: string; + if (this.applicationHasDevices()) { + message = this.translate.instant('APPLICATION.DELETE-HAS-DEVICES-PROMPT'); } - onDeleteApplication() { - let message: string; - if (this.applicationHasDevices()) { - message = this.translate.instant('APPLICATION.DELETE-HAS-DEVICES-PROMPT'); + this.deleteDialogSubscription = this.deleteDialogService + .showSimpleDialog(message) + .subscribe((response) => { + if (response) { + this.applicationService + .deleteApplication(this.application.id) + .subscribe((response) => { + if (response.ok && response.body.affected > 0) { + console.log( + 'delete application with id:' + this.application.id.toString() + ); + this.router.navigate(['applications']); + } else { + this.errorMessage = response?.error?.message; + } + }); + } else { + console.log(response); } + }); + } - this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog(message).subscribe( - (response) => { - if (response) { - this.applicationService.deleteApplication(this.application.id).subscribe((response) => { - if (response.ok && response.body.affected > 0) { - console.log('delete application with id:' + this.application.id.toString()); - this.router.navigate(['applications']); - } else { - this.errorMessage = response?.error?.message; - } - }); - } else { - console.log(response); - } - } - ); - } + applicationHasDevices(): boolean { + return this.application.iotDevices?.length > 0; + } - applicationHasDevices(): boolean { - return this.application.iotDevices?.length > 0; - } + bindApplication(id: number): void { + this.applicationsSubscription = this.applicationService + .getApplication(id) + .subscribe((application) => { + this.application = application; + }); + } - - bindApplication(id: number): void { - this.applicationsSubscription = this.applicationService.getApplication(id).subscribe((application) => { - this.application = application; - }); + ngOnDestroy() { + if (this.applicationsSubscription) { + this.applicationsSubscription.unsubscribe(); } - - ngOnDestroy() { - if (this.applicationsSubscription) { - this.applicationsSubscription.unsubscribe(); - } - if (this.deleteDialogSubscription) { - this.deleteDialogSubscription.unsubscribe(); - } + if (this.deleteDialogSubscription) { + this.deleteDialogSubscription.unsubscribe(); } + } } From 93dea8cbe83316393f361547cc4ab67dba832eab Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Mon, 24 Jan 2022 13:27:09 +0100 Subject: [PATCH 26/37] Cleanup --- src/app/admin/admin-routing.module.ts | 2 -- src/app/admin/admin.module.ts | 2 -- .../api-key-detail/api-key-detail.component.html | 1 - .../api-key-detail/api-key-detail.component.scss | 0 .../api-key-detail/api-key-detail.component.ts | 15 --------------- 5 files changed, 20 deletions(-) delete mode 100644 src/app/admin/api-key/api-key-detail/api-key-detail.component.html delete mode 100644 src/app/admin/api-key/api-key-detail/api-key-detail.component.scss delete mode 100644 src/app/admin/api-key/api-key-detail/api-key-detail.component.ts diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts index c2c7250c..e5343681 100644 --- a/src/app/admin/admin-routing.module.ts +++ b/src/app/admin/admin-routing.module.ts @@ -15,7 +15,6 @@ import { UsersComponent } from './users/users.component'; import { ApiKeyComponent } from './api-key/api-key.component'; import { ApiKeyListComponent } from './api-key/api-key-list/api-key-list.component'; import { ApiKeyEditComponent } from './api-key/api-key-edit/api-key-edit.component'; -import { ApiKeyDetailComponent } from './api-key/api-key-detail/api-key-detail.component'; const adminRoutes: Routes = [ @@ -54,7 +53,6 @@ const adminRoutes: Routes = [ children: [ { path: '', component: ApiKeyListComponent }, { path: 'new-api-key', component: ApiKeyEditComponent }, - { path: ':api-key-id', component: ApiKeyDetailComponent }, { path: ':api-key-id/edit-api-key', component: ApiKeyEditComponent, diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index 51bb4216..93210070 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -32,7 +32,6 @@ import { ApiKeyComponent } from './api-key/api-key.component'; import { ApiKeyListComponent } from './api-key/api-key-list/api-key-list.component'; import { ApiKeyTableComponent } from './api-key/api-key-list/api-key-table/api-key-table.component'; import { ApiKeyEditComponent } from './api-key/api-key-edit/api-key-edit.component'; -import { ApiKeyDetailComponent } from './api-key/api-key-detail/api-key-detail.component'; @NgModule({ declarations: [ @@ -55,7 +54,6 @@ import { ApiKeyDetailComponent } from './api-key/api-key-detail/api-key-detail.c ApiKeyListComponent, ApiKeyTableComponent, ApiKeyEditComponent, - ApiKeyDetailComponent, ], imports: [ AdminRoutingModule, diff --git a/src/app/admin/api-key/api-key-detail/api-key-detail.component.html b/src/app/admin/api-key/api-key-detail/api-key-detail.component.html deleted file mode 100644 index 903c1911..00000000 --- a/src/app/admin/api-key/api-key-detail/api-key-detail.component.html +++ /dev/null @@ -1 +0,0 @@ -

api-key-detail works!

diff --git a/src/app/admin/api-key/api-key-detail/api-key-detail.component.scss b/src/app/admin/api-key/api-key-detail/api-key-detail.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/app/admin/api-key/api-key-detail/api-key-detail.component.ts b/src/app/admin/api-key/api-key-detail/api-key-detail.component.ts deleted file mode 100644 index 25bc4048..00000000 --- a/src/app/admin/api-key/api-key-detail/api-key-detail.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-api-key-detail', - templateUrl: './api-key-detail.component.html', - styleUrls: ['./api-key-detail.component.scss'] -}) -export class ApiKeyDetailComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} From 0fb18cbc82d374f4734f42c7ef48b1caaf8116ec Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Mon, 24 Jan 2022 13:31:36 +0100 Subject: [PATCH 27/37] Remove unique permissions pipe --- src/app/shared/pipes/pipes.module.ts | 3 --- .../unique-permission-organizations.pipe.ts | 19 ------------------- tsconfig.json | 1 - 3 files changed, 23 deletions(-) delete mode 100644 src/app/shared/pipes/unique-permission-organizations.pipe.ts diff --git a/src/app/shared/pipes/pipes.module.ts b/src/app/shared/pipes/pipes.module.ts index 997ccc8b..c5aacc28 100644 --- a/src/app/shared/pipes/pipes.module.ts +++ b/src/app/shared/pipes/pipes.module.ts @@ -6,7 +6,6 @@ import { isGlobalAdminPipe } from './is-global-admin.pipe'; import { CreatedUpdatedByPipe } from './created-updated-by.pipe'; import { CustomDatePipe, CustomTableDatePipe } from './custom-date.pipe'; import { FilterDevicesPipe } from './filter-devices.pipe'; -import { UniquePermissionOrganizationsPipe } from './unique-permission-organizations.pipe'; @NgModule({ declarations: [ @@ -17,7 +16,6 @@ import { UniquePermissionOrganizationsPipe } from './unique-permission-organizat CustomTableDatePipe, CreatedUpdatedByPipe, FilterDevicesPipe, - UniquePermissionOrganizationsPipe, ], imports: [CommonModule], exports: [ @@ -28,7 +26,6 @@ import { UniquePermissionOrganizationsPipe } from './unique-permission-organizat CustomTableDatePipe, CreatedUpdatedByPipe, FilterDevicesPipe, - UniquePermissionOrganizationsPipe, ], }) export class PipesModule {} diff --git a/src/app/shared/pipes/unique-permission-organizations.pipe.ts b/src/app/shared/pipes/unique-permission-organizations.pipe.ts deleted file mode 100644 index 5e646d71..00000000 --- a/src/app/shared/pipes/unique-permission-organizations.pipe.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; -import { PermissionResponse } from '@app/admin/permission/permission.model'; - -@Pipe({ - name: 'uniquePermissionOrganizations', -}) -export class UniquePermissionOrganizationsPipe implements PipeTransform { - transform( - value: PermissionResponse[], - ..._: unknown[] - ): PermissionResponse[] { - // Ensure that no element appears twice. Orders it so that the duplicate (second match) takes priority - const uniqueArr = Array.from( - new Map(value.map((item) => [item.organization?.id, item])).values() - ); - - return uniqueArr; - } -} diff --git a/tsconfig.json b/tsconfig.json index 7eecb11c..fc6b17bc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,6 +30,5 @@ "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true - // "strictTemplates": true // Highly recommended for type-safety in html. Apply if there's ever time to fix the 30+ errors that appear } } From 4ee49a239a5392a1ffe1ce6d73048656f3ee6398 Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Mon, 24 Jan 2022 13:38:35 +0100 Subject: [PATCH 28/37] API key cleanup --- src/app/admin/api-key/api-key.component.html | 1 - src/app/admin/api-key/api-key.component.scss | 0 src/app/admin/api-key/api-key.component.ts | 10 +++------- 3 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 src/app/admin/api-key/api-key.component.html delete mode 100644 src/app/admin/api-key/api-key.component.scss diff --git a/src/app/admin/api-key/api-key.component.html b/src/app/admin/api-key/api-key.component.html deleted file mode 100644 index 0680b43f..00000000 --- a/src/app/admin/api-key/api-key.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/admin/api-key/api-key.component.scss b/src/app/admin/api-key/api-key.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/app/admin/api-key/api-key.component.ts b/src/app/admin/api-key/api-key.component.ts index be1ea4b8..97c6b2fe 100644 --- a/src/app/admin/api-key/api-key.component.ts +++ b/src/app/admin/api-key/api-key.component.ts @@ -2,14 +2,10 @@ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-api-key', - templateUrl: './api-key.component.html', - styleUrls: ['./api-key.component.scss'] + template: '', }) export class ApiKeyComponent implements OnInit { + constructor() {} - constructor() { } - - ngOnInit(): void { - } - + ngOnInit(): void {} } From ae572e0207c36b641f0ca11d6335885ed885b474 Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Tue, 25 Jan 2022 13:21:46 +0100 Subject: [PATCH 29/37] Fix sigfox group id --- .../sigfox/sigfox-groups-edit/sigfox-groups-edit.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/sigfox/sigfox-groups-edit/sigfox-groups-edit.component.ts b/src/app/sigfox/sigfox-groups-edit/sigfox-groups-edit.component.ts index b6d2e1e4..11f9d15a 100644 --- a/src/app/sigfox/sigfox-groups-edit/sigfox-groups-edit.component.ts +++ b/src/app/sigfox/sigfox-groups-edit/sigfox-groups-edit.component.ts @@ -109,7 +109,7 @@ export class SigfoxGroupsEditComponent implements OnInit, OnDestroy { return; } - this.sigfoxService.getGroup(this.sigfoxGroup.id).subscribe( + this.sigfoxService.getGroup(this.sigfoxGroupId).subscribe( (response: any) => { if (response.data.length !== 0 || response.data === undefined) { this.update(); From fc74b7ba5931060e3cc95109c1ab50f27947fc5d Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Wed, 26 Jan 2022 16:29:37 +0100 Subject: [PATCH 30/37] Add edit api key --- src/app/admin/admin.module.ts | 1 - .../api-key-edit/api-key-edit.component.ts | 41 +++++++++++++++---- .../api-key-table.component.html | 34 ++++++++++++--- src/app/admin/api-key/api-key.model.ts | 2 +- src/app/admin/api-key/api-key.service.ts | 6 +++ 5 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index 93210070..b6f9121e 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -90,7 +90,6 @@ import { ApiKeyEditComponent } from './api-key/api-key-edit/api-key-edit.compone ApiKeyComponent, ApiKeyListComponent, ApiKeyTableComponent, - ApiKeyEditComponent, ], }) export class AdminModule {} diff --git a/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts b/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts index 015f7852..ac9c9dfd 100644 --- a/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts +++ b/src/app/admin/api-key/api-key-edit/api-key-edit.component.ts @@ -30,6 +30,7 @@ export class ApiKeyEditComponent implements OnInit { public formFailedSubmit = false; public permissions: PermissionResponse[] = []; private organizationId: number; + id: number; constructor( private translate: TranslateService, @@ -39,9 +40,7 @@ export class ApiKeyEditComponent implements OnInit { private permissionService: PermissionService, private errorMessageService: ErrorMessageService, private sharedVariableService: SharedVariableService - ) { - translate.use('da'); - } + ) {} ngOnInit(): void { this.getPermissions(); @@ -54,6 +53,11 @@ export class ApiKeyEditComponent implements OnInit { this.submitButton = translations['API-KEY.EDIT.SAVE']; }); + this.id = +this.route.snapshot.paramMap.get('api-key-id'); + + if (this.id > 0) { + this.getApiKey(this.id); + } this.organizationId = this.sharedVariableService.getSelectedOrganisationId(); } @@ -68,8 +72,8 @@ export class ApiKeyEditComponent implements OnInit { this.organizationId ) .subscribe( - (permissions) => { - this.permissions = permissions.data.filter( + (permissionsResponse) => { + this.permissions = permissionsResponse.data.filter( (x) => x.organization?.id === this.organizationId ); }, @@ -79,8 +83,17 @@ export class ApiKeyEditComponent implements OnInit { ); } + private getApiKey(id: number) { + this.apiKeyService.get(id).subscribe((key) => { + this.apiKeyRequest = new ApiKeyRequest(); + this.apiKeyRequest.id = key.id; + this.apiKeyRequest.name = key.name; + this.apiKeyRequest.permissions = key.permissions.map((pm) => pm.id); + }); + } + onSubmit(): void { - this.create(); + this.id ? this.update() : this.create(); } private create(): void { @@ -90,11 +103,21 @@ export class ApiKeyEditComponent implements OnInit { ); } - public compare(o1: any, o2: any): boolean { - return o1 === o2; + private update(): void { + this.apiKeyService.update(this.apiKeyRequest, this.id).subscribe( + () => this.routeBack(), + (err) => this.showError(err) + ); + } + + public compare( + matOptionValue: number, + ngModelObject: number + ): boolean { + return matOptionValue === ngModelObject; } - private showError(err: HttpErrorResponse) { + showError(err: HttpErrorResponse) { const result = this.errorMessageService.handleErrorMessageWithFields(err); this.errorFields = result.errorFields; this.errorMessages = result.errorMessages; diff --git a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html index e52194d0..09321fe8 100644 --- a/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html +++ b/src/app/admin/api-key/api-key-list/api-key-table/api-key-table.component.html @@ -50,12 +50,34 @@
diff --git a/src/app/admin/api-key/api-key.model.ts b/src/app/admin/api-key/api-key.model.ts index 050a799b..9fdefc6b 100644 --- a/src/app/admin/api-key/api-key.model.ts +++ b/src/app/admin/api-key/api-key.model.ts @@ -3,7 +3,7 @@ import { PermissionResponse } from '../permission/permission.model'; export class ApiKeyRequest { id: number; name: string; - permissions?: PermissionResponse[]; + permissions?: number[]; } export interface ApiKeyResponse { diff --git a/src/app/admin/api-key/api-key.service.ts b/src/app/admin/api-key/api-key.service.ts index 91a6dc2c..77db1c4c 100644 --- a/src/app/admin/api-key/api-key.service.ts +++ b/src/app/admin/api-key/api-key.service.ts @@ -25,6 +25,12 @@ export class ApiKeyService { }); } + update(body: ApiKeyRequest, id: number): Observable { + return this.restService.put(this.endpoint, body, id, { + observe: 'response', + }); + } + get(id: number): Observable { return this.restService.get(this.endpoint, {}, id).pipe( map((response: ApiKeyResponse) => { From aa8a3e9075e6365f5254e743a2820c7e051a3b7d Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Thu, 27 Jan 2022 14:09:40 +0100 Subject: [PATCH 31/37] Edit API key PR --- src/app/admin/admin.module.ts | 1 + .../admin/api-key/api-key-edit/api-key-edit.component.html | 2 +- src/app/admin/api-key/api-key-edit/api-key-edit.component.ts | 5 ++--- src/app/admin/api-key/api-key.model.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index b6f9121e..93210070 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -90,6 +90,7 @@ import { ApiKeyEditComponent } from './api-key/api-key-edit/api-key-edit.compone ApiKeyComponent, ApiKeyListComponent, ApiKeyTableComponent, + ApiKeyEditComponent, ], }) export class AdminModule {} diff --git a/src/app/admin/api-key/api-key-edit/api-key-edit.component.html b/src/app/admin/api-key/api-key-edit/api-key-edit.component.html index 268d6972..9a90bf14 100644 --- a/src/app/admin/api-key/api-key-edit/api-key-edit.component.html +++ b/src/app/admin/api-key/api-key-edit/api-key-edit.component.html @@ -42,7 +42,7 @@ class="form-control" name="permissions" [compareWith]="compare" - [(ngModel)]="apiKeyRequest.permissions" + [(ngModel)]="apiKeyRequest.permissionIds" [multiple]="true" > { - this.apiKeyRequest = new ApiKeyRequest(); this.apiKeyRequest.id = key.id; this.apiKeyRequest.name = key.name; - this.apiKeyRequest.permissions = key.permissions.map((pm) => pm.id); + this.apiKeyRequest.permissionIds = key.permissions.map((pm) => pm.id); }); } diff --git a/src/app/admin/api-key/api-key.model.ts b/src/app/admin/api-key/api-key.model.ts index 9fdefc6b..ed8380f9 100644 --- a/src/app/admin/api-key/api-key.model.ts +++ b/src/app/admin/api-key/api-key.model.ts @@ -3,7 +3,7 @@ import { PermissionResponse } from '../permission/permission.model'; export class ApiKeyRequest { id: number; name: string; - permissions?: number[]; + permissionIds?: number[]; } export interface ApiKeyResponse { From 996e9dac5167d2e1d1ca4287f8705d9f52d32576 Mon Sep 17 00:00:00 2001 From: Aram Al-Sabti Date: Fri, 4 Feb 2022 14:05:12 +0100 Subject: [PATCH 32/37] Spell organization with "z" --- src/app/admin/api-key/api-key.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/admin/api-key/api-key.service.ts b/src/app/admin/api-key/api-key.service.ts index 77db1c4c..87d1c3c8 100644 --- a/src/app/admin/api-key/api-key.service.ts +++ b/src/app/admin/api-key/api-key.service.ts @@ -51,7 +51,7 @@ export class ApiKeyService { orderByColumn?: string, orderByDirection?: string, userId?: number, - organisationId?: number + organizationId?: number ): Observable { if (userId) { return this.restService.get(this.endpoint, { @@ -61,13 +61,13 @@ export class ApiKeyService { sort: orderByDirection, userId, }); - } else if (organisationId) { + } else if (organizationId) { return this.restService.get(this.endpoint, { limit, offset, orderOn: orderByColumn, sort: orderByDirection, - organisationId, + organizationId, }); } else { return this.restService.get(this.endpoint, { From 38c11a508e8cb5b6a8218ba15b6c089c8f7d35c7 Mon Sep 17 00:00:00 2001 From: August Andersen Date: Mon, 14 Feb 2022 09:16:53 +0100 Subject: [PATCH 33/37] Multicast text description changes --- .../multicast-edit/multicast-edit.component.html | 15 +++++++++++++++ src/assets/i18n/da.json | 14 +++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html index 42e456dc..7be14e07 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html @@ -36,6 +36,9 @@ 'QUESTION.GIVE-MULTICAST-ADDRESS' | translate }}* + * + * + + {{'QUESTION.MULTICAST.HINT-DATA-RATE' | translate}} + * +
+ {{'QUESTION.MULTICAST.HINT-FREQUENCY' | translate}} +
Date: Mon, 14 Feb 2022 11:40:36 +0100 Subject: [PATCH 34/37] Fixed deleted word "adressen" in multicast creation. Is added again. --- src/assets/i18n/da.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 4b910cf3..bf9a16d8 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -476,7 +476,7 @@ "GIVE-ORGANISATION-NAME": "Navngiv organisation", "GIVE-ORGANISATION-NAME-PLACEHOLDER": "F.eks. 'Aarhus Kommune'", "GIVE-MULTICAST-NAME":"Navngiv multicast", - "GIVE-MULTICAST-ADDRESS": "Angiv multicast (mcAddr)", + "GIVE-MULTICAST-ADDRESS": "Angiv multicast adressen (mcAddr)", "GIVE-MULTICAST-NETWORK-KEY": "Angiv multicast network session key (mcNwkSKey)", "GIVE-MULTICAST-APPLICATION-KEY": "Angiv multicast application session key (mcAppSKey)", "GIVE-MULTICAST-FRAMECOUNTER": "Angiv frame counter", From 1bed9dde01df29b4de229ed836364ee48372e8af Mon Sep 17 00:00:00 2001 From: August Andersen Date: Mon, 14 Feb 2022 14:34:33 +0100 Subject: [PATCH 35/37] Moved hint description under the input form instead of before det input form. Matches the design of the rest of the document. --- .../multicast-edit.component.html | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html index 7be14e07..8a2cd4cf 100644 --- a/src/app/applications/multicast/multicast-edit/multicast-edit.component.html +++ b/src/app/applications/multicast/multicast-edit/multicast-edit.component.html @@ -36,9 +36,6 @@ 'QUESTION.GIVE-MULTICAST-ADDRESS' | translate }}* - +
@@ -62,9 +62,6 @@ 'QUESTION.GIVE-MULTICAST-NETWORK-KEY' | translate }}* - +
@@ -87,9 +87,6 @@ 'QUESTION.GIVE-MULTICAST-APPLICATION-KEY' | translate }}* - +
@@ -134,9 +134,6 @@ 'QUESTION.GIVE-MULTICAST-DATARATE' | translate }}* -
- {{'QUESTION.MULTICAST.HINT-DATA-RATE' | translate}} -
+
@@ -157,9 +157,6 @@ 'QUESTION.GIVE-MULTICAST-FREQUENCY' | translate }}* -
- {{'QUESTION.MULTICAST.HINT-FREQUENCY' | translate}} -
+
From f31a6f317bdb66014b232e82364f4608f865ea1f Mon Sep 17 00:00:00 2001 From: Bartek <88727464+bkdkmd@users.noreply.github.com> Date: Thu, 17 Feb 2022 14:27:47 +0100 Subject: [PATCH 36/37] FIWARE datatarget (#67) * Datatarget multi-types administration and new FIWARE Datatarget type * PR fixes * DatatargetTypesService pr fixes --- src/app/app.module.ts | 2 + .../application-detail.component.html | 2 +- .../application-detail.component.ts | 4 +- .../applications-routing.module.ts | 11 +- ...get-detail-type-selector.directive.spec.ts | 11 + ...tatarget-detail-type-selector.directive.ts | 9 + .../datatarget-detail.component.html | 75 +--- .../datatarget-detail.component.ts | 114 ++--- .../datatarget-detail/datatarget-detail.ts | 2 + ...arget-edit-type-selector.directive.spec.ts | 11 + ...datatarget-edit-type-selector.directive.ts | 10 + .../datatarget-edit.component.html | 142 +----- .../datatarget-edit.component.ts | 402 ++--------------- .../datatarget-edit/datatarget-edit.ts | 2 + .../datatarget-new.component.html | 34 ++ .../datatarget-new.component.scss | 44 ++ .../datatarget-new.component.spec.ts | 28 ++ .../datatarget-new.component.ts | 63 +++ .../datatarget/datatarget-response.model.ts | 2 + .../datatarget-table.component.html | 2 +- .../datatarget-table.component.ts | 3 +- .../datatarget-types.service.spec.ts | 16 + .../datatarget/datatarget-types.service.ts | 84 ++++ .../datatarget/datatarget.model.ts | 12 + .../datatarget/datatarget.module.ts | 25 +- .../datatarget/datatarget.service.ts | 2 + .../fiware-detail.component.html | 76 ++++ .../fiware-detail.component.scss | 0 .../fiware-detail.component.spec.ts | 28 ++ .../fiware-detail/fiware-detail.component.ts | 98 +++++ .../fiware-edit/fiware-edit.component.html | 167 +++++++ .../fiware-edit/fiware-edit.component.scss | 4 + .../fiware-edit/fiware-edit.component.spec.ts | 28 ++ .../fiware-edit/fiware-edit.component.ts | 336 +++++++++++++++ .../httppush-detail.component.html | 74 ++++ .../httppush-detail.component.scss | 3 + .../httppush-detail.component.spec.ts | 28 ++ .../httppush-detail.component.ts | 98 +++++ .../httppush-edit.component.html | 139 ++++++ .../httppush-edit.component.scss | 0 .../httppush-edit.component.spec.ts | 28 ++ .../httppush-edit/httppush-edit.component.ts | 406 ++++++++++++++++++ .../snack-bar/snack-bar.component.html | 9 + .../snack-bar/snack-bar.component.scss | 21 + .../snack-bar/snack-bar.component.ts | 18 + src/app/shared/enums/datatarget-type.ts | 3 +- src/app/shared/services/snack.service.ts | 16 + src/assets/i18n/da.json | 28 +- src/assets/images/logo_FIWARE.png | Bin 0 -> 1338 bytes src/assets/images/logo_opendatadk.svg | 1 + src/styles.scss | 35 ++ 51 files changed, 2086 insertions(+), 670 deletions(-) create mode 100644 src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.spec.ts create mode 100644 src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.ts create mode 100644 src/app/applications/datatarget/datatarget-detail/datatarget-detail.ts create mode 100644 src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.spec.ts create mode 100644 src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.ts create mode 100644 src/app/applications/datatarget/datatarget-edit/datatarget-edit.ts create mode 100644 src/app/applications/datatarget/datatarget-new/datatarget-new.component.html create mode 100644 src/app/applications/datatarget/datatarget-new/datatarget-new.component.scss create mode 100644 src/app/applications/datatarget/datatarget-new/datatarget-new.component.spec.ts create mode 100644 src/app/applications/datatarget/datatarget-new/datatarget-new.component.ts create mode 100644 src/app/applications/datatarget/datatarget-types.service.spec.ts create mode 100644 src/app/applications/datatarget/datatarget-types.service.ts create mode 100644 src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.html create mode 100644 src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.scss create mode 100644 src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.spec.ts create mode 100644 src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.ts create mode 100644 src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.html create mode 100644 src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.scss create mode 100644 src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.spec.ts create mode 100644 src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.ts create mode 100644 src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.html create mode 100644 src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.scss create mode 100644 src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.spec.ts create mode 100644 src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.ts create mode 100644 src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.html create mode 100644 src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.scss create mode 100644 src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.spec.ts create mode 100644 src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.ts create mode 100644 src/app/shared/components/snack-bar/snack-bar.component.html create mode 100644 src/app/shared/components/snack-bar/snack-bar.component.scss create mode 100644 src/app/shared/components/snack-bar/snack-bar.component.ts create mode 100644 src/assets/images/logo_FIWARE.png create mode 100644 src/assets/images/logo_opendatadk.svg diff --git a/src/app/app.module.ts b/src/app/app.module.ts index aab4ebc9..82d3163a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -23,6 +23,7 @@ import { MonacoEditorModule } from 'ngx-monaco-editor'; import { MatInputModule } from '@angular/material/input'; import { MatPaginatorIntl } from '@angular/material/paginator'; import { MatPaginatorIntlDa } from '@shared/helpers/mat-paginator-intl-da'; +import { MatTooltipModule } from '@angular/material/tooltip'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); @@ -62,6 +63,7 @@ export function tokenGetter() { SearchModule, HttpClientModule, MatInputModule, + MatTooltipModule, JwtModule.forRoot({ config: { tokenGetter diff --git a/src/app/applications/application-detail/application-detail.component.html b/src/app/applications/application-detail/application-detail.component.html index ff4a356a..7c11602f 100644 --- a/src/app/applications/application-detail/application-detail.component.html +++ b/src/app/applications/application-detail/application-detail.component.html @@ -66,7 +66,7 @@

Detaljer

+ [ctaLabelPrimary]="'FORM.CREATE-NEW-DATATARGET' | translate" [ctaRouterLinkPrimary]="'datatarget-new'">
diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index 288804b8..2b6dfdbe 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -52,9 +52,7 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { label: '', editRouterLink: '../../edit-application/' + this.id, isErasable: true, - }; - - console.log(this.id); + }; } this.translate diff --git a/src/app/applications/applications-routing.module.ts b/src/app/applications/applications-routing.module.ts index d5dcd3eb..5d285e70 100644 --- a/src/app/applications/applications-routing.module.ts +++ b/src/app/applications/applications-routing.module.ts @@ -11,7 +11,7 @@ import { DatatargetDetailComponent } from './datatarget/datatarget-detail/datata import { BulkImportComponent } from './bulk-import/bulk-import.component'; import { MulticastEditComponent } from './multicast/multicast-edit/multicast-edit.component'; import { MulticastDetailComponent } from './multicast/multicast-detail/multicast-detail.component'; - +import { DatatargetNewComponent } from './datatarget/datatarget-new/datatarget-new.component'; const applicationRoutes: Routes = [ { @@ -27,12 +27,11 @@ const applicationRoutes: Routes = [ { path: '', component: ApplicationDetailComponent }, { path: 'new-iot-device', component: IotDeviceEditComponent, }, { path: 'iot-device-edit/:deviceId', component: IotDeviceEditComponent, }, - { path: 'iot-device/:deviceId', component: IoTDeviceDetailComponent, }, - + { path: 'iot-device/:deviceId', component: IoTDeviceDetailComponent, }, + { path: 'datatarget-new', component: DatatargetNewComponent }, { path: 'datatarget-edit', component: DatatargetEditComponent }, { path: 'datatarget-edit/:datatargetId', component: DatatargetEditComponent }, - { path: 'datatarget/:datatargetId', component: DatatargetDetailComponent }, - + { path: 'datatarget/:datatargetId', component: DatatargetDetailComponent }, { path: 'multicast-edit', component: MulticastEditComponent}, { path: 'multicast-edit/:multicastId', component: MulticastEditComponent }, { path: 'multicast/:multicastId', component: MulticastDetailComponent }, @@ -41,7 +40,7 @@ const applicationRoutes: Routes = [ ], }, - ], + ], }, ]; diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.spec.ts b/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.spec.ts new file mode 100644 index 00000000..3a9402c7 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.spec.ts @@ -0,0 +1,11 @@ +/* tslint:disable:no-unused-variable */ + +import { ViewContainerRef } from '@angular/core'; +import { DatatargetDetailTypeSelectorDirective } from './datatarget-detail-type-selector.directive'; +let viewContainerRef: ViewContainerRef; +describe('Directive: DatatargetDetailTypeSelector', () => { + it('should create an instance', () => { + const directive = new DatatargetDetailTypeSelectorDirective(viewContainerRef); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.ts b/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.ts new file mode 100644 index 00000000..84a3f73a --- /dev/null +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail-type-selector.directive.ts @@ -0,0 +1,9 @@ +import { Directive, ViewContainerRef } from '@angular/core'; + +@Directive({ + selector: '[detail-component]' +}) +export class DatatargetDetailTypeSelectorDirective { + + constructor(public viewContainerRef: ViewContainerRef) { } +} diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.html b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.html index cc9829db..1c0c268c 100644 --- a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.html +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.html @@ -1,74 +1,3 @@ -
- -
-
-
-
-

{{ 'DATATARGET.DETAILS' | translate }}

- - -

{{ 'DATATARGET.URL' | translate }}{{datatarget.url}}

-

{{ 'DATATARGET.TIMEOUT' | translate }}{{datatarget.timeout}}

-

{{ 'DATATARGET.TYPE' | translate }}{{datatarget.type | translate}}

- -

{{ 'DATATARGET.AUTHORIZATIONHEADER' | translate }}

-
{{datatarget.authorizationHeader}}
- -

{{ 'DATATARGET.NO-AUTHORIZATIONHEADER' | translate }}

-
- -
-
-
-
-

{{ 'DATATARGET.OPENDATA-DK' | translate }}

-
- -
- -

{{ 'DATATARGET.NO-OPENDATA-DK' | translate }}

-
-
-
-
- -
-
-
-
-

{{ 'DATATARGET.RELATIONS' | translate }}

-
-

{{'DATATARGET.NO-RELATIONS' | translate}}

-
-
-
-
-
-

{{'DATATARGET.PAYLOADEDECODER' | translate}} - {{relation.payloadDecoder.name}} - - {{ 'DATATARGET.NO-PAYLOADDECODER' | translate}} -

-
-
- -
-
-

{{'DATATARGET.IOTDEVICE' | translate}} - - , {{device.name}} - -

-
- -
-
-
-
-
-
-
-
+
+
\ No newline at end of file diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts index af38e9d9..17970ff3 100644 --- a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts @@ -1,16 +1,11 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { Component, ComponentFactoryResolver, OnDestroy, OnInit, Type, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; -import { PayloadDeviceDatatargetGetByDataTarget } from '@app/payload-decoder/payload-device-data.model'; -import { PayloadDeviceDatatargetService } from '@app/payload-decoder/payload-device-datatarget.service'; -import { BackButton } from '@shared/models/back-button.model'; -import { DatatargetService } from '../datatarget.service'; -import { Location } from '@angular/common'; -import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { DataTargetType } from '@shared/enums/datatarget-type'; +import { DatatargetTypesService } from '../datatarget-types.service'; import { Datatarget } from '../datatarget.model'; -import { DropdownButton } from '@shared/models/dropdown-button.model'; -import { faArrowsAltH } from '@fortawesome/free-solid-svg-icons'; +import { DatatargetService } from '../datatarget.service'; +import { DatatargetDetail } from './datatarget-detail'; +import { DatatargetDetailTypeSelectorDirective } from './datatarget-detail-type-selector.directive'; @Component({ selector: 'app-datatarget-detail', @@ -19,79 +14,46 @@ import { faArrowsAltH } from '@fortawesome/free-solid-svg-icons'; }) export class DatatargetDetailComponent implements OnInit, OnDestroy { - public datatargetSubscription: Subscription; + @ViewChild(DatatargetDetailTypeSelectorDirective, {static: true}) adHost!: DatatargetDetailTypeSelectorDirective; + public datatarget: Datatarget; - public backButton: BackButton = { label: '', routerLink: '/datatarget-list' }; - public dataTargetRelations: PayloadDeviceDatatargetGetByDataTarget[]; - private deleteDialogSubscription: Subscription; - public dropdownButton: DropdownButton; - arrowsAltH = faArrowsAltH; - private applicationName: string; - - constructor( - private route: ActivatedRoute, - private deleteDialogService: DeleteDialogService, - private location: Location, - private datatargetRelationServicer: PayloadDeviceDatatargetService, - private datatargetService: DatatargetService, - public translate: TranslateService) { } + private datatargetType: DataTargetType; - ngOnInit(): void { - const id: number = +this.route.snapshot.paramMap.get('datatargetId'); - this.applicationName = this.route.snapshot.paramMap.get('name'); - if (id) { - this.getDatatarget(id); - this.getDatatargetRelations(id); - this.dropdownButton = { - label: '', - editRouterLink: '../../datatarget-edit/' + id, - isErasable: true, - } - } - this.translate.get(['NAV.MY-DATATARGET', 'DATATARGET.SHOW-OPTIONS']) - .subscribe(translations => { - this.backButton.label = translations['NAV.MY-DATATARGET']; - this.dropdownButton.label = translations['DATATARGET.SHOW-OPTIONS'] - }); - } + constructor(private componentFactoryResolver: ComponentFactoryResolver, + private datatargetService: DatatargetService, + private route: ActivatedRoute, + private datatargetTypesService: DatatargetTypesService + ) { } - getDatatarget(id: number) { - this.datatargetService.get(id) - .subscribe((dataTarget: Datatarget) => { - this.datatarget = dataTarget; - this.setBackButton(this.datatarget.applicationId); - }); - } - private setBackButton(applicationId: number) { - this.backButton.routerLink = ['applications', applicationId.toString()] - } + loadComponent(componentType: Type) { - onDeleteDatatarget() { - this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog().subscribe( - (response) => { - if (response) { - this.datatargetService.delete(this.datatarget.id).subscribe((response) => { - }); - this.location.back(); - } else { - console.log(response); - } - } - ); - } + const viewContainerRef = this.adHost.viewContainerRef; - getDatatargetRelations(id: number) { - this.datatargetRelationServicer.getByDataTarget(id) - .subscribe((response) => { - this.dataTargetRelations = response.data; - }); + viewContainerRef.clear(); + const factory = this.componentFactoryResolver.resolveComponentFactory(componentType); + viewContainerRef.createComponent(factory); } - ngOnDestroy(): void { - if (this.deleteDialogSubscription) { - this.deleteDialogSubscription.unsubscribe(); - } + ngOnInit(): void { + + const id: number = +this.route.snapshot.paramMap.get('datatargetId'); + + this.datatargetService.get(id) + .subscribe((dataTarget: Datatarget) => { + this.datatarget = dataTarget; + this.datatargetType = dataTarget.type; + + const component = this.datatargetTypesService.getDetailComponent(this.datatargetType); + + this.loadComponent(component); + + }); + + } + ngOnDestroy() { + + } } diff --git a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.ts b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.ts new file mode 100644 index 00000000..4d5f7bfb --- /dev/null +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.ts @@ -0,0 +1,2 @@ +export interface DatatargetDetail { +} diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.spec.ts b/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.spec.ts new file mode 100644 index 00000000..1c28242d --- /dev/null +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.spec.ts @@ -0,0 +1,11 @@ +/* tslint:disable:no-unused-variable */ + +import { ViewContainerRef } from '@angular/core'; +import { DatatargetEditTypeSelectorDirective } from './datatarget-edit-type-selector.directive'; +let viewContainerRef: ViewContainerRef; +describe('Directive: DatatargetEditTypeSelector', () => { + it('should create an instance', () => { + const directive = new DatatargetEditTypeSelectorDirective(viewContainerRef); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.ts b/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.ts new file mode 100644 index 00000000..03b06ddb --- /dev/null +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit-type-selector.directive.ts @@ -0,0 +1,10 @@ +import { Directive, ViewContainerRef } from '@angular/core'; + +@Directive({ + selector: '[edit-component]' +}) +export class DatatargetEditTypeSelectorDirective { + + constructor(public viewContainerRef: ViewContainerRef) { } + +} diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.html b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.html index 4d4df069..bb9f14e1 100644 --- a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.html +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.html @@ -1,139 +1,3 @@ - - - -
-
-
    -
  • - {{error | translate}} -
  • -
-
- - {{'DATATARGET.ADD-TO-OPENDATADK' | translate}} - -
-
- * - -
-
- -
-
- * - -
-
-
-
- * - -
-
-
-
- - -
-
- -
-
-
-
{{'QUESTION.DATATARGET.RELATIONS' | translate}}
-
-
- {{ 'API-KEY.TABLE-ROW.DELETE' | translate }} - +
- - - - - - - -
-
- - {{'QUESTION.DATATARGET.SELECT-DEVICES' | translate}} - - - - - - {{device.name}} - - -
-
-
- - {{'QUESTION.DATATARGET.SELECT-PAYLOADDECODER' | translate}} - - - {{'QUESTION.DATATARGET.NO-PAYLOAD-DECODER-SELECTED' | translate}} - - - {{payloadDecoder.name}} - - - -
-
- -
- -

{{'DATATARGET.DELETE' | translate}}

-
-
-
- -
-
- - -
- \ No newline at end of file +
+ +
\ No newline at end of file diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts index fa1f81f9..432a5e2b 100644 --- a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.component.ts @@ -1,26 +1,11 @@ -import { Component, OnInit, Input, OnDestroy } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { AfterViewInit, Component, ComponentFactoryResolver, OnDestroy, OnInit, QueryList, Type, ViewChild, ViewChildren } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { DataTargetType } from '@shared/enums/datatarget-type'; +import { DatatargetTypesService } from '../datatarget-types.service'; import { Datatarget } from '../datatarget.model'; -import { Observable, Subscription } from 'rxjs'; -import { Application } from '@applications/application.model'; -import { IotDevice } from '@applications/iot-devices/iot-device.model'; -import { faTimesCircle } from '@fortawesome/free-solid-svg-icons'; -import { PayloadDeviceDatatarget, PayloadDeviceDatatargetGetByDataTargetResponse } from '@payload-decoder/payload-device-data.model'; import { DatatargetService } from '../datatarget.service'; -import { ApplicationService } from '@applications/application.service'; -import { PayloadDecoderService } from '@payload-decoder/payload-decoder.service'; -import { PayloadDeviceDatatargetService } from '@payload-decoder/payload-device-datatarget.service'; -import { SnackService } from '@shared/services/snack.service'; -import { MatDialog } from '@angular/material/dialog'; -import { HttpErrorResponse } from '@angular/common/http'; -import { PayloadDecoderMappedResponse } from '@payload-decoder/payload-decoder.model'; -import { DeleteDialogComponent } from '@shared/components/delete-dialog/delete-dialog.component'; -import { ErrorMessageService } from '@shared/error-message.service'; -import { OpendatadkDialogService } from '@shared/components/opendatadk-dialog/opendatadk-dialog.service'; -import { OpendatadkService } from '@shared/services/opendatadk.service'; -import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; -import { OpenDataDkDataset } from '../opendatadk/opendatadk-dataset.model'; +import { DatatargetEdit } from './datatarget-edit'; +import { DatatargetEditTypeSelectorDirective } from './datatarget-edit-type-selector.directive'; @Component({ selector: 'app-datatarget-edit', @@ -28,364 +13,57 @@ import { OpenDataDkDataset } from '../opendatadk/opendatadk-dataset.model'; styleUrls: ['./datatarget-edit.component.scss'] }) export class DatatargetEditComponent implements OnInit, OnDestroy { - public multiPage = false; - public title = ''; - public sectionTitle = ''; - public backButtonTitle = ''; - @Input() submitButton: string; - public datatarget: Datatarget = new Datatarget(); - faTimesCircle = faTimesCircle; - public datatargetSubscription: Subscription; - public relationSubscription: Subscription; - public applicationSubscription: Subscription; - public payloadDecoderSubscription: Subscription; - public errorMessages: any; - public errorFields: string[]; - public formFailedSubmit = false; - public datatargetid: number; - private applicationId: number; - private applicationName: string; - public application: Application; - public devices: IotDevice[]; - public payloadDecoders = []; - private counter: number; - private dataSetExcists = false; - private isMailDialogAlreadyShown = false; - payloadDeviceDatatarget: PayloadDeviceDatatarget[]; - newDynamic: any = {}; + @ViewChild(DatatargetEditTypeSelectorDirective, {static: true}) adHost!: DatatargetEditTypeSelectorDirective; - constructor( - public translate: TranslateService, - private route: ActivatedRoute, - private router: Router, - private datatargetService: DatatargetService, - private applicationService: ApplicationService, - private payloadDecoderService: PayloadDecoderService, - private payloadDeviceDataTargetService: PayloadDeviceDatatargetService, - private saveSnackService: SnackService, - private dialog: MatDialog, - private errorMessageService: ErrorMessageService, - private opendatadkService: OpendatadkService, - private opendatadkDialogService: OpendatadkDialogService, - private scrollToTopService: ScrollToTopService, - ) { - translate.use('da'); - } - - ngOnInit() { - this.translate - .get([ - 'FORM.CREATE-NEW-DATATARGET', - 'FORM.EDIT-DATATARGET', - 'DATATARGET.SAVE', - 'NAV.DATATARGET', - ]) - .subscribe((translations) => { - const datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); - if (datatargetid !== 0) { - this.title = translations['FORM.EDIT-DATATARGET']; - } else { - this.title = translations['FORM.CREATE-NEW-DATATARGET']; - } - this.submitButton = translations['DATATARGET.SAVE']; - this.backButtonTitle = translations['NAV.DATATARGET']; - }); + public datatarget: Datatarget; + private datatargetType: DataTargetType; - this.datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); - this.applicationId = +this.route.snapshot.paramMap.get('id'); - this.applicationName = this.route.snapshot.paramMap.get('name'); - if (this.datatargetid !== 0) { - this.getDatatarget(this.datatargetid); - this.getPayloadDeviceDatatarget(this.datatargetid); - } - if (this.applicationId !== 0) { - this.getDevices(); - } - this.getPayloadDecoders(); - this.setDataSetExcists(); - } + constructor(private componentFactoryResolver: ComponentFactoryResolver, + private datatargetService: DatatargetService, + private route: ActivatedRoute, + private datatargetTypesService: DatatargetTypesService + ) { } - addRow() { - if (!this.payloadDeviceDatatarget) { - this.payloadDeviceDatatarget = []; - } - this.payloadDeviceDatatarget.push({ id: null, iotDeviceIds: [], payloadDecoderId: null, dataTargetId: this.datatargetid }); - } - private deleteRow(index) { - if (this.payloadDeviceDatatarget.length === 0) { - } else if (this.payloadDeviceDatatarget[index]?.id === null) { - this.payloadDeviceDatatarget.splice(index, 1); - } else { - this.payloadDeviceDataTargetService.delete(this.payloadDeviceDatatarget[index].id) - .subscribe((response) => { - this.payloadDeviceDatatarget.splice(index, 1); - }); - } + loadComponent(componentType: Type) { + const viewContainerRef = this.adHost.viewContainerRef; + viewContainerRef.clear(); + const factory = this.componentFactoryResolver.resolveComponentFactory(componentType); + viewContainerRef.createComponent(factory); } - openDeleteDialog(index) { - const dialog = this.dialog.open(DeleteDialogComponent, { - data: { - showAccept: true, - showCancel: true, - message: 'Er du sikker på at du vil slette?' - } - }); - - dialog.afterClosed().subscribe((result) => { - if (result === true) { - this.deleteRow(index); - } - }); - } - - onSubmit(): void { - this.counter = 0; - if (this.datatargetid) { - this.updateDatatarget(); - this.addPayloadDeviceDatatarget(); - } else { - this.createDatatarget(); - } - } - - public compare(o1: any, o2: any): boolean { - return o1 === o2; - } + ngOnInit(): void { - updateDatatarget() { - this.resetErrors(); - this.counter = 1 + (this.payloadDeviceDatatarget?.length ? this.payloadDeviceDatatarget?.length : 0); - this.datatargetService.update(this.datatarget) - .subscribe( - (response: Datatarget) => { - this.datatarget = response; - if (this.datatarget.openDataDkDataset != null) { - this.datatarget.openDataDkDataset.acceptTerms = true; - } - this.shouldShowMailDialog().subscribe( - (response) => { - this.countToRedirect(); - } - ); - }, - (error: HttpErrorResponse) => { - this.checkDataTargetModelOpendatadkdatasaet(); - this.handleError(error); - this.formFailedSubmit = true; - } - ); - } + const id: number = +this.route.snapshot.paramMap.get('datatargetId'); - addPayloadDeviceDatatarget() { - this.payloadDeviceDatatarget.map( - pdd => { - if (pdd.payloadDecoderId === 0) { - pdd.payloadDecoderId = null; - } - } - ); - this.payloadDeviceDatatarget.forEach((relation) => { - if (relation.id) { - this.payloadDeviceDataTargetService.put(relation).subscribe( - (response) => { - this.countToRedirect(); - }, - (error) => { - this.handleError(error); - } - ); + if (id > 0) { + this.datatargetService.get(id) + .subscribe((dataTarget: Datatarget) => { + this.datatarget = dataTarget; + this.datatargetType = dataTarget.type; + const component = this.datatargetTypesService.getEditComponent(this.datatargetType); + this.loadComponent(component); + }); } else { - this.payloadDeviceDataTargetService.post(relation).subscribe( - (res: any) => { - this.countToRedirect(); - }, - (error) => { - this.handleError(error); - } - ); - } - }); - } - - countToRedirect() { - this.counter -= 1; - if (this.counter <= 0 && !this.formFailedSubmit) { - this.showSavedSnack(); - this.routeToDatatargets(); - } - } - - getPayloadDeviceDatatarget(id: number) { - this.relationSubscription = this.payloadDeviceDataTargetService - .getByDataTarget(id) - .subscribe((response: PayloadDeviceDatatargetGetByDataTargetResponse) => { - this.mapToDatatargetDevicePayload(response); - }); - } - - createDatatarget() { - this.resetErrors(); - this.datatarget.applicationId = this.applicationId; - this.datatargetService.create(this.datatarget) - .subscribe((response: Datatarget) => { - this.datatargetid = response.id; - this.datatarget = response; - if (this.datatarget.openDataDkDataset != null) { - this.datatarget.openDataDkDataset.acceptTerms = true; + let datatargetTypeParam = this.route.snapshot.paramMap.get('datatargetType'); + this.datatargetType = this.enumFromStringValue(DataTargetType, datatargetTypeParam); + if (this.datatargetType) { + const component = this.datatargetTypesService.getEditComponent(this.datatargetType); + this.loadComponent(component); } - this.showSavedSnack(); - this.routeToDatatargets(); - }, - (error: HttpErrorResponse) => { - this.checkDataTargetModelOpendatadkdatasaet(); - this.handleError(error); - this.formFailedSubmit = true; - }); - - } - - private resetErrors() { - this.errorFields = []; - this.errorMessages = undefined; - this.formFailedSubmit = false; - } - - checkDataTargetModelOpendatadkdatasaet() { - if (!this.datatarget.openDataDkDataset) { - this.datatarget.openDataDkDataset = new OpenDataDkDataset(); - } - } - - getDevices(): void { - this.applicationSubscription = this.applicationService.getApplication(this.applicationId) - .subscribe((application: Application) => { - this.devices = application.iotDevices; - }); - } - - public selectAllDevices(index: number) { - this.payloadDeviceDatatarget[index].iotDeviceIds = this.devices.map(device => device.id); - } - - public deSelectAllDevices(index: number) { - this.payloadDeviceDatatarget[index].iotDeviceIds = []; - } - - getPayloadDecoders() { - this.payloadDecoderSubscription = this.payloadDecoderService.getMultiple(1000, 0, 'id', 'ASC') - .subscribe((response: PayloadDecoderMappedResponse) => { - this.payloadDecoders = response.data; - }); - } - - handleError(error: HttpErrorResponse) { - const errors = this.errorMessageService.handleErrorMessageWithFields(error); - this.errorFields = errors.errorFields; - this.errorMessages = errors.errorMessages; - this.scrollToTopService.scrollToTop(); - } - - routeToDatatargets(): void { - this.router.navigate(['applications',this.applicationId.toString()]) - } - - onCoordinateKey(event: any) { - if (event.target.value.length > event.target.maxLength) { - event.target.value = event.target.value.slice( - 0, - event.target.maxLength - ); - } - } - - getDatatarget(id: number) { - this.datatargetSubscription = this.datatargetService - .get(id) - .subscribe((response: Datatarget) => { - this.datatarget = response; - }); - } - - showSavedSnack() { - this.saveSnackService.showSavedSnack(); - } - - private setDataSetExcists() { - this.opendatadkService.get().subscribe( - (response) => { - this.dataSetExcists = response.dataset.length === 0 ? false : true; } - ); - } - private shouldShowMailDialog(): Observable { - return new Observable( - (observer) => { - if (!this.dataSetExcists && this.datatarget.setToOpendataDk && !this.isMailDialogAlreadyShown) { - this.isMailDialogAlreadyShown = true; - this.opendatadkDialogService.showDialog().subscribe( - response => { - if (response) { - this.showMailClient(); - } - observer.next(response); - } - ); - } else { - observer.next(true); - } - } - ) - } - private showMailClient() { - if (!this.datatarget.openDataDkDataset.url) { - this.datatarget.openDataDkDataset.url = this.datatargetService.getOpendataSharingApiUrl() - } - window.location.href = 'mailto:FG2V@kk.dk?subject=Oprettelse%20af%20datas%C3%A6t%20i%20OpenDataDK&body=K%C3%A6re%20Frans%0D%0A%0D%0AHermed%20fremsendes%20linket%20til%20DCAT%20kataloget%20%2C%20du%20bedes%20registrere%20p%C3%A5%20Open%20Data%20DK%20platformen.%0D%0A%0D%0ALink%3A ' + this.datatarget.openDataDkDataset.url; } - disableSaveButton(): boolean { - let disable = true; - if (!this.datatarget.setToOpendataDk) { - disable = false; - } else if (this.datatarget.openDataDkDataset?.acceptTerms) { - disable = false; - } else { - disable = true; - } - return disable; + enumFromStringValue(enm: { [s: string]: T}, value: string): T | undefined { + return (Object.values(enm) as unknown as string[]).includes(value) + ? value as unknown as T + : undefined; } - ngOnDestroy(): void { - if (this.relationSubscription) { - this.relationSubscription.unsubscribe(); - } - if (this.applicationSubscription) { - this.applicationSubscription.unsubscribe(); - } - if (this.datatargetSubscription) { - this.datatargetSubscription.unsubscribe(); - } - if (this.payloadDecoderSubscription) { - this.payloadDecoderSubscription.unsubscribe(); - } - } + ngOnDestroy() { - private mapToDatatargetDevicePayload(dto: PayloadDeviceDatatargetGetByDataTargetResponse) { - this.payloadDeviceDatatarget = []; - dto.data.forEach( - (element) => { - this.payloadDeviceDatatarget.push({ - id: element.id, - iotDeviceIds: element.iotDevices.map((x) => x.id), - payloadDecoderId: element.payloadDecoder?.id === undefined ? 0 : element.payloadDecoder?.id, - dataTargetId: element.dataTarget.id - }); - } - ); - } + } } diff --git a/src/app/applications/datatarget/datatarget-edit/datatarget-edit.ts b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.ts new file mode 100644 index 00000000..16def301 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-edit/datatarget-edit.ts @@ -0,0 +1,2 @@ +export interface DatatargetEdit { +} diff --git a/src/app/applications/datatarget/datatarget-new/datatarget-new.component.html b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.html new file mode 100644 index 00000000..6d654898 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.html @@ -0,0 +1,34 @@ + + + +
+
+ + +
+
+ + + +
+ {{dataTargetType.name}} +
+ + {{dataTargetType.provider}} + +
+ + {{ dataTargetType.description }} + + + + + + + + +
+
+
diff --git a/src/app/applications/datatarget/datatarget-new/datatarget-new.component.scss b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.scss new file mode 100644 index 00000000..71485bcb --- /dev/null +++ b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.scss @@ -0,0 +1,44 @@ +.data-component { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin-left: 10px; +} + +.mat-card{ + display:flex; + flex-direction: column; + flex-wrap: wrap; + width: 300px; + min-width: 300px; + max-width: 400px; + height: 320px; + margin: 10px; +} + +.mat-card-header { + flex-shrink: 1; + +} + +.mat-card-content{ + flex-grow: 1; + overflow: auto; + margin-left: 10px; +} + + +mat-card img{ + object-fit: contain; /*this makes the image in src fit to the size specified below*/ + object-position: left; + width: 50%; /* Here you can use wherever you want to specify the width and also the height of the */ + height: 20px; + margin-left: 3px; + margin-top: 3px; + margin-bottom: 3px; +} + +.img-placeholder{ + height: 26px; + clear: both; +} diff --git a/src/app/applications/datatarget/datatarget-new/datatarget-new.component.spec.ts b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.spec.ts new file mode 100644 index 00000000..4f1f6508 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { DatatargetNewComponent } from './datatarget-new.component'; + +describe('DatatargetNewComponent', () => { + let component: DatatargetNewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DatatargetNewComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DatatargetNewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/datatarget-new/datatarget-new.component.ts b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.ts new file mode 100644 index 00000000..d3f90b12 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-new/datatarget-new.component.ts @@ -0,0 +1,63 @@ +import { Component, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { DatatargetTypeDescriptor } from '../datatarget.model'; +import { DatatargetTypesService } from '../datatarget-types.service'; + + + +@Component({ + selector: 'app-datatarget-new', + templateUrl: './datatarget-new.component.html', + styleUrls: ['./datatarget-new.component.scss'] +}) +export class DatatargetNewComponent implements OnInit { + + public title = ''; + public sectionTitle = ''; + public backButtonTitle = ''; + public submitButton = ''; + public avaiableDataTargetTypes : DatatargetTypeDescriptor[]; + + constructor( + public translate: TranslateService, + private route: ActivatedRoute, + private router: Router, + private dataTargetTypesService: DatatargetTypesService + ) { + translate.use('da'); + } + + ngOnInit() { + + this.translate + .get([ + 'FORM.CREATE-NEW-DATATARGET', + 'FORM.EDIT-DATATARGET', + 'DATATARGET.SAVE', + 'NAV.DATATARGET', + ]) + .subscribe((translations) => { + const datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); + if (datatargetid !== 0) { + this.title = translations['FORM.EDIT-DATATARGET']; + } else { + this.title = translations['FORM.CREATE-NEW-DATATARGET']; + } + this.submitButton = translations['DATATARGET.SAVE']; + this.backButtonTitle = translations['NAV.DATATARGET']; + }); + + this.avaiableDataTargetTypes = this.dataTargetTypesService.getAvailableDataTargetTypes(); + + } + + public createNewOf(typeDescriptor: DatatargetTypeDescriptor) + { + this.router.navigate(['../datatarget-edit', {datatargetType: typeDescriptor.type}], {relativeTo:this. route}); + } + public showReadMe(typeDescriptor: DatatargetTypeDescriptor) + { + window.open(typeDescriptor.readMoreUrl, "_blank"); + } +} diff --git a/src/app/applications/datatarget/datatarget-response.model.ts b/src/app/applications/datatarget/datatarget-response.model.ts index 1d6b809d..fcb40286 100644 --- a/src/app/applications/datatarget/datatarget-response.model.ts +++ b/src/app/applications/datatarget/datatarget-response.model.ts @@ -9,6 +9,8 @@ export class DatatargetResponse { timeout: number; type: DataTargetType; url: string; + tenant: string; + context: string; authorizationHeader: string; openDataDkDataset: OpenDataDkDataset; createdAt: string; diff --git a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html index c839d784..d2b4aca8 100644 --- a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html +++ b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html @@ -21,7 +21,7 @@ {{ 'DATATARGET-TABLE.TYPE' | translate }} - {{element.type | translate}} + {{'DATATARGET.' + element.type | translate}} diff --git a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts index fa4eb35d..65c04906 100644 --- a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts +++ b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts @@ -46,8 +46,7 @@ export class DatatargetTableComponent implements OnInit, AfterViewInit, OnDestro } ngOnInit(): void { - this.applicationId = +Number(this.route.parent.snapshot.paramMap.get('id')); - console.log(this.applicationId); + this.applicationId = +Number(this.route.parent.snapshot.paramMap.get('id')); this.getDatatarget(); this.canEdit = this.meService.canWriteInTargetOrganization() } diff --git a/src/app/applications/datatarget/datatarget-types.service.spec.ts b/src/app/applications/datatarget/datatarget-types.service.spec.ts new file mode 100644 index 00000000..d5ef25d5 --- /dev/null +++ b/src/app/applications/datatarget/datatarget-types.service.spec.ts @@ -0,0 +1,16 @@ +/* tslint:disable:no-unused-variable */ + +import { TestBed, async, inject } from '@angular/core/testing'; +import { DatatargetTypesService } from './datatarget-types.service'; + +describe('Service: DatatargetTypesService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [DatatargetTypesService] + }); + }); + + it('should ...', inject([DatatargetTypesService], (service: DatatargetTypesService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/applications/datatarget/datatarget-types.service.ts b/src/app/applications/datatarget/datatarget-types.service.ts new file mode 100644 index 00000000..581788cf --- /dev/null +++ b/src/app/applications/datatarget/datatarget-types.service.ts @@ -0,0 +1,84 @@ +import { Injectable, Type } from '@angular/core'; +import { DataTargetType } from '@shared/enums/datatarget-type'; +import { DatatargetDetail } from './datatarget-detail/datatarget-detail'; +import { DatatargetEdit } from './datatarget-edit/datatarget-edit'; +import { DatatargetTypeDescriptor } from './datatarget.model'; +import { FiwareDetailComponent } from './fiware/fiware-detail/fiware-detail.component'; +import { FiwareEditComponent } from './fiware/fiware-edit/fiware-edit.component'; +import { HttppushDetailComponent } from './httppush/httppush-detail/httppush-detail.component'; +import { HttppushEditComponent } from './httppush/httppush-edit/httppush-edit.component'; + +@Injectable({ + providedIn: 'root' +}) +export class DatatargetTypesService { + +constructor() { } + + getAvailableDataTargetTypes() : DatatargetTypeDescriptor[] + { + return [ + { name: 'Generisk HTTP Push', + type: DataTargetType.HTTPPUSH, + icon: null, + description: 'Send data med HTTP POST requests til et HTTP URL endpoint', + readMoreUrl: '', + provider: 'OS2' + + }, + { name: 'Open Data DK', + type: DataTargetType.OPENDATADK, + icon: '/assets/images/logo_opendatadk.svg', + description: 'Offentliggør datasæt i Open Data DK\'s åbne dataportal.', + readMoreUrl: 'https://www.opendata.dk/', + provider: 'OS2' + + }, + { name: 'FIWARE connection', + type: DataTargetType.FIWARE, + icon: '/assets/images/logo_FIWARE.png', + description: 'En integration til FIWARE Context Broker' , + readMoreUrl: 'https://www.kmd.dk', + provider: 'KMD A/S' + } + ] + } + + getDetailComponent(dataTargetType: DataTargetType): Type + { + if (dataTargetType === DataTargetType.HTTPPUSH) + { + return HttppushDetailComponent; + } + + if (dataTargetType === DataTargetType.OPENDATADK) + { + return HttppushDetailComponent; + } + + if (dataTargetType === DataTargetType.FIWARE) + { + return FiwareDetailComponent; + } + } + + getEditComponent(dataTargetType: DataTargetType): Type + { + if (dataTargetType === DataTargetType.HTTPPUSH) + { + return HttppushEditComponent; + } + + if (dataTargetType === DataTargetType.OPENDATADK) + { + return HttppushEditComponent; + } + + if (dataTargetType === DataTargetType.FIWARE) + { + return FiwareEditComponent; + } + } + +} + diff --git a/src/app/applications/datatarget/datatarget.model.ts b/src/app/applications/datatarget/datatarget.model.ts index 03fba64b..12e846c9 100644 --- a/src/app/applications/datatarget/datatarget.model.ts +++ b/src/app/applications/datatarget/datatarget.model.ts @@ -7,6 +7,8 @@ export class Datatarget { applicationId: number; type: DataTargetType = DataTargetType.HTTPPUSH; url: string; + tenant: string; + context: string; //default 30 sec timeout: number = 30000; authorizationHeader: string; @@ -24,4 +26,14 @@ export class DatatargetData { data: Datatarget[]; ok?: boolean; count?: number; +} + +export class DatatargetTypeDescriptor +{ + name: string; + type: DataTargetType; + icon: string; + description: string; + readMoreUrl: string; + provider: string; } \ No newline at end of file diff --git a/src/app/applications/datatarget/datatarget.module.ts b/src/app/applications/datatarget/datatarget.module.ts index 46be0a33..ac65cbea 100644 --- a/src/app/applications/datatarget/datatarget.module.ts +++ b/src/app/applications/datatarget/datatarget.module.ts @@ -14,15 +14,29 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { SharedModule } from '@shared/shared.module'; import { PipesModule } from '@shared/pipes/pipes.module'; +import { DatatargetNewComponent } from './datatarget-new/datatarget-new.component'; +import { FiwareEditComponent } from './fiware/fiware-edit/fiware-edit.component'; +import { FiwareDetailComponent } from './fiware/fiware-detail/fiware-detail.component'; +import { HttppushDetailComponent } from './httppush/httppush-detail/httppush-detail.component'; +import { HttppushEditComponent } from './httppush/httppush-edit/httppush-edit.component'; +import { DatatargetDetailTypeSelectorDirective } from './datatarget-detail/datatarget-detail-type-selector.directive'; +import { DatatargetEditTypeSelectorDirective } from './datatarget-edit/datatarget-edit-type-selector.directive'; @NgModule({ - declarations: [ + declarations: [ DatatargetTableComponent, DatatargetEditComponent, + DatatargetNewComponent, DatatargetDetailComponent, + FiwareDetailComponent, + FiwareEditComponent, + HttppushDetailComponent, + HttppushEditComponent, OpendatadkComponent, OpendatadkEditComponent, - OpendatadkDetailComponent], + OpendatadkDetailComponent, + DatatargetDetailTypeSelectorDirective, + DatatargetEditTypeSelectorDirective], imports: [ CommonModule, RouterModule, @@ -38,7 +52,12 @@ import { PipesModule } from '@shared/pipes/pipes.module'; exports: [ DatatargetTableComponent, DatatargetEditComponent, - DatatargetDetailComponent, + DatatargetNewComponent, + DatatargetDetailComponent, + FiwareDetailComponent, + FiwareEditComponent, + HttppushDetailComponent, + HttppushEditComponent, NGMaterialModule ] }) diff --git a/src/app/applications/datatarget/datatarget.service.ts b/src/app/applications/datatarget/datatarget.service.ts index 74e3688c..6647ed30 100644 --- a/src/app/applications/datatarget/datatarget.service.ts +++ b/src/app/applications/datatarget/datatarget.service.ts @@ -90,6 +90,8 @@ export class DatatargetService { timeout: dataTargetResponse.timeout, type: dataTargetResponse.type, url: dataTargetResponse.url, + tenant: dataTargetResponse.tenant, + context: dataTargetResponse.context, authorizationHeader: dataTargetResponse.authorizationHeader, applicationId: dataTargetResponse.application.id, setToOpendataDk: dataTargetResponse?.openDataDkDataset ? true : false, diff --git a/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.html b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.html new file mode 100644 index 00000000..8e895d9c --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.html @@ -0,0 +1,76 @@ +
+ +
+
+
+
+

{{ 'DATATARGET.DETAILS' | translate }}

+ + +

Context Broker{{datatarget.url}}

+

{{ 'DATATARGET.TIMEOUT' | translate }}{{datatarget.timeout}}

+

{{ 'DATATARGET.TYPE' | translate }}{{'DATATARGET.' + datatarget.type | translate}}

+ + +

{{ 'DATATARGET.TENANT' | translate }}

+
{{datatarget.tenant}}
+ +

{{ 'DATATARGET.NO-TENANT' | translate }}

+
+ +

{{ 'DATATARGET.CONTEXT' | translate }}

+
{{datatarget.context}}
+ +

{{ 'DATATARGET.NO-CONTEXT' | translate }}

+
+ +

{{ 'DATATARGET.AUTHORIZATIONHEADER' | translate }}

+
{{datatarget.authorizationHeader}}
+ +

{{ 'DATATARGET.NO-AUTHORIZATIONHEADER' | translate }}

+
+ +
+
+ + +
+
+
+
+

{{ 'DATATARGET.RELATIONS' | translate }}

+
+

{{'DATATARGET.NO-RELATIONS' | translate}}

+
+
+
+
+
+

{{'DATATARGET.PAYLOADEDECODER' | translate}} + {{relation.payloadDecoder.name}} + + {{ 'DATATARGET.NO-PAYLOADDECODER' | translate}} +

+
+
+ +
+
+

{{'DATATARGET.IOTDEVICE' | translate}} + + , {{device.name}} + +

+
+ +
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.scss b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.spec.ts b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.spec.ts new file mode 100644 index 00000000..7f5198d9 --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { FiwareDetailComponent } from './fiware-detail.component'; + +describe('FiwareDetailComponent', () => { + let component: FiwareDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FiwareDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FiwareDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.ts b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.ts new file mode 100644 index 00000000..d56148e6 --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-detail/fiware-detail.component.ts @@ -0,0 +1,98 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { PayloadDeviceDatatargetGetByDataTarget } from '@app/payload-decoder/payload-device-data.model'; +import { PayloadDeviceDatatargetService } from '@app/payload-decoder/payload-device-datatarget.service'; +import { BackButton } from '@shared/models/back-button.model'; +import { DatatargetService } from '../../datatarget.service'; +import { Location } from '@angular/common'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { Datatarget } from '../../datatarget.model'; +import { DropdownButton } from '@shared/models/dropdown-button.model'; +import { faArrowsAltH } from '@fortawesome/free-solid-svg-icons'; +import { DatatargetDetail } from '@applications/datatarget/datatarget-detail/datatarget-detail'; + + +@Component({ + selector: 'app-fiware-detail', + templateUrl: './fiware-detail.component.html', + styleUrls: ['./fiware-detail.component.scss'] +}) +export class FiwareDetailComponent implements DatatargetDetail, OnInit, OnDestroy { + + public datatargetSubscription: Subscription; + public datatarget: Datatarget; + public backButton: BackButton = { label: '', routerLink: '' }; + public dataTargetRelations: PayloadDeviceDatatargetGetByDataTarget[]; + private deleteDialogSubscription: Subscription; + public dropdownButton: DropdownButton; + arrowsAltH = faArrowsAltH; + + constructor( + private route: ActivatedRoute, + private deleteDialogService: DeleteDialogService, + private location: Location, + private datatargetRelationServicer: PayloadDeviceDatatargetService, + private datatargetService: DatatargetService, + public translate: TranslateService) { } + + ngOnInit(): void { + const id: number = +this.route.snapshot.paramMap.get('datatargetId'); + + if (id) { + this.getDatatarget(id); + this.getDatatargetRelations(id); + this.dropdownButton = { + label: '', + editRouterLink: '../../datatarget-edit/' + id, + isErasable: true, + }; + } + this.translate.get(['NAV.MY-DATATARGET', 'DATATARGET.SHOW-OPTIONS']) + .subscribe(translations => { + this.backButton.label = translations['NAV.MY-DATATARGET']; + this.dropdownButton.label = translations['DATATARGET.SHOW-OPTIONS']; + }); + } + + getDatatarget(id: number) { + this.datatargetService.get(id) + .subscribe((dataTarget: Datatarget) => { + this.datatarget = dataTarget; + this.setBackButton(this.datatarget.applicationId); + }); + } + + private setBackButton(applicationId: number) { + this.backButton.routerLink = ['applications', applicationId.toString() ]; + } + + onDeleteDatatarget() { + this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog().subscribe( + (response) => { + if (response) { + this.datatargetService.delete(this.datatarget.id).subscribe((response) => { + }); + this.location.back(); + } else { + console.log(response); + } + } + ); + } + + getDatatargetRelations(id: number) { + this.datatargetRelationServicer.getByDataTarget(id) + .subscribe((response) => { + this.dataTargetRelations = response.data; + }); + } + + ngOnDestroy(): void { + if (this.deleteDialogSubscription) { + this.deleteDialogSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.html b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.html new file mode 100644 index 00000000..02ec7529 --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.html @@ -0,0 +1,167 @@ + + + +
+ +
+
    +
  • + {{error | translate}} +
  • +
+
+ +
+
+ * + +
+
+ +
+
+ * + +
+
+ +
+
+ + + +
+
+ +
+
+ + + +
+
+ +
+
+ * + +
+
+ +
+
+ + +
+
+
{{'QUESTION.DATATARGET.RELATIONS' | translate}}
+
+
+ {{'QUESTION.ADD-RELATIONS' | translate}} + + + + + + + + + +
+
+ + {{'QUESTION.DATATARGET.SELECT-DEVICES' | translate}} + + + + + + {{device.name}} + + +
+
+
+ + {{'QUESTION.DATATARGET.SELECT-PAYLOADDECODER' | translate}} + + + {{'QUESTION.DATATARGET.NO-PAYLOAD-DECODER-SELECTED' | translate}} + + + {{payloadDecoder.name}} + + + +
+
+ +
+ +

{{'DATATARGET.DELETE' | translate}}

+
+
+
+
+
+ +
+ + +
+
+
diff --git a/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.scss b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.scss new file mode 100644 index 00000000..bb96f657 --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.scss @@ -0,0 +1,4 @@ +.form-info-icon { + margin-left: 5px; + cursor: pointer; +} \ No newline at end of file diff --git a/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.spec.ts b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.spec.ts new file mode 100644 index 00000000..6a58e7f6 --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { FiwareEditComponent } from './fiware-edit.component'; + +describe('FiwareEditComponent', () => { + let component: FiwareEditComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FiwareEditComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FiwareEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.ts b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.ts new file mode 100644 index 00000000..b52a823b --- /dev/null +++ b/src/app/applications/datatarget/fiware/fiware-edit/fiware-edit.component.ts @@ -0,0 +1,336 @@ +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Datatarget } from '../../datatarget.model'; +import { Observable, Subscription } from 'rxjs'; +import { Application } from '@applications/application.model'; +import { IotDevice } from '@applications/iot-devices/iot-device.model'; +import { faTimesCircle } from '@fortawesome/free-solid-svg-icons'; +import { PayloadDeviceDatatarget, PayloadDeviceDatatargetGetByDataTargetResponse } from '@payload-decoder/payload-device-data.model'; +import { DatatargetService } from '../../datatarget.service'; +import { ApplicationService } from '@applications/application.service'; +import { PayloadDecoderService } from '@payload-decoder/payload-decoder.service'; +import { PayloadDeviceDatatargetService } from '@payload-decoder/payload-device-datatarget.service'; +import { SnackService } from '@shared/services/snack.service'; +import { MatDialog } from '@angular/material/dialog'; +import { HttpErrorResponse } from '@angular/common/http'; +import { PayloadDecoderMappedResponse } from '@payload-decoder/payload-decoder.model'; +import { DeleteDialogComponent } from '@shared/components/delete-dialog/delete-dialog.component'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; +import { DataTargetType } from '@shared/enums/datatarget-type'; +import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; +import { DatatargetEdit } from '@applications/datatarget/datatarget-edit/datatarget-edit'; + +@Component({ + selector: 'app-fiware-edit', + templateUrl: './fiware-edit.component.html', + styleUrls: ['./fiware-edit.component.scss'] +}) +export class FiwareEditComponent implements DatatargetEdit, OnInit, OnDestroy { + + + public multiPage = false; + public title = ''; + public sectionTitle = ''; + public backButtonTitle = ''; + @Input() submitButton: string; + public datatarget: Datatarget = new Datatarget(); + faTimesCircle = faTimesCircle; + public datatargetSubscription: Subscription; + public relationSubscription: Subscription; + public applicationSubscription: Subscription; + public payloadDecoderSubscription: Subscription; + public errorMessages: any; + public errorFields: string[]; + public formFailedSubmit = false; + public datatargetid: number; + private applicationId: number; + public application: Application; + public devices: IotDevice[]; + public payloadDecoders = []; + private counter: number; + payloadDeviceDatatarget: PayloadDeviceDatatarget[]; + newDynamic: any = {}; + faQuestionCircle = faQuestionCircle; + + + constructor( + public translate: TranslateService, + private route: ActivatedRoute, + private router: Router, + private datatargetService: DatatargetService, + private applicationService: ApplicationService, + private payloadDecoderService: PayloadDecoderService, + private payloadDeviceDataTargetService: PayloadDeviceDatatargetService, + private snackService: SnackService, + private dialog: MatDialog, + private errorMessageService: ErrorMessageService, + private scrollToTopService: ScrollToTopService, + ) { + translate.use('da'); + } + + + + ngOnInit() { + this.translate + .get([ + 'FORM.CREATE-NEW-DATATARGET', + 'FORM.EDIT-DATATARGET', + 'DATATARGET.SAVE', + 'NAV.DATATARGET', + ]) + .subscribe((translations) => { + const datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); + if (datatargetid !== 0) { + this.title = translations['FORM.EDIT-DATATARGET']; + } else { + this.title = translations['FORM.CREATE-NEW-DATATARGET']; + } + this.submitButton = translations['DATATARGET.SAVE']; + this.backButtonTitle = translations['NAV.DATATARGET']; + }); + + this.datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); + this.applicationId = +this.route.snapshot.paramMap.get('id'); + + this.datatarget.type = DataTargetType.FIWARE; + + if (this.datatargetid !== 0) { + this.getDatatarget(this.datatargetid); + this.getPayloadDeviceDatatarget(this.datatargetid); + } + if (this.applicationId !== 0) { + this.getDevices(); + } + this.getPayloadDecoders(); + + } + + + addRow() { + if (!this.payloadDeviceDatatarget) { + this.payloadDeviceDatatarget = []; + } + this.payloadDeviceDatatarget.push({ id: null, iotDeviceIds: [], payloadDecoderId: null, dataTargetId: this.datatargetid }); + } + + private deleteRow(index) { + if (this.payloadDeviceDatatarget.length === 0) { + } else if (this.payloadDeviceDatatarget[index]?.id === null) { + this.payloadDeviceDatatarget.splice(index, 1); + } else { + this.payloadDeviceDataTargetService.delete(this.payloadDeviceDatatarget[index].id) + .subscribe((response) => { + this.payloadDeviceDatatarget.splice(index, 1); + }); + } + } + + openDeleteDialog(index) { + const dialog = this.dialog.open(DeleteDialogComponent, { + data: { + showAccept: true, + showCancel: true, + message: 'Er du sikker på at du vil slette?' + } + }); + + dialog.afterClosed().subscribe((result) => { + if (result === true) { + this.deleteRow(index); + } + }); + } + + onSubmit(): void { + this.counter = 0; + if (this.datatargetid) { + this.updateDatatarget(); + this.addPayloadDeviceDatatarget(); + } else { + this.createDatatarget(); + } + } + + public compare(o1: any, o2: any): boolean { + return o1 === o2; + } + + updateDatatarget() { + this.resetErrors(); + this.counter = 1 + (this.payloadDeviceDatatarget?.length ? this.payloadDeviceDatatarget?.length : 0); + this.datatargetService.update(this.datatarget) + .subscribe( + (response: Datatarget) => { + this.datatarget = response; + this.countToRedirect(); + }, + (error: HttpErrorResponse) => { + this.handleError(error); + this.formFailedSubmit = true; + } + ); + } + + addPayloadDeviceDatatarget() { + this.payloadDeviceDatatarget.map( + pdd => { + if (pdd.payloadDecoderId === 0) { + pdd.payloadDecoderId = null; + } + } + ); + this.payloadDeviceDatatarget.forEach((relation) => { + if (relation.id) { + this.payloadDeviceDataTargetService.put(relation).subscribe( + (response) => { + this.countToRedirect(); + }, + (error) => { + this.handleError(error); + } + ); + } else { + this.payloadDeviceDataTargetService.post(relation).subscribe( + (res: any) => { + this.countToRedirect(); + }, + (error) => { + this.handleError(error); + } + ); + } + }); + } + + countToRedirect() { + this.counter -= 1; + if (this.counter <= 0 && !this.formFailedSubmit) { + this.showSavedSnack(); + this.routeToDatatargets(); + } + } + + getPayloadDeviceDatatarget(id: number) { + this.relationSubscription = this.payloadDeviceDataTargetService + .getByDataTarget(id) + .subscribe((response: PayloadDeviceDatatargetGetByDataTargetResponse) => { + this.mapToDatatargetDevicePayload(response); + }); + } + + createDatatarget() { + this.resetErrors(); + this.datatarget.applicationId = this.applicationId; + this.datatargetService.create(this.datatarget) + .subscribe((response: Datatarget) => { + this.datatargetid = response.id; + this.datatarget = response; + this.showSavedSnack(); + }, + (error: HttpErrorResponse) => { + this.handleError(error); + this.formFailedSubmit = true; + }); + + } + + private resetErrors() { + this.errorFields = []; + this.errorMessages = undefined; + this.formFailedSubmit = false; + } + + + + getDevices(): void { + this.applicationSubscription = this.applicationService.getApplication(this.applicationId) + .subscribe((application: Application) => { + this.devices = application.iotDevices; + }); + } + + public selectAllDevices(index: number) { + this.payloadDeviceDatatarget[index].iotDeviceIds = this.devices.map(device => device.id); + } + + public deSelectAllDevices(index: number) { + this.payloadDeviceDatatarget[index].iotDeviceIds = []; + } + + getPayloadDecoders() { + this.payloadDecoderSubscription = this.payloadDecoderService.getMultiple(1000, 0, 'id', 'ASC') + .subscribe((response: PayloadDecoderMappedResponse) => { + this.payloadDecoders = response.data; + }); + } + + handleError(error: HttpErrorResponse) { + const errors = this.errorMessageService.handleErrorMessageWithFields(error); + this.errorFields = errors.errorFields; + this.errorMessages = errors.errorMessages; + this.scrollToTopService.scrollToTop(); + } + + routeToDatatargets(): void { + this.router.navigate(['applications', this.applicationId.toString()]); + } + + onCoordinateKey(event: any) { + if (event.target.value.length > event.target.maxLength) { + event.target.value = event.target.value.slice( + 0, + event.target.maxLength + ); + } + } + + getDatatarget(id: number) { + this.datatargetSubscription = this.datatargetService + .get(id) + .subscribe((response: Datatarget) => { + this.datatarget = response; + }); + } + + showSavedSnack() { + this.snackService.showSavedSnack(); + } + + disableSaveButton(): boolean { + const disable = false; + + return disable; + } + + ngOnDestroy(): void { + if (this.relationSubscription) { + this.relationSubscription.unsubscribe(); + } + if (this.applicationSubscription) { + this.applicationSubscription.unsubscribe(); + } + if (this.datatargetSubscription) { + this.datatargetSubscription.unsubscribe(); + } + if (this.payloadDecoderSubscription) { + this.payloadDecoderSubscription.unsubscribe(); + } + } + + private mapToDatatargetDevicePayload(dto: PayloadDeviceDatatargetGetByDataTargetResponse) { + this.payloadDeviceDatatarget = []; + dto.data.forEach( + (element) => { + this.payloadDeviceDatatarget.push({ + id: element.id, + iotDeviceIds: element.iotDevices.map((x) => x.id), + payloadDecoderId: element.payloadDecoder?.id === undefined ? 0 : element.payloadDecoder?.id, + dataTargetId: element.dataTarget.id + }); + } + ); + } +} diff --git a/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.html b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.html new file mode 100644 index 00000000..42cf6d1f --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.html @@ -0,0 +1,74 @@ +
+ +
+
+
+
+

{{ 'DATATARGET.DETAILS' | translate }}

+ + +

{{ 'DATATARGET.URL' | translate }}{{datatarget.url}}

+

{{ 'DATATARGET.TIMEOUT' | translate }}{{datatarget.timeout}}

+

{{ 'DATATARGET.TYPE' | translate }}{{'DATATARGET.' + datatarget.type | translate}}

+ +

{{ 'DATATARGET.AUTHORIZATIONHEADER' | translate }}

+
{{datatarget.authorizationHeader}}
+ +

{{ 'DATATARGET.NO-AUTHORIZATIONHEADER' | translate }}

+
+ +
+
+
+
+

{{ 'DATATARGET.OPENDATA-DK' | translate }}

+
+ +
+ +

{{ 'DATATARGET.NO-OPENDATA-DK' | translate }}

+
+
+
+
+ +
+
+
+
+

{{ 'DATATARGET.RELATIONS' | translate }}

+
+

{{'DATATARGET.NO-RELATIONS' | translate}}

+
+
+
+
+
+

{{'DATATARGET.PAYLOADEDECODER' | translate}} + {{relation.payloadDecoder.name}} + + {{ 'DATATARGET.NO-PAYLOADDECODER' | translate}} +

+
+
+ +
+
+

{{'DATATARGET.IOTDEVICE' | translate}} + + , {{device.name}} + +

+
+ +
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.scss b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.scss new file mode 100644 index 00000000..e5e49dff --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.scss @@ -0,0 +1,3 @@ +pre { + word-wrap: break-word; + } \ No newline at end of file diff --git a/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.spec.ts b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.spec.ts new file mode 100644 index 00000000..a212025a --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { HttppushDetailComponent } from './httppush-detail.component'; + +describe('HttppushDetailComponent', () => { + let component: HttppushDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HttppushDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HttppushDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.ts b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.ts new file mode 100644 index 00000000..179aebcf --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-detail/httppush-detail.component.ts @@ -0,0 +1,98 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { PayloadDeviceDatatargetGetByDataTarget } from '@app/payload-decoder/payload-device-data.model'; +import { PayloadDeviceDatatargetService } from '@app/payload-decoder/payload-device-datatarget.service'; +import { BackButton } from '@shared/models/back-button.model'; +import { DatatargetService } from '../../datatarget.service'; +import { Location } from '@angular/common'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { Datatarget } from '../../datatarget.model'; +import { DropdownButton } from '@shared/models/dropdown-button.model'; +import { faArrowsAltH } from '@fortawesome/free-solid-svg-icons'; +import { DatatargetDetail } from '@applications/datatarget/datatarget-detail/datatarget-detail'; + +@Component({ + selector: 'app-httppush-detail', + templateUrl: './httppush-detail.component.html', + styleUrls: ['./httppush-detail.component.scss'] +}) +export class HttppushDetailComponent implements DatatargetDetail, OnInit, OnDestroy { + + public datatargetSubscription: Subscription; + public datatarget: Datatarget; + public backButton: BackButton = { label: '', routerLink: '' }; + public dataTargetRelations: PayloadDeviceDatatargetGetByDataTarget[]; + private deleteDialogSubscription: Subscription; + public dropdownButton: DropdownButton; + arrowsAltH = faArrowsAltH; + private applicationName: string; + + constructor( + private route: ActivatedRoute, + private deleteDialogService: DeleteDialogService, + private location: Location, + private datatargetRelationServicer: PayloadDeviceDatatargetService, + private datatargetService: DatatargetService, + public translate: TranslateService) { } + + ngOnInit(): void { + const id: number = +this.route.snapshot.paramMap.get('datatargetId'); + this.applicationName = this.route.snapshot.paramMap.get('name'); + if (id) { + this.getDatatarget(id); + this.getDatatargetRelations(id); + this.dropdownButton = { + label: '', + editRouterLink: '../../datatarget-edit/' + id, + isErasable: true, + }; + } + this.translate.get(['NAV.MY-DATATARGET', 'DATATARGET.SHOW-OPTIONS']) + .subscribe(translations => { + this.backButton.label = translations['NAV.MY-DATATARGET']; + this.dropdownButton.label = translations['DATATARGET.SHOW-OPTIONS']; + }); + } + + getDatatarget(id: number) { + this.datatargetService.get(id) + .subscribe((dataTarget: Datatarget) => { + this.datatarget = dataTarget; + this.setBackButton(this.datatarget.applicationId); + }); + } + + private setBackButton(applicationId: number) { + this.backButton.routerLink = ['applications', applicationId.toString()]; + } + + onDeleteDatatarget() { + this.deleteDialogSubscription = this.deleteDialogService.showSimpleDialog().subscribe( + (response) => { + if (response) { + this.datatargetService.delete(this.datatarget.id).subscribe((response) => { + }); + this.location.back(); + } else { + console.log(response); + } + } + ); + } + + getDatatargetRelations(id: number) { + this.datatargetRelationServicer.getByDataTarget(id) + .subscribe((response) => { + this.dataTargetRelations = response.data; + }); + } + + ngOnDestroy(): void { + if (this.deleteDialogSubscription) { + this.deleteDialogSubscription.unsubscribe(); + } + } + +} diff --git a/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.html b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.html new file mode 100644 index 00000000..4d4df069 --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.html @@ -0,0 +1,139 @@ + + + +
+
+
    +
  • + {{error | translate}} +
  • +
+
+ + {{'DATATARGET.ADD-TO-OPENDATADK' | translate}} + +
+
+ * + +
+
+ +
+
+ * + +
+
+
+
+ * + +
+
+
+
+ + +
+
+ +
+
+
+
{{'QUESTION.DATATARGET.RELATIONS' | translate}}
+
+
+ {{'QUESTION.ADD-RELATIONS' | translate}} + + + + + + + + + +
+
+ + {{'QUESTION.DATATARGET.SELECT-DEVICES' | translate}} + + + + + + {{device.name}} + + +
+
+
+ + {{'QUESTION.DATATARGET.SELECT-PAYLOADDECODER' | translate}} + + + {{'QUESTION.DATATARGET.NO-PAYLOAD-DECODER-SELECTED' | translate}} + + + {{payloadDecoder.name}} + + + +
+
+ +
+ +

{{'DATATARGET.DELETE' | translate}}

+
+
+
+
+
+
+ + +
+
\ No newline at end of file diff --git a/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.scss b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.spec.ts b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.spec.ts new file mode 100644 index 00000000..1ff864db --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.spec.ts @@ -0,0 +1,28 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { HttppushEditComponent } from './httppush-edit.component'; + +describe('HttppushEditComponent', () => { + let component: HttppushEditComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HttppushEditComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HttppushEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.ts b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.ts new file mode 100644 index 00000000..d192c2a6 --- /dev/null +++ b/src/app/applications/datatarget/httppush/httppush-edit/httppush-edit.component.ts @@ -0,0 +1,406 @@ +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Datatarget } from '../../datatarget.model'; +import { Observable, Subscription } from 'rxjs'; +import { Application } from '@applications/application.model'; +import { IotDevice } from '@applications/iot-devices/iot-device.model'; +import { faTimesCircle } from '@fortawesome/free-solid-svg-icons'; +import { + PayloadDeviceDatatarget, + PayloadDeviceDatatargetGetByDataTargetResponse, +} from '@payload-decoder/payload-device-data.model'; +import { DatatargetService } from '../../datatarget.service'; +import { ApplicationService } from '@applications/application.service'; +import { PayloadDecoderService } from '@payload-decoder/payload-decoder.service'; +import { PayloadDeviceDatatargetService } from '@payload-decoder/payload-device-datatarget.service'; +import { SnackService } from '@shared/services/snack.service'; +import { MatDialog } from '@angular/material/dialog'; +import { HttpErrorResponse } from '@angular/common/http'; +import { PayloadDecoderMappedResponse } from '@payload-decoder/payload-decoder.model'; +import { DeleteDialogComponent } from '@shared/components/delete-dialog/delete-dialog.component'; +import { ErrorMessageService } from '@shared/error-message.service'; +import { OpendatadkDialogService } from '@shared/components/opendatadk-dialog/opendatadk-dialog.service'; +import { OpendatadkService } from '@shared/services/opendatadk.service'; +import { ScrollToTopService } from '@shared/services/scroll-to-top.service'; +import { OpenDataDkDataset } from '../../opendatadk/opendatadk-dataset.model'; +import { DataTargetType } from '@shared/enums/datatarget-type'; +import { DatatargetEdit } from '@applications/datatarget/datatarget-edit/datatarget-edit'; + +@Component({ + selector: 'app-httppush-edit', + templateUrl: './httppush-edit.component.html', + styleUrls: ['./httppush-edit.component.scss'], +}) +export class HttppushEditComponent + implements DatatargetEdit, OnInit, OnDestroy { + public multiPage = false; + public title = ''; + public sectionTitle = ''; + public backButtonTitle = ''; + @Input() submitButton: string; + public datatarget: Datatarget = new Datatarget(); + faTimesCircle = faTimesCircle; + public datatargetSubscription: Subscription; + public relationSubscription: Subscription; + public applicationSubscription: Subscription; + public payloadDecoderSubscription: Subscription; + public errorMessages: any; + public errorFields: string[]; + public formFailedSubmit = false; + public datatargetid: number; + private applicationId: number; + private applicationName: string; + public application: Application; + public devices: IotDevice[]; + public payloadDecoders = []; + private counter: number; + private dataSetExcists = false; + private isMailDialogAlreadyShown = false; + + payloadDeviceDatatarget: PayloadDeviceDatatarget[]; + newDynamic: any = {}; + + constructor( + public translate: TranslateService, + private route: ActivatedRoute, + private router: Router, + private datatargetService: DatatargetService, + private applicationService: ApplicationService, + private payloadDecoderService: PayloadDecoderService, + private payloadDeviceDataTargetService: PayloadDeviceDatatargetService, + private saveSnackService: SnackService, + private dialog: MatDialog, + private errorMessageService: ErrorMessageService, + private opendatadkService: OpendatadkService, + private opendatadkDialogService: OpendatadkDialogService, + private scrollToTopService: ScrollToTopService + ) { + translate.use('da'); + } + + ngOnInit() { + this.translate + .get([ + 'FORM.CREATE-NEW-DATATARGET', + 'FORM.EDIT-DATATARGET', + 'DATATARGET.SAVE', + 'NAV.DATATARGET', + ]) + .subscribe((translations) => { + const datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); + if (datatargetid !== 0) { + this.title = translations['FORM.EDIT-DATATARGET']; + } else { + this.title = translations['FORM.CREATE-NEW-DATATARGET']; + } + this.submitButton = translations['DATATARGET.SAVE']; + this.backButtonTitle = translations['NAV.DATATARGET']; + }); + + this.datatargetid = +this.route.snapshot.paramMap.get('datatargetId'); + this.applicationId = +this.route.snapshot.paramMap.get('id'); + this.applicationName = this.route.snapshot.paramMap.get('name'); + if (this.datatargetid !== 0) { + this.getDatatarget(this.datatargetid); + this.getPayloadDeviceDatatarget(this.datatargetid); + } + if (this.applicationId !== 0) { + this.getDevices(); + } + this.getPayloadDecoders(); + this.setDataSetExcists(); + } + + addRow() { + if (!this.payloadDeviceDatatarget) { + this.payloadDeviceDatatarget = []; + } + this.payloadDeviceDatatarget.push({ + id: null, + iotDeviceIds: [], + payloadDecoderId: null, + dataTargetId: this.datatargetid, + }); + } + + private deleteRow(index) { + if (this.payloadDeviceDatatarget.length === 0) { + } else if (this.payloadDeviceDatatarget[index]?.id === null) { + this.payloadDeviceDatatarget.splice(index, 1); + } else { + this.payloadDeviceDataTargetService + .delete(this.payloadDeviceDatatarget[index].id) + .subscribe((response) => { + this.payloadDeviceDatatarget.splice(index, 1); + }); + } + } + + openDeleteDialog(index) { + const dialog = this.dialog.open(DeleteDialogComponent, { + data: { + showAccept: true, + showCancel: true, + message: 'Er du sikker på at du vil slette?', + }, + }); + + dialog.afterClosed().subscribe((result) => { + if (result === true) { + this.deleteRow(index); + } + }); + } + + onSubmit(): void { + this.counter = 0; + if (this.datatargetid) { + this.updateDatatarget(); + this.addPayloadDeviceDatatarget(); + } else { + this.createDatatarget(); + } + } + + public compare(o1: any, o2: any): boolean { + return o1 === o2; + } + + updateDatatarget() { + this.resetErrors(); + this.counter = + 1 + + (this.payloadDeviceDatatarget?.length + ? this.payloadDeviceDatatarget?.length + : 0); + this.datatargetService.update(this.datatarget).subscribe( + (response: Datatarget) => { + this.datatarget = response; + if (this.datatarget.openDataDkDataset != null) { + this.datatarget.openDataDkDataset.acceptTerms = true; + } + this.shouldShowMailDialog().subscribe((response) => { + this.countToRedirect(); + }); + }, + (error: HttpErrorResponse) => { + this.checkDataTargetModelOpendatadkdatasaet(); + this.handleError(error); + this.formFailedSubmit = true; + } + ); + } + + addPayloadDeviceDatatarget() { + this.payloadDeviceDatatarget.map((pdd) => { + if (pdd.payloadDecoderId === 0) { + pdd.payloadDecoderId = null; + } + }); + this.payloadDeviceDatatarget.forEach((relation) => { + if (relation.id) { + this.payloadDeviceDataTargetService.put(relation).subscribe( + (response) => { + this.countToRedirect(); + }, + (error) => { + this.handleError(error); + } + ); + } else { + this.payloadDeviceDataTargetService.post(relation).subscribe( + (res: any) => { + this.countToRedirect(); + }, + (error) => { + this.handleError(error); + } + ); + } + }); + } + + countToRedirect() { + this.counter -= 1; + if (this.counter <= 0 && !this.formFailedSubmit) { + this.showSavedSnack(); + this.routeToDatatargets(); + } + } + + getPayloadDeviceDatatarget(id: number) { + this.relationSubscription = this.payloadDeviceDataTargetService + .getByDataTarget(id) + .subscribe((response: PayloadDeviceDatatargetGetByDataTargetResponse) => { + this.mapToDatatargetDevicePayload(response); + }); + } + + createDatatarget() { + this.resetErrors(); + this.datatarget.applicationId = this.applicationId; + this.datatargetService.create(this.datatarget).subscribe( + (response: Datatarget) => { + this.datatargetid = response.id; + this.datatarget = response; + if (this.datatarget.openDataDkDataset != null) { + this.datatarget.openDataDkDataset.acceptTerms = true; + } + this.showSavedSnack(); + this.routeToDatatargets(); + }, + (error: HttpErrorResponse) => { + this.checkDataTargetModelOpendatadkdatasaet(); + this.handleError(error); + this.formFailedSubmit = true; + } + ); + } + + private resetErrors() { + this.errorFields = []; + this.errorMessages = undefined; + this.formFailedSubmit = false; + } + + checkDataTargetModelOpendatadkdatasaet() { + if (!this.datatarget.openDataDkDataset) { + this.datatarget.openDataDkDataset = new OpenDataDkDataset(); + } + } + + getDevices(): void { + this.applicationSubscription = this.applicationService + .getApplication(this.applicationId) + .subscribe((application: Application) => { + this.devices = application.iotDevices; + }); + } + + public selectAllDevices(index: number) { + this.payloadDeviceDatatarget[index].iotDeviceIds = this.devices.map( + (device) => device.id + ); + } + + public deSelectAllDevices(index: number) { + this.payloadDeviceDatatarget[index].iotDeviceIds = []; + } + + getPayloadDecoders() { + this.payloadDecoderSubscription = this.payloadDecoderService + .getMultiple(1000, 0, 'id', 'ASC') + .subscribe((response: PayloadDecoderMappedResponse) => { + this.payloadDecoders = response.data; + }); + } + + handleError(error: HttpErrorResponse) { + const errors = this.errorMessageService.handleErrorMessageWithFields(error); + this.errorFields = errors.errorFields; + this.errorMessages = errors.errorMessages; + this.scrollToTopService.scrollToTop(); + } + + routeToDatatargets(): void { + this.router.navigate(['applications', this.applicationId.toString()]); + } + + onCoordinateKey(event: any) { + if (event.target.value.length > event.target.maxLength) { + event.target.value = event.target.value.slice(0, event.target.maxLength); + } + } + + getDatatarget(id: number) { + this.datatargetSubscription = this.datatargetService + .get(id) + .subscribe((response: Datatarget) => { + this.datatarget = response; + }); + } + + showSavedSnack() { + this.saveSnackService.showSavedSnack(); + } + + private setDataSetExcists() { + this.opendatadkService.get().subscribe((response) => { + this.dataSetExcists = response.dataset.length === 0 ? false : true; + }); + } + + private shouldShowMailDialog(): Observable { + return new Observable((observer) => { + if ( + !this.dataSetExcists && + this.datatarget.setToOpendataDk && + !this.isMailDialogAlreadyShown + ) { + this.isMailDialogAlreadyShown = true; + this.opendatadkDialogService.showDialog().subscribe((response) => { + if (response) { + this.showMailClient(); + } + observer.next(response); + }); + } else { + observer.next(true); + } + }); + } + + private showMailClient() { + if (!this.datatarget.openDataDkDataset.url) { + this.datatarget.openDataDkDataset.url = this.datatargetService.getOpendataSharingApiUrl(); + } + window.location.href = + 'mailto:FG2V@kk.dk?subject=Oprettelse%20af%20datas%C3%A6t%20i%20OpenDataDK&body=K%C3%A6re%20Frans%0D%0A%0D%0AHermed%20fremsendes%20linket%20til%20DCAT%20kataloget%20%2C%20du%20bedes%20registrere%20p%C3%A5%20Open%20Data%20DK%20platformen.%0D%0A%0D%0ALink%3A ' + + this.datatarget.openDataDkDataset.url; + } + + disableSaveButton(): boolean { + let disable = true; + if (!this.datatarget.setToOpendataDk) { + disable = false; + } else if (this.datatarget.openDataDkDataset?.acceptTerms) { + disable = false; + } else { + disable = true; + } + return disable; + } + + ngOnDestroy(): void { + if (this.relationSubscription) { + this.relationSubscription.unsubscribe(); + } + if (this.applicationSubscription) { + this.applicationSubscription.unsubscribe(); + } + if (this.datatargetSubscription) { + this.datatargetSubscription.unsubscribe(); + } + if (this.payloadDecoderSubscription) { + this.payloadDecoderSubscription.unsubscribe(); + } + } + + private mapToDatatargetDevicePayload( + dto: PayloadDeviceDatatargetGetByDataTargetResponse + ) { + this.payloadDeviceDatatarget = []; + dto.data.forEach((element) => { + this.payloadDeviceDatatarget.push({ + id: element.id, + iotDeviceIds: element.iotDevices.map((x) => x.id), + payloadDecoderId: + element.payloadDecoder?.id === undefined + ? 0 + : element.payloadDecoder?.id, + dataTargetId: element.dataTarget.id, + }); + }); + } +} diff --git a/src/app/shared/components/snack-bar/snack-bar.component.html b/src/app/shared/components/snack-bar/snack-bar.component.html new file mode 100644 index 00000000..aa504cd5 --- /dev/null +++ b/src/app/shared/components/snack-bar/snack-bar.component.html @@ -0,0 +1,9 @@ +
+ {{data.title}} +
+
+ {{data.message}} +
+
+ +
\ No newline at end of file diff --git a/src/app/shared/components/snack-bar/snack-bar.component.scss b/src/app/shared/components/snack-bar/snack-bar.component.scss new file mode 100644 index 00000000..29916f9b --- /dev/null +++ b/src/app/shared/components/snack-bar/snack-bar.component.scss @@ -0,0 +1,21 @@ +.snackbar-title-style { + font-weight: 500; + letter-spacing: 0.5px; +} +.snackbar-content-style { + letter-spacing: 0.5px; + border: none; + padding: 0 10px; + margin-top: 10px; + margin-bottom: 10px; +} +.snackbar-button-div-style { + text-align: center; + + .button-style { + letter-spacing: 0.5px; + border: 1px solid #000; + border-radius: 5px; + padding: 5px 15px; + } +} diff --git a/src/app/shared/components/snack-bar/snack-bar.component.ts b/src/app/shared/components/snack-bar/snack-bar.component.ts new file mode 100644 index 00000000..2bf27e0b --- /dev/null +++ b/src/app/shared/components/snack-bar/snack-bar.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit, Inject } from "@angular/core"; +import { MAT_SNACK_BAR_DATA, MatSnackBarRef } from "@angular/material/snack-bar"; + + +@Component({ + selector: 'app-snackbar', + templateUrl: './snack-bar.component.html', + styleUrls: ['./snack-bar.component.scss'] +}) +export class SnackBarComponent implements OnInit { + + constructor( + @Inject(MAT_SNACK_BAR_DATA) public data: any, + public snackBarRef: MatSnackBarRef) {} + + ngOnInit(): void { + } +} diff --git a/src/app/shared/enums/datatarget-type.ts b/src/app/shared/enums/datatarget-type.ts index 5dea3827..795c317d 100644 --- a/src/app/shared/enums/datatarget-type.ts +++ b/src/app/shared/enums/datatarget-type.ts @@ -1,4 +1,5 @@ export enum DataTargetType { HTTPPUSH = 'HTTP_PUSH', - OPENDATADK = 'OPENDATADK' + OPENDATADK = 'OPENDATADK', + FIWARE = 'FIWARE' } diff --git a/src/app/shared/services/snack.service.ts b/src/app/shared/services/snack.service.ts index d5831815..f759968f 100644 --- a/src/app/shared/services/snack.service.ts +++ b/src/app/shared/services/snack.service.ts @@ -1,6 +1,8 @@ import { Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { TranslateService } from '@ngx-translate/core'; +import { SnackBarComponent } from '@shared/components/snack-bar/snack-bar.component'; + @Injectable({ providedIn: 'root', @@ -41,4 +43,18 @@ export class SnackService { duration: 10000, }); } + + public showSnackBar(title: string, displayMessage: string, buttonText: string) { + this.snackBar.openFromComponent(SnackBarComponent, { + data: { + title: title, + message: displayMessage, + buttonText: buttonText + }, + duration: 5000, + horizontalPosition: 'right', + verticalPosition: 'top', + panelClass: 'snackBar' + }); + } } diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index bf9a16d8..af3bc49c 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -151,6 +151,8 @@ "DATATARGET": { "DETAILS": "Detaljer", "SAVE": "Gem datatarget", + "CREATE": "Opret", + "READMORE": "Læs mere", "DESCRIPTION": "Beskrivelse", "URL": "URL", "TIMEOUT": "Timeout", @@ -163,11 +165,17 @@ "NO-PAYLOADDECODER": "Ingen payload decoder", "IOTDEVICE": "IoT enhed(er)", "NO-RELATIONS": "ingen tilknyttede relationer", + "TENANT": "Tenant", + "NO-TENANT": "Ingen Tenant angivet", + "CONTEXT": "Context", + "NO-CONTEXT": "Ingen Context angivet", "AUTHORIZATIONHEADER": "Authorization header", "NO-AUTHORIZATIONHEADER": "Ingen Authorization header angivet", "ADD-TO-OPENDATADK": "Send data til OpenDataDK", - "OPENDATA-DK": "OpenDataDK", - "NO-OPENDATA-DK": "Der er ikke oprettet nogen datadeling med Open Data DK endnu" + "OPENDATA-DK": "OpenDataDK", + "NO-OPENDATA-DK": "Der er ikke oprettet nogen datadeling med Open Data DK endnu", + "HTTP_PUSH": "HTTP Push", + "FIWARE": "FIWARE" }, "MULTICAST": { "SAVE": "Gem multicast", @@ -945,5 +953,19 @@ "DELETE": "Slet" } }, - "NoUsersAdded": "Ingen brugergrupper er tilføjet" + "NoUsersAdded": "Ingen brugergrupper er tilføjet", + + "FIWARE": { + "QUESTION": { + "GIVE-DATATARGET-CONTEXTBROKER-URL": "Angiv datatargets 'context broker' URL", + "GIVE-DATATARGET-CONTEXTBROKER-URL-PLACEHOLDER": "https://contextBroker.dk/", + "GIVE-DATATARGET-TENANT": "Angiv 'tenant'", + "GIVE-DATATARGET-TENANT-INFO": "hvis tom, vil default tenant blive brugt", + "GIVE-DATATARGET-TENANT-PLACEHOLDER": "Tenant's navn", + "GIVE-DATATARGET-CONTEXT": "Angiv 'context'", + "GIVE-DATATARGET-CONTEXT-INFO": "hvis tom, skal den angives i 'payload'", + "GIVE-DATATARGET-CONTEXT-PLACEHOLDER": "https://os2iot/context-file.json" + } + + } } diff --git a/src/assets/images/logo_FIWARE.png b/src/assets/images/logo_FIWARE.png new file mode 100644 index 0000000000000000000000000000000000000000..beb19973fe397cb52684f9fd57ca5a18582e6810 GIT binary patch literal 1338 zcmV-A1;zS_P)-}H%G`RLhz+}a9n$_{_ zizczb@&;Lu)tHw=%Z`G6CLB&fRBUPgQrRf%5x?fl|4XOY^Aae9U`-+tj{6~+ctpH1?rA;E4W zJDw*{7Gh&@bLtopmM07l;LR8SY5J{IhL<#5QxKbd=U7voR z!D(91y7<(ebuIClohD;)&{J9?QniLv#q>;-h}%(W?>6)cdyIvCVekp#)iS6f2wDLQ z;dw9R$*x&R`@DjT;YZsTG(-a)2YyH|1s&j752L;6#iO0^dyt?j$`-_!(qG^h5l;u{ zPxVM2kYnaT+mvLq3bYXhKRStzJM&+!yvI+vM7AKwBz>B`++t-ja$TKua;By#TAs_wN3m{J2(@R!*Na(WNC8@gp zCh+n9>ACg@40#c|RzPsdb#bPWKEj`8>wvVXfC9i6oiF{dLG_Yf^cxq)5_ z;=AXq8u*><9irb05d8&uy4Hh5LA6Bn6R#bfBZ6-$K=^$LRLj&a2Ibk`CIh0w5R~oqpf^%Ktd}{oVvb%@9VA5%Y`f0E zp1kgb*1yu@Ki8YFt39WfRWII^(BOq149c-|xL11e3B9@AE%mBNdb-w?Xuwoz2n&CG z)+6o^^I5%N3r}PGdNhyY7d9`0U}k@ui%k|~=o6LGQ^%N`hkrhe11g4Q*s2648UJhW z#;U?v6LTU2TH#u&A+3E6i%=81m6R&BY8on{Wv)Db9(ivUQ)bY z=~*Q4JAN)cjY__%=hu3CO>bX!4_peJ1mpm z#U1ytvd5m}vR=Xy3q5>@7xYkF=IjPp;r9*_u%WxIXZ)Th(8HjSxu7>ymx`(Ea6?&q zi6soiRXrq&7i*%m+r3#EjpYD?H}z0me81;%Vh=EV4R6g$vQyYy)x-47;F{OJ&f@BF zx~1n~hq}T-uGhGz7ya7qnjYF}K3~ #EIgCrdRx=~R@osPKP@=u`Pt@?g zQFk23LA6ZZKm{|- w6X*Vg-#W^vp)Yr=C+8$7goT$J+xzC^eOpen Data DK diff --git a/src/styles.scss b/src/styles.scss index 91b964e8..4455b654 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -53,3 +53,38 @@ font-family: 'roboto-bold'; src: url('./assets/fonts/Roboto-Bold.ttf') format('truetype'); } + +body { + // For Google Chrome + &::-webkit-scrollbar { + width: 5px; + height: 5px; + } + + &::-webkit-scrollbar-thumb { + background: rgba($color: #000000, $alpha: 0.5); + } + + &::-webkit-scrollbar-track { + background: rgba($color: #000000, $alpha: 0.4); + } + + // For FireFox + scrollbar-width: 5px; + + // For Internet Explorer + & { + scrollbar-face-color: rgba($color: #000000, $alpha: 0.5); + scrollbar-track-color: rgba($color: #000000, $alpha: 0.4); + } +} + +.snackBar.mat-snack-bar-container{ + background: #f4f4f4; + color: #000; +} + +.mat-tooltip { + font-size: 1rem !important; +} + From 7ce8ede3774257939772124fc38ed8575f5bfade Mon Sep 17 00:00:00 2001 From: AramAlsabti <92869496+AramAlsabti@users.noreply.github.com> Date: Tue, 22 Feb 2022 11:52:59 +0100 Subject: [PATCH 37/37] Optimize bulk import and the load on chirpstack (#65) * Batch bulk import create. Update missing * Reworked update many and cleanup * Comment on bulk import * Added device model error codes Co-authored-by: nlg --- .../bulk-import/bulk-import.component.ts | 230 ++++++++++++++---- .../bulk-import/bulk-import.model.ts | 2 +- .../iot-devices/iot-device.model.ts | 105 ++++---- .../iot-devices/iot-device.service.ts | 18 +- src/app/shared/error-message.service.ts | 13 +- src/app/shared/helpers/array.helper.ts | 11 + .../shared/services/bulk-import.service.ts | 12 + src/assets/i18n/da.json | 7 +- 8 files changed, 284 insertions(+), 114 deletions(-) create mode 100644 src/app/shared/helpers/array.helper.ts create mode 100644 src/app/shared/services/bulk-import.service.ts diff --git a/src/app/applications/bulk-import/bulk-import.component.ts b/src/app/applications/bulk-import/bulk-import.component.ts index e28d2506..5d7a6293 100644 --- a/src/app/applications/bulk-import/bulk-import.component.ts +++ b/src/app/applications/bulk-import/bulk-import.component.ts @@ -2,24 +2,36 @@ import { HttpErrorResponse } from '@angular/common/http'; import { Component, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; +import { + IotDeviceImportRequest, + IotDevicesImportResponse, +} from '@applications/iot-devices/iot-device.model'; import { IoTDeviceService } from '@applications/iot-devices/iot-device.service'; import { faDownload, faTrash } from '@fortawesome/free-solid-svg-icons'; import { TranslateService } from '@ngx-translate/core'; import { ErrorMessageService } from '@shared/error-message.service'; +import { splitList } from '@shared/helpers/array.helper'; import { Download } from '@shared/helpers/download.helper'; +import { BulkImportService } from '@shared/services/bulk-import.service'; import { DownloadService } from '@shared/services/download.service'; import { Papa } from 'ngx-papaparse'; -import { Observable } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; +import { takeWhile } from 'rxjs/operators'; import { BulkImport } from './bulk-import.model'; import { BulkMapping } from './bulkMapping'; @Component({ selector: 'app-bulk-import', templateUrl: './bulk-import.component.html', - styleUrls: ['./bulk-import.component.scss'] + styleUrls: ['./bulk-import.component.scss'], }) export class BulkImportComponent implements OnInit { - displayedColumns: string[] = ['name', 'type', 'importStatus', 'errorMessages']; + displayedColumns: string[] = [ + 'name', + 'type', + 'importStatus', + 'errorMessages', + ]; isLoading = false; bulkImport: BulkImport[]; bulkImportResult: BulkImport[]; @@ -28,10 +40,19 @@ export class BulkImportComponent implements OnInit { faTrash = faTrash; faDownload = faDownload; samples = [ - { name: 'generic-http-sample.csv', url: '../../../assets/docs/iotdevice_generichttp.csv' }, - { name: 'lorawan-otaa-sample.csv', url: '../../../assets/docs/iotdevice_lorawan_otaa.csv' }, - { name: 'lorawan-abp-sample.csv', url: '../../../assets/docs/iotdevice_lorawan_abp.csv' }, - ] + { + name: 'generic-http-sample.csv', + url: '../../../assets/docs/iotdevice_generichttp.csv', + }, + { + name: 'lorawan-otaa-sample.csv', + url: '../../../assets/docs/iotdevice_lorawan_otaa.csv', + }, + { + name: 'lorawan-abp-sample.csv', + url: '../../../assets/docs/iotdevice_lorawan_abp.csv', + }, + ]; download$: Observable; private bulkMapper = new BulkMapping(); public backButtonTitle: string; @@ -44,25 +65,23 @@ export class BulkImportComponent implements OnInit { private titleService: Title, private translate: TranslateService, private downloads: DownloadService, - private errorMessageService: ErrorMessageService + private errorMessageService: ErrorMessageService, + private bulkImportService: BulkImportService ) { this.translate.use('da'); - } + } ngOnInit(): void { - this.translate.get(['TITLE.BULKIMPORT']) - .subscribe(translations => { - this.titleService.setTitle(translations['TITLE.BULKIMPORT']); - }); + this.translate.get(['TITLE.BULKIMPORT']).subscribe((translations) => { + this.titleService.setTitle(translations['TITLE.BULKIMPORT']); + }); this.applicationId = +this.route.snapshot.paramMap.get('id'); - } - download({ name, url }: { name: string, url: string }) { + download({ name, url }: { name: string; url: string }) { this.download$ = this.downloads.download(url, name); } - deleteAttachment(index) { this.files.splice(index, 1); } @@ -76,13 +95,14 @@ export class BulkImportComponent implements OnInit { } this.bulkImport = []; this.bulkImportResult = []; - for (let index = 0; index < evt.length; index++) { - const element = evt[index]; + + for (const element of evt) { this.files.push(element.name); } + // handle csv data this.isLoading = true; - const files = evt; // File List object + const files = evt; // File List object const file = files[0]; const reader = new FileReader(); reader.readAsText(file); @@ -91,7 +111,7 @@ export class BulkImportComponent implements OnInit { this.papa.parse(csv, { skipEmptyLines: true, header: true, - complete: results => { + complete: (results) => { this.mapData(results.data); // this step ensures material can read from the array - should be fixed. this.bulkImportResult = this.bulkImport; @@ -100,9 +120,8 @@ export class BulkImportComponent implements OnInit { } else { return this.bulkImport; } - } - } - ); + }, + }); this.isLoading = false; }; } @@ -118,43 +137,162 @@ export class BulkImportComponent implements OnInit { private mapData(data: any[]) { data.forEach((device) => { - const mappedDevice = this.bulkMapper.dataMapper(device, this.applicationId); + const mappedDevice = this.bulkMapper.dataMapper( + device, + this.applicationId + ); if (mappedDevice) { this.bulkImport.push(new BulkImport(mappedDevice)); } else { - this.translate.get(['ERROR.SEMANTIC']) - .subscribe(translations => { - this.bulkImport.push(new BulkImport(null, [translations['ERROR.SEMANTIC']])); - }); + this.translate.get(['ERROR.SEMANTIC']).subscribe((translations) => { + this.bulkImport.push( + new BulkImport(null, [translations['ERROR.SEMANTIC']]) + ); + }); } }); } addIoTDevice() { - this.bulkImportResult.forEach((requestItem) => { - if (requestItem.device?.id) { - this.iotDeviceService.updateIoTDevice(requestItem.device, requestItem.device.id).subscribe( + // Subscribe to subject in service, Emit the index of next item in the array to be previous + // The emit will activate the subscription which should call the updateIoTDevice + const { newDevices, updatedDevices } = this.splitDevices(); + + this.postBulkImportPayload( + newDevices, + this.bulkImportService.nextCreateIotDeviceBatchIndex$, + this.iotDeviceService.createIoTDevices.bind(this.iotDeviceService) + ); + this.postBulkImportPayload( + updatedDevices, + this.bulkImportService.nextUpdateDeviceBatchIndex$, + this.iotDeviceService.updateIoTDevices.bind(this.iotDeviceService) + ); + } + + private postBulkImportPayload( + bulkDevices: BulkImport[][], + batchIndex$: Subject, + importDevices: ( + payload: IotDeviceImportRequest + ) => Observable + ): void { + if (!bulkDevices.length) { + return; + } + + let batchIndex = 0; + + // takeWhile() will unsubscribe once the condition is false + batchIndex$.pipe(takeWhile(() => batchIndex in bulkDevices)).subscribe( + () => { + const requestItems = bulkDevices[batchIndex]; + const devices: IotDeviceImportRequest = { + data: requestItems.map((bulkResult) => bulkResult.device), + }; + importDevices(devices).subscribe( (response) => { - console.log(response); - requestItem.importStatus = 'success'; + this.onSuccessfulImport(response, requestItems); + ++batchIndex; + batchIndex$.next(); }, (error: HttpErrorResponse) => { - requestItem.errorMessages = this.errorMessageService.handleErrorMessageWithFields(error).errorMessages; - requestItem.importStatus = 'Failed'; - } - ); - } else if (requestItem.device) { - this.iotDeviceService.createIoTDevice(requestItem.device).subscribe( - (res: any) => { - console.log(res); - requestItem.importStatus = 'success'; - }, - (error) => { - requestItem.errorMessages = this.errorMessageService.handleErrorMessage(error); - requestItem.importStatus = 'Failed'; + requestItems.forEach((item) => { + item.errorMessages = this.errorMessageService.handleErrorMessageWithFields( + error + ).errorMessages; + item.importStatus = 'Failed'; + }); + // Continue processing the next batches + ++batchIndex; + batchIndex$.next(); } ); + }, + (_error: HttpErrorResponse) => { + // Should not happen + }, + () => { + // Process any devices whose status hasn't been set and mark them as errors. + this.onCompleteImport(bulkDevices); + } + ); + + // Trigger our listener + batchIndex$.next(); + } + + private onSuccessfulImport( + response: IotDevicesImportResponse[], + requestItems: BulkImport[] + ) { + response.forEach((responseItem) => { + const match = requestItems.find( + ({ device }) => + device.name === responseItem.idMetadata.name && + device.applicationId === responseItem.idMetadata.applicationId + ); + if (!match) { + return; + } + + if (responseItem.error && match) { + match.errorMessages = this.errorMessageService.handleErrorMessageWithFields( + { error: responseItem.error } + ).errorMessages; + match.importStatus = 'Failed'; + } else { + match.errorMessages = []; + match.importStatus = 'Success'; } }); } + + private onCompleteImport(devicesBulk: BulkImport[][]) { + for (const bulk of devicesBulk) { + for (const device of bulk) { + if (!device.importStatus) { + device.importStatus = 'Failed'; + device.errorMessages = this.errorMessageService.handleErrorMessageWithFields( + { + error: { + message: 'MESSAGE.FAILED-TO-CREATE-OR-UPDATE-IOT-DEVICE', + }, + } + ).errorMessages; + } + } + } + } + + private splitDevices(): { + newDevices: BulkImport[][]; + updatedDevices: BulkImport[][]; + } { + if (!this.bulkImportResult) { + return { newDevices: [], updatedDevices: [] }; + } + + const { updatedDevices, newDevices } = this.bulkImportResult.reduce( + ( + res: { + newDevices: BulkImport[]; + updatedDevices: BulkImport[]; + }, + curr + ) => { + if (curr.device.id) { + res.updatedDevices.push(curr); + } else if (curr.device) { + res.newDevices.push(curr); + } + return res; + }, + { updatedDevices: [], newDevices: [] } + ); + return { + newDevices: splitList(newDevices), + updatedDevices: splitList(updatedDevices), + }; + } } diff --git a/src/app/applications/bulk-import/bulk-import.model.ts b/src/app/applications/bulk-import/bulk-import.model.ts index 05d07be5..904569f7 100644 --- a/src/app/applications/bulk-import/bulk-import.model.ts +++ b/src/app/applications/bulk-import/bulk-import.model.ts @@ -2,7 +2,7 @@ import { IotDevice } from '@applications/iot-devices/iot-device.model'; export class BulkImport { public device: IotDevice; - public errorMessages = []; + public errorMessages: unknown[] = []; public importStatus = ''; constructor(device: IotDevice, errorMessages = [], importStatus = '') { this.device = device; diff --git a/src/app/applications/iot-devices/iot-device.model.ts b/src/app/applications/iot-devices/iot-device.model.ts index 114e3d85..039647fd 100644 --- a/src/app/applications/iot-devices/iot-device.model.ts +++ b/src/app/applications/iot-devices/iot-device.model.ts @@ -8,70 +8,63 @@ import { ReceivedMessageMetadata } from '@shared/models/received-message-metadat import { LatestReceivedMessage } from './latestReceivedMessage.model'; export class IotDevice { - name: string; - application?: Application; - location: JsonLocation; - commentOnLocation: string; - comment: string; - type: DeviceType = DeviceType.GENERICHTTP; - receivedMessagesMetadata: ReceivedMessageMetadata[]; - metadata?: JSON; - apiKey?: string; - id: number; - createdAt: Date; - updatedAt: Date; - createdBy: number; - updatedBy: number; - createdByName: string; - updatedByName: string; - applicationId: number; - longitude = 0; - latitude = 0; - deviceModelId?: number; - latestReceivedMessage: LatestReceivedMessage; - lorawanSettings = new LorawanSettings(); - sigfoxSettings = new SigfoxSettings(); - deviceModel?: DeviceModel; + name: string; + application?: Application; + location: JsonLocation; + commentOnLocation: string; + comment: string; + type: DeviceType = DeviceType.GENERICHTTP; + receivedMessagesMetadata: ReceivedMessageMetadata[]; + metadata?: JSON; + apiKey?: string; + id: number; + createdAt: Date; + updatedAt: Date; + createdBy: number; + updatedBy: number; + createdByName: string; + updatedByName: string; + applicationId: number; + longitude = 0; + latitude = 0; + deviceModelId?: number; + latestReceivedMessage: LatestReceivedMessage; + lorawanSettings = new LorawanSettings(); + sigfoxSettings = new SigfoxSettings(); + deviceModel?: DeviceModel; } -export class IotDeviceResponse { - name: string; - application?: Application; - location: JsonLocation; - commentOnLocation: string; - comment: string; - type: DeviceType = DeviceType.GENERICHTTP; - receivedMessagesMetadata: ReceivedMessageMetadata[]; - metadata?: JSON; - apiKey?: string; - id: number; - createdAt: Date; - updatedAt: Date; - applicationId: number; - longitude = 0; - latitude = 0; - deviceModelId?: DeviceModel; - latestReceivedMessage: LatestReceivedMessage; - lorawanSettings = new LorawanSettings(); - sigfoxSettings = new SigfoxSettings(); -} +export class IotDeviceResponse extends IotDevice {} export interface IotDevicesResponse { - data: IotDevice[]; - ok?: boolean; - count?: number; + data: IotDevice[]; + ok?: boolean; + count?: number; } -export class IoTDeviceMinimal { - id: number; +export interface IotDeviceImportRequest { + data: IotDevice[]; +} + +export interface IotDevicesImportResponse { + data: IotDevice; + idMetadata: { name: string; - canRead: boolean; - organizationId: number; applicationId: number; - lastActiveTime: Date; + }; + error?: Omit; +} + +export class IoTDeviceMinimal { + id: number; + name: string; + canRead: boolean; + organizationId: number; + applicationId: number; + lastActiveTime: Date; } export class IoTDevicesMinimalResponse { - data: IoTDeviceMinimal[]; - count: number; -} \ No newline at end of file + data: IoTDeviceMinimal[]; + count: number; +} diff --git a/src/app/applications/iot-devices/iot-device.service.ts b/src/app/applications/iot-devices/iot-device.service.ts index bedbf529..ff2d5fb5 100644 --- a/src/app/applications/iot-devices/iot-device.service.ts +++ b/src/app/applications/iot-devices/iot-device.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { IotDevice, IoTDevicesMinimalResponse, IotDevicesResponse } from './iot-device.model'; +import { IotDevice, IoTDevicesMinimalResponse, IotDevicesImportResponse, IotDeviceImportRequest } from './iot-device.model'; import { RestService } from 'src/app/shared/services/rest.service'; import { map } from 'rxjs/operators'; import { UserMinimalService } from '@app/admin/users/user-minimal.service'; @@ -14,17 +14,25 @@ export class IoTDeviceService { constructor( private restService: RestService, - private userMinimalService: UserMinimalService + private userMinimalService: UserMinimalService ) { } - createIoTDevice(body: IotDevice): Observable { + createIoTDevice(body: IotDevice): Observable { return this.restService.post(this.BASEURL, body); } - updateIoTDevice(body: IotDevice, id: number): Observable { + updateIoTDevice(body: IotDevice, id: number): Observable { return this.restService.put(this.BASEURL, body, id, { observe: 'response' }); } + createIoTDevices(body: IotDeviceImportRequest): Observable { + return this.restService.post(`${this.BASEURL}/createMany`, body); + } + + updateIoTDevices(body: IotDeviceImportRequest): Observable { + return this.restService.post(`${this.BASEURL}/updateMany`, body); + } + getIoTDevice(id: number): Observable { return this.restService.get(this.BASEURL, {}, id).pipe( map( @@ -60,7 +68,7 @@ export class IoTDeviceService { } getIoTDevicesUsingPayloadDecoderMinimal(payloadDecoderId: number, limit: number, offset: number): Observable { - return this.restService.get(`iot-device/minimalByPayloadDecoder`, {limit: limit, offset: offset}, payloadDecoderId) + return this.restService.get(`${this.BASEURL}/minimalByPayloadDecoder`, {limit, offset}, payloadDecoderId); } deleteIoTDevice(id: number) { diff --git a/src/app/shared/error-message.service.ts b/src/app/shared/error-message.service.ts index 7446ab11..0e76e396 100644 --- a/src/app/shared/error-message.service.ts +++ b/src/app/shared/error-message.service.ts @@ -13,8 +13,7 @@ export class ErrorMessageService { errorMessages.push(err.error.message); } else if (err.error.chirpstackError) { errorMessages.push(err.error.chirpstackError.message); - } - else { + } else { err.error.message.forEach( (err) => { if (err.property === 'lorawanSettings') { err.children.forEach( (element) => { @@ -32,9 +31,13 @@ export class ErrorMessageService { return errorMessages; } - public handleErrorMessageWithFields(error: HttpErrorResponse): ErrorMessage { + public handleErrorMessageWithFields(error: HttpErrorResponse | Pick): ErrorMessage { const errors: ErrorMessage = {errorFields: [], errorMessages: []}; - if (typeof error.error.message === 'string') { + if (typeof error.error === 'string') { + errors.errorMessages.push(error.error); + } else if (typeof error.error?.error === 'string') { + errors.errorMessages.push(error.error.error); + } else if (typeof error.error?.message === 'string') { errors.errorMessages.push(error.error.message); } else { error.error.message.forEach((err) => { @@ -65,7 +68,7 @@ export class ErrorMessageService { } else if (err.message) { errors.errorFields.push(err.field); errors.errorMessages.push(err.message); - } else { + } else if (err.constraints) { errors.errorFields.push(err.property); errors.errorMessages = errors.errorMessages.concat( Object.values(err.constraints) diff --git a/src/app/shared/helpers/array.helper.ts b/src/app/shared/helpers/array.helper.ts new file mode 100644 index 00000000..779ffa89 --- /dev/null +++ b/src/app/shared/helpers/array.helper.ts @@ -0,0 +1,11 @@ +export const splitList = ( + data: T[], + batchSize = 50 +): typeof data[] => { + const dataBatches: typeof data[] = []; + for (let i = 0; i < data.length; i += batchSize) { + dataBatches.push(data.slice(i, i + batchSize)); + } + + return dataBatches; +}; diff --git a/src/app/shared/services/bulk-import.service.ts b/src/app/shared/services/bulk-import.service.ts new file mode 100644 index 00000000..b093be00 --- /dev/null +++ b/src/app/shared/services/bulk-import.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class BulkImportService { + public readonly nextCreateIotDeviceBatchIndex$: Subject = new Subject(); + public readonly nextUpdateDeviceBatchIndex$: Subject = new Subject(); + + constructor() {} +} diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index af3bc49c..ff0293ac 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -720,7 +720,12 @@ "OTAA-INFO-MISSING": "OTAA nøgle mangler eller er ikke gyldig.", "ABP-INFO-MISSING": "ABP nøgle mangler eller er ikke gyldig.", "DIFFERENT-SERVICE-PROFILE": "Dine devices har forskellige service profiles. De skal have den samme service profile!", - "WRONG-SERVICE-PROFILE": "Dine devices har forkert service profile. Vælg devices som har samme service profile som din multicast." + "WRONG-SERVICE-PROFILE": "Dine devices har forkert service profile. Vælg devices som har samme service profile som din multicast.", + "ID-DOES-NOT-EXIST": "Id'et findes ikke", + "APPLICATION-DOES-NOT-EXIST": "Den tilhørende applikation findes ikke", + "FAILED-TO-CREATE-OR-UPDATE-IOT-DEVICE": "Enheden kunne ikke oprettes eller opdateres", + "DEVICE-MODEL-ORGANIZATION-DOES-NOT-MATCH": "Organisationsid'et på device modellen matcher ikke den tilhørende applikation", + "DEVICE-MODEL-DOES-NOT-EXIST": "Device model findes ikke" }, "PROFILES": { "NAME": "LoRaWAN profiler",