diff --git a/application/src/main/data/json/demo/dashboards/gateway_list.json b/application/src/main/data/json/demo/dashboards/gateway_list.json index 643672e954..b24ff907b1 100644 --- a/application/src/main/data/json/demo/dashboards/gateway_list.json +++ b/application/src/main/data/json/demo/dashboards/gateway_list.json @@ -44,7 +44,8 @@ "enableSearch": true, "enableSelectColumnDisplay": false, "enableStickyHeader": true, - "enableStickyAction": true, + "enableStickyAction": false, + "showCellActionsMenu": true, "reserveSpaceForHiddenAction": "true", "displayEntityName": false, "displayEntityLabel": false, @@ -570,9 +571,9 @@ "padding": "8px", "settings": { "useMarkdownTextFunction": true, - "markdownTextFunction": "var blockData = '';\nfunction createDataBlock(value, label, dividerStyle, mobile) {\n blockData += `\n \n
\n \n \n ${label}\n \n ${value}\n
`;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `${(data[1]?data[1].count:0)} `\n + \" | \" + \n `${(data[2]?data[2][\"count 2\"]:0)} `\n , \"Devices\", '');\ncreateDataBlock(\n `${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} `\n + \" | \" + \n `${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} `\n , \"Connectors\", '');\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red');\nreturn `
${blockData}
`;", + "markdownTextFunction": "var blockData = '';\nvar connectorsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Connecotrs\");\nvar logsIndex = ctx.actionsApi.getActionDescriptors('elementClick').findIndex(action=>action.name==\"Logs\");\nfunction generateMatHeader(index) {\n if( index !== undefined && index > -1) {\n return ``\n } else {\n return \"\" \n }\n}\nfunction createDataBlock(value, label, dividerStyle, mobile, index) {\n blockData += `\n \n
\n \n ${generateMatHeader(index)}\n ${label}\n
\n ${value}\n `;\n}\ncreateDataBlock(data[0].Status, \"Status\", data[0].Status === \"Active\"? 'divider-green' : 'divider-red');\ncreateDataBlock(data[0].Name, \"Gateway Name\", '', ctx.isMobile);\ncreateDataBlock(data[0].Type, \"Gateway Type\", '');\ncreateDataBlock(\n `${(data[1]?data[1].count:0)} `\n + \" | \" + \n `${(data[2]?data[2][\"count 2\"]:0)} `\n , \"Devices (Active | Inactive)\", '');\ncreateDataBlock(\n `${(data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0)} `\n + \" | \" + \n `${(data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0)} `\n , \"Connectors (Active | Inactive)\", '', '', connectorsIndex);\ncreateDataBlock(data[0].ALL_ERRORS_COUNT || 0, \"Errors\", (data[0].ALL_ERRORS_COUNT || 0) === 0 ? 'divider-green' : 'divider-red', '', logsIndex);\nreturn `
${blockData}
`;", "applyDefaultMarkdownStyle": false, - "markdownCss": ".divider {\n position: absolute;\n width: 3px;\n top: 8px;\n border-radius: 2px;\n bottom: 8px;\n border: 1px solid rgba(31, 70, 144, 1);\n background-color: rgba(31, 70, 144, 1);\n left: 10px;\n}\n.divider-green .divider {\n border: 1px solid rgb(25,128,56);\n background-color: rgb(25,128,56);\n}\n\n.divider-green .mat-mdc-card-content {\n color: rgb(25,128,56);\n}\n\n.divider-red .divider {\n border: 1px solid rgb(203,37,48);\n background-color: rgb(203,37,48);\n}\n\n.divider-red .mat-mdc-card-content {\n color: rgb(203,37,48);\n}\n\n.mdc-card {\n position: relative;\n padding-left: 10px;\n margin-bottom: 1px;\n}\n\n.mat-mdc-card-subtitle {\n font-weight: 400;\n font-size: 12px;\n}\n\n.mat-mdc-card-header {\n padding: 8px 16px 0;\n}\n\n.mat-mdc-card-content:last-child {\n padding-bottom: 8px;\n font-size: 16px;\n}\n\n.cards-container {\n height: calc(100% - 1px);\n justify-content: stretch;\n align-items: center;\n margin-bottom: 1px;\n}" + "markdownCss": ".divider {\n position: absolute;\n width: 3px;\n top: 8px;\n border-radius: 2px;\n bottom: 8px;\n border: 1px solid rgba(31, 70, 144, 1);\n background-color: rgba(31, 70, 144, 1);\n left: 10px;\n}\n.divider-green .divider {\n border: 1px solid rgb(25,128,56);\n background-color: rgb(25,128,56);\n}\n\n.divider-green .mat-mdc-card-content {\n color: rgb(25,128,56);\n}\n\n.divider-red .divider {\n border: 1px solid rgb(203,37,48);\n background-color: rgb(203,37,48);\n}\n\n.divider-red .mat-mdc-card-content {\n color: rgb(203,37,48);\n}\n\n.mdc-card {\n position: relative;\n padding-left: 10px;\n margin-bottom: 1px;\n}\n\n.mat-mdc-card-subtitle {\n font-weight: 400;\n font-size: 12px;\n}\n\n.mat-mdc-card-header {\n padding: 8px 16px 0;\n}\n\n.mat-mdc-card-content:last-child {\n padding-bottom: 8px;\n font-size: 16px;\n}\n\n.cards-container {\n height: calc(100% - 1px);\n justify-content: stretch;\n align-items: center;\n margin-bottom: 1px;\n}\n\n::ng-deep.tb-home-widget-link > div {\n flex-grow: 1;\n cursor: pointer;\n}\n\n .tb-home-widget-link {\n width: 100%;\n }\n\n .tb-home-widget-link:hover::after{\n color: inherit;\n }\n \n .tb-home-widget-link::after{\n content: 'arrow_forward';\n display: inline-block;\n transform: rotate(315deg);\n font-family: 'Material Icons';\n font-weight: normal;\n font-style: normal;\n font-size: 18px;\n color: rgba(0, 0, 0, 0.12);\n vertical-align: bottom;\n margin-left: 6px;\n}" }, "title": "New Markdown/HTML Card", "showTitleIcon": false, @@ -594,7 +595,39 @@ "noDataDisplayMessage": "", "enableDataExport": false, "displayTimewindow": true, - "borderRadius": "4px" + "borderRadius": "4px", + "actions": { + "elementClick": [ + { + "name": "Connecotrs", + "icon": "more_horiz", + "useShowWidgetActionFunction": null, + "showWidgetActionFunction": "return true;", + "type": "openDashboardState", + "targetDashboardStateId": "connectors", + "setEntityId": true, + "stateEntityParamName": null, + "openRightLayout": false, + "openInSeparateDialog": false, + "openInPopover": false, + "id": "ee7216d8-ad7a-20db-1abc-2531e221f24c" + }, + { + "name": "Logs", + "icon": "more_horiz", + "useShowWidgetActionFunction": null, + "showWidgetActionFunction": "return true;", + "type": "openDashboardState", + "targetDashboardStateId": "logs", + "setEntityId": true, + "stateEntityParamName": null, + "openRightLayout": false, + "openInSeparateDialog": false, + "openInPopover": false, + "id": "ceec0446-c25f-2b9b-b1b1-58ed751476ff" + } + ] + } }, "row": 0, "col": 0, @@ -784,7 +817,7 @@ "color": "rgba(0, 0, 0, 0.87)", "padding": "8px", "settings": {}, - "title": "Gateway Logs", + "title": "Logs", "showTitleIcon": false, "dropShadow": true, "enableFullscreen": true, @@ -961,7 +994,7 @@ "customLegendEnabled": false, "dataKeysListForLabels": [] }, - "title": "Statistics", + "title": "Gateway Custom Statistics", "dropShadow": true, "enableFullscreen": true, "titleStyle": { @@ -1249,7 +1282,7 @@ "customLegendEnabled": false, "dataKeysListForLabels": [] }, - "title": "General Statistics", + "title": "Gateway General Chart Statistics", "dropShadow": true, "enableFullscreen": true, "titleStyle": { diff --git a/application/src/main/data/json/system/widget_types/gateway_general_statistics.json b/application/src/main/data/json/system/widget_types/gateway_general_statistics.json index 349beebc06..d0608f64e7 100644 --- a/application/src/main/data/json/system/widget_types/gateway_general_statistics.json +++ b/application/src/main/data/json/system/widget_types/gateway_general_statistics.json @@ -20,4 +20,4 @@ "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\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Gateway general chart statistics\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}" } -} +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_types/gateway_statistics.json b/application/src/main/data/json/system/widget_types/gateway_statistics.json index b72377c771..f05e25768a 100644 --- a/application/src/main/data/json/system/widget_types/gateway_statistics.json +++ b/application/src/main/data/json/system/widget_types/gateway_statistics.json @@ -20,4 +20,4 @@ "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\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Gateway custom statistics \",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}" } -} +} \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html index 26bcacd64a..c860a11229 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.html @@ -549,9 +549,9 @@
gateway.statistics.commands
gateway.hints.commands
-
-
+
gateway.statistics.attribute-name @@ -612,7 +612,7 @@
+ [class.no-padding-bottom]="gatewayConfigGroup.get('thingsboard.checkingDeviceActivity.checkDeviceInactivity').value">
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss index fa2b10f677..23d725bcbf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.scss @@ -54,6 +54,14 @@ flex: 1; } + .statistics-container { + width: 50%; + + .command-container { + width: 100%; + } + } + .actions { grid-row: 3; padding: 8px; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts index bef0c2cc74..ed64c96799 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-configuration.component.ts @@ -48,6 +48,7 @@ import { StorageTypes, StorageTypesTranslationMap } from './gateway-widget.models'; +import { deepTrim } from '@core/utils'; @Component({ selector: 'tb-gateway-configuration', @@ -91,7 +92,7 @@ export class GatewayConfigurationComponent implements OnInit { ngOnInit() { this.gatewayConfigGroup = this.fb.group({ thingsboard: this.fb.group({ - host: [window.location.hostname, [Validators.required]], + host: [window.location.hostname, [Validators.required, Validators.pattern(/^[^\s]+$/)]], port: [1883, [Validators.required, Validators.min(1), Validators.max(65535), Validators.pattern(/^-?[0-9]+$/)]], remoteShell: [false, []], remoteConfiguration: [true, []], @@ -107,30 +108,30 @@ export class GatewayConfigurationComponent implements OnInit { handleDeviceRenaming: [true, []], checkingDeviceActivity: this.fb.group({ checkDeviceInactivity: [false, []], - inactivityTimeoutSeconds: [200, [Validators.min(1)]], - inactivityCheckPeriodSeconds: [500, [Validators.min(1)]] + inactivityTimeoutSeconds: [200, [Validators.min(1), Validators.pattern(/^[^.\s]+$/)]], + inactivityCheckPeriodSeconds: [500, [Validators.min(1), Validators.pattern(/^[^.\s]+$/)]] }), security: this.fb.group({ type: [SecurityTypes.ACCESS_TOKEN, [Validators.required]], - accessToken: [null, [Validators.required]], - clientId: [null, []], - username: [null, []], - password: [null, []], + accessToken: [null, [Validators.required, Validators.pattern(/^[^.\s]+$/)]], + clientId: [null, [Validators.pattern(/^[^.\s]+$/)]], + username: [null, [Validators.pattern(/^[^.\s]+$/)]], + password: [null, [Validators.pattern(/^[^.\s]+$/)]], caCert: [null, []], cert: [null, []], privateKey: [null, []], }), - qos: [1, [Validators.min(0), Validators.max(1), Validators.required]] + qos: [1, [Validators.min(0), Validators.max(1), Validators.required, Validators.pattern(/^[^.\s]+$/)]] }), storage: this.fb.group({ type: [StorageTypes.MEMORY, [Validators.required]], - read_records_count: [100, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]], - max_records_count: [100000, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]], - data_folder_path: ['./data/', []], + read_records_count: [100, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required, Validators.pattern(/^[^.\s]+$/)]], + max_records_count: [100000, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required, Validators.pattern(/^[^.\s]+$/)]], + data_folder_path: ['./data/', [Validators.pattern(/^[^\s]+$/)]], max_file_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], max_read_records_count: [10, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], max_records_per_file: [10000, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], - data_file_path: ['./data/data.db', []], + data_file_path: ['./data/data.db', [Validators.pattern(/^[^\s]+$/)]], messages_ttl_check_in_hours: [1, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], messages_ttl_in_days: [7, [Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], @@ -147,9 +148,9 @@ export class GatewayConfigurationComponent implements OnInit { }), connectors: this.fb.array([]), logs: this.fb.group({ - dateFormat: ['%Y-%m-%d %H:%M:%S', [Validators.required]], + dateFormat: ['%Y-%m-%d %H:%M:%S', [Validators.required, Validators.pattern(/^[^\s].*[^\s]$/)]], logFormat: ['%(asctime)s - |%(levelname)s| - [%(filename)s] - %(module)s - %(funcName)s - %(lineno)d - %(message)s', - [Validators.required]], + [Validators.required, Validators.pattern(/^[^\s].*[^\s]$/)]], type: ['remote', [Validators.required]], remote: this.fb.group({ enabled: [false], @@ -204,7 +205,7 @@ export class GatewayConfigurationComponent implements OnInit { securityGroup.get('type').valueChanges.subscribe(type => { this.removeAllSecurityValidators(); if (type === SecurityTypes.ACCESS_TOKEN) { - securityGroup.get('accessToken').addValidators([Validators.required]); + securityGroup.get('accessToken').addValidators([Validators.required, Validators.pattern(/^[^.\s]+$/)]); securityGroup.get('accessToken').updateValueAndValidity(); } else if (type === SecurityTypes.TLS_PRIVATE_KEY) { securityGroup.get('caCert').addValidators([Validators.required]); @@ -214,7 +215,7 @@ export class GatewayConfigurationComponent implements OnInit { securityGroup.get('cert').addValidators([Validators.required]); securityGroup.get('cert').updateValueAndValidity(); } else if (type === SecurityTypes.TLS_ACCESS_TOKEN) { - securityGroup.get('accessToken').addValidators([Validators.required]); + securityGroup.get('accessToken').addValidators([Validators.required, Validators.pattern(/^[^.\s]+$/)]); securityGroup.get('accessToken').updateValueAndValidity(); securityGroup.get('caCert').addValidators([Validators.required]); securityGroup.get('caCert').updateValueAndValidity(); @@ -239,7 +240,7 @@ export class GatewayConfigurationComponent implements OnInit { storageGroup.get('read_records_count').updateValueAndValidity({emitEvent: false}); storageGroup.get('max_records_count').updateValueAndValidity({emitEvent: false}); } else if (type === StorageTypes.FILE) { - storageGroup.get('data_folder_path').addValidators([Validators.required]); + storageGroup.get('data_folder_path').addValidators([Validators.required, Validators.pattern(/^[^.\s]+$/)]); storageGroup.get('max_file_count').addValidators( [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]); storageGroup.get('max_read_records_count').addValidators( @@ -251,7 +252,7 @@ export class GatewayConfigurationComponent implements OnInit { storageGroup.get('max_read_records_count').updateValueAndValidity({emitEvent: false}); storageGroup.get('max_records_per_file').updateValueAndValidity({emitEvent: false}); } else if (type === StorageTypes.SQLITE) { - storageGroup.get('data_file_path').addValidators([Validators.required]); + storageGroup.get('data_file_path').addValidators([Validators.required, Validators.pattern(/^[^.\s]+$/)]); storageGroup.get('messages_ttl_check_in_hours').addValidators( [Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.required]); storageGroup.get('messages_ttl_in_days').addValidators( @@ -342,8 +343,8 @@ export class GatewayConfigurationComponent implements OnInit { this.initialCredentials = credentials; if (credentials.credentialsType === DeviceCredentialsType.ACCESS_TOKEN || security.type === SecurityTypes.TLS_ACCESS_TOKEN) { this.gatewayConfigGroup.get('thingsboard.security.type').setValue(security.type === SecurityTypes.TLS_ACCESS_TOKEN - ? SecurityTypes.TLS_ACCESS_TOKEN - : SecurityTypes.ACCESS_TOKEN); + ? SecurityTypes.TLS_ACCESS_TOKEN + : SecurityTypes.ACCESS_TOKEN); this.gatewayConfigGroup.get('thingsboard.security.accessToken').setValue(credentials.credentialsId); if(security.type === SecurityTypes.TLS_ACCESS_TOKEN) { this.gatewayConfigGroup.get('thingsboard.security.caCert').setValue(security.caCert); @@ -407,9 +408,9 @@ export class GatewayConfigurationComponent implements OnInit { addCommand(command: any = {}): void { const commandsFormArray = this.commandFormArray(); const commandFormGroup = this.fb.group({ - attributeOnGateway: [command.attributeOnGateway || null, [Validators.required]], - command: [command.command || null, [Validators.required]], - timeout: [command.timeout || null, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/)]], + attributeOnGateway: [command.attributeOnGateway || null, [Validators.required, Validators.pattern(/^[^.\s]+$/)]], + command: [command.command || null, [Validators.required, Validators.pattern(/^[^.\s]+$/)]], + timeout: [command.timeout || null, [Validators.required, Validators.min(1), Validators.pattern(/^-?[0-9]+$/), Validators.pattern(/^[^.\s]+$/)]], }); commandsFormArray.push(commandFormGroup); } @@ -542,7 +543,7 @@ export class GatewayConfigurationComponent implements OnInit { } saveConfig(): void { - const value = this.removeEmpty(this.gatewayConfigGroup.value); + const value = deepTrim(this.removeEmpty(this.gatewayConfigGroup.value)); value.thingsboard.statistics.commands = Object.values(value.thingsboard.statistics.commands); const attributes = []; attributes.push({ @@ -609,8 +610,8 @@ export class GatewayConfigurationComponent implements OnInit { credentialsValue.password = securityConfig.password; } newCredentials = { - credentialsType, - credentialsValue: JSON.stringify(credentialsValue) + credentialsType, + credentialsValue: JSON.stringify(credentialsValue) }; } } else if (securityConfig.type === SecurityTypes.ACCESS_TOKEN || securityConfig.type === SecurityTypes.TLS_ACCESS_TOKEN) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html index 973b3e02b7..e195dcece9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.html @@ -25,13 +25,13 @@ matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> - {{ 'widgets.gateway.created-time' | translate }} + {{ 'widgets.gateway.created-time' | translate }} {{ attribute.ts | date:'yyyy-MM-dd HH:mm:ss' }} - {{ 'widgets.gateway.status' | translate }} + {{ 'widgets.gateway.status' | translate }} {{ attribute.status }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts index 2e1deb900a..503f5643bf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-statistics.component.ts @@ -50,6 +50,7 @@ export class GatewayStatisticsComponent implements AfterViewInit { public general: boolean; public isNumericData = false; + public dataTypeDefined: boolean = false; public chartInited: boolean; private flot: TbFlot; private flotCtx: WidgetContext; @@ -155,6 +156,7 @@ export class GatewayStatisticsComponent implements AfterViewInit { subscriptionInfo[0].timeseries = [{name: attr, label: attr}]; this.subscriptionInfo = subscriptionInfo; this.changeSubscription(subscriptionInfo); + this.ctx.defaultSubscription.unsubscribe(); } private createGeneralChartsSubscription(gateway: BaseData, attrData: string[]) { @@ -175,8 +177,8 @@ export class GatewayStatisticsComponent implements AfterViewInit { subscriptionInfo[0].timeseries.push({name: dataKey.name, label: dataKey.label}); }); - this.subscriptionInfo = subscriptionInfo; this.changeSubscription(subscriptionInfo); + this.ctx.defaultSubscription.unsubscribe(); } private init = () => { @@ -252,7 +254,10 @@ export class GatewayStatisticsComponent implements AfterViewInit { return; } this.dataSource.data = this.subscription.data.length ? this.subscription.data[0].data : []; - this.isNumericData = this.dataSource.data.every(data => !isNaN(+data[1])); + if (this.dataSource.data.length && !this.dataTypeDefined) { + this.dataTypeDefined = true; + this.isNumericData = this.dataSource.data.every(data => !isNaN(+data[1])); + } } @@ -263,6 +268,7 @@ export class GatewayStatisticsComponent implements AfterViewInit { if (this.ctx.datasources[0].entity) { this.ctx.subscriptionApi.createSubscriptionFromInfo(widgetType.timeseries, subscriptionInfo, this.subscriptionOptions, false, true).subscribe(subscription => { + this.dataTypeDefined = false; this.subscription = subscription; this.isDataOnlyNumbers(); this.legendData = this.subscription.legendData; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gateway/gateway-logs-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gateway/gateway-logs-settings.component.ts index 5d3ea09447..c543ee99fb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gateway/gateway-logs-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gateway/gateway-logs-settings.component.ts @@ -41,14 +41,14 @@ export class GatewayLogsSettingsComponent extends WidgetSettingsComponent { protected defaultSettings(): WidgetSettings { return { isConnectorLog: false, - connectorLogState: 'default' + connectorLogState: '' }; } protected onSettingsSet(settings: WidgetSettings) { this.gatewayLogSettingForm = this.fb.group({ isConnectorLog: [false, []], - connectorLogState: ['default', Validators.required] + connectorLogState: ['', Validators.required] }); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 7397f82993..053c1713bc 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2683,7 +2683,7 @@ "rpc-command-result": "Result", "rpc-command-edit-params": "Edit parameters", "select-connector": "Select connector", - "gateway-configuration": "Gateway Configuration", + "gateway-configuration": "General Configuration", "docker-label": "In order to run ThingsBoard IoT gateway in docker with credentials for this device you can use the following commands.", "copy-command": "Copy docker command", "create-new-gateway": "Create a new gateway", @@ -5329,8 +5329,8 @@ "events-title": "Gateway events form title", "events-filter": "Events filter", "event-key-contains": "Event key contains...", - "is-connector": "Is Connector", - "state-param-name": "State parameter connector name", + "is-connector": "Show for the connector", + "state-param-name": "Connector state parameter key", "status": "Status", "message": "Message", "created-time": "Created time"