Commit ce1a3ea56713b327a3b5c23dc4ceb5880a4415c4
1 parent
fa3bbb28
UI: OTA updates add support external URL
Showing
6 changed files
with
141 additions
and
57 deletions
... | ... | @@ -98,10 +98,10 @@ export class DeviceProfileService { |
98 | 98 | text += this.translate.instant('ota-update.change-firmware', {count: deviceFirmwareUpdate}); |
99 | 99 | } |
100 | 100 | if (deviceSoftwareUpdate > 0) { |
101 | - text += text.length ? '<br/>' : ''; | |
101 | + text += text.length ? ' ' : ''; | |
102 | 102 | text += this.translate.instant('ota-update.change-software', {count: deviceSoftwareUpdate}); |
103 | 103 | } |
104 | - return text !== '' ? this.dialogService.confirm('', text) : of(true); | |
104 | + return text !== '' ? this.dialogService.confirm('', text, null, this.translate.instant('common.proceed')) : of(true); | |
105 | 105 | }), |
106 | 106 | mergeMap((update) => update ? this.saveDeviceProfile(deviceProfile, config) : throwError('Canceled saving device profiles'))); |
107 | 107 | } | ... | ... |
... | ... | @@ -35,6 +35,10 @@ import { PageLink } from '@shared/models/page/page-link'; |
35 | 35 | import { OtaUpdateComponent } from '@home/pages/ota-update/ota-update.component'; |
36 | 36 | import { EntityAction } from '@home/models/entity/entity-component.models'; |
37 | 37 | import { FileSizePipe } from '@shared/pipe/file-size.pipe'; |
38 | +import { ClipboardService } from 'ngx-clipboard'; | |
39 | +import { ActionNotificationShow } from '@core/notification/notification.actions'; | |
40 | +import { Store } from '@ngrx/store'; | |
41 | +import { AppState } from '@core/core.state'; | |
38 | 42 | |
39 | 43 | @Injectable() |
40 | 44 | export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<OtaPackage, PageLink, OtaPackageInfo>> { |
... | ... | @@ -44,8 +48,10 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot |
44 | 48 | |
45 | 49 | constructor(private translate: TranslateService, |
46 | 50 | private datePipe: DatePipe, |
51 | + private store: Store<AppState>, | |
47 | 52 | private otaPackageService: OtaPackageService, |
48 | - private fileSize: FileSizePipe) { | |
53 | + private fileSize: FileSizePipe, | |
54 | + private clipboardService: ClipboardService) { | |
49 | 55 | this.config.entityType = EntityType.OTA_PACKAGE; |
50 | 56 | this.config.entityComponent = OtaUpdateComponent; |
51 | 57 | this.config.entityTranslations = entityTypeTranslations.get(EntityType.OTA_PACKAGE); |
... | ... | @@ -62,15 +68,21 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot |
62 | 68 | }), |
63 | 69 | new EntityTableColumn<OtaPackageInfo>('fileName', 'ota-update.file-name', '25%'), |
64 | 70 | new EntityTableColumn<OtaPackageInfo>('dataSize', 'ota-update.file-size', '70px', entity => { |
65 | - return this.fileSize.transform(entity.dataSize || 0); | |
71 | + return entity.dataSize ? this.fileSize.transform(entity.dataSize) : ''; | |
66 | 72 | }), |
67 | 73 | new EntityTableColumn<OtaPackageInfo>('checksum', 'ota-update.checksum', '540px', entity => { |
68 | - return `${ChecksumAlgorithmTranslationMap.get(entity.checksumAlgorithm)}: ${entity.checksum}`; | |
74 | + return entity.checksum ? `${ChecksumAlgorithmTranslationMap.get(entity.checksumAlgorithm)}: ${entity.checksum}` : ''; | |
69 | 75 | }, () => ({}), false) |
70 | 76 | ); |
71 | 77 | |
72 | 78 | this.config.cellActionDescriptors.push( |
73 | 79 | { |
80 | + name: this.translate.instant('ota-update.copy-checksum'), | |
81 | + icon: 'content_copy', | |
82 | + isEnabled: (otaPackage) => !!otaPackage.checksum, | |
83 | + onAction: ($event, entity) => this.copyPackageChecksum($event, entity) | |
84 | + }, | |
85 | + { | |
74 | 86 | name: this.translate.instant('ota-update.download'), |
75 | 87 | icon: 'file_download', |
76 | 88 | isEnabled: (otaPackage) => otaPackage.hasData, |
... | ... | @@ -101,7 +113,26 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot |
101 | 113 | if ($event) { |
102 | 114 | $event.stopPropagation(); |
103 | 115 | } |
104 | - this.otaPackageService.downloadOtaPackage(otaPackageInfo.id.id).subscribe(); | |
116 | + if (otaPackageInfo.url) { | |
117 | + window.open(otaPackageInfo.url, '_blank'); | |
118 | + } else { | |
119 | + this.otaPackageService.downloadOtaPackage(otaPackageInfo.id.id).subscribe(); | |
120 | + } | |
121 | + } | |
122 | + | |
123 | + copyPackageChecksum($event: Event, otaPackageInfo: OtaPackageInfo) { | |
124 | + if ($event) { | |
125 | + $event.stopPropagation(); | |
126 | + } | |
127 | + this.clipboardService.copy(otaPackageInfo?.checksum); | |
128 | + this.store.dispatch(new ActionNotificationShow( | |
129 | + { | |
130 | + message: this.translate.instant('ota-update.checksum-copied-message'), | |
131 | + type: 'success', | |
132 | + duration: 750, | |
133 | + verticalPosition: 'bottom', | |
134 | + horizontalPosition: 'right' | |
135 | + })); | |
105 | 136 | } |
106 | 137 | |
107 | 138 | onPackageAction(action: EntityAction<OtaPackageInfo>): boolean { |
... | ... | @@ -109,6 +140,9 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot |
109 | 140 | case 'uploadPackage': |
110 | 141 | this.exportPackage(action.event, action.entity); |
111 | 142 | return true; |
143 | + case 'copyChecksum': | |
144 | + this.copyPackageChecksum(action.event, action.entity); | |
145 | + return true; | |
112 | 146 | } |
113 | 147 | return false; |
114 | 148 | } | ... | ... |
... | ... | @@ -31,6 +31,7 @@ |
31 | 31 | <div fxLayout="row" fxLayout.xs="column"> |
32 | 32 | <button mat-raised-button |
33 | 33 | ngxClipboard |
34 | + [disabled]="(isLoading$ | async)" | |
34 | 35 | (cbOnSuccess)="onPackageIdCopied()" |
35 | 36 | [cbContent]="entity?.id?.id" |
36 | 37 | [fxShow]="!isEdit"> |
... | ... | @@ -38,10 +39,9 @@ |
38 | 39 | <span translate>ota-update.copyId</span> |
39 | 40 | </button> |
40 | 41 | <button mat-raised-button |
41 | - ngxClipboard | |
42 | - (cbOnSuccess)="onPackageChecksumCopied()" | |
43 | - [cbContent]="entity?.checksum" | |
44 | - [fxShow]="!isEdit"> | |
42 | + [disabled]="(isLoading$ | async)" | |
43 | + (click)="onEntityAction($event, 'copyChecksum')" | |
44 | + [fxShow]="!isEdit && entity?.checksum"> | |
45 | 45 | <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
46 | 46 | <span translate>ota-update.copy-checksum</span> |
47 | 47 | </button> |
... | ... | @@ -49,7 +49,7 @@ |
49 | 49 | </div> |
50 | 50 | <div class="mat-padding" fxLayout="column" fxLayoutGap="8px"> |
51 | 51 | <form [formGroup]="entityForm"> |
52 | - <fieldset [disabled]="(isLoading$ | async) || !isEdit" fxLayout="column" fxLayoutGap="8px"> | |
52 | + <fieldset [disabled]="(isLoading$ | async) || !isEdit" fxLayout="column"> | |
53 | 53 | <div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column"> |
54 | 54 | <mat-form-field class="mat-block" fxFlex="45"> |
55 | 55 | <mat-label translate>ota-update.title</mat-label> |
... | ... | @@ -83,44 +83,68 @@ |
83 | 83 | </mat-option> |
84 | 84 | </mat-select> |
85 | 85 | </mat-form-field> |
86 | - <div class="mat-caption" translate *ngIf="isAdd">ota-update.warning-after-save-no-edit</div> | |
87 | - <div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayoutGap.sm="8px" fxLayout.xs="column" fxLayout.md="column"> | |
88 | - <mat-form-field class="mat-block" fxFlex="33"> | |
89 | - <mat-label translate>ota-update.checksum-algorithm</mat-label> | |
90 | - <mat-select formControlName="checksumAlgorithm"> | |
91 | - <mat-option *ngFor="let checksumAlgorithm of checksumAlgorithms" [value]="checksumAlgorithm"> | |
92 | - {{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }} | |
93 | - </mat-option> | |
94 | - </mat-select> | |
95 | - </mat-form-field> | |
96 | - <mat-form-field class="mat-block" fxFlex> | |
97 | - <mat-label translate>ota-update.checksum</mat-label> | |
98 | - <input matInput formControlName="checksum" type="text" [readonly]="!isAdd"> | |
99 | - </mat-form-field> | |
100 | - </div> | |
101 | 86 | <section *ngIf="isAdd"> |
102 | - <tb-file-input | |
103 | - formControlName="file" | |
104 | - workFromFileObj="true" | |
105 | - required | |
106 | - dropLabel="{{'ota-update.drop-file' | translate}}"> | |
107 | - </tb-file-input> | |
87 | + <div class="mat-caption" style="margin: -8px 0 8px;" translate>ota-update.warning-after-save-no-edit</div> | |
88 | + <mat-radio-group formControlName="resource" fxLayoutGap="16px"> | |
89 | + <mat-radio-button value="file">Upload binary file</mat-radio-button> | |
90 | + <mat-radio-button value="url">Use external URL</mat-radio-button> | |
91 | + </mat-radio-group> | |
108 | 92 | </section> |
109 | - <section *ngIf="!isAdd"> | |
110 | - <div fxLayout="row" fxLayoutGap.gt-md="8px" fxLayoutGap.sm="8px" fxLayout.xs="column" fxLayout.md="column"> | |
93 | + <section *ngIf="entityForm.get('resource').value === 'file'"> | |
94 | + <section *ngIf="isAdd"> | |
95 | + <tb-file-input | |
96 | + formControlName="file" | |
97 | + workFromFileObj="true" | |
98 | + [required]="entityForm.get('resource').value === 'file'" | |
99 | + dropLabel="{{'ota-update.drop-file' | translate}}"> | |
100 | + </tb-file-input> | |
101 | + <mat-checkbox formControlName="generateChecksum" style="margin-top: 16px"> | |
102 | + {{ 'ota-update.auto-generate-checksum' | translate }} | |
103 | + </mat-checkbox> | |
104 | + </section> | |
105 | + <div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayoutGap.sm="8px" | |
106 | + fxLayout.xs="column" fxLayout.md="column" *ngIf="!(isAdd && this.entityForm.get('generateChecksum').value)"> | |
111 | 107 | <mat-form-field class="mat-block" fxFlex="33"> |
112 | - <mat-label translate>ota-update.file-name</mat-label> | |
113 | - <input matInput formControlName="fileName" type="text" readonly> | |
108 | + <mat-label translate>ota-update.checksum-algorithm</mat-label> | |
109 | + <mat-select formControlName="checksumAlgorithm"> | |
110 | + <mat-option *ngFor="let checksumAlgorithm of checksumAlgorithms" [value]="checksumAlgorithm"> | |
111 | + {{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }} | |
112 | + </mat-option> | |
113 | + </mat-select> | |
114 | 114 | </mat-form-field> |
115 | 115 | <mat-form-field class="mat-block" fxFlex> |
116 | - <mat-label translate>ota-update.file-size-bytes</mat-label> | |
117 | - <input matInput formControlName="dataSize" type="text" readonly> | |
118 | - </mat-form-field> | |
119 | - <mat-form-field class="mat-block" fxFlex> | |
120 | - <mat-label translate>ota-update.content-type</mat-label> | |
121 | - <input matInput formControlName="contentType" type="text" readonly> | |
116 | + <mat-label translate>ota-update.checksum</mat-label> | |
117 | + <input matInput formControlName="checksum" type="text"> | |
118 | + <mat-hint *ngIf="isAdd" translate>ota-update.checksum-hint</mat-hint> | |
122 | 119 | </mat-form-field> |
123 | 120 | </div> |
121 | + <section *ngIf="!isAdd"> | |
122 | + <div fxLayout="row" fxLayoutGap.gt-md="8px" fxLayoutGap.sm="8px" fxLayout.xs="column" fxLayout.md="column"> | |
123 | + <mat-form-field class="mat-block" fxFlex="33"> | |
124 | + <mat-label translate>ota-update.file-name</mat-label> | |
125 | + <input matInput formControlName="fileName" type="text"> | |
126 | + </mat-form-field> | |
127 | + <mat-form-field class="mat-block" fxFlex> | |
128 | + <mat-label translate>ota-update.file-size-bytes</mat-label> | |
129 | + <input matInput formControlName="dataSize" type="text"> | |
130 | + </mat-form-field> | |
131 | + <mat-form-field class="mat-block" fxFlex> | |
132 | + <mat-label translate>ota-update.content-type</mat-label> | |
133 | + <input matInput formControlName="contentType" type="text"> | |
134 | + </mat-form-field> | |
135 | + </div> | |
136 | + </section> | |
137 | + </section> | |
138 | + <section *ngIf="entityForm.get('resource').value === 'url'" style="margin-top: 8px"> | |
139 | + <mat-form-field class="mat-block"> | |
140 | + <mat-label translate>ota-update.url</mat-label> | |
141 | + <input matInput formControlName="url" | |
142 | + type="text" | |
143 | + [required]="entityForm.get('resource').value === 'url'"> | |
144 | + <mat-error *ngIf="entityForm.get('url').hasError('required')" translate> | |
145 | + ota-update.url-required | |
146 | + </mat-error> | |
147 | + </mat-form-field> | |
124 | 148 | </section> |
125 | 149 | <div formGroupName="additionalInfo"> |
126 | 150 | <mat-form-field class="mat-block"> | ... | ... |
... | ... | @@ -30,6 +30,8 @@ import { |
30 | 30 | OtaUpdateTypeTranslationMap |
31 | 31 | } from '@shared/models/ota-package.models'; |
32 | 32 | import { ActionNotificationShow } from '@core/notification/notification.actions'; |
33 | +import { filter, takeUntil } from 'rxjs/operators'; | |
34 | +import { isNotEmptyStr } from '@core/utils'; | |
33 | 35 | |
34 | 36 | @Component({ |
35 | 37 | selector: 'tb-ota-update', |
... | ... | @@ -52,6 +54,26 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O |
52 | 54 | super(store, fb, entityValue, entitiesTableConfigValue); |
53 | 55 | } |
54 | 56 | |
57 | + ngOnInit() { | |
58 | + super.ngOnInit(); | |
59 | + this.entityForm.get('resource').valueChanges.pipe( | |
60 | + filter(() => this.isAdd), | |
61 | + takeUntil(this.destroy$) | |
62 | + ).subscribe((resource) => { | |
63 | + if (resource === 'file') { | |
64 | + this.entityForm.get('url').clearValidators(); | |
65 | + this.entityForm.get('file').setValidators(Validators.required); | |
66 | + this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); | |
67 | + this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); | |
68 | + } else { | |
69 | + this.entityForm.get('file').clearValidators(); | |
70 | + this.entityForm.get('url').setValidators(Validators.required); | |
71 | + this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); | |
72 | + this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); | |
73 | + } | |
74 | + }); | |
75 | + } | |
76 | + | |
55 | 77 | ngOnDestroy() { |
56 | 78 | super.ngOnDestroy(); |
57 | 79 | this.destroy$.next(); |
... | ... | @@ -74,6 +96,8 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O |
74 | 96 | deviceProfileId: [entity ? entity.deviceProfileId : null, Validators.required], |
75 | 97 | checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256], |
76 | 98 | checksum: [entity ? entity.checksum : '', Validators.maxLength(1020)], |
99 | + url: [entity ? entity.url : ''], | |
100 | + resource: ['file'], | |
77 | 101 | additionalInfo: this.fb.group( |
78 | 102 | { |
79 | 103 | description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], |
... | ... | @@ -82,6 +106,7 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O |
82 | 106 | }); |
83 | 107 | if (this.isAdd) { |
84 | 108 | form.addControl('file', this.fb.control(null, Validators.required)); |
109 | + form.addControl('generateChecksum', this.fb.control(true)); | |
85 | 110 | } else { |
86 | 111 | form.addControl('fileName', this.fb.control(null)); |
87 | 112 | form.addControl('dataSize', this.fb.control(null)); |
... | ... | @@ -101,6 +126,8 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O |
101 | 126 | fileName: entity.fileName, |
102 | 127 | dataSize: entity.dataSize, |
103 | 128 | contentType: entity.contentType, |
129 | + url: entity.url, | |
130 | + resource: isNotEmptyStr(entity.url) ? 'url' : 'file', | |
104 | 131 | additionalInfo: { |
105 | 132 | description: entity.additionalInfo ? entity.additionalInfo.description : '' |
106 | 133 | } |
... | ... | @@ -108,8 +135,6 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O |
108 | 135 | if (!this.isAdd && this.entityForm.enabled) { |
109 | 136 | this.entityForm.disable({emitEvent: false}); |
110 | 137 | this.entityForm.get('additionalInfo').enable({emitEvent: false}); |
111 | - // this.entityForm.get('dataSize').disable({emitEvent: false}); | |
112 | - // this.entityForm.get('contentType').disable({emitEvent: false}); | |
113 | 138 | } |
114 | 139 | } |
115 | 140 | |
... | ... | @@ -124,14 +149,9 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O |
124 | 149 | })); |
125 | 150 | } |
126 | 151 | |
127 | - onPackageChecksumCopied() { | |
128 | - this.store.dispatch(new ActionNotificationShow( | |
129 | - { | |
130 | - message: this.translate.instant('ota-update.checksum-copied-message'), | |
131 | - type: 'success', | |
132 | - duration: 750, | |
133 | - verticalPosition: 'bottom', | |
134 | - horizontalPosition: 'right' | |
135 | - })); | |
152 | + prepareFormValue(formValue: any): any { | |
153 | + delete formValue.resource; | |
154 | + delete formValue.generateChecksum; | |
155 | + return super.prepareFormValue(formValue); | |
136 | 156 | } |
137 | 157 | } | ... | ... |
... | ... | @@ -575,7 +575,8 @@ |
575 | 575 | "enter-password": "Enter password", |
576 | 576 | "enter-search": "Enter search", |
577 | 577 | "created-time": "Created time", |
578 | - "loading": "Loading..." | |
578 | + "loading": "Loading...", | |
579 | + "proceed": "Proceed" | |
579 | 580 | }, |
580 | 581 | "content-type": { |
581 | 582 | "json": "Json", |
... | ... | @@ -2152,12 +2153,14 @@ |
2152 | 2153 | "assign-firmware-required": "Assigned firmware is required", |
2153 | 2154 | "assign-software": "Assigned software", |
2154 | 2155 | "assign-software-required": "Assigned software is required", |
2156 | + "auto-generate-checksum": "Auto-generate checksum", | |
2155 | 2157 | "checksum": "Checksum", |
2158 | + "checksum-hint": "If checksum is empty, it will be generated automatically", | |
2156 | 2159 | "checksum-algorithm": "Checksum algorithm", |
2157 | 2160 | "checksum-copied-message": "Package checksum has been copied to clipboard", |
2158 | - "change-firmware": "You have changed the firmware. This may cause update of the { count, plural, 1 {1 device} other {# devices} }.", | |
2159 | - "change-software": "You have changed the software. This may cause update of the { count, plural, 1 {1 device} other {# devices} }.", | |
2160 | - "chose-compatible-device-profile": "Choose compatible device profile. The uploaded package will be available only for devices with the chosen profile.", | |
2161 | + "change-firmware": "Change of the firmware may cause update of { count, plural, 1 {1 device} other {# devices} }.", | |
2162 | + "change-software": "Change of the software may cause update of { count, plural, 1 {1 device} other {# devices} }.", | |
2163 | + "chose-compatible-device-profile": "The uploaded package will be available only for devices with the chosen profile.", | |
2161 | 2164 | "chose-firmware-distributed-device": "Choose firmware that will be distributed to the devices", |
2162 | 2165 | "chose-software-distributed-device": "Choose software that will be distributed to the devices", |
2163 | 2166 | "content-type": "Content type", |
... | ... | @@ -2193,6 +2196,8 @@ |
2193 | 2196 | "firmware": "Firmware", |
2194 | 2197 | "software": "Software" |
2195 | 2198 | }, |
2199 | + "url": "Direct URL", | |
2200 | + "url-required": "Direct URL is required", | |
2196 | 2201 | "version": "Version", |
2197 | 2202 | "version-required": "Version is required.", |
2198 | 2203 | "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type." | ... | ... |