Commit e31a95619c0cfffb9094c8be8ff37c8062dcc9cf

Authored by Vladyslav_Prykhodko
1 parent 45fc32ce

UI: Added firmware autocomplete component; Add firmware to device and device profile

... ... @@ -74,6 +74,7 @@ import {
74 74 StringOperation
75 75 } from '@shared/models/query/query.models';
76 76 import { alarmFields } from '@shared/models/alarm.models';
  77 +import { FirmwareService } from '@core/http/firmware.service';
77 78
78 79 @Injectable({
79 80 providedIn: 'root'
... ... @@ -93,6 +94,7 @@ export class EntityService {
93 94 private dashboardService: DashboardService,
94 95 private entityRelationService: EntityRelationService,
95 96 private attributeService: AttributeService,
  97 + private firmwareService: FirmwareService,
96 98 private utils: UtilsService
97 99 ) { }
98 100
... ... @@ -128,6 +130,9 @@ export class EntityService {
128 130 case EntityType.ALARM:
129 131 console.error('Get Alarm Entity is not implemented!');
130 132 break;
  133 + case EntityType.FIRMWARE:
  134 + observable = this.firmwareService.getFirmwareInfo(entityId, config);
  135 + break;
131 136 }
132 137 return observable;
133 138 }
... ... @@ -326,6 +331,10 @@ export class EntityService {
326 331 case EntityType.ALARM:
327 332 console.error('Get Alarm Entities is not implemented!');
328 333 break;
  334 + case EntityType.FIRMWARE:
  335 + pageLink.sortOrder.property = 'title';
  336 + entitiesObservable = this.firmwareService.getFirmwares(pageLink, true, config);
  337 + break;
329 338 }
330 339 return entitiesObservable;
331 340 }
... ...
... ... @@ -23,8 +23,6 @@ import { PageData } from '@shared/models/page/page-data';
23 23 import { Firmware, FirmwareInfo } from '@shared/models/firmware.models';
24 24 import { catchError, map, mergeMap } from 'rxjs/operators';
25 25 import { deepClone, isDefinedAndNotNull } from '@core/utils';
26   -import { InterceptorHttpParams } from '@core/interceptors/interceptor-http-params';
27   -import { InterceptorConfig } from '@core/interceptors/interceptor-config';
28 26
29 27 @Injectable({
30 28 providedIn: 'root'
... ... @@ -37,10 +35,11 @@ export class FirmwareService {
37 35 }
38 36
39 37 public getFirmwares(pageLink: PageLink, hasData?: boolean, config?: RequestConfig): Observable<PageData<FirmwareInfo>> {
40   - let url = `/api/firmwares${pageLink.toQuery()}`;
  38 + let url = `/api/firmwares`;
41 39 if (isDefinedAndNotNull(hasData)) {
42   - url += `&hasData=${hasData}`;
  40 + url += `/${hasData}`;
43 41 }
  42 + url += `${pageLink.toQuery()}`;
44 43 return this.http.get<PageData<FirmwareInfo>>(url, defaultHttpOptionsFromConfig(config));
45 44 }
46 45
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<mat-form-field [formGroup]="firmwareFormGroup" class="mat-block">
  19 + <input matInput type="text" placeholder="{{ placeholderText | translate }}"
  20 + #firmwareInput
  21 + formControlName="firmwareId"
  22 + (focusin)="onFocus()"
  23 + [required]="required"
  24 + [matAutocomplete]="firmwareAutocomplete">
  25 + <button *ngIf="firmwareFormGroup.get('firmwareId').value && !disabled"
  26 + type="button"
  27 + matSuffix mat-button mat-icon-button aria-label="Clear"
  28 + (click)="clear()">
  29 + <mat-icon class="material-icons">close</mat-icon>
  30 + </button>
  31 + <mat-autocomplete class="tb-autocomplete"
  32 + #firmwareAutocomplete="matAutocomplete"
  33 + [displayWith]="displayFirmwareFn">
  34 + <mat-option *ngFor="let firmware of filteredFirmwares | async" [value]="firmware">
  35 + <span [innerHTML]="this.firmwareTitleText(firmware) | highlight:searchText"></span>
  36 + </mat-option>
  37 + <mat-option *ngIf="!(filteredFirmwares | async)?.length" [value]="null" class="tb-not-found">
  38 + <div class="tb-not-found-content" (click)="$event.stopPropagation()">
  39 + <div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
  40 + <span translate>firmware.no-firmware-text</span>
  41 + </div>
  42 + <ng-template #searchNotEmpty>
  43 + <span>
  44 + {{ translate.get('firmware.no-firmware-matching',
  45 + {entity: truncate.transform(searchText, true, 6, &apos;...&apos;)}) | async }}
  46 + </span>
  47 + </ng-template>
  48 + </div>
  49 + </mat-option>
  50 + </mat-autocomplete>
  51 + <mat-error *ngIf="firmwareFormGroup.get('firmwareId').hasError('required')">
  52 + {{ requiredErrorText | translate }}
  53 + </mat-error>
  54 +</mat-form-field>
... ...
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
  18 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
  19 +import { Observable } from 'rxjs';
  20 +import { map, mergeMap, share, tap } from 'rxjs/operators';
  21 +import { Store } from '@ngrx/store';
  22 +import { AppState } from '@core/core.state';
  23 +import { TranslateService } from '@ngx-translate/core';
  24 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
  25 +import { EntityId } from '@shared/models/id/entity-id';
  26 +import { EntityType } from '@shared/models/entity-type.models';
  27 +import { BaseData } from '@shared/models/base-data';
  28 +import { EntityService } from '@core/http/entity.service';
  29 +import { TruncatePipe } from '@shared/pipe/truncate.pipe';
  30 +import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
  31 +import { FirmwareInfo } from '@shared/models/firmware.models';
  32 +import { FirmwareService } from '@core/http/firmware.service';
  33 +import { PageLink } from '@shared/models/page/page-link';
  34 +import { Direction } from '@shared/models/page/sort-order';
  35 +
  36 +@Component({
  37 + selector: 'tb-firmware-autocomplete',
  38 + templateUrl: './firmware-autocomplete.component.html',
  39 + styleUrls: [],
  40 + providers: [{
  41 + provide: NG_VALUE_ACCESSOR,
  42 + useExisting: forwardRef(() => FirmwareAutocompleteComponent),
  43 + multi: true
  44 + }]
  45 +})
  46 +export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnInit {
  47 +
  48 + firmwareFormGroup: FormGroup;
  49 +
  50 + modelValue: string | null;
  51 +
  52 + @Input()
  53 + labelText: string;
  54 +
  55 + @Input()
  56 + requiredText: string;
  57 +
  58 + @Input()
  59 + useFullEntityId = false;
  60 +
  61 + private requiredValue: boolean;
  62 +
  63 + get required(): boolean {
  64 + return this.requiredValue;
  65 + }
  66 +
  67 + @Input()
  68 + set required(value: boolean) {
  69 + this.requiredValue = coerceBooleanProperty(value);
  70 + }
  71 +
  72 + @Input()
  73 + disabled: boolean;
  74 +
  75 + @ViewChild('firmwareInput', {static: true}) firmwareInput: ElementRef;
  76 + @ViewChild('firmwareInput', {read: MatAutocompleteTrigger}) firmwareAutocomplete: MatAutocompleteTrigger;
  77 +
  78 + filteredFirmwares: Observable<Array<FirmwareInfo>>;
  79 +
  80 + searchText = '';
  81 +
  82 + private dirty = false;
  83 +
  84 + private propagateChange = (v: any) => { };
  85 +
  86 + constructor(private store: Store<AppState>,
  87 + public translate: TranslateService,
  88 + public truncate: TruncatePipe,
  89 + private entityService: EntityService,
  90 + private firmwareService: FirmwareService,
  91 + private fb: FormBuilder) {
  92 + this.firmwareFormGroup = this.fb.group({
  93 + firmwareId: [null]
  94 + });
  95 + }
  96 +
  97 + registerOnChange(fn: any): void {
  98 + this.propagateChange = fn;
  99 + }
  100 +
  101 + registerOnTouched(fn: any): void {
  102 + }
  103 +
  104 + ngOnInit() {
  105 + this.filteredFirmwares = this.firmwareFormGroup.get('firmwareId').valueChanges
  106 + .pipe(
  107 + tap(value => {
  108 + let modelValue;
  109 + if (typeof value === 'string' || !value) {
  110 + modelValue = null;
  111 + } else {
  112 + modelValue = this.useFullEntityId ? value.id : value.id.id;
  113 + }
  114 + this.updateView(modelValue);
  115 + if (value === null) {
  116 + this.clear();
  117 + }
  118 + }),
  119 + map(value => value ? (typeof value === 'string' ? value : value.title) : ''),
  120 + mergeMap(name => this.fetchFirmware(name)),
  121 + share()
  122 + );
  123 + }
  124 +
  125 + ngAfterViewInit(): void {
  126 + }
  127 +
  128 + getCurrentEntity(): BaseData<EntityId> | null {
  129 + const currentRuleChain = this.firmwareFormGroup.get('firmwareId').value;
  130 + if (currentRuleChain && typeof currentRuleChain !== 'string') {
  131 + return currentRuleChain as BaseData<EntityId>;
  132 + } else {
  133 + return null;
  134 + }
  135 + }
  136 +
  137 + setDisabledState(isDisabled: boolean): void {
  138 + this.disabled = isDisabled;
  139 + if (this.disabled) {
  140 + this.firmwareFormGroup.disable({emitEvent: false});
  141 + } else {
  142 + this.firmwareFormGroup.enable({emitEvent: false});
  143 + }
  144 + }
  145 +
  146 + textIsNotEmpty(text: string): boolean {
  147 + return (text && text.length > 0);
  148 + }
  149 +
  150 + writeValue(value: string | EntityId | null): void {
  151 + this.searchText = '';
  152 + if (value != null && value !== '') {
  153 + let firmwareId = '';
  154 + if (typeof value === 'string') {
  155 + firmwareId = value;
  156 + } else if (value.entityType && value.id) {
  157 + firmwareId = value.id;
  158 + }
  159 + if (firmwareId !== '') {
  160 + this.entityService.getEntity(EntityType.FIRMWARE, firmwareId, {ignoreLoading: true, ignoreErrors: true}).subscribe(
  161 + (entity) => {
  162 + this.modelValue = entity.id.id;
  163 + this.firmwareFormGroup.get('firmwareId').patchValue(entity, {emitEvent: false});
  164 + },
  165 + () => {
  166 + this.modelValue = null;
  167 + this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
  168 + if (value !== null) {
  169 + this.propagateChange(this.modelValue);
  170 + }
  171 + }
  172 + );
  173 + } else {
  174 + this.modelValue = null;
  175 + this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
  176 + }
  177 + } else {
  178 + this.modelValue = null;
  179 + this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
  180 + }
  181 + this.dirty = true;
  182 + }
  183 +
  184 + onFocus() {
  185 + if (this.dirty) {
  186 + this.firmwareFormGroup.get('firmwareId').updateValueAndValidity({onlySelf: true, emitEvent: true});
  187 + this.dirty = false;
  188 + }
  189 + }
  190 +
  191 + reset() {
  192 + this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false});
  193 + }
  194 +
  195 + updateView(value: string | null) {
  196 + if (this.modelValue !== value) {
  197 + this.modelValue = value;
  198 + this.propagateChange(this.modelValue);
  199 + }
  200 + }
  201 +
  202 + displayFirmwareFn(firmware?: FirmwareInfo): string | undefined {
  203 + return firmware ? `${firmware.title} (${firmware.version})` : undefined;
  204 + }
  205 +
  206 + fetchFirmware(searchText?: string): Observable<Array<FirmwareInfo>> {
  207 + this.searchText = searchText;
  208 + const pageLink = new PageLink(50, 0, searchText, {
  209 + property: 'title',
  210 + direction: Direction.ASC
  211 + });
  212 + return this.firmwareService.getFirmwares(pageLink, true, {ignoreLoading: true}).pipe(
  213 + map((data) => data && data.data.length ? data.data : null)
  214 + );
  215 + }
  216 +
  217 + clear() {
  218 + this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: true});
  219 + setTimeout(() => {
  220 + this.firmwareInput.nativeElement.blur();
  221 + this.firmwareInput.nativeElement.focus();
  222 + }, 0);
  223 + }
  224 +
  225 + get placeholderText(): string {
  226 + return this.labelText || 'firmware.firmware';
  227 + }
  228 +
  229 + get requiredErrorText(): string {
  230 + return this.requiredText || 'firmware.firmware-required';
  231 + }
  232 +
  233 + firmwareTitleText(firmware: FirmwareInfo): string {
  234 + return `${firmware.title} (${firmware.version})`;
  235 + }
  236 +}
