Implement Events table

This commit is contained in:
Igor Kulikov 2019-08-22 18:44:48 +03:00
parent 9d4b79c91c
commit fea3c368a3
34 changed files with 990 additions and 67 deletions

View File

@ -53,10 +53,11 @@ public class EventController extends BaseController {
@RequestParam("tenantId") String strTenantId,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false, defaultValue = "false") boolean ascOrder
) throws ThingsboardException {
@RequestParam(required = false) Long endTime) throws ThingsboardException {
checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType);
try {
@ -64,8 +65,7 @@ public class EventController extends BaseController {
EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
checkEntityId(entityId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, "",
"createdTime", ascOrder ? "asc" : "desc", startTime, endTime);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(eventService.findEvents(tenantId, entityId, eventType, pageLink));
} catch (Exception e) {
throw handleException(e);
@ -81,10 +81,11 @@ public class EventController extends BaseController {
@RequestParam("tenantId") String strTenantId,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false, defaultValue = "false") boolean ascOrder
) throws ThingsboardException {
@RequestParam(required = false) Long endTime) throws ThingsboardException {
checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType);
try {
@ -93,8 +94,7 @@ public class EventController extends BaseController {
EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
checkEntityId(entityId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, "",
"createdTime", ascOrder ? "asc" : "desc", startTime, endTime);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(eventService.findEvents(tenantId, entityId, pageLink));
} catch (Exception e) {

View File

@ -15,10 +15,10 @@
*/
package org.thingsboard.server.dao.sql.event;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.dao.model.sql.EventEntity;
@ -30,7 +30,7 @@ import java.util.List;
* Created by Valerii Sosliuk on 5/3/2017.
*/
@SqlDao
public interface EventRepository extends CrudRepository<EventEntity, String>, JpaSpecificationExecutor<EventEntity> {
public interface EventRepository extends PagingAndSortingRepository<EventEntity, String> {
EventEntity findByTenantIdAndEntityTypeAndEntityIdAndEventTypeAndEventUid(String tenantId,
EntityType entityType,
@ -51,4 +51,34 @@ public interface EventRepository extends CrudRepository<EventEntity, String>, Jp
@Param("eventType") String eventType,
Pageable pageable);
@Query("SELECT e FROM EventEntity e WHERE " +
"e.tenantId = :tenantId " +
"AND e.entityType = :entityType AND e.entityId = :entityId " +
"AND (:startId IS NULL OR e.id >= :startId) " +
"AND (:endId IS NULL OR e.id <= :endId) " +
"AND LOWER(e.eventType) LIKE LOWER(CONCAT(:textSearch, '%'))"
)
Page<EventEntity> findEventsByTenantIdAndEntityId(@Param("tenantId") String tenantId,
@Param("entityType") EntityType entityType,
@Param("entityId") String entityId,
@Param("textSearch") String textSearch,
@Param("startId") String startId,
@Param("endId") String endId,
Pageable pageable);
@Query("SELECT e FROM EventEntity e WHERE " +
"e.tenantId = :tenantId " +
"AND e.entityType = :entityType AND e.entityId = :entityId " +
"AND e.eventType = :eventType " +
"AND (:startId IS NULL OR e.id >= :startId) " +
"AND (:endId IS NULL OR e.id <= :endId)"
)
Page<EventEntity> findEventsByTenantIdAndEntityIdAndEventType(@Param("tenantId") String tenantId,
@Param("entityType") EntityType entityType,
@Param("entityId") String entityId,
@Param("eventType") String eventType,
@Param("startId") String startId,
@Param("endId") String endId,
Pageable pageable);
}

View File

@ -21,8 +21,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
@ -40,13 +38,11 @@ import org.thingsboard.server.dao.sql.JpaAbstractSearchTimeDao;
import org.thingsboard.server.dao.util.SqlDao;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.*;
import static org.springframework.data.jpa.domain.Specifications.where;
import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
import static org.thingsboard.server.dao.DaoUtil.endTimeToId;
import static org.thingsboard.server.dao.DaoUtil.startTimeToId;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
/**
@ -109,15 +105,30 @@ public class JpaBaseEventDao extends JpaAbstractSearchTimeDao<EventEntity, Event
@Override
public PageData<Event> findEvents(UUID tenantId, EntityId entityId, TimePageLink pageLink) {
return findEvents(tenantId, entityId, null, pageLink);
return DaoUtil.toPageData(
eventRepository
.findEventsByTenantIdAndEntityId(
fromTimeUUID(tenantId),
entityId.getEntityType(),
fromTimeUUID(entityId.getId()),
Objects.toString(pageLink.getTextSearch(), ""),
startTimeToId(pageLink.getStartTime()),
endTimeToId(pageLink.getEndTime()),
DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<Event> findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink) {
Specification<EventEntity> timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id");
Specification<EventEntity> fieldsSpec = getEntityFieldsSpec(tenantId, entityId, eventType);
Pageable pageable = DaoUtil.toPageable(pageLink);
return DaoUtil.toPageData(eventRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable));
return DaoUtil.toPageData(
eventRepository
.findEventsByTenantIdAndEntityIdAndEventType(
fromTimeUUID(tenantId),
entityId.getEntityType(),
fromTimeUUID(entityId.getId()),
eventType,
startTimeToId(pageLink.getStartTime()),
endTimeToId(pageLink.getEndTime()),
DaoUtil.toPageable(pageLink)));
}
@Override

View File

@ -0,0 +1,41 @@
///
/// 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 { defaultHttpOptions } from './http-utils';
import { Observable } from 'rxjs/index';
import { HttpClient } from '@angular/common/http';
import { TimePageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
import { EntityId } from '@shared/models/id/entity-id';
import { DebugEventType, Event, EventType } from '@shared/models/event.models';
@Injectable({
providedIn: 'root'
})
export class EventService {
constructor(
private http: HttpClient
) { }
public getEvents(entityId: EntityId, eventType: EventType | DebugEventType, tenantId: string, pageLink: TimePageLink,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<Event>> {
return this.http.get<PageData<Event>>(`/api/events/${entityId.entityType}/${entityId.id}/${eventType}` +
`${pageLink.toQuery()}&tenantId=${tenantId}`,
defaultHttpOptions(ignoreLoading, ignoreErrors));
}
}

View File

@ -37,14 +37,15 @@
<mat-toolbar class="mat-table-toolbar" [fxShow]="!textSearchMode && dataSource.selection.isEmpty()">
<div class="mat-toolbar-tools">
<span *ngIf="entitiesTableConfig.tableTitle" class="tb-entity-table-title">{{ entitiesTableConfig.tableTitle }}</span>
<tb-anchor #entityTableHeader></tb-anchor>
<tb-timewindow *ngIf="entitiesTableConfig.useTimePageLink" [(ngModel)]="timewindow"
(ngModelChange)="onTimewindowChange()"
asButton historyOnly></tb-timewindow>
<tb-anchor #entityTableHeader></tb-anchor>
<span fxFlex *ngIf="!this.entitiesTableConfig.headerComponent"></span>
<span fxFlex *ngIf="!entitiesTableConfig.headerComponent ||
entitiesTableConfig.useTimePageLink"></span>
<div [fxShow]="addEnabled()">
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
*ngIf="!this.entitiesTableConfig.addActionDescriptors.length; else addActions"
*ngIf="!entitiesTableConfig.addActionDescriptors.length; else addActions"
(click)="addEntity($event)"
matTooltip="{{ translations.add | translate }}"
matTooltipPosition="above">
@ -52,12 +53,12 @@
</button>
<ng-template #addActions>
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
*ngIf="this.entitiesTableConfig.addActionDescriptors.length === 1; else addActionsMenu"
[fxShow]="this.entitiesTableConfig.addActionDescriptors[0].isEnabled()"
(click)="this.entitiesTableConfig.addActionDescriptors[0].onAction($event)"
matTooltip="{{ this.entitiesTableConfig.addActionDescriptors[0].name }}"
*ngIf="entitiesTableConfig.addActionDescriptors.length === 1; else addActionsMenu"
[fxShow]="entitiesTableConfig.addActionDescriptors[0].isEnabled()"
(click)="entitiesTableConfig.addActionDescriptors[0].onAction($event)"
matTooltip="{{ entitiesTableConfig.addActionDescriptors[0].name }}"
matTooltipPosition="above">
<mat-icon>{{this.entitiesTableConfig.addActionDescriptors[0].icon}}</mat-icon>
<mat-icon>{{entitiesTableConfig.addActionDescriptors[0].icon}}</mat-icon>
</button>
<ng-template #addActionsMenu>
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
@ -67,7 +68,7 @@
<mat-icon>add</mat-icon>
</button>
<mat-menu #addActionsMenu="matMenu" xPosition="before">
<button mat-menu-item *ngFor="let actionDescriptor of this.entitiesTableConfig.addActionDescriptors"
<button mat-menu-item *ngFor="let actionDescriptor of entitiesTableConfig.addActionDescriptors"
[disabled]="isLoading$ | async"
[fxShow]="actionDescriptor.isEnabled()"
(click)="actionDescriptor.onAction($event)">
@ -152,10 +153,19 @@
</mat-cell>
</ng-container>
<ng-container [matColumnDef]="column.key" *ngFor="let column of columns; trackBy: trackByColumnKey; let col = index">
<mat-header-cell *matHeaderCellDef [ngStyle]="{maxWidth: column.maxWidth}" mat-sort-header [disabled]="!column.sortable"> {{ column.title | translate }} </mat-header-cell>
<mat-header-cell *matHeaderCellDef [ngStyle]="headerCellStyle(column, col)" mat-sort-header [disabled]="!column.sortable"> {{ column.title | translate }} </mat-header-cell>
<mat-cell *matCellDef="let entity; let row = index"
[innerHTML]="cellContent(entity, column, row, col)"
[ngStyle]="cellStyle(entity, column, row, col)"></mat-cell>
[ngStyle]="cellStyle(entity, column, row, col)">
<button *ngIf="column.actionDescriptor; let actionDescriptor" mat-button mat-icon-button [disabled]="isLoading$ | async"
[fxShow]="actionDescriptor.isEnabled(entity)"
matTooltip="{{ actionDescriptor.nameFunction ? actionDescriptor.nameFunction(entity) : actionDescriptor.name }}"
matTooltipPosition="above"
(click)="actionDescriptor.onAction($event, entity)">
<mat-icon [svgIcon]="actionDescriptor.mdiIcon" [ngStyle]="actionDescriptor.style">
{{actionDescriptor.icon}}</mat-icon>
</button>
</mat-cell>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (cellActionDescriptors.length * 40) + 'px', maxWidth: (cellActionDescriptors.length * 40) + 'px' }">

View File

@ -42,7 +42,8 @@ import {
EntityTableColumn,
EntityTableConfig,
GroupActionDescriptor,
HeaderActionDescriptor
HeaderActionDescriptor,
EntityColumn
} from '@home/models/entity/entities-table-config.models';
import { EntityTypeTranslation } from '@shared/models/entity-type.models';
import { DialogService } from '@core/services/dialog.service';
@ -72,8 +73,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
groupActionDescriptors: Array<GroupActionDescriptor<BaseData<HasId>>>;
cellActionDescriptors: Array<CellActionDescriptor<BaseData<HasId>>>;
columns: Array<EntityTableColumn<BaseData<HasId>>>;
displayedColumns: string[] = [];
columns: Array<EntityColumn<BaseData<HasId>>>;
displayedColumns: string[];
headerCellStyleCache: Array<any> = [];
cellContentCache: Array<SafeHtml> = [];
@ -143,22 +146,12 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
}
);
this.columns = [...this.entitiesTableConfig.columns];
const enabledGroupActionDescriptors =
this.groupActionDescriptors.filter((descriptor) => descriptor.isEnabled);
this.selectionEnabled = this.entitiesTableConfig.selectionEnabled && enabledGroupActionDescriptors.length;
if (this.selectionEnabled) {
this.displayedColumns.push('select');
}
this.columns.forEach(
(column) => {
this.displayedColumns.push(column.key);
}
);
this.displayedColumns.push('actions');
this.columnsUpdated();
const sortOrder: SortOrder = { property: this.entitiesTableConfig.defaultSortOrder.property,
direction: this.entitiesTableConfig.defaultSortOrder.direction };
@ -235,6 +228,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
}
private dataLoaded() {
this.headerCellStyleCache.length = 0;
this.cellContentCache.length = 0;
this.cellStyleCache.length = 0;
}
@ -365,21 +359,65 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
}
}
cellContent(entity: BaseData<HasId>, column: EntityTableColumn<BaseData<HasId>>, row: number, col: number) {
const index = row * this.columns.length + col;
let res = this.cellContentCache[index];
columnsUpdated(resetData: boolean = false) {
this.columns = [...this.entitiesTableConfig.columns];
this.displayedColumns = [];
if (this.selectionEnabled) {
this.displayedColumns.push('select');
}
this.columns.forEach(
(column) => {
this.displayedColumns.push(column.key);
}
);
this.displayedColumns.push('actions');
this.headerCellStyleCache.length = 0;
this.cellContentCache.length = 0;
this.cellStyleCache.length = 0;
if (resetData) {
this.dataSource.reset();
}
}
headerCellStyle(column: EntityColumn<BaseData<HasId>>, col: number) {
const index = col;
let res = this.headerCellStyleCache[index];
if (!res) {
res = this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key));
this.cellContentCache[index] = res;
if (column instanceof EntityTableColumn) {
res = {...column.headerCellStyleFunction(column.key), ...{maxWidth: column.maxWidth}};
} else {
res = {maxWidth: column.maxWidth};
}
this.headerCellStyleCache[index] = res;
}
return res;
}
cellStyle(entity: BaseData<HasId>, column: EntityTableColumn<BaseData<HasId>>, row: number, col: number) {
cellContent(entity: BaseData<HasId>, column: EntityColumn<BaseData<HasId>>, row: number, col: number) {
if (column instanceof EntityTableColumn) {
const index = row * this.columns.length + col;
let res = this.cellContentCache[index];
if (!res) {
res = this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key));
this.cellContentCache[index] = res;
}
return res;
} else {
return null;
}
}
cellStyle(entity: BaseData<HasId>, column: EntityColumn<BaseData<HasId>>, row: number, col: number) {
const index = row * this.columns.length + col;
let res = this.cellStyleCache[index];
if (!res) {
res = {...column.cellStyleFunction(entity, column.key), ...{maxWidth: column.maxWidth}};
if (column instanceof EntityTableColumn) {
res = {...column.cellStyleFunction(entity, column.key), ...{maxWidth: column.maxWidth}};
} else {
res = {maxWidth: column.maxWidth};
}
this.cellStyleCache[index] = res;
}
return res;

View File

@ -37,6 +37,8 @@ import { selectAuthUser, getCurrentAuthUser } from '@core/auth/auth.selectors';
import { AuthUser } from '@shared/models/user.model';
import { EntityType } from '@shared/models/entity-type.models';
import { AuditLogMode } from '@shared/models/audit-log.models';
import { DebugEventType, EventType } from '@shared/models/event.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
export abstract class EntityTabsComponent<T extends BaseData<HasId>> extends PageComponent implements OnInit, AfterViewInit {
@ -46,8 +48,14 @@ export abstract class EntityTabsComponent<T extends BaseData<HasId>> extends Pag
auditLogModes = AuditLogMode;
eventTypes = EventType;
debugEventTypes = DebugEventType;
authUser: AuthUser;
nullUid = NULL_UUID;
entityValue: T;
@ViewChildren(MatTab) entityTabs: QueryList<MatTab>;

View File

@ -0,0 +1,212 @@
///
/// 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 {
DateEntityTableColumn,
EntityActionTableColumn,
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import { DebugEventType, Event, EventType } from '@shared/models/event.models';
import { TimePageLink } from '@shared/models/page/page-link';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { MatDialog } from '@angular/material/dialog';
import { EntityId } from '@shared/models/id/entity-id';
import { EventService } from '@app/core/http/event.service';
import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component';
import { EntityTypeResource } from '@shared/models/entity-type.models';
import { Observable } from 'rxjs';
import { PageData } from '@shared/models/page/page-data';
import { Direction } from '@shared/models/page/sort-order';
import { MsgDataType } from '@shared/models/rule-node.models';
import { DialogService } from '@core/services/dialog.service';
export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
eventTypeValue: EventType | DebugEventType;
set eventType(eventType: EventType | DebugEventType) {
if (this.eventTypeValue !== eventType) {
this.eventTypeValue = eventType;
this.updateColumns(true);
}
}
get eventType(): EventType | DebugEventType {
return this.eventTypeValue;
}
eventTypes: Array<EventType | DebugEventType>;
constructor(private eventService: EventService,
private dialogService: DialogService,
private translate: TranslateService,
private datePipe: DatePipe,
private dialog: MatDialog,
public entityId: EntityId,
public tenantId: string,
private defaultEventType: EventType | DebugEventType,
private disabledEventTypes: Array<EventType | DebugEventType> = null,
private debugEventTypes: Array<DebugEventType> = null) {
super();
this.loadDataOnInit = false;
this.tableTitle = '';
this.useTimePageLink = true;
this.detailsPanelEnabled = false;
this.selectionEnabled = false;
this.searchEnabled = false;
this.addEnabled = false;
this.entitiesDeleteEnabled = false;
this.headerComponent = EventTableHeaderComponent;
this.eventTypes = Object.keys(EventType).map(type => EventType[type]);
if (disabledEventTypes && disabledEventTypes.length) {
this.eventTypes = this.eventTypes.filter(type => disabledEventTypes.indexOf(type) === -1);
}
if (debugEventTypes && debugEventTypes.length) {
this.eventTypes = [...this.eventTypes, ...debugEventTypes];
}
this.eventTypeValue = defaultEventType;
this.entityTranslations = {
noEntities: 'event.no-events-prompt'
};
this.entityResources = {
} as EntityTypeResource;
this.entitiesFetchFunction = pageLink => this.fetchEvents(pageLink);
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.updateColumns();
}
fetchEvents(pageLink: TimePageLink): Observable<PageData<Event>> {
return this.eventService.getEvents(this.entityId, this.eventType, this.tenantId, pageLink);
}
updateColumns(updateTableColumns: boolean = false): void {
this.columns = [];
this.columns.push(
new DateEntityTableColumn<Event>('createdTime', 'event.event-time', this.datePipe, '150px'),
new EntityTableColumn<Event>('server', 'event.server', '150px',
(entity) => entity.body.server, entity => ({}), false));
switch (this.eventType) {
case EventType.ERROR:
this.columns.push(
new EntityTableColumn<Event>('method', 'event.method', '100%',
(entity) => entity.body.method, entity => ({}), false),
new EntityActionTableColumn<Event>('error', 'event.error',
{
name: this.translate.instant('action.view'),
icon: 'more_horiz',
isEnabled: (entity) => entity.body.error && entity.body.error.length > 0,
onAction: ($event, entity) => this.showContent($event, entity.body.error, 'event.error')
},
'100px')
);
break;
case EventType.LC_EVENT:
this.columns.push(
new EntityTableColumn<Event>('method', 'event.event', '100%',
(entity) => entity.body.event, entity => ({}), false),
new EntityTableColumn<Event>('status', 'event.status', '100%',
(entity) =>
this.translate.instant(entity.body.success ? 'event.success' : 'event.failed'), entity => ({}), false),
new EntityActionTableColumn<Event>('error', 'event.error',
{
name: this.translate.instant('action.view'),
icon: 'more_horiz',
isEnabled: (entity) => entity.body.error && entity.body.error.length > 0,
onAction: ($event, entity) => this.showContent($event, entity.body.error, 'event.error')
},
'100px')
);
break;
case EventType.STATS:
this.columns.push(
new EntityTableColumn<Event>('messagesProcessed', 'event.messages-processed', '100%',
(entity) => entity.body.messagesProcessed + '',
entity => ({justifyContent: 'flex-end'}),
false,
key => ({justifyContent: 'flex-end'})),
new EntityTableColumn<Event>('errorsOccurred', 'event.errors-occurred', '100%',
(entity) => entity.body.errorsOccurred + '',
entity => ({justifyContent: 'flex-end'}),
false,
key => ({justifyContent: 'flex-end'}))
);
break;
case DebugEventType.DEBUG_RULE_NODE:
case DebugEventType.DEBUG_RULE_CHAIN:
this.columns.push(
new EntityTableColumn<Event>('type', 'event.type', '100%',
(entity) => entity.body.type, entity => ({}), false),
new EntityTableColumn<Event>('entity', 'event.entity', '100%',
(entity) => entity.body.entityName, entity => ({}), false),
new EntityTableColumn<Event>('msgId', 'event.message-id', '100%',
(entity) => entity.body.msgId, entity => ({}), false),
new EntityTableColumn<Event>('msgType', 'event.message-type', '100%',
(entity) => entity.body.msgType, entity => ({}), false),
new EntityTableColumn<Event>('relationType', 'event.relation-type', '100%',
(entity) => entity.body.relationType, entity => ({}), false),
new EntityActionTableColumn<Event>('data', 'event.data',
{
name: this.translate.instant('action.view'),
icon: 'more_horiz',
isEnabled: (entity) => entity.body.data && entity.body.data.length > 0,
onAction: ($event, entity) => this.showContent($event, entity.body.data,
'event.data', entity.body.dataType)
},
'60px'),
new EntityActionTableColumn<Event>('metadata', 'event.metadata',
{
name: this.translate.instant('action.view'),
icon: 'more_horiz',
isEnabled: (entity) => entity.body.metadata && entity.body.metadata.length > 0,
onAction: ($event, entity) => this.showContent($event, entity.body.metadata,
'event.metadata', MsgDataType.JSON)
},
'60px'),
new EntityActionTableColumn<Event>('error', 'event.error',
{
name: this.translate.instant('action.view'),
icon: 'more_horiz',
isEnabled: (entity) => entity.body.error && entity.body.error.length > 0,
onAction: ($event, entity) => this.showContent($event, entity.body.error,
'event.error')
},
'60px')
);
break;
}
if (updateTableColumns) {
this.table.columnsUpdated(true);
}
}
showContent($event: MouseEvent, content: string, title: string, contentType: MsgDataType = null): void {
if ($event) {
$event.stopPropagation();
}
// TODO:
this.dialogService.todo();
}
}

View File

@ -0,0 +1,27 @@
<!--
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 class="mat-block" style="width: 200px;">
<mat-label translate>event.event-type</mat-label>
<mat-select matInput [ngModel]="eventTableConfig.eventType"
(ngModelChange)="eventTypeChanged($event)">
<mat-option *ngFor="let type of eventTableConfig.eventTypes" [value]="type">
{{ eventTypeTranslationsMap.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>

View File

@ -0,0 +1,35 @@
/**
* 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.
*/
:host {
/* flex: 1;
display: flex;
justify-content: flex-start; */
padding-right: 8px;
}
:host ::ng-deep {
mat-form-field {
font-size: 16px;
.mat-form-field-wrapper {
padding-bottom: 0;
}
.mat-form-field-underline {
bottom: 0;
}
}
}

View File

@ -0,0 +1,45 @@
///
/// 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 } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component';
import { DebugEventType, Event, EventType, eventTypeTranslations } from '@app/shared/models/event.models';
import { EventTableConfig } from '@home/components/event/event-table-config';
@Component({
selector: 'tb-event-table-header',
templateUrl: './event-table-header.component.html',
styleUrls: ['./event-table-header.component.scss']
})
export class EventTableHeaderComponent extends EntityTableHeaderComponent<Event> {
eventTypeTranslationsMap = eventTypeTranslations;
get eventTableConfig(): EventTableConfig {
return this.entitiesTableConfig as EventTableConfig;
}
constructor(protected store: Store<AppState>) {
super(store);
}
eventTypeChanged(eventType: EventType | DebugEventType) {
this.eventTableConfig.eventType = eventType;
this.eventTableConfig.table.updateData();
}
}

View File

@ -0,0 +1,18 @@
<!--
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.
-->
<tb-entities-table [entitiesTableConfig]="eventTableConfig"></tb-entities-table>

View File

@ -0,0 +1,22 @@
/**
* 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.
*/
:host ::ng-deep {
tb-entities-table {
.mat-drawer-container {
background-color: white;
}
}
}

View File

@ -0,0 +1,112 @@
///
/// 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, Input, OnInit, ViewChild } from '@angular/core';
import { AuditLogService } from '@core/http/audit-log.service';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { MatDialog } from '@angular/material';
import { AuditLogMode } from '@shared/models/audit-log.models';
import { EntityId } from '@shared/models/id/entity-id';
import { UserId } from '@shared/models/id/user-id';
import { CustomerId } from '@shared/models/id/customer-id';
import { AuditLogTableConfig } from '@home/components/audit-log/audit-log-table-config';
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { Authority } from '@shared/models/authority.enum';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { EventTableConfig } from './event-table-config';
import { EventService } from '@core/http/event.service';
import { DialogService } from '@core/services/dialog.service';
import { DebugEventType, EventType } from '@shared/models/event.models';
@Component({
selector: 'tb-event-table',
templateUrl: './event-table.component.html',
styleUrls: ['./event-table.component.scss']
})
export class EventTableComponent implements OnInit {
@Input()
tenantId: string;
@Input()
defaultEventType: EventType | DebugEventType;
@Input()
disabledEventTypes: Array<EventType | DebugEventType>;
@Input()
debugEventTypes: Array<DebugEventType>;
activeValue = false;
dirtyValue = false;
entityIdValue: EntityId;
@Input()
set active(active: boolean) {
if (this.activeValue !== active) {
this.activeValue = active;
if (this.activeValue && this.dirtyValue) {
this.dirtyValue = false;
this.entitiesTable.updateData();
}
}
}
@Input()
set entityId(entityId: EntityId) {
this.entityIdValue = entityId;
if (this.eventTableConfig && this.eventTableConfig.entityId !== entityId) {
this.eventTableConfig.eventType = this.defaultEventType;
this.eventTableConfig.entityId = entityId;
this.entitiesTable.resetSortAndFilter(this.activeValue);
if (!this.activeValue) {
this.dirtyValue = true;
}
}
}
@ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent;
eventTableConfig: EventTableConfig;
constructor(private eventService: EventService,
private dialogService: DialogService,
private translate: TranslateService,
private datePipe: DatePipe,
private dialog: MatDialog,
private store: Store<AppState>) {
}
ngOnInit() {
this.dirtyValue = !this.activeValue;
this.eventTableConfig = new EventTableConfig(
this.eventService,
this.dialogService,
this.translate,
this.datePipe,
this.dialog,
this.entityIdValue,
this.tenantId,
this.defaultEventType,
this.disabledEventTypes,
this.debugEventTypes
);
}
}

View File

@ -24,11 +24,14 @@ import {EntityDetailsPanelComponent} from './entity/entity-details-panel.compone
import {ContactComponent} from './contact.component';
import { AuditLogDetailsDialogComponent } from './audit-log/audit-log-details-dialog.component';
import { AuditLogTableComponent } from './audit-log/audit-log-table.component';
import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component';
import { EventTableComponent } from '@home/components/event/event-table.component';
@NgModule({
entryComponents: [
AddEntityDialogComponent,
AuditLogDetailsDialogComponent
AuditLogDetailsDialogComponent,
EventTableHeaderComponent
],
declarations:
[
@ -38,7 +41,9 @@ import { AuditLogTableComponent } from './audit-log/audit-log-table.component';
EntityDetailsPanelComponent,
ContactComponent,
AuditLogTableComponent,
AuditLogDetailsDialogComponent
AuditLogDetailsDialogComponent,
EventTableHeaderComponent,
EventTableComponent
],
imports: [
CommonModule,
@ -50,7 +55,8 @@ import { AuditLogTableComponent } from './audit-log/audit-log-table.component';
DetailsPanelComponent,
EntityDetailsPanelComponent,
ContactComponent,
AuditLogTableComponent
AuditLogTableComponent,
EventTableComponent
]
})
export class HomeComponentsModule { }

View File

@ -50,6 +50,13 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink =
this.pageDataSubject.complete();
}
reset() {
const pageData = emptyPageData<T>();
this.entitiesSubject.next(pageData.data);
this.pageDataSubject.next(pageData);
this.dataLoadedFunction();
}
loadEntities(pageLink: P): Observable<PageData<T>> {
const result = new ReplaySubject<PageData<T>>();
this.fetchFunction(pageLink).pipe(

View File

@ -41,6 +41,7 @@ export type EntityActionFunction<T extends BaseData<HasId>> = (action: EntityAct
export type CreateEntityOperation<T extends BaseData<HasId>> = () => Observable<T>;
export type CellContentFunction<T extends BaseData<HasId>> = (entity: T, key: string) => string;
export type HeaderCellStyleFunction<T extends BaseData<HasId>> = (key: string) => object;
export type CellStyleFunction<T extends BaseData<HasId>> = (entity: T, key: string) => object;
export interface CellActionDescriptor<T extends BaseData<HasId>> {
@ -67,13 +68,35 @@ export interface HeaderActionDescriptor {
onAction: ($event: MouseEvent) => void;
}
export class EntityTableColumn<T extends BaseData<HasId>> {
export type EntityTableColumnType = 'content' | 'action';
export class BaseEntityTableColumn<T extends BaseData<HasId>> {
constructor(public type: EntityTableColumnType,
public key: string,
public title: string,
public maxWidth: string = '100%',
public sortable: boolean = true) {
}
}
export class EntityTableColumn<T extends BaseData<HasId>> extends BaseEntityTableColumn<T> {
constructor(public key: string,
public title: string,
public maxWidth: string = '100%',
public cellContentFunction: CellContentFunction<T> = (entity, property) => entity[property],
public cellStyleFunction: CellStyleFunction<T> = () => ({}),
public sortable: boolean = true) {
public sortable: boolean = true,
public headerCellStyleFunction: HeaderCellStyleFunction<T> = () => ({})) {
super('content', key, title, maxWidth, sortable);
}
}
export class EntityActionTableColumn<T extends BaseData<HasId>> extends BaseEntityTableColumn<T> {
constructor(public key: string,
public title: string,
public actionDescriptor: CellActionDescriptor<T>,
public maxWidth: string = '100%') {
super('action', key, title, maxWidth, false);
}
}
@ -92,6 +115,8 @@ export class DateEntityTableColumn<T extends BaseData<HasId>> extends EntityTabl
}
}
export type EntityColumn<T extends BaseData<HasId>> = EntityTableColumn<T> | EntityActionTableColumn<T>;
export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = PageLink> {
constructor() {}
@ -116,7 +141,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
entityTabsComponent: Type<EntityTabsComponent<T>>;
addDialogStyle = {};
defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC};
columns: Array<EntityTableColumn<T>> = [];
columns: Array<EntityColumn<T>> = [];
cellActionDescriptors: Array<CellActionDescriptor<T>> = [];
groupActionDescriptors: Array<GroupActionDescriptor<T>> = [];
headerActionDescriptors: Array<HeaderActionDescriptor> = [];

View File

@ -15,6 +15,11 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'asset.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
[entityId]="entity.id"></tb-event-table>
</mat-tab>
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>

View File

@ -15,6 +15,11 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'customer.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
[entityId]="entity.id"></tb-event-table>
</mat-tab>
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.CUSTOMER" [customerId]="entity.id" detailsMode="true"></tb-audit-log-table>

View File

@ -15,6 +15,11 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'device.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
[entityId]="entity.id"></tb-event-table>
</mat-tab>
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>

View File

@ -15,6 +15,11 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'entity-view.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
[entityId]="entity.id"></tb-event-table>
</mat-tab>
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>

View File

@ -15,6 +15,14 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'rulechain.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive"
[debugEventTypes]="[debugEventTypes.DEBUG_RULE_CHAIN]"
[defaultEventType]="debugEventTypes.DEBUG_RULE_CHAIN"
[tenantId]="entity.tenantId.id"
[entityId]="entity.id"></tb-event-table>
</mat-tab>
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>

View File

@ -0,0 +1,22 @@
<!--
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-tab *ngIf="entity"
label="{{ 'tenant.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid"
[entityId]="entity.id"></tb-event-table>
</mat-tab>

View File

@ -0,0 +1,38 @@
///
/// 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 } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
import { Tenant } from '@shared/models/tenant.model';
@Component({
selector: 'tb-tenant-tabs',
templateUrl: './tenant-tabs.component.html',
styleUrls: []
})
export class TenantTabsComponent extends EntityTabsComponent<Tenant> {
constructor(protected store: Store<AppState>) {
super(store);
}
ngOnInit() {
super.ngOnInit();
}
}

View File

@ -20,13 +20,16 @@ import { SharedModule } from '@shared/shared.module';
import {TenantComponent} from '@modules/home/pages/tenant/tenant.component';
import {TenantRoutingModule} from '@modules/home/pages/tenant/tenant-routing.module';
import {HomeComponentsModule} from '@modules/home/components/home-components.module';
import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component';
@NgModule({
entryComponents: [
TenantComponent
TenantComponent,
TenantTabsComponent
],
declarations: [
TenantComponent
TenantComponent,
TenantTabsComponent
],
imports: [
CommonModule,

View File

@ -35,6 +35,7 @@ import {
import { TenantComponent } from '@modules/home/pages/tenant/tenant.component';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { User } from '@shared/models/user.model';
import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component';
@Injectable()
export class TenantsTableConfigResolver implements Resolve<EntityTableConfig<Tenant>> {
@ -48,6 +49,7 @@ export class TenantsTableConfigResolver implements Resolve<EntityTableConfig<Ten
this.config.entityType = EntityType.TENANT;
this.config.entityComponent = TenantComponent;
this.config.entityTabsComponent = TenantTabsComponent;
this.config.entityTranslations = entityTypeTranslations.get(EntityType.TENANT);
this.config.entityResources = entityTypeResources.get(EntityType.TENANT);

View File

@ -0,0 +1,21 @@
<!--
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-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.USER" [userId]="entity.id" detailsMode="true"></tb-audit-log-table>
</mat-tab>

View File

@ -0,0 +1,38 @@
///
/// 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 } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
import { User } from '@app/shared/models/user.model';
@Component({
selector: 'tb-user-tabs',
templateUrl: './user-tabs.component.html',
styleUrls: []
})
export class UserTabsComponent extends EntityTabsComponent<User> {
constructor(protected store: Store<AppState>) {
super(store);
}
ngOnInit() {
super.ngOnInit();
}
}

View File

@ -22,15 +22,18 @@ import { UserRoutingModule } from '@modules/home/pages/user/user-routing.module'
import { AddUserDialogComponent } from '@modules/home/pages/user/add-user-dialog.component';
import { ActivationLinkDialogComponent } from '@modules/home/pages/user/activation-link-dialog.component';
import {HomeComponentsModule} from '@modules/home/components/home-components.module';
import { UserTabsComponent } from '@home/pages/user/user-tabs.component';
@NgModule({
entryComponents: [
UserComponent,
UserTabsComponent,
AddUserDialogComponent,
ActivationLinkDialogComponent
],
declarations: [
UserComponent,
UserTabsComponent,
AddUserDialogComponent,
ActivationLinkDialogComponent
],

View File

@ -57,6 +57,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
import { Customer } from '@shared/models/customer.model';
import {TenantService} from '@app/core/http/tenant.service';
import {TenantId} from '@app/shared/models/id/tenant-id';
import { UserTabsComponent } from '@home/pages/user/user-tabs.component';
export interface UsersTableRouteData {
authority: Authority;
@ -83,6 +84,7 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
this.config.entityType = EntityType.USER;
this.config.entityComponent = UserComponent;
this.config.entityTabsComponent = UserTabsComponent;
this.config.entityTranslations = entityTypeTranslations.get(EntityType.USER);
this.config.entityResources = entityTypeResources.get(EntityType.USER);

View File

@ -0,0 +1,89 @@
///
/// 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 { ActionStatus, ActionType } from '@shared/models/audit-log.models';
import { BaseData } from '@shared/models/base-data';
import { AuditLogId } from '@shared/models/id/audit-log-id';
import { TenantId } from '@shared/models/id/tenant-id';
import { CustomerId } from '@shared/models/id/customer-id';
import { EntityId } from '@shared/models/id/entity-id';
import { UserId } from '@shared/models/id/user-id';
import { EventId } from './id/event-id';
import { MsgDataType } from './rule-node.models';
export enum EventType {
ERROR = 'ERROR',
LC_EVENT = 'LC_EVENT',
STATS = 'STATS'
}
export enum DebugEventType {
DEBUG_RULE_NODE = 'DEBUG_RULE_NODE',
DEBUG_RULE_CHAIN = 'DEBUG_RULE_CHAIN'
}
export const eventTypeTranslations = new Map<EventType | DebugEventType, string>(
[
[EventType.ERROR, 'event.type-error'],
[EventType.LC_EVENT, 'event.type-lc-event'],
[EventType.STATS, 'event.type-stats'],
[DebugEventType.DEBUG_RULE_NODE, 'event.type-debug-rule-node'],
[DebugEventType.DEBUG_RULE_CHAIN, 'event.type-debug-rule-chain'],
]
);
export interface BaseEventBody {
server: string;
}
export interface ErrorEventBody extends BaseEventBody {
method: string;
error: string;
}
export interface LcEventEventBody extends BaseEventBody {
event: string;
success: boolean;
error: string;
}
export interface StatsEventBody extends BaseEventBody {
messagesProcessed: number;
errorsOccurred: number;
}
export interface DebugRuleNodeEventBody extends BaseEventBody {
type: string;
entityId: string;
entityName: string;
msgId: string;
msgType: string;
relationType: string;
dataType: MsgDataType;
data: string;
metadata: string;
error: string;
}
export type EventBody = ErrorEventBody & LcEventEventBody & StatsEventBody & DebugRuleNodeEventBody;
export interface Event extends BaseData<EventId> {
tenantId: TenantId;
entityId: EntityId;
type: string;
uid: string;
body: EventBody;
}

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 EventId implements HasUUID {
id: string;
constructor(id: string) {
this.id = id;
}
}

View File

@ -21,6 +21,12 @@ 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 enum MsgDataType {
JSON = 'JSON',
TEXT = 'TEXT',
BINARY = 'BINARY'
}
export interface RuleNodeConfiguration {
todo: Array<any>;
// TODO:

View File

@ -308,7 +308,7 @@ $tb-dark-theme: get-tb-dark-theme(
}
.mat-cell, .mat-header-cell {
min-width: 80px;
min-width: 40px;
&:last-child {
padding: 0 12px 0 0;
}