* Allow configuration of checkout folder and version tracking type
via Plugin Configuration
* Display message to use if checkout folder is not configured or a
non-release version is running and version tracking against releases
is enabled
* Clear version cache when a change in the check configuration is
detected
* Mark check configurations for which an update is not possible with
a little exclamation mark
569 lines
21 KiB
JavaScript
569 lines
21 KiB
JavaScript
$(function() {
|
|
function SoftwareUpdateViewModel(parameters) {
|
|
var self = this;
|
|
|
|
self.loginState = parameters[0];
|
|
self.printerState = parameters[1];
|
|
self.settings = parameters[2];
|
|
self.popup = undefined;
|
|
|
|
self.updateInProgress = false;
|
|
self.waitingForRestart = false;
|
|
self.restartTimeout = undefined;
|
|
|
|
self.currentlyBeingUpdated = [];
|
|
|
|
self.working = ko.observable(false);
|
|
self.workingTitle = ko.observable();
|
|
self.workingDialog = undefined;
|
|
self.workingOutput = undefined;
|
|
self.loglines = ko.observableArray([]);
|
|
|
|
self.octoprintUnconfigured = ko.observable();
|
|
self.octoprintUnreleased = ko.observable();
|
|
|
|
self.config_cacheTtl = ko.observable();
|
|
self.config_checkoutFolder = ko.observable();
|
|
self.config_checkType = ko.observable();
|
|
|
|
self.configurationDialog = $("#settings_plugin_softwareupdate_configurationdialog");
|
|
|
|
self.config_availableCheckTypes = [
|
|
{"key": "github_release", "name": gettext("Release")},
|
|
{"key": "git_commit", "name": gettext("Commit")}
|
|
];
|
|
|
|
self.versions = new ItemListHelper(
|
|
"plugin.softwareupdate.versions",
|
|
{
|
|
"name": function(a, b) {
|
|
// sorts ascending, puts octoprint first
|
|
if (a.key.toLocaleLowerCase() == "octoprint") return -1;
|
|
if (b.key.toLocaleLowerCase() == "octoprint") return 1;
|
|
|
|
if (a.displayName.toLocaleLowerCase() < b.displayName.toLocaleLowerCase()) return -1;
|
|
if (a.displayName.toLocaleLowerCase() > b.displayName.toLocaleLowerCase()) return 1;
|
|
return 0;
|
|
}
|
|
},
|
|
{},
|
|
"name",
|
|
[],
|
|
[],
|
|
5
|
|
);
|
|
|
|
self.onUserLoggedIn = function() {
|
|
self.performCheck();
|
|
};
|
|
|
|
self._showPopup = function(options, eventListeners) {
|
|
self._closePopup();
|
|
self.popup = new PNotify(options);
|
|
|
|
if (eventListeners) {
|
|
var popupObj = self.popup.get();
|
|
_.each(eventListeners, function(value, key) {
|
|
popupObj.on(key, value);
|
|
})
|
|
}
|
|
};
|
|
|
|
self._updatePopup = function(options) {
|
|
if (self.popup === undefined) {
|
|
self._showPopup(options);
|
|
} else {
|
|
self.popup.update(options);
|
|
}
|
|
};
|
|
|
|
self._closePopup = function() {
|
|
if (self.popup !== undefined) {
|
|
self.popup.remove();
|
|
}
|
|
};
|
|
|
|
self.showPluginSettings = function() {
|
|
self._copyConfig();
|
|
self.configurationDialog.modal();
|
|
};
|
|
|
|
self.savePluginSettings = function() {
|
|
var data = {
|
|
plugins: {
|
|
softwareupdate: {
|
|
cache_ttl: parseInt(self.config_cacheTtl()),
|
|
octoprint_checkout_folder: self.config_checkoutFolder(),
|
|
octoprint_type: self.config_checkType()
|
|
}
|
|
}
|
|
};
|
|
self.settings.saveData(data, function() {
|
|
self.configurationDialog.modal("hide");
|
|
self._copyConfig();
|
|
self.performCheck();
|
|
});
|
|
};
|
|
|
|
self._copyConfig = function() {
|
|
self.config_cacheTtl(self.settings.settings.plugins.softwareupdate.cache_ttl());
|
|
self.config_checkoutFolder(self.settings.settings.plugins.softwareupdate.octoprint_checkout_folder());
|
|
self.config_checkType(self.settings.settings.plugins.softwareupdate.octoprint_type());
|
|
};
|
|
|
|
self.fromCheckResponse = function(data, ignoreSeen, showIfNothingNew) {
|
|
var versions = [];
|
|
_.each(data.information, function(value, key) {
|
|
value["key"] = key;
|
|
|
|
if (!value.hasOwnProperty("displayName") || value.displayName == "") {
|
|
value.displayName = value.key;
|
|
}
|
|
if (!value.hasOwnProperty("displayVersion") || value.displayVersion == "") {
|
|
value.displayVersion = value.information.local.name;
|
|
}
|
|
|
|
versions.push(value);
|
|
});
|
|
self.versions.updateItems(versions);
|
|
|
|
var octoprint = data.information["octoprint"];
|
|
if (octoprint && octoprint.hasOwnProperty("check")) {
|
|
var check = octoprint.check;
|
|
if (BRANCH != "master" && check["type"] == "github_release") {
|
|
self.octoprintUnreleased(true);
|
|
} else {
|
|
self.octoprintUnreleased(false);
|
|
}
|
|
|
|
var checkoutFolder = (check["checkout_folder"] || "").trim();
|
|
var updateFolder = (check["update_folder"] || "").trim();
|
|
var checkType = check["type"] || "";
|
|
if ((checkType == "github_release" || checkType == "git_commit") && checkoutFolder == "" && updateFolder == "") {
|
|
self.octoprintUnconfigured(true);
|
|
} else {
|
|
self.octoprintUnconfigured(false);
|
|
}
|
|
}
|
|
|
|
if (data.status == "updateAvailable" || data.status == "updatePossible") {
|
|
var text = gettext("There are updates available for the following components:");
|
|
|
|
text += "<ul>";
|
|
_.each(self.versions.items(), function(update_info) {
|
|
if (update_info.updateAvailable) {
|
|
var displayName = update_info.key;
|
|
if (update_info.hasOwnProperty("displayName")) {
|
|
displayName = update_info.displayName;
|
|
}
|
|
text += "<li>" + displayName + (update_info.updatePossible ? " <i class=\"icon-ok\"></i>" : "") + "</li>";
|
|
}
|
|
});
|
|
text += "</ul>";
|
|
|
|
text += "<small>" + gettext("Those components marked with <i class=\"icon-ok\"></i> can be updated directly.") + "</small>";
|
|
|
|
var options = {
|
|
title: gettext("Update Available"),
|
|
text: text,
|
|
hide: false
|
|
};
|
|
var eventListeners = {};
|
|
|
|
if (data.status == "updatePossible" && self.loginState.isAdmin()) {
|
|
// if user is admin, add action buttons
|
|
options["confirm"] = {
|
|
confirm: true,
|
|
buttons: [{
|
|
text: gettext("Ignore"),
|
|
click: function() {
|
|
self._markNotificationAsSeen(data.information);
|
|
self._showPopup({
|
|
text: gettext("You can make this message display again via \"Settings\" > \"Software Update\" > \"Check for update now\"")
|
|
});
|
|
}
|
|
}, {
|
|
text: gettext("Update now"),
|
|
addClass: "btn-primary",
|
|
click: self.update
|
|
}]
|
|
};
|
|
options["buttons"] = {
|
|
closer: false,
|
|
sticker: false
|
|
};
|
|
}
|
|
|
|
if (ignoreSeen || !self._hasNotificationBeenSeen(data.information)) {
|
|
self._showPopup(options, eventListeners);
|
|
}
|
|
} else if (data.status == "current") {
|
|
if (showIfNothingNew) {
|
|
self._showPopup({
|
|
title: gettext("Everything is up-to-date"),
|
|
hide: false,
|
|
type: "success"
|
|
});
|
|
} else {
|
|
self._closePopup();
|
|
}
|
|
}
|
|
};
|
|
|
|
self.performCheck = function(showIfNothingNew, force, ignoreSeen) {
|
|
if (!self.loginState.isUser()) return;
|
|
|
|
var url = PLUGIN_BASEURL + "softwareupdate/check";
|
|
if (force) {
|
|
url += "?force=true";
|
|
}
|
|
|
|
$.ajax({
|
|
url: url,
|
|
type: "GET",
|
|
dataType: "json",
|
|
success: function(data) {
|
|
self.fromCheckResponse(data, ignoreSeen, showIfNothingNew);
|
|
}
|
|
});
|
|
};
|
|
|
|
self._markNotificationAsSeen = function(data) {
|
|
if (!Modernizr.localstorage)
|
|
return false;
|
|
localStorage["plugin.softwareupdate.seen_information"] = JSON.stringify(self._informationToRemoteVersions(data));
|
|
};
|
|
|
|
self._hasNotificationBeenSeen = function(data) {
|
|
if (!Modernizr.localstorage)
|
|
return false;
|
|
|
|
if (localStorage["plugin.softwareupdate.seen_information"] == undefined)
|
|
return false;
|
|
|
|
var knownData = JSON.parse(localStorage["plugin.softwareupdate.seen_information"]);
|
|
var freshData = self._informationToRemoteVersions(data);
|
|
|
|
var hasBeenSeen = true;
|
|
_.each(freshData, function(value, key) {
|
|
if (!_.has(knownData, key) || knownData[key] != freshData[key]) {
|
|
hasBeenSeen = false;
|
|
}
|
|
});
|
|
return hasBeenSeen;
|
|
};
|
|
|
|
self._informationToRemoteVersions = function(data) {
|
|
var result = {};
|
|
_.each(data, function(value, key) {
|
|
result[key] = value.information.remote.value;
|
|
});
|
|
return result;
|
|
};
|
|
|
|
self.performUpdate = function(force) {
|
|
self.updateInProgress = true;
|
|
|
|
var options = {
|
|
title: gettext("Updating..."),
|
|
text: gettext("Now updating, please wait."),
|
|
icon: "icon-cog icon-spin",
|
|
hide: false,
|
|
buttons: {
|
|
closer: false,
|
|
sticker: false
|
|
}
|
|
};
|
|
self._showPopup(options);
|
|
|
|
$.ajax({
|
|
url: PLUGIN_BASEURL + "softwareupdate/update",
|
|
type: "POST",
|
|
dataType: "json",
|
|
contentType: "application/json; charset=UTF-8",
|
|
data: JSON.stringify({force: (force == true)}),
|
|
error: function() {
|
|
self.updateInProgress = false;
|
|
self._showPopup({
|
|
title: gettext("Update not started!"),
|
|
text: gettext("The update could not be started. Is it already active? Please consult the log for details."),
|
|
type: "error",
|
|
hide: false,
|
|
buttons: {
|
|
sticker: false
|
|
}
|
|
});
|
|
},
|
|
success: function(data) {
|
|
self.currentlyBeingUpdated = data.checks;
|
|
self._markWorking(gettext("Updating..."), gettext("Updating, please wait."));
|
|
}
|
|
});
|
|
};
|
|
|
|
self.update = function(force) {
|
|
if (self.updateInProgress) return;
|
|
if (!self.loginState.isAdmin()) return;
|
|
|
|
force = (force == true);
|
|
|
|
if (self.printerState.isPrinting()) {
|
|
self._showPopup({
|
|
title: gettext("Can't update while printing"),
|
|
text: gettext("A print job is currently in progress. Updating will be prevented until it is done."),
|
|
type: "error"
|
|
});
|
|
} else {
|
|
showConfirmationDialog({
|
|
message: gettext("This will update your OctoPrint installation and restart the server."),
|
|
onproceed: function(e) {
|
|
self.performUpdate(force);
|
|
}
|
|
});
|
|
}
|
|
|
|
};
|
|
|
|
self._showWorkingDialog = function(title) {
|
|
if (!self.loginState.isAdmin()) {
|
|
return;
|
|
}
|
|
|
|
self.working(true);
|
|
self.workingTitle(title);
|
|
self.workingDialog.modal("show");
|
|
};
|
|
|
|
self._markWorking = function(title, line, stream) {
|
|
if (stream === undefined) {
|
|
stream = "message";
|
|
}
|
|
|
|
self.loglines.removeAll();
|
|
self.loglines.push({line: line, stream: stream});
|
|
self._showWorkingDialog(title);
|
|
};
|
|
|
|
self._markDone = function(line, stream) {
|
|
if (stream === undefined) {
|
|
stream = "message";
|
|
}
|
|
|
|
self.working(false);
|
|
self.loglines.push({line: "", stream: stream});
|
|
self.loglines.push({line: line, stream: stream});
|
|
self._scrollWorkingOutputToEnd();
|
|
};
|
|
|
|
self._scrollWorkingOutputToEnd = function() {
|
|
self.workingOutput.scrollTop(self.workingOutput[0].scrollHeight - self.workingOutput.height());
|
|
};
|
|
|
|
self.onStartup = function() {
|
|
self.workingDialog = $("#settings_plugin_softwareupdate_workingdialog");
|
|
self.workingOutput = $("#settings_plugin_softwareupdate_workingdialog_output");
|
|
};
|
|
|
|
self.onServerDisconnect = function() {
|
|
if (self.restartTimeout !== undefined) {
|
|
clearTimeout(self.restartTimeout);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
self.onDataUpdaterReconnect = function() {
|
|
if (self.waitingForRestart) {
|
|
self.waitingForRestart = false;
|
|
|
|
var options = {
|
|
title: gettext("Restart successful!"),
|
|
text: gettext("The server was restarted successfully. The page will now reload automatically."),
|
|
type: "success",
|
|
hide: false
|
|
};
|
|
self._showPopup(options);
|
|
self.updateInProgress = false;
|
|
|
|
var delay = 5 + Math.floor(Math.random() * 5) + 1;
|
|
setTimeout(function() {location.reload(true);}, delay * 1000);
|
|
}
|
|
};
|
|
|
|
self.onDataUpdaterPluginMessage = function(plugin, data) {
|
|
if (plugin != "softwareupdate") {
|
|
return;
|
|
}
|
|
|
|
var messageType = data.type;
|
|
var messageData = data.data;
|
|
|
|
var options = undefined;
|
|
|
|
var restartType = undefined;
|
|
var title = undefined;
|
|
var text = undefined;
|
|
|
|
switch (messageType) {
|
|
case "loglines": {
|
|
if (self.working()) {
|
|
_.each(messageData.loglines, function(line) {
|
|
self.loglines.push(line);
|
|
});
|
|
self._scrollWorkingOutputToEnd();
|
|
}
|
|
break;
|
|
}
|
|
case "updating": {
|
|
console.log(JSON.stringify(messageData));
|
|
|
|
if (!self.working()) {
|
|
self._markWorking(gettext("Updating..."), gettext("Updating, please wait."));
|
|
}
|
|
|
|
text = _.sprintf(gettext("Now updating %(name)s to %(version)s"), {name: messageData.name, version: messageData.version});
|
|
self.loglines.push({line: "", stream: "separator"});
|
|
self.loglines.push({line: _.repeat("+", text.length), stream: "separator"});
|
|
self.loglines.push({line: text, stream: "message"});
|
|
self.loglines.push({line: _.repeat("+", text.length), stream: "separator"});
|
|
self._updatePopup({
|
|
text: text,
|
|
hide: false,
|
|
buttons: {
|
|
sticker: false
|
|
}
|
|
});
|
|
|
|
break;
|
|
}
|
|
case "restarting": {
|
|
console.log(JSON.stringify(messageData));
|
|
|
|
title = gettext("Update successful, restarting!");
|
|
text = gettext("The update finished successfully and the server will now be restarted.");
|
|
|
|
options = {
|
|
title: title,
|
|
text: text,
|
|
type: "success",
|
|
hide: false,
|
|
buttons: {
|
|
sticker: false
|
|
}
|
|
};
|
|
|
|
self.loglines.push({line: text, stream: "message"});
|
|
|
|
self.waitingForRestart = true;
|
|
self.restartTimeout = setTimeout(function() {
|
|
title = gettext("Restart failed");
|
|
text = gettext("The server apparently did not restart by itself, you'll have to do it manually. Please consult the log file on what went wrong.");
|
|
|
|
self._showPopup({
|
|
title: title,
|
|
text: text,
|
|
type: "error",
|
|
hide: false,
|
|
buttons: {
|
|
sticker: false
|
|
}
|
|
});
|
|
self.waitingForRestart = false;
|
|
|
|
self._markDone(text, "message_error");
|
|
}, 30000);
|
|
|
|
break;
|
|
}
|
|
case "restart_manually": {
|
|
console.log(JSON.stringify(messageData));
|
|
|
|
restartType = messageData.restart_type;
|
|
text = gettext("The update finished successfully, please restart OctoPrint now.");
|
|
if (restartType == "environment") {
|
|
text = gettext("The update finished successfully, please reboot the server now.");
|
|
}
|
|
|
|
title = gettext("Update successful, restart required!");
|
|
options = {
|
|
title: title,
|
|
text: text,
|
|
type: "success",
|
|
hide: false,
|
|
buttons: {
|
|
sticker: false
|
|
}
|
|
};
|
|
self.updateInProgress = false;
|
|
self._markDone(text);
|
|
break;
|
|
}
|
|
case "restart_failed": {
|
|
restartType = messageData.restart_type;
|
|
text = gettext("Restarting OctoPrint failed, please restart it manually. You might also want to consult the log file on what went wrong here.");
|
|
if (restartType == "environment") {
|
|
text = gettext("Rebooting the server failed, please reboot it manually. You might also want to consult the log file on what went wrong here.");
|
|
}
|
|
|
|
title = gettext("Restart failed");
|
|
options = {
|
|
title: title,
|
|
test: text,
|
|
type: "error",
|
|
hide: false,
|
|
buttons: {
|
|
sticker: false
|
|
}
|
|
};
|
|
self.waitingForRestart = false;
|
|
self.updateInProgress = false;
|
|
self._markDone(text, "message_error");
|
|
break;
|
|
}
|
|
case "success": {
|
|
title = gettext("Update successful!");
|
|
text = gettext("The update finished successfully.");
|
|
options = {
|
|
title: title,
|
|
text: text,
|
|
type: "success",
|
|
hide: false,
|
|
buttons: {
|
|
sticker: false
|
|
}
|
|
};
|
|
self.updateInProgress = false;
|
|
self._markDone(text);
|
|
break;
|
|
}
|
|
case "error": {
|
|
title = gettext("Update failed!");
|
|
text = gettext("The update did not finish successfully. Please consult the log for details.");
|
|
self._showPopup({
|
|
title: title,
|
|
text: text,
|
|
type: "error",
|
|
hide: false,
|
|
buttons: {
|
|
sticker: false
|
|
}
|
|
});
|
|
self.updateInProgress = false;
|
|
self._markDone(text, "message_error");
|
|
break;
|
|
}
|
|
case "update_versions": {
|
|
self.performCheck();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (options != undefined) {
|
|
self._showPopup(options);
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
// view model class, parameters for constructor, container to bind to
|
|
ADDITIONAL_VIEWMODELS.push([SoftwareUpdateViewModel, ["loginStateViewModel", "printerStateViewModel", "settingsViewModel"], document.getElementById("settings_plugin_softwareupdate")]);
|
|
});
|