Browse Source

Merge remote-tracking branch 'upstream/develop/3.5.2' into feature/test-rulenode-script-function-with-selected-event

pull/8836/head
rusikv 3 years ago
parent
commit
1d0e89bd3f
  1. 8
      application/src/main/data/json/system/widget_bundles/alarm_widgets.json
  2. 14
      application/src/main/data/json/system/widget_bundles/charts.json
  3. 7
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  4. 47
      application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
  5. 4
      common/data/src/main/java/org/thingsboard/server/common/data/SaveDeviceWithCredentialsRequest.java
  6. 4
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  7. 1
      ui-ngx/angular.json
  8. 7
      ui-ngx/src/app/core/http/device.service.ts
  9. 37
      ui-ngx/src/app/core/utils.ts
  10. 4
      ui-ngx/src/app/modules/common/modules-map.ts
  11. 6
      ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html
  12. 19
      ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts
  13. 23
      ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-select-panel.component.ts
  14. 12
      ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-select.component.html
  15. 55
      ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-select.component.ts
  16. 23
      ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss
  17. 68
      ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html
  18. 70
      ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts
  19. 2
      ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.html
  20. 17
      ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html
  21. 16
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  22. 15
      ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html
  23. 10
      ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.scss
  24. 7
      ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.html
  25. 17
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.html
  26. 23
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.scss
  27. 48
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts
  28. 5
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html
  29. 6
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.scss
  30. 17
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts
  31. 107
      ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html
  32. 160
      ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts
  33. 14
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  34. 18
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html
  35. 7
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.ts
  36. 30
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html
  37. 26
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts
  38. 19
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html
  39. 18
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts
  40. 122
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html
  41. 168
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts
  42. 14
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html
  43. 27
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss
  44. 11
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts
  45. 28
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html
  46. 55
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss
  47. 14
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts
  48. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.html
  49. 6
      ui-ngx/src/app/modules/home/components/widget/config/data-key-config-dialog.component.html
  50. 36
      ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html
  51. 107
      ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.scss
  52. 2
      ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.ts
  53. 1
      ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html
  54. 2
      ui-ngx/src/app/modules/home/components/widget/config/datasource.component.ts
  55. 23
      ui-ngx/src/app/modules/home/components/widget/config/datasources.component.html
  56. 24
      ui-ngx/src/app/modules/home/components/widget/config/datasources.component.ts
  57. 17
      ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.html
  58. 11
      ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.ts
  59. 3
      ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts
  60. 15
      ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts
  61. 19
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html
  62. 13
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.html
  63. 13
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.html
  64. 96
      ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-key-settings.component.html
  65. 150
      ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.html
  66. 24
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-key-settings.component.html
  67. 48
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-widget-settings.component.html
  68. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-label.component.ts
  69. 6
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/simple-card-widget-settings.component.html
  70. 16
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-key-settings.component.html
  71. 22
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-latest-key-settings.component.html
  72. 42
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html
  73. 297
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html
  74. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.ts
  75. 43
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.html
  76. 72
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html
  77. 66
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss
  78. 15
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts
  79. 496
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html
  80. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.ts
  81. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/label-data-key.component.ts
  82. 56
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.html
  83. 57
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.ts
  84. 50
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html
  85. 14
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.ts
  86. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.ts
  87. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/gauge-highlight.component.ts
  88. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts
  89. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.ts
  90. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.ts
  91. 119
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  92. 2
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  93. 1
      ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html
  94. 4
      ui-ngx/src/app/modules/home/components/widget/widget-preview.component.ts
  95. 143
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html
  96. 51
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss
  97. 319
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts
  98. 5
      ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
  99. 8
      ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.ts
  100. 3
      ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.ts

8
application/src/main/data/json/system/widget_bundles/alarm_widgets.json

@ -3,7 +3,9 @@
"alias": "alarm_widgets",
"title": "Alarm widgets",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAOPElEQVR42u2deVsTVx+G/Xb9ALVeffuHtmql2kVrVWytWltr64KoqIgbolRFRMQNtCwioiLIqii7grIoShARwnbee3Js3pgibzYQzPNcXLmGyUwmc+ae35kE7jOzjDHDw8MvFCVCGRoaAqpZlqqxsTGjKGEHkMAJqGaJKmUy2HLAUlsokY0DVk9PjxpCiWyASmApAksRWIrAEliKwFI+DLD4pst3gbKyss7OTjWcEi5Yubm5X3zxxejoqP01ISGhtLRUDaeEC9ZPP/30xx9/UKj8wBoYGLh9+3Z5ebllrrW1taOj49atW263u6mpqaWl5ebNm/39/c+ePbt+/bq3yD1//vzatWsNDQ1q+qgG6/Hjx+vWrauvr9+8ebMvWPyVccWKFZmZmXv37k1MTGT+rl27li1bduLECfD67LPPjh07lpyc/N133+3YsSMrK2vhwoUvX77s7u5mzvnz59evX3/lyhW1fvSCBR95eXlMAERvb68XLMoSzLW3t9+9e3fx4sUWrJKSEiYAKzY21q4+b948ChsT27Ztq6mpoZKtXLmSFYHs0aNHav0oBYs+jquruLi4PXv2fPPNN1QaL1ivX7/++eefjx49mpaWFhMTY8G6c+eOBWv16tX2FebPn287SupWZWUlE3SdW7duXbVqFVVQrR+lYAEQPWCnJw8ePKCn84IFJfBh+8pFixYFCFZtbS0XXvxaUVFBDVPrRylYf/75p/ea3V7FNzY2WrC4Kl++fDld3u+///7pp59CTyBg0ZlydbVp0yYY9X1lJRo/FU4Qe/0UbAYHB71fXigCS1EEliKwFIGlKAJLEViKwFIUgaUILEVgKYrAUgSWIrAURWApAksRWIoisBSBpXz4YO3evXvleEHameJ3WVBQoEP14YAFQ0HNn7xM/RZHRkaCWp4RLt7j1qMOLLyd77///qOPPlq6dOmpU6dmBFjV1dWYkrzt1NTUwNc6c+YMCi7jXIQv4m7YsIH9xRB++vQptrDfC+JjFhUVqWI5WbBgAboYLYLyigN98ODBnJwchDCU1ydPnvT19WEvopG5XC6/FfGt/bZoBxCf1Ozfv/+vv/7CI2JkAEb/PXLkCO8TrRIpnIYCHexIhp9g5qVLl3DdEHoZZODcuXNYcXPnzv31119Pnz798OHDGzduWDU8qKAwffzxx69evUIc5xVQyeGMRkPDZAKLk+3SnnV1dbxJe7lCecO941lWiUawkKcZwYEDk5KSQnNw2VRYWMhR2bdvH3MOHTrEkfNdC4eR1gQ77xZpbpZn/qS2CEeRQVC+/vpryi2jnvz444/FxcWIkOCSnZ3NBOOaoFJyFBmi4t69exRj9o63SqnjbGEt9vHw4cPIlZw2IbyBq1evfvXVV7wUkO3cuZMXbGtr4zWTkpIOHDgA0zxCGJqn8QxiANw4w7wf63VGHVhM/Pbbb19++SWNAliczRwJ5lCrOOE44/99wgEfvjWaK1u0VE3BUCKXL19mKGnomTNnDiQx9gnvLT8/n5n0j2vXrmX8nNmzZzOTksZ7pkQZz+AUXrA4hehM6ctC2DrbZR+plCDFJixYbJ2CRO2kNFqwQJZhCsw/o2Pgl9OMdAVRChb7D1i2gHOQOC/Bi2PDYaDCc2D+vS6tDE9scWqoMp6BBRiogi1u374dpildHFR7gYj8zTFmgpJGwaAHZzwmX7BYzDYOJ0xo15T0a7/88gutQXVvbm5OT0/nBRkcyl72bdmyhSsKOlwuvBhWg01/8sknjOFD1WQV2zlG3adCPjfR2XGRa8ECKe8nKS5oJvgcRN1ii1M87JHvxdy4n/je9TGQFbno5g1T2CKydTvtuzk7x6/RpuDqM2Jg8X3VuN9jMSZWCFuiaFHbbVtQpWj9wNdl/KMZ9EmboZ0Y9UTfY+mbd0VgKQJLEVgCSxFYisBSBJaiCCxlRoDFXzF143UlsgEqVSxFXaEisBSBJbAUgaUILEVgKYrAUgSWIrD8M30Ue+WDAmv6KPbTKSNmbNLviee1JDAD8N6CXZ07vX/gYCFB4KOiK/GI/BmpN42CPIlN0pRsbi14M125xtRufuvZzlzTM7nCMboi5hl3IcXIOHv2bAh+M45hVFQsa95ZICAM/47GQn1GRqXhsFXxQnkP9LDcvhUP2C6JnFnjCQtwO9aMjAy6Zqxz5EScO3xo5hiPeIi7zKuxOiIQE2zCaoyhg1U027xsMO4eU/wfB6zhl6Z2m6neYPrbTFehcVWb/sfm3iZzP84M95vWDHNvc6RoQ1XFSbRF6+LFixYs7EUkVSsqImTTSjQCFj86K24c06iqqNKsgi3NNI5hFIGFvLtx40YGQUD1ZKwLWoQ/gNtGRNFk2lpl69atwyjn9r4suWbNmlxPcOiQNquqqhDevWekfQQjjE2W5JDQprwswOGZhQVWfaKpTzCt6c4EYIERhaphv2lIMi1HTcffpiLWmfnkvGlJNTUbTddVp4uM0PFAp/b+asFir2kNWg+J3LYDbcgjd3oHph9++IGhHDC2UTVh0SqvUQQWDUR5t2cYYFF7qDSccxYRwKLYGI9DTJMhGbMkjQhVLE8FYj53leaW0l6kLJQWLDuHtRhuhBU5m8MC60mOKVvm/DwvdcDqLDA1Gxyk6ve+Aaskxgy5nPnQBli9dRG8ukK89qtYGNgFnrS3t9t2AB1aiUFWaBkMafssbcUwE1HXFdJSmOmMlnHhwoVxwUJsT/IE69cuSctasBgNgTOSzpFlWJ5SzzlKj0CRYzwML1gM0QG7LBbWWEKA1Z5jWk+ZugTzvNwBq7vEIenOcqfLs2B15pmKFQ55/a2RBctWKXYNm55RGyxY6OPsFDuL8m/b4dtvv7VgsTy/UqqxzBlAhaEl6DSp31H3qfBdZrqtWL7P+i3pa5SPeGLG88ontvXDuPbxvJnhAefSqqvozcfDSQv77nc/9omHI/A+y/XANBm0beoU+4lDSecyYrp/1eB+YVrPvIFMCRksRRFYisBSBJaiCCxFYCkCS1EEljKdwJJir0ixV9QVKgJLDaEILEVgKQJLUQSWIrAUgeUXmdDKpIAV1Sb02/9y7vzq+7/kE9zDDQs5HKvRE5nQ/ycz0oTu7jbcHHX1ahTH/8HETTrz8kxdncGvev3aJCW9c3V8obqwpB2Z0IFmhpnQKSkmJ8eZ4IalbW0mPt6sXeuAVVho0GUXLjS4ZYcPO8whleAc44Bw7+dNm5wf7mAdHlgyoYMGa8aY0Bs3Gt8iMWcOHZLJzXWAKyhwkHrxwixdyj3BTUKC89TZs6a21sGO3fz77zDBkgkdNFgzxoQGHTAimZlOKZo715n+N1gXL5rUVKduUdWYmZhotmyh0oYJlkzooMGaMSY0RWjJEqeP4wdufMGi15s3z+EGsFwu51KMprhwgf7JmV60yNBbhX2NJRM6lMwYE3pwcPz5brff/vhPRCIyoSOWmWFCKxEBS1EEliKwFIGlKAJLEViKwFIUgaVMJ7BkQisyoRV1hYrAUkMoAksRWIrAUhSBpQgsRWD5RSa0MilgTXMTmv/v9k5b99d3zlRmZHTkvex1VIMVlONmzacA17KOio21dOycMD3pQ3eTXwz0lHXeyWzI4tftZfFjZmxiqi42Z0f2qKAhlZeX+81EbRoYGEAe7OrqElhvIYJKiquE1YTShMrMI9OoWjyOC5Z1ne0jRhRd8PHjx9GbUDpxUex8BClec/HixXbJoqIiPGnMuxyPzWzt4aCS+zCv+llNen3G/qoDPYM9KfdSe929p+szTj5I63W/LOkoOV13pvb5/cyGc8fvnyxoLRweGb788Epnf+e5pgssXNlVNWrGeDajPjO7JSeEQ8If2rgIsdoqAzewv7jg9fX1S5YswZxDm6PF7CnE/iLG4cnRArhfUQpWbW0terilh7EuUlJSsOFoEY496rNdBj72eTJ//nzztvGMIc0j6qbb7T5y5Ah9H/NdLldcXBzz8en8lme7HIBUnNIg0+hqutScDSKZjVnFT24WtBV29HdUP7sLK9cfF4NLRVely+3aVbFn1IxuLY0bHB3cU7mvsacp7cGpvqE+pmu677Lu4Mjg9rIdIRySkydPMuYFxiXmUnJyMpIqEjnnHnhhYmI/AxnWIY4hDcUOcgrBWXp6epSCxamWn5/PBDIqFwrx8fGHPKG93tUVWtfZFxc0c1uHaFzmdHZ2ckL7LWMfaXE8fY5KsC3iHnFvK4271JINTFtKtz3qbb3RfutMQxb9XeHja4D1sPdRn7vvQNVBFo6/s9MLVlbjebrFXeV76EZzH+XbZ0M4JCjzEMObZ6AA6hZ4UZ4pSL5gARMNiLXLApQxNOjm5uboAouOyRYhKjxIQRJnJPMxnlGcORHp494FFhix4ueff/4usJhGfabysRXvMrGxsdQzLkToO0JrFKpR3Yv6V0Ov1havh5XKp5V7q/YlViXR5QUCFs/urkg8cT8thIqFzG3bh3OPRgCdzZ6wv3R8aZ4wTQ8YExPDYkzTqrQSlxZR/akwWE93OADD2O81WYU5IBvW0CBvZ3RsNPCF6QRvdty63VF6rPZ4BD5y/qN9O9XUz8O2722SboM9eWBNpQkd2cAWVL3H5qbglbTfHhgZ0PdYiiKwFIGlCCxFEViKwFIElqIILGWagyUTWpEJragrVASWGkIRWIrAUgSWoggsRWApAssvMqGVSQFLJnSAkQn9HsCSCR1CZEIHB5ZM6EAiEzo4sGRCBxiZ0MGBJRM6wMiEDigyoYOKTOhQP0DJhA6+xWRCT5fIhJ7WYCmKwFIEliKwFEVgKdMMLL5hUkMokQ1QOWDNlL+ZKzMi4OSANTQ0JLaUyFLFl4izjOcbxe7ubv709lRRwggIAZL9avq/0p2LbK71A+cAAAAASUVORK5CYII=",
"description": "Visualization of alarms for devices, assets and other entities."
"description": "Visualization of alarms for devices, assets and other entities.",
"externalId": null,
"name": "Alarm widgets"
},
"widgetTypes": [
{
@ -23,7 +25,9 @@
"dataKeySettingsSchema": "",
"settingsDirective": "tb-alarms-table-widget-settings",
"dataKeySettingsDirective": "tb-alarms-table-key-settings",
"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,\"allowAcknowledgment\":true,\"allowClear\":true,\"allowAssign\":true,\"displayActivity\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418},{\"name\":\"assignee\",\"type\":\"alarm\",\"label\":\"Assignee\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.5008441077416634}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false}"
"hasBasicMode": true,
"basicModeDirective": "tb-alarms-table-basic-config",
"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,\"allowAcknowledgment\":true,\"allowClear\":true,\"allowAssign\":true,\"displayActivity\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\",\"enableSelectColumnDisplay\":true,\"enableStickyAction\":false,\"enableFilter\":true,\"entitiesTitle\":null,\"alarmsTitle\":\"Alarms\"},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418},{\"name\":\"assignee\",\"type\":\"alarm\",\"label\":\"Assignee\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.5008441077416634}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5,\"showTitleIcon\":false,\"titleIcon\":\"warning\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{},\"alarmStatusList\":[],\"alarmSeverityList\":[],\"alarmTypeList\":[],\"searchPropagatedAlarms\":false,\"configMode\":\"basic\",\"alarmFilterConfig\":null}"
}
}
]

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

@ -161,7 +161,9 @@
"settingsDirective": "tb-flot-line-widget-settings",
"dataKeySettingsDirective": "tb-flot-line-key-settings",
"latestDataKeySettingsDirective": "tb-flot-latest-key-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":false,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":0,\"max\":1.2,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"shadowSize\":4,\"smoothLines\":false,\"comparisonEnabled\":false,\"timeForComparison\":\"previousInterval\",\"comparisonCustomIntervalValue\":7200000,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"right\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false,\"dataKeysListForLabels\":[]},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
"hasBasicMode": true,
"basicModeDirective": "tb-flot-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":false,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":0,\"max\":1.2,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"shadowSize\":4,\"smoothLines\":false,\"comparisonEnabled\":false,\"timeForComparison\":\"previousInterval\",\"comparisonCustomIntervalValue\":7200000,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"right\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false,\"dataKeysListForLabels\":[]},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false},\"configMode\":\"basic\",\"showTitleIcon\":false,\"titleIcon\":\"waterfall_chart\",\"iconColor\":\"#1F6BDD\"}"
}
},
{
@ -183,7 +185,9 @@
"settingsDirective": "tb-flot-line-widget-settings",
"dataKeySettingsDirective": "tb-flot-line-key-settings",
"latestDataKeySettingsDirective": "tb-flot-latest-key-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"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;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"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\":\"8px\",\"settings\":{\"stack\":false,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":null,\"max\":null,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"\"},\"shadowSize\":4,\"smoothLines\":false,\"comparisonEnabled\":false,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"bottom\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false},\"title\":\"Timeseries Line Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"
"hasBasicMode": true,
"basicModeDirective": "tb-flot-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"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;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"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\":\"8px\",\"settings\":{\"stack\":false,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":null,\"max\":null,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"\"},\"shadowSize\":4,\"smoothLines\":false,\"comparisonEnabled\":false,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"bottom\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false},\"title\":\"Timeseries Line Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\"}"
}
},
{
@ -204,8 +208,10 @@
"settingsDirective": "tb-flot-bar-widget-settings",
"dataKeySettingsDirective": "tb-flot-bar-key-settings",
"latestDataKeySettingsDirective": "tb-flot-latest-key-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":true,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":null,\"max\":null,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"\"},\"defaultBarWidth\":600,\"barAlignment\":\"left\",\"comparisonEnabled\":false,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"bottom\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false},\"title\":\"Timeseries Bar Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}"
"hasBasicMode": true,
"basicModeDirective": "tb-flot-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"stack\":true,\"fontSize\":10,\"fontColor\":\"#545454\",\"showTooltip\":true,\"tooltipIndividual\":false,\"tooltipCumulative\":false,\"hideZeros\":false,\"grid\":{\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1,\"color\":\"#545454\",\"backgroundColor\":null,\"tickColor\":\"#DDDDDD\"},\"xaxis\":{\"title\":null,\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"min\":null,\"max\":null,\"title\":null,\"showLabels\":true,\"color\":\"#545454\",\"tickSize\":null,\"tickDecimals\":0,\"ticksFormatter\":\"\"},\"defaultBarWidth\":600,\"barAlignment\":\"left\",\"comparisonEnabled\":false,\"xaxisSecond\":{\"axisPosition\":\"top\",\"title\":null,\"showLabels\":true},\"showLegend\":true,\"legendConfig\":{\"direction\":\"column\",\"position\":\"bottom\",\"sortDataKeys\":false,\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false,\"showLatest\":false},\"customLegendEnabled\":false},\"title\":\"Timeseries Bar Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"configMode\":\"basic\",\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"#1F6BDD\"}"
}
}
]
}
}

