Commit 7c6cdb148c3c1b54ee31b477667144675bb45840

Authored by Igor Kulikov
Committed by GitHub
1 parent 31b24892

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

* TB-39: UI: Add Gateway flag.

* TB-39: UI: Update package version.
1 1 {
2 2 "name": "thingsboard",
3 3 "private": true,
4   - "version": "1.0.1",
  4 + "version": "1.1.0",
5 5 "description": "Thingsboard UI",
6 6 "licenses": [
7 7 {
... ...
... ... @@ -52,6 +52,11 @@ const apiProxy = httpProxy.createProxyServer({
52 52 }
53 53 });
54 54
  55 +apiProxy.on('error', function (err, req, res) {
  56 + console.warn('API proxy error: ' + err);
  57 + res.end('Error.');
  58 +});
  59 +
55 60 console.info(`Forwarding API requests to http://${forwardHost}:${forwardPort}`);
56 61
57 62 app.all('/api/*', (req, res) => {
... ...
... ... @@ -256,6 +256,9 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
256 256 type: types.dataKeyType.timeseries,
257 257 onData: function (data) {
258 258 onData(data, types.dataKeyType.timeseries);
  259 + },
  260 + onReconnected: function() {
  261 + onReconnected();
259 262 }
260 263 };
261 264
... ... @@ -278,6 +281,9 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
278 281 type: types.dataKeyType.timeseries,
279 282 onData: function (data) {
280 283 onData(data, types.dataKeyType.timeseries);
  284 + },
  285 + onReconnected: function() {
  286 + onReconnected();
281 287 }
282 288 };
283 289
... ... @@ -299,6 +305,9 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
299 305 type: types.dataKeyType.attribute,
300 306 onData: function (data) {
301 307 onData(data, types.dataKeyType.attribute);
  308 + },
  309 + onReconnected: function() {
  310 + onReconnected();
302 311 }
303 312 };
304 313
... ... @@ -428,6 +437,25 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
428 437 }
429 438 }
430 439
  440 + function onReconnected() {
  441 + if (datasourceType === types.datasourceType.device) {
  442 + for (var key in dataKeys) {
  443 + var dataKeysList = dataKeys[key];
  444 + for (var i = 0; i < dataKeysList.length; i++) {
  445 + var dataKey = dataKeysList[i];
  446 + var datasourceKey = key + '_' + i;
  447 + datasourceData[datasourceKey] = [];
  448 + for (var l in listeners) {
  449 + var listener = listeners[l];
  450 + listener.dataUpdated(datasourceData[datasourceKey],
  451 + listener.datasourceIndex,
  452 + dataKey.index);
  453 + }
  454 + }
  455 + }
  456 + }
  457 + }
  458 +
