Commit 1e8fc7086b1d977fbecc69873f266a8e396c8d47
1 parent
5de2e7b6
Device profile import/export support
Showing
4 changed files
with
116 additions
and
5 deletions
... | ... | @@ -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" | ... | ... |