7
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -75,6 +75,7 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import javax.annotation.Nullable;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@ -210,9 +211,9 @@ public class DeviceController extends BaseController {
@RequestMapping(value = "/device-with-credentials", method = RequestMethod.POST)
@ResponseBody
public Device saveDeviceWithCredentials(@ApiParam(value = "The JSON object with device and credentials. See method description above for example.")
@RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException {
Device device = checkNotNull(deviceAndCredentials.getDevice());
DeviceCredentials credentials = checkNotNull(deviceAndCredentials.getCredentials());
@Valid @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException {
Device device = deviceAndCredentials.getDevice();
DeviceCredentials credentials = deviceAndCredentials.getCredentials();
device.setTenantId(getCurrentUser().getTenantId());
checkEntity(device.getId(), device, Resource.DEVICE);
return tbDeviceService.saveDeviceWithCredentials(device, credentials, getCurrentUser());

47
application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java

@ -244,6 +244,53 @@ public class DeviceControllerTest extends AbstractControllerTest {
testNotificationUpdateGatewayOneTime(savedDevice, oldDevice);
}
@Test
public void testSaveDeviceWithCredentials_CredentialsIsNull() throws Exception {
Device device = new Device();
device.setName("My device");
device.setType("default");
SaveDeviceWithCredentialsRequest saveRequest = new SaveDeviceWithCredentialsRequest(device, null);
doPost("/api/device-with-credentials", saveRequest).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Validation error: credentials must not be null")));
}
@Test
public void testSaveDeviceWithCredentials_DeviceIsNull() throws Exception {
String testToken = "TEST_TOKEN";
DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId(testToken);
SaveDeviceWithCredentialsRequest saveRequest = new SaveDeviceWithCredentialsRequest(null, deviceCredentials);
doPost("/api/device-with-credentials", saveRequest).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Validation error: device must not be null")));
}
@Test
public void testSaveDeviceWithCredentials_WithExistingName() throws Exception {
String testToken = "TEST_TOKEN";
Device device = new Device();
device.setName("My device");
device.setType("default");
DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId(testToken);
SaveDeviceWithCredentialsRequest saveRequest = new SaveDeviceWithCredentialsRequest(device, deviceCredentials);
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
Device savedDevice = readResponse(doPost("/api/device-with-credentials", saveRequest).andExpect(status().isOk()), Device.class);
Assert.assertNotNull(savedDevice);
doPost("/api/device-with-credentials", saveRequest).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device with such name already exists!")));
}
@Test
public void saveDeviceWithViolationOfValidation() throws Exception {
Device device = new Device();

4
common/data/src/main/java/org/thingsboard/server/common/data/SaveDeviceWithCredentialsRequest.java

@ -20,13 +20,17 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import javax.validation.constraints.NotNull;
@ApiModel
@Data
public class SaveDeviceWithCredentialsRequest {
@ApiModelProperty(position = 1, value = "The JSON with device entity.", required = true)
@NotNull
private final Device device;
@ApiModelProperty(position = 2, value = "The JSON with credentials entity.", required = true)
@NotNull
private final DeviceCredentials credentials;
}

4
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -174,10 +174,6 @@ public class DeviceServiceImpl extends AbstractCachedEntityService<DeviceCacheKe
@Transactional
@Override
public Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials) {
if (device.getId() == null) {
Device deviceWithName = this.findDeviceByTenantIdAndName(device.getTenantId(), device.getName());
device = deviceWithName == null ? device : deviceWithName.updateDevice(device);
}
Device savedDevice = this.saveDeviceWithoutCredentials(device, true);
deviceCredentials.setDeviceId(savedDevice.getId());
if (device.getId() == null) {

1
ui-ngx/angular.json

@ -82,6 +82,7 @@
],
"styles": [
"src/styles.scss",
"src/form.scss",
"node_modules/jquery.terminal/css/jquery.terminal.min.css",
"node_modules/tooltipster/dist/css/tooltipster.bundle.min.css",
"node_modules/tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css",

7
ui-ngx/src/app/core/http/device.service.ts

@ -87,6 +87,13 @@ export class DeviceService {
return this.http.post<Device>('/api/device', device, defaultHttpOptionsFromConfig(config));
}
public saveDeviceWithCredentials(device: Device, credentials: DeviceCredentials, config?: RequestConfig): Observable<Device> {
return this.http.post<Device>('/api/device-with-credentials', {
device,
credentials
}, defaultHttpOptionsFromConfig(config));
}
public deleteDevice(deviceId: string, config?: RequestConfig) {
return this.http.delete(`/api/device/${deviceId}`, defaultHttpOptionsFromConfig(config));
}

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

@ -20,7 +20,7 @@ import { finalize, share } from 'rxjs/operators';
import { Datasource, DatasourceData, FormattedData, ReplaceInfo } from '@app/shared/models/widget.models';
import { EntityId } from '@shared/models/id/entity-id';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { EntityType, baseDetailsPageByEntityType } from '@shared/models/entity-type.models';
import { baseDetailsPageByEntityType, EntityType } from '@shared/models/entity-type.models';
import { HttpErrorResponse } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';
import { serverErrorCodesTranslations } from '@shared/models/constants';
@ -126,15 +126,6 @@ export function isString(value: any): boolean {
return typeof value === 'string';
}
export function isEmpty(obj: any): boolean {
for (const key of Object.keys(obj)) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
return false;
}
}
return true;
}
export function isLiteralObject(value: any) {
return (!!value) && (value.constructor === Object);
}
@ -320,9 +311,29 @@ export function extractType<T extends object>(target: any, keysOfProps: (keyof T
return _.pick(target, keysOfProps);
}
export function isEqual(a: any, b: any): boolean {
return _.isEqual(a, b);
}
export const isEqual = (a: any, b: any): boolean => _.isEqual(a, b);
export const isEmpty = (a: any): boolean => _.isEmpty(a);
export const isEqualIgnoreUndefined = (a: any, b: any): boolean => {
if (a === b) {
return true;
}
if (isDefinedAndNotNull(a) && isDefinedAndNotNull(b)) {
return isEqual(a, b);
} else {
return (isUndefinedOrNull(a) || !a) && (isUndefinedOrNull(b) || !b);
}
};
export const isArraysEqualIgnoreUndefined = (a: any[], b: any[]): boolean => {
const res = isEqualIgnoreUndefined(a, b);
if (!res) {
return (isUndefinedOrNull(a) || !a?.length) && (isUndefinedOrNull(b) || !b?.length);
} else {
return res;
}
};
export function mergeDeep<T>(target: T, ...sources: T[]): T {
return _.merge(target, ...sources);

4
ui-ngx/src/app/modules/common/modules-map.ts

@ -179,6 +179,8 @@ import * as ProtobufContentComponent from '@shared/components/protobuf-content.c
import * as SlackConversationAutocompleteComponent from '@shared/components/slack-conversation-autocomplete.component';
import * as StringItemsListComponent from '@shared/components/string-items-list.component';
import * as ToggleHeaderComponent from '@shared/components/toggle-header.component';
import * as ToggleSelectComponent from '@shared/components/toggle-select.component';
import * as UnitInputComponent from '@shared/components/unit-input.component';
import * as AddEntityDialogComponent from '@home/components/entity/add-entity-dialog.component';
import * as EntitiesTableComponent from '@home/components/entity/entities-table.component';
@ -478,6 +480,8 @@ class ModulesMap implements IModulesMap {
'@shared/components/slack-conversation-autocomplete.component': SlackConversationAutocompleteComponent,
'@shared/components/string-items-list.component': StringItemsListComponent,
'@shared/components/toggle-header.component': ToggleHeaderComponent,
'@shared/components/toggle-select.component': ToggleSelectComponent,
'@shared/components/unit-input.component': UnitInputComponent,
'@home/components/entity/add-entity-dialog.component': AddEntityDialogComponent,
'@home/components/entity/entities-table.component': EntitiesTableComponent,

6
ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.html

@ -26,10 +26,14 @@
#userAutocomplete="matAutocomplete"
[displayWith]="displayUserFn"
(optionSelected)="selected($event)">
<mat-option [fxHide]="!assigneeId" [value]="null">
<mat-option *ngIf="displayAssigneeNotSet" [value]="assigneeOptions.noAssignee">
<mat-icon class="unassigned-icon">account_circle</mat-icon>
<span>{{ assigneeNotSetText | translate }}</span>
</mat-option>
<mat-option *ngIf="displayAssignedToCurrentUser" [value]="assigneeOptions.currentUser">
<mat-icon class="unassigned-icon">account_circle</mat-icon>
<span>{{ assignedToCurrentUserText | translate }}</span>
</mat-option>
<mat-option *ngFor="let user of filteredUsers | async" [value]="user"
[fxHide]="assigneeId === user.id.id">
<span class="user-avatar" [innerHTML]="getUserInitials(user)"

19
ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts

@ -44,6 +44,7 @@ import { AlarmService } from '@core/http/alarm.service';
import { OverlayRef } from '@angular/cdk/overlay';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { UtilsService } from '@core/services/utils.service';
import { AlarmAssigneeOption } from '@shared/models/alarm.models';
export const ALARM_ASSIGNEE_PANEL_DATA = new InjectionToken<any>('AlarmAssigneePanelData');
@ -59,15 +60,19 @@ export interface AlarmAssigneePanelData {
})
export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDestroy {
assigneeOptions = AlarmAssigneeOption;
private dirty = false;
alarmId: string;
assigneeId?: string;
assigneeOption?: AlarmAssigneeOption = null;
assigneeNotSetText = 'alarm.unassigned';
assignedToCurrentUserText = '';
reassigned: boolean = false;
reassigned = false;
selectUserFormGroup: FormGroup;
@ -77,6 +82,14 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe
searchText = '';
get displayAssigneeNotSet(): boolean {
return !!this.assigneeId;
}
get displayAssignedToCurrentUser(): boolean {
return false;
}
private destroy$ = new Subject<void>();
constructor(@Inject(ALARM_ASSIGNEE_PANEL_DATA) public data: AlarmAssigneePanelData,
@ -124,8 +137,8 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe
selected(event: MatAutocompleteSelectedEvent): void {
this.clear();
const user: User = event.option.value;
if (user) {
if (event.option.value?.id) {
const user: User = event.option.value;
this.assign(user);
} else {
this.unassign();

23
ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-select-panel.component.ts

@ -36,11 +36,14 @@ import { emptyPageData } from '@shared/models/page/page-data';
import { OverlayRef } from '@angular/cdk/overlay';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { UtilsService } from '@core/services/utils.service';
import { AlarmAssigneeOption } from '@shared/models/alarm.models';
export const ALARM_ASSIGNEE_SELECT_PANEL_DATA = new InjectionToken<any>('AlarmAssigneeSelectPanelData');
export interface AlarmAssigneeSelectPanelData {
assigneeId?: string;
assigneeOption?: AlarmAssigneeOption;
userMode?: boolean;
}
@Component({
@ -50,11 +53,15 @@ export interface AlarmAssigneeSelectPanelData {
})
export class AlarmAssigneeSelectPanelComponent implements OnInit, AfterViewInit, OnDestroy {
assigneeOptions = AlarmAssigneeOption;
private dirty = false;
assigneeId?: string;
assigneeOption?: AlarmAssigneeOption;
assigneeNotSetText = 'alarm.assignee-not-set';
assignedToCurrentUserText = this.data.userMode ? 'alarm.assigned-to-me' : 'alarm.assigned-to-current-user';
selectUserFormGroup: FormGroup;
@ -67,6 +74,15 @@ export class AlarmAssigneeSelectPanelComponent implements OnInit, AfterViewInit
userSelected = false;
result?: UserEmailInfo;
optionResult?: AlarmAssigneeOption;
get displayAssigneeNotSet(): boolean {
return this.assigneeOption !== AlarmAssigneeOption.noAssignee;
}
get displayAssignedToCurrentUser(): boolean {
return this.assigneeOption !== AlarmAssigneeOption.currentUser;
}
private destroy$ = new Subject<void>();
@ -77,6 +93,7 @@ export class AlarmAssigneeSelectPanelComponent implements OnInit, AfterViewInit
private fb: FormBuilder,
private utilsService: UtilsService) {
this.assigneeId = data.assigneeId;
this.assigneeOption = data.assigneeOption;
this.selectUserFormGroup = this.fb.group({
user: [null]
});
@ -112,7 +129,11 @@ export class AlarmAssigneeSelectPanelComponent implements OnInit, AfterViewInit
selected(event: MatAutocompleteSelectedEvent): void {
this.clear();
this.userSelected = true;
this.result = event.option.value;
if (event.option.value?.id) {
this.result = event.option.value;
} else {
this.optionResult = event.option.value;
}
this.overlayRef.dispose();
}

12
ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-select.component.html

@ -16,16 +16,16 @@
-->
<ng-container [formGroup]="assigneeFormGroup">
<mat-form-field fxFlex class="mat-block" style="margin-bottom: 25px"
<mat-form-field [ngStyle]="inline ? {width: '100%'} : {marginBottom: '25px'}"
(click)="openAlarmAssigneeSelectPanel($event)"
subscriptSizing="dynamic">
<mat-label translate>alarm.assignee</mat-label>
subscriptSizing="dynamic" [appearance]="inline ? 'outline' : 'fill'">
<mat-label *ngIf="!inline" translate>alarm.assignee</mat-label>
<input matInput readonly formControlName="assignee">
<span *ngIf="assignee" matPrefix class="user-avatar"
<span *ngIf="assignee" matPrefix class="user-avatar" [ngClass]="{'inline': inline}"
[style.backgroundColor]="getAvatarBgColor()">
{{ getUserInitials() }}
</span>
<mat-icon *ngIf="!assignee" matPrefix class="unassigned-icon">account_circle</mat-icon>
<mat-icon *ngIf="!disabled" matSuffix>arrow_drop_down</mat-icon>
<mat-icon *ngIf="!assignee" matPrefix class="unassigned-icon" [ngClass]="{'inline': inline}">account_circle</mat-icon>
<mat-icon *ngIf="!disabled" matSuffix class="drop-down-icon" [ngClass]="{'inline': inline}">arrow_drop_down</mat-icon>
</mat-form-field>
</ng-container>

55
ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-select.component.ts

@ -30,6 +30,8 @@ import {
AlarmAssigneeSelectPanelComponent,
AlarmAssigneeSelectPanelData
} from '@home/components/alarm/alarm-assignee-select-panel.component';
import { coerceBoolean } from '@shared/decorators/coercion';
import { AlarmAssigneeOption } from '@shared/models/alarm.models';
@Component({
selector: 'tb-alarm-assignee-select',
@ -47,8 +49,17 @@ export class AlarmAssigneeSelectComponent implements OnInit, ControlValueAccesso
@Input() disabled: boolean;
@coerceBoolean()
@Input()
inline = false;
@coerceBoolean()
@Input()
userMode = false;
assigneeFormGroup: UntypedFormGroup;
assignee?: User | UserEmailInfo;
assigneeOption?: AlarmAssigneeOption;
private propagateChange = (_: any) => {};
@ -82,7 +93,15 @@ export class AlarmAssigneeSelectComponent implements OnInit, ControlValueAccesso
}
}
writeValue(userId?: UserId): void {
writeValue(value?: UserId | AlarmAssigneeOption): void {
let userId: UserId;
if (value && (value as UserId).id) {
userId = value as UserId;
this.assigneeOption = null;
} else {
userId = null;
this.assigneeOption = value ? value as AlarmAssigneeOption : AlarmAssigneeOption.noAssignee;
}
const userObservable = userId ? this.userService.getUser(userId.id, {ignoreErrors: true}).pipe(
catchError(() => of(null))
) : of(null);
@ -92,15 +111,31 @@ export class AlarmAssigneeSelectComponent implements OnInit, ControlValueAccesso
}),
map((user) => this.getAssignee(user))
).subscribe((assignee) => {
this.assigneeFormGroup.get('assignee').patchValue(assignee, {emitEvent: false});
if (assignee) {
this.assigneeFormGroup.get('assignee').patchValue(assignee, {emitEvent: false});
} else {
if (!this.assigneeOption) {
this.assigneeOption = AlarmAssigneeOption.noAssignee;
}
assignee = this.getAssigneeOption(this.assigneeOption);
this.assigneeFormGroup.get('assignee').patchValue(assignee, {emitEvent: false});
}
});
}
private getAssignee(user?: User| UserEmailInfo): string {
private getAssignee(user?: User| UserEmailInfo): string | null {
if (user) {
return this.getUserDisplayName(user);
} else {
return null;
}
}
private getAssigneeOption(assigneeOption: AlarmAssigneeOption): string {
if (assigneeOption === AlarmAssigneeOption.noAssignee) {
return this.translateService.instant('alarm.assignee-not-set');
} else {
return this.translateService.instant(this.userMode ? 'alarm.assigned-to-me' : 'alarm.assigned-to-current-user');
}
}
@ -169,7 +204,9 @@ export class AlarmAssigneeSelectComponent implements OnInit, ControlValueAccesso
{
provide: ALARM_ASSIGNEE_SELECT_PANEL_DATA,
useValue: {
assigneeId: this.assignee?.id?.id
assigneeId: this.assignee?.id?.id,
assigneeOption: this.assigneeOption,
userMode: this.userMode
} as AlarmAssigneeSelectPanelData
},
{
@ -183,8 +220,14 @@ export class AlarmAssigneeSelectComponent implements OnInit, ControlValueAccesso
component.onDestroy(() => {
if (component.instance.userSelected) {
this.assignee = component.instance.result;
this.assigneeFormGroup.get('assignee').patchValue(this.getAssignee(this.assignee), {emitEvent: false});
this.propagateChange(this.assignee?.id);
this.assigneeOption = component.instance.optionResult;
if (this.assignee) {
this.assigneeFormGroup.get('assignee').patchValue(this.getAssignee(this.assignee), {emitEvent: false});
this.propagateChange(this.assignee?.id);
} else if (this.assigneeOption) {
this.assigneeFormGroup.get('assignee').patchValue(this.getAssigneeOption(this.assigneeOption), {emitEvent: false});
this.propagateChange(this.assigneeOption);
}
}
});
}

23
ui-ngx/src/app/modules/home/components/alarm/alarm-assignee.component.scss

@ -19,20 +19,14 @@
justify-content: center;
align-items: center;
border-radius: 50%;
width: 28px;
height: 28px;
min-width: 28px;
min-height: 28px;
color: white;
font-size: 13px;
font-weight: 700;
margin-left: 12px;
margin-right: 20px;
}
.unassigned-icon {
width: 28px;
height: 28px;
font-size: 28px;
color: rgba(0, 0, 0, 0.38);
overflow: visible;
@ -40,3 +34,20 @@
margin-right: 20px;
padding: 0;
}
.user-avatar, .unassigned-icon {
width: 28px;
height: 28px;
margin-left: 12px;
margin-right: 20px;
&.inline {
margin-left: 0;
margin-right: 8px;
}
}
.drop-down-icon {
&.inline {
margin-right: -12px;
}
}

68
ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html

@ -33,6 +33,13 @@
<form fxLayout="column" class="mat-content mat-padding" (ngSubmit)="update()">
<ng-container *ngTemplateOutlet="alarmFilter"></ng-container>
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
<button type="button"
mat-button
color="primary"
(click)="reset()">
{{ 'action.reset' | translate }}
</button>
<span fxFlex></span>
<button type="button"
mat-button
(click)="cancel()">
@ -48,32 +55,26 @@
</form>
</ng-template>
<ng-template #alarmFilter>
<div fxLayout="column" fxLayoutAlign="center" [formGroup]="alarmFilterConfigForm">
<div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start start"
fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-status-list</mat-label>
<mat-select formControlName="statusList" multiple
placeholder="{{ !alarmFilterConfigForm.get('statusList').value?.length ? ('alarm.any-status' | translate) : '' }}">
<mat-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus">
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-severity-list</mat-label>
<mat-select formControlName="severityList" multiple
placeholder="{{ !alarmFilterConfigForm.get('severityList').value?.length ? ('alarm.any-severity' | translate) : '' }}">
<mat-option *ngFor="let alarmSeverity of alarmSeverities" [value]="alarmSeverity">
{{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<div class="tb-form-panel no-padding no-border" [formGroup]="alarmFilterConfigForm">
<div class="tb-form-row space-between">
<div class="fixed-title-width" translate>alarm.alarm-status-list</div>
<mat-chip-listbox multiple formControlName="statusList">
<mat-chip-option *ngFor="let searchStatus of alarmSearchStatuses" [value]="searchStatus">
{{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }}
</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" translate>alarm.alarm-severity-list</div>
<mat-chip-listbox multiple formControlName="severityList">
<mat-chip-option *ngFor="let alarmSeverity of alarmSeverities" [value]="alarmSeverity">
{{ alarmSeverityTranslationMap.get(alarmSeverityEnum[alarmSeverity]) | translate }}
</mat-chip-option>
</mat-chip-listbox>
</div>
<div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start start"
fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-label translate>alarm.alarm-type-list</mat-label>
<div class="tb-form-row">
<div class="fixed-title-width" translate>alarm.alarm-type-list</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic" class="tb-chips">
<mat-chip-grid #alarmTypeChipList formControlName="typeList">
<mat-chip-row *ngFor="let type of alarmTypeList()"
[removable]="true" (removed)="removeAlarmType(type)">
@ -88,16 +89,15 @@
</mat-chip-grid>
</mat-form-field>
</div>
<div fxLayout="column" fxLayoutGap="16px">
<mat-slide-toggle *ngIf="propagatedFilter" formControlName="searchPropagatedAlarms">
{{ 'alarm.search-propagated-alarms' | translate }}
</mat-slide-toggle>
<mat-slide-toggle formControlName="assignedToCurrentUser">
{{ (userMode ? 'alarm.assigned-to-me' : 'alarm.assigned-to-current-user') | translate }}
</mat-slide-toggle>
<tb-alarm-assignee-select
*ngIf="!alarmFilterConfigForm.get('assignedToCurrentUser').value" formControlName="assigneeId">
<div class="tb-form-row">
<div class="fixed-title-width" translate>alarm.assignee</div>
<tb-alarm-assignee-select fxFlex inline="true"
[userMode]="userMode"
formControlName="assigneeId">
</tb-alarm-assignee-select>
</div>
<mat-slide-toggle class="mat-slide" *ngIf="propagatedFilter" formControlName="searchPropagatedAlarms">
{{ 'alarm.search-propagated-alarms' | translate }}
</mat-slide-toggle>
</div>
</ng-template>

70
ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.ts

@ -29,11 +29,12 @@ import {
ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { AlarmFilterConfig } from '@shared/models/query/query.models';
import { AlarmFilterConfig, alarmFilterConfigEquals } from '@shared/models/query/query.models';
import { coerceBoolean } from '@shared/decorators/coercion';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import {
AlarmAssigneeOption,
AlarmSearchStatus,
alarmSearchStatusTranslations,
AlarmSeverity,
@ -42,6 +43,7 @@ import {
import { MatChipInputEvent } from '@angular/material/chips';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { TranslateService } from '@ngx-translate/core';
import { deepClone } from '@core/utils';
export const ALARM_FILTER_CONFIG_DATA = new InjectionToken<any>('AlarmFilterConfigData');
@ -49,6 +51,7 @@ export interface AlarmFilterConfigData {
panelMode: boolean;
userMode: boolean;
alarmFilterConfig: AlarmFilterConfig;
initialAlarmFilterConfig?: AlarmFilterConfig;
}
// @dynamic
@ -83,6 +86,9 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
@Input()
propagatedFilter = true;
@Input()
initialAlarmFilterConfig: AlarmFilterConfig;
panelMode = false;
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
@ -127,18 +133,20 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
this.panelMode = this.data.panelMode;
this.userMode = this.data.userMode;
this.alarmFilterConfig = this.data.alarmFilterConfig;
this.initialAlarmFilterConfig = this.data.initialAlarmFilterConfig;
if (this.panelMode && !this.initialAlarmFilterConfig) {
this.initialAlarmFilterConfig = deepClone(this.alarmFilterConfig);
}
}
this.alarmFilterConfigForm = this.fb.group({
statusList: [null, []],
severityList: [null, []],
typeList: [null, []],
searchPropagatedAlarms: [false, []],
assignedToCurrentUser: [false, []],
assigneeId: [null, []]
assigneeId: [AlarmAssigneeOption.noAssignee, []]
});
this.alarmFilterConfigForm.valueChanges.subscribe(
() => {
this.updateValidators();
if (!this.buttonMode) {
this.alarmConfigUpdated(this.alarmFilterConfigForm.value);
}
@ -165,26 +173,18 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
this.alarmFilterConfigForm.disable({emitEvent: false});
} else {
this.alarmFilterConfigForm.enable({emitEvent: false});
this.updateValidators();
}
}
writeValue(alarmFilterConfig?: AlarmFilterConfig): void {
this.alarmFilterConfig = alarmFilterConfig;
if (!this.initialAlarmFilterConfig && alarmFilterConfig) {
this.initialAlarmFilterConfig = deepClone(alarmFilterConfig);
}
this.updateButtonDisplayValue();
this.updateAlarmConfigForm(alarmFilterConfig);
}
private updateValidators() {
const assignedToCurrentUser = this.alarmFilterConfigForm.get('assignedToCurrentUser').value;
if (assignedToCurrentUser) {
this.alarmFilterConfigForm.get('assigneeId').disable({emitEvent: false});
} else {
this.alarmFilterConfigForm.get('assigneeId').enable({emitEvent: false});
}
this.alarmFilterConfigForm.get('assigneeId').updateValueAndValidity({emitEvent: false});
}
toggleAlarmFilterPanel($event: Event) {
if ($event) {
$event.stopPropagation();
@ -226,6 +226,7 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
update() {
this.alarmConfigUpdated(this.alarmFilterConfigForm.value);
this.alarmFilterConfigForm.markAsPristine();
if (this.panelMode) {
this.panelResult = this.alarmFilterConfig;
}
@ -236,6 +237,25 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
}
}
reset() {
if (this.initialAlarmFilterConfig) {
if (this.buttonMode || this.panelMode) {
const alarmFilterConfig = this.alarmFilterConfigFromFormValue(this.alarmFilterConfigForm.value);
if (!alarmFilterConfigEquals(alarmFilterConfig, this.initialAlarmFilterConfig)) {
this.updateAlarmConfigForm(this.initialAlarmFilterConfig);
this.alarmFilterConfigForm.markAsDirty();
}
} else {
if (!alarmFilterConfigEquals(this.alarmFilterConfig, this.initialAlarmFilterConfig)) {
this.alarmFilterConfig = this.initialAlarmFilterConfig;
this.updateButtonDisplayValue();
this.updateAlarmConfigForm(this.alarmFilterConfig);
this.propagateChange(this.alarmFilterConfig);
}
}
}
}
public alarmTypeList(): string[] {
return this.alarmFilterConfigForm.get('typeList').value;
}
@ -276,18 +296,28 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
severityList: alarmFilterConfig?.severityList,
typeList: alarmFilterConfig?.typeList,
searchPropagatedAlarms: alarmFilterConfig?.searchPropagatedAlarms,
assignedToCurrentUser: alarmFilterConfig?.assignedToCurrentUser,
assigneeId: alarmFilterConfig?.assigneeId
assigneeId: alarmFilterConfig?.assignedToCurrentUser ? AlarmAssigneeOption.currentUser :
(alarmFilterConfig?.assigneeId ? alarmFilterConfig?.assigneeId : AlarmAssigneeOption.noAssignee)
}, {emitEvent: false});
this.updateValidators();
}
private alarmConfigUpdated(alarmFilterConfig: AlarmFilterConfig) {
this.alarmFilterConfig = alarmFilterConfig;
private alarmConfigUpdated(formValue: any) {
this.alarmFilterConfig = this.alarmFilterConfigFromFormValue(formValue);
this.updateButtonDisplayValue();
this.propagateChange(this.alarmFilterConfig);
}
private alarmFilterConfigFromFormValue(formValue: any): AlarmFilterConfig {
return {
statusList: formValue.statusList,
severityList: formValue.severityList,
typeList: formValue.typeList,
searchPropagatedAlarms: formValue.searchPropagatedAlarms,
assignedToCurrentUser: formValue.assigneeId === AlarmAssigneeOption.currentUser,
assigneeId: formValue.assigneeId?.id ? formValue.assigneeId : null
};
}
private updateButtonDisplayValue() {
if (this.buttonMode) {
const filterTextParts: string[] = [];

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

@ -42,7 +42,7 @@
#entityAliasAutocomplete="matAutocomplete"
[displayWith]="displayEntityAliasFn">
<mat-option *ngFor="let entityAlias of filteredEntityAliases | async" [value]="entityAlias">
<span [innerHTML]="entityAlias.alias | highlight:searchText"></span>
<span [innerHTML]="entityAlias.alias | highlight:searchText:true:'ig'"></span>
</mat-option>
<mat-option *ngIf="!(filteredEntityAliases | async)?.length" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">

17
ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html

@ -20,18 +20,10 @@
<h2 translate>widget.add</h2>
<span fxFlex>: {{data.widgetInfo.widgetName}}</span>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<tb-toggle-header *ngIf="hasBasicMode" (valueChange)="widgetConfigMode = $event" ignoreMdLgSize="true"
appearance="fill-invert" [options]="[
{
name: translate.instant('widget.basic-mode'),
value: 'basic'
},
{
name: translate.instant('widget.advanced-mode'),
value: 'advanced'
}
]" [value]="widgetConfigMode" name="widgetConfigModeHeader" useSelectOnMdLg="false">
</tb-toggle-header>
<tb-toggle-select *ngIf="hasBasicMode" appearance="fill-invert" [(ngModel)]="widgetConfigMode" [ngModelOptions]="{standalone: true}">
<tb-toggle-option value="basic">{{ 'widget.basic-mode' | translate }}</tb-toggle-option>
<tb-toggle-option value="advanced">{{ 'widget.advanced-mode' | translate }}</tb-toggle-option>
</tb-toggle-select>
<div [tb-help]="helpLinkIdForWidgetType()"></div>
</div>
<button mat-icon-button
@ -57,6 +49,7 @@
<tb-widget-preview *ngIf="previewMode" class="tb-absolute-fill"
[aliasController]="aliasController"
[stateController]="stateController"
[dashboardTimewindow]="dashboard.configuration.timewindow"
[widget]="widget"
[widgetConfig]="widgetFormGroup.get('widgetConfig').value.config">
<div class="tb-preview-panel-content">

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

@ -360,18 +360,10 @@
[isReadOnly]="true"
(closeDetails)="onEditWidgetClosed()">
<div class="details-buttons" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<tb-toggle-header *ngIf="tbEditWidget.hasBasicMode" (valueChange)="tbEditWidget.widgetConfigMode = $event" ignoreMdLgSize="true"
appearance="fill-invert" [options]="[
{
name: translate.instant('widget.basic-mode'),
value: 'basic'
},
{
name: translate.instant('widget.advanced-mode'),
value: 'advanced'
}
]" [value]="tbEditWidget.widgetConfigMode" name="widgetConfigModeHeader" useSelectOnMdLg="false">
</tb-toggle-header>
<tb-toggle-select *ngIf="tbEditWidget.hasBasicMode" appearance="fill-invert" [(ngModel)]="tbEditWidget.widgetConfigMode">
<tb-toggle-option value="basic">{{ 'widget.basic-mode' | translate }}</tb-toggle-option>
<tb-toggle-option value="advanced">{{ 'widget.advanced-mode' | translate }}</tb-toggle-option>
</tb-toggle-select>
<div [tb-help]="helpLinkIdForWidgetType()"></div>
</div>
<tb-edit-widget #tbEditWidget

15
ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html

@ -54,11 +54,14 @@
</button>
</div>
</tb-widget-config>
<tb-widget-preview *ngIf="previewMode" class="widget-preview-section"
[aliasController]="aliasController"
[stateController]="stateController"
[widget]="widget"
[widgetConfig]="widgetFormGroup.get('widgetConfig').value.config">
</tb-widget-preview>
<div *ngIf="previewMode" class="widget-preview-background">
<tb-widget-preview class="widget-preview-section"
[aliasController]="aliasController"
[stateController]="stateController"
[dashboardTimewindow]="dashboard.configuration.timewindow"
[widget]="widget"
[widgetConfig]="widgetFormGroup.get('widgetConfig').value.config">
</tb-widget-preview>
</div>
</fieldset>
</form>

10
ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.scss

@ -14,9 +14,17 @@
* limitations under the License.
*/
:host {
.widget-preview-section {
.widget-preview-background {
position: absolute;
top: 72px;
left: 0;
right: 0;
bottom: 0;
background: #fff;
}
.widget-preview-section {
position: absolute;
top: 0;
left: 16px;
right: 16px;
bottom: 16px;

7
ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.html

@ -26,13 +26,14 @@
matTooltip="{{ 'device.generate-client-id' | translate }}"
matTooltipPosition="above"
(click)="generate('clientId')"
*ngIf="!deviceCredentialsMqttFormGroup.get('clientId').value; else copyClientId">
*ngIf="!deviceCredentialsMqttFormGroup.get('clientId').value && !disabled; else copyClientId">
<mat-icon>autorenew</mat-icon>
</button>
<ng-template #copyClientId>
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="deviceCredentialsMqttFormGroup.get('clientId').value"
[copyText]="deviceCredentialsMqttFormGroup.get('clientId').value"
tooltipText="{{ 'device.copy-client-id' | translate }}"
tooltipPosition="above"
@ -53,7 +54,7 @@
matTooltip="{{ 'device.generate-user-name' | translate }}"
matTooltipPosition="above"
(click)="generate('userName')"
*ngIf="!deviceCredentialsMqttFormGroup.get('userName').value; else copyUserName">
*ngIf="!deviceCredentialsMqttFormGroup.get('userName').value && !disabled; else copyUserName">
<mat-icon>autorenew</mat-icon>
</button>
<ng-template #copyUserName>
@ -85,7 +86,7 @@
matTooltip="{{ 'device.generate-password' | translate }}"
matTooltipPosition="above"
(click)="generate('password')"
*ngIf="!deviceCredentialsMqttFormGroup.get('password').value; else copyPassword">
*ngIf="!deviceCredentialsMqttFormGroup.get('password').value && !disabled; else copyPassword">
<mat-icon>autorenew</mat-icon>
</button>
<ng-template #copyPassword>

17
ui-ngx/src/app/modules/home/components/device/device-credentials.component.html

@ -16,14 +16,14 @@
-->
<section [formGroup]="deviceCredentialsFormGroup">
<mat-form-field class="mat-block" [fxShow]="credentialsTypes?.length > 1">
<mat-label translate>device.credentials-type</mat-label>
<mat-select formControlName="credentialsType">
<mat-option *ngFor="let credentialsType of credentialsTypes" [value]="credentialsType">
<section [fxShow]="credentialsTypes?.length > 1" style="margin-bottom: 16px">
<div class="tb-type-title" translate>device.credentials-type</div>
<tb-toggle-select formControlName="credentialsType" appearance="fill">
<tb-toggle-option *ngFor="let credentialsType of credentialsTypes" [value]="credentialsType">
{{ credentialTypeNamesMap.get(credentialsType) }}
</mat-option>
</mat-select>
</mat-form-field>
</tb-toggle-option>
</tb-toggle-select>
</section>
<div [ngSwitch]="deviceCredentialsFormGroup.get('credentialsType').value">
<ng-template [ngSwitchCase]="deviceCredentialsType.ACCESS_TOKEN">
<mat-form-field class="mat-block">
@ -36,13 +36,14 @@
matTooltip="{{ 'device.generate-access-token' | translate }}"
matTooltipPosition="above"
(click)="generate('credentialsId')"
*ngIf="!deviceCredentialsFormGroup.get('credentialsId').value; else copyAccessToken">
*ngIf="!deviceCredentialsFormGroup.get('credentialsId').value && !disabled; else copyAccessToken">
<mat-icon>autorenew</mat-icon>
</button>
<ng-template #copyAccessToken>
<tb-copy-button
matSuffix
miniButton="false"
*ngIf="deviceCredentialsFormGroup.get('credentialsId').value"
[copyText]="deviceCredentialsFormGroup.get('credentialsId').value"
tooltipText="{{ 'device.copy-access-token' | translate }}"
tooltipPosition="above"

23
ui-ngx/src/app/modules/home/components/device/device-credentials.component.scss

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host{
.tb-type-title {
font-size: 12px;
line-height: 16px;
letter-spacing: 0.25px;
margin-bottom: 8px;
}
}

48
ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts

@ -17,9 +17,9 @@
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import {
ControlValueAccessor,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator,
@ -35,6 +35,7 @@ import {
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { generateSecret, isDefinedAndNotNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-device-credentials',
@ -50,7 +51,7 @@ import { generateSecret, isDefinedAndNotNull } from '@core/utils';
useExisting: forwardRef(() => DeviceCredentialsComponent),
multi: true,
}],
styleUrls: []
styleUrls: ['./device-credentials.component.scss']
})
export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, Validator, OnDestroy {
@ -73,9 +74,13 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
}
}
@Input()
@coerceBoolean()
initAccessToken = false;
private destroy$ = new Subject<void>();
deviceCredentialsFormGroup: UntypedFormGroup;
deviceCredentialsFormGroup: FormGroup;
deviceCredentialsType = DeviceCredentialsType;
@ -83,9 +88,10 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
credentialTypeNamesMap = credentialTypeNames;
private propagateChange = (v: any) => {};
private propagateChange = null;
private propagateChangePending = false;
constructor(public fb: UntypedFormBuilder) {
constructor(public fb: FormBuilder) {
this.deviceCredentialsFormGroup = this.fb.group({
credentialsType: [DeviceCredentialsType.ACCESS_TOKEN],
credentialsId: [null],
@ -98,8 +104,8 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
});
this.deviceCredentialsFormGroup.get('credentialsType').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(() => {
this.credentialsTypeChanged();
).subscribe((value) => {
this.credentialsTypeChanged(value);
});
}
@ -107,6 +113,10 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
if (this.disabled) {
this.deviceCredentialsFormGroup.disable({emitEvent: false});
}
if (this.initAccessToken && !this.deviceCredentialsFormGroup.get('credentialsId').value &&
this.deviceCredentialsFormGroup.get('credentialsType').value === DeviceCredentialsType.ACCESS_TOKEN) {
this.deviceCredentialsFormGroup.get('credentialsId').patchValue(generateSecret(20));
}
}
ngOnDestroy() {
@ -128,11 +138,21 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
updateView() {
const deviceCredentialsValue = this.deviceCredentialsFormGroup.value;
this.propagateChange(deviceCredentialsValue);
if (this.propagateChange) {
this.propagateChange(deviceCredentialsValue);
} else {
this.propagateChangePending = true;
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
if (this.propagateChangePending) {
this.propagateChangePending = false;
setTimeout(() => {
this.updateView();
}, 0);
}
}
registerOnTouched(fn: any): void {}
@ -144,11 +164,10 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
} else {
this.deviceCredentialsFormGroup.enable({emitEvent: false});
this.updateValidators();
this.deviceCredentialsFormGroup.updateValueAndValidity();
}
}
public validate(c: UntypedFormControl) {
public validate(c: FormControl) {
return this.deviceCredentialsFormGroup.valid ? null : {
deviceCredentials: {
valid: false,
@ -156,12 +175,15 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit,
};
}
credentialsTypeChanged(): void {
credentialsTypeChanged(type: DeviceCredentialsType): void {
this.deviceCredentialsFormGroup.patchValue({
credentialsId: null,
credentialsValue: null
});
this.updateValidators();
if (type === DeviceCredentialsType.ACCESS_TOKEN && this.initAccessToken) {
this.deviceCredentialsFormGroup.get('credentialsId').patchValue(generateSecret(20));
}
}
updateValidators(): void {

5
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html

@ -43,6 +43,11 @@
(click)="editDeviceProfile($event)">
<mat-icon class="material-icons">edit</mat-icon>
</button>
<button mat-button color="primary" matSuffix
(click)="createDeviceProfile($event, '')"
*ngIf="!selectDeviceProfileFormGroup.get('deviceProfile').value && !disabled && addNewProfile && showCreateNewButton">
<span style="white-space: nowrap">{{ 'notification.create-new' | translate }}</span>
</button>
<mat-autocomplete
class="tb-autocomplete"
(closed)="onPanelClosed()"

6
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.scss

@ -19,3 +19,9 @@
color: inherit;
}
}
:host ::ng-deep {
.mat-mdc-form-field-icon-suffix {
display: flex;
}
}

17
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts

@ -49,6 +49,7 @@ import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './a
import { emptyPageData } from '@shared/models/page/page-data';
import { getEntityDetailsPageURL } from '@core/utils';
import { SubscriptSizing } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-device-profile-autocomplete',
@ -70,6 +71,7 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
subscriptSizing: SubscriptSizing = 'fixed';
@Input()
@coerceBoolean()
selectDefaultProfile = false;
@Input()
@ -79,25 +81,26 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
displayAllOnEmpty = false;
@Input()
@coerceBoolean()
editProfileEnabled = true;
@Input()
@coerceBoolean()
addNewProfile = true;
@Input()
@coerceBoolean()
showCreateNewButton = false;
@Input()
showDetailsPageLink = false;
@Input()
transportType: DeviceTransportType = null;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@coerceBoolean()
required = false;
@Input()
disabled: boolean;

107
ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.html

@ -0,0 +1,107 @@
<!--
Copyright © 2016-2023 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ng-container [formGroup]="alarmsTableWidgetConfigForm">
<tb-timewindow-config-panel formControlName="timewindowConfig">
</tb-timewindow-config-panel>
<div class="tb-form-panel">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div class="tb-form-panel-title" translate>alarm.filter</div>
<button mat-button color="primary"
(click)="alarmFilterConfig.reset()">
{{ 'action.reset' | translate }}
</button>
</div>
<tb-alarm-filter-config #alarmFilterConfig buttonMode="false" formControlName="alarmFilterConfig"
[initialAlarmFilterConfig]="{}"></tb-alarm-filter-config>
</div>
<tb-datasources
[configMode]="basicMode"
hideDataKeys
formControlName="datasources">
</tb-datasources>
<tb-data-keys-panel
panelTitle="{{ 'widgets.table.columns' | translate }}"
addKeyTitle="{{ 'widgets.table.add-column' | translate }}"
keySettingsTitle="{{ 'widgets.table.column-settings' | translate }}"
removeKeyTitle="{{ 'widgets.table.remove-column' | translate }}"
noKeysText="{{ 'widgets.table.no-columns' | translate }}"
hideDataKeyColor
hideUnits
hideDecimals
[datasourceType]="alarmSource?.type"
[deviceId]="alarmSource?.deviceId"
[entityAliasId]="alarmSource?.entityAliasId"
formControlName="columns">
</tb-data-keys-panel>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.card-title' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between same-padding">
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
{{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<tb-material-icon-select asBoxInput
[color]="alarmsTableWidgetConfigForm.get('iconColor').value"
formControlName="titleIcon">
</tb-material-icon-select>
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="iconColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="search">{{ 'action.search' | translate }}</mat-chip-option>
<mat-chip-option value="filter">{{ 'alarm.alarm-filter' | translate }}</mat-chip-option>
<mat-chip-option value="columnsToDisplay">{{ 'widgets.table.columns-to-display' | translate }}</mat-chip-option>
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.text-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.background-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="backgroundColor">
</tb-color-input>
</div>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

160
ui-ngx/src/app/modules/home/components/widget/config/basic/alarm/alarms-table-basic-config.component.ts

@ -0,0 +1,160 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { DataKey, Datasource, WidgetConfig } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { isUndefined } from '@core/utils';
import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component';
@Component({
selector: 'tb-alarms-table-basic-config',
templateUrl: './alarms-table-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class AlarmsTableBasicConfigComponent extends BasicWidgetConfigComponent {
public get alarmSource(): Datasource {
const datasources: Datasource[] = this.alarmsTableWidgetConfigForm.get('datasources').value;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
alarmsTableWidgetConfigForm: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.alarmsTableWidgetConfigForm;
}
protected onConfigSet(configData: WidgetConfigComponentData) {
this.alarmsTableWidgetConfigForm = this.fb.group({
timewindowConfig: [getTimewindowConfig(configData.config), []],
alarmFilterConfig: [configData.config.alarmFilterConfig, []],
datasources: [[configData.config.alarmSource], []],
columns: [this.getColumns(configData.config.alarmSource), []],
showTitle: [configData.config.showTitle, []],
title: [configData.config.settings?.alarmsTitle, []],
showTitleIcon: [configData.config.showTitleIcon, []],
titleIcon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []],
cardButtons: [this.getCardButtons(configData.config), []],
color: [configData.config.color, []],
backgroundColor: [configData.config.backgroundColor, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
this.widgetConfig.config.useDashboardTimewindow = config.timewindowConfig.useDashboardTimewindow;
this.widgetConfig.config.displayTimewindow = config.timewindowConfig.displayTimewindow;
this.widgetConfig.config.timewindow = config.timewindowConfig.timewindow;
this.widgetConfig.config.alarmFilterConfig = config.alarmFilterConfig;
this.widgetConfig.config.alarmSource = config.datasources[0];
this.setColumns(config.columns, this.widgetConfig.config.alarmSource);
this.widgetConfig.config.actions = config.actions;
this.widgetConfig.config.showTitle = config.showTitle;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.alarmsTitle = config.title;
this.widgetConfig.config.showTitleIcon = config.showTitleIcon;
this.widgetConfig.config.titleIcon = config.titleIcon;
this.widgetConfig.config.iconColor = config.iconColor;
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.color = config.color;
this.widgetConfig.config.backgroundColor = config.backgroundColor;
return this.widgetConfig;
}
protected validatorTriggers(): string[] {
return ['showTitle', 'showTitleIcon'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.alarmsTableWidgetConfigForm.get('showTitle').value;
const showTitleIcon: boolean = this.alarmsTableWidgetConfigForm.get('showTitleIcon').value;
if (showTitle) {
this.alarmsTableWidgetConfigForm.get('title').enable();
this.alarmsTableWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false});
if (showTitleIcon) {
this.alarmsTableWidgetConfigForm.get('titleIcon').enable();
this.alarmsTableWidgetConfigForm.get('iconColor').enable();
} else {
this.alarmsTableWidgetConfigForm.get('titleIcon').disable();
this.alarmsTableWidgetConfigForm.get('iconColor').disable();
}
} else {
this.alarmsTableWidgetConfigForm.get('title').disable();
this.alarmsTableWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false});
this.alarmsTableWidgetConfigForm.get('titleIcon').disable();
this.alarmsTableWidgetConfigForm.get('iconColor').disable();
}
this.alarmsTableWidgetConfigForm.get('title').updateValueAndValidity({emitEvent});
this.alarmsTableWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false});
this.alarmsTableWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent});
this.alarmsTableWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent});
}
private getColumns(alarmSource?: Datasource): DataKey[] {
if (alarmSource) {
return alarmSource.dataKeys || [];
}
return [];
}
private setColumns(columns: DataKey[], alarmSource?: Datasource) {
if (alarmSource) {
alarmSource.dataKeys = columns;
}
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.settings?.enableSearch) || config.settings?.enableSearch) {
buttons.push('search');
}
if (isUndefined(config.settings?.enableFilter) || config.settings?.enableFilter) {
buttons.push('filter');
}
if (isUndefined(config.settings?.enableSelectColumnDisplay) || config.settings?.enableSelectColumnDisplay) {
buttons.push('columnsToDisplay');
}
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.settings.enableSearch = buttons.includes('search');
config.settings.enableFilter = buttons.includes('filter');
config.settings.enableSelectColumnDisplay = buttons.includes('columnsToDisplay');
config.enableFullscreen = buttons.includes('fullscreen');
}
}

14
ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts

@ -33,6 +33,11 @@ import { DataKeyRowComponent } from '@home/components/widget/config/basic/common
import {
TimeseriesTableBasicConfigComponent
} from '@home/components/widget/config/basic/cards/timeseries-table-basic-config.component';
import { FlotBasicConfigComponent } from '@home/components/widget/config/basic/chart/flot-basic-config.component';
import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module';
import {
AlarmsTableBasicConfigComponent
} from '@home/components/widget/config/basic/alarm/alarms-table-basic-config.component';
@NgModule({
declarations: [
@ -40,12 +45,15 @@ import {
SimpleCardBasicConfigComponent,
EntitiesTableBasicConfigComponent,
TimeseriesTableBasicConfigComponent,
FlotBasicConfigComponent,
AlarmsTableBasicConfigComponent,
DataKeyRowComponent,
DataKeysPanelComponent
],
imports: [
CommonModule,
SharedModule,
WidgetSettingsModule,
WidgetConfigComponentsModule
],
exports: [
@ -53,6 +61,8 @@ import {
SimpleCardBasicConfigComponent,
EntitiesTableBasicConfigComponent,
TimeseriesTableBasicConfigComponent,
FlotBasicConfigComponent,
AlarmsTableBasicConfigComponent,
DataKeyRowComponent,
DataKeysPanelComponent
]
@ -63,5 +73,7 @@ export class BasicWidgetConfigModule {
export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetConfigComponent>} = {
'tb-simple-card-basic-config': SimpleCardBasicConfigComponent,
'tb-entities-table-basic-config': EntitiesTableBasicConfigComponent,
'tb-timeseries-table-basic-config': TimeseriesTableBasicConfigComponent
'tb-timeseries-table-basic-config': TimeseriesTableBasicConfigComponent,
'tb-flot-basic-config': FlotBasicConfigComponent,
'tb-alarms-table-basic-config': AlarmsTableBasicConfigComponent
};

18
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/entities-table-basic-config.component.html

@ -37,17 +37,17 @@
[entityAliasId]="datasource?.entityAliasId"
formControlName="columns">
</tb-data-keys-panel>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widget-config.appearance</div>
<div class="tb-widget-config-row">
<mat-slide-toggle class="mat-slide" formControlName="showTitle">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.card-title' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-row space-between same-padding">
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
{{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle>
@ -62,15 +62,15 @@
</tb-color-input>
</div>
</div>
<div class="tb-widget-config-row space-between">
<div translate>widgets.table.show-card-buttons</div>
<div class="tb-form-row space-between">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="search">{{ 'action.search' | translate }}</mat-chip-option>
<mat-chip-option value="columnsToDisplay">{{ 'widgets.table.columns-to-display' | translate }}</mat-chip-option>
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.text-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
@ -79,7 +79,7 @@
</tb-color-input>
</div>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.background-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>

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

@ -29,6 +29,7 @@ import {
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { isUndefined } from '@core/utils';
import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component';
@Component({
selector: 'tb-entities-table-basic-config',
@ -74,11 +75,7 @@ export class EntitiesTableBasicConfigComponent extends BasicWidgetConfigComponen
protected onConfigSet(configData: WidgetConfigComponentData) {
this.entitiesTableWidgetConfigForm = this.fb.group({
timewindowConfig: [{
useDashboardTimewindow: configData.config.useDashboardTimewindow,
displayTimewindow: configData.config.displayTimewindow,
timewindow: configData.config.timewindow
}, []],
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
columns: [this.getColumns(configData.config.datasources), []],
showTitle: [configData.config.showTitle, []],

30
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.html

@ -28,15 +28,15 @@
hideDataKeyDecimals
formControlName="datasources">
</tb-datasources>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widget-config.appearance</div>
<div class="tb-widget-config-row">
<div translate>widgets.simple-card.label</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widgets.simple-card.label</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div translate>widgets.simple-card.label-position</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="labelPosition">
@ -49,19 +49,25 @@
</mat-select>
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div translate>widget-config.units-short</div>
<tb-widget-units
<tb-unit-input
formControlName="units">
</tb-widget-units>
</tb-unit-input>
</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-row space-between">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.text-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
@ -70,7 +76,7 @@
</tb-color-input>
</div>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.background' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>

26
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/simple-card-basic-config.component.ts

@ -23,10 +23,12 @@ import { WidgetConfigComponentData } from '@home/models/widget-component.models'
import {
Datasource,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation,
datasourcesHasOnlyComparisonAggregation, WidgetConfig,
} from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component';
import { isUndefined } from '@core/utils';
@Component({
selector: 'tb-simple-card-basic-config',
@ -63,16 +65,13 @@ export class SimpleCardBasicConfigComponent extends BasicWidgetConfigComponent {
protected onConfigSet(configData: WidgetConfigComponentData) {
this.simpleCardWidgetConfigForm = this.fb.group({
timewindowConfig: [{
useDashboardTimewindow: configData.config.useDashboardTimewindow,
displayTimewindow: configData.config.useDashboardTimewindow,
timewindow: configData.config.timewindow
}, []],
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
label: [this.getDataKeyLabel(configData.config.datasources), []],
labelPosition: [configData.config.settings?.labelPosition, []],
units: [configData.config.units, []],
decimals: [configData.config.decimals, []],
cardButtons: [this.getCardButtons(configData.config), []],
color: [configData.config.color, []],
backgroundColor: [configData.config.backgroundColor, []],
actions: [configData.config.actions || {}, []]
@ -88,9 +87,10 @@ export class SimpleCardBasicConfigComponent extends BasicWidgetConfigComponent {
this.widgetConfig.config.actions = config.actions;
this.widgetConfig.config.units = config.units;
this.widgetConfig.config.decimals = config.decimals;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.color = config.color;
this.widgetConfig.config.backgroundColor = config.backgroundColor;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.labelPosition = config.labelPosition;
return this.widgetConfig;
}
@ -114,4 +114,16 @@ export class SimpleCardBasicConfigComponent extends BasicWidgetConfigComponent {
}
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.enableFullscreen = buttons.includes('fullscreen');
}
}

19
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.html

@ -21,6 +21,7 @@
<tb-datasources
[configMode]="basicMode"
hideDataKeys
forceSingleDatasource
formControlName="datasources">
</tb-datasources>
<tb-data-keys-panel
@ -36,17 +37,17 @@
[entityAliasId]="datasource?.entityAliasId"
formControlName="columns">
</tb-data-keys-panel>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widget-config.appearance</div>
<div class="tb-widget-config-row">
<mat-slide-toggle class="mat-slide" formControlName="showTitle">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.card-title' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-row space-between same-padding">
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
{{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle>
@ -61,15 +62,15 @@
</tb-color-input>
</div>
</div>
<div class="tb-widget-config-row space-between">
<div translate>widgets.table.show-card-buttons</div>
<div class="tb-form-row space-between">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="search">{{ 'action.search' | translate }}</mat-chip-option>
<mat-chip-option value="columnsToDisplay">{{ 'widgets.table.columns-to-display' | translate }}</mat-chip-option>
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.text-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
@ -78,7 +79,7 @@
</tb-color-input>
</div>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.background-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>

18
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/timeseries-table-basic-config.component.ts

@ -20,15 +20,11 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import {
DataKey,
Datasource,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation, WidgetConfig
} from '@shared/models/widget.models';
import { DataKey, Datasource, WidgetConfig } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { deepClone, isUndefined } from '@core/utils';
import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component';
@Component({
selector: 'tb-timeseries-table-basic-config',
@ -65,11 +61,7 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon
protected onConfigSet(configData: WidgetConfigComponentData) {
this.timeseriesTableWidgetConfigForm = this.fb.group({
timewindowConfig: [{
useDashboardTimewindow: configData.config.useDashboardTimewindow,
displayTimewindow: configData.config.displayTimewindow,
timewindow: configData.config.timewindow
}, []],
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
columns: [this.getColumns(configData.config.datasources), []],
showTitle: [configData.config.showTitle, []],
@ -92,11 +84,11 @@ export class TimeseriesTableBasicConfigComponent extends BasicWidgetConfigCompon
this.setColumns(config.columns, this.widgetConfig.config.datasources);
this.widgetConfig.config.actions = config.actions;
this.widgetConfig.config.showTitle = config.showTitle;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.entitiesTitle = config.title;
this.widgetConfig.config.title = config.title;
this.widgetConfig.config.showTitleIcon = config.showTitleIcon;
this.widgetConfig.config.titleIcon = config.titleIcon;
this.widgetConfig.config.iconColor = config.iconColor;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.color = config.color;
this.widgetConfig.config.backgroundColor = config.backgroundColor;

122
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.html

@ -0,0 +1,122 @@
<!--
Copyright © 2016-2023 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ng-container [formGroup]="flotWidgetConfigForm">
<tb-timewindow-config-panel formControlName="timewindowConfig">
</tb-timewindow-config-panel>
<tb-datasources
[configMode]="basicMode"
hideDataKeys
forceSingleDatasource
formControlName="datasources">
</tb-datasources>
<tb-data-keys-panel
panelTitle="{{ 'widgets.chart.series' | translate }}"
addKeyTitle="{{ 'widgets.chart.add-series' | translate }}"
keySettingsTitle="{{ 'widgets.chart.series-settings' | translate }}"
removeKeyTitle="{{ 'widgets.chart.remove-series' | translate }}"
noKeysText="{{ 'widgets.chart.no-series' | translate }}"
requiredKeysText="{{ 'widgets.chart.no-series-error' | translate }}"
hideSourceSelection
[datasourceType]="datasource?.type"
[deviceId]="datasource?.deviceId"
[entityAliasId]="datasource?.entityAliasId"
formControlName="series">
</tb-data-keys-panel>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.card-title' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between same-padding">
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
{{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<tb-material-icon-select asBoxInput
[color]="flotWidgetConfigForm.get('iconColor').value"
formControlName="titleIcon">
</tb-material-icon-select>
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="iconColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.text-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.background-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="backgroundColor">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.chart.chart-appearance</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="verticalLines">
{{ 'widgets.chart.vertical-grid-lines' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="horizontalLines">
{{ 'widgets.chart.horizontal-grid-lines' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="flotWidgetConfigForm.get('showLegend').value" [disabled]="!flotWidgetConfigForm.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showLegend" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widget-config.legend' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-legend-config formControlName="legendConfig">
</tb-legend-config>
</ng-template>
</mat-expansion-panel>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

168
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/flot-basic-config.component.ts

@ -0,0 +1,168 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { DataKey, Datasource, WidgetConfig } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { isUndefined } from '@core/utils';
import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component';
@Component({
selector: 'tb-flot-basic-config',
templateUrl: './flot-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class FlotBasicConfigComponent extends BasicWidgetConfigComponent {
public get datasource(): Datasource {
const datasources: Datasource[] = this.flotWidgetConfigForm.get('datasources').value;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
flotWidgetConfigForm: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.flotWidgetConfigForm;
}
protected setupDefaults(configData: WidgetConfigComponentData) {
this.setupDefaultDatasource(configData,
[{ name: 'temperature', label: 'Temperature', type: DataKeyType.timeseries, units: '°C', decimals: 0 }]);
}
protected onConfigSet(configData: WidgetConfigComponentData) {
this.flotWidgetConfigForm = this.fb.group({
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
series: [this.getSeries(configData.config.datasources), []],
showTitle: [configData.config.showTitle, []],
title: [configData.config.title, []],
showTitleIcon: [configData.config.showTitleIcon, []],
titleIcon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []],
cardButtons: [this.getCardButtons(configData.config), []],
color: [configData.config.color, []],
backgroundColor: [configData.config.backgroundColor, []],
verticalLines: [configData.config.settings?.grid?.verticalLines, []],
horizontalLines: [configData.config.settings?.grid?.horizontalLines, []],
showLegend: [configData.config.settings?.showLegend, []],
legendConfig: [configData.config.settings?.legendConfig, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
this.widgetConfig.config.useDashboardTimewindow = config.timewindowConfig.useDashboardTimewindow;
this.widgetConfig.config.displayTimewindow = config.timewindowConfig.displayTimewindow;
this.widgetConfig.config.timewindow = config.timewindowConfig.timewindow;
this.widgetConfig.config.datasources = config.datasources;
this.setSeries(config.series, this.widgetConfig.config.datasources);
this.widgetConfig.config.actions = config.actions;
this.widgetConfig.config.showTitle = config.showTitle;
this.widgetConfig.config.title = config.title;
this.widgetConfig.config.showTitleIcon = config.showTitleIcon;
this.widgetConfig.config.titleIcon = config.titleIcon;
this.widgetConfig.config.iconColor = config.iconColor;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.color = config.color;
this.widgetConfig.config.backgroundColor = config.backgroundColor;
this.widgetConfig.config.settings.grid = this.widgetConfig.config.settings.grid || {};
this.widgetConfig.config.settings.grid.verticalLines = config.verticalLines;
this.widgetConfig.config.settings.grid.horizontalLines = config.horizontalLines;
this.widgetConfig.config.settings.showLegend = config.showLegend;
this.widgetConfig.config.settings.legendConfig = config.legendConfig;
return this.widgetConfig;
}
protected validatorTriggers(): string[] {
return ['showTitle', 'showTitleIcon', 'showLegend'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.flotWidgetConfigForm.get('showTitle').value;
const showTitleIcon: boolean = this.flotWidgetConfigForm.get('showTitleIcon').value;
const showLegend: boolean = this.flotWidgetConfigForm.get('showLegend').value;
if (showTitle) {
this.flotWidgetConfigForm.get('title').enable();
this.flotWidgetConfigForm.get('showTitleIcon').enable({emitEvent: false});
if (showTitleIcon) {
this.flotWidgetConfigForm.get('titleIcon').enable();
this.flotWidgetConfigForm.get('iconColor').enable();
} else {
this.flotWidgetConfigForm.get('titleIcon').disable();
this.flotWidgetConfigForm.get('iconColor').disable();
}
} else {
this.flotWidgetConfigForm.get('title').disable();
this.flotWidgetConfigForm.get('showTitleIcon').disable({emitEvent: false});
this.flotWidgetConfigForm.get('titleIcon').disable();
this.flotWidgetConfigForm.get('iconColor').disable();
}
if (showLegend) {
this.flotWidgetConfigForm.get('legendConfig').enable();
} else {
this.flotWidgetConfigForm.get('legendConfig').disable();
}
this.flotWidgetConfigForm.get('title').updateValueAndValidity({emitEvent});
this.flotWidgetConfigForm.get('showTitleIcon').updateValueAndValidity({emitEvent: false});
this.flotWidgetConfigForm.get('titleIcon').updateValueAndValidity({emitEvent});
this.flotWidgetConfigForm.get('iconColor').updateValueAndValidity({emitEvent});
this.flotWidgetConfigForm.get('legendConfig').updateValueAndValidity({emitEvent});
}
private getSeries(datasources?: Datasource[]): DataKey[] {
if (datasources && datasources.length) {
return datasources[0].dataKeys || [];
}
return [];
}
private setSeries(series: DataKey[], datasources?: Datasource[]) {
if (datasources && datasources.length) {
datasources[0].dataKeys = series;
}
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.enableFullscreen = buttons.includes('fullscreen');
}
}

14
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<div [formGroup]="keyRowFormGroup" class="tb-data-key-row">
<div [formGroup]="keyRowFormGroup" class="tb-form-table-row tb-data-key-row">
<mat-form-field *ngIf="hasAdditionalLatestDataKeys" class="tb-inline-field tb-source-field" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="latest">
<mat-option [value]="false">{{ 'datakey.timeseries' | translate }}</mat-option>
@ -147,17 +147,17 @@
formControlName="color">
</tb-color-input>
</div>
<div class="tb-units-field">
<tb-widget-units *ngIf="displayUnitsOrDigits"
<div *ngIf="!hideUnits" class="tb-units-field">
<tb-unit-input *ngIf="displayUnitsOrDigits"
formControlName="units">
</tb-widget-units>
</tb-unit-input>
</div>
<div class="tb-decimals-field">
<mat-form-field *ngIf="displayUnitsOrDigits" appearance="outline" class="tb-inline-field center number" subscriptSizing="dynamic">
<div *ngIf="!hideDecimals" class="tb-decimals-field">
<mat-form-field *ngIf="displayUnitsOrDigits" appearance="outline" class="tb-inline-field number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-data-keys-table-row-buttons">
<div class="tb-form-table-row-cell-buttons">
<button type="button"
mat-icon-button
(click)="editKey(true)"

27
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss

@ -14,12 +14,6 @@
* limitations under the License.
*/
.tb-data-key-row {
height: 38px;
display: flex;
flex-direction: row;
gap: 12px;
padding-left: 12px;
.mat-mdc-form-field.tb-inline-field.tb-key-field {
.mat-mdc-text-field-wrapper:not(.mdc-text-field--outlined) {
.mat-mdc-form-field-infix {
@ -48,26 +42,17 @@
}
.tb-color-field, .tb-units-field, .tb-decimals-field {
width: 60px;
display: flex;
flex-direction: row;
place-content: center;
align-items: center;
}
}
.tb-data-keys-table-row-buttons {
display: flex;
flex-direction: row;
button.mat-mdc-icon-button.mat-mdc-button-base {
padding: 7px;
width: 38px;
height: 38px;
.mat-icon {
color: rgba(0, 0, 0, 0.38);
}
&.tb-hidden {
visibility: hidden;
}
.tb-units-field {
width: 80px;
}
.tb-color-field, .tb-decimals-field {
width: 60px;
}
}

11
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts

@ -145,6 +145,14 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
return this.dataKeysPanelComponent.hideDataKeyColor;
}
get hideUnits(): boolean {
return this.dataKeysPanelComponent.hideUnits;
}
get hideDecimals(): boolean {
return this.dataKeysPanelComponent.hideDecimals;
}
get widgetType(): widgetType {
return this.widgetConfigComponent.widgetType;
}
@ -154,8 +162,7 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
}
get hasAdditionalLatestDataKeys(): boolean {
return this.widgetConfigComponent.widgetType === widgetType.timeseries &&
this.widgetConfigComponent.modelValue?.typeParameters?.hasAdditionalLatestDataKeys;
return this.dataKeysPanelComponent.hasAdditionalLatestDataKeys;
}
get widget(): Widget {

28
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html

@ -15,24 +15,24 @@
limitations under the License.
-->
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title">{{ panelTitle }}</div>
<div class="tb-data-keys-table">
<div class="tb-data-keys-header">
<div *ngIf="hasAdditionalLatestDataKeys" class="tb-data-keys-header-cell tb-source-header" translate>datakey.source</div>
<div class="tb-data-keys-header-cell" fxFlex translate>datakey.key</div>
<div class="tb-data-keys-header-cell" fxFlex translate>datakey.label</div>
<div *ngIf="!hideDataKeyColor" class="tb-data-keys-header-cell tb-color-header" translate>datakey.color</div>
<div class="tb-data-keys-header-cell tb-units-header" translate>widget-config.units-short</div>
<div class="tb-data-keys-header-cell tb-decimals-header" translate>widget-config.decimals-short</div>
<div class="tb-data-keys-header-cell tb-actions-header"></div>
<div class="tb-form-panel">
<div class="tb-form-panel-title">{{ panelTitle }}</div>
<div class="tb-form-table">
<div class="tb-form-table-header">
<div *ngIf="hasAdditionalLatestDataKeys" class="tb-form-table-header-cell tb-source-header" translate>datakey.source</div>
<div class="tb-form-table-header-cell" fxFlex translate>datakey.key</div>
<div class="tb-form-table-header-cell" fxFlex translate>datakey.label</div>
<div *ngIf="!hideDataKeyColor" class="tb-form-table-header-cell tb-color-header" translate>datakey.color</div>
<div *ngIf="!hideUnits" class="tb-form-table-header-cell tb-units-header" translate>widget-config.units-short</div>
<div *ngIf="!hideDecimals" class="tb-form-table-header-cell tb-decimals-header" translate>widget-config.decimals-short</div>
<div class="tb-form-table-header-cell tb-actions-header"></div>
</div>
<div *ngIf="keysFormArray().controls.length; else noKeys" class="tb-data-keys-body tb-drop-list"
<div *ngIf="keysFormArray().controls.length; else noKeys" class="tb-form-table-body tb-drop-list"
cdkDropList cdkDropListOrientation="vertical"
[cdkDropListDisabled]="!dragEnabled"
(cdkDropListDropped)="keyDrop($event)">
<div cdkDrag [cdkDragDisabled]="!dragEnabled"
class="tb-data-keys-table-row tb-draggable"
class="tb-form-table-row tb-draggable"
*ngFor="let keyControl of keysFormArray().controls; trackBy: trackByKey; let $index = index;">
<tb-data-key-row fxFlex
[formControl]="keyControl"
@ -41,7 +41,7 @@
[entityAliasId]="entityAliasId"
(keyRemoved)="removeKey($index)">
</tb-data-key-row>
<div class="tb-data-keys-table-row-buttons">
<div class="tb-form-table-row-cell-buttons">
<button mat-icon-button
type="button"
cdkDragHandle

55
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss

@ -13,52 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-data-keys-table {
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 6px;
display: flex;
flex-direction: column;
gap: 12px;
padding-bottom: 12px;
.tb-data-keys-header {
height: 48px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
display: flex;
flex-direction: row;
place-content: center flex-start;
align-items: center;
gap: 12px;
padding-left: 12px;
.tb-data-keys-header-cell {
font-weight: 400;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.2px;
color: rgba(0, 0, 0, 0.54);
&.tb-source-header {
width: 140px;
}
&.tb-color-header, &.tb-units-header, &.tb-decimals-header {
width: 60px;
}
&.tb-actions-header {
width: 114px;
}
}
.tb-form-table-header-cell {
&.tb-source-header {
width: 140px;
}
.tb-data-keys-body {
display: flex;
flex-direction: column;
gap: 12px;
&.tb-units-header {
width: 80px;
}
.tb-prompt {
height: 38px;
&.tb-color-header, &.tb-decimals-header {
width: 60px;
}
&.tb-actions-header {
width: 114px;
}
}
.tb-data-keys-table-row {
height: 38px;
display: flex;
flex-direction: row;
background: #fff;
}

14
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts

@ -100,6 +100,18 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
@coerceBoolean()
hideDataKeyColor = false;
@Input()
@coerceBoolean()
hideUnits = false;
@Input()
@coerceBoolean()
hideDecimals = false;
@Input()
@coerceBoolean()
hideSourceSelection = false;
dataKeyType: DataKeyType;
alarmKeys: Array<DataKey>;
functionTypeKeys: Array<DataKey>;
@ -117,7 +129,7 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
}
get hasAdditionalLatestDataKeys(): boolean {
return this.widgetConfigComponent.widgetType === widgetType.timeseries &&
return !this.hideSourceSelection && this.widgetConfigComponent.widgetType === widgetType.timeseries &&
this.widgetConfigComponent.modelValue?.typeParameters?.hasAdditionalLatestDataKeys;
}

4
ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.html

@ -15,9 +15,9 @@
limitations under the License.
-->
<div class="tb-widget-config-panel" (click)="manageWidgetActions()" style="cursor: pointer;">
<div class="tb-form-panel" (click)="manageWidgetActions()" style="cursor: pointer;">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<div class="tb-widget-config-panel-title" translate style="padding-right: 48px;">widget-config.actions</div>
<div class="tb-form-panel-title" translate style="padding-right: 48px;">widget-config.actions</div>
<mat-chip-listbox fxFlex>
<ng-container *ngFor="let actionSourceId of widgetActionSourceIds">
<mat-chip *ngFor="let widgetAction of widgetActionsByActionSourceId(actionSourceId)">

6
ui-ngx/src/app/modules/home/components/widget/config/data-key-config-dialog.component.html

@ -19,10 +19,10 @@
<mat-toolbar color="primary">
<h2>{{ 'datakey.configuration' | translate }}</h2>
<span fxFlex></span>
<tb-toggle-header *ngIf="hasAdvanced" (valueChange)="dataKeyConfigMode = $event" ignoreMdLgSize="true"
<tb-toggle-select *ngIf="hasAdvanced"
appearance="fill-invert" [options]="dataKeyConfigHeaderOptions"
[value]="dataKeyConfigMode" name="dataKeyConfigModeHeader" useSelectOnMdLg="false">
</tb-toggle-header>
[(ngModel)]="dataKeyConfigMode" [ngModelOptions]="{ standalone: true }">
</tb-toggle-select>
<button mat-icon-button
(click)="cancel()"
type="button">

36
ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html

@ -15,11 +15,11 @@
limitations under the License.
-->
<div class="tb-datakey-config" *ngIf="dataKeyConfigMode === dataKeyConfigModes.general; else advanced"
<div *ngIf="dataKeyConfigMode === dataKeyConfigModes.general; else advanced"
[formGroup]="dataKeyFormGroup">
<div class="mat-padding" fxLayout="column" style="gap: 16px;">
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>datakey.general</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>datakey.general</div>
<mat-form-field *ngIf="modelValue.type !== dataKeyTypes.function" subscriptSizing="dynamic">
<mat-label>{{ 'entity.key' | translate }}</mat-label>
<input matInput type="text" placeholder="{{ 'entity.key-name' | translate }}"
@ -46,20 +46,20 @@
<input matInput formControlName="label" required>
</mat-form-field>
<ng-container *ngIf="modelValue.type !== dataKeyTypes.alarm">
<div class="tb-widget-config-row space-between" *ngIf="!hideDataKeyUnits">
<div class="tb-form-row space-between" *ngIf="!hideDataKeyUnits">
<div translate>widget-config.units-short</div>
<tb-widget-units
<tb-unit-input
formControlName="units">
</tb-widget-units>
</tb-unit-input>
</div>
<div class="tb-widget-config-row space-between" *ngIf="!hideDataKeyDecimals">
<div class="tb-form-row space-between" *ngIf="!hideDataKeyDecimals">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</ng-container>
<div class="tb-widget-config-row space-between same-padding" *ngIf="!hideDataKeyColor">
<div class="tb-form-row space-between same-padding" *ngIf="!hideDataKeyColor">
<div>{{ 'datakey.color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
@ -149,7 +149,7 @@
</tb-js-func>
</section>
</div>
<div class="tb-widget-config-panel tb-slide-toggle"
<div class="tb-form-panel tb-slide-toggle"
*ngIf="(modelValue.type === dataKeyTypes.timeseries || modelValue.type === dataKeyTypes.attribute || modelValue.type === dataKeyTypes.count) && showPostProcessing">
<mat-expansion-panel class="tb-settings" [expanded]="dataKeyFormGroup.get('usePostProcessing').value" [disabled]="!dataKeyFormGroup.get('usePostProcessing').value">
<mat-expansion-panel-header fxLayout="row wrap">
@ -182,14 +182,12 @@
</div>
</div>
<ng-template #advanced>
<div class="tb-datakey-config" [formGroup]="dataKeySettingsFormGroup">
<div class="mat-padding" fxLayout="column">
<tb-widget-settings
[dashboard]="dashboard"
[aliasController]="aliasController"
[widget]="widget"
formControlName="settings">
</tb-widget-settings>
</div>
<div class="mat-padding" fxLayout="column" [formGroup]="dataKeySettingsFormGroup">
<tb-widget-settings
[dashboard]="dashboard"
[aliasController]="aliasController"
[widget]="widget"
formControlName="settings">
</tb-widget-settings>
</div>
</ng-template>

107
ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.scss

@ -1,107 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../../scss/constants';
:host {
.tb-datakey-config {
.fields-group {
padding: 0 16px 8px;
margin-bottom: 10px;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
legend {
color: rgba(0, 0, 0, .7);
width: fit-content;
}
legend + * {
display: block;
margin-top: 16px;
}
&.fields-group-slider {
padding: 0;
legend {
margin-left: 16px;
}
> .tb-settings {
margin-top: 0;
padding: 0 16px 8px;
}
}
}
.tb-hint.after-fields {
margin-top: -0.75em;
max-width: fit-content;
line-height: 15px;
}
}
}
:host ::ng-deep {
.tb-datakey-config {
.mat-expansion-panel {
&.tb-settings {
box-shadow: none;
.mat-content {
overflow: visible;
}
.mat-expansion-panel-header {
padding: 0;
color: rgba(0, 0, 0, 0.87);
&:hover {
background: none;
}
.mat-expansion-indicator {
padding: 2px;
}
}
&.comparison {
.mat-expansion-panel-header {
height: fit-content;
}
}
.mat-expansion-panel-header-description {
align-items: center;
}
> .mat-expansion-panel-content {
> .mat-expansion-panel-body {
padding: 0;
}
}
}
.mat-expansion-panel-content {
font: inherit;
}
}
.mat-slide {
margin: 8px 0;
}
}
}

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

@ -57,7 +57,7 @@ import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-data-key-config',
templateUrl: './data-key-config.component.html',
styleUrls: ['./data-key-config.component.scss'],
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,

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

@ -38,6 +38,7 @@
datasourceFormGroup.get('type').value === datasourceType.alarmCount ? datasourceFormGroup.get('type').value : ''">
<tb-alarm-filter-config *ngIf="datasourceFormGroup.get('type').value === datasourceType.alarmCount"
propagatedFilter="false"
[initialAlarmFilterConfig]="{ statusList: [alarmSearchStatus.ACTIVE] }"
style="height: 56px; margin-bottom: 22px;"
formControlName="alarmFilterConfig"></tb-alarm-filter-config>
<tb-entity-autocomplete *ngIf="datasourceFormGroup.get('type').value === datasourceType.device"

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

@ -154,6 +154,8 @@ export class DatasourceComponent implements ControlValueAccessor, OnInit, Valida
datasourceTypes: Array<DatasourceType> = [];
datasourceTypesTranslations = datasourceTypeTranslationMap;
alarmSearchStatus = AlarmSearchStatus;
datasourceFormGroup: UntypedFormGroup;
private propagateChange = (_val: any) => {};

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

@ -15,28 +15,21 @@
limitations under the License.
-->
<div [formGroup]="datasourcesFormGroup" class="tb-widget-config-panel tb-datasources" [ngClass]="{'no-padding-bottom': !showAddDatasource}">
<div [formGroup]="datasourcesFormGroup" class="tb-form-panel tb-datasources" [ngClass]="{'no-padding-bottom': !showAddDatasource}">
<div fxLayout="column">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div class="tb-widget-config-panel-title">{{ (singleDatasource ? 'widget-config.datasource' : 'widget-config.datasources') | translate }}</div>
<div class="tb-form-panel-title">{{ (singleDatasource ? (isAlarmSource ? 'widget-config.alarm-source' : 'widget-config.datasource') : 'widget-config.datasources') | translate }}</div>
<div fxFlex fxLayout="row" fxLayoutAlign="center center" *ngIf="timeseriesKeyError">
<mat-error >{{ 'widget-config.timeseries-key-error' | translate }}</mat-error>
</div>
<tb-toggle-header *ngIf="basicMode" (valueChange)="datasourcesModeChange($event)" ignoreMdLgSize="true"
[options]="[
{
name: translate.instant('device.device'),
value: datasourceType.device
},
{
name: translate.instant('entity.entity-alias'),
value: datasourceType.entity
}
]" [value]="datasourcesMode" name="widgetConfigModeHeader" useSelectOnMdLg="false">
</tb-toggle-header>
<tb-toggle-select *ngIf="basicMode" [ngModel]="datasourcesMode" (ngModelChange)="datasourcesModeChange($event)"
[ngModelOptions]="{ standalone: true }">
<tb-toggle-option [value]="datasourceType.device">{{ 'device.device' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="datasourceType.entity">{{ 'entity.entity-alias' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<div *ngIf="maxDatasources > 1"
class="tb-widget-config-panel-hint">{{ 'widget-config.maximum-datasources' | translate:{count: maxDatasources} }}</div>
class="tb-form-panel-hint">{{ 'widget-config.maximum-datasources' | translate:{count: maxDatasources} }}</div>
</div>
<div *ngIf="datasourcesFormArray.length === 0; else datasourcesTemplate">
<span translate fxLayoutAlign="center center"

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

@ -64,12 +64,17 @@ export class DatasourcesComponent implements ControlValueAccessor, OnInit, Valid
datasourceType = DatasourceType;
public get isAlarmSource(): boolean {
return this.widgetConfigComponent.widgetType === widgetType.alarm;
}
public get basicMode(): boolean {
return !this.widgetConfigComponent.widgetEditMode && this.configMode === WidgetConfigMode.basic;
}
public get maxDatasources(): number {
return this.widgetConfigComponent.modelValue?.typeParameters?.maxDatasources;
return (this.forceSingleDatasource || this.isAlarmSource) ? 1 : this.widgetConfigComponent.modelValue?.typeParameters?.maxDatasources;
}
public get singleDatasource(): boolean {
@ -108,6 +113,10 @@ export class DatasourcesComponent implements ControlValueAccessor, OnInit, Valid
@coerceBoolean()
hideDataKeys = false;
@Input()
@coerceBoolean()
forceSingleDatasource = false;
@Input()
configMode: WidgetConfigMode;
@ -175,13 +184,20 @@ export class DatasourcesComponent implements ControlValueAccessor, OnInit, Valid
this.datasourcesMode = this.detectDatasourcesMode(datasources);
let changed = false;
if (datasources) {
datasources.forEach((datasource) => {
let length;
if (this.maxDatasources === -1) {
length = datasources.length;
} else {
length = Math.min(this.maxDatasources, datasources.length);
}
for (let i = 0; i < length; i++) {
const datasource = datasources[i];
if (this.basicMode && datasource.type !== this.datasourcesMode) {
datasource.type = this.datasourcesMode;
changed = true;
}
this.datasourcesFormArray.push(this.fb.control(datasource, []), {emitEvent: false});
});
}
}
if (this.singleDatasource && !this.datasourcesFormArray.length) {
this.addDatasource(false);
@ -255,7 +271,7 @@ export class DatasourcesComponent implements ControlValueAccessor, OnInit, Valid
private configModeChanged() {
if (this.basicMode) {
let datasourcesMode = this.detectDatasourcesMode(this.datasourcesFormGroup.get('datasources').value);
const datasourcesMode = this.detectDatasourcesMode(this.datasourcesFormGroup.get('datasources').value);
this.datasourcesModeChange(datasourcesMode);
}
}

17
ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.html

@ -15,18 +15,15 @@
limitations under the License.
-->
<div [formGroup]="timewindowConfig" class="tb-widget-config-panel">
<div [formGroup]="timewindowConfig" class="tb-form-panel">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div class="tb-widget-config-panel-title" translate>timewindow.timewindow</div>
<tb-toggle-header (valueChange)="timewindowConfig.get('useDashboardTimewindow').patchValue($event)" ignoreMdLgSize="true"
[options]="[
{ name: translate.instant('widget-config.use-dashboard-timewindow'), value: true},
{ name: translate.instant('widget-config.use-widget-timewindow'), value: false}
]"
[value]="timewindowConfig.get('useDashboardTimewindow').value" name="useDashboardTimewindow" useSelectOnMdLg="false">
</tb-toggle-header>
<div class="tb-form-panel-title" translate>timewindow.timewindow</div>
<tb-toggle-select formControlName="useDashboardTimewindow">
<tb-toggle-option [value]="true">{{ 'widget-config.use-dashboard-timewindow' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="false">{{ 'widget-config.use-widget-timewindow' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<div class="tb-widget-config-row">
<div class="tb-form-row">
<tb-timewindow asButton="true"
strokedButton
isEdit="true"

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

@ -17,10 +17,11 @@
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { widgetType } from '@shared/models/widget.models';
import { WidgetConfig, widgetType } from '@shared/models/widget.models';
import { Timewindow } from '@shared/models/time/time.models';
import { TranslateService } from '@ngx-translate/core';
import { coerceBoolean } from '@shared/decorators/coercion';
import { isDefined } from '@core/utils';
export interface TimewindowConfigData {
useDashboardTimewindow: boolean;
@ -28,6 +29,14 @@ export interface TimewindowConfigData {
timewindow: Timewindow;
}
export const getTimewindowConfig = (config: WidgetConfig): TimewindowConfigData => ({
useDashboardTimewindow: isDefined(config.useDashboardTimewindow) ?
config.useDashboardTimewindow : true,
displayTimewindow: isDefined(config.displayTimewindow) ?
config.displayTimewindow : true,
timewindow: config.timewindow
});
@Component({
selector: 'tb-timewindow-config-panel',
templateUrl: './timewindow-config-panel.component.html',

3
ui-ngx/src/app/modules/home/components/widget/config/widget-config-components.module.ts

@ -29,7 +29,6 @@ import { FilterSelectComponent } from '@home/components/filter/filter-select.com
import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module';
import { WidgetSettingsComponent } from '@home/components/widget/config/widget-settings.component';
import { TimewindowConfigPanelComponent } from '@home/components/widget/config/timewindow-config-panel.component';
import { WidgetUnitsComponent } from '@home/components/widget/config/widget-units.component';
@NgModule({
declarations:
@ -44,7 +43,6 @@ import { WidgetUnitsComponent } from '@home/components/widget/config/widget-unit
EntityAliasSelectComponent,
FilterSelectComponent,
TimewindowConfigPanelComponent,
WidgetUnitsComponent,
WidgetSettingsComponent
],
imports: [
@ -63,7 +61,6 @@ import { WidgetUnitsComponent } from '@home/components/widget/config/widget-unit
EntityAliasSelectComponent,
FilterSelectComponent,
TimewindowConfigPanelComponent,
WidgetUnitsComponent,
WidgetSettingsComponent
]
})

15
ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts

@ -138,6 +138,8 @@ import {
AlarmFilterConfigComponent,
AlarmFilterConfigData
} from '@home/components/alarm/alarm-filter-config.component';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { UserId } from '@shared/models/id/user-id';
interface AlarmsTableWidgetSettings extends TableWidgetSettings {
alarmsTitle: string;
@ -382,7 +384,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : 1024;
const alarmFilter = this.entityService.resolveAlarmFilter(this.widgetConfig.alarmFilterConfig);
const alarmFilter = this.entityService.resolveAlarmFilter(this.widgetConfig.alarmFilterConfig, false);
this.pageLink = {...this.pageLink, ...alarmFilter};
this.noDataDisplayMessageText =
@ -606,6 +608,9 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose();
});
const authUser = getCurrentAuthUser(this.store);
const assignedToCurrentUser = isDefinedAndNotNull(this.pageLink.assigneeId) && this.pageLink.assigneeId.id === authUser.userId;
const assigneeId = assignedToCurrentUser ? null : this.pageLink.assigneeId;
const providers: StaticProvider[] = [
{
provide: ALARM_FILTER_CONFIG_DATA,
@ -617,8 +622,10 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
severityList: deepClone(this.pageLink.severityList),
typeList: deepClone(this.pageLink.typeList),
searchPropagatedAlarms: this.pageLink.searchPropagatedAlarms,
assignedToCurrentUser: isDefinedAndNotNull(this.pageLink.assigneeId)
}
assignedToCurrentUser,
assigneeId
},
initialAlarmFilterConfig: deepClone(this.widgetConfig.alarmFilterConfig)
} as AlarmFilterConfigData
},
{
@ -632,7 +639,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
componentRef.onDestroy(() => {
if (componentRef.instance.panelResult) {
const result = componentRef.instance.panelResult;
const alarmFilter = this.entityService.resolveAlarmFilter(result);
const alarmFilter = this.entityService.resolveAlarmFilter(result, false);
this.pageLink = {...this.pageLink, ...alarmFilter};
this.resetPageIndex();
this.updateData();

19
ui-ngx/src/app/modules/home/components/widget/lib/home-page/getting-started-widget.component.html

@ -111,21 +111,10 @@
</ng-template>
<div [innerHTML]="'widgets.getting-started.tenant-admin.step2.content-before' | translate | safe: 'html'"></div>
<div class="tb-bordered-content">
<tb-toggle-header #publishCommandSteps value="ubuntu" name="publishCommandSteps" useSelectOnMdLg="false"
[options]="[
{
name: 'Ubuntu',
value: 'ubuntu'
},
{
name: 'MacOS',
value: 'macos'
},
{
name: 'Windows',
value: 'windows'
}
]">
<tb-toggle-header #publishCommandSteps value="ubuntu" name="publishCommandSteps" useSelectOnMdLg="false">
<tb-toggle-option value="ubuntu">Ubuntu</tb-toggle-option>
<tb-toggle-option value="macos">MacOS</tb-toggle-option>
<tb-toggle-option value="windows">Windows</tb-toggle-option>
</tb-toggle-header>
<ng-container [ngSwitch]="publishCommandSteps.value">
<ng-template [ngSwitchCase]="'ubuntu'">

13
ui-ngx/src/app/modules/home/components/widget/lib/home-page/recent-dashboards-widget.component.html

@ -20,16 +20,9 @@
<div class="tb-card-header">
<a class="tb-title-link" routerLink="/dashboards">{{ 'widgets.recent-dashboards.title' | translate }}</a>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutAlign.lt-md="space-between center" fxLayoutGap="8px">
<tb-toggle-header #dashboardsToggle [value]="toggleValue" name="usageToggle" (valueChange)="toggleValueChange($event)" [options]="[
{
name: ctx.translate.instant('widgets.recent-dashboards.last'),
value: 'last'
},
{
name: ctx.translate.instant('widgets.recent-dashboards.starred'),
value: 'starred'
}
]">
<tb-toggle-header #dashboardsToggle [value]="toggleValue" name="usageToggle" (valueChange)="toggleValueChange($event)">
<tb-toggle-option value="last">{{ 'widgets.recent-dashboards.last' | translate }}</tb-toggle-option>
<tb-toggle-option value="starred">{{ 'widgets.recent-dashboards.starred' | translate }}</tb-toggle-option>
</tb-toggle-header>
<a *ngIf="authUser.authority === authority.TENANT_ADMIN" fxHide.md
mat-flat-button color="primary" routerLink="/dashboards" [queryParams]="{action: 'add'}">{{ 'dashboard.add' | translate }}</a>

13
ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.html

@ -19,16 +19,9 @@
<ng-container *ngIf="hasUsageInfoAccess; else noDataAvailable">
<div class="tb-card-header">
<a class="tb-title-link" routerLink="/usage">{{ 'widgets.usage-info.title' | translate }}</a>
<tb-toggle-header #usageToggle [value]="toggleValue" name="usageToggle" [options]="[
{
name: ctx.translate.instant('widgets.usage-info.entities'),
value: 'entities'
},
{
name: ctx.translate.instant('widgets.usage-info.api-calls'),
value: 'apiCalls'
}
]">
<tb-toggle-header #usageToggle [value]="toggleValue" name="usageToggle">
<tb-toggle-option value="entities">{{ 'widgets.usage-info.entities' | translate }}</tb-toggle-option>
<tb-toggle-option value="apiCalls">{{ 'widgets.usage-info.api-calls' | translate }}</tb-toggle-option>
</tb-toggle-header>
</div>
<ng-container [ngSwitch]="usageToggle.value">

96
ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-key-settings.component.html

@ -15,21 +15,56 @@
limitations under the License.
-->
<section class="tb-widget-settings" [formGroup]="alarmsTableKeySettingsForm" fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.table.custom-title</mat-label>
<input matInput formControlName="customTitle">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.table.column-width</mat-label>
<input matInput formControlName="columnWidth">
</mat-form-field>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.table.cell-style</legend>
<ng-container [formGroup]="alarmsTableKeySettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.column-settings</div>
<div class="tb-form-row">
<div class="fixed-title-width">{{ 'widgets.table.custom-title' | translate }}</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="customTitle" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.table.column-width' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="columnWidth" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.table.default-column-visibility' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="defaultColumnVisibility">
<mat-option [value]="'visible'">
{{ 'widgets.table.column-visibility-visible' | translate }}
</mat-option>
<mat-option [value]="'hidden'">
{{ 'widgets.table.column-visibility-hidden' | translate }}
</mat-option>
<mat-option [value]="'hidden-mobile'">
{{ 'widgets.table.column-visibility-hidden-mobile' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.table.column-selection-to-display' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="columnSelectionToDisplay">
<mat-option [value]="'enabled'">
{{ 'widgets.table.column-selection-to-display-enabled' | translate }}
</mat-option>
<mat-option [value]="'disabled'">
{{ 'widgets.table.column-selection-to-display-disabled' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="alarmsTableKeySettingsForm.get('useCellStyleFunction').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle formControlName="useCellStyleFunction" (click)="$event.stopPropagation()"
<mat-slide-toggle class="mat-slide" formControlName="useCellStyleFunction" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.table.use-cell-style-function' | translate }}
</mat-slide-toggle>
@ -48,13 +83,12 @@
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.table.cell-content</legend>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="alarmsTableKeySettingsForm.get('useCellContentFunction').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle formControlName="useCellContentFunction" (click)="$event.stopPropagation()"
<mat-slide-toggle class="mat-slide" formControlName="useCellContentFunction" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.table.use-cell-content-function' | translate }}
</mat-slide-toggle>
@ -73,27 +107,5 @@
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</fieldset>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.table.default-column-visibility</mat-label>
<mat-select formControlName="defaultColumnVisibility">
<mat-option [value]="'visible'">
{{ 'widgets.table.column-visibility-visible' | translate }}
</mat-option>
<mat-option [value]="'hidden'">
{{ 'widgets.table.column-visibility-hidden' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.table.column-selection-to-display</mat-label>
<mat-select formControlName="columnSelectionToDisplay">
<mat-option [value]="'enabled'">
{{ 'widgets.table.column-selection-to-display-enabled' | translate }}
</mat-option>
<mat-option [value]="'disabled'">
{{ 'widgets.table.column-selection-to-display-disabled' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</section>
</div>
</ng-container>

150
ui-ngx/src/app/modules/home/components/widget/lib/settings/alarm/alarms-table-widget-settings.component.html

@ -15,38 +15,62 @@
limitations under the License.
-->
<section class="tb-widget-settings" [formGroup]="alarmsTableWidgetSettingsForm" fxLayout="column">
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.table.common-table-settings</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.table.alarms-table-title</mat-label>
<input matInput formControlName="alarmsTitle">
</mat-form-field>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px">
<section fxLayout="column" fxFlex>
<mat-checkbox formControlName="enableSelection">
{{ 'widgets.table.enable-alarms-selection' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="enableSearch">
{{ 'widgets.table.enable-alarms-search' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="enableSelectColumnDisplay">
{{ 'widgets.table.enable-select-column-display' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="enableFilter">
{{ 'widgets.table.enable-alarm-filter' | translate }}
</mat-checkbox>
</section>
<section fxLayout="column" fxFlex>
<mat-checkbox formControlName="enableStickyHeader">
{{ 'widgets.table.enable-sticky-header' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="enableStickyAction">
{{ 'widgets.table.enable-sticky-action' | translate }}
</mat-checkbox>
</section>
</section>
<mat-form-field fxFlex class="mat-block">
<ng-container [formGroup]="alarmsTableWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.table-header</div>
<div class="tb-form-row">
<div class="fixed-title-width">{{ 'widgets.table.alarms-table-title' | translate }}</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="alarmsTitle" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="enableStickyHeader">
{{ 'widgets.table.enable-sticky-header' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.table.header-buttons</div>
<mat-slide-toggle class="mat-slide" formControlName="enableSearch">
{{ 'widgets.table.enable-alarms-search' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="enableSelectColumnDisplay">
{{ 'widgets.table.enable-select-column-display' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="enableFilter">
{{ 'widgets.table.enable-alarm-filter' | translate }}
</mat-slide-toggle>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.columns</div>
<mat-slide-toggle class="mat-slide" formControlName="enableSelection">
{{ 'widgets.table.enable-alarms-selection' | translate }}
</mat-slide-toggle>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="enableStickyAction">
{{ 'widgets.table.enable-sticky-action' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.table.table-buttons</div>
<mat-slide-toggle class="mat-slide" formControlName="displayActivity">
{{ 'widgets.table.display-alarm-activity' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="displayDetails">
{{ 'widgets.table.display-alarm-details' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="allowAssign">
{{ 'widgets.table.allow-alarms-assign' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="allowAcknowledgment">
{{ 'widgets.table.allow-alarms-ack' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="allowClear">
{{ 'widgets.table.allow-alarms-clear' | translate }}
</mat-slide-toggle>
</div>
<mat-form-field fxFlex subscriptSizing="dynamic">
<mat-label translate>widgets.table.hidden-cell-button-display-mode</mat-label>
<mat-select formControlName="reserveSpaceForHiddenAction">
<mat-option [value]="'true'">
@ -57,48 +81,34 @@
</mat-option>
</mat-select>
</mat-form-field>
<section fxLayout="column" fxLayoutGap="8px">
<mat-slide-toggle formControlName="displayActivity">
{{ 'widgets.table.display-alarm-activity' | translate }}
</mat-slide-toggle>
<mat-slide-toggle fxFlex formControlName="displayDetails">
{{ 'widgets.table.display-alarm-details' | translate }}
</mat-slide-toggle>
<mat-slide-toggle formControlName="allowAcknowledgment">
{{ 'widgets.table.allow-alarms-ack' | translate }}
</mat-slide-toggle>
<mat-slide-toggle formControlName="allowClear">
{{ 'widgets.table.allow-alarms-clear' | translate }}
</mat-slide-toggle>
<mat-slide-toggle formControlName="allowAssign">
{{ 'widgets.table.allow-alarms-assign' | translate }}
</mat-slide-toggle>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap="8px" fxLayoutAlign.gt-xs="start center">
<mat-slide-toggle fxFlex formControlName="displayPagination">
{{ 'widgets.table.display-pagination' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.table.default-page-size</mat-label>
<input matInput type="number" min="1" step="1" formControlName="defaultPageSize">
</mat-form-field>
</section>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.table.default-sort-order</mat-label>
<input matInput formControlName="defaultSortOrder">
<mat-form-field fxFlex subscriptSizing="dynamic">
<mat-label translate>widgets.table.default-sort-order</mat-label>
<input matInput formControlName="defaultSortOrder">
</mat-form-field>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.pagination</div>
<mat-slide-toggle class="mat-slide" formControlName="displayPagination">
{{ 'widgets.table.display-pagination' | translate }}
</mat-slide-toggle>
<div class="tb-form-row space-between">
<div translate>widgets.table.default-page-size</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="defaultPageSize" type="number" min="1" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</section>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.table.row-style</legend>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle">
<div class="tb-form-panel-title" translate>widgets.table.rows</div>
<mat-expansion-panel class="tb-settings" [expanded]="alarmsTableWidgetSettingsForm.get('useRowStyleFunction').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="useRowStyleFunction" (click)="$event.stopPropagation()"
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="useRowStyleFunction" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.table.use-row-style-function' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
@ -112,5 +122,5 @@
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</fieldset>
</section>
</div>
</ng-container>

24
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/entities-table-key-settings.component.html

@ -16,23 +16,23 @@
-->
<ng-container [formGroup]="entitiesTableKeySettingsForm">
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.column-settings</div>
<div class="tb-widget-config-row">
<div>{{ 'widgets.table.custom-title' | translate }}</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.column-settings</div>
<div class="tb-form-row">
<div class="fixed-title-width">{{ 'widgets.table.custom-title' | translate }}</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="customTitle" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div>{{ 'widgets.table.column-width' | translate }}</div>
<mat-form-field appearance="outline" class="center" subscriptSizing="dynamic">
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="columnWidth" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div>{{ 'widgets.table.default-column-visibility' | translate }}</div>
<mat-form-field style="width: 220px;" appearance="outline" subscriptSizing="dynamic">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="defaultColumnVisibility">
<mat-option [value]="'visible'">
{{ 'widgets.table.column-visibility-visible' | translate }}
@ -46,9 +46,9 @@
</mat-select>
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div>{{ 'widgets.table.column-selection-to-display' | translate }}</div>
<mat-form-field style="width: 220px;" appearance="outline" subscriptSizing="dynamic">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="columnSelectionToDisplay">
<mat-option [value]="'enabled'">
{{ 'widgets.table.column-selection-to-display-enabled' | translate }}
@ -60,7 +60,7 @@
</mat-form-field>
</div>
</div>
<div class="tb-widget-config-panel tb-slide-toggle">
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="entitiesTableKeySettingsForm.get('useCellStyleFunction').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
@ -84,7 +84,7 @@
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-widget-config-panel tb-slide-toggle">
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="entitiesTableKeySettingsForm.get('useCellContentFunction').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">

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

@ -16,53 +16,53 @@
-->
<ng-container [formGroup]="entitiesTableWidgetSettingsForm">
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.table-header</div>
<div class="tb-widget-config-row">
<div>{{ 'widgets.table.entities-table-title' | translate }}</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.table-header</div>
<div class="tb-form-row">
<div class="fixed-title-width">{{ 'widgets.table.entities-table-title' | translate }}</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="entitiesTitle" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row">
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="enableStickyHeader">
{{ 'widgets.table.enable-sticky-header' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-widget-config-panel stroked">
<div class="tb-widget-config-panel-title" translate>widgets.table.header-buttons</div>
<mat-slide-toggle class="mat-slide no-margin" formControlName="enableSearch">
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.table.header-buttons</div>
<mat-slide-toggle class="mat-slide" formControlName="enableSearch">
{{ 'widgets.table.enable-search' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide no-margin" formControlName="enableSelectColumnDisplay">
<mat-slide-toggle class="mat-slide" formControlName="enableSelectColumnDisplay">
{{ 'widgets.table.enable-select-column-display' | translate }}
</mat-slide-toggle>
</div>
</div>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.columns</div>
<div class="tb-widget-config-row">
<mat-slide-toggle class="mat-slide" formControlName="displayEntityName">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.columns</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="displayEntityName">
{{ 'widgets.table.display-entity-name' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="entityNameColumnTitle" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row">
<mat-slide-toggle class="mat-slide" formControlName="displayEntityName">
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="displayEntityName">
{{ 'widgets.table.display-entity-label' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="entityLabelColumnTitle" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row">
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="displayEntityType">
{{ 'widgets.table.display-entity-type' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-widget-config-row">
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="enableStickyAction">
{{ 'widgets.table.enable-sticky-action' | translate }}
</mat-slide-toggle>
@ -83,20 +83,20 @@
<input matInput formControlName="defaultSortOrder">
</mat-form-field>
</div>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.pagination</div>
<mat-slide-toggle class="mat-slide no-margin" formControlName="displayPagination">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.pagination</div>
<mat-slide-toggle class="mat-slide" formControlName="displayPagination">
{{ 'widgets.table.display-pagination' | translate }}
</mat-slide-toggle>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div translate>widgets.table.default-page-size</div>
<mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="defaultPageSize" type="number" min="1" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<div class="tb-widget-config-panel tb-slide-toggle">
<div class="tb-widget-config-panel-title" translate>widgets.table.rows</div>
<div class="tb-form-panel tb-slide-toggle">
<div class="tb-form-panel-title" translate>widgets.table.rows</div>
<mat-expansion-panel class="tb-settings" [expanded]="entitiesTableWidgetSettingsForm.get('useRowStyleFunction').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/label-widget-label.component.ts

@ -33,7 +33,7 @@ export interface LabelWidgetLabel {
@Component({
selector: 'tb-label-widget-label',
templateUrl: './label-widget-label.component.html',
styleUrls: ['./label-widget-label.component.scss', './../widget-settings.scss'],
styleUrls: ['./label-widget-label.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,

6
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/simple-card-widget-settings.component.html

@ -15,9 +15,9 @@
limitations under the License.
-->
<div [formGroup]="simpleCardWidgetSettingsForm" class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.simple-card.label</div>
<div class="tb-widget-config-row space-between">
<div [formGroup]="simpleCardWidgetSettingsForm" class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.simple-card.label</div>
<div class="tb-form-row space-between">
<div translate>widgets.simple-card.label-position</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="labelPosition">

16
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-key-settings.component.html

@ -16,11 +16,11 @@
-->
<ng-container [formGroup]="timeseriesTableKeySettingsForm">
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.column-settings</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.column-settings</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.table.default-column-visibility' | translate }}</div>
<mat-form-field style="width: 220px;" appearance="outline" subscriptSizing="dynamic">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="defaultColumnVisibility">
<mat-option [value]="'visible'">
{{ 'widgets.table.column-visibility-visible' | translate }}
@ -34,9 +34,9 @@
</mat-select>
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div>{{ 'widgets.table.column-selection-to-display' | translate }}</div>
<mat-form-field style="width: 220px;" appearance="outline" subscriptSizing="dynamic">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="columnSelectionToDisplay">
<mat-option [value]="'enabled'">
{{ 'widgets.table.column-selection-to-display-enabled' | translate }}
@ -48,7 +48,7 @@
</mat-form-field>
</div>
</div>
<div class="tb-widget-config-panel tb-slide-toggle">
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableKeySettingsForm.get('useCellStyleFunction').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
@ -72,7 +72,7 @@
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-widget-config-panel tb-slide-toggle">
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableKeySettingsForm.get('useCellContentFunction').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">

22
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-latest-key-settings.component.html

@ -16,20 +16,20 @@
-->
<ng-container [formGroup]="timeseriesTableLatestKeySettingsForm">
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.column-settings</div>
<mat-slide-toggle class="mat-slide no-margin" formControlName="show">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.column-settings</div>
<mat-slide-toggle class="mat-slide" formControlName="show">
{{ 'widgets.table.show-latest-data-column' | translate }}
</mat-slide-toggle>
<div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-widget-config-row space-between">
<div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-form-row space-between">
<div translate>widgets.table.latest-data-column-order</div>
<mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="order" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-widget-config-row space-between">
<div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-form-row space-between">
<div>{{ 'widgets.table.default-column-visibility' | translate }}</div>
<mat-form-field style="width: 220px;" appearance="outline" subscriptSizing="dynamic">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="defaultColumnVisibility">
<mat-option [value]="'visible'">
{{ 'widgets.table.column-visibility-visible' | translate }}
@ -43,9 +43,9 @@
</mat-select>
</mat-form-field>
</div>
<div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-widget-config-row space-between">
<div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-form-row space-between">
<div>{{ 'widgets.table.column-selection-to-display' | translate }}</div>
<mat-form-field style="width: 220px;" appearance="outline" subscriptSizing="dynamic">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="columnSelectionToDisplay">
<mat-option [value]="'enabled'">
{{ 'widgets.table.column-selection-to-display-enabled' | translate }}
@ -57,7 +57,7 @@
</mat-form-field>
</div>
</div>
<div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-widget-config-panel tb-slide-toggle">
<div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableLatestKeySettingsForm.get('useCellStyleFunction').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
@ -81,7 +81,7 @@
</ng-template>
</mat-expansion-panel>
</div>
<div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-widget-config-panel tb-slide-toggle">
<div [fxShow]="timeseriesTableLatestKeySettingsForm.get('show').value" class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableLatestKeySettingsForm.get('useCellContentFunction').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">

42
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html

@ -16,27 +16,27 @@
-->
<ng-container [formGroup]="timeseriesTableWidgetSettingsForm">
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.table-header</div>
<mat-slide-toggle class="mat-slide no-margin" formControlName="enableStickyHeader">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.table-header</div>
<mat-slide-toggle class="mat-slide" formControlName="enableStickyHeader">
{{ 'widgets.table.enable-sticky-header' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide no-margin" formControlName="enableSearch">
<mat-slide-toggle class="mat-slide" formControlName="enableSearch">
{{ 'widgets.table.enable-search' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide no-margin" formControlName="enableSelectColumnDisplay">
<mat-slide-toggle class="mat-slide" formControlName="enableSelectColumnDisplay">
{{ 'widgets.table.enable-select-column-display' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.columns</div>
<mat-slide-toggle class="mat-slide no-margin" formControlName="showTimestamp">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.columns</div>
<mat-slide-toggle class="mat-slide" formControlName="showTimestamp">
{{ 'widgets.table.display-timestamp' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide no-margin" formControlName="showMilliseconds">
<mat-slide-toggle class="mat-slide" formControlName="showMilliseconds">
{{ 'widgets.table.display-milliseconds' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide no-margin" formControlName="enableStickyAction">
<mat-slide-toggle class="mat-slide" formControlName="enableStickyAction">
{{ 'widgets.table.enable-sticky-action' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex subscriptSizing="dynamic">
@ -51,27 +51,27 @@
</mat-select>
</mat-form-field>
</div>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.pagination</div>
<mat-slide-toggle class="mat-slide no-margin" formControlName="displayPagination">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.pagination</div>
<mat-slide-toggle class="mat-slide" formControlName="displayPagination">
{{ 'widgets.table.display-pagination' | translate }}
</mat-slide-toggle>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div translate>widgets.table.default-page-size</div>
<mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="defaultPageSize" type="number" min="1" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widgets.table.table-tabs</div>
<mat-slide-toggle class="mat-slide no-margin" formControlName="useEntityLabel">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.table.table-tabs</div>
<mat-slide-toggle class="mat-slide" formControlName="useEntityLabel">
{{ 'widgets.table.use-entity-label-tab-name' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-widget-config-panel tb-slide-toggle">
<div class="tb-widget-config-panel-title" style="padding-bottom: 16px;" translate>widgets.table.rows</div>
<mat-slide-toggle class="mat-slide no-margin" style="padding-left: 16px;" formControlName="hideEmptyLines">
<div class="tb-form-panel tb-slide-toggle">
<div class="tb-form-panel-title" style="padding-bottom: 16px;" translate>widgets.table.rows</div>
<mat-slide-toggle class="mat-slide" style="padding-left: 16px;" formControlName="hideEmptyLines">
{{ 'widgets.table.hide-empty-lines' | translate }}
</mat-slide-toggle>
<mat-expansion-panel class="tb-settings" [expanded]="timeseriesTableWidgetSettingsForm.get('useRowStyleFunction').value">

297
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html

@ -15,81 +15,85 @@
limitations under the License.
-->
<section class="tb-widget-settings" [formGroup]="flotKeySettingsFormGroup" fxLayout="column">
<fieldset class="fields-group" fxLayout="column">
<legend class="group-title" translate>widgets.chart.common-settings</legend>
<mat-checkbox formControlName="hideDataByDefault">
<ng-container [formGroup]="flotKeySettingsFormGroup">
<div class="tb-form-panel">
<mat-slide-toggle class="mat-slide" formControlName="hideDataByDefault">
{{ 'widgets.chart.data-is-hidden-by-default' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="disableDataHiding">
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="disableDataHiding">
{{ 'widgets.chart.disable-data-hiding' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="removeFromLegend">
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="removeFromLegend">
{{ 'widgets.chart.remove-from-legend' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="excludeFromStacking">
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="excludeFromStacking">
{{ 'widgets.chart.exclude-from-stacking' | translate }}
</mat-checkbox>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.chart.line-settings</legend>
</mat-slide-toggle>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="flotKeySettingsFormGroup.get('showLines').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="showLines" (click)="$event.stopPropagation()"
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="showLines" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.show-line' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.line-width</mat-label>
<input matInput type="number" min="0" formControlName="lineWidth">
<div class="tb-form-row space-between">
<div>{{ 'widgets.chart.line-width' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="lineWidth" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
<mat-checkbox formControlName="fillLines">
{{ 'widgets.chart.fill-line' | translate }}
</mat-checkbox>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.fill-line-opacity</mat-label>
<input matInput type="number" min="0" max="1" step="0.1" formControlName="fillLinesOpacity">
</div>
<mat-slide-toggle class="mat-slide" formControlName="fillLines">
{{ 'widgets.chart.fill-line' | translate }}
</mat-slide-toggle>
<div [fxShow]="flotKeySettingsFormGroup.get('fillLines').value" class="tb-form-row space-between">
<div>{{ 'widgets.chart.fill-line-opacity' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="fillLinesOpacity" type="number" min="0" max="1" step="0.1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</section>
</div>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.chart.points-settings</legend>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="flotKeySettingsFormGroup.get('showPoints').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="showPoints" (click)="$event.stopPropagation()"
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="showPoints" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.show-points' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section fxLayout="column">
<section fxFlex fxLayout="row" fxLayoutGap="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.points-line-width</mat-label>
<input matInput type="number" min="0" formControlName="showPointsLineWidth">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.points-radius</mat-label>
<input matInput type="number" min="0" formControlName="showPointsRadius">
</mat-form-field>
</section>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.point-shape</mat-label>
<div class="tb-form-row space-between">
<div>{{ 'widgets.chart.points-line-width' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="showPointsLineWidth" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.chart.points-radius' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="showPointsRadius" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.chart.point-shape' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="showPointShape">
<mat-option [value]="'circle'">
{{ 'widgets.chart.point-shape-circle' | translate }}
@ -111,19 +115,19 @@
</mat-option>
</mat-select>
</mat-form-field>
<tb-js-func [fxShow]="flotKeySettingsFormGroup.get('showPointShape').value === 'custom'"
formControlName="pointShapeFormatter"
[globalVariables]="functionScopeVariables"
[functionArgs]="['ctx', 'x', 'y', 'radius', 'shadow']"
functionTitle="{{ 'widgets.chart.point-shape-draw-function' | translate }}"
helpId="widget/lib/flot/point_shape_format_fn">
</tb-js-func>
</section>
</div>
<tb-js-func [fxShow]="flotKeySettingsFormGroup.get('showPointShape').value === 'custom'"
formControlName="pointShapeFormatter"
[globalVariables]="functionScopeVariables"
[functionArgs]="['ctx', 'x', 'y', 'radius', 'shadow']"
functionTitle="{{ 'widgets.chart.point-shape-draw-function' | translate }}"
helpId="widget/lib/flot/point_shape_format_fn">
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.chart.tooltip-settings</legend>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.chart.tooltip-settings</div>
<tb-js-func
formControlName="tooltipValueFormatter"
[globalVariables]="functionScopeVariables"
@ -131,49 +135,57 @@
functionTitle="{{ 'widgets.chart.tooltip-value-format-function' | translate }}"
helpId="widget/lib/flot/tooltip_value_format_fn">
</tb-js-func>
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.chart.yaxis-settings</legend>
<mat-slide-toggle formControlName="showSeparateAxis" class="slide-block">
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.chart.vertical-axis</div>
<mat-slide-toggle class="mat-slide" formControlName="showSeparateAxis">
{{ 'widgets.chart.show-separate-axis' | translate }}
</mat-slide-toggle>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.axis-title</mat-label>
<input matInput formControlName="axisTitle">
</mat-form-field>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.min-scale-value</mat-label>
<input matInput type="number" formControlName="axisMin">
<div class="tb-form-row">
<div class="fixed-title-width" translate>widgets.chart.axis-title</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="axisTitle" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.max-scale-value</mat-label>
<input matInput type="number" formControlName="axisMax">
</div>
<div class="tb-form-row space-between">
<div translate>widgets.chart.min-scale-value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisMin" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</section>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.axis-position</mat-label>
<mat-select formControlName="axisPosition">
<mat-option [value]="'left'">
{{ 'widgets.chart.axis-position-left' | translate }}
</mat-option>
<mat-option [value]="'right'">
{{ 'widgets.chart.axis-position-right' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.chart.yaxis-tick-labels-settings</legend>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.tick-step-size</mat-label>
<input matInput type="number" formControlName="axisTickSize">
</div>
<div class="tb-form-row space-between">
<div translate>widgets.chart.max-scale-value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisMax" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.chart.axis-position' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="axisPosition">
<mat-option [value]="'left'">
{{ 'widgets.chart.axis-position-left' | translate }}
</mat-option>
<mat-option [value]="'right'">
{{ 'widgets.chart.axis-position-right' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.chart.ticks</div>
<div class="tb-form-row space-between">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisTickDecimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.number-of-decimals</mat-label>
<input matInput type="number" min="0" formControlName="axisTickDecimals">
</div>
<div class="tb-form-row space-between">
<div translate>widgets.chart.tick-step-size</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="axisTickSize" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</section>
</div>
<tb-js-func
formControlName="axisTicksFormatter"
[globalVariables]="functionScopeVariables"
@ -181,63 +193,58 @@
functionTitle="{{ 'widgets.chart.ticks-formatter-function' | translate }}"
helpId="widget/lib/flot/ticks_formatter_fn">
</tb-js-func>
</fieldset>
</fieldset>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.chart.thresholds</legend>
<div fxLayout="column">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="thresholdDrop($event)">
<div cdkDrag class="tb-draggable" *ngFor="let thresholdControl of thresholdsFormArray().controls; trackBy: trackByThreshold;
let $index = index; last as isLast;"
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-flot-threshold [formControl]="thresholdControl"
[expanded]="thresholdControl.new"
[aliasController]="aliasController"
(removeThreshold)="removeThreshold($index)">
</tb-flot-threshold>
</div>
</div>
<div *ngIf="!thresholdsFormArray().controls.length">
<span translate fxLayoutAlign="center center"
class="tb-prompt">widgets.chart.no-thresholds</span>
</div>
<div style="padding-top: 16px;">
<button mat-raised-button color="primary"
type="button"
(click)="addThreshold()">
<span translate>widgets.chart.add-threshold</span>
</button>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" fxLayout="row" fxLayoutAlign="space-between center">
<div translate>widgets.chart.thresholds</div>
<button mat-stroked-button
color="primary"
type="button" (click)="addThreshold()">
<span translate>widgets.chart.add-threshold</span>
</button>
</div>
<div *ngIf="thresholdsFormArray().length" class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="thresholdDrop($event)" fxLayout="column" fxLayoutGap="12px">
<div cdkDrag class="tb-draggable" *ngFor="let thresholdControl of thresholdsFormArray().controls; trackBy: trackByThreshold;
let $index = index;">
<tb-flot-threshold [formControl]="thresholdControl"
[expanded]="thresholdControl.new"
[aliasController]="aliasController"
(removeThreshold)="removeThreshold($index)">
</tb-flot-threshold>
</div>
</div>
</fieldset>
<fieldset class="fields-group fields-group-slider" formGroupName="comparisonSettings" >
<legend class="group-title" translate>widgets.chart.comparison-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="flotKeySettingsFormGroup.get('comparisonSettings.showValuesForComparison').value">
</div>
<div class="tb-form-panel tb-slide-toggle" formGroupName="comparisonSettings">
<div class="tb-form-panel-title" translate>widgets.chart.comparison-settings</div>
<mat-expansion-panel class="tb-settings" [expanded]="flotKeySettingsFormGroup.get('comparisonSettings.showValuesForComparison').value"
[disabled]="!flotKeySettingsFormGroup.get('comparisonSettings.showValuesForComparison').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title fxFlex="60">
<mat-slide-toggle formControlName="showValuesForComparison" (click)="$event.stopPropagation()"
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showValuesForComparison" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.show-values-for-comparison' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section fxFlex fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.comparison-values-label</mat-label>
<input matInput formControlName="comparisonValuesLabel">
<div class="tb-form-row">
<div class="fixed-title-width" translate>widgets.chart.comparison-values-label</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="comparisonValuesLabel" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-color-input fxFlex
formControlName="color"
icon="format_color_fill"
label="{{ 'widgets.chart.color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
</section>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widgets.chart.comparison-line-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</fieldset>
</section>
</div>
</ng-container>

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.ts

@ -225,6 +225,7 @@ export class FlotKeySettingsComponent extends PageComponent implements OnInit, C
this.flotKeySettingsFormGroup.disable({emitEvent: false});
} else {
this.flotKeySettingsFormGroup.enable({emitEvent: false});
this.updateValidators(false);
}
}

43
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.html

@ -15,34 +15,37 @@
limitations under the License.
-->
<section class="tb-widget-settings" [formGroup]="flotLatestKeySettingsForm" fxLayout="column">
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.chart.threshold-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="flotLatestKeySettingsForm.get('useAsThreshold').value">
<ng-container [formGroup]="flotLatestKeySettingsForm">
<div class="tb-form-panel tb-slide-toggle">
<div class="tb-form-panel-title" translate>widgets.chart.threshold-settings</div>
<mat-expansion-panel class="tb-settings" [expanded]="flotLatestKeySettingsForm.get('useAsThreshold').value"
[disabled]="!flotLatestKeySettingsForm.get('useAsThreshold').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="useAsThreshold" (click)="$event.stopPropagation()"
<mat-slide-toggle class="mat-slide" formControlName="useAsThreshold" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.use-as-threshold' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.threshold-line-width</mat-label>
<input type="number" min="0" matInput formControlName="thresholdLineWidth">
<div class="tb-form-row space-between">
<div translate>widgets.chart.threshold-line-width</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="thresholdLineWidth" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
<tb-color-input fxFlex
formControlName="thresholdColor"
icon="format_color_fill"
label="{{ 'widgets.chart.threshold-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
</section>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widgets.chart.threshold-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="thresholdColor">
</tb-color-input>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</fieldset>
</section>
</div>
</ng-container>

72
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html

@ -15,44 +15,34 @@
limitations under the License.
-->
<mat-expansion-panel class="flot-threshold" fxFlex [formGroup]="thresholdFormGroup" [(expanded)]="expanded">
<mat-expansion-panel-header>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<mat-panel-title>
<div fxLayout="row" fxFlex fxLayoutAlign="start center">
<div>{{ thresholdText() }}</div>
<div class="tb-color-preview" style="margin-left: 6px;">
<div class="tb-color-result" [ngStyle]="{background: thresholdFormGroup.get('color').value}"></div>
</div>
</div>
</mat-panel-title>
<span fxFlex></span>
<button *ngIf="!disabled" mat-icon-button style="min-width: 40px;"
type="button"
(click)="removeThreshold.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div fxLayout="column" fxLayoutGap="0.5em">
<mat-divider></mat-divider>
<section class="tb-widget-settings" fxLayout="column">
<tb-value-source [aliasController]="aliasController" formControlName="valueSource"></tb-value-source>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.line-width</mat-label>
<input type="number" min="0" matInput formControlName="lineWidth">
</mat-form-field>
<tb-color-input fxFlex
formControlName="color"
icon="format_color_fill"
label="{{ 'widgets.chart.color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
</section>
</section>
</div>
</ng-template>
</mat-expansion-panel>
<div class="tb-flot-threshold">
<mat-expansion-panel class="tb-settings tb-flot-threshold-settings" fxFlex [formGroup]="thresholdFormGroup" [(expanded)]="expanded">
<mat-expansion-panel-header class="fill-width">
<div class="tb-threshold-header">
<div class="tb-threshold-text">{{ thresholdText() }}</div>
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
<mat-divider vertical></mat-divider>
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-value-source [aliasController]="aliasController" formControlName="valueSource"></tb-value-source>
<div class="tb-form-row space-between">
<div translate>widgets.chart.line-width</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="lineWidth" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
<button *ngIf="!disabled" mat-icon-button
type="button"
(click)="removeThreshold.emit()"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>

66
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss

@ -13,28 +13,60 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
display: block;
.mat-expansion-panel {
.tb-flot-threshold {
display: flex;
flex-direction: row;
align-items: start;
gap: 4px;
.mat-expansion-panel.tb-settings.tb-flot-threshold-settings {
box-shadow: none;
&.flot-threshold {
border: 1px groove rgba(0, 0, 0, .25);
.mat-expansion-panel-header {
padding: 0 24px 0 8px;
&.mat-expanded {
height: 48px;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.12);
.mat-expansion-panel-header {
height: 56px;
border-radius: 0;
display: flex;
flex-direction: row;
align-items: stretch;
.tb-threshold-header {
flex: 1;
display: flex;
flex-direction: row;
gap: 16px;
align-items: center;
padding-left: 16px;
.mat-divider-vertical {
height: 100%;
}
}
.tb-threshold-text {
flex: 1;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 16px;
letter-spacing: 0.15px;
}
.mat-expansion-indicator {
margin-right: 22px;
margin-left: 22px;
margin-top: 12px;
}
}
}
}
:host ::ng-deep {
.mat-expansion-panel {
&.flot-threshold {
.mat-expansion-panel-body {
padding: 0 8px 8px;
> .mat-expansion-panel-content {
> .mat-expansion-panel-body {
padding: 16px !important;
}
}
&.mat-expanded {
.mat-expansion-panel-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
}
}
> .mdc-icon-button {
margin-top: 4px;
color: rgba(0, 0, 0, 0.54);
}
}

15
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts

@ -15,8 +15,14 @@
///
import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component';
import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Component, EventEmitter, forwardRef, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormGroup,
Validators
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@ -28,14 +34,15 @@ import { TbFlotKeyThreshold } from '@home/components/widget/lib/flot-widget.mode
@Component({
selector: 'tb-flot-threshold',
templateUrl: './flot-threshold.component.html',
styleUrls: ['./flot-threshold.component.scss', './../widget-settings.scss'],
styleUrls: ['./flot-threshold.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FlotThresholdComponent),
multi: true
}
]
],
encapsulation: ViewEncapsulation.None
})
export class FlotThresholdComponent extends PageComponent implements OnInit, ControlValueAccessor {

496
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html

@ -15,35 +15,33 @@
limitations under the License.
-->
<section class="tb-widget-settings" [formGroup]="flotSettingsFormGroup" fxLayout="column">
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.chart.common-settings</legend>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px" style="margin-bottom: 16px;">
<mat-checkbox fxFlex formControlName="stack">
{{ 'widgets.chart.enable-stacking-mode' | translate }}
</mat-checkbox>
<mat-checkbox fxFlex formControlName="enableSelection">
{{ 'widgets.chart.enable-selection-mode' | translate }}
</mat-checkbox>
</section>
<section *ngIf="chartType === 'graph'" fxLayout="column" fxLayout.gt-xs="row"
fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.line-shadow-size</mat-label>
<input matInput type="number" min="0" formControlName="shadowSize">
<ng-container [formGroup]="flotSettingsFormGroup">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.chart.common-settings</div>
<mat-slide-toggle class="mat-slide" formControlName="stack">
{{ 'widgets.chart.enable-stacking-mode' | translate }}
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="enableSelection">
{{ 'widgets.chart.enable-selection-mode' | translate }}
</mat-slide-toggle>
<mat-slide-toggle *ngIf="chartType === 'graph'" class="mat-slide" formControlName="smoothLines">
{{ 'widgets.chart.display-smooth-lines' | translate }}
</mat-slide-toggle>
<div *ngIf="chartType === 'graph'" class="tb-form-row space-between">
<div translate>widgets.chart.line-shadow-size</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="shadowSize" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-checkbox fxFlex formControlName="smoothLines">
{{ 'widgets.chart.display-smooth-lines' | translate }}
</mat-checkbox>
</section>
<section *ngIf="chartType === 'bar'" fxLayout="column" fxLayout.gt-xs="row"
fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.default-bar-width</mat-label>
<input matInput type="number" min="0" formControlName="defaultBarWidth">
</div>
<div *ngIf="chartType === 'bar'" class="tb-form-row space-between">
<div translate>widgets.chart.default-bar-width</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="defaultBarWidth" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.bar-alignment</mat-label>
</div>
<div *ngIf="chartType === 'bar'" class="tb-form-row space-between">
<div>{{ 'widgets.chart.bar-alignment' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="barAlignment">
<mat-option [value]="'left'">
{{ 'widgets.chart.bar-alignment-left' | translate }}
@ -56,211 +54,255 @@
</mat-option>
</mat-select>
</mat-form-field>
</section>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.default-font-size</mat-label>
<input matInput type="number" min="0" formControlName="fontSize">
</div>
<div *ngIf="chartType === 'graph' || chartType === 'bar'" class="tb-form-row space-between">
<div translate>widgets.chart.thresholds-line-width</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="thresholdsLineWidth" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-color-input fxFlex
formControlName="fontColor"
icon="format_color_fill"
label="{{ 'widgets.chart.default-font-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
</section>
<mat-form-field *ngIf="chartType === 'graph' || chartType === 'bar'" fxFlex class="mat-block">
<mat-label translate>widgets.chart.thresholds-line-width</mat-label>
<input matInput type="number" min="0" formControlName="thresholdsLineWidth">
</mat-form-field>
</fieldset>
<fieldset *ngIf="chartType === 'graph' || chartType === 'bar'" class="fields-group fields-group-slider">
<legend class="group-title" translate>widget-config.legend</legend>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widgets.chart.default-font' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="fontSize" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="fontColor">
</tb-color-input>
</div>
</div>
</div>
<div *ngIf="chartType === 'graph' || chartType === 'bar'" class="tb-form-panel tb-slide-toggle">
<div class="tb-form-panel-title" translate>widget-config.legend</div>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row" style="min-height: 48px;">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle formControlName="showLegend" (click)="$event.stopPropagation()"
fxLayoutAlign="center" style="height: 100%;">
{{ 'widget-config.display-legend' | translate }}
<mat-slide-toggle class="mat-slide" formControlName="showLegend" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widget-config.legend' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxFlex="40" style="white-space: nowrap;" fxLayoutAlign="end center" fxHide.xs translate>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-legend-config formControlName="legendConfig"></tb-legend-config>
<tb-legend-config formControlName="legendConfig">
</tb-legend-config>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.chart.tooltip-settings</legend>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.chart.axis</div>
<div class="tb-form-panel stroked" formGroupName="yaxis">
<div class="tb-form-panel-title" translate>widgets.chart.vertical-axis</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widgets.chart.axis-title</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.chart.min-scale-value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="min" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.chart.max-scale-value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="max" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-panel stroked tb-slide-toggle">
<div class="tb-form-panel-title" translate>widgets.chart.ticks</div>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('yaxis.showLabels').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="showLabels" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.ticks' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tickDecimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.chart.tick-step-size</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tickSize" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<tb-js-func
formControlName="ticksFormatter"
[globalVariables]="functionScopeVariables"
[functionArgs]="['value']"
functionTitle="{{ 'widgets.chart.ticks-formatter-function' | translate }}"
helpId="widget/lib/flot/ticks_formatter_fn">
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</div>
</div>
<div class="tb-form-panel stroked" formGroupName="xaxis">
<div class="tb-form-panel-title" translate>widgets.chart.horizontal-axis</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widgets.chart.axis-title</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-panel stroked tb-slide-toggle">
<div class="tb-form-panel-title" translate>widgets.chart.ticks</div>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('xaxis.showLabels').value">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="showLabels" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.ticks' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</div>
</div>
</div>
<div class="tb-form-panel" formGroupName="grid">
<div class="tb-form-panel-title" translate>widgets.chart.chart-background</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="verticalLines">
{{ 'widgets.chart.vertical-grid-lines' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="horizontalLines">
{{ 'widgets.chart.horizontal-grid-lines' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widgets.chart.grid-lines-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="tickColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widgets.chart.border' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="outlineWidth" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widgets.chart.background-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="backgroundColor">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle">
<div class="tb-form-panel-title" translate>widgets.chart.tooltip</div>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('showTooltip').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="showTooltip" (click)="$event.stopPropagation()"
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.show-tooltip' | translate }}
{{ 'widgets.chart.tooltip' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section fxLayout="column">
<mat-slide-toggle formControlName="tooltipIndividual" class="slide-block">
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="tooltipIndividual">
{{ 'widgets.chart.hover-individual-points' | translate }}
</mat-slide-toggle>
<mat-slide-toggle formControlName="tooltipCumulative" class="slide-block">
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="tooltipCumulative">
{{ 'widgets.chart.show-cumulative-values' | translate }}
</mat-slide-toggle>
<mat-slide-toggle formControlName="hideZeros" class="slide-block">
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="hideZeros">
{{ 'widgets.chart.hide-zero-false-values' | translate }}
</mat-slide-toggle>
<tb-js-func
formControlName="tooltipValueFormatter"
[globalVariables]="functionScopeVariables"
[functionArgs]="['value', 'latestData']"
functionTitle="{{ 'widgets.chart.tooltip-value-format-function' | translate }}"
helpId="widget/lib/flot/tooltip_value_format_fn">
</tb-js-func>
</section>
</div>
<tb-js-func
formControlName="tooltipValueFormatter"
[globalVariables]="functionScopeVariables"
[functionArgs]="['value', 'latestData']"
functionTitle="{{ 'widgets.chart.tooltip-value-format-function' | translate }}"
helpId="widget/lib/flot/tooltip_value_format_fn">
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset class="fields-group" formGroupName="grid">
<legend class="group-title" translate>widgets.chart.grid-settings</legend>
<mat-checkbox formControlName="verticalLines">
{{ 'widgets.chart.show-vertical-lines' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="horizontalLines">
{{ 'widgets.chart.show-horizontal-lines' | translate }}
</mat-checkbox>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.grid-outline-border-width</mat-label>
<input matInput type="number" min="0" formControlName="outlineWidth">
</mat-form-field>
<tb-color-input fxFlex
formControlName="color"
icon="format_color_fill"
label="{{ 'widgets.chart.primary-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
<tb-color-input fxFlex
formControlName="backgroundColor"
icon="format_color_fill"
label="{{ 'widgets.chart.background-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
<tb-color-input fxFlex
formControlName="tickColor"
icon="format_color_fill"
label="{{ 'widgets.chart.ticks-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
</fieldset>
<fieldset class="fields-group" formGroupName="xaxis">
<legend class="group-title" translate>widgets.chart.xaxis-settings</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.axis-title</mat-label>
<input matInput formControlName="title">
</mat-form-field>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.chart.xaxis-tick-labels-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('xaxis.showLabels').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="showLabels" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.show-tick-labels' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-color-input fxFlex
formControlName="color"
icon="format_color_fill"
label="{{ 'widgets.chart.ticks-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
</ng-template>
</mat-expansion-panel>
</fieldset>
</fieldset>
<fieldset class="fields-group" formGroupName="yaxis">
<legend class="group-title" translate>widgets.chart.yaxis-settings</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.axis-title</mat-label>
<input matInput formControlName="title">
</mat-form-field>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.min-scale-value</mat-label>
<input matInput type="number" formControlName="min">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.max-scale-value</mat-label>
<input matInput type="number" formControlName="max">
</mat-form-field>
</section>
<fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.chart.yaxis-tick-labels-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('yaxis.showLabels').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="showLabels" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.show-tick-labels' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-color-input fxFlex
formControlName="color"
icon="format_color_fill"
label="{{ 'widgets.chart.ticks-color' | translate }}" openOnInput colorClearButton>
</tb-color-input>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.tick-step-size</mat-label>
<input matInput type="number" formControlName="tickSize">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.number-of-decimals</mat-label>
<input matInput type="number" min="0" formControlName="tickDecimals">
</mat-form-field>
</section>
<tb-js-func
formControlName="ticksFormatter"
[globalVariables]="functionScopeVariables"
[functionArgs]="['value']"
functionTitle="{{ 'widgets.chart.ticks-formatter-function' | translate }}"
helpId="widget/lib/flot/ticks_formatter_fn">
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</fieldset>
</fieldset>
<fieldset *ngIf="chartType === 'graph' || chartType === 'bar'" class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.chart.comparison-settings</legend>
</div>
<div *ngIf="chartType === 'graph' || chartType === 'bar'" class="tb-form-panel tb-slide-toggle">
<div class="tb-form-panel-title" translate>widgets.chart.comparison-settings</div>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('comparisonEnabled').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle formControlName="comparisonEnabled" (click)="$event.stopPropagation()"
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle class="mat-slide" formControlName="comparisonEnabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.enable-comparison' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<section fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.time-for-comparison</mat-label>
<div class="tb-form-row space-between">
<div>{{ 'widgets.chart.time-for-comparison' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="timeForComparison">
<mat-option [value]="'previousInterval'">
{{ 'widgets.chart.time-for-comparison-previous-interval' | translate }}
@ -282,18 +324,29 @@
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field [fxShow]="flotSettingsFormGroup.get('timeForComparison').value === 'customInterval'" fxFlex class="mat-block">
<mat-label translate>widgets.chart.custom-interval-value</mat-label>
<input matInput type="number" min="0" formControlName="comparisonCustomIntervalValue">
</div>
<div [fxShow]="flotSettingsFormGroup.get('timeForComparison').value === 'customInterval'" class="tb-form-row space-between">
<div translate>widgets.chart.custom-interval-value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="comparisonCustomIntervalValue" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<fieldset class="fields-group" formGroupName="xaxisSecond">
<legend class="group-title" translate>widgets.chart.comparison-x-axis-settings</legend>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.axis-title</mat-label>
<input matInput formControlName="title">
</div>
<div class="tb-form-panel stroked" formGroupName="xaxisSecond">
<div class="tb-form-panel-title" translate>widgets.chart.comparison-x-axis-settings</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widgets.chart.axis-title</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.chart.axis-position</mat-label>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showLabels">
{{ 'widgets.chart.show-tick-labels' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.chart.axis-position' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="axisPosition">
<mat-option [value]="'top'">
{{ 'widgets.chart.axis-position-top' | translate }}
@ -303,31 +356,28 @@
</mat-option>
</mat-select>
</mat-form-field>
<mat-slide-toggle formControlName="showLabels" class="slide-block">
{{ 'widgets.chart.show-tick-labels' | translate }}
</mat-slide-toggle>
</fieldset>
</section>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</fieldset>
<fieldset *ngIf="chartType === 'graph' || chartType === 'bar'" class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.chart.custom-legend-settings</legend>
</div>
<div *ngIf="chartType === 'graph' || chartType === 'bar'" class="tb-form-panel tb-slide-toggle">
<div class="tb-form-panel-title" translate>widgets.chart.custom-legend-settings</div>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('customLegendEnabled').value">
<mat-expansion-panel-header fxLayout="row" style="height: max-content; min-height: 48px;">
<mat-expansion-panel-header fxLayout="row wrap" class="fill-width">
<mat-panel-title fxFlex="60">
<mat-slide-toggle formControlName="customLegendEnabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center" style="height: 100%;">
<mat-slide-toggle class="mat-slide" formControlName="customLegendEnabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.chart.enable-custom-legend' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxFlex="40" style="white-space: nowrap;" fxLayoutAlign="end center" fxHide.xs translate>
<mat-panel-description fxFlex="40" fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<fieldset class="fields-group">
<legend class="group-title" translate>widgets.chart.label-keys-list</legend>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.chart.label-keys-list</div>
<div fxLayout="column">
<div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
(cdkDropListDropped)="labelDataKeyDrop($event)">
@ -353,8 +403,8 @@
</button>
</div>
</div>
</fieldset>
</div>
</ng-template>
</mat-expansion-panel>
</fieldset>
</section>
</div>
</ng-container>

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.ts

@ -284,6 +284,7 @@ export class FlotWidgetSettingsComponent extends PageComponent implements OnInit
this.flotSettingsFormGroup.disable({emitEvent: false});
} else {
this.flotSettingsFormGroup.enable({emitEvent: false});
this.updateValidators(false);
}
}
@ -379,9 +380,11 @@ export class FlotWidgetSettingsComponent extends PageComponent implements OnInit
} else {
this.flotSettingsFormGroup.get('comparisonCustomIntervalValue').disable({emitEvent});
}
this.flotSettingsFormGroup.get('xaxisSecond').enable({emitEvent: false});
} else {
this.flotSettingsFormGroup.get('timeForComparison').disable({emitEvent: false});
this.flotSettingsFormGroup.get('comparisonCustomIntervalValue').disable({emitEvent});
this.flotSettingsFormGroup.get('xaxisSecond').disable({emitEvent: false});
}
if (customLegendEnabled) {
this.flotSettingsFormGroup.get('dataKeysListForLabels').enable({emitEvent});
@ -392,6 +395,7 @@ export class FlotWidgetSettingsComponent extends PageComponent implements OnInit
this.flotSettingsFormGroup.get('legendConfig').updateValueAndValidity({emitEvent: false});
this.flotSettingsFormGroup.get('timeForComparison').updateValueAndValidity({emitEvent: false});
this.flotSettingsFormGroup.get('comparisonCustomIntervalValue').updateValueAndValidity({emitEvent: false});
this.flotSettingsFormGroup.get('xaxisSecond').updateValueAndValidity({emitEvent: false});
this.flotSettingsFormGroup.get('dataKeysListForLabels').updateValueAndValidity({emitEvent: false});
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/label-data-key.component.ts

@ -47,7 +47,7 @@ export function labelDataKeyValidator(control: AbstractControl): ValidationError
@Component({
selector: 'tb-label-data-key',
templateUrl: './label-data-key.component.html',
styleUrls: ['./label-data-key.component.scss', './../widget-settings.scss'],
styleUrls: ['./label-data-key.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,

56
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.html

@ -15,20 +15,21 @@
limitations under the License.
-->
<form [formGroup]="legendConfigForm">
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
fxLayoutGap="8px">
<mat-form-field fxFlex>
<mat-label translate>legend.direction</mat-label>
<mat-select matInput formControlName="direction">
<ng-container [formGroup]="legendConfigForm">
<div class="tb-form-row space-between">
<div>{{ 'legend.direction' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="direction">
<mat-option *ngFor="let direction of legendDirections" [value]="direction">
{{ legendDirectionTranslations.get(legendDirection[direction]) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label translate>legend.position</mat-label>
<mat-select matInput formControlName="position">
</div>
<div class="tb-form-row space-between">
<div>{{ 'legend.position' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="position">
<mat-option *ngFor="let pos of legendPositions" [value]="pos"
[disabled]="legendConfigForm.get('direction').value === legendDirection.row &&
(pos === legendPosition.left || pos === legendPosition.right)">
@ -37,28 +38,17 @@
</mat-select>
</mat-form-field>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-checkbox formControlName="showMin" fxFlex>
{{ 'legend.show-min' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="showMax" fxFlex>
{{ 'legend.show-max' | translate }}
</mat-checkbox>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-checkbox formControlName="showAvg" fxFlex>
{{ 'legend.show-avg' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="showTotal" fxFlex>
{{ 'legend.show-total' | translate }}
</mat-checkbox>
</div>
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-checkbox formControlName="showLatest" fxFlex>
{{ 'legend.show-latest' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="sortDataKeys" fxFlex>
{{ 'legend.sort-legend' | translate }}
</mat-checkbox>
<div class="tb-form-row space-between">
<div translate>legend.show-values</div>
<mat-chip-listbox multiple formControlName="showValues">
<mat-chip-option value="min">{{ 'legend.min-option' | translate }}</mat-chip-option>
<mat-chip-option value="max">{{ 'legend.max-option' | translate }}</mat-chip-option>
<mat-chip-option value="average">{{ 'legend.average-option' | translate }}</mat-chip-option>
<mat-chip-option value="total">{{ 'legend.total-option' | translate }}</mat-chip-option>
<mat-chip-option value="latest">{{ 'legend.latest-option' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
</form>
<mat-slide-toggle class="mat-slide" formControlName="sortDataKeys">
{{ 'legend.sort-legend' | translate }}
</mat-slide-toggle>
</ng-container>

57
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.ts

@ -15,7 +15,7 @@
///
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { isDefined } from '@core/utils';
import {
LegendConfig,
@ -30,7 +30,7 @@ import { Subscription } from 'rxjs';
@Component({
selector: 'tb-legend-config',
templateUrl: './legend-config.component.html',
styleUrls: [],
styleUrls: ['./../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
@ -62,12 +62,8 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
this.legendConfigForm = this.fb.group({
direction: [null, []],
position: [null, []],
sortDataKeys: [null, []],
showMin: [null, []],
showMax: [null, []],
showAvg: [null, []],
showTotal: [null, []],
showLatest: [null, []]
showValues: [[], []],
sortDataKeys: [null, []]
});
this.legendSettingsFormDirectionChanges$ = this.legendConfigForm.get('direction').valueChanges
.subscribe((direction: LegendDirection) => {
@ -121,18 +117,49 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
this.legendConfigForm.patchValue({
direction: legendConfig.direction,
position: legendConfig.position,
sortDataKeys: isDefined(legendConfig.sortDataKeys) ? legendConfig.sortDataKeys : false,
showMin: isDefined(legendConfig.showMin) ? legendConfig.showMin : false,
showMax: isDefined(legendConfig.showMax) ? legendConfig.showMax : false,
showAvg: isDefined(legendConfig.showAvg) ? legendConfig.showAvg : false,
showTotal: isDefined(legendConfig.showTotal) ? legendConfig.showTotal : false,
showLatest: isDefined(legendConfig.showLatest) ? legendConfig.showLatest : false
showValues: this.getShowValues(legendConfig),
sortDataKeys: isDefined(legendConfig.sortDataKeys) ? legendConfig.sortDataKeys : false
}, {emitEvent: false});
}
this.onDirectionChanged(legendConfig.direction);
}
private legendConfigUpdated() {
this.propagateChange(this.legendConfigForm.value);
const configValue = this.legendConfigForm.value;
const legendConfig: Partial<LegendConfig> = {
direction: configValue.direction,
position: configValue.position,
sortDataKeys: configValue.sortDataKeys
};
this.setShowValues(configValue.showValues, legendConfig);
this.propagateChange(legendConfig);
}
private getShowValues(legendConfig: LegendConfig): string[] {
const showValues: string[] = [];
if (isDefined(legendConfig.showMin) && legendConfig.showMin) {
showValues.push('min');
}
if (isDefined(legendConfig.showMax) && legendConfig.showMax) {
showValues.push('max');
}
if (isDefined(legendConfig.showAvg) && legendConfig.showAvg) {
showValues.push('average');
}
if (isDefined(legendConfig.showTotal) && legendConfig.showTotal) {
showValues.push('total');
}
if (isDefined(legendConfig.showLatest) && legendConfig.showLatest) {
showValues.push('latest');
}
return showValues;
}
private setShowValues(showValues: string[], legendConfig: Partial<LegendConfig>) {
legendConfig.showMin = showValues.includes('min');
legendConfig.showMax = showValues.includes('max');
legendConfig.showAvg = showValues.includes('average');
legendConfig.showTotal = showValues.includes('total');
legendConfig.showLatest = showValues.includes('latest');
}
}

50
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.html

@ -15,27 +15,27 @@
limitations under the License.
-->
<section [formGroup]="valueSourceFormGroup" fxLayout="column">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>widgets.value-source.value-source</mat-label>
<mat-select formControlName="valueSource">
<mat-option [value]="'predefinedValue'">
<ng-container [formGroup]="valueSourceFormGroup">
<div style="display: flex;">
<tb-toggle-select formControlName="valueSource" style="min-width: 300px;">
<tb-toggle-option [value]="'predefinedValue'">
{{ 'widgets.value-source.predefined-value' | translate }}
</mat-option>
<mat-option [value]="'entityAttribute'">
</tb-toggle-option>
<tb-toggle-option [value]="'entityAttribute'">
{{ 'widgets.value-source.entity-attribute' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field [fxShow]="valueSourceFormGroup.get('valueSource').value === 'predefinedValue'" fxFlex class="mat-block">
<mat-label translate>widgets.value-source.value</mat-label>
<input matInput type="number" formControlName="value">
</mat-form-field>
<section [fxShow]="valueSourceFormGroup.get('valueSource').value === 'entityAttribute'"
fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px" fxLayoutAlign="start center">
<mat-form-field fxFlex class="mat-block">
<mat-label>{{ 'widgets.value-source.source-entity-alias' | translate }}</mat-label>
<input matInput type="text" placeholder="{{ 'entity.entity-alias' | translate }}"
</tb-toggle-option>
</tb-toggle-select>
</div>
<div [fxShow]="valueSourceFormGroup.get('valueSource').value === 'predefinedValue'" class="tb-form-row space-between">
<div translate>widgets.value-source.value</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="value" type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div [fxShow]="valueSourceFormGroup.get('valueSource').value === 'entityAttribute'" class="tb-form-row space-between">
<div translate>widgets.value-source.source-entity-alias</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<input matInput type="text" placeholder="{{ 'widget-config.set' | translate }}"
#entityAliasInput
formControlName="entityAlias"
[matAutocomplete]="entityAliasAutocomplete">
@ -53,9 +53,11 @@
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>{{ 'widgets.value-source.source-entity-attribute' | translate }}</mat-label>
<input matInput type="text" placeholder="{{ 'entity.key-name' | translate }}"
</div>
<div [fxShow]="valueSourceFormGroup.get('valueSource').value === 'entityAttribute'" class="tb-form-row space-between">
<div translate>widgets.value-source.source-entity-attribute</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<input matInput type="text" placeholder="{{ 'widget-config.set' | translate }}"
#keyInput
formControlName="attribute"
[matAutocomplete]="keyAutocomplete">
@ -73,5 +75,5 @@
</mat-option>
</mat-autocomplete>
</mat-form-field>
</section>
</section>
</div>
</ng-container>

14
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source.component.ts

@ -14,8 +14,8 @@
/// limitations under the License.
///
import { Component, ElementRef, forwardRef, HostBinding, Input, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@ -39,7 +39,7 @@ export interface ValueSourceProperty {
@Component({
selector: 'tb-value-source',
templateUrl: './value-source.component.html',
styleUrls: [],
styleUrls: ['./../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
@ -50,8 +50,6 @@ export interface ValueSourceProperty {
})
export class ValueSourceComponent extends PageComponent implements OnInit, ControlValueAccessor {
@HostBinding('style.display') display = 'block';
@ViewChild('entityAliasInput') entityAliasInput: ElementRef;
@ViewChild('keyInput') keyInput: ElementRef;
@ -212,15 +210,13 @@ export class ValueSourceComponent extends PageComponent implements OnInit, Contr
private fetchEntityKeys(entityAliasId: string, dataKeyTypes: Array<DataKeyType>): Observable<Array<DataKey>> {
return this.aliasController.getAliasInfo(entityAliasId).pipe(
mergeMap((aliasInfo) => {
return this.entityService.getEntityKeysByEntityFilter(
mergeMap((aliasInfo) => this.entityService.getEntityKeysByEntityFilter(
aliasInfo.entityFilter,
dataKeyTypes, [],
{ignoreLoading: true, ignoreErrors: true}
).pipe(
catchError(() => of([]))
);
}),
)),
catchError(() => of([] as Array<DataKey>))
);
}

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/fixed-color-level.component.ts

@ -50,7 +50,7 @@ export function fixedColorLevelValidator(control: AbstractControl): ValidationEr
@Component({
selector: 'tb-fixed-color-level',
templateUrl: './fixed-color-level.component.html',
styleUrls: ['./fixed-color-level.component.scss', './../widget-settings.scss'],
styleUrls: ['./fixed-color-level.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/gauge-highlight.component.ts

@ -31,7 +31,7 @@ export interface GaugeHighlight {
@Component({
selector: 'tb-gauge-highlight',
templateUrl: './gauge-highlight.component.html',
styleUrls: ['./gauge-highlight.component.scss', './../widget-settings.scss'],
styleUrls: ['./gauge-highlight.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts

@ -27,7 +27,7 @@ import { IAliasController } from '@core/api/widget-api.models';
@Component({
selector: 'tb-tick-value',
templateUrl: './tick-value.component.html',
styleUrls: ['./tick-value.component.scss', './../widget-settings.scss'],
styleUrls: ['./tick-value.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/gpio/gpio-item.component.ts

@ -56,7 +56,7 @@ export const gpioItemValidator = (hasColor: boolean): ValidatorFn => (control: A
@Component({
selector: 'tb-gpio-item',
templateUrl: './gpio-item.component.html',
styleUrls: ['./gpio-item.component.scss', './../widget-settings.scss'],
styleUrls: ['./gpio-item.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/input/datakey-select-option.component.ts

@ -46,7 +46,7 @@ export const dataKeySelectOptionValidator = (control: AbstractControl) => {
@Component({
selector: 'tb-datakey-select-option',
templateUrl: './datakey-select-option.component.html',
styleUrls: ['./datakey-select-option.component.scss', './../widget-settings.scss'],
styleUrls: ['./datakey-select-option.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,

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

@ -17,9 +17,9 @@
-->
<div class="tb-widget-config tb-absolute-fill">
<div class="tb-widget-config-header" *ngIf="!hideHeader">
<tb-toggle-header *ngIf="!hideToggleHeader && widgetConfigMode === widgetConfigModes.advanced" (valueChange)="selectedOption = $event" ignoreMdLgSize="true"
appearance="fill" [options]="headerOptions" [value]="selectedOption" name="widgetConfigHeader" useSelectOnMdLg="false">
</tb-toggle-header>
<tb-toggle-select *ngIf="!hideToggleHeader && widgetConfigMode === widgetConfigModes.advanced"
appearance="fill" [options]="headerOptions" [(ngModel)]="selectedOption">
</tb-toggle-select>
<ng-content select=".tb-widget-config-header-prefix"></ng-content>
<ng-content select=".tb-widget-config-header-suffix"></ng-content>
</div>
@ -31,22 +31,24 @@
<ng-container *ngTemplateOutlet="appearance"></ng-container>
</div>
<div [fxShow]="selectedOption === 'card'" [formGroup]="widgetSettings" class="mat-content">
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widget-config.card-title</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-title</div>
<mat-slide-toggle class="mat-slide" formControlName="showTitle">
{{ 'widget-config.display-title' | translate }}
</mat-slide-toggle>
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex>
<mat-label translate>widget-config.title</mat-label>
<input matInput formControlName="title">
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-config.title</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-form-field fxFlex>
<mat-label translate>widget-config.title-tooltip</mat-label>
<input matInput formControlName="titleTooltip">
</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-config.title-tooltip</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="titleTooltip" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-row space-between same-padding">
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
{{ 'widget-config.display-icon' | translate }}
</mat-slide-toggle>
@ -55,7 +57,7 @@
[color]="widgetSettings.get('iconColor').value"
formControlName="titleIcon">
</tb-material-icon-select>
<mat-form-field appearance="outline" subscriptSizing="dynamic" style="width: 60px;">
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="iconSize">
</mat-form-field>
<mat-divider vertical></mat-divider>
@ -80,9 +82,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widget-config.card-style</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-style</div>
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.text-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
@ -91,7 +93,7 @@
</tb-color-input>
</div>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div class="tb-form-row space-between same-padding">
<div>{{ 'widget-config.background-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
@ -100,18 +102,24 @@
</tb-color-input>
</div>
</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div>{{ 'widget-config.padding' | translate }}</div>
<mat-form-field appearance="outline" class="center" subscriptSizing="dynamic">
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="padding" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div>{{ 'widget-config.margin' | translate }}</div>
<mat-form-field appearance="outline" class="center" subscriptSizing="dynamic">
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="margin" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.border-radius' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<mat-slide-toggle class="mat-slide slide-block" formControlName="dropShadow">
{{ 'widget-config.drop-shadow' | translate }}
</mat-slide-toggle>
@ -124,7 +132,6 @@
<ng-template matExpansionPanelContent>
<tb-json-object-edit
[editorStyle]="{minHeight: '100px'}"
required
label="{{ 'widget-config.widget-style' | translate }}"
formControlName="widgetStyle"
></tb-json-object-edit>
@ -135,15 +142,15 @@
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widget-config.card-buttons</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-buttons</div>
<mat-slide-toggle class="mat-slide slide-block" formControlName="enableFullscreen">
{{ 'widget-config.enable-fullscreen' | translate }}
</mat-slide-toggle>
</div>
</div>
<div [fxShow]="selectedOption === 'actions'" [formGroup]="actionsSettings" class="mat-content" style="height: 100%;">
<div class="tb-widget-config-panel" style="height: 100%;">
<div class="tb-form-panel" style="height: 100%;">
<tb-manage-widget-actions
[callbacks]="widgetConfigCallbacks"
[widgetType] = "modelValue.widgetType"
@ -153,26 +160,26 @@
</div>
</div>
<div [fxShow]="selectedOption === 'mobile'" [formGroup]="layoutSettings" class="mat-content">
<div class="tb-widget-config-panel">
<div class="tb-form-panel">
<mat-slide-toggle class="mat-slide" formControlName="mobileHide">
{{ 'widget-config.mobile-hide' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-widget-config-panel">
<div class="tb-form-panel">
<mat-slide-toggle class="mat-slide" formControlName="desktopHide">
{{ 'widget-config.desktop-hide' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-row space-between">
<div class="tb-form-panel">
<div class="tb-form-row space-between">
<div translate>widget-config.order</div>
<mat-form-field appearance="outline" class="number center" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="mobileOrder" type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div class="tb-form-row space-between">
<div translate>widget-config.height</div>
<mat-form-field appearance="outline" class="number center" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="mobileHeight" type="number" min="1" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
@ -205,8 +212,16 @@
formControlName="timewindowConfig">
</tb-timewindow-config-panel>
</ng-container>
<div *ngIf="widgetType === widgetTypes.alarm" [formGroup]="dataSettings" class="tb-widget-config-panel" fxLayout="column" fxLayoutAlign="center">
<tb-alarm-filter-config buttonMode="false" formControlName="alarmFilterConfig"></tb-alarm-filter-config>
<div *ngIf="widgetType === widgetTypes.alarm" [formGroup]="dataSettings" class="tb-form-panel">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div class="tb-form-panel-title" translate>alarm.filter</div>
<button mat-button color="primary"
(click)="alarmFilterConfig.reset()">
{{ 'action.reset' | translate }}
</button>
</div>
<tb-alarm-filter-config #alarmFilterConfig buttonMode="false"
formControlName="alarmFilterConfig" [initialAlarmFilterConfig]="{}"></tb-alarm-filter-config>
</div>
<div *ngIf="widgetType !== widgetTypes.rpc &&
widgetType !== widgetTypes.alarm &&
@ -217,9 +232,9 @@
formControlName="datasources">
</tb-datasources>
</div>
<div class="tb-widget-config-panel" *ngIf="widgetType === widgetTypes.rpc &&
<div class="tb-form-panel" *ngIf="widgetType === widgetTypes.rpc &&
modelValue?.isDataEnabled">
<div class="tb-widget-config-panel-title" translate>widget-config.target-device</div>
<div class="tb-form-panel-title" translate>widget-config.target-device</div>
<div [formGroup]="targetDeviceSettings">
<tb-entity-alias-select fxFlex
[tbRequired]="!widgetEditMode"
@ -231,39 +246,39 @@
</div>
</div>
<div *ngIf="widgetType === widgetTypes.alarm && modelValue?.isDataEnabled"
[formGroup]="dataSettings" class="tb-widget-config-panel" >
<div class="tb-widget-config-panel-title" translate>widget-config.alarm-source</div>
[formGroup]="dataSettings" class="tb-form-panel" >
<div class="tb-form-panel-title" translate>widget-config.alarm-source</div>
<tb-datasource
formControlName="alarmSource">
</tb-datasource>
</div>
<div *ngIf="displayLimits" class="tb-widget-config-panel" [formGroup]="widgetSettings">
<div class="tb-widget-config-panel-title" translate>widget-config.limits</div>
<div class="tb-widget-config-row space-between">
<div *ngIf="displayLimits" class="tb-form-panel" [formGroup]="widgetSettings">
<div class="tb-form-panel-title" translate>widget-config.limits</div>
<div class="tb-form-row space-between">
<div translate>widget-config.data-page-size</div>
<mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pageSize" type="number" min="1" step="1">
</mat-form-field>
</div>
</div>
</ng-template>
<ng-template #appearance>
<div *ngIf="displayAppearanceDataSettings" class="tb-widget-config-panel" [formGroup]="widgetSettings">
<div class="tb-widget-config-panel-title" translate>widget-config.data-settings</div>
<div *ngIf="displayUnitsConfig" class="tb-widget-config-row space-between">
<div *ngIf="displayAppearanceDataSettings" class="tb-form-panel" [formGroup]="widgetSettings">
<div class="tb-form-panel-title" translate>widget-config.data-settings</div>
<div *ngIf="displayUnitsConfig" class="tb-form-row space-between">
<div translate>widget-config.units</div>
<tb-widget-units
<tb-unit-input
formControlName="units">
</tb-widget-units>
</tb-unit-input>
</div>
<div *ngIf="displayUnitsConfig" class="tb-widget-config-row space-between">
<div *ngIf="displayUnitsConfig" class="tb-form-row space-between">
<div translate>widget-config.decimals</div>
<mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div *ngIf="displayNoDataDisplayMessageConfig" class="tb-widget-config-row">
<div translate>widget-config.no-data-display-message</div>
<div *ngIf="displayNoDataDisplayMessageConfig" class="tb-form-row">
<div class="fixed-title-width" translate>widget-config.no-data-display-message</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="noDataDisplayMessage" placeholder="{{ 'widget-config.set-message' | translate }}">
</mat-form-field>

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

@ -224,6 +224,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
color: [null, []],
padding: [null, []],
margin: [null, []],
borderRadius: [null, []],
widgetStyle: [null, []],
widgetCss: [null, []],
titleStyle: [null, []],
@ -484,6 +485,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
color: config.color,
padding: config.padding,
margin: config.margin,
borderRadius: config.borderRadius,
widgetStyle: isDefined(config.widgetStyle) ? config.widgetStyle : {},
widgetCss: isDefined(config.widgetCss) ? config.widgetCss : '',
titleStyle: isDefined(config.titleStyle) ? config.titleStyle : {

1
ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html

@ -18,6 +18,7 @@
<tb-dashboard class="tb-preview-dashboard"
[aliasController]="aliasController"
[stateController]="stateController"
[dashboardTimewindow]="dashboardTimewindow"
[widgets]="widgets"
[autofillHeight]="true"
[columns]="24"

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

@ -21,6 +21,7 @@ import { Widget, WidgetConfig } from '@shared/models/widget.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { deepClone } from '@core/utils';
import { Timewindow } from '@shared/models/time/time.models';
@Component({
selector: 'tb-widget-preview',
@ -35,6 +36,9 @@ export class WidgetPreviewComponent extends PageComponent implements OnInit, OnC
@Input()
stateController: IStateController;
@Input()
dashboardTimewindow: Timewindow;
@Input()
widget: Widget;

143
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html

@ -27,9 +27,12 @@
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<mat-horizontal-stepper [linear]="true" [labelPosition]="labelPosition" #addDeviceWizardStepper (selectionChange)="changeStep($event)">
<mat-horizontal-stepper #addDeviceWizardStepper
[linear]="true"
[labelPosition]="(stepperLabelPosition | async)"
[orientation]="(stepperOrientation | async)"
(selectionChange)="changeStep($event)">
<ng-template matStepperIcon="edit">
<mat-icon>check</mat-icon>
</ng-template>
@ -54,133 +57,49 @@
{{ 'device.label-max-length' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayoutGap="16px">
<mat-radio-group fxLayout="column" formControlName="addProfileType" fxLayoutAlign="space-around">
<mat-radio-button [value]="0" color="primary">
<span translate>device.wizard.existing-device-profile</span>
</mat-radio-button>
<mat-radio-button [value]="1" color="primary">
<span translate>device.wizard.new-device-profile</span>
</mat-radio-button>
</mat-radio-group>
<div fxLayout="column">
<tb-device-profile-autocomplete
[required]="!createProfile"
formControlName="deviceProfileId"
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 0}"
[addNewProfile]="false"
[selectDefaultProfile]="true"
[editProfileEnabled]="false"
(deviceProfileChanged)="deviceProfileChanged($event)">
</tb-device-profile-autocomplete>
<mat-form-field fxFlex class="mat-block"
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 1}">
<mat-label translate>device-profile.new-device-profile-name</mat-label>
<input matInput formControlName="newDeviceProfileTitle"
[required]="createProfile">
<mat-error *ngIf="deviceWizardFormGroup.get('newDeviceProfileTitle').hasError('required')">
{{ 'device-profile.new-device-profile-name-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="column" fxLayoutAlign="flex-end start">
<tb-rule-chain-autocomplete
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 1}"
labelText="device-profile.default-rule-chain"
formControlName="defaultRuleChainId">
</tb-rule-chain-autocomplete>
</div>
<div fxLayout="column" fxLayoutAlign="flex-end start">
<tb-queue-autocomplete
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 1}"
[queueType]="serviceType"
formControlName="defaultQueueName">
</tb-queue-autocomplete>
</div>
</div>
<div fxLayout="row" fxLayoutGap="16px" style="padding-bottom: 16px;">
<mat-checkbox formControlName="gateway">
<tb-device-profile-autocomplete
required
addNewProfile
showCreateNewButton
selectDefaultProfile
formControlName="deviceProfileId"
(deviceProfileChanged)="deviceProfileChanged($event)">
</tb-device-profile-autocomplete>
<div class="toggle-group">
<mat-slide-toggle formControlName="gateway" style="min-width: 165px">
{{ 'device.is-gateway' | translate }}
</mat-checkbox>
<mat-checkbox *ngIf="deviceWizardFormGroup.get('gateway').value"
formControlName="overwriteActivityTime">
</mat-slide-toggle>
<mat-slide-toggle *ngIf="deviceWizardFormGroup.get('gateway').value"
formControlName="overwriteActivityTime">
{{ 'device.overwrite-activity-time' | translate }}
</mat-checkbox>
</mat-slide-toggle>
</div>
<mat-form-field class="mat-block">
<tb-entity-autocomplete
formControlName="customerId"
labelText="device.wizard.customer-to-assign-device"
[entityType]="entityType.CUSTOMER">
</tb-entity-autocomplete>
<mat-form-field class="mat-block" subscriptSizing="dynamic">
<mat-label translate>device.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea>
<textarea matInput formControlName="description" rows="1"></textarea>
</mat-form-field>
</fieldset>
</form>
</mat-step>
<mat-step [stepControl]="transportConfigFormGroup" [optional]="true" *ngIf="createProfile">
<form [formGroup]="transportConfigFormGroup" style="padding-bottom: 16px;">
<ng-template matStepLabel>{{ 'device-profile.transport-configuration' | translate }}</ng-template><mat-form-field class="mat-block" style="padding-bottom: 14px;">
<mat-label translate>device-profile.transport-type</mat-label>
<mat-select formControlName="transportType" required>
<mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
{{deviceTransportTypeTranslations.get(type) | translate}}
</mat-option>
</mat-select>
<mat-hint *ngIf="transportConfigFormGroup.get('transportType').value">
{{deviceTransportTypeHints.get(transportConfigFormGroup.get('transportType').value) | translate}}
</mat-hint>
<mat-error *ngIf="transportConfigFormGroup.get('transportType').hasError('required')">
{{ 'device-profile.transport-type-required' | translate }}
</mat-error>
</mat-form-field>
<tb-device-profile-transport-configuration
formControlName="transportConfiguration"
isAdd="true"
required>
</tb-device-profile-transport-configuration>
</form>
</mat-step>
<mat-step [stepControl]="alarmRulesFormGroup" [optional]="true" *ngIf="createProfile">
<form [formGroup]="alarmRulesFormGroup" style="padding-bottom: 16px;">
<ng-template matStepLabel>{{'device-profile.alarm-rules-with-count' | translate:
{count: alarmRulesFormGroup.get('alarms').value ?
alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
<tb-device-profile-alarms
formControlName="alarms"
[deviceProfileId]="null">
</tb-device-profile-alarms>
</form>
</mat-step>
<mat-step [stepControl]="provisionConfigFormGroup" [optional]="true" *ngIf="createProfile">
<form [formGroup]="provisionConfigFormGroup" style="padding-bottom: 16px;">
<ng-template matStepLabel>{{ 'device-profile.device-provisioning' | translate }}</ng-template>
<tb-device-profile-provision-configuration
formControlName="provisionConfiguration">
</tb-device-profile-provision-configuration>
</form>
</mat-step>
<mat-step [stepControl]="credentialsFormGroup" [optional]="true">
<mat-step [stepControl]="credentialsFormGroup" [optional]="credentialsOptionalStep">
<ng-template matStepLabel>{{ 'device.credentials' | translate }}</ng-template>
<form [formGroup]="credentialsFormGroup" style="padding-bottom: 16px;">
<mat-checkbox style="padding-bottom: 16px;" formControlName="setCredential">{{ 'device.wizard.add-credentials' | translate }}</mat-checkbox>
<tb-device-credentials
[fxShow]="credentialsFormGroup.get('setCredential').value"
[deviceTransportType]="deviceTransportType"
initAccessToken
formControlName="credential">
</tb-device-credentials>
</form>
</mat-step>
<mat-step [stepControl]="customerFormGroup" [optional]="true">
<ng-template matStepLabel>{{ 'customer.customer' | translate }}</ng-template>
<form [formGroup]="customerFormGroup" style="padding-bottom: 16px;">
<tb-entity-autocomplete
formControlName="customerId"
labelText="device.wizard.customer-to-assign-device"
[entityType]="entityType.CUSTOMER">
</tb-entity-autocomplete>
</form>
</mat-step>
</mat-horizontal-stepper>
</div>
<div mat-dialog-actions style="padding: 0">
<div class="dialog-actions-row" fxFlex fxLayout="row" fxLayoutAlign="end center">
<div mat-dialog-actions class="tb-dialog-actions">
<div class="dialog-actions-row">
<button mat-stroked-button *ngIf="selectedIndex > 0"
[disabled]="(isLoading$ | async)"
(click)="previousStep()">{{ 'action.back' | translate }}</button>
@ -192,7 +111,7 @@
(click)="nextStep()">{{ 'action.next-with-label' | translate:{label: (getFormLabel(this.selectedIndex+1) | translate)} }}</button>
</div>
<mat-divider style="width: 100%"></mat-divider>
<div class="dialog-actions-row" fxFlex fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="end center">
<div class="dialog-actions-row">
<button mat-button
[disabled]="(isLoading$ | async)"
(click)="cancel()">{{ 'action.cancel' | translate }}</button>

51
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss

@ -18,49 +18,62 @@
:host {
height: 100%;
display: grid;
grid-template-rows: min-content 4px auto min-content;
.dialog-actions-row {
padding: 8px;
.toggle-group {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 16px;
margin-bottom: 16px;
}
@media #{$mat-sm} {
min-width: 470px;
}
@media #{$mat-gt-sm} {
min-width: 650px;
}
}
:host-context(.tb-fullscreen-dialog .mat-mdc-dialog-container) {
@media #{$mat-lt-sm} {
.mat-mdc-dialog-content {
max-height: 75vh;
:host-context(.mat-mdc-dialog-container) {
.tb-dialog-actions {
padding: 0;
grid-row: 4;
.dialog-actions-row {
padding: 8px;
display: flex;
gap: 8px;
justify-content: flex-end;
flex: 1;
}
}
.invisible{
visibility: hidden;
.mat-mdc-dialog-content {
grid-row: 3;
padding: 0;
}
}
:host ::ng-deep {
.mat-mdc-dialog-content {
display: flex;
flex-direction: column;
height: 100%;
padding: 0 !important;
.mat-stepper-horizontal {
display: flex;
height: 100%;
overflow: hidden;
.mat-horizontal-stepper-wrapper {
flex: 1 1 100%;
width: 100%;
}
.mat-horizontal-content-container {
height: 680px;
height: 500px;
max-height: 100%;
width: 100%;;
overflow-y: auto;
scrollbar-gutter: stable;
@media #{$mat-gt-sm} {
min-width: 500px;
}
}
}
}

319
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts

@ -14,201 +14,83 @@
/// limitations under the License.
///
import { Component, Inject, OnDestroy, SkipSelf, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Component, ViewChild } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import {
createDeviceProfileConfiguration,
createDeviceProfileTransportConfiguration,
DeviceProfile,
DeviceProfileInfo,
DeviceProfileType,
DeviceProvisionConfiguration,
DeviceProvisionType,
DeviceTransportType,
deviceTransportTypeHintMap,
deviceTransportTypeTranslationMap
} from '@shared/models/device.models';
import { MatStepper } from '@angular/material/stepper';
import { AddEntityDialogData } from '@home/models/entity/entity-component.models';
import { Device, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models';
import { MatStepper, StepperOrientation } from '@angular/material/stepper';
import { BaseData, HasId } from '@shared/models/base-data';
import { EntityType } from '@shared/models/entity-type.models';
import { DeviceProfileService } from '@core/http/device-profile.service';
import { EntityId } from '@shared/models/id/entity-id';
import { Observable, of, Subscription, throwError } from 'rxjs';
import { catchError, map, mergeMap, tap } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { DeviceService } from '@core/http/device.service';
import { ErrorStateMatcher } from '@angular/material/core';
import { StepperSelectionEvent } from '@angular/cdk/stepper';
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { ServiceType } from '@shared/models/queue.models';
import { deepTrim } from '@core/utils';
import { CustomerId } from '@shared/models/id/customer-id';
import { HttpErrorResponse } from '@angular/common/http';
@Component({
selector: 'tb-device-wizard',
templateUrl: './device-wizard-dialog.component.html',
providers: [],
styleUrls: ['./device-wizard-dialog.component.scss']
})
export class DeviceWizardDialogComponent extends
DialogComponent<DeviceWizardDialogComponent, boolean> implements OnDestroy, ErrorStateMatcher {
export class DeviceWizardDialogComponent extends DialogComponent<DeviceWizardDialogComponent, boolean> {
@ViewChild('addDeviceWizardStepper', {static: true}) addDeviceWizardStepper: MatStepper;
selectedIndex = 0;
showNext = true;
createProfile = false;
entityType = EntityType;
deviceTransportTypes = Object.values(DeviceTransportType);
deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
stepperOrientation: Observable<StepperOrientation>;
deviceTransportTypeHints = deviceTransportTypeHintMap;
stepperLabelPosition: Observable<'bottom' | 'end'>;
deviceWizardFormGroup: UntypedFormGroup;
transportConfigFormGroup: UntypedFormGroup;
alarmRulesFormGroup: UntypedFormGroup;
selectedIndex = 0;
provisionConfigFormGroup: UntypedFormGroup;
credentialsOptionalStep = true;
credentialsFormGroup: UntypedFormGroup;
showNext = true;
customerFormGroup: UntypedFormGroup;
entityType = EntityType;
labelPosition: MatStepper['labelPosition'] = 'end';
deviceWizardFormGroup: FormGroup;
serviceType = ServiceType.TB_RULE_ENGINE;
credentialsFormGroup: FormGroup;
private subscriptions: Subscription[] = [];
private currentDeviceProfileTransportType = DeviceTransportType.DEFAULT;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData<BaseData<EntityId>>,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<DeviceWizardDialogComponent, boolean>,
private deviceProfileService: DeviceProfileService,
private deviceService: DeviceService,
private breakpointObserver: BreakpointObserver,
private fb: UntypedFormBuilder) {
private fb: FormBuilder) {
super(store, router, dialogRef);
this.stepperOrientation = this.breakpointObserver.observe(MediaBreakpoints['gt-sm'])
.pipe(map(({matches}) => matches ? 'horizontal' : 'vertical'));
this.stepperLabelPosition = this.breakpointObserver.observe(MediaBreakpoints['gt-sm'])
.pipe(map(({matches}) => matches ? 'end' : 'bottom'));
this.deviceWizardFormGroup = this.fb.group({
name: ['', [Validators.required, Validators.maxLength(255)]],
label: ['', Validators.maxLength(255)],
gateway: [false],
overwriteActivityTime: [false],
addProfileType: [0],
customerId: [null],
deviceProfileId: [null, Validators.required],
newDeviceProfileTitle: [{value: null, disabled: true}],
defaultRuleChainId: [{value: null, disabled: true}],
defaultQueueName: [{value: null, disabled: true}],
description: ['']
}
);
this.subscriptions.push(this.deviceWizardFormGroup.get('addProfileType').valueChanges.subscribe(
(addProfileType: number) => {
if (addProfileType === 0) {
this.deviceWizardFormGroup.get('deviceProfileId').setValidators([Validators.required]);
this.deviceWizardFormGroup.get('deviceProfileId').enable();
this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null);
this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable();
this.deviceWizardFormGroup.get('defaultRuleChainId').disable();
this.deviceWizardFormGroup.get('defaultQueueName').disable();
this.deviceWizardFormGroup.updateValueAndValidity();
this.createProfile = false;
} else {
this.deviceWizardFormGroup.get('deviceProfileId').setValidators(null);
this.deviceWizardFormGroup.get('deviceProfileId').disable();
this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]);
this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable();
this.deviceWizardFormGroup.get('defaultRuleChainId').enable();
this.deviceWizardFormGroup.get('defaultQueueName').enable();
this.deviceWizardFormGroup.updateValueAndValidity();
this.createProfile = true;
}
}
));
this.transportConfigFormGroup = this.fb.group(
{
transportType: [DeviceTransportType.DEFAULT, Validators.required],
transportConfiguration: [createDeviceProfileTransportConfiguration(DeviceTransportType.DEFAULT), Validators.required]
}
);
this.subscriptions.push(this.transportConfigFormGroup.get('transportType').valueChanges.subscribe((transportType) => {
this.deviceProfileTransportTypeChanged(transportType);
}));
this.alarmRulesFormGroup = this.fb.group({
alarms: [null]
}
);
this.provisionConfigFormGroup = this.fb.group(
{
provisionConfiguration: [{
type: DeviceProvisionType.DISABLED
} as DeviceProvisionConfiguration, [Validators.required]]
}
);
this.credentialsFormGroup = this.fb.group({
setCredential: [false],
credential: [{value: null, disabled: true}]
}
);
this.subscriptions.push(this.credentialsFormGroup.get('setCredential').valueChanges.subscribe((value) => {
if (value) {
this.credentialsFormGroup.get('credential').enable();
} else {
this.credentialsFormGroup.get('credential').disable();
}
}));
this.customerFormGroup = this.fb.group({
customerId: [null]
credential: []
}
);
this.labelPosition = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']) ? 'end' : 'bottom';
this.subscriptions.push(this.breakpointObserver
.observe(MediaBreakpoints['gt-sm'])
.subscribe((state: BreakpointState) => {
if (state.matches) {
this.labelPosition = 'end';
} else {
this.labelPosition = 'bottom';
}
}
));
}
ngOnDestroy() {
super.ngOnDestroy();
this.subscriptions.forEach(s => s.unsubscribe());
}
isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = !!(control && control.invalid);
return originalErrorState || customErrorState;
}
cancel(): void {
@ -224,24 +106,11 @@ export class DeviceWizardDialogComponent extends
}
getFormLabel(index: number): string {
if (index > 0) {
if (!this.createProfile) {
index += 3;
}
}
switch (index) {
case 0:
return 'device.wizard.device-details';
case 1:
return 'device-profile.transport-configuration';
case 2:
return 'device-profile.alarm-rules';
case 3:
return 'device-profile.device-provisioning';
case 4:
return 'device.credentials';
case 5:
return 'customer.customer';
}
}
@ -249,88 +118,30 @@ export class DeviceWizardDialogComponent extends
return this.addDeviceWizardStepper?._steps?.length - 1;
}
private deviceProfileTransportTypeChanged(deviceTransportType: DeviceTransportType): void {
this.transportConfigFormGroup.patchValue(
{transportConfiguration: createDeviceProfileTransportConfiguration(deviceTransportType)});
const setCredentialBox = this.credentialsFormGroup.get('setCredential');
if (deviceTransportType === DeviceTransportType.LWM2M) {
setCredentialBox.patchValue(true);
setCredentialBox.disable();
} else {
setCredentialBox.patchValue(false);
setCredentialBox.enable();
}
}
add(): void {
if (this.allValid()) {
this.createDeviceProfile().pipe(
mergeMap(profileId => this.createDevice(profileId)),
mergeMap(device => this.saveCredentials(device))
).subscribe(
(created) => {
this.dialogRef.close(created);
}
this.createDevice().subscribe(
() => this.dialogRef.close(true)
);
}
}
get deviceTransportType(): DeviceTransportType {
if (this.deviceWizardFormGroup.get('addProfileType').value) {
return this.transportConfigFormGroup.get('transportType').value;
} else {
return this.currentDeviceProfileTransportType;
}
return this.currentDeviceProfileTransportType;
}
deviceProfileChanged(deviceProfile: DeviceProfileInfo) {
if (deviceProfile) {
this.currentDeviceProfileTransportType = deviceProfile.transportType;
this.credentialsOptionalStep = this.currentDeviceProfileTransportType !== DeviceTransportType.LWM2M;
}
}
private createDeviceProfile(): Observable<EntityId> {
if (this.deviceWizardFormGroup.get('addProfileType').value) {
const deviceProvisionConfiguration: DeviceProvisionConfiguration = this.provisionConfigFormGroup.get('provisionConfiguration').value;
const provisionDeviceKey = deviceProvisionConfiguration.provisionDeviceKey;
delete deviceProvisionConfiguration.provisionDeviceKey;
const deviceProfile: DeviceProfile = {
name: this.deviceWizardFormGroup.get('newDeviceProfileTitle').value,
type: DeviceProfileType.DEFAULT,
defaultQueueName: this.deviceWizardFormGroup.get('defaultQueueName').value,
transportType: this.transportConfigFormGroup.get('transportType').value,
provisionType: deviceProvisionConfiguration.type,
provisionDeviceKey,
profileData: {
configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT),
transportConfiguration: this.transportConfigFormGroup.get('transportConfiguration').value,
alarms: this.alarmRulesFormGroup.get('alarms').value,
provisionConfiguration: deviceProvisionConfiguration
}
};
if (this.deviceWizardFormGroup.get('defaultRuleChainId').value) {
deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceWizardFormGroup.get('defaultRuleChainId').value);
}
return this.deviceProfileService.saveDeviceProfile(deepTrim(deviceProfile)).pipe(
tap((profile) => {
this.currentDeviceProfileTransportType = profile.transportType;
this.deviceWizardFormGroup.patchValue({
deviceProfileId: profile.id,
addProfileType: 0
});
}),
map(profile => profile.id)
);
} else {
return of(this.deviceWizardFormGroup.get('deviceProfileId').value);
}
}
private createDevice(profileId): Observable<BaseData<HasId>> {
const device = {
private createDevice(): Observable<BaseData<HasId>> {
const device: Device = {
name: this.deviceWizardFormGroup.get('name').value,
label: this.deviceWizardFormGroup.get('label').value,
deviceProfileId: profileId,
deviceProfileId: this.deviceWizardFormGroup.get('deviceProfileId').value,
additionalInfo: {
gateway: this.deviceWizardFormGroup.get('gateway').value,
overwriteActivityTime: this.deviceWizardFormGroup.get('overwriteActivityTime').value,
@ -338,13 +149,22 @@ export class DeviceWizardDialogComponent extends
},
customerId: null
};
if (this.customerFormGroup.get('customerId').value) {
device.customerId = {
entityType: EntityType.CUSTOMER,
id: this.customerFormGroup.get('customerId').value
};
if (this.deviceWizardFormGroup.get('customerId').value) {
device.customerId = new CustomerId(this.deviceWizardFormGroup.get('customerId').value);
}
if (this.addDeviceWizardStepper.steps.last.completed || this.addDeviceWizardStepper.selectedIndex > 0) {
return this.deviceService.saveDeviceWithCredentials(deepTrim(device), deepTrim(this.credentialsFormGroup.value.credential)).pipe(
catchError((e: HttpErrorResponse) => {
if (e.error.message.includes('Device credentials')) {
this.addDeviceWizardStepper.selectedIndex = 1;
} else {
this.addDeviceWizardStepper.selectedIndex = 0;
}
return throwError(() => e);
})
);
}
return this.data.entitiesTableConfig.saveEntity(deepTrim(device)).pipe(
return this.deviceService.saveDevice(deepTrim(device)).pipe(
catchError(e => {
this.addDeviceWizardStepper.selectedIndex = 0;
return throwError(e);
@ -352,31 +172,8 @@ export class DeviceWizardDialogComponent extends
);
}
private saveCredentials(device: BaseData<HasId>): Observable<boolean> {
if (this.credentialsFormGroup.get('setCredential').value) {
return this.deviceService.getDeviceCredentials(device.id.id).pipe(
mergeMap(
(deviceCredentials) => {
const deviceCredentialsValue = {...deviceCredentials, ...this.credentialsFormGroup.value.credential};
return this.deviceService.saveDeviceCredentials(deviceCredentialsValue).pipe(
catchError(e => {
this.addDeviceWizardStepper.selectedIndex = 1;
return this.deviceService.deleteDevice(device.id.id).pipe(
mergeMap(() => {
return throwError(e);
}
));
})
);
}
),
map(() => true));
}
return of(true);
}
allValid(): boolean {
if (this.addDeviceWizardStepper.steps.find((item, index) => {
return !this.addDeviceWizardStepper.steps.find((item, index) => {
if (item.stepControl.invalid) {
item.interacted = true;
this.addDeviceWizardStepper.selectedIndex = index;
@ -384,19 +181,11 @@ export class DeviceWizardDialogComponent extends
} else {
return false;
}
} )) {
return false;
} else {
return true;
}
});
}
changeStep($event: StepperSelectionEvent): void {
this.selectedIndex = $event.selectedIndex;
if (this.selectedIndex === this.maxStepperIndex) {
this.showNext = false;
} else {
this.showNext = true;
}
this.showNext = this.selectedIndex !== this.maxStepperIndex;
}
}

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

@ -328,6 +328,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
backgroundColor: string;
padding: string;
margin: string;
borderRadius: string;
title: string;
customTranslatedTitle: string;
@ -427,6 +428,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
this.backgroundColor = this.widget.config.backgroundColor || '#fff';
this.padding = this.widget.config.padding || '8px';
this.margin = this.widget.config.margin || '0px';
this.borderRadius = this.widget.config.borderRadius;
this.title = isDefined(this.widgetContext.widgetTitle)
&& this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title;
@ -478,7 +480,8 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
color: this.color,
backgroundColor: this.backgroundColor,
padding: this.padding,
margin: this.margin};
margin: this.margin,
borderRadius: this.borderRadius};
if (this.widget.config.widgetStyle) {
this.style = {...this.style, ...this.widget.config.widgetStyle};
}

8
ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.ts

@ -17,10 +17,10 @@
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
UntypedFormBuilder,
UntypedFormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormGroup,
ValidationErrors,
Validator,
Validators
@ -104,9 +104,7 @@ export class DeviceTransportConfigurationComponent implements ControlValueAccess
if (configuration) {
delete configuration.type;
}
setTimeout(() => {
this.deviceTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false});
}, 0);
this.deviceTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false});
}
validate(): ValidationErrors | null {

3
ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.ts

@ -127,6 +127,9 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc
this.snmpDeviceTransportConfigurationFormGroup.disable({emitEvent: false});
} else {
this.snmpDeviceTransportConfigurationFormGroup.enable({emitEvent: false});
this.updateDisabledFormValue(
this.snmpDeviceTransportConfigurationFormGroup.get('protocolVersion').value || SnmpDeviceProtocolVersion.V2C
);
}
}

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

Loading…
Cancel
Save