431 459 function onData(sourceData, type) {
432 460 for (var keyName in sourceData) {
433 461 var keyData = sourceData[keyName];
... ...
... ... @@ -307,12 +307,12 @@ function DeviceService($http, $q, $filter, telemetryWebsocketService, types) {
307 307 onSubscriptionData(data, subscriptionId);
308 308 }
309 309 };
310   - telemetryWebsocketService.subscribe(subscriber);
311 310 deviceAttributesSubscription = {
312 311 subscriber: subscriber,
313 312 attributes: null
314 313 }
315 314 deviceAttributesSubscriptionMap[subscriptionId] = deviceAttributesSubscription;
  315 + telemetryWebsocketService.subscribe(subscriber);
316 316 }
317 317 return subscriptionId;
318 318 }
... ...
... ... @@ -20,11 +20,17 @@ export default angular.module('thingsboard.api.telemetryWebsocket', [thingsboard
20 20 .factory('telemetryWebsocketService', TelemetryWebsocketService)
21 21 .name;
22 22
  23 +const RECONNECT_INTERVAL = 5000;
  24 +const WS_IDLE_TIMEOUT = 90000;
  25 +
23 26 /*@ngInject*/
24   -function TelemetryWebsocketService($websocket, $timeout, $window, types, userService) {
  27 +function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, types, userService) {
25 28
26 29 var isOpening = false,
27 30 isOpened = false,
  31 + isActive = false,
  32 + isReconnect = false,
  33 + reconnectSubscribers = [],
28 34 lastCmdId = 0,
29 35 subscribers = {},
30 36 subscribersCount = 0,
... ... @@ -36,7 +42,8 @@ function TelemetryWebsocketService($websocket, $timeout, $window, types, userSer
36 42 telemetryUri,
37 43 dataStream,
38 44 location = $window.location,
39   - socketCloseTimer;
  45 + socketCloseTimer,
  46 + reconnectTimer;
40 47
41 48 if (location.protocol === "https:") {
42 49 telemetryUri = "wss:";
... ... @@ -46,11 +53,18 @@ function TelemetryWebsocketService($websocket, $timeout, $window, types, userSer
46 53 telemetryUri += "//" + location.hostname + ":" + location.port;
47 54 telemetryUri += "/api/ws/plugins/telemetry";
48 55
  56 +
49 57 var service = {
50 58 subscribe: subscribe,
51 59 unsubscribe: unsubscribe
52 60 }
53 61
  62 + $rootScope.telemetryWsLogoutHandle = $rootScope.$on('unauthenticated', function (event, doLogout) {
  63 + if (doLogout) {
  64 + reset(true);
  65 + }
  66 + });
  67 +
54 68 return service;
55 69
56 70 function publishCommands () {
... ... @@ -74,12 +88,42 @@ function TelemetryWebsocketService($websocket, $timeout, $window, types, userSer
74 88 function onOpen () {
75 89 isOpening = false;
76 90 isOpened = true;
77   - publishCommands();
  91 + if (reconnectTimer) {
  92 + $timeout.cancel(reconnectTimer);
  93 + reconnectTimer = null;
  94 + }
  95 + if (isReconnect) {
  96 + isReconnect = false;
  97 + for (var r in reconnectSubscribers) {
  98 + var reconnectSubscriber = reconnectSubscribers[r];
  99 + if (reconnectSubscriber.onReconnected) {
  100 + reconnectSubscriber.onReconnected();
  101 + }
  102 + subscribe(reconnectSubscriber);
  103 + }
  104 + reconnectSubscribers = [];
  105 + } else {
  106 + publishCommands();
  107 + }
78 108 }
79 109
80 110 function onClose () {
81 111 isOpening = false;
82 112 isOpened = false;
  113 + if (isActive) {
  114 + if (!isReconnect) {
  115 + reconnectSubscribers = [];
  116 + for (var id in subscribers) {
  117 + reconnectSubscribers.push(subscribers[id]);
  118 + }
  119 + reset(false);
  120 + isReconnect = true;
  121 + }
  122 + if (reconnectTimer) {
  123 + $timeout.cancel(reconnectTimer);
  124 + }
  125 + reconnectTimer = $timeout(tryOpenSocket, RECONNECT_INTERVAL, false);
  126 + }
83 127 }
84 128
85 129 function onMessage (message) {
... ... @@ -137,28 +181,60 @@ function TelemetryWebsocketService($websocket, $timeout, $window, types, userSer
137 181 function checkToClose () {
138 182 if (subscribersCount === 0 && isOpened) {
139 183 if (!socketCloseTimer) {
140   - socketCloseTimer = $timeout(closeSocket, 90000, false);
  184 + socketCloseTimer = $timeout(closeSocket, WS_IDLE_TIMEOUT, false);
141 185 }
142 186 }
143 187 }
144 188
145 189 function tryOpenSocket () {
  190 + isActive = true;
146 191 if (!isOpened && !isOpening) {
147 192 isOpening = true;
148   - dataStream = $websocket(telemetryUri + '?token=' + userService.getJwtToken());
149   - dataStream.onError(onError);
150   - dataStream.onOpen(onOpen);
151   - dataStream.onClose(onClose);
152   - dataStream.onMessage(onMessage);
  193 + if (userService.isJwtTokenValid()) {
  194 + openSocket(userService.getJwtToken());
  195 + } else {
  196 + userService.refreshJwtToken().then(function success() {
  197 + openSocket(userService.getJwtToken());
  198 + }, function fail() {
  199 + isOpening = false;
  200 + $rootScope.$broadcast('unauthenticated');
  201 + });
  202 + }
153 203 }
154 204 if (socketCloseTimer) {
155 205 $timeout.cancel(socketCloseTimer);
  206 + socketCloseTimer = null;
156 207 }
157 208 }
158 209
  210 + function openSocket(token) {
  211 + dataStream = $websocket(telemetryUri + '?token=' + token);
  212 + dataStream.onError(onError);
  213 + dataStream.onOpen(onOpen);
  214 + dataStream.onClose(onClose);
  215 + dataStream.onMessage(onMessage);
  216 + }
  217 +
159 218 function closeSocket() {
  219 + isActive = false;
160 220 if (isOpened) {
161 221 dataStream.close();
162 222 }
163 223 }
  224 +
  225 + function reset(closeSocket) {
  226 + if (socketCloseTimer) {
  227 + $timeout.cancel(socketCloseTimer);
  228 + socketCloseTimer = null;
  229 + }
  230 + lastCmdId = 0;
  231 + subscribers = {};
  232 + subscribersCount = 0;
  233 + cmdsWrapper.tsSubCmds = [];
  234 + cmdsWrapper.historyCmds = [];
  235 + cmdsWrapper.attrSubCmds = [];
  236 + if (closeSocket) {
  237 + closeSocket();
  238 + }
  239 + }
164 240 }
... ...
... ... @@ -147,6 +147,12 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
147 147 scope.subscriptionId = newSubscriptionId;
148 148 }
149 149
  150 + scope.$on('$destroy', function() {
  151 + if (scope.subscriptionId) {
  152 + deviceService.unsubscribeForDeviceAttributes(scope.subscriptionId);
  153 + }
  154 + });
  155 +
150 156 scope.editAttribute = function($event, attribute) {
151 157 if (!scope.attributeScope.clientSide) {
152 158 $event.stopPropagation();
... ...
... ... @@ -60,6 +60,11 @@
60 60 </div>
61 61 </md-input-container>
62 62 <md-input-container class="md-block">
  63 + <md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'device.is-gateway' | translate }}"
  64 + ng-model="device.additionalInfo.gateway">{{ 'device.is-gateway' | translate }}
  65 + </md-checkbox>
  66 + </md-input-container>
  67 + <md-input-container class="md-block">
63 68 <label translate>device.description</label>
64 69 <textarea ng-model="device.additionalInfo.description" rows="2"></textarea>
65 70 </md-input-container>
... ...
... ... @@ -32,11 +32,13 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl
32 32
33 33 scope.$watch('device', function(newVal) {
34 34 if (newVal) {
35   - deviceService.getDeviceCredentials(scope.device.id.id).then(
36   - function success(credentials) {
37   - scope.deviceCredentials = credentials;
38   - }
39   - );
  35 + if (scope.device.id) {
  36 + deviceService.getDeviceCredentials(scope.device.id.id).then(
  37 + function success(credentials) {
  38 + scope.deviceCredentials = credentials;
  39 + }
  40 + );
  41 + }
40 42 if (scope.device.customerId && scope.device.customerId.id !== types.id.nullUid) {
41 43 scope.isAssignedToCustomer = true;
42 44 customerService.getCustomer(scope.device.customerId.id).then(
... ...
... ... @@ -323,7 +323,8 @@
323 323 "accessTokenCopiedMessage": "Device access token has been copied to clipboard",
324 324 "assignedToCustomer": "Assigned to customer",
325 325 "unable-delete-device-alias-title": "Unable to delete device alias",
326   - "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}"
  326 + "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):<br/>{{widgetsList}}",
  327 + "is-gateway": "Is gateway"
327 328 },
328 329 "dialog": {
329 330 "close": "Close dialog"
... ...