... ...
... ... @@ -134,6 +134,7 @@ import { DashboardStateDialogComponent } from '@home/components/dashboard-page/s
134 134 import { EmbedDashboardDialogComponent } from '@home/components/widget/dialog/embed-dashboard-dialog.component';
135 135 import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token';
136 136 import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component';
  137 +import { FirmwareAutocompleteComponent } from '@home/components/firmware/firmware-autocomplete.component';
137 138
138 139 @NgModule({
139 140 declarations:
... ... @@ -247,7 +248,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag
247 248 ManageDashboardStatesDialogComponent,
248 249 DashboardStateDialogComponent,
249 250 EmbedDashboardDialogComponent,
250   - DisplayWidgetTypesPanelComponent
  251 + DisplayWidgetTypesPanelComponent,
  252 + FirmwareAutocompleteComponent
251 253 ],
252 254 imports: [
253 255 CommonModule,
... ... @@ -351,7 +353,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag
351 353 ManageDashboardStatesDialogComponent,
352 354 DashboardStateDialogComponent,
353 355 EmbedDashboardDialogComponent,
354   - DisplayWidgetTypesPanelComponent
  356 + DisplayWidgetTypesPanelComponent,
  357 + FirmwareAutocompleteComponent
355 358 ],
356 359 providers: [
357 360 WidgetComponentService,
... ...
... ... @@ -60,6 +60,10 @@
60 60 {{ 'device-profile.type-required' | translate }}
61 61 </mat-error>
62 62 </mat-form-field>
  63 + <tb-firmware-autocomplete
  64 + [useFullEntityId]="true"
  65 + formControlName="firmwareId">
  66 + </tb-firmware-autocomplete>
63 67 <mat-form-field class="mat-block">
64 68 <mat-label translate>device-profile.description</mat-label>
65 69 <textarea matInput formControlName="description" rows="2"></textarea>
... ...
... ... @@ -48,7 +48,7 @@ import { MatHorizontalStepper } from '@angular/material/stepper';
48 48 import { RuleChainId } from '@shared/models/id/rule-chain-id';
49 49 import { StepperSelectionEvent } from '@angular/cdk/stepper';
50 50 import { deepTrim } from '@core/utils';
51   -import {ServiceType} from "@shared/models/queue.models";
  51 +import { ServiceType } from '@shared/models/queue.models';
52 52
53 53 export interface AddDeviceProfileDialogData {
54 54 deviceProfileName: string;
... ... @@ -72,13 +72,13 @@ export class AddDeviceProfileDialogComponent extends
72 72
73 73 entityType = EntityType;
74 74
75   - deviceProfileTypes = Object.keys(DeviceProfileType);
  75 + deviceProfileTypes = Object.values(DeviceProfileType);
76 76
77 77 deviceProfileTypeTranslations = deviceProfileTypeTranslationMap;
78 78
79 79 deviceTransportTypeHints = deviceTransportTypeHintMap;
80 80
81   - deviceTransportTypes = Object.keys(DeviceTransportType);
  81 + deviceTransportTypes = Object.values(DeviceTransportType);
82 82
83 83 deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
84 84
... ... @@ -108,6 +108,7 @@ export class AddDeviceProfileDialogComponent extends
108 108 type: [DeviceProfileType.DEFAULT, [Validators.required]],
109 109 defaultRuleChainId: [null, []],
110 110 defaultQueueName: ['', []],
  111 + firmwareId: [null],
111 112 description: ['', []]
112 113 }
113 114 );
... ... @@ -186,6 +187,7 @@ export class AddDeviceProfileDialogComponent extends
186 187 transportType: this.transportConfigFormGroup.get('transportType').value,
187 188 provisionType: deviceProvisionConfiguration.type,
188 189 provisionDeviceKey,
  190 + firmwareId: this.deviceProfileDetailsFormGroup.get('firmwareId').value,
189 191 description: this.deviceProfileDetailsFormGroup.get('description').value,
190 192 profileData: {
191 193 configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),
... ...
... ... @@ -63,6 +63,10 @@
63 63 [queueType]="serviceType"
64 64 formControlName="defaultQueueName">
65 65 </tb-queue-type-list>
  66 + <tb-firmware-autocomplete
  67 + [useFullEntityId]="true"
  68 + formControlName="firmwareId">
  69 + </tb-firmware-autocomplete>
66 70 <mat-form-field fxHide class="mat-block">
67 71 <mat-label translate>device-profile.type</mat-label>
68 72 <mat-select formControlName="type" required>
... ...
... ... @@ -53,11 +53,11 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
53 53
54 54 entityType = EntityType;
55 55
56   - deviceProfileTypes = Object.keys(DeviceProfileType);
  56 + deviceProfileTypes = Object.values(DeviceProfileType);
57 57
58 58 deviceProfileTypeTranslations = deviceProfileTypeTranslationMap;
59 59
60   - deviceTransportTypes = Object.keys(DeviceTransportType);
  60 + deviceTransportTypes = Object.values(DeviceTransportType);
61 61
62 62 deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
63 63
... ... @@ -109,6 +109,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
109 109 }),
110 110 defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []],
111 111 defaultQueueName: [entity ? entity.defaultQueueName : '', []],
  112 + firmwareId: [entity ? entity.firmwareId : null],
