Commit 1e8fc7086b1d977fbecc69873f266a8e396c8d47

Authored by Igor Kulikov
1 parent 5de2e7b6

Device profile import/export support

@@ -55,6 +55,8 @@ import { RequestConfig } from '@core/http/http-utils'; @@ -55,6 +55,8 @@ import { RequestConfig } from '@core/http/http-utils';
55 import { RuleChain, RuleChainImport, RuleChainMetaData } from '@shared/models/rule-chain.models'; 55 import { RuleChain, RuleChainImport, RuleChainMetaData } from '@shared/models/rule-chain.models';
56 import { RuleChainService } from '@core/http/rule-chain.service'; 56 import { RuleChainService } from '@core/http/rule-chain.service';
57 import { FiltersInfo } from '@shared/models/query/query.models'; 57 import { FiltersInfo } from '@shared/models/query/query.models';
  58 +import { DeviceProfileService } from '@core/http/device-profile.service';
  59 +import { DeviceProfile } from '@shared/models/device.models';
58 60
59 // @dynamic 61 // @dynamic
60 @Injectable() 62 @Injectable()
@@ -67,6 +69,7 @@ export class ImportExportService { @@ -67,6 +69,7 @@ export class ImportExportService {
67 private dashboardService: DashboardService, 69 private dashboardService: DashboardService,
68 private dashboardUtils: DashboardUtilsService, 70 private dashboardUtils: DashboardUtilsService,
69 private widgetService: WidgetService, 71 private widgetService: WidgetService,
  72 + private deviceProfileService: DeviceProfileService,
70 private entityService: EntityService, 73 private entityService: EntityService,
71 private ruleChainService: RuleChainService, 74 private ruleChainService: RuleChainService,
72 private utils: UtilsService, 75 private utils: UtilsService,
@@ -420,6 +423,37 @@ export class ImportExportService { @@ -420,6 +423,37 @@ export class ImportExportService {
420 ); 423 );
421 } 424 }
422 425
  426 + public exportDeviceProfile(deviceProfileId: string) {
  427 + this.deviceProfileService.getDeviceProfile(deviceProfileId).subscribe(
  428 + (deviceProfile) => {
  429 + let name = deviceProfile.name;
  430 + name = name.toLowerCase().replace(/\W/g, '_');
  431 + this.exportToPc(this.prepareDeviceProfileExport(deviceProfile), name);
  432 + },
  433 + (e) => {
  434 + this.handleExportError(e, 'device-profile.export-failed-error');
  435 + }
  436 + );
  437 + }
  438 +
  439 + public importDeviceProfile(): Observable<DeviceProfile> {
  440 + return this.openImportDialog('device-profile.import', 'device-profile.device-profile-file').pipe(
  441 + mergeMap((deviceProfile: DeviceProfile) => {
  442 + if (!this.validateImportedDeviceProfile(deviceProfile)) {
  443 + this.store.dispatch(new ActionNotificationShow(
  444 + {message: this.translate.instant('device-profile.invalid-device-profile-file-error'),
  445 + type: 'error'}));
  446 + throw new Error('Invalid device profile file');
  447 + } else {
  448 + return this.deviceProfileService.saveDeviceProfile(deviceProfile);
  449 + }
  450 + }),
  451 + catchError((err) => {
  452 + return of(null);
  453 + })
  454 + );
  455 + }
  456 +
