thingsboard-aplcache

TB-39: UI: Add Gateway flag. (#44) * TB-39: UI: Add Gateway

1/31/2017 10:54:48 AM

Details

ui/package.json 2(+1 -1)

diff --git a/ui/package.json b/ui/package.json
index 9a12bb5..fd69493 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,7 +1,7 @@
 {
   "name": "thingsboard",
   "private": true,
-  "version": "1.0.1",
+  "version": "1.1.0",
   "description": "Thingsboard UI",
   "licenses": [
     {

ui/server.js 5(+5 -0)

diff --git a/ui/server.js b/ui/server.js
index 4987bd2..1f1d82b 100644
--- a/ui/server.js
+++ b/ui/server.js
@@ -52,6 +52,11 @@ const apiProxy = httpProxy.createProxyServer({
     }
 });
 
+apiProxy.on('error', function (err, req, res) {
+    console.warn('API proxy error: ' + err);
+    res.end('Error.');
+});
+
 console.info(`Forwarding API requests to http://${forwardHost}:${forwardPort}`);
 
 app.all('/api/*', (req, res) => {
diff --git a/ui/src/app/api/datasource.service.js b/ui/src/app/api/datasource.service.js
index 6a8ae64..a519d8b 100644
--- a/ui/src/app/api/datasource.service.js
+++ b/ui/src/app/api/datasource.service.js
@@ -256,6 +256,9 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                         type: types.dataKeyType.timeseries,
                         onData: function (data) {
                             onData(data, types.dataKeyType.timeseries);
+                        },
+                        onReconnected: function() {
+                            onReconnected();
                         }
                     };
 
@@ -278,6 +281,9 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                         type: types.dataKeyType.timeseries,
                         onData: function (data) {
                             onData(data, types.dataKeyType.timeseries);
+                        },
+                        onReconnected: function() {
+                            onReconnected();
                         }
                     };
 
@@ -299,6 +305,9 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                     type: types.dataKeyType.attribute,
                     onData: function (data) {
                         onData(data, types.dataKeyType.attribute);
+                    },
+                    onReconnected: function() {
+                        onReconnected();
                     }
                 };
 
@@ -428,6 +437,25 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
         }
     }
 
+    function onReconnected() {
+        if (datasourceType === types.datasourceType.device) {
+            for (var key in dataKeys) {
+                var dataKeysList = dataKeys[key];
+                for (var i = 0; i < dataKeysList.length; i++) {
+                    var dataKey = dataKeysList[i];
+                    var datasourceKey = key + '_' + i;
+                    datasourceData[datasourceKey] = [];
+                    for (var l in listeners) {
+                        var listener = listeners[l];
+                        listener.dataUpdated(datasourceData[datasourceKey],
+                            listener.datasourceIndex,
+                            dataKey.index);
+                    }
+                }
+            }
+        }
+    }
+
     function onData(sourceData, type) {
         for (var keyName in sourceData) {
             var keyData = sourceData[keyName];
diff --git a/ui/src/app/api/device.service.js b/ui/src/app/api/device.service.js
index 2edaeb1..46c64da 100644
--- a/ui/src/app/api/device.service.js
+++ b/ui/src/app/api/device.service.js
@@ -307,12 +307,12 @@ function DeviceService($http, $q, $filter, telemetryWebsocketService, types) {
                     onSubscriptionData(data, subscriptionId);
                 }
             };
-            telemetryWebsocketService.subscribe(subscriber);
             deviceAttributesSubscription = {
                 subscriber: subscriber,
                 attributes: null
             }
             deviceAttributesSubscriptionMap[subscriptionId] = deviceAttributesSubscription;
+            telemetryWebsocketService.subscribe(subscriber);
         }
         return subscriptionId;
     }
