18 changed files with 684 additions and 202 deletions
@ -0,0 +1,253 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2020 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 { |
||||
|
EdgeEvent, edgeEventActionTypeTranslations, |
||||
|
EdgeEventStatus, |
||||
|
edgeEventStatusColor, |
||||
|
EdgeEventType, |
||||
|
edgeEventTypeTranslations |
||||
|
} from '@shared/models/edge.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 { EntityTypeResource } from '@shared/models/entity-type.models'; |
||||
|
import { Observable, of } from 'rxjs'; |
||||
|
import { PageData } from '@shared/models/page/page-data'; |
||||
|
import { Direction } from '@shared/models/page/sort-order'; |
||||
|
import { DialogService } from '@core/services/dialog.service'; |
||||
|
import { ContentType } from '@shared/models/constants'; |
||||
|
import { |
||||
|
EventContentDialogComponent, |
||||
|
EventContentDialogData |
||||
|
} from '@home/components/event/event-content-dialog.component'; |
||||
|
import { sortObjectKeys } from '@core/utils'; |
||||
|
import { RuleChainService } from "@core/http/rule-chain.service"; |
||||
|
import { AttributeService } from "@core/http/attribute.service"; |
||||
|
import { AttributeScope } from "@shared/models/telemetry/telemetry.models"; |
||||
|
import { EdgeDownlinkTableHeaderComponent } from "@home/components/edge/edge-downlink-table-header.component"; |
||||
|
import { EdgeService } from "@core/http/edge.service"; |
||||
|
import { map } from "rxjs/operators"; |
||||
|
import { AssetService } from "@core/http/asset.service"; |
||||
|
import { DeviceService } from "@core/http/device.service"; |
||||
|
import { EntityViewService } from "@core/http/entity-view.service"; |
||||
|
import { actionTypeTranslations } from "@shared/models/audit-log.models"; |
||||
|
|
||||
|
export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePageLink> { |
||||
|
|
||||
|
queueStartTs: number; |
||||
|
|
||||
|
constructor(private edgeService: EdgeService, |
||||
|
private dialogService: DialogService, |
||||
|
private translate: TranslateService, |
||||
|
private deviceService: DeviceService, |
||||
|
private assetService: AssetService, |
||||
|
private entityViewService: EntityViewService, |
||||
|
private ruleChainService: RuleChainService, |
||||
|
private attributeService: AttributeService, |
||||
|
private datePipe: DatePipe, |
||||
|
private dialog: MatDialog, |
||||
|
public entityId: EntityId, |
||||
|
public tenantId: string) { |
||||
|
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 = EdgeDownlinkTableHeaderComponent; |
||||
|
|
||||
|
this.entityTranslations = { |
||||
|
noEntities: 'edge.no-downlinks-prompt' |
||||
|
}; |
||||
|
this.entityResources = {} as EntityTypeResource<EdgeEvent>; |
||||
|
this.entitiesFetchFunction = pageLink => this.fetchEvents(pageLink); |
||||
|
|
||||
|
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; |
||||
|
|
||||
|
this.updateColumns(); |
||||
|
} |
||||
|
|
||||
|
fetchEvents(pageLink: TimePageLink): Observable<PageData<EdgeEvent>> { |
||||
|
this.loadEdgeInfo(); |
||||
|
return this.edgeService.getEdgeEvents(this.entityId, pageLink); |
||||
|
} |
||||
|
|
||||
|
updateColumns(updateTableColumns: boolean = false): void { |
||||
|
this.columns = []; |
||||
|
this.columns.push( |
||||
|
new DateEntityTableColumn<EdgeEvent>('createdTime', 'event.event-time', this.datePipe, '120px'), |
||||
|
new EntityTableColumn<EdgeEvent>('type', 'event.type', '25%', |
||||
|
entity => this.translate.instant(edgeEventTypeTranslations.get(entity.type)), entity => ({}), false), |
||||
|
new EntityTableColumn<EdgeEvent>('action', 'edge.event-action', '25%', |
||||
|
entity => this.translate.instant(edgeEventActionTypeTranslations.get(entity.action)), entity => ({}), false), |
||||
|
new EntityTableColumn<EdgeEvent>('entityId', 'edge.entity-id', '40%', |
||||
|
(entity) => entity.entityId, entity => ({}), false), |
||||
|
new EntityTableColumn<EdgeEvent>('status', 'event.status', '10%', |
||||
|
(entity) => this.updateEdgeEventStatus(entity.createdTime), |
||||
|
entity => ({ |
||||
|
color: this.isPending(entity.createdTime) ? edgeEventStatusColor.get(EdgeEventStatus.PENDING) : edgeEventStatusColor.get(EdgeEventStatus.DEPLOYED) |
||||
|
}), false), |
||||
|
new EntityActionTableColumn<EdgeEvent>('data', 'event.data', |
||||
|
{ |
||||
|
name: this.translate.instant('action.view'), |
||||
|
icon: 'more_horiz', |
||||
|
isEnabled: (entity) => this.isEdgeEventHasData(entity.type), |
||||
|
onAction: ($event, entity) => |
||||
|
{ |
||||
|
this.prepareEdgeEventContent(entity).subscribe((content) => { |
||||
|
this.showEdgeEventContent($event, content,'event.data'); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
'40px'), |
||||
|
); |
||||
|
if (updateTableColumns) { |
||||
|
this.table.columnsUpdated(true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
showContent($event: MouseEvent, content: string, title: string, contentType: ContentType = null, sortKeys = false): void { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
if (contentType === ContentType.JSON && sortKeys) { |
||||
|
try { |
||||
|
content = JSON.stringify(sortObjectKeys(JSON.parse(content))); |
||||
|
} catch (e) { |
||||
|
} |
||||
|
} |
||||
|
this.dialog.open<EventContentDialogComponent, EventContentDialogData>(EventContentDialogComponent, { |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
content, |
||||
|
title, |
||||
|
contentType |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
isEdgeEventHasData(edgeEventType: EdgeEventType) { |
||||
|
switch (edgeEventType) { |
||||
|
case EdgeEventType.WIDGET_TYPE: |
||||
|
case EdgeEventType.WIDGETS_BUNDLE: |
||||
|
case EdgeEventType.ADMIN_SETTINGS: |
||||
|
return false; |
||||
|
default: |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
prepareEdgeEventContent(entity) { |
||||
|
// TODO: voba - extend this function with different cases based on action and entity type
|
||||
|
switch (entity.type) { |
||||
|
case EdgeEventType.RELATION: |
||||
|
return of(JSON.stringify(entity.body)); |
||||
|
case EdgeEventType.ASSET: |
||||
|
return this.assetService.getAsset(entity.entityId, null).pipe( |
||||
|
map((asset) => { |
||||
|
return JSON.stringify(asset); |
||||
|
}) |
||||
|
); |
||||
|
case EdgeEventType.DEVICE: |
||||
|
return this.deviceService.getDevice(entity.entityId, null).pipe( |
||||
|
map((device) => { |
||||
|
return JSON.stringify(device); |
||||
|
}) |
||||
|
); |
||||
|
case EdgeEventType.ENTITY_VIEW: |
||||
|
return this.entityViewService.getEntityView(entity.entityId, null).pipe( |
||||
|
map((entityView) => { |
||||
|
return JSON.stringify(entityView); |
||||
|
}) |
||||
|
); |
||||
|
case EdgeEventType.RULE_CHAIN_METADATA: |
||||
|
return this.ruleChainService.getRuleChainMetadata(entity.entityId, null).pipe( |
||||
|
map((ruleChainMetaData) => { |
||||
|
return JSON.stringify(ruleChainMetaData.nodes); |
||||
|
}) |
||||
|
); |
||||
|
default: |
||||
|
return of(JSON.stringify(entity)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
showEdgeEventContent($event: MouseEvent, content: string, title: string, sortKeys = false): void { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
var contentType = ContentType.JSON; |
||||
|
if (contentType === ContentType.JSON && sortKeys) { |
||||
|
try { |
||||
|
content = JSON.stringify(sortObjectKeys(JSON.parse(content))); |
||||
|
} catch (e) { |
||||
|
} |
||||
|
} |
||||
|
this.dialog.open<EventContentDialogComponent, EventContentDialogData>(EventContentDialogComponent, { |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
content, |
||||
|
title, |
||||
|
contentType |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
updateEdgeEventStatus(createdTime) { |
||||
|
if (this.queueStartTs && createdTime < this.queueStartTs) { |
||||
|
return this.translate.instant('edge.success'); |
||||
|
} else { |
||||
|
return this.translate.instant('edge.failed'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
isPending(createdTime) { |
||||
|
return createdTime > this.queueStartTs; |
||||
|
} |
||||
|
|
||||
|
loadEdgeInfo() { |
||||
|
this.attributeService.getEntityAttributes(this.entityId, AttributeScope.SERVER_SCOPE, ['queueStartTs']) |
||||
|
.subscribe( |
||||
|
attributes => this.onUpdate(attributes) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
onUpdate(attributes) { |
||||
|
this.queueStartTs = 0; |
||||
|
let edge = attributes.reduce(function (map, attribute) { |
||||
|
map[attribute.key] = attribute; |
||||
|
return map; |
||||
|
}, {}); |
||||
|
if (edge.queueStartTs) { |
||||
|
this.queueStartTs = edge.queueStartTs.lastUpdateTs; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,19 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2020 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"> |
||||
|
</mat-form-field> |
||||
@ -0,0 +1,41 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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 '../../../../../scss/constants'; |
||||
|
|
||||
|
:host { |
||||
|
padding-right: 8px; |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
mat-form-field { |
||||
|
|
||||
|
.mat-form-field-wrapper { |
||||
|
padding-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.mat-form-field-underline { |
||||
|
bottom: 0; |
||||
|
} |
||||
|
|
||||
|
@media #{$mat-xs} { |
||||
|
width: 100%; |
||||
|
|
||||
|
.mat-form-field-infix { |
||||
|
width: auto !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2020 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 { EdgeEvent } from "@shared/models/edge.models"; |
||||
|
import { EdgeDownlinkTableConfig } from "@home/components/edge/edge-downlink-table-config"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-edge-downlink-table-header', |
||||
|
templateUrl: './edge-downlink-table-header.component.html', |
||||
|
styleUrls: ['./edge-downlink-table-header.component.scss'] |
||||
|
}) |
||||
|
export class EdgeDownlinkTableHeaderComponent extends EntityTableHeaderComponent<EdgeEvent> { |
||||
|
|
||||
|
get eventTableConfig(): EdgeDownlinkTableConfig { |
||||
|
return this.entitiesTableConfig as EdgeDownlinkTableConfig; |
||||
|
} |
||||
|
|
||||
|
constructor(protected store: Store<AppState>) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
eventTypeChanged() { |
||||
|
this.eventTableConfig.table.resetSortAndFilter(true, true); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2020 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]="edgeDownlinkTableConfig"></tb-entities-table> |
||||
@ -0,0 +1,22 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,103 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2020 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 { 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 { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; |
||||
|
import { EdgeDownlinkTableConfig } from './edge-downlink-table-config'; |
||||
|
import { DialogService } from '@core/services/dialog.service'; |
||||
|
import { RuleChainService } from "@core/http/rule-chain.service"; |
||||
|
import { AttributeService } from "@core/http/attribute.service"; |
||||
|
import { EdgeService } from "@core/http/edge.service"; |
||||
|
import { DeviceService } from "@core/http/device.service"; |
||||
|
import { AssetService } from "@core/http/asset.service"; |
||||
|
import { EntityViewService } from "@core/http/entity-view.service"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-edge-downlink-table', |
||||
|
templateUrl: './edge-downlink-table.component.html', |
||||
|
styleUrls: ['./edge-downlink-table.component.scss'] |
||||
|
}) |
||||
|
export class EdgeDownlinkTableComponent implements OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
tenantId: string; |
||||
|
|
||||
|
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.edgeDownlinkTableConfig && this.edgeDownlinkTableConfig.entityId !== entityId) { |
||||
|
this.edgeDownlinkTableConfig.entityId = entityId; |
||||
|
this.entitiesTable.resetSortAndFilter(this.activeValue); |
||||
|
if (!this.activeValue) { |
||||
|
this.dirtyValue = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; |
||||
|
|
||||
|
edgeDownlinkTableConfig: EdgeDownlinkTableConfig; |
||||
|
|
||||
|
constructor(private edgeService: EdgeService, |
||||
|
private deviceService: DeviceService, |
||||
|
private assetService: AssetService, |
||||
|
private entityViewService: EntityViewService, |
||||
|
private dialogService: DialogService, |
||||
|
private translate: TranslateService, |
||||
|
private attributeService: AttributeService, |
||||
|
private ruleChainService: RuleChainService, |
||||
|
private datePipe: DatePipe, |
||||
|
private dialog: MatDialog) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.dirtyValue = !this.activeValue; |
||||
|
this.edgeDownlinkTableConfig = new EdgeDownlinkTableConfig( |
||||
|
this.edgeService, |
||||
|
this.dialogService, |
||||
|
this.translate, |
||||
|
this.deviceService, |
||||
|
this.assetService, |
||||
|
this.entityViewService, |
||||
|
this.ruleChainService, |
||||
|
this.attributeService, |
||||
|
this.datePipe, |
||||
|
this.dialog, |
||||
|
this.entityIdValue, |
||||
|
this.tenantId |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue