thingsboard-aplcache
Changes
ui/package.json 2(+1 -1)
ui/server.js 5(+5 -0)
ui/src/app/api/datasource.service.js 28(+28 -0)
ui/src/app/api/device.service.js 2(+1 -1)
ui/src/app/device/device.directive.js 12(+7 -5)
ui/src/locale/en_US.json 3(+2 -1)
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) => {
ui/src/app/api/datasource.service.js 28(+28 -0)
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];
ui/src/app/api/device.service.js 2(+1 -1)
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();
ui/src/app/device/device.directive.js 12(+7 -5)
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>
ui/src/locale/en_US.json 3(+2 -1)
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"