112 113 description: [entity ? entity.description : '', []],
113 114 }
114 115 );
... ... @@ -184,6 +185,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
184 185 }}, {emitEvent: false});
185 186 this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false});
186 187 this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false});
  188 + this.entityForm.patchValue({firmwareId: entity.firmwareId}, {emitEvent: false});
187 189 this.entityForm.patchValue({description: entity.description}, {emitEvent: false});
188 190 }
189 191
... ...
... ... @@ -48,6 +48,10 @@
48 48 <mat-label translate>device.label</mat-label>
49 49 <input matInput formControlName="label">
50 50 </mat-form-field>
  51 + <tb-firmware-autocomplete
  52 + [useFullEntityId]="true"
  53 + formControlName="firmwareId">
  54 + </tb-firmware-autocomplete>
51 55 <mat-form-field class="mat-block" style="padding-bottom: 14px;">
52 56 <mat-label translate>device-profile.transport-type</mat-label>
53 57 <mat-select formControlName="transportType" required>
... ...
... ... @@ -70,7 +70,7 @@ export class DeviceWizardDialogComponent extends
70 70
71 71 entityType = EntityType;
72 72
73   - deviceTransportTypes = Object.keys(DeviceTransportType);
  73 + deviceTransportTypes = Object.values(DeviceTransportType);
