Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/applications/applications-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { IoTDeviceDetailComponent } from "./iot-devices/iot-device-detail/iot-de
import { IotDeviceEditComponent } from "./iot-devices/iot-device-edit/iot-device-edit.component";
import { MulticastDetailComponent } from "./multicast/multicast-detail/multicast-detail.component";
import { MulticastEditComponent } from "./multicast/multicast-edit/multicast-edit.component";
import { IotDeviceCopyComponent } from "./iot-devices/iot-device-copy/iot-device-copy.component";

const applicationRoutes: Routes = [
{
Expand Down Expand Up @@ -67,6 +68,10 @@ const applicationRoutes: Routes = [
},
],
},
{
path: "iot-device-copy/:deviceId",
component: IotDeviceCopyComponent,
},
{ path: "datatarget-new", component: DatatargetNewComponent },
{ path: "datatarget-edit", component: DatatargetEditComponent },
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<app-iot-device-edit [isDeviceCopy]="true"></app-iot-device-edit>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";

import { IotDeviceEditComponent } from "./iot-device-edit.component";

describe("IotDeviceEditComponent", () => {
let component: IotDeviceEditComponent;
let fixture: ComponentFixture<IotDeviceEditComponent>;

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [IotDeviceEditComponent],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(IotDeviceEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it("should create", () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component, OnInit } from "@angular/core";

@Component({
selector: "app-iot-device-copy",
templateUrl: "./iot-device-copy.component.html",
styleUrls: ["./iot-device-copy.component.scss"],
})
export class IotDeviceCopyComponent implements OnInit {
constructor() {}

ngOnInit(): void {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { MeService } from "@shared/services/me.service";
import { OrganizationAccessScope } from "@shared/enums/access-scopes";
import { IotDeviceDetailsService } from "@applications/iot-devices/iot-device-details-service";
import { IoTDeviceChangeApplicationDialogComponent } from "../iot-device-change-application-dialog/iot-device-change-application-dialog.component";
import { ApplicationDialogModel, IoTDeviceApplicationDialogModel } from "@shared/models/dialog.model";
import { IoTDeviceApplicationDialogModel } from "@shared/models/dialog.model";

@Component({
selector: "app-iot-device",
Expand Down Expand Up @@ -56,6 +56,7 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy {
private deleteDialogSubscription: Subscription;
public dropdownButton: DropdownButton;
public canEdit = false;
private copyDeviceButtonId = "COPY-DEVICE";

private resetApiKeyId = "RESET-API-KEY";
private resetApiKeyOption: ExtraDropdownOption;
Expand Down Expand Up @@ -148,6 +149,14 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy {
onClick: () => this.onOpenChangeApplicationDialog(),
});
});

this.translate.get("IOTDEVICE.COPY-SETTINGS-TO-NEW-DEVICE").subscribe(translation => {
this.dropdownButton.extraOptions.push({
id: this.copyDeviceButtonId,
label: translation,
onClick: () => this.navigateToCopy(),
});
});
}
});
}
Expand Down Expand Up @@ -191,6 +200,10 @@ export class IoTDeviceDetailComponent implements OnInit, OnDestroy {
});
}

navigateToCopy() {
this.router.navigate(["applications", this.application.id, "iot-device-copy", this.deviceId]);
}

