TB-43: Implement updates checking service

This commit is contained in:
Igor Kulikov 2017-03-08 20:06:04 +02:00
parent c745c1ce02
commit 05404d8727
13 changed files with 274 additions and 23 deletions

1
.gitignore vendored
View File

@ -30,3 +30,4 @@ pom.xml.versionsBackup
**/target
**/Californium.properties
**/.env
.instance_id

View File

@ -88,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")
@ -103,14 +111,6 @@ buildRpm {
into "/usr/lib/systemd/system"
}
from("target/conf") {
include "${pkgName}.conf"
filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm'])
fileType CONFIG | NOREPLACE
fileMode 0754
into "conf"
}
directory(pkgLogFolder, 0755)
link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
@ -125,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")
@ -138,14 +146,6 @@ buildDeb {
user pkgName
permissionGroup pkgName
from("target/conf") {
include "${pkgName}.conf"
filter(ReplaceTokens, tokens: ['pkg.platform': 'deb'])
fileType CONFIG | NOREPLACE
fileMode 0754
into "conf"
}
directory(pkgLogFolder, 0755)
link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar")
link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")

View File

@ -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
@ -76,10 +81,9 @@ public class AdminController extends BaseController {
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/updates", method = RequestMethod.GET)
@ResponseBody
public String checkUpdates() throws ThingsboardException {
public UpdateMessage checkUpdates() throws ThingsboardException {
try {
return "<div>New update Thingsboard version 1.2 is available.<br/>" +
"<a href='https://github.com/thingsboard/thingsboard/releases/download/v1.1/thingsboard-1.1.deb'>Download package</a></div>";
return updateService.checkUpdates();
} catch (Exception e) {
throw handleException(e);
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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}"

View File

@ -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",

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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>

View File

@ -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;

View File

@ -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
});
}