Showing
33 changed files
with
1551 additions
and
42 deletions
... | ... | @@ -68,7 +68,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt |
68 | 68 | public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; |
69 | 69 | public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public"; |
70 | 70 | public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; |
71 | - protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/static/**", "/api/noauth/**", "/webjars/**"}; | |
71 | + protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**"}; | |
72 | 72 | public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; |
73 | 73 | public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**"; |
74 | 74 | |
... | ... | @@ -155,7 +155,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt |
155 | 155 | |
156 | 156 | @Override |
157 | 157 | public void configure(WebSecurity web) throws Exception { |
158 | - web.ignoring().antMatchers("/static/**"); | |
158 | + web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**"); | |
159 | 159 | } |
160 | 160 | |
161 | 161 | @Override | ... | ... |
... | ... | @@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping; |
21 | 21 | @Controller |
22 | 22 | public class WebConfig { |
23 | 23 | |
24 | - @RequestMapping(value = "/{path:^(?!api$)(?!static$)(?!webjars$)[^\\.]*}/**") | |
24 | + @RequestMapping(value = "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)[^\\.]*}/**") | |
25 | 25 | public String redirect() { |
26 | 26 | return "forward:/index.html"; |
27 | 27 | } | ... | ... |
... | ... | @@ -31,9 +31,9 @@ export class CustomerService { |
31 | 31 | private http: HttpClient |
32 | 32 | ) { } |
33 | 33 | |
34 | - public getCustomers(tenantId: string, pageLink: PageLink, ignoreErrors: boolean = false, | |
34 | + public getCustomers(pageLink: PageLink, ignoreErrors: boolean = false, | |
35 | 35 | ignoreLoading: boolean = false): Observable<PageData<Customer>> { |
36 | - return this.http.get<PageData<Customer>>(`/api/tenant/${tenantId}/customers${pageLink.toQuery()}`, | |
36 | + return this.http.get<PageData<Customer>>(`/api/customers${pageLink.toQuery()}`, | |
37 | 37 | defaultHttpOptions(ignoreLoading, ignoreErrors)); |
38 | 38 | } |
39 | 39 | ... | ... |
... | ... | @@ -16,15 +16,16 @@ |
16 | 16 | |
17 | 17 | import { Injectable } from '@angular/core'; |
18 | 18 | import { defaultHttpOptions } from './http-utils'; |
19 | -import { Observable } from 'rxjs/index'; | |
19 | +import { Observable, Subject, ReplaySubject } from 'rxjs/index'; | |
20 | 20 | import { HttpClient } from '@angular/common/http'; |
21 | 21 | import { PageLink } from '@shared/models/page/page-link'; |
22 | 22 | import { PageData } from '@shared/models/page/page-data'; |
23 | 23 | import { Tenant } from '@shared/models/tenant.model'; |
24 | 24 | import {DashboardInfo, Dashboard} from '@shared/models/dashboard.models'; |
25 | 25 | import {map} from 'rxjs/operators'; |
26 | -import {DeviceInfo, Device} from '@app/shared/models/device.models'; | |
26 | +import {DeviceInfo, Device, DeviceCredentials} from '@app/shared/models/device.models'; | |
27 | 27 | import {EntitySubtype} from '@app/shared/models/entity-type.models'; |
28 | +import {AuthService} from '../auth/auth.service'; | |
28 | 29 | |
29 | 30 | @Injectable({ |
30 | 31 | providedIn: 'root' |
... | ... | @@ -67,6 +68,40 @@ export class DeviceService { |
67 | 68 | return this.http.get<Array<EntitySubtype>>('/api/device/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); |
68 | 69 | } |
69 | 70 | |
71 | + public getDeviceCredentials(deviceId: string, sync: boolean = false, ignoreErrors: boolean = false, | |
72 | + ignoreLoading: boolean = false): Observable<DeviceCredentials> { | |
73 | + const url = `/api/device/${deviceId}/credentials`; | |
74 | + if (sync) { | |
75 | + const responseSubject = new ReplaySubject<DeviceCredentials>(); | |
76 | + const request = new XMLHttpRequest(); | |
77 | + request.open('GET', url, false); | |
78 | + request.setRequestHeader('Accept', 'application/json, text/plain, */*'); | |
79 | + const jwtToken = AuthService.getJwtToken(); | |
80 | + if (jwtToken) { | |
81 | + request.setRequestHeader('X-Authorization', 'Bearer ' + jwtToken); | |
82 | + } | |
83 | + request.send(null); | |
84 | + if (request.status === 200) { | |
85 | + const credentials = JSON.parse(request.responseText) as DeviceCredentials; | |
86 | + responseSubject.next(credentials); | |
87 | + } else { | |
88 | + responseSubject.error(null); | |
89 | + } | |
90 | + return responseSubject.asObservable(); | |
91 | + } else { | |
92 | + return this.http.get<DeviceCredentials>(url, defaultHttpOptions(ignoreLoading, ignoreErrors)); | |
93 | + } | |
94 | + } | |
95 | + | |
96 | + public saveDeviceCredentials(deviceCredentials: DeviceCredentials, ignoreErrors: boolean = false, | |
97 | + ignoreLoading: boolean = false): Observable<DeviceCredentials> { | |
98 | + return this.http.post<DeviceCredentials>('/api/device/credentials', deviceCredentials, defaultHttpOptions(ignoreLoading, ignoreErrors)); | |
99 | + } | |
100 | + | |
101 | + public makeDevicePublic(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> { | |
102 | + return this.http.post<Device>(`/api/customer/public/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); | |
103 | + } | |
104 | + | |
70 | 105 | public unassignDeviceFromCustomer(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { |
71 | 106 | return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
72 | 107 | } | ... | ... |
ui-ngx/src/app/core/http/entity.service.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; | |
18 | +import {Observable, throwError, of, empty, EMPTY} from 'rxjs/index'; | |
19 | +import {HttpClient} from '@angular/common/http'; | |
20 | +import {PageLink} from '@shared/models/page/page-link'; | |
21 | +import {EntityType} from '@shared/models/entity-type.models'; | |
22 | +import {BaseData} from '@shared/models/base-data'; | |
23 | +import {EntityId} from '@shared/models/id/entity-id'; | |
24 | +import {DeviceService} from '@core/http/device.service'; | |
25 | +import {TenantService} from '@core/http/tenant.service'; | |
26 | +import {CustomerService} from '@core/http/customer.service'; | |
27 | +import {UserService} from './user.service'; | |
28 | +import {DashboardService} from '@core/http/dashboard.service'; | |
29 | +import {Direction} from '@shared/models/page/sort-order'; | |
30 | +import {PageData} from '@shared/models/page/page-data'; | |
31 | +import {getCurrentAuthUser} from '../auth/auth.selectors'; | |
32 | +import {Store} from '@ngrx/store'; | |
33 | +import {AppState} from '@core/core.state'; | |
34 | +import {Authority} from '@shared/models/authority.enum'; | |
35 | +import {Tenant} from '@shared/models/tenant.model'; | |
36 | +import {concatMap, expand, map, toArray} from 'rxjs/operators'; | |
37 | +import {Customer} from '@app/shared/models/customer.model'; | |
38 | + | |
39 | +@Injectable({ | |
40 | + providedIn: 'root' | |
41 | +}) | |
42 | +export class EntityService { | |
43 | + | |
44 | + constructor( | |
45 | + private http: HttpClient, | |
46 | + private store: Store<AppState>, | |
47 | + private deviceService: DeviceService, | |
48 | + private tenantService: TenantService, | |
49 | + private customerService: CustomerService, | |
50 | + private userService: UserService, | |
51 | + private dashboardService: DashboardService | |
52 | + ) { } | |
53 | + | |
54 | + private getEntityObservable(entityType: EntityType, entityId: string, | |
55 | + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<BaseData<EntityId>> { | |
56 | + | |
57 | + let observable: Observable<BaseData<EntityId>>; | |
58 | + switch (entityType) { | |
59 | + case EntityType.DEVICE: | |
60 | + observable = this.deviceService.getDevice(entityId, ignoreErrors, ignoreLoading); | |
61 | + break; | |
62 | + case EntityType.ASSET: | |
63 | + // TODO: | |
64 | + break; | |
65 | + case EntityType.ENTITY_VIEW: | |
66 | + // TODO: | |
67 | + break; | |
68 | + case EntityType.TENANT: | |
69 | + observable = this.tenantService.getTenant(entityId, ignoreErrors, ignoreLoading); | |
70 | + break; | |
71 | + case EntityType.CUSTOMER: | |
72 | + observable = this.customerService.getCustomer(entityId, ignoreErrors, ignoreLoading); | |
73 | + break; | |
74 | + case EntityType.DASHBOARD: | |
75 | + observable = this.dashboardService.getDashboardInfo(entityId, ignoreErrors, ignoreLoading); | |
76 | + break; | |
77 | + case EntityType.USER: | |
78 | + observable = this.userService.getUser(entityId, ignoreErrors, ignoreLoading); | |
79 | + break; | |
80 | + case EntityType.RULE_CHAIN: | |
81 | + // TODO: | |
82 | + break; | |
83 | + case EntityType.ALARM: | |
84 | + console.error('Get Alarm Entity is not implemented!'); | |
85 | + break; | |
86 | + } | |
87 | + return observable; | |
88 | + } | |
89 | + | |
90 | + public getEntity(entityType: EntityType, entityId: string, | |
91 | + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<BaseData<EntityId>> { | |
92 | + const entityObservable = this.getEntityObservable(entityType, entityId, ignoreErrors, ignoreLoading); | |
93 | + if (entityObservable) { | |
94 | + return entityObservable; | |
95 | + } else { | |
96 | + return throwError(null); | |
97 | + } | |
98 | + } | |
99 | + | |
100 | + private getSingleTenantByPageLinkObservable(pageLink: PageLink, | |
101 | + ignoreErrors: boolean = false, | |
102 | + ignoreLoading: boolean = false): Observable<PageData<Tenant>> { | |
103 | + const authUser = getCurrentAuthUser(this.store); | |
104 | + const tenantId = authUser.tenantId; | |
105 | + return this.tenantService.getTenant(tenantId, ignoreErrors, ignoreLoading).pipe( | |
106 | + map((tenant) => { | |
107 | + const result = { | |
108 | + data: [], | |
109 | + totalPages: 0, | |
110 | + totalElements: 0, | |
111 | + hasNext: false | |
112 | + } as PageData<Tenant>; | |
113 | + if (tenant.title.toLowerCase().startsWith(pageLink.textSearch.toLowerCase())) { | |
114 | + result.data.push(tenant); | |
115 | + result.totalPages = 1; | |
116 | + result.totalElements = 1; | |
117 | + } | |
118 | + return result; | |
119 | + }) | |
120 | + ); | |
121 | + } | |
122 | + | |
123 | + private getSingleCustomerByPageLinkObservable(pageLink: PageLink, | |
124 | + ignoreErrors: boolean = false, | |
125 | + ignoreLoading: boolean = false): Observable<PageData<Customer>> { | |
126 | + const authUser = getCurrentAuthUser(this.store); | |
127 | + const customerId = authUser.customerId; | |
128 | + return this.customerService.getCustomer(customerId, ignoreErrors, ignoreLoading).pipe( | |
129 | + map((customer) => { | |
130 | + const result = { | |
131 | + data: [], | |
132 | + totalPages: 0, | |
133 | + totalElements: 0, | |
134 | + hasNext: false | |
135 | + } as PageData<Customer>; | |
136 | + if (customer.title.toLowerCase().startsWith(pageLink.textSearch.toLowerCase())) { | |
137 | + result.data.push(customer); | |
138 | + result.totalPages = 1; | |
139 | + result.totalElements = 1; | |
140 | + } | |
141 | + return result; | |
142 | + }) | |
143 | + ); | |
144 | + } | |
145 | + | |
146 | + private getEntitiesByPageLinkObservable(entityType: EntityType, pageLink: PageLink, subType: string = '', | |
147 | + ignoreErrors: boolean = false, | |
148 | + ignoreLoading: boolean = false): Observable<PageData<BaseData<EntityId>>> { | |
149 | + let entitiesObservable: Observable<PageData<BaseData<EntityId>>>; | |
150 | + const authUser = getCurrentAuthUser(this.store); | |
151 | + const customerId = authUser.customerId; | |
152 | + switch (entityType) { | |
153 | + case EntityType.DEVICE: | |
154 | + pageLink.sortOrder.property = 'name'; | |
155 | + if (authUser.authority === Authority.CUSTOMER_USER) { | |
156 | + entitiesObservable = this.deviceService.getCustomerDeviceInfos(customerId, pageLink, subType, ignoreErrors, ignoreLoading); | |
157 | + } else { | |
158 | + entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, ignoreErrors, ignoreLoading); | |
159 | + } | |
160 | + break; | |
161 | + case EntityType.ASSET: | |
162 | + pageLink.sortOrder.property = 'name'; | |
163 | + if (authUser.authority === Authority.CUSTOMER_USER) { | |
164 | + // TODO: | |
165 | + } else { | |
166 | + // TODO: | |
167 | + } | |
168 | + break; | |
169 | + case EntityType.ENTITY_VIEW: | |
170 | + pageLink.sortOrder.property = 'name'; | |
171 | + if (authUser.authority === Authority.CUSTOMER_USER) { | |
172 | + // TODO: | |
173 | + } else { | |
174 | + // TODO: | |
175 | + } | |
176 | + break; | |
177 | + case EntityType.TENANT: | |
178 | + pageLink.sortOrder.property = 'title'; | |
179 | + if (authUser.authority === Authority.TENANT_ADMIN) { | |
180 | + entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading); | |
181 | + } else { | |
182 | + entitiesObservable = this.tenantService.getTenants(pageLink, ignoreErrors, ignoreLoading); | |
183 | + } | |
184 | + break; | |
185 | + case EntityType.CUSTOMER: | |
186 | + pageLink.sortOrder.property = 'title'; | |
187 | + if (authUser.authority === Authority.CUSTOMER_USER) { | |
188 | + entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading); | |
189 | + } else { | |
190 | + entitiesObservable = this.customerService.getCustomers(pageLink, ignoreErrors, ignoreLoading); | |
191 | + } | |
192 | + break; | |
193 | + case EntityType.RULE_CHAIN: | |
194 | + pageLink.sortOrder.property = 'name'; | |
195 | + // TODO: | |
196 | + break; | |
197 | + case EntityType.DASHBOARD: | |
198 | + pageLink.sortOrder.property = 'title'; | |
199 | + if (authUser.authority === Authority.CUSTOMER_USER) { | |
200 | + entitiesObservable = this.dashboardService.getCustomerDashboards(customerId, pageLink, ignoreErrors, ignoreLoading); | |
201 | + } else { | |
202 | + entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, ignoreErrors, ignoreLoading); | |
203 | + } | |
204 | + break; | |
205 | + case EntityType.USER: | |
206 | + console.error('Get User Entities is not implemented!'); | |
207 | + break; | |
208 | + case EntityType.ALARM: | |
209 | + console.error('Get Alarm Entities is not implemented!'); | |
210 | + break; | |
211 | + } | |
212 | + return entitiesObservable; | |
213 | + } | |
214 | + | |
215 | + private getEntitiesByPageLink(entityType: EntityType, pageLink: PageLink, subType: string = '', | |
216 | + ignoreErrors: boolean = false, | |
217 | + ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> { | |
218 | + const entitiesObservable: Observable<PageData<BaseData<EntityId>>> = | |
219 | + this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); | |
220 | + if (entitiesObservable) { | |
221 | + return entitiesObservable.pipe( | |
222 | + expand((data) => { | |
223 | + if (data.hasNext) { | |
224 | + pageLink.page += 1; | |
225 | + return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); | |
226 | + } else { | |
227 | + return EMPTY; | |
228 | + } | |
229 | + }), | |
230 | + map((data) => data.data), | |
231 | + concatMap((data) => data), | |
232 | + toArray() | |
233 | + ); | |
234 | + } else { | |
235 | + return of(null); | |
236 | + } | |
237 | + } | |
238 | + | |
239 | + public getEntitiesByNameFilter(entityType: EntityType, entityNameFilter: string, | |
240 | + pageSize: number, subType: string = '', | |
241 | + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> { | |
242 | + const pageLink = new PageLink(pageSize, 0, entityNameFilter, { | |
243 | + property: 'name', | |
244 | + direction: Direction.ASC | |
245 | + }); | |
246 | + if (pageSize === -1) { // all | |
247 | + pageLink.pageSize = 100; | |
248 | + return this.getEntitiesByPageLink(entityType, pageLink, subType, ignoreErrors, ignoreLoading).pipe( | |
249 | + map((data) => data && data.length ? data : null) | |
250 | + ); | |
251 | + } else { | |
252 | + const entitiesObservable: Observable<PageData<BaseData<EntityId>>> = | |
253 | + this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); | |
254 | + if (entitiesObservable) { | |
255 | + return entitiesObservable.pipe( | |
256 | + map((data) => data && data.data.length ? data.data : null) | |
257 | + ); | |
258 | + } else { | |
259 | + return of(null); | |
260 | + } | |
261 | + } | |
262 | + } | |
263 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 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 #deviceCredentialsForm="ngForm" [formGroup]="deviceCredentialsFormGroup" (ngSubmit)="save()"> | |
19 | + <mat-toolbar fxLayout="row" color="primary"> | |
20 | + <h2 translate>device.device-credentials</h2> | |
21 | + <span fxFlex></span> | |
22 | + <button mat-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 | + <fieldset [disabled]="(isLoading$ | async) || isReadOnly"> | |
33 | + <mat-form-field class="mat-block"> | |
34 | + <mat-label translate>device.credentials-type</mat-label> | |
35 | + <mat-select matInput formControlName="credentialsType" | |
36 | + (ngModelChange)="credentialsTypeChanged()"> | |
37 | + <mat-option *ngFor="let credentialsType of credentialsTypes" [value]="credentialsType"> | |
38 | + {{ credentialTypeNamesMap.get(credentialsType) }} | |
39 | + </mat-option> | |
40 | + </mat-select> | |
41 | + </mat-form-field> | |
42 | + <mat-form-field *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.ACCESS_TOKEN" | |
43 | + class="mat-block"> | |
44 | + <mat-label translate>device.access-token</mat-label> | |
45 | + <input matInput formControlName="credentialsId" required> | |
46 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('required')"> | |
47 | + {{ 'device.access-token-required' | translate }} | |
48 | + </mat-error> | |
49 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('pattern')"> | |
50 | + {{ 'device.access-token-invalid' | translate }} | |
51 | + </mat-error> | |
52 | + </mat-form-field> | |
53 | + <mat-form-field *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.X509_CERTIFICATE" | |
54 | + class="mat-block"> | |
55 | + <mat-label translate>device.rsa-key</mat-label> | |
56 | + <textarea matInput formControlName="credentialsValue" cols="15" rows="5" required></textarea> | |
57 | + <mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsValue').hasError('required')"> | |
58 | + {{ 'device.rsa-key-required' | translate }} | |
59 | + </mat-error> | |
60 | + </mat-form-field> | |
61 | + </fieldset> | |
62 | + </div> | |
63 | + <div mat-dialog-actions fxLayout="row"> | |
64 | + <span fxFlex></span> | |
65 | + <button *ngIf="!isReadOnly" mat-button mat-raised-button color="primary" | |
66 | + type="submit" | |
67 | + [disabled]="(isLoading$ | async) || deviceCredentialsForm.invalid | |
68 | + || !deviceCredentialsForm.dirty"> | |
69 | + {{ 'action.save' | translate }} | |
70 | + </button> | |
71 | + <button mat-button color="primary" | |
72 | + style="margin-right: 20px;" | |
73 | + type="button" | |
74 | + [disabled]="(isLoading$ | async)" | |
75 | + (click)="cancel()" cdkFocusInitial> | |
76 | + {{ (isReadOnly ? 'action.close' : 'action.cancel') | translate }} | |
77 | + </button> | |
78 | + </div> | |
79 | +</form> | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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, OnInit, SkipSelf, Inject} from '@angular/core'; | |
18 | +import {ErrorStateMatcher, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material'; | |
19 | +import { PageComponent } from '@shared/components/page.component'; | |
20 | +import { Store } from '@ngrx/store'; | |
21 | +import { AppState } from '@core/core.state'; | |
22 | +import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators} from '@angular/forms'; | |
23 | +import { ActionNotificationShow } from '@core/notification/notification.actions'; | |
24 | +import { TranslateService } from '@ngx-translate/core'; | |
25 | +import { AuthService } from '@core/auth/auth.service'; | |
26 | +import {DeviceService} from '@core/http/device.service'; | |
27 | +import {DeviceCredentials, DeviceCredentialsType, credentialTypeNames} from '@shared/models/device.models'; | |
28 | + | |
29 | +export interface DeviceCredentialsDialogData { | |
30 | + isReadOnly: boolean; | |
31 | + deviceId: string; | |
32 | +} | |
33 | + | |
34 | +@Component({ | |
35 | + selector: 'tb-device-credentials-dialog', | |
36 | + templateUrl: './device-credentials-dialog.component.html', | |
37 | + providers: [{provide: ErrorStateMatcher, useExisting: DeviceCredentialsDialogComponent}], | |
38 | + styleUrls: [] | |
39 | +}) | |
40 | +export class DeviceCredentialsDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { | |
41 | + | |
42 | + deviceCredentialsFormGroup: FormGroup; | |
43 | + | |
44 | + isReadOnly: boolean; | |
45 | + | |
46 | + deviceCredentials: DeviceCredentials; | |
47 | + | |
48 | + submitted = false; | |
49 | + | |
50 | + deviceCredentialsType = DeviceCredentialsType; | |
51 | + | |
52 | + credentialsTypes = Object.keys(DeviceCredentialsType); | |
53 | + | |
54 | + credentialTypeNamesMap = credentialTypeNames; | |
55 | + | |
56 | + constructor(protected store: Store<AppState>, | |
57 | + @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData, | |
58 | + private deviceService: DeviceService, | |
59 | + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, | |
60 | + public dialogRef: MatDialogRef<DeviceCredentialsDialogComponent, DeviceCredentials>, | |
61 | + public fb: FormBuilder) { | |
62 | + super(store); | |
63 | + | |
64 | + this.isReadOnly = data.isReadOnly; | |
65 | + } | |
66 | + | |
67 | + ngOnInit(): void { | |
68 | + this.deviceCredentialsFormGroup = this.fb.group({ | |
69 | + credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], | |
70 | + credentialsId: [''], | |
71 | + credentialsValue: [''] | |
72 | + }); | |
73 | + if (this.isReadOnly) { | |
74 | + this.deviceCredentialsFormGroup.disable({emitEvent: false}); | |
75 | + } else { | |
76 | + this.registerDisableOnLoadFormControl(this.deviceCredentialsFormGroup.get('credentialsType')); | |
77 | + } | |
78 | + this.loadDeviceCredentials(); | |
79 | + } | |
80 | + | |
81 | + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { | |
82 | + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); | |
83 | + const customErrorState = !!(control && control.invalid && this.submitted); | |
84 | + return originalErrorState || customErrorState; | |
85 | + } | |
86 | + | |
87 | + loadDeviceCredentials() { | |
88 | + this.deviceService.getDeviceCredentials(this.data.deviceId).subscribe( | |
89 | + (deviceCredentials) => { | |
90 | + this.deviceCredentials = deviceCredentials; | |
91 | + this.deviceCredentialsFormGroup.patchValue({ | |
92 | + credentialsType: deviceCredentials.credentialsType, | |
93 | + credentialsId: deviceCredentials.credentialsId, | |
94 | + credentialsValue: deviceCredentials.credentialsValue | |
95 | + }); | |
96 | + this.updateValidators(); | |
97 | + } | |
98 | + ); | |
99 | + } | |
100 | + | |
101 | + credentialsTypeChanged(): void { | |
102 | + this.deviceCredentialsFormGroup.patchValue( | |
103 | + {credentialsId: null, credentialsValue: null}, {emitEvent: true}); | |
104 | + this.updateValidators(); | |
105 | + } | |
106 | + | |
107 | + updateValidators(): void { | |
108 | + const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; | |
109 | + switch (crendetialsType) { | |
110 | + case DeviceCredentialsType.ACCESS_TOKEN: | |
111 | + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.max(20), Validators.pattern(/^.{1,20}$/)]); | |
112 | + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); | |
113 | + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); | |
114 | + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); | |
115 | + break; | |
116 | + case DeviceCredentialsType.X509_CERTIFICATE: | |
117 | + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); | |
118 | + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); | |
119 | + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); | |
120 | + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); | |
121 | + break; | |
122 | + } | |
123 | + } | |
124 | + | |
125 | + cancel(): void { | |
126 | + this.dialogRef.close(null); | |
127 | + } | |
128 | + | |
129 | + save(): void { | |
130 | + this.submitted = true; | |
131 | + this.deviceCredentials = {...this.deviceCredentials, ...this.deviceCredentialsFormGroup.value}; | |
132 | + this.deviceService.saveDeviceCredentials(this.deviceCredentials).subscribe( | |
133 | + (deviceCredentials) => { | |
134 | + this.dialogRef.close(deviceCredentials); | |
135 | + } | |
136 | + ); | |
137 | + } | |
138 | +} | ... | ... |
... | ... | @@ -26,6 +26,10 @@ import { Authority } from '@shared/models/authority.enum'; |
26 | 26 | import {DeviceInfo} from '@shared/models/device.models'; |
27 | 27 | import {EntityType} from '@shared/models/entity-type.models'; |
28 | 28 | import {NULL_UUID} from '@shared/models/id/has-uuid'; |
29 | +import {ActionNotificationShow} from '@core/notification/notification.actions'; | |
30 | +import {TranslateService} from '@ngx-translate/core'; | |
31 | +import {DeviceService} from '@core/http/device.service'; | |
32 | +import {ClipboardService} from 'ngx-clipboard'; | |
29 | 33 | |
30 | 34 | @Component({ |
31 | 35 | selector: 'tb-device', |
... | ... | @@ -39,6 +43,9 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { |
39 | 43 | deviceScope: 'tenant' | 'customer' | 'customer_user'; |
40 | 44 | |
41 | 45 | constructor(protected store: Store<AppState>, |
46 | + protected translate: TranslateService, | |
47 | + private deviceService: DeviceService, | |
48 | + private clipboardService: ClipboardService, | |
42 | 49 | public fb: FormBuilder) { |
43 | 50 | super(store); |
44 | 51 | } |
... | ... | @@ -85,4 +92,35 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> { |
85 | 92 | this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); |
86 | 93 | } |
87 | 94 | |
95 | + | |
96 | + onDeviceIdCopied($event) { | |
97 | + this.store.dispatch(new ActionNotificationShow( | |
98 | + { | |
99 | + message: this.translate.instant('device.idCopiedMessage'), | |
100 | + type: 'success', | |
101 | + duration: 750, | |
102 | + verticalPosition: 'bottom', | |
103 | + horizontalPosition: 'right' | |
104 | + })); | |
105 | + } | |
106 | + | |
107 | + copyAccessToken($event) { | |
108 | + if (this.entity.id) { | |
109 | + this.deviceService.getDeviceCredentials(this.entity.id.id, true).subscribe( | |
110 | + (deviceCredentials) => { | |
111 | + const credentialsId = deviceCredentials.credentialsId; | |
112 | + if (this.clipboardService.copyFromContent(credentialsId)) { | |
113 | + this.store.dispatch(new ActionNotificationShow( | |
114 | + { | |
115 | + message: this.translate.instant('device.accessTokenCopiedMessage'), | |
116 | + type: 'success', | |
117 | + duration: 750, | |
118 | + verticalPosition: 'bottom', | |
119 | + horizontalPosition: 'right' | |
120 | + })); | |
121 | + } | |
122 | + } | |
123 | + ); | |
124 | + } | |
125 | + } | |
88 | 126 | } | ... | ... |
... | ... | @@ -20,15 +20,18 @@ import { SharedModule } from '@shared/shared.module'; |
20 | 20 | import {DeviceComponent} from '@modules/home/pages/device/device.component'; |
21 | 21 | import {DeviceRoutingModule} from './device-routing.module'; |
22 | 22 | import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; |
23 | +import {DeviceCredentialsDialogComponent} from '@modules/home/pages/device/device-credentials-dialog.component'; | |
23 | 24 | |
24 | 25 | @NgModule({ |
25 | 26 | entryComponents: [ |
26 | 27 | DeviceComponent, |
27 | - DeviceTableHeaderComponent | |
28 | + DeviceTableHeaderComponent, | |
29 | + DeviceCredentialsDialogComponent | |
28 | 30 | ], |
29 | 31 | declarations: [ |
30 | 32 | DeviceComponent, |
31 | - DeviceTableHeaderComponent | |
33 | + DeviceTableHeaderComponent, | |
34 | + DeviceCredentialsDialogComponent | |
32 | 35 | ], |
33 | 36 | imports: [ |
34 | 37 | CommonModule, | ... | ... |
... | ... | @@ -24,7 +24,8 @@ import { |
24 | 24 | checkBoxCell, |
25 | 25 | DateEntityTableColumn, |
26 | 26 | EntityTableColumn, |
27 | - EntityTableConfig | |
27 | + EntityTableConfig, | |
28 | + HeaderActionDescriptor | |
28 | 29 | } from '@shared/components/entity/entities-table-config.models'; |
29 | 30 | import { TenantService } from '@core/http/tenant.service'; |
30 | 31 | import { TranslateService } from '@ngx-translate/core'; |
... | ... | @@ -37,7 +38,7 @@ import { |
37 | 38 | import { TenantComponent } from '@modules/home/pages/tenant/tenant.component'; |
38 | 39 | import { EntityAction } from '@shared/components/entity/entity-component.models'; |
39 | 40 | import { User } from '@shared/models/user.model'; |
40 | -import {Device, DeviceInfo} from '@app/shared/models/device.models'; | |
41 | +import {Device, DeviceCredentials, DeviceInfo} from '@app/shared/models/device.models'; | |
41 | 42 | import {DeviceComponent} from '@modules/home/pages/device/device.component'; |
42 | 43 | import {Observable, of} from 'rxjs'; |
43 | 44 | import {select, Store} from '@ngrx/store'; |
... | ... | @@ -51,6 +52,12 @@ import {Customer} from '@app/shared/models/customer.model'; |
51 | 52 | import {NULL_UUID} from '@shared/models/id/has-uuid'; |
52 | 53 | import {BroadcastService} from '@core/services/broadcast.service'; |
53 | 54 | import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; |
55 | +import {MatDialog} from '@angular/material'; | |
56 | +import { | |
57 | + DeviceCredentialsDialogComponent, | |
58 | + DeviceCredentialsDialogData | |
59 | +} from '@modules/home/pages/device/device-credentials-dialog.component'; | |
60 | +import {DialogService} from '@core/services/dialog.service'; | |
54 | 61 | |
55 | 62 | @Injectable() |
56 | 63 | export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> { |
... | ... | @@ -63,9 +70,11 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev |
63 | 70 | private broadcast: BroadcastService, |
64 | 71 | private deviceService: DeviceService, |
65 | 72 | private customerService: CustomerService, |
73 | + private dialogService: DialogService, | |
66 | 74 | private translate: TranslateService, |
67 | 75 | private datePipe: DatePipe, |
68 | - private router: Router) { | |
76 | + private router: Router, | |
77 | + private dialog: MatDialog) { | |
69 | 78 | |
70 | 79 | this.config.entityType = EntityType.CUSTOMER; |
71 | 80 | this.config.entityComponent = DeviceComponent; |
... | ... | @@ -122,6 +131,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev |
122 | 131 | this.config.columns = this.configureColumns(this.config.componentsData.deviceScope); |
123 | 132 | this.configureEntityFunctions(this.config.componentsData.deviceScope); |
124 | 133 | this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.deviceScope); |
134 | + this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.deviceScope); | |
125 | 135 | return this.config; |
126 | 136 | }) |
127 | 137 | ); |
... | ... | @@ -154,16 +164,18 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev |
154 | 164 | |
155 | 165 | configureEntityFunctions(deviceScope: string): void { |
156 | 166 | if (deviceScope === 'tenant') { |
157 | - this.config.entitiesFetchFunction = pageLink => this.deviceService.getTenantDeviceInfos(pageLink, this.config.componentsData.deviceType); | |
167 | + this.config.entitiesFetchFunction = pageLink => | |
168 | + this.deviceService.getTenantDeviceInfos(pageLink, this.config.componentsData.deviceType); | |
158 | 169 | this.config.deleteEntity = id => this.deviceService.deleteDevice(id.id); |
159 | 170 | } else { |
160 | - this.config.entitiesFetchFunction = pageLink => this.deviceService.getCustomerDeviceInfos(this.customerId, pageLink, this.config.componentsData.deviceType); | |
171 | + this.config.entitiesFetchFunction = pageLink => | |
172 | + this.deviceService.getCustomerDeviceInfos(this.customerId, pageLink, this.config.componentsData.deviceType); | |
161 | 173 | this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id); |
162 | 174 | } |
163 | 175 | } |
164 | 176 | |
165 | - configureCellActions(deviceScope: string): Array<CellActionDescriptor<Device | DeviceInfo>> { | |
166 | - const actions: Array<CellActionDescriptor<Device | DeviceInfo>> = []; | |
177 | + configureCellActions(deviceScope: string): Array<CellActionDescriptor<DeviceInfo>> { | |
178 | + const actions: Array<CellActionDescriptor<Device>> = []; | |
167 | 179 | if (deviceScope === 'tenant') { |
168 | 180 | actions.push( |
169 | 181 | { |
... | ... | @@ -177,18 +189,122 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev |
177 | 189 | return actions; |
178 | 190 | } |
179 | 191 | |
192 | + configureAddActions(deviceScope: string): Array<HeaderActionDescriptor> { | |
193 | + const actions: Array<HeaderActionDescriptor> = []; | |
194 | + actions.push( | |
195 | + { | |
196 | + name: this.translate.instant('device.add-device-text'), | |
197 | + icon: 'insert_drive_file', | |
198 | + isEnabled: () => true, | |
199 | + onAction: ($event) => this.config.table.addEntity($event) | |
200 | + }, | |
201 | + { | |
202 | + name: this.translate.instant('device.import'), | |
203 | + icon: 'file_upload', | |
204 | + isEnabled: () => true, | |
205 | + onAction: ($event) => this.importDevices($event) | |
206 | + } | |
207 | + ); | |
208 | + return actions; | |
209 | + } | |
210 | + | |
211 | + importDevices($event: Event) { | |
212 | + if ($event) { | |
213 | + $event.stopPropagation(); | |
214 | + } | |
215 | + // TODO: | |
216 | + } | |
217 | + | |
180 | 218 | makePublic($event: Event, device: Device) { |
181 | 219 | if ($event) { |
182 | 220 | $event.stopPropagation(); |
183 | 221 | } |
222 | + this.dialogService.confirm( | |
223 | + this.translate.instant('device.make-public-device-title', {deviceName: device.name}), | |
224 | + this.translate.instant('device.make-public-device-text'), | |
225 | + this.translate.instant('action.no'), | |
226 | + this.translate.instant('action.yes'), | |
227 | + true | |
228 | + ).subscribe((res) => { | |
229 | + if (res) { | |
230 | + this.deviceService.makeDevicePublic(device.id.id).subscribe( | |
231 | + () => { | |
232 | + this.config.table.updateData(); | |
233 | + } | |
234 | + ); | |
235 | + } | |
236 | + } | |
237 | + ); | |
238 | + } | |
239 | + | |
240 | + assignToCustomer($event: Event, device: Device) { | |
241 | + if ($event) { | |
242 | + $event.stopPropagation(); | |
243 | + } | |
184 | 244 | // TODO: |
185 | 245 | } |
186 | 246 | |
187 | - onDeviceAction(action: EntityAction<Device | DeviceInfo>): boolean { | |
247 | + unassignFromCustomer($event: Event, device: DeviceInfo) { | |
248 | + if ($event) { | |
249 | + $event.stopPropagation(); | |
250 | + } | |
251 | + const isPublic = device.customerIsPublic; | |
252 | + let title; | |
253 | + let content; | |
254 | + if (isPublic) { | |
255 | + title = this.translate.instant('device.make-private-device-title', {deviceName: device.name}); | |
256 | + content = this.translate.instant('device.make-private-device-text'); | |
257 | + } else { | |
258 | + title = this.translate.instant('device.unassign-device-title', {deviceName: device.name}); | |
259 | + content = this.translate.instant('device.unassign-device-text'); | |
260 | + } | |
261 | + this.dialogService.confirm( | |
262 | + this.translate.instant(title), | |
263 | + this.translate.instant(content), | |
264 | + this.translate.instant('action.no'), | |
265 | + this.translate.instant('action.yes'), | |
266 | + true | |
267 | + ).subscribe((res) => { | |
268 | + if (res) { | |
269 | + this.deviceService.unassignDeviceFromCustomer(device.id.id).subscribe( | |
270 | + () => { | |
271 | + this.config.table.updateData(); | |
272 | + } | |
273 | + ); | |
274 | + } | |
275 | + } | |
276 | + ); | |
277 | + } | |
278 | + | |
279 | + manageCredentials($event: Event, device: Device) { | |
280 | + if ($event) { | |
281 | + $event.stopPropagation(); | |
282 | + } | |
283 | + this.dialog.open<DeviceCredentialsDialogComponent, DeviceCredentialsDialogData, | |
284 | + DeviceCredentials>(DeviceCredentialsDialogComponent, { | |
285 | + disableClose: true, | |
286 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | |
287 | + data: { | |
288 | + deviceId: device.id.id, | |
289 | + isReadOnly: this.config.componentsData.deviceScope === 'customer_user' | |
290 | + } | |
291 | + }); | |
292 | + } | |
293 | + | |
294 | + onDeviceAction(action: EntityAction<DeviceInfo>): boolean { | |
188 | 295 | switch (action.action) { |
189 | 296 | case 'makePublic': |
190 | 297 | this.makePublic(action.event, action.entity); |
191 | 298 | return true; |
299 | + case 'assignToCustomer': | |
300 | + this.assignToCustomer(action.event, action.entity); | |
301 | + return true; | |
302 | + case 'unassignFromCustomer': | |
303 | + this.unassignFromCustomer(action.event, action.entity); | |
304 | + return true; | |
305 | + case 'manageCredentials': | |
306 | + this.manageCredentials(action.event, action.entity); | |
307 | + return true; | |
192 | 308 | } |
193 | 309 | return false; |
194 | 310 | } | ... | ... |
... | ... | @@ -14,26 +14,24 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, OnInit } from '@angular/core'; | |
18 | -import { UserService } from '@core/http/user.service'; | |
19 | -import { User } from '@shared/models/user.model'; | |
20 | -import { Authority } from '@shared/models/authority.enum'; | |
21 | -import { PageComponent } from '@shared/components/page.component'; | |
22 | -import { select, Store } from '@ngrx/store'; | |
23 | -import { AppState } from '@core/core.state'; | |
24 | -import { getCurrentAuthUser, selectAuthUser } from '@core/auth/auth.selectors'; | |
25 | -import { mergeMap, take } from 'rxjs/operators'; | |
26 | -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | |
27 | -import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; | |
28 | -import { ActionAuthUpdateUserDetails } from '@core/auth/auth.actions'; | |
29 | -import { environment as env } from '@env/environment'; | |
30 | -import { TranslateService } from '@ngx-translate/core'; | |
31 | -import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions'; | |
32 | -import { ChangePasswordDialogComponent } from '@modules/home/pages/profile/change-password-dialog.component'; | |
33 | -import { MatDialog } from '@angular/material'; | |
34 | -import { DialogService } from '@core/services/dialog.service'; | |
35 | -import { AuthService } from '@core/auth/auth.service'; | |
36 | -import { ActivatedRoute } from '@angular/router'; | |
17 | +import {Component, OnInit} from '@angular/core'; | |
18 | +import {UserService} from '@core/http/user.service'; | |
19 | +import {User} from '@shared/models/user.model'; | |
20 | +import {Authority} from '@shared/models/authority.enum'; | |
21 | +import {PageComponent} from '@shared/components/page.component'; | |
22 | +import {Store} from '@ngrx/store'; | |
23 | +import {AppState} from '@core/core.state'; | |
24 | +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; | |
25 | +import {HasConfirmForm} from '@core/guards/confirm-on-exit.guard'; | |
26 | +import {ActionAuthUpdateUserDetails} from '@core/auth/auth.actions'; | |
27 | +import {environment as env} from '@env/environment'; | |
28 | +import {TranslateService} from '@ngx-translate/core'; | |
29 | +import {ActionSettingsChangeLanguage} from '@core/settings/settings.actions'; | |
30 | +import {ChangePasswordDialogComponent} from '@modules/home/pages/profile/change-password-dialog.component'; | |
31 | +import {MatDialog} from '@angular/material'; | |
32 | +import {DialogService} from '@core/services/dialog.service'; | |
33 | +import {AuthService} from '@core/auth/auth.service'; | |
34 | +import {ActivatedRoute} from '@angular/router'; | |
37 | 35 | |
38 | 36 | @Component({ |
39 | 37 | selector: 'tb-profile', | ... | ... |
... | ... | @@ -122,6 +122,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P |
122 | 122 | cellActionDescriptors: Array<CellActionDescriptor<T>> = []; |
123 | 123 | groupActionDescriptors: Array<GroupActionDescriptor<T>> = []; |
124 | 124 | headerActionDescriptors: Array<HeaderActionDescriptor> = []; |
125 | + addActionDescriptors: Array<HeaderActionDescriptor> = []; | |
125 | 126 | headerComponent: Type<EntityTableHeaderComponent<T>>; |
126 | 127 | addEntity: CreateEntityOperation<T> = null; |
127 | 128 | detailsReadonly: EntityBooleanFunction<T> = () => false; | ... | ... |
... | ... | @@ -42,11 +42,32 @@ |
42 | 42 | asButton historyOnly></tb-timewindow> |
43 | 43 | <tb-anchor #entityTableHeader></tb-anchor> |
44 | 44 | <span fxFlex *ngIf="!this.entitiesTableConfig.headerComponent"></span> |
45 | - <button mat-button mat-icon-button [disabled]="isLoading$ | async" [fxShow]="addEnabled()" (click)="addEntity($event)" | |
46 | - matTooltip="{{ translations.add | translate }}" | |
47 | - matTooltipPosition="above"> | |
48 | - <mat-icon>add</mat-icon> | |
49 | - </button> | |
45 | + <div [fxShow]="addEnabled()"> | |
46 | + <button mat-button mat-icon-button [disabled]="isLoading$ | async" | |
47 | + *ngIf="!this.entitiesTableConfig.addActionDescriptors.length; else addActions" | |
48 | + (click)="addEntity($event)" | |
49 | + matTooltip="{{ translations.add | translate }}" | |
50 | + matTooltipPosition="above"> | |
51 | + <mat-icon>add</mat-icon> | |
52 | + </button> | |
53 | + <ng-template #addActions> | |
54 | + <button mat-button mat-icon-button [disabled]="isLoading$ | async" | |
55 | + matTooltip="{{ translations.add | translate }}" | |
56 | + matTooltipPosition="above" | |
57 | + [matMenuTriggerFor]="addActionsMenu"> | |
58 | + <mat-icon>add</mat-icon> | |
59 | + </button> | |
60 | + <mat-menu #addActionsMenu="matMenu" xPosition="before"> | |
61 | + <button mat-menu-item *ngFor="let actionDescriptor of this.entitiesTableConfig.addActionDescriptors" | |
62 | + [disabled]="isLoading$ | async" | |
63 | + [fxShow]="actionDescriptor.isEnabled()" | |
64 | + (click)="actionDescriptor.onAction($event)"> | |
65 | + <mat-icon>{{actionDescriptor.icon}}</mat-icon> | |
66 | + <span>{{ actionDescriptor.name }}</span> | |
67 | + </button> | |
68 | + </mat-menu> | |
69 | + </ng-template> | |
70 | + </div> | |
50 | 71 | <button mat-button mat-icon-button [disabled]="isLoading$ | async" |
51 | 72 | [fxShow]="actionDescriptor.isEnabled()" *ngFor="let actionDescriptor of headerActionDescriptors" |
52 | 73 | matTooltip="{{ actionDescriptor.name }}" | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2019 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]="selectEntityFormGroup" class="mat-block"> | |
19 | + <input matInput type="text" placeholder="{{ entityText | translate }}" | |
20 | + #entityInput | |
21 | + formControlName="entity" | |
22 | + [required]="required" | |
23 | + [matAutocomplete]="entityAutocomplete"> | |
24 | + <button *ngIf="selectEntityFormGroup.get('entity').value && !disabled" | |
25 | + type="button" | |
26 | + matSuffix mat-button mat-icon-button aria-label="Clear" | |
27 | + (click)="clear()"> | |
28 | + <mat-icon class="material-icons">close</mat-icon> | |
29 | + </button> | |
30 | + <mat-autocomplete #entityAutocomplete="matAutocomplete" [displayWith]="displayEntityFn"> | |
31 | + <mat-option *ngFor="let entity of filteredEntities | async" [value]="entity"> | |
32 | + <span [innerHTML]="entity.name | highlight:searchText"></span> | |
33 | + </mat-option> | |
34 | + <mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null"> | |
35 | + <span> | |
36 | + {{ translate.get(noEntitiesMatchingText, {entity: searchText}) | async }} | |
37 | + </span> | |
38 | + </mat-option> | |
39 | + </mat-autocomplete> | |
40 | + <mat-error *ngIf="selectEntityFormGroup.get('entity').hasError('required')"> | |
41 | + {{ entityRequiredText | translate }} | |
42 | + </mat-error> | |
43 | +</mat-form-field> | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 {AfterViewInit, 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, startWith, tap} from 'rxjs/operators'; | |
21 | +import {Store} from '@ngrx/store'; | |
22 | +import {AppState} from '@app/core/core.state'; | |
23 | +import {TranslateService} from '@ngx-translate/core'; | |
24 | +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; | |
25 | +import {BaseData} from '@shared/models/base-data'; | |
26 | +import {EntityId} from '@shared/models/id/entity-id'; | |
27 | +import {EntityService} from '@core/http/entity.service'; | |
28 | + | |
29 | +@Component({ | |
30 | + selector: 'tb-entity-autocomplete', | |
31 | + templateUrl: './entity-autocomplete.component.html', | |
32 | + styleUrls: [], | |
33 | + providers: [{ | |
34 | + provide: NG_VALUE_ACCESSOR, | |
35 | + useExisting: forwardRef(() => EntityAutocompleteComponent), | |
36 | + multi: true | |
37 | + }] | |
38 | +}) | |
39 | +export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit { | |
40 | + | |
41 | + selectEntityFormGroup: FormGroup; | |
42 | + | |
43 | + modelValue: string | null; | |
44 | + | |
45 | + entityTypeValue: EntityType | AliasEntityType; | |
46 | + | |
47 | + entitySubtypeValue: string; | |
48 | + | |
49 | + @Input() | |
50 | + set entityType(entityType: EntityType) { | |
51 | + if (this.entityTypeValue !== entityType) { | |
52 | + this.entityTypeValue = entityType; | |
53 | + this.load(); | |
54 | + } | |
55 | + } | |
56 | + | |
57 | + @Input() | |
58 | + set entitySubtype(entitySubtype: string) { | |
59 | + if (this.entitySubtypeValue !== entitySubtype) { | |
60 | + this.entitySubtypeValue = entitySubtype; | |
61 | + const currentEntity = this.getCurrentEntity(); | |
62 | + if (currentEntity) { | |
63 | + if ((currentEntity as any).type !== this.entitySubtypeValue) { | |
64 | + this.reset(); | |
65 | + } | |
66 | + } | |
67 | + } | |
68 | + } | |
69 | + | |
70 | + @Input() | |
71 | + excludeEntityIds: Array<string>; | |
72 | + | |
73 | + @Input() | |
74 | + required: boolean; | |
75 | + | |
76 | + @Input() | |
77 | + disabled: boolean; | |
78 | + | |
79 | + @ViewChild('entityInput', {static: true}) entityInput: ElementRef; | |
80 | + | |
81 | + entityText: string; | |
82 | + noEntitiesMatchingText: string; | |
83 | + entityRequiredText: string; | |
84 | + | |
85 | + filteredEntities: Observable<Array<BaseData<EntityId>>>; | |
86 | + | |
87 | + private searchText = ''; | |
88 | + | |
89 | + private propagateChange = (v: any) => { }; | |
90 | + | |
91 | + constructor(private store: Store<AppState>, | |
92 | + public translate: TranslateService, | |
93 | + private entityService: EntityService, | |
94 | + private fb: FormBuilder) { | |
95 | + this.selectEntityFormGroup = this.fb.group({ | |
96 | + entity: [null] | |
97 | + }); | |
98 | + } | |
99 | + | |
100 | + registerOnChange(fn: any): void { | |
101 | + this.propagateChange = fn; | |
102 | + } | |
103 | + | |
104 | + registerOnTouched(fn: any): void { | |
105 | + } | |
106 | + | |
107 | + ngOnInit() { | |
108 | + this.filteredEntities = this.selectEntityFormGroup.get('dashboard').valueChanges | |
109 | + .pipe( | |
110 | + tap(value => { | |
111 | + let modelValue; | |
112 | + if (typeof value === 'string' || !value) { | |
113 | + modelValue = null; | |
114 | + } else { | |
115 | + modelValue = value.id.id; | |
116 | + } | |
117 | + this.updateView(modelValue); | |
118 | + }), | |
119 | + startWith<string | BaseData<EntityId>>(''), | |
120 | + map(value => value ? (typeof value === 'string' ? value : value.name) : ''), | |
121 | + mergeMap(name => this.fetchEntities(name) ) | |
122 | + ); | |
123 | + } | |
124 | + | |
125 | + ngAfterViewInit(): void {} | |
126 | + | |
127 | + load(): void { | |
128 | + if (this.entityTypeValue) { | |
129 | + switch (this.entityTypeValue) { | |
130 | + case EntityType.ASSET: | |
131 | + this.entityText = 'asset.asset'; | |
132 | + this.noEntitiesMatchingText = 'asset.no-assets-matching'; | |
133 | + this.entityRequiredText = 'asset.asset-required'; | |
134 | + break; | |
135 | + case EntityType.DEVICE: | |
136 | + this.entityText = 'device.device'; | |
137 | + this.noEntitiesMatchingText = 'device.no-devices-matching'; | |
138 | + this.entityRequiredText = 'device.device-required'; | |
139 | + break; | |
140 | + case EntityType.ENTITY_VIEW: | |
141 | + this.entityText = 'entity-view.entity-view'; | |
142 | + this.noEntitiesMatchingText = 'entity-view.no-entity-views-matching'; | |
143 | + this.entityRequiredText = 'entity-view.entity-view-required'; | |
144 | + break; | |
145 | + case EntityType.RULE_CHAIN: | |
146 | + this.entityText = 'rulechain.rulechain'; | |
147 | + this.noEntitiesMatchingText = 'rulechain.no-rulechains-matching'; | |
148 | + this.entityRequiredText = 'rulechain.rulechain-required'; | |
149 | + break; | |
150 | + case EntityType.TENANT: | |
151 | + this.entityText = 'tenant.tenant'; | |
152 | + this.noEntitiesMatchingText = 'tenant.no-tenants-matching'; | |
153 | + this.entityRequiredText = 'tenant.tenant-required'; | |
154 | + break; | |
155 | + case EntityType.CUSTOMER: | |
156 | + this.entityText = 'customer.customer'; | |
157 | + this.noEntitiesMatchingText = 'customer.no-customers-matching'; | |
158 | + this.entityRequiredText = 'customer.customer-required'; | |
159 | + break; | |
160 | + case EntityType.USER: | |
161 | + this.entityText = 'user.user'; | |
162 | + this.noEntitiesMatchingText = 'user.no-users-matching'; | |
163 | + this.entityRequiredText = 'user.user-required'; | |
164 | + break; | |
165 | + case EntityType.DASHBOARD: | |
166 | + this.entityText = 'dashboard.dashboard'; | |
167 | + this.noEntitiesMatchingText = 'dashboard.no-dashboards-matching'; | |
168 | + this.entityRequiredText = 'dashboard.dashboard-required'; | |
169 | + break; | |
170 | + case EntityType.ALARM: | |
171 | + this.entityText = 'alarm.alarm'; | |
172 | + this.noEntitiesMatchingText = 'alarm.no-alarms-matching'; | |
173 | + this.entityRequiredText = 'alarm.alarm-required'; | |
174 | + break; | |
175 | + case AliasEntityType.CURRENT_CUSTOMER: | |
176 | + this.entityText = 'customer.default-customer'; | |
177 | + this.noEntitiesMatchingText = 'customer.no-customers-matching'; | |
178 | + this.entityRequiredText = 'customer.default-customer-required'; | |
179 | + break; | |
180 | + } | |
181 | + } | |
182 | + const currentEntity = this.getCurrentEntity(); | |
183 | + if (currentEntity) { | |
184 | + const currentEntityType = currentEntity.id.entityType; | |
185 | + if (this.entityTypeValue && currentEntityType !== this.entityTypeValue) { | |
186 | + this.reset(); | |
187 | + } | |
188 | + } | |
189 | + } | |
190 | + | |
191 | + getCurrentEntity(): BaseData<EntityId> | null { | |
192 | + const currentEntity = this.selectEntityFormGroup.get('entity').value; | |
193 | + if (currentEntity && typeof currentEntity !== 'string') { | |
194 | + return currentEntity as BaseData<EntityId>; | |
195 | + } else { | |
196 | + return null; | |
197 | + } | |
198 | + } | |
199 | + | |
200 | + setDisabledState(isDisabled: boolean): void { | |
201 | + this.disabled = isDisabled; | |
202 | + } | |
203 | + | |
204 | + writeValue(value: string | null): void { | |
205 | + this.searchText = ''; | |
206 | + if (value != null) { | |
207 | + let targetEntityType = this.entityTypeValue; | |
208 | + if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { | |
209 | + targetEntityType = EntityType.CUSTOMER; | |
210 | + } | |
211 | + this.entityService.getEntity(targetEntityType, value).subscribe( | |
212 | + (entity) => { | |
213 | + this.modelValue = entity.id.id; | |
214 | + this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); | |
215 | + } | |
216 | + ); | |
217 | + } else { | |
218 | + this.modelValue = null; | |
219 | + this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true}); | |
220 | + } | |
221 | + } | |
222 | + | |
223 | + reset() { | |
224 | + this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true}); | |
225 | + } | |
226 | + | |
227 | + updateView(value: string | null) { | |
228 | + if (this.modelValue !== value) { | |
229 | + this.modelValue = value; | |
230 | + this.propagateChange(this.modelValue); | |
231 | + } | |
232 | + } | |
233 | + | |
234 | + displayEntityFn(entity?: BaseData<EntityId>): string | undefined { | |
235 | + return entity ? entity.name : undefined; | |
236 | + } | |
237 | + | |
238 | + fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> { | |
239 | + this.searchText = searchText; | |
240 | + let targetEntityType = this.entityTypeValue; | |
241 | + if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { | |
242 | + targetEntityType = EntityType.CUSTOMER; | |
243 | + } | |
244 | + return this.entityService.getEntitiesByNameFilter(targetEntityType, searchText, | |
245 | + 50, this.entitySubtypeValue, false, true).pipe( | |
246 | + map((data) => { | |
247 | + if (data) { | |
248 | + if (this.excludeEntityIds && this.excludeEntityIds.length) { | |
249 | + const entities: Array<BaseData<EntityId>> = []; | |
250 | + data.forEach((entity) => { | |
251 | + if (this.excludeEntityIds.indexOf(entity.id.id) === -1) { | |
252 | + entities.push(entity); | |
253 | + } | |
254 | + }); | |
255 | + return entities; | |
256 | + } else { | |
257 | + return data; | |
258 | + } | |
259 | + } else { | |
260 | + return []; | |
261 | + } | |
262 | + } | |
263 | + )); | |
264 | + } | |
265 | + | |
266 | + clear() { | |
267 | + this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true}); | |
268 | + setTimeout(() => { | |
269 | + this.entityInput.nativeElement.blur(); | |
270 | + this.entityInput.nativeElement.focus(); | |
271 | + }, 0); | |
272 | + } | |
273 | + | |
274 | +} | ... | ... |
ui-ngx/src/app/shared/models/alarm.models.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; | |
18 | +import {AssetId} from '@shared/models/id/asset-id'; | |
19 | +import {TenantId} from '@shared/models/id/tenant-id'; | |
20 | +import {CustomerId} from '@shared/models/id/customer-id'; | |
21 | +import {AlarmId} from '@shared/models/id/alarm-id'; | |
22 | +import {EntityId} from '@shared/models/id/entity-id'; | |
23 | + | |
24 | +export enum AlarmSeverity { | |
25 | + CRITICAL = 'CRITICAL', | |
26 | + MAJOR = 'MAJOR', | |
27 | + MINOR = 'MINOR', | |
28 | + WARNING = 'WARNING', | |
29 | + INDETERMINATE = 'INDETERMINATE' | |
30 | +} | |
31 | + | |
32 | +export enum AlarmStatus { | |
33 | + ACTIVE_UNACK = 'ACTIVE_UNACK', | |
34 | + ACTIVE_ACK = 'ACTIVE_ACK', | |
35 | + CLEARED_UNACK = 'CLEARED_UNACK', | |
36 | + CLEARED_ACK = 'CLEARED_ACK' | |
37 | +} | |
38 | + | |
39 | +export interface Alarm extends BaseData<AlarmId> { | |
40 | + tenantId: TenantId; | |
41 | + type: string; | |
42 | + originator: EntityId; | |
43 | + severity: AlarmSeverity; | |
44 | + status: AlarmStatus; | |
45 | + startTs: number; | |
46 | + endTs: number; | |
47 | + ackTs: number; | |
48 | + clearTs: number; | |
49 | + propagate: boolean; | |
50 | + details?: any; | |
51 | +} | ... | ... |
ui-ngx/src/app/shared/models/asset.models.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; | |
18 | +import {AssetId} from './id/asset-id'; | |
19 | +import {TenantId} from '@shared/models/id/tenant-id'; | |
20 | +import {CustomerId} from '@shared/models/id/customer-id'; | |
21 | +import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id'; | |
22 | + | |
23 | +export interface Asset extends BaseData<AssetId> { | |
24 | + tenantId: TenantId; | |
25 | + customerId: CustomerId; | |
26 | + name: string; | |
27 | + type: string; | |
28 | + additionalInfo?: any; | |
29 | +} | |
30 | + | |
31 | +export interface AssetInfo extends Asset { | |
32 | + customerTitle: string; | |
33 | + customerIsPublic: boolean; | |
34 | +} | ... | ... |
... | ... | @@ -18,6 +18,7 @@ import {BaseData} from '@shared/models/base-data'; |
18 | 18 | import {DeviceId} from './id/device-id'; |
19 | 19 | import {TenantId} from '@shared/models/id/tenant-id'; |
20 | 20 | import {CustomerId} from '@shared/models/id/customer-id'; |
21 | +import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id'; | |
21 | 22 | |
22 | 23 | export interface Device extends BaseData<DeviceId> { |
23 | 24 | tenantId: TenantId; |
... | ... | @@ -32,3 +33,22 @@ export interface DeviceInfo extends Device { |
32 | 33 | customerTitle: string; |
33 | 34 | customerIsPublic: boolean; |
34 | 35 | } |
36 | + | |
37 | +export enum DeviceCredentialsType { | |
38 | + ACCESS_TOKEN = 'ACCESS_TOKEN', | |
39 | + X509_CERTIFICATE = 'X509_CERTIFICATE' | |
40 | +} | |
41 | + | |
42 | +export const credentialTypeNames = new Map<DeviceCredentialsType, string>( | |
43 | + [ | |
44 | + [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'], | |
45 | + [DeviceCredentialsType.X509_CERTIFICATE, 'X.509 Certificate'], | |
46 | + ] | |
47 | +); | |
48 | + | |
49 | +export interface DeviceCredentials extends BaseData<DeviceCredentialsId> { | |
50 | + deviceId: DeviceId; | |
51 | + credentialsType: DeviceCredentialsType; | |
52 | + credentialsId: string; | |
53 | + credentialsValue: string; | |
54 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; | |
18 | +import {AssetId} from './id/asset-id'; | |
19 | +import {TenantId} from '@shared/models/id/tenant-id'; | |
20 | +import {CustomerId} from '@shared/models/id/customer-id'; | |
21 | +import {EntityViewId} from '@shared/models/id/entity-view-id'; | |
22 | +import {EntityId} from '@shared/models/id/entity-id'; | |
23 | + | |
24 | +export interface AttributesEntityView { | |
25 | + cs: Array<string>; | |
26 | + ss: Array<string>; | |
27 | + sh: Array<string>; | |
28 | +} | |
29 | + | |
30 | +export interface TelemetryEntityView { | |
31 | + timeseries: Array<string>; | |
32 | + attributes: AttributesEntityView; | |
33 | +} | |
34 | + | |
35 | +export interface EntityView extends BaseData<EntityViewId> { | |
36 | + tenantId: TenantId; | |
37 | + customerId: CustomerId; | |
38 | + entityId: EntityId; | |
39 | + name: string; | |
40 | + type: string; | |
41 | + keys: TelemetryEntityView; | |
42 | + startTimeMs: number; | |
43 | + endTimeMs: number; | |
44 | + additionalInfo?: any; | |
45 | +} | |
46 | + | |
47 | +export interface EntityViewInfo extends EntityView { | |
48 | + customerTitle: string; | |
49 | + customerIsPublic: boolean; | |
50 | +} | ... | ... |
ui-ngx/src/app/shared/models/id/alarm-id.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class AlarmId implements EntityId { | |
21 | + entityType = EntityType.ALARM; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
ui-ngx/src/app/shared/models/id/asset-id.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class AssetId implements EntityId { | |
21 | + entityType = EntityType.ASSET; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { HasUUID } from '@shared/models/id/has-uuid'; | |
18 | + | |
19 | +export class DeviceCredentialsId implements HasUUID { | |
20 | + id: string; | |
21 | + constructor(id: string) { | |
22 | + this.id = id; | |
23 | + } | |
24 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class EntityViewId implements EntityId { | |
21 | + entityType = EntityType.ENTITY_VIEW; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class RuleChainId implements EntityId { | |
21 | + entityType = EntityType.RULE_CHAIN; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class RuleNodeId implements EntityId { | |
21 | + entityType = EntityType.RULE_NODE; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class WidgetTypeId implements EntityId { | |
21 | + entityType = EntityType.WIDGET_TYPE; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class WidgetsBundleId implements EntityId { | |
21 | + entityType = EntityType.WIDGETS_BUNDLE; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; | |
18 | +import {AssetId} from '@shared/models/id/asset-id'; | |
19 | +import {TenantId} from '@shared/models/id/tenant-id'; | |
20 | +import {CustomerId} from '@shared/models/id/customer-id'; | |
21 | +import {RuleChainId} from '@shared/models/id/rule-chain-id'; | |
22 | +import {RuleNodeId} from '@shared/models/id/rule-node-id'; | |
23 | + | |
24 | +export interface RuleChainConfiguration { | |
25 | + todo: Array<any>; | |
26 | + // TODO: | |
27 | +} | |
28 | + | |
29 | +export interface RuleChain extends BaseData<RuleChainId> { | |
30 | + tenantId: TenantId; | |
31 | + name: string; | |
32 | + firstRuleNodeId: RuleNodeId; | |
33 | + root: boolean; | |
34 | + debugMode: boolean; | |
35 | + configuration: RuleChainConfiguration; | |
36 | + additionalInfo?: any; | |
37 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; | |
18 | +import {AssetId} from '@shared/models/id/asset-id'; | |
19 | +import {TenantId} from '@shared/models/id/tenant-id'; | |
20 | +import {CustomerId} from '@shared/models/id/customer-id'; | |
21 | +import {RuleChainId} from '@shared/models/id/rule-chain-id'; | |
22 | +import {RuleNodeId} from '@shared/models/id/rule-node-id'; | |
23 | + | |
24 | +export interface RuleNodeConfiguration { | |
25 | + todo: Array<any>; | |
26 | + // TODO: | |
27 | +} | |
28 | + | |
29 | +export interface RuleNode extends BaseData<RuleNodeId> { | |
30 | + ruleChainId: RuleChainId; | |
31 | + type: string; | |
32 | + name: string; | |
33 | + debugMode: boolean; | |
34 | + configuration: RuleNodeConfiguration; | |
35 | + additionalInfo?: any; | |
36 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; | |
18 | +import {TenantId} from '@shared/models/id/tenant-id'; | |
19 | +import {WidgetsBundleId} from '@shared/models/id/widgets-bundle-id'; | |
20 | +import {WidgetTypeId} from '@shared/models/id/widget-type-id'; | |
21 | + | |
22 | +export interface WidgetTypeDescriptor { | |
23 | + todo: Array<any>; | |
24 | + // TODO: | |
25 | +} | |
26 | + | |
27 | +export interface WidgetType extends BaseData<WidgetTypeId> { | |
28 | + tenantId: TenantId; | |
29 | + bundleAlias: string; | |
30 | + alias: string; | |
31 | + name: string; | |
32 | + descriptor: WidgetTypeDescriptor; | |
33 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; | |
18 | +import {TenantId} from '@shared/models/id/tenant-id'; | |
19 | +import {WidgetsBundleId} from '@shared/models/id/widgets-bundle-id'; | |
20 | + | |
21 | +export interface WidgetsBundle extends BaseData<WidgetsBundleId> { | |
22 | + tenantId: TenantId; | |
23 | + alias: string; | |
24 | + title: string; | |
25 | + image: string; | |
26 | +} | ... | ... |
... | ... | @@ -80,6 +80,7 @@ import { HighlightPipe } from '@shared/pipe/highlight.pipe'; |
80 | 80 | import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component'; |
81 | 81 | import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/entity-subtype-autocomplete.component'; |
82 | 82 | import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; |
83 | +import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component'; | |
83 | 84 | |
84 | 85 | @NgModule({ |
85 | 86 | providers: [ |
... | ... | @@ -122,6 +123,7 @@ import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-s |
122 | 123 | DashboardAutocompleteComponent, |
123 | 124 | EntitySubTypeAutocompleteComponent, |
124 | 125 | EntitySubTypeSelectComponent, |
126 | + EntityAutocompleteComponent, | |
125 | 127 | NospacePipe, |
126 | 128 | MillisecondsToTimeStringPipe, |
127 | 129 | EnumToArrayPipe, |
... | ... | @@ -189,6 +191,7 @@ import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-s |
189 | 191 | DashboardAutocompleteComponent, |
190 | 192 | EntitySubTypeAutocompleteComponent, |
191 | 193 | EntitySubTypeSelectComponent, |
194 | + EntityAutocompleteComponent, | |
192 | 195 | // ValueInputComponent, |
193 | 196 | MatButtonModule, |
194 | 197 | MatCheckboxModule, | ... | ... |