Devices page implementation

This commit is contained in:
Igor Kulikov 2019-08-14 19:55:24 +03:00
parent 6c6d4fca83
commit 3700bcaa5d
33 changed files with 1552 additions and 43 deletions

View File

@ -68,7 +68,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/static/**", "/api/noauth/**", "/webjars/**"};
protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**"};
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**";
@ -155,7 +155,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**");
web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**");
}
@Override

View File

@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class WebConfig {
@RequestMapping(value = "/{path:^(?!api$)(?!static$)(?!webjars$)[^\\.]*}/**")
@RequestMapping(value = "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)[^\\.]*}/**")
public String redirect() {
return "forward:/index.html";
}

View File

@ -31,9 +31,9 @@ export class CustomerService {
private http: HttpClient
) { }
public getCustomers(tenantId: string, pageLink: PageLink, ignoreErrors: boolean = false,
public getCustomers(pageLink: PageLink, ignoreErrors: boolean = false,
ignoreLoading: boolean = false): Observable<PageData<Customer>> {
return this.http.get<PageData<Customer>>(`/api/tenant/${tenantId}/customers${pageLink.toQuery()}`,
return this.http.get<PageData<Customer>>(`/api/customers${pageLink.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors));
}

View File

@ -16,15 +16,16 @@
import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils';
import { Observable } from 'rxjs/index';
import { Observable, Subject, ReplaySubject } from 'rxjs/index';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
import { Tenant } from '@shared/models/tenant.model';
import {DashboardInfo, Dashboard} from '@shared/models/dashboard.models';
import {map} from 'rxjs/operators';
import {DeviceInfo, Device} from '@app/shared/models/device.models';
import {DeviceInfo, Device, DeviceCredentials} from '@app/shared/models/device.models';
import {EntitySubtype} from '@app/shared/models/entity-type.models';
import {AuthService} from '../auth/auth.service';
@Injectable({
providedIn: 'root'
@ -67,6 +68,40 @@ export class DeviceService {
return this.http.get<Array<EntitySubtype>>('/api/device/types', defaultHttpOptions(ignoreLoading, ignoreErrors));
}
public getDeviceCredentials(deviceId: string, sync: boolean = false, ignoreErrors: boolean = false,
ignoreLoading: boolean = false): Observable<DeviceCredentials> {
const url = `/api/device/${deviceId}/credentials`;
if (sync) {
const responseSubject = new ReplaySubject<DeviceCredentials>();
const request = new XMLHttpRequest();
request.open('GET', url, false);
request.setRequestHeader('Accept', 'application/json, text/plain, */*');
const jwtToken = AuthService.getJwtToken();
if (jwtToken) {
request.setRequestHeader('X-Authorization', 'Bearer ' + jwtToken);
}
request.send(null);
if (request.status === 200) {
const credentials = JSON.parse(request.responseText) as DeviceCredentials;
responseSubject.next(credentials);
} else {
responseSubject.error(null);
}
return responseSubject.asObservable();
} else {
return this.http.get<DeviceCredentials>(url, defaultHttpOptions(ignoreLoading, ignoreErrors));
}
}
public saveDeviceCredentials(deviceCredentials: DeviceCredentials, ignoreErrors: boolean = false,
ignoreLoading: boolean = false): Observable<DeviceCredentials> {
return this.http.post<DeviceCredentials>('/api/device/credentials', deviceCredentials, defaultHttpOptions(ignoreLoading, ignoreErrors));
}
public makeDevicePublic(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> {
return this.http.post<Device>(`/api/customer/public/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors));
}
public unassignDeviceFromCustomer(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {
return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors));
}

View File

@ -0,0 +1,263 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {Injectable} from '@angular/core';
import {Observable, throwError, of, empty, EMPTY} from 'rxjs/index';
import {HttpClient} from '@angular/common/http';
import {PageLink} from '@shared/models/page/page-link';
import {EntityType} from '@shared/models/entity-type.models';
import {BaseData} from '@shared/models/base-data';
import {EntityId} from '@shared/models/id/entity-id';
import {DeviceService} from '@core/http/device.service';
import {TenantService} from '@core/http/tenant.service';
import {CustomerService} from '@core/http/customer.service';
import {UserService} from './user.service';
import {DashboardService} from '@core/http/dashboard.service';
import {Direction} from '@shared/models/page/sort-order';
import {PageData} from '@shared/models/page/page-data';
import {getCurrentAuthUser} from '../auth/auth.selectors';
import {Store} from '@ngrx/store';
import {AppState} from '@core/core.state';
import {Authority} from '@shared/models/authority.enum';
import {Tenant} from '@shared/models/tenant.model';
import {concatMap, expand, map, toArray} from 'rxjs/operators';
import {Customer} from '@app/shared/models/customer.model';
@Injectable({
providedIn: 'root'
})
export class EntityService {
constructor(
private http: HttpClient,
private store: Store<AppState>,
private deviceService: DeviceService,
private tenantService: TenantService,
private customerService: CustomerService,
private userService: UserService,
private dashboardService: DashboardService
) { }
private getEntityObservable(entityType: EntityType, entityId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<BaseData<EntityId>> {
let observable: Observable<BaseData<EntityId>>;
switch (entityType) {
case EntityType.DEVICE:
observable = this.deviceService.getDevice(entityId, ignoreErrors, ignoreLoading);
break;
case EntityType.ASSET:
// TODO:
break;
case EntityType.ENTITY_VIEW:
// TODO:
break;
case EntityType.TENANT:
observable = this.tenantService.getTenant(entityId, ignoreErrors, ignoreLoading);
break;
case EntityType.CUSTOMER:
observable = this.customerService.getCustomer(entityId, ignoreErrors, ignoreLoading);
break;
case EntityType.DASHBOARD:
observable = this.dashboardService.getDashboardInfo(entityId, ignoreErrors, ignoreLoading);
break;
case EntityType.USER:
observable = this.userService.getUser(entityId, ignoreErrors, ignoreLoading);
break;
case EntityType.RULE_CHAIN:
// TODO:
break;
case EntityType.ALARM:
console.error('Get Alarm Entity is not implemented!');
break;
}
return observable;
}
public getEntity(entityType: EntityType, entityId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<BaseData<EntityId>> {
const entityObservable = this.getEntityObservable(entityType, entityId, ignoreErrors, ignoreLoading);
if (entityObservable) {
return entityObservable;
} else {
return throwError(null);
}
}
private getSingleTenantByPageLinkObservable(pageLink: PageLink,
ignoreErrors: boolean = false,
ignoreLoading: boolean = false): Observable<PageData<Tenant>> {
const authUser = getCurrentAuthUser(this.store);
const tenantId = authUser.tenantId;
return this.tenantService.getTenant(tenantId, ignoreErrors, ignoreLoading).pipe(
map((tenant) => {
const result = {
data: [],
totalPages: 0,
totalElements: 0,
hasNext: false
} as PageData<Tenant>;
if (tenant.title.toLowerCase().startsWith(pageLink.textSearch.toLowerCase())) {
result.data.push(tenant);
result.totalPages = 1;
result.totalElements = 1;
}
return result;
})
);
}
private getSingleCustomerByPageLinkObservable(pageLink: PageLink,
ignoreErrors: boolean = false,
ignoreLoading: boolean = false): Observable<PageData<Customer>> {
const authUser = getCurrentAuthUser(this.store);
const customerId = authUser.customerId;
return this.customerService.getCustomer(customerId, ignoreErrors, ignoreLoading).pipe(
map((customer) => {
const result = {
data: [],
totalPages: 0,
totalElements: 0,
hasNext: false
} as PageData<Customer>;
if (customer.title.toLowerCase().startsWith(pageLink.textSearch.toLowerCase())) {
result.data.push(customer);
result.totalPages = 1;
result.totalElements = 1;
}
return result;
})
);
}
private getEntitiesByPageLinkObservable(entityType: EntityType, pageLink: PageLink, subType: string = '',
ignoreErrors: boolean = false,
ignoreLoading: boolean = false): Observable<PageData<BaseData<EntityId>>> {
let entitiesObservable: Observable<PageData<BaseData<EntityId>>>;
const authUser = getCurrentAuthUser(this.store);
const customerId = authUser.customerId;
switch (entityType) {
case EntityType.DEVICE:
pageLink.sortOrder.property = 'name';
if (authUser.authority === Authority.CUSTOMER_USER) {
entitiesObservable = this.deviceService.getCustomerDeviceInfos(customerId, pageLink, subType, ignoreErrors, ignoreLoading);
} else {
entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, ignoreErrors, ignoreLoading);
}
break;
case EntityType.ASSET:
pageLink.sortOrder.property = 'name';
if (authUser.authority === Authority.CUSTOMER_USER) {
// TODO:
} else {
// TODO:
}
break;
case EntityType.ENTITY_VIEW:
pageLink.sortOrder.property = 'name';
if (authUser.authority === Authority.CUSTOMER_USER) {
// TODO:
} else {
// TODO:
}
break;
case EntityType.TENANT:
pageLink.sortOrder.property = 'title';
if (authUser.authority === Authority.TENANT_ADMIN) {
entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading);
} else {
entitiesObservable = this.tenantService.getTenants(pageLink, ignoreErrors, ignoreLoading);
}
break;
case EntityType.CUSTOMER:
pageLink.sortOrder.property = 'title';
if (authUser.authority === Authority.CUSTOMER_USER) {
entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading);
} else {
entitiesObservable = this.customerService.getCustomers(pageLink, ignoreErrors, ignoreLoading);
}
break;
case EntityType.RULE_CHAIN:
pageLink.sortOrder.property = 'name';
// TODO:
break;
case EntityType.DASHBOARD:
pageLink.sortOrder.property = 'title';
if (authUser.authority === Authority.CUSTOMER_USER) {
entitiesObservable = this.dashboardService.getCustomerDashboards(customerId, pageLink, ignoreErrors, ignoreLoading);
} else {
entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, ignoreErrors, ignoreLoading);
}
break;
case EntityType.USER:
console.error('Get User Entities is not implemented!');
break;
case EntityType.ALARM:
console.error('Get Alarm Entities is not implemented!');
break;
}
return entitiesObservable;
}
private getEntitiesByPageLink(entityType: EntityType, pageLink: PageLink, subType: string = '',
ignoreErrors: boolean = false,
ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> {
const entitiesObservable: Observable<PageData<BaseData<EntityId>>> =
this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading);
if (entitiesObservable) {
return entitiesObservable.pipe(
expand((data) => {
if (data.hasNext) {
pageLink.page += 1;
return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading);
} else {
return EMPTY;
}
}),
map((data) => data.data),
concatMap((data) => data),
toArray()
);
} else {
return of(null);
}
}
public getEntitiesByNameFilter(entityType: EntityType, entityNameFilter: string,
pageSize: number, subType: string = '',
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<BaseData<EntityId>>> {
const pageLink = new PageLink(pageSize, 0, entityNameFilter, {
property: 'name',
direction: Direction.ASC
});
if (pageSize === -1) { // all
pageLink.pageSize = 100;
return this.getEntitiesByPageLink(entityType, pageLink, subType, ignoreErrors, ignoreLoading).pipe(
map((data) => data && data.length ? data : null)
);
} else {
const entitiesObservable: Observable<PageData<BaseData<EntityId>>> =
this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading);
if (entitiesObservable) {
return entitiesObservable.pipe(
map((data) => data && data.data.length ? data.data : null)
);
} else {
return of(null);
}
}
}
}

View File

@ -0,0 +1,79 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form #deviceCredentialsForm="ngForm" [formGroup]="deviceCredentialsFormGroup" (ngSubmit)="save()">
<mat-toolbar fxLayout="row" color="primary">
<h2 translate>device.device-credentials</h2>
<span fxFlex></span>
<button mat-button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<fieldset [disabled]="(isLoading$ | async) || isReadOnly">
<mat-form-field class="mat-block">
<mat-label translate>device.credentials-type</mat-label>
<mat-select matInput formControlName="credentialsType"
(ngModelChange)="credentialsTypeChanged()">
<mat-option *ngFor="let credentialsType of credentialsTypes" [value]="credentialsType">
{{ credentialTypeNamesMap.get(credentialsType) }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.ACCESS_TOKEN"
class="mat-block">
<mat-label translate>device.access-token</mat-label>
<input matInput formControlName="credentialsId" required>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('required')">
{{ 'device.access-token-required' | translate }}
</mat-error>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsId').hasError('pattern')">
{{ 'device.access-token-invalid' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field *ngIf="deviceCredentialsFormGroup.get('credentialsType').value === deviceCredentialsType.X509_CERTIFICATE"
class="mat-block">
<mat-label translate>device.rsa-key</mat-label>
<textarea matInput formControlName="credentialsValue" cols="15" rows="5" required></textarea>
<mat-error *ngIf="deviceCredentialsFormGroup.get('credentialsValue').hasError('required')">
{{ 'device.rsa-key-required' | translate }}
</mat-error>
</mat-form-field>
</fieldset>
</div>
<div mat-dialog-actions fxLayout="row">
<span fxFlex></span>
<button *ngIf="!isReadOnly" mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || deviceCredentialsForm.invalid
|| !deviceCredentialsForm.dirty">
{{ 'action.save' | translate }}
</button>
<button mat-button color="primary"
style="margin-right: 20px;"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ (isReadOnly ? 'action.close' : 'action.cancel') | translate }}
</button>
</div>
</form>

View File

@ -0,0 +1,138 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {Component, OnInit, SkipSelf, Inject} from '@angular/core';
import {ErrorStateMatcher, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators} from '@angular/forms';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { TranslateService } from '@ngx-translate/core';
import { AuthService } from '@core/auth/auth.service';
import {DeviceService} from '@core/http/device.service';
import {DeviceCredentials, DeviceCredentialsType, credentialTypeNames} from '@shared/models/device.models';
export interface DeviceCredentialsDialogData {
isReadOnly: boolean;
deviceId: string;
}
@Component({
selector: 'tb-device-credentials-dialog',
templateUrl: './device-credentials-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: DeviceCredentialsDialogComponent}],
styleUrls: []
})
export class DeviceCredentialsDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher {
deviceCredentialsFormGroup: FormGroup;
isReadOnly: boolean;
deviceCredentials: DeviceCredentials;
submitted = false;
deviceCredentialsType = DeviceCredentialsType;
credentialsTypes = Object.keys(DeviceCredentialsType);
credentialTypeNamesMap = credentialTypeNames;
constructor(protected store: Store<AppState>,
@Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData,
private deviceService: DeviceService,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<DeviceCredentialsDialogComponent, DeviceCredentials>,
public fb: FormBuilder) {
super(store);
this.isReadOnly = data.isReadOnly;
}
ngOnInit(): void {
this.deviceCredentialsFormGroup = this.fb.group({
credentialsType: [DeviceCredentialsType.ACCESS_TOKEN],
credentialsId: [''],
credentialsValue: ['']
});
if (this.isReadOnly) {
this.deviceCredentialsFormGroup.disable({emitEvent: false});
} else {
this.registerDisableOnLoadFormControl(this.deviceCredentialsFormGroup.get('credentialsType'));
}
this.loadDeviceCredentials();
}
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = !!(control && control.invalid && this.submitted);
return originalErrorState || customErrorState;
}
loadDeviceCredentials() {
this.deviceService.getDeviceCredentials(this.data.deviceId).subscribe(
(deviceCredentials) => {
this.deviceCredentials = deviceCredentials;
this.deviceCredentialsFormGroup.patchValue({
credentialsType: deviceCredentials.credentialsType,
credentialsId: deviceCredentials.credentialsId,
credentialsValue: deviceCredentials.credentialsValue
});
this.updateValidators();
}
);
}
credentialsTypeChanged(): void {
this.deviceCredentialsFormGroup.patchValue(
{credentialsId: null, credentialsValue: null}, {emitEvent: true});
this.updateValidators();
}
updateValidators(): void {
const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType;
switch (crendetialsType) {
case DeviceCredentialsType.ACCESS_TOKEN:
this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.max(20), Validators.pattern(/^.{1,20}$/)]);
this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity();
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity();
break;
case DeviceCredentialsType.X509_CERTIFICATE:
this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]);
this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity();
this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]);
this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity();
break;
}
}
cancel(): void {
this.dialogRef.close(null);
}
save(): void {
this.submitted = true;
this.deviceCredentials = {...this.deviceCredentials, ...this.deviceCredentialsFormGroup.value};
this.deviceService.saveDeviceCredentials(this.deviceCredentials).subscribe(
(deviceCredentials) => {
this.dialogRef.close(deviceCredentials);
}
);
}
}

View File

@ -26,6 +26,10 @@ import { Authority } from '@shared/models/authority.enum';
import {DeviceInfo} from '@shared/models/device.models';
import {EntityType} from '@shared/models/entity-type.models';
import {NULL_UUID} from '@shared/models/id/has-uuid';
import {ActionNotificationShow} from '@core/notification/notification.actions';
import {TranslateService} from '@ngx-translate/core';
import {DeviceService} from '@core/http/device.service';
import {ClipboardService} from 'ngx-clipboard';
@Component({
selector: 'tb-device',
@ -39,6 +43,9 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
deviceScope: 'tenant' | 'customer' | 'customer_user';
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
private deviceService: DeviceService,
private clipboardService: ClipboardService,
public fb: FormBuilder) {
super(store);
}
@ -85,4 +92,35 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}});
}
onDeviceIdCopied($event) {
this.store.dispatch(new ActionNotificationShow(
{
message: this.translate.instant('device.idCopiedMessage'),
type: 'success',
duration: 750,
verticalPosition: 'bottom',
horizontalPosition: 'right'
}));
}
copyAccessToken($event) {
if (this.entity.id) {
this.deviceService.getDeviceCredentials(this.entity.id.id, true).subscribe(
(deviceCredentials) => {
const credentialsId = deviceCredentials.credentialsId;
if (this.clipboardService.copyFromContent(credentialsId)) {
this.store.dispatch(new ActionNotificationShow(
{
message: this.translate.instant('device.accessTokenCopiedMessage'),
type: 'success',
duration: 750,
verticalPosition: 'bottom',
horizontalPosition: 'right'
}));
}
}
);
}
}
}

View File

@ -20,15 +20,18 @@ import { SharedModule } from '@shared/shared.module';
import {DeviceComponent} from '@modules/home/pages/device/device.component';
import {DeviceRoutingModule} from './device-routing.module';
import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component';
import {DeviceCredentialsDialogComponent} from '@modules/home/pages/device/device-credentials-dialog.component';
@NgModule({
entryComponents: [
DeviceComponent,
DeviceTableHeaderComponent
DeviceTableHeaderComponent,
DeviceCredentialsDialogComponent
],
declarations: [
DeviceComponent,
DeviceTableHeaderComponent
DeviceTableHeaderComponent,
DeviceCredentialsDialogComponent
],
imports: [
CommonModule,

View File

@ -24,7 +24,8 @@ import {
checkBoxCell,
DateEntityTableColumn,
EntityTableColumn,
EntityTableConfig
EntityTableConfig,
HeaderActionDescriptor
} from '@shared/components/entity/entities-table-config.models';
import { TenantService } from '@core/http/tenant.service';
import { TranslateService } from '@ngx-translate/core';
@ -37,7 +38,7 @@ import {
import { TenantComponent } from '@modules/home/pages/tenant/tenant.component';
import { EntityAction } from '@shared/components/entity/entity-component.models';
import { User } from '@shared/models/user.model';
import {Device, DeviceInfo} from '@app/shared/models/device.models';
import {Device, DeviceCredentials, DeviceInfo} from '@app/shared/models/device.models';
import {DeviceComponent} from '@modules/home/pages/device/device.component';
import {Observable, of} from 'rxjs';
import {select, Store} from '@ngrx/store';
@ -51,6 +52,12 @@ import {Customer} from '@app/shared/models/customer.model';
import {NULL_UUID} from '@shared/models/id/has-uuid';
import {BroadcastService} from '@core/services/broadcast.service';
import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component';
import {MatDialog} from '@angular/material';
import {
DeviceCredentialsDialogComponent,
DeviceCredentialsDialogData
} from '@modules/home/pages/device/device-credentials-dialog.component';
import {DialogService} from '@core/services/dialog.service';
@Injectable()
export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> {
@ -63,9 +70,11 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
private broadcast: BroadcastService,
private deviceService: DeviceService,
private customerService: CustomerService,
private dialogService: DialogService,
private translate: TranslateService,
private datePipe: DatePipe,
private router: Router) {
private router: Router,
private dialog: MatDialog) {
this.config.entityType = EntityType.CUSTOMER;
this.config.entityComponent = DeviceComponent;
@ -122,6 +131,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
this.config.columns = this.configureColumns(this.config.componentsData.deviceScope);
this.configureEntityFunctions(this.config.componentsData.deviceScope);
this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.deviceScope);
this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.deviceScope);
return this.config;
})
);
@ -154,16 +164,18 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
configureEntityFunctions(deviceScope: string): void {
if (deviceScope === 'tenant') {
this.config.entitiesFetchFunction = pageLink => this.deviceService.getTenantDeviceInfos(pageLink, this.config.componentsData.deviceType);
this.config.entitiesFetchFunction = pageLink =>
this.deviceService.getTenantDeviceInfos(pageLink, this.config.componentsData.deviceType);
this.config.deleteEntity = id => this.deviceService.deleteDevice(id.id);
} else {
this.config.entitiesFetchFunction = pageLink => this.deviceService.getCustomerDeviceInfos(this.customerId, pageLink, this.config.componentsData.deviceType);
this.config.entitiesFetchFunction = pageLink =>
this.deviceService.getCustomerDeviceInfos(this.customerId, pageLink, this.config.componentsData.deviceType);
this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id);
}
}
configureCellActions(deviceScope: string): Array<CellActionDescriptor<Device | DeviceInfo>> {
const actions: Array<CellActionDescriptor<Device | DeviceInfo>> = [];
configureCellActions(deviceScope: string): Array<CellActionDescriptor<DeviceInfo>> {
const actions: Array<CellActionDescriptor<Device>> = [];
if (deviceScope === 'tenant') {
actions.push(
{
@ -177,18 +189,122 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
return actions;
}
makePublic($event: Event, device: Device) {
configureAddActions(deviceScope: string): Array<HeaderActionDescriptor> {
const actions: Array<HeaderActionDescriptor> = [];
actions.push(
{
name: this.translate.instant('device.add-device-text'),
icon: 'insert_drive_file',
isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event)
},
{
name: this.translate.instant('device.import'),
icon: 'file_upload',
isEnabled: () => true,
onAction: ($event) => this.importDevices($event)
}
);
return actions;
}
importDevices($event: Event) {
if ($event) {
$event.stopPropagation();
}
// TODO:
}
onDeviceAction(action: EntityAction<Device | DeviceInfo>): boolean {
makePublic($event: Event, device: Device) {
if ($event) {
$event.stopPropagation();
}
this.dialogService.confirm(
this.translate.instant('device.make-public-device-title', {deviceName: device.name}),
this.translate.instant('device.make-public-device-text'),
this.translate.instant('action.no'),
this.translate.instant('action.yes'),
true
).subscribe((res) => {
if (res) {
this.deviceService.makeDevicePublic(device.id.id).subscribe(
() => {
this.config.table.updateData();
}
);
}
}
);
}
assignToCustomer($event: Event, device: Device) {
if ($event) {
$event.stopPropagation();
}
// TODO:
}
unassignFromCustomer($event: Event, device: DeviceInfo) {
if ($event) {
$event.stopPropagation();
}
const isPublic = device.customerIsPublic;
let title;
let content;
if (isPublic) {
title = this.translate.instant('device.make-private-device-title', {deviceName: device.name});
content = this.translate.instant('device.make-private-device-text');
} else {
title = this.translate.instant('device.unassign-device-title', {deviceName: device.name});
content = this.translate.instant('device.unassign-device-text');
}
this.dialogService.confirm(
this.translate.instant(title),
this.translate.instant(content),
this.translate.instant('action.no'),
this.translate.instant('action.yes'),
true
).subscribe((res) => {
if (res) {
this.deviceService.unassignDeviceFromCustomer(device.id.id).subscribe(
() => {
this.config.table.updateData();
}
);
}
}
);
}
manageCredentials($event: Event, device: Device) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<DeviceCredentialsDialogComponent, DeviceCredentialsDialogData,
DeviceCredentials>(DeviceCredentialsDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
deviceId: device.id.id,
isReadOnly: this.config.componentsData.deviceScope === 'customer_user'
}
});
}
onDeviceAction(action: EntityAction<DeviceInfo>): boolean {
switch (action.action) {
case 'makePublic':
this.makePublic(action.event, action.entity);
return true;
case 'assignToCustomer':
this.assignToCustomer(action.event, action.entity);
return true;
case 'unassignFromCustomer':
this.unassignFromCustomer(action.event, action.entity);
return true;
case 'manageCredentials':
this.manageCredentials(action.event, action.entity);
return true;
}
return false;
}

View File

@ -14,26 +14,24 @@
/// limitations under the License.
///
import { Component, OnInit } from '@angular/core';
import { UserService } from '@core/http/user.service';
import { User } from '@shared/models/user.model';
import { Authority } from '@shared/models/authority.enum';
import { PageComponent } from '@shared/components/page.component';
import { select, Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { getCurrentAuthUser, selectAuthUser } from '@core/auth/auth.selectors';
import { mergeMap, take } from 'rxjs/operators';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
import { ActionAuthUpdateUserDetails } from '@core/auth/auth.actions';
import { environment as env } from '@env/environment';
import { TranslateService } from '@ngx-translate/core';
import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions';
import { ChangePasswordDialogComponent } from '@modules/home/pages/profile/change-password-dialog.component';
import { MatDialog } from '@angular/material';
import { DialogService } from '@core/services/dialog.service';
import { AuthService } from '@core/auth/auth.service';
import { ActivatedRoute } from '@angular/router';
import {Component, OnInit} from '@angular/core';
import {UserService} from '@core/http/user.service';
import {User} from '@shared/models/user.model';
import {Authority} from '@shared/models/authority.enum';
import {PageComponent} from '@shared/components/page.component';
import {Store} from '@ngrx/store';
import {AppState} from '@core/core.state';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {HasConfirmForm} from '@core/guards/confirm-on-exit.guard';
import {ActionAuthUpdateUserDetails} from '@core/auth/auth.actions';
import {environment as env} from '@env/environment';
import {TranslateService} from '@ngx-translate/core';
import {ActionSettingsChangeLanguage} from '@core/settings/settings.actions';
import {ChangePasswordDialogComponent} from '@modules/home/pages/profile/change-password-dialog.component';
import {MatDialog} from '@angular/material';
import {DialogService} from '@core/services/dialog.service';
import {AuthService} from '@core/auth/auth.service';
import {ActivatedRoute} from '@angular/router';
@Component({
selector: 'tb-profile',

View File

@ -122,6 +122,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
cellActionDescriptors: Array<CellActionDescriptor<T>> = [];
groupActionDescriptors: Array<GroupActionDescriptor<T>> = [];
headerActionDescriptors: Array<HeaderActionDescriptor> = [];
addActionDescriptors: Array<HeaderActionDescriptor> = [];
headerComponent: Type<EntityTableHeaderComponent<T>>;
addEntity: CreateEntityOperation<T> = null;
detailsReadonly: EntityBooleanFunction<T> = () => false;

View File

@ -42,11 +42,32 @@
asButton historyOnly></tb-timewindow>
<tb-anchor #entityTableHeader></tb-anchor>
<span fxFlex *ngIf="!this.entitiesTableConfig.headerComponent"></span>
<button mat-button mat-icon-button [disabled]="isLoading$ | async" [fxShow]="addEnabled()" (click)="addEntity($event)"
matTooltip="{{ translations.add | translate }}"
matTooltipPosition="above">
<mat-icon>add</mat-icon>
</button>
<div [fxShow]="addEnabled()">
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
*ngIf="!this.entitiesTableConfig.addActionDescriptors.length; else addActions"
(click)="addEntity($event)"
matTooltip="{{ translations.add | translate }}"
matTooltipPosition="above">
<mat-icon>add</mat-icon>
</button>
<ng-template #addActions>
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ translations.add | translate }}"
matTooltipPosition="above"
[matMenuTriggerFor]="addActionsMenu">
<mat-icon>add</mat-icon>
</button>
<mat-menu #addActionsMenu="matMenu" xPosition="before">
<button mat-menu-item *ngFor="let actionDescriptor of this.entitiesTableConfig.addActionDescriptors"
[disabled]="isLoading$ | async"
[fxShow]="actionDescriptor.isEnabled()"
(click)="actionDescriptor.onAction($event)">
<mat-icon>{{actionDescriptor.icon}}</mat-icon>
<span>{{ actionDescriptor.name }}</span>
</button>
</mat-menu>
</ng-template>
</div>
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
[fxShow]="actionDescriptor.isEnabled()" *ngFor="let actionDescriptor of headerActionDescriptors"
matTooltip="{{ actionDescriptor.name }}"

View File

@ -0,0 +1,43 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-form-field [formGroup]="selectEntityFormGroup" class="mat-block">
<input matInput type="text" placeholder="{{ entityText | translate }}"
#entityInput
formControlName="entity"
[required]="required"
[matAutocomplete]="entityAutocomplete">
<button *ngIf="selectEntityFormGroup.get('entity').value && !disabled"
type="button"
matSuffix mat-button mat-icon-button aria-label="Clear"
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete #entityAutocomplete="matAutocomplete" [displayWith]="displayEntityFn">
<mat-option *ngFor="let entity of filteredEntities | async" [value]="entity">
<span [innerHTML]="entity.name | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null">
<span>
{{ translate.get(noEntitiesMatchingText, {entity: searchText}) | async }}
</span>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="selectEntityFormGroup.get('entity').hasError('required')">
{{ entityRequiredText | translate }}
</mat-error>
</mat-form-field>

View File

@ -0,0 +1,274 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild} from '@angular/core';
import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms';
import {Observable} from 'rxjs';
import {map, mergeMap, startWith, tap} from 'rxjs/operators';
import {Store} from '@ngrx/store';
import {AppState} from '@app/core/core.state';
import {TranslateService} from '@ngx-translate/core';
import {AliasEntityType, EntityType} from '@shared/models/entity-type.models';
import {BaseData} from '@shared/models/base-data';
import {EntityId} from '@shared/models/id/entity-id';
import {EntityService} from '@core/http/entity.service';
@Component({
selector: 'tb-entity-autocomplete',
templateUrl: './entity-autocomplete.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EntityAutocompleteComponent),
multi: true
}]
})
export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit {
selectEntityFormGroup: FormGroup;
modelValue: string | null;
entityTypeValue: EntityType | AliasEntityType;
entitySubtypeValue: string;
@Input()
set entityType(entityType: EntityType) {
if (this.entityTypeValue !== entityType) {
this.entityTypeValue = entityType;
this.load();
}
}
@Input()
set entitySubtype(entitySubtype: string) {
if (this.entitySubtypeValue !== entitySubtype) {
this.entitySubtypeValue = entitySubtype;
const currentEntity = this.getCurrentEntity();
if (currentEntity) {
if ((currentEntity as any).type !== this.entitySubtypeValue) {
this.reset();
}
}
}
}
@Input()
excludeEntityIds: Array<string>;
@Input()
required: boolean;
@Input()
disabled: boolean;
@ViewChild('entityInput', {static: true}) entityInput: ElementRef;
entityText: string;
noEntitiesMatchingText: string;
entityRequiredText: string;
filteredEntities: Observable<Array<BaseData<EntityId>>>;
private searchText = '';
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
public translate: TranslateService,
private entityService: EntityService,
private fb: FormBuilder) {
this.selectEntityFormGroup = this.fb.group({
entity: [null]
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.filteredEntities = this.selectEntityFormGroup.get('dashboard').valueChanges
.pipe(
tap(value => {
let modelValue;
if (typeof value === 'string' || !value) {
modelValue = null;
} else {
modelValue = value.id.id;
}
this.updateView(modelValue);
}),
startWith<string | BaseData<EntityId>>(''),
map(value => value ? (typeof value === 'string' ? value : value.name) : ''),
mergeMap(name => this.fetchEntities(name) )
);
}
ngAfterViewInit(): void {}
load(): void {
if (this.entityTypeValue) {
switch (this.entityTypeValue) {
case EntityType.ASSET:
this.entityText = 'asset.asset';
this.noEntitiesMatchingText = 'asset.no-assets-matching';
this.entityRequiredText = 'asset.asset-required';
break;
case EntityType.DEVICE:
this.entityText = 'device.device';
this.noEntitiesMatchingText = 'device.no-devices-matching';
this.entityRequiredText = 'device.device-required';
break;
case EntityType.ENTITY_VIEW:
this.entityText = 'entity-view.entity-view';
this.noEntitiesMatchingText = 'entity-view.no-entity-views-matching';
this.entityRequiredText = 'entity-view.entity-view-required';
break;
case EntityType.RULE_CHAIN:
this.entityText = 'rulechain.rulechain';
this.noEntitiesMatchingText = 'rulechain.no-rulechains-matching';
this.entityRequiredText = 'rulechain.rulechain-required';
break;
case EntityType.TENANT:
this.entityText = 'tenant.tenant';
this.noEntitiesMatchingText = 'tenant.no-tenants-matching';
this.entityRequiredText = 'tenant.tenant-required';
break;
case EntityType.CUSTOMER:
this.entityText = 'customer.customer';
this.noEntitiesMatchingText = 'customer.no-customers-matching';
this.entityRequiredText = 'customer.customer-required';
break;
case EntityType.USER:
this.entityText = 'user.user';
this.noEntitiesMatchingText = 'user.no-users-matching';
this.entityRequiredText = 'user.user-required';
break;
case EntityType.DASHBOARD:
this.entityText = 'dashboard.dashboard';
this.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
this.entityRequiredText = 'dashboard.dashboard-required';
break;
case EntityType.ALARM:
this.entityText = 'alarm.alarm';
this.noEntitiesMatchingText = 'alarm.no-alarms-matching';
this.entityRequiredText = 'alarm.alarm-required';
break;
case AliasEntityType.CURRENT_CUSTOMER:
this.entityText = 'customer.default-customer';
this.noEntitiesMatchingText = 'customer.no-customers-matching';
this.entityRequiredText = 'customer.default-customer-required';
break;
}
}
const currentEntity = this.getCurrentEntity();
if (currentEntity) {
const currentEntityType = currentEntity.id.entityType;
if (this.entityTypeValue && currentEntityType !== this.entityTypeValue) {
this.reset();
}
}
}
getCurrentEntity(): BaseData<EntityId> | null {
const currentEntity = this.selectEntityFormGroup.get('entity').value;
if (currentEntity && typeof currentEntity !== 'string') {
return currentEntity as BaseData<EntityId>;
} else {
return null;
}
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
writeValue(value: string | null): void {
this.searchText = '';
if (value != null) {
let targetEntityType = this.entityTypeValue;
if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) {
targetEntityType = EntityType.CUSTOMER;
}
this.entityService.getEntity(targetEntityType, value).subscribe(
(entity) => {
this.modelValue = entity.id.id;
this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true});
}
);
} else {
this.modelValue = null;
this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true});
}
}
reset() {
this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true});
}
updateView(value: string | null) {
if (this.modelValue !== value) {
this.modelValue = value;
this.propagateChange(this.modelValue);
}
}
displayEntityFn(entity?: BaseData<EntityId>): string | undefined {
return entity ? entity.name : undefined;
}
fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
this.searchText = searchText;
let targetEntityType = this.entityTypeValue;
if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) {
targetEntityType = EntityType.CUSTOMER;
}
return this.entityService.getEntitiesByNameFilter(targetEntityType, searchText,
50, this.entitySubtypeValue, false, true).pipe(
map((data) => {
if (data) {
if (this.excludeEntityIds && this.excludeEntityIds.length) {
const entities: Array<BaseData<EntityId>> = [];
data.forEach((entity) => {
if (this.excludeEntityIds.indexOf(entity.id.id) === -1) {
entities.push(entity);
}
});
return entities;
} else {
return data;
}
} else {
return [];
}
}
));
}
clear() {
this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true});
setTimeout(() => {
this.entityInput.nativeElement.blur();
this.entityInput.nativeElement.focus();
}, 0);
}
}

View File

@ -0,0 +1,51 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {BaseData} from '@shared/models/base-data';
import {AssetId} from '@shared/models/id/asset-id';
import {TenantId} from '@shared/models/id/tenant-id';
import {CustomerId} from '@shared/models/id/customer-id';
import {AlarmId} from '@shared/models/id/alarm-id';
import {EntityId} from '@shared/models/id/entity-id';
export enum AlarmSeverity {
CRITICAL = 'CRITICAL',
MAJOR = 'MAJOR',
MINOR = 'MINOR',
WARNING = 'WARNING',
INDETERMINATE = 'INDETERMINATE'
}
export enum AlarmStatus {
ACTIVE_UNACK = 'ACTIVE_UNACK',
ACTIVE_ACK = 'ACTIVE_ACK',
CLEARED_UNACK = 'CLEARED_UNACK',
CLEARED_ACK = 'CLEARED_ACK'
}
export interface Alarm extends BaseData<AlarmId> {
tenantId: TenantId;
type: string;
originator: EntityId;
severity: AlarmSeverity;
status: AlarmStatus;
startTs: number;
endTs: number;
ackTs: number;
clearTs: number;
propagate: boolean;
details?: any;
}

View File

@ -0,0 +1,34 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {BaseData} from '@shared/models/base-data';
import {AssetId} from './id/asset-id';
import {TenantId} from '@shared/models/id/tenant-id';
import {CustomerId} from '@shared/models/id/customer-id';
import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id';
export interface Asset extends BaseData<AssetId> {
tenantId: TenantId;
customerId: CustomerId;
name: string;
type: string;
additionalInfo?: any;
}
export interface AssetInfo extends Asset {
customerTitle: string;
customerIsPublic: boolean;
}

View File

@ -18,6 +18,7 @@ import {BaseData} from '@shared/models/base-data';
import {DeviceId} from './id/device-id';
import {TenantId} from '@shared/models/id/tenant-id';
import {CustomerId} from '@shared/models/id/customer-id';
import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id';
export interface Device extends BaseData<DeviceId> {
tenantId: TenantId;
@ -32,3 +33,22 @@ export interface DeviceInfo extends Device {
customerTitle: string;
customerIsPublic: boolean;
}
export enum DeviceCredentialsType {
ACCESS_TOKEN = 'ACCESS_TOKEN',
X509_CERTIFICATE = 'X509_CERTIFICATE'
}
export const credentialTypeNames = new Map<DeviceCredentialsType, string>(
[
[DeviceCredentialsType.ACCESS_TOKEN, 'Access token'],
[DeviceCredentialsType.X509_CERTIFICATE, 'X.509 Certificate'],
]
);
export interface DeviceCredentials extends BaseData<DeviceCredentialsId> {
deviceId: DeviceId;
credentialsType: DeviceCredentialsType;
credentialsId: string;
credentialsValue: string;
}

View File

@ -31,6 +31,10 @@ export enum EntityType {
WIDGET_TYPE = 'WIDGET_TYPE'
}
export enum AliasEntityType {
CURRENT_CUSTOMER = 'CURRENT_CUSTOMER'
}
export interface EntityTypeTranslation {
type: string;
typePlural: string;

View File

@ -0,0 +1,50 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {BaseData} from '@shared/models/base-data';
import {AssetId} from './id/asset-id';
import {TenantId} from '@shared/models/id/tenant-id';
import {CustomerId} from '@shared/models/id/customer-id';
import {EntityViewId} from '@shared/models/id/entity-view-id';
import {EntityId} from '@shared/models/id/entity-id';
export interface AttributesEntityView {
cs: Array<string>;
ss: Array<string>;
sh: Array<string>;
}
export interface TelemetryEntityView {
timeseries: Array<string>;
attributes: AttributesEntityView;
}
export interface EntityView extends BaseData<EntityViewId> {
tenantId: TenantId;
customerId: CustomerId;
entityId: EntityId;
name: string;
type: string;
keys: TelemetryEntityView;
startTimeMs: number;
endTimeMs: number;
additionalInfo?: any;
}
export interface EntityViewInfo extends EntityView {
customerTitle: string;
customerIsPublic: boolean;
}

View File

@ -0,0 +1,26 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { EntityId } from './entity-id';
import { EntityType } from '@shared/models/entity-type.models';
export class AlarmId implements EntityId {
entityType = EntityType.ALARM;
id: string;
constructor(id: string) {
this.id = id;
}
}

View File

@ -0,0 +1,26 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { EntityId } from './entity-id';
import { EntityType } from '@shared/models/entity-type.models';
export class AssetId implements EntityId {
entityType = EntityType.ASSET;
id: string;
constructor(id: string) {
this.id = id;
}
}

View File

@ -0,0 +1,24 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { HasUUID } from '@shared/models/id/has-uuid';
export class DeviceCredentialsId implements HasUUID {
id: string;
constructor(id: string) {
this.id = id;
}
}

View File

@ -0,0 +1,26 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { EntityId } from './entity-id';
import { EntityType } from '@shared/models/entity-type.models';
export class EntityViewId implements EntityId {
entityType = EntityType.ENTITY_VIEW;
id: string;
constructor(id: string) {
this.id = id;
}
}

View File

@ -0,0 +1,26 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { EntityId } from './entity-id';
import { EntityType } from '@shared/models/entity-type.models';
export class RuleChainId implements EntityId {
entityType = EntityType.RULE_CHAIN;
id: string;
constructor(id: string) {
this.id = id;
}
}

View File

@ -0,0 +1,26 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { EntityId } from './entity-id';
import { EntityType } from '@shared/models/entity-type.models';
export class RuleNodeId implements EntityId {
entityType = EntityType.RULE_NODE;
id: string;
constructor(id: string) {
this.id = id;
}
}

View File

@ -0,0 +1,26 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { EntityId } from './entity-id';
import { EntityType } from '@shared/models/entity-type.models';
export class WidgetTypeId implements EntityId {
entityType = EntityType.WIDGET_TYPE;
id: string;
constructor(id: string) {
this.id = id;
}
}

View File

@ -0,0 +1,26 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { EntityId } from './entity-id';
import { EntityType } from '@shared/models/entity-type.models';
export class WidgetsBundleId implements EntityId {
entityType = EntityType.WIDGETS_BUNDLE;
id: string;
constructor(id: string) {
this.id = id;
}
}

View File

@ -0,0 +1,37 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {BaseData} from '@shared/models/base-data';
import {AssetId} from '@shared/models/id/asset-id';
import {TenantId} from '@shared/models/id/tenant-id';
import {CustomerId} from '@shared/models/id/customer-id';
import {RuleChainId} from '@shared/models/id/rule-chain-id';
import {RuleNodeId} from '@shared/models/id/rule-node-id';
export interface RuleChainConfiguration {
todo: Array<any>;
// TODO:
}
export interface RuleChain extends BaseData<RuleChainId> {
tenantId: TenantId;
name: string;
firstRuleNodeId: RuleNodeId;
root: boolean;
debugMode: boolean;
configuration: RuleChainConfiguration;
additionalInfo?: any;
}

View File

@ -0,0 +1,36 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {BaseData} from '@shared/models/base-data';
import {AssetId} from '@shared/models/id/asset-id';
import {TenantId} from '@shared/models/id/tenant-id';
import {CustomerId} from '@shared/models/id/customer-id';
import {RuleChainId} from '@shared/models/id/rule-chain-id';
import {RuleNodeId} from '@shared/models/id/rule-node-id';
export interface RuleNodeConfiguration {
todo: Array<any>;
// TODO:
}
export interface RuleNode extends BaseData<RuleNodeId> {
ruleChainId: RuleChainId;
type: string;
name: string;
debugMode: boolean;
configuration: RuleNodeConfiguration;
additionalInfo?: any;
}

View File

@ -0,0 +1,33 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {BaseData} from '@shared/models/base-data';
import {TenantId} from '@shared/models/id/tenant-id';
import {WidgetsBundleId} from '@shared/models/id/widgets-bundle-id';
import {WidgetTypeId} from '@shared/models/id/widget-type-id';
export interface WidgetTypeDescriptor {
todo: Array<any>;
// TODO:
}
export interface WidgetType extends BaseData<WidgetTypeId> {
tenantId: TenantId;
bundleAlias: string;
alias: string;
name: string;
descriptor: WidgetTypeDescriptor;
}

View File

@ -0,0 +1,26 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {BaseData} from '@shared/models/base-data';
import {TenantId} from '@shared/models/id/tenant-id';
import {WidgetsBundleId} from '@shared/models/id/widgets-bundle-id';
export interface WidgetsBundle extends BaseData<WidgetsBundleId> {
tenantId: TenantId;
alias: string;
title: string;
image: string;
}

View File

@ -80,6 +80,7 @@ import { HighlightPipe } from '@shared/pipe/highlight.pipe';
import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component';
import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/entity-subtype-autocomplete.component';
import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component';
import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component';
@NgModule({
providers: [
@ -122,6 +123,7 @@ import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-s
DashboardAutocompleteComponent,
EntitySubTypeAutocompleteComponent,
EntitySubTypeSelectComponent,
EntityAutocompleteComponent,
NospacePipe,
MillisecondsToTimeStringPipe,
EnumToArrayPipe,
@ -189,6 +191,7 @@ import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-s
DashboardAutocompleteComponent,
EntitySubTypeAutocompleteComponent,
EntitySubTypeSelectComponent,
EntityAutocompleteComponent,
// ValueInputComponent,
MatButtonModule,
MatCheckboxModule,