Browse Source

Merge branch 'develop/3.0' of https://github.com/thingsboard/thingsboard into map/3.0

pull/2535/head
Artem Halushko 6 years ago
parent
commit
f8e2b3b599
  1. 2
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 14
      application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json
  3. 18
      application/src/main/data/json/system/widget_bundles/gateway_widgets.json
  4. 73
      application/src/main/data/json/system/widget_bundles/maps__deprecated_.json
  5. 2
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java
  7. 1
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java
  8. 4
      docker/.env
  9. 12
      docker/compose-utils.sh
  10. 18
      docker/docker-compose.hybrid.yml
  11. 3
      docker/docker-compose.postgres.yml
  12. 18
      docker/haproxy/config/haproxy.cfg
  13. 5
      docker/tb-node.cassandra.env
  14. 9
      docker/tb-node.hybrid.env
  15. 1
      docker/tb-node.postgres.env
  16. 2
      msa/tb/README.md
  17. 18
      msa/tb/docker-cassandra/Dockerfile
  18. 18
      msa/tb/docker-cassandra/start-db.sh
  19. 3
      msa/tb/docker-cassandra/stop-db.sh
  20. 7
      msa/tb/docker-postgres/Dockerfile
  21. 6
      msa/tb/docker-postgres/start-db.sh
  22. 4
      msa/tb/docker-postgres/stop-db.sh
  23. 1
      msa/tb/docker-tb/Dockerfile
  24. 31
      msa/web-ui/server.js
  25. 153
      ui-ngx/package-lock.json
  26. 1
      ui-ngx/package.json
  27. 32
      ui-ngx/src/app/core/api/alias-controller.ts
  28. 4
      ui-ngx/src/app/core/api/widget-api.models.ts
  29. 26
      ui-ngx/src/app/core/api/widget-subscription.ts
  30. 15
      ui-ngx/src/app/core/http/attribute.service.ts
  31. 11
      ui-ngx/src/app/core/services/item-buffer.service.ts
  32. 7
      ui-ngx/src/app/core/utils.ts
  33. 6
      ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html
  34. 4
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  35. 2
      ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.html
  36. 2
      ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html
  37. 11
      ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts
  38. 4
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts
  39. 2
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html
  40. 4
      ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts
  41. 4
      ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts
  42. 2
      ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html
  43. 7
      ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts
  44. 139
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts
  45. 159
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts
  46. 2
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.html
  47. 3
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.scss
  48. 6
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts
  49. 3
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.scss
  50. 3
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss
  51. 3
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.scss
  52. 11
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  53. 12
      ui-ngx/src/app/modules/home/home.component.ts
  54. 13
      ui-ngx/src/app/modules/home/menu/menu-link.component.html
  55. 19
      ui-ngx/src/app/modules/home/menu/menu-toggle.component.html
  56. 66
      ui-ngx/src/app/modules/home/menu/side-menu.component.scss
  57. 15
      ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
  58. 18
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  59. 2
      ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html
  60. 38
      ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts
  61. 5
      ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts
  62. 4
      ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts
  63. 2
      ui-ngx/src/app/shared/models/time/time.models.ts
  64. 10
      ui-ngx/src/styles.scss

2
application/src/main/data/json/system/widget_bundles/cards.json

@ -31,7 +31,7 @@
"resources": [],
"templateHtml": "<tb-entities-table-widget \n [ctx]=\"ctx\">\n</tb-entities-table-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}",
"dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
"defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}]}"

14
application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json

File diff suppressed because one or more lines are too long

18
application/src/main/data/json/system/widget_bundles/gateway_widgets.json

@ -5,22 +5,6 @@
"image": null
},
"widgetTypes": [
{
"alias": "extension_configuration_widget",
"name": "Extensions table",
"descriptor": {
"type": "latest",
"sizeX": 9,
"sizeY": 6.5,
"resources": [],
"templateHtml": "<tb-extensions-table-widget \n ctx=\"ctx\">\n</tb-extensions-table-widget>",
"templateCss": "#container {\n overflow: auto;\n}",
"controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n}\n\nself.onResize = function() {\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"ExtensionTableSettings\",\n \"properties\": {\n \"extensionsTitle\": {\n \"title\": \"Extension table title\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"extensionsTitle\"\n ]\n}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{},\"title\":\"Extensions table\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
},
{
"alias": "gateway_configuration",
"name": "Gateway Configuration",
@ -38,4 +22,4 @@
}
}
]
}
}

73
application/src/main/data/json/system/widget_bundles/maps__deprecated_.json

File diff suppressed because one or more lines are too long

2
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -295,7 +295,7 @@ public class ActorSystemContext {
@Getter
private final AtomicInteger jsInvokeFailuresCount = new AtomicInteger(0);
@Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}")
@Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}")
public void printStats() {
if (statisticsEnabled) {
if (jsInvokeRequestsCount.get() > 0 || jsInvokeResponsesCount.get() > 0 || jsInvokeFailuresCount.get() > 0) {

2
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java

@ -70,7 +70,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
@Value("${js.local.stats.enabled:false}")
private boolean statsEnabled;
@Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms:10000}")
@Scheduled(fixedDelayString = "${js.local.stats.print_interval_ms:10000}")
public void printStats() {
if (statsEnabled) {
int pushedMsgs = jsPushedMsgs.getAndSet(0);

1
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java

@ -95,6 +95,7 @@ public abstract class AbstractAssetEntity<T extends Asset> extends BaseSqlEntity
this.customerId = assetEntity.getCustomerId();
this.type = assetEntity.getType();
this.name = assetEntity.getName();
this.label = assetEntity.getLabel();
this.searchText = assetEntity.getSearchText();
this.additionalInfo = assetEntity.getAdditionalInfo();
}

4
docker/.env

@ -10,8 +10,8 @@ COAP_TRANSPORT_DOCKER_NAME=tb-coap-transport
TB_VERSION=latest
# Database used by ThingsBoard, can be either postgres (PostgreSQL) or cassandra (Cassandra).
# According to the database type corresponding docker service will be deployed (see docker-compose.postgres.yml, docker-compose.cassandra.yml for details).
# Database used by ThingsBoard, can be either postgres (PostgreSQL) or hybrid (PostgreSQL for entities database and Cassandra for timeseries database).
# According to the database type corresponding docker service will be deployed (see docker-compose.postgres.yml, docker-compose.hybrid.yml for details).
DATABASE=postgres

12
docker/compose-utils.sh

@ -22,11 +22,11 @@ function additionalComposeArgs() {
postgres)
ADDITIONAL_COMPOSE_ARGS="-f docker-compose.postgres.yml"
;;
cassandra)
ADDITIONAL_COMPOSE_ARGS="-f docker-compose.cassandra.yml"
hybrid)
ADDITIONAL_COMPOSE_ARGS="-f docker-compose.hybrid.yml"
;;
*)
echo "Unknown DATABASE value specified: '${DATABASE}'. Should be either postgres or cassandra." >&2
echo "Unknown DATABASE value specified: '${DATABASE}'. Should be either postgres or hybrid." >&2
exit 1
esac
echo $ADDITIONAL_COMPOSE_ARGS
@ -39,11 +39,11 @@ function additionalStartupServices() {
postgres)
ADDITIONAL_STARTUP_SERVICES=postgres
;;
cassandra)
ADDITIONAL_STARTUP_SERVICES=cassandra
hybrid)
ADDITIONAL_STARTUP_SERVICES="postgres cassandra"
;;
*)
echo "Unknown DATABASE value specified: '${DATABASE}'. Should be either postgres or cassandra." >&2
echo "Unknown DATABASE value specified: '${DATABASE}'. Should be either postgres or hybrid." >&2
exit 1
esac
echo $ADDITIONAL_STARTUP_SERVICES

18
docker/docker-compose.cassandra.yml → docker/docker-compose.hybrid.yml

@ -17,24 +17,36 @@
version: '2.2'
services:
postgres:
restart: always
image: "postgres:11.6"
ports:
- "5432"
environment:
POSTGRES_DB: thingsboard
POSTGRES_PASSWORD: postgres
volumes:
- ./tb-node/postgres:/var/lib/postgresql/data
cassandra:
restart: always
image: "cassandra:3.11.3"
ports:
- "9042"
- "9042"
volumes:
- ./tb-node/cassandra:/var/lib/cassandra
tb1:
env_file:
- tb-node.cassandra.env
- tb-node.hybrid.env
depends_on:
- kafka
- redis
- postgres
- cassandra
tb2:
env_file:
- tb-node.cassandra.env
- tb-node.hybrid.env
depends_on:
- kafka
- redis
- postgres
- cassandra

3
docker/docker-compose.postgres.yml

@ -19,11 +19,12 @@ version: '2.2'
services:
postgres:
restart: always
image: "postgres:10"
image: "postgres:11.6"
ports:
- "5432"
environment:
POSTGRES_DB: thingsboard
POSTGRES_PASSWORD: postgres
volumes:
- ./tb-node/postgres:/var/lib/postgresql/data
tb1:

18
docker/haproxy/config/haproxy.cfg

