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:",