Commit 7c6cdb148c3c1b54ee31b477667144675bb45840
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.
Showing
9 changed files
with
140 additions
and
17 deletions
... | ... | @@ -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" | ... | ... |