Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
18,101 changes: 23 additions & 18,078 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function tokenGetter() {
@NgModule({
declarations: [
AppComponent,
ErrorPageComponent
ErrorPageComponent,
],
imports: [
SharedVariableModule.forRoot(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ <h3>{{ 'IOTDEVICE.DETAIL' | translate }}</h3>
{{ 'IOTDEVICE.NOCOMMENT' | translate}}
</ng-template>
</p>
<ng-container *ngIf="metadataTags.length">
<mat-divider></mat-divider>
<p *ngFor="let tag of metadataTags">
<strong>{{ tag.key }}</strong>
<span>{{ tag.value }}</span>
</p>
</ng-container>
</div>
</div>
<div class="col-md-6 d-flex align-items-stretch">
Expand Down Expand Up @@ -72,4 +79,4 @@ <h3>{{ 'IOTDEVICE.LOCATION' | translate }}</h3>
</p>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core';
import { Location } from '@angular/common';
import {
Component,
EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges,
} from '@angular/core';
import { IotDevice } from '@applications/iot-devices/iot-device.model';
import { IoTDeviceService } from '@applications/iot-devices/iot-device.service';
import { TranslateService } from '@ngx-translate/core';
import { Location } from '@angular/common';
import { DeviceType } from '@shared/enums/device-type';
import { MatDialog } from '@angular/material/dialog';
import { DeleteDialogComponent } from '@shared/components/delete-dialog/delete-dialog.component';
import { Subscription } from 'rxjs';
import { DeleteDialogService } from '@shared/components/delete-dialog/delete-dialog.service';
import { jsonToList } from '@shared/helpers/json.helper';
import { KeyValue } from '@shared/types/tuple.type';

@Component({
selector: 'app-iot-device-detail-generic',
templateUrl: './iot-device-detail-generic.component.html',
styleUrls: ['./iot-device-detail-generic.component.scss']
styleUrls: ['./iot-device-detail-generic.component.scss'],
})
export class IotDeviceDetailGenericComponent implements OnInit, OnChanges, OnDestroy {
export class IotDeviceDetailGenericComponent
implements OnInit, OnChanges, OnDestroy {
batteryStatusColor = 'green';
batteryStatusPercentage: number;
metadataTags: KeyValue[] = [];
@Input() device: IotDevice;
@Input() latitude = 0;
@Input() longitude = 0;
Expand All @@ -27,16 +34,21 @@ export class IotDeviceDetailGenericComponent implements OnInit, OnChanges, OnDes
constructor(
private translate: TranslateService,
public iotDeviceService: IoTDeviceService,
private location: Location,
private location: Location
) {}

) { }

ngOnInit(): void {
}
ngOnInit(): void {}

ngOnChanges(): void {
ngOnChanges(changes: SimpleChanges): void {
this.batteryStatusPercentage = this.getBatteryProcentage();

if (
changes?.device?.previousValue?.metadata !==
changes?.device?.currentValue?.metadata &&
this.device.metadata
) {
this.metadataTags = jsonToList(this.device.metadata);
}
}

routeBack(): void {
Expand All @@ -48,19 +60,19 @@ export class IotDeviceDetailGenericComponent implements OnInit, OnChanges, OnDes
latitude: this.latitude,
draggable: false,
editEnabled: false,
useGeolocation: false
useGeolocation: false,
};
}

getBatteryProcentage(): number {
if (this.device?.lorawanSettings?.deviceStatusBattery === this.CHIRPSTACK_BATTERY_NOT_AVAILIBLE) {
if (
this.device?.lorawanSettings?.deviceStatusBattery ===
this.CHIRPSTACK_BATTERY_NOT_AVAILIBLE
) {
return null;
}
return Math.round(this.device?.lorawanSettings?.deviceStatusBattery);
}

ngOnDestroy(): void {

}

ngOnDestroy(): void {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@ <h3>{{'IOTDEVICE.LORAWANSETUP' | translate}}</h3>
<label class="form-label" for="serviceProfileID">{{'QUESTION.CHOOSE-SERVICEPROFILE' | translate}}</label>*
<select id="serviceProfileID" class="form-select" name="serviceProfileID"
[(ngModel)]="iotDevice.lorawanSettings.serviceProfileID" required
[value]="iotDevice.lorawanSettings.serviceProfileID"
[disabled]="editmode"
[value]="iotDevice.lorawanSettings.serviceProfileID" [disabled]="editmode"
[ngClass]="{'is-invalid' : formFailedSubmit && errorFields.includes('serviceProfileID'), 'is-valid' : formFailedSubmit && !errorFields.includes('serviceProfileID')}">
<option *ngFor="let serviceProfile of serviceProfiles" [value]="serviceProfile.id"
[selected]="serviceProfile.id === iotDevice.lorawanSettings.serviceProfileID">
Expand Down Expand Up @@ -181,8 +180,8 @@ <h3>{{'QUESTION.ABP' | translate}}</h3>
</div>

<div class="form-group mt-5">
<label class="form-label"
for="applicationSessionKey">{{'QUESTION.APPLICATIONSESSIONKEY' | translate}}*</label>
<label class="form-label" for="applicationSessionKey">{{'QUESTION.APPLICATIONSESSIONKEY' |
translate}}*</label>
<input type="text" id="applicationSessionKey" name="applicationSessionKey" maxLength="32"
[placeholder]="'QUESTION.APPLICATIONSESSIONKEY-PLACEHOLDER' | translate" class="form-control"
[(ngModel)]="iotDevice.lorawanSettings.applicationSessionKey"
Expand Down Expand Up @@ -222,8 +221,13 @@ <h3>{{'QUESTION.ABP' | translate}}</h3>
</app-sigfox-device-edit>
</ng-container>

<div class="mt-5 row">
<h3>{{ "QUESTION.METADATA" | translate}}</h3>
<app-form-key-value-list [(tags)]="metadataTags" [errorFieldId]="errorMetadataFieldId">
</app-form-key-value-list>
</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>
</div>
</form>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ServiceProfileService } from '@profiles/service-profiles/service-profil
import { ActivationType } from '@shared/enums/activation-type';
import { DeviceType } from '@shared/enums/device-type';
import { ErrorMessageService } from '@shared/error-message.service';
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';
Expand Down Expand Up @@ -43,6 +44,8 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
iotDevice = new IotDevice();
editmode = false;
public OTAA = true;
metadataTags: {key?: string, value?: string}[] = [];
errorMetadataFieldId: string | undefined;

public deviceSubscription: Subscription;
private applicationsSubscription: Subscription;
Expand Down Expand Up @@ -95,7 +98,7 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
getDeviceModels() {
this.deviceModelService.getMultiple(
1000,
0,
0,
"id",
"ASC",
this.shareVariable.getSelectedOrganisationId()
Expand Down Expand Up @@ -132,6 +135,9 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
if (!device.deviceModelId || device.deviceModelId === null) {
this.iotDevice.deviceModelId = 0;
}
if (device.metadata) {
this.metadataTags = jsonToList(device.metadata);
}
});
}

Expand Down Expand Up @@ -177,13 +183,40 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {

onSubmit(): void {
this.adjustModelBasedOnType();

if (this.isMetadataSet()) {
const invalidKey = this.validateMetadata();

if (!invalidKey) {
this.setMetadata();
} else {
this.handleMetadataError(invalidKey);
return;
}
}

if (this.deviceId !== 0) {
this.updateIoTDevice(this.deviceId);
this.updateIoTDevice(this.deviceId);
} else {
this.postIoTDevice();
this.postIoTDevice();
}
}

private handleMetadataError(invalidKey: string) {
this.handleError({
error: {
message: [
{
field: 'metadata',
message: 'MESSAGE.DUPLICATE-METADATA-KEY',
},
],
},
});
this.errorMetadataFieldId = invalidKey;
this.formFailedSubmit = true;
}

setActivationType() {
if (this.OTAA) {
this.iotDevice.lorawanSettings.activationType = ActivationType.OTAA;
Expand Down Expand Up @@ -220,6 +253,36 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
}
}

private isMetadataSet(): boolean {
return this.metadataTags.length && this.metadataTags.some((tag) => tag.key && tag.value);
}

private validateMetadata(): string | undefined {
const seen = new Set();

for (const tag of this.metadataTags) {
if (seen.size === seen.add(tag.key).size) {
return tag.key;
}
}
}

private setMetadata(): void {
if (
this.metadataTags.length &&
this.metadataTags.some((tag) => tag.key && tag.value)
) {
const metadata: Record<string, string> = {};
this.metadataTags.forEach((tag) => {
if (!tag.key) {
return;
}
metadata[tag.key] = tag.value;
});
this.iotDevice.metadata = JSON.stringify(metadata);
}
}

postIoTDevice() {
this.iotDeviceService.createIoTDevice(this.iotDevice).subscribe(
() => {
Expand Down Expand Up @@ -252,7 +315,7 @@ export class IotDeviceEditComponent implements OnInit, OnDestroy {
this.location.back();
}

handleError(error: HttpErrorResponse) {
handleError(error: Pick<HttpErrorResponse, 'error'>) {
if (error?.error?.message == "MESSAGE.OTAA-INFO-MISSING") {
this.errorFields = ["OTAAapplicationKey"];
this.errorMessages = [error?.error?.message];
Expand Down
2 changes: 1 addition & 1 deletion src/app/applications/iot-devices/iot-device.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class IotDevice {
comment: string;
type: DeviceType = DeviceType.GENERICHTTP;
receivedMessagesMetadata: ReceivedMessageMetadata[];
metadata?: JSON;
metadata?: string;
apiKey?: string;
id: number;
createdAt: Date;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<button (click)="addNewTag()" class="btn btn-secondary mb-3" type="button">{{ "FORM.ADD-METADATA-ROW" | translate
}}</button>

<table class="table table-striped table-bordered">
<tbody>
<ng-container *ngFor="let pair of tags; let i = index">
<!-- Global table styling breaks if <tr> isn't a direct child of <tbody> -->
<tr app-form-key-value-pair [id]="i" [pair]="pair" [errorFieldId]="errorFieldId" (deletePair)="deleteTag($event)">
</tr>
</ng-container>
</tbody>
</table>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, Input, OnInit } from '@angular/core';
import { KeyValue } from '@shared/types/tuple.type';

@Component({
selector: 'app-form-key-value-list',
templateUrl: './form-key-value-list.component.html',
styleUrls: ['./form-key-value-list.component.scss'],
})
export class FormKeyValueListComponent implements OnInit {
@Input() tags: KeyValue[] = [{}];
@Input() errorFieldId: string | undefined;

constructor() {}

ngOnInit(): void {}

addNewTag(): void {
this.tags.push({});
}

deleteTag(id: number): void {
this.tags = this.tags.filter((_tag, i) => i !== id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<td>
<div class="row px-2">
<input type="text" id="keyValuePairKey" name="keyValuePairKey" maxLength="100" [placeholder]="'GEN.KEY' | translate"
class="form-control border-bottom-i-1" [(ngModel)]="pair.key"
[ngClass]="{'is-invalid' : errorFieldId && errorFieldId === pair.key, 'is-valid' : errorFieldId && errorFieldId !== pair.key}">
</div>
</td>
<td>
<div class="row px-2">
<input type="text" id="keyValuePairValue" name="keyValuePairValue" maxLength="100"
[placeholder]="'GEN.VALUE' | translate" class="form-control border-bottom-i-1" [(ngModel)]="pair.value">
</div>
</td>
<td>
<a (click)="openDeleteDialog()">
<div class="text-center">
<fa-icon [icon]="faTimesCircle"></fa-icon>
<p class="mt-i-0">{{'DATATARGET.DELETE' | translate}}</p>
</div>
</a>
</td>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.mt-i-0 {
margin-top: 0 !important;
}

.border-bottom-i-1 {
border-bottom-width: 1px !important;
}
Loading