diff --git a/ui/src/app/api/telemetry-websocket.service.js b/ui/src/app/api/telemetry-websocket.service.js
index dde4c62..99a7c80 100644
--- a/ui/src/app/api/telemetry-websocket.service.js
+++ b/ui/src/app/api/telemetry-websocket.service.js
@@ -20,11 +20,17 @@ export default angular.module('thingsboard.api.telemetryWebsocket', [thingsboard
     .factory('telemetryWebsocketService', TelemetryWebsocketService)
     .name;
 
+const RECONNECT_INTERVAL = 5000;
+const WS_IDLE_TIMEOUT = 90000;
+
 /*@ngInject*/
-function TelemetryWebsocketService($websocket, $timeout, $window, types, userService) {
+function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, types, userService) {
 
     var isOpening = false,
         isOpened = false,
+        isActive = false,
+        isReconnect = false,
+        reconnectSubscribers = [],
         lastCmdId = 0,
         subscribers = {},
         subscribersCount = 0,
@@ -36,7 +42,8 @@ function TelemetryWebsocketService($websocket, $timeout, $window, types, userSer
         telemetryUri,
         dataStream,
         location = $window.location,
-        socketCloseTimer;
+        socketCloseTimer,
+        reconnectTimer;
 
     if (location.protocol === "https:") {
         telemetryUri = "wss:";
@@ -46,11 +53,18 @@ function TelemetryWebsocketService($websocket, $timeout, $window, types, userSer
     telemetryUri += "//" + location.hostname + ":" + location.port;
     telemetryUri += "/api/ws/plugins/telemetry";
 
+
     var service = {
         subscribe: subscribe,
         unsubscribe: unsubscribe
     }
 
+    $rootScope.telemetryWsLogoutHandle = $rootScope.$on('unauthenticated', function (event, doLogout) {
+        if (doLogout) {
+            reset(true);
+        }
+    });
+
     return service;
 
     function publishCommands () {
@@ -74,12 +88,42 @@ function TelemetryWebsocketService($websocket, $timeout, $window, types, userSer
     function onOpen () {
         isOpening = false;
         isOpened = true;
-        publishCommands();
+        if (reconnectTimer) {
+            $timeout.cancel(reconnectTimer);
+            reconnectTimer = null;
+        }
+        if (isReconnect) {
+            isReconnect = false;
+            for (var r in reconnectSubscribers) {
+                var reconnectSubscriber = reconnectSubscribers[r];
+                if (reconnectSubscriber.onReconnected) {
+                    reconnectSubscriber.onReconnected();
+                }
+                subscribe(reconnectSubscriber);
+            }
+            reconnectSubscribers = [];
+        } else {
+            publishCommands();
+        }
     }
 
     function onClose () {
         isOpening = false;
         isOpened = false;
+        if (isActive) {
+            if (!isReconnect) {
+                reconnectSubscribers = [];
+                for (var id in subscribers) {
+                    reconnectSubscribers.push(subscribers[id]);
+                }
+                reset(false);
+                isReconnect = true;
+            }
+            if (reconnectTimer) {
+                $timeout.cancel(reconnectTimer);
+            }
+            reconnectTimer = $timeout(tryOpenSocket, RECONNECT_INTERVAL, false);
+        }
     }
 
     function onMessage (message) {
@@ -137,28 +181,60 @@ function TelemetryWebsocketService($websocket, $timeout, $window, types, userSer
     function checkToClose () {
         if (subscribersCount === 0 && isOpened) {
             if (!socketCloseTimer) {
-                socketCloseTimer = $timeout(closeSocket, 90000, false);
+                socketCloseTimer = $timeout(closeSocket, WS_IDLE_TIMEOUT, false);
             }
         }
     }
 
     function tryOpenSocket () {
+        isActive = true;
         if (!isOpened && !isOpening) {
             isOpening = true;
-            dataStream = $websocket(telemetryUri + '?token=' + userService.getJwtToken());
-            dataStream.onError(onError);
-            dataStream.onOpen(onOpen);
-            dataStream.onClose(onClose);
-            dataStream.onMessage(onMessage);
+            if (userService.isJwtTokenValid()) {
+                openSocket(userService.getJwtToken());
+            } else {
+                userService.refreshJwtToken().then(function success() {
+                    openSocket(userService.getJwtToken());
+                }, function fail() {
+                    isOpening = false;
+                    $rootScope.$broadcast('unauthenticated');
+                });
+            }
         }
         if (socketCloseTimer) {
             $timeout.cancel(socketCloseTimer);
+            socketCloseTimer = null;
         }
     }
 
+    function openSocket(token) {
+        dataStream = $websocket(telemetryUri + '?token=' + token);
+        dataStream.onError(onError);
+        dataStream.onOpen(onOpen);
+        dataStream.onClose(onClose);
+        dataStream.onMessage(onMessage);
+    }
+
     function closeSocket() {
+        isActive = false;
         if (isOpened) {
             dataStream.close();
         }
     }
+
+    function reset(closeSocket) {
+        if (socketCloseTimer) {
+            $timeout.cancel(socketCloseTimer);
+            socketCloseTimer = null;
+        }
+        lastCmdId = 0;
+        subscribers = {};
+        subscribersCount = 0;
+        cmdsWrapper.tsSubCmds = [];
+        cmdsWrapper.historyCmds = [];
+        cmdsWrapper.attrSubCmds = [];
+        if (closeSocket) {
+            closeSocket();
+        }
+    }
 }
diff --git a/ui/src/app/device/attribute/attribute-table.directive.js b/ui/src/app/device/attribute/attribute-table.directive.js
index bafe2b2..a3a4390 100644
--- a/ui/src/app/device/attribute/attribute-table.directive.js
+++ b/ui/src/app/device/attribute/attribute-table.directive.js
@@ -147,6 +147,12 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
             scope.subscriptionId = newSubscriptionId;
         }
 
+        scope.$on('$destroy', function() {
+            if (scope.subscriptionId) {
+                deviceService.unsubscribeForDeviceAttributes(scope.subscriptionId);
+            }
+        });
+
         scope.editAttribute = function($event, attribute) {
             if (!scope.attributeScope.clientSide) {
                 $event.stopPropagation();
diff --git a/ui/src/app/device/device.directive.js b/ui/src/app/device/device.directive.js
index 5dda015..9c927ae 100644
--- a/ui/src/app/device/device.directive.js
+++ b/ui/src/app/device/device.directive.js
@@ -32,11 +32,13 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl
 
         scope.$watch('device', function(newVal) {
             if (newVal) {
-                deviceService.getDeviceCredentials(scope.device.id.id).then(
-                    function success(credentials) {
-                        scope.deviceCredentials = credentials;
-                    }
-                );
+                if (scope.device.id) {
+                    deviceService.getDeviceCredentials(scope.device.id.id).then(
+                        function success(credentials) {
+                            scope.deviceCredentials = credentials;
+                        }
+                    );
+                }
                 if (scope.device.customerId && scope.device.customerId.id !== types.id.nullUid) {
                     scope.isAssignedToCustomer = true;
                     customerService.getCustomer(scope.device.customerId.id).then(
diff --git a/ui/src/app/device/device-fieldset.tpl.html b/ui/src/app/device/device-fieldset.tpl.html
index 3a7ab9a..99cedbd 100644
--- a/ui/src/app/device/device-fieldset.tpl.html
+++ b/ui/src/app/device/device-fieldset.tpl.html
@@ -60,6 +60,11 @@
 	    	</div>				
 		</md-input-container>
         <md-input-container class="md-block">
+            <md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'device.is-gateway' | translate }}"
+                         ng-model="device.additionalInfo.gateway">{{ 'device.is-gateway' | translate }}
+            </md-checkbox>
+        </md-input-container>
+        <md-input-container class="md-block">
             <label translate>device.description</label>
             <textarea ng-model="device.additionalInfo.description" rows="2"></textarea>
         </md-input-container>
diff --git a/ui/src/locale/en_US.json b/ui/src/locale/en_US.json
index f042993..cf1a97b 100644
--- a/ui/src/locale/en_US.json
+++ b/ui/src/locale/en_US.json
@@ -323,7 +323,8 @@
     "accessTokenCopiedMessage": "Device access token has been copied to clipboard",
     "assignedToCustomer": "Assigned to customer",
     "unable-delete-device-alias-title": "Unable to delete device alias",
-    "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}"
+    "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
+    "is-gateway": "Is gateway"
   },
   "dialog": {
     "close": "Close dialog"