Commit ce1a3ea56713b327a3b5c23dc4ceb5880a4415c4

Authored by Vladyslav_Prykhodko
1 parent fa3bbb28

UI: OTA updates add support external URL

... ... @@ -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 }
... ...
... ... @@ -87,6 +87,7 @@ export interface OtaPackageInfo extends BaseData<OtaPackageId> {
87 87 title?: string;
88 88 version?: string;
89 89 hasData?: boolean;
  90 + url?: string;
90 91 fileName: string;
91 92 checksum?: string;
92 93 checksumAlgorithm?: ChecksumAlgorithm;
... ...
... ... @@ -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."
... ...