ngOnDestroy() {
// prevent memory leak by unsubscribing
if (this.iotDeviceSubscription) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
class="form-check-input"
required
[attr.disabled]="editmode ? '' : null"
(change)="isChecked($event)"
(change)="isDeviceTypeChecked($event)"
[checked]="iotDevice.type.toString().includes('LORAWAN')"
/>
<div class="image-container">
Expand All @@ -48,7 +48,7 @@
class="form-check-input"
required
[attr.disabled]="editmode ? '' : null"
(change)="isChecked($event)"
(change)="isDeviceTypeChecked($event)"
[checked]="iotDevice.type.toString().includes('GENERIC_HTTP')"
/>
<div class="image-container">
Expand All @@ -65,7 +65,7 @@
required
class="form-check-input"
[attr.disabled]="editmode ? '' : null"
(change)="isChecked($event)"
(change)="isDeviceTypeChecked($event)"
[checked]="iotDevice.type.toString().includes('MQTT')"
/>
<div class="image-container">
Expand All @@ -82,7 +82,7 @@
class="form-check-input"
required
[attr.disabled]="editmode ? '' : null"
(change)="isChecked($event)"
(change)="isDeviceTypeChecked($event)"
[checked]="iotDevice.type.toString().includes('SIGFOX')"
/>
<div class="image-container">
Expand Down Expand Up @@ -238,7 +238,7 @@ <h3>{{ "IOTDEVICE.LORAWANSETUP" | translate }}</h3>
[placeholder]="'QUESTION.DEVEUI-PLACEHOLDER' | translate"
class="form-control"
[(ngModel)]="iotDevice.lorawanSettings.devEUI"
[disabled]="editmode"
[disabled]="editmode && !isDeviceCopy"
[ngClass]="{
'is-invalid': formFailedSubmit && errorFields.includes('devEUI'),
'is-valid': formFailedSubmit && !errorFields.includes('devEUI')
Expand Down Expand Up @@ -417,6 +417,20 @@ <h3>{{ "QUESTION.ABP" | translate }}</h3>
<h3>{{ "QUESTION.METADATA" | translate }}</h3>
<app-form-key-value-list [(tags)]="metadataTags" [errorFieldId]="errorMetadataFieldId"> </app-form-key-value-list>
</div>
<div *ngIf="isDeviceCopy" class="form-group mt-5">
<label class="form-label" for="copyPayloadAndDatatarget">{{
"IOTDEVICE.COPY-DATATAGET-AND-PAYLOAD" | translate
}}</label>
<div>
<mat-checkbox
[(ngModel)]="copyPayloadAndDatatarget"
name="copyPayloadAndDatatarget"
id="copyPayloadAndDatatarget"
>
{{ "QUESTION.SKIPFCNTCHECK-YES" | translate }}
</mat-checkbox>
</div>
</div>
<div class="form-group mt-5">
<button (click)="routeBack()" class="btn btn-secondary" type="button">{{ "GEN.CANCEL" | translate }}</button>
<button class="btn btn-primary ml-2" type="submit">{{ "GEN.SAVE" | translate }}</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Location } from "@angular/common";
import { HttpErrorResponse } from "@angular/common/http";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Component, Input, OnDestroy, OnInit } from "@angular/core";
import { Title } from "@angular/platform-browser";
import { ActivatedRoute, Router } from "@angular/router";
import { Application } from "@app/applications/application.model";
Expand All @@ -17,18 +17,22 @@ import { jsonToList } from "@shared/helpers/json.helper";
import { ErrorMessage } from "@shared/models/error-message.model";
import { ScrollToTopService } from "@shared/services/scroll-to-top.service";
import { SharedVariableService } from "@shared/shared-variable/shared-variable.service";
import { Subscription } from "rxjs";
import { forkJoin, Subscription } from "rxjs";
import { IotDevice } from "../iot-device.model";
import { IoTDeviceService } from "../iot-device.service";
import { MeService } from "@shared/services/me.service";
import { OrganizationAccessScope } from "@shared/enums/access-scopes";
import { PayloadDeviceDatatargetService } from "@payload-decoder/payload-device-datatarget.service";
import { PayloadDeviceDatatargetGetManyResponse } from "@payload-decoder/payload-device-data.model";

@Component({
selector: "app-iot-device-edit",
templateUrl: "./iot-device-edit.component.html",
styleUrls: ["./iot-device-edit.component.scss"],
})
export class IotDeviceEditComponent implements OnInit, OnDestroy {
@Input() isDeviceCopy: boolean = false;
public copyPayloadAndDatatarget: boolean = false;
public errorMessages: any;
public errorFields: string[];
public formFailedSubmit = false;
Expand Down Expand Up @@ -57,6 +61,7 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
private deviceProfileService: DeviceProfileService,
private applicationService: ApplicationService,
private iotDeviceService: IoTDeviceService,
private datatargetPayloadService: PayloadDeviceDatatargetService,
private location: Location,
private shareVariable: SharedVariableService,
private deviceModelService: DeviceModelService,
Expand Down Expand Up @@ -113,14 +118,18 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
});
}

