Skip to content

Commit a1ec2d0

Browse files
possible to copy and then append to datatargets if chosen
1 parent d96febb commit a1ec2d0

14 files changed

+224
-45
lines changed

src/app/applications/applications-routing.module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { IoTDeviceDetailComponent } from "./iot-devices/iot-device-detail/iot-de
2424
import { IotDeviceEditComponent } from "./iot-devices/iot-device-edit/iot-device-edit.component";
2525
import { MulticastDetailComponent } from "./multicast/multicast-detail/multicast-detail.component";
2626
import { MulticastEditComponent } from "./multicast/multicast-edit/multicast-edit.component";
27+
import { IotDeviceCopyComponent } from "./iot-devices/iot-device-copy/iot-device-copy.component";
2728

2829
const applicationRoutes: Routes = [
2930
{
@@ -67,6 +68,10 @@ const applicationRoutes: Routes = [
6768
},
6869
],
6970
},
71+
{
72+
path: "iot-device-copy/:deviceId",
73+
component: IotDeviceCopyComponent,
74+
},
7075
{ path: "datatarget-new", component: DatatargetNewComponent },
7176
{ path: "datatarget-edit", component: DatatargetEditComponent },
7277
{
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<app-iot-device-edit [isDeviceCopy]="true"></app-iot-device-edit>

src/app/applications/iot-devices/iot-device-copy/iot-device-copy.component.scss

Whitespace-only changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
2+
3+
import { IotDeviceEditComponent } from "./iot-device-edit.component";
4+
5+
describe("IotDeviceEditComponent", () => {
6+
let component: IotDeviceEditComponent;
7+
let fixture: ComponentFixture<IotDeviceEditComponent>;
8+
9+
beforeEach(waitForAsync(() => {
10+
TestBed.configureTestingModule({
11+
declarations: [IotDeviceEditComponent],
12+
}).compileComponents();
13+
}));
14+
15+
beforeEach(() => {
16+
fixture = TestBed.createComponent(IotDeviceEditComponent);
17+
component = fixture.componentInstance;
18+
fixture.detectChanges();
19+
});
20+
21+
it("should create", () => {
22+
expect(component).toBeTruthy();
23+
});
24+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Component, OnInit } from "@angular/core";
2+
3+
@Component({
4+
selector: "app-iot-device-copy",
5+
templateUrl: "./iot-device-copy.component.html",
6+
styleUrls: ["./iot-device-copy.component.scss"],
7+
})
8+
export class IotDeviceCopyComponent implements OnInit {
9+
constructor() {}
10+
11+
ngOnInit(): void {}
12+
}

src/app/applications/iot-devices/iot-device-detail/iot-device-detail.component.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { MeService } from "@shared/services/me.service";
1616
import { OrganizationAccessScope } from "@shared/enums/access-scopes";
1717
import { IotDeviceDetailsService } from "@applications/iot-devices/iot-device-details-service";
1818
import { IoTDeviceChangeApplicationDialogComponent } from "../iot-device-change-application-dialog/iot-device-change-application-dialog.component";
19-
import { ApplicationDialogModel, IoTDeviceApplicationDialogModel } from "@shared/models/dialog.model";
19+
import { IoTDeviceApplicationDialogModel } from "@shared/models/dialog.model";
2020

2121
@Component({
2222
selector: "app-iot-device",
@@ -56,6 +56,7 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy {
5656
private deleteDialogSubscription: Subscription;
5757
public dropdownButton: DropdownButton;
5858
public canEdit = false;
59+
private copyDeviceButtonId = "COPY-DEVICE";
5960

6061
private resetApiKeyId = "RESET-API-KEY";
6162
private resetApiKeyOption: ExtraDropdownOption;
@@ -148,6 +149,14 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy {
148149
onClick: () => this.onOpenChangeApplicationDialog(),
149150
});
150151
});
152+
153+
this.translate.get("IOTDEVICE.COPY-SETTINGS-TO-NEW-DEVICE").subscribe(translation => {
154+
this.dropdownButton.extraOptions.push({
155+
id: this.copyDeviceButtonId,
156+
label: translation,
157+
onClick: () => this.navigateToCopy(),
158+
});
159+
});
151160
}
152161
});
153162
}
@@ -191,6 +200,10 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy {
191200
});
192201
}
193202

