Merge branch 'feature/bulk-import/device-credentials' of https://github.com/vvlladd28/thingsboard into feature/bulk-import
This commit is contained in:
commit
41e572490e
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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'],
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user