Browse Source

UI: RuleNode configuration

pull/2411/head
Igor Kulikov 7 years ago
parent
commit
a42bd5d7ee
  1. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
  2. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java
  3. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java
  4. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java
  5. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java
  6. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java
  7. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java
  8. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
  9. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java
  10. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
  11. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
  12. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css
  13. 22
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  14. 5
      ui-ngx/angular.json
  15. 5
      ui-ngx/package-lock.json
  16. 1
      ui-ngx/package.json
  17. 35
      ui-ngx/proxy.conf.js
  18. 22
      ui-ngx/src/app/core/api/public-api.ts
  19. 1
      ui-ngx/src/app/core/http/dashboard.service.ts
  20. 34
      ui-ngx/src/app/core/http/public-api.ts
  21. 63
      ui-ngx/src/app/core/http/rule-chain.service.ts
  22. 1
      ui-ngx/src/app/core/local-storage/local-storage.service.ts
  23. 23
      ui-ngx/src/app/core/public-api.ts
  24. 1
      ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts
  25. 32
      ui-ngx/src/app/core/services/public-api.ts
  26. 1
      ui-ngx/src/app/core/services/raf.service.ts
  27. 52
      ui-ngx/src/app/core/services/resources.service.ts
  28. 31
      ui-ngx/src/app/core/services/script/node-script-test.service.ts
  29. 4
      ui-ngx/src/app/core/services/utils.service.ts
  30. 9
      ui-ngx/src/app/core/utils.ts
  31. 1
      ui-ngx/src/app/core/ws/telemetry-websocket.service.ts
  32. 1
      ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts
  33. 2
      ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts
  34. 2
      ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.ts
  35. 2
      ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html
  36. 5
      ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts
  37. 4
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html
  38. 1
      ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts
  39. 17
      ui-ngx/src/app/modules/home/components/public-api.ts
  40. 8
      ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html
  41. 4
      ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts
  42. 3
      ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts
  43. 2
      ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts
  44. 2
      ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts
  45. 2
      ui-ngx/src/app/modules/home/components/widget/legend-config.component.html
  46. 1
      ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts
  47. 10
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts
  48. 15
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts
  49. 5
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  50. 8
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  51. 6
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  52. 1
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts
  53. 1
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts
  54. 2
      ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts
  55. 17
      ui-ngx/src/app/modules/home/pages/public-api.ts
  56. 56
      ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html
  57. 2
      ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html
  58. 8
      ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts
  59. 28
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html
  60. 27
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.scss
  61. 208
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts
  62. 6
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html
  63. 1
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts
  64. 101
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
  65. 26
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts
  66. 10
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts
  67. 4
      ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html
  68. 2
      ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts
  69. 1
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts
  70. 1
      ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts
  71. 17
      ui-ngx/src/app/modules/home/public-api.ts
  72. 2
      ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts
  73. 1
      ui-ngx/src/app/shared/components/dashboard-select.component.ts
  74. 2
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts
  75. 2
      ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts
  76. 2
      ui-ngx/src/app/shared/components/entity/entity-list.component.ts
  77. 2
      ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts
  78. 2
      ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts
  79. 2
      ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts
  80. 1
      ui-ngx/src/app/shared/components/fab-toolbar.component.ts
  81. 4
      ui-ngx/src/app/shared/components/json-form/json-form.component.ts
  82. 1
      ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx
  83. 4
      ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx
  84. 3
      ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx
  85. 4
      ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts
  86. 2
      ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts
  87. 17
      ui-ngx/src/app/shared/components/public-api.ts
  88. 2
      ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.ts
  89. 8
      ui-ngx/src/app/shared/components/time/timewindow.component.html
  90. 1
      ui-ngx/src/app/shared/components/time/timewindow.component.ts
  91. 1
      ui-ngx/src/app/shared/models/constants.ts
  92. 33
      ui-ngx/src/app/shared/models/id/public-api.ts
  93. 3
      ui-ngx/src/app/shared/models/material.models.ts
  94. 19
      ui-ngx/src/app/shared/models/page/public-api.ts
  95. 47
      ui-ngx/src/app/shared/models/public-api.ts
  96. 69
      ui-ngx/src/app/shared/models/rule-node.models.ts
  97. 4
      ui-ngx/src/app/shared/models/user.model.ts
  98. 2
      ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts
  99. 1
      ui-ngx/src/app/shared/pipe/keyboard-shortcut.pipe.ts
  100. 22
      ui-ngx/src/app/shared/pipe/public-api.ts

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java

@ -41,7 +41,7 @@ import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
nodeDescription = "Periodically generates messages",
nodeDetails = "Generates messages with configurable period. Javascript function used for message generation.",
inEnabled = false,
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeGeneratorConfig",
icon = "repeat"
)

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java

@ -32,7 +32,7 @@ import org.thingsboard.server.common.msg.TbMsg;
relationTypes = {"True", "False"},
nodeDescription = "Filter incoming messages by Message Type",
nodeDetails = "If incoming MessageType is expected - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbFilterNodeMessageTypeConfig")
public class TbMsgTypeFilterNode implements TbNode {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java

@ -30,7 +30,7 @@ import org.thingsboard.server.common.msg.TbMsg;
relationTypes = {"True", "False"},
nodeDescription = "Filter incoming messages by message Originator Type",
nodeDetails = "If Originator Type of incoming message is expected - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbFilterNodeOriginatorTypeConfig")
public class TbOriginatorTypeFilterNode implements TbNode {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java

@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
"If Latest Telemetry enrichment configured, latest telemetry added into metadata. " +
"To access those attributes in other nodes this template can be used " +
"<code>metadata.temperature</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbEnrichmentNodeCustomerAttributesConfig")
public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java

@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
"If Latest Telemetry enrichment configured, latest telemetry added into metadata. " +
"To access those attributes in other nodes this template can be used " +
"<code>metadata.temperature</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbEnrichmentNodeRelatedAttributesConfig")
public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java

@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
"If Latest Telemetry enrichment configured, latest telemetry added into metadata. " +
"To access those attributes in other nodes this template can be used " +
"<code>metadata.temperature</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbEnrichmentNodeTenantAttributesConfig")
public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java

@ -47,7 +47,7 @@ import java.util.concurrent.TimeoutException;
configClazz = TbMqttNodeConfiguration.class,
nodeDescription = "Publish messages to the MQTT broker",
nodeDetails = "Will publish message payload to the MQTT broker with QoS <b>AT_LEAST_ONCE</b>.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeMqttConfig",
icon = "call_split"
)

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java

@ -42,7 +42,7 @@ import java.util.Set;
configClazz = TbMsgAttributesNodeConfiguration.class,
nodeDescription = "Saves attributes data",
nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeAttributesConfig",
icon = "file_upload"
)

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java

@ -43,7 +43,7 @@ import java.util.Map;
configClazz = TbMsgTimeseriesNodeConfiguration.class,
nodeDescription = "Saves timeseries data",
nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeTimeseriesConfig",
icon = "file_upload"
)

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java

@ -44,7 +44,7 @@ import java.util.HashSet;
nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
"If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded.<br/>" +
"Alarm Originator found only in case original Originator is <code>Alarm</code> entity.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbTransformationNodeChangeOriginatorConfig",
icon = "find_replace"
)

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java

@ -33,7 +33,7 @@ import org.thingsboard.server.common.msg.TbMsg;
"Should return the following structure:<br/>" +
"<code>{ msg: <i style=\"color: #666;\">new payload</i>,<br/>&nbsp&nbsp&nbspmetadata: <i style=\"color: #666;\">new metadata</i>,<br/>&nbsp&nbsp&nbspmsgType: <i style=\"color: #666;\">new msgType</i> }</code><br/>" +
"All fields in resulting object are optional and will be taken from original message if not specified.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbTransformationNodeScriptConfig")
public class TbTransformMsgNode extends TbAbstractTransformNode {

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css

@ -1,2 +0,0 @@
.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}.tb-generator-config tb-json-content.tb-message-body,.tb-generator-config tb-json-object-edit.tb-metadata-json{height:200px;display:block}.tb-mqtt-config .tb-credentials-panel-group .tb-panel-title{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:90px}@media (min-width:960px){.tb-mqtt-config .tb-credentials-panel-group .tb-panel-title{min-width:180px}}.tb-mqtt-config .tb-credentials-panel-group .tb-panel-prompt{font-size:14px;color:rgba(0,0,0,.87);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tb-mqtt-config .tb-credentials-panel-group.disabled .tb-panel-prompt,.tb-mqtt-config .tb-credentials-panel-group.disabled .tb-panel-title{color:rgba(0,0,0,.38)}.tb-mqtt-config .tb-credentials-panel-group md-icon.md-expansion-panel-icon{margin-right:0}.tb-mqtt-config .tb-container{width:100%}.tb-mqtt-config .dropdown-messages .tb-error-message{padding:5px 0 0}.tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}.tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}.tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}.tb-kv-map-config .body .row{padding-top:5px;max-height:40px}.tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}.tb-kv-map-config .body md-input-container.cell{margin:0;max-height:40px}.tb-kv-map-config .body .md-button{margin:0}
/*# sourceMappingURL=rulenode-core-config.css.map*/

