Merge branch 'feature/bulk-import/device-credentials' of https://github.com/vvlladd28/thingsboard into feature/bulk-import

This commit is contained in:
Viacheslav Klimov 2021-08-03 16:05:36 +03:00
commit 41e572490e
12 changed files with 395 additions and 48 deletions

View File

@ -53,7 +53,7 @@ import {
ImportEntityData
} from '@shared/models/entity.models';
import { EntityRelationService } from '@core/http/entity-relation.service';
import { deepClone, generateSecret, guid, isDefined, isDefinedAndNotNull } from '@core/utils';
import { deepClone, generateSecret, guid, isDefined, isDefinedAndNotNull, isNotEmptyStr } from '@core/utils';
import { Asset } from '@shared/models/asset.models';
import { Device, DeviceCredentialsType } from '@shared/models/device.models';
import { AttributeService } from '@core/http/attribute.service';
@ -954,7 +954,12 @@ export class EntityService {
map(() => {
return { create: { entity: 1 } } as ImportEntitiesResultInfo;
}),
catchError(err => of({ error: { entity: 1 } } as ImportEntitiesResultInfo))
catchError(err => of({
error: {
entity: 1,
errors: err.message
}
} as ImportEntitiesResultInfo))
);
}),
catchError(err => {
@ -978,13 +983,28 @@ export class EntityService {
map(() => {
return { update: { entity: 1 } } as ImportEntitiesResultInfo;
}),
catchError(updateError => of({ error: { entity: 1 } } as ImportEntitiesResultInfo))
catchError(updateError => of({
error: {
entity: 1,
errors: updateError.message
}
} as ImportEntitiesResultInfo))
);
}),
catchError(findErr => of({ error: { entity: 1 } } as ImportEntitiesResultInfo))
catchError(findErr => of({
error: {
entity: 1,
errors: `Line: ${entityData.lineNumber}; Error: ${findErr.error.message}`
}
} as ImportEntitiesResultInfo))
);
} else {
return of({ error: { entity: 1 } } as ImportEntitiesResultInfo);
return of({
error: {
entity: 1,
errors: `Line: ${entityData.lineNumber}; Error: ${err.error.message}`
}
} as ImportEntitiesResultInfo);
}
})
);
@ -1040,7 +1060,6 @@ export class EntityService {
break;
}
return saveEntityObservable;
}
private getUpdateEntityTasks(entityType: EntityType, entityData: ImportEntityData | EdgeImportEntityData,
@ -1113,15 +1132,31 @@ export class EntityService {
public saveEntityData(entityId: EntityId, entityData: ImportEntityData, config?: RequestConfig): Observable<any> {
const observables: Observable<string>[] = [];
let observable: Observable<string>;
if (entityData.accessToken && entityData.accessToken !== '') {
if (Object.keys(entityData.credential).length) {
let credentialsType: DeviceCredentialsType;
let credentialsId: string = null;
let credentialsValue: string = null;
if (isDefinedAndNotNull(entityData.credential.mqtt)) {
credentialsType = DeviceCredentialsType.MQTT_BASIC;
credentialsValue = JSON.stringify(entityData.credential.mqtt);
} else if (isDefinedAndNotNull(entityData.credential.lwm2m)) {
credentialsType = DeviceCredentialsType.LWM2M_CREDENTIALS;
credentialsValue = JSON.stringify(entityData.credential.lwm2m);
} else if (isNotEmptyStr(entityData.credential.x509)) {
credentialsType = DeviceCredentialsType.X509_CERTIFICATE;
credentialsValue = entityData.credential.x509;
} else {
credentialsType = DeviceCredentialsType.ACCESS_TOKEN;
credentialsId = entityData.credential.accessToken;
}
observable = this.deviceService.getDeviceCredentials(entityId.id, false, config).pipe(
mergeMap((credentials) => {
credentials.credentialsId = entityData.accessToken;
credentials.credentialsType = DeviceCredentialsType.ACCESS_TOKEN;
credentials.credentialsValue = null;
credentials.credentialsId = credentialsId;
credentials.credentialsType = credentialsType;
credentials.credentialsValue = credentialsValue;
return this.deviceService.saveDeviceCredentials(credentials, config).pipe(
map(() => 'ok'),
catchError(err => of('error'))
catchError(err => of(`Line: ${entityData.lineNumber}; Error: ${err.error.message}`))
);
})
);
@ -1131,7 +1166,7 @@ export class EntityService {
observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SHARED_SCOPE,
entityData.attributes.shared, config).pipe(
map(() => 'ok'),
catchError(err => of('error'))
catchError(err => of(`Line: ${entityData.lineNumber}; Error: ${err.error.message}`))
);
observables.push(observable);
}
@ -1139,23 +1174,23 @@ export class EntityService {
observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SERVER_SCOPE,
entityData.attributes.server, config).pipe(
map(() => 'ok'),
catchError(err => of('error'))
catchError(err => of(`Line: ${entityData.lineNumber}; Error: ${err.error.message}`))
);
observables.push(observable);
}
if (entityData.timeseries && entityData.timeseries.length) {
observable = this.attributeService.saveEntityTimeseries(entityId, 'time', entityData.timeseries, config).pipe(
map(() => 'ok'),
catchError(err => of('error'))
catchError(err => of(`Line: ${entityData.lineNumber}; Error: ${err.error.message}`))
);
observables.push(observable);
}
if (observables.length) {
return forkJoin(observables).pipe(
map((response) => {
const hasError = response.filter((status) => status === 'error').length > 0;
if (hasError) {
throw Error();
const hasError = response.filter((status) => status !== 'ok');
if (hasError.length > 0) {
throw Error(hasError.join('\n'));
} else {
return response;
}

View File

@ -94,7 +94,7 @@
<mat-step [stepControl]="columnTypesFormGroup">
<form [formGroup]="columnTypesFormGroup">
<ng-template matStepLabel>{{ 'import.stepper-text.column-type' | translate }}</ng-template>
<tb-table-columns-assignment formControlName="columnsParam" [entityType]="entityType"></tb-table-columns-assignment>
<tb-table-columns-assignment #columnsAssignmentComponent formControlName="columnsParam" [entityType]="entityType"></tb-table-columns-assignment>
</form>
<div fxLayout="row wrap" fxLayoutAlign="space-between center">
<button mat-button
@ -125,9 +125,20 @@
<p class="mat-body-1" *ngIf="this.statistical?.update && this.statistical?.update.entity">
{{ translate.instant('import.message.update-entities', {count: this.statistical.update.entity}) }}
</p>
<p class="mat-body-1" *ngIf="this.statistical?.error && this.statistical?.error.entity">
<p class="mat-body-1" style="margin-bottom: 0.8em" *ngIf="this.statistical?.error && this.statistical?.error.entity">
{{ translate.instant('import.message.error-entities', {count: this.statistical.error.entity}) }}
</p>
<mat-expansion-panel class="advanced-logs" [expanded]="false"
*ngIf="this.statistical?.error && this.statistical?.error.entity"
(opened)="initEditor()">
<mat-expansion-panel-header [collapsedHeight]="'38px'" [expandedHeight]="'38px'">
<mat-panel-title>
<div class="tb-small" translate>import.details</div>
</mat-panel-title>
</mat-expansion-panel-header>
<mat-divider></mat-divider>
<div #failureDetailsEditor class="tb-failure-details"></div>
</mat-expansion-panel>
</div>
<div fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="20px">
<button mat-raised-button

View File

@ -26,5 +26,30 @@
.tb-import-progress{
margin: 7px 0;
}
.tb-failure-details {
width: 100%;
min-width: 300px;
height: 100%;
min-height: 50px;
margin-top: 8px;
}
.mat-expansion-panel {
box-shadow: none;
&.advanced-logs {
border: 1px groove rgba(0, 0, 0, .25);
padding: 0;
margin-bottom: 1.6em;
.mat-expansion-panel-header {
padding: 0 8px;
}
.mat-expansion-panel-body {
padding: 0;
}
}
}
}
}

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
import { AfterViewInit, Component, ElementRef, Inject, Renderer2, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@ -34,6 +34,12 @@ import {
} from '@home/components/import-export/import-export.models';
import { EdgeImportEntityData, ImportEntitiesResultInfo, ImportEntityData } from '@app/shared/models/entity.models';
import { ImportExportService } from '@home/components/import-export/import-export.service';
import { TableColumnsAssignmentComponent } from '@home/components/import-export/table-columns-assignment.component';
import { getDeviceCredentialMQTTDefault } from '@shared/models/device.models';
import { isDefinedAndNotNull } from '@core/utils';
import { getLwm2mSecurityConfigModelsDefault } from '@shared/models/lwm2m-security-config.models';
import { Ace } from 'ace-builds';
import { getAce } from '@shared/models/ace/ace.models';
export interface ImportDialogCsvData {
entityType: EntityType;
@ -48,15 +54,21 @@ export interface ImportDialogCsvData {
styleUrls: ['./import-dialog-csv.component.scss']
})
export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvComponent, boolean>
implements OnInit {
implements AfterViewInit {
@ViewChild('importStepper', {static: true}) importStepper: MatVerticalStepper;
@ViewChild('columnsAssignmentComponent', {static: true})
columnsAssignmentComponent: TableColumnsAssignmentComponent;
@ViewChild('failureDetailsEditor')
failureDetailsEditorElmRef: ElementRef;
entityType: EntityType;
importTitle: string;
importFileLabel: string;
delimiters: {key: string, value: string}[] = [{
delimiters: { key: string, value: string }[] = [{
key: ',',
value: ','
}, {
@ -80,6 +92,8 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom
progressCreate = 0;
statistical: ImportEntitiesResultInfo;
private allowAssignColumn: ImportEntityColumnType[];
private initEditorComponent = false;
private parseData: CsvToJsonResult;
constructor(protected store: Store<AppState>,
@ -88,7 +102,8 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom
public dialogRef: MatDialogRef<ImportDialogCsvComponent, boolean>,
public translate: TranslateService,
private importExport: ImportExportService,
private fb: FormBuilder) {
private fb: FormBuilder,
private renderer: Renderer2) {
super(store, router, dialogRef);
this.entityType = data.entityType;
this.importTitle = data.importTitle;
@ -109,7 +124,12 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom
});
}
ngOnInit(): void {
ngAfterViewInit() {
let columns = this.columnsAssignmentComponent.columnTypes;
if (this.entityType === EntityType.DEVICE) {
columns = columns.concat(this.columnsAssignmentComponent.columnDeviceCredentials);
}
this.allowAssignColumn = columns.map(column => column.value);
}
cancel(): void {
@ -157,8 +177,10 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom
return convertCSVToJson(importData, config,
(messageId, params) => {
this.store.dispatch(new ActionNotificationShow(
{message: this.translate.instant(messageId, params),
type: 'error'}));
{
message: this.translate.instant(messageId, params),
type: 'error'
}));
}
);
}
@ -168,9 +190,14 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom
const isHeader: boolean = this.importParametersFormGroup.get('isHeader').value;
for (let i = 0; i < this.parseData.headers.length; i++) {
let columnParam: CsvColumnParam;
if (isHeader && this.parseData.headers[i].search(/^(name|type|label)$/im) === 0) {
let findEntityColumnType: ImportEntityColumnType;
if (isHeader) {
const headerColumnName = this.parseData.headers[i].toUpperCase();
findEntityColumnType = this.allowAssignColumn.find(column => column === headerColumnName);
}
if (isHeader && findEntityColumnType) {
columnParam = {
type: ImportEntityColumnType[this.parseData.headers[i].toLowerCase()],
type: findEntityColumnType,
key: this.parseData.headers[i].toLowerCase(),
sampleData: this.parseData.rows[0][i]
};
@ -189,13 +216,19 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom
private addEntities() {
const importData = this.parseData;
const isHeader: boolean = this.importParametersFormGroup.get('isHeader').value;
const parameterColumns: CsvColumnParam[] = this.columnTypesFormGroup.get('columnsParam').value;
const entitiesData: ImportEntityData[] = [];
let sentDataLength = 0;
const startLineNumber = isHeader ? 2 : 1;
for (let row = 0; row < importData.rows.length; row++) {
const entityData: ImportEntityData = this.constructDraftImportEntityData();
const i = row;
entityData.lineNumber = startLineNumber + i;
for (let j = 0; j < parameterColumns.length; j++) {
if (!isDefinedAndNotNull(importData.rows[i][j]) || importData.rows[i][j] === '') {
continue;
}
switch (parameterColumns[j].type) {
case ImportEntityColumnType.serverAttribute:
entityData.attributes.server.push({
@ -215,9 +248,6 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom
value: importData.rows[i][j]
});
break;
case ImportEntityColumnType.accessToken:
entityData.accessToken = importData.rows[i][j];
break;
case ImportEntityColumnType.name:
entityData.name = importData.rows[i][j];
break;
@ -233,6 +263,96 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom
case ImportEntityColumnType.description:
entityData.description = importData.rows[i][j];
break;
case ImportEntityColumnType.accessToken:
entityData.credential.accessToken = importData.rows[i][j];
break;
case ImportEntityColumnType.x509:
entityData.credential.x509 = importData.rows[i][j];
break;
case ImportEntityColumnType.mqttClientId:
if (!entityData.credential.mqtt) {
entityData.credential.mqtt = getDeviceCredentialMQTTDefault();
}
entityData.credential.mqtt.clientId = importData.rows[i][j];
break;
case ImportEntityColumnType.mqttUserName:
if (!entityData.credential.mqtt) {
entityData.credential.mqtt = getDeviceCredentialMQTTDefault();
}
entityData.credential.mqtt.userName = importData.rows[i][j];
break;
case ImportEntityColumnType.mqttPassword:
if (!entityData.credential.mqtt) {
entityData.credential.mqtt = getDeviceCredentialMQTTDefault();
}
entityData.credential.mqtt.password = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mClientEndpoint:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.client.endpoint = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mClientSecurityConfigMode:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.client.securityConfigClientMode = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mClientIdentity:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.client.identity = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mClientKey:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.client.key = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mClientCert:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.client.cert = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mBootstrapServerSecurityMode:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.bootstrap.bootstrapServer.securityMode = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mBootstrapServerClientPublicKeyOrId:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.bootstrap.bootstrapServer.clientPublicKeyOrId = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mBootstrapServerClientSecretKey:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.bootstrap.bootstrapServer.clientSecretKey = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mServerSecurityMode:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.bootstrap.lwm2mServer.securityMode = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mServerClientPublicKeyOrId:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.bootstrap.lwm2mServer.clientPublicKeyOrId = importData.rows[i][j];
break;
case ImportEntityColumnType.lwm2mServerClientSecretKey:
if (!entityData.credential.lwm2m) {
entityData.credential.lwm2m = getLwm2mSecurityConfigModelsDefault();
}
entityData.credential.lwm2m.bootstrap.lwm2mServer.clientSecretKey = importData.rows[i][j];
break;
case ImportEntityColumnType.edgeLicenseKey:
(entityData as EdgeImportEntityData).edgeLicenseKey = importData.rows[i][j];
break;
@ -268,16 +388,17 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom
private constructDraftImportEntityData(): ImportEntityData {
const entityData: ImportEntityData = {
lineNumber: 1,
name: '',
type: '',
description: '',
gateway: null,
label: '',
accessToken: '',
attributes: {
server: [],
shared: []
},
credential: {},
timeseries: []
};
if (this.entityType === EntityType.EDGE) {
@ -292,5 +413,49 @@ export class ImportDialogCsvComponent extends DialogComponent<ImportDialogCsvCom
}
}
initEditor() {
if (!this.initEditorComponent) {
this.createEditor(this.failureDetailsEditorElmRef, this.statistical.error.errors);
}
}
private createEditor(editorElementRef: ElementRef, content: string): void {
const editorElement = editorElementRef.nativeElement;
let editorOptions: Partial<Ace.EditorOptions> = {
mode: 'ace/mode/java',
theme: 'ace/theme/github',
showGutter: false,
showPrintMargin: false,
readOnly: true
};
const advancedOptions = {
enableSnippets: false,
enableBasicAutocompletion: false,
enableLiveAutocompletion: false
};
editorOptions = {...editorOptions, ...advancedOptions};
getAce().subscribe(
(ace) => {
const editor = ace.edit(editorElement, editorOptions);
editor.session.setUseWrapMode(false);
editor.setValue(content, -1);
this.updateEditorSize(editorElement, content, editor);
}
);
}
private updateEditorSize(editorElement: any, content: string, editor: Ace.Editor) {
let newHeight = 200;
if (content && content.length > 0) {
const lines = content.split('\n');
newHeight = 16 * lines.length + 24;
}
const minHeight = Math.min(200, newHeight);
this.renderer.setStyle(editorElement, 'minHeight', minHeight.toString() + 'px');
this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px');
editor.resize();
}
}

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Widget, WidgetType, WidgetTypeDetails } from '@app/shared/models/widget.models';
import { Widget, WidgetTypeDetails } from '@app/shared/models/widget.models';
import { DashboardLayoutId } from '@shared/models/dashboard.models';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
@ -46,8 +46,22 @@ export enum ImportEntityColumnType {
sharedAttribute = 'SHARED_ATTRIBUTE',
serverAttribute = 'SERVER_ATTRIBUTE',
timeseries = 'TIMESERIES',
entityField = 'ENTITY_FIELD',
accessToken = 'ACCESS_TOKEN',
x509 = 'X509',
mqttClientId = 'MQTT_CLIENT_ID',
mqttUserName = 'MQTT_USER_NAME',
mqttPassword = 'MQTT_PASSWORD',
lwm2mClientEndpoint = 'LWM2M_CLIENT_ENDPOINT',
lwm2mClientSecurityConfigMode = 'LWM2M_CLIENT_SECURITY_CONFIG_MODE',
lwm2mClientIdentity = 'LWM2M_CLIENT_IDENTITY',
lwm2mClientKey = 'LWM2M_CLIENT_KEY',
lwm2mClientCert = 'LWM2M_CLIENT_CERT',
lwm2mBootstrapServerSecurityMode = 'LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE',
lwm2mBootstrapServerClientPublicKeyOrId = 'LWM2M_BOOTSTRAP_SERVER_PUBLIC_KEY_OR_ID',
lwm2mBootstrapServerClientSecretKey = 'LWM2M_BOOTSTRAP_SERVER_SECRET_KEY',
lwm2mServerSecurityMode = 'LWM2M_SERVER_SECURITY_MODE',
lwm2mServerClientPublicKeyOrId = 'LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID',
lwm2mServerClientSecretKey = 'LWM2M_SERVER_CLIENT_SECRET_KEY',
isGateway = 'IS_GATEWAY',
description = 'DESCRIPTION',
edgeLicenseKey = 'EDGE_LICENSE_KEY',
@ -68,8 +82,22 @@ export const importEntityColumnTypeTranslations = new Map<ImportEntityColumnType
[ImportEntityColumnType.sharedAttribute, 'import.column-type.shared-attribute'],
[ImportEntityColumnType.serverAttribute, 'import.column-type.server-attribute'],
[ImportEntityColumnType.timeseries, 'import.column-type.timeseries'],
[ImportEntityColumnType.entityField, 'import.column-type.entity-field'],
[ImportEntityColumnType.accessToken, 'import.column-type.access-token'],
[ImportEntityColumnType.x509, 'import.column-type.x509'],
[ImportEntityColumnType.mqttClientId, 'import.column-type.mqtt.client-id'],
[ImportEntityColumnType.mqttUserName, 'import.column-type.mqtt.user-name'],
[ImportEntityColumnType.mqttPassword, 'import.column-type.mqtt.password'],
[ImportEntityColumnType.lwm2mClientEndpoint, 'import.column-type.lwm2m.client-endpoint'],
[ImportEntityColumnType.lwm2mClientSecurityConfigMode, 'import.column-type.lwm2m.security-config-mode'],
[ImportEntityColumnType.lwm2mClientIdentity, 'import.column-type.lwm2m.client-identity'],
[ImportEntityColumnType.lwm2mClientKey, 'import.column-type.lwm2m.client-key'],
[ImportEntityColumnType.lwm2mClientCert, 'import.column-type.lwm2m.client-cert'],
[ImportEntityColumnType.lwm2mBootstrapServerSecurityMode, 'import.column-type.lwm2m.bootstrap-server-security-mode'],
[ImportEntityColumnType.lwm2mBootstrapServerClientPublicKeyOrId, 'import.column-type.lwm2m.bootstrap-server-public-key-id'],
[ImportEntityColumnType.lwm2mBootstrapServerClientSecretKey, 'import.column-type.lwm2m.bootstrap-server-secret-key'],
[ImportEntityColumnType.lwm2mServerSecurityMode, 'import.column-type.lwm2m.lwm2m-server-security-mode'],
[ImportEntityColumnType.lwm2mServerClientPublicKeyOrId, 'import.column-type.lwm2m.lwm2m-server-public-key-id'],
[ImportEntityColumnType.lwm2mServerClientSecretKey, 'import.column-type.lwm2m.lwm2m-server-secret-key'],
[ImportEntityColumnType.isGateway, 'import.column-type.isgateway'],
[ImportEntityColumnType.description, 'import.column-type.description'],
[ImportEntityColumnType.edgeLicenseKey, 'import.column-type.edge-license-key'],

View File

@ -21,7 +21,7 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { Dashboard, DashboardLayoutId } from '@shared/models/dashboard.models';
import { deepClone, isDefined, isObject, isUndefined } from '@core/utils';
import { deepClone, isDefined, isObject, isString, isUndefined } from '@core/utils';
import { WINDOW } from '@core/services/window.service';
import { DOCUMENT } from '@angular/common';
import {
@ -563,6 +563,8 @@ export class ImportExportService {
if (isObject(obj2[key])) {
obj1[key] = obj1[key] || {};
obj1[key] = {...obj1[key], ...this.sumObject(obj1[key], obj2[key])};
} else if (isString(obj2[key])) {
obj1[key] = (obj1[key] || '') + `${obj2[key]}\n`;
} else {
obj1[key] = (obj1[key] || 0) + obj2[key];
}

View File

@ -31,10 +31,15 @@
<ng-container matColumnDef="type">
<mat-header-cell *matHeaderCellDef style="flex: 0 0 40%" class="mat-column-type"> {{ 'import.column-type.column-type' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let column">
<mat-select matInput [(ngModel)]="column.type" (ngModelChange)="columnsUpdated()">
<mat-select [(ngModel)]="column.type" (ngModelChange)="columnsUpdated()">
<mat-option *ngFor="let type of columnTypes" [value]="type.value" [disabled]="type.disabled">
{{ columnTypesTranslations.get(type.value) | translate }}
</mat-option>
<mat-optgroup label="{{ 'import.credentials' | translate }}" *ngIf="entityType === entityTypeDevice">
<mat-option *ngFor="let credential of columnDeviceCredentials" [value]="credential.value" [disabled]="credential.disabled">
{{ columnTypesTranslations.get(credential.value) | translate }}
</mat-option>
</mat-optgroup>
</mat-select>
</mat-cell>
</ng-container>

View File

@ -57,8 +57,12 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce
columnTypes: AssignmentColumnType[] = [];
columnDeviceCredentials: AssignmentColumnType[] = [];
columnTypesTranslations = importEntityColumnTypeTranslations;
readonly entityTypeDevice = EntityType.DEVICE;
private columns: CsvColumnParam[];
private valid = true;
@ -83,9 +87,26 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce
{ value: ImportEntityColumnType.sharedAttribute },
{ value: ImportEntityColumnType.serverAttribute },
{ value: ImportEntityColumnType.timeseries },
{ value: ImportEntityColumnType.accessToken },
{ value: ImportEntityColumnType.isGateway }
);
this.columnDeviceCredentials.push(
{ value: ImportEntityColumnType.accessToken },
{ value: ImportEntityColumnType.x509 },
{ value: ImportEntityColumnType.mqttClientId },
{ value: ImportEntityColumnType.mqttUserName },
{ value: ImportEntityColumnType.mqttPassword },
{ value: ImportEntityColumnType.lwm2mClientEndpoint },
{ value: ImportEntityColumnType.lwm2mClientSecurityConfigMode },
{ value: ImportEntityColumnType.lwm2mClientIdentity },
{ value: ImportEntityColumnType.lwm2mClientKey },
{ value: ImportEntityColumnType.lwm2mClientCert },
{ value: ImportEntityColumnType.lwm2mBootstrapServerSecurityMode },
{ value: ImportEntityColumnType.lwm2mBootstrapServerClientPublicKeyOrId },
{ value: ImportEntityColumnType.lwm2mBootstrapServerClientSecretKey },
{ value: ImportEntityColumnType.lwm2mServerSecurityMode },
{ value: ImportEntityColumnType.lwm2mServerClientPublicKeyOrId },
{ value: ImportEntityColumnType.lwm2mServerClientSecretKey },
);
break;
case EntityType.ASSET:
this.columnTypes.push(
@ -123,8 +144,6 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce
const isSelectName = this.columns.findIndex((column) => column.type === ImportEntityColumnType.name) > -1;
const isSelectType = this.columns.findIndex((column) => column.type === ImportEntityColumnType.type) > -1;
const isSelectLabel = this.columns.findIndex((column) => column.type === ImportEntityColumnType.label) > -1;
const isSelectCredentials = this.columns.findIndex((column) => column.type === ImportEntityColumnType.accessToken) > -1;
const isSelectGateway = this.columns.findIndex((column) => column.type === ImportEntityColumnType.isGateway) > -1;
const isSelectDescription = this.columns.findIndex((column) => column.type === ImportEntityColumnType.description) > -1;
const isSelectEdgeLicenseKey = this.columns.findIndex((column) => column.type === ImportEntityColumnType.edgeLicenseKey) > -1;
const isSelectCloudEndpoint = this.columns.findIndex((column) => column.type === ImportEntityColumnType.cloudEndpoint) > -1;
@ -139,14 +158,19 @@ export class TableColumnsAssignmentComponent implements OnInit, ControlValueAcce
this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.label).disabled = isSelectLabel;
this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.description).disabled = isSelectDescription;
if (this.entityType === EntityType.DEVICE) {
const isSelectGateway = this.columns.findIndex((column) => column.type === ImportEntityColumnType.isGateway) > -1;
const isGatewayColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.isGateway);
if (isGatewayColumnType) {
isGatewayColumnType.disabled = isSelectGateway;
}
const accessTokenColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.accessToken);
if (accessTokenColumnType) {
accessTokenColumnType.disabled = isSelectCredentials;
this.columnDeviceCredentials.forEach((columnCredential) => {
columnCredential.disabled = this.columns.findIndex(column => column.type === columnCredential.value) > -1;
});
}
const edgeLicenseKeyColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.edgeLicenseKey);
if (edgeLicenseKeyColumnType) {
edgeLicenseKeyColumnType.disabled = isSelectEdgeLicenseKey;

View File

@ -743,6 +743,14 @@ export interface DeviceCredentialMQTTBasic {
password: string;
}
export function getDeviceCredentialMQTTDefault(): DeviceCredentialMQTTBasic {
return {
clientId: '',
userName: '',
password: ''
};
}
export interface DeviceSearchQuery extends EntitySearchQuery {
deviceTypes: Array<string>;
}

View File

@ -17,6 +17,8 @@
import { EntityType } from '@shared/models/entity-type.models';
import { AttributeData } from './telemetry/telemetry.models';
import { EntityId } from '@shared/models/id/entity-id';
import { DeviceCredentialMQTTBasic } from '@shared/models/device.models';
import { Lwm2mSecurityConfigModels } from '@shared/models/lwm2m-security-config.models';
export interface EntityInfo {
name?: string;
@ -32,12 +34,18 @@ export interface EntityInfoData {
}
export interface ImportEntityData {
lineNumber: number;
name: string;
type: string;
label: string;
gateway: boolean;
description: string;
accessToken: string;
credential: {
accessToken?: string;
x509?: string;
mqtt?: DeviceCredentialMQTTBasic;
lwm2m?: Lwm2mSecurityConfigModels;
};
attributes: {
server: AttributeData[],
shared: AttributeData[]
@ -61,6 +69,7 @@ export interface ImportEntitiesResultInfo {
};
error?: {
entity: number;
errors?: string;
};
}

View File

@ -60,6 +60,20 @@ export interface Lwm2mSecurityConfigModels {
bootstrap: BootstrapSecurityConfig;
}
export function getLwm2mSecurityConfigModelsDefault(): Lwm2mSecurityConfigModels {
return {
client: {
securityConfigClientMode: Lwm2mSecurityType.NO_SEC,
endpoint: ''
},
bootstrap: {
bootstrapServer: getDefaultServerSecurityConfig(),
lwm2mServer: getDefaultServerSecurityConfig()
}
};
}
export function getDefaultClientSecurityConfig(securityConfigMode: Lwm2mSecurityType, endPoint = ''): ClientSecurityConfig {
let security = {
securityConfigClientMode: securityConfigMode,

View File

@ -2184,9 +2184,11 @@
"column-title": "Title",
"column-example": "Example value data",
"column-key": "Attribute/telemetry key",
"credentials": "Credentials",
"csv-delimiter": "CSV delimiter",
"csv-first-line-header": "First line contains column names",
"csv-update-data": "Update attributes/telemetry",
"details": "Details",
"import-csv-number-columns-error": "A file should contain at least two columns",
"import-csv-invalid-format-error": "Invalid file format. Line: '{{line}}'",
"column-type": {
@ -2200,6 +2202,25 @@
"timeseries": "Timeseries",
"entity-field": "Entity field",
"access-token": "Access token",
"x509": "X.509",
"mqtt": {
"client-id": "MQTT client ID",
"user-name": "MQTT user name",
"password": "MQTT password"
},
"lwm2m": {
"client-endpoint": "LwM2M endpoint client name",
"security-config-mode": "LwM2M security config mode",
"client-identity": "LwM2M client identity",
"client-key": "LwM2M client key",
"client-cert": "LwM2M client public key",
"bootstrap-server-security-mode": "LwM2M bootstrap server security mode",
"bootstrap-server-secret-key": "LwM2M bootstrap server secret key",
"bootstrap-server-public-key-id": "LwM2M bootstrap server public key or id",
"lwm2m-server-security-mode": "LwM2M server security mode",
"lwm2m-server-secret-key": "LwM2M server secret key",
"lwm2m-server-public-key-id": "LwM2M server public key or id"
},
"isgateway": "Is Gateway",
"activity-time-from-gateway-device": "Activity time from gateway device",
"description": "Description",