Browse Source

UI: Rename firmware to OtaPackage

pull/4661/head
Vladyslav_Prykhodko 5 years ago
parent
commit
874f5e8448
  1. 20
      ui-ngx/src/app/core/http/entity.service.ts
  2. 123
      ui-ngx/src/app/core/http/firmware.service.ts
  3. 123
      ui-ngx/src/app/core/http/ota-package.service.ts
  4. 8
      ui-ngx/src/app/core/services/menu.service.ts
  5. 1
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html
  6. 3
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts
  7. 12
      ui-ngx/src/app/modules/home/components/profile/device-profile.component.html
  8. 4
      ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts
  9. 4
      ui-ngx/src/app/modules/home/models/services.map.ts
  10. 12
      ui-ngx/src/app/modules/home/pages/device/device.component.html
  11. 4
      ui-ngx/src/app/modules/home/pages/device/device.component.ts
  12. 115
      ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts
  13. 4
      ui-ngx/src/app/modules/home/pages/home-pages.module.ts
  14. 14
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts
  15. 116
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts
  16. 80
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html
  17. 44
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts
  18. 10
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts
  19. 27
      ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html
  20. 110
      ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts
  21. 1
      ui-ngx/src/app/shared/models/constants.ts
  22. 10
      ui-ngx/src/app/shared/models/device.models.ts
  23. 20
      ui-ngx/src/app/shared/models/entity-type.models.ts
  24. 4
      ui-ngx/src/app/shared/models/id/ota-package-id.ts
  25. 43
      ui-ngx/src/app/shared/models/ota-package.models.ts
  26. 6
      ui-ngx/src/app/shared/shared.module.ts
  27. 96
      ui-ngx/src/assets/locale/locale.constant-en_US.json

20
ui-ngx/src/app/core/http/entity.service.ts