22
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

5
ui-ngx/angular.json

@ -78,7 +78,8 @@
"node_modules/ace-builds/src-min/snippets/css.js",
"node_modules/ace-builds/src-min/snippets/json.js",
"node_modules/ace-builds/src-min/snippets/java.js",
"node_modules/ace-builds/src-min/snippets/javascript.js"
"node_modules/ace-builds/src-min/snippets/javascript.js",
"node_modules/systemjs/dist/system.js"
],
"es5BrowserSupport": true,
"customWebpackConfig": {
@ -116,7 +117,7 @@
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"browserTarget": "thingsboard:build",
"proxyConfig": "proxy.conf.json"
"proxyConfig": "proxy.conf.js"
},
"configurations": {
"production": {

5
ui-ngx/package-lock.json

@ -12624,6 +12624,11 @@
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
"dev": true
},
"systemjs": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/systemjs/-/systemjs-0.21.5.tgz",
"integrity": "sha512-GWzZhN/x7Fsae2CYkz2GF7OgOS+YDgKulcgd5L1kTogZHMKDrPx5T8zI8I0y5RoU9Dx78Z7j1XMfuFa1thD84A=="
},
"tapable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",

1
ui-ngx/package.json

@ -74,6 +74,7 @@
"schema-inspector": "^1.6.8",
"screenfull": "^5.0.0",
"split.js": "^1.5.11",
"systemjs": "0.21.5",
"tinycolor2": "^1.4.1",
"tooltipster": "^4.2.7",
"tslib": "^1.10.0",

35
ui-ngx/proxy.conf.js

@ -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.
*/
const ruleNodeUiforwardHost = 'localhost';
const ruleNodeUiforwardPort = 8080;
const PROXY_CONFIG = {
'/api': {
'target': 'http://localhost:8080',
'secure': false
},
'/static/rulenode': {
'target': `http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`,
'secure': false
},
'/api/ws': {
'target': 'ws://localhost:8080',
'ws': true
}
}
module.exports = PROXY_CONFIG;

22
ui-ngx/src/app/core/api/public-api.ts

@ -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.
///
export * from './alias-controller';
export * from './data-aggregator';
export * from './datasource.service';
export * from './datasource-subcription';
export * from './widget-api.models';
export * from './widget-subscription';

1
ui-ngx/src/app/core/http/dashboard.service.ts

@ -25,6 +25,7 @@ import {WINDOW} from '@core/services/window.service';
import { ActivationEnd, NavigationEnd, Router } from '@angular/router';
import { filter, map, publishReplay, refCount } from 'rxjs/operators';
// @dynamic
@Injectable({
providedIn: 'root'
})

34
ui-ngx/src/app/core/http/public-api.ts

@ -0,0 +1,34 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
export * from './admin.service';
export * from './alarm.service';
export * from './asset.service';
export * from './attribute.service';
export * from './audit-log.service';
export * from './component-descriptor.service';
export * from './customer.service';
export * from './dashboard.service';
export * from './device.service';
export * from './entity.service';
export * from './entity-relation.service';
export * from './entity-view.service';
export * from './event.service';
export * from './http-utils';
export * from './rule-chain.service';
export * from './tenant.service';
export * from './user.service';
export * from './widget.service';