@ -56,20 +56,17 @@ frontend http-in
reqadd X-Forwarded-Proto:\ http
acl acl_static path_beg /static/ /index.html
acl acl_static path /
acl acl_static_rulenode path_beg /static/rulenode/
acl transport_http_acl path_beg /api/v1/
acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/
acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/
redirect scheme https if !letsencrypt_http_acl !transport_http_acl { env(FORCE_HTTPS_REDIRECT) -m str true }
use_backend letsencrypt_http if letsencrypt_http_acl
use_backend tb-http-backend if transport_http_acl
use_backend tb-web-backend if acl_static !acl_static_rulenode
use_backend tb-api-backend if tb_api_acl
default_backend tb-api-backend
default_backend tb-web-backend
frontend https_in
bind *:${HTTPS_PORT} ssl crt /usr/local/etc/haproxy/default.pem crt /usr/local/etc/haproxy/certs.d ciphers ECDHE-RSA-AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM
@ -79,15 +76,12 @@ frontend https_in
reqadd X-Forwarded-Proto:\ https
acl transport_http_acl path_beg /api/v1/
acl acl_static path_beg /static/ /index.html
acl acl_static path /
acl acl_static_rulenode path_beg /static/rulenode/
acl tb_api_acl path_beg /api/ /swagger /webjars /v2/ /static/rulenode/
use_backend tb-http-backend if transport_http_acl
use_backend tb-web-backend if acl_static !acl_static_rulenode
use_backend tb-api-backend if tb_api_acl
default_backend tb-api-backend
default_backend tb-web-backend
backend letsencrypt_http
server letsencrypt_http_srv 127.0.0.1:8080

5
docker/tb-node.cassandra.env

@ -1,5 +0,0 @@
# ThingsBoard server configuration for Cassandra database
DATABASE_TS_TYPE=cassandra
DATABASE_ENTITIES_TYPE=cassandra
CASSANDRA_URL=cassandra:9042

9
docker/tb-node.hybrid.env

@ -0,0 +1,9 @@
# ThingsBoard server configuration for Cassandra database
DATABASE_TS_TYPE=cassandra
CASSANDRA_URL=cassandra:9042
SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/thingsboard
SPRING_DATASOURCE_USERNAME=postgres
SPRING_DATASOURCE_PASSWORD=postgres

1
docker/tb-node.postgres.env

@ -1,7 +1,6 @@
# ThingsBoard server configuration for PostgreSQL database
DATABASE_TS_TYPE=sql
DATABASE_ENTITIES_TYPE=sql
SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/thingsboard

2
msa/tb/README.md

@ -4,7 +4,7 @@ This project provides the build for the ThingsBoard single docker images.
* `thingsboard/tb` - single instance of ThingsBoard with embedded HSQLDB database.
* `thingsboard/tb-postgres` - single instance of ThingsBoard with PostgreSQL database.
* `thingsboard/tb-cassandra` - single instance of ThingsBoard with Cassandra database.
* `thingsboard/tb-cassandra` - single instance of ThingsBoard with Hybrid PostgreSQL (entities) and Cassandra (timeseries) database.
## Running

18
msa/tb/docker-cassandra/Dockerfile

