thingsboard-aplcache
Changes
.gitignore 1(+1 -0)
application/build.gradle 20(+20 -0)
application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java 128(+128 -0)
ui/package.json 1(+1 -0)
ui/src/app/api/admin.service.js 14(+13 -1)
ui/src/app/api/user.service.js 10(+9 -1)
ui/src/app/services/info-toast.tpl.html 25(+25 -0)
ui/src/app/services/toast.scss 7(+7 -0)
ui/src/app/services/toast.service.js 14(+12 -2)
Details
.gitignore 1(+1 -0)
diff --git a/.gitignore b/.gitignore
index e14c866..6f6dd61 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,4 @@ pom.xml.versionsBackup
**/target
**/Californium.properties
**/.env
+.instance_id
application/build.gradle 20(+20 -0)
diff --git a/application/build.gradle b/application/build.gradle
index 75a0e52..25ad51d 100644
--- a/application/build.gradle
+++ b/application/build.gradle
@@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+import org.apache.tools.ant.filters.ReplaceTokens
+
buildscript {
ext {
osPackageVersion = "3.8.0"
@@ -56,6 +59,7 @@ ospackage {
// Copy the config files
from("target/conf") {
+ exclude "${pkgName}.conf"
fileType CONFIG | NOREPLACE
fileMode 0754
into "conf"
@@ -84,6 +88,14 @@ buildRpm {
requires("java-1.8.0")
+ from("target/conf") {
+ include "${pkgName}.conf"
+ filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm'])
+ fileType CONFIG | NOREPLACE
+ fileMode 0754
+ into "${pkgInstallFolder}/conf"
+ }
+
preInstall file("${buildDir}/control/rpm/preinst")
postInstall file("${buildDir}/control/rpm/postinst")
preUninstall file("${buildDir}/control/rpm/prerm")
@@ -113,6 +125,14 @@ buildDeb {
requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer").or("openjdk-8-jre-headless")
+ from("target/conf") {
+ include "${pkgName}.conf"
+ filter(ReplaceTokens, tokens: ['pkg.platform': 'deb'])
+ fileType CONFIG | NOREPLACE
+ fileMode 0754
+ into "${pkgInstallFolder}/conf"
+ }
+
configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf")
configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml")
configurationFile("${pkgInstallFolder}/conf/logback.xml")
diff --git a/application/src/main/conf/thingsboard.conf b/application/src/main/conf/thingsboard.conf
index 328054f..ef977ee 100644
--- a/application/src/main/conf/thingsboard.conf
+++ b/application/src/main/conf/thingsboard.conf
@@ -14,6 +14,6 @@
# limitations under the License.
#
-export JAVA_OPTS="$JAVA_OPTS"
+export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@"
export LOG_FILENAME=${pkg.name}.out
export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions
diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java
index b17c394..bc3c47e 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java
@@ -22,6 +22,8 @@ import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.mail.MailService;
+import org.thingsboard.server.service.update.UpdateService;
+import org.thingsboard.server.service.update.model.UpdateMessage;
@RestController
@RequestMapping("/api/admin")
@@ -33,6 +35,9 @@ public class AdminController extends BaseController {
@Autowired
private AdminSettingsService adminSettingsService;
+ @Autowired
+ private UpdateService updateService;
+
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/{key}", method = RequestMethod.GET)
@ResponseBody
@@ -72,4 +77,16 @@ public class AdminController extends BaseController {
throw handleException(e);
}
}
+
+ @PreAuthorize("hasAuthority('SYS_ADMIN')")
+ @RequestMapping(value = "/updates", method = RequestMethod.GET)
+ @ResponseBody
+ public UpdateMessage checkUpdates() throws ThingsboardException {
+ try {
+ return updateService.checkUpdates();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
}
diff --git a/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java
new file mode 100644
index 0000000..0856b8a
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.server.service.update;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+import org.thingsboard.server.service.update.model.UpdateMessage;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+@Service
+@Slf4j
+public class DefaultUpdateService implements UpdateService {
+
+ private static final String INSTANCE_ID_FILE = ".instance_id";
+ private static final String UPDATE_SERVER_BASE_URL = "https://updates.thingsboard.io";
+
+ private static final String PLATFORM_PARAM = "platform";
+ private static final String VERSION_PARAM = "version";
+ private static final String INSTANCE_ID_PARAM = "instanceId";
+
+ @Value("${updates.enabled}")
+ private boolean updatesEnabled;
+
+ private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
+
+ private ScheduledFuture checkUpdatesFuture = null;
+ private RestTemplate restClient = new RestTemplate();
+
+ private UpdateMessage updateMessage;
+
+ private String platform;
+ private String version;
+ private UUID instanceId = null;
+
+ @PostConstruct
+ private void init() {
+ updateMessage = new UpdateMessage("", false);
+ if (updatesEnabled) {
+ try {
+ platform = System.getProperty("platform", "unknown");
+ version = getClass().getPackage().getImplementationVersion();
+ if (version == null) {
+ version = "unknown";
+ }
+ Path instanceIdPath = Paths.get(INSTANCE_ID_FILE);
+ if (Files.exists(instanceIdPath)) {
+ byte[] data = Files.readAllBytes(instanceIdPath);
+ if (data != null && data.length > 0) {
+ try {
+ instanceId = UUID.fromString(new String(data));
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ }
+ if (instanceId == null) {
+ instanceId = UUID.randomUUID();
+ Files.write(instanceIdPath, instanceId.toString().getBytes());
+ }
+ checkUpdatesFuture = scheduler.scheduleAtFixedRate(checkUpdatesRunnable, 0, 1, TimeUnit.HOURS);
+ } catch (Exception e) {}
+ }
+ }
+
+ @PreDestroy
+ private void destroy() {
+ try {
+ if (checkUpdatesFuture != null) {
+ checkUpdatesFuture.cancel(true);
+ }
+ scheduler.shutdownNow();
+ } catch (Exception e) {}
+ }
+
+ Runnable checkUpdatesRunnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ log.trace("Executing check update method for instanceId [{}], platform [{}] and version [{}]", instanceId, platform, version);
+ ObjectNode request = new ObjectMapper().createObjectNode();
+ request.put(PLATFORM_PARAM, platform);
+ request.put(VERSION_PARAM, version);
+ request.put(INSTANCE_ID_PARAM, instanceId.toString());
+ JsonNode response = restClient.postForObject(UPDATE_SERVER_BASE_URL+"/api/thingsboard/updates", request, JsonNode.class);
+ updateMessage = new UpdateMessage(
+ response.get("message").asText(),
+ response.get("updateAvailable").asBoolean()
+ );
+ } catch (Exception e) {
+ log.trace(e.getMessage());
+ }
+ }
+ };
+
+ @Override
+ public UpdateMessage checkUpdates() {
+ return updateMessage;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/update/model/UpdateMessage.java b/application/src/main/java/org/thingsboard/server/service/update/model/UpdateMessage.java
new file mode 100644
index 0000000..bbb8511
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/update/model/UpdateMessage.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.update.model;
+
+import lombok.Data;
+
+@Data
+public class UpdateMessage {
+
+ private final String message;
+ private final boolean isUpdateAvailable;
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java b/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java
new file mode 100644
index 0000000..18bfb2f
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/update/UpdateService.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.server.service.update;
+
+import org.thingsboard.server.service.update.model.UpdateMessage;
+
+public interface UpdateService {
+
+ UpdateMessage checkUpdates();
+
+}
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 42b37db..778406a 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -184,3 +184,7 @@ cache:
policy: "${CACHE_DEVICE_CREDENTIAL_MAX_SIZE_POLICY:PER_NODE}"
size: "${CACHE_DEVICE_CREDENTIAL_MAX_SIZE_SIZE:1000000}"
+# Check new version updates parameters
+updates:
+ # Enable/disable updates checking.
+ enabled: "${UPDATES_ENABLED:true}"
diff --git a/application/src/main/scripts/windows/service.xml b/application/src/main/scripts/windows/service.xml
index b2acc45..becbcdc 100644
--- a/application/src/main/scripts/windows/service.xml
+++ b/application/src/main/scripts/windows/service.xml
@@ -7,6 +7,7 @@
<logmode>rotate</logmode>
<env name="LOADER_PATH" value="%BASE%\conf,%BASE%\extensions" />
<executable>java</executable>
+ <startargument>-Dplatform=windows</startargument>
<startargument>-jar</startargument>
<startargument>%BASE%\lib\${pkg.name}.jar</startargument>
</service>
ui/package.json 1(+1 -0)
diff --git a/ui/package.json b/ui/package.json
index ed4b5b1..a36bb09 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -64,6 +64,7 @@
"ngreact": "^0.3.0",
"objectpath": "^1.2.1",
"oclazyload": "^1.0.9",
+ "raphael": "^2.2.7",
"rc-select": "^6.6.1",
"react": "^15.4.1",
"react-ace": "^4.1.0",
ui/src/app/api/admin.service.js 14(+13 -1)
diff --git a/ui/src/app/api/admin.service.js b/ui/src/app/api/admin.service.js
index cd41966..1591397 100644
--- a/ui/src/app/api/admin.service.js
+++ b/ui/src/app/api/admin.service.js
@@ -23,7 +23,8 @@ function AdminService($http, $q) {
var service = {
getAdminSettings: getAdminSettings,
saveAdminSettings: saveAdminSettings,
- sendTestMail: sendTestMail
+ sendTestMail: sendTestMail,
+ checkUpdates: checkUpdates
}
return service;
@@ -60,4 +61,15 @@ function AdminService($http, $q) {
});
return deferred.promise;
}
+
+ function checkUpdates() {
+ var deferred = $q.defer();
+ var url = '/api/admin/updates';
+ $http.get(url, null).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
}
ui/src/app/api/user.service.js 10(+9 -1)
diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js
index ea86cd6..516d823 100644
--- a/ui/src/app/api/user.service.js
+++ b/ui/src/app/api/user.service.js
@@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin,
.name;
/*@ngInject*/
-function UserService($http, $q, $rootScope, store, jwtHelper, $translate, $state) {
+function UserService($http, $q, $rootScope, adminService, toast, store, jwtHelper, $translate, $state) {
var currentUser = null,
currentUserDetails = null,
userLoaded = false;
@@ -382,6 +382,14 @@ function UserService($http, $q, $rootScope, store, jwtHelper, $translate, $state
place = 'home.dashboards.dashboard';
params = {dashboardId: currentUserDetails.additionalInfo.defaultDashboardId};
}
+ } else if (currentUser.authority === 'SYS_ADMIN') {
+ adminService.checkUpdates().then(
+ function (updateMessage) {
+ if (updateMessage && updateMessage.updateAvailable) {
+ toast.showInfo(updateMessage.message, 0, null, 'bottom right');
+ }
+ }
+ );
}
$state.go(place, params);
} else {
ui/src/app/services/info-toast.tpl.html 25(+25 -0)
diff --git a/ui/src/app/services/info-toast.tpl.html b/ui/src/app/services/info-toast.tpl.html
new file mode 100644
index 0000000..7a1f29d
--- /dev/null
+++ b/ui/src/app/services/info-toast.tpl.html
@@ -0,0 +1,25 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-toast class="tb-info-toast">
+ <div class="md-toast-content">
+ <div class="md-toast-text" ng-bind-html="vm.message"></div>
+ <md-button class="md-action md-highlight md-accent" ng-click="vm.closeToast()">
+ {{ 'action.close' | translate }}
+ </md-button>
+ </div>
+</md-toast>
ui/src/app/services/toast.scss 7(+7 -0)
diff --git a/ui/src/app/services/toast.scss b/ui/src/app/services/toast.scss
index b1740f1..5730d88 100644
--- a/ui/src/app/services/toast.scss
+++ b/ui/src/app/services/toast.scss
@@ -13,6 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+md-toast.tb-info-toast .md-toast-content {
+ font-size: 18px;
+ padding: 18px;
+ height: 100%;
+}
+
md-toast.tb-success-toast .md-toast-content {
font-size: 18px !important;
background-color: green;
ui/src/app/services/toast.service.js 14(+12 -2)
diff --git a/ui/src/app/services/toast.service.js b/ui/src/app/services/toast.service.js
index d9cd7b0..b18abda 100644
--- a/ui/src/app/services/toast.service.js
+++ b/ui/src/app/services/toast.service.js
@@ -15,6 +15,7 @@
*/
/* eslint-disable import/no-unresolved, import/default */
+import infoToast from './info-toast.tpl.html';
import successToast from './success-toast.tpl.html';
import errorToast from './error-toast.tpl.html';
@@ -26,6 +27,7 @@ export default function Toast($mdToast, $document) {
var showing = false;
var service = {
+ showInfo: showInfo,
showSuccess: showSuccess,
showError: showError,
hide: hide
@@ -33,7 +35,15 @@ export default function Toast($mdToast, $document) {
return service;
+ function showInfo(infoMessage, delay, toastParent, position) {
+ showMessage(infoToast, infoMessage, delay, toastParent, position);
+ }
+
function showSuccess(successMessage, delay, toastParent, position) {
+ showMessage(successToast, successMessage, delay, toastParent, position);
+ }
+
+ function showMessage(templateUrl, message, delay, toastParent, position) {
if (!toastParent) {
toastParent = angular.element($document[0].getElementById('toast-parent'));
}
@@ -45,8 +55,8 @@ export default function Toast($mdToast, $document) {
position: position,
controller: 'ToastController',
controllerAs: 'vm',
- templateUrl: successToast,
- locals: {message: successMessage},
+ templateUrl: templateUrl,
+ locals: {message: message},
parent: toastParent
});
}