Commit e31a95619c0cfffb9094c8be8ff37c8062dcc9cf
1 parent
45fc32ce
UI: Added firmware autocomplete component; Add firmware to device and device profile
Showing
17 changed files
with
366 additions
and
13 deletions
... | ... | @@ -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, '...')}) | 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", | ... | ... |