@ -75,12 +75,12 @@ import {
StringOperation
} from '@shared/models/query/query.models';
import { alarmFields } from '@shared/models/alarm.models';
import { FirmwareService } from '@core/http/firmware.service';
import { EdgeService } from "@core/http/edge.service";
import { OtaPackageService } from '@core/http/ota-package.service';
import { EdgeService } from '@core/http/edge.service';
import { Edge, EdgeEventType } from '@shared/models/edge.models';
import { RuleChainType } from "@shared/models/rule-chain.models";
import { WidgetService } from "@core/http/widget.service";
import { DeviceProfileService } from "@core/http/device-profile.service";
import { RuleChainType } from '@shared/models/rule-chain.models';
import { WidgetService } from '@core/http/widget.service';
import { DeviceProfileService } from '@core/http/device-profile.service';
@Injectable({
providedIn: 'root'
@ -101,7 +101,7 @@ export class EntityService {
private dashboardService: DashboardService,
private entityRelationService: EntityRelationService,
private attributeService: AttributeService,
private firmwareService: FirmwareService,
private otaPackageService: OtaPackageService,
private widgetService: WidgetService,
private deviceProfileService: DeviceProfileService,
private utils: UtilsService
@ -142,8 +142,8 @@ export class EntityService {
case EntityType.ALARM:
console.error('Get Alarm Entity is not implemented!');
break;
case EntityType.FIRMWARE:
observable = this.firmwareService.getFirmwareInfo(entityId, config);
case EntityType.OTA_PACKAGE:
observable = this.otaPackageService.getOtaPackageInfo(entityId, config);
break;
}
return observable;
@ -359,9 +359,9 @@ export class EntityService {
case EntityType.ALARM:
console.error('Get Alarm Entities is not implemented!');
break;
case EntityType.FIRMWARE:
case EntityType.OTA_PACKAGE:
pageLink.sortOrder.property = 'title';
entitiesObservable = this.firmwareService.getFirmwares(pageLink, config);
entitiesObservable = this.otaPackageService.getOtaPackages(pageLink, config);
break;
}
return entitiesObservable;

123
ui-ngx/src/app/core/http/firmware.service.ts

@ -1,123 +0,0 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs';
import { PageData } from '@shared/models/page/page-data';
import { ChecksumAlgorithm, Firmware, FirmwareInfo, FirmwareType } from '@shared/models/firmware.models';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { deepClone } from '@core/utils';
@Injectable({
providedIn: 'root'
})
export class FirmwareService {
constructor(
private http: HttpClient
) {
}
public getFirmwares(pageLink: PageLink, config?: RequestConfig): Observable<PageData<FirmwareInfo>> {
return this.http.get<PageData<FirmwareInfo>>(`/api/firmwares${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
}
public getFirmwaresInfoByDeviceProfileId(pageLink: PageLink, deviceProfileId: string, type: FirmwareType,
hasData = true, config?: RequestConfig): Observable<PageData<FirmwareInfo>> {
const url = `/api/firmwares/${deviceProfileId}/${type}/${hasData}${pageLink.toQuery()}`;
return this.http.get<PageData<FirmwareInfo>>(url, defaultHttpOptionsFromConfig(config));
}
public getFirmware(firmwareId: string, config?: RequestConfig): Observable<Firmware> {
return this.http.get<Firmware>(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config));
}
public getFirmwareInfo(firmwareId: string, config?: RequestConfig): Observable<FirmwareInfo> {
return this.http.get<FirmwareInfo>(`/api/firmware/info/${firmwareId}`, defaultHttpOptionsFromConfig(config));
}
public downloadFirmware(firmwareId: string): Observable<any> {
return this.http.get(`/api/firmware/${firmwareId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe(
map((response) => {
const headers = response.headers;
const filename = headers.get('x-filename');
const contentType = headers.get('content-type');
const linkElement = document.createElement('a');
try {
const blob = new Blob([response.body], { type: contentType });
const url = URL.createObjectURL(blob);
linkElement.setAttribute('href', url);
linkElement.setAttribute('download', filename);
const clickEvent = new MouseEvent('click',
{
view: window,
bubbles: true,
cancelable: false
}
);
linkElement.dispatchEvent(clickEvent);
return null;
} catch (e) {
throw e;
}
})
);
}
public saveFirmware(firmware: Firmware, config?: RequestConfig): Observable<Firmware> {
if (!firmware.file) {
return this.saveFirmwareInfo(firmware, config);
}
const firmwareInfo = deepClone(firmware);
delete firmwareInfo.file;
delete firmwareInfo.checksum;
delete firmwareInfo.checksumAlgorithm;
return this.saveFirmwareInfo(firmwareInfo, config).pipe(
mergeMap(res => {
return this.uploadFirmwareFile(res.id.id, firmware.file, firmware.checksumAlgorithm, firmware.checksum).pipe(
catchError(() => this.deleteFirmware(res.id.id))
);
})
);
}
public saveFirmwareInfo(firmware: FirmwareInfo, config?: RequestConfig): Observable<Firmware> {
return this.http.post<Firmware>('/api/firmware', firmware, defaultHttpOptionsFromConfig(config));
}
public uploadFirmwareFile(firmwareId: string, file: File, checksumAlgorithm: ChecksumAlgorithm,
checksum?: string, config?: RequestConfig): Observable<any> {
if (!config) {
config = {};
}
const formData = new FormData();
formData.append('file', file);
let url = `/api/firmware/${firmwareId}?checksumAlgorithm=${checksumAlgorithm}`;
if (checksum) {
url += `&checksum=${checksum}`;
}
return this.http.post(url, formData,
defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest));
}
public deleteFirmware(firmwareId: string, config?: RequestConfig) {
return this.http.delete(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config));
}
}

123
ui-ngx/src/app/core/http/ota-package.service.ts

@ -0,0 +1,123 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs';
import { PageData } from '@shared/models/page/page-data';
import { ChecksumAlgorithm, OtaPackage, OtaPackageInfo, OtaUpdateType } from '@shared/models/ota-package.models';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { deepClone } from '@core/utils';
@Injectable({
providedIn: 'root'
})
export class OtaPackageService {
constructor(
private http: HttpClient
) {
}
public getOtaPackages(pageLink: PageLink, config?: RequestConfig): Observable<PageData<OtaPackageInfo>> {
return this.http.get<PageData<OtaPackageInfo>>(`/api/otaPackages${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
}
public getOtaPackagesInfoByDeviceProfileId(pageLink: PageLink, deviceProfileId: string, type: OtaUpdateType,
hasData = true, config?: RequestConfig): Observable<PageData<OtaPackageInfo>> {
const url = `/api/otaPackages/${deviceProfileId}/${type}/${hasData}${pageLink.toQuery()}`;
return this.http.get<PageData<OtaPackageInfo>>(url, defaultHttpOptionsFromConfig(config));
}
public getOtaPackage(otaPackageId: string, config?: RequestConfig): Observable<OtaPackage> {
return this.http.get<OtaPackage>(`/api/otaPackages/${otaPackageId}`, defaultHttpOptionsFromConfig(config));
}
public getOtaPackageInfo(otaPackageId: string, config?: RequestConfig): Observable<OtaPackageInfo> {
return this.http.get<OtaPackageInfo>(`/api/otaPackage/info/${otaPackageId}`, defaultHttpOptionsFromConfig(config));
}
public downloadOtaPackage(otaPackageId: string): Observable<any> {
return this.http.get(`/api/otaPackage/${otaPackageId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe(
map((response) => {
const headers = response.headers;
const filename = headers.get('x-filename');
const contentType = headers.get('content-type');
const linkElement = document.createElement('a');
try {
const blob = new Blob([response.body], { type: contentType });
const url = URL.createObjectURL(blob);
linkElement.setAttribute('href', url);
linkElement.setAttribute('download', filename);
const clickEvent = new MouseEvent('click',
{
view: window,
bubbles: true,
cancelable: false
}
);
linkElement.dispatchEvent(clickEvent);
return null;
} catch (e) {
throw e;
}
})
);
}
public saveOtaPackage(otaPackage: OtaPackage, config?: RequestConfig): Observable<OtaPackage> {
if (!otaPackage.file) {
return this.saveOtaPackageInfo(otaPackage, config);
}
const otaPackageInfo = deepClone(otaPackage);
delete otaPackageInfo.file;
delete otaPackageInfo.checksum;
delete otaPackageInfo.checksumAlgorithm;
return this.saveOtaPackageInfo(otaPackageInfo, config).pipe(
mergeMap(res => {
return this.uploadOtaPackageFile(res.id.id, otaPackage.file, otaPackage.checksumAlgorithm, otaPackage.checksum).pipe(
catchError(() => this.deleteOtaPackage(res.id.id))
);
})
);
}
public saveOtaPackageInfo(otaPackageInfo: OtaPackageInfo, config?: RequestConfig): Observable<OtaPackage> {
return this.http.post<OtaPackage>('/api/otaPackage', otaPackageInfo, defaultHttpOptionsFromConfig(config));
}
public uploadOtaPackageFile(otaPackageId: string, file: File, checksumAlgorithm: ChecksumAlgorithm,
checksum?: string, config?: RequestConfig): Observable<any> {
if (!config) {
config = {};
}
const formData = new FormData();
formData.append('file', file);
let url = `/api/otaPackage/${otaPackageId}?checksumAlgorithm=${checksumAlgorithm}`;
if (checksum) {
url += `&checksum=${checksum}`;
}
return this.http.post(url, formData,
defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest));
}
public deleteOtaPackage(otaPackageId: string, config?: RequestConfig) {
return this.http.delete(`/api/otaPackage/${otaPackageId}`, defaultHttpOptionsFromConfig(config));
}
}

8
ui-ngx/src/app/core/services/menu.service.ts

@ -276,9 +276,9 @@ export class MenuService {
},
{
id: guid(),
name: 'firmware.firmware',
name: 'ota-update.ota-updates',
type: 'link',
path: '/firmwares',
path: '/otaUpdates',
icon: 'memory'
},
{
@ -423,9 +423,9 @@ export class MenuService {
path: '/deviceProfiles'
},
{
name: 'firmware.firmware',
name: 'ota-update.ota-updates',
icon: 'memory',
path: '/firmwares'
path: '/otaUpdates'
}
]
},

1
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html

@ -66,4 +66,5 @@
<mat-error *ngIf="selectDeviceProfileFormGroup.get('deviceProfile').hasError('required')">
{{ 'device-profile.device-profile-required' | translate }}
</mat-error>
<mat-hint *ngIf="!!hint">{{ hint | translate }}</mat-hint>
</mat-form-field>

3
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts

@ -91,6 +91,9 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
@Input()
disabled: boolean;
@Input()
hint: string;
@Output()
deviceProfileUpdated = new EventEmitter<DeviceProfileId>();

12
ui-ngx/src/app/modules/home/components/profile/device-profile.component.html

@ -67,18 +67,18 @@
[queueType]="serviceType"
formControlName="defaultQueueName">
</tb-queue-type-list>
<tb-firmware-autocomplete
<tb-ota-package-autocomplete
[useFullEntityId]="true"
[type]="firmwareTypes.FIRMWARE"
[type]="otaUpdateType.FIRMWARE"
[deviceProfileId]="deviceProfileId?.id"
formControlName="firmwareId">
</tb-firmware-autocomplete>
<tb-firmware-autocomplete
</tb-ota-package-autocomplete>
<tb-ota-package-autocomplete
[useFullEntityId]="true"
[type]="firmwareTypes.SOFTWARE"
[type]="otaUpdateType.SOFTWARE"
[deviceProfileId]="deviceProfileId?.id"
formControlName="softwareId">
</tb-firmware-autocomplete>
</tb-ota-package-autocomplete>
<mat-form-field fxHide class="mat-block">
<mat-label translate>device-profile.type</mat-label>
<mat-select formControlName="type" required>

4
ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts

@ -40,7 +40,7 @@ import { EntityType } from '@shared/models/entity-type.models';
import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { ServiceType } from '@shared/models/queue.models';
import { EntityId } from '@shared/models/id/entity-id';
import { FirmwareType } from '@shared/models/firmware.models';
import { OtaUpdateType } from '@shared/models/ota-package.models';
import { DashboardId } from '@shared/models/id/dashboard-id';
@Component({
@ -71,7 +71,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
deviceProfileId: EntityId;
firmwareTypes = FirmwareType;
otaUpdateType = OtaUpdateType;
constructor(protected store: Store<AppState>,
protected translate: TranslateService,

4
ui-ngx/src/app/modules/home/models/services.map.ts

@ -35,7 +35,7 @@ import { Router } from '@angular/router';
import { BroadcastService } from '@core/services/broadcast.service';
import { ImportExportService } from '@home/components/import-export/import-export.service';
import { DeviceProfileService } from '@core/http/device-profile.service';
import { FirmwareService } from '@core/http/firmware.service';
import { OtaPackageService } from '@core/http/ota-package.service';
export const ServicesMap = new Map<string, Type<any>>(
[
@ -59,6 +59,6 @@ export const ServicesMap = new Map<string, Type<any>>(
['router', Router],
['importExport', ImportExportService],
['deviceProfileService', DeviceProfileService],
['firmwareService', FirmwareService]
['otaPackageService', OtaPackageService]
]
);

12
ui-ngx/src/app/modules/home/pages/device/device.component.html

@ -101,18 +101,18 @@
<mat-label translate>device.label</mat-label>
<input matInput formControlName="label">
</mat-form-field>
<tb-firmware-autocomplete
<tb-ota-package-autocomplete
[useFullEntityId]="true"
[type]="firmwareTypes.FIRMWARE"
[type]="otaUpdateType.FIRMWARE"
[deviceProfileId]="entityForm.get('deviceProfileId').value?.id"
formControlName="firmwareId">
</tb-firmware-autocomplete>
<tb-firmware-autocomplete
</tb-ota-package-autocomplete>
<tb-ota-package-autocomplete
[useFullEntityId]="true"
[type]="firmwareTypes.SOFTWARE"
[type]="otaUpdateType.SOFTWARE"
[deviceProfileId]="entityForm.get('deviceProfileId').value?.id"
formControlName="softwareId">
</tb-firmware-autocomplete>
</tb-ota-package-autocomplete>
<tb-device-data
formControlName="deviceData"
required>

4
ui-ngx/src/app/modules/home/pages/device/device.component.ts

@ -34,7 +34,7 @@ import { ActionNotificationShow } from '@core/notification/notification.actions'
import { TranslateService } from '@ngx-translate/core';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { Subject } from 'rxjs';
import { FirmwareType } from '@shared/models/firmware.models';
import { OtaUpdateType } from '@shared/models/ota-package.models';
@Component({
selector: 'tb-device',
@ -49,7 +49,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
deviceScope: 'tenant' | 'customer' | 'customer_user' | 'edge';
firmwareTypes = FirmwareType;
otaUpdateType = OtaUpdateType;
constructor(protected store: Store<AppState>,
protected translate: TranslateService,

115
ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts

@ -1,115 +0,0 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import {
DateEntityTableColumn,
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import {
ChecksumAlgorithmTranslationMap,
Firmware,
FirmwareInfo,
FirmwareTypeTranslationMap
} from '@shared/models/firmware.models';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { FirmwareService } from '@core/http/firmware.service';
import { PageLink } from '@shared/models/page/page-link';
import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { FileSizePipe } from '@shared/pipe/file-size.pipe';
@Injectable()
export class FirmwareTableConfigResolve implements Resolve<EntityTableConfig<Firmware, PageLink, FirmwareInfo>> {
private readonly config: EntityTableConfig<Firmware, PageLink, FirmwareInfo> = new EntityTableConfig<Firmware, PageLink, FirmwareInfo>();
constructor(private translate: TranslateService,
private datePipe: DatePipe,
private firmwareService: FirmwareService,
private fileSize: FileSizePipe) {
this.config.entityType = EntityType.FIRMWARE;
this.config.entityComponent = FirmwaresComponent;
this.config.entityTranslations = entityTypeTranslations.get(EntityType.FIRMWARE);
this.config.entityResources = entityTypeResources.get(EntityType.FIRMWARE);
this.config.entityTitle = (firmware) => firmware ? firmware.title : '';
this.config.columns.push(
new DateEntityTableColumn<FirmwareInfo>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<FirmwareInfo>('title', 'firmware.title', '25%'),
new EntityTableColumn<FirmwareInfo>('version', 'firmware.version', '25%'),
new EntityTableColumn<FirmwareInfo>('type', 'firmware.type', '25%', entity => {
return this.translate.instant(FirmwareTypeTranslationMap.get(entity.type));
}),
new EntityTableColumn<FirmwareInfo>('fileName', 'firmware.file-name', '25%'),
new EntityTableColumn<FirmwareInfo>('dataSize', 'firmware.file-size', '70px', entity => {
return this.fileSize.transform(entity.dataSize || 0);
}),
new EntityTableColumn<FirmwareInfo>('checksum', 'firmware.checksum', '540px', entity => {
return `${ChecksumAlgorithmTranslationMap.get(entity.checksumAlgorithm)}: ${entity.checksum}`;
}, () => ({}), false)
);
this.config.cellActionDescriptors.push(
{
name: this.translate.instant('firmware.download'),
icon: 'file_download',
isEnabled: (firmware) => firmware.hasData,
onAction: ($event, entity) => this.exportFirmware($event, entity)
}
);
this.config.deleteEntityTitle = firmware => this.translate.instant('firmware.delete-firmware-title',
{ firmwareTitle: firmware.title });
this.config.deleteEntityContent = () => this.translate.instant('firmware.delete-firmware-text');
this.config.deleteEntitiesTitle = count => this.translate.instant('firmware.delete-firmwares-title', {count});
this.config.deleteEntitiesContent = () => this.translate.instant('firmware.delete-firmwares-text');
this.config.entitiesFetchFunction = pageLink => this.firmwareService.getFirmwares(pageLink);
this.config.loadEntity = id => this.firmwareService.getFirmwareInfo(id.id);
this.config.saveEntity = firmware => this.firmwareService.saveFirmware(firmware);
this.config.deleteEntity = id => this.firmwareService.deleteFirmware(id.id);
this.config.onEntityAction = action => this.onFirmwareAction(action);
}
resolve(): EntityTableConfig<Firmware, PageLink, FirmwareInfo> {
this.config.tableTitle = this.translate.instant('firmware.firmware');
return this.config;
}
exportFirmware($event: Event, firmware: FirmwareInfo) {
if ($event) {
$event.stopPropagation();
}
this.firmwareService.downloadFirmware(firmware.id.id).subscribe();
}
onFirmwareAction(action: EntityAction<FirmwareInfo>): boolean {
switch (action.action) {
case 'uploadFirmware':
this.exportFirmware(action.event, action.entity);
return true;
}
return false;
}
}

4
ui-ngx/src/app/modules/home/pages/home-pages.module.ts

@ -35,7 +35,7 @@ import { modulesMap } from '../../common/modules-map';
import { DeviceProfileModule } from './device-profile/device-profile.module';
import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module';
import { EdgeModule } from '@home/pages/edge/edge.module';
import { FirmwareModule } from '@home/pages/firmware/firmware.module';
import { OtaUpdateModule } from '@home/pages/ota-update/ota-update.module';
@NgModule({
exports: [
@ -55,7 +55,7 @@ import { FirmwareModule } from '@home/pages/firmware/firmware.module';
DashboardModule,
AuditLogModule,
ApiUsageModule,
FirmwareModule,
OtaUpdateModule,
UserModule
],
providers: [

14
ui-ngx/src/app/modules/home/pages/firmware/firmware-routing.module.ts → ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts

@ -18,22 +18,22 @@ import { RouterModule, Routes } from '@angular/router';
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum';
import { NgModule } from '@angular/core';
import { FirmwareTableConfigResolve } from '@home/pages/firmware/firmware-table-config.resolve';
import { OtaUpdateTableConfigResolve } from '@home/pages/ota-update/ota-update-table-config.resolve';
const routes: Routes = [
{
path: 'firmwares',
path: 'otaUpdates',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'firmware.firmware',
title: 'ota-update.ota-updates',
breadcrumb: {
label: 'firmware.firmware',
label: 'ota-update.ota-updates',
icon: 'memory'
}
},
resolve: {
entitiesTableConfig: FirmwareTableConfigResolve
entitiesTableConfig: OtaUpdateTableConfigResolve
}
}
];
@ -42,7 +42,7 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [
FirmwareTableConfigResolve
OtaUpdateTableConfigResolve
]
})
export class FirmwareRoutingModule{ }
export class OtaUpdateRoutingModule { }

116
ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts

@ -0,0 +1,116 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import {
DateEntityTableColumn,
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import {
ChecksumAlgorithmTranslationMap,
OtaPackage,
OtaPackageInfo,
OtaUpdateTypeTranslationMap
} from '@shared/models/ota-package.models';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { OtaPackageService } from '@core/http/ota-package.service';
import { PageLink } from '@shared/models/page/page-link';
import { OtaUpdateComponent } from '@home/pages/ota-update/ota-update.component';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { FileSizePipe } from '@shared/pipe/file-size.pipe';
@Injectable()
export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<OtaPackage, PageLink, OtaPackageInfo>> {
private readonly config: EntityTableConfig<OtaPackage, PageLink, OtaPackageInfo> =
new EntityTableConfig<OtaPackage, PageLink, OtaPackageInfo>();
constructor(private translate: TranslateService,
private datePipe: DatePipe,
private otaPackageService: OtaPackageService,
private fileSize: FileSizePipe) {
this.config.entityType = EntityType.OTA_PACKAGE;
this.config.entityComponent = OtaUpdateComponent;
this.config.entityTranslations = entityTypeTranslations.get(EntityType.OTA_PACKAGE);
this.config.entityResources = entityTypeResources.get(EntityType.OTA_PACKAGE);
this.config.entityTitle = (otaPackage) => otaPackage ? otaPackage.title : '';
this.config.columns.push(
new DateEntityTableColumn<OtaPackageInfo>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<OtaPackageInfo>('title', 'ota-update.title', '25%'),
new EntityTableColumn<OtaPackageInfo>('version', 'ota-update.version', '25%'),
new EntityTableColumn<OtaPackageInfo>('type', 'ota-update.package-type', '25%', entity => {
return this.translate.instant(OtaUpdateTypeTranslationMap.get(entity.type));
}),
new EntityTableColumn<OtaPackageInfo>('fileName', 'ota-update.file-name', '25%'),
new EntityTableColumn<OtaPackageInfo>('dataSize', 'ota-update.file-size', '70px', entity => {
return this.fileSize.transform(entity.dataSize || 0);
}),
new EntityTableColumn<OtaPackageInfo>('checksum', 'ota-update.checksum', '540px', entity => {
return `${ChecksumAlgorithmTranslationMap.get(entity.checksumAlgorithm)}: ${entity.checksum}`;
}, () => ({}), false)
);
this.config.cellActionDescriptors.push(
{
name: this.translate.instant('ota-update.download'),
icon: 'file_download',
isEnabled: (otaPackage) => otaPackage.hasData,
onAction: ($event, entity) => this.exportPackage($event, entity)
}
);
this.config.deleteEntityTitle = otaPackage => this.translate.instant('ota-update.delete-ota-update-title',
{ title: otaPackage.title });
this.config.deleteEntityContent = () => this.translate.instant('ota-update.delete-ota-update-text');
this.config.deleteEntitiesTitle = count => this.translate.instant('ota-update.delete-ota-updates-title', {count});
this.config.deleteEntitiesContent = () => this.translate.instant('ota-update.delete-ota-updates-text');
this.config.entitiesFetchFunction = pageLink => this.otaPackageService.getOtaPackages(pageLink);
this.config.loadEntity = id => this.otaPackageService.getOtaPackageInfo(id.id);
this.config.saveEntity = otaPackage => this.otaPackageService.saveOtaPackage(otaPackage);
this.config.deleteEntity = id => this.otaPackageService.deleteOtaPackage(id.id);
this.config.onEntityAction = action => this.onPackageAction(action);
}
resolve(): EntityTableConfig<OtaPackage, PageLink, OtaPackageInfo> {
this.config.tableTitle = this.translate.instant('ota-update.packages-repository');
return this.config;
}
exportPackage($event: Event, otaPackageInfo: OtaPackageInfo) {
if ($event) {
$event.stopPropagation();
}
this.otaPackageService.downloadOtaPackage(otaPackageInfo.id.id).subscribe();
}
onPackageAction(action: EntityAction<OtaPackageInfo>): boolean {
switch (action.action) {
case 'uploadPackage':
this.exportPackage(action.event, action.entity);
return true;
}
return false;
}
}

80
ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html → ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html

@ -18,114 +18,112 @@
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary" fxFlex.xs
[disabled]="(isLoading$ | async) || !entity?.hasData"
(click)="onEntityAction($event, 'uploadFirmware')"
(click)="onEntityAction($event, 'uploadPackage')"
[fxShow]="!isEdit">
{{ 'firmware.download' | translate }}
{{ 'ota-update.download' | translate }}
</button>
<button mat-raised-button color="primary" fxFlex.xs
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[fxShow]="!hideDelete() && !isEdit">
{{ 'firmware.delete' | translate }}
{{ 'ota-update.delete' | translate }}
</button>
<div fxLayout="row" fxLayout.xs="column">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onFirmwareIdCopied()"
(cbOnSuccess)="onPackageIdCopied()"
[cbContent]="entity?.id?.id"
[fxShow]="!isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>firmware.copyId</span>
<span translate>ota-update.copyId</span>
</button>
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onFirmwareChecksumCopied()"
(cbOnSuccess)="onPackageChecksumCopied()"
[cbContent]="entity?.checksum"
[fxShow]="!isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>firmware.copy-checksum</span>
<span translate>ota-update.copy-checksum</span>
</button>
</div>
</div>
<div class="mat-padding" fxLayout="column">
<div class="mat-padding" fxLayout="column" fxLayoutGap="8px">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-hint class="tb-hint" translate *ngIf="isAdd">firmware.warning-after-save-no-edit</mat-hint>
<fieldset [disabled]="(isLoading$ | async) || !isEdit" fxLayout="column" fxLayoutGap="8px">
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field class="mat-block" fxFlex="45">
<mat-label translate>firmware.title</mat-label>
<mat-label translate>ota-update.title</mat-label>
<input matInput formControlName="title" type="text" required [readonly]="!isAdd">
<mat-error *ngIf="entityForm.get('title').hasError('required')">
{{ 'firmware.title-required' | translate }}
{{ 'ota-update.title-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>firmware.version</mat-label>
<mat-label translate>ota-update.version</mat-label>
<input matInput formControlName="version" type="text" required [readonly]="!isAdd">
<mat-error *ngIf="entityForm.get('version').hasError('required')">
{{ 'firmware.version-required' | translate }}
{{ 'ota-update.version-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayout.xs="column">
<mat-form-field fxFlex="45">
<mat-label translate>firmware.type</mat-label>
<input *ngIf="!isAdd" matInput type="text" [readonly]="isEdit" [disabled]="!isEdit"
value="{{ firmwareTypeTranslationMap.get(entityForm.get('type').value) | translate }}">
<mat-select formControlName="type" required *ngIf="isAdd">
<mat-option *ngFor="let firmwareType of firmwareTypes" [value]="firmwareType">
{{ firmwareTypeTranslationMap.get(firmwareType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<tb-device-profile-autocomplete
formControlName="deviceProfileId" fxFlex
[editProfileEnabled]="false">
</tb-device-profile-autocomplete>
</div>
<tb-device-profile-autocomplete
formControlName="deviceProfileId"
required
[hint]="'ota-update.chose-compatible-device-profile'"
[editProfileEnabled]="false"
[addNewProfile]="false"
[selectDefaultProfile]="true">
</tb-device-profile-autocomplete>
<mat-form-field class="mat-block">
<mat-label translate>ota-update.package-type</mat-label>
<mat-select formControlName="type" required>
<mat-option *ngFor="let packageType of packageTypes" [value]="packageType">
{{ otaUpdateTypeTranslationMap.get(packageType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<div class="mat-caption" translate *ngIf="isAdd">ota-update.warning-after-save-no-edit</div>
<div fxLayout="row" fxLayoutGap.gt-xs="8px" fxLayoutGap.sm="8px" fxLayout.xs="column" fxLayout.md="column">
<mat-form-field class="mat-block" fxFlex="33">
<mat-label translate>firmware.checksum-algorithm</mat-label>
<input *ngIf="!isAdd" matInput type="text" [readonly]="isEdit" [disabled]="!isEdit"
value="{{ checksumAlgorithmTranslationMap.get(entityForm.get('checksumAlgorithm').value) | translate }}">
<mat-select formControlName="checksumAlgorithm" *ngIf="isAdd">
<mat-label translate>ota-update.checksum-algorithm</mat-label>
<mat-select formControlName="checksumAlgorithm">
<mat-option *ngFor="let checksumAlgorithm of checksumAlgorithms" [value]="checksumAlgorithm">
{{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>firmware.checksum</mat-label>
<mat-label translate>ota-update.checksum</mat-label>
<input matInput formControlName="checksum" type="text" [readonly]="!isAdd">
</mat-form-field>
</div>
<section *ngIf="isAdd" style="padding-top: 8px">
<section *ngIf="isAdd">
<tb-file-input
formControlName="file"
workFromFileObj="true"
required
dropLabel="{{'resource.drop-file' | translate}}">
dropLabel="{{'ota-update.drop-file' | translate}}">
</tb-file-input>
</section>
<section *ngIf="!isAdd">
<div fxLayout="row" fxLayoutGap.gt-md="8px" fxLayoutGap.sm="8px" fxLayout.xs="column" fxLayout.md="column">
<mat-form-field class="mat-block" fxFlex="33">
<mat-label translate>firmware.file-name</mat-label>
<mat-label translate>ota-update.file-name</mat-label>
<input matInput formControlName="fileName" type="text" readonly>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>firmware.file-size-bytes</mat-label>
<mat-label translate>ota-update.file-size-bytes</mat-label>
<input matInput formControlName="dataSize" type="text" readonly>
</mat-form-field>
<mat-form-field class="mat-block" fxFlex>
<mat-label translate>firmware.content-type</mat-label>
<mat-label translate>ota-update.content-type</mat-label>
<input matInput formControlName="contentType" type="text" readonly>
</mat-form-field>
</div>
</section>
<div formGroupName="additionalInfo">
<mat-form-field class="mat-block">
<mat-label translate>firmware.description</mat-label>
<mat-label translate>ota-update.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea>
</mat-form-field>
</div>

44
ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts → ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts

@ -25,29 +25,29 @@ import { EntityComponent } from '@home/components/entity/entity.component';
import {
ChecksumAlgorithm,
ChecksumAlgorithmTranslationMap,
Firmware,
FirmwareType,
FirmwareTypeTranslationMap
} from '@shared/models/firmware.models';
OtaPackage,
OtaUpdateType,
OtaUpdateTypeTranslationMap
} from '@shared/models/ota-package.models';
import { ActionNotificationShow } from '@core/notification/notification.actions';
@Component({
selector: 'tb-firmware',
templateUrl: './firmwares.component.html'
selector: 'tb-ota-update',
templateUrl: './ota-update.component.html'
})
export class FirmwaresComponent extends EntityComponent<Firmware> implements OnInit, OnDestroy {
export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements OnInit, OnDestroy {
private destroy$ = new Subject();
checksumAlgorithms = Object.values(ChecksumAlgorithm);
checksumAlgorithmTranslationMap = ChecksumAlgorithmTranslationMap;
firmwareTypes = Object.values(FirmwareType);
firmwareTypeTranslationMap = FirmwareTypeTranslationMap;
packageTypes = Object.values(OtaUpdateType);
otaUpdateTypeTranslationMap = OtaUpdateTypeTranslationMap;
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: Firmware,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<Firmware>,
@Inject('entity') protected entityValue: OtaPackage,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<OtaPackage>,
public fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfigValue);
}
@ -66,12 +66,12 @@ export class FirmwaresComponent extends EntityComponent<Firmware> implements OnI
}
}
buildForm(entity: Firmware): FormGroup {
buildForm(entity: OtaPackage): FormGroup {
const form = this.fb.group({
title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]],
type: [entity?.type ? entity.type : FirmwareType.FIRMWARE, [Validators.required]],
deviceProfileId: [entity ? entity.deviceProfileId : null],
type: [entity?.type ? entity.type : OtaUpdateType.FIRMWARE, Validators.required],
deviceProfileId: [entity ? entity.deviceProfileId : null, Validators.required],
checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256],
checksum: [entity ? entity.checksum : '', Validators.maxLength(1020)],
additionalInfo: this.fb.group(
@ -90,7 +90,7 @@ export class FirmwaresComponent extends EntityComponent<Firmware> implements OnI
return form;
}
updateForm(entity: Firmware) {
updateForm(entity: OtaPackage) {
this.entityForm.patchValue({
title: entity.title,
version: entity.version,
@ -105,12 +105,18 @@ export class FirmwaresComponent extends EntityComponent<Firmware> implements OnI
description: entity.additionalInfo ? entity.additionalInfo.description : ''
}
});
if (!this.isAdd && this.entityForm.enabled) {
this.entityForm.disable({emitEvent: false});
this.entityForm.get('additionalInfo').enable({emitEvent: false});
// this.entityForm.get('dataSize').disable({emitEvent: false});
// this.entityForm.get('contentType').disable({emitEvent: false});
}
}
onFirmwareIdCopied() {
onPackageIdCopied() {
this.store.dispatch(new ActionNotificationShow(
{
message: this.translate.instant('firmware.idCopiedMessage'),
message: this.translate.instant('ota-update.idCopiedMessage'),
type: 'success',
duration: 750,
verticalPosition: 'bottom',
@ -118,10 +124,10 @@ export class FirmwaresComponent extends EntityComponent<Firmware> implements OnI
}));
}
onFirmwareChecksumCopied() {
onPackageChecksumCopied() {
this.store.dispatch(new ActionNotificationShow(
{
message: this.translate.instant('firmware.checksum-copied-message'),
message: this.translate.instant('ota-update.checksum-copied-message'),
type: 'success',
duration: 750,
verticalPosition: 'bottom',

10
ui-ngx/src/app/modules/home/pages/firmware/firmware.module.ts → ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts

@ -18,18 +18,18 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { HomeComponentsModule } from '@home/components/home-components.module';
import { FirmwareRoutingModule } from '@home/pages/firmware/firmware-routing.module';
import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component';
import { OtaUpdateRoutingModule } from '@home/pages/ota-update/ota-update-routing.module';
import { OtaUpdateComponent } from '@home/pages/ota-update/ota-update.component';
@NgModule({
declarations: [
FirmwaresComponent
OtaUpdateComponent
],
imports: [
CommonModule,
SharedModule,
HomeComponentsModule,
FirmwareRoutingModule
OtaUpdateRoutingModule
]
})
export class FirmwareModule { }
export class OtaUpdateModule { }

27
ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.html → ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html

@ -15,40 +15,41 @@
limitations under the License.
-->
<mat-form-field [formGroup]="firmwareFormGroup" class="mat-block">
<mat-form-field [formGroup]="otaPackageFormGroup" class="mat-block">
<input matInput type="text" placeholder="{{ placeholderText | translate }}"
#firmwareInput
formControlName="firmwareId"
#packageInput
formControlName="packageId"
(focusin)="onFocus()"
[required]="required"
[matAutocomplete]="firmwareAutocomplete">
<button *ngIf="firmwareFormGroup.get('firmwareId').value && !disabled"
[matAutocomplete]="packageAutocomplete">
<button *ngIf="otaPackageFormGroup.get('packageId').value && !disabled"
type="button"
matSuffix mat-button mat-icon-button aria-label="Clear"
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete class="tb-autocomplete"
#firmwareAutocomplete="matAutocomplete"
[displayWith]="displayFirmwareFn">
<mat-option *ngFor="let firmware of filteredFirmwares | async" [value]="firmware">
<span [innerHTML]="this.firmwareTitleText(firmware) | highlight:searchText"></span>
#packageAutocomplete="matAutocomplete"
[displayWith]="displayPackageFn">
<mat-option *ngFor="let package of filteredPackages | async" [value]="package">
<span [innerHTML]="this.packageTitleText(package) | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredFirmwares | async)?.length" [value]="null" class="tb-not-found">
<mat-option *ngIf="!(filteredPackages | async)?.length" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
<span>{{ notFoundFirmware | translate }}</span>
<span>{{ notFoundPackage | translate }}</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ translate.get(notMatchingFirmware,
{{ translate.get(notMatchingPackage,
{entity: truncate.transform(searchText, true, 6, &apos;...&apos;)}) | async }}
</span>
</ng-template>
</div>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="firmwareFormGroup.get('firmwareId').hasError('required')">
<mat-error *ngIf="otaPackageFormGroup.get('packageId').hasError('required')">
{{ requiredErrorText | translate }}
</mat-error>
<mat-hint *ngIf="!disabled">{{ hintText | translate }}</mat-hint>
</mat-form-field>

110
ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts → ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts

@ -28,29 +28,29 @@ import { BaseData } from '@shared/models/base-data';
import { EntityService } from '@core/http/entity.service';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { FirmwareInfo, FirmwareType } from '@shared/models/firmware.models';
import { FirmwareService } from '@core/http/firmware.service';
import { OtaPackageInfo, OtaUpdateTranslation, OtaUpdateType } from '@shared/models/ota-package.models';
import { OtaPackageService } from '@core/http/ota-package.service';
import { PageLink } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
@Component({
selector: 'tb-firmware-autocomplete',
templateUrl: './firmware-autocomplete.component.html',
selector: 'tb-ota-package-autocomplete',
templateUrl: './ota-package-autocomplete.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FirmwareAutocompleteComponent),
useExisting: forwardRef(() => OtaPackageAutocompleteComponent),
multi: true
}]
})
export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnInit {
export class OtaPackageAutocompleteComponent implements ControlValueAccessor, OnInit {
firmwareFormGroup: FormGroup;
otaPackageFormGroup: FormGroup;
modelValue: string | EntityId | null;
@Input()
type = FirmwareType.FIRMWARE;
type = OtaUpdateType.FIRMWARE;
@Input()
deviceProfileId: string;
@ -78,42 +78,24 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn
@Input()
disabled: boolean;
@ViewChild('firmwareInput', {static: true}) firmwareInput: ElementRef;
@ViewChild('firmwareInput', {read: MatAutocompleteTrigger}) firmwareAutocomplete: MatAutocompleteTrigger;
@ViewChild('packageInput', {static: true}) packageInput: ElementRef;
filteredFirmwares: Observable<Array<FirmwareInfo>>;
filteredPackages: Observable<Array<OtaPackageInfo>>;
searchText = '';
private dirty = false;
private firmwareTypeTranslation = new Map<FirmwareType, any>(
[
[FirmwareType.FIRMWARE, {
label: 'firmware.firmware',
required: 'firmware.firmware-required',
noFound: 'firmware.no-firmware-text',
noMatching: 'firmware.no-firmware-matching'
}],
[FirmwareType.SOFTWARE, {
label: 'firmware.software',
required: 'firmware.software-required',
noFound: 'firmware.no-software-text',
noMatching: 'firmware.no-software-matching'
}]
]
);
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
public translate: TranslateService,
public truncate: TruncatePipe,
private entityService: EntityService,
private firmwareService: FirmwareService,
private otaPackageService: OtaPackageService,
private fb: FormBuilder) {
this.firmwareFormGroup = this.fb.group({
firmwareId: [null]
this.otaPackageFormGroup = this.fb.group({
packageId: [null]
});
}
@ -125,7 +107,7 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn
}
ngOnInit() {
this.filteredFirmwares = this.firmwareFormGroup.get('firmwareId').valueChanges
this.filteredPackages = this.otaPackageFormGroup.get('packageId').valueChanges
.pipe(
tap(value => {
let modelValue;
@ -140,7 +122,7 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn
}
}),
map(value => value ? (typeof value === 'string' ? value : value.title) : ''),
mergeMap(name => this.fetchFirmware(name)),
mergeMap(name => this.fetchPackages(name)),
share()
);
}
@ -149,7 +131,7 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn
}
getCurrentEntity(): BaseData<EntityId> | null {
const currentRuleChain = this.firmwareFormGroup.get('firmwareId').value;
const currentRuleChain = this.otaPackageFormGroup.get('packageId').value;
if (currentRuleChain && typeof currentRuleChain !== 'string') {
return currentRuleChain as BaseData<EntityId>;
} else {
@ -160,9 +142,9 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.firmwareFormGroup.disable({emitEvent: false});
this.otaPackageFormGroup.disable({emitEvent: false});
} else {
this.firmwareFormGroup.enable({emitEvent: false});
this.otaPackageFormGroup.enable({emitEvent: false});
}
}
@ -173,21 +155,21 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn
writeValue(value: string | EntityId | null): void {
this.searchText = '';
if (value != null && value !== '') {
let firmwareId = '';
let packageId = '';
if (typeof value === 'string') {
firmwareId = value;
packageId = value;
} else if (value.entityType && value.id) {
firmwareId = value.id;
packageId = value.id;
}
if (firmwareId !== '') {
this.entityService.getEntity(EntityType.FIRMWARE, firmwareId, {ignoreLoading: true, ignoreErrors: true}).subscribe(
if (packageId !== '') {
this.entityService.getEntity(EntityType.OTA_PACKAGE, packageId, {ignoreLoading: true, ignoreErrors: true}).subscribe(
(entity) => {
this.modelValue = this.useFullEntityId ? entity.id : entity.id.id;
this.firmwareFormGroup.get('firmwareId').patchValue(entity, {emitEvent: false});
this.otaPackageFormGroup.get('packageId').patchValue(entity, {emitEvent: false});
},
() => {
this.modelValue = null;
this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
this.otaPackageFormGroup.get('packageId').patchValue('', {emitEvent: false});
if (value !== null) {
this.propagateChange(this.modelValue);
}
@ -195,25 +177,25 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn
);
} else {
this.modelValue = null;
this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
this.otaPackageFormGroup.get('packageId').patchValue('', {emitEvent: false});
this.propagateChange(null);
}
} else {
this.modelValue = null;
this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
this.otaPackageFormGroup.get('packageId').patchValue('', {emitEvent: false});
}
this.dirty = true;
}
onFocus() {
if (this.dirty) {
this.firmwareFormGroup.get('firmwareId').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.otaPackageFormGroup.get('packageId').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
reset() {
this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
this.otaPackageFormGroup.get('packageId').patchValue('', {emitEvent: false});
}
updateView(value: string | null) {
@ -223,47 +205,51 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn
}
}
displayFirmwareFn(firmware?: FirmwareInfo): string | undefined {
return firmware ? `${firmware.title} (${firmware.version})` : undefined;
displayPackageFn(packageInfo?: OtaPackageInfo): string | undefined {
return packageInfo ? `${packageInfo.title} (${packageInfo.version})` : undefined;
}
fetchFirmware(searchText?: string): Observable<Array<FirmwareInfo>> {
fetchPackages(searchText?: string): Observable<Array<OtaPackageInfo>> {
this.searchText = searchText;
const pageLink = new PageLink(50, 0, searchText, {
property: 'title',
direction: Direction.ASC
});
return this.firmwareService.getFirmwaresInfoByDeviceProfileId(pageLink, this.deviceProfileId, this.type,
return this.otaPackageService.getOtaPackagesInfoByDeviceProfileId(pageLink, this.deviceProfileId, this.type,
true, {ignoreLoading: true}).pipe(
map((data) => data && data.data.length ? data.data : null)
);
}
clear() {
this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: true});
this.otaPackageFormGroup.get('packageId').patchValue('', {emitEvent: true});
setTimeout(() => {
this.firmwareInput.nativeElement.blur();
this.firmwareInput.nativeElement.focus();
this.packageInput.nativeElement.blur();
this.packageInput.nativeElement.focus();
}, 0);
}
get placeholderText(): string {
return this.labelText || this.firmwareTypeTranslation.get(this.type).label;
return this.labelText || OtaUpdateTranslation.get(this.type).label;
}
get requiredErrorText(): string {
return this.requiredText || this.firmwareTypeTranslation.get(this.type).required;
return this.requiredText || OtaUpdateTranslation.get(this.type).required;
}
get notFoundPackage(): string {
return OtaUpdateTranslation.get(this.type).noFound;
}
get notFoundFirmware(): string {
return this.firmwareTypeTranslation.get(this.type).noFound;
get notMatchingPackage(): string {
return OtaUpdateTranslation.get(this.type).noMatching;
}
get notMatchingFirmware(): string {
return this.firmwareTypeTranslation.get(this.type).noMatching;
get hintText(): string {
return OtaUpdateTranslation.get(this.type).hint;
}
firmwareTitleText(firmware: FirmwareInfo): string {
return `${firmware.title} (${firmware.version})`;
packageTitleText(firpackageInfomware: OtaPackageInfo): string {
return `${firpackageInfomware.title} (${firpackageInfomware.version})`;
}
}

1
ui-ngx/src/app/shared/models/constants.ts

@ -121,6 +121,7 @@ export const HelpLinks = {
entitiesImport: helpBaseUrl + '/docs/user-guide/bulk-provisioning',
rulechains: helpBaseUrl + '/docs/user-guide/ui/rule-chains',
dashboards: helpBaseUrl + '/docs/user-guide/ui/dashboards',
otaUpdates: helpBaseUrl + '/docs/user-guide/ui/ota-updates',
widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library#bundles',
widgetsConfig: helpBaseUrl + '/docs/user-guide/ui/dashboards#widget-configuration',
widgetsConfigTimeseries: helpBaseUrl + '/docs/user-guide/ui/dashboards#timeseries',

10
ui-ngx/src/app/shared/models/device.models.ts

@ -27,7 +27,7 @@ import { KeyFilter } from '@shared/models/query/query.models';
import { TimeUnit } from '@shared/models/time/time.models';
import * as _moment from 'moment';
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { FirmwareId } from '@shared/models/id/firmware-id';
import { OtaPackageId } from '@shared/models/id/ota-package-id';
import { DashboardId } from '@shared/models/id/dashboard-id';
export enum DeviceProfileType {
@ -500,8 +500,8 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> {
defaultRuleChainId?: RuleChainId;
defaultDashboardId?: DashboardId;
defaultQueueName?: string;
firmwareId?: FirmwareId;
softwareId?: FirmwareId;
firmwareId?: OtaPackageId;
softwareId?: OtaPackageId;
profileData: DeviceProfileData;
}
@ -563,8 +563,8 @@ export interface Device extends BaseData<DeviceId> {
name: string;
type: string;
label: string;
firmwareId?: FirmwareId;
softwareId?: FirmwareId;
firmwareId?: OtaPackageId;
softwareId?: OtaPackageId;
deviceProfileId?: DeviceProfileId;
deviceData?: DeviceData;
additionalInfo?: any;

20
ui-ngx/src/app/shared/models/entity-type.models.ts

@ -35,7 +35,7 @@ export enum EntityType {
WIDGET_TYPE = 'WIDGET_TYPE',
API_USAGE_STATE = 'API_USAGE_STATE',
TB_RESOURCE = 'TB_RESOURCE',
FIRMWARE = 'FIRMWARE'
OTA_PACKAGE = 'OTA_PACKAGE'
}
export enum AliasEntityType {
@ -296,14 +296,14 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
}
],
[
EntityType.FIRMWARE,
EntityType.OTA_PACKAGE,
{
type: 'entity.type-firmware',
details: 'firmware.firmware-details',
add: 'firmware.add',
noEntities: 'firmware.no-firmware-text',
search: 'firmware.search',
selectedEntities: 'firmware.selected-firmware'
type: 'entity.type-ota-package',
details: 'ota-update.ota-update-details',
add: 'ota-update.add',
noEntities: 'ota-update.no-packages-text',
search: 'ota-update.search',
selectedEntities: 'ota-update.selected-package'
}
]
]
@ -390,9 +390,9 @@ export const entityTypeResources = new Map<EntityType, EntityTypeResource<BaseDa
}
],
[
EntityType.FIRMWARE,
EntityType.OTA_PACKAGE,
{
helpLinkId: 'firmware'
helpLinkId: 'otaUpdates'
}
]
]

4
ui-ngx/src/app/shared/models/id/firmware-id.ts → ui-ngx/src/app/shared/models/id/ota-package-id.ts

@ -17,8 +17,8 @@
import { EntityId } from '@shared/models/id/entity-id';
import { EntityType } from '@shared/models/entity-type.models';
export class FirmwareId implements EntityId {
entityType = EntityType.FIRMWARE;
export class OtaPackageId implements EntityId {
entityType = EntityType.OTA_PACKAGE;
id: string;
constructor(id: string) {
this.id = id;

43
ui-ngx/src/app/shared/models/firmware.models.ts → ui-ngx/src/app/shared/models/ota-package.models.ts

@ -16,7 +16,7 @@
import { BaseData } from '@shared/models/base-data';
import { TenantId } from '@shared/models/id/tenant-id';
import { FirmwareId } from '@shared/models/id/firmware-id';
import { OtaPackageId } from '@shared/models/id/ota-package-id';
import { DeviceProfileId } from '@shared/models/id/device-profile-id';
export enum ChecksumAlgorithm {
@ -41,21 +41,48 @@ export const ChecksumAlgorithmTranslationMap = new Map<ChecksumAlgorithm, string
]
);
export enum FirmwareType {
export enum OtaUpdateType {
FIRMWARE = 'FIRMWARE',
SOFTWARE = 'SOFTWARE'
}
export const FirmwareTypeTranslationMap = new Map<FirmwareType, string>(
export const OtaUpdateTypeTranslationMap = new Map<OtaUpdateType, string>(
[
[FirmwareType.FIRMWARE, 'firmware.types.firmware'],
[FirmwareType.SOFTWARE, 'firmware.types.software']
[OtaUpdateType.FIRMWARE, 'ota-update.types.firmware'],
[OtaUpdateType.SOFTWARE, 'ota-update.types.software']
]
);
export interface FirmwareInfo extends BaseData<FirmwareId> {
export interface OtaUpdateTranslation {
label: string;
required: string;
noFound: string;
noMatching: string;
hint: string;
}
export const OtaUpdateTranslation = new Map<OtaUpdateType, OtaUpdateTranslation>(
[
[OtaUpdateType.FIRMWARE, {
label: 'ota-update.assign-firmware',
required: 'ota-update.assign-firmware-required',
noFound: 'ota-update.no-firmware-text',
noMatching: 'ota-update.no-firmware-matching',
hint: 'ota-update.chose-firmware-distributed-device'
}],
[OtaUpdateType.SOFTWARE, {
label: 'ota-update.assign-software',
required: 'ota-update.assign-software-required',
noFound: 'ota-update.no-software-text',
noMatching: 'ota-update.no-software-matching',
hint: 'ota-update.chose-software-distributed-device'
}]
]
);
export interface OtaPackageInfo extends BaseData<OtaPackageId> {
tenantId?: TenantId;
type: FirmwareType;
type: OtaUpdateType;
deviceProfileId?: DeviceProfileId;
title?: string;
version?: string;
@ -68,7 +95,7 @@ export interface FirmwareInfo extends BaseData<FirmwareId> {
additionalInfo?: any;
}
export interface Firmware extends FirmwareInfo {
export interface OtaPackage extends OtaPackageInfo {
file?: File;
data: string;
}

6
ui-ngx/src/app/shared/shared.module.ts

@ -141,7 +141,7 @@ import { FileSizePipe } from '@shared/pipe/file-size.pipe';
import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-search.component';
import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe';
import { QuickTimeIntervalComponent } from '@shared/components/time/quick-time-interval.component';
import { FirmwareAutocompleteComponent } from '@shared/components/firmware/firmware-autocomplete.component';
import { OtaPackageAutocompleteComponent } from '@shared/components/ota-package/ota-package-autocomplete.component';
import { MAT_DATE_LOCALE } from '@angular/material/core';
@NgModule({
@ -239,7 +239,7 @@ import { MAT_DATE_LOCALE } from '@angular/material/core';
HistorySelectorComponent,
EntityGatewaySelectComponent,
ContactComponent,
FirmwareAutocompleteComponent,
OtaPackageAutocompleteComponent,
WidgetsBundleSearchComponent
],
imports: [
@ -411,7 +411,7 @@ import { MAT_DATE_LOCALE } from '@angular/material/core';
HistorySelectorComponent,
EntityGatewaySelectComponent,
ContactComponent,
FirmwareAutocompleteComponent,
OtaPackageAutocompleteComponent,
WidgetsBundleSearchComponent
]
})

96
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1516,7 +1516,7 @@
"list-of-edges": "{ count, plural, 1 {One edge} other {List of # edges} }",
"edge-name-starts-with": "Edges whose names start with '{{prefix}}'",
"type-tb-resource": "Resource",
"type-firmware": "Firmware"
"type-ota-package": "OTA package"
},
"entity-field": {
"created-time": "Created time",
@ -1927,51 +1927,6 @@
"inherit-owner": "Inherit from owner",
"source-attribute-not-set": "If source attribute isn't set"
},
"firmware": {
"add": "Add firmware",
"checksum": "Checksum",
"checksum-copied-message": "Firmware checksum has been copied to clipboard",
"checksum-required": "Checksum is required.",
"checksum-algorithm": "Checksum algorithm",
"content-type": "Content type",
"copy-checksum": "Copy checksum",
"copyId": "Copy firmware Id",
"description": "Description",
"delete": "Delete firmware",
"delete-firmware-text": "Be careful, after the confirmation the firmware will become unrecoverable.",
"delete-firmware-title": "Are you sure you want to delete the firmware '{{firmwareTitle}}'?",
"delete-firmwares-action-title": "Delete { count, plural, 1 {1 firmware} other {# firmwares} }",
"delete-firmwares-text": "Be careful, after the confirmation all selected resources will be removed.",
"delete-firmwares-title": "Are you sure you want to delete { count, plural, 1 {1 firmware} other {# firmwares} }?",
"download": "Download firmware",
"drop-file": "Drop a firmware file or click to select a file to upload.",
"empty": "Firmware is empty",
"idCopiedMessage": "Firmware Id has been copied to clipboard",
"no-firmware-matching": "No firmware matching '{{entity}}' were found.",
"no-firmware-text": "No firmwares found",
"no-software-matching": "No sowtware matching '{{entity}}' were found.",
"no-software-text": "No software found",
"file-name": "File name",
"file-size": "File size",
"file-size-bytes": "File size in bytes",
"firmware": "Firmware",
"firmware-details": "Firmware details",
"firmware-required": "Firmware is required.",
"search": "Search firmwares",
"selected-firmware": "{ count, plural, 1 {1 firmware} other {# firmwares} } selected",
"software": "Software",
"software-required": "Software is required.",
"title": "Title",
"title-required": "Title is required.",
"type": "Firmware type",
"types": {
"firmware": "Firmware",
"software": "Software"
},
"version": "Version",
"version-required": "Version is required.",
"warning-after-save-no-edit": "Once the firmware is saved, it will not be possible to change the title and version fields."
},
"fullscreen": {
"expand": "Expand to fullscreen",
"exit": "Exit fullscreen",
@ -2191,6 +2146,55 @@
"or": "or",
"error": "Login error"
},
"ota-update": {
"add": "Add package",
"assign-firmware": "Assigned firmware",
"assign-firmware-required": "Assigned firmware is required",
"assign-software": "Assigned software",
"assign-software-required": "Assigned software is required",
"checksum": "Checksum",
"checksum-algorithm": "Checksum algorithm",
"checksum-copied-message": "Package checksum has been copied to clipboard",
"chose-compatible-device-profile": "Choose compatible device profile",
"chose-firmware-distributed-device": "Choose firmware that will be distributed to the devices",
"chose-software-distributed-device": "Choose software that will be distributed to the devices",
"content-type": "Content type",
"copy-checksum": "Copy checksum",
"copyId": "Copy package Id",
"idCopiedMessage": "Package Id has been copied to clipboard",
"description": "Description",
"delete": "Delete package",
"delete-ota-update-text": "Be careful, after the confirmation the OTA update will become unrecoverable.",
"delete-ota-update-title": "Are you sure you want to delete the OTA update '{{title}}'?",
"delete-ota-updates-text": "Be careful, after the confirmation all selected OTA updates will be removed.",
"delete-ota-updates-title": "Are you sure you want to delete { count, plural, 1 {1 OTA update} other {# OTA updates} }?",
"drop-file": "Drop a package file or click to select a file to upload.",
"download": "Download package",
"file-name": "File name",
"file-size": "File size",
"file-size-bytes": "File size in bytes",
"no-packages-text": "No packages found",
"no-firmware-text": "No compatible Firmware OTA Update packages provisioned.",
"no-firmware-matching": "No compatible Firmware OTA Update packages matching '{{entity}}' were found.",
"no-software-text": "No compatible Software OTA Update packages provisioned.",
"no-software-matching": "No compatible Software OTA Update packages matching '{{entity}}' were found.",
"search": "Search packages",
"selected-package": "{ count, plural, 1 {1 package} other {# packages} } selected",
"ota-update": "OTA update",
"ota-update-details": "OTA update details",
"ota-updates": "OTA updates",
"package-type": "Package type",
"packages-repository": "Packages repository",
"title": "Title",
"title-required": "Title is required.",
"types": {
"firmware": "Firmware",
"software": "Software"
},
"version": "Version",
"version-required": "Version is required.",
"warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type."
},
"position": {
"top": "Top",
"bottom": "Bottom",

Loading…
Cancel
Save