MrDraw/src/octoprint/plugins/softwareupdate/static/js/softwareupdate.js
Gina Häußge 8666a28f64 SWU: Various improvements for better usability
* 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
2015-08-26 15:50:57 +02:00

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")]);
});