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,6 +74,7 @@ import { | ||
74 | StringOperation | 74 | StringOperation |
75 | } from '@shared/models/query/query.models'; | 75 | } from '@shared/models/query/query.models'; |
76 | import { alarmFields } from '@shared/models/alarm.models'; | 76 | import { alarmFields } from '@shared/models/alarm.models'; |
77 | +import { FirmwareService } from '@core/http/firmware.service'; | ||
77 | 78 | ||
78 | @Injectable({ | 79 | @Injectable({ |
79 | providedIn: 'root' | 80 | providedIn: 'root' |
@@ -93,6 +94,7 @@ export class EntityService { | @@ -93,6 +94,7 @@ export class EntityService { | ||
93 | private dashboardService: DashboardService, | 94 | private dashboardService: DashboardService, |
94 | private entityRelationService: EntityRelationService, | 95 | private entityRelationService: EntityRelationService, |
95 | private attributeService: AttributeService, | 96 | private attributeService: AttributeService, |
97 | + private firmwareService: FirmwareService, | ||
96 | private utils: UtilsService | 98 | private utils: UtilsService |
97 | ) { } | 99 | ) { } |
98 | 100 | ||
@@ -128,6 +130,9 @@ export class EntityService { | @@ -128,6 +130,9 @@ export class EntityService { | ||
128 | case EntityType.ALARM: | 130 | case EntityType.ALARM: |
129 | console.error('Get Alarm Entity is not implemented!'); | 131 | console.error('Get Alarm Entity is not implemented!'); |
130 | break; | 132 | break; |
133 | + case EntityType.FIRMWARE: | ||
134 | + observable = this.firmwareService.getFirmwareInfo(entityId, config); | ||
135 | + break; | ||
131 | } | 136 | } |
132 | return observable; | 137 | return observable; |
133 | } | 138 | } |
@@ -326,6 +331,10 @@ export class EntityService { | @@ -326,6 +331,10 @@ export class EntityService { | ||
326 | case EntityType.ALARM: | 331 | case EntityType.ALARM: |
327 | console.error('Get Alarm Entities is not implemented!'); | 332 | console.error('Get Alarm Entities is not implemented!'); |
328 | break; | 333 | break; |
334 | + case EntityType.FIRMWARE: | ||
335 | + pageLink.sortOrder.property = 'title'; | ||
336 | + entitiesObservable = this.firmwareService.getFirmwares(pageLink, true, config); | ||
337 | + break; | ||
329 | } | 338 | } |
330 | return entitiesObservable; | 339 | return entitiesObservable; |
331 | } | 340 | } |
@@ -23,8 +23,6 @@ import { PageData } from '@shared/models/page/page-data'; | @@ -23,8 +23,6 @@ import { PageData } from '@shared/models/page/page-data'; | ||
23 | import { Firmware, FirmwareInfo } from '@shared/models/firmware.models'; | 23 | import { Firmware, FirmwareInfo } from '@shared/models/firmware.models'; |
24 | import { catchError, map, mergeMap } from 'rxjs/operators'; | 24 | import { catchError, map, mergeMap } from 'rxjs/operators'; |
25 | import { deepClone, isDefinedAndNotNull } from '@core/utils'; | 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 | @Injectable({ | 27 | @Injectable({ |
30 | providedIn: 'root' | 28 | providedIn: 'root' |
@@ -37,10 +35,11 @@ export class FirmwareService { | @@ -37,10 +35,11 @@ export class FirmwareService { | ||
37 | } | 35 | } |
38 | 36 | ||
39 | public getFirmwares(pageLink: PageLink, hasData?: boolean, config?: RequestConfig): Observable<PageData<FirmwareInfo>> { | 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 | if (isDefinedAndNotNull(hasData)) { | 39 | if (isDefinedAndNotNull(hasData)) { |
42 | - url += `&hasData=${hasData}`; | 40 | + url += `/${hasData}`; |
43 | } | 41 | } |
42 | + url += `${pageLink.toQuery()}`; | ||
44 | return this.http.get<PageData<FirmwareInfo>>(url, defaultHttpOptionsFromConfig(config)); | 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,6 +134,7 @@ import { DashboardStateDialogComponent } from '@home/components/dashboard-page/s | ||
134 | import { EmbedDashboardDialogComponent } from '@home/components/widget/dialog/embed-dashboard-dialog.component'; | 134 | import { EmbedDashboardDialogComponent } from '@home/components/widget/dialog/embed-dashboard-dialog.component'; |
135 | import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token'; | 135 | import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token'; |
136 | import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component'; | 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 | @NgModule({ | 139 | @NgModule({ |
139 | declarations: | 140 | declarations: |
@@ -247,7 +248,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag | @@ -247,7 +248,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag | ||
247 | ManageDashboardStatesDialogComponent, | 248 | ManageDashboardStatesDialogComponent, |
248 | DashboardStateDialogComponent, | 249 | DashboardStateDialogComponent, |
249 | EmbedDashboardDialogComponent, | 250 | EmbedDashboardDialogComponent, |
250 | - DisplayWidgetTypesPanelComponent | 251 | + DisplayWidgetTypesPanelComponent, |
252 | + FirmwareAutocompleteComponent | ||
251 | ], | 253 | ], |
252 | imports: [ | 254 | imports: [ |
253 | CommonModule, | 255 | CommonModule, |
@@ -351,7 +353,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag | @@ -351,7 +353,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag | ||
351 | ManageDashboardStatesDialogComponent, | 353 | ManageDashboardStatesDialogComponent, |
352 | DashboardStateDialogComponent, | 354 | DashboardStateDialogComponent, |
353 | EmbedDashboardDialogComponent, | 355 | EmbedDashboardDialogComponent, |
354 | - DisplayWidgetTypesPanelComponent | 356 | + DisplayWidgetTypesPanelComponent, |
357 | + FirmwareAutocompleteComponent | ||
355 | ], | 358 | ], |
356 | providers: [ | 359 | providers: [ |
357 | WidgetComponentService, | 360 | WidgetComponentService, |
@@ -60,6 +60,10 @@ | @@ -60,6 +60,10 @@ | ||
60 | {{ 'device-profile.type-required' | translate }} | 60 | {{ 'device-profile.type-required' | translate }} |
61 | </mat-error> | 61 | </mat-error> |
62 | </mat-form-field> | 62 | </mat-form-field> |
63 | + <tb-firmware-autocomplete | ||
64 | + [useFullEntityId]="true" | ||
65 | + formControlName="firmwareId"> | ||
66 | + </tb-firmware-autocomplete> | ||
63 | <mat-form-field class="mat-block"> | 67 | <mat-form-field class="mat-block"> |
64 | <mat-label translate>device-profile.description</mat-label> | 68 | <mat-label translate>device-profile.description</mat-label> |
65 | <textarea matInput formControlName="description" rows="2"></textarea> | 69 | <textarea matInput formControlName="description" rows="2"></textarea> |
@@ -48,7 +48,7 @@ import { MatHorizontalStepper } from '@angular/material/stepper'; | @@ -48,7 +48,7 @@ import { MatHorizontalStepper } from '@angular/material/stepper'; | ||
48 | import { RuleChainId } from '@shared/models/id/rule-chain-id'; | 48 | import { RuleChainId } from '@shared/models/id/rule-chain-id'; |
49 | import { StepperSelectionEvent } from '@angular/cdk/stepper'; | 49 | import { StepperSelectionEvent } from '@angular/cdk/stepper'; |
50 | import { deepTrim } from '@core/utils'; | 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 | export interface AddDeviceProfileDialogData { | 53 | export interface AddDeviceProfileDialogData { |
54 | deviceProfileName: string; | 54 | deviceProfileName: string; |
@@ -72,13 +72,13 @@ export class AddDeviceProfileDialogComponent extends | @@ -72,13 +72,13 @@ export class AddDeviceProfileDialogComponent extends | ||
72 | 72 | ||
73 | entityType = EntityType; | 73 | entityType = EntityType; |
74 | 74 | ||
75 | - deviceProfileTypes = Object.keys(DeviceProfileType); | 75 | + deviceProfileTypes = Object.values(DeviceProfileType); |
76 | 76 | ||
77 | deviceProfileTypeTranslations = deviceProfileTypeTranslationMap; | 77 | deviceProfileTypeTranslations = deviceProfileTypeTranslationMap; |
78 | 78 | ||
79 | deviceTransportTypeHints = deviceTransportTypeHintMap; | 79 | deviceTransportTypeHints = deviceTransportTypeHintMap; |
80 | 80 | ||
81 | - deviceTransportTypes = Object.keys(DeviceTransportType); | 81 | + deviceTransportTypes = Object.values(DeviceTransportType); |
82 | 82 | ||
83 | deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; | 83 | deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; |
84 | 84 | ||
@@ -108,6 +108,7 @@ export class AddDeviceProfileDialogComponent extends | @@ -108,6 +108,7 @@ export class AddDeviceProfileDialogComponent extends | ||
108 | type: [DeviceProfileType.DEFAULT, [Validators.required]], | 108 | type: [DeviceProfileType.DEFAULT, [Validators.required]], |
109 | defaultRuleChainId: [null, []], | 109 | defaultRuleChainId: [null, []], |
110 | defaultQueueName: ['', []], | 110 | defaultQueueName: ['', []], |
111 | + firmwareId: [null], | ||
111 | description: ['', []] | 112 | description: ['', []] |
112 | } | 113 | } |
113 | ); | 114 | ); |
@@ -186,6 +187,7 @@ export class AddDeviceProfileDialogComponent extends | @@ -186,6 +187,7 @@ export class AddDeviceProfileDialogComponent extends | ||
186 | transportType: this.transportConfigFormGroup.get('transportType').value, | 187 | transportType: this.transportConfigFormGroup.get('transportType').value, |
187 | provisionType: deviceProvisionConfiguration.type, | 188 | provisionType: deviceProvisionConfiguration.type, |
188 | provisionDeviceKey, | 189 | provisionDeviceKey, |
190 | + firmwareId: this.deviceProfileDetailsFormGroup.get('firmwareId').value, | ||
189 | description: this.deviceProfileDetailsFormGroup.get('description').value, | 191 | description: this.deviceProfileDetailsFormGroup.get('description').value, |
190 | profileData: { | 192 | profileData: { |
191 | configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), | 193 | configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), |
@@ -63,6 +63,10 @@ | @@ -63,6 +63,10 @@ | ||
63 | [queueType]="serviceType" | 63 | [queueType]="serviceType" |
64 | formControlName="defaultQueueName"> | 64 | formControlName="defaultQueueName"> |
65 | </tb-queue-type-list> | 65 | </tb-queue-type-list> |
66 | + <tb-firmware-autocomplete | ||
67 | + [useFullEntityId]="true" | ||
68 | + formControlName="firmwareId"> | ||
69 | + </tb-firmware-autocomplete> | ||
66 | <mat-form-field fxHide class="mat-block"> | 70 | <mat-form-field fxHide class="mat-block"> |
67 | <mat-label translate>device-profile.type</mat-label> | 71 | <mat-label translate>device-profile.type</mat-label> |
68 | <mat-select formControlName="type" required> | 72 | <mat-select formControlName="type" required> |
@@ -53,11 +53,11 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { | @@ -53,11 +53,11 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { | ||
53 | 53 | ||
54 | entityType = EntityType; | 54 | entityType = EntityType; |
55 | 55 | ||
56 | - deviceProfileTypes = Object.keys(DeviceProfileType); | 56 | + deviceProfileTypes = Object.values(DeviceProfileType); |
57 | 57 | ||
58 | deviceProfileTypeTranslations = deviceProfileTypeTranslationMap; | 58 | deviceProfileTypeTranslations = deviceProfileTypeTranslationMap; |
59 | 59 | ||
60 | - deviceTransportTypes = Object.keys(DeviceTransportType); | 60 | + deviceTransportTypes = Object.values(DeviceTransportType); |
61 | 61 | ||
62 | deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; | 62 | deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; |
63 | 63 | ||
@@ -109,6 +109,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { | @@ -109,6 +109,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { | ||
109 | }), | 109 | }), |
110 | defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], | 110 | defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], |
111 | defaultQueueName: [entity ? entity.defaultQueueName : '', []], | 111 | defaultQueueName: [entity ? entity.defaultQueueName : '', []], |
112 | + firmwareId: [entity ? entity.firmwareId : null], | ||
112 | description: [entity ? entity.description : '', []], | 113 | description: [entity ? entity.description : '', []], |
113 | } | 114 | } |
114 | ); | 115 | ); |
@@ -184,6 +185,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { | @@ -184,6 +185,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { | ||
184 | }}, {emitEvent: false}); | 185 | }}, {emitEvent: false}); |
185 | this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false}); | 186 | this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false}); |
186 | this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false}); | 187 | this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false}); |
188 | + this.entityForm.patchValue({firmwareId: entity.firmwareId}, {emitEvent: false}); | ||
187 | this.entityForm.patchValue({description: entity.description}, {emitEvent: false}); | 189 | this.entityForm.patchValue({description: entity.description}, {emitEvent: false}); |
188 | } | 190 | } |
189 | 191 |
@@ -48,6 +48,10 @@ | @@ -48,6 +48,10 @@ | ||
48 | <mat-label translate>device.label</mat-label> | 48 | <mat-label translate>device.label</mat-label> |
49 | <input matInput formControlName="label"> | 49 | <input matInput formControlName="label"> |
50 | </mat-form-field> | 50 | </mat-form-field> |
51 | + <tb-firmware-autocomplete | ||
52 | + [useFullEntityId]="true" | ||
53 | + formControlName="firmwareId"> | ||
54 | + </tb-firmware-autocomplete> | ||
51 | <mat-form-field class="mat-block" style="padding-bottom: 14px;"> | 55 | <mat-form-field class="mat-block" style="padding-bottom: 14px;"> |
52 | <mat-label translate>device-profile.transport-type</mat-label> | 56 | <mat-label translate>device-profile.transport-type</mat-label> |
53 | <mat-select formControlName="transportType" required> | 57 | <mat-select formControlName="transportType" required> |
@@ -70,7 +70,7 @@ export class DeviceWizardDialogComponent extends | @@ -70,7 +70,7 @@ export class DeviceWizardDialogComponent extends | ||
70 | 70 | ||
71 | entityType = EntityType; | 71 | entityType = EntityType; |
72 | 72 | ||
73 | - deviceTransportTypes = Object.keys(DeviceTransportType); | 73 | + deviceTransportTypes = Object.values(DeviceTransportType); |
74 | 74 | ||
75 | deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; | 75 | deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; |
76 | 76 | ||
@@ -107,6 +107,7 @@ export class DeviceWizardDialogComponent extends | @@ -107,6 +107,7 @@ export class DeviceWizardDialogComponent extends | ||
107 | this.deviceWizardFormGroup = this.fb.group({ | 107 | this.deviceWizardFormGroup = this.fb.group({ |
108 | name: ['', Validators.required], | 108 | name: ['', Validators.required], |
109 | label: [''], | 109 | label: [''], |
110 | + firmwareId: [null], | ||
110 | gateway: [false], | 111 | gateway: [false], |
111 | overwriteActivityTime: [false], | 112 | overwriteActivityTime: [false], |
112 | transportType: [DeviceTransportType.DEFAULT, Validators.required], | 113 | transportType: [DeviceTransportType.DEFAULT, Validators.required], |
@@ -312,6 +313,7 @@ export class DeviceWizardDialogComponent extends | @@ -312,6 +313,7 @@ export class DeviceWizardDialogComponent extends | ||
312 | const device = { | 313 | const device = { |
313 | name: this.deviceWizardFormGroup.get('name').value, | 314 | name: this.deviceWizardFormGroup.get('name').value, |
314 | label: this.deviceWizardFormGroup.get('label').value, | 315 | label: this.deviceWizardFormGroup.get('label').value, |
316 | + firmwareId: this.deviceWizardFormGroup.get('firmwareId').value, | ||
315 | deviceProfileId: profileId, | 317 | deviceProfileId: profileId, |
316 | additionalInfo: { | 318 | additionalInfo: { |
317 | gateway: this.deviceWizardFormGroup.get('gateway').value, | 319 | gateway: this.deviceWizardFormGroup.get('gateway').value, |
@@ -95,6 +95,10 @@ | @@ -95,6 +95,10 @@ | ||
95 | <mat-label translate>device.label</mat-label> | 95 | <mat-label translate>device.label</mat-label> |
96 | <input matInput formControlName="label"> | 96 | <input matInput formControlName="label"> |
97 | </mat-form-field> | 97 | </mat-form-field> |
98 | + <tb-firmware-autocomplete | ||
99 | + [useFullEntityId]="true" | ||
100 | + formControlName="firmwareId"> | ||
101 | + </tb-firmware-autocomplete> | ||
98 | <tb-device-data | 102 | <tb-device-data |
99 | formControlName="deviceData" | 103 | formControlName="deviceData" |
100 | required> | 104 | required> |
@@ -79,6 +79,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { | @@ -79,6 +79,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { | ||
79 | { | 79 | { |
80 | name: [entity ? entity.name : '', [Validators.required]], | 80 | name: [entity ? entity.name : '', [Validators.required]], |
81 | deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]], | 81 | deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]], |
82 | + firmwareId: [entity ? entity.firmwareId : null], | ||
82 | label: [entity ? entity.label : ''], | 83 | label: [entity ? entity.label : ''], |
83 | deviceData: [entity ? entity.deviceData : null, [Validators.required]], | 84 | deviceData: [entity ? entity.deviceData : null, [Validators.required]], |
84 | additionalInfo: this.fb.group( | 85 | additionalInfo: this.fb.group( |
@@ -95,6 +96,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { | @@ -95,6 +96,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { | ||
95 | updateForm(entity: DeviceInfo) { | 96 | updateForm(entity: DeviceInfo) { |
96 | this.entityForm.patchValue({name: entity.name}); | 97 | this.entityForm.patchValue({name: entity.name}); |
97 | this.entityForm.patchValue({deviceProfileId: entity.deviceProfileId}); | 98 | this.entityForm.patchValue({deviceProfileId: entity.deviceProfileId}); |
99 | + this.entityForm.patchValue({firmwareId: entity.firmwareId}); | ||
98 | this.entityForm.patchValue({label: entity.label}); | 100 | this.entityForm.patchValue({label: entity.label}); |
99 | this.entityForm.patchValue({deviceData: entity.deviceData}); | 101 | this.entityForm.patchValue({deviceData: entity.deviceData}); |
100 | this.entityForm.patchValue({ | 102 | this.entityForm.patchValue({ |
@@ -28,6 +28,16 @@ | @@ -28,6 +28,16 @@ | ||
28 | [fxShow]="!hideDelete() && !isEdit"> | 28 | [fxShow]="!hideDelete() && !isEdit"> |
29 | {{'resource.delete' | translate }} | 29 | {{'resource.delete' | translate }} |
30 | </button> | 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 | </div> | 41 | </div> |
32 | <div class="mat-padding" fxLayout="column"> | 42 | <div class="mat-padding" fxLayout="column"> |
33 | <form [formGroup]="entityForm"> | 43 | <form [formGroup]="entityForm"> |
@@ -24,6 +24,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | @@ -24,6 +24,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | ||
24 | import { EntityComponent } from '@home/components/entity/entity.component'; | 24 | import { EntityComponent } from '@home/components/entity/entity.component'; |
25 | import { ChecksumAlgorithm, ChecksumAlgorithmTranslationMap, Firmware } from '@shared/models/firmware.models'; | 25 | import { ChecksumAlgorithm, ChecksumAlgorithmTranslationMap, Firmware } from '@shared/models/firmware.models'; |
26 | import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; | 26 | import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; |
27 | +import { ActionNotificationShow } from '@core/notification/notification.actions'; | ||
27 | 28 | ||
28 | @Component({ | 29 | @Component({ |
29 | selector: 'tb-firmware', | 30 | selector: 'tb-firmware', |
@@ -109,4 +110,15 @@ export class FirmwaresComponent extends EntityComponent<Firmware> implements OnI | @@ -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,6 +27,7 @@ import { KeyFilter } from '@shared/models/query/query.models'; | ||
27 | import { TimeUnit } from '@shared/models/time/time.models'; | 27 | import { TimeUnit } from '@shared/models/time/time.models'; |
28 | import * as _moment from 'moment'; | 28 | import * as _moment from 'moment'; |
29 | import { AbstractControl, ValidationErrors } from '@angular/forms'; | 29 | import { AbstractControl, ValidationErrors } from '@angular/forms'; |
30 | +import { FirmwareId } from '@shared/models/id/firmware-id'; | ||
30 | 31 | ||
31 | export enum DeviceProfileType { | 32 | export enum DeviceProfileType { |
32 | DEFAULT = 'DEFAULT' | 33 | DEFAULT = 'DEFAULT' |
@@ -445,6 +446,7 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> { | @@ -445,6 +446,7 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> { | ||
445 | provisionDeviceKey?: string; | 446 | provisionDeviceKey?: string; |
446 | defaultRuleChainId?: RuleChainId; | 447 | defaultRuleChainId?: RuleChainId; |
447 | defaultQueueName?: string; | 448 | defaultQueueName?: string; |
449 | + firmwareId?: FirmwareId; | ||
448 | profileData: DeviceProfileData; | 450 | profileData: DeviceProfileData; |
449 | } | 451 | } |
450 | 452 | ||
@@ -499,6 +501,7 @@ export interface Device extends BaseData<DeviceId> { | @@ -499,6 +501,7 @@ export interface Device extends BaseData<DeviceId> { | ||
499 | name: string; | 501 | name: string; |
500 | type: string; | 502 | type: string; |
501 | label: string; | 503 | label: string; |
504 | + firmwareId?: FirmwareId; | ||
502 | deviceProfileId?: DeviceProfileId; | 505 | deviceProfileId?: DeviceProfileId; |
503 | deviceData?: DeviceData; | 506 | deviceData?: DeviceData; |
504 | additionalInfo?: any; | 507 | additionalInfo?: any; |
@@ -1698,6 +1698,7 @@ | @@ -1698,6 +1698,7 @@ | ||
1698 | "checksum": "Checksum", | 1698 | "checksum": "Checksum", |
1699 | "checksum-required": "Checksum is required.", | 1699 | "checksum-required": "Checksum is required.", |
1700 | "checksum-algorithm": "Checksum algorithm", | 1700 | "checksum-algorithm": "Checksum algorithm", |
1701 | + "copyId": "Copy firmware Id", | ||
1701 | "description": "Description", | 1702 | "description": "Description", |
1702 | "delete": "Delete firmware", | 1703 | "delete": "Delete firmware", |
1703 | "delete-firmware-text": "Be careful, after the confirmation the firmware will become unrecoverable.", | 1704 | "delete-firmware-text": "Be careful, after the confirmation the firmware will become unrecoverable.", |
@@ -1708,10 +1709,12 @@ | @@ -1708,10 +1709,12 @@ | ||
1708 | "drop-file": "Drop a firmware file or click to select a file to upload.", | 1709 | "drop-file": "Drop a firmware file or click to select a file to upload.", |
1709 | "empty": "Firmware is empty", | 1710 | "empty": "Firmware is empty", |
1710 | "export": "Export firmware", | 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 | "no-firmware-text": "No firmwares found", | 1714 | "no-firmware-text": "No firmwares found", |
1713 | "firmware": "Firmware", | 1715 | "firmware": "Firmware", |
1714 | "firmware-details": "Firmware details", | 1716 | "firmware-details": "Firmware details", |
1717 | + "firmware-required": "Firmware is required.", | ||
1715 | "search": "Search firmwares", | 1718 | "search": "Search firmwares", |
1716 | "selected-firmware": "{ count, plural, 1 {1 firmware} other {# firmwares} } selected", | 1719 | "selected-firmware": "{ count, plural, 1 {1 firmware} other {# firmwares} } selected", |
1717 | "title": "Title", | 1720 | "title": "Title", |