isChecked(event) {
isDeviceTypeChecked(event) {
if (event.target.checked) {
this.iotDevice.type = event.target.name;
} else if (!event.target.checked && this.iotDevice.type.toString().includes(event.target.name)) {
event.target.checked = true;
}
}

isCopyPayloadAndDatatargetChecked(event) {
this.copyPayloadAndDatatarget = event.target.checked;
}

getDevice(id: number): void {
this.deviceSubscription = this.iotDeviceService.getIoTDevice(id).subscribe((device: IotDevice) => {
this.iotDevice = device;
Expand All @@ -140,6 +149,46 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
if (device.metadata) {
this.metadataTags = jsonToList(device.metadata);
}

//If coming from copy, reset all these properties
if (this.isDeviceCopy) {
this.iotDevice.id = undefined;
this.iotDevice.name = undefined;
this.iotDevice.createdAt = undefined;
this.iotDevice.createdBy = undefined;
this.iotDevice.createdByName = undefined;
this.iotDevice.updatedAt = undefined;
this.iotDevice.updatedBy = undefined;
this.iotDevice.updatedByName = undefined;
this.copyPayloadAndDatatarget = true;

switch (this.iotDevice.type) {
case DeviceType.GENERIC_HTTP: {
this.iotDevice.apiKey = undefined;
break;
}
case DeviceType.LORAWAN: {
this.iotDevice.lorawanSettings.devEUI = undefined;
this.iotDevice.lorawanSettings.OTAAapplicationKey = undefined;
this.iotDevice.lorawanSettings.applicationSessionKey = undefined;
this.iotDevice.lorawanSettings.networkSessionKey = undefined;
this.iotDevice.lorawanSettings.devAddr = undefined;
this.iotDevice.lorawanSettings.fCntUp = undefined;
this.iotDevice.lorawanSettings.nFCntDown = undefined;
break;
}
case DeviceType.MQTT_INTERNAL_BROKER: {
this.iotDevice.mqttInternalBrokerSettings.caCertificate = undefined;
this.iotDevice.mqttInternalBrokerSettings.deviceCertificate = undefined;
this.iotDevice.mqttInternalBrokerSettings.deviceCertificateKey = undefined;
this.iotDevice.mqttInternalBrokerSettings.mqttPort = undefined;
this.iotDevice.mqttInternalBrokerSettings.mqttURL = undefined;
this.iotDevice.mqttInternalBrokerSettings.mqttpassword = undefined;
this.iotDevice.mqttInternalBrokerSettings.mqtttopicname = undefined;
this.iotDevice.mqttInternalBrokerSettings.mqttusername = undefined;
}
}
}
});
}

Expand Down Expand Up @@ -190,7 +239,7 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
}
}

if (this.deviceId !== 0) {
if (this.deviceId !== 0 && !this.isDeviceCopy) {
this.updateIoTDevice(this.deviceId);
} else {
this.postIoTDevice();
Expand Down Expand Up @@ -275,23 +324,55 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
}
}

private navigateToDeviceDetails(device: IotDevice) {
this.router.navigate(["applications/" + this.iotDevice.applicationId + "/iot-device/" + device.id + "/details"]);
}

postIoTDevice() {
// Sanitize devEUI for non-hex characters
// Sanitize devEUI
if (this.iotDevice.type === DeviceType.LORAWAN && this.iotDevice.lorawanSettings.devEUI) {
this.iotDevice.lorawanSettings.devEUI = this.iotDevice.lorawanSettings.devEUI.replace(/[^0-9A-Fa-f]/g, "");
}

this.iotDeviceService.createIoTDevice(this.iotDevice).subscribe(
(response: IotDevice) => {
this.router.navigate([
"applications/" + this.iotDevice.applicationId + "/iot-device/" + response.id + "/details",
]);
this.iotDeviceService.createIoTDevice(this.iotDevice).subscribe({
next: (createdDevice: IotDevice) => {
if (!this.copyPayloadAndDatatarget) {
this.navigateToDeviceDetails(createdDevice);
return;
}

this.datatargetPayloadService.getByIoTDevice(this.deviceId).subscribe({
next: (result: PayloadDeviceDatatargetGetManyResponse) => {
const appendObservables = result.data.map(element =>
this.datatargetPayloadService.appendCopiedIoTDevice(element.id, { deviceId: createdDevice.id })
);

if (appendObservables.length === 0) {
this.navigateToDeviceDetails(createdDevice);
return;
}

forkJoin(appendObservables).subscribe({
next: () => this.navigateToDeviceDetails(createdDevice),
error: (error: HttpErrorResponse) => {
this.formFailedSubmitHandleError(error);
},
});
},
error: (error: HttpErrorResponse) => {
this.formFailedSubmitHandleError(error);
},
});
},
(error: HttpErrorResponse) => {
this.handleError(error);
this.formFailedSubmit = true;
}
);
error: (error: HttpErrorResponse) => {
this.formFailedSubmitHandleError(error);
},
});
}

formFailedSubmitHandleError(error: HttpErrorResponse) {
this.handleError(error);
this.formFailedSubmit = true;
}

updateIoTDevice(id: number) {
Expand All @@ -301,8 +382,7 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
this.routeBack();
},
(error: HttpErrorResponse) => {
this.handleError(error);
this.formFailedSubmit = true;
this.formFailedSubmitHandleError(error);
}
);
}
Expand Down
Loading