74 74
75 75 deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
76 76
... ... @@ -107,6 +107,7 @@ export class DeviceWizardDialogComponent extends
107 107 this.deviceWizardFormGroup = this.fb.group({
108 108 name: ['', Validators.required],
109 109 label: [''],
  110 + firmwareId: [null],
110 111 gateway: [false],
111 112 overwriteActivityTime: [false],
112 113 transportType: [DeviceTransportType.DEFAULT, Validators.required],
... ... @@ -312,6 +313,7 @@ export class DeviceWizardDialogComponent extends
312 313 const device = {
313 314 name: this.deviceWizardFormGroup.get('name').value,
314 315 label: this.deviceWizardFormGroup.get('label').value,
  316 + firmwareId: this.deviceWizardFormGroup.get('firmwareId').value,
315 317 deviceProfileId: profileId,
316 318 additionalInfo: {
317 319 gateway: this.deviceWizardFormGroup.get('gateway').value,
... ...
... ... @@ -95,6 +95,10 @@
95 95 <mat-label translate>device.label</mat-label>
96 96 <input matInput formControlName="label">
97 97 </mat-form-field>
  98 + <tb-firmware-autocomplete
  99 + [useFullEntityId]="true"
  100 + formControlName="firmwareId">
  101 + </tb-firmware-autocomplete>
98 102 <tb-device-data
99 103 formControlName="deviceData"
100 104 required>
... ...
... ... @@ -79,6 +79,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
79 79 {
80 80 name: [entity ? entity.name : '', [Validators.required]],
81 81 deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]],
  82 + firmwareId: [entity ? entity.firmwareId : null],
82 83 label: [entity ? entity.label : ''],
83 84 deviceData: [entity ? entity.deviceData : null, [Validators.required]],
84 85 additionalInfo: this.fb.group(
... ... @@ -95,6 +96,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
95 96 updateForm(entity: DeviceInfo) {
96 97 this.entityForm.patchValue({name: entity.name});
97 98 this.entityForm.patchValue({deviceProfileId: entity.deviceProfileId});
  99 + this.entityForm.patchValue({firmwareId: entity.firmwareId});
98 100 this.entityForm.patchValue({label: entity.label});
99 101 this.entityForm.patchValue({deviceData: entity.deviceData});
100 102 this.entityForm.patchValue({
... ...
... ... @@ -28,6 +28,16 @@
28 28 [fxShow]="!hideDelete() && !isEdit">
29 29 {{'resource.delete' | translate }}
30 30 </button>
  31 + <div fxLayout="row" fxLayout.xs="column">
  32 + <button mat-raised-button
  33 + ngxClipboard
  34 + (cbOnSuccess)="onFirmwareIdCopied($event)"
  35 + [cbContent]="entity?.id?.id"
  36 + [fxShow]="!isEdit">
  37 + <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
  38 + <span translate>firmware.copyId</span>
  39 + </button>
  40 + </div>
31 41 </div>
32 42 <div class="mat-padding" fxLayout="column">
33 43 <form [formGroup]="entityForm">
... ...
... ... @@ -24,6 +24,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
24 24 import { EntityComponent } from '@home/components/entity/entity.component';
25 25 import { ChecksumAlgorithm, ChecksumAlgorithmTranslationMap, Firmware } from '@shared/models/firmware.models';
26 26 import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
  27 +import { ActionNotificationShow } from '@core/notification/notification.actions';
27 28
28 29 @Component({
29 30 selector: 'tb-firmware',
... ... @@ -109,4 +110,15 @@ export class FirmwaresComponent extends EntityComponent<Firmware> implements OnI
109 110 }
110 111 });
111 112 }
  113 +
  114 + onFirmwareIdCopied($event) {
  115 + this.store.dispatch(new ActionNotificationShow(
  116 + {
  117 + message: this.translate.instant('firmware.idCopiedMessage'),
  118 + type: 'success',
  119 + duration: 750,
  120 + verticalPosition: 'bottom',
  121 + horizontalPosition: 'right'
  122 + }));
  123 + }
112 124 }
... ...
... ... @@ -27,6 +27,7 @@ import { KeyFilter } from '@shared/models/query/query.models';
27 27 import { TimeUnit } from '@shared/models/time/time.models';
28 28 import * as _moment from 'moment';
29 29 import { AbstractControl, ValidationErrors } from '@angular/forms';
  30 +import { FirmwareId } from '@shared/models/id/firmware-id';
30 31
31 32 export enum DeviceProfileType {
32 33 DEFAULT = 'DEFAULT'
... ... @@ -445,6 +446,7 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> {
445 446 provisionDeviceKey?: string;
446 447 defaultRuleChainId?: RuleChainId;
447 448 defaultQueueName?: string;
  449 + firmwareId?: FirmwareId;
448 450 profileData: DeviceProfileData;
449 451 }
450 452
... ... @@ -499,6 +501,7 @@ export interface Device extends BaseData<DeviceId> {
499 501 name: string;
500 502 type: string;
501 503 label: string;
  504 + firmwareId?: FirmwareId;
502 505 deviceProfileId?: DeviceProfileId;
503 506 deviceData?: DeviceData;
504 507 additionalInfo?: any;
... ...
... ... @@ -1698,6 +1698,7 @@
1698 1698 "checksum": "Checksum",
1699 1699 "checksum-required": "Checksum is required.",
1700 1700 "checksum-algorithm": "Checksum algorithm",
  1701 + "copyId": "Copy firmware Id",
1701 1702 "description": "Description",
1702 1703 "delete": "Delete firmware",
1703 1704 "delete-firmware-text": "Be careful, after the confirmation the firmware will become unrecoverable.",
... ... @@ -1708,10 +1709,12 @@
1708 1709 "drop-file": "Drop a firmware file or click to select a file to upload.",
1709 1710 "empty": "Firmware is empty",
1710 1711 "export": "Export firmware",
1711   - "no-firmware-matching": "No firmware matching '{{firmware}}' were found.",
  1712 + "idCopiedMessage": "Firmware Id has been copied to clipboard",
  1713 + "no-firmware-matching": "No firmware matching '{{entity}}' were found.",
1712 1714 "no-firmware-text": "No firmwares found",
1713 1715 "firmware": "Firmware",
1714 1716 "firmware-details": "Firmware details",
  1717 + "firmware-required": "Firmware is required.",
1715 1718 "search": "Search firmwares",
1716 1719 "selected-firmware": "{ count, plural, 1 {1 firmware} other {# firmwares} } selected",
1717 1720 "title": "Title",
... ...