@ -18,13 +18,19 @@ FROM thingsboard/openjdk8
RUN apt-get update
RUN apt-get install -y curl nmap procps
RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null
RUN curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo 'deb http://www.apache.org/dist/cassandra/debian 311x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null
RUN curl -L https://www.apache.org/dist/cassandra/KEYS | apt-key add -
RUN apt-get update
RUN apt-get install -y cassandra cassandra-tools
RUN apt-get install -y cassandra cassandra-tools postgresql-11
RUN update-rc.d cassandra disable
RUN update-rc.d postgresql disable
RUN sed -i.old '/ulimit/d' /etc/init.d/cassandra
RUN mkdir -p /var/log/postgres
RUN chown -R postgres:postgres /var/log/postgres
COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/
RUN chmod a+x /tmp/*.sh \
@ -45,7 +51,15 @@ ENV DATA_FOLDER=/data
ENV HTTP_BIND_PORT=9090
ENV DATABASE_TS_TYPE=cassandra
ENV DATABASE_ENTITIES_TYPE=cassandra
ENV PGDATA=/data/db
ENV CASSANDRA_DATA=/data/cassandra
ENV SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
ENV SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
ENV SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/thingsboard
ENV SPRING_DATASOURCE_USERNAME=postgres
ENV SPRING_DATASOURCE_PASSWORD=postgres
ENV CASSANDRA_HOST=localhost
ENV CASSANDRA_PORT=9042

18
msa/tb/docker-cassandra/start-db.sh

@ -15,7 +15,23 @@
# limitations under the License.
#
cassandra_data_dir=${DATA_FOLDER}/db
firstlaunch=${DATA_FOLDER}/.firstlaunch
export PG_CTL=$(find /usr/lib/postgresql/ -name pg_ctl)
if [ ! -d ${PGDATA} ]; then
mkdir -p ${PGDATA}
chown -R postgres:postgres ${PGDATA}
su postgres -c '${PG_CTL} initdb -U postgres'
fi
su postgres -c '${PG_CTL} -l /var/log/postgres/postgres.log -w start'
if [ ! -f ${firstlaunch} ]; then
su postgres -c 'psql -U postgres -d postgres -c "CREATE DATABASE thingsboard"'
fi
cassandra_data_dir=${CASSANDRA_DATA}
cassandra_data_link=/var/lib/cassandra
if [ ! -L ${cassandra_data_link} ]; then

3
msa/tb/docker-cassandra/stop-db.sh

@ -15,4 +15,7 @@
# limitations under the License.
#
export PG_CTL=$(find /usr/lib/postgresql/ -name pg_ctl)
service cassandra stop
su postgres -c '${PG_CTL} stop'

7
msa/tb/docker-postgres/Dockerfile

@ -17,7 +17,11 @@
FROM thingsboard/openjdk8
RUN apt-get update
RUN apt-get install -y postgresql postgresql-contrib
RUN apt-get install -y curl
RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null
RUN curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN apt-get update
RUN apt-get install -y postgresql-11
RUN update-rc.d postgresql disable
RUN mkdir -p /var/log/postgres
@ -43,7 +47,6 @@ ENV DATA_FOLDER=/data
ENV HTTP_BIND_PORT=9090
ENV DATABASE_TS_TYPE=sql
ENV DATABASE_ENTITIES_TYPE=sql
ENV PGDATA=/data/db

6
msa/tb/docker-postgres/start-db.sh

@ -17,13 +17,15 @@
firstlaunch=${DATA_FOLDER}/.firstlaunch
export PG_CTL=$(find /usr/lib/postgresql/ -name pg_ctl)
if [ ! -d ${PGDATA} ]; then
mkdir -p ${PGDATA}
chown -R postgres:postgres ${PGDATA}
su postgres -c '/usr/lib/postgresql/10/bin/pg_ctl initdb -U postgres'
su postgres -c '${PG_CTL} initdb -U postgres'
fi
su postgres -c '/usr/lib/postgresql/10/bin/pg_ctl -l /var/log/postgres/postgres.log -w start'
su postgres -c '${PG_CTL} -l /var/log/postgres/postgres.log -w start'
if [ ! -f ${firstlaunch} ]; then
su postgres -c 'psql -U postgres -d postgres -c "CREATE DATABASE thingsboard"'

4
msa/tb/docker-postgres/stop-db.sh

@ -15,4 +15,6 @@
# limitations under the License.
#
su postgres -c '/usr/lib/postgresql/10/bin/pg_ctl stop'
export PG_CTL=$(find /usr/lib/postgresql/ -name pg_ctl)
su postgres -c '${PG_CTL} stop'

1
msa/tb/docker-tb/Dockerfile

@ -36,7 +36,6 @@ ENV DATA_FOLDER=/data
ENV HTTP_BIND_PORT=9090
ENV DATABASE_TS_TYPE=sql
ENV DATABASE_ENTITIES_TYPE=sql
ENV SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.HSQLDialect
ENV SPRING_DRIVER_CLASS_NAME=org.hsqldb.jdbc.JDBCDriver

31
msa/web-ui/server.js

@ -65,23 +65,26 @@ var server;
apiProxy.on('error', function (err, req, res) {
logger.warn('API proxy error: %s', err.message);
res.writeHead(500);
if (err.code && err.code === 'ECONNREFUSED') {
if (res.writeHead) {
res.writeHead(500);
if (err.code && err.code === 'ECONNREFUSED') {
res.end('Unable to connect to ThingsBoard server.');
} else {
} else {
res.end('Thingsboard server connection error: ' + err.code ? err.code : '');
}
}
});
}
if (useApiProxy) {
app.all('/api/*', (req, res) => {
logger.debug(req.method + ' ' + req.originalUrl);
apiProxy.web(req, res);
logger.debug(req.method + ' ' + req.originalUrl);
apiProxy.web(req, res);
});
app.all('/static/rulenode/*', (req, res) => {
apiProxy.web(req, res);
apiProxy.web(req, res);
});
server.on('upgrade', (req, socket, head) => {
apiProxy.ws(req, socket, head);
});
}
@ -92,16 +95,6 @@ var server;
app.use(express.static(root));
if (useApiProxy) {
app.get('*', (req, res) => {
apiProxy.web(req, res);
});
server.on('upgrade', (req, socket, head) => {
apiProxy.ws(req, socket, head);
});
}
server.listen(bindPort, bindAddress, (error) => {
if (error) {
logger.error('Failed to start ThingsBoard Web UI Microservice: %s', e.message);

153
ui-ngx/package-lock.json

@ -4920,32 +4920,6 @@
"mimic-response": "1.0.1"
}
},
"deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.1.tgz",
"integrity": "sha512-7Et6r6XfNW61CPPCIYfm1YPGSmh6+CliYeL4km7GWJcpX5LTAflGF8drLLR+MZX+2P3NZfAfSduutBbSWqER4g==",
"requires": {
"es-abstract": "1.17.4",
"es-get-iterator": "1.1.0",
"is-arguments": "1.0.4",
"is-date-object": "1.0.2",
"is-regex": "1.0.5",
"isarray": "2.0.5",
"object-is": "1.0.2",
"object-keys": "1.1.1",
"regexp.prototype.flags": "1.3.0",
"side-channel": "1.0.2",
"which-boxed-primitive": "1.0.1",
"which-collection": "1.0.1"
},
"dependencies": {
"isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
}
}
},
"deep-freeze-strict": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-freeze-strict/-/deep-freeze-strict-1.1.1.tgz",
@ -5022,6 +4996,7 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"dev": true,
"requires": {
"object-keys": "1.1.1"
}
@ -5597,6 +5572,7 @@
"version": "1.17.4",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
"integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==",
"dev": true,
"requires": {
"es-to-primitive": "1.2.1",
"function-bind": "1.1.1",
@ -5611,31 +5587,11 @@
"string.prototype.trimright": "2.1.1"
}
},
"es-get-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz",
"integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==",
"requires": {
"es-abstract": "1.17.4",
"has-symbols": "1.0.1",
"is-arguments": "1.0.4",
"is-map": "2.0.1",
"is-set": "2.0.1",
"is-string": "1.0.5",
"isarray": "2.0.5"
},
"dependencies": {
"isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
}
}
},
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
"dev": true,
"requires": {
"is-callable": "1.1.5",
"is-date-object": "1.0.2",
@ -6341,7 +6297,8 @@
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"gauge": {
"version": "2.7.4",
@ -6761,6 +6718,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "1.1.1"
}
@ -6810,7 +6768,8 @@
"has-symbols": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
"dev": true
},
"has-to-string-tag-x": {
"version": "1.4.1",
@ -7385,18 +7344,14 @@
"is-arguments": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
"integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA=="
"integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
"dev": true
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
},
"is-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz",
"integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g=="
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -7406,11 +7361,6 @@
"binary-extensions": "2.0.0"
}
},
"is-boolean-object": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz",
"integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ=="
},
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
@ -7420,7 +7370,8 @@
"is-callable": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q=="
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
"dev": true
},
"is-color-stop": {
"version": "1.1.0",
@ -7459,7 +7410,8 @@
"is-date-object": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
"integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g=="
"integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
"dev": true
},
"is-descriptor": {
"version": "0.1.6",
@ -7531,22 +7483,12 @@
"integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
"dev": true
},
"is-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz",
"integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw=="
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"is-number-object": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz",
"integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw=="
},
"is-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
@ -7605,6 +7547,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
"integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
"dev": true,
"requires": {
"has": "1.0.3"
}
@ -7620,21 +7563,11 @@
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
"integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg=="
},
"is-set": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz",
"integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA=="
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"is-string": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
"integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ=="
},
"is-subset": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz",
@ -7653,6 +7586,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
"dev": true,
"requires": {
"has-symbols": "1.0.1"
}
@ -7675,16 +7609,6 @@
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
},
"is-weakmap": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
"integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA=="
},
"is-weakset": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz",
"integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw=="
},
"is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
@ -9800,17 +9724,20 @@
"object-inspect": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
"integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw=="
"integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==",
"dev": true
},
"object-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz",
"integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ=="
"integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==",
"dev": true
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true
},
"object-visit": {
"version": "1.0.1",
@ -9825,6 +9752,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
"integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
"dev": true,
"requires": {
"define-properties": "1.1.3",
"function-bind": "1.1.1",
@ -11882,6 +11810,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz",
"integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==",
"dev": true,
"requires": {
"define-properties": "1.1.3",
"es-abstract": "1.17.4"
@ -12562,15 +12491,6 @@
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
"side-channel": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz",
"integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==",
"requires": {
"es-abstract": "1.17.4",
"object-inspect": "1.7.0"
}
},
"sigmund": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
@ -13287,6 +13207,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz",
"integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==",
"dev": true,
"requires": {
"define-properties": "1.1.3",
"function-bind": "1.1.1"
@ -13296,6 +13217,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz",
"integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==",
"dev": true,
"requires": {
"define-properties": "1.1.3",
"function-bind": "1.1.1"
@ -16044,29 +15966,6 @@
"isexe": "2.0.0"
}
},
"which-boxed-primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz",
"integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==",
"requires": {
"is-bigint": "1.0.0",
"is-boolean-object": "1.0.1",
"is-number-object": "1.0.4",
"is-string": "1.0.5",
"is-symbol": "1.0.3"
}
},
"which-collection": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz",
"integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==",
"requires": {
"is-map": "2.0.1",
"is-set": "2.0.1",
"is-weakmap": "2.0.1",
"is-weakset": "2.0.1"
}
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",

1
ui-ngx/package.json

@ -46,7 +46,6 @@
"compass-sass-mixins": "^0.12.7",
"core-js": "^3.6.4",
"date-fns": "^2.9.0",
"deep-equal": "^2.0.1",
"flot": "git://github.com/thingsboard/flot.git#0.9-work",
"flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master",
"font-awesome": "^4.7.0",

32
ui-ngx/src/app/core/api/alias-controller.ts

@ -17,17 +17,16 @@
import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models';
import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs';
import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models';
import { deepClone } from '@core/utils';
import { deepClone, isEqual } from '@core/utils';
import { EntityService } from '@core/http/entity.service';
import { UtilsService } from '@core/services/utils.service';
import { EntityAliases } from '@shared/models/alias.models';
import { EntityInfo } from '@shared/models/entity.models';
import * as equal from 'deep-equal';
import { map } from 'rxjs/operators';
export class AliasController implements IAliasController {
private entityAliasesChangedSubject = new Subject<Array<string>>();
entityAliasesChangedSubject = new Subject<Array<string>>();
entityAliasesChanged: Observable<Array<string>> = this.entityAliasesChangedSubject.asObservable();
private entityAliasResolvedSubject = new Subject<string>();
@ -53,7 +52,7 @@ export class AliasController implements IAliasController {
for (const aliasId of Object.keys(newEntityAliases)) {
const newEntityAlias = newEntityAliases[aliasId];
const prevEntityAlias = this.entityAliases[aliasId];
if (!equal(newEntityAlias, prevEntityAlias)) {
if (!isEqual(newEntityAlias, prevEntityAlias)) {
changedAliasIds.push(aliasId);
this.setAliasUnresolved(aliasId);
}
@ -70,13 +69,30 @@ export class AliasController implements IAliasController {
}
}
updateAliases(aliasIds?: Array<string>) {
if (!aliasIds) {
aliasIds = [];
for (const aliasId of Object.keys(this.resolvedAliases)) {
aliasIds.push(aliasId);
}
}
const tasks: Observable<AliasInfo>[] = [];
for (const aliasId of aliasIds) {
this.setAliasUnresolved(aliasId);
tasks.push(this.getAliasInfo(aliasId));
}
forkJoin(tasks).subscribe(() => {
this.entityAliasesChangedSubject.next(aliasIds);
});
}
dashboardStateChanged() {
const changedAliasIds: Array<string> = [];
for (const aliasId of Object.keys(this.resolvedAliasesToStateEntities)) {
const stateEntityInfo = this.resolvedAliasesToStateEntities[aliasId];
const newEntityId = this.stateControllerHolder().getEntityId(stateEntityInfo.entityParamName);
const prevEntityId = stateEntityInfo.entityId;
if (!equal(newEntityId, prevEntityId)) {
if (!isEqual(newEntityId, prevEntityId)) {
changedAliasIds.push(aliasId);
this.setAliasUnresolved(aliasId);
}
@ -86,7 +102,7 @@ export class AliasController implements IAliasController {
}
}
private setAliasUnresolved(aliasId: string) {
setAliasUnresolved(aliasId: string) {
delete this.resolvedAliases[aliasId];
delete this.resolvedAliasesObservable[aliasId];
delete this.resolvedAliasesToStateEntities[aliasId];
@ -226,7 +242,7 @@ export class AliasController implements IAliasController {
resolveAlarmSource(alarmSource: Datasource): Observable<Datasource> {
return this.resolveDatasource(alarmSource, true).pipe(
map((datasources) => {
const datasource = datasources[0];
const datasource = datasources && datasources.length ? datasources[0] : deepClone(alarmSource);
if (datasource.type === DatasourceType.function) {
let name: string;
if (datasource.name && datasource.name.length) {
@ -319,7 +335,7 @@ export class AliasController implements IAliasController {
const aliasInfo = this.resolvedAliases[aliasId];
if (aliasInfo) {
const prevCurrentEntity = aliasInfo.currentEntity;
if (!equal(currentEntity, prevCurrentEntity)) {
if (!isEqual(currentEntity, prevCurrentEntity)) {
aliasInfo.currentEntity = currentEntity;
this.entityAliasesChangedSubject.next([aliasId]);
}

4
ui-ngx/src/app/core/api/widget-api.models.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Observable } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { EntityId } from '@app/shared/models/id/entity-id';
import {
DataSet,
@ -99,6 +99,7 @@ export interface IAliasController {
getEntityAliases(): EntityAliases;
updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo);
updateEntityAliases(entityAliases: EntityAliases);
updateAliases(aliasIds?: Array<string>);
dashboardStateChanged();
}
@ -218,6 +219,7 @@ export interface SubscriptionEntityInfo {
export interface IWidgetSubscription {
options: WidgetSubscriptionOptions;
id: string;
init$: Observable<IWidgetSubscription>;
ctx: WidgetSubscriptionContext;

26
ui-ngx/src/app/core/api/widget-subscription.ts

@ -47,10 +47,9 @@ import { Observable, ReplaySubject, Subject, throwError } from 'rxjs';
import { CancelAnimationFrame } from '@core/services/raf.service';
import { EntityType } from '@shared/models/entity-type.models';
import { AlarmInfo, AlarmSearchStatus } from '@shared/models/alarm.models';
import { deepClone, isDefined } from '@core/utils';
import { deepClone, isDefined, isEqual } from '@core/utils';
import { AlarmSourceListener } from '@core/http/alarm.service';
import { DatasourceListener } from '@core/api/datasource.service';
import * as deepEqual from 'deep-equal';
import { EntityId } from '@app/shared/models/id/entity-id';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { entityFields } from '@shared/models/entity.models';
@ -128,7 +127,7 @@ export class WidgetSubscription implements IWidgetSubscription {
targetDeviceName: string;
executingSubjects: Array<Subject<any>>;
constructor(subscriptionContext: WidgetSubscriptionContext, options: WidgetSubscriptionOptions) {
constructor(subscriptionContext: WidgetSubscriptionContext, public options: WidgetSubscriptionOptions) {
const subscriptionSubject = new ReplaySubject<IWidgetSubscription>();
this.init$ = subscriptionSubject.asObservable();
this.ctx = subscriptionContext;
@ -539,7 +538,7 @@ export class WidgetSubscription implements IWidgetSubscription {
onDashboardTimewindowChanged(newDashboardTimewindow: Timewindow): void {
if (this.type === widgetType.timeseries || this.type === widgetType.alarm) {
if (this.useDashboardTimewindow) {
if (!deepEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
this.timeWindowConfig = deepClone(newDashboardTimewindow);
this.update();
}
@ -879,8 +878,8 @@ export class WidgetSubscription implements IWidgetSubscription {
}
private checkAlarmSource(aliasIds: Array<string>): boolean {
if (this.alarmSource && this.alarmSource.entityAliasId) {
return aliasIds.indexOf(this.alarmSource.entityAliasId) > -1;
if (this.options.alarmSource && this.options.alarmSource.entityAliasId) {
return aliasIds.indexOf(this.options.alarmSource.entityAliasId) > -1;
} else {
return false;
}
@ -888,11 +887,14 @@ export class WidgetSubscription implements IWidgetSubscription {
private checkSubscriptions(aliasIds: Array<string>): boolean {
let subscriptionsChanged = false;
for (const listener of this.datasourceListeners) {
if (listener.datasource.entityAliasId) {
if (aliasIds.indexOf(listener.datasource.entityAliasId) > -1) {
subscriptionsChanged = true;
break;
const datasources = this.options.datasources;
if (datasources) {
for (const datasource of datasources) {
if (datasource.entityAliasId) {
if (aliasIds.indexOf(datasource.entityAliasId) > -1) {
subscriptionsChanged = true;
break;
}
}
}
}
@ -1010,7 +1012,7 @@ export class WidgetSubscription implements IWidgetSubscription {
private alarmsUpdated(alarms: Array<AlarmInfo>) {
this.notifyDataLoaded();
const updated = !this.alarms || !deepEqual(this.alarms, alarms);
const updated = !this.alarms || !isEqual(this.alarms, alarms);
this.alarms = alarms;
if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) {
this.updateTimewindow();

15
ui-ngx/src/app/core/http/attribute.service.ts

@ -20,6 +20,7 @@ import { forkJoin, Observable, of } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { EntityId } from '@shared/models/id/entity-id';
import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { isDefinedAndNotNull } from '@core/utils';
@Injectable({
providedIn: 'root'
@ -31,10 +32,12 @@ export class AttributeService {
) { }
public getEntityAttributes(entityId: EntityId, attributeScope: AttributeScope,
config?: RequestConfig): Observable<Array<AttributeData>> {
return this.http.get<Array<AttributeData>>(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/` +
`${attributeScope}`,
defaultHttpOptionsFromConfig(config));
keys?: Array<string>, config?: RequestConfig): Observable<Array<AttributeData>> {
let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/${attributeScope}`;
if (keys && keys.length) {
url += `?keys=${keys.join(',')}`;
}
return this.http.get<Array<AttributeData>>(url, defaultHttpOptionsFromConfig(config));
}
public deleteEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array<AttributeData>,
@ -58,7 +61,7 @@ export class AttributeService {
const attributesData: {[key: string]: any} = {};
const deleteAttributes: AttributeData[] = [];
attributes.forEach((attribute) => {
if (attribute.value !== null) {
if (isDefinedAndNotNull(attribute.value)) {
attributesData[attribute.key] = attribute.value;
} else {
deleteAttributes.push(attribute);
@ -85,7 +88,7 @@ export class AttributeService {
const timeseriesData: {[key: string]: any} = {};
const deleteTimeseries: AttributeData[] = [];
timeseries.forEach((attribute) => {
if (attribute.value !== null) {
if (isDefinedAndNotNull(attribute.value)) {
timeseriesData[attribute.key] = attribute.value;
} else {
deleteTimeseries.push(attribute);

11
ui-ngx/src/app/core/services/item-buffer.service.ts

@ -19,16 +19,13 @@ import { Dashboard, DashboardLayoutId } from '@app/shared/models/dashboard.model
import { EntityAlias, EntityAliasFilter, EntityAliases, EntityAliasInfo, AliasesInfo } from '@shared/models/alias.models';
import { DatasourceType, Widget, WidgetPosition, WidgetSize } from '@shared/models/widget.models';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { deepClone } from '@core/utils';
import * as equal from 'deep-equal';
import { deepClone, isEqual } from '@core/utils';
import { UtilsService } from '@core/services/utils.service';
import { Observable, of, throwError } from 'rxjs';
import { map } from 'rxjs/operators';
import { FcRuleEdge, FcRuleNode, ruleNodeTypeDescriptors } from '@shared/models/rule-node.models';
import { FcRuleNode, ruleNodeTypeDescriptors } from '@shared/models/rule-node.models';
import { RuleChainService } from '@core/http/rule-chain.service';
import { RuleChainImport } from '@shared/models/rule-chain.models';
import { Simulate } from 'react-dom/test-utils';
import error = Simulate.error;
const WIDGET_ITEM = 'widget_item';
const WIDGET_REFERENCE = 'widget_reference';
@ -219,7 +216,7 @@ export class ItemBufferService {
let callAliasUpdateFunction = false;
if (aliasesInfo) {
const newEntityAliases = this.updateAliases(theDashboard, widget, aliasesInfo);
const aliasesUpdated = !equal(newEntityAliases, theDashboard.configuration.entityAliases);
const aliasesUpdated = !isEqual(newEntityAliases, theDashboard.configuration.entityAliases);
if (aliasesUpdated) {
theDashboard.configuration.entityAliases = newEntityAliases;
if (onAliasesUpdateFunction) {
@ -405,7 +402,7 @@ export class ItemBufferService {
}
private isEntityAliasEqual(alias1: EntityAliasInfo, alias2: EntityAliasInfo): boolean {
return equal(alias1.filter, alias2.filter);
return isEqual(alias1.filter, alias2.filter);
}
private getEntityAliasId(entityAliases: EntityAliases, aliasInfo: EntityAliasInfo): string {

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

@ -14,7 +14,8 @@
/// limitations under the License.
///
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import _ from 'lodash';
import { Observable, Subject } from 'rxjs';
import { finalize, share } from 'rxjs/operators';
import base64js from 'base64-js';
@ -384,6 +385,10 @@ export function deepClone<T>(target: T, ignoreFields?: string[]): T {
return target;
}
export function isEqual(a: any, b: any): boolean {
return _.isEqual(a, b);
}
export function guid(): string {
function s4(): string {
return Math.floor((1 + Math.random()) * 0x10000)

6
ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html

@ -79,12 +79,10 @@
(contextmenu)="openWidgetContextMenu($event, widget)">
<div fxLayout="row" fxLayoutAlign="space-between start">
<div class="tb-widget-title" fxLayout="column" fxLayoutAlign="center start" [fxShow]="widget.showWidgetTitlePanel">
<div *ngIf="widget.hasWidgetTitleTemplate">
TODO:
</div>
<span [fxShow]="widget.showTitle"
[ngStyle]="widget.titleStyle"
[matTooltip]="widget.titleTooltip"
matTooltipClass="tb-tooltip-multiline"
matTooltipPosition="above"
class="mat-subheading-2 title">
<mat-icon *ngIf="widget.showTitleIcon" [ngStyle]="widget.titleIconStyle">{{widget.titleIcon}}</mat-icon>
@ -100,7 +98,7 @@
</div>
<div [fxShow]="widget.showWidgetActions"
class="tb-widget-actions"
[ngClass]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel&&(widget.hasWidgetTitleTemplate||widget.showTitle||widget.hasAggregation))}"
[ngClass]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel&&(widget.showTitle||widget.hasAggregation))}"
fxLayout="row"
fxLayoutAlign="start center"
(mousedown)="$event.stopPropagation()">

4
ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts

@ -51,7 +51,7 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models';
import { DialogService } from '@core/services/dialog.service';
import { AddEntityDialogComponent } from './add-entity-dialog.component';
import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models';
import { historyInterval, Timewindow } from '@shared/models/time/time.models';
import { historyInterval, HistoryWindowType, Timewindow } from '@shared/models/time/time.models';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
import { isDefined, isUndefined } from '@core/utils';
@ -218,7 +218,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()];
if (this.entitiesTableConfig.useTimePageLink) {
const timePageLink = this.pageLink as TimePageLink;
if (this.timewindow.history.timewindowMs) {
if (this.timewindow.history.historyType === HistoryWindowType.LAST_INTERVAL) {
const currentTime = Date.now();
timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs;
timePageLink.endTime = currentTime;

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

@ -43,7 +43,7 @@
[(ngModel)]="action.customFunction"
(ngModelChange)="onActionUpdated()"
[fillHeight]="true"
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams']"
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams', 'entityLabel']"
[validationArgs]="[]">
</tb-js-func>
</div>

2
ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html

@ -93,7 +93,7 @@
<tb-js-func
[(ngModel)]="action.customFunction"
(ngModelChange)="notifyActionUpdated()"
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams']"
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'htmlTemplate', 'additionalParams', 'entityLabel']"
[validationArgs]="[]">
</tb-js-func>
</mat-tab>

11
ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts

@ -109,15 +109,20 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
}
private validate(): boolean {
for (const resource of this.action.customResources) {
if (!resource.url) {
return false;
if (this.action.customResources) {
for (const resource of this.action.customResources) {
if (!resource.url) {
return false;
}
}
}
return true;
}
public addResource() {
if (!this.action.customResources) {
this.action.customResources = [];
}
this.action.customResources.push({url: ''});
this.notifyActionUpdated();
}

4
ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts

@ -28,7 +28,7 @@ import { PageLink } from '@shared/models/page/page-link';
import { catchError, map, publishReplay, refCount, share, take, tap } from 'rxjs/operators';
import { entityTypeTranslations } from '@shared/models/entity-type.models';
import { UtilsService } from '@core/services/utils.service';
import { deepClone, isUndefined } from '@core/utils';
import { deepClone, isDefined, isUndefined } from '@core/utils';
import customSampleJs from '!raw-loader!./custom-sample-js.raw';
import customSampleCss from '!raw-loader!./custom-sample-css.raw';
@ -72,7 +72,7 @@ export function toCustomAction(action: WidgetActionDescriptorInfo): CustomAction
customFunction: action.customFunction
};
}
result.customResources = action ? deepClone(action.customResources) : [];
result.customResources = action && isDefined(action.customResources) ? deepClone(action.customResources) : [];
return result;
}

2
ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html

@ -133,7 +133,7 @@
<ng-template [ngSwitchCase]="widgetActionType.custom">
<tb-js-func
formControlName="customFunction"
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams']"
[functionArgs]="['$event', 'widgetContext', 'entityId', 'entityName', 'additionalParams', 'entityLabel']"
[validationArgs]="[]"
></tb-js-func>
</ng-template>

4
ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts

@ -102,8 +102,4 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
}));
}
widgetForceReInit() {
this.ctx.widgetForceReInit();
}
}

4
ui-ngx/src/app/modules/home/components/widget/lib/digital-gauge.ts

@ -193,7 +193,9 @@ export class TbCanvasDigitalGauge {
}
const value = tvPair[1];
if(value !== this.gauge.value) {
this.gauge._value = value;
if (!this.gauge.options.animation) {
this.gauge._value = value;
}
this.gauge.value = value;
} else if (this.localSettings.showTimestamp && this.gauge.timestamp !== timestamp) {
this.gauge.timestamp = timestamp;

2
ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html

@ -84,7 +84,7 @@
<mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
<mat-row [ngClass]="{'tb-current-entity': entityDatasource.isCurrentEntity(entity)}"
*matRowDef="let entity; columns: displayedColumns;"
(click)="onRowClick($event, entity)"></mat-row>
(click)="onRowClick($event, entity)" (dblclick)="onRowClick($event, entity, true)"></mat-row>
</mat-table>
<span [fxShow]="entityDatasource.isEmpty() | async"
fxLayoutAlign="center center"

7
ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts

@ -304,7 +304,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
const dataKeys: Array<DataKey> = [];
const datasource = this.subscription.datasources[0];
const datasource = this.subscription.options.datasources ? this.subscription.options.datasources[0] : null;
if (datasource) {
datasource.dataKeys.forEach((entityDataKey) => {
@ -475,12 +475,13 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
}
}
public onRowClick($event: Event, entity: EntityData) {
public onRowClick($event: Event, entity: EntityData, isDouble?: boolean) {
if ($event) {
$event.stopPropagation();
}
this.entityDatasource.toggleCurrentEntity(entity);
const descriptors = this.ctx.actionsApi.getActionDescriptors('rowClick');
const actionSourceId = isDouble ? 'rowDoubleClick' : 'rowClick';
const descriptors = this.ctx.actionsApi.getActionDescriptors(actionSourceId);
if (descriptors.length) {
let entityId;
let entityName;

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

@ -79,6 +79,18 @@ export interface TbFlotSeriesHoverInfo {
distance: number;
}
export interface TbFlotThresholdMarking {
lineWidth?: number;
color?: string;
[key: string]: any;
}
export interface TbFlotThresholdKeySettings {
yaxis: number;
lineWidth: number;
color: string;
}
export interface TbFlotGridSettings {
color: string;
backgroundColor: string;
@ -133,12 +145,19 @@ export interface TbFlotComparisonSettings {
xaxisSecond: TbFlotSecondXAxisSettings;
}
export interface TbFlotGraphSettings extends TbFlotBaseSettings, TbFlotComparisonSettings {
export interface TbFlotThresholdsSettings {
thresholdsLineWidth: number;
}
export interface TbFlotGraphSettings extends TbFlotBaseSettings, TbFlotThresholdsSettings, TbFlotComparisonSettings {
smoothLines: boolean;
}
export interface TbFlotBarSettings extends TbFlotBaseSettings, TbFlotComparisonSettings {
export declare type BarAlignment = 'left' | 'right' | 'center';
export interface TbFlotBarSettings extends TbFlotBaseSettings, TbFlotThresholdsSettings, TbFlotComparisonSettings {
defaultBarWidth: number;
barAlignment: BarAlignment;
}
export interface TbFlotPieSettings {
@ -158,6 +177,23 @@ export interface TbFlotPieSettings {
export declare type TbFlotYAxisPosition = 'left' | 'right';
export declare type TbFlotXAxisPosition = 'top' | 'bottom';
export declare type TbFlotThresholdValueSource = 'predefinedValue' | 'entityAttribute';
export interface TbFlotKeyThreshold {
thresholdValueSource: TbFlotThresholdValueSource;
thresholdEntityAlias: string;
thresholdAttribute: string;
thresholdValue: number;
lineWidth: number;
color: string;
}
export interface TbFlotKeyComparisonSettings {
showValuesForComparison: boolean;
comparisonValuesLabel: string;
color: string;
}
export interface TbFlotKeySettings {
excludeFromStacking: boolean;
hideDataByDefault: boolean;
@ -180,6 +216,8 @@ export interface TbFlotKeySettings {
axisTickSize: number;
axisPosition: TbFlotYAxisPosition;
axisTicksFormatter: string;
thresholds: TbFlotKeyThreshold[];
comparisonSettings: TbFlotKeyComparisonSettings;
}
export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema {
@ -212,6 +250,17 @@ export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema {
type: 'number',
default: 600
};
properties.barAlignment = {
title: 'Bar alignment',
type: 'string',
default: 'left'
};
}
if (chartType === 'graph' || chartType === 'bar') {
properties.thresholdsLineWidth = {
title: 'Default line width for all thresholds',
type: 'number'
};
}
properties.shadowSize = {
title: 'Shadow size',
@ -362,6 +411,28 @@ export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema {
}
if (chartType === 'bar') {
schema.form.push('defaultBarWidth');
schema.form.push({
key: 'barAlignment',
type: 'rc-select',
multiple: false,
items: [
{
value: 'left',
label: 'Left'
},
{
value: 'right',
label: 'Right'
},
{
value: 'center',
label: 'Center'
}
]
});
}
if (chartType === 'graph' || chartType === 'bar') {
schema.form.push('thresholdsLineWidth');
}
schema.form.push('shadowSize');
schema.form.push({
@ -837,6 +908,70 @@ export function flotDatakeySettingsSchema(defaultShowLines: boolean, chartType:
const properties = schema.schema.properties;
if (chartType === 'graph' || chartType === 'bar') {
properties.thresholds = {
title: 'Thresholds',
type: 'array',
items: {
title: 'Threshold',
type: 'object',
properties: {
thresholdValueSource: {
title: 'Threshold value source',
type: 'string',
default: 'predefinedValue'
},
thresholdEntityAlias: {
title: 'Thresholds source entity alias',
type: 'string'
},
thresholdAttribute: {
title: 'Threshold source entity attribute',
type: 'string'
},
thresholdValue: {
title: 'Threshold value (if predefined value is selected)',
type: 'number'
},
lineWidth: {
title: 'Line width',
type: 'number'
},
color: {
title: 'Color',
type: 'string'
}
}
},
required: []
};
schema.form.push({
key: 'thresholds',
items: [
{
key: 'thresholds[].thresholdValueSource',
type: 'rc-select',
multiple: false,
items: [
{
value: 'predefinedValue',
label: 'Predefined value (Default)'
},
{
value: 'entityAttribute',
label: 'Value taken from entity attribute'
}
]
},
'thresholds[].thresholdValue',
'thresholds[].thresholdEntityAlias',
'thresholds[].thresholdAttribute',
{
key: 'thresholds[].color',
type: 'color'
},
'thresholds[].lineWidth'
]
});
properties.comparisonSettings = {
title: 'Comparison Settings',
type: 'object',

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

@ -16,12 +16,20 @@
import { WidgetContext } from '@home/models/widget-component.models';
import { deepClone, isDefined, isNumber, isUndefined } from '@app/core/utils';
import { IWidgetSubscription } from '@core/api/widget-api.models';
import { DatasourceData, JsonSettingsSchema } from '@app/shared/models/widget.models';
import { deepClone, isDefined, isEqual, isNumber, isUndefined } from '@app/core/utils';
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import {
DataKey,
Datasource,
DatasourceData,
DatasourceType,
JsonSettingsSchema,
widgetType
} from '@app/shared/models/widget.models';
import {
ChartType,
flotDatakeySettingsSchema, flotPieDatakeySettingsSchema,
flotDatakeySettingsSchema,
flotPieDatakeySettingsSchema,
flotPieSettingsSchema,
flotSettingsSchema,
TbFlotAxisOptions,
@ -33,6 +41,8 @@ import {
TbFlotSeries,
TbFlotSeriesHoverInfo,
TbFlotSettings,
TbFlotThresholdKeySettings,
TbFlotThresholdMarking,
TbFlotTicksFormatterFunction,
TooltipValueFormatFunction
} from './flot-widget.models';
@ -40,8 +50,9 @@ 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;
import { UtilsService } from '@core/services/utils.service';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import Timeout = NodeJS.Timeout;
const tinycolor = tinycolor_;
const moment = moment_;
@ -51,12 +62,13 @@ const flotPieDatakeySettingsSchemaValue = flotPieDatakeySettingsSchema;
export class TbFlot {
private readonly utils: UtilsService;
private settings: TbFlotSettings;
private readonly tooltip: JQuery<any>;
private readonly yAxisTickFormatter: TbFlotTicksFormatterFunction;
private ticksFormatterFunction: TbFlotTicksFormatterFunction;
private readonly yaxis: TbFlotAxisOptions;
private readonly xaxis: TbFlotAxisOptions;
private yaxes: Array<TbFlotAxisOptions>;
@ -73,6 +85,9 @@ export class TbFlot {
private readonly defaultBarWidth: number;
private thresholdsSourcesSubscription: IWidgetSubscription;
private predefinedThresholds: TbFlotThresholdMarking[];
private plotInited = false;
private plot: JQueryPlot;
@ -118,7 +133,7 @@ export class TbFlot {
constructor(private ctx: WidgetContext, private readonly chartType: ChartType) {
this.chartType = this.chartType || 'line';
this.settings = ctx.settings as TbFlotSettings;
const utils = this.ctx.$injector.get(UtilsService);
this.utils = this.ctx.$injector.get(UtilsService);
this.tooltip = $('#flot-series-tooltip');
if (this.tooltip.length === 0) {
this.tooltip = this.createTooltipElement();
@ -145,7 +160,8 @@ export class TbFlot {
grid: {
hoverable: true,
mouseActiveRadius: 10,
autoHighlight: this.tooltipIndividual === true
autoHighlight: this.tooltipIndividual === true,
markings: []
},
selection : { mode : ctx.isMobile ? null : 'x' },
legend : {
@ -167,7 +183,7 @@ export class TbFlot {
};
if (this.settings.xaxis) {
this.xaxis.font.color = this.settings.xaxis.color || this.xaxis.font.color;
this.xaxis.label = utils.customTranslation(this.settings.xaxis.title, this.settings.xaxis.title) || null;
this.xaxis.label = this.utils.customTranslation(this.settings.xaxis.title, this.settings.xaxis.title) || null;
this.xaxis.labelFont.color = this.xaxis.font.color;
this.xaxis.labelFont.size = this.xaxis.font.size + 2;
this.xaxis.labelFont.weight = 'bold';
@ -181,7 +197,7 @@ export class TbFlot {
this.yaxis.font.color = this.settings.yaxis.color || this.yaxis.font.color;
this.yaxis.min = isDefined(this.settings.yaxis.min) ? this.settings.yaxis.min : null;
this.yaxis.max = isDefined(this.settings.yaxis.max) ? this.settings.yaxis.max : null;
this.yaxis.label = utils.customTranslation(this.settings.yaxis.title, this.settings.yaxis.title) || null;
this.yaxis.label = this.utils.customTranslation(this.settings.yaxis.title, this.settings.yaxis.title) || null;
this.yaxis.labelFont.color = this.yaxis.font.color;
this.yaxis.labelFont.size = this.yaxis.font.size + 2;
this.yaxis.labelFont.weight = 'bold';
@ -244,7 +260,7 @@ export class TbFlot {
return '';
};
}
xaxis.label = utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null;
xaxis.label = this.utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null;
xaxis.position = this.settings.xaxisSecond.axisPosition;
}
xaxis.tickLength = 0;
@ -270,6 +286,10 @@ export class TbFlot {
};
}
if (this.chartType === 'line' && isFinite(this.settings.thresholdsLineWidth)) {
this.options.grid.markingsLineWidth = this.settings.thresholdsLineWidth;
}
if (this.chartType === 'bar') {
this.options.series.lines = {
show: false,
@ -279,7 +299,8 @@ export class TbFlot {
this.options.series.bars = {
show: true,
lineWidth: 0,
fill: 0.9
fill: 0.9,
align: this.settings.barAlignment || 'left'
};
this.defaultBarWidth = this.settings.defaultBarWidth || 600;
}
@ -346,6 +367,8 @@ export class TbFlot {
const colors: string[] = [];
this.yaxes = [];
const yaxesMap: {[units: string]: TbFlotAxisOptions} = {};
const predefinedThresholds: TbFlotThresholdMarking[] = [];
const thresholdsDatasources: Datasource[] = [];
let tooltipValueFormatFunction: TooltipValueFormatFunction = null;
if (this.settings.tooltipValueFormatter && this.settings.tooltipValueFormatter.length) {
@ -441,8 +464,54 @@ export class TbFlot {
series.yaxis = series.yaxisIndex + 1;
yaxis.keysInfo[i] = {hidden: false};
yaxis.show = true;
if (keySettings.thresholds && keySettings.thresholds.length) {
for (const threshold of keySettings.thresholds) {
if (threshold.thresholdValueSource === 'predefinedValue' && isFinite(threshold.thresholdValue)) {
const colorIndex = this.subscription.data.length + predefinedThresholds.length;
this.generateThreshold(predefinedThresholds, series.yaxis, threshold.lineWidth,
threshold.color, colorIndex, threshold.thresholdValue);
} else if (threshold.thresholdEntityAlias && threshold.thresholdAttribute) {
const entityAliasId = this.ctx.aliasController.getEntityAliasId(threshold.thresholdEntityAlias);
if (!entityAliasId) {
continue;
}
let datasource = thresholdsDatasources.filter((thresholdDatasource) => {
return thresholdDatasource.entityAliasId === entityAliasId;
})[0];
const dataKey: DataKey = {
type: DataKeyType.attribute,
name: threshold.thresholdAttribute,
label: threshold.thresholdAttribute,
settings: {
yaxis: series.yaxis,
lineWidth: threshold.lineWidth,
color: threshold.color
} as TbFlotThresholdKeySettings,
_hash: Math.random()
};
if (datasource) {
datasource.dataKeys.push(dataKey);
} else {
datasource = {
type: DatasourceType.entity,
name: threshold.thresholdEntityAlias,
aliasName: threshold.thresholdEntityAlias,
entityAliasId,
dataKeys: [ dataKey ]
};
thresholdsDatasources.push(datasource);
}
}
}
}
}
}
this.subscribeForThresholdsAttributes(thresholdsDatasources);
this.options.grid.markings = predefinedThresholds;
this.predefinedThresholds = predefinedThresholds;
this.options.colors = colors;
this.options.yaxes = deepClone(this.yaxes);
if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') {
@ -714,6 +783,72 @@ export class TbFlot {
return yaxis;
}
private subscribeForThresholdsAttributes(datasources: Datasource[]) {
const thresholdsSourcesSubscriptionOptions: WidgetSubscriptionOptions = {
datasources,
useDashboardTimewindow: false,
type: widgetType.latest,
callbacks: {
onDataUpdated: (subscription) => {this.thresholdsSourcesDataUpdated(subscription.data)}
}
};
this.ctx.subscriptionApi.createSubscription(thresholdsSourcesSubscriptionOptions, true).subscribe(
(subscription) => {
this.thresholdsSourcesSubscription = subscription;
}
);
}
private thresholdsSourcesDataUpdated(data: DatasourceData[]) {
const allThresholds = deepClone(this.predefinedThresholds);
data.forEach((keyData) => {
if (keyData && keyData.data && keyData.data[0]) {
const attrValue = keyData.data[0][1];
if (isFinite(attrValue)) {
const settings: TbFlotThresholdKeySettings = keyData.dataKey.settings;
const colorIndex = this.subscription.data.length + allThresholds.length;
this.generateThreshold(allThresholds, settings.yaxis, settings.lineWidth, settings.color, colorIndex, attrValue);
}
}
});
this.options.grid.markings = allThresholds;
this.redrawPlot();
}
private generateThreshold(existingThresholds: TbFlotThresholdMarking[], yaxis: number, lineWidth: number,
color: string, defaultColorIndex: number, thresholdValue: number) {
const marking: TbFlotThresholdMarking = {};
let markingYAxis;
if (yaxis !== 1) {
markingYAxis = 'y' + yaxis + 'axis';
} else {
markingYAxis = 'yaxis';
}
if (isFinite(lineWidth)) {
marking.lineWidth = lineWidth;
}
if (isDefined(color)) {
marking.color = color;
} else {
marking.color = this.utils.getMaterialColor(defaultColorIndex);
}
marking[markingYAxis] = {
from: thresholdValue,
to: thresholdValue
};
const similarMarkings = existingThresholds.filter((existingMarking) => {
return isEqual(existingMarking[markingYAxis], marking[markingYAxis]);
});
if (!similarMarkings.length) {
existingThresholds.push(marking);
}
}
private seriesInfoDiv(label: string, color: string, value: any,
units: string, trackDecimals: number, active: boolean,
percent: number, valueFormatFunction: TooltipValueFormatFunction): JQuery<HTMLElement> {

2
ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.html

@ -29,7 +29,7 @@
<div class="top"></div>
<div #knobErrorContainer class="error-container" [ngStyle]="{'background': error?.length ? 'rgba(255,255,255,0.25)' : 'none'}"
fxLayout="row" fxLayoutAlign="center center">
<span #knobError class="knob-error">{{ error }}</span>
<span #knobError class="knob-error" [innerHTML]="error"></span>
</div>
<div #knobTitleContainer class="title-container" fxLayout="row" fxLayoutAlign="center center">
<span #knobTitle class="knob-title">{{ title }}</span>

3
ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.scss

@ -26,13 +26,10 @@ $minmax-height: percentage(.04) !default;
$minmax-container-margin-pct: percentage(.18) !default;
$minmax-container-margin-bottom-pct: percentage(.12) !default;
$background-color: #e6e7e8 !default;
:host {
.tb-knob {
width: 100%;
height: 100%;
background: $background-color;
.knob {
position: relative;

6
ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts

@ -24,6 +24,7 @@ import { isDefined, isNumber } from '@core/utils';
import { CanvasDigitalGauge, CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
import GenericOptions = CanvasGauges.GenericOptions;
import * as tinycolor_ from 'tinycolor2';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
const tinycolor = tinycolor_;
@ -338,6 +339,9 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
let textWidth = this.measureTextWidth(text, fontSize);
while (textWidth > maxWidth) {
fontSize--;
if (fontSize < 0) {
break;
}
textWidth = this.measureTextWidth(text, fontSize);
}
element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
@ -345,7 +349,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
private measureTextWidth(text: string, fontSize: number): number {
this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'});
this.textMeasure.text(text);
this.textMeasure.html(text);
return this.textMeasure.width();
}

3
ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.scss

@ -17,13 +17,10 @@
$error-height: 14px !default;
$background-color: #e6e7e8 !default;
:host {
.tb-led-indicator {
width: 100%;
height: 100%;
background: $background-color;
.title-container {
.led-title {

3
ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss

@ -17,13 +17,10 @@
$error-height: 14px !default;
$background-color: #e6e7e8 !default;
:host {
.tb-round-switch {
width: 100%;
height: 100%;
background: $background-color;
.title-container {
.switch-title {

3
ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.scss

@ -19,8 +19,6 @@ $thumb-checked-img: url("./svg/thumb-checked.svg") !default;
$thumb-bar-img: url("./svg/thumb-bar.svg") !default;
$thumb-bar-checked-img: url("./svg/thumb-bar-checked.svg") !default;
$background-color: #e6e7e8 !default;
$error-height: 14px !default;
:host {
@ -28,7 +26,6 @@ $error-height: 14px !default;
.tb-switch {
width: 100%;
height: 100%;
background: $background-color;
.error-container {
position: absolute;

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

@ -263,7 +263,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.widgetContext.servicesMap = ServicesMap;
this.widgetContext.isEdit = this.isEdit;
this.widgetContext.isMobile = this.isMobile;
this.widgetContext.widgetForceReInit = this.reInit.bind(this);
this.widgetContext.subscriptionApi = {
createSubscription: this.createSubscription.bind(this),
@ -1007,8 +1006,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
additionalParams = {};
}
const customActionFunction = new Function('$event', 'widgetContext', 'entityId',
'entityName', 'additionalParams', customFunction);
customActionFunction($event, this.widgetContext, entityId, entityName, additionalParams);
'entityName', 'additionalParams', 'entityLabel', customFunction);
customActionFunction($event, this.widgetContext, entityId, entityName, additionalParams, entityLabel);
} catch (e) {
//
}
@ -1032,8 +1031,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
additionalParams = {};
}
const customActionPrettyFunction = new Function('$event', 'widgetContext', 'entityId',
'entityName', 'htmlTemplate', 'additionalParams', customPrettyFunction);
customActionPrettyFunction($event, this.widgetContext, entityId, entityName, htmlTemplate, additionalParams);
'entityName', 'htmlTemplate', 'additionalParams', 'entityLabel', customPrettyFunction);
customActionPrettyFunction($event, this.widgetContext, entityId, entityName, htmlTemplate, additionalParams, entityLabel);
} catch (e) {
//
}
@ -1096,7 +1095,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.cssParser.createStyleElement(actionNamespace, customCss, 'nonamespace');
}
const resourceTasks: Observable<string>[] = [];
if (customResources.length > 0) {
if (isDefined(customResources) && customResources.length > 0) {
customResources.forEach((resource) => {
resourceTasks.push(
this.resources.loadResource(resource.url).pipe(

12
ui-ngx/src/app/modules/home/home.component.ts

@ -30,12 +30,14 @@ import { getCurrentAuthState, selectAuthUser, selectUserDetails } from '@core/au
import { MediaBreakpoints } from '@shared/models/constants';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { Router } from '@angular/router';
import * as screenfull from 'screenfull';
import * as _screenfull from 'screenfull';
import { MatSidenav } from '@angular/material/sidenav';
import { AuthState } from '@core/auth/auth.models';
import { WINDOW } from '@core/services/window.service';
import { ISearchableComponent, instanceOfSearchableComponent } from '@home/models/searchable-component.models';
const screenfull = _screenfull as _screenfull.Screenfull;
@Component({
selector: 'tb-home',
templateUrl: './home.component.html',
@ -60,8 +62,7 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
@ViewChild('searchInput') searchInputField: ElementRef;
// @ts-ignore
fullscreenEnabled = screenfull.enabled;
fullscreenEnabled = screenfull.isEnabled;
authUser$: Observable<any>;
userDetails$: Observable<User>;
@ -125,15 +126,12 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
}
toggleFullscreen() {
// @ts-ignore
if (screenfull.enabled) {
// @ts-ignore
if (screenfull.isEnabled) {
screenfull.toggle();
}
}
isFullscreen() {
// @ts-ignore
return screenfull.isFullscreen;
}

13
ui-ngx/src/app/modules/home/menu/menu-link.component.html

@ -15,9 +15,10 @@
limitations under the License.
-->
<button mat-button
routerLinkActive="tb-active" [routerLinkActiveOptions]="{exact: true}" routerLink="{{section.path}}">
<mat-icon *ngIf="!section.isMdiIcon && section.icon != null" class="material-icons">{{section.icon}}</mat-icon>
<mat-icon *ngIf="section.isMdiIcon && section.icon != null" [svgIcon]="section.icon"></mat-icon>
<span>{{section.name | translate}}</span>
</button>
<a class="mat-button" routerLinkActive="tb-active" [routerLinkActiveOptions]="{exact: true}" routerLink="{{section.path}}">
<span class="mat-button-wrapper">
<mat-icon *ngIf="!section.isMdiIcon && section.icon != null" class="material-icons">{{section.icon}}</mat-icon>
<mat-icon *ngIf="section.isMdiIcon && section.icon != null" [svgIcon]="section.icon"></mat-icon>
<span>{{section.name | translate}}</span>
</span>
</a>

19
ui-ngx/src/app/modules/home/menu/menu-toggle.component.html

@ -15,15 +15,16 @@
limitations under the License.
-->
<button mat-button
routerLinkActive="tb-active" [routerLinkActiveOptions]="{exact: true}" routerLink="{{section.path}}"
class="tb-button-toggle">
<mat-icon *ngIf="!section.isMdiIcon && section.icon != null" class="material-icons">{{section.icon}}</mat-icon>
<mat-icon *ngIf="section.isMdiIcon && section.icon != null" [svgIcon]="section.icon"></mat-icon>
<span>{{section.name | translate}}</span>
<span class=" pull-right fa fa-chevron-down tb-toggle-icon"
[ngClass]="{'tb-toggled' : sectionActive()}"></span>
</button>
<a class="mat-button tb-button-toggle"
routerLinkActive="tb-active" [routerLinkActiveOptions]="{exact: true}" routerLink="{{section.path}}">
<span class="mat-button-wrapper">
<mat-icon *ngIf="!section.isMdiIcon && section.icon != null" class="material-icons">{{section.icon}}</mat-icon>
<mat-icon *ngIf="section.isMdiIcon && section.icon != null" [svgIcon]="section.icon"></mat-icon>
<span>{{section.name | translate}}</span>
<span class=" pull-right fa fa-chevron-down tb-toggle-icon"
[ngClass]="{'tb-toggled' : sectionActive()}"></span>
</span>
</a>
<ul id="docs-menu-{{section.name | nospace}}" class="tb-menu-toggle-list" [ngStyle]="{height: sectionHeight()}">
<li *ngFor="let page of section.pages">
<tb-menu-link [section]="page"></tb-menu-link>

66
ui-ngx/src/app/modules/home/menu/side-menu.component.scss

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
width: 100%;
}
@ -30,7 +31,8 @@
border-bottom: 1px solid rgba(0, 0, 0, .12);
}
button {
a.mat-button {
text-transform: uppercase;
display: flex;
width: 100%;
max-height: 40px;
@ -46,52 +48,46 @@
cursor: pointer;
border-radius: 0;
&:hover {
border-bottom: none;
background-color: rgba(255,255,255,0.08);
}
&:focus {
border-bottom: none;
}
&.tb-active {
font-weight: 500;
background-color: rgba(255, 255, 255, .15);
}
.mat-button-wrapper {
width: 100%;
mat-icon {
margin-right: 8px;
margin-left: 0;
}
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&.tb-toggle-icon {
padding-top: 12px;
padding-bottom: 12px;
}
}
}
}
button.tb-active {
font-weight: 500;
background-color: rgba(255, 255, 255, .15);
}
span.tb-toggle-icon {
padding-top: 12px;
padding-bottom: 12px;
}
mat-icon {
margin-right: 8px;
margin-left: 0;
}
.tb-menu-toggle-list button {
padding: 0 16px 0 32px;
font-weight: 500;
text-transform: none;
text-rendering: optimizeLegibility;
}
.tb-button-toggle .tb-toggle-icon {
display: inline-block;
width: 15px;
margin: auto 0 auto auto;
background-size: 100% auto;
transition: transform .3s, ease-in-out;
}
.tb-button-toggle .tb-toggle-icon.tb-toggled {
transform: rotateZ(180deg);
.tb-button-toggle {
.tb-toggle-icon {
display: inline-block;
width: 15px;
margin: auto 0 auto auto;
background-size: 100% auto;
transition: transform .3s, ease-in-out;
&.tb-toggled {
transform: rotateZ(180deg);
}
}
}
.tb-menu-toggle-list {
@ -103,7 +99,7 @@
transition-property: height;
button {
a.mat-button {
padding: 0 16px 0 32px;
font-weight: 500;
text-transform: none !important;

15
ui-ngx/src/app/modules/home/models/dashboard-component.models.ts

@ -20,10 +20,9 @@ import { WidgetLayout, WidgetLayouts } from '@app/shared/models/dashboard.models
import { IDashboardWidget, WidgetAction, WidgetContext, WidgetHeaderAction } from './widget-component.models';
import { Timewindow } from '@shared/models/time/time.models';
import { Observable, of, Subject } from 'rxjs';
import { guid, isDefined, isUndefined } from '@app/core/utils';
import { guid, isDefined, isEqual, isUndefined } from '@app/core/utils';
import { IterableDiffer, KeyValueDiffer, NgZone } from '@angular/core';
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
import * as deepEqual from 'deep-equal';
export interface WidgetsData {
widgets: Array<Widget>;
@ -166,8 +165,8 @@ export class DashboardWidgets implements Iterable<DashboardWidget> {
index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widgetId === record.widgetId);
if (index > -1) {
const prevDashboardWidget = this.dashboardWidgets[index];
if (!deepEqual(prevDashboardWidget.widget, record.widget) ||
!deepEqual(prevDashboardWidget.widgetLayout, record.widgetLayout)) {
if (!isEqual(prevDashboardWidget.widget, record.widget) ||
!isEqual(prevDashboardWidget.widgetLayout, record.widgetLayout)) {
this.dashboardWidgets[index] = new DashboardWidget(this.dashboard, record.widget, record.widgetLayout);
this.dashboardWidgets[index].highlighted = prevDashboardWidget.highlighted;
this.dashboardWidgets[index].selected = prevDashboardWidget.selected;
@ -312,9 +311,6 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
style: {[klass: string]: any};
hasWidgetTitleTemplate: boolean;
widgetTitleTemplate: string;
showWidgetTitlePanel: boolean;
showWidgetActions: boolean;
@ -396,11 +392,8 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
this.style = {...this.widget.config.widgetStyle, ...this.style};
}
this.hasWidgetTitleTemplate = this.widgetContext.widgetTitleTemplate ? true : false;
this.widgetTitleTemplate = this.widgetContext.widgetTitleTemplate ? this.widgetContext.widgetTitleTemplate : '';
this.showWidgetTitlePanel = this.widgetContext.hideTitlePanel ? false :
this.hasWidgetTitleTemplate || this.showTitle || this.hasTimewindow;
this.showTitle || this.hasTimewindow;
this.showWidgetActions = this.widgetContext.hideTitlePanel ? false : true;

18
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -14,7 +14,6 @@
/// limitations under the License.
///
import { ExceptionData } from '@shared/models/error.models';
import { IDashboardComponent } from '@home/models/dashboard-component.models';
import {
DataSet,
@ -51,7 +50,7 @@ import { AssetService } from '@app/core/http/asset.service';
import { DialogService } from '@core/services/dialog.service';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { isDefined, formatValue } from '@core/utils';
import { Observable, of, ReplaySubject } from 'rxjs';
import { forkJoin, Observable, of, ReplaySubject } from 'rxjs';
import { WidgetSubscription } from '@core/api/widget-subscription';
export interface IWidgetAction {
@ -161,8 +160,6 @@ export class WidgetContext {
isEdit: boolean;
isMobile: boolean;
widgetForceReInit?: () => void;
widgetNamespace?: string;
subscriptionApi?: WidgetSubscriptionApi;
@ -176,7 +173,6 @@ export class WidgetContext {
hideTitlePanel = false;
widgetTitleTemplate?: string;
widgetTitle?: string;
widgetTitleTooltip?: string;
customHeaderActions?: Array<WidgetHeaderAction>;
@ -188,6 +184,11 @@ export class WidgetContext {
ngZone?: NgZone;
rxjs = {
forkJoin,
of
};
detectChanges(updateWidgetParams: boolean = false) {
if (!this.destroyed) {
if (updateWidgetParams) {
@ -209,12 +210,14 @@ export class WidgetContext {
}
}
updateAliases(aliasIds?: Array<string>) {
this.aliasController.updateAliases(aliasIds);
}
reset() {
this.destroyed = false;
this.hideTitlePanel = false;
this.widgetTitleTemplate = undefined;
this.widgetTitle = undefined;
this.customHeaderActions = undefined;
this.widgetActions = undefined;
}
}
@ -228,7 +231,6 @@ export interface IDynamicWidgetComponent {
rpcErrorText: string;
rpcRejection: HttpErrorResponse;
raf: RafService;
widgetForceReInit(): void;
[key: string]: any;
}

2
ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<form #statesForm="ngForm" [formGroup]="statesFormGroup" (ngSubmit)="save()" style="min-width: 600px;">
<form #statesForm="ngForm" [formGroup]="statesFormGroup" (ngSubmit)="save()" style="min-width: 620px;">
<mat-toolbar fxLayout="row" color="primary">
<h2 translate>dashboard.manage-states</h2>
<span fxFlex></span>

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

@ -14,31 +14,21 @@
/// limitations under the License.
///
import {COMMA, ENTER, SEMICOLON} from '@angular/cdk/keycodes';
import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, SkipSelf, ViewChild} from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
FormGroupDirective,
NG_VALUE_ACCESSOR, NgForm
} from '@angular/forms';
import {Observable, of} from 'rxjs';
import {map, mergeMap, startWith, tap, share, pairwise, filter} from 'rxjs/operators';
import {Store} from '@ngrx/store';
import {AppState} from '@app/core/core.state';
import {TranslateService} from '@ngx-translate/core';
import {AliasEntityType, EntityType} from '@shared/models/entity-type.models';
import {BaseData} from '@shared/models/base-data';
import {EntityId} from '@shared/models/id/entity-id';
import {EntityService} from '@core/http/entity.service';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, mergeMap, share } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { EntityId } from '@shared/models/id/entity-id';
import { EntityService } from '@core/http/entity.service';
import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatChipList, MatChipInputEvent } from '@angular/material/chips';
import { ErrorStateMatcher } from '@angular/material/core';
import { MatChipInputEvent, MatChipList } from '@angular/material/chips';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {DataKeyType} from '@shared/models/telemetry/telemetry.models';
import * as equal from 'deep-equal';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { isEqual } from '@core/utils';
@Component({
selector: 'tb-entity-keys-list',
@ -62,7 +52,7 @@ export class EntityKeysListComponent implements ControlValueAccessor, OnInit, Af
@Input()
set entityId(entityId: EntityId) {
if (!equal(this.entityIdValue, entityId)) {
if (!isEqual(this.entityIdValue, entityId)) {
this.entityIdValue = entityId;
this.dirty = true;
}

5
ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts

@ -24,8 +24,7 @@ import {
JsonSchemaData,
JsonFormData
} from './json-form.models';
import { isDefined, isString, isUndefined } from '@core/utils';
import * as equal from 'deep-equal';
import { isDefined, isEqual, isString, isUndefined } from '@core/utils';
function validateBySchema(schema: any, value: any): SchemaValidationResult {
return tv.validateResult(value, schema);
@ -530,7 +529,7 @@ function setValue(obj: any, key: string, val: any): boolean {
changed = true;
}
} else {
changed = !equal(obj[key], val);
changed = !isEqual(obj[key], val);
obj[key] = val;
}
}

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

@ -14,10 +14,6 @@
/// limitations under the License.
///
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 { GroupInfo } from '@shared/models/widget.models';

2
ui-ngx/src/app/shared/models/time/time.models.ts

@ -110,7 +110,9 @@ export interface WidgetTimewindow {
export function historyInterval(timewindowMs: number): Timewindow {
const timewindow: Timewindow = {
selectedTab: TimewindowType.HISTORY,
history: {
historyType: HistoryWindowType.LAST_INTERVAL,
timewindowMs
}
};

10
ui-ngx/src/styles.scss

@ -42,6 +42,7 @@ body {
margin: 0;
padding: 0;
background-color: rgb(250,250,250);
overflow: hidden;
}
tb-root {
@ -968,4 +969,13 @@ mat-label {
height: 100%;
}
}
.tb-tooltip-multiline {
max-width: 400px;
height: auto !important;
padding-top: 6px;
padding-bottom: 6px;
line-height: 1.5;
white-space: pre-line;
}
}

Loading…
Cancel
Save