203+
navigateToCopy() {
204+
this.router.navigate(["applications", this.application.id, "iot-device-copy", this.deviceId]);
205+
}
206+
194207
ngOnDestroy() {
195208
// prevent memory leak by unsubscribing
196209
if (this.iotDeviceSubscription) {

src/app/applications/iot-devices/iot-device-edit/iot-device-edit.component.html

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
class="form-check-input"
3232
required
3333
[attr.disabled]="editmode ? '' : null"
34-
(change)="isChecked($event)"
34+
(change)="isDeviceTypeChecked($event)"
3535
[checked]="iotDevice.type.toString().includes('LORAWAN')"
3636
/>
3737
<div class="image-container">
@@ -48,7 +48,7 @@
4848
class="form-check-input"
4949
required
5050
[attr.disabled]="editmode ? '' : null"
51-
(change)="isChecked($event)"
51+
(change)="isDeviceTypeChecked($event)"
5252
[checked]="iotDevice.type.toString().includes('GENERIC_HTTP')"
5353
/>
5454
<div class="image-container">
@@ -65,7 +65,7 @@
6565
required
6666
class="form-check-input"
6767
[attr.disabled]="editmode ? '' : null"
68-
(change)="isChecked($event)"
68+
(change)="isDeviceTypeChecked($event)"
6969
[checked]="iotDevice.type.toString().includes('MQTT')"
7070
/>
7171
<div class="image-container">
@@ -82,7 +82,7 @@
8282
class="form-check-input"
8383
required
8484
[attr.disabled]="editmode ? '' : null"
85-
(change)="isChecked($event)"
85+
(change)="isDeviceTypeChecked($event)"
8686
[checked]="iotDevice.type.toString().includes('SIGFOX')"
8787
/>
8888
<div class="image-container">
@@ -238,7 +238,7 @@ <h3>{{ "IOTDEVICE.LORAWANSETUP" | translate }}</h3>
238238
[placeholder]="'QUESTION.DEVEUI-PLACEHOLDER' | translate"
239239
class="form-control"
240240
[(ngModel)]="iotDevice.lorawanSettings.devEUI"
241-
[disabled]="editmode"
241+
[disabled]="editmode && !isDeviceCopy"
242242
[ngClass]="{
243243
'is-invalid': formFailedSubmit && errorFields.includes('devEUI'),
244244
'is-valid': formFailedSubmit && !errorFields.includes('devEUI')
@@ -417,6 +417,20 @@ <h3>{{ "QUESTION.ABP" | translate }}</h3>
417417
<h3>{{ "QUESTION.METADATA" | translate }}</h3>
418418
<app-form-key-value-list [(tags)]="metadataTags" [errorFieldId]="errorMetadataFieldId"> </app-form-key-value-list>
419419
</div>
420+
<div *ngIf="isDeviceCopy" class="form-group mt-5">
421+
<label class="form-label" for="copyPayloadAndDatatarget">{{
422+
"IOTDEVICE.COPY-DATATAGET-AND-PAYLOAD" | translate
423+
}}</label>
424+
<div>
425+
<mat-checkbox
426+
[(ngModel)]="copyPayloadAndDatatarget"
427+
name="copyPayloadAndDatatarget"
428+
id="copyPayloadAndDatatarget"
429+
>
430+
{{ "QUESTION.SKIPFCNTCHECK-YES" | translate }}
431+
</mat-checkbox>
432+
</div>
433+
</div>
420434
<div class="form-group mt-5">
421435
<button (click)="routeBack()" class="btn btn-secondary" type="button">{{ "GEN.CANCEL" | translate }}</button>
422436
<button class="btn btn-primary ml-2" type="submit">{{ "GEN.SAVE" | translate }}</button>

src/app/applications/iot-devices/iot-device-edit/iot-device-edit.component.ts

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Location } from "@angular/common";
22
import { HttpErrorResponse } from "@angular/common/http";
3-
import { Component, OnDestroy, OnInit } from "@angular/core";
3+
import { Component, Input, OnDestroy, OnInit } from "@angular/core";
44
import { Title } from "@angular/platform-browser";
55
import { ActivatedRoute, Router } from "@angular/router";
66
import { Application } from "@app/applications/application.model";
@@ -17,18 +17,22 @@ import { jsonToList } from "@shared/helpers/json.helper";
1717
import { ErrorMessage } from "@shared/models/error-message.model";
1818
import { ScrollToTopService } from "@shared/services/scroll-to-top.service";
1919
import { SharedVariableService } from "@shared/shared-variable/shared-variable.service";
20-
import { Subscription } from "rxjs";
20+
import { forkJoin, Subscription } from "rxjs";
2121
import { IotDevice } from "../iot-device.model";
2222
import { IoTDeviceService } from "../iot-device.service";
2323
import { MeService } from "@shared/services/me.service";
2424
import { OrganizationAccessScope } from "@shared/enums/access-scopes";
25+
import { PayloadDeviceDatatargetService } from "@payload-decoder/payload-device-datatarget.service";
26+
import { PayloadDeviceDatatargetGetManyResponse } from "@payload-decoder/payload-device-data.model";
2527

2628
@Component({
2729
selector: "app-iot-device-edit",
2830
templateUrl: "./iot-device-edit.component.html",
2931
styleUrls: ["./iot-device-edit.component.scss"],
3032
})
3133
export class IotDeviceEditComponent implements OnInit, OnDestroy {
34+
@Input() isDeviceCopy: boolean = false;
35+
public copyPayloadAndDatatarget: boolean = false;
3236
public errorMessages: any;
3337
public errorFields: string[];
3438
public formFailedSubmit = false;
@@ -57,6 +61,7 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
5761
private deviceProfileService: DeviceProfileService,
5862
private applicationService: ApplicationService,
5963
private iotDeviceService: IoTDeviceService,
64+
private datatargetPayloadService: PayloadDeviceDatatargetService,
6065
private location: Location,
6166
private shareVariable: SharedVariableService,
6267
private deviceModelService: DeviceModelService,
@@ -113,14 +118,18 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
113118
});
114119
}
115120