63
ui-ngx/src/app/core/http/rule-chain.service.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { ComponentFactory, Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { forkJoin, Observable, of } from 'rxjs/index';
import { HttpClient } from '@angular/common/http';
@ -28,12 +28,16 @@ import {
ruleNodeTypeComponentTypes, unknownNodeComponent
} from '@shared/models/rule-chain.models';
import { ComponentDescriptorService } from './component-descriptor.service';
import { LinkLabel, RuleNodeComponentDescriptor } from '@app/shared/models/rule-node.models';
import {
IRuleNodeConfigurationComponent,
LinkLabel,
RuleNodeComponentDescriptor
} from '@app/shared/models/rule-node.models';
import { ResourcesService } from '../services/resources.service';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { EntityType } from '@shared/models/entity-type.models';
import { deepClone } from '@core/utils';
import { deepClone, snakeCase } from '@core/utils';
@Injectable({
providedIn: 'root'
@ -41,6 +45,7 @@ import { deepClone } from '@core/utils';
export class RuleChainService {
private ruleNodeComponents: Array<RuleNodeComponentDescriptor>;
private ruleNodeConfigFactories: {[directive: string]: ComponentFactory<IRuleNodeConfigurationComponent>} = {};
constructor(
private http: HttpClient,
@ -105,13 +110,14 @@ export class RuleChainService {
);
}
public getRuleNodeComponents(config?: RequestConfig): Observable<Array<RuleNodeComponentDescriptor>> {
public getRuleNodeComponents(ruleNodeConfigResourcesModulesMap: {[key: string]: any}, config?: RequestConfig):
Observable<Array<RuleNodeComponentDescriptor>> {
if (this.ruleNodeComponents) {
return of(this.ruleNodeComponents);
} else {
return this.loadRuleNodeComponents(config).pipe(
mergeMap((components) => {
return this.resolveRuleNodeComponentsUiResources(components).pipe(
return this.resolveRuleNodeComponentsUiResources(components, ruleNodeConfigResourcesModulesMap).pipe(
map((ruleNodeComponents) => {
this.ruleNodeComponents = ruleNodeComponents;
this.ruleNodeComponents.push(ruleChainNodeComponent);
@ -132,6 +138,10 @@ export class RuleChainService {
}
}
public getRuleNodeConfigFactory(directive: string): ComponentFactory<IRuleNodeConfigurationComponent> {
return this.ruleNodeConfigFactories[directive];
}
public getRuleNodeComponentByClazz(clazz: string): RuleNodeComponentDescriptor {
const found = this.ruleNodeComponents.filter((component) => component.clazz === clazz);
if (found && found.length) {
@ -192,11 +202,12 @@ export class RuleChainService {
);
}
private resolveRuleNodeComponentsUiResources(components: Array<RuleNodeComponentDescriptor>):
private resolveRuleNodeComponentsUiResources(components: Array<RuleNodeComponentDescriptor>,
ruleNodeConfigResourcesModulesMap: {[key: string]: any}):
Observable<Array<RuleNodeComponentDescriptor>> {
const tasks: Observable<RuleNodeComponentDescriptor>[] = [];
components.forEach((component) => {
tasks.push(this.resolveRuleNodeComponentUiResources(component));
tasks.push(this.resolveRuleNodeComponentUiResources(component, ruleNodeConfigResourcesModulesMap));
});
return forkJoin(tasks).pipe(
catchError((err) => {
@ -205,13 +216,39 @@ export class RuleChainService {
);
}
private resolveRuleNodeComponentUiResources(component: RuleNodeComponentDescriptor): Observable<RuleNodeComponentDescriptor> {
const uiResources = component.configurationDescriptor.nodeDefinition.uiResources;
private resolveRuleNodeComponentUiResources(component: RuleNodeComponentDescriptor,
ruleNodeConfigResourcesModulesMap: {[key: string]: any}):
Observable<RuleNodeComponentDescriptor> {
const nodeDefinition = component.configurationDescriptor.nodeDefinition;
const uiResources = nodeDefinition.uiResources;
if (uiResources && uiResources.length) {
const commonResources = uiResources.filter((resource) => !resource.endsWith('.js'));
const moduleResource = uiResources.find((resource) => resource.endsWith('.js'));
const tasks: Observable<any>[] = [];
uiResources.forEach((uiResource) => {
tasks.push(this.resourcesService.loadResource(uiResource));
});
if (commonResources && commonResources.length) {
commonResources.forEach((resource) => {
tasks.push(this.resourcesService.loadResource(resource));
});
}
if (moduleResource) {
tasks.push(this.resourcesService.loadModule(moduleResource, ruleNodeConfigResourcesModulesMap).pipe(
map((res) => {
if (nodeDefinition.configDirective && nodeDefinition.configDirective.length) {
const selector = snakeCase(nodeDefinition.configDirective, '-');
const componentFactory = res.componentFactories.find((factory) =>
factory.selector === selector);
if (componentFactory) {
this.ruleNodeConfigFactories[nodeDefinition.configDirective] = componentFactory;
} else {
component.configurationDescriptor.nodeDefinition.uiResourceLoadError =
this.translate.instant('rulenode.directive-is-not-loaded',
{directiveName: nodeDefinition.configDirective});
}
}
return of(component);
})
));
}
return forkJoin(tasks).pipe(
map((res) => {
return component;
@ -231,7 +268,7 @@ export class RuleChainService {
map(ruleChain => ruleChain),
catchError((err) => {
const ruleChain = {
id: {
id: {
entityType: EntityType.RULE_CHAIN,
id: ruleChainId
}

1
ui-ngx/src/app/core/local-storage/local-storage.service.ts

@ -18,6 +18,7 @@ import { Injectable } from '@angular/core';
const APP_PREFIX = 'TB-';
// @dynamic
@Injectable(
{
providedIn: 'root'

23
ui-ngx/src/app/core/public-api.ts

@ -0,0 +1,23 @@
///
/// 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.
///
export * from './api/public-api';
export * from './http/public-api';
export * from './local-storage/local-storage.service';
export * from './services/public-api';
export * from './ws/telemetry-websocket.service';
export * from './core.state';
export * from './core.module';

1
ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts

@ -24,6 +24,7 @@ export interface ConfirmDialogData {
ok: string;
}
// @dynamic
@Component({
selector: 'tb-confirm-dialog',
templateUrl: './confirm-dialog.component.html',

32
ui-ngx/src/app/core/services/public-api.ts

@ -0,0 +1,32 @@
///
/// 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.
///
export * from './script/node-script-test.service';
export * from './broadcast.models';
export * from './broadcast.service';
export * from './dashboard-utils.service';
export * from './dialog.service';
export * from './dynamic-component-factory.service';
export * from './item-buffer.service';
export * from './menu.models';
export * from './menu.service';
export * from './notification.service';
export * from './raf.service';
export * from './resources.service';
export * from './time.service';
export * from './title.service';
export * from './utils.service';
export * from './window.service';

1
ui-ngx/src/app/core/services/raf.service.ts

@ -20,6 +20,7 @@ import { WINDOW } from '@core/services/window.service';
export type CancelAnimationFrame = () => void;
// @dynamic
@Injectable({
providedIn: 'root'
})

52
ui-ngx/src/app/core/services/resources.service.ts

@ -14,20 +14,25 @@
/// limitations under the License.
///
import { Injectable, Inject } from '@angular/core';
import { Injectable, Inject, ModuleWithComponentFactories, Compiler, Injector } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { ReplaySubject, Observable, throwError } from 'rxjs';
declare const SystemJS;
@Injectable({
providedIn: 'root'
})
export class ResourcesService {
private loadedResources: { [url: string]: ReplaySubject<any> } = {};
private loadedModules: { [url: string]: ReplaySubject<ModuleWithComponentFactories<any>> } = {};
private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0];
constructor(@Inject(DOCUMENT) private readonly document: any) {}
constructor(@Inject(DOCUMENT) private readonly document: any,
private compiler: Compiler,
private injector: Injector) {}
public loadResource(url: string): Observable<any> {
if (this.loadedResources[url]) {
@ -47,6 +52,49 @@ export class ResourcesService {
return this.loadResourceByType(fileType, url);
}
public loadModule(url: string, modulesMap: {[key: string]: any}): Observable<ModuleWithComponentFactories<any>> {
if (this.loadedModules[url]) {
return this.loadedModules[url].asObservable();
}
const subject = new ReplaySubject<ModuleWithComponentFactories<any>>();
this.loadedModules[url] = subject;
if (modulesMap) {
for (const moduleId of Object.keys(modulesMap)) {
SystemJS.set(moduleId, modulesMap[moduleId]);
}
}
SystemJS.import(url).then(
(module) => {
if (module.default) {
this.compiler.compileModuleAndAllComponentsAsync(module.default).then(
(compiled) => {
try {
compiled.ngModuleFactory.create(this.injector);
this.loadedModules[url].next(compiled);
this.loadedModules[url].complete();
} catch (e) {
this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`));
delete this.loadedModules[url];
}
},
(e) => {
this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`));
delete this.loadedModules[url];
}
);
} else {
this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export!`));
delete this.loadedModules[url];
}
},
(e) => {
this.loadedModules[url].error(new Error(`Unable to load module from url: ${url}`));
delete this.loadedModules[url];
}
);
return subject.asObservable();
}
private loadResourceByType(type: 'css' | 'js', url: string): Observable<any> {
const subject = new ReplaySubject();
this.loadedResources[url] = subject;

31
ui-ngx/src/app/core/services/script/node-script-test.service.ts

@ -0,0 +1,31 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class NodeScriptTestService {
testNodeScript(script: string, scriptType: any, functionTitle: string,
functionName: string, argNames: string[], ruleNodeId: string): Observable<string> {
console.log(`testNodeScript TODO: ${script}`);
return of(script);
}
}

4
ui-ngx/src/app/core/services/utils.service.ts

@ -14,6 +14,9 @@
/// limitations under the License.
///
// tslint:disable-next-line:no-reference
/// <reference path="../../../../src/typings/rawloader.typings.d.ts" />
import { Inject, Injectable, NgZone } from '@angular/core';
import { WINDOW } from '@core/services/window.service';
import { ExceptionData } from '@app/shared/models/error.models';
@ -67,6 +70,7 @@ const commonMaterialIcons: Array<string> = [ 'more_horiz', 'more_vert', 'open_in
'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export',
'share', 'add', 'edit', 'done' ];
// @dynamic
@Injectable({
providedIn: 'root'
})

9
ui-ngx/src/app/core/utils.ts

@ -387,3 +387,12 @@ export function guid(): string {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
const SNAKE_CASE_REGEXP = /[A-Z]/g;
export function snakeCase(name: string, separator: string): string {
separator = separator || '_';
return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => {
return (pos ? separator : '') + letter.toLowerCase();
});
}

1
ui-ngx/src/app/core/ws/telemetry-websocket.service.ts

@ -40,6 +40,7 @@ const RECONNECT_INTERVAL = 2000;
const WS_IDLE_TIMEOUT = 90000;
const MAX_PUBLISH_COMMANDS = 10;
// @dynamic
@Injectable({
providedIn: 'root'
})

1
ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts

@ -30,6 +30,7 @@ import {
AliasesEntitySelectPanelData
} from './aliases-entity-select-panel.component';
// @dynamic
@Component({
selector: 'tb-aliases-entity-select',
templateUrl: './aliases-entity-select.component.html',

2
ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts

@ -73,7 +73,7 @@ export class EntityAliasDialogComponent extends DialogComponent<EntityAliasDialo
public dialogRef: MatDialogRef<EntityAliasDialogComponent, EntityAlias>,
private fb: FormBuilder,
private utils: UtilsService,
private translate: TranslateService,
public translate: TranslateService,
private entityService: EntityService) {
super(store, router, dialogRef);
this.isAdd = data.isAdd;

2
ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.ts

@ -92,7 +92,7 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit,
filteredEntityAliases: Observable<Array<EntityAlias>>;
private searchText = '';
searchText = '';
private dirty = false;

2
ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html

@ -42,7 +42,7 @@
<fieldset [disabled]="isLoading$ | async">
<div fxFlex fxLayout="row" fxLayoutAlign="start center"
formArrayName="entityAliases"
*ngFor="let entityAliasControl of entityAliasesFormGroup.get('entityAliases').controls; let $index = index">
*ngFor="let entityAliasControl of entityAliasesFormArray().controls; let $index = index">
<span fxFlex="5">{{$index + 1}}.</span>
<div class="mat-elevation-z4 tb-alias" fxFlex="95" fxLayout="row" fxLayoutAlign="start center">
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block" fxFlex="150px">

5
ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts

@ -154,6 +154,11 @@ export class EntityAliasesDialogComponent extends DialogComponent<EntityAliasesD
return aliasFormControl;
}
entityAliasesFormArray(): FormArray {
return this.entityAliasesFormGroup.get('entityAliases') as FormArray;
}
ngOnInit(): void {
}

4
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html

@ -112,7 +112,7 @@
[selectFirstBundle]="false"
[selectBundleAlias]="selectedWidgetsBundleAlias"
[(ngModel)]="widgetsBundle"
(ngModelChange)="onWidgetsBundleChanged($event)">
(ngModelChange)="onWidgetsBundleChanged()">
</tb-widgets-bundle-select>
</div>
<button mat-button mat-raised-button [fxShow]="widgetsList.length > 0"
@ -195,7 +195,7 @@
<ngx-hm-carousel fxFlex *ngIf="mode === 'widget' && widgetsList.length > 0"
#carousel
[(ngModel)]="widgetsCarouselIndex"
(ngModelChange)="onWidgetsCarouselIndexChanged($event)"
(ngModelChange)="onWidgetsCarouselIndexChanged()"
[data]="widgetsList"
[infinite]="false"
class="carousel c-accent">

1
ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts

@ -54,6 +54,7 @@ import { ImportDialogCsvComponent, ImportDialogCsvData } from './import-dialog-c
import { ImportEntityData, ImportEntitiesResultInfo } from '@shared/models/entity.models';
import { RequestConfig } from '@core/http/http-utils';
// @dynamic
@Injectable()
export class ImportExportService {

17
ui-ngx/src/app/modules/home/components/public-api.ts

@ -0,0 +1,17 @@
///
/// 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.
///
export * from './home-components.module';

8
ui-ngx/src/app/modules/home/components/relation/relation-filters.component.html

@ -16,17 +16,17 @@
-->
<div class="tb-relation-filters" [formGroup]="relationFiltersFormGroup">
<div class="header" [fxShow]="relationFiltersFormGroup.get('relationFilters').length">
<div class="header" [fxShow]="relationFiltersFormArray().length">
<div fxLayout="row" fxLayoutAlign="start center">
<span class="cell" style="width: 200px; min-width: 200px;" translate>relation.type</span>
<span class="cell" fxFlex translate>entity.entity-types</span>
<span class="cell" style="width: 40px; min-width: 40px;">&nbsp;</span>
</div>
</div>
<div class="body" [fxShow]="relationFiltersFormGroup.get('relationFilters').length">
<div class="body" [fxShow]="relationFiltersFormArray().length">
<div class="row" fxFlex fxLayout="row"
fxLayoutAlign="start center" formArrayName="relationFilters"
*ngFor="let relationFilterControl of relationFiltersFormGroup.get('relationFilters').controls; let $index = index">
*ngFor="let relationFilterControl of relationFiltersFormArray().controls; let $index = index">
<div class="mat-elevation-z1" fxFlex fxLayout="row" fxLayoutAlign="start center">
<tb-relation-type-autocomplete
class="cell" style="width: 200px; min-width: 200px;"
@ -48,7 +48,7 @@
</div>
</div>
</div>
<div class="any-filter" [fxShow]="!relationFiltersFormGroup.get('relationFilters').length">
<div class="any-filter" [fxShow]="!relationFiltersFormArray().length">
<span fxLayoutAlign="center center"
class="tb-prompt" translate>relation.any-relation</span>
</div>

4
ui-ngx/src/app/modules/home/components/relation/relation-filters.component.ts

@ -69,6 +69,10 @@ export class RelationFiltersComponent extends PageComponent implements ControlVa
this.fb.array([]));
}
relationFiltersFormArray(): FormArray {
return this.relationFiltersFormGroup.get('relationFilters') as FormArray;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}

3
ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts

@ -14,6 +14,9 @@
/// limitations under the License.
///
// tslint:disable-next-line:no-reference
/// <reference path="../../../../../../../src/typings/split.js.typings.d.ts" />
import {
AfterViewInit,
ChangeDetectionStrategy,

2
ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts

@ -78,7 +78,7 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
displayAdvanced = false;
private modelValue: DataKey;
modelValue: DataKey;
private propagateChange = null;

2
ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts

@ -140,7 +140,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie
placeholder: string;
requiredText: string;
private searchText = '';
searchText = '';
private latestSearchTextResult: Array<DataKey> = null;
private dirty = false;

2
ui-ngx/src/app/modules/home/components/widget/legend-config.component.html

@ -17,7 +17,7 @@
-->
<button cdkOverlayOrigin #legendConfigPanelOrigin="cdkOverlayOrigin" [disabled]="disabled"
type="button"
mat-button mat-raised-button color="primary" (click)="openEditMode($event)">
mat-button mat-raised-button color="primary" (click)="openEditMode()">
<mat-icon class="material-icons">toc</mat-icon>
<span translate>legend.settings</span>
</button>

1
ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts

@ -59,6 +59,7 @@ import {
LegendConfigPanelData
} from '@home/components/widget/legend-config-panel.component';
// @dynamic
@Component({
selector: 'tb-legend-config',
templateUrl: './legend-config.component.html',

10
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts

@ -14,6 +14,9 @@
/// limitations under the License.
///
// tslint:disable-next-line:no-reference
/// <reference path="../../../../../../../src/typings/jquery.flot.typings.d.ts" />
import { JsonSettingsSchema, DataKey, DatasourceData } from '@shared/models/widget.models';
export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph';
@ -395,8 +398,8 @@ export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema {
return schema;
}
export function flotPieSettingsSchema(): JsonSettingsSchema {
return {
export const flotPieSettingsSchema: JsonSettingsSchema =
{
schema: {
type: 'object',
title: 'Settings',
@ -477,8 +480,7 @@ export function flotPieSettingsSchema(): JsonSettingsSchema {
},
'fontSize'
]
};
}
};
export function flotDatakeySettingsSchema(defaultShowLines: boolean): JsonSettingsSchema {
return {

15
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts

@ -36,12 +36,17 @@ import {
TbFlotTicksFormatterFunction,
TooltipValueFormatFunction
} from './flot-widget.models';
import * as moment from 'moment';
import * as tinycolor from 'tinycolor2';
import * as moment_ from 'moment';
import * as tinycolor_ from 'tinycolor2';
import { AggregationType } from '@shared/models/time/time.models';
import { CancelAnimationFrame } from '@core/services/raf.service';
import Timeout = NodeJS.Timeout;
const tinycolor = tinycolor_;
const moment = moment_;
const flotPieSettingsSchemaValue = flotPieSettingsSchema;
export class TbFlot {
private settings: TbFlotSettings;
@ -89,11 +94,11 @@ export class TbFlot {
private pieAnimationLastTime: number;
private pieAnimationCaf: CancelAnimationFrame;
static get pieSettingsSchema(): JsonSettingsSchema {
return flotPieSettingsSchema();
static pieSettingsSchema(): JsonSettingsSchema {
return flotPieSettingsSchemaValue;
}
static get pieDatakeySettingsSchema(): JsonSettingsSchema {
static pieDatakeySettingsSchema(): JsonSettingsSchema {
return {};
}

5
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -38,14 +38,17 @@ import { SharedModule } from '@shared/shared.module';
import { WidgetComponentsModule } from '@home/components/widget/widget-components.module';
import { WINDOW } from '@core/services/window.service';
import * as tinycolor from 'tinycolor2';
import * as tinycolor_ from 'tinycolor2';
import { TbFlot } from './lib/flot-widget';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { WidgetTypeId } from '@app/shared/models/id/widget-type-id';
import { TenantId } from '@app/shared/models/id/tenant-id';
const tinycolor = tinycolor_;
// declare var jQuery: any;
// @dynamic
@Injectable()
export class WidgetComponentService {

8
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -71,7 +71,7 @@
class="tb-hint">{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div *ngIf="dataSettings.get('datasources').length === 0; else datasourcesTemplate">
<div *ngIf="datasourcesFormArray().length === 0; else datasourcesTemplate">
<span translate fxLayoutAlign="center center"
class="tb-prompt">datasource.add-datasource-prompt</span>
</div>
@ -89,7 +89,7 @@
<div style="overflow: auto; padding-bottom: 15px;">
<div fxFlex fxLayout="row" fxLayoutAlign="start center"
formArrayName="datasources"
*ngFor="let datasourceControl of dataSettings.get('datasources').controls; let $index = index;">
*ngFor="let datasourceControl of datasourcesFormArray().controls; let $index = index;">
<span fxFlex="5">{{$index + 1}}.</span>
<div [formGroupName]="$index" class="mat-elevation-z4" fxFlex
fxLayout="row"
@ -109,7 +109,7 @@
</mat-form-field>
<section class="tb-datasource" [ngSwitch]="datasourceControl.get('type').value">
<ng-template [ngSwitchCase]="datasourceType.function">
<mat-form-field floatLabel="always" [fxShow]="widgetType !== widgetTypes.alarm"
<mat-form-field floatLabel="always"
class="tb-datasource-name" style="min-width: 200px;">
<mat-label></mat-label>
<input matInput
@ -156,7 +156,7 @@
type="button"
mat-button mat-raised-button color="primary"
[fxShow]="modelValue?.typeParameters &&
(modelValue?.typeParameters.maxDatasources == -1 || dataSettings.get('datasources').controls.length < modelValue?.typeParameters.maxDatasources)"
(modelValue?.typeParameters.maxDatasources == -1 || datasourcesFormArray().controls.length < modelValue?.typeParameters.maxDatasources)"
(click)="addDatasource()"
matTooltip="{{ 'widget-config.add-datasource' | translate }}"
matTooltipPosition="above">

6
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -128,7 +128,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
selectedTab: number;
private modelValue: WidgetConfigComponentData;
modelValue: WidgetConfigComponentData;
private propagateChange = null;
@ -307,6 +307,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
this.fb.control(null, []));
}
datasourcesFormArray(): FormArray {
return this.dataSettings.get('datasources') as FormArray;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}

1
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts

@ -86,6 +86,7 @@ import {
import { ImportExportService } from '@home/components/import-export/import-export.service';
import { AuthState } from '@app/core/auth/auth.models';
// @dynamic
@Component({
selector: 'tb-dashboard-page',
templateUrl: './dashboard-page.component.html',

1
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts

@ -90,6 +90,7 @@ const routes: Routes = [
}
];
// @dynamic
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],

2
ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts

@ -47,7 +47,7 @@ import { map } from 'rxjs/operators';
})
export class EntityStateControllerComponent extends StateControllerComponent implements OnInit, OnDestroy {
private selectedStateIndex = -1;
selectedStateIndex = -1;
constructor(protected router: Router,
protected route: ActivatedRoute,

17
ui-ngx/src/app/modules/home/pages/public-api.ts

@ -0,0 +1,17 @@
///
/// 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.
///
export * from './home-pages.module';

56
ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html

@ -0,0 +1,56 @@
<!--
Copyright © 2016-2019 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form #ruleNodeForm="ngForm" (ngSubmit)="add()" style="min-width: 650px;">
<mat-toolbar fxLayout="row" color="primary">
<h2 translate>rulenode.add</h2>
<span fxFlex></span>
<div [tb-help]="helpLinkIdForRuleNodeType()"></div>
<button mat-button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async">
<tb-rule-node #tbRuleNode
[ruleNode]="ruleNode"
[ruleChainId]="ruleChainId"
[isEdit]="true"
[isReadOnly]="false">
</tb-rule-node>
</fieldset>
</div>
<div mat-dialog-actions fxLayout="row">
<span fxFlex></span>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || tbRuleNode.ruleNodeForm.invalid || !tbRuleNode.ruleNodeForm.dirty">
{{ 'action.add' | translate }}
</button>
<button mat-button color="primary"
style="margin-right: 20px;"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
</div>
</form>

2
ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html

@ -45,7 +45,7 @@
<mat-option *ngFor="let label of filteredLabels | async" [value]="label">
<span [innerHTML]="label.name | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredLabels | async)?.length" [value]="null" class="tb-not-found">
<mat-option *ngIf="(filteredLabels | async)?.length === 0" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
<span translate>rulenode.no-link-labels-found</span>

8
ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts

@ -94,9 +94,9 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
filteredLabels: Observable<Array<LinkLabel>>;
private labels: Array<LinkLabel> = [];
labels: Array<LinkLabel> = [];
private searchText = '';
searchText = '';
private propagateChange = (v: any) => { };
@ -190,7 +190,7 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
}
add(event: MatChipInputEvent): void {
if (!this.matAutocomplete.isOpen) {
if (!this.matAutocomplete.isOpen || this.allowCustom) {
this.transformLinkLabel(event.value);
}
}
@ -250,8 +250,8 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan
if (this.required) {
this.chipList.errorState = false;
}
this.updateModel();
}
this.updateModel();
}
clear(value: string = '') {

28
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html

@ -0,0 +1,28 @@
<!--
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.
-->
<div [formGroup]="ruleNodeConfigFormGroup">
<ng-container #definedConfigContent></ng-container>
<div class="tb-rulenode-directive-error" *ngIf="definedDirectiveError">{{definedDirectiveError}}</div>
<tb-json-object-edit *ngIf="!useDefinedDirective()" #jsonObjectEditComponent
class="tb-rule-node-configuration-json"
formControlName="configuration"
[label]="'rulenode.configuration' | translate"
[required]="required"
[fillHeight]="true">
</tb-json-object-edit>
</div>

27
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.scss

@ -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.
*/
:host {
tb-json-object-edit.tb-rule-node-configuration-json {
display: block;
height: 300px;
}
.tb-rulenode-directive-error {
font-size: 13px;
font-weight: 400;
color: rgb(221, 44, 0);
}
}

208
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts

@ -0,0 +1,208 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
AfterViewInit,
Component, ElementRef,
EventEmitter, forwardRef,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild,
Compiler,
Injector, ComponentRef, OnDestroy
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, NgForm, Validators } from '@angular/forms';
import { FcRuleNode, FcRuleEdge } from './rulechain-page.models';
import { RuleNodeType, LinkLabel, RuleNodeDefinition, RuleNodeConfiguration, IRuleNodeConfigurationComponent } from '@shared/models/rule-node.models';
import { EntityType } from '@shared/models/entity-type.models';
import { Observable, of, Subscription } from 'rxjs';
import { RuleChainService } from '@core/http/rule-chain.service';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { deepClone } from '@core/utils';
import { EntityAlias } from '@shared/models/alias.models';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import { MatChipList, MatAutocomplete, MatChipInputEvent, MatAutocompleteSelectedEvent } from '@angular/material';
import { TranslateService } from '@ngx-translate/core';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { catchError, map, mergeMap, share } from 'rxjs/operators';
import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component';
import { SharedModule } from '@shared/shared.module';
import { WidgetComponentsModule } from '@home/components/widget/widget-components.module';
import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
import { ViewContainerRef } from '@angular/core';
import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component';
@Component({
selector: 'tb-rule-node-config',
templateUrl: './rule-node-config.component.html',
styleUrls: ['./rule-node-config.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RuleNodeConfigComponent),
multi: true
}]
})
export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, OnDestroy, AfterViewInit {
@ViewChild('definedConfigContent', {read: ViewContainerRef, static: true}) definedConfigContainer: ViewContainerRef;
@ViewChild('jsonObjectEditComponent', {static: false}) jsonObjectEditComponent: JsonObjectEditComponent;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
@Input()
ruleNodeId: string;
nodeDefinitionValue: RuleNodeDefinition;
@Input()
set nodeDefinition(nodeDefinition: RuleNodeDefinition) {
if (this.nodeDefinitionValue !== nodeDefinition) {
this.nodeDefinitionValue = nodeDefinition;
if (this.nodeDefinitionValue) {
this.validateDefinedDirective();
}
}
}
get nodeDefinition(): RuleNodeDefinition {
return this.nodeDefinitionValue;
}
definedDirectiveError: string;
ruleNodeConfigFormGroup: FormGroup;
changeSubscription: Subscription;
private definedConfigComponentRef: ComponentRef<IRuleNodeConfigurationComponent>;
private definedConfigComponent: IRuleNodeConfigurationComponent;
private configuration: RuleNodeConfiguration;
private propagateChange = (v: any) => { };
constructor(private translate: TranslateService,
private ruleChainService: RuleChainService,
private fb: FormBuilder) {
this.ruleNodeConfigFormGroup = this.fb.group({
configuration: [null, Validators.required]
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit(): void {
}
ngOnDestroy(): void {
if (this.definedConfigComponentRef) {
this.definedConfigComponentRef.destroy();
}
}
ngAfterViewInit(): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.ruleNodeConfigFormGroup.disable({emitEvent: false});
} else {
this.ruleNodeConfigFormGroup.enable({emitEvent: false});
}
}
writeValue(value: RuleNodeConfiguration): void {
this.configuration = value;
if (this.changeSubscription) {
this.changeSubscription.unsubscribe();
this.changeSubscription = null;
}
if (this.definedConfigComponent) {
this.definedConfigComponent.configuration = this.configuration;
this.changeSubscription = this.definedConfigComponent.configurationChanged.subscribe((configuration) => {
this.updateModel(configuration);
});
} else {
this.ruleNodeConfigFormGroup.get('configuration').patchValue(value, {emitEvent: false});
this.changeSubscription = this.ruleNodeConfigFormGroup.get('configuration').valueChanges.subscribe(
(configuration: RuleNodeConfiguration) => {
this.updateModel(configuration);
}
);
}
}
useDefinedDirective(): boolean {
return this.nodeDefinition &&
(this.nodeDefinition.configDirective &&
this.nodeDefinition.configDirective.length) && !this.definedDirectiveError;
}
private updateModel(configuration: RuleNodeConfiguration) {
if (this.definedConfigComponent || this.ruleNodeConfigFormGroup.valid) {
this.propagateChange(configuration);
} else {
this.propagateChange(this.required ? null : configuration);
}
}
private validateDefinedDirective() {
if (this.definedConfigComponentRef) {
this.definedConfigComponentRef.destroy();
this.definedConfigComponentRef = null;
}
if (this.nodeDefinition.uiResourceLoadError && this.nodeDefinition.uiResourceLoadError.length) {
this.definedDirectiveError = this.nodeDefinition.uiResourceLoadError;
} else if (this.nodeDefinition.configDirective && this.nodeDefinition.configDirective.length) {
if (this.changeSubscription) {
this.changeSubscription.unsubscribe();
this.changeSubscription = null;
}
this.definedConfigContainer.clear();
const factory = this.ruleChainService.getRuleNodeConfigFactory(this.nodeDefinition.configDirective);
this.definedConfigComponentRef = this.definedConfigContainer.createComponent(factory);
this.definedConfigComponent = this.definedConfigComponentRef.instance;
this.definedConfigComponent.ruleNodeId = this.ruleNodeId;
this.definedConfigComponent.configuration = this.configuration;
this.changeSubscription = this.definedConfigComponent.configurationChanged.subscribe((configuration) => {
this.updateModel(configuration);
});
}
}
}

6
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html

@ -31,7 +31,11 @@
{{ 'rulenode.debug-mode' | translate }}
</mat-checkbox>
</section>
TODO: tb-rule-node-config
<tb-rule-node-config
formControlName="configuration"
[ruleNodeId]="ruleNode.ruleNodeId?.id"
[nodeDefinition]="ruleNode.component.configurationDescriptor.nodeDefinition">
</tb-rule-node-config>
<div formGroupName="additionalInfo" fxLayout="column">
<mat-form-field class="mat-block">
<mat-label translate>rulenode.description</mat-label>

1
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts

@ -70,6 +70,7 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
this.ruleNodeFormGroup = this.fb.group({
name: [this.ruleNode.name, [Validators.required]],
debugMode: [this.ruleNode.debugMode, []],
configuration: [this.ruleNode.configuration, [Validators.required]],
additionalInfo: this.fb.group(
{
description: [this.ruleNode.additionalInfo ? this.ruleNode.additionalInfo.description : ''],

101
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts

@ -151,7 +151,6 @@ export class RuleChainPageComponent extends PageComponent
return source.type === FlowchartConstants.rightConnectorType && destination.type === FlowchartConstants.leftConnectorType;
},
createEdge: (event, edge: FcRuleEdge) => {
console.log('TODO');
const sourceNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source) as FcRuleNode;
if (sourceNode.component.type === RuleNodeType.INPUT) {
const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination) as FcRuleNode;
@ -185,9 +184,8 @@ export class RuleChainPageComponent extends PageComponent
}
}
},
dropNode: (event, node) => {
console.log('TODO dropNode');
console.log(node);
dropNode: (event, node: FcRuleNode) => {
this.addRuleNode(node);
}
};
@ -269,7 +267,7 @@ export class RuleChainPageComponent extends PageComponent
this.createRuleChainModel();
}
private updateRuleChainLibrary() {
updateRuleChainLibrary() {
const search = this.ruleNodeTypeSearch.toUpperCase();
const res = this.ruleNodeComponents.filter(
(ruleNodeComponent) => ruleNodeComponent.name.toUpperCase().includes(search));
@ -734,6 +732,46 @@ export class RuleChainPageComponent extends PageComponent
this.createRuleChainModel();
}
addRuleNode(ruleNode: FcRuleNode) {
ruleNode.configuration = deepClone(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration);
const ruleChainId = this.ruleChain.id ? this.ruleChain.id.id : null;
this.dialog.open<AddRuleNodeDialogComponent, AddRuleNodeDialogData,
FcRuleNode>(AddRuleNodeDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
ruleNode,
ruleChainId
}
}).afterClosed().subscribe(
(addedRuleNode) => {
if (addedRuleNode) {
addedRuleNode.id = 'rule-chain-node-' + this.nextNodeID++;
addedRuleNode.connectors = [];
if (addedRuleNode.component.configurationDescriptor.nodeDefinition.inEnabled) {
addedRuleNode.connectors.push(
{
id: (this.nextConnectorID++) + '',
type: FlowchartConstants.leftConnectorType
}
);
}
if (addedRuleNode.component.configurationDescriptor.nodeDefinition.outEnabled) {
addedRuleNode.connectors.push(
{
id: (this.nextConnectorID++) + '',
type: FlowchartConstants.rightConnectorType
}
);
}
this.ruleChainModel.nodes.push(addedRuleNode);
this.onModelChanged();
this.updateRuleNodesHighlight();
}
}
);
}
addRuleNodeLink(link: FcRuleEdge, labels: {[label: string]: LinkLabel}, allowCustomLabels: boolean): Observable<FcRuleEdge> {
return this.dialog.open<AddRuleNodeLinkDialogComponent, AddRuleNodeLinkDialogData,
FcRuleEdge>(AddRuleNodeLinkDialogComponent, {
@ -885,3 +923,56 @@ export class AddRuleNodeLinkDialogComponent extends DialogComponent<AddRuleNodeL
this.dialogRef.close(this.link);
}
}
export interface AddRuleNodeDialogData {
ruleNode: FcRuleNode;
ruleChainId: string;
}
@Component({
selector: 'tb-add-rule-node-dialog',
templateUrl: './add-rule-node-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: AddRuleNodeDialogComponent}],
styleUrls: []
})
export class AddRuleNodeDialogComponent extends DialogComponent<AddRuleNodeDialogComponent, FcRuleNode>
implements OnInit, ErrorStateMatcher {
ruleNode: FcRuleNode;
ruleChainId: string;
submitted = false;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AddRuleNodeDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<AddRuleNodeDialogComponent, FcRuleNode>) {
super(store, router, dialogRef);
this.ruleNode = this.data.ruleNode;
this.ruleChainId = this.data.ruleChainId;
}
ngOnInit(): void {
}
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = !!(control && control.invalid && this.submitted);
return originalErrorState || customErrorState;
}
helpLinkIdForRuleNodeType(): string {
return getRuleNodeHelpLink(this.ruleNode.component);
}
cancel(): void {
this.dialogRef.close(null);
}
add(): void {
this.submitted = true;
this.dialogRef.close(this.ruleNode);
}
}

26
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts

@ -39,6 +39,29 @@ import { RuleChainPageComponent } from '@home/pages/rulechain/rulechain-page.com
import { RuleNodeComponentDescriptor } from '@shared/models/rule-node.models';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import * as AngularCommon from '@angular/common';
import * as AngularCore from '@angular/core';
import * as AngularForms from '@angular/forms';
import * as AngularCdkCoercion from '@angular/cdk/coercion';
import * as NgrxStore from '@ngrx/store';
import * as TranslateCore from '@ngx-translate/core';
import * as TbCore from '@core/public-api';
import * as TbShared from '@shared/public-api';
declare const SystemJS;
const ruleNodeConfigResourcesModulesMap = {
'@angular/core': SystemJS.newModule(AngularCore),
'@angular/common': SystemJS.newModule(AngularCommon),
'@angular/forms': SystemJS.newModule(AngularForms),
'@ngrx/store': SystemJS.newModule(NgrxStore),
'@ngx-translate/core': SystemJS.newModule(TranslateCore),
'@core/public-api': SystemJS.newModule(TbCore),
'@shared/public-api': SystemJS.newModule(TbShared)
};
const t = SystemJS.newModule(AngularCore);
@Injectable()
export class RuleChainResolver implements Resolve<RuleChain> {
@ -71,7 +94,7 @@ export class RuleNodeComponentsResolver implements Resolve<Array<RuleNodeCompone
}
resolve(route: ActivatedRouteSnapshot): Observable<Array<RuleNodeComponentDescriptor>> {
return this.ruleChainService.getRuleNodeComponents();
return this.ruleChainService.getRuleNodeComponents(ruleNodeConfigResourcesModulesMap);
}
}
@ -150,6 +173,7 @@ const routes: Routes = [
}
];
// @dynamic
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],

10
ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts

@ -21,19 +21,21 @@ import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.compon
import {RuleChainRoutingModule} from '@modules/home/pages/rulechain/rulechain-routing.module';
import {HomeComponentsModule} from '@modules/home/components/home-components.module';
import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.component';
import { RuleChainPageComponent, AddRuleNodeLinkDialogComponent } from './rulechain-page.component';
import { RuleChainPageComponent, AddRuleNodeLinkDialogComponent, AddRuleNodeDialogComponent } from './rulechain-page.component';
import { RuleNodeComponent } from '@home/pages/rulechain/rulenode.component';
import { FC_NODE_COMPONENT_CONFIG } from 'ngx-flowchart/dist/ngx-flowchart';
import { RuleNodeDetailsComponent } from './rule-node-details.component';
import { RuleNodeLinkComponent } from './rule-node-link.component';
import { LinkLabelsComponent } from '@home/pages/rulechain/link-labels.conponent';
import { RuleNodeConfigComponent } from './rule-node-config.component';
@NgModule({
entryComponents: [
RuleChainComponent,
RuleChainTabsComponent,
RuleNodeComponent,
AddRuleNodeLinkDialogComponent
AddRuleNodeLinkDialogComponent,
AddRuleNodeDialogComponent
],
declarations: [
RuleChainComponent,
@ -41,9 +43,11 @@ import { LinkLabelsComponent } from '@home/pages/rulechain/link-labels.conponent
RuleChainPageComponent,
RuleNodeComponent,
RuleNodeDetailsComponent,
RuleNodeConfigComponent,
LinkLabelsComponent,
RuleNodeLinkComponent,
AddRuleNodeLinkDialogComponent
AddRuleNodeLinkDialogComponent,
AddRuleNodeDialogComponent
],
providers: [
{

4
ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html

@ -34,8 +34,8 @@
<mat-form-field class="mat-block">
<mat-label translate>user.activation-method</mat-label>
<mat-select matInput [ngModelOptions]="{standalone: true}" [(ngModel)]="activationMethod">
<mat-option *ngFor="let activationMethod of (activationMethods | enumToArray)" [value]="activationMethods[activationMethod]">
{{ activationMethodTranslations.get(activationMethods[activationMethod]) | translate }}
<mat-option *ngFor="let activationMethod of activationMethods" [value]="activationMethod">
{{ activationMethodTranslations.get(activationMethod) | translate }}
</mat-option>
</mat-select>
</mat-form-field>

2
ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts

@ -49,7 +49,7 @@ export class AddUserDialogComponent extends DialogComponent<AddUserDialogCompone
detailsForm: NgForm;
user: User;
activationMethods = ActivationMethod;
activationMethods = Object.keys(ActivationMethod);
activationMethodTranslations = activationMethodTranslations;

1
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts

@ -48,6 +48,7 @@ import {
} from '@home/pages/widget/save-widget-type-as-dialog.component';
import { Subscription } from 'rxjs';
// @dynamic
@Component({
selector: 'tb-widget-editor',
templateUrl: './widget-editor.component.html',

1
ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts

@ -201,6 +201,7 @@ export const routes: Routes = [
}
];
// @dynamic
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],

17
ui-ngx/src/app/modules/home/public-api.ts

@ -0,0 +1,17 @@
///
/// 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.
///
export * from './home.module';

2
ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts

@ -81,7 +81,7 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI
filteredDashboards: Observable<Array<DashboardInfo>>;
private searchText = '';
searchText = '';
private propagateChange = (v: any) => { };

1
ui-ngx/src/app/shared/components/dashboard-select.component.ts

@ -40,6 +40,7 @@ import {
} from './dashboard-select-panel.component';
import { NULL_UUID } from '@shared/models/id/has-uuid';
// @dynamic
@Component({
selector: 'tb-dashboard-select',
templateUrl: './dashboard-select.component.html',

2
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts

@ -94,7 +94,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
filteredEntities: Observable<Array<BaseData<EntityId>>>;
private searchText = '';
searchText = '';
private dirty = false;

2
ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts

@ -92,7 +92,7 @@ export class EntityKeysListComponent implements ControlValueAccessor, OnInit, Af
separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
private searchText = '';
searchText = '';
private dirty = false;

2
ui-ngx/src/app/shared/components/entity/entity-list.component.ts

@ -90,7 +90,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
entities: Array<BaseData<EntityId>> = [];
filteredEntities: Observable<Array<BaseData<EntityId>>>;
private searchText = '';
searchText = '';
private dirty = false;

2
ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts

@ -79,7 +79,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor,
private broadcastSubscription: Subscription;
private searchText = '';
searchText = '';
private dirty = false;

2
ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts

@ -106,7 +106,7 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
private searchText = '';
searchText = '';
private dirty = false;

2
ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts

@ -102,7 +102,7 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af
placeholder: string;
secondaryPlaceholder: string;
private searchText = '';
searchText = '';
private dirty = false;

1
ui-ngx/src/app/shared/components/fab-toolbar.component.ts

@ -63,6 +63,7 @@ export class FabActionsDirective implements OnInit {
}
// @dynamic
@Component({
selector: 'mat-fab-toolbar',
templateUrl: './fab-toolbar.component.html',

4
ui-ngx/src/app/shared/components/json-form/json-form.component.ts

@ -35,7 +35,7 @@ import { deepClone, isString } from '@app/core/utils';
import { TranslateService } from '@ngx-translate/core';
import { JsonFormProps } from './react/json-form.models';
import inspector from 'schema-inspector';
import * as tinycolor from 'tinycolor2';
import * as tinycolor_ from 'tinycolor2';
import { DialogService } from '@app/core/services/dialog.service';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
@ -43,6 +43,8 @@ import ReactSchemaForm from './react/json-form-react';
import JsonFormUtils from './react/json-form-utils';
import { JsonFormComponentData } from './json-form-component.models';
const tinycolor = tinycolor_;
@Component({
selector: 'tb-json-form',
templateUrl: './json-form.component.html',

1
ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx

@ -20,7 +20,6 @@ import reactCSS from 'reactcss';
import ReactAce from 'react-ace';
import Button from '@material-ui/core/Button';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import * as tinycolor from 'tinycolor2';
import { IEditorProps } from 'react-ace/src/types';
interface ThingsboardAceEditorProps extends JsonFormFieldProps {

4
ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx

@ -17,13 +17,15 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom';
import ThingsboardBaseComponent from './json-form-base-component';
import reactCSS from 'reactcss';
import * as tinycolor from 'tinycolor2';
import * as tinycolor_ from 'tinycolor2';
import TextField from '@material-ui/core/TextField';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import IconButton from '@material-ui/core/IconButton';
import ClearIcon from '@material-ui/icons/Clear';
import Tooltip from '@material-ui/core/Tooltip';
const tinycolor = tinycolor_;
interface ThingsboardColorState extends JsonFormFieldState {
color: tinycolor.ColorFormats.RGBA | null;
focused: boolean;

3
ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx

@ -35,7 +35,8 @@ import ThingsboardFieldSet from './json-form-fieldset';
import { JsonFormProps, GroupInfo, JsonFormData, onChangeFn, OnColorClickFn } from './json-form.models';
import _ from 'lodash';
import * as tinycolor from 'tinycolor2';
import * as tinycolor_ from 'tinycolor2';
const tinycolor = tinycolor_;
class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {

4
ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts

@ -18,7 +18,9 @@ import { isUndefined, isDefined, isString } from '@app/core/utils';
import * as equal from 'deep-equal';
import ObjectPath from 'objectpath';
import * as React from 'react';
import * as tinycolor from 'tinycolor2';
import * as tinycolor_ from 'tinycolor2';
const tinycolor = tinycolor_;
export interface SchemaValidationResult {
valid: boolean;

2
ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts

@ -49,7 +49,7 @@ export class MatChipDraggableDirective implements AfterViewInit {
private elementRef: ElementRef<HTMLElement>) {
}
@HostListener('document:mouseup', ['$event'])
@HostListener('document:mouseup')
onDocumentMouseUp() {
this.draggableChips.forEach((draggableChip) => {
draggableChip.preventDrag = false;

17
ui-ngx/src/app/shared/components/public-api.ts

@ -0,0 +1,17 @@
///
/// 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.
///
export * from './page.component';

2
ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.ts

@ -69,7 +69,7 @@ export class RelationTypeAutocompleteComponent implements ControlValueAccessor,
filteredRelationTypes: Observable<Array<string>>;
private searchText = '';
searchText = '';
private dirty = false;

8
ui-ngx/src/app/shared/components/time/timewindow.component.html

@ -17,7 +17,7 @@
-->
<button *ngIf="asButton" cdkOverlayOrigin #timewindowPanelOrigin="cdkOverlayOrigin" [disabled]="disabled"
type="button"
mat-raised-button color="primary" (click)="openEditMode($event)">
mat-raised-button color="primary" (click)="openEditMode()">
<mat-icon class="material-icons">query_builder</mat-icon>
<span>{{innerValue?.displayValue}}</span>
</button>
@ -25,20 +25,20 @@
class="tb-timewindow" fxLayout="row" fxLayoutAlign="start center">
<button *ngIf="direction === 'left'" [disabled]="disabled" mat-button mat-icon-button class="tb-mat-32"
type="button"
(click)="openEditMode($event)"
(click)="openEditMode()"
matTooltip="{{ 'timewindow.edit' | translate }}"
[matTooltipPosition]="tooltipPosition">
<mat-icon class="material-icons">query_builder</mat-icon>
</button>
<span [fxHide]="hideLabel()"
(click)="openEditMode($event)"
(click)="openEditMode()"
matTooltip="{{ 'timewindow.edit' | translate }}"
[matTooltipPosition]="tooltipPosition">
{{innerValue?.displayValue}}
</span>
<button *ngIf="direction === 'right'" [disabled]="disabled" mat-button mat-icon-button class="tb-mat-32"
type="button"
(click)="openEditMode($event)"
(click)="openEditMode()"
matTooltip="{{ 'timewindow.edit' | translate }}"
[matTooltipPosition]="tooltipPosition">
<mat-icon class="material-icons">query_builder</mat-icon>

1
ui-ngx/src/app/shared/components/time/timewindow.component.ts

@ -54,6 +54,7 @@ import { TimeService } from '@core/services/time.service';
import { TooltipPosition } from '@angular/material/typings/tooltip';
import { deepClone } from '@core/utils';
// @dynamic
@Component({
selector: 'tb-timewindow',
templateUrl: './timewindow.component.html',

1
ui-ngx/src/app/shared/models/constants.ts

@ -59,6 +59,7 @@ export const HelpLinks = {
securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings',
ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/',
ruleNodeCheckRelation: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-relation-filter-node',
ruleNodeCheckExistenceFields: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-existence-fields-node',
ruleNodeJsFilter: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#script-filter-node',
ruleNodeJsSwitch: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#switch-node',
ruleNodeMessageTypeFilter: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#message-type-filter-node',

33
ui-ngx/src/app/shared/models/id/public-api.ts

@ -0,0 +1,33 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
export * from './alarm-id';
export * from './asset-id';
export * from './audit-log-id';
export * from './customer-id';
export * from './dashboard-id';
export * from './device-credentials-id';
export * from './device-id';
export * from './entity-id';
export * from './entity-view-id';
export * from './event-id';
export * from './has-uuid';
export * from './rule-chain-id';
export * from './rule-node-id';
export * from './tenant-id';
export * from './user-id';
export * from './widget-type-id';
export * from './widgets-bundle-id';

3
ui-ngx/src/app/shared/models/material.models.ts

@ -14,7 +14,8 @@
/// limitations under the License.
///
import * as tinycolor from 'tinycolor2';
import * as tinycolor_ from 'tinycolor2';
const tinycolor = tinycolor_;
export interface MaterialColorItem {
value: string;

19
ui-ngx/src/app/shared/models/page/public-api.ts

@ -0,0 +1,19 @@
///
/// 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.
///
export * from './page-data';
export * from './page-link';
export * from './sort-order';

47
ui-ngx/src/app/shared/models/public-api.ts

@ -0,0 +1,47 @@
///
/// 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.
///
export * from './id/public-api';
export * from './page/public-api';
export * from './telemetry/telemetry.models';
export * from './time/time.models';
export * from './alarm.models';
export * from './alias.models';
export * from './audit-log.models';
export * from './authority.enum';
export * from './base-data';
export * from './component-descriptor.models';
export * from './constants';
export * from './contact-based.model';
export * from './customer.model';
export * from './dashboard.models';
export * from './device.models';
export * from './entity.models';
export * from './entity-type.models';
export * from './entity-view.models';
export * from './error.models';
export * from './event.models';
export * from './login.models';
export * from './material.models';
export * from './relation.models';
export * from './rule-chain.models';
export * from './rule-node.models';
export * from './settings.models';
export * from './tenant.model';
export * from './user.model';
export * from './widget.models';
export * from './widgets-bundle.model';
export * from './window-message.model';

69
ui-ngx/src/app/shared/models/rule-node.models.ts

@ -22,6 +22,12 @@ import {RuleChainId} from '@shared/models/id/rule-chain-id';
import {RuleNodeId} from '@shared/models/id/rule-node-id';
import { ComponentDescriptor, ComponentType } from '@shared/models/component-descriptor.models';
import { EntityType, EntityTypeResource } from '@shared/models/entity-type.models';
import { Observable } from 'rxjs';
import { PageComponent } from '@shared/components/page.component';
import { ComponentFactory, EventEmitter, Inject, OnDestroy, OnInit } from '@angular/core';
import { RafService } from '@core/services/raf.service';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
export enum MsgDataType {
JSON = 'JSON',
@ -48,23 +54,57 @@ export interface LinkLabel {
value: string;
}
export interface RuleNodeDefinition {
description: string;
details: string;
inEnabled: boolean;
outEnabled: boolean;
relationTypes: string[];
customRelations: boolean;
defaultConfiguration: RuleNodeConfiguration;
icon?: string;
iconUrl?: string;
docUrl?: string;
uiResources?: string[];
uiResourceLoadError?: string;
configDirective?: string;
}
export interface RuleNodeConfigurationDescriptor {
nodeDefinition: {
description: string;
details: string;
inEnabled: boolean;
outEnabled: boolean;
relationTypes: string[];
customRelations: boolean;
defaultConfiguration: any;
icon?: string;
iconUrl?: string;
docUrl?: string;
uiResources?: string[];
uiResourceLoadError?: string;
};
nodeDefinition: RuleNodeDefinition;
}
export interface IRuleNodeConfigurationComponent {
ruleNodeId: string;
configuration: RuleNodeConfiguration;
configurationChanged: Observable<RuleNodeConfiguration>;
[key: string]: any;
}
export abstract class RuleNodeConfigurationComponent extends PageComponent implements
IRuleNodeConfigurationComponent, OnInit {
ruleNodeId: string;
configuration: RuleNodeConfiguration;
configurationChangedEmiter = new EventEmitter<RuleNodeConfiguration>();
configurationChanged = this.configurationChangedEmiter.asObservable();
protected constructor(@Inject(Store) protected store: Store<AppState>) {
super(store);
}
ngOnInit() {
this.onConfigurationSet(this.configuration);
}
protected abstract onConfigurationSet(configuration: RuleNodeConfiguration);
protected notifyConfigurationUpdated(configuration: RuleNodeConfiguration) {
this.configurationChangedEmiter.emit(configuration);
}
}
export enum RuleNodeType {
FILTER = 'FILTER',
ENRICHMENT = 'ENRICHMENT',
@ -187,6 +227,7 @@ export interface RuleNodeComponentDescriptor extends ComponentDescriptor {
const ruleNodeClazzHelpLinkMap = {
'org.thingsboard.rule.engine.filter.TbCheckRelationNode': 'ruleNodeCheckRelation',
'org.thingsboard.rule.engine.filter.TbCheckMessageNode': 'ruleNodeCheckExistenceFields',
'org.thingsboard.rule.engine.filter.TbJsFilterNode': 'ruleNodeJsFilter',
'org.thingsboard.rule.engine.filter.TbJsSwitchNode': 'ruleNodeJsSwitch',
'org.thingsboard.rule.engine.filter.TbMsgTypeFilterNode': 'ruleNodeMessageTypeFilter',

4
ui-ngx/src/app/shared/models/user.model.ts

@ -31,8 +31,8 @@ export interface User extends BaseData<UserId> {
}
export enum ActivationMethod {
DISPLAY_ACTIVATION_LINK,
SEND_ACTIVATION_MAIL
DISPLAY_ACTIVATION_LINK = 'DISPLAY_ACTIVATION_LINK',
SEND_ACTIVATION_MAIL = 'SEND_ACTIVATION_MAIL'
}
export const activationMethodTranslations = new Map<ActivationMethod, string>(

2
ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts

@ -20,7 +20,7 @@ import { Pipe, PipeTransform } from '@angular/core';
name: 'enumToArray'
})
export class EnumToArrayPipe implements PipeTransform {
transform(data: object) {
transform(data: object): string[] {
const keys = Object.keys(data);
return keys.slice(keys.length / 2);
}

1
ui-ngx/src/app/shared/pipe/keyboard-shortcut.pipe.ts

@ -17,6 +17,7 @@
import { Inject, Pipe, PipeTransform } from '@angular/core';
import { WINDOW } from '@core/services/window.service';
// @dynamic
@Pipe({
name: 'keyboardShortcut'
})

22
ui-ngx/src/app/shared/pipe/public-api.ts

@ -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.
///
export * from './enum-to-array.pipe';
export * from './highlight.pipe';
export * from './keyboard-shortcut.pipe';
export * from './milliseconds-to-time-string.pipe';
export * from './nospace.pipe';
export * from './truncate.pipe';

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save