Commit 3700bcaa5d7cbecd9b573b82d14c51a9060e0403

Authored by Igor Kulikov
1 parent 6c6d4fca

Devices page implementation

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 }
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
... ... @@ -31,6 +31,10 @@ export enum EntityType {
31 31 WIDGET_TYPE = 'WIDGET_TYPE'
32 32 }
33 33
  34 +export enum AliasEntityType {
  35 + CURRENT_CUSTOMER = 'CURRENT_CUSTOMER'
  36 +}
  37 +
34 38 export interface EntityTypeTranslation {
35 39 type: string;
36 40 typePlural: string;
... ...
  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 +}
... ...
  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 +}
... ...
  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,
... ...