Commit fea3c368a360c8314290b20c405d008471241f72

Authored by Igor Kulikov
1 parent 9d4b79c9

Implement Events table

Showing 34 changed files with 990 additions and 67 deletions
... ... @@ -53,10 +53,11 @@ public class EventController extends BaseController {
53 53 @RequestParam("tenantId") String strTenantId,
54 54 @RequestParam int pageSize,
55 55 @RequestParam int page,
  56 + @RequestParam(required = false) String textSearch,
  57 + @RequestParam(required = false) String sortProperty,
  58 + @RequestParam(required = false) String sortOrder,
56 59 @RequestParam(required = false) Long startTime,
57   - @RequestParam(required = false) Long endTime,
58   - @RequestParam(required = false, defaultValue = "false") boolean ascOrder
59   - ) throws ThingsboardException {
  60 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
60 61 checkParameter("EntityId", strEntityId);
61 62 checkParameter("EntityType", strEntityType);
62 63 try {
... ... @@ -64,8 +65,7 @@ public class EventController extends BaseController {
64 65
65 66 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
66 67 checkEntityId(entityId, Operation.READ);
67   - TimePageLink pageLink = createTimePageLink(pageSize, page, "",
68   - "createdTime", ascOrder ? "asc" : "desc", startTime, endTime);
  68 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
69 69 return checkNotNull(eventService.findEvents(tenantId, entityId, eventType, pageLink));
70 70 } catch (Exception e) {
71 71 throw handleException(e);
... ... @@ -81,10 +81,11 @@ public class EventController extends BaseController {
81 81 @RequestParam("tenantId") String strTenantId,
82 82 @RequestParam int pageSize,
83 83 @RequestParam int page,
  84 + @RequestParam(required = false) String textSearch,
  85 + @RequestParam(required = false) String sortProperty,
  86 + @RequestParam(required = false) String sortOrder,
84 87 @RequestParam(required = false) Long startTime,
85   - @RequestParam(required = false) Long endTime,
86   - @RequestParam(required = false, defaultValue = "false") boolean ascOrder
87   - ) throws ThingsboardException {
  88 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
88 89 checkParameter("EntityId", strEntityId);
89 90 checkParameter("EntityType", strEntityType);
90 91 try {
... ... @@ -93,8 +94,7 @@ public class EventController extends BaseController {
93 94 EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
94 95 checkEntityId(entityId, Operation.READ);
95 96
96   - TimePageLink pageLink = createTimePageLink(pageSize, page, "",
97   - "createdTime", ascOrder ? "asc" : "desc", startTime, endTime);
  97 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
98 98
99 99 return checkNotNull(eventService.findEvents(tenantId, entityId, pageLink));
100 100 } catch (Exception e) {
... ...
... ... @@ -15,10 +15,10 @@
15 15 */
16 16 package org.thingsboard.server.dao.sql.event;
17 17
  18 +import org.springframework.data.domain.Page;
18 19 import org.springframework.data.domain.Pageable;
19   -import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
20 20 import org.springframework.data.jpa.repository.Query;
21   -import org.springframework.data.repository.CrudRepository;
  21 +import org.springframework.data.repository.PagingAndSortingRepository;
22 22 import org.springframework.data.repository.query.Param;
23 23 import org.thingsboard.server.common.data.EntityType;
24 24 import org.thingsboard.server.dao.model.sql.EventEntity;
... ... @@ -30,7 +30,7 @@ import java.util.List;
30 30 * Created by Valerii Sosliuk on 5/3/2017.
31 31 */
32 32 @SqlDao
33   -public interface EventRepository extends CrudRepository<EventEntity, String>, JpaSpecificationExecutor<EventEntity> {
  33 +public interface EventRepository extends PagingAndSortingRepository<EventEntity, String> {
34 34
35 35 EventEntity findByTenantIdAndEntityTypeAndEntityIdAndEventTypeAndEventUid(String tenantId,
36 36 EntityType entityType,
... ... @@ -51,4 +51,34 @@ public interface EventRepository extends CrudRepository<EventEntity, String>, Jp
51 51 @Param("eventType") String eventType,
52 52 Pageable pageable);
53 53
  54 + @Query("SELECT e FROM EventEntity e WHERE " +
  55 + "e.tenantId = :tenantId " +
  56 + "AND e.entityType = :entityType AND e.entityId = :entityId " +
  57 + "AND (:startId IS NULL OR e.id >= :startId) " +
  58 + "AND (:endId IS NULL OR e.id <= :endId) " +
  59 + "AND LOWER(e.eventType) LIKE LOWER(CONCAT(:textSearch, '%'))"
  60 + )
  61 + Page<EventEntity> findEventsByTenantIdAndEntityId(@Param("tenantId") String tenantId,
  62 + @Param("entityType") EntityType entityType,
  63 + @Param("entityId") String entityId,
  64 + @Param("textSearch") String textSearch,
  65 + @Param("startId") String startId,
  66 + @Param("endId") String endId,
  67 + Pageable pageable);
  68 +
  69 + @Query("SELECT e FROM EventEntity e WHERE " +
  70 + "e.tenantId = :tenantId " +
  71 + "AND e.entityType = :entityType AND e.entityId = :entityId " +
  72 + "AND e.eventType = :eventType " +
  73 + "AND (:startId IS NULL OR e.id >= :startId) " +
  74 + "AND (:endId IS NULL OR e.id <= :endId)"
  75 + )
  76 + Page<EventEntity> findEventsByTenantIdAndEntityIdAndEventType(@Param("tenantId") String tenantId,
  77 + @Param("entityType") EntityType entityType,
  78 + @Param("entityId") String entityId,
  79 + @Param("eventType") String eventType,
  80 + @Param("startId") String startId,
  81 + @Param("endId") String endId,
  82 + Pageable pageable);
  83 +
54 84 }
... ...
... ... @@ -21,8 +21,6 @@ import lombok.extern.slf4j.Slf4j;
21 21 import org.apache.commons.lang3.StringUtils;
22 22 import org.springframework.beans.factory.annotation.Autowired;
23 23 import org.springframework.data.domain.PageRequest;
24   -import org.springframework.data.domain.Pageable;
25   -import org.springframework.data.domain.Sort;
26 24 import org.springframework.data.jpa.domain.Specification;
27 25 import org.springframework.data.repository.CrudRepository;
28 26 import org.springframework.stereotype.Component;
... ... @@ -40,13 +38,11 @@ import org.thingsboard.server.dao.sql.JpaAbstractSearchTimeDao;
40 38 import org.thingsboard.server.dao.util.SqlDao;
41 39
42 40 import javax.persistence.criteria.Predicate;
43   -import java.util.ArrayList;
44   -import java.util.List;
45   -import java.util.Optional;
46   -import java.util.UUID;
  41 +import java.util.*;
47 42
48   -import static org.springframework.data.jpa.domain.Specifications.where;
49   -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
  43 +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
  44 +import static org.thingsboard.server.dao.DaoUtil.endTimeToId;
  45 +import static org.thingsboard.server.dao.DaoUtil.startTimeToId;
50 46 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
51 47
52 48 /**
... ... @@ -109,15 +105,30 @@ public class JpaBaseEventDao extends JpaAbstractSearchTimeDao<EventEntity, Event
109 105
110 106 @Override
111 107 public PageData<Event> findEvents(UUID tenantId, EntityId entityId, TimePageLink pageLink) {
112   - return findEvents(tenantId, entityId, null, pageLink);
  108 + return DaoUtil.toPageData(
  109 + eventRepository
  110 + .findEventsByTenantIdAndEntityId(
  111 + fromTimeUUID(tenantId),
  112 + entityId.getEntityType(),
  113 + fromTimeUUID(entityId.getId()),
  114 + Objects.toString(pageLink.getTextSearch(), ""),
  115 + startTimeToId(pageLink.getStartTime()),
  116 + endTimeToId(pageLink.getEndTime()),
  117 + DaoUtil.toPageable(pageLink)));
113 118 }
114 119
115 120 @Override
116 121 public PageData<Event> findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink) {
117   - Specification<EventEntity> timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id");
118   - Specification<EventEntity> fieldsSpec = getEntityFieldsSpec(tenantId, entityId, eventType);
119   - Pageable pageable = DaoUtil.toPageable(pageLink);
120   - return DaoUtil.toPageData(eventRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable));
  122 + return DaoUtil.toPageData(
  123 + eventRepository
  124 + .findEventsByTenantIdAndEntityIdAndEventType(
  125 + fromTimeUUID(tenantId),
  126 + entityId.getEntityType(),
  127 + fromTimeUUID(entityId.getId()),
  128 + eventType,
  129 + startTimeToId(pageLink.getStartTime()),
  130 + endTimeToId(pageLink.getEndTime()),
  131 + DaoUtil.toPageable(pageLink)));
121 132 }
122 133
123 134 @Override
... ...
  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 { defaultHttpOptions } from './http-utils';
  19 +import { Observable } from 'rxjs/index';
  20 +import { HttpClient } from '@angular/common/http';
  21 +import { TimePageLink } from '@shared/models/page/page-link';
  22 +import { PageData } from '@shared/models/page/page-data';
  23 +import { EntityId } from '@shared/models/id/entity-id';
  24 +import { DebugEventType, Event, EventType } from '@shared/models/event.models';
  25 +
  26 +@Injectable({
  27 + providedIn: 'root'
  28 +})
  29 +export class EventService {
  30 +
  31 + constructor(
  32 + private http: HttpClient
  33 + ) { }
  34 +
  35 + public getEvents(entityId: EntityId, eventType: EventType | DebugEventType, tenantId: string, pageLink: TimePageLink,
  36 + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<Event>> {
  37 + return this.http.get<PageData<Event>>(`/api/events/${entityId.entityType}/${entityId.id}/${eventType}` +
  38 + `${pageLink.toQuery()}&tenantId=${tenantId}`,
  39 + defaultHttpOptions(ignoreLoading, ignoreErrors));
  40 + }
  41 +}
... ...
... ... @@ -37,14 +37,15 @@
37 37 <mat-toolbar class="mat-table-toolbar" [fxShow]="!textSearchMode && dataSource.selection.isEmpty()">
38 38 <div class="mat-toolbar-tools">
39 39 <span *ngIf="entitiesTableConfig.tableTitle" class="tb-entity-table-title">{{ entitiesTableConfig.tableTitle }}</span>
  40 + <tb-anchor #entityTableHeader></tb-anchor>
40 41 <tb-timewindow *ngIf="entitiesTableConfig.useTimePageLink" [(ngModel)]="timewindow"
41 42 (ngModelChange)="onTimewindowChange()"
42 43 asButton historyOnly></tb-timewindow>
43   - <tb-anchor #entityTableHeader></tb-anchor>
44   - <span fxFlex *ngIf="!this.entitiesTableConfig.headerComponent"></span>
  44 + <span fxFlex *ngIf="!entitiesTableConfig.headerComponent ||
  45 + entitiesTableConfig.useTimePageLink"></span>
45 46 <div [fxShow]="addEnabled()">
46 47 <button mat-button mat-icon-button [disabled]="isLoading$ | async"
47   - *ngIf="!this.entitiesTableConfig.addActionDescriptors.length; else addActions"
  48 + *ngIf="!entitiesTableConfig.addActionDescriptors.length; else addActions"
48 49 (click)="addEntity($event)"
49 50 matTooltip="{{ translations.add | translate }}"
50 51 matTooltipPosition="above">
... ... @@ -52,12 +53,12 @@
52 53 </button>
53 54 <ng-template #addActions>
54 55 <button mat-button mat-icon-button [disabled]="isLoading$ | async"
55   - *ngIf="this.entitiesTableConfig.addActionDescriptors.length === 1; else addActionsMenu"
56   - [fxShow]="this.entitiesTableConfig.addActionDescriptors[0].isEnabled()"
57   - (click)="this.entitiesTableConfig.addActionDescriptors[0].onAction($event)"
58   - matTooltip="{{ this.entitiesTableConfig.addActionDescriptors[0].name }}"
  56 + *ngIf="entitiesTableConfig.addActionDescriptors.length === 1; else addActionsMenu"
  57 + [fxShow]="entitiesTableConfig.addActionDescriptors[0].isEnabled()"
  58 + (click)="entitiesTableConfig.addActionDescriptors[0].onAction($event)"
  59 + matTooltip="{{ entitiesTableConfig.addActionDescriptors[0].name }}"
59 60 matTooltipPosition="above">
60   - <mat-icon>{{this.entitiesTableConfig.addActionDescriptors[0].icon}}</mat-icon>
  61 + <mat-icon>{{entitiesTableConfig.addActionDescriptors[0].icon}}</mat-icon>
61 62 </button>
62 63 <ng-template #addActionsMenu>
63 64 <button mat-button mat-icon-button [disabled]="isLoading$ | async"
... ... @@ -67,7 +68,7 @@
67 68 <mat-icon>add</mat-icon>
68 69 </button>
69 70 <mat-menu #addActionsMenu="matMenu" xPosition="before">
70   - <button mat-menu-item *ngFor="let actionDescriptor of this.entitiesTableConfig.addActionDescriptors"
  71 + <button mat-menu-item *ngFor="let actionDescriptor of entitiesTableConfig.addActionDescriptors"
71 72 [disabled]="isLoading$ | async"
72 73 [fxShow]="actionDescriptor.isEnabled()"
73 74 (click)="actionDescriptor.onAction($event)">
... ... @@ -152,10 +153,19 @@
152 153 </mat-cell>
153 154 </ng-container>
154 155 <ng-container [matColumnDef]="column.key" *ngFor="let column of columns; trackBy: trackByColumnKey; let col = index">
155   - <mat-header-cell *matHeaderCellDef [ngStyle]="{maxWidth: column.maxWidth}" mat-sort-header [disabled]="!column.sortable"> {{ column.title | translate }} </mat-header-cell>
  156 + <mat-header-cell *matHeaderCellDef [ngStyle]="headerCellStyle(column, col)" mat-sort-header [disabled]="!column.sortable"> {{ column.title | translate }} </mat-header-cell>
156 157 <mat-cell *matCellDef="let entity; let row = index"
157 158 [innerHTML]="cellContent(entity, column, row, col)"
158   - [ngStyle]="cellStyle(entity, column, row, col)"></mat-cell>
  159 + [ngStyle]="cellStyle(entity, column, row, col)">
  160 + <button *ngIf="column.actionDescriptor; let actionDescriptor" mat-button mat-icon-button [disabled]="isLoading$ | async"
  161 + [fxShow]="actionDescriptor.isEnabled(entity)"
  162 + matTooltip="{{ actionDescriptor.nameFunction ? actionDescriptor.nameFunction(entity) : actionDescriptor.name }}"
  163 + matTooltipPosition="above"
  164 + (click)="actionDescriptor.onAction($event, entity)">
  165 + <mat-icon [svgIcon]="actionDescriptor.mdiIcon" [ngStyle]="actionDescriptor.style">
  166 + {{actionDescriptor.icon}}</mat-icon>
  167 + </button>
  168 + </mat-cell>
159 169 </ng-container>
160 170 <ng-container matColumnDef="actions" stickyEnd>
161 171 <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (cellActionDescriptors.length * 40) + 'px', maxWidth: (cellActionDescriptors.length * 40) + 'px' }">
... ...
... ... @@ -42,7 +42,8 @@ import {
42 42 EntityTableColumn,
43 43 EntityTableConfig,
44 44 GroupActionDescriptor,
45   - HeaderActionDescriptor
  45 + HeaderActionDescriptor,
  46 + EntityColumn
46 47 } from '@home/models/entity/entities-table-config.models';
47 48 import { EntityTypeTranslation } from '@shared/models/entity-type.models';
48 49 import { DialogService } from '@core/services/dialog.service';
... ... @@ -72,8 +73,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
72 73 groupActionDescriptors: Array<GroupActionDescriptor<BaseData<HasId>>>;
73 74 cellActionDescriptors: Array<CellActionDescriptor<BaseData<HasId>>>;
74 75
75   - columns: Array<EntityTableColumn<BaseData<HasId>>>;
76   - displayedColumns: string[] = [];
  76 + columns: Array<EntityColumn<BaseData<HasId>>>;
  77 + displayedColumns: string[];
  78 +
  79 + headerCellStyleCache: Array<any> = [];
77 80
78 81 cellContentCache: Array<SafeHtml> = [];
79 82
... ... @@ -143,22 +146,12 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
143 146 }
144 147 );
145 148
146   - this.columns = [...this.entitiesTableConfig.columns];
147   -
148 149 const enabledGroupActionDescriptors =
149 150 this.groupActionDescriptors.filter((descriptor) => descriptor.isEnabled);
150 151
151 152 this.selectionEnabled = this.entitiesTableConfig.selectionEnabled && enabledGroupActionDescriptors.length;
152 153
153   - if (this.selectionEnabled) {
154   - this.displayedColumns.push('select');
155   - }
156   - this.columns.forEach(
157   - (column) => {
158   - this.displayedColumns.push(column.key);
159   - }
160   - );
161   - this.displayedColumns.push('actions');
  154 + this.columnsUpdated();
162 155
163 156 const sortOrder: SortOrder = { property: this.entitiesTableConfig.defaultSortOrder.property,
164 157 direction: this.entitiesTableConfig.defaultSortOrder.direction };
... ... @@ -235,6 +228,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
235 228 }
236 229
237 230 private dataLoaded() {
  231 + this.headerCellStyleCache.length = 0;
238 232 this.cellContentCache.length = 0;
239 233 this.cellStyleCache.length = 0;
240 234 }
... ... @@ -365,21 +359,65 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
365 359 }
366 360 }
367 361
368   - cellContent(entity: BaseData<HasId>, column: EntityTableColumn<BaseData<HasId>>, row: number, col: number) {
369   - const index = row * this.columns.length + col;
370   - let res = this.cellContentCache[index];
  362 + columnsUpdated(resetData: boolean = false) {
  363 + this.columns = [...this.entitiesTableConfig.columns];
  364 +
  365 + this.displayedColumns = [];
  366 +
  367 + if (this.selectionEnabled) {
  368 + this.displayedColumns.push('select');
  369 + }
  370 + this.columns.forEach(
  371 + (column) => {
  372 + this.displayedColumns.push(column.key);
  373 + }
  374 + );
  375 + this.displayedColumns.push('actions');
  376 + this.headerCellStyleCache.length = 0;
  377 + this.cellContentCache.length = 0;
  378 + this.cellStyleCache.length = 0;
  379 + if (resetData) {
  380 + this.dataSource.reset();
  381 + }
  382 + }
  383 +
  384 + headerCellStyle(column: EntityColumn<BaseData<HasId>>, col: number) {
  385 + const index = col;
  386 + let res = this.headerCellStyleCache[index];
371 387 if (!res) {
372   - res = this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key));
373   - this.cellContentCache[index] = res;
  388 + if (column instanceof EntityTableColumn) {
  389 + res = {...column.headerCellStyleFunction(column.key), ...{maxWidth: column.maxWidth}};
  390 + } else {
  391 + res = {maxWidth: column.maxWidth};
  392 + }
  393 + this.headerCellStyleCache[index] = res;
374 394 }
375 395 return res;
376 396 }
377 397
378   - cellStyle(entity: BaseData<HasId>, column: EntityTableColumn<BaseData<HasId>>, row: number, col: number) {
  398 + cellContent(entity: BaseData<HasId>, column: EntityColumn<BaseData<HasId>>, row: number, col: number) {
  399 + if (column instanceof EntityTableColumn) {
  400 + const index = row * this.columns.length + col;
  401 + let res = this.cellContentCache[index];
  402 + if (!res) {
  403 + res = this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key));
  404 + this.cellContentCache[index] = res;
  405 + }
  406 + return res;
  407 + } else {
  408 + return null;
  409 + }
  410 + }
  411 +
  412 + cellStyle(entity: BaseData<HasId>, column: EntityColumn<BaseData<HasId>>, row: number, col: number) {
379 413 const index = row * this.columns.length + col;
380 414 let res = this.cellStyleCache[index];
381 415 if (!res) {
382   - res = {...column.cellStyleFunction(entity, column.key), ...{maxWidth: column.maxWidth}};
  416 + if (column instanceof EntityTableColumn) {
  417 + res = {...column.cellStyleFunction(entity, column.key), ...{maxWidth: column.maxWidth}};
  418 + } else {
  419 + res = {maxWidth: column.maxWidth};
  420 + }
383 421 this.cellStyleCache[index] = res;
384 422 }
385 423 return res;
... ...
... ... @@ -37,6 +37,8 @@ import { selectAuthUser, getCurrentAuthUser } from '@core/auth/auth.selectors';
37 37 import { AuthUser } from '@shared/models/user.model';
38 38 import { EntityType } from '@shared/models/entity-type.models';
39 39 import { AuditLogMode } from '@shared/models/audit-log.models';
  40 +import { DebugEventType, EventType } from '@shared/models/event.models';
  41 +import { NULL_UUID } from '@shared/models/id/has-uuid';
40 42
41 43 export abstract class EntityTabsComponent<T extends BaseData<HasId>> extends PageComponent implements OnInit, AfterViewInit {
42 44
... ... @@ -46,8 +48,14 @@ export abstract class EntityTabsComponent<T extends BaseData<HasId>> extends Pag
46 48
47 49 auditLogModes = AuditLogMode;
48 50
  51 + eventTypes = EventType;
  52 +
  53 + debugEventTypes = DebugEventType;
  54 +
49 55 authUser: AuthUser;
50 56
  57 + nullUid = NULL_UUID;
  58 +
51 59 entityValue: T;
52 60
53 61 @ViewChildren(MatTab) entityTabs: QueryList<MatTab>;
... ...
  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 {
  18 + DateEntityTableColumn,
  19 + EntityActionTableColumn,
  20 + EntityTableColumn,
  21 + EntityTableConfig
  22 +} from '@home/models/entity/entities-table-config.models';
  23 +import { DebugEventType, Event, EventType } from '@shared/models/event.models';
  24 +import { TimePageLink } from '@shared/models/page/page-link';
  25 +import { TranslateService } from '@ngx-translate/core';
  26 +import { DatePipe } from '@angular/common';
  27 +import { MatDialog } from '@angular/material/dialog';
  28 +import { EntityId } from '@shared/models/id/entity-id';
  29 +import { EventService } from '@app/core/http/event.service';
  30 +import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component';
  31 +import { EntityTypeResource } from '@shared/models/entity-type.models';
  32 +import { Observable } from 'rxjs';
  33 +import { PageData } from '@shared/models/page/page-data';
  34 +import { Direction } from '@shared/models/page/sort-order';
  35 +import { MsgDataType } from '@shared/models/rule-node.models';
  36 +import { DialogService } from '@core/services/dialog.service';
  37 +
  38 +export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
  39 +
  40 + eventTypeValue: EventType | DebugEventType;
  41 +
  42 + set eventType(eventType: EventType | DebugEventType) {
  43 + if (this.eventTypeValue !== eventType) {
  44 + this.eventTypeValue = eventType;
  45 + this.updateColumns(true);
  46 + }
  47 + }
  48 +
  49 + get eventType(): EventType | DebugEventType {
  50 + return this.eventTypeValue;
  51 + }
  52 +
  53 + eventTypes: Array<EventType | DebugEventType>;
  54 +
  55 + constructor(private eventService: EventService,
  56 + private dialogService: DialogService,
  57 + private translate: TranslateService,
  58 + private datePipe: DatePipe,
  59 + private dialog: MatDialog,
  60 + public entityId: EntityId,
  61 + public tenantId: string,
  62 + private defaultEventType: EventType | DebugEventType,
  63 + private disabledEventTypes: Array<EventType | DebugEventType> = null,
  64 + private debugEventTypes: Array<DebugEventType> = null) {
  65 + super();
  66 + this.loadDataOnInit = false;
  67 + this.tableTitle = '';
  68 + this.useTimePageLink = true;
  69 + this.detailsPanelEnabled = false;
  70 + this.selectionEnabled = false;
  71 + this.searchEnabled = false;
  72 + this.addEnabled = false;
  73 + this.entitiesDeleteEnabled = false;
  74 +
  75 + this.headerComponent = EventTableHeaderComponent;
  76 +
  77 + this.eventTypes = Object.keys(EventType).map(type => EventType[type]);
  78 +
  79 + if (disabledEventTypes && disabledEventTypes.length) {
  80 + this.eventTypes = this.eventTypes.filter(type => disabledEventTypes.indexOf(type) === -1);
  81 + }
  82 +
  83 + if (debugEventTypes && debugEventTypes.length) {
  84 + this.eventTypes = [...this.eventTypes, ...debugEventTypes];
  85 + }
  86 +
  87 + this.eventTypeValue = defaultEventType;
  88 +
  89 + this.entityTranslations = {
  90 + noEntities: 'event.no-events-prompt'
  91 + };
  92 + this.entityResources = {
  93 + } as EntityTypeResource;
  94 + this.entitiesFetchFunction = pageLink => this.fetchEvents(pageLink);
  95 +
  96 + this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
  97 +
  98 + this.updateColumns();
  99 + }
  100 +
  101 + fetchEvents(pageLink: TimePageLink): Observable<PageData<Event>> {
  102 + return this.eventService.getEvents(this.entityId, this.eventType, this.tenantId, pageLink);
  103 + }
  104 +
  105 + updateColumns(updateTableColumns: boolean = false): void {
  106 + this.columns = [];
  107 + this.columns.push(
  108 + new DateEntityTableColumn<Event>('createdTime', 'event.event-time', this.datePipe, '150px'),
  109 + new EntityTableColumn<Event>('server', 'event.server', '150px',
  110 + (entity) => entity.body.server, entity => ({}), false));
  111 + switch (this.eventType) {
  112 + case EventType.ERROR:
  113 + this.columns.push(
  114 + new EntityTableColumn<Event>('method', 'event.method', '100%',
  115 + (entity) => entity.body.method, entity => ({}), false),
  116 + new EntityActionTableColumn<Event>('error', 'event.error',
  117 + {
  118 + name: this.translate.instant('action.view'),
  119 + icon: 'more_horiz',
  120 + isEnabled: (entity) => entity.body.error && entity.body.error.length > 0,
  121 + onAction: ($event, entity) => this.showContent($event, entity.body.error, 'event.error')
  122 + },
  123 + '100px')
  124 + );
  125 + break;
  126 + case EventType.LC_EVENT:
  127 + this.columns.push(
  128 + new EntityTableColumn<Event>('method', 'event.event', '100%',
  129 + (entity) => entity.body.event, entity => ({}), false),
  130 + new EntityTableColumn<Event>('status', 'event.status', '100%',
  131 + (entity) =>
  132 + this.translate.instant(entity.body.success ? 'event.success' : 'event.failed'), entity => ({}), false),
  133 + new EntityActionTableColumn<Event>('error', 'event.error',
  134 + {
  135 + name: this.translate.instant('action.view'),
  136 + icon: 'more_horiz',
  137 + isEnabled: (entity) => entity.body.error && entity.body.error.length > 0,
  138 + onAction: ($event, entity) => this.showContent($event, entity.body.error, 'event.error')
  139 + },
  140 + '100px')
  141 + );
  142 + break;
  143 + case EventType.STATS:
  144 + this.columns.push(
  145 + new EntityTableColumn<Event>('messagesProcessed', 'event.messages-processed', '100%',
  146 + (entity) => entity.body.messagesProcessed + '',
  147 + entity => ({justifyContent: 'flex-end'}),
  148 + false,
  149 + key => ({justifyContent: 'flex-end'})),
  150 + new EntityTableColumn<Event>('errorsOccurred', 'event.errors-occurred', '100%',
  151 + (entity) => entity.body.errorsOccurred + '',
  152 + entity => ({justifyContent: 'flex-end'}),
  153 + false,
  154 + key => ({justifyContent: 'flex-end'}))
  155 + );
  156 + break;
  157 + case DebugEventType.DEBUG_RULE_NODE:
  158 + case DebugEventType.DEBUG_RULE_CHAIN:
  159 + this.columns.push(
  160 + new EntityTableColumn<Event>('type', 'event.type', '100%',
  161 + (entity) => entity.body.type, entity => ({}), false),
  162 + new EntityTableColumn<Event>('entity', 'event.entity', '100%',
  163 + (entity) => entity.body.entityName, entity => ({}), false),
  164 + new EntityTableColumn<Event>('msgId', 'event.message-id', '100%',
  165 + (entity) => entity.body.msgId, entity => ({}), false),
  166 + new EntityTableColumn<Event>('msgType', 'event.message-type', '100%',
  167 + (entity) => entity.body.msgType, entity => ({}), false),
  168 + new EntityTableColumn<Event>('relationType', 'event.relation-type', '100%',
  169 + (entity) => entity.body.relationType, entity => ({}), false),
  170 + new EntityActionTableColumn<Event>('data', 'event.data',
  171 + {
  172 + name: this.translate.instant('action.view'),
  173 + icon: 'more_horiz',
  174 + isEnabled: (entity) => entity.body.data && entity.body.data.length > 0,
  175 + onAction: ($event, entity) => this.showContent($event, entity.body.data,
  176 + 'event.data', entity.body.dataType)
  177 + },
  178 + '60px'),
  179 + new EntityActionTableColumn<Event>('metadata', 'event.metadata',
  180 + {
  181 + name: this.translate.instant('action.view'),
  182 + icon: 'more_horiz',
  183 + isEnabled: (entity) => entity.body.metadata && entity.body.metadata.length > 0,
  184 + onAction: ($event, entity) => this.showContent($event, entity.body.metadata,
  185 + 'event.metadata', MsgDataType.JSON)
  186 + },
  187 + '60px'),
  188 + new EntityActionTableColumn<Event>('error', 'event.error',
  189 + {
  190 + name: this.translate.instant('action.view'),
  191 + icon: 'more_horiz',
  192 + isEnabled: (entity) => entity.body.error && entity.body.error.length > 0,
  193 + onAction: ($event, entity) => this.showContent($event, entity.body.error,
  194 + 'event.error')
  195 + },
  196 + '60px')
  197 + );
  198 + break;
  199 + }
  200 + if (updateTableColumns) {
  201 + this.table.columnsUpdated(true);
  202 + }
  203 + }
  204 +
  205 + showContent($event: MouseEvent, content: string, title: string, contentType: MsgDataType = null): void {
  206 + if ($event) {
  207 + $event.stopPropagation();
  208 + }
  209 + // TODO:
  210 + this.dialogService.todo();
  211 + }
  212 +}
... ...
  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 +
  19 +<mat-form-field class="mat-block" style="width: 200px;">
  20 + <mat-label translate>event.event-type</mat-label>
  21 + <mat-select matInput [ngModel]="eventTableConfig.eventType"
  22 + (ngModelChange)="eventTypeChanged($event)">
  23 + <mat-option *ngFor="let type of eventTableConfig.eventTypes" [value]="type">
  24 + {{ eventTypeTranslationsMap.get(type) | translate }}
  25 + </mat-option>
  26 + </mat-select>
  27 +</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 +:host {
  17 +/* flex: 1;
  18 + display: flex;
  19 + justify-content: flex-start; */
  20 + padding-right: 8px;
  21 +}
  22 +
  23 +:host ::ng-deep {
  24 + mat-form-field {
  25 + font-size: 16px;
  26 +
  27 + .mat-form-field-wrapper {
  28 + padding-bottom: 0;
  29 + }
  30 +
  31 + .mat-form-field-underline {
  32 + bottom: 0;
  33 + }
  34 + }
  35 +}
... ...
  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 } from '@angular/core';
  18 +import { Store } from '@ngrx/store';
  19 +import { AppState } from '@core/core.state';
  20 +import { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component';
  21 +import { DebugEventType, Event, EventType, eventTypeTranslations } from '@app/shared/models/event.models';
  22 +import { EventTableConfig } from '@home/components/event/event-table-config';
  23 +
  24 +@Component({
  25 + selector: 'tb-event-table-header',
  26 + templateUrl: './event-table-header.component.html',
  27 + styleUrls: ['./event-table-header.component.scss']
  28 +})
  29 +export class EventTableHeaderComponent extends EntityTableHeaderComponent<Event> {
  30 +
  31 + eventTypeTranslationsMap = eventTypeTranslations;
  32 +
  33 + get eventTableConfig(): EventTableConfig {
  34 + return this.entitiesTableConfig as EventTableConfig;
  35 + }
  36 +
  37 + constructor(protected store: Store<AppState>) {
  38 + super(store);
  39 + }
  40 +
  41 + eventTypeChanged(eventType: EventType | DebugEventType) {
  42 + this.eventTableConfig.eventType = eventType;
  43 + this.eventTableConfig.table.updateData();
  44 + }
  45 +}
... ...
  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 +<tb-entities-table [entitiesTableConfig]="eventTableConfig"></tb-entities-table>
... ...
  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 +:host ::ng-deep {
  17 + tb-entities-table {
  18 + .mat-drawer-container {
  19 + background-color: white;
  20 + }
  21 + }
  22 +}
... ...
  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, Input, OnInit, ViewChild } from '@angular/core';
  18 +import { AuditLogService } from '@core/http/audit-log.service';
  19 +import { TranslateService } from '@ngx-translate/core';
  20 +import { DatePipe } from '@angular/common';
  21 +import { MatDialog } from '@angular/material';
  22 +import { AuditLogMode } from '@shared/models/audit-log.models';
  23 +import { EntityId } from '@shared/models/id/entity-id';
  24 +import { UserId } from '@shared/models/id/user-id';
  25 +import { CustomerId } from '@shared/models/id/customer-id';
  26 +import { AuditLogTableConfig } from '@home/components/audit-log/audit-log-table-config';
  27 +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
  28 +import { Store } from '@ngrx/store';
  29 +import { AppState } from '@core/core.state';
  30 +import { Authority } from '@shared/models/authority.enum';
  31 +import { getCurrentAuthUser } from '@core/auth/auth.selectors';
  32 +import { EventTableConfig } from './event-table-config';
  33 +import { EventService } from '@core/http/event.service';
  34 +import { DialogService } from '@core/services/dialog.service';
  35 +import { DebugEventType, EventType } from '@shared/models/event.models';
  36 +
  37 +@Component({
  38 + selector: 'tb-event-table',
  39 + templateUrl: './event-table.component.html',
  40 + styleUrls: ['./event-table.component.scss']
  41 +})
  42 +export class EventTableComponent implements OnInit {
  43 +
  44 + @Input()
  45 + tenantId: string;
  46 +
  47 + @Input()
  48 + defaultEventType: EventType | DebugEventType;
  49 +
  50 + @Input()
  51 + disabledEventTypes: Array<EventType | DebugEventType>;
  52 +
  53 + @Input()
  54 + debugEventTypes: Array<DebugEventType>;
  55 +
  56 + activeValue = false;
  57 + dirtyValue = false;
  58 + entityIdValue: EntityId;
  59 +
  60 + @Input()
  61 + set active(active: boolean) {
  62 + if (this.activeValue !== active) {
  63 + this.activeValue = active;
  64 + if (this.activeValue && this.dirtyValue) {
  65 + this.dirtyValue = false;
  66 + this.entitiesTable.updateData();
  67 + }
  68 + }
  69 + }
  70 +
  71 + @Input()
  72 + set entityId(entityId: EntityId) {
  73 + this.entityIdValue = entityId;
  74 + if (this.eventTableConfig && this.eventTableConfig.entityId !== entityId) {
  75 + this.eventTableConfig.eventType = this.defaultEventType;
  76 + this.eventTableConfig.entityId = entityId;
  77 + this.entitiesTable.resetSortAndFilter(this.activeValue);
  78 + if (!this.activeValue) {
  79 + this.dirtyValue = true;
  80 + }
  81 + }
  82 + }
  83 +
  84 + @ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent;
  85 +
  86 + eventTableConfig: EventTableConfig;
  87 +
  88 + constructor(private eventService: EventService,
  89 + private dialogService: DialogService,
  90 + private translate: TranslateService,
  91 + private datePipe: DatePipe,
  92 + private dialog: MatDialog,
  93 + private store: Store<AppState>) {
  94 + }
  95 +
  96 + ngOnInit() {
  97 + this.dirtyValue = !this.activeValue;
  98 + this.eventTableConfig = new EventTableConfig(
  99 + this.eventService,
  100 + this.dialogService,
  101 + this.translate,
  102 + this.datePipe,
  103 + this.dialog,
  104 + this.entityIdValue,
  105 + this.tenantId,
  106 + this.defaultEventType,
  107 + this.disabledEventTypes,
  108 + this.debugEventTypes
  109 + );
  110 + }
  111 +
  112 +}
... ...
... ... @@ -24,11 +24,14 @@ import {EntityDetailsPanelComponent} from './entity/entity-details-panel.compone
24 24 import {ContactComponent} from './contact.component';
25 25 import { AuditLogDetailsDialogComponent } from './audit-log/audit-log-details-dialog.component';
26 26 import { AuditLogTableComponent } from './audit-log/audit-log-table.component';
  27 +import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component';
  28 +import { EventTableComponent } from '@home/components/event/event-table.component';
27 29
28 30 @NgModule({
29 31 entryComponents: [
30 32 AddEntityDialogComponent,
31   - AuditLogDetailsDialogComponent
  33 + AuditLogDetailsDialogComponent,
  34 + EventTableHeaderComponent
32 35 ],
33 36 declarations:
34 37 [
... ... @@ -38,7 +41,9 @@ import { AuditLogTableComponent } from './audit-log/audit-log-table.component';
38 41 EntityDetailsPanelComponent,
39 42 ContactComponent,
40 43 AuditLogTableComponent,
41   - AuditLogDetailsDialogComponent
  44 + AuditLogDetailsDialogComponent,
  45 + EventTableHeaderComponent,
  46 + EventTableComponent
42 47 ],
43 48 imports: [
44 49 CommonModule,
... ... @@ -50,7 +55,8 @@ import { AuditLogTableComponent } from './audit-log/audit-log-table.component';
50 55 DetailsPanelComponent,
51 56 EntityDetailsPanelComponent,
52 57 ContactComponent,
53   - AuditLogTableComponent
  58 + AuditLogTableComponent,
  59 + EventTableComponent
54 60 ]
55 61 })
56 62 export class HomeComponentsModule { }
... ...
... ... @@ -50,6 +50,13 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink =
50 50 this.pageDataSubject.complete();
51 51 }
52 52
  53 + reset() {
  54 + const pageData = emptyPageData<T>();
  55 + this.entitiesSubject.next(pageData.data);
  56 + this.pageDataSubject.next(pageData);
  57 + this.dataLoadedFunction();
  58 + }
  59 +
53 60 loadEntities(pageLink: P): Observable<PageData<T>> {
54 61 const result = new ReplaySubject<PageData<T>>();
55 62 this.fetchFunction(pageLink).pipe(
... ...
... ... @@ -41,6 +41,7 @@ export type EntityActionFunction<T extends BaseData<HasId>> = (action: EntityAct
41 41 export type CreateEntityOperation<T extends BaseData<HasId>> = () => Observable<T>;
42 42
43 43 export type CellContentFunction<T extends BaseData<HasId>> = (entity: T, key: string) => string;
  44 +export type HeaderCellStyleFunction<T extends BaseData<HasId>> = (key: string) => object;
44 45 export type CellStyleFunction<T extends BaseData<HasId>> = (entity: T, key: string) => object;
45 46
46 47 export interface CellActionDescriptor<T extends BaseData<HasId>> {
... ... @@ -67,13 +68,35 @@ export interface HeaderActionDescriptor {
67 68 onAction: ($event: MouseEvent) => void;
68 69 }
69 70
70   -export class EntityTableColumn<T extends BaseData<HasId>> {
  71 +export type EntityTableColumnType = 'content' | 'action';
  72 +
  73 +export class BaseEntityTableColumn<T extends BaseData<HasId>> {
  74 + constructor(public type: EntityTableColumnType,
  75 + public key: string,
  76 + public title: string,
  77 + public maxWidth: string = '100%',
  78 + public sortable: boolean = true) {
  79 + }
  80 +}
  81 +
  82 +export class EntityTableColumn<T extends BaseData<HasId>> extends BaseEntityTableColumn<T> {
71 83 constructor(public key: string,
72 84 public title: string,
73 85 public maxWidth: string = '100%',
74 86 public cellContentFunction: CellContentFunction<T> = (entity, property) => entity[property],
75 87 public cellStyleFunction: CellStyleFunction<T> = () => ({}),
76   - public sortable: boolean = true) {
  88 + public sortable: boolean = true,
  89 + public headerCellStyleFunction: HeaderCellStyleFunction<T> = () => ({})) {
  90 + super('content', key, title, maxWidth, sortable);
  91 + }
  92 +}
  93 +
  94 +export class EntityActionTableColumn<T extends BaseData<HasId>> extends BaseEntityTableColumn<T> {
  95 + constructor(public key: string,
  96 + public title: string,
  97 + public actionDescriptor: CellActionDescriptor<T>,
  98 + public maxWidth: string = '100%') {
  99 + super('action', key, title, maxWidth, false);
77 100 }
78 101 }
79 102
... ... @@ -92,6 +115,8 @@ export class DateEntityTableColumn<T extends BaseData<HasId>> extends EntityTabl
92 115 }
93 116 }
94 117
  118 +export type EntityColumn<T extends BaseData<HasId>> = EntityTableColumn<T> | EntityActionTableColumn<T>;
  119 +
95 120 export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = PageLink> {
96 121
97 122 constructor() {}
... ... @@ -116,7 +141,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
116 141 entityTabsComponent: Type<EntityTabsComponent<T>>;
117 142 addDialogStyle = {};
118 143 defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC};
119   - columns: Array<EntityTableColumn<T>> = [];
  144 + columns: Array<EntityColumn<T>> = [];
120 145 cellActionDescriptors: Array<CellActionDescriptor<T>> = [];
121 146 groupActionDescriptors: Array<GroupActionDescriptor<T>> = [];
122 147 headerActionDescriptors: Array<HeaderActionDescriptor> = [];
... ...
... ... @@ -15,6 +15,11 @@
15 15 limitations under the License.
16 16
17 17 -->
  18 +<mat-tab *ngIf="entity"
  19 + label="{{ 'asset.events' | translate }}" #eventsTab="matTab">
  20 + <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
  21 + [entityId]="entity.id"></tb-event-table>
  22 +</mat-tab>
18 23 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
19 24 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
20 25 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>
... ...
... ... @@ -15,6 +15,11 @@
15 15 limitations under the License.
16 16
17 17 -->
  18 +<mat-tab *ngIf="entity"
  19 + label="{{ 'customer.events' | translate }}" #eventsTab="matTab">
  20 + <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
  21 + [entityId]="entity.id"></tb-event-table>
  22 +</mat-tab>
18 23 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
19 24 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
20 25 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.CUSTOMER" [customerId]="entity.id" detailsMode="true"></tb-audit-log-table>
... ...
... ... @@ -15,6 +15,11 @@
15 15 limitations under the License.
16 16
17 17 -->
  18 +<mat-tab *ngIf="entity"
  19 + label="{{ 'device.events' | translate }}" #eventsTab="matTab">
  20 + <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
  21 + [entityId]="entity.id"></tb-event-table>
  22 +</mat-tab>
18 23 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
19 24 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
20 25 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>
... ...
... ... @@ -15,6 +15,11 @@
15 15 limitations under the License.
16 16
17 17 -->
  18 +<mat-tab *ngIf="entity"
  19 + label="{{ 'entity-view.events' | translate }}" #eventsTab="matTab">
  20 + <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
  21 + [entityId]="entity.id"></tb-event-table>
  22 +</mat-tab>
18 23 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
19 24 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
20 25 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>
... ...
... ... @@ -15,6 +15,14 @@
15 15 limitations under the License.
16 16
17 17 -->
  18 +<mat-tab *ngIf="entity"
  19 + label="{{ 'rulechain.events' | translate }}" #eventsTab="matTab">
  20 + <tb-event-table [active]="eventsTab.isActive"
  21 + [debugEventTypes]="[debugEventTypes.DEBUG_RULE_CHAIN]"
  22 + [defaultEventType]="debugEventTypes.DEBUG_RULE_CHAIN"
  23 + [tenantId]="entity.tenantId.id"
  24 + [entityId]="entity.id"></tb-event-table>
  25 +</mat-tab>
18 26 <mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
19 27 label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
20 28 <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>
... ...
  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-tab *ngIf="entity"
  19 + label="{{ 'tenant.events' | translate }}" #eventsTab="matTab">
  20 + <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid"
  21 + [entityId]="entity.id"></tb-event-table>
  22 +</mat-tab>
... ...
  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 } from '@angular/core';
  18 +import { Store } from '@ngrx/store';
  19 +import { AppState } from '@core/core.state';
  20 +import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
  21 +import { Tenant } from '@shared/models/tenant.model';
  22 +
  23 +@Component({
  24 + selector: 'tb-tenant-tabs',
  25 + templateUrl: './tenant-tabs.component.html',
  26 + styleUrls: []
  27 +})
  28 +export class TenantTabsComponent extends EntityTabsComponent<Tenant> {
  29 +
  30 + constructor(protected store: Store<AppState>) {
  31 + super(store);
  32 + }
  33 +
  34 + ngOnInit() {
  35 + super.ngOnInit();
  36 + }
  37 +
  38 +}
... ...
... ... @@ -20,13 +20,16 @@ import { SharedModule } from '@shared/shared.module';
20 20 import {TenantComponent} from '@modules/home/pages/tenant/tenant.component';
21 21 import {TenantRoutingModule} from '@modules/home/pages/tenant/tenant-routing.module';
22 22 import {HomeComponentsModule} from '@modules/home/components/home-components.module';
  23 +import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component';
23 24
24 25 @NgModule({
25 26 entryComponents: [
26   - TenantComponent
  27 + TenantComponent,
  28 + TenantTabsComponent
27 29 ],
28 30 declarations: [
29   - TenantComponent
  31 + TenantComponent,
  32 + TenantTabsComponent
30 33 ],
31 34 imports: [
32 35 CommonModule,
... ...
... ... @@ -35,6 +35,7 @@ import {
35 35 import { TenantComponent } from '@modules/home/pages/tenant/tenant.component';
36 36 import { EntityAction } from '@home/models/entity/entity-component.models';
37 37 import { User } from '@shared/models/user.model';
  38 +import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component';
38 39
39 40 @Injectable()
40 41 export class TenantsTableConfigResolver implements Resolve<EntityTableConfig<Tenant>> {
... ... @@ -48,6 +49,7 @@ export class TenantsTableConfigResolver implements Resolve<EntityTableConfig<Ten
48 49
49 50 this.config.entityType = EntityType.TENANT;
50 51 this.config.entityComponent = TenantComponent;
  52 + this.config.entityTabsComponent = TenantTabsComponent;
51 53 this.config.entityTranslations = entityTypeTranslations.get(EntityType.TENANT);
52 54 this.config.entityResources = entityTypeResources.get(EntityType.TENANT);
53 55
... ...
  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-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
  19 + label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
  20 + <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.USER" [userId]="entity.id" detailsMode="true"></tb-audit-log-table>
  21 +</mat-tab>
... ...
  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 } from '@angular/core';
  18 +import { Store } from '@ngrx/store';
  19 +import { AppState } from '@core/core.state';
  20 +import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
  21 +import { User } from '@app/shared/models/user.model';
  22 +
  23 +@Component({
  24 + selector: 'tb-user-tabs',
  25 + templateUrl: './user-tabs.component.html',
  26 + styleUrls: []
  27 +})
  28 +export class UserTabsComponent extends EntityTabsComponent<User> {
  29 +
  30 + constructor(protected store: Store<AppState>) {
  31 + super(store);
  32 + }
  33 +
  34 + ngOnInit() {
  35 + super.ngOnInit();
  36 + }
  37 +
  38 +}
... ...
... ... @@ -22,15 +22,18 @@ import { UserRoutingModule } from '@modules/home/pages/user/user-routing.module'
22 22 import { AddUserDialogComponent } from '@modules/home/pages/user/add-user-dialog.component';
23 23 import { ActivationLinkDialogComponent } from '@modules/home/pages/user/activation-link-dialog.component';
24 24 import {HomeComponentsModule} from '@modules/home/components/home-components.module';
  25 +import { UserTabsComponent } from '@home/pages/user/user-tabs.component';
25 26
26 27 @NgModule({
27 28 entryComponents: [
28 29 UserComponent,
  30 + UserTabsComponent,
29 31 AddUserDialogComponent,
30 32 ActivationLinkDialogComponent
31 33 ],
32 34 declarations: [
33 35 UserComponent,
  36 + UserTabsComponent,
34 37 AddUserDialogComponent,
35 38 ActivationLinkDialogComponent
36 39 ],
... ...
... ... @@ -57,6 +57,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
57 57 import { Customer } from '@shared/models/customer.model';
58 58 import {TenantService} from '@app/core/http/tenant.service';
59 59 import {TenantId} from '@app/shared/models/id/tenant-id';
  60 +import { UserTabsComponent } from '@home/pages/user/user-tabs.component';
60 61
61 62 export interface UsersTableRouteData {
62 63 authority: Authority;
... ... @@ -83,6 +84,7 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
83 84
84 85 this.config.entityType = EntityType.USER;
85 86 this.config.entityComponent = UserComponent;
  87 + this.config.entityTabsComponent = UserTabsComponent;
86 88 this.config.entityTranslations = entityTypeTranslations.get(EntityType.USER);
87 89 this.config.entityResources = entityTypeResources.get(EntityType.USER);
88 90
... ...
  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 { ActionStatus, ActionType } from '@shared/models/audit-log.models';
  18 +import { BaseData } from '@shared/models/base-data';
  19 +import { AuditLogId } from '@shared/models/id/audit-log-id';
  20 +import { TenantId } from '@shared/models/id/tenant-id';
  21 +import { CustomerId } from '@shared/models/id/customer-id';
  22 +import { EntityId } from '@shared/models/id/entity-id';
  23 +import { UserId } from '@shared/models/id/user-id';
  24 +import { EventId } from './id/event-id';
  25 +import { MsgDataType } from './rule-node.models';
  26 +
  27 +export enum EventType {
  28 + ERROR = 'ERROR',
  29 + LC_EVENT = 'LC_EVENT',
  30 + STATS = 'STATS'
  31 +}
  32 +
  33 +export enum DebugEventType {
  34 + DEBUG_RULE_NODE = 'DEBUG_RULE_NODE',
  35 + DEBUG_RULE_CHAIN = 'DEBUG_RULE_CHAIN'
  36 +}
  37 +
  38 +export const eventTypeTranslations = new Map<EventType | DebugEventType, string>(
  39 + [
  40 + [EventType.ERROR, 'event.type-error'],
  41 + [EventType.LC_EVENT, 'event.type-lc-event'],
  42 + [EventType.STATS, 'event.type-stats'],
  43 + [DebugEventType.DEBUG_RULE_NODE, 'event.type-debug-rule-node'],
  44 + [DebugEventType.DEBUG_RULE_CHAIN, 'event.type-debug-rule-chain'],
  45 + ]
  46 +);
  47 +
  48 +export interface BaseEventBody {
  49 + server: string;
  50 +}
  51 +
  52 +export interface ErrorEventBody extends BaseEventBody {
  53 + method: string;
  54 + error: string;
  55 +}
  56 +
  57 +export interface LcEventEventBody extends BaseEventBody {
  58 + event: string;
  59 + success: boolean;
  60 + error: string;
  61 +}
  62 +
  63 +export interface StatsEventBody extends BaseEventBody {
  64 + messagesProcessed: number;
  65 + errorsOccurred: number;
  66 +}
  67 +
  68 +export interface DebugRuleNodeEventBody extends BaseEventBody {
  69 + type: string;
  70 + entityId: string;
  71 + entityName: string;
  72 + msgId: string;
  73 + msgType: string;
  74 + relationType: string;
  75 + dataType: MsgDataType;
  76 + data: string;
  77 + metadata: string;
  78 + error: string;
  79 +}
  80 +
  81 +export type EventBody = ErrorEventBody & LcEventEventBody & StatsEventBody & DebugRuleNodeEventBody;
  82 +
  83 +export interface Event extends BaseData<EventId> {
  84 + tenantId: TenantId;
  85 + entityId: EntityId;
  86 + type: string;
  87 + uid: string;
  88 + body: EventBody;
  89 +}
... ...
  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 EventId implements HasUUID {
  20 + id: string;
  21 + constructor(id: string) {
  22 + this.id = id;
  23 + }
  24 +}
... ...
... ... @@ -21,6 +21,12 @@ import {CustomerId} from '@shared/models/id/customer-id';
21 21 import {RuleChainId} from '@shared/models/id/rule-chain-id';
22 22 import {RuleNodeId} from '@shared/models/id/rule-node-id';
23 23
  24 +export enum MsgDataType {
  25 + JSON = 'JSON',
  26 + TEXT = 'TEXT',
  27 + BINARY = 'BINARY'
  28 +}
  29 +
24 30 export interface RuleNodeConfiguration {
25 31 todo: Array<any>;
26 32 // TODO:
... ...
... ... @@ -308,7 +308,7 @@ $tb-dark-theme: get-tb-dark-theme(
308 308 }
309 309
310 310 .mat-cell, .mat-header-cell {
311   - min-width: 80px;
  311 + min-width: 40px;
312 312 &:last-child {
313 313 padding: 0 12px 0 0;
314 314 }
... ...