diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss
index 462554287c..399105d3a9 100644
--- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss
+++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss
@@ -19,6 +19,22 @@
.tb-rulechain {
width: 100%;
height: 100%;
+
+ .mat-tab-group.tb-rulenode-details {
+ > .mat-tab-body-wrapper {
+ position: absolute;
+ top: 49px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+ > .mat-tab-header {
+ .mat-tab-label {
+ min-width: 40px;
+ }
+ }
+ }
+
button.mat-button.mat-icon-button.tb-fullscreen-button {
position: absolute;
top: 10px;
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
index 516057a1d9..1eac5e3b67 100644
--- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
+++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
@@ -38,9 +38,11 @@ import { DialogService } from '@core/services/dialog.service';
import { AuthService } from '@core/auth/auth.service';
import { ActivatedRoute, Router } from '@angular/router';
import {
- inputNodeComponent,
+ inputNodeComponent, NodeConnectionInfo,
ResolvedRuleChainMetaData,
RuleChain,
+ RuleChainConnectionInfo, RuleChainImport,
+ RuleChainMetaData,
ruleChainNodeComponent
} from '@shared/models/rule-chain.models';
import { FcItemInfo, FlowchartConstants, NgxFlowchartComponent, UserCallbacks } from 'ngx-flowchart/dist/ngx-flowchart';
@@ -50,6 +52,7 @@ import {
FcRuleNodeType,
getRuleNodeHelpLink,
LinkLabel,
+ RuleNode,
RuleNodeComponentDescriptor,
RuleNodeType,
ruleNodeTypeDescriptors,
@@ -66,8 +69,10 @@ import { RuleNodeLinkComponent } from './rule-node-link.component';
import { DialogComponent } from '@shared/components/dialog.component';
import { MatMenuTrigger } from '@angular/material/menu';
import { ItemBufferService, RuleNodeConnection } from '@core/services/item-buffer.service';
+import { Hotkey } from 'angular2-hotkeys';
+import { EntityType } from '@shared/models/entity-type.models';
import Timeout = NodeJS.Timeout;
-import { Hotkey, HotkeysService } from 'angular2-hotkeys';
+import { DebugEventType, EventType } from '@shared/models/event.models';
@Component({
selector: 'tb-rulechain-page',
@@ -94,6 +99,10 @@ export class RuleChainPageComponent extends PageComponent
@ViewChild('ruleChainMenuTrigger', {static: true}) ruleChainMenuTrigger: MatMenuTrigger;
+ eventTypes = EventType;
+
+ debugEventTypes = DebugEventType;
+
ruleChainMenuPosition = { x: '0px', y: '0px' };
contextMenuEvent: MouseEvent;
@@ -260,13 +269,15 @@ export class RuleChainPageComponent extends PageComponent
private init() {
this.initHotKeys();
- this.ruleChain = this.route.snapshot.data.ruleChain;
- if (this.route.snapshot.data.import && !this.ruleChain) {
- this.router.navigateByUrl('ruleChains');
- return;
- }
this.isImport = this.route.snapshot.data.import;
- this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData;
+ if (this.isImport) {
+ const ruleChainImport: RuleChainImport = this.itembuffer.getRuleChainImport();
+ this.ruleChain = ruleChainImport.ruleChain;
+ this.ruleChainMetaData = ruleChainImport.resolvedMetadata;
+ } else {
+ this.ruleChain = this.route.snapshot.data.ruleChain;
+ this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData;
+ }
this.ruleNodeComponents = this.route.snapshot.data.ruleNodeComponents;
for (const type of ruleNodeTypesLibrary) {
const desc = ruleNodeTypeDescriptors.get(type);
@@ -564,7 +575,7 @@ export class RuleChainPageComponent extends PageComponent
if (!ruleChainNode) {
ruleChainNode = {
id: 'rule-chain-node-' + this.nextNodeID++,
- name: ruleChain.name ? name : 'Unresolved',
+ name: ruleChain.name ? ruleChain.name : 'Unresolved',
targetRuleChainId: ruleChain.name ? ruleChainConnection.targetRuleChainId.id : null,
error: ruleChain.name ? undefined : this.translate.instant('rulenode.invalid-target-rulechain'),
additionalInfo: ruleChainConnection.additionalInfo,
@@ -833,7 +844,6 @@ export class RuleChainPageComponent extends PageComponent
}
onModelChanged() {
- console.log('Model changed!');
this.isDirtyValue = true;
this.validate();
}
@@ -1162,7 +1172,86 @@ export class RuleChainPageComponent extends PageComponent
}
saveRuleChain() {
- // TODO:
+ let saveRuleChainObservable: Observable
;
+ if (this.isImport) {
+ saveRuleChainObservable = this.ruleChainService.saveRuleChain(this.ruleChain);
+ } else {
+ saveRuleChainObservable = of(this.ruleChain);
+ }
+ saveRuleChainObservable.subscribe((ruleChain) => {
+ this.ruleChain = ruleChain;
+ const ruleChainMetaData: RuleChainMetaData = {
+ ruleChainId: this.ruleChain.id,
+ nodes: [],
+ connections: [],
+ ruleChainConnections: []
+ };
+ const nodes: FcRuleNode[] = [];
+ this.ruleChainModel.nodes.forEach((node) => {
+ if (node.component.type !== RuleNodeType.INPUT && node.component.type !== RuleNodeType.RULE_CHAIN) {
+ const ruleNode: RuleNode = {
+ id: node.ruleNodeId,
+ type: node.component.clazz,
+ name: node.name,
+ configuration: node.configuration,
+ additionalInfo: node.additionalInfo ? node.additionalInfo : {},
+ debugMode: node.debugMode
+ };
+ ruleNode.additionalInfo.layoutX = Math.round(node.x);
+ ruleNode.additionalInfo.layoutY = Math.round(node.y);
+ ruleChainMetaData.nodes.push(ruleNode);
+ nodes.push(node);
+ }
+ });
+ const firstNodeEdge = this.ruleChainModel.edges.find((edge) => edge.source === this.inputConnectorId+'');
+ if (firstNodeEdge) {
+ const firstNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(firstNodeEdge.destination);
+ ruleChainMetaData.firstNodeIndex = nodes.indexOf(firstNode);
+ }
+ this.ruleChainModel.edges.forEach((edge) => {
+ const sourceNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source);
+ const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination);
+ if (sourceNode.component.type !== RuleNodeType.INPUT) {
+ const fromIndex = nodes.indexOf(sourceNode);
+ if (destNode.component.type === RuleNodeType.RULE_CHAIN) {
+ const ruleChainConnection = {
+ fromIndex,
+ targetRuleChainId: {entityType: EntityType.RULE_CHAIN, id: destNode.targetRuleChainId},
+ additionalInfo: destNode.additionalInfo ? destNode.additionalInfo : {}
+ } as RuleChainConnectionInfo;
+ ruleChainConnection.additionalInfo.layoutX = Math.round(destNode.x);
+ ruleChainConnection.additionalInfo.layoutY = Math.round(destNode.y);
+ ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id;
+ edge.labels.forEach((label) => {
+ const newRuleChainConnection = deepClone(ruleChainConnection);
+ newRuleChainConnection.type = label;
+ ruleChainMetaData.ruleChainConnections.push(newRuleChainConnection);
+ });
+ } else {
+ const toIndex = nodes.indexOf(destNode);
+ const nodeConnection = {
+ fromIndex,
+ toIndex
+ } as NodeConnectionInfo;
+ edge.labels.forEach((label) => {
+ const newNodeConnection = deepClone(nodeConnection);
+ newNodeConnection.type = label;
+ ruleChainMetaData.connections.push(newNodeConnection);
+ });
+ }
+ }
+ });
+ this.ruleChainService.saveAndGetResolvedRuleChainMetadata(ruleChainMetaData).subscribe((ruleChainMetaData) => {
+ this.ruleChainMetaData = ruleChainMetaData;
+ if (this.isImport) {
+ this.isDirtyValue = false;
+ this.isImport = false;
+ this.router.navigateByUrl(`ruleChains/${this.ruleChain.id.id}`);
+ } else {
+ this.createRuleChainModel();
+ }
+ });
+ });
}
revertRuleChain() {
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts
index 16dc106694..9fb8b17daa 100644
--- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts
+++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts
@@ -16,7 +16,14 @@
import * as AngularCore from '@angular/core';
import { Injectable, NgModule } from '@angular/core';
-import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router';
+import {
+ ActivatedRouteSnapshot,
+ CanActivate,
+ Resolve, Router,
+ RouterModule,
+ RouterStateSnapshot,
+ Routes, UrlTree
+} from '@angular/router';
import { EntitiesTableComponent } from '../../components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum';
@@ -41,6 +48,7 @@ import * as TranslateCore from '@ngx-translate/core';
import * as TbCore from '@core/public-api';
import * as TbShared from '@shared/public-api';
import * as TbHomeComponents from '@home/components/public-api';
+import { ItemBufferService } from '@core/public-api';
declare const SystemJS;
@@ -98,6 +106,23 @@ export class RuleNodeComponentsResolver implements Resolve | Promise | boolean | UrlTree {
+ if (this.itembuffer.hasRuleChainImport()) {
+ return true;
+ } else {
+ return this.router.parseUrl('ruleChains');
+ }
+ }
+
+}
+
export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => {
let label: string = component.ruleChain.name;
if (component.ruleChain.root) {
@@ -153,6 +178,7 @@ const routes: Routes = [
{
path: 'ruleChain/import',
component: RuleChainPageComponent,
+ canActivate: [RuleChainImportGuard],
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
@@ -164,8 +190,6 @@ const routes: Routes = [
import: true
},
resolve: {
- ruleChain: 'importRuleChain',
- ruleChainMetaData: 'importRuleChainMetadata',
ruleNodeComponents: RuleNodeComponentsResolver
}
}
@@ -182,20 +206,7 @@ const routes: Routes = [
RuleChainResolver,
ResolvedRuleChainMetaDataResolver,
RuleNodeComponentsResolver,
- {
- provide: 'importRuleChain',
- useValue: (route: ActivatedRouteSnapshot) => {
- const ruleChainImport: RuleChainImport = route.params.ruleChainImport;
- return ruleChainImport.ruleChain;
- }
- },
- {
- provide: 'importRuleChainMetadata',
- useValue: (route: ActivatedRouteSnapshot) => {
- const ruleChainImport: RuleChainImport = route.params.ruleChainImport;
- return ruleChainImport.metadata;
- }
- }
+ RuleChainImportGuard
]
})
export class RuleChainRoutingModule { }
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts
index e7c7c4d76a..e725ed8f9a 100644
--- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts
+++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts
@@ -32,6 +32,8 @@ import {RuleChainService} from '@core/http/rule-chain.service';
import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.component';
import {DialogService} from '@core/services/dialog.service';
import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.component';
+import { ImportExportService } from '@home/components/import-export/import-export.service';
+import { ItemBufferService } from '@core/services/item-buffer.service';
@Injectable()
export class RuleChainsTableConfigResolver implements Resolve> {
@@ -40,6 +42,8 @@ export class RuleChainsTableConfigResolver implements Resolve {
+ if (ruleChainImport) {
+ this.itembuffer.storeRuleChainImport(ruleChainImport);
+ this.router.navigateByUrl(`ruleChains/ruleChain/import`);
+ }
+ });
}
openRuleChain($event: Event, ruleChain: RuleChain) {
@@ -135,8 +143,7 @@ export class RuleChainsTableConfigResolver implements Resolve { return {
+ getStateParams(): StateParams {
+ return {};
+ }
+ } as IStateController},
+ {});
@ViewChild('dashboard', {static: true}) dashboard: IDashboardComponent;
@@ -97,7 +100,9 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit {
private dialogService: DialogService,
private importExport: ImportExportService,
private dialog: MatDialog,
- private translate: TranslateService) {
+ private translate: TranslateService,
+ private utils: UtilsService,
+ private entityService: EntityService) {
super(store);
this.authUser = getCurrentAuthUser(this.store);
diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts
index 97cec76f72..829f066c02 100644
--- a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts
+++ b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts
@@ -190,7 +190,7 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af
this.entityTypeList = [];
value.forEach((entityType) => {
this.entityTypeList.push({
- name: this.translate.instant(entityTypeTranslations.get(entityType).type),
+ name: entityTypeTranslations.has(entityType) ? this.translate.instant(entityTypeTranslations.get(entityType).type) : 'Unknown',
value: entityType
});
});
diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts
index 6d408e1d22..f014f865a7 100644
--- a/ui-ngx/src/app/shared/models/alarm.models.ts
+++ b/ui-ngx/src/app/shared/models/alarm.models.ts
@@ -22,6 +22,8 @@ import {AlarmId} from '@shared/models/id/alarm-id';
import {EntityId} from '@shared/models/id/entity-id';
import { ActionStatus } from '@shared/models/audit-log.models';
import { TimePageLink } from '@shared/models/page/page-link';
+import { NULL_UUID } from '@shared/models/id/has-uuid';
+import { EntityType } from '@shared/models/entity-type.models';
export enum AlarmSeverity {
CRITICAL = 'CRITICAL',
@@ -103,6 +105,27 @@ export interface AlarmInfo extends Alarm {
originatorName: string;
}
+export const simulatedAlarm: AlarmInfo = {
+ tenantId: new TenantId(NULL_UUID),
+ createdTime: new Date().getTime(),
+ startTs: new Date().getTime(),
+ endTs: 0,
+ ackTs: 0,
+ clearTs: 0,
+ originatorName: 'Simulated',
+ originator: {
+ entityType: EntityType.DEVICE,
+ id: "1"
+ },
+ type: 'TEMPERATURE',
+ severity: AlarmSeverity.MAJOR,
+ status: AlarmStatus.ACTIVE_UNACK,
+ details: {
+ message: "Temperature is high!"
+ },
+ propagate: false
+};
+
export interface AlarmField {
keyName: string;
value: string;
diff --git a/ui-ngx/src/app/shared/models/event.models.ts b/ui-ngx/src/app/shared/models/event.models.ts
index a8f8927a99..b19a376109 100644
--- a/ui-ngx/src/app/shared/models/event.models.ts
+++ b/ui-ngx/src/app/shared/models/event.models.ts
@@ -14,15 +14,11 @@
/// 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';
+import { ContentType } from '@shared/models/constants';
export enum EventType {
ERROR = 'ERROR',
@@ -72,7 +68,7 @@ export interface DebugRuleNodeEventBody extends BaseEventBody {
msgId: string;
msgType: string;
relationType: string;
- dataType: MsgDataType;
+ dataType: ContentType;
data: string;
metadata: string;
error: string;
diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts
index e4e35a481e..307d1e4e3f 100644
--- a/ui-ngx/src/app/shared/models/page/page-link.ts
+++ b/ui-ngx/src/app/shared/models/page/page-link.ts
@@ -94,14 +94,15 @@ export class PageLink {
pageData.data = pageData.data.filter((entity) => searchFunction(entity, this.textSearch));
}
pageData.totalElements = pageData.data.length;
- pageData.totalPages = Math.ceil(pageData.totalElements / this.pageSize);
+ pageData.totalPages = this.pageSize === Number.POSITIVE_INFINITY ? 1 : Math.ceil(pageData.totalElements / this.pageSize);
if (this.sortOrder) {
pageData.data = pageData.data.sort((a, b) => this.sort(a, b));
}
- const startIndex = this.pageSize * this.page;
- const endIndex = startIndex + this.pageSize;
- pageData.data = pageData.data.slice(startIndex, startIndex + this.pageSize);
- pageData.hasNext = pageData.totalElements > startIndex + pageData.data.length;
+ if (this.pageSize !== Number.POSITIVE_INFINITY) {
+ const startIndex = this.pageSize * this.page;
+ pageData.data = pageData.data.slice(startIndex, startIndex + this.pageSize);
+ pageData.hasNext = pageData.totalElements > startIndex + pageData.data.length;
+ }
return pageData;
}
diff --git a/ui-ngx/src/app/shared/models/page/sort-order.ts b/ui-ngx/src/app/shared/models/page/sort-order.ts
index b51791ca7d..c1511e07f8 100644
--- a/ui-ngx/src/app/shared/models/page/sort-order.ts
+++ b/ui-ngx/src/app/shared/models/page/sort-order.ts
@@ -24,3 +24,19 @@ export enum Direction {
ASC = 'ASC',
DESC = 'DESC'
}
+
+export function sortOrderFromString(strSortOrder: string): SortOrder {
+ let property: string;
+ let direction = Direction.ASC;
+ if (strSortOrder.startsWith('-')) {
+ direction = Direction.DESC;
+ property = strSortOrder.substring(1);
+ } else {
+ if (strSortOrder.startsWith('+')) {
+ property = strSortOrder.substring(1);
+ } else {
+ property = strSortOrder;
+ }
+ }
+ return {property, direction};
+}
diff --git a/ui-ngx/src/app/shared/models/rule-chain.models.ts b/ui-ngx/src/app/shared/models/rule-chain.models.ts
index 3f6c205e08..8596bf4ba7 100644
--- a/ui-ngx/src/app/shared/models/rule-chain.models.ts
+++ b/ui-ngx/src/app/shared/models/rule-chain.models.ts
@@ -33,7 +33,7 @@ export interface RuleChain extends BaseData {
export interface RuleChainMetaData {
ruleChainId: RuleChainId;
- firstNodeIndex: number;
+ firstNodeIndex?: number;
nodes: Array;
connections: Array;
ruleChainConnections: Array;
@@ -45,7 +45,8 @@ export interface ResolvedRuleChainMetaData extends RuleChainMetaData {
export interface RuleChainImport {
ruleChain: RuleChain;
- metadata: ResolvedRuleChainMetaData;
+ metadata: RuleChainMetaData;
+ resolvedMetadata?: ResolvedRuleChainMetaData;
}
export interface NodeConnectionInfo {
diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts
index e80e43a03b..082d82f40b 100644
--- a/ui-ngx/src/app/shared/models/rule-node.models.ts
+++ b/ui-ngx/src/app/shared/models/rule-node.models.ts
@@ -26,18 +26,12 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AbstractControl, FormGroup } from '@angular/forms';
-export enum MsgDataType {
- JSON = 'JSON',
- TEXT = 'TEXT',
- BINARY = 'BINARY'
-}
-
export interface RuleNodeConfiguration {
[key: string]: any;
}
export interface RuleNode extends BaseData {
- ruleChainId: RuleChainId;
+ ruleChainId?: RuleChainId;
type: string;
name: string;
debugMode: boolean;
diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
index d0c837332a..cf2f9d93db 100644
--- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
+++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
@@ -229,7 +229,7 @@ export interface TelemetryService {
export class TelemetrySubscriber {
- private dataSubject = new ReplaySubject();
+ private dataSubject = new ReplaySubject(1);
private reconnectSubject = new Subject();
private zone: NgZone;
diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts
index ab42427e58..32ec5316af 100644
--- a/ui-ngx/src/app/shared/models/widget.models.ts
+++ b/ui-ngx/src/app/shared/models/widget.models.ts
@@ -363,8 +363,6 @@ export interface WidgetConfig {
datasources?: Array;
targetDeviceAliasIds?: Array;
[key: string]: any;
-
- // TODO:
}
export interface Widget {
diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss
index 73180b4eae..e5d7b04f67 100644
--- a/ui-ngx/src/styles.scss
+++ b/ui-ngx/src/styles.scss
@@ -509,10 +509,10 @@ mat-label {
.mat-row {
transition: background-color .2s;
&:hover:not(.tb-current-entity) {
- background-color: #f4f4f4;
+ background-color: rgba(221, 221, 221, 0.3);
}
&.tb-current-entity {
- background-color: #e9e9e9;
+ background-color: rgba(221, 221, 221, 0.65);
}
}
@@ -561,6 +561,9 @@ mat-label {
text-overflow: ellipsis;
white-space: nowrap;
}
+ &.mat-table-sticky {
+ background: transparent;
+ }
}
.mat-cell, .mat-footer-cell {