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 8af078044..9a1424abe 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 @@ -1,6 +1,6 @@
+ [dropDownButton]="dropdownButton" (deleteSelectedInDropdown)="clickDelete()" (extraDropdownOptions)="clickExtraDropdownOption($event)"> diff --git a/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.ts b/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.ts index 2142e5375..e70572fbc 100644 --- a/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.ts +++ b/src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.ts @@ -11,7 +11,7 @@ import { Subscription } from 'rxjs'; import { Downlink } from '../downlink.model'; import { IotDevice, IoTDeviceStatsResponse } from '../iot-device.model'; import { IoTDeviceService } from '../iot-device.service'; -import { DropdownButton } from '@shared/models/dropdown-button.model'; +import { DropdownButton, ExtraDropdownOption } from '@shared/models/dropdown-button.model'; import { Title } from '@angular/platform-browser'; import { MeService } from '@shared/services/me.service'; import { MatTabChangeEvent } from '@angular/material/tabs'; @@ -72,6 +72,12 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy { public dropdownButton: DropdownButton; public canStartDownlink = false; + private resetApiKeyId = 'RESET-API-KEY'; + private resetApiKeyOption: ExtraDropdownOption; + private resetApiKeyBody: string; + private resetApiKeyConfirm: string; + private resetApiKeyCancel: string; + public genericHttpDeviceUrl: string; private hasFetchedDeviceStats = false; dataRateChartData: ChartConfiguration['data'] = { datasets: [] }; @@ -79,6 +85,7 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy { snrChartData: ChartConfiguration['data'] = { datasets: [] }; rssiChartOptions = defaultChartOptions; snrChartOptions: typeof defaultChartOptions = defaultChartOptions; + dataRateChartOptions: typeof defaultChartOptions = { ...defaultChartOptions, scales: { @@ -102,7 +109,7 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy { private deleteDialogService: DeleteDialogService, private dialog: MatDialog, private titleService: Title, - private meService: MeService + private meService: MeService, ) { } ngOnInit(): void { @@ -118,12 +125,31 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy { }; } - this.translate.get(['NAV.APPLICATIONS', 'IOTDEVICE-TABLE-ROW.SHOW-OPTIONS', 'TITLE.IOTDEVICE']) - .subscribe(translations => { - this.backButton.label = translations['NAV.APPLICATIONS']; - this.dropdownButton.label = translations['IOTDEVICE-TABLE-ROW.SHOW-OPTIONS']; - this.titleService.setTitle(translations['TITLE.IOTDEVICE']); - }); + this.translate + .get([ + 'NAV.APPLICATIONS', + 'IOTDEVICE-TABLE-ROW.SHOW-OPTIONS', + 'TITLE.IOTDEVICE', + 'IOTDEVICE-TABLE-ROW.RESET-API-KEY', + 'IOTDEVICE.GENERIC_HTTP.RESET-API-KEY', + 'GEN.CANCEL' + ]) + .subscribe((translations) => { + this.backButton.label = translations['NAV.APPLICATIONS']; + this.dropdownButton.label = + translations['IOTDEVICE-TABLE-ROW.SHOW-OPTIONS']; + this.titleService.setTitle(translations['TITLE.IOTDEVICE']); + + this.resetApiKeyOption = { + id: this.resetApiKeyId, + label: translations['IOTDEVICE-TABLE-ROW.RESET-API-KEY'], + }; + this.resetApiKeyBody = translations['IOTDEVICE.GENERIC_HTTP.RESET-API-KEY']['BODY']; + this.resetApiKeyConfirm = translations['IOTDEVICE.GENERIC_HTTP.RESET-API-KEY']['YESRESET']; + this.resetApiKeyCancel = translations['GEN.CANCEL']; + }); + + this.dropdownButton.extraOptions = []; } bindIoTDeviceAndApplication(deviceId: number) { @@ -135,6 +161,13 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy { this.longitude = this.device.location.coordinates[0]; this.latitude = this.device.location.coordinates[1]; } + + if ( + this.meService.canWriteInTargetOrganization() && + this.device.type === DeviceType.GENERIC_HTTP + ) { + this.dropdownButton.extraOptions.push(this.resetApiKeyOption); + } }); } @@ -287,4 +320,28 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy { this.deleteDialogSubscription.unsubscribe(); } } + + clickExtraDropdownOption(id: string) { + if (id === this.resetApiKeyId) { + this.deleteDialogService + .showSimpleDialog( + this.resetApiKeyBody, + true, + true, + false, + '', + this.resetApiKeyConfirm, + this.resetApiKeyCancel + ) + .subscribe((isConfirmed) => { + if (isConfirmed) { + this.iotDeviceService + .resetHttpDeviceApiKey(this.device.id) + .subscribe((response) => { + this.device.apiKey = response.apiKey; + }); + } + }); + } + } } diff --git a/src/app/applications/iot-devices/iot-device.service.ts b/src/app/applications/iot-devices/iot-device.service.ts index 3119744e5..99eb12af2 100644 --- a/src/app/applications/iot-devices/iot-device.service.ts +++ b/src/app/applications/iot-devices/iot-device.service.ts @@ -78,4 +78,8 @@ export class IoTDeviceService { getDeviceStats(id: number): Observable { return this.restService.get(`${this.BASEURL}/stats`, null, id); } + + resetHttpDeviceApiKey(id: number): Observable> { + return this.restService.put(`${this.BASEURL}/resetHttpDeviceApiKey`, null, id); + } } diff --git a/src/app/shared/components/delete-dialog/delete-dialog.component.html b/src/app/shared/components/delete-dialog/delete-dialog.component.html index d431a0d5f..e16eb66e3 100644 --- a/src/app/shared/components/delete-dialog/delete-dialog.component.html +++ b/src/app/shared/components/delete-dialog/delete-dialog.component.html @@ -5,12 +5,12 @@

