From 8da6fca7a7a470579e716f493f99ea1c27db4c54 Mon Sep 17 00:00:00 2001 From: Frederik Christ Vestergaard Date: Thu, 5 Oct 2023 11:40:37 +0200 Subject: [PATCH 1/6] Fixed routing of gateway list + fixed memory leak by unsubscribing properly from gateway fetches --- .../gateway-overview.component.ts | 13 +- .../gateway-list/gateway-list.component.ts | 2 +- .../gateway-map/gateway-map.component.ts | 17 +- .../gateway-table/gateway-table.component.ts | 288 +++++++++--------- 4 files changed, 169 insertions(+), 151 deletions(-) diff --git a/src/app/gateway/gateway-overview/gateway-overview.component.ts b/src/app/gateway/gateway-overview/gateway-overview.component.ts index 4280a62e..f84937de 100644 --- a/src/app/gateway/gateway-overview/gateway-overview.component.ts +++ b/src/app/gateway/gateway-overview/gateway-overview.component.ts @@ -9,7 +9,7 @@ import { environment } from '@environments/environment'; import { Title } from '@angular/platform-browser'; import { OrganizationAccessScope } from '@shared/enums/access-scopes'; import { GatewayService } from '@app/gateway/gateway.service'; -import { Router } from '@angular/router'; +import { NavigationEnd, Router } from '@angular/router'; @Component({ selector: 'app-gateway-list', @@ -40,6 +40,7 @@ export class GatewayOverviewComponent implements OnInit, OnChanges, OnDestroy { organisations: Organisation[]; private deleteDialogSubscription: Subscription; + private routerSubscription: Subscription; canEdit: boolean; constructor( @@ -65,9 +66,12 @@ export class GatewayOverviewComponent implements OnInit, OnChanges, OnDestroy { this.canEdit = this.meService.hasAccessToTargetOrganization( OrganizationAccessScope.GatewayWrite ); - if (this.router.url === '/gateways') { - this.router.navigateByUrl('/gateways/list', { replaceUrl: true }); - } + // Subscribe to route change to root and route to list view + this.router.events.subscribe((e) => { + if (e instanceof NavigationEnd && e.url === '/gateways') { + this.router.navigateByUrl('/gateways/list', { replaceUrl: true }); + } + }); } ngOnChanges() {} @@ -82,5 +86,6 @@ export class GatewayOverviewComponent implements OnInit, OnChanges, OnDestroy { // prevent memory leak by unsubscribing this.gatewaySubscription?.unsubscribe(); this.deleteDialogSubscription?.unsubscribe(); + this.routerSubscription.unsubscribe(); } } diff --git a/src/app/gateway/gateway-overview/gateway-tabs/gateway-list/gateway-list.component.ts b/src/app/gateway/gateway-overview/gateway-tabs/gateway-list/gateway-list.component.ts index 938b6f89..f5cc5772 100644 --- a/src/app/gateway/gateway-overview/gateway-tabs/gateway-list/gateway-list.component.ts +++ b/src/app/gateway/gateway-overview/gateway-tabs/gateway-list/gateway-list.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, OnInit } from '@angular/core'; +import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'; import { GatewayService } from '@app/gateway/gateway.service'; @Component({ diff --git a/src/app/gateway/gateway-overview/gateway-tabs/gateway-map/gateway-map.component.ts b/src/app/gateway/gateway-overview/gateway-tabs/gateway-map/gateway-map.component.ts index fd287edd..49b33194 100644 --- a/src/app/gateway/gateway-overview/gateway-tabs/gateway-map/gateway-map.component.ts +++ b/src/app/gateway/gateway-overview/gateway-tabs/gateway-map/gateway-map.component.ts @@ -15,6 +15,7 @@ export class GatewayMapComponent implements OnInit, OnDestroy, AfterViewInit { public gateways: Gateway[]; public coordinateList = []; private gatewaySubscription: Subscription; + private organizationChangeSubscription: Subscription; isLoadingResults = true; constructor( @@ -26,13 +27,15 @@ export class GatewayMapComponent implements OnInit, OnDestroy, AfterViewInit { ngOnInit(): void {} ngAfterViewInit() { - this.gatewayService.organisationChangeSubject.subscribe((x) => { - if (x) { - this.getGatewayWith(x); - } else { - this.getGateways(); + this.organizationChangeSubscription = this.gatewayService.organisationChangeSubject.subscribe( + (x) => { + if (x) { + this.getGatewayWith(x); + } else { + this.getGateways(); + } } - }); + ); if (this.gatewayService.selectedOrg) { this.getGatewayWith(this.gatewayService.selectedOrg); } else { @@ -110,6 +113,6 @@ export class GatewayMapComponent implements OnInit, OnDestroy, AfterViewInit { ngOnDestroy() { // prevent memory leak by unsubscribing this.gatewaySubscription?.unsubscribe(); - // this.deleteDialogSubscription?.unsubscribe(); + this.organizationChangeSubscription.unsubscribe(); } } diff --git a/src/app/gateway/gateway-table/gateway-table.component.ts b/src/app/gateway/gateway-table/gateway-table.component.ts index f5e05dd6..04013452 100644 --- a/src/app/gateway/gateway-table/gateway-table.component.ts +++ b/src/app/gateway/gateway-table/gateway-table.component.ts @@ -1,139 +1,149 @@ -import { ChirpstackGatewayService } from 'src/app/shared/services/chirpstack-gateway.service'; -import { TranslateService } from '@ngx-translate/core'; -import { Gateway, GatewayResponseMany } from '../gateway.model'; -import { - faExclamationTriangle, - faCheckCircle, -} from '@fortawesome/free-solid-svg-icons'; -import moment from 'moment'; -import { Component, ViewChild, AfterViewInit, Input } from '@angular/core'; -import { MatPaginator, PageEvent } from '@angular/material/paginator'; -import { Observable, of as observableOf, Subject } from 'rxjs'; -import { catchError, map, startWith, switchMap } from 'rxjs/operators'; -import { MeService } from '@shared/services/me.service'; -import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; -import { environment } from '@environments/environment'; -import { MatSort } from '@angular/material/sort'; -import { MatTableDataSource } from '@angular/material/table'; -import { tableSorter } from '@shared/helpers/table-sorting.helper'; -import { OrganizationAccessScope } from '@shared/enums/access-scopes'; -import { DefaultPageSizeOptions } from '@shared/constants/page.constants'; - -@Component({ - selector: 'app-gateway-table', - templateUrl: './gateway-table.component.html', - styleUrls: ['./gateway-table.component.scss'], -}) -export class GatewayTableComponent implements AfterViewInit { - @Input() organisationChangeSubject: Subject; - organizationId?: number; - displayedColumns: string[] = [ - 'name', - 'gateway-id', - 'location', - 'internalOrganizationName', - 'last-seen', - 'status', - 'menu', - ]; - data: Gateway[] = []; - dataSource: MatTableDataSource; - public pageSize = environment.tablePageSize; - public pageSizeOptions = DefaultPageSizeOptions; - - faExclamationTriangle = faExclamationTriangle; - faCheckCircle = faCheckCircle; - refetchIntervalId: NodeJS.Timeout; - batteryStatusColor = 'green'; - batteryStatusPercentage = 50; - resultsLength = 0; - isLoadingResults = true; - - @ViewChild(MatPaginator) paginator: MatPaginator; - @ViewChild(MatSort) sort: MatSort; - - constructor( - private chirpstackGatewayService: ChirpstackGatewayService, - public translate: TranslateService, - private meService: MeService, - private deleteDialogService: DeleteDialogService - ) { - this.translate.use('da'); - moment.locale('da'); -} - - ngAfterViewInit() { - this.organisationChangeSubject.subscribe((x) => { - this.organizationId = x; - this.refresh(); - }); - this.refetchIntervalId = setInterval(() => this.refresh(), 60 * 1000) - this.refresh(); - } - - ngOnDestroy() { - clearInterval(this.refetchIntervalId) - } - - private refresh() { - this.getGateways().subscribe((data) => { - data.result.forEach((gw) => { - gw.canEdit = this.canEdit(gw.internalOrganizationId); - }); - this.data = data.result; - this.resultsLength = data.totalCount; - this.isLoadingResults = false; - this.dataSource = new MatTableDataSource(this.data); - this.dataSource.sortingDataAccessor = tableSorter; - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; - }); - } - - canEdit(internalOrganizationId: number): boolean { - return this.meService.hasAccessToTargetOrganization(OrganizationAccessScope.GatewayWrite, internalOrganizationId); - } - - private getGateways(): Observable { - const params = { - limit: this.paginator.pageSize, - offset: this.paginator.pageIndex * this.paginator.pageSize, - }; - if (this.organizationId > 0) { - params['organizationId'] = this.organizationId; - } - return this.chirpstackGatewayService.getMultiple(params); - } - - gatewayStatus(gateway: Gateway): boolean { - return this.chirpstackGatewayService.isGatewayActive(gateway); - } - - lastActive(gateway: Gateway): string { - if (gateway?.lastSeenAt) { - const lastSeenAtUnixTimestamp = moment(gateway?.lastSeenAt).valueOf(); - const now = moment(new Date()).valueOf(); - return moment(Math.min(lastSeenAtUnixTimestamp, now)).fromNow(); - } else { - return this.translate.instant('ACTIVITY.NEVER'); - } - } - - clickDelete(element) { - this.deleteGateway(element.id); - } - - deleteGateway(id: string) { - this.deleteDialogService.showSimpleDialog().subscribe((response) => { - if (response) { - this.chirpstackGatewayService.delete(id).subscribe((response) => { - if (response.ok && response.body.success === true) { - this.refresh(); - } - }); - } else { - console.error(response); - } - }); - } -} +import { ChirpstackGatewayService } from 'src/app/shared/services/chirpstack-gateway.service'; +import { TranslateService } from '@ngx-translate/core'; +import { Gateway, GatewayResponseMany } from '../gateway.model'; +import { + faExclamationTriangle, + faCheckCircle, +} from '@fortawesome/free-solid-svg-icons'; +import moment from 'moment'; +import { + Component, + ViewChild, + AfterViewInit, + Input, + OnDestroy, +} from '@angular/core'; +import { MatPaginator, PageEvent } from '@angular/material/paginator'; +import { Observable, of as observableOf, Subject, Subscription } from 'rxjs'; +import { catchError, map, startWith, switchMap } from 'rxjs/operators'; +import { MeService } from '@shared/services/me.service'; +import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service'; +import { environment } from '@environments/environment'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { tableSorter } from '@shared/helpers/table-sorting.helper'; +import { OrganizationAccessScope } from '@shared/enums/access-scopes'; +import { DefaultPageSizeOptions } from '@shared/constants/page.constants'; + +@Component({ + selector: 'app-gateway-table', + templateUrl: './gateway-table.component.html', + styleUrls: ['./gateway-table.component.scss'], +}) +export class GatewayTableComponent implements AfterViewInit, OnDestroy { + @Input() organisationChangeSubject: Subject; + organizationId?: number; + displayedColumns: string[] = [ + 'name', + 'gateway-id', + 'location', + 'internalOrganizationName', + 'last-seen', + 'status', + 'menu', + ]; + data: Gateway[] = []; + dataSource: MatTableDataSource; + public pageSize = environment.tablePageSize; + public pageSizeOptions = DefaultPageSizeOptions; + + faExclamationTriangle = faExclamationTriangle; + faCheckCircle = faCheckCircle; + refetchIntervalId: NodeJS.Timeout; + resultsLength = 0; + isLoadingResults = true; + private fetchSubscription: Subscription; + + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + + constructor( + private chirpstackGatewayService: ChirpstackGatewayService, + public translate: TranslateService, + private meService: MeService, + private deleteDialogService: DeleteDialogService + ) { + this.translate.use('da'); + moment.locale('da'); + } + + ngAfterViewInit() { + this.fetchSubscription = this.organisationChangeSubject.subscribe((x) => { + this.organizationId = x; + this.refresh(); + }); + this.refetchIntervalId = setInterval(() => this.refresh(), 60 * 1000); + this.refresh(); + } + + ngOnDestroy() { + clearInterval(this.refetchIntervalId); + this.fetchSubscription.unsubscribe(); + } + + private refresh() { + console.log('Getting Gateways'); + this.getGateways().subscribe((data) => { + data.result.forEach((gw) => { + gw.canEdit = this.canEdit(gw.internalOrganizationId); + }); + this.data = data.result; + this.resultsLength = data.totalCount; + this.isLoadingResults = false; + this.dataSource = new MatTableDataSource(this.data); + this.dataSource.sortingDataAccessor = tableSorter; + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + }); + } + + canEdit(internalOrganizationId: number): boolean { + return this.meService.hasAccessToTargetOrganization( + OrganizationAccessScope.GatewayWrite, + internalOrganizationId + ); + } + + private getGateways(): Observable { + const params = { + limit: this.paginator.pageSize, + offset: this.paginator.pageIndex * this.paginator.pageSize, + }; + if (this.organizationId > 0) { + params['organizationId'] = this.organizationId; + } + return this.chirpstackGatewayService.getMultiple(params); + } + + gatewayStatus(gateway: Gateway): boolean { + return this.chirpstackGatewayService.isGatewayActive(gateway); + } + + lastActive(gateway: Gateway): string { + if (gateway?.lastSeenAt) { + const lastSeenAtUnixTimestamp = moment(gateway?.lastSeenAt).valueOf(); + const now = moment(new Date()).valueOf(); + return moment(Math.min(lastSeenAtUnixTimestamp, now)).fromNow(); + } else { + return this.translate.instant('ACTIVITY.NEVER'); + } + } + + clickDelete(element) { + this.deleteGateway(element.id); + } + + deleteGateway(id: string) { + this.deleteDialogService.showSimpleDialog().subscribe((response) => { + if (response) { + this.chirpstackGatewayService.delete(id).subscribe((response) => { + if (response.ok && response.body.success === true) { + this.refresh(); + } + }); + } else { + console.error(response); + } + }); + } +} From 0cc25c930865ea7b008033f9cd0b517bdfcf0db0 Mon Sep 17 00:00:00 2001 From: Frederik Christ Vestergaard Date: Thu, 9 Nov 2023 09:28:10 +0100 Subject: [PATCH 2/6] Fixed routing errors in gateway list --- .../gateway/gateway-overview/gateway-overview.component.ts | 7 +++++-- .../gateway-tabs/gateway-list/gateway-list.component.ts | 2 +- src/app/gateway/gateway-table/gateway-table.component.ts | 1 - 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/gateway/gateway-overview/gateway-overview.component.ts b/src/app/gateway/gateway-overview/gateway-overview.component.ts index f84937de..c91c1468 100644 --- a/src/app/gateway/gateway-overview/gateway-overview.component.ts +++ b/src/app/gateway/gateway-overview/gateway-overview.component.ts @@ -66,8 +66,11 @@ export class GatewayOverviewComponent implements OnInit, OnChanges, OnDestroy { this.canEdit = this.meService.hasAccessToTargetOrganization( OrganizationAccessScope.GatewayWrite ); + if (this.router.url === '/gateways') { + this.router.navigateByUrl('/gateways/list', { replaceUrl: true }); + } // Subscribe to route change to root and route to list view - this.router.events.subscribe((e) => { + this.routerSubscription = this.router.events.subscribe((e) => { if (e instanceof NavigationEnd && e.url === '/gateways') { this.router.navigateByUrl('/gateways/list', { replaceUrl: true }); } @@ -86,6 +89,6 @@ export class GatewayOverviewComponent implements OnInit, OnChanges, OnDestroy { // prevent memory leak by unsubscribing this.gatewaySubscription?.unsubscribe(); this.deleteDialogSubscription?.unsubscribe(); - this.routerSubscription.unsubscribe(); + this.routerSubscription?.unsubscribe(); } } diff --git a/src/app/gateway/gateway-overview/gateway-tabs/gateway-list/gateway-list.component.ts b/src/app/gateway/gateway-overview/gateway-tabs/gateway-list/gateway-list.component.ts index f5cc5772..7867e6cd 100644 --- a/src/app/gateway/gateway-overview/gateway-tabs/gateway-list/gateway-list.component.ts +++ b/src/app/gateway/gateway-overview/gateway-tabs/gateway-list/gateway-list.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'; +import { AfterViewInit, Component } from '@angular/core'; import { GatewayService } from '@app/gateway/gateway.service'; @Component({ diff --git a/src/app/gateway/gateway-table/gateway-table.component.ts b/src/app/gateway/gateway-table/gateway-table.component.ts index 04013452..6fc4bc43 100644 --- a/src/app/gateway/gateway-table/gateway-table.component.ts +++ b/src/app/gateway/gateway-table/gateway-table.component.ts @@ -82,7 +82,6 @@ export class GatewayTableComponent implements AfterViewInit, OnDestroy { } private refresh() { - console.log('Getting Gateways'); this.getGateways().subscribe((data) => { data.result.forEach((gw) => { gw.canEdit = this.canEdit(gw.internalOrganizationId); From 618d6d34aa5a9f57b7a55570ea7b12cc6a1677ad Mon Sep 17 00:00:00 2001 From: Frederik Christ Vestergaard Date: Fri, 10 Nov 2023 14:44:43 +0100 Subject: [PATCH 3/6] Changed mqtt datatarget topic placeholder + added tooltip --- .../mqtt-edit/mqtt-edit.component.html | 140 ++++++++++-------- src/assets/i18n/da.json | 3 +- 2 files changed, 77 insertions(+), 66 deletions(-) diff --git a/src/app/applications/datatarget/mqtt-edit/mqtt-edit.component.html b/src/app/applications/datatarget/mqtt-edit/mqtt-edit.component.html index 6977d652..acd45b98 100644 --- a/src/app/applications/datatarget/mqtt-edit/mqtt-edit.component.html +++ b/src/app/applications/datatarget/mqtt-edit/mqtt-edit.component.html @@ -14,9 +14,9 @@
* + [placeholder]="'QUESTION.GIVE-DATATARGET-NAME-PLACEHOLDER' | translate" maxlength="50" required + [(ngModel)]="datatarget.name" + [ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('name'), 'is-valid' : formFailedSubmit && !errorFields.includes('name')}">
@@ -24,9 +24,9 @@
* + [placeholder]="'QUESTION.DATATARGET.MQTT.GIVE-URL-PLACEHOLDER' | translate" required + [(ngModel)]="datatarget.url" + [ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('url'), 'is-valid' : formFailedSubmit && !errorFields.includes('url')}">
@@ -34,9 +34,10 @@
* + [placeholder]="'QUESTION.DATATARGET.MQTT.GIVE-PORT-PLACEHOLDER' | translate" + [(ngModel)]="datatarget.mqttPort" + step="1" min="1025" max="65535" + [ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('mqttPort'), 'is-valid' : formFailedSubmit && !errorFields.includes('mqttPort')}">
@@ -44,22 +45,31 @@
* + [matTooltip]="'QUESTION.DATATARGET.MQTT.GIVE-QOS-INFO' | translate" matTooltipPosition="above" + matTooltipShowDelay="600" matTooltipHideDelay="2000"> + [placeholder]="'QUESTION.DATATARGET.MQTT.GIVE-QOS-PLACEHOLDER' | translate" + [(ngModel)]="datatarget.mqttQos" + step="1" min="0" max="2" + [ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('mqttQos'), 'is-valid' : formFailedSubmit && !errorFields.includes('mqttQos')}">
* + + [placeholder]="'QUESTION.DATATARGET.MQTT.GIVE-TOPIC-PLACEHOLDER' | translate" required + [(ngModel)]="datatarget.mqttTopic" + [ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('mqttTopic'), 'is-valid' : formFailedSubmit && !errorFields.includes('mqttTopic')}">
@@ -67,8 +77,8 @@
* + [(ngModel)]="datatarget.mqttUsername" + [ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('mqttUsername'), 'is-valid' : formFailedSubmit && !errorFields.includes('mqttUsername')}">
@@ -76,8 +86,8 @@
* + [(ngModel)]="datatarget.mqttPassword" + [ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('mqttPassword'), 'is-valid' : formFailedSubmit && !errorFields.includes('mqttPassword')}">
@@ -85,8 +95,8 @@
* + step="1" min="0" max="100000" maxlength="7" + [ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('timeout'), 'is-valid' : formFailedSubmit && !errorFields.includes('timeout')}">
@@ -99,49 +109,49 @@
{{'QUESTION.DATATARGET.RELATIONS' | translate}}
- - + + + - - - + + +
-
- - {{'QUESTION.DATATARGET.SELECT-DEVICES' | translate}} - - - - - - {{device.name}} - - +
+
+ + {{'QUESTION.DATATARGET.SELECT-DEVICES' | translate}} + + + + + + {{device.name}} + + +
+
+
+ + {{'QUESTION.DATATARGET.SELECT-PAYLOADDECODER' | translate}} + + + {{'QUESTION.DATATARGET.NO-PAYLOAD-DECODER-SELECTED' | translate}} + + + {{payloadDecoder.name}} + + + +
+
+ +
+ +

{{'DATATARGET.DELETE' | translate}}

-
-
- - {{'QUESTION.DATATARGET.SELECT-PAYLOADDECODER' | translate}} - - - {{'QUESTION.DATATARGET.NO-PAYLOAD-DECODER-SELECTED' | translate}} - - - {{payloadDecoder.name}} - - - -
-
- -
- -

{{'DATATARGET.DELETE' | translate}}

-
-
-
diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 861ab51e..d5967c7b 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -656,7 +656,8 @@ "GIVE-QOS-PLACEHOLDER": "0, 1, 2", "GIVE-QOS-INFO": "QoS (Quality of Service) bestemmer vigtigheden af, at en besked leveres til modtageren. Se dokumentationen for at vide mere.", "GIVE-TOPIC": "Angiv hvilket topic på brokeren der skal sendes til", - "GIVE-TOPIC-PLACEHOLDER": "device/+/event", + "GIVE-TOPIC-PLACEHOLDER": "device/event", + "GIVE-TOPIC-INFO": "Topic'et kan være givet af den udbyder som dataen skal sendes til. Der kan ikke bruges wildcards", "GIVE-AUTHENTICATION-METHOD": "Vælg autentificeringsmetode", "USERNAME-PASSWORD": "Brugernavn og password", "CERTIFICATE": "Certifikat", From d152beee0d0758864e5a55e850521e756a186faa Mon Sep 17 00:00:00 2001 From: Frederik Christ Vestergaard Date: Wed, 15 Nov 2023 09:34:28 +0100 Subject: [PATCH 4/6] Added additional text changes from Product Owner --- src/assets/i18n/da.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index d5967c7b..45db724d 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -652,19 +652,19 @@ "GIVE-URL-PLACEHOLDER": "https://min-mqtt-broker.dk", "GIVE-PORT": "Angiv datatargets broker port", "GIVE-PORT-PLACEHOLDER": "1883", - "GIVE-QOS": "Angiv hvilken QoS niveau data skal sendes med", + "GIVE-QOS": "Angiv hvilken QoS-niveau data skal sendes med", "GIVE-QOS-PLACEHOLDER": "0, 1, 2", - "GIVE-QOS-INFO": "QoS (Quality of Service) bestemmer vigtigheden af, at en besked leveres til modtageren. Se dokumentationen for at vide mere.", + "GIVE-QOS-INFO": "QoS (Quality of Service) fastlægger, hvor sikker leveringen af beskeden skal være. Der er tre muligheder: 0 (at most once), 1 (at least once) og 2 (exactly once)", "GIVE-TOPIC": "Angiv hvilket topic på brokeren der skal sendes til", - "GIVE-TOPIC-PLACEHOLDER": "device/event", - "GIVE-TOPIC-INFO": "Topic'et kan være givet af den udbyder som dataen skal sendes til. Der kan ikke bruges wildcards", + "GIVE-TOPIC-PLACEHOLDER": "Topic på ekstern broker", + "GIVE-TOPIC-INFO": "Topic'et kan være givet af den udbyder, som data skal sendes til. Der kan ikke bruges wildcards", "GIVE-AUTHENTICATION-METHOD": "Vælg autentificeringsmetode", "USERNAME-PASSWORD": "Brugernavn og password", "CERTIFICATE": "Certifikat", "USERNAME": "Brugernavn", - "GIVE-USERNAME": "Angiv et brugernavn med adgang til brokeren og den valgte topic", + "GIVE-USERNAME": "Angiv et brugernavn med adgang til brokeren og det valgte topic", "PASSWORD": "Adgangskode", - "GIVE-PASSWORD": "Angiv adgangskoden til brugeren" + "GIVE-PASSWORD": "Angiv adgangskoden" } }, "MULTICAST": { @@ -964,9 +964,9 @@ "REPORTBATTERYSTATUS": "Rapporter enhedens batteriniveau til applikationsserveren", "REPORTBATTERYMARGIN": "Rapporter device link margin til applikationsserver", "DRRATEMIN": "Min data rate", - "DRRATEMINEXPLAINED": "Minimum tilladte data rate. Skal være et tal fra 0-7. \nBrugt til ADR", + "DRRATEMINEXPLAINED": "Minimum tilladte data rate. Brugt til ADR", "DRRATEMAX": "Max data rate", - "DRRATEMAXEXPLAINED": "Maximum tilladte data rate. Skal være et tal fra 0-7. \nBrugt til ADR", + "DRRATEMAXEXPLAINED": "Maximum tilladte data rate. Brugt til ADR", "SAVE": "Gem", "CANCEL": "Annuller", "GOBACK": "Gå tilbage", From 2eba4775f75905573cba6e9d9dfe459348c174fb Mon Sep 17 00:00:00 2001 From: Frederik Christ Vestergaard Date: Wed, 15 Nov 2023 12:24:04 +0100 Subject: [PATCH 5/6] Removed maxLenght from device AND gateway EUI, now removes non-hex digits on submit --- .../iot-device-edit.component.html | 6 +- .../iot-device-edit.component.ts | 8 +++ .../gateway-edit/gateway-edit.component.html | 10 ++-- .../gateway-edit/gateway-edit.component.ts | 58 +++++++++---------- src/assets/i18n/da.json | 4 +- 5 files changed, 47 insertions(+), 39 deletions(-) diff --git a/src/app/applications/iot-devices/iot-device-edit/iot-device-edit.component.html b/src/app/applications/iot-devices/iot-device-edit/iot-device-edit.component.html index 02b91ff2..0bbfa5aa 100644 --- a/src/app/applications/iot-devices/iot-device-edit/iot-device-edit.component.html +++ b/src/app/applications/iot-devices/iot-device-edit/iot-device-edit.component.html @@ -80,7 +80,7 @@
- @@ -88,7 +88,7 @@
- @@ -123,7 +123,7 @@

{{'IOTDEVICE.LORAWANSETUP' | translate}}

- diff --git a/src/app/applications/iot-devices/iot-device-edit/iot-device-edit.component.ts b/src/app/applications/iot-devices/iot-device-edit/iot-device-edit.component.ts index 5e462ccb..f9f1d263 100644 --- a/src/app/applications/iot-devices/iot-device-edit/iot-device-edit.component.ts +++ b/src/app/applications/iot-devices/iot-device-edit/iot-device-edit.component.ts @@ -320,6 +320,14 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy { } postIoTDevice() { + // Sanitize devEUI for non-hex characters + if (this.iotDevice.type === DeviceType.LORAWAN) { + this.iotDevice.lorawanSettings.devEUI = this.iotDevice.lorawanSettings.devEUI.replace( + /[^0-9A-Fa-f]/g, + '' + ); + } + this.iotDeviceService.createIoTDevice(this.iotDevice).subscribe( (response: IotDevice) => { this.router.navigate([ diff --git a/src/app/gateway/gateway-edit/gateway-edit.component.html b/src/app/gateway/gateway-edit/gateway-edit.component.html index 9dd722f0..d16adf76 100644 --- a/src/app/gateway/gateway-edit/gateway-edit.component.html +++ b/src/app/gateway/gateway-edit/gateway-edit.component.html @@ -28,8 +28,8 @@
*
@@ -40,15 +40,15 @@
-
- - {{'GATEWAY.LATITUDE' | translate}} + diff --git a/src/app/gateway/gateway-edit/gateway-edit.component.ts b/src/app/gateway/gateway-edit/gateway-edit.component.ts index 13675a44..4c1cc3c0 100644 --- a/src/app/gateway/gateway-edit/gateway-edit.component.ts +++ b/src/app/gateway/gateway-edit/gateway-edit.component.ts @@ -12,10 +12,9 @@ import { Gateway, GatewayResponse } from '../gateway.model'; @Component({ selector: 'app-gateway-edit', templateUrl: './gateway-edit.component.html', - styleUrls: ['./gateway-edit.component.scss'] + styleUrls: ['./gateway-edit.component.scss'], }) export class GatewayEditComponent implements OnInit, OnDestroy { - public backButton: BackButton = { label: '', routerLink: ['gateways'] }; public multiPage = false; public title = ''; @@ -39,7 +38,7 @@ export class GatewayEditComponent implements OnInit, OnDestroy { private loraGatewayService: ChirpstackGatewayService, private errorMessageService: ErrorMessageService, private scrollToTopService: ScrollToTopService - ) { } + ) {} ngOnInit(): void { this.translate.use('da'); @@ -47,10 +46,11 @@ export class GatewayEditComponent implements OnInit, OnDestroy { if (this.id) { this.getGateway(this.id); this.editMode = true; - this.backButton.routerLink = ['gateways', 'gateway-detail', this.id] + this.backButton.routerLink = ['gateways', 'gateway-detail', this.id]; } - this.translate.get(['NAV.LORA-GATEWAYS', 'FORM.EDIT-NEW-GATEWAY', 'GATEWAY.SAVE']) - .subscribe(translations => { + this.translate + .get(['NAV.LORA-GATEWAYS', 'FORM.EDIT-NEW-GATEWAY', 'GATEWAY.SAVE']) + .subscribe((translations) => { this.backButton.label = translations['NAV.LORA-GATEWAYS']; this.title = translations['FORM.EDIT-NEW-GATEWAY']; this.submitButton = translations['GATEWAY.SAVE']; @@ -72,36 +72,35 @@ export class GatewayEditComponent implements OnInit, OnDestroy { latitude: this.gateway.location.latitude, draggable: true, useGeolocation: !this.editMode, - editMode: this.editMode + editMode: this.editMode, }; } createGateway(): void { - this.loraGatewayService.post(this.gateway) - .subscribe( - (response) => { - this.routeBack(); - }, - (error: HttpErrorResponse) => { - this.showError(error); - this.formFailedSubmit = true; - } - ); + this.gateway.id = this.gateway.id.replace(/[^0-9A-Fa-f]/g, ''); + this.loraGatewayService.post(this.gateway).subscribe( + (response) => { + this.routeBack(); + }, + (error: HttpErrorResponse) => { + this.showError(error); + this.formFailedSubmit = true; + } + ); } updateGateway(): void { // Gateway ID not allowed in update. this.gateway.id = undefined; - this.loraGatewayService - .put(this.gateway, this.id) - .subscribe( - (response) => { - this.routeBack(); - }, - (error) => { - this.showError(error); - this.formFailedSubmit = true; - }); + this.loraGatewayService.put(this.gateway, this.id).subscribe( + (response) => { + this.routeBack(); + }, + (error) => { + this.showError(error); + this.formFailedSubmit = true; + } + ); } onSubmit(): void { @@ -134,10 +133,11 @@ export class GatewayEditComponent implements OnInit, OnDestroy { } private showError(error: HttpErrorResponse) { - const errorResponse = this.errorMessageService.handleErrorMessageWithFields(error); + const errorResponse = this.errorMessageService.handleErrorMessageWithFields( + error + ); this.errorFields = errorResponse.errorFields; this.errorMessages = errorResponse.errorMessages; this.scrollToTopService.scrollToTop(); } - } diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 45db724d..08570a16 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -628,7 +628,7 @@ "NFCNTDOWN": "nfcntdown", "NFCNTDOWN-PLACEHOLDER": "Angiv download framerate", "DEVEUI": "Enheds EUI (DevEUI)", - "DEVEUI-PLACEHOLDER": "Enheds EUI (DevEUI)", + "DEVEUI-PLACEHOLDER": "Angiv DevEUI (16 tegn)", "SKIPFCNTCHECK": "SkipFCntCheck", "SKIPFCNTCHECK-NO": "Nej", "SKIPFCNTCHECK-YES": "Ja", @@ -786,7 +786,7 @@ "DESCRIPTION": "LoRaWAN gateway beskrivelse", "DESCRIPTION-PLACEHOLDER": "Beskrivelse af LoRaWAN gateway", "GATEWAYID": "Gateway id (EUI)", - "GATEWAYID-PLACEHOLDER": "0000-0000-0000-0000", + "GATEWAYID-PLACEHOLDER": "0000000000000000", "METADATA": "Gateway tags", "METADATA-PLACEHOLDER": "Angiv JSON her", "ALTITUDE": "Højde", From 4b7510e288e4c2b60a352c572c3aaa95ca2bdbbb Mon Sep 17 00:00:00 2001 From: Frederik Christ Vestergaard Date: Fri, 17 Nov 2023 12:57:27 +0100 Subject: [PATCH 6/6] Added sticky to name column on gateway status table --- src/app/gateway/gateway-status/gateway-status.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/gateway/gateway-status/gateway-status.component.html b/src/app/gateway/gateway-status/gateway-status.component.html index eddb95d1..b050143a 100644 --- a/src/app/gateway/gateway-status/gateway-status.component.html +++ b/src/app/gateway/gateway-status/gateway-status.component.html @@ -20,7 +20,7 @@

{{title}}

- +
{{element.name}} {{element.name}}