From 67031e43da791fe64b758eda1c689e09eaedf934 Mon Sep 17 00:00:00 2001 From: Maksym Dudnik Date: Mon, 31 Jul 2023 12:24:07 +0300 Subject: [PATCH] connector status and new gateway cards --- .../json/demo/dashboards/gateway_list.json | 10 ++- .../gateway/gateway-connectors.component.html | 14 +++++ .../gateway/gateway-connectors.component.scss | 16 +++++ .../gateway/gateway-connectors.component.ts | 61 ++++++++++++++++++- .../lib/gateway/gateway-logs.component.ts | 2 +- .../assets/locale/locale.constant-en_US.json | 2 +- 6 files changed, 100 insertions(+), 5 deletions(-) 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 5d4760fb37..24b547c8bc 100644 --- a/application/src/main/data/json/demo/dashboards/gateway_list.json +++ b/application/src/main/data/json/demo/dashboards/gateway_list.json @@ -479,6 +479,14 @@ "funcBody": null, "usePostProcessing": true, "postFuncBody": "var newValue = value == 'true' ? \"Active\" : \"Inactive\";\nreturn newValue;" + }, + { + "name": "ALL_ERRORS_COUNT", + "type": "timeseries", + "label": "ALL_ERRORS_COUNT", + "color": "#ffeb3b", + "settings": {}, + "_hash": 0.2770587478725004 } ], "alarmFilterConfig": { @@ -550,7 +558,7 @@ "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((data[1]?data[1].count:0) + \" | \" + (data[2]?data[2][\"count 2\"]:0), \"Devices\", '');\ncreateDataBlock((data[0].active_connectors?JSON.parse(data[0].active_connectors).length:0) + \" | \" + (data[0].inactive_connectors?JSON.parse(data[0].inactive_connectors).length:0), \"Connectors\", '');\nreturn `
${blockData}
`;", + "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}
`;", "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}" }, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index f35754aea2..652f73fa1b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -69,6 +69,20 @@ }">{{isConnectorSynced(attribute)?'sync' : 'out of sync'}} + + + {{ 'gateway.connectors-table-status' | translate }} + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss index 6d5a6c4585..87e099d652 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.scss @@ -90,6 +90,22 @@ mat-row { cursor: pointer; } + + .dot { + height: 25px; + width: 25px; + background-color: #bbb; + border-radius: 50%; + display: inline-block; + } + + .hasErrors { + background-color: rgb(203, 37, 48); + } + + .noErrors { + background-color: rgb(25, 128, 56); + } } :host ::ng-deep tb-json-object-edit > div { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index dccf0e8657..f673f67cf4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -39,6 +39,10 @@ import { DialogService } from '@core/services/dialog.service'; import { WidgetContext } from '@home/models/widget-component.models'; import { deepClone } from '@core/utils'; import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; +import { DatasourceType, widgetType } from '@shared/models/widget.models'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityType } from '@shared/models/entity-type.models'; export interface gatewayConnector { @@ -88,7 +92,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie dataSource: MatTableDataSource; - displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'actions']; + displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'errors', 'actions']; gatewayConnectorDefaultTypes = GatewayConnectorDefaultTypesTranslates; @@ -123,6 +127,19 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie initialConnector: gatewayConnector; + subscriptionOptions: WidgetSubscriptionOptions = { + callbacks: { + onDataUpdated: () => this.ctx.ngZone.run(() => { + this.onDataUpdated(); + }), + onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => { + this.onDataUpdateError(e); + }) + } + }; + + subscription: IWidgetSubscription; + constructor(protected router: Router, protected store: Store, protected fb: FormBuilder, @@ -132,6 +149,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie protected dialogService: DialogService, private telemetryWsService: TelemetryWebsocketService, private zone: NgZone, + private utils: UtilsService, private cd: ChangeDetectorRef, public dialog: MatDialog) { super(store); @@ -270,6 +288,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.attributeDataSource.loadAttributes(this.device, AttributeScope.CLIENT_SCOPE, this.pageLink, reload).subscribe(data => { this.activeData = data.data.filter(value => this.activeConnectors.includes(value.key)); this.combineData(); + this.generateSubscription(); }); this.inactiveConnectorsDataSource.loadAttributes(this.device, AttributeScope.SHARED_SCOPE, this.pageLink, reload).subscribe(data => { this.sharedAttributeData = data.data.filter(value => this.activeConnectors.includes(value.key)); @@ -279,7 +298,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.inactiveData = data.data.filter(value => this.inactiveConnectors.includes(value.key)); this.combineData(); }); - } isConnectorSynced(attribute: AttributeData) { @@ -457,4 +475,43 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie }); } + onDataUpdateError(e: any) { + const exceptionData = this.utils.parseException(e); + let errorText = exceptionData.name; + if (exceptionData.message) { + errorText += ': ' + exceptionData.message; + } + console.error(errorText); + } + + onDataUpdated() { + this.cd.detectChanges(); + } + + generateSubscription() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + if (this.device) { + const subscriptionInfo = [{ + type: DatasourceType.entity, + entityType: EntityType.DEVICE, + entityId: this.device.id, + entityName: "Gateway", + timeseries: [] + }]; + this.dataSource.data.forEach(value => { + subscriptionInfo[0].timeseries.push({name: `${value.key}_ERRORS_COUNT`, label: `${value.key}_ERRORS_COUNT`}) + }) + this.ctx.subscriptionApi.createSubscriptionFromInfo(widgetType.latest, subscriptionInfo,this.subscriptionOptions, false, true).subscribe(subscription => { + this.subscription = subscription; + }); + } + } + + getErrorsCount(attribute) { + const connectorName = attribute.key; + const connector = this.subscription.data.find(data=>data && data.dataKey.name === `${connectorName}_ERRORS_COUNT`); + return (connector && this.activeConnectors.includes(connectorName))? connector.data[0][1]: 'inactive'; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts index c4fb8a3534..f724e9dff9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-logs.component.ts @@ -219,7 +219,7 @@ export class GatewayLogsComponent extends PageComponent implements AfterViewInit } changeSubscription() { - if (this.ctx.datasources[0].entity) { + if (this.ctx.datasources && this.ctx.datasources[0].entity && this.ctx.defaultSubscription.options.datasources) { this.ctx.defaultSubscription.options.datasources[0].dataKeys = [{ name: this.activeLink.key, type: DataKeyType.timeseries, 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 b9d14eff0e..25969fb9e0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2617,7 +2617,7 @@ "connectors-table-enabled": "Enabled", "connectors-table-name": "Name", "connectors-table-type": "Type", - "connectors-table-status": "Sync status", + "connectors-table-status": "Status", "connectors-table-actions": "Actions", "connectors-table-key": "Key", "connectors-table-class": "Class",