From 59391599d8dd7f9bc6c293ed1635179e9d5678c4 Mon Sep 17 00:00:00 2001 From: jeppeej Date: Wed, 16 Dec 2020 13:30:55 +0100 Subject: [PATCH 01/39] IOT-1144 add readonly mode for sigfox device type, serviceprofile and bulkimport --- .../application-detail.component.html | 14 +++-- .../application-detail.component.ts | 4 ++ .../service-profiles-edit.component.html | 53 +++---------------- .../service-profiles-edit.component.ts | 4 ++ .../sigfox-device-type-table.component.ts | 2 +- .../sigfox-device-types-edit.component.html | 10 ++-- .../sigfox-device-types-edit.component.ts | 4 ++ 7 files changed, 36 insertions(+), 55 deletions(-) diff --git a/src/app/applications/application-detail/application-detail.component.html b/src/app/applications/application-detail/application-detail.component.html index 1ff41ca5..1eac588d 100644 --- a/src/app/applications/application-detail/application-detail.component.html +++ b/src/app/applications/application-detail/application-detail.component.html @@ -21,10 +21,16 @@

Detaljer

- - +
+ + +
+ + + +
diff --git a/src/app/applications/application-detail/application-detail.component.ts b/src/app/applications/application-detail/application-detail.component.ts index b8dfaec1..28f283a9 100644 --- a/src/app/applications/application-detail/application-detail.component.ts +++ b/src/app/applications/application-detail/application-detail.component.ts @@ -6,6 +6,7 @@ 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 { MeService } from '@shared/services/me.service'; import { Subscription } from 'rxjs'; @Component({ @@ -22,12 +23,14 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { 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 deleteDialogService: DeleteDialogService ) { } @@ -48,6 +51,7 @@ export class ApplicationDetailComponent implements OnInit, OnDestroy { this.backButton.label = translations['NAV.APPLICATIONS']; this.dropdownButton.label = translations['APPLICATION-TABLE-ROW.SHOW-OPTIONS']; }); + this.canEdit = this.meService.canWriteInTargetOrganization(); } onDeleteApplication() { diff --git a/src/app/profiles/service-profiles/service-profiles-edit/service-profiles-edit.component.html b/src/app/profiles/service-profiles/service-profiles-edit/service-profiles-edit.component.html index 24ba15e8..00e35a66 100644 --- a/src/app/profiles/service-profiles/service-profiles-edit/service-profiles-edit.component.html +++ b/src/app/profiles/service-profiles/service-profiles-edit/service-profiles-edit.component.html @@ -17,7 +17,7 @@
- @@ -38,7 +38,8 @@
- @@ -47,46 +48,13 @@
- -
- @@ -94,16 +62,13 @@
- - -
- @@ -112,14 +77,12 @@
- -
- @@ -128,7 +91,7 @@
- @@ -143,7 +106,7 @@ -
diff --git a/src/app/profiles/service-profiles/service-profiles-edit/service-profiles-edit.component.ts b/src/app/profiles/service-profiles/service-profiles-edit/service-profiles-edit.component.ts index 0debe3c6..eef7d899 100644 --- a/src/app/profiles/service-profiles/service-profiles-edit/service-profiles-edit.component.ts +++ b/src/app/profiles/service-profiles/service-profiles-edit/service-profiles-edit.component.ts @@ -8,6 +8,7 @@ import { Location } from '@angular/common'; import { FormGroup } from '@angular/forms'; import { BackButton } from '@shared/models/back-button.model'; import { ServiceProfileService } from '../service-profile.service'; +import { SharedVariableService } from '@shared/shared-variable/shared-variable.service'; @Component({ @@ -25,6 +26,7 @@ export class ServiceProfilesEditComponent implements OnInit { public formFailedSubmit = false; public form: FormGroup; public submitButton = ''; + public canEdit = false; id: string; serviceId: number; editMode = false; @@ -36,6 +38,7 @@ export class ServiceProfilesEditComponent implements OnInit { private route: ActivatedRoute, private router: Router, private translate: TranslateService, + private sharedVariableService: SharedVariableService, private serviceProfileService: ServiceProfileService, private location: Location, ) { @@ -53,6 +56,7 @@ export class ServiceProfilesEditComponent implements OnInit { if (this.id) { this.getServiceProfile(this.id); } + this.canEdit = this.sharedVariableService.getHasAnyWritePermission(); } private getServiceProfile(id: string) { diff --git a/src/app/sigfox/sigfox-groups-detail/sigfox-device-type-table/sigfox-device-type-table.component.ts b/src/app/sigfox/sigfox-groups-detail/sigfox-device-type-table/sigfox-device-type-table.component.ts index 3f41463f..a997c0a3 100644 --- a/src/app/sigfox/sigfox-groups-detail/sigfox-device-type-table/sigfox-device-type-table.component.ts +++ b/src/app/sigfox/sigfox-groups-detail/sigfox-device-type-table/sigfox-device-type-table.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; import { MatTableDataSource } from '@angular/material/table'; import { ActivatedRoute, Router } from '@angular/router'; diff --git a/src/app/sigfox/sigfox-groups-detail/sigfox-device-types-edit/sigfox-device-types-edit.component.html b/src/app/sigfox/sigfox-groups-detail/sigfox-device-types-edit/sigfox-device-types-edit.component.html index b4b38fff..6b22f17a 100644 --- a/src/app/sigfox/sigfox-groups-detail/sigfox-device-types-edit/sigfox-device-types-edit.component.html +++ b/src/app/sigfox/sigfox-groups-detail/sigfox-device-types-edit/sigfox-device-types-edit.component.html @@ -13,14 +13,14 @@
* -
- @@ -28,7 +28,7 @@
* - @@ -37,7 +37,7 @@
{{'QUESTION.SIGFOX.DEVICETYPE.CHOOSE-CONTRACT' | translate}} -
diff --git a/src/app/admin/users/user-list/user-table/user-table.component.html b/src/app/admin/users/user-list/user-table/user-table.component.html index 68563897..a8a44c63 100644 --- a/src/app/admin/users/user-list/user-table/user-table.component.html +++ b/src/app/admin/users/user-list/user-table/user-table.component.html @@ -1,7 +1,7 @@ -
+
+ +
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 3705d059..fb80f29e 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,8 +1,7 @@ -
- -
- +
+ +
{{errorMessage | translate}} 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 3abc5a63..43de08b9 100644 --- a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html +++ b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.html @@ -1,54 +1,53 @@ - - - -
-
+
+
+ +
+
- - - - - + + + + + - - - - + - + {{element.type | translate}} + - - - - - + + + + + - - -
- {{ 'DATATARGET-TABLE.NAME' | translate }} - - {{element.name}} - + {{ 'DATATARGET-TABLE.NAME' | translate }} + + {{element.name}} + - {{ 'DATATARGET-TABLE.TYPE' | translate }} + + + + {{ 'DATATARGET-TABLE.TYPE' | translate }} - {{element.type | translate}} - - + +
- - -
- \ No newline at end of file + + + + + +
\ No newline at end of file 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 62aa17ae..c3bbc0df 100644 --- a/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts +++ b/src/app/applications/datatarget/datatarget-table/datatarget-table.component.ts @@ -25,7 +25,7 @@ export class DatatargetTableComponent implements OnInit, AfterViewInit, OnDestro datatargets: Datatarget[]; resultsLength = 0; public canEdit = false; - @Input() isLoadingResults: boolean; + @Input() isLoadingResults: boolean = true; public pageSize = environment.tablePageSize; @Input() pageLimit: number; diff --git a/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.html b/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.html index b65779d2..49f04edf 100644 --- a/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.html +++ b/src/app/applications/iot-devices/iot-devices-table/iot-devices-table.component.html @@ -1,6 +1,7 @@ - -
+
+ +
diff --git a/src/app/device-model/device-model-table/device-model-table.component.html b/src/app/device-model/device-model-table/device-model-table.component.html index f295e764..cfdf1a7e 100644 --- a/src/app/device-model/device-model-table/device-model-table.component.html +++ b/src/app/device-model/device-model-table/device-model-table.component.html @@ -1,8 +1,8 @@ - -
- +
+ +
diff --git a/src/app/gateway/gateway-detail/gateway-detail.component.html b/src/app/gateway/gateway-detail/gateway-detail.component.html index c369ca7b..f6981726 100644 --- a/src/app/gateway/gateway-detail/gateway-detail.component.html +++ b/src/app/gateway/gateway-detail/gateway-detail.component.html @@ -48,6 +48,9 @@

{{ 'GATEWAY.LOCATION' | translate }}

{{ 'GATEWAY.STATS' | translate }}

+
+ +
diff --git a/src/app/gateway/gateway-detail/gateway-detail.component.ts b/src/app/gateway/gateway-detail/gateway-detail.component.ts index 829090a5..b9e75007 100644 --- a/src/app/gateway/gateway-detail/gateway-detail.component.ts +++ b/src/app/gateway/gateway-detail/gateway-detail.component.ts @@ -33,6 +33,7 @@ export class GatewayDetailComponent implements OnInit, OnDestroy, AfterViewInit deleteGateway = new EventEmitter(); private deleteDialogSubscription: Subscription; public dropdownButton: DropdownButton; + isLoadingResults = true; constructor( private gatewayService: ChirpstackGatewayService, @@ -81,6 +82,7 @@ export class GatewayDetailComponent implements OnInit, OnDestroy, AfterViewInit this.resultLength = this.gatewayStats.length; this.dataSource.paginator = this.paginator; this.setDropdownButton(); + this.isLoadingResults = false; }); } diff --git a/src/app/gateway/gateway-table/gateway-table.component.html b/src/app/gateway/gateway-table/gateway-table.component.html index cfdb14d7..e47a2c15 100644 --- a/src/app/gateway/gateway-table/gateway-table.component.html +++ b/src/app/gateway/gateway-table/gateway-table.component.html @@ -1,4 +1,7 @@
+
+ +
diff --git a/src/app/payload-decoder/payload-decoder-list/payload-decoder-table/payload-decoder-table.component.html b/src/app/payload-decoder/payload-decoder-list/payload-decoder-table/payload-decoder-table.component.html index 7cd79767..075fcf62 100644 --- a/src/app/payload-decoder/payload-decoder-list/payload-decoder-table/payload-decoder-table.component.html +++ b/src/app/payload-decoder/payload-decoder-list/payload-decoder-table/payload-decoder-table.component.html @@ -4,6 +4,9 @@
+
+ +
diff --git a/src/assets/scss/components/_tables.scss b/src/assets/scss/components/_tables.scss index ad133ea7..877209ad 100644 --- a/src/assets/scss/components/_tables.scss +++ b/src/assets/scss/components/_tables.scss @@ -98,3 +98,21 @@ table { } } } + +.loading-shade { + position: relative; + min-height: 200px; +} + +.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; +} \ No newline at end of file From 3f109f8618a15d9f1fe115a659a1165a95f8602d Mon Sep 17 00:00:00 2001 From: jeppeej Date: Thu, 17 Dec 2020 10:35:49 +0100 Subject: [PATCH 05/39] IOT-1167 add title to dashboard --- src/app/dashboard/dashboard.component.ts | 16 +++++++++++----- src/assets/i18n/da.json | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index 6c7d2301..3503f7a5 100644 --- a/src/app/dashboard/dashboard.component.ts +++ b/src/app/dashboard/dashboard.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { UserMinimalService } from '@app/admin/users/user-minimal.service'; import { AuthService } from '@auth/auth.service'; @@ -23,15 +24,20 @@ export class DashboardComponent implements OnInit { private router: Router, private sharedVariableService: SharedVariableService, private translate: TranslateService, + private titleService: Title, private userMinimalService: UserMinimalService, ) { this.route.queryParams.subscribe(async (params) => { this.translate.use('da'); - await this.translate.get(['DASHBOARD.NO-JOB-ACCESS', 'DASHBOARD.KOMBIT-LOGIN-ERROR','DASHBOARD.USER-INACTIVE']).toPromise().then(translations => { - this.unauthorizedMessage = translations['DASHBOARD.NO-JOB-ACCESS']; - this.kombitError = translations['DASHBOARD.KOMBIT-LOGIN-ERROR']; - this.noAccess = translations['DASHBOARD.USER-INACTIVE']; - }); + await this.translate.get(['DASHBOARD.NO-JOB-ACCESS','TITLE.DASHBOARD', 'DASHBOARD.KOMBIT-LOGIN-ERROR','DASHBOARD.USER-INACTIVE']) + .toPromise() + .then(translations => { + this.unauthorizedMessage = translations['DASHBOARD.NO-JOB-ACCESS']; + this.kombitError = translations['DASHBOARD.KOMBIT-LOGIN-ERROR']; + this.noAccess = translations['DASHBOARD.USER-INACTIVE']; + this.titleService.setTitle(translations['TITLE.DASHBOARD']); + } + ); // this is used when a user is returned from Kombit login const jwt = params['jwt']; if (jwt) { diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index e871071d..50ca0995 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -813,7 +813,8 @@ "ORGANIZATION": "OS2IoT - Organisationer", "DATATARGET": "OS2IoT - Datatarget", "BULKIMPORT": "OS2IoT - Bulk import", - "IOTDEVICE": "OS2IoT - IoT enhed" + "IOTDEVICE": "OS2IoT - IoT enhed", + "DASHBOARD": "OS2IoT - Dashboard" }, "false": "Nej", From 0eb4150cac5038f1dc96514af81ba8ecd364c5d7 Mon Sep 17 00:00:00 2001 From: jeppeej Date: Thu, 17 Dec 2020 11:10:11 +0100 Subject: [PATCH 06/39] Text da.json corrections --- src/assets/i18n/da.json | 140 ++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 50ca0995..5ba2c09b 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -14,13 +14,13 @@ "DASHBOARD": "Dashboard", "APPLICATIONS": "Applikationer", "APPLICATION": "Applikation", - "ALL-IOT-DEVICES": "Alle IoT-enheder", - "LORA-GATEWAYS": "LoRaWAN Gateways", + "ALL-IOT-DEVICES": "Alle IoT enheder", + "LORA-GATEWAYS": "LoRaWAN gateways", "DATATARGET": "Datatarget", "MY-DATATARGET": "Tilbage", "DATATARGET-APPLIKATION": "for applikation:", - "PROFILES": "LoRaWAN Profiler", - "PAYLOAD-DECODER": "Payload Decoder", + "PROFILES": "LoRaWAN profiler", + "PAYLOAD-DECODER": "Payload decoder", "ORGANISATIONS": "Organisationer", "PERMISSIONS": "Brugergrupper", "USERS": "Brugere", @@ -51,26 +51,26 @@ "NAME": "Navn", "ID": "Id", "ORGANISATION": "Organisation", - "PLACEHOLDER": "Der kan søges efter IoT-enheder, applikationer og gateways", + "PLACEHOLDER": "Der kan søges efter IoT enheder, applikationer og gateways", "QUERY": "resultater for", - "NO-RESULTS": "Du kun kan søge efter IoT-enheder, applikationer og gateways", + "NO-RESULTS": "Du kun kan søge efter IoT enheder, applikationer og gateways", "FETCHING": "Henter søgeresultater" }, "SIGFOX-GROUP": { - "NAME": "Sigfox Administration", + "NAME": "Sigfox administration", "SIGFOX-GROUP": "Sigfox grupper", "SIGFOX-LOGIN": "Sigfox API login", "API-ACCESS": "For at kunne tilgå din Sigfox gruppe skal du først have oprettet en API adgang til gruppen i Sigfox. Denne skal bruges til at logge ind med nedenfor. Læs hvordan du opretter adgangen her -", "LOGIN": "Login", "ADMINISTRATION": { - "NAME": "Sigfox Grupper", + "NAME": "Sigfox grupper", "CREATE-NEW-GROUP": "Opret forbindelse til ny gruppe" }, "PROFILES": { "NAME": "Sigfox enhedstyper", "DEVICE-TYPE": "Sigfox enhedstyper", "DEVICE-TYPE-NAME": "navn", - "DEVICE-TYPE-CONTRACT-ID": "kontrakt Id", + "DEVICE-TYPE-CONTRACT-ID": "kontrakt id", "DEVICE-TYPE-ALERT-EMAIL": "Alarm email", "CREATE-NEW-DEVICE-TYPE": "Opret ny enhedstype", "SAVE": "Gem enhedstype" @@ -82,9 +82,9 @@ "DELETE": "Slet applikation", "NAME": "Applikationens navn", "DESCRIPTION": "Applikationens beskrivelse", - "ATTACHED-IOT": "Tilknyttede IoT-enheder", + "ATTACHED-IOT": "Tilknyttede IoT enheder", "DATATARGET-SHOW": "Tilknyttede data targets", - "IMPORT-CSV": "Bulk-import af IoT-enheder", + "IMPORT-CSV": "Bulk import af IoT enheder", "BULK": { "TEMPLATE": { "GENERIC": "Generic HTTP sample", @@ -92,7 +92,7 @@ "SIGFOX": "Sigfox sample" }, "TITLE": "Import af .csv", - "DOWNLOAD": "Download Sample CSV Fil", + "DOWNLOAD": "Download sample CSV fil", "DROPFILE": "Klik eller drop din .csv file her", "NAME": "Navn", "TYPE": "Type", @@ -106,12 +106,12 @@ "DETAILS":"Detaljer", "CREATED": "Oprettet", "UPDATED": "Senest opdateret", - "DESCRIPTION": "LoRaWAN Gateway beskrivelse", + "DESCRIPTION": "LoRaWAN gateway beskrivelse", "LONGITUDE": "Længdegrad", "LATITUDE": "Breddegrad", "ALTITUDE": "Højde", "SAVE": "Gem gateway", - "ID": "Gateway ID", + "ID": "Gateway id", "LOCATION": "Placering", "TAGS": "Tags", "NAME": "Gatewayens navn", @@ -141,7 +141,7 @@ "NAME": "Datatarget navn", "SHOW-OPTIONS":"Håndter datatarget", "DELETE": "Slet", - "RELATIONS": "Tilknyttede IoT-enheder og payload decoders", + "RELATIONS": "Tilknyttede IoT enheder og payload decoders", "PAYLOADEDECODER": "Payload decoder", "NO-PAYLOADDECODER": "Ingen payload decoder", "IOTDEVICE": "IoT enhed(er)", @@ -149,7 +149,7 @@ "AUTHORIZATIONHEADER": "Authorization header", "NO-AUTHORIZATIONHEADER": "Ingen Authorization header angivet", "ADD-TO-OPENDATADK": "Send data til OpenDataDK", - "OPENDATA-DK": "Open Data DK", + "OPENDATA-DK": "OpenDataDK", "NO-OPENDATA-DK": "Der er ikke oprettet nogen datapakke endnu" }, "OPENDATADK": { @@ -161,7 +161,7 @@ "GIVE-OPENDATADK-DESCRIPTION": "Beskrivelse", "GIVE-OPENDATADK-DESCRIPTION-PLACEHOLDER": "Beskrivelse", "GIVE-OPENDATADK-KEYWORDS": "Angiv kommasepareret nøgleord", - "GIVE-OPENDATADK-KEYWORDS-PLACEHOLDER": "IOT, Sensor, LoRaWAN, Temeratur", + "GIVE-OPENDATADK-KEYWORDS-PLACEHOLDER": "IOT, sensor, LoRaWAN, temeratur", "GIVE-OPENDATADK-AUTHORNAME": "Forfatter", "GIVE-OPENDATADK-AUTHORNAME-PLACEHOLDER": "Jens Jensen", "GIVE-OPENDATADK-AUTHOR-EMAIL": "Forfatterens e-mail", @@ -184,7 +184,7 @@ }, "APPLICATION-TABLE": { "NAME": "Navn", - "IOT-DEVICES": "IoT-enheder", + "IOT-DEVICES": "IoT enheder", "UPDATED": "Senest opdateret" }, "IOT-TABLE": { @@ -225,15 +225,15 @@ "FUNCTION" : "Enhedsfunktioner", "ENERGYLIMITATIONCLASS" : "Energibegrænsningsklassen", "SUPPORTEDPROTOCOL" : "Understøttede protokoller", - "DETAIL-TITLE": "Device Model detalje", - "DEVICE-MODEL": "Device Model", - "SHOW-OPTIONS": "Håndter Device Model" + "DETAIL-TITLE": "Device model detalje", + "DEVICE-MODEL": "Device nodel", + "SHOW-OPTIONS": "Håndter device model" }, "LORA-GATEWAY-TABLE-ROW": { - "SHOW-OPTIONS": "Håndtér Gateway" + "SHOW-OPTIONS": "Håndtér gateway" }, "LORA-GATEWAY": { - "CREATE": "Opret LoRaWAN Gateway" + "CREATE": "Opret LoRaWAN gateway" }, "DATATARGET-TABLE": { "NAME": "Navn", @@ -275,14 +275,14 @@ "NAME": "Navn", "ID": "Payload decoder id", "ORGANIZATION": "Organisation", - "BELONGS-TO-ORGANIZATION": "Tilhørende organisation", + "BELONGS-TO-ORGANIZATION": "Organisation", "DECODINGFUNCTION": "Decoder funktion", "DETAIL": { "DROPDOWN": "Håndter payload decoder", "EDIT": "Redigér", "DELETE": "Slet" }, - "IOT-DEVICES": "Tilknyttede IoT-enheder" + "IOT-DEVICES": "Tilknyttede IoT enheder" }, "PAYLOAD-DECODER-TABLE-ROW": { "EDIT": "Redigér", @@ -323,8 +323,8 @@ "SAVE": "Gem brugergruppe", "DETAIL": { "TYPE": "Adgangstype ", - "ORG": "Tilhørende organisation ", - "DROPDOWN": "Håndter Brugergruppe", + "ORG": "Organisation ", + "DROPDOWN": "Håndter brugergruppe", "EDIT": "Rediger", "DELETE": "Slet", "USERS": "Brugergruppens brugere", @@ -353,31 +353,31 @@ } }, "PERMISSION-TYPE": { - "GlobalAdmin": "Global Administrator", - "OrganizationAdmin": "Organisations Administrator", + "GlobalAdmin": "Global administrator", + "OrganizationAdmin": "Organisations administrator", "Write": "Skrive-rettigheder", "Read": "Læse-rettigheder" }, "FORM": { "CREATE-NEW-APPLICATION": "Opret ny applikation", - "CREATE-NEW-LORA-GATEWAY": "Opret ny LoRaWAN Gateway", - "CREATE-NEW-DEVICE-MODEL": "Opret ny Device Model", + "CREATE-NEW-LORA-GATEWAY": "Opret ny LoRaWAN gateway", + "CREATE-NEW-DEVICE-MODEL": "Opret ny device model", "CREATE-NEW-DATATARGET": "Opret nyt datatarget", "EDIT-DATATARGET": "Redigér datatarget", "CREATE-NEW-IOT-DEVICE": "Tilføj en IoT enhed", - "EDIT-NEW-GATEWAY": "Redigér LoRaWAN Gateway", + "EDIT-NEW-GATEWAY": "Redigér LoRaWAN gateway", "EDIT-NEW-APPLICATION": "Redigér applikation", "TRANSMISSION_PROTOCOL_TEXT": "Vælg transmissionsprotokol", "TRANSMISSION_PROTOCOL_DESCRIPTION": "Transmissions protokollen angiver måden IoT-enheden forbinder til OS2IoT", "ALERT-NO-DELETE-DEVICES": "Du kan ikke slette denne applikation da den indeholder IOT enhed(er)", - "CREATE-NEW-PAYLOAD-DECODER": "Opret Payload Decoder", - "EDIT-PAYLOAD-DECODER": "Redigér Payload Decoder", + "CREATE-NEW-PAYLOAD-DECODER": "Opret payload decoder", + "EDIT-PAYLOAD-DECODER": "Redigér payload decoder", "EDIT-DEVICE-PREDIT-SIGFOX-GROUPSOFILE": "Redigér device profil", "CREATE-NEW-ORGANISATION": "Opret organisation", "EDIT-ORGANISATION": "Redigér organisation", "CREATE-NEW-PERMISSION": "Opret ny brugergruppe", "EDIT-PERMISSION": "Redigér brugergruppe", - "EDIT-SIGFOX-DEVICE-TYPE": "Redigér Sigfox Enhedstype", + "EDIT-SIGFOX-DEVICE-TYPE": "Redigér Sigfox enhedstype", "EDIT-SIGFOX-GROUPS": "Redigér Sigfox grupper", "EDIT-SIGFOX-GROUP": "Redigér Sigfox gruppe", "EDIT-USERS": "Redigér bruger", @@ -407,30 +407,30 @@ "GIVE-DATATARGET-AUTHORIZATIONHEADER": "Angiv authorization header", "GIVE-DATATARGET-URL": "Angiv datatargets URL", "GIVE-DATATARGET-URL-PLACEHOLDER": "https://datatarget.dk/data", - "GIVE-PAYLOADDECODER-NAME": "Navngiv Payload Decoder", - "GIVE-PAYLOADDECODER-NAME-PLACEHOLDER": "F.eks. Kloak sensor decoder", - "GIVE-PAYLOADDECODER-DECODINGFUNCTION": "Payload Decoder funktion", - "GIVE-PAYLOADDECODER-CHOOSE-APPLICATION": "Vælg Applikation", - "GIVE-PAYLOADDECODER-CHOOSE-DEVICE": "Vælg Enhed", + "GIVE-PAYLOADDECODER-NAME": "Navngiv payload decoder", + "GIVE-PAYLOADDECODER-NAME-PLACEHOLDER": "F.eks. kloak sensor decoder", + "GIVE-PAYLOADDECODER-DECODINGFUNCTION": "Payload decoder funktion", + "GIVE-PAYLOADDECODER-CHOOSE-APPLICATION": "Vælg applikation", + "GIVE-PAYLOADDECODER-CHOOSE-DEVICE": "Vælg enhed", "GIVE-PAYLOADDECODER-PAYLOAD": "Test med den valgte enheds payload", "GIVE-PAYLOADDECODER-METADATA": "Test med den valgte enheds metadata", "GIVE-PAYLOADDECODER-TESTBUTTON": "Test din kode", - "GIVE-PAYLOADDECODER-OUTPUT": "Test preview af Payload Decoder Output", + "GIVE-PAYLOADDECODER-OUTPUT": "Test preview af payload decoder Output", "GIVE-PAYLOADDECODER-METADATA-PLACEHOLDER": "// Vælg applikation og enhed først for at \n// teste din decoder funktion med metadata", "GIVE-PAYLOADDECODER-PAYLOAD-PLACEHOLDER": "// Vælg applikation og enhed først for at \n// teste din decoder funktion med payload data", "GIVE-PAYLOADDECODER-METADATA-ERRORMESSAGE": "// Kan ikke finde noget metadata på den pågældende enhed. \n// Det kan enten skyldes at vi ikke har modtaget noget fra enheden eller der er ikke blevet tilknyttet en devicemodel til enheden", "GIVE-PAYLOADDECODER-PAYLOAD-ERRORMESSAGE": "// Kan ikke finde noget payload på den pågældende enhed. \n// Det kan enten skyldes at vi ikke har modtaget noget fra enheden eller der er ikke blevet tilknyttet en devicemodel til enheden", - "GIVE-PAYLOADDECODER-OUTPUT-PLACEHOLDER": "// Tryk 'Test Koden' for at decode payload og metadata", + "GIVE-PAYLOADDECODER-OUTPUT-PLACEHOLDER": "// Tryk 'Test koden' for at decode payload og metadata", "GIVE-PAYLOADDECODER-METADATA-INVALID-JSON": "Det angivne JSON var ikke gyldigt i feltet device metadata", "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'", "OTAAAPPLICATIONKEY": "OTAA application key (AppKey)", - "OTAAAPPLICATIONKEY-PLACEHOLDER": "Indtast OTAA Application key", + "OTAAAPPLICATIONKEY-PLACEHOLDER": "Indtast OTAA application key", "DEVADDR": "Device adress", - "DEVADDR-PLACEHOLDER": "Indtast Device adress", + "DEVADDR-PLACEHOLDER": "Indtast device adress", "NETWORKSESSIONKEY": "Networksessionkey", - "NETWORKSESSIONKEY-PLACEHOLDER": "Indtast Network session key", + "NETWORKSESSIONKEY-PLACEHOLDER": "Indtast network session key", "APPLICATIONSESSIONKEY": "Applicationsessionkey", "APPLICATIONSESSIONKEY-PLACEHOLDER": "Indtast application session key", "FCNTUP": "fCntUp", @@ -448,7 +448,7 @@ "CHOOSE-SERVICEPROFILE": "Vælg service profile", "CHOOSE-DEVICE": "Vælg device profile", "OTAA-ACTIVATE": "Opret forbindelse vha. OTAA", - "ADD-RELATIONS": "Tilføj device og payload decoder", + "ADD-RELATIONS": "Tilføj IoT enhed og payload decoder", "DATATARGET": { "SELECT-PAYLOADDECODER": "Vælg payload decoder", "NO-PAYLOAD-DECODER-SELECTED": "Ingen payload decoder valgt", @@ -466,7 +466,7 @@ "DEVICETYPEID": "Vælg en Sigfox enhedstype", "DEVICETYPEID-PLACEHOLDER": "Enhedstype", "PAC": "PAC", - "PAC-PLACEHOLDER": "Porting Authorization Code", + "PAC-PLACEHOLDER": "Porting authorization code", "ENDPRODUCTCERTIFICATE": "Produkt certifikat", "ENDPRODUCTCERTIFICATE-PLACEHOLDER": "Produkt certifikat", "ENDPRODUCTCERTIFICATE-HELP-TITLE": "Hjælp til at finde produkt certifikatet", @@ -491,7 +491,7 @@ "GIVE-NAME": "Angiv navn", "GIVE-NAME-PLACEHOLDER": "F.eks. Elsys-tx3", "GIVE-ID": "Angiv unik identifikator (id)", - "GIVE-ID-PLACEHOLDER": "Angiv Id", + "GIVE-ID-PLACEHOLDER": "Angiv id", "GIVE-BRANDNAME": "Angiv mærket (brandName)", "GIVE-BRANDNAME-PLACEHOLDER": "Angiv mærket", "GIVE-MODELNAME": "Angiv model navnet (modelName)", @@ -508,13 +508,13 @@ } }, "QUESTION-LORA-GATEWAY": { - "NAME": "Navngiv LoRaWAN Gateway", - "NAME-PLACEHOLDER": "Navn på LoRaWAN Gateway", - "DESCRIPTION": "LoRaWAN Gateway beskrivelse", - "DESCRIPTION-PLACEHOLDER": "Beskrivelse af LoRaWAN Gateway", - "GATEWAYID": "Gateway ID", + "NAME": "Navngiv LoRaWAN gateway", + "NAME-PLACEHOLDER": "Navn på LoRaWAN gateway", + "DESCRIPTION": "LoRaWAN gateway beskrivelse", + "DESCRIPTION-PLACEHOLDER": "Beskrivelse af LoRaWAN gateway", + "GATEWAYID": "Gateway id", "GATEWAYID-PLACEHOLDER": "0000-0000-0000-0000", - "METADATA": "Gateway Tags", + "METADATA": "Gateway tags", "METADATA-PLACEHOLDER": "Angiv JSON her", "ALTITUDE": "Højde", "ALTITUDE-PLACEHOLDER": "00", @@ -540,7 +540,7 @@ }, "IOTDEVICE": { "HEADING": { - "PROTOCOL": "Transmission Protokol", + "PROTOCOL": "Transmission protokol", "BASIC": "Basis info", "OPTIONAL": "Yderligere info" }, @@ -552,7 +552,7 @@ "START": "Sæt downlink i kø", "ERROR-SIGFOX-LENGTH": "Fejl i payload længde - payload må ikke overskride 16 karaktere", "ERROR-FORMAT": "Fejl i formatet. Downlink skal være på hexidecimal format: a-f 0-9", - "DESCRIPTION": "Her kan du starte et downlink til dit device.", + "DESCRIPTION": "Her kan du starte et downlink til din enhed", "CONFIRMEDDOWNLINK": "Bekræft downlink", "DIALOG-TITLE": "Downlink", "DIALOG-MESSAGE": "Der ligger allerede et downlink i kø, vil du overskrive?", @@ -586,7 +586,7 @@ "APIKEY": "API kald", "INSTRUCTIONS": "Du kalder denne url:" }, - "LORAWANSETUP": "LoRaWAN Specifik opsætning", + "LORAWANSETUP": "LoRaWAN specifik opsætning", "LATEST-DATAPACKAGE": "Seneste datapakke sendt fra enheden:", "NO-DATAPACKAGE": "Der er ikke modtaget nogle datapakker", "DATAPACKAGE": "Datapakke", @@ -632,8 +632,8 @@ "ABP-INFO-MISSING": "ABP nøgle mangler eller er ikke gyldig." }, "PROFILES": { - "NAME": "LoRaWAN Profiler", - "DELETE-FAILED":"Slet Fejlede", + "NAME": "LoRaWAN profiler", + "DELETE-FAILED":"Slet fejlede", "SERVICE_PROFILE": { "CREATED":"Oprettet", "UPDATED":"Senest opdateret", @@ -641,7 +641,7 @@ "DELETE-FROM-LIST":"Slet", "HEADLINE": "Service profiler", "DROPDOWN": "Håndter service profil", - "CREATE-NEW-SERVICE-PROFILE": "Opret Service Profile", + "CREATE-NEW-SERVICE-PROFILE": "Opret service profile", "EDIT": "Rediger profil", "DELETE": "Slet profil", "NAME": "Service profil navn", @@ -662,8 +662,8 @@ "SAVE": "Gem", "CANCEL": "Annuller", "GOBACK": "Gå tilbage", - "ADDSERVICEPROFILE": "Redigér Service Profile", - "ID": "Service profilens ID" + "ADDSERVICEPROFILE": "Redigér service profile", + "ID": "Service profilens id" }, "DEVICE_PROFILE": { "CREATED":"Oprettet", @@ -672,13 +672,13 @@ "DELETE-FROM-LIST":"Slet", "HEADLINE": "Device profiler", "DROPDOWN": "Håndter device profil", - "CREATE-NEW-DEVICE-PROFILE": "Opret Device Profile", + "CREATE-NEW-DEVICE-PROFILE": "Opret device profile", "EDIT": "Rediger profil", "DELETE": "Slet profil", "NAME": "Device profil navn", "NAME_PLACEHOLDER": "Device profil navn", "MACVERSION": "LoRaWAN MAC version ", - "REGPARAMSREVISION": "LoRaWAN Regional Parameters revision ", + "REGPARAMSREVISION": "LoRaWAN regional parameters revision ", "MAXEIRP": "Max EIRP ", "GEOLOCBUFFERTTL": "Geolocation buffer TTL (seconds)", "GEOLOCMINBUFFERSIZE": "Geolocation minimum buffer size", @@ -710,17 +710,17 @@ "PINGSLOTDR_PLACEHOLDER": "0", "PINGSLOTFREQ_PLACEHOLDER": "0", "CLASSCTIMEOUT_PLACEHOLDER": "0", - "ID": "Device profilens ID", - "ORGANIZATION": "Tilhørende organisation" + "ID": "Device profilens id", + "ORGANIZATION": "Organisation" } }, "USERS": { "SAVE": "Gem", "NAME": "Navn", "EMAIL": "Email", - "GLOBALADMIN": "Global Administrator", + "GLOBALADMIN": "Global administrator", "STATUS": "Status", - "LASTLOG": "Sidste Login", + "LASTLOG": "Sidste login", "EDIT": "Redigér User", "CREATE": "Opret ny bruger", "LOGIN": "Sidst aktiv", @@ -762,7 +762,7 @@ "AUTH": { "EMAIL": "Email", "PASSWORD": "Adgangskode", - "KOMBIT-BUTTON": "Login med KOMBIT Adgangsstyring" + "KOMBIT-BUTTON": "Login med KOMBIT adgangsstyring" }, "DASHBOARD": { "WELCOME": "Velkommen til OS2iot – din nye indgang til administration af IoT enheder på tværs af netværkstyper.", @@ -805,7 +805,7 @@ "APPLICATION": "OS2IoT - Applikationer", "SIGFOX": "OS2IoT - Sigfox administration", "DEVICEMODEL": "OS2IoT - Device model", - "LORAWAN-GATEWAY": "OS2IoT - LoRaWAN Gateways", + "LORAWAN-GATEWAY": "OS2IoT - LoRaWAN gateways", "LORAWAN-PROFILE": "OS2IoT - LoRaWAN profiler", "PAYLOADDECODER": "OS2IoT - Payload decoder", "USER": "OS2IoT - Brugere", From a3f4b8b22f038d1de99c44f4c5179fa85aa71991 Mon Sep 17 00:00:00 2001 From: jeppeej Date: Thu, 17 Dec 2020 11:15:20 +0100 Subject: [PATCH 07/39] IOT-1168 - removed top bar for user with no permissions --- src/app/dashboard/dashboard.component.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html index f11bf10f..b6a3f8cf 100644 --- a/src/app/dashboard/dashboard.component.html +++ b/src/app/dashboard/dashboard.component.html @@ -1,5 +1,7 @@ Grandma's Heavy Metal Festival Journal - +
+ +
From 76c3780db2f79964a61a3e1acf9bbe530595e601 Mon Sep 17 00:00:00 2001 From: jeppeej Date: Thu, 17 Dec 2020 12:37:55 +0100 Subject: [PATCH 08/39] IOT-1167 - text changes --- src/app/dashboard/dashboard.component.ts | 4 ++-- src/assets/i18n/da.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index 3503f7a5..50b33f2d 100644 --- a/src/app/dashboard/dashboard.component.ts +++ b/src/app/dashboard/dashboard.component.ts @@ -29,13 +29,13 @@ export class DashboardComponent implements OnInit { ) { this.route.queryParams.subscribe(async (params) => { this.translate.use('da'); - await this.translate.get(['DASHBOARD.NO-JOB-ACCESS','TITLE.DASHBOARD', 'DASHBOARD.KOMBIT-LOGIN-ERROR','DASHBOARD.USER-INACTIVE']) + await this.translate.get(['DASHBOARD.NO-JOB-ACCESS','TITLE.FRONTPAGE', 'DASHBOARD.KOMBIT-LOGIN-ERROR','DASHBOARD.USER-INACTIVE']) .toPromise() .then(translations => { this.unauthorizedMessage = translations['DASHBOARD.NO-JOB-ACCESS']; this.kombitError = translations['DASHBOARD.KOMBIT-LOGIN-ERROR']; this.noAccess = translations['DASHBOARD.USER-INACTIVE']; - this.titleService.setTitle(translations['TITLE.DASHBOARD']); + this.titleService.setTitle(translations['TITLE.FRONTPAGE']); } ); // this is used when a user is returned from Kombit login diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 5ba2c09b..24bf71e6 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -814,7 +814,7 @@ "DATATARGET": "OS2IoT - Datatarget", "BULKIMPORT": "OS2IoT - Bulk import", "IOTDEVICE": "OS2IoT - IoT enhed", - "DASHBOARD": "OS2IoT - Dashboard" + "FRONTPAGE": "OS2IoT - Forside" }, "false": "Nej", From 44bbeadb69a43452e4b872dd151f76a90bb8b8c2 Mon Sep 17 00:00:00 2001 From: jeppeej Date: Thu, 17 Dec 2020 12:55:06 +0100 Subject: [PATCH 09/39] IOT-1169 - add read permission restrictions to downlink and edit sigfox groups --- .../iot-device-detail/iot-device-detail.component.html | 2 +- .../iot-device-detail/iot-device-detail.component.ts | 7 +++++-- .../sigfox-groups-list-item.component.html | 2 +- .../sigfox-groups-list-item.component.ts | 4 ++++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.html b/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.html index 9b0c3b2c..6e5b25b2 100644 --- a/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.html +++ b/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.html @@ -15,7 +15,7 @@ - +
-
+
diff --git a/src/app/sigfox/sigfox-groups-list/sigfox-groups-list-item/sigfox-groups-list-item.component.ts b/src/app/sigfox/sigfox-groups-list/sigfox-groups-list-item/sigfox-groups-list-item.component.ts index 39fb4c63..7f398981 100644 --- a/src/app/sigfox/sigfox-groups-list/sigfox-groups-list-item/sigfox-groups-list-item.component.ts +++ b/src/app/sigfox/sigfox-groups-list/sigfox-groups-list-item/sigfox-groups-list-item.component.ts @@ -2,6 +2,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { faEdit, faPen, faToolbox } from '@fortawesome/free-solid-svg-icons'; import { SigfoxGroup } from '@shared/models/sigfox-group.model'; +import { MeService } from '@shared/services/me.service'; @Component({ selector: 'app-sigfox-groups-list-item', @@ -12,14 +13,17 @@ export class SigfoxGroupsListItemComponent implements OnInit { faToolbox = faToolbox; faEdit = faEdit; @Input() sigfoxGroup: SigfoxGroup; + public canEdit = false; constructor( private route: ActivatedRoute, private router: Router, + private meService: MeService ) { } ngOnInit(): void { console.log(this.sigfoxGroup); + this.canEdit = this.meService.canWriteInTargetOrganization(); } onEditSigfoxGroup() { From 194a33e49425196fa69e7aaa75fffc906f6c2808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troels=20Beck=20Kr=C3=B8gh?= Date: Thu, 17 Dec 2020 15:55:36 +0100 Subject: [PATCH 10/39] Avoid overflow on frontpage since topbar is now included --- src/app/dashboard/dashboard.component.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/dashboard/dashboard.component.scss b/src/app/dashboard/dashboard.component.scss index 0aa62efd..641a5ec4 100644 --- a/src/app/dashboard/dashboard.component.scss +++ b/src/app/dashboard/dashboard.component.scss @@ -42,7 +42,7 @@ } } - height: 85vh; + height: 82vh; background-image: linear-gradient( to right bottom, rgba($color-primary, 0.8), @@ -54,9 +54,9 @@ position: relative; @supports (clip-path: polygon(0 0)) or (-webkit-clip-path: polygon(0 0)) { - -webkit-clip-path: polygon(0 0, 100% 0, 100% 75vh, 0 100%); - clip-path: polygon(0 0, 100% 0, 100% 75vh, 0 100%); - height: 90vh; + -webkit-clip-path: polygon(0 0, 100% 0, 100% 69vh, 0 100%); + clip-path: polygon(0 0, 100% 0, 100% 69vh, 0 100%); + height: 82vh; } &__logo-box { From aa3bdfde4b76d1720988bc2d48d55000a8680806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troels=20Beck=20Kr=C3=B8gh?= Date: Tue, 15 Dec 2020 12:16:33 +0100 Subject: [PATCH 11/39] IOT-1039: Only suggest to create organization if current user is global admin --- src/app/dashboard/dashboard.component.html | 2 +- src/app/dashboard/dashboard.component.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html index b6a3f8cf..1f651b2d 100644 --- a/src/app/dashboard/dashboard.component.html +++ b/src/app/dashboard/dashboard.component.html @@ -37,7 +37,7 @@

-
+
Start med at oprette en organisation diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index 50b33f2d..1c6916ae 100644 --- a/src/app/dashboard/dashboard.component.ts +++ b/src/app/dashboard/dashboard.component.ts @@ -17,6 +17,7 @@ export class DashboardComponent implements OnInit { noAccess: string; isLoadingResults = true; hasSomePermission: boolean; + isGlobalAdmin = false; constructor( private route: ActivatedRoute, @@ -60,6 +61,7 @@ export class DashboardComponent implements OnInit { await this.sharedVariableService.setOrganizationInfo(); this.userMinimalService.setUserMinimalList() this.hasSomePermission = this.sharedVariableService.getHasAnyPermission(); + this.isGlobalAdmin = this.sharedVariableService.isGlobalAdmin(); this.isLoadingResults = false; }); } From 8f3e68beaee7e6a70dbfd7b3a8fd47bbf14d9610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troels=20Beck=20Kr=C3=B8gh?= Date: Tue, 15 Dec 2020 13:48:00 +0100 Subject: [PATCH 12/39] IOT-1094: Add DeviceModelId and seperate LoRaWAN OTAA and ABP CSVs --- src/app/applications/bulk-import/bulk-import.component.ts | 7 ++++--- src/app/applications/bulk-import/bulkMapping.ts | 7 ++++--- src/assets/docs/iotdevice_generichttp.csv | 2 +- src/assets/docs/iotdevice_lorawan.csv | 1 - src/assets/docs/iotdevice_lorawan_abp.csv | 1 + src/assets/docs/iotdevice_lorawan_otaa.csv | 1 + src/assets/docs/iotdevice_sigfox.csv | 2 +- 7 files changed, 12 insertions(+), 9 deletions(-) delete mode 100644 src/assets/docs/iotdevice_lorawan.csv create mode 100644 src/assets/docs/iotdevice_lorawan_abp.csv create mode 100644 src/assets/docs/iotdevice_lorawan_otaa.csv diff --git a/src/app/applications/bulk-import/bulk-import.component.ts b/src/app/applications/bulk-import/bulk-import.component.ts index d96da6c4..b647cd2d 100644 --- a/src/app/applications/bulk-import/bulk-import.component.ts +++ b/src/app/applications/bulk-import/bulk-import.component.ts @@ -28,9 +28,10 @@ export class BulkImportComponent implements OnInit { faTrash = faTrash; faDownload = faDownload; samples = [ - { name: 'Generic-Http-sample.csv', url: '../../../assets/docs/iotdevice_generichttp.csv' }, - { name: 'Lorawan-sample.csv', url: '../../../assets/docs/iotdevice_lorawan.csv' }, - { name: 'Sigfox-sample.csv', url: '../../../assets/docs/iotdevice_sigfox.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' }, + { name: 'sigfox-sample.csv', url: '../../../assets/docs/iotdevice_sigfox.csv' }, ] download$: Observable; private bulkMapper = new BulkMapping(); diff --git a/src/app/applications/bulk-import/bulkMapping.ts b/src/app/applications/bulk-import/bulkMapping.ts index d941c95e..2c17540a 100644 --- a/src/app/applications/bulk-import/bulkMapping.ts +++ b/src/app/applications/bulk-import/bulkMapping.ts @@ -28,8 +28,8 @@ export class BulkMapping { applicationSessionKey: data.applicationSessionKey ? data.applicationSessionKey : undefined, serviceProfileID: data.serviceProfileID ? data.serviceProfileID : undefined, deviceProfileID: data.deviceProfileID ? data.deviceProfileID : undefined, - fCntUp: data.fCntUp ? data.fCntUp : undefined, - nFCntDown: data.nFCntDown ? data.nFCntDown : undefined, + fCntUp: data.fCntUp ? +data.fCntUp : undefined, + nFCntDown: data.nFCntDown ? +data.nFCntDown : undefined, deviceStatusBattery: undefined, deviceStatusMargin: undefined }; @@ -69,7 +69,8 @@ export class BulkMapping { createdBy: undefined, updatedBy: undefined, updatedByName: undefined, - createdByName: undefined + createdByName: undefined, + deviceModelId: data.deviceModelId }; } } diff --git a/src/assets/docs/iotdevice_generichttp.csv b/src/assets/docs/iotdevice_generichttp.csv index 19517b57..62b46d1e 100644 --- a/src/assets/docs/iotdevice_generichttp.csv +++ b/src/assets/docs/iotdevice_generichttp.csv @@ -1 +1 @@ -name,id,type,longitude,latitude,commentOnLocation,comment +name,id,type,longitude,latitude,commentOnLocation,comment,deviceModelId diff --git a/src/assets/docs/iotdevice_lorawan.csv b/src/assets/docs/iotdevice_lorawan.csv deleted file mode 100644 index 497ced0a..00000000 --- a/src/assets/docs/iotdevice_lorawan.csv +++ /dev/null @@ -1 +0,0 @@ -name,id,type,longitude,latitude,commentOnLocation,comment,devEUI,serviceProfileID,deviceProfileID,skipFCntCheck,activationType,OTAAapplicationKey,devAddr,networkSessionKey,applicationSessionKey,fCntUp,nFCntDown diff --git a/src/assets/docs/iotdevice_lorawan_abp.csv b/src/assets/docs/iotdevice_lorawan_abp.csv new file mode 100644 index 00000000..b17a871c --- /dev/null +++ b/src/assets/docs/iotdevice_lorawan_abp.csv @@ -0,0 +1 @@ +name,id,type,longitude,latitude,commentOnLocation,comment,deviceModelId,devEUI,serviceProfileID,deviceProfileID,skipFCntCheck,activationType,devAddr,networkSessionKey,applicationSessionKey,fCntUp,nFCntDown diff --git a/src/assets/docs/iotdevice_lorawan_otaa.csv b/src/assets/docs/iotdevice_lorawan_otaa.csv new file mode 100644 index 00000000..0ea36cf5 --- /dev/null +++ b/src/assets/docs/iotdevice_lorawan_otaa.csv @@ -0,0 +1 @@ +name,id,type,longitude,latitude,commentOnLocation,comment,deviceModelId,devEUI,serviceProfileID,deviceProfileID,skipFCntCheck,activationType,OTAAapplicationKey diff --git a/src/assets/docs/iotdevice_sigfox.csv b/src/assets/docs/iotdevice_sigfox.csv index 0b15110b..54720447 100644 --- a/src/assets/docs/iotdevice_sigfox.csv +++ b/src/assets/docs/iotdevice_sigfox.csv @@ -1 +1 @@ -name,id,type,longitude,latitude,commentOnLocation,comment,deviceId,deviceTypeId +name,id,type,longitude,latitude,commentOnLocation,comment,deviceModelId,deviceId,deviceTypeId From 2a44f6fcf142b0a799d0f6e980a769d195576c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troels=20Beck=20Kr=C3=B8gh?= Date: Wed, 16 Dec 2020 11:18:14 +0100 Subject: [PATCH 13/39] IOT-1151: Set OpenDataDK checkbox to checked to be remembered --- .../datatarget/opendatadk/opendatadk-dataset.model.ts | 1 + .../opendatadk-edit/opendatadk-edit.component.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/applications/datatarget/opendatadk/opendatadk-dataset.model.ts b/src/app/applications/datatarget/opendatadk/opendatadk-dataset.model.ts index 8328c5c7..36fe3b5c 100644 --- a/src/app/applications/datatarget/opendatadk/opendatadk-dataset.model.ts +++ b/src/app/applications/datatarget/opendatadk/opendatadk-dataset.model.ts @@ -1,4 +1,5 @@ export class OpenDataDkDataset { + id?: number; name: string; //reqired - max 120 resourceTitle: string; description: string; diff --git a/src/app/applications/datatarget/opendatadk/opendatadk-edit/opendatadk-edit.component.ts b/src/app/applications/datatarget/opendatadk/opendatadk-edit/opendatadk-edit.component.ts index b45e689f..8ec2e7a2 100644 --- a/src/app/applications/datatarget/opendatadk/opendatadk-edit/opendatadk-edit.component.ts +++ b/src/app/applications/datatarget/opendatadk/opendatadk-edit/opendatadk-edit.component.ts @@ -4,18 +4,18 @@ import { OpenDataDkDataset } from '../opendatadk-dataset.model'; @Component({ selector: 'app-opendatadk-edit', templateUrl: './opendatadk-edit.component.html', - styleUrls: ['./opendatadk-edit.component.scss'] + styleUrls: ['./opendatadk-edit.component.scss'], }) export class OpendatadkEditComponent implements OnInit { - public errorMessages: any; @Input() errorFields: string[]; @Input() formFailedSubmit = false; @Input() openDataDk: OpenDataDkDataset; - constructor() { } + constructor() {} ngOnInit(): void { + // Set box to checked, if this already exists. + this.openDataDk.acceptTerms = this.openDataDk.id != null; } - } From 755aac8a40e96a0bd9ff93163d9cb1ff9f92057b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troels=20Beck=20Kr=C3=B8gh?= Date: Wed, 16 Dec 2020 12:35:00 +0100 Subject: [PATCH 14/39] IOT-1161: Datatarget details page add links to payload decoders and iotdevice in use --- .../datatarget-detail/datatarget-detail.component.html | 6 ++++-- .../datatarget-detail/datatarget-detail.component.ts | 4 ---- 2 files changed, 4 insertions(+), 6 deletions(-) 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 869f168e..0fbea839 100644 --- a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.html +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.html @@ -47,7 +47,7 @@

{{ 'DATATARGET.RELATIONS' | translate }}

{{'DATATARGET.PAYLOADEDECODER' | translate}} - {{relation.payloadDecoder.name}} + {{relation.payloadDecoder.name}} {{ 'DATATARGET.NO-PAYLOADDECODER' | translate}} @@ -59,7 +59,9 @@

{{ 'DATATARGET.RELATIONS' | translate }}

{{'DATATARGET.IOTDEVICE' | translate}} - {{getJoinedDeviceNames(relation.iotDevices)}} + + , {{device.name}} +

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 8749d0f6..7af25889 100644 --- a/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts +++ b/src/app/applications/datatarget/datatarget-detail/datatarget-detail.component.ts @@ -82,10 +82,6 @@ export class DatatargetDetailComponent implements OnInit, OnDestroy { ); } - getJoinedDeviceNames(element: IotDevice[]): string { - return element.map(device => device.name).join(', '); - } - getDatatargetRelations(id: number) { this.datatargetRelationServicer.getByDataTarget(id) .subscribe((response) => { From 8a2961411f4e3dea07bc64682e815f48580205b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troels=20Beck=20Kr=C3=B8gh?= Date: Wed, 16 Dec 2020 15:52:43 +0100 Subject: [PATCH 15/39] IOT-1147: Search for users and applications in permissions --- src/app/admin/admin.module.ts | 4 +- .../permission-edit.component.html | 43 +-- .../permission-edit.component.ts | 101 ++++- src/app/app.module.ts | 2 + .../downlink-dialog.component.ts | 10 +- .../mat-select-search.module.ts | 13 + .../mat-select-search.component.html | 15 + .../mat-select-search.component.scss | 48 +++ .../mat-select-search.component.ts | 361 ++++++++++++++++++ src/assets/i18n/da.json | 2 +- src/assets/scss/setup/_variables.scss | 8 + 11 files changed, 567 insertions(+), 40 deletions(-) create mode 100644 src/app/shared/components/mat-select-search/mat-select-search.module.ts create mode 100644 src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.html create mode 100644 src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.scss create mode 100644 src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.ts diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index 0281bb3a..c7c52faf 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -27,6 +27,7 @@ import { UserTableComponent } from './users/user-list/user-table/user-table.comp import { UsersComponent } from './users/users.component'; import { MatSelectModule } from '@angular/material/select'; import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectSearchModule } from '@shared/components/mat-select-search/mat-select-search.module'; @NgModule({ declarations: [ @@ -60,6 +61,7 @@ import { MatFormFieldModule } from '@angular/material/form-field'; SharedModule, MatSelectModule, MatFormFieldModule, + MatSelectSearchModule, ], exports: [ UserDetailComponent, @@ -79,4 +81,4 @@ import { MatFormFieldModule } from '@angular/material/form-field'; OrganisationListComponent, ], }) -export class AdminModule { } +export class AdminModule {} diff --git a/src/app/admin/permission/permission-edit/permission-edit.component.html b/src/app/admin/permission/permission-edit/permission-edit.component.html index 7271c86f..513afb75 100644 --- a/src/app/admin/permission/permission-edit/permission-edit.component.html +++ b/src/app/admin/permission/permission-edit/permission-edit.component.html @@ -31,19 +31,16 @@
- - - - {{user.name}} ({{user.email}}) - + + + + + {{user.name}} + - +
@@ -61,26 +58,28 @@
-
-
+
+
- + + + + {{app.name}} + +
-
+
- + {{'PERMISSION.EDIT.ADD-APPLICATION-ON-CREATE' | translate}}
diff --git a/src/app/admin/permission/permission-edit/permission-edit.component.ts b/src/app/admin/permission/permission-edit/permission-edit.component.ts index 55a94048..169eb06e 100644 --- a/src/app/admin/permission/permission-edit/permission-edit.component.ts +++ b/src/app/admin/permission/permission-edit/permission-edit.component.ts @@ -1,9 +1,9 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; -import { FormGroup } from '@angular/forms'; +import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; -import { Subscription } from 'rxjs'; +import { ReplaySubject, Subject, Subscription } from 'rxjs'; import { Location } from '@angular/common'; import { PermissionService } from '../permission.service'; import { PermissionRequest, PermissionType } from '../permission.model'; @@ -15,13 +15,14 @@ import { ApplicationService } from '@applications/application.service'; import { Application } from '@applications/application.model'; import { BackButton } from '@shared/models/back-button.model'; import { ErrorMessageService } from '@shared/error-message.service'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-permission-edit', templateUrl: './permission-edit.component.html', styleUrls: ['./permission-edit.component.scss'], }) -export class PermissionEditComponent implements OnInit { +export class PermissionEditComponent implements OnInit, OnDestroy { permission = new PermissionRequest(); public organisations: OrganisationResponse[]; public users: UserResponse[]; @@ -31,7 +32,10 @@ export class PermissionEditComponent implements OnInit { public errorFields: string[]; public formFailedSubmit = false; public form: FormGroup; - public backButton: BackButton = { label: '', routerLink: ['admin','permissions'] }; + public backButton: BackButton = { + label: '', + routerLink: ['admin', 'permissions'], + }; public title = ''; public submitButton = ''; public isEditMode = false; @@ -41,6 +45,18 @@ export class PermissionEditComponent implements OnInit { userSubscription: Subscription; applicationSubscription: Subscription; + public userMultiCtrl: FormControl = new FormControl(); + public userMultiFilterCtrl: FormControl = new FormControl(); + public filteredUsersMulti: ReplaySubject = new ReplaySubject< + UserResponse[] + >(1); + + public applicationMultiCtrl: FormControl = new FormControl(); + public applicationMultiFilterCtrl: FormControl = new FormControl(); + public filteredApplicationsMulti: ReplaySubject< + Application[] + > = new ReplaySubject(1); + constructor( private translate: TranslateService, private route: ActivatedRoute, @@ -69,6 +85,56 @@ export class PermissionEditComponent implements OnInit { this.isEditMode = true; this.setBackButton(); } + + this.userMultiFilterCtrl.valueChanges + .pipe(takeUntil(this._onDestroy)) + .subscribe(() => { + this.filterUsersMulti(); + }); + + this.applicationMultiFilterCtrl.valueChanges + .pipe(takeUntil(this._onDestroy)) + .subscribe(() => { + this.filterApplicationsMulti(); + }); + } + + private filterApplicationsMulti() { + if (!this.applications) { + return; + } + // get the search keyword + let search = this.applicationMultiFilterCtrl.value; + if (!search) { + this.filteredApplicationsMulti.next(this.applications.slice()); + return; + } else { + search = search.toLowerCase(); + } + // filter the banks + this.filteredApplicationsMulti.next( + this.applications.filter( + (app) => app.name.toLowerCase().indexOf(search) > -1 + ) + ); + } + + private filterUsersMulti() { + if (!this.users) { + return; + } + // get the search keyword + let search = this.userMultiFilterCtrl.value; + if (!search) { + this.filteredUsersMulti.next(this.users.slice()); + return; + } else { + search = search.toLowerCase(); + } + // filter the banks + this.filteredUsersMulti.next( + this.users.filter((user) => user.name.toLowerCase().indexOf(search) > -1) + ); } private setBackButton() { @@ -92,6 +158,7 @@ export class PermissionEditComponent implements OnInit { this.userSubscription = this.userService.getMultiple().subscribe( (users) => { this.users = users.data; + this.filteredUsersMulti.next(this.users.slice()); }, (error: HttpErrorResponse) => { this.showError(error); @@ -113,6 +180,7 @@ export class PermissionEditComponent implements OnInit { .subscribe( (res) => { this.applications = res.data; + this.filteredApplicationsMulti.next(this.applications.slice()); }, (error: HttpErrorResponse) => { this.showError(error); @@ -127,7 +195,9 @@ export class PermissionEditComponent implements OnInit { this.permission.name = response.name; this.permission.level = response.type; this.permission.userIds = response.users.map((x) => x.id); - this.permission.automaticallyAddNewApplications = response.automaticallyAddNewApplications; + this.userMultiCtrl.setValue(this.permission.userIds); + this.permission.automaticallyAddNewApplications = + response.automaticallyAddNewApplications; if (response.type !== PermissionType.GlobalAdmin) { this.permission.organizationId = response?.organization?.id; @@ -141,6 +211,7 @@ export class PermissionEditComponent implements OnInit { this.permission.applicationIds = response.applications.map( (x) => x.id ); + this.applicationMultiCtrl.setValue(this.permission.applicationIds); } }, (error: HttpErrorResponse) => { @@ -199,17 +270,19 @@ export class PermissionEditComponent implements OnInit { } } - isOrganizationAdministrationPermission() { + isOrganizationApplicationPermission() { return ( this.permission.level == PermissionType.OrganizationApplicationPermissions || - this.permission.level == PermissionType.Write || - this.permission.level == PermissionType.Read + this.isReadOrWrite() ); } isReadOrWrite(): boolean { - return this.permission.level === PermissionType.Read || this.permission.level === PermissionType.Write + return ( + this.permission.level === PermissionType.Read || + this.permission.level === PermissionType.Write + ); } onSubmit(): void { @@ -229,4 +302,12 @@ export class PermissionEditComponent implements OnInit { routeBack(): void { this.location.back(); } + + /** Subject that emits when the component has been destroyed. */ + private _onDestroy = new Subject(); + + ngOnDestroy() { + this._onDestroy.next(); + this._onDestroy.complete(); + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e26b25f3..d3da8671 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -20,6 +20,7 @@ import { ErrorPageComponent } from './error-page/error-page.component'; import { SearchModule } from './search/search.module'; import { JwtModule } from '@auth0/angular-jwt'; import { MonacoEditorModule } from 'ngx-monaco-editor'; +import { MatInputModule } from '@angular/material/input'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); @@ -58,6 +59,7 @@ export function tokenGetter() { GatewayModule, SearchModule, HttpClientModule, + MatInputModule, JwtModule.forRoot({ config: { tokenGetter diff --git a/src/app/applications/iot-devices/iot-device-detail/downlink/downlink-dialog/downlink-dialog.component.ts b/src/app/applications/iot-devices/iot-device-detail/downlink/downlink-dialog/downlink-dialog.component.ts index ca05c76c..37c6355b 100644 --- a/src/app/applications/iot-devices/iot-device-detail/downlink/downlink-dialog/downlink-dialog.component.ts +++ b/src/app/applications/iot-devices/iot-device-detail/downlink/downlink-dialog/downlink-dialog.component.ts @@ -1,19 +1,18 @@ -import { Component, OnInit, Output } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; -import { EventEmitter } from 'protractor'; import { DownlinkComponent } from '../downlink.component'; @Component({ selector: 'app-downlink-dialog', templateUrl: './downlink-dialog.component.html', - styleUrls: ['./downlink-dialog.component.scss'] + styleUrls: ['./downlink-dialog.component.scss'], }) export class DownlinkDialogComponent implements OnInit { - constructor( private translate: TranslateService, - public dialog: MatDialogRef) {} + public dialog: MatDialogRef + ) {} ngOnInit(): void { this.translate.use('da'); @@ -22,5 +21,4 @@ export class DownlinkDialogComponent implements OnInit { cancel() { this.dialog.close(); } - } diff --git a/src/app/shared/components/mat-select-search/mat-select-search.module.ts b/src/app/shared/components/mat-select-search/mat-select-search.module.ts new file mode 100644 index 00000000..fdccd6b7 --- /dev/null +++ b/src/app/shared/components/mat-select-search/mat-select-search.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { MatSelectSearchComponent } from './mat-select-search/mat-select-search.component'; +import { CommonModule } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; + +@NgModule({ + imports: [CommonModule, MatButtonModule, MatIconModule, MatInputModule], + declarations: [MatSelectSearchComponent], + exports: [MatButtonModule, MatInputModule, MatSelectSearchComponent], +}) +export class MatSelectSearchModule {} diff --git a/src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.html b/src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.html new file mode 100644 index 00000000..3c3b7807 --- /dev/null +++ b/src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.html @@ -0,0 +1,15 @@ + + +
+ + +
+ +
+ {{noEntriesFoundLabel}} +
\ No newline at end of file diff --git a/src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.scss b/src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.scss new file mode 100644 index 00000000..adb9957b --- /dev/null +++ b/src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.scss @@ -0,0 +1,48 @@ +$mat-menu-side-padding: 16px !default; +$scrollbar-width: 17px; +$clear-button-width: 20px; +$multiple-check-width: 33px; + +.mat-select-search-hidden { + visibility: hidden; +} +.mat-select-search-inner { + position: absolute; + top: 0; + width: calc(100% + #{2 * $mat-menu-side-padding - $scrollbar-width}); + border-bottom: 1px solid #cccccc; + background: white; + z-index: 100; + &.mat-select-search-inner-multiple { + width: calc( + 100% + #{2 * $mat-menu-side-padding - $scrollbar-width + + $multiple-check-width} + ); + } +} + +.mat-select-search-panel { + /* allow absolute positioning relative to outer options container */ + transform: none !important; + max-height: 350px; + overflow: hidden; +} + +.mat-select-search-input { + padding: $mat-menu-side-padding; + padding-right: $mat-menu-side-padding + $clear-button-width; + box-sizing: border-box; +} +.mat-select-search-no-entries-found { + padding: $mat-menu-side-padding; +} +.mat-select-search-clear { + position: absolute; + right: 0; + top: 4px; +} + +.cdk-overlay-pane-select-search { + /* correct offsetY so that the selected option is at the position of the select box when opening */ + margin-top: -50px; +} diff --git a/src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.ts b/src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.ts new file mode 100644 index 00000000..e7aaee88 --- /dev/null +++ b/src/app/shared/components/mat-select-search/mat-select-search/mat-select-search.component.ts @@ -0,0 +1,361 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + forwardRef, + Inject, + Input, + OnDestroy, + OnInit, + QueryList, + ViewChild, +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { MatOption } from '@angular/material/core'; +import { Subject } from 'rxjs'; +import { take, takeUntil } from 'rxjs/operators'; +import { MatSelect} from '@angular/material/select'; + +/* tslint:disable:member-ordering */ +/** + * Component providing an input field for searching MatSelect options. + * + * Example usage: + * + * interface Bank { + * id: string; + * name: string; + * } + * + * @Component({ + * selector: 'my-app-data-selection', + * template: ` + * + * + * + * + * {{bank.name}} + * + * + * + * ` + * }) + * export class DataSelectionComponent implements OnInit, OnDestroy { + * + * // control for the selected bank + * public bankCtrl: FormControl = new FormControl(); + * // control for the MatSelect filter keyword + * public bankFilterCtrl: FormControl = new FormControl(); + * + * // list of banks + * private banks: Bank[] = [{name: 'Bank A', id: 'A'}, {name: 'Bank B', id: 'B'}, {name: 'Bank C', id: 'C'}]; + * // list of banks filtered by search keyword + * public filteredBanks: ReplaySubject = new ReplaySubject(1); + * + * // Subject that emits when the component has been destroyed. + * private _onDestroy = new Subject(); + * + * + * ngOnInit() { + * // load the initial bank list + * this.filteredBanks.next(this.banks.slice()); + * // listen for search field value changes + * this.bankFilterCtrl.valueChanges + * .pipe(takeUntil(this._onDestroy)) + * .subscribe(() => { + * this.filterBanks(); + * }); + * } + * + * ngOnDestroy() { + * this._onDestroy.next(); + * this._onDestroy.complete(); + * } + * + * private filterBanks() { + * if (!this.banks) { + * return; + * } + * + * // get the search keyword + * let search = this.bankFilterCtrl.value; + * if (!search) { + * this.filteredBanks.next(this.banks.slice()); + * return; + * } else { + * search = search.toLowerCase(); + * } + * + * // filter the banks + * this.filteredBanks.next( + * this.banks.filter(bank => bank.name.toLowerCase().indexOf(search) > -1) + * ); + * } + * } + */ +@Component({ + selector: 'mat-select-search', + templateUrl: './mat-select-search.component.html', + styleUrls: ['./mat-select-search.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MatSelectSearchComponent), + multi: true, + }, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MatSelectSearchComponent + implements OnInit, OnDestroy, AfterViewInit, ControlValueAccessor { + /** Label of the search placeholder */ + @Input() placeholderLabel = 'Søg efter ...'; + + /** Label to be shown when no entries are found. Set to null if no message should be shown. */ + @Input() noEntriesFoundLabel = 'Ingen elementer blev fundet'; + + /** Reference to the search input field */ + @ViewChild('searchSelectInput', { static: false, read: ElementRef }) + searchSelectInput: ElementRef; + + /** Current search value */ + get value(): string { + return this._value; + } + private _value: string; + + onChange: Function = (_: any) => {}; + onTouched: Function = (_: any) => {}; + + /** Reference to the MatSelect options */ + public _options: QueryList; + + /** Previously selected values when using */ + private previousSelectedValues: any[]; + + /** Whether the backdrop class has been set */ + private overlayClassSet = false; + + /** Event that emits when the current value changes */ + private change = new EventEmitter(); + + /** Subject that emits when the component has been destroyed. */ + private _onDestroy = new Subject(); + + constructor( + @Inject(MatSelect) public matSelect: MatSelect, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit() { + // set custom panel class + const panelClass = 'mat-select-search-panel'; + if (this.matSelect.panelClass) { + if (Array.isArray(this.matSelect.panelClass)) { + this.matSelect.panelClass.push(panelClass); + } else if (typeof this.matSelect.panelClass === 'string') { + this.matSelect.panelClass = [this.matSelect.panelClass, panelClass]; + } else if (typeof this.matSelect.panelClass === 'object') { + this.matSelect.panelClass[panelClass] = true; + } + } else { + this.matSelect.panelClass = panelClass; + } + + // when the select dropdown panel is opened or closed + this.matSelect.openedChange + .pipe(takeUntil(this._onDestroy)) + .subscribe((opened) => { + if (opened) { + // focus the search field when opening + this._focus(); + } else { + // clear it when closing + this._reset(); + } + }); + + // set the first item active after the options changed + this.matSelect.openedChange + .pipe(take(1)) + .pipe(takeUntil(this._onDestroy)) + .subscribe(() => { + this._options = this.matSelect.options; + this._options.changes.pipe(takeUntil(this._onDestroy)).subscribe(() => { + const keyManager = this.matSelect._keyManager; + if (keyManager && this.matSelect.panelOpen) { + // avoid "expression has been changed" error + setTimeout(() => { + keyManager.setFirstItemActive(); + }); + } + }); + }); + + // detect changes when the input changes + this.change.pipe(takeUntil(this._onDestroy)).subscribe(() => { + this.changeDetectorRef.detectChanges(); + }); + + this.initMultipleHandling(); + } + + ngOnDestroy() { + this._onDestroy.next(); + this._onDestroy.complete(); + } + + ngAfterViewInit() { + this.setOverlayClass(); + } + + /** + * Handles the key down event with MatSelect. + * Allows e.g. selecting with enter key, navigation with arrow keys, etc. + * @param {KeyboardEvent} event + * @private + */ + _handleKeydown(event: KeyboardEvent) { + if (event.keyCode === 32) { + // do not propagate spaces to MatSelect, as this would select the currently active option + event.stopPropagation(); + } + } + + writeValue(value: string) { + const valueChanged = value !== this._value; + if (valueChanged) { + this._value = value; + this.change.emit(value); + } + } + + onInputChange(value) { + const valueChanged = value !== this._value; + if (valueChanged) { + this._value = value; + this.onChange(value); + this.change.emit(value); + } + } + + onBlur(value: string) { + this.writeValue(value); + this.onTouched(); + } + + registerOnChange(fn: Function) { + this.onChange = fn; + } + + registerOnTouched(fn: Function) { + this.onTouched = fn; + } + + /** + * Focuses the search input field + * @private + */ + public _focus() { + if (!this.searchSelectInput) { + return; + } + // save and restore scrollTop of panel, since it will be reset by focus() + // note: this is hacky + const panel = this.matSelect.panel.nativeElement; + const scrollTop = panel.scrollTop; + + // focus + this.searchSelectInput.nativeElement.focus(); + + panel.scrollTop = scrollTop; + } + + /** + * Resets the current search value + * @param {boolean} focus whether to focus after resetting + * @private + */ + public _reset(focus?: boolean) { + if (!this.searchSelectInput) { + return; + } + this.searchSelectInput.nativeElement.value = ''; + this.onInputChange(''); + if (focus) { + this._focus(); + } + } + + /** + * Sets the overlay class to correct offsetY + * so that the selected option is at the position of the select box when opening + */ + private setOverlayClass() { + if (this.overlayClassSet) { + return; + } + const overlayClass = 'cdk-overlay-pane-select-search'; + + this.matSelect.overlayDir.attach + .pipe(takeUntil(this._onDestroy)) + .subscribe(() => { + // note: this is hacky, but currently there is no better way to do this + this.searchSelectInput.nativeElement.parentElement.parentElement.parentElement.parentElement.parentElement.classList.add( + overlayClass + ); + }); + + this.overlayClassSet = true; + } + + /** + * Initializes handling + * Note: to improve this code, mat-select should be extended to allow disabling resetting the selection while filtering. + */ + private initMultipleHandling() { + // if + // store previously selected values and restore them when they are deselected + // because the option is not available while we are currently filtering + this.matSelect.valueChange + .pipe(takeUntil(this._onDestroy)) + .subscribe((values) => { + if (this.matSelect.multiple) { + let restoreSelectedValues = false; + if ( + this._value && + this._value.length && + this.previousSelectedValues && + Array.isArray(this.previousSelectedValues) + ) { + if (!values || !Array.isArray(values)) { + values = []; + } + const optionValues = this.matSelect.options.map( + (option) => option.value + ); + this.previousSelectedValues.forEach((previousValue) => { + if ( + values.indexOf(previousValue) === -1 && + optionValues.indexOf(previousValue) === -1 + ) { + // if a value that was selected before is deselected and not found in the options, it was deselected + // due to the filtering, so we restore it. + values.push(previousValue); + restoreSelectedValues = true; + } + }); + } + + if (restoreSelectedValues) { + this.matSelect._onChange(values); + } + + this.previousSelectedValues = values; + } + }); + } +} diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 24bf71e6..7ec78e8e 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -342,7 +342,7 @@ "USERS-PLACEHOLDER": "Brugere", "ORG": "Vælg en organisation", "ORG-PLACEHOLDER": "Organisation", - "APPS": "Vælg en applikation", + "APPS": "Vælg en eller flere applikationer", "APPS-PLACEHOLDER": "Applikationer", "ADD-APPLICATION-ON-CREATE": "Skal nye applikationer automatisk tilføjes til brugergruppen", "ADD-APPLICATION-AUTOMATIC": "Automatisk tilføjelse af applikationer til brugergruppen" diff --git a/src/assets/scss/setup/_variables.scss b/src/assets/scss/setup/_variables.scss index d01472a8..4abaac4b 100644 --- a/src/assets/scss/setup/_variables.scss +++ b/src/assets/scss/setup/_variables.scss @@ -374,3 +374,11 @@ $orange-outline: rgba(244, 121, 47, 0.6); $red-light: #f8e7e9; $red: #dc3545; + +.overflow-x-hidden { + overflow-x: hidden !important; +} + +.overflow-y-hidden { + overflow-y: hidden !important; +} From 99ca6cce76dc9898289f4e5c0b52276a1ed3d163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troels=20Beck=20Kr=C3=B8gh?= Date: Thu, 17 Dec 2020 12:19:37 +0100 Subject: [PATCH 16/39] IOT-1157: Gateway table pagination client side --- .../gateway-table.component.html | 21 +- .../gateway-table/gateway-table.component.ts | 214 ++++++++---------- .../shared/helpers/table-sorting.helper.ts | 5 +- src/assets/scss/setup/_variables.scss | 6 + 4 files changed, 120 insertions(+), 126 deletions(-) diff --git a/src/app/gateway/gateway-table/gateway-table.component.html b/src/app/gateway/gateway-table/gateway-table.component.html index e47a2c15..4f6086d0 100644 --- a/src/app/gateway/gateway-table/gateway-table.component.html +++ b/src/app/gateway/gateway-table/gateway-table.component.html @@ -2,11 +2,11 @@
-
+
- + @@ -26,7 +26,8 @@ - + @@ -36,14 +37,14 @@ -
+ {{ 'LORA-GATEWAY-TABLE.NAME' | translate }} @@ -18,7 +18,7 @@ - {{ 'LORA-GATEWAY-TABLE.GATEWAYID' | translate }}{{ 'LORA-GATEWAY-TABLE.GATEWAYID' | translate }} {{ element.id }} {{ 'LORA-GATEWAY-TABLE.ORGANIZATION' | translate }}{{ 'LORA-GATEWAY-TABLE.ORGANIZATION' | translate }} + {{ element.internalOrganizationName }} {{ 'LORA-GATEWAY-TABLE.LOCATION' | translate }} - {{ element.location.longitude | number:'2.1-6' }}, - {{ element.location.latitude | number:'2.1-6' }} + {{ element.location.latitude | number:'2.1-6' }}, + {{ element.location.longitude | number:'2.1-6' }} + {{ 'LORA-GATEWAY-TABLE.LAST-SEEN-AT' | translate }} @@ -53,7 +54,7 @@ - + {{ 'LORA-GATEWAY-TABLE.STATUS' | translate }} @@ -78,8 +79,8 @@