116-
isChecked(event) {
121+
isDeviceTypeChecked(event) {
117122
if (event.target.checked) {
118123
this.iotDevice.type = event.target.name;
119124
} else if (!event.target.checked && this.iotDevice.type.toString().includes(event.target.name)) {
120125
event.target.checked = true;
121126
}
122127
}
123128

129+
isCopyPayloadAndDatatargetChecked(event) {
130+
this.copyPayloadAndDatatarget = event.target.checked;
131+
}
132+
124133
getDevice(id: number): void {
125134
this.deviceSubscription = this.iotDeviceService.getIoTDevice(id).subscribe((device: IotDevice) => {
126135
this.iotDevice = device;
@@ -140,6 +149,46 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
140149
if (device.metadata) {
141150
this.metadataTags = jsonToList(device.metadata);
142151
}
152+
153+
//If coming from copy, reset all these properties
154+
if (this.isDeviceCopy) {
155+
this.iotDevice.id = undefined;
156+
this.iotDevice.name = undefined;
157+
this.iotDevice.createdAt = undefined;
158+
this.iotDevice.createdBy = undefined;
159+
this.iotDevice.createdByName = undefined;
160+
this.iotDevice.updatedAt = undefined;
161+
this.iotDevice.updatedBy = undefined;
162+
this.iotDevice.updatedByName = undefined;
163+
this.copyPayloadAndDatatarget = true;
164+
165+
switch (this.iotDevice.type) {
166+
case DeviceType.GENERIC_HTTP: {
167+
this.iotDevice.apiKey = undefined;
168+
break;
169+
}
170+
case DeviceType.LORAWAN: {
171+
this.iotDevice.lorawanSettings.devEUI = undefined;
172+
this.iotDevice.lorawanSettings.OTAAapplicationKey = undefined;
173+
this.iotDevice.lorawanSettings.applicationSessionKey = undefined;
174+
this.iotDevice.lorawanSettings.networkSessionKey = undefined;
175+
this.iotDevice.lorawanSettings.devAddr = undefined;
176+
this.iotDevice.lorawanSettings.fCntUp = undefined;
177+
this.iotDevice.lorawanSettings.nFCntDown = undefined;
178+
break;
179+
}
180+
case DeviceType.MQTT_INTERNAL_BROKER: {
181+
this.iotDevice.mqttInternalBrokerSettings.caCertificate = undefined;
182+
this.iotDevice.mqttInternalBrokerSettings.deviceCertificate = undefined;
183+
this.iotDevice.mqttInternalBrokerSettings.deviceCertificateKey = undefined;
184+
this.iotDevice.mqttInternalBrokerSettings.mqttPort = undefined;
185+
this.iotDevice.mqttInternalBrokerSettings.mqttURL = undefined;
186+
this.iotDevice.mqttInternalBrokerSettings.mqttpassword = undefined;
187+
this.iotDevice.mqttInternalBrokerSettings.mqtttopicname = undefined;
188+
this.iotDevice.mqttInternalBrokerSettings.mqttusername = undefined;
189+
}
190+
}
191+
}
143192
});
144193
}
145194

@@ -190,7 +239,7 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
190239
}
191240
}
192241