{{dialogModel.infoTitle | transl

-
\ No newline at end of file + diff --git a/src/app/shared/components/delete-dialog/delete-dialog.service.ts b/src/app/shared/components/delete-dialog/delete-dialog.service.ts index 90a6257ab..b931b9371 100644 --- a/src/app/shared/components/delete-dialog/delete-dialog.service.ts +++ b/src/app/shared/components/delete-dialog/delete-dialog.service.ts @@ -20,7 +20,9 @@ export class DeleteDialogService { showAccept = true, showCancel = true, showOk = false, - infoTitle = '' + infoTitle = '', + acceptText?: string, + cancelText?: string ): Observable { return new Observable((observer) => { const dialog = this.dialog.open(DeleteDialogComponent, { @@ -30,6 +32,8 @@ export class DeleteDialogService { showAccept, showCancel, message: message ? message : 'Er du sikker på at du vil slette?', + acceptText, + cancelText }, }); diff --git a/src/app/shared/components/top-bar/top-bar.component.html b/src/app/shared/components/top-bar/top-bar.component.html index ff94196ee..c63fceb5b 100644 --- a/src/app/shared/components/top-bar/top-bar.component.html +++ b/src/app/shared/components/top-bar/top-bar.component.html @@ -31,7 +31,7 @@

{{title || staticTitle}}

- + - \ No newline at end of file + diff --git a/src/app/shared/components/top-bar/top-bar.component.ts b/src/app/shared/components/top-bar/top-bar.component.ts index f5a58fe38..cf691fea3 100644 --- a/src/app/shared/components/top-bar/top-bar.component.ts +++ b/src/app/shared/components/top-bar/top-bar.component.ts @@ -48,6 +48,7 @@ export class TopBarComponent implements OnInit { @Output() updatePageLimit = new EventEmitter(); @Output() deleteSelectedInDropdown = new EventEmitter(); + @Output() extraDropdownOptions = new EventEmitter(); @Input() addDetailDowndown: boolean; @Input() dropDownButton: DropdownButton; public canEdit = false; @@ -107,4 +108,8 @@ export class TopBarComponent implements OnInit { onClickDelete() { this.deleteSelectedInDropdown.emit(); } + + onClickExtraDropdownOption(id: string) { + this.extraDropdownOptions.emit(id); + } } diff --git a/src/app/shared/models/dialog.model.ts b/src/app/shared/models/dialog.model.ts index 3027452dd..fd54f8c18 100644 --- a/src/app/shared/models/dialog.model.ts +++ b/src/app/shared/models/dialog.model.ts @@ -4,4 +4,6 @@ export class DialogModel { showAccept = true; showCancel = true; message: string; + acceptText: string; + cancelText: string; } diff --git a/src/app/shared/models/dropdown-button.model.ts b/src/app/shared/models/dropdown-button.model.ts index fb30e37ea..74eb40c13 100644 --- a/src/app/shared/models/dropdown-button.model.ts +++ b/src/app/shared/models/dropdown-button.model.ts @@ -1,5 +1,20 @@ +import { PermissionType } from "@app/admin/permission/permission.model"; + +export interface ExtraDropdownOption { + id: string | number; + label: string; +} + export interface DropdownButton { label: string; editRouterLink: string | string[]; isErasable: boolean; + /** + * Show extra dropdown options + * + * **NB**: This interface does not scale well. It doesn't work for generic options and it's used in many components. + * + * By representing any new options in a separate property, this avoids changes in all dependent components and thus merge conflicts. + */ + extraOptions?: ExtraDropdownOption[]; } diff --git a/src/assets/i18n/da.json b/src/assets/i18n/da.json index 4625390e5..88efc9499 100644 --- a/src/assets/i18n/da.json +++ b/src/assets/i18n/da.json @@ -361,6 +361,8 @@ "DELETE": "Slet", "EDIT": "Redigér", "SHOW-OPTIONS": "Håndter IoT enhed", + "BATTERY-ERROR-MESSAGE":"Den valgte teknologi understøtter ikke batteristatus", + "RESET-API-KEY": "Nulstil API nøgle", "NOT-SUPPORTED": "Ikke understøttet", "NOT-SUPPORTED-SHORT": "-", "NOT-AVAILABLE": "N/A" @@ -760,7 +762,11 @@ "NOCOMMENTONLOCATION": "Ingen beskrivelse af placeringen er angivet", "GENERIC_HTTP": { "APIKEY": "API kald", - "INSTRUCTIONS": "Du kalder denne url:" + "INSTRUCTIONS": "Du kalder denne url:", + "RESET-API-KEY": { + "BODY": "Advarsel! Nulstilling vil stoppe modtagelse af data på den eksisterende API Key. Vil du fortsætte?", + "YESRESET": "Ja, nulstil" + } }, "LORAWANSETUP": "LoRaWAN specifik opsætning", "LATEST-DATAPACKAGE": "Seneste datapakke sendt fra enheden:",