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 55 import { RuleChain, RuleChainImport, RuleChainMetaData } from '@shared/models/rule-chain.models';
56 56 import { RuleChainService } from '@core/http/rule-chain.service';
57 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 61 // @dynamic
60 62 @Injectable()
... ... @@ -67,6 +69,7 @@ export class ImportExportService {
67 69 private dashboardService: DashboardService,
68 70 private dashboardUtils: DashboardUtilsService,
69 71 private widgetService: WidgetService,
  72 + private deviceProfileService: DeviceProfileService,
70 73 private entityService: EntityService,
71 74 private ruleChainService: RuleChainService,
72 75 private utils: UtilsService,
... ... @@ -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 457 public exportJSZip(data: object, filename: string) {
424 458 import('jszip').then((JSZip) => {
425 459 const jsZip = new JSZip.default();
... ... @@ -463,6 +497,17 @@ export class ImportExportService {
463 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 511 private sumObject(obj1: any, obj2: any): any {
467 512 Object.keys(obj2).map((key) => {
468 513 if (isObject(obj2[key])) {
... ... @@ -740,6 +785,12 @@ export class ImportExportService {
740 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 794 private prepareExport(data: any): any {
744 795 const exportedData = deepClone(data);
745 796 if (isDefined(exportedData.id)) {
... ...
... ... @@ -18,6 +18,12 @@
18 18 <div class="tb-details-buttons" fxLayout.xs="column" *ngIf="!standalone">
19 19 <button mat-raised-button color="primary"
20 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 27 (click)="onEntityAction($event, 'setDefault')"
22 28 [fxShow]="!isEdit && !entity?.default">
23 29 {{'device-profile.set-default' | translate }}
... ...
... ... @@ -20,7 +20,8 @@ import {
20 20 checkBoxCell,
21 21 DateEntityTableColumn,
22 22 EntityTableColumn,
23   - EntityTableConfig
  23 + EntityTableConfig,
  24 + HeaderActionDescriptor
24 25 } from '@home/models/entity/entities-table-config.models';
25 26 import { TranslateService } from '@ngx-translate/core';
26 27 import { DatePipe } from '@angular/common';
... ... @@ -33,14 +34,15 @@ import {
33 34 deviceTransportTypeTranslationMap
34 35 } from '@shared/models/device.models';
35 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 38 import { DeviceProfileTabsComponent } from './device-profile-tabs.component';
38 39 import { Observable } from 'rxjs';
39 40 import { MatDialog } from '@angular/material/dialog';
40 41 import {
41 42 AddDeviceProfileDialogComponent,
42 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 47 @Injectable()
46 48 export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableConfig<DeviceProfile>> {
... ... @@ -48,6 +50,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
48 50 private readonly config: EntityTableConfig<DeviceProfile> = new EntityTableConfig<DeviceProfile>();
49 51
50 52 constructor(private deviceProfileService: DeviceProfileService,
  53 + private importExport: ImportExportService,
51 54 private translate: TranslateService,
52 55 private datePipe: DatePipe,
53 56 private dialogService: DialogService,
... ... @@ -81,6 +84,12 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
81 84
82 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 93 name: this.translate.instant('device-profile.set-default'),
85 94 icon: 'flag',
86 95 isEnabled: (deviceProfile) => !deviceProfile.default,
... ... @@ -101,7 +110,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
101 110 this.config.onEntityAction = action => this.onDeviceProfileAction(action);
102 111 this.config.deleteEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default;
103 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 116 resolve(): EntityTableConfig<DeviceProfile> {
... ... @@ -110,6 +119,25 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
110 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 141 addDeviceProfile(): Observable<DeviceProfile> {
114 142 return this.dialog.open<AddDeviceProfileDialogComponent, AddDeviceProfileDialogData,
115 143 DeviceProfile>(AddDeviceProfileDialogComponent, {
... ... @@ -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 192 onDeviceProfileAction(action: EntityAction<DeviceProfile>): boolean {
148 193 switch (action.action) {
149 194 case 'setDefault':
150 195 this.setDefaultDeviceProfile(action.event, action.entity);
151 196 return true;
  197 + case 'export':
  198 + this.exportDeviceProfile(action.event, action.entity);
  199 + return true;
152 200 }
153 201 return false;
154 202 }
... ...
... ... @@ -1091,7 +1091,13 @@
1091 1091 "schedule-time": "Time",
1092 1092 "schedule-time-from": "From",
1093 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 1102 "dialog": {
1097 1103 "close": "Close dialog"
... ...