423 public exportJSZip(data: object, filename: string) { 457 public exportJSZip(data: object, filename: string) {
424 import('jszip').then((JSZip) => { 458 import('jszip').then((JSZip) => {
425 const jsZip = new JSZip.default(); 459 const jsZip = new JSZip.default();
@@ -463,6 +497,17 @@ export class ImportExportService { @@ -463,6 +497,17 @@ export class ImportExportService {
463 return true; 497 return true;
464 } 498 }
465 499
  500 + private validateImportedDeviceProfile(deviceProfile: DeviceProfile): boolean {
  501 + if (isUndefined(deviceProfile.name)
  502 + || isUndefined(deviceProfile.type)
  503 + || isUndefined(deviceProfile.transportType)
  504 + || isUndefined(deviceProfile.provisionType)
  505 + || isUndefined(deviceProfile.profileData)) {
  506 + return false;
  507 + }
  508 + return true;
  509 + }
  510 +
466 private sumObject(obj1: any, obj2: any): any { 511 private sumObject(obj1: any, obj2: any): any {
467 Object.keys(obj2).map((key) => { 512 Object.keys(obj2).map((key) => {
468 if (isObject(obj2[key])) { 513 if (isObject(obj2[key])) {
@@ -740,6 +785,12 @@ export class ImportExportService { @@ -740,6 +785,12 @@ export class ImportExportService {
740 return dashboard; 785 return dashboard;
741 } 786 }
742 787
  788 + private prepareDeviceProfileExport(deviceProfile: DeviceProfile): DeviceProfile {
  789 + deviceProfile = this.prepareExport(deviceProfile);
  790 + deviceProfile.default = false;
  791 + return deviceProfile;
  792 + }
  793 +
743 private prepareExport(data: any): any { 794 private prepareExport(data: any): any {
744 const exportedData = deepClone(data); 795 const exportedData = deepClone(data);
745 if (isDefined(exportedData.id)) { 796 if (isDefined(exportedData.id)) {
@@ -18,6 +18,12 @@ @@ -18,6 +18,12 @@
18 <div class="tb-details-buttons" fxLayout.xs="column" *ngIf="!standalone"> 18 <div class="tb-details-buttons" fxLayout.xs="column" *ngIf="!standalone">
19 <button mat-raised-button color="primary" 19 <button mat-raised-button color="primary"
20 [disabled]="(isLoading$ | async)" 20 [disabled]="(isLoading$ | async)"
  21 + (click)="onEntityAction($event, 'export')"
  22 + [fxShow]="!isEdit">
  23 + {{'device-profile.export' | translate }}
  24 + </button>
  25 + <button mat-raised-button color="primary"
  26 + [disabled]="(isLoading$ | async)"
21 (click)="onEntityAction($event, 'setDefault')" 27 (click)="onEntityAction($event, 'setDefault')"
22 [fxShow]="!isEdit && !entity?.default"> 28 [fxShow]="!isEdit && !entity?.default">
23 {{'device-profile.set-default' | translate }} 29 {{'device-profile.set-default' | translate }}
@@ -20,7 +20,8 @@ import { @@ -20,7 +20,8 @@ import {
20 checkBoxCell, 20 checkBoxCell,
21 DateEntityTableColumn, 21 DateEntityTableColumn,
22 EntityTableColumn, 22 EntityTableColumn,
23 - EntityTableConfig 23 + EntityTableConfig,
  24 + HeaderActionDescriptor
24 } from '@home/models/entity/entities-table-config.models'; 25 } from '@home/models/entity/entities-table-config.models';
25 import { TranslateService } from '@ngx-translate/core'; 26 import { TranslateService } from '@ngx-translate/core';
26 import { DatePipe } from '@angular/common'; 27 import { DatePipe } from '@angular/common';
@@ -33,14 +34,15 @@ import { @@ -33,14 +34,15 @@ import {
33 deviceTransportTypeTranslationMap 34 deviceTransportTypeTranslationMap
34 } from '@shared/models/device.models'; 35 } from '@shared/models/device.models';
35 import { DeviceProfileService } from '@core/http/device-profile.service'; 36 import { DeviceProfileService } from '@core/http/device-profile.service';
36 -import { DeviceProfileComponent } from '../../components/profile/device-profile.component'; 37 +import { DeviceProfileComponent } from '@home/components/profile/device-profile.component';
37 import { DeviceProfileTabsComponent } from './device-profile-tabs.component'; 38 import { DeviceProfileTabsComponent } from './device-profile-tabs.component';
38 import { Observable } from 'rxjs'; 39 import { Observable } from 'rxjs';
39 import { MatDialog } from '@angular/material/dialog'; 40 import { MatDialog } from '@angular/material/dialog';
40 import { 41 import {
41 AddDeviceProfileDialogComponent, 42 AddDeviceProfileDialogComponent,
42 AddDeviceProfileDialogData 43 AddDeviceProfileDialogData
43 -} from '../../components/profile/add-device-profile-dialog.component'; 44 +} from '@home/components/profile/add-device-profile-dialog.component';
  45 +import { ImportExportService } from '@home/components/import-export/import-export.service';
44 46
45 @Injectable() 47 @Injectable()
46 export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableConfig<DeviceProfile>> { 48 export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableConfig<DeviceProfile>> {
@@ -48,6 +50,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -48,6 +50,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
48 private readonly config: EntityTableConfig<DeviceProfile> = new EntityTableConfig<DeviceProfile>(); 50 private readonly config: EntityTableConfig<DeviceProfile> = new EntityTableConfig<DeviceProfile>();
49 51
50 constructor(private deviceProfileService: DeviceProfileService, 52 constructor(private deviceProfileService: DeviceProfileService,
  53 + private importExport: ImportExportService,
51 private translate: TranslateService, 54 private translate: TranslateService,
52 private datePipe: DatePipe, 55 private datePipe: DatePipe,
53 private dialogService: DialogService, 56 private dialogService: DialogService,
@@ -81,6 +84,12 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -81,6 +84,12 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
81 84
82 this.config.cellActionDescriptors.push( 85 this.config.cellActionDescriptors.push(
83 { 86 {
  87 + name: this.translate.instant('device-profile.export'),
  88 + icon: 'file_download',
  89 + isEnabled: () => true,
  90 + onAction: ($event, entity) => this.exportDeviceProfile($event, entity)
  91 + },
  92 + {
84 name: this.translate.instant('device-profile.set-default'), 93 name: this.translate.instant('device-profile.set-default'),
85 icon: 'flag', 94 icon: 'flag',
86 isEnabled: (deviceProfile) => !deviceProfile.default, 95 isEnabled: (deviceProfile) => !deviceProfile.default,
@@ -101,7 +110,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -101,7 +110,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
101 this.config.onEntityAction = action => this.onDeviceProfileAction(action); 110 this.config.onEntityAction = action => this.onDeviceProfileAction(action);
102 this.config.deleteEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default; 111 this.config.deleteEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default;
103 this.config.entitySelectionEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default; 112 this.config.entitySelectionEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default;
104 - this.config.addEntity = () => this.addDeviceProfile(); 113 + this.config.addActionDescriptors = this.configureAddActions();
105 } 114 }
106 115
107 resolve(): EntityTableConfig<DeviceProfile> { 116 resolve(): EntityTableConfig<DeviceProfile> {
@@ -110,6 +119,25 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -110,6 +119,25 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
110 return this.config; 119 return this.config;
111 } 120 }
112 121
  122 + configureAddActions(): Array<HeaderActionDescriptor> {
  123 + const actions: Array<HeaderActionDescriptor> = [];
  124 + actions.push(
  125 + {
  126 + name: this.translate.instant('device-profile.create-device-profile'),
  127 + icon: 'insert_drive_file',
  128 + isEnabled: () => true,
  129 + onAction: () => this.addDeviceProfile()
  130 + },
  131 + {
  132 + name: this.translate.instant('device-profile.import'),
  133 + icon: 'file_upload',
  134 + isEnabled: () => true,
  135 + onAction: ($event) => this.importDeviceProfile($event)
  136 + }
  137 + );
  138 + return actions;
  139 + }
  140 +
113 addDeviceProfile(): Observable<DeviceProfile> { 141 addDeviceProfile(): Observable<DeviceProfile> {
114 return this.dialog.open<AddDeviceProfileDialogComponent, AddDeviceProfileDialogData, 142 return this.dialog.open<AddDeviceProfileDialogComponent, AddDeviceProfileDialogData,
115 DeviceProfile>(AddDeviceProfileDialogComponent, { 143 DeviceProfile>(AddDeviceProfileDialogComponent, {
@@ -144,11 +172,31 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon @@ -144,11 +172,31 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
144 ); 172 );
145 } 173 }
146 174
  175 + importDeviceProfile($event: Event) {
  176 + this.importExport.importDeviceProfile().subscribe(
  177 + (deviceProfile) => {
  178 + if (deviceProfile) {
  179 + this.config.table.updateData();
  180 + }
  181 + }
  182 + );
  183 + }
  184 +
  185 + exportDeviceProfile($event: Event, deviceProfile: DeviceProfile) {
  186 + if ($event) {
  187 + $event.stopPropagation();
  188 + }
  189 + this.importExport.exportDeviceProfile(deviceProfile.id.id);
  190 + }
  191 +
147 onDeviceProfileAction(action: EntityAction<DeviceProfile>): boolean { 192 onDeviceProfileAction(action: EntityAction<DeviceProfile>): boolean {
148 switch (action.action) { 193 switch (action.action) {
149 case 'setDefault': 194 case 'setDefault':
150 this.setDefaultDeviceProfile(action.event, action.entity); 195 this.setDefaultDeviceProfile(action.event, action.entity);
151 return true; 196 return true;
  197 + case 'export':
  198 + this.exportDeviceProfile(action.event, action.entity);
  199 + return true;
152 } 200 }
153 return false; 201 return false;
154 } 202 }
@@ -1091,7 +1091,13 @@ @@ -1091,7 +1091,13 @@
1091 "schedule-time": "Time", 1091 "schedule-time": "Time",
1092 "schedule-time-from": "From", 1092 "schedule-time-from": "From",
1093 "schedule-time-to": "To", 1093 "schedule-time-to": "To",
1094 - "schedule-days-of-week-required": "At least one day of week should be selected." 1094 + "schedule-days-of-week-required": "At least one day of week should be selected.",
  1095 + "create-device-profile": "Create new device profile",
  1096 + "import": "Import device profile",
  1097 + "export": "Export device profile",
  1098 + "export-failed-error": "Unable to export device profile: {{error}}",
  1099 + "device-profile-file": "Device profile file",
  1100 + "invalid-device-profile-file-error": "Unable to import device profile: Invalid device profile data structure."
1095 }, 1101 },
1096 "dialog": { 1102 "dialog": {
1097 "close": "Close dialog" 1103 "close": "Close dialog"