193-
if (this.deviceId !== 0) {
242+
if (this.deviceId !== 0 && !this.isDeviceCopy) {
194243
this.updateIoTDevice(this.deviceId);
195244
} else {
196245
this.postIoTDevice();
@@ -275,23 +324,55 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
275324
}
276325
}
277326

327+
private navigateToDeviceDetails(device: IotDevice) {
328+
this.router.navigate(["applications/" + this.iotDevice.applicationId + "/iot-device/" + device.id + "/details"]);
329+
}
330+
278331
postIoTDevice() {
279-
// Sanitize devEUI for non-hex characters
332+
// Sanitize devEUI
280333
if (this.iotDevice.type === DeviceType.LORAWAN && this.iotDevice.lorawanSettings.devEUI) {
281334
this.iotDevice.lorawanSettings.devEUI = this.iotDevice.lorawanSettings.devEUI.replace(/[^0-9A-Fa-f]/g, "");
282335
}
283336

284-
this.iotDeviceService.createIoTDevice(this.iotDevice).subscribe(
285-
(response: IotDevice) => {
286-
this.router.navigate([
287-
"applications/" + this.iotDevice.applicationId + "/iot-device/" + response.id + "/details",
288-
]);
337+
this.iotDeviceService.createIoTDevice(this.iotDevice).subscribe({
338+
next: (createdDevice: IotDevice) => {
339+
if (!this.copyPayloadAndDatatarget) {
340+
this.navigateToDeviceDetails(createdDevice);
341+
return;
342+
}
343+
344+
this.datatargetPayloadService.getByIoTDevice(this.deviceId).subscribe({
345+
next: (result: PayloadDeviceDatatargetGetManyResponse) => {
346+
const appendObservables = result.data.map(element =>
347+
this.datatargetPayloadService.appendCopiedIoTDevice(element.id, { deviceId: createdDevice.id })
348+
);
349+
350+
if (appendObservables.length === 0) {
351+
this.navigateToDeviceDetails(createdDevice);
352+
return;
353+
}
354+
355+
forkJoin(appendObservables).subscribe({
356+
next: () => this.navigateToDeviceDetails(createdDevice),
357+
error: (error: HttpErrorResponse) => {
358+
this.formFailedSubmitHandleError(error);
359+
},
360+
});
361+
},
362+
error: (error: HttpErrorResponse) => {
363+
this.formFailedSubmitHandleError(error);
364+
},
365+
});
289366
},
290-
(error: HttpErrorResponse) => {
291-
this.handleError(error);
292-
this.formFailedSubmit = true;
293-
}
294-
);
367+
error: (error: HttpErrorResponse) => {
368+
this.formFailedSubmitHandleError(error);
369+
},
370+
});
371+
}
372+
373+
formFailedSubmitHandleError(error: HttpErrorResponse) {
374+
this.handleError(error);
375+
this.formFailedSubmit = true;
295376
}
296377

297378
updateIoTDevice(id: number) {
@@ -301,8 +382,7 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
301382
this.routeBack();
302383
},
303384
(error: HttpErrorResponse) => {
304-
this.handleError(error);
305-
this.formFailedSubmit = true;
385+
this.formFailedSubmitHandleError(error);
306386
}
307387
);
308388
}

0 commit comments

Comments
 (0)