Commit 1e87c728181c94d586e389a8485be8812af47ed2
1 parent
3adbb481
UI: Device profile autocomplete. Device profile data.
Showing
22 changed files
with
1237 additions
and
7 deletions
... | ... | @@ -94,6 +94,8 @@ import { DeviceProfileDataComponent } from './profile/device-profile-data.compon |
94 | 94 | import { DeviceProfileComponent } from './profile/device-profile.component'; |
95 | 95 | import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component'; |
96 | 96 | import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component'; |
97 | +import { DeviceProfileDialogComponent } from './profile/device-profile-dialog.component'; | |
98 | +import { DeviceProfileAutocompleteComponent } from './profile/device-profile-autocomplete.component'; | |
97 | 99 | |
98 | 100 | @NgModule({ |
99 | 101 | declarations: |
... | ... | @@ -165,12 +167,14 @@ import { DeviceProfileTransportConfigurationComponent } from './profile/device/d |
165 | 167 | TenantProfileDataComponent, |
166 | 168 | TenantProfileComponent, |
167 | 169 | TenantProfileDialogComponent, |
170 | + DeviceProfileAutocompleteComponent, | |
168 | 171 | DefaultDeviceProfileConfigurationComponent, |
169 | 172 | DeviceProfileConfigurationComponent, |
170 | 173 | DefaultDeviceProfileTransportConfigurationComponent, |
171 | 174 | DeviceProfileTransportConfigurationComponent, |
172 | 175 | DeviceProfileDataComponent, |
173 | - DeviceProfileComponent | |
176 | + DeviceProfileComponent, | |
177 | + DeviceProfileDialogComponent | |
174 | 178 | ], |
175 | 179 | imports: [ |
176 | 180 | CommonModule, |
... | ... | @@ -231,12 +235,14 @@ import { DeviceProfileTransportConfigurationComponent } from './profile/device/d |
231 | 235 | TenantProfileDataComponent, |
232 | 236 | TenantProfileComponent, |
233 | 237 | TenantProfileDialogComponent, |
238 | + DeviceProfileAutocompleteComponent, | |
234 | 239 | DefaultDeviceProfileConfigurationComponent, |
235 | 240 | DeviceProfileConfigurationComponent, |
236 | 241 | DefaultDeviceProfileTransportConfigurationComponent, |
237 | 242 | DeviceProfileTransportConfigurationComponent, |
238 | 243 | DeviceProfileDataComponent, |
239 | - DeviceProfileComponent | |
244 | + DeviceProfileComponent, | |
245 | + DeviceProfileDialogComponent | |
240 | 246 | ], |
241 | 247 | providers: [ |
242 | 248 | WidgetComponentService, | ... | ... |
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2020 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]="selectDeviceProfileFormGroup" class="mat-block"> | |
19 | + <input matInput type="text" placeholder="{{ 'device-profile.device-profile' | translate }}" | |
20 | + #deviceProfileInput | |
21 | + formControlName="deviceProfile" | |
22 | + [required]="required" | |
23 | + (keydown)="deviceProfileEnter($event)" | |
24 | + (keypress)="deviceProfileEnter($event)" | |
25 | + [matAutocomplete]="deviceProfileAutocomplete"> | |
26 | + <button *ngIf="selectDeviceProfileFormGroup.get('deviceProfile').value && !disabled" | |
27 | + type="button" | |
28 | + matSuffix mat-button mat-icon-button aria-label="Clear" | |
29 | + (click)="clear()"> | |
30 | + <mat-icon class="material-icons">close</mat-icon> | |
31 | + </button> | |
32 | + <button *ngIf="selectDeviceProfileFormGroup.get('deviceProfile').value && !disabled" | |
33 | + type="button" | |
34 | + matSuffix mat-button mat-icon-button aria-label="Edit" | |
35 | + matTooltip="{{ 'device-profile.edit' | translate }}" | |
36 | + matTooltipPosition="above" | |
37 | + (click)="editDeviceProfile($event)"> | |
38 | + <mat-icon class="material-icons">edit</mat-icon> | |
39 | + </button> | |
40 | + <mat-autocomplete | |
41 | + class="tb-autocomplete" | |
42 | + #deviceProfileAutocomplete="matAutocomplete" | |
43 | + [displayWith]="displayDeviceProfileFn"> | |
44 | + <mat-option *ngFor="let deviceProfile of filteredDeviceProfiles | async" [value]="deviceProfile"> | |
45 | + <span [innerHTML]="deviceProfile.name | highlight:searchText"></span> | |
46 | + </mat-option> | |
47 | + <mat-option *ngIf="!(filteredDeviceProfiles | async)?.length" [value]="null" class="tb-not-found"> | |
48 | + <div class="tb-not-found-content" (click)="$event.stopPropagation()"> | |
49 | + <div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty"> | |
50 | + <span translate>device-profile.no-device-profiles-found</span> | |
51 | + </div> | |
52 | + <ng-template #searchNotEmpty> | |
53 | + <span> | |
54 | + {{ translate.get('device-profile.no-device-profiles-matching', | |
55 | + {entity: truncate.transform(searchText, true, 6, '...')}) | async }} | |
56 | + </span> | |
57 | + </ng-template> | |
58 | + <span> | |
59 | + <a translate (click)="createDeviceProfile($event, searchText)">device-profile.create-new-device-profile</a> | |
60 | + </span> | |
61 | + </div> | |
62 | + </mat-option> | |
63 | + </mat-autocomplete> | |
64 | + <mat-error *ngIf="selectDeviceProfileFormGroup.get('deviceProfile').hasError('required')"> | |
65 | + {{ 'device-profile.device-profile-required' | translate }} | |
66 | + </mat-error> | |
67 | +</mat-form-field> | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2020 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, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; | |
18 | +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; | |
19 | +import { Observable } from 'rxjs'; | |
20 | +import { PageLink } from '@shared/models/page/page-link'; | |
21 | +import { Direction } from '@shared/models/page/sort-order'; | |
22 | +import { map, mergeMap, startWith, tap } from 'rxjs/operators'; | |
23 | +import { Store } from '@ngrx/store'; | |
24 | +import { AppState } from '@app/core/core.state'; | |
25 | +import { TranslateService } from '@ngx-translate/core'; | |
26 | +import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
27 | +import { entityIdEquals } from '@shared/models/id/entity-id'; | |
28 | +import { TruncatePipe } from '@shared//pipe/truncate.pipe'; | |
29 | +import { ENTER } from '@angular/cdk/keycodes'; | |
30 | +import { MatDialog } from '@angular/material/dialog'; | |
31 | +import { DeviceProfileId } from '@shared/models/id/device-profile-id'; | |
32 | +import { | |
33 | + createDeviceProfileConfiguration, | |
34 | + createDeviceProfileTransportConfiguration, | |
35 | + DeviceProfile, | |
36 | + DeviceProfileInfo, | |
37 | + DeviceProfileType, | |
38 | + DeviceTransportType | |
39 | +} from '@shared/models/device.models'; | |
40 | +import { DeviceProfileService } from '@core/http/device-profile.service'; | |
41 | +import { DeviceProfileDialogComponent, DeviceProfileDialogData } from './device-profile-dialog.component'; | |
42 | + | |
43 | +@Component({ | |
44 | + selector: 'tb-device-profile-autocomplete', | |
45 | + templateUrl: './device-profile-autocomplete.component.html', | |
46 | + styleUrls: [], | |
47 | + providers: [{ | |
48 | + provide: NG_VALUE_ACCESSOR, | |
49 | + useExisting: forwardRef(() => DeviceProfileAutocompleteComponent), | |
50 | + multi: true | |
51 | + }] | |
52 | +}) | |
53 | +export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, OnInit { | |
54 | + | |
55 | + selectDeviceProfileFormGroup: FormGroup; | |
56 | + | |
57 | + modelValue: DeviceProfileId | null; | |
58 | + | |
59 | + @Input() | |
60 | + selectDefaultProfile = false; | |
61 | + | |
62 | + private requiredValue: boolean; | |
63 | + get required(): boolean { | |
64 | + return this.requiredValue; | |
65 | + } | |
66 | + @Input() | |
67 | + set required(value: boolean) { | |
68 | + this.requiredValue = coerceBooleanProperty(value); | |
69 | + } | |
70 | + | |
71 | + @Input() | |
72 | + disabled: boolean; | |
73 | + | |
74 | + @Output() | |
75 | + deviceProfileUpdated = new EventEmitter<DeviceProfileId>(); | |
76 | + | |
77 | + @Output() | |
78 | + deviceProfileChanged = new EventEmitter<DeviceProfileInfo>(); | |
79 | + | |
80 | + @ViewChild('deviceProfileInput', {static: true}) deviceProfileInput: ElementRef; | |
81 | + | |
82 | + filteredDeviceProfiles: Observable<Array<DeviceProfileInfo>>; | |
83 | + | |
84 | + searchText = ''; | |
85 | + | |
86 | + private propagateChange = (v: any) => { }; | |
87 | + | |
88 | + constructor(private store: Store<AppState>, | |
89 | + public translate: TranslateService, | |
90 | + public truncate: TruncatePipe, | |
91 | + private deviceProfileService: DeviceProfileService, | |
92 | + private fb: FormBuilder, | |
93 | + private dialog: MatDialog) { | |
94 | + this.selectDeviceProfileFormGroup = this.fb.group({ | |
95 | + deviceProfile: [null] | |
96 | + }); | |
97 | + } | |
98 | + | |
99 | + registerOnChange(fn: any): void { | |
100 | + this.propagateChange = fn; | |
101 | + } | |
102 | + | |
103 | + registerOnTouched(fn: any): void { | |
104 | + } | |
105 | + | |
106 | + ngOnInit() { | |
107 | + this.filteredDeviceProfiles = this.selectDeviceProfileFormGroup.get('deviceProfile').valueChanges | |
108 | + .pipe( | |
109 | + tap((value: DeviceProfileInfo | string) => { | |
110 | + let modelValue: DeviceProfileInfo | null; | |
111 | + if (typeof value === 'string' || !value) { | |
112 | + modelValue = null; | |
113 | + } else { | |
114 | + modelValue = value; | |
115 | + } | |
116 | + this.updateView(modelValue); | |
117 | + }), | |
118 | + startWith<string | DeviceProfileInfo>(''), | |
119 | + map(value => value ? (typeof value === 'string' ? value : value.name) : ''), | |
120 | + mergeMap(name => this.fetchDeviceProfiles(name) ) | |
121 | + ); | |
122 | + } | |
123 | + | |
124 | + selectDefaultDeviceProfileIfNeeded(): void { | |
125 | + if (this.selectDefaultProfile && !this.modelValue) { | |
126 | + this.deviceProfileService.getDefaultDeviceProfileInfo().subscribe( | |
127 | + (profile) => { | |
128 | + if (profile) { | |
129 | + this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false}); | |
130 | + this.updateView(profile); | |
131 | + } | |
132 | + } | |
133 | + ); | |
134 | + } | |
135 | + } | |
136 | + | |
137 | + setDisabledState(isDisabled: boolean): void { | |
138 | + this.disabled = isDisabled; | |
139 | + } | |
140 | + | |
141 | + writeValue(value: DeviceProfileId | null): void { | |
142 | + this.searchText = ''; | |
143 | + if (value != null) { | |
144 | + this.deviceProfileService.getDeviceProfileInfo(value.id).subscribe( | |
145 | + (profile) => { | |
146 | + this.modelValue = new DeviceProfileId(profile.id.id); | |
147 | + this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: true}); | |
148 | + } | |
149 | + ); | |
150 | + } else { | |
151 | + this.modelValue = null; | |
152 | + this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(null, {emitEvent: true}); | |
153 | + this.selectDefaultDeviceProfileIfNeeded(); | |
154 | + } | |
155 | + } | |
156 | + | |
157 | + updateView(deviceProfile: DeviceProfileInfo | null) { | |
158 | + const idValue = deviceProfile ? new DeviceProfileId(deviceProfile.id.id) : null; | |
159 | + if (!entityIdEquals(this.modelValue, idValue)) { | |
160 | + this.modelValue = idValue; | |
161 | + this.propagateChange(this.modelValue); | |
162 | + this.deviceProfileChanged.emit(deviceProfile); | |
163 | + } | |
164 | + } | |
165 | + | |
166 | + displayDeviceProfileFn(profile?: DeviceProfileInfo): string | undefined { | |
167 | + return profile ? profile.name : undefined; | |
168 | + } | |
169 | + | |
170 | + fetchDeviceProfiles(searchText?: string): Observable<Array<DeviceProfileInfo>> { | |
171 | + this.searchText = searchText; | |
172 | + const pageLink = new PageLink(10, 0, searchText, { | |
173 | + property: 'name', | |
174 | + direction: Direction.ASC | |
175 | + }); | |
176 | + return this.deviceProfileService.getDeviceProfileInfos(pageLink, {ignoreLoading: true}).pipe( | |
177 | + map(pageData => { | |
178 | + return pageData.data; | |
179 | + }) | |
180 | + ); | |
181 | + } | |
182 | + | |
183 | + clear() { | |
184 | + this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(null, {emitEvent: true}); | |
185 | + setTimeout(() => { | |
186 | + this.deviceProfileInput.nativeElement.blur(); | |
187 | + this.deviceProfileInput.nativeElement.focus(); | |
188 | + }, 0); | |
189 | + } | |
190 | + | |
191 | + textIsNotEmpty(text: string): boolean { | |
192 | + return (text && text.length > 0); | |
193 | + } | |
194 | + | |
195 | + deviceProfileEnter($event: KeyboardEvent) { | |
196 | + if ($event.keyCode === ENTER) { | |
197 | + $event.preventDefault(); | |
198 | + if (!this.modelValue) { | |
199 | + this.createDeviceProfile($event, this.searchText); | |
200 | + } | |
201 | + } | |
202 | + } | |
203 | + | |
204 | + createDeviceProfile($event: Event, profileName: string) { | |
205 | + $event.preventDefault(); | |
206 | + const deviceProfile: DeviceProfile = { | |
207 | + id: null, | |
208 | + name: profileName, | |
209 | + type: DeviceProfileType.DEFAULT, | |
210 | + transportType: DeviceTransportType.DEFAULT, | |
211 | + profileData: { | |
212 | + configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), | |
213 | + transportConfiguration: createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT) | |
214 | + } | |
215 | + }; | |
216 | + this.openDeviceProfileDialog(deviceProfile, true); | |
217 | + } | |
218 | + | |
219 | + editDeviceProfile($event: Event) { | |
220 | + $event.preventDefault(); | |
221 | + this.deviceProfileService.getDeviceProfile(this.modelValue.id).subscribe( | |
222 | + (deviceProfile) => { | |
223 | + this.openDeviceProfileDialog(deviceProfile, false); | |
224 | + } | |
225 | + ); | |
226 | + } | |
227 | + | |
228 | + openDeviceProfileDialog(deviceProfile: DeviceProfile, isAdd: boolean) { | |
229 | + this.dialog.open<DeviceProfileDialogComponent, DeviceProfileDialogData, | |
230 | + DeviceProfile>(DeviceProfileDialogComponent, { | |
231 | + disableClose: true, | |
232 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | |
233 | + data: { | |
234 | + isAdd, | |
235 | + deviceProfile | |
236 | + } | |
237 | + }).afterClosed().subscribe( | |
238 | + (savedDeviceProfile) => { | |
239 | + if (!savedDeviceProfile) { | |
240 | + setTimeout(() => { | |
241 | + this.deviceProfileInput.nativeElement.blur(); | |
242 | + this.deviceProfileInput.nativeElement.focus(); | |
243 | + }, 0); | |
244 | + } else { | |
245 | + this.deviceProfileService.getDeviceProfileInfo(savedDeviceProfile.id.id).subscribe( | |
246 | + (profile) => { | |
247 | + this.modelValue = new DeviceProfileId(profile.id.id); | |
248 | + this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: true}); | |
249 | + if (isAdd) { | |
250 | + this.propagateChange(this.modelValue); | |
251 | + } else { | |
252 | + this.deviceProfileUpdated.next(savedDeviceProfile.id); | |
253 | + } | |
254 | + this.deviceProfileChanged.emit(profile); | |
255 | + } | |
256 | + ); | |
257 | + } | |
258 | + } | |
259 | + ); | |
260 | + } | |
261 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2020 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 | +<form (ngSubmit)="save()" style="min-width: 600px;"> | |
19 | + <mat-toolbar color="primary"> | |
20 | + <h2>{{ (isAdd ? 'device-profile.add' : 'device-profile.edit' ) | translate }}</h2> | |
21 | + <span fxFlex></span> | |
22 | + <button mat-icon-button | |
23 | + (click)="cancel()" | |
24 | + type="button"> | |
25 | + <mat-icon class="material-icons">close</mat-icon> | |
26 | + </button> | |
27 | + </mat-toolbar> | |
28 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | |
29 | + </mat-progress-bar> | |
30 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | |
31 | + <div mat-dialog-content> | |
32 | + <tb-device-profile | |
33 | + #deviceProfileComponent | |
34 | + [standalone]="true" | |
35 | + [entity]="deviceProfile" | |
36 | + [isEdit]="true"> | |
37 | + </tb-device-profile> | |
38 | + </div> | |
39 | + <div mat-dialog-actions fxLayoutAlign="end center"> | |
40 | + <button mat-raised-button color="primary" | |
41 | + type="submit" | |
42 | + [disabled]="(isLoading$ | async) || deviceProfileComponent.entityForm?.invalid || !deviceProfileComponent.entityForm?.dirty"> | |
43 | + {{ (isAdd ? 'action.add' : 'action.save') | translate }} | |
44 | + </button> | |
45 | + <button mat-button color="primary" | |
46 | + type="button" | |
47 | + cdkFocusInitial | |
48 | + [disabled]="(isLoading$ | async)" | |
49 | + (click)="cancel()"> | |
50 | + {{ 'action.cancel' | translate }} | |
51 | + </button> | |
52 | + </div> | |
53 | +</form> | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2020 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 { | |
18 | + AfterViewInit, | |
19 | + Component, | |
20 | + ComponentFactoryResolver, | |
21 | + Inject, | |
22 | + Injector, | |
23 | + SkipSelf, | |
24 | + ViewChild | |
25 | +} from '@angular/core'; | |
26 | +import { ErrorStateMatcher } from '@angular/material/core'; | |
27 | +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; | |
28 | +import { Store } from '@ngrx/store'; | |
29 | +import { AppState } from '@core/core.state'; | |
30 | +import { FormControl, FormGroupDirective, NgForm } from '@angular/forms'; | |
31 | +import { DialogComponent } from '@shared/components/dialog.component'; | |
32 | +import { Router } from '@angular/router'; | |
33 | +import { DeviceProfile } from '@shared/models/device.models'; | |
34 | +import { DeviceProfileComponent } from './device-profile.component'; | |
35 | +import { DeviceProfileService } from '@core/http/device-profile.service'; | |
36 | + | |
37 | +export interface DeviceProfileDialogData { | |
38 | + deviceProfile: DeviceProfile; | |
39 | + isAdd: boolean; | |
40 | +} | |
41 | + | |
42 | +@Component({ | |
43 | + selector: 'tb-device-profile-dialog', | |
44 | + templateUrl: './device-profile-dialog.component.html', | |
45 | + providers: [{provide: ErrorStateMatcher, useExisting: DeviceProfileDialogComponent}], | |
46 | + styleUrls: [] | |
47 | +}) | |
48 | +export class DeviceProfileDialogComponent extends | |
49 | + DialogComponent<DeviceProfileDialogComponent, DeviceProfile> implements ErrorStateMatcher, AfterViewInit { | |
50 | + | |
51 | + isAdd: boolean; | |
52 | + deviceProfile: DeviceProfile; | |
53 | + | |
54 | + submitted = false; | |
55 | + | |
56 | + @ViewChild('deviceProfileComponent', {static: true}) deviceProfileComponent: DeviceProfileComponent; | |
57 | + | |
58 | + constructor(protected store: Store<AppState>, | |
59 | + protected router: Router, | |
60 | + @Inject(MAT_DIALOG_DATA) public data: DeviceProfileDialogData, | |
61 | + public dialogRef: MatDialogRef<DeviceProfileDialogComponent, DeviceProfile>, | |
62 | + private componentFactoryResolver: ComponentFactoryResolver, | |
63 | + private injector: Injector, | |
64 | + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, | |
65 | + private deviceProfileService: DeviceProfileService) { | |
66 | + super(store, router, dialogRef); | |
67 | + this.isAdd = this.data.isAdd; | |
68 | + this.deviceProfile = this.data.deviceProfile; | |
69 | + } | |
70 | + | |
71 | + ngAfterViewInit(): void { | |
72 | + if (this.isAdd) { | |
73 | + setTimeout(() => { | |
74 | + this.deviceProfileComponent.entityForm.markAsDirty(); | |
75 | + }, 0); | |
76 | + } | |
77 | + } | |
78 | + | |
79 | + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { | |
80 | + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); | |
81 | + const customErrorState = !!(control && control.invalid && this.submitted); | |
82 | + return originalErrorState || customErrorState; | |
83 | + } | |
84 | + | |
85 | + cancel(): void { | |
86 | + this.dialogRef.close(null); | |
87 | + } | |
88 | + | |
89 | + save(): void { | |
90 | + this.submitted = true; | |
91 | + if (this.deviceProfileComponent.entityForm.valid) { | |
92 | + this.deviceProfile = {...this.deviceProfile, ...this.deviceProfileComponent.entityFormValue()}; | |
93 | + this.deviceProfileService.saveDeviceProfile(this.deviceProfile).subscribe( | |
94 | + (deviceProfile) => { | |
95 | + this.dialogRef.close(deviceProfile); | |
96 | + } | |
97 | + ); | |
98 | + } | |
99 | + } | |
100 | + | |
101 | +} | ... | ... |
... | ... | @@ -18,10 +18,10 @@ |
18 | 18 | <div [formGroup]="deviceProfileTransportConfigurationFormGroup"> |
19 | 19 | <div [ngSwitch]="transportType"> |
20 | 20 | <ng-template [ngSwitchCase]="deviceTransportType.DEFAULT"> |
21 | - <tb-default-device-profile-configuration | |
21 | + <tb-default-device-profile-transport-configuration | |
22 | 22 | [required]="required" |
23 | 23 | formControlName="configuration"> |
24 | - </tb-default-device-profile-configuration> | |
24 | + </tb-default-device-profile-transport-configuration> | |
25 | 25 | </ng-template> |
26 | 26 | </div> |
27 | 27 | </div> | ... | ... |
ui-ngx/src/app/modules/home/pages/device/data/default-device-configuration.component.html
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2020 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 | +<form [formGroup]="defaultDeviceConfigurationFormGroup" style="padding-bottom: 16px;"> | |
19 | + <tb-json-object-edit | |
20 | + [required]="required" | |
21 | + label="{{ 'device-profile.type-default' | translate }}" | |
22 | + formControlName="configuration"> | |
23 | + </tb-json-object-edit> | |
24 | +</form> | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2020 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, forwardRef, Input, OnInit } from '@angular/core'; | |
18 | +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; | |
19 | +import { Store } from '@ngrx/store'; | |
20 | +import { AppState } from '@app/core/core.state'; | |
21 | +import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
22 | +import { | |
23 | + DefaultDeviceConfiguration, | |
24 | + DeviceConfiguration, | |
25 | + DeviceProfileType | |
26 | +} from '@shared/models/device.models'; | |
27 | + | |
28 | +@Component({ | |
29 | + selector: 'tb-default-device-configuration', | |
30 | + templateUrl: './default-device-configuration.component.html', | |
31 | + styleUrls: [], | |
32 | + providers: [{ | |
33 | + provide: NG_VALUE_ACCESSOR, | |
34 | + useExisting: forwardRef(() => DefaultDeviceConfigurationComponent), | |
35 | + multi: true | |
36 | + }] | |
37 | +}) | |
38 | +export class DefaultDeviceConfigurationComponent implements ControlValueAccessor, OnInit { | |
39 | + | |
40 | + defaultDeviceConfigurationFormGroup: FormGroup; | |
41 | + | |
42 | + private requiredValue: boolean; | |
43 | + get required(): boolean { | |
44 | + return this.requiredValue; | |
45 | + } | |
46 | + @Input() | |
47 | + set required(value: boolean) { | |
48 | + this.requiredValue = coerceBooleanProperty(value); | |
49 | + } | |
50 | + | |
51 | + @Input() | |
52 | + disabled: boolean; | |
53 | + | |
54 | + private propagateChange = (v: any) => { }; | |
55 | + | |
56 | + constructor(private store: Store<AppState>, | |
57 | + private fb: FormBuilder) { | |
58 | + } | |
59 | + | |
60 | + registerOnChange(fn: any): void { | |
61 | + this.propagateChange = fn; | |
62 | + } | |
63 | + | |
64 | + registerOnTouched(fn: any): void { | |
65 | + } | |
66 | + | |
67 | + ngOnInit() { | |
68 | + this.defaultDeviceConfigurationFormGroup = this.fb.group({ | |
69 | + configuration: [null, Validators.required] | |
70 | + }); | |
71 | + this.defaultDeviceConfigurationFormGroup.valueChanges.subscribe(() => { | |
72 | + this.updateModel(); | |
73 | + }); | |
74 | + } | |
75 | + | |
76 | + setDisabledState(isDisabled: boolean): void { | |
77 | + this.disabled = isDisabled; | |
78 | + if (this.disabled) { | |
79 | + this.defaultDeviceConfigurationFormGroup.disable({emitEvent: false}); | |
80 | + } else { | |
81 | + this.defaultDeviceConfigurationFormGroup.enable({emitEvent: false}); | |
82 | + } | |
83 | + } | |
84 | + | |
85 | + writeValue(value: DefaultDeviceConfiguration | null): void { | |
86 | + this.defaultDeviceConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); | |
87 | + } | |
88 | + | |
89 | + private updateModel() { | |
90 | + let configuration: DeviceConfiguration = null; | |
91 | + if (this.defaultDeviceConfigurationFormGroup.valid) { | |
92 | + configuration = this.defaultDeviceConfigurationFormGroup.getRawValue().configuration; | |
93 | + configuration.type = DeviceProfileType.DEFAULT; | |
94 | + } | |
95 | + this.propagateChange(configuration); | |
96 | + } | |
97 | +} | ... | ... |
ui-ngx/src/app/modules/home/pages/device/data/default-device-transport-configuration.component.html
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2020 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 | +<form [formGroup]="defaultDeviceTransportConfigurationFormGroup" style="padding-bottom: 16px;"> | |
19 | + <tb-json-object-edit | |
20 | + [required]="required" | |
21 | + label="{{ 'device-profile.transport-type-default' | translate }}" | |
22 | + formControlName="configuration"> | |
23 | + </tb-json-object-edit> | |
24 | +</form> | ... | ... |
ui-ngx/src/app/modules/home/pages/device/data/default-device-transport-configuration.component.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2020 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, forwardRef, Input, OnInit } from '@angular/core'; | |
18 | +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; | |
19 | +import { Store } from '@ngrx/store'; | |
20 | +import { AppState } from '@app/core/core.state'; | |
21 | +import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
22 | +import { | |
23 | + DefaultDeviceTransportConfiguration, | |
24 | + DeviceTransportConfiguration, | |
25 | + DeviceTransportType | |
26 | +} from '@shared/models/device.models'; | |
27 | + | |
28 | +@Component({ | |
29 | + selector: 'tb-default-device-transport-configuration', | |
30 | + templateUrl: './default-device-transport-configuration.component.html', | |
31 | + styleUrls: [], | |
32 | + providers: [{ | |
33 | + provide: NG_VALUE_ACCESSOR, | |
34 | + useExisting: forwardRef(() => DefaultDeviceTransportConfigurationComponent), | |
35 | + multi: true | |
36 | + }] | |
37 | +}) | |
38 | +export class DefaultDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { | |
39 | + | |
40 | + defaultDeviceTransportConfigurationFormGroup: FormGroup; | |
41 | + | |
42 | + private requiredValue: boolean; | |
43 | + get required(): boolean { | |
44 | + return this.requiredValue; | |
45 | + } | |
46 | + @Input() | |
47 | + set required(value: boolean) { | |
48 | + this.requiredValue = coerceBooleanProperty(value); | |
49 | + } | |
50 | + | |
51 | + @Input() | |
52 | + disabled: boolean; | |
53 | + | |
54 | + private propagateChange = (v: any) => { }; | |
55 | + | |
56 | + constructor(private store: Store<AppState>, | |
57 | + private fb: FormBuilder) { | |
58 | + } | |
59 | + | |
60 | + registerOnChange(fn: any): void { | |
61 | + this.propagateChange = fn; | |
62 | + } | |
63 | + | |
64 | + registerOnTouched(fn: any): void { | |
65 | + } | |
66 | + | |
67 | + ngOnInit() { | |
68 | + this.defaultDeviceTransportConfigurationFormGroup = this.fb.group({ | |
69 | + configuration: [null, Validators.required] | |
70 | + }); | |
71 | + this.defaultDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => { | |
72 | + this.updateModel(); | |
73 | + }); | |
74 | + } | |
75 | + | |
76 | + setDisabledState(isDisabled: boolean): void { | |
77 | + this.disabled = isDisabled; | |
78 | + if (this.disabled) { | |
79 | + this.defaultDeviceTransportConfigurationFormGroup.disable({emitEvent: false}); | |
80 | + } else { | |
81 | + this.defaultDeviceTransportConfigurationFormGroup.enable({emitEvent: false}); | |
82 | + } | |
83 | + } | |
84 | + | |
85 | + writeValue(value: DefaultDeviceTransportConfiguration | null): void { | |
86 | + this.defaultDeviceTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); | |
87 | + } | |
88 | + | |
89 | + private updateModel() { | |
90 | + let configuration: DeviceTransportConfiguration = null; | |
91 | + if (this.defaultDeviceTransportConfigurationFormGroup.valid) { | |
92 | + configuration = this.defaultDeviceTransportConfigurationFormGroup.getRawValue().configuration; | |
93 | + configuration.type = DeviceTransportType.DEFAULT; | |
94 | + } | |
95 | + this.propagateChange(configuration); | |
96 | + } | |
97 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2020 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 | +<div [formGroup]="deviceConfigurationFormGroup"> | |
19 | + <div [ngSwitch]="type"> | |
20 | + <ng-template [ngSwitchCase]="deviceProfileType.DEFAULT"> | |
21 | + <tb-default-device-configuration | |
22 | + [required]="required" | |
23 | + formControlName="configuration"> | |
24 | + </tb-default-device-configuration> | |
25 | + </ng-template> | |
26 | + </div> | |
27 | +</div> | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2020 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, forwardRef, Input, OnInit } from '@angular/core'; | |
18 | +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; | |
19 | +import { Store } from '@ngrx/store'; | |
20 | +import { AppState } from '@app/core/core.state'; | |
21 | +import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
22 | +import { DeviceConfiguration, DeviceProfileType } from '@shared/models/device.models'; | |
23 | +import { deepClone } from '@core/utils'; | |
24 | + | |
25 | +@Component({ | |
26 | + selector: 'tb-device-configuration', | |
27 | + templateUrl: './device-configuration.component.html', | |
28 | + styleUrls: [], | |
29 | + providers: [{ | |
30 | + provide: NG_VALUE_ACCESSOR, | |
31 | + useExisting: forwardRef(() => DeviceConfigurationComponent), | |
32 | + multi: true | |
33 | + }] | |
34 | +}) | |
35 | +export class DeviceConfigurationComponent implements ControlValueAccessor, OnInit { | |
36 | + | |
37 | + deviceProfileType = DeviceProfileType; | |
38 | + | |
39 | + deviceConfigurationFormGroup: FormGroup; | |
40 | + | |
41 | + private requiredValue: boolean; | |
42 | + get required(): boolean { | |
43 | + return this.requiredValue; | |
44 | + } | |
45 | + @Input() | |
46 | + set required(value: boolean) { | |
47 | + this.requiredValue = coerceBooleanProperty(value); | |
48 | + } | |
49 | + | |
50 | + @Input() | |
51 | + disabled: boolean; | |
52 | + | |
53 | + type: DeviceProfileType; | |
54 | + | |
55 | + private propagateChange = (v: any) => { }; | |
56 | + | |
57 | + constructor(private store: Store<AppState>, | |
58 | + private fb: FormBuilder) { | |
59 | + } | |
60 | + | |
61 | + registerOnChange(fn: any): void { | |
62 | + this.propagateChange = fn; | |
63 | + } | |
64 | + | |
65 | + registerOnTouched(fn: any): void { | |
66 | + } | |
67 | + | |
68 | + ngOnInit() { | |
69 | + this.deviceConfigurationFormGroup = this.fb.group({ | |
70 | + configuration: [null, Validators.required] | |
71 | + }); | |
72 | + this.deviceConfigurationFormGroup.valueChanges.subscribe(() => { | |
73 | + this.updateModel(); | |
74 | + }); | |
75 | + } | |
76 | + | |
77 | + setDisabledState(isDisabled: boolean): void { | |
78 | + this.disabled = isDisabled; | |
79 | + if (this.disabled) { | |
80 | + this.deviceConfigurationFormGroup.disable({emitEvent: false}); | |
81 | + } else { | |
82 | + this.deviceConfigurationFormGroup.enable({emitEvent: false}); | |
83 | + } | |
84 | + } | |
85 | + | |
86 | + writeValue(value: DeviceConfiguration | null): void { | |
87 | + this.type = value?.type; | |
88 | + const configuration = deepClone(value); | |
89 | + if (configuration) { | |
90 | + delete configuration.type; | |
91 | + } | |
92 | + this.deviceConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); | |
93 | + } | |
94 | + | |
95 | + private updateModel() { | |
96 | + let configuration: DeviceConfiguration = null; | |
97 | + if (this.deviceConfigurationFormGroup.valid) { | |
98 | + configuration = this.deviceConfigurationFormGroup.getRawValue().configuration; | |
99 | + configuration.type = this.type; | |
100 | + } | |
101 | + this.propagateChange(configuration); | |
102 | + } | |
103 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2020 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 | +<div [formGroup]="deviceDataFormGroup" style="padding-bottom: 16px;"> | |
19 | + <mat-accordion multi="true"> | |
20 | + <mat-expansion-panel [expanded]="true"> | |
21 | + <mat-expansion-panel-header> | |
22 | + <mat-panel-title> | |
23 | + <div translate>device.device-configuration</div> | |
24 | + </mat-panel-title> | |
25 | + </mat-expansion-panel-header> | |
26 | + <tb-device-configuration | |
27 | + formControlName="configuration" | |
28 | + required> | |
29 | + </tb-device-configuration> | |
30 | + </mat-expansion-panel> | |
31 | + <mat-expansion-panel [expanded]="true"> | |
32 | + <mat-expansion-panel-header> | |
33 | + <mat-panel-title> | |
34 | + <div translate>device.transport-configuration</div> | |
35 | + </mat-panel-title> | |
36 | + </mat-expansion-panel-header> | |
37 | + <tb-device-transport-configuration | |
38 | + formControlName="transportConfiguration" | |
39 | + required> | |
40 | + </tb-device-transport-configuration> | |
41 | + </mat-expansion-panel> | |
42 | + </mat-accordion> | |
43 | +</div> | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2020 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, forwardRef, Input, OnInit } from '@angular/core'; | |
18 | +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; | |
19 | +import { Store } from '@ngrx/store'; | |
20 | +import { AppState } from '@app/core/core.state'; | |
21 | +import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
22 | +import { DeviceData } from '@shared/models/device.models'; | |
23 | + | |
24 | +@Component({ | |
25 | + selector: 'tb-device-data', | |
26 | + templateUrl: './device-data.component.html', | |
27 | + styleUrls: [], | |
28 | + providers: [{ | |
29 | + provide: NG_VALUE_ACCESSOR, | |
30 | + useExisting: forwardRef(() => DeviceDataComponent), | |
31 | + multi: true | |
32 | + }] | |
33 | +}) | |
34 | +export class DeviceDataComponent implements ControlValueAccessor, OnInit { | |
35 | + | |
36 | + deviceDataFormGroup: FormGroup; | |
37 | + | |
38 | + private requiredValue: boolean; | |
39 | + get required(): boolean { | |
40 | + return this.requiredValue; | |
41 | + } | |
42 | + @Input() | |
43 | + set required(value: boolean) { | |
44 | + this.requiredValue = coerceBooleanProperty(value); | |
45 | + } | |
46 | + | |
47 | + @Input() | |
48 | + disabled: boolean; | |
49 | + | |
50 | + private propagateChange = (v: any) => { }; | |
51 | + | |
52 | + constructor(private store: Store<AppState>, | |
53 | + private fb: FormBuilder) { | |
54 | + } | |
55 | + | |
56 | + registerOnChange(fn: any): void { | |
57 | + this.propagateChange = fn; | |
58 | + } | |
59 | + | |
60 | + registerOnTouched(fn: any): void { | |
61 | + } | |
62 | + | |
63 | + ngOnInit() { | |
64 | + this.deviceDataFormGroup = this.fb.group({ | |
65 | + configuration: [null, Validators.required], | |
66 | + transportConfiguration: [null, Validators.required] | |
67 | + }); | |
68 | + this.deviceDataFormGroup.valueChanges.subscribe(() => { | |
69 | + this.updateModel(); | |
70 | + }); | |
71 | + } | |
72 | + | |
73 | + setDisabledState(isDisabled: boolean): void { | |
74 | + this.disabled = isDisabled; | |
75 | + if (this.disabled) { | |
76 | + this.deviceDataFormGroup.disable({emitEvent: false}); | |
77 | + } else { | |
78 | + this.deviceDataFormGroup.enable({emitEvent: false}); | |
79 | + } | |
80 | + } | |
81 | + | |
82 | + writeValue(value: DeviceData | null): void { | |
83 | + this.deviceDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false}); | |
84 | + this.deviceDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false}); | |
85 | + } | |
86 | + | |
87 | + private updateModel() { | |
88 | + let deviceData: DeviceData = null; | |
89 | + if (this.deviceDataFormGroup.valid) { | |
90 | + deviceData = this.deviceDataFormGroup.getRawValue(); | |
91 | + } | |
92 | + this.propagateChange(deviceData); | |
93 | + } | |
94 | +} | ... | ... |
ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2020 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 | +<div [formGroup]="deviceTransportConfigurationFormGroup"> | |
19 | + <div [ngSwitch]="transportType"> | |
20 | + <ng-template [ngSwitchCase]="deviceTransportType.DEFAULT"> | |
21 | + <tb-default-device-transport-configuration | |
22 | + [required]="required" | |
23 | + formControlName="configuration"> | |
24 | + </tb-default-device-transport-configuration> | |
25 | + </ng-template> | |
26 | + </div> | |
27 | +</div> | ... | ... |
ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2020 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, forwardRef, Input, OnInit } from '@angular/core'; | |
18 | +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; | |
19 | +import { Store } from '@ngrx/store'; | |
20 | +import { AppState } from '@app/core/core.state'; | |
21 | +import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
22 | +import { | |
23 | + DeviceTransportConfiguration, | |
24 | + DeviceTransportType | |
25 | +} from '@shared/models/device.models'; | |
26 | +import { deepClone } from '@core/utils'; | |
27 | + | |
28 | +@Component({ | |
29 | + selector: 'tb-device-transport-configuration', | |
30 | + templateUrl: './device-transport-configuration.component.html', | |
31 | + styleUrls: [], | |
32 | + providers: [{ | |
33 | + provide: NG_VALUE_ACCESSOR, | |
34 | + useExisting: forwardRef(() => DeviceTransportConfigurationComponent), | |
35 | + multi: true | |
36 | + }] | |
37 | +}) | |
38 | +export class DeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { | |
39 | + | |
40 | + deviceTransportType = DeviceTransportType; | |
41 | + | |
42 | + deviceTransportConfigurationFormGroup: FormGroup; | |
43 | + | |
44 | + private requiredValue: boolean; | |
45 | + get required(): boolean { | |
46 | + return this.requiredValue; | |
47 | + } | |
48 | + @Input() | |
49 | + set required(value: boolean) { | |
50 | + this.requiredValue = coerceBooleanProperty(value); | |
51 | + } | |
52 | + | |
53 | + @Input() | |
54 | + disabled: boolean; | |
55 | + | |
56 | + transportType: DeviceTransportType; | |
57 | + | |
58 | + private propagateChange = (v: any) => { }; | |
59 | + | |
60 | + constructor(private store: Store<AppState>, | |
61 | + private fb: FormBuilder) { | |
62 | + } | |
63 | + | |
64 | + registerOnChange(fn: any): void { | |
65 | + this.propagateChange = fn; | |
66 | + } | |
67 | + | |
68 | + registerOnTouched(fn: any): void { | |
69 | + } | |
70 | + | |
71 | + ngOnInit() { | |
72 | + this.deviceTransportConfigurationFormGroup = this.fb.group({ | |
73 | + configuration: [null, Validators.required] | |
74 | + }); | |
75 | + this.deviceTransportConfigurationFormGroup.valueChanges.subscribe(() => { | |
76 | + this.updateModel(); | |
77 | + }); | |
78 | + } | |
79 | + | |
80 | + setDisabledState(isDisabled: boolean): void { | |
81 | + this.disabled = isDisabled; | |
82 | + if (this.disabled) { | |
83 | + this.deviceTransportConfigurationFormGroup.disable({emitEvent: false}); | |
84 | + } else { | |
85 | + this.deviceTransportConfigurationFormGroup.enable({emitEvent: false}); | |
86 | + } | |
87 | + } | |
88 | + | |
89 | + writeValue(value: DeviceTransportConfiguration | null): void { | |
90 | + this.transportType = value?.type; | |
91 | + const configuration = deepClone(value); | |
92 | + if (configuration) { | |
93 | + delete configuration.type; | |
94 | + } | |
95 | + this.deviceTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); | |
96 | + } | |
97 | + | |
98 | + private updateModel() { | |
99 | + let configuration: DeviceTransportConfiguration = null; | |
100 | + if (this.deviceTransportConfigurationFormGroup.valid) { | |
101 | + configuration = this.deviceTransportConfigurationFormGroup.getRawValue().configuration; | |
102 | + configuration.type = this.transportType; | |
103 | + } | |
104 | + this.propagateChange(configuration); | |
105 | + } | |
106 | +} | ... | ... |
... | ... | @@ -83,6 +83,13 @@ |
83 | 83 | {{ 'device.name-required' | translate }} |
84 | 84 | </mat-error> |
85 | 85 | </mat-form-field> |
86 | + <tb-device-profile-autocomplete | |
87 | + [selectDefaultProfile]="isAdd" | |
88 | + required | |
89 | + formControlName="deviceProfileId" | |
90 | + (deviceProfileUpdated)="onDeviceProfileUpdated()" | |
91 | + (deviceProfileChanged)="onDeviceProfileChanged($event)"> | |
92 | + </tb-device-profile-autocomplete> | |
86 | 93 | <tb-entity-subtype-autocomplete |
87 | 94 | formControlName="type" |
88 | 95 | [required]="true" |
... | ... | @@ -93,6 +100,10 @@ |
93 | 100 | <mat-label translate>device.label</mat-label> |
94 | 101 | <input matInput formControlName="label"> |
95 | 102 | </mat-form-field> |
103 | + <tb-device-data | |
104 | + formControlName="deviceData" | |
105 | + required> | |
106 | + </tb-device-data> | |
96 | 107 | <div formGroupName="additionalInfo" fxLayout="column"> |
97 | 108 | <mat-checkbox fxFlex formControlName="gateway" style="padding-bottom: 16px;"> |
98 | 109 | {{ 'device.is-gateway' | translate }} | ... | ... |
... | ... | @@ -19,7 +19,16 @@ import { Store } from '@ngrx/store'; |
19 | 19 | import { AppState } from '@core/core.state'; |
20 | 20 | import { EntityComponent } from '../../components/entity/entity.component'; |
21 | 21 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
22 | -import { DeviceInfo } from '@shared/models/device.models'; | |
22 | +import { | |
23 | + createDeviceConfiguration, | |
24 | + createDeviceProfileConfiguration, createDeviceTransportConfiguration, | |
25 | + DeviceData, | |
26 | + DeviceInfo, | |
27 | + DeviceProfileData, | |
28 | + DeviceProfileInfo, | |
29 | + DeviceProfileType, | |
30 | + DeviceTransportType | |
31 | +} from '@shared/models/device.models'; | |
23 | 32 | import { EntityType } from '@shared/models/entity-type.models'; |
24 | 33 | import { NULL_UUID } from '@shared/models/id/has-uuid'; |
25 | 34 | import { ActionNotificationShow } from '@core/notification/notification.actions'; |
... | ... | @@ -126,4 +135,36 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { |
126 | 135 | ); |
127 | 136 | } |
128 | 137 | } |
138 | + | |
139 | + onDeviceProfileUpdated() { | |
140 | + this.entitiesTableConfig.table.updateData(false); | |
141 | + } | |
142 | + | |
143 | + onDeviceProfileChanged(deviceProfile: DeviceProfileInfo) { | |
144 | + if (deviceProfile) { | |
145 | + const deviceProfileType: DeviceProfileType = deviceProfile.type; | |
146 | + const deviceTransportType: DeviceTransportType = deviceProfile.transportType; | |
147 | + let deviceData: DeviceData = this.entityForm.getRawValue().deviceData; | |
148 | + if (!deviceData) { | |
149 | + deviceData = { | |
150 | + configuration: createDeviceConfiguration(deviceProfileType), | |
151 | + transportConfiguration: createDeviceTransportConfiguration(deviceTransportType) | |
152 | + }; | |
153 | + this.entityForm.patchValue({deviceData}); | |
154 | + } else { | |
155 | + let changed = false; | |
156 | + if (deviceData.configuration.type !== deviceProfileType) { | |
157 | + deviceData.configuration = createDeviceConfiguration(deviceProfileType); | |
158 | + changed = true; | |
159 | + } | |
160 | + if (deviceData.transportConfiguration.type !== deviceTransportType) { | |
161 | + deviceData.transportConfiguration = createDeviceTransportConfiguration(deviceTransportType); | |
162 | + changed = true; | |
163 | + } | |
164 | + if (changed) { | |
165 | + this.entityForm.patchValue({deviceData}); | |
166 | + } | |
167 | + } | |
168 | + } | |
169 | + } | |
129 | 170 | } | ... | ... |
... | ... | @@ -24,9 +24,19 @@ import { DeviceCredentialsDialogComponent } from '@modules/home/pages/device/dev |
24 | 24 | import { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; |
25 | 25 | import { HomeComponentsModule } from '@modules/home/components/home-components.module'; |
26 | 26 | import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; |
27 | +import { DefaultDeviceConfigurationComponent } from './data/default-device-configuration.component'; | |
28 | +import { DeviceConfigurationComponent } from './data/device-configuration.component'; | |
29 | +import { DeviceDataComponent } from './data/device-data.component'; | |
30 | +import { DefaultDeviceTransportConfigurationComponent } from './data/default-device-transport-configuration.component'; | |
31 | +import { DeviceTransportConfigurationComponent } from './data/device-transport-configuration.component'; | |
27 | 32 | |
28 | 33 | @NgModule({ |
29 | 34 | declarations: [ |
35 | + DefaultDeviceConfigurationComponent, | |
36 | + DeviceConfigurationComponent, | |
37 | + DefaultDeviceTransportConfigurationComponent, | |
38 | + DeviceTransportConfigurationComponent, | |
39 | + DeviceDataComponent, | |
30 | 40 | DeviceComponent, |
31 | 41 | DeviceTabsComponent, |
32 | 42 | DeviceTableHeaderComponent, | ... | ... |
... | ... | @@ -86,6 +86,8 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev |
86 | 86 | this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE); |
87 | 87 | this.config.entityResources = entityTypeResources.get(EntityType.DEVICE); |
88 | 88 | |
89 | + this.config.addDialogStyle = {width: '600px'}; | |
90 | + | |
89 | 91 | this.config.deleteEntityTitle = device => this.translate.instant('device.delete-device-title', { deviceName: device.name }); |
90 | 92 | this.config.deleteEntityContent = () => this.translate.instant('device.delete-device-text'); |
91 | 93 | this.config.deleteEntitiesTitle = count => this.translate.instant('device.delete-devices-title', {count}); | ... | ... |
... | ... | @@ -91,6 +91,19 @@ export function createDeviceProfileConfiguration(type: DeviceProfileType): Devic |
91 | 91 | return configuration; |
92 | 92 | } |
93 | 93 | |
94 | +export function createDeviceConfiguration(type: DeviceProfileType): DeviceConfiguration { | |
95 | + let configuration: DeviceConfiguration = null; | |
96 | + if (type) { | |
97 | + switch (type) { | |
98 | + case DeviceProfileType.DEFAULT: | |
99 | + const defaultConfiguration: DefaultDeviceConfiguration = {}; | |
100 | + configuration = {...defaultConfiguration, type: DeviceProfileType.DEFAULT}; | |
101 | + break; | |
102 | + } | |
103 | + } | |
104 | + return configuration; | |
105 | +} | |
106 | + | |
94 | 107 | export function createDeviceProfileTransportConfiguration(type: DeviceTransportType): DeviceProfileTransportConfiguration { |
95 | 108 | let transportConfiguration: DeviceProfileTransportConfiguration = null; |
96 | 109 | if (type) { |
... | ... | @@ -112,6 +125,27 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT |
112 | 125 | return transportConfiguration; |
113 | 126 | } |
114 | 127 | |
128 | +export function createDeviceTransportConfiguration(type: DeviceTransportType): DeviceTransportConfiguration { | |
129 | + let transportConfiguration: DeviceTransportConfiguration = null; | |
130 | + if (type) { | |
131 | + switch (type) { | |
132 | + case DeviceTransportType.DEFAULT: | |
133 | + const defaultTransportConfiguration: DefaultDeviceTransportConfiguration = {}; | |
134 | + transportConfiguration = {...defaultTransportConfiguration, type: DeviceTransportType.DEFAULT}; | |
135 | + break; | |
136 | + case DeviceTransportType.MQTT: | |
137 | + const mqttTransportConfiguration: MqttDeviceTransportConfiguration = {}; | |
138 | + transportConfiguration = {...mqttTransportConfiguration, type: DeviceTransportType.MQTT}; | |
139 | + break; | |
140 | + case DeviceTransportType.LWM2M: | |
141 | + const lwm2mTransportConfiguration: Lwm2mDeviceTransportConfiguration = {}; | |
142 | + transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M}; | |
143 | + break; | |
144 | + } | |
145 | + } | |
146 | + return transportConfiguration; | |
147 | +} | |
148 | + | |
115 | 149 | export interface DeviceProfileData { |
116 | 150 | configuration: DeviceProfileConfiguration; |
117 | 151 | transportConfiguration: DeviceProfileTransportConfiguration; |
... | ... | @@ -121,7 +155,7 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> { |
121 | 155 | tenantId?: TenantId; |
122 | 156 | name: string; |
123 | 157 | description?: string; |
124 | - default: boolean; | |
158 | + default?: boolean; | |
125 | 159 | type: DeviceProfileType; |
126 | 160 | transportType: DeviceTransportType; |
127 | 161 | defaultRuleChainId?: RuleChainId; | ... | ... |
... | ... | @@ -748,7 +748,9 @@ |
748 | 748 | "import": "Import device", |
749 | 749 | "device-file": "Device file", |
750 | 750 | "search": "Search devices", |
751 | - "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } selected" | |
751 | + "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } selected", | |
752 | + "device-configuration": "Device configuration", | |
753 | + "transport-configuration": "Transport configuration" | |
752 | 754 | }, |
753 | 755 | "device-profile": { |
754 | 756 | "device-profile": "Device profile", | ... | ... |