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/8] 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/8] 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/8] 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/8] 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/8] 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/8] 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}} From 8aa035cf404f8e91f5d20973fea3304c9c268240 Mon Sep 17 00:00:00 2001 From: Frederik Christ Vestergaard Date: Wed, 22 Nov 2023 11:13:19 +0100 Subject: [PATCH 7/8] Implemented application table column selection --- src/app/applications/application.model.ts | 2 + .../applications-table.component.html | 149 ++++++++++++- .../applications-table.component.scss | 27 +-- .../applications-table.component.ts | 199 +++++++++++++++++- src/app/applications/applications.module.ts | 2 + src/app/shared/shared.module.ts | 4 +- src/assets/i18n/da.json | 13 +- src/styles.scss | 5 + 8 files changed, 369 insertions(+), 32 deletions(-) diff --git a/src/app/applications/application.model.ts b/src/app/applications/application.model.ts index 07982ae4..6ebbf7d7 100644 --- a/src/app/applications/application.model.ts +++ b/src/app/applications/application.model.ts @@ -6,6 +6,7 @@ import { ApplicationStatus } from './enums/status.enum'; import { IotDevice } from './iot-devices/iot-device.model'; import { ApplicationDeviceType } from './models/application-device-type.model'; import { PermissionResponse } from '@app/admin/permission/permission.model'; +import { Datatarget } from '@applications/datatarget/datatarget.model'; export class Application { public id: number; @@ -33,6 +34,7 @@ export class Application { public deviceTypes?: ApplicationDeviceType[]; public permissions: PermissionResponse[]; public permissionIds: number[]; + public dataTargets: Datatarget[]; } export class ApplicationRequest { diff --git a/src/app/applications/applications-list/applications-table/applications-table.component.html b/src/app/applications/applications-list/applications-table/applications-table.component.html index 14d26618..83f97521 100644 --- a/src/app/applications/applications-list/applications-table/applications-table.component.html +++ b/src/app/applications/applications-list/applications-table/applications-table.component.html @@ -1,3 +1,32 @@ +
+ + + + + + + {{option.display | translate}} + + +
+
@@ -8,16 +37,34 @@
+ matSortDirection="asc"> - + + + + + + + + + + @@ -26,7 +73,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -36,26 +165,28 @@ +
+ {{ 'APPLICATION-TABLE.NAME' | translate }} {{element.name}} + class="application-link">{{element.name}} + + {{'APPLICATION-TABLE.OWNER' | translate}} + + {{application.owner ?? '-'}} + + {{'APPLICATION-TABLE.CONTACT-PERSON' | translate}} + + {{application.contactPerson ?? '-'}} {{ 'APPLICATION-TABLE.IOT-DEVICES' | translate }} - {{element ?.iotDevices?.length ? element?.iotDevices?.length : 0}} + {{element?.iotDevices?.length ?? 0}} + + {{ 'APPLICATION-TABLE.DATA-TARGETS' | translate}} + + {{application?.dataTargets?.length ?? 0}} + + {{ 'APPLICATION-TABLE.OPEN-DATA-DK' | translate }} + + {{isOpenDataDK(application.dataTargets) | yesNo}} + + {{'APPLICATION-TABLE.STATUS' | translate}} + + {{'APPLICATION.STATUS.' + application.status | translate}} + + {{'APPLICATION-TABLE.PERSONAL-DATA' | translate}} + + + {{!application.personalData ? '-' : ''}} + + {{'APPLICATION-TABLE.START-DATE' | translate}} + + {{(application.startDate | dateOnly) ?? '-'}} + + {{'APPLICATION-TABLE.END-DATE' | translate}} + + {{(application.endDate | dateOnly) ?? '-'}} + + {{'APPLICATION-TABLE.CATEGORY' | translate}} + + {{application.category ?? '-'}} + + {{'APPLICATION-TABLE.CONTROLLED-PROPERTIES' | translate}} + + {{mapControlledProperties(application.controlledProperties) ?? '-'}} + + {{'APPLICATION-TABLE.DEVICE-TYPES' | translate}} + + {{mapDeviceTypes(application.deviceTypes) ?? '-'}}
- + diff --git a/src/app/applications/applications-list/applications-table/applications-table.component.scss b/src/app/applications/applications-list/applications-table/applications-table.component.scss index 0002005e..244dbca5 100644 --- a/src/app/applications/applications-list/applications-table/applications-table.component.scss +++ b/src/app/applications/applications-list/applications-table/applications-table.component.scss @@ -1,12 +1,15 @@ -// .example-loading-shade { -// position: absolute; -// top: 0; -// left: 0; -// bottom: 0; -// right: 0; -// background: rgba(0, 0, 0, 0.15); -// z-index: 1; -// display: flex; -// align-items: center; -// justify-content: center; -// } +.table-select { + width: 180px; + margin: 5px; + +} + +.select-container { + display: flex; + flex-direction: row; + justify-content: flex-end; +} + +.flag-icon { + color: red; +} 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 5b60ddf2..d66352af 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,9 +1,9 @@ import { - Component, - ViewChild, AfterViewInit, + Component, Input, OnInit, + ViewChild, } from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; @@ -13,15 +13,104 @@ 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 { MeService } from '@shared/services/me.service'; import { merge, Observable, of as observableOf } from 'rxjs'; import { catchError, map, startWith, switchMap } from 'rxjs/operators'; -import { OrganizationAccessScope } from '@shared/enums/access-scopes'; import { DefaultPageSizeOptions } from '@shared/constants/page.constants'; +import { MatSelectChange } from '@angular/material/select'; +import { ControlledProperty } from '@shared/models/controlled-property.model'; +import { ApplicationDeviceTypeEntries } from '@shared/enums/device-type'; +import { ApplicationDeviceType } from '@applications/models/application-device-type.model'; +import { Datatarget } from '@applications/datatarget/datatarget.model'; +import { faFlag } from '@fortawesome/free-solid-svg-icons'; +import { FormControl, FormGroup } from '@angular/forms'; + +const columnDefinitions: TableColumn[] = [ + { + id: 'name', + display: 'APPLICATION-TABLE.NAME', + default: true, + toggleable: false, + }, + { + id: 'owner', + display: 'APPLICATION-TABLE.OWNER', + default: true, + toggleable: true, + }, + { + id: 'contactPerson', + display: 'APPLICATION-TABLE.CONTACT-PERSON', + default: false, + toggleable: true, + }, + { + id: 'devices', + display: 'APPLICATION-TABLE.IOT-DEVICES', + default: true, + toggleable: true, + }, + { + id: 'dataTargets', + display: 'APPLICATION-TABLE.DATA-TARGETS', + default: true, + toggleable: true, + }, + { + id: 'openDataDkEnabled', + display: 'APPLICATION-TABLE.OPEN-DATA-DK', + default: false, + toggleable: true, + }, + { + id: 'status', + display: 'APPLICATION-TABLE.STATUS', + default: true, + toggleable: true, + }, + { + id: 'personalData', + display: 'APPLICATION-TABLE.PERSONAL-DATA', + default: true, + toggleable: true, + }, + { + id: 'startDate', + display: 'APPLICATION-TABLE.START-DATE', + default: false, + toggleable: true, + }, + { + id: 'endDate', + display: 'APPLICATION-TABLE.END-DATE', + default: false, + toggleable: true, + }, + { + id: 'category', + display: 'APPLICATION-TABLE.CATEGORY', + default: false, + toggleable: true, + }, + { + id: 'controlledProperties', + display: 'APPLICATION-TABLE.CONTROLLED-PROPERTIES', + default: false, + toggleable: true, + }, + { + id: 'deviceTypes', + display: 'APPLICATION-TABLE.DEVICE-TYPES', + default: false, + toggleable: true, + }, + { + id: 'menu', + display: '', + default: true, + toggleable: false, + }, +]; -/** - * @title Table retrieving data through HTTP - */ @Component({ selector: 'app-applications-table', styleUrls: ['./applications-table.component.scss'], @@ -30,7 +119,14 @@ import { DefaultPageSizeOptions } from '@shared/constants/page.constants'; export class ApplicationsTableComponent implements AfterViewInit, OnInit { @Input() organizationId: number; @Input() permissionId: number; - displayedColumns: string[] = ['name', 'devices', 'menu']; + + faFlagIcon = faFlag; + + displayedColumns: string[] = []; + optionalColumnsSelected: string[] = []; + optionalColumnOptions: TableColumn[] = []; + d; + data: Application[] = []; public pageSize = environment.tablePageSize; @@ -39,6 +135,8 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit { isLoadingResults = true; public errorMessage: string; + applicationSavedColumns = 'applicationSavedColumns'; + @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -50,6 +148,23 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit { ) {} ngOnInit() { + this.optionalColumnOptions = columnDefinitions.filter((o) => o.toggleable); + + const userDisplayedColumns = localStorage.getItem( + this.applicationSavedColumns + ); + if (userDisplayedColumns) { + const chosenColumns = userDisplayedColumns.split(','); + this.displayedColumns = chosenColumns; + this.optionalColumnsSelected = chosenColumns; + } else { + this.optionalColumnsSelected = columnDefinitions + .filter((o) => o.toggleable && o.default) + .map((o) => o.id); + this.displayedColumns = columnDefinitions + .filter((o) => o.default) + .map((o) => o.id); + } } ngAfterViewInit() { @@ -115,7 +230,75 @@ export class ApplicationsTableComponent implements AfterViewInit, OnInit { } }); } + navigateToEditPage(applicationId: string) { this.router.navigate(['applications', 'edit-application', applicationId]); } + + handleColumnSelection({ + source: { value: selectedColumns }, + }: MatSelectChange) { + const displayedColumns = columnDefinitions + .filter((o) => !o.toggleable || selectedColumns.includes(o.id)) + .map((o) => o.id); + + localStorage.setItem( + this.applicationSavedColumns, + displayedColumns.join(',') + ); + this.displayedColumns = displayedColumns; + } + + mapControlledProperties(value: ControlledProperty[]) { + if (!value.length) return '-'; + + return value.map((p) => p.type).join(', '); + } + + mapDeviceTypes(value: ApplicationDeviceType[]) { + const deviceTypeTranslationPrefix = 'IOT-DEVICE-TYPES.'; + const deviceTypeTranslationKeys = ApplicationDeviceTypeEntries.map( + (x) => `${deviceTypeTranslationPrefix}${x.key}` + ); + + const result = []; + + this.translate + .get([ + ...deviceTypeTranslationKeys, + deviceTypeTranslationPrefix + 'OTHER', + ]) + .subscribe((translations) => { + value.forEach((p) => + result.push(translations[deviceTypeTranslationPrefix + p.type]) + ); + }); + return result.length ? result.join(', ') : '-'; + } + + isOpenDataDK(dataTargets: Datatarget[]): boolean { + const result = dataTargets.find((t) => t.type === 'OPENDATADK'); + + return !!result; + } + + selectAll() { + const allOptional = this.optionalColumnOptions.map((o) => o.id); + this.handleColumnSelection({ + source: { value: allOptional }, + } as MatSelectChange); + this.optionalColumnsSelected = allOptional; + } + + deSelectAll() { + this.handleColumnSelection({ source: { value: [] } } as MatSelectChange); + this.optionalColumnsSelected = []; + } +} + +interface TableColumn { + id: string; + display: string; + default: boolean; + toggleable: boolean; } diff --git a/src/app/applications/applications.module.ts b/src/app/applications/applications.module.ts index acc30053..f7c9c34c 100644 --- a/src/app/applications/applications.module.ts +++ b/src/app/applications/applications.module.ts @@ -18,6 +18,7 @@ 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'; +import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ declarations: [ @@ -46,6 +47,7 @@ import { MulticastModule } from './multicast/multicast.module'; NGMaterialModule, PipesModule, MulticastModule, + ReactiveFormsModule, ], }) export class ApplicationsModule {} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 4d52159e..f7490d5c 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -41,7 +41,7 @@ import { MetadataDetailsComponent } from './components/metadata-details/metadata FormsModule, NGMaterialModule, FontAwesomeModule, - PipesModule + PipesModule, ], exports: [ AlertComponent, @@ -57,4 +57,4 @@ import { MetadataDetailsComponent } from './components/metadata-details/metadata MetadataDetailsComponent, ], }) -export class SharedModule { } +export class SharedModule {} diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 08570a16..6b539287 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -327,7 +327,18 @@ "APPLICATION-TABLE": { "NAME": "Navn", "IOT-DEVICES": "IoT enheder", - "UPDATED": "Senest opdateret" + "UPDATED": "Senest opdateret", + "DATA-TARGETS": "Datatarges", + "STATUS": "Status", + "START-DATE": "Startdato", + "END-DATE": "Slutdato", + "CATEGORY": "Kategori", + "OWNER": "Ejer", + "CONTACT-PERSON": "Kontaktperson", + "PERSONAL-DATA": "Persondata", + "CONTROLLED-PROPERTIES": "Data", + "DEVICE-TYPES": "Forbindelsesteknologi", + "OPEN-DATA-DK": "Open Data DK" }, "IOT-TABLE": { "APPLICATION": "Applikation", diff --git a/src/styles.scss b/src/styles.scss index 633ee025..f520f47e 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -113,3 +113,8 @@ body { .line-break { white-space: pre-line; } + +.mat-select-panel.tall { + max-height: 400px; + height: 400px; +} From 1ec5bf44fcd4e26838f0f6569a23c02c1dbe0e7d Mon Sep 17 00:00:00 2001 From: Frederik Christ Vestergaard Date: Wed, 22 Nov 2023 12:02:19 +0100 Subject: [PATCH 8/8] Removed unused controller name from select --- .../applications-table/applications-table.component.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/applications/applications-list/applications-table/applications-table.component.html b/src/app/applications/applications-list/applications-table/applications-table.component.html index 83f97521..6f014aa6 100644 --- a/src/app/applications/applications-list/applications-table/applications-table.component.html +++ b/src/app/applications/applications-list/applications-table/applications-table.component.html @@ -5,7 +5,6 @@ [(value)]="optionalColumnsSelected" (selectionChange)="handleColumnSelection($event)" panelClass="tall" - formControlName="column" >
+ {{ 'APPLICATION-TABLE.OPEN-DATA-DK' | translate }}