Merge remote-tracking branch 'remotes/upstream/devel' into dev/folderSupport

Conflicts:
	src/octoprint/static/js/app/viewmodels/files.js
This commit is contained in:
Salandora 2015-10-03 12:33:11 +02:00
commit 59cb448913
52 changed files with 2550 additions and 1195 deletions

View file

@ -2,6 +2,19 @@
## 1.3.0 (unreleased)
### New Features
* A new wizard dialog for system setups that can also be extended by plugins. Replaces the first run dialog
for setting up access control and can also be triggered in other cases than only the first run, e.g.
if Plugins necessitate user input to function properly.
* An About dialog including licenses, authors, the changelog and more, extendable by plugins if necessary.
* New features within the plugin system (TODO):
* New plugin mixin `UiPlugin` for plugins that want to provide an alternative web interface delivered by the
server.
* Extracted a Javascript client library for utilizing the server's API, can be reused by `UiPlugin`s. (TODO)
(TODO) = needs to be further described and documented
### Improvements
* Upgraded versioneer, generated version numbers are now PEP440 compatible (relevant
@ -41,6 +54,8 @@
but a target temperature is set - this way the graph should be more responsive
while monitoring a manual heatup.
* Documentation improvements
* Test buttons for webcam snapshot & stream URL, ffmpeg path and some other settings
(see also [#183](https://github.com/foosel/OctoPrint/issues/183)).
### Bug Fixes

View file

@ -53,26 +53,22 @@ $(function() {
};
self._sendData = function(data, callback) {
$.ajax({
url: BASEURL + "plugin/corewizard/acl",
type: "POST",
dataType: "json",
data: data,
success: function() {
OctoPrint.postJson("plugin/corewizard/acl", data)
.done(function() {
self.setup(true);
self.decision(data.ac);
if (data.ac) {
// we now log the user in
var user = data.user;
var pass = data.pass1;
self.loginStateViewModel.login(user, pass, true, function() {
if (callback) callback();
});
self.loginStateViewModel.login(user, pass, true)
.done(function() {
if (callback) callback();
});
} else {
if (callback) callback();
}
}
});
});
};
self.onWizardTabChange = function(current, next) {

View file

@ -70,6 +70,7 @@ $(function() {
dataType: "json",
maxNumberOfFiles: 1,
autoUpload: false,
headers: OctoPrint.getRequestHeaders(),
add: function(e, data) {
if (data.files.length == 0) {
return false;
@ -131,14 +132,11 @@ $(function() {
return (item.key == data.key);
});
$.ajax({
url: data.resource(),
type: "DELETE",
success: function() {
OctoPrint.slicing.deleteProfileForSlicer("cura", data.key, {url: data.resource})
.done(function() {
self.requestData();
self.slicingViewModel.requestData();
}
});
});
};
self.makeProfileDefault = function(data) {
@ -156,16 +154,10 @@ $(function() {
item.isdefault(true);
}
$.ajax({
url: data.resource(),
type: "PATCH",
dataType: "json",
data: JSON.stringify({default: true}),
contentType: "application/json; charset=UTF-8",
success: function() {
OctoPrint.slicing.updateProfileForSlicer("cura", data.key, {default: true}, {url: data.resource()})
.done(function() {
self.requestData();
}
});
});
};
self.showImportProfileDialog = function(makeDefault) {
@ -176,13 +168,9 @@ $(function() {
$("#settings_plugin_cura_import").modal("show");
};
self.testEnginePath = function(successCallback) {
self.sendTestRequest(self.settings.plugins.cura.cura_engine());
};
self.sendTestRequest = function(enginePath, successCallback) {
if (successCallback == undefined) {
successCallback = function(response) {
self.testEnginePath = function() {
OctoPrint.util.testExecutable(self.settings.plugins.cura.cura_engine())
.done(function(response) {
if (!response.result) {
if (!response.exists) {
self.pathText(gettext("The path doesn't exist"));
@ -196,31 +184,12 @@ $(function() {
}
self.pathOk(response.result);
self.pathBroken(!response.result);
}
}
$.ajax({
url: API_BASEURL + "util/test",
type: "POST",
dataType: "json",
data: JSON.stringify({
command: "path",
path: enginePath,
check_type: "file",
check_access: "x"
}),
contentType: "application/json; charset=UTF-8",
success: successCallback
})
});
};
self.requestData = function() {
$.ajax({
url: API_BASEURL + "slicing/cura/profiles",
type: "GET",
dataType: "json",
success: self.fromResponse
});
OctoPrint.slicing.listProfilesForSlicer("cura")
.done(self.fromResponse);
};
self.fromResponse = function(data) {

View file

@ -1,3 +1,70 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var exports = {};
exports.get = function(refresh, opts) {
return OctoPrint.get(OctoPrint.getSimpleApiUrl("pluginmanager") + ((refresh) ? "?refresh_repository=true" : ""), opts);
};
exports.getWithRefresh = function(opts) {
return exports.get(true, opts);
};
exports.getWithoutRefresh = function(opts) {
return exports.get(false, opts);
};
exports.install = function(pluginUrl, dependencyLinks, opts) {
var data = {
url: pluginUrl,
dependency_links: !!dependencyLinks
};
return OctoPrint.simpleApiCommand("pluginmanager", "install", data, opts);
};
exports.reinstall = function(plugin, pluginUrl, dependencyLinks, opts) {
var data = {
url: pluginUrl,
dependency_links: !!dependencyLinks,
reinstall: plugin,
force: true
};
return OctoPrint.simpleApiCommand("pluginmanager", "install", data, opts);
};
exports.uninstall = function(plugin, opts) {
var data = {
plugin: plugin
};
return OctoPrint.simpleApiCommand("pluginmanager", "uninstall", data, opts);
};
exports.enable = function(plugin, opts) {
var data = {
plugin: plugin
};
return OctoPrint.simpleApiCommand("pluginmanager", "enable", data, opts);
};
exports.disable = function(plugin, opts) {
var data = {
plugin: plugin
};
return OctoPrint.simpleApiCommand("pluginmanager", "disable", data, opts);
};
exports.upload = function(file) {
return OctoPrint.upload(OctoPrint.getBlueprintUrl("pluginmanager") + "upload_archive", file);
};
OctoPrint.plugins.pluginmanager = exports;
});
$(function() {
function PluginManagerViewModel(parameters) {
var self = this;
@ -5,6 +72,7 @@ $(function() {
self.loginState = parameters[0];
self.settingsViewModel = parameters[1];
self.printerState = parameters[2];
self.systemViewModel = parameters[3];
self.config_repositoryUrl = ko.observable();
self.config_repositoryTtl = ko.observable();
@ -104,6 +172,20 @@ $(function() {
self.workingDialog = undefined;
self.workingOutput = undefined;
self.restartCommandSpec = undefined;
self.systemViewModel.systemActions.subscribe(function() {
var lastResponse = self.systemViewModel.lastCommandResponse;
if (!lastResponse || !lastResponse.core) {
self.restartCommandSpec = undefined;
return;
}
var restartSpec = _.filter(lastResponse.core, function(spec) { return spec.action == "restart" });
self.restartCommandSpec = restartSpec != undefined && restartSpec.length > 0 ? restartSpec[0] : undefined;
});
self.notifications = [];
self.enableManagement = ko.computed(function() {
return !self.printerState.isPrinting();
});
@ -247,12 +329,8 @@ $(function() {
return;
}
$.ajax({
url: API_BASEURL + "plugin/pluginmanager" + ((includeRepo) ? "?refresh_repository=true" : ""),
type: "GET",
dataType: "json",
success: self.fromResponse
});
OctoPrint.plugins.pluginmanager.get(includeRepo)
.done(self.fromResponse);
};
self.togglePlugin = function(data) {
@ -266,19 +344,25 @@ $(function() {
if (data.key == "pluginmanager") return;
var command = self._getToggleCommand(data);
var onSuccess = self.requestData,
onError = function() {
new PNotify({
title: gettext("Something went wrong"),
text: gettext("Please consult octoprint.log for details"),
type: "error",
hide: false
})
};
var payload = {plugin: data.key};
self._postCommand(command, payload, function(response) {
self.requestData();
}, function() {
new PNotify({
title: gettext("Something went wrong"),
text: gettext("Please consult octoprint.log for details"),
type: "error",
hide: false
})
});
if (self._getToggleCommand(data) == "enable") {
OctoPrint.plugins.pluginmanager.enable(data.key)
.done(onSuccess)
.fail(onError);
} else {
OctoPrint.plugins.pluginmanager.disable(data.key)
.done(onSuccess)
.fail(onError);
}
};
self.showRepository = function() {
@ -298,11 +382,7 @@ $(function() {
return;
}
if (self.installed(data)) {
self.installPlugin(data.archive, data.title, data.id, data.follow_dependency_links || self.followDependencyLinks());
} else {
self.installPlugin(data.archive, data.title, undefined, data.follow_dependency_links || self.followDependencyLinks());
}
self.installPlugin(data.archive, data.title, (self.installed(data) ? data.id : undefined), data.follow_dependency_links || self.followDependencyLinks());
};
self.installPlugin = function(url, name, reinstall, followDependencyLinks) {
@ -337,26 +417,33 @@ $(function() {
}
self._markWorking(workTitle, workText);
var command = "install";
var payload = {url: url, dependency_links: followDependencyLinks};
if (reinstall) {
payload["plugin"] = reinstall;
payload["force"] = true;
}
var onSuccess = function() {
self.requestData();
self.installUrl("");
},
onError = function() {
new PNotify({
title: gettext("Something went wrong"),
text: gettext("Please consult octoprint.log for details"),
type: "error",
hide: false
});
},
onAlways = function() {
self._markDone();
};
self._postCommand(command, payload, function(response) {
self.requestData();
self._markDone();
self.installUrl("");
}, function() {
new PNotify({
title: gettext("Something went wrong"),
text: gettext("Please consult octoprint.log for details"),
type: "error",
hide: false
});
self._markDone();
});
if (reinstall) {
OctoPrint.plugins.pluginmanager.reinstall(reinstall, url, followDependencyLinks)
.done(onSuccess)
.fail(onError)
.always(onAlways);
} else {
OctoPrint.plugins.pluginmanager.install(url, followDependencyLinks)
.done(onSuccess)
.fail(onError)
.always(onAlways);
}
};
self.uninstallPlugin = function(data) {
@ -373,20 +460,19 @@ $(function() {
self._markWorking(gettext("Uninstalling plugin..."), _.sprintf(gettext("Uninstalling plugin \"%(name)s\""), {name: data.name}));
var command = "uninstall";
var payload = {plugin: data.key};
self._postCommand(command, payload, function(response) {
self.requestData();
self._markDone();
}, function() {
new PNotify({
title: gettext("Something went wrong"),
text: gettext("Please consult octoprint.log for details"),
type: "error",
hide: false
OctoPrint.plugins.pluginmanager.uninstall(data.key)
.done(self.requestData)
.fail(function() {
new PNotify({
title: gettext("Something went wrong"),
text: gettext("Please consult octoprint.log for details"),
type: "error",
hide: false
});
})
.always(function() {
self._markDone();
});
self._markDone();
});
};
self.refreshRepository = function() {
@ -464,15 +550,59 @@ $(function() {
};
self._displayNotification = function(response, titleSuccess, textSuccess, textRestart, textReload, titleError, textError) {
var notification;
var beforeClose = function(notification) {
self.notifications = _.without(self.notifications, notification);
};
if (response.result) {
if (response.needs_restart) {
new PNotify({
var options = {
title: titleSuccess,
text: textRestart,
buttons: {
closer: false,
sticker: false
},
callbacks: {
before_close: beforeClose
},
hide: false
});
};
if (self.restartCommandSpec) {
options.confirm = {
confirm: true,
buttons: [{
text: gettext("Restart now"),
click: function () {
showConfirmationDialog({
message: gettext("This will restart your OctoPrint server."),
onproceed: function() {
OctoPrint.system.executeCommand("core", "restart")
.done(function() {
new PNotify({
title: gettext("Restart in progress"),
text: gettext("The server is now being restarted in the background")
})
})
.fail(function() {
new PNotify({
title: gettext("Something went wrong"),
text: gettext("Trying to restart the server produced an error, please check octoprint.log for details. You'll have to restart manually.")
})
});
}
});
}
}]
}
}
notification = PNotify.singleButtonNotify(options);
} else if (response.needs_refresh) {
new PNotify({
notification = PNotify.singleButtonNotify({
title: titleSuccess,
text: textReload,
confirm: {
@ -488,51 +618,35 @@ $(function() {
closer: false,
sticker: false
},
callbacks: {
before_close: beforeClose
},
hide: false
})
} else {
new PNotify({
notification = new PNotify({
title: titleSuccess,
text: textSuccess,
type: "success",
callbacks: {
before_close: beforeClose
},
hide: false
})
}
} else {
new PNotify({
notification = new PNotify({
title: titleError,
text: textError,
type: "error",
callbacks: {
before_close: beforeClose
},
hide: false
});
}
};
self._postCommand = function (command, data, successCallback, failureCallback, alwaysCallback, timeout) {
var payload = _.extend(data, {command: command});
var params = {
url: API_BASEURL + "plugin/pluginmanager",
type: "POST",
dataType: "json",
data: JSON.stringify(payload),
contentType: "application/json; charset=UTF-8",
success: function(response) {
if (successCallback) successCallback(response);
},
error: function() {
if (failureCallback) failureCallback();
},
complete: function() {
if (alwaysCallback) alwaysCallback();
}
};
if (timeout != undefined) {
params.timeout = timeout;
}
$.ajax(params);
self.notifications.push(notification);
};
self._markWorking = function(title, line) {
@ -577,6 +691,16 @@ $(function() {
self.onUserLoggedIn = function(user) {
if (user.admin) {
self.requestData();
} else {
self.onUserLoggedOut();
}
};
self.onUserLoggedOut = function() {
if (self.notifications) {
_.each(self.notifications, function(notification) {
notification.remove();
});
}
};
@ -730,5 +854,9 @@ $(function() {
}
// view model class, parameters for constructor, container to bind to
ADDITIONAL_VIEWMODELS.push([PluginManagerViewModel, ["loginStateViewModel", "settingsViewModel", "printerStateViewModel"], "#settings_plugin_pluginmanager"]);
ADDITIONAL_VIEWMODELS.push([
PluginManagerViewModel,
["loginStateViewModel", "settingsViewModel", "printerStateViewModel", "systemViewModel"],
"#settings_plugin_pluginmanager"
]);
});

View file

@ -1,3 +1,43 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var exports = {};
var url = OctoPrint.getBlueprintUrl("softwareupdate");
var checkUrl = url + "check";
var updateUrl = url + "update";
exports.check = function(force, opts) {
return OctoPrint.get(checkUrl + ((!!force) ? "?force=true" : ""), opts);
};
exports.update = function(entries, force, opts) {
entries = entries || [];
if (typeof entries == "string") {
entries = [entries];
}
var data = {
entries: entries,
force: !!force
};
return OctoPrint.postJson(updateUrl, data, opts);
};
exports.updateAll = function(force, opts) {
var data = {
force: !!force
};
return OctoPrint.postJson(updateUrl, data, opts);
};
OctoPrint.plugins.softwareupdate = exports;
});
$(function() {
function SoftwareUpdateViewModel(parameters) {
var self = this;
@ -221,20 +261,10 @@ $(function() {
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) {
OctoPrint.plugins.softwareupdate.check(force)
.done(function(data) {
self.fromCheckResponse(data, ignoreSeen, showIfNothingNew);
}
});
});
};
self._markNotificationAsSeen = function(data) {
@ -285,13 +315,12 @@ $(function() {
};
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() {
OctoPrint.plugins.softwareupdate.updateAll(force)
.done(function(data) {
self.currentlyBeingUpdated = data.checks;
self._markWorking(gettext("Updating..."), gettext("Updating, please wait."));
})
.fail(function() {
self.updateInProgress = false;
self._showPopup({
title: gettext("Update not started!"),
@ -302,12 +331,7 @@ $(function() {
sticker: false
}
});
},
success: function(data) {
self.currentlyBeingUpdated = data.checks;
self._markWorking(gettext("Updating..."), gettext("Updating, please wait."));
}
});
});
};
self.update = function(force) {

View file

@ -912,6 +912,26 @@ class Server(object):
"js/lib/loglevel.min.js",
"js/lib/sockjs-0.3.4.min.js"
]
js_client = [
"js/app/client/base.js",
"js/app/client/socket.js",
"js/app/client/browser.js",
"js/app/client/connection.js",
"js/app/client/control.js",
"js/app/client/files.js",
"js/app/client/job.js",
"js/app/client/languages.js",
"js/app/client/logs.js",
"js/app/client/printer.js",
"js/app/client/printerprofiles.js",
"js/app/client/settings.js",
"js/app/client/slicing.js",
"js/app/client/system.js",
"js/app/client/timelapse.js",
"js/app/client/users.js",
"js/app/client/util.js",
"js/app/client/wizard.js"
]
js_app = dynamic_assets["js"] + [
"js/app/dataupdater.js",
"js/app/helpers.js",
@ -966,8 +986,10 @@ class Server(object):
js_libs_bundle = Bundle(*js_libs, output="webassets/packed_libs.js", filters="js_delimiter_bundler")
if minify:
js_client_bundle = Bundle(*js_client, output="webassets/packed_client.js", filters="rjsmin, js_delimiter_bundler")
js_app_bundle = Bundle(*js_app, output="webassets/packed_app.js", filters="rjsmin, js_delimiter_bundler")
else:
js_client_bundle = Bundle(*js_client, output="webassets/packed_client.js", filters="js_delimiter_bundler")
js_app_bundle = Bundle(*js_app, output="webassets/packed_app.js", filters="js_delimiter_bundler")
css_libs_bundle = Bundle(*css_libs, output="webassets/packed_libs.css")
@ -976,6 +998,7 @@ class Server(object):
all_less_bundle = Bundle(*less_app, output="webassets/packed_app.less", filters="cssrewrite, less_importrewrite")
assets.register("js_libs", js_libs_bundle)
assets.register("js_client", js_client_bundle)
assets.register("js_app", js_app_bundle)
assets.register("css_libs", css_libs_bundle)
assets.register("css_app", css_app_bundle)

View file

@ -38,6 +38,7 @@ from . import log as api_logs
from . import slicing as api_slicing
from . import printer_profiles as api_printer_profiles
from . import languages as api_languages
from . import system as api_system
VERSION = "0.1"
@ -177,51 +178,21 @@ def apiVersion():
"api": VERSION
})
#~~ system control
@api.route("/system", methods=["POST"])
@restricted_access
@admin_permission.require(403)
def performSystemAction():
logger = logging.getLogger(__name__)
if "action" in request.values.keys():
action = request.values["action"]
available_actions = s().get(["system", "actions"])
for availableAction in available_actions:
if availableAction["action"] == action:
async = availableAction["async"] if "async" in availableAction else False
ignore = availableAction["ignore"] if "ignore" in availableAction else False
logger.info("Performing command: %s" % availableAction["command"])
try:
# we run this with shell=True since we have to trust whatever
# our admin configured as command and since we want to allow
# shell-alike handling here...
p = sarge.run(availableAction["command"], stderr=sarge.Capture(), shell=True, async=async)
if not async:
if not ignore and p.returncode != 0:
returncode = p.returncode
stderr_text = p.stderr.text
logger.warn("Command failed with return code %i: %s" % (returncode, stderr_text))
return make_response(("Command failed with return code %i: %s" % (returncode, stderr_text), 500, []))
except Exception, e:
if not ignore:
logger.warn("Command failed: %s" % e)
return make_response(("Command failed: %s" % e, 500, []))
break
return NO_CONTENT
#~~ Login/user handling
@api.route("/login", methods=["POST"])
def login():
if octoprint.server.userManager is not None and "user" in request.values.keys() and "pass" in request.values.keys():
username = request.values["user"]
password = request.values["pass"]
data = request.values
if hasattr(request, "json") and request.json:
data = request.json
if "remember" in request.values.keys() and request.values["remember"] == "true":
if octoprint.server.userManager is not None and "user" in data and "pass" in data:
username = data["user"]
password = data["pass"]
if "remember" in data and data["remember"] in valid_boolean_trues:
remember = True
else:
remember = False
@ -241,7 +212,7 @@ def login():
return jsonify(user.asDict())
return make_response(("User unknown or password incorrect", 401, []))
elif "passive" in request.values:
elif "passive" in data:
return passive_login()
return NO_CONTENT

View file

@ -0,0 +1,176 @@
# coding=utf-8
from __future__ import absolute_import
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms of the AGPLv3 License"
import collections
import logging
import sarge
from flask import request, make_response, jsonify, url_for
from flask.ext.babel import gettext
from octoprint.settings import settings as s
from octoprint.server import admin_permission, NO_CONTENT
from octoprint.server.api import api
from octoprint.server.util.flask import restricted_access, get_remote_address
@api.route("/system", methods=["POST"])
@restricted_access
@admin_permission.require(403)
def performSystemAction():
logging.getLogger(__name__).warn("Deprecated API call to /api/system made by {}, should be migrated to use /system/commands/custom/<action>".format(get_remote_address(request)))
data = request.values
if hasattr(request, "json") and request.json:
data = request.json
if not "action" in data:
return make_response("action for perform is not defined", 400)
return executeSystemCommand("custom", data["action"])
@api.route("/system/commands", methods=["GET"])
@restricted_access
@admin_permission.require(403)
def retrieveSystemCommands():
return jsonify(core=_to_client_specs(_get_core_command_specs()),
custom=_to_client_specs(_get_custom_command_specs()))
@api.route("/system/commands/<string:source>", methods=["GET"])
@restricted_access
@admin_permission.require(403)
def retrieveSystemCommandsForSource(source):
if source == "core":
specs = _get_core_command_specs()
elif source == "custom":
specs = _get_custom_command_specs()
else:
return make_response("Unknown system command source: {}".format(source), 400)
return jsonify(_to_client_specs(specs))
@api.route("/system/commands/<string:source>/<string:command>", methods=["POST"])
@restricted_access
@admin_permission.require(403)
def executeSystemCommand(source, command):
logger = logging.getLogger(__name__)
command_spec = _get_command_spec(source, command)
if not command_spec:
return make_response("Command {}:{} not found".format(source, command), 404)
if not "command" in command_spec:
return make_response("Command {}:{} does not define a command to execute, can't proceed".format(source, command), 500)
async = command_spec["async"] if "async" in command_spec else False
ignore = command_spec["ignore"] if "ignore" in command_spec else False
logger.info("Performing command for {}:{}: {}".format(source, command, command_spec["command"]))
try:
# we run this with shell=True since we have to trust whatever
# our admin configured as command and since we want to allow
# shell-alike handling here...
p = sarge.run(command_spec["command"],
stdout=sarge.Capture(),
stderr=sarge.Capture(),
shell=True,
async=async)
if not async:
if not ignore and p.returncode != 0:
returncode = p.returncode
stdout_text = p.stdout.text
stderr_text = p.stderr.text
error = "Command failed with return code {}:\nSTDOUT: {}\nSTDERR: {}".format(returncode, stdout_text, stderr_text)
logger.warn(error)
return make_response(error, 500)
except Exception, e:
if not ignore:
error = "Command failed: {}".format(str(e))
logger.warn(error)
return make_response(error, 500)
return NO_CONTENT
def _to_client_specs(specs):
result = list()
for spec in specs.values():
if not "action" in spec or not "source" in spec:
continue
copied = dict((k, v) for k, v in spec.items() if k in ("source", "action", "name", "confirm"))
copied["resource"] = url_for(".executeSystemCommand",
source=spec["source"],
command=spec["action"],
_external=True)
result.append(copied)
return result
def _get_command_spec(source, action):
if source == "core":
return _get_core_command_spec(action)
elif source == "custom":
return _get_custom_command_spec(action)
else:
return None
def _get_core_command_specs():
commands = collections.OrderedDict(
shutdown=dict(
command=s().get(["server", "commands", "systemShutdownCommand"]),
name=gettext("Shutdown"),
confirm=gettext("You are about to shutdown the system.")),
reboot=dict(
command=s().get(["server", "commands", "systemRestartCommand"]),
name=gettext("Reboot"),
confirm=gettext("You are about to reboot the system.")),
restart=dict(
command=s().get(["server", "commands", "serverRestartCommand"]),
name="Restart OctoPrint",
confirm="You are about to restart the OctoPrint server.")
)
available_commands = dict()
for action, spec in commands.items():
if not spec["command"]:
continue
spec.update(dict(action=action, source="core", async=True, ignore=True))
available_commands[action] = spec
return available_commands
def _get_core_command_spec(action):
available_actions = _get_core_command_specs()
if not action in available_actions:
logging.getLogger(__name__).warn("Command for core action {} is not configured, you need to configure the command before it can be used".format(action))
return None
return available_actions[action]
def _get_custom_command_specs():
specs = collections.OrderedDict()
for spec in s().get(["system", "actions"]):
if not "action" in spec:
continue
copied = dict(spec)
copied["source"] = "custom"
specs[spec["action"]] = copied
return specs
def _get_custom_command_spec(action):
available_actions = _get_custom_command_specs()
if not action in available_actions:
return None
return available_actions[action]

View file

@ -68,52 +68,56 @@ def deleteTimelapse(filename):
@api.route("/timelapse", methods=["POST"])
@restricted_access
def setTimelapseConfig():
if "type" in request.values:
data = request.values
if hasattr(request, "json") and request.json:
data = request.json
if "type" in data:
config = {
"type": request.values["type"],
"type": data["type"],
"postRoll": 0,
"fps": 25,
"options": {}
}
if "postRoll" in request.values:
if "postRoll" in data:
try:
postRoll = int(request.values["postRoll"])
postRoll = int(data["postRoll"])
except ValueError:
return make_response("Invalid value for postRoll: %r" % request.values["postRoll"], 400)
return make_response("Invalid value for postRoll: %r" % data["postRoll"], 400)
else:
if postRoll >= 0:
config["postRoll"] = postRoll
else:
return make_response("Invalid value for postRoll: %d" % postRoll, 400)
if "fps" in request.values:
if "fps" in data:
try:
fps = int(request.values["fps"])
fps = int(data["fps"])
except ValueError:
return make_response("Invalid value for fps: %r" % request.values["fps"], 400)
return make_response("Invalid value for fps: %r" % data["fps"], 400)
else:
if fps > 0:
config["fps"] = fps
else:
return make_response("Invalid value for fps: %d" % fps, 400)
if "interval" in request.values:
if "interval" in data:
config["options"] = {
"interval": 10
}
try:
interval = int(request.values["interval"])
interval = int(data["interval"])
except ValueError:
return make_response("Invalid value for interval: %r" % request.values["interval"])
return make_response("Invalid value for interval: %r" % data["interval"])
else:
if interval > 0:
config["options"]["interval"] = interval
else:
return make_response("Invalid value for interval: %d" % interval)
if admin_permission.can() and "save" in request.values and request.values["save"] in valid_boolean_trues:
if admin_permission.can() and "save" in data and data["save"] in valid_boolean_trues:
octoprint.timelapse.configureTimelapse(config, True)
else:
octoprint.timelapse.configureTimelapse(config)

View file

@ -631,6 +631,7 @@ def collect_plugin_assets(enable_gcodeviewer=True, preferred_stylesheet="css"):
'js/app/viewmodels/printerprofiles.js',
'js/app/viewmodels/settings.js',
'js/app/viewmodels/slicing.js',
'js/app/viewmodels/system.js',
'js/app/viewmodels/temperature.js',
'js/app/viewmodels/terminal.js',
'js/app/viewmodels/timelapse.js',

View file

@ -573,4 +573,3 @@ def localeJs(locale, domain):
def plugin_assets(name, filename):
return redirect(url_for("plugin." + name + ".static", filename=filename))

View file

@ -0,0 +1,249 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define("OctoPrint", ["jquery", "lodash"], factory);
} else {
global.OctoPrint = factory(window.$, window._);
}
})(window || this, function($, _) {
var OctoPrint = {};
var noCache = function(opts) {
opts = opts || {};
var params = $.extend({}, opts);
params.headers = $.extend({}, params.headers || {});
params.headers["Cache-Control"] = "no-cache";
return params;
};
var contentTypeJson = function(opts) {
opts = opts || {};
var params = $.extend({}, opts);
params.contentType = "application/json; charset=UTF-8";
return params;
};
OctoPrint.options = {
"baseurl": undefined,
"apikey": undefined
};
OctoPrint.plugins = {};
OctoPrint.getBaseUrl = function() {
var url = OctoPrint.options.baseurl;
if (!_.endsWith(url, "/")) {
url = url + "/";
}
return url;
};
OctoPrint.getRequestHeaders = function(additional) {
additional = additional || {};
var headers = $.extend({}, additional);
headers["X-Api-Key"] = OctoPrint.options.apikey;
return headers;
};
OctoPrint.ajax = function(method, url, opts) {
opts = opts || {};
method = opts.method || method || "GET";
url = opts.url || url || "";
var urlToCall = url;
if (!_.startsWith(url, "http://") && !_.startsWith(url, "https://")) {
urlToCall = OctoPrint.getBaseUrl() + url;
}
var headers = OctoPrint.getRequestHeaders(opts.headers);
var params = $.extend({}, opts);
params.type = method;
params.headers = headers;
params.dataType = params.dataType || "json";
return $.ajax(urlToCall, params);
};
OctoPrint.ajaxWithData = function(method, url, data, opts) {
opts = opts || {};
var params = $.extend({}, opts);
params.data = data;
return OctoPrint.ajax(method, url, params);
};
OctoPrint.get = function(url, opts) {
return OctoPrint.ajax("GET", url, opts);
};
OctoPrint.post = function(url, data, opts) {
return OctoPrint.ajaxWithData("POST", url, data, noCache(opts));
};
OctoPrint.postJson = function(url, data, opts) {
return OctoPrint.post(url, JSON.stringify(data), contentTypeJson(opts));
};
OctoPrint.put = function(url, data, opts) {
return OctoPrint.ajaxWithData("PUT", url, data, noCache(opts));
};
OctoPrint.putJson = function(url, data, opts) {
return OctoPrint.put(url, data, contentTypeJson(opts));
};
OctoPrint.patch = function(url, data, opts) {
return OctoPrint.ajaxWithData("PATCH", url, data, noCache(opts));
};
OctoPrint.patchJson = function(url, data, opts) {
return OctoPrint.patch(url, JSON.stringify(data), contentTypeJson(opts));
};
OctoPrint.delete = function(url, opts) {
return OctoPrint.ajax("DELETE", url, opts);
};
OctoPrint.download = function(url, opts) {
var params = $.extend({}, opts || {});
params.dataType = "text";
return OctoPrint.get(url, params);
};
OctoPrint.upload = function(url, file, filename, additional) {
additional = additional || {};
var fileData;
if (file instanceof jQuery) {
fileData = file[0].files[0];
} else if (typeof file == "string") {
fileData = $(file)[0].files[0];
} else {
fileData = file;
}
filename = filename || fileData.name;
var form = new FormData();
form.append("file", fileData, filename);
_.each(additional, function(value, key) {
form.append(key, value);
});
var deferred = $.Deferred();
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState == 4) {
deferred.notify({loaded: filesize, total: filesize});
var success = request.status >= 200 && request.status < 300
|| request.status === 304;
var error, json, statusText;
try {
json = JSON.parse(request.response);
statusText = "success";
} catch (e) {
success = false;
error = e;
statusText = "parsererror";
}
if (success) {
deferred.resolve([json, statusText, request]);
} else {
if (!statusText) {
statusText = request.statusText;
}
deferred.reject([request, statusText, error]);
}
}
};
request.ontimeout = function() {
deferred.reject([request, "timeout", "Timeout"]);
};
request.upload.addEventListener("loadstart", function(e) {
deferred.notify({loaded: e.loaded, total: e.total});
});
request.upload.addEventListener("progress", function(e) {
deferred.notify({loaded: e.loaded, total: e.total});
});
request.upload.addEventListener("loadend", function(e) {
deferred.notify({loaded: e.loaded, total: e.total});
});
var headers = OctoPrint.getRequestHeaders();
request.open("POST", OctoPrint.getBaseUrl() + url);
_.each(headers, function(value, key) {
request.setRequestHeader(key, value);
});
request.send(form);
return deferred.promise();
};
OctoPrint.issueCommand = function(url, command, payload, opts) {
payload = payload || {};
var data = $.extend({}, payload);
data.command = command;
return OctoPrint.postJson(url, data, opts);
};
OctoPrint.getSimpleApiUrl = function(plugin) {
return "api/plugin/" + plugin;
};
OctoPrint.simpleApiGet = function(plugin, opts) {
return OctoPrint.get(OctoPrint.getSimpleApiUrl(plugin), opts);
};
OctoPrint.simpleApiCommand = function(plugin, command, payload, opts) {
return OctoPrint.issueCommand(OctoPrint.getSimpleApiUrl(plugin), command, payload, opts);
};
OctoPrint.getBlueprintUrl = function(plugin) {
return "plugin/" + plugin + "/";
};
OctoPrint.createRejectedDeferred = function() {
var deferred = $.Deferred();
deferred.reject(arguments);
return deferred;
};
OctoPrint.createCustomException = function(name) {
var constructor;
if (_.isFunction(name)) {
constructor = name;
} else {
constructor = function(message) {
this.name = name;
this.message = message;
this.stack = (new Error()).stack;
};
}
constructor.prototype = Object.create(Error.prototype);
constructor.prototype.constructor = constructor;
return constructor;
};
OctoPrint.InvalidArgumentError = OctoPrint.createCustomException("InvalidArgumentError");
return OctoPrint;
});

View file

@ -0,0 +1,29 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var loginUrl = "api/login";
var logoutUrl = "api/logout";
OctoPrint.browser = {
login: function(username, password, remember, opts) {
var data = {
user: username,
pass: password,
remember: !!remember
};
return OctoPrint.postJson(loginUrl, data, opts);
},
passiveLogin: function(opts) {
return OctoPrint.postJson(loginUrl, {passive: true}, opts);
},
logout: function(opts) {
return OctoPrint.postJson(logoutUrl, {}, opts);
}
};
});

View file

@ -0,0 +1,27 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var url = "api/connection";
OctoPrint.connection = {
getSettings: function(opts) {
return OctoPrint.get(url, opts);
},
connect: function(data, opts) {
return OctoPrint.issueCommand(url, "connect", data || {}, opts);
},
disconnect: function(opts) {
return OctoPrint.issueCommand(url, "disconnect", {}, opts);
},
fakeAck: function(opts) {
return OctoPrint.issueCommand(url, "fake_ack", {}, opts);
}
}
});

View file

@ -0,0 +1,46 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var customUrl = "api/printer/command/custom";
var commandUrl = "api/printer/command";
var sendGcodeWithParameters = function(commands, parameters, opts) {
commands = commands || [];
parameters = parameters || {};
if (typeof commands === "string") {
commands = [commands];
}
return OctoPrint.postJson(commandUrl, {
commands: commands,
parameters: parameters
}, opts);
};
OctoPrint.control = {
sendGcodeWithParameters: sendGcodeWithParameters,
getCustomControls: function (opts) {
return OctoPrint.get(customUrl, opts);
},
sendGcode: function (commands, opts) {
return sendGcodeWithParameters(commands, undefined, opts);
},
sendGcodeScript: function (script, context, opts) {
script = script || "";
context = context || {};
return OctoPrint.postJson(commandUrl, {
script: script,
context: context
}, opts);
}
}
});

View file

@ -0,0 +1,85 @@
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(["OctoPrint", "jquery"], factory);
} else {
// Browser globals:
factory(window.OctoPrint, window.jQuery);
}
})(function(OctoPrint, $) {
var url = "api/files";
var resourceForLocation = function(location) {
return url + "/" + location;
};
var resourceForFile = function(location, filename) {
return resourceForLocation(location) + "/" + filename;
};
var issueFileCommand = function(location, filename, command, data, opts) {
var url = resourceForFile(location, filename);
return OctoPrint.issueCommand(url, command, data, opts);
};
var getFile = function(location, filename, opts) {
return OctoPrint.get(resourceForFile(location, filename), opts);
};
OctoPrint.files = {
get: getFile,
list: function (opts) {
return OctoPrint.get(url, opts);
},
listForLocation: function (location, opts) {
return OctoPrint.get(resourceForLocation(location), opts);
},
select: function (location, filename, print, opts) {
print = print || false;
var data = {
print: print
};
return issueFileCommand(location, filename, "select", data, opts);
},
slice: function (location, filename, parameters, opts) {
return issueFileCommand(location, filename, "slice",
parameters || {}, opts);
},
delete: function (location, filename, opts) {
return OctoPrint.delete(resourceForFile(location, filename), opts);
},
upload: function (location, file, data) {
data = data || {};
var filename = data.filename || undefined;
return OctoPrint.upload(resourceForLocation(location), file, filename, data);
},
download: function (location, filename, opts) {
var deferred = $.Deferred();
getFile(location, filename, opts)
.done(function (response) {
OctoPrint.download(response.refs.download, opts)
.done(function () {
deferred.resolve.apply(null, arguments);
})
.fail(function () {
deferred.reject.apply(null, arguments);
});
})
.fail(function () {
deferred.reject.apply(null, arguments);
});
return deferred.promise();
}
}
});

View file

@ -0,0 +1,31 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var url = "api/job";
var issueCommand = function(command, opts) {
return OctoPrint.issueCommand(url, command, {}, opts);
};
OctoPrint.job = {
get: function(opts) {
return OctoPrint.get(url, opts);
},
start: function(opts) {
return issueCommand("start", opts);
},
restart: function(opts) {
return issueCommand("restart", opts);
},
pause: function(opts) {
return issueCommand("pause", opts);
},
cancel: function(opts) {
return issueCommand("cancel", opts);
}
}
});

View file

@ -0,0 +1,22 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var url = "api/languages";
OctoPrint.languages = {
list: function(opts) {
return OctoPrint.get(url, opts);
},
upload: function(file) {
return OctoPrint.upload(url, file);
},
delete: function(locale, pack, opts) {
var packUrl = url + "/" + locale + "/" + pack;
return OctoPrint.delete(packUrl, opts);
}
};
});

View file

@ -0,0 +1,25 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var url = "api/logs";
OctoPrint.logs = {
list: function(opts) {
return OctoPrint.get(url, opts);
},
delete: function(file, opts) {
var fileUrl = url + "/" + file;
return OctoPrint.delete(fileUrl, opts);
},
download: function(file, opts) {
var fileUrl = url + "/" + file;
return OctoPrint.download(fileUrl, opts);
}
}
});

View file

@ -0,0 +1,207 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var url = "api/printer";
var printheadUrl = url + "/printhead";
var toolUrl = url + "/tool";
var bedUrl = url + "/bed";
var sdUrl = url + "/sd";
var issuePrintheadCommand = function (command, payload, opts) {
return OctoPrint.issueCommand(printheadUrl, command, payload, opts);
};
var issueToolCommand = function (command, payload, opts) {
return OctoPrint.issueCommand(toolUrl, command, payload, opts);
};
var issueBedCommand = function (command, payload, opts) {
return OctoPrint.issueCommand(bedUrl, command, payload, opts);
};
var issueSdCommand = function (command, payload, opts) {
return OctoPrint.issueCommand(sdUrl, command, payload, opts);
};
OctoPrint.printer = {
getFullState: function (data, opts) {
data = data || {};
var history = data.history || undefined;
var limit = data.limit || undefined;
var exclude = data.exclude || undefined;
var getUrl = url;
if (history || exclude) {
getUrl += "?";
if (history) {
getUrl += "history=true&";
if (limit) {
getUrl += "limit=" + limit + "&";
}
}
if (exclude) {
getUrl += "exclude=" + exclude.join(",") + "&";
}
}
return OctoPrint.get(getUrl, opts);
},
getToolState: function (data, opts) {
data = data || {};
var history = data.history || undefined;
var limit = data.limit || undefined;
var getUrl = toolUrl;
if (history) {
getUrl += "?history=true";
if (limit) {
getUrl += "&limit=" + limit;
}
}
return OctoPrint.get(getUrl, opts);
},
getBedState: function (data, opts) {
data = data || {};
var history = data.history || undefined;
var limit = data.limit || undefined;
var getUrl = bedUrl;
if (history) {
getUrl += "?history=true";
if (limit) {
getUrl += "&limit=" + limit;
}
}
return OctoPrint.get(getUrl, opts);
},
getSdState: function (opts) {
return OctoPrint.get(sdUrl, opts);
},
jog: function (data, opts) {
data = data || {};
var payload = {};
if (data.x) payload.x = data.x;
if (data.y) payload.y = data.y;
if (data.z) payload.z = data.z;
return issuePrintheadCommand("jog", payload, opts);
},
home: function (axes, opts) {
axes = axes || [];
var payload = {
axes: axes
};
return issuePrintheadCommand("home", payload, opts);
},
setFeedrate: function (factor, opts) {
factor = factor || 100;
var payload = {
factor: factor
};
return issuePrintheadCommand("feedrate", payload, opts);
},
setToolTargetTemperatures: function (targets, opts) {
targets = targets || {};
var payload = {
targets: targets
};
return issueToolCommand("target", payload, opts);
},
setToolTemperatureOffsets: function (offsets, opts) {
offsets = offsets || {};
var payload = {
offsets: offsets
};
return issueToolCommand("offset", payload, opts);
},
selectTool: function (tool, opts) {
tool = tool || undefined;
var payload = {
tool: tool
};
return issueToolCommand("select", payload, opts);
},
extrude: function (amount, opts) {
amount = amount || undefined;
var payload = {
amount: amount
};
return issueToolCommand("extrude", payload, opts);
},
setFlowrate: function (factor, opts) {
factor = factor || 100;
var payload = {
factor: factor
};
return issueToolCommand("flowrate", payload, opts);
},
setBedTargetTemperature: function (temperature, opts) {
temperature = temperature || 0;
var payload = {
target: temperature
};
return issueBedCommand("target", payload, opts);
},
setBedTemperatureOffset: function (offset, opts) {
offset = offset || 0;
var payload = {
offset: offset
};
return issueBedCommand("offset", payload, opts);
},
initSd: function (opts) {
return issueSdCommand("init", {}, opts);
},
refreshSd: function (opts) {
return issueSdCommand("refresh", {}, opts);
},
releaseSd: function (opts) {
return issueSdCommand("release", {}, opts);
}
}
});

View file

@ -0,0 +1,43 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint", "jquery"], factory);
} else {
factory(window.OctoPrint, window.$);
}
})(window || this, function(OctoPrint, $) {
var url = "api/printerprofiles";
var profileUrl = function(profile) {
return url + "/" + profile;
};
OctoPrint.printerprofiles = {
get: function (opts) {
return OctoPrint.get(url, opts);
},
add: function (profile, additional, opts) {
profile = profile || {};
additional = additional || {};
var data = $.extend({}, additional);
data.profile = profile;
return OctoPrint.postJson(url, data, opts);
},
update: function (id, profile, additional, opts) {
profile = profile || {};
additional = addtional || {};
var data = $.extend({}, additional);
data.profile = profile;
return OctoPrint.patchJson(profileUrl(id), data, opts);
},
delete: function (id, opts) {
return OctoPrint.delete(profileUrl(id), opts);
}
}
});

View file

@ -0,0 +1,43 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint", "jquery"], factory);
} else {
factory(window.OctoPrint, window.$);
}
})(window || this, function(OctoPrint, $) {
var url = "api/settings";
var get = function(opts) {
return OctoPrint.get(url, opts);
};
var save = function(settings, opts) {
settings = settings || {};
return OctoPrint.postJson(url, settings, opts);
};
OctoPrint.settings = {
get: get,
save: save,
getPluginSettings: function (plugin, opts) {
return get(opts)
.then(function (settings, statusText, request) {
if (!settings.plugins || !settings.plugins[plugin]) {
return $.Deferred()
.reject(request, "dataerror", "No settings for plugin " + plugin)
.promise();
} else {
return settings.plugins[plugin];
}
});
},
savePluginSettings: function (plugin, settings, opts) {
var data = {};
data["plugins"] = {};
data["plugins"][plugin] = settings;
return save(data, opts);
}
}
});

View file

@ -0,0 +1,45 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var url = "api/slicing";
var slicerUrl = function(slicer) {
return url + "/" + slicer;
};
var profileUrl = function(slicer, profileId) {
return slicerUrl(slicer) + "/profiles/" + profileId;
};
OctoPrint.slicing = {
listAllSlicersAndProfiles: function(opts) {
return OctoPrint.get(url, opts);
},
listProfilesForSlicer: function(slicer, opts) {
return OctoPrint.get(slicerUrl(slicer) + "/profiles", opts);
},
getProfileForSlicer: function(slicer, profileId, opts) {
return OctoPrint.get(profileUrl(slicer, profileId), opts);
},
addProfileForSlicer: function(slicer, profileId, profile, opts) {
profile = profile || {};
return OctoPrint.putJson(profileUrl(slicer, profileId), profile, opts);
},
updateProfileForSlicer: function(slicer, profileId, profile, opts) {
profile = profile || {};
return OctoPrint.patchJson(profileUrl(slicer, profileId), profile, opts);
},
deleteProfileForSlicer: function(slicer, profileId, opts) {
return OctoPrint.delete(profileUrl(slicer, profileId), opts);
}
}
});

View file

@ -0,0 +1,112 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint", "jquery", "lodash", "sockjs"], factory);
} else {
factory(window.OctoPrint, window.$, window._, window.SockJS);
}
})(window || this, function(OctoPrint, $, _, SockJS) {
var exports = {};
exports.options = {
timeouts: [0, 1, 1, 2, 3, 5, 8, 13, 20, 40, 100]
};
var normalClose = 1000;
var socket = undefined;
var reconnecting = false;
var reconnectTrial = 0;
var registeredHandlers = {};
var onOpen = function() {
reconnecting = false;
reconnectTrial = 0;
};
var onClose = function(e) {
if (e.code == normalClose) {
return;
}
if (exports.onReconnectAttempt(reconnectTrial)) {
return;
}
if (reconnectTrial < exports.options.timeouts.length) {
var timeout = exports.options.timeouts[reconnectTrial];
setTimeout(exports.reconnect, timeout * 1000);
reconnectTrial++;
} else {
exports.onReconnectFailed();
}
};
var onMessage = function(msg) {
_.each(msg.data, function(data, key) {
propagateMessage(key, data);
});
};
var propagateMessage = function(event, data) {
if (!registeredHandlers.hasOwnProperty(event)) {
return;
}
var eventObj = {event: event, data: data};
var catchAllHandlers = registeredHandlers["*"];
if (catchAllHandlers && catchAllHandlers.length) {
_.each(catchAllHandlers, function(handler) {
handler({event: eventObj})
});
}
var handlers = registeredHandlers[event];
if (handlers && handlers.length) {
_.each(handlers, function(handler) {
handler(eventObj);
});
}
};
exports.connect = function(opts) {
opts = opts || {};
exports.disconnect();
var url = OctoPrint.options.baseurl;
if (!_.endsWith(url, "/")) {
url += "/";
}
socket = new SockJS(url + "sockjs", undefined, opts);
socket.onopen = onOpen;
socket.onclose = onClose;
socket.onmessage = onMessage;
};
exports.reconnect = function() {
exports.disconnect();
socket = undefined;
exports.connect();
};
exports.disconnect = function() {
if (socket != undefined) {
socket.close();
}
};
exports.onMessage = function(message, handler) {
if (!registeredHandlers.hasOwnProperty(message)) {
registeredHandlers[message] = [];
}
registeredHandlers[message].push(handler);
return exports;
};
exports.onReconnectAttempt = function(trial) {};
exports.onReconnectFailed = function() {};
OctoPrint.socket = exports;
});

View file

@ -0,0 +1,24 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var url = "api/system";
var commandUrl = "api/system/commands";
OctoPrint.system = {
getCommands: function (opts) {
return OctoPrint.get(commandUrl, opts);
},
getCommandsForSource: function (source, opts) {
return OctoPrint.get(commandUrl + "/" + source, opts);
},
executeCommand: function (source, action, opts) {
return OctoPrint.postJson(commandUrl + "/" + source + "/" + action, {}, opts);
}
};
});

View file

@ -0,0 +1,60 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint", "jquery"], factory);
} else {
factory(window.OctoPrint, window.$);
}
})(window || this, function(OctoPrint, $) {
var url = "api/timelapse";
var timelapseUrl = function(filename) {
return url + "/" + filename;
};
var getTimelapseData = function (opts) {
return OctoPrint.get(url, opts);
};
OctoPrint.timelapse = {
get: getTimelapseData,
list: function (opts) {
var deferred = $.Deferred();
getTimelapseData(opts)
.done(function (response, status, request) {
deferred.resolve(response.files, status, request);
})
.fail(function () {
deferred.reject.apply(null, arguments);
});
return deferred.promise();
},
download: function (filename, opts) {
return OctoPrint.download(timelapseUrl(filename), opts);
},
delete: function (filename, opts) {
return OctoPrint.delete(timelapseUrl(filename), opts);
},
getConfig: function (opts) {
var deferred = $.Deferred();
getTimelapseData(opts)
.done(function (response, status, request) {
deferred.resolve(response.config, status, request);
})
.fail(function () {
deferred.reject.apply(null, arguments);
});
return deferred.promise();
},
saveConfig: function (config, opts) {
config = config || {};
return OctoPrint.postJson(url, config, opts);
}
}
});

View file

@ -0,0 +1,110 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var baseUrl = "api/users";
var url = function() {
if (arguments.length) {
return baseUrl + "/" + Array.prototype.join.call(arguments, "/");
} else {
return baseUrl;
}
};
OctoPrint.users = {
list: function (opts) {
return OctoPrint.get(url(), opts);
},
add: function (user, opts) {
if (!user.name || !user.password) {
throw new OctoPrint.InvalidArgumentError("Both user's name and password need to be set");
}
var data = {
name: user.name,
password: user.password,
active: user.hasOwnProperty("active") ? !!user.active : true,
admin: user.hasOwnProperty("admin") ? !!user.admin : false
};
return OctoPrint.postJson(url(), data, opts);
},
get: function (name, opts) {
if (!name) {
throw new OctoPrint.InvalidArgumentError("user name must be set");
}
return OctoPrint.get(url(name), opts);
},
update: function (name, active, admin, opts) {
if (!name) {
throw new OctoPrint.InvalidArgumentError("user name must be set");
}
var data = {
active: !!active,
admin: !!admin
};
return OctoPrint.putJson(url(name), data, opts);
},
delete: function (name, opts) {
if (!name) {
throw new OctoPrint.InvalidArgumentError("user name must be set");
}
return OctoPrint.delete(url(name), opts);
},
changePassword: function (name, password, opts) {
if (!name || !password) {
throw new OctoPrint.InvalidArgumentError("user name and password must be set");
}
var data = {
password: password
};
return OctoPrint.putJson(url(name, "password"), data, opts);
},
generateApiKey: function (name, opts) {
if (!name) {
throw new OctoPrint.InvalidArgumentError("user name must be set");
}
return OctoPrint.postJson(url(name, "apikey"), opts);
},
resetApiKey: function (name, opts) {
if (!name) {
throw new OctoPrint.InvalidArgumentError("user name must be set");
}
return OctoPrint.delete(url(name, "apikey"), opts);
},
getSettings: function (name, opts) {
if (!name) {
throw new OctoPrint.InvalidArgumentError("user name must be set");
}
return OctoPrint.get(url(name, "settings"), opts);
},
saveSettings: function (name, settings, opts) {
if (!name) {
throw new OctoPrint.InvalidArgumentError("user name must be set");
}
settings = settings || {};
return OctoPrint.patchJson(url(name, "settings"), settings, opts);
}
};
});

View file

@ -0,0 +1,47 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint", "jquery"], factory);
} else {
factory(window.OctoPrint, window.$);
}
})(window || this, function(OctoPrint, $) {
var url = "api/util";
var testUrl = url + "/test";
var test = function(command, data, opts) {
return OctoPrint.issueCommand(testUrl, command, data, opts);
};
OctoPrint.util = {
test: test,
testPath: function(path, additional, opts) {
additional = additional || {};
var data = $.extend({}, additional);
data.path = path;
return test("path", data, opts);
},
testExecutable: function(path, additional, opts) {
additional = additional || {};
var data = $.extend({}, additional);
data.path = path;
data.check_type = "file";
data.check_access = "x";
return test("path", data, opts);
},
testUrl: function(url, additional, opts) {
additional = additional || {};
var data = $.extend({}, additional);
data.url = url;
return test("url", data, opts);
}
};
});

View file

@ -0,0 +1,18 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["OctoPrint"], factory);
} else {
factory(window.OctoPrint);
}
})(window || this, function(OctoPrint) {
var url = "api/setup/wizard";
OctoPrint.wizard = {
get: function(opts) {
return OctoPrint.get(url, opts);
},
finish: function(handled, opts) {
return OctoPrint.postJson(url, {handled: handled || []}, opts);
}
};
});

View file

@ -3,12 +3,6 @@ function DataUpdater(allViewModels) {
self.allViewModels = allViewModels;
self._socket = undefined;
self._autoReconnecting = false;
self._autoReconnectTrial = 0;
self._autoReconnectTimeouts = [0, 1, 1, 2, 3, 5, 8, 13, 20, 40, 100];
self._autoReconnectDialogIndex = 1;
self._pluginHash = undefined;
self._configHash = undefined;
@ -16,65 +10,39 @@ function DataUpdater(allViewModels) {
$("#reloadui_overlay_reload").click(function() { location.reload(true); });
self.connect = function() {
var options = {};
if (SOCKJS_DEBUG) {
options["debug"] = true;
}
self._socket = new SockJS(SOCKJS_URI, undefined, options);
self._socket.onopen = self._onconnect;
self._socket.onclose = self._onclose;
self._socket.onmessage = self._onmessage;
OctoPrint.socket.connect({debug: !!SOCKJS_DEBUG});
};
self.reconnect = function() {
self._socket.close();
delete self._socket;
self.connect();
OctoPrint.socket.reconnect();
};
self._onconnect = function() {
self._autoReconnecting = false;
self._autoReconnectTrial = 0;
};
self._onclose = function(e) {
if (e.code == SOCKJS_CLOSE_NORMAL) {
self._onReconnectAttempt = function(trial) {
if (trial <= 0) {
// Only consider it a real disconnect if the trial number has exceeded our threshold.
return;
}
if (self._autoReconnectTrial >= self._autoReconnectDialogIndex) {
// Only consider it a real disconnect if the trial number has exceeded our threshold.
var handled = false;
callViewModelsIf(
self.allViewModels,
"onServerDisconnect",
function() { return !handled; },
function(method) { handled = !method() || handled; }
);
var handled = false;
callViewModelsIf(
self.allViewModels,
"onServerDisconnect",
function() { return !handled; },
function(method) { handled = !method() || handled; }
);
if (handled) {
return;
}
showOfflineOverlay(
gettext("Server is offline"),
gettext("The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect automatically <strong>over the next couple of minutes</strong>, however you are welcome to try a manual reconnect anytime using the button below."),
self.reconnect
);
if (handled) {
return true;
}
if (self._autoReconnectTrial < self._autoReconnectTimeouts.length) {
var timeout = self._autoReconnectTimeouts[self._autoReconnectTrial];
log.info("Reconnect trial #" + self._autoReconnectTrial + ", waiting " + timeout + "s");
setTimeout(self.reconnect, timeout * 1000);
self._autoReconnectTrial++;
} else {
self._onreconnectfailed();
}
showOfflineOverlay(
gettext("Server is offline"),
gettext("The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect automatically <strong>over the next couple of minutes</strong>, however you are welcome to try a manual reconnect anytime using the button below."),
self.reconnect
);
};
self._onreconnectfailed = function() {
self._onReconnectFailed = function() {
var handled = false;
callViewModelsIf(
self.allViewModels,
@ -91,182 +59,189 @@ function DataUpdater(allViewModels) {
$("#offline_overlay_message").html(gettext("The server appears to be offline, at least I'm not getting any response from it. I <strong>could not reconnect automatically</strong>, but you may try a manual reconnect using the button below."));
};
self._onmessage = function(e) {
for (var prop in e.data) {
if (!e.data.hasOwnProperty(prop)) {
continue;
self._onConnected = function(event) {
var data = event.data;
// update version information
var oldVersion = VERSION;
VERSION = data["version"];
DISPLAY_VERSION = data["display_version"];
BRANCH = data["branch"];
$("span.version").text(DISPLAY_VERSION);
// update plugin hash
var oldPluginHash = self._pluginHash;
self._pluginHash = data["plugin_hash"];
// update config hash
var oldConfigHash = self._configHash;
self._configHash = data["config_hash"];
// if the offline overlay is still showing, now's a good time to
// hide it, plus reload the camera feed if it's currently displayed
if ($("#offline_overlay").is(":visible")) {
hideOfflineOverlay();
callViewModels(self.allViewModels, "onDataUpdaterReconnect");
if ($('#tabs li[class="active"] a').attr("href") == "#control") {
$("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime());
}
}
var data = e.data[prop];
var gcodeUploadProgress = $("#gcode_upload_progress");
var gcodeUploadProgressBar = $(".bar", gcodeUploadProgress);
switch (prop) {
case "connected": {
// update the current UI API key and send it with any request
UI_API_KEY = data["apikey"];
$.ajaxSetup({
headers: {"X-Api-Key": UI_API_KEY}
});
var oldVersion = VERSION;
VERSION = data["version"];
DISPLAY_VERSION = data["display_version"];
BRANCH = data["branch"];
$("span.version").text(DISPLAY_VERSION);
var oldPluginHash = self._pluginHash;
self._pluginHash = data["plugin_hash"];
var oldConfigHash = self._configHash;
self._configHash = data["config_hash"];
if ($("#offline_overlay").is(":visible")) {
hideOfflineOverlay();
callViewModels(self.allViewModels, "onDataUpdaterReconnect");
if ($('#tabs li[class="active"] a').attr("href") == "#control") {
$("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime());
}
}
var versionChanged = oldVersion != VERSION;
var pluginsChanged = oldPluginHash != undefined && oldPluginHash != self._pluginHash;
var configChanged = oldConfigHash != undefined && oldConfigHash != self._configHash;
if (versionChanged || pluginsChanged || configChanged) {
self.reloadOverlay.show();
}
break;
}
case "history": {
callViewModels(self.allViewModels, "fromHistoryData", [data]);
break;
}
case "current": {
callViewModels(self.allViewModels, "fromCurrentData", [data]);
break;
}
case "slicingProgress": {
gcodeUploadProgressBar.text(_.sprintf(gettext("Slicing ... (%(percentage)d%%)"), {percentage: Math.round(data["progress"])}));
callViewModels(self.allViewModels, "onSlicingProgress", [
data["slicer"],
data["model_path"],
data["machinecode_path"],
data["progress"]
]);
break;
}
case "event": {
var type = data["type"];
var payload = data["payload"];
var html = "";
var format = {};
log.debug("Got event " + type + " with payload: " + JSON.stringify(payload));
if (type == "SettingsUpdated") {
if (payload && payload.hasOwnProperty("config_hash")) {
self._configHash = payload.config_hash;
}
} else if (type == "MovieRendering") {
new PNotify({title: gettext("Rendering timelapse"), text: _.sprintf(gettext("Now rendering timelapse %(movie_basename)s"), payload)});
} else if (type == "MovieDone") {
new PNotify({title: gettext("Timelapse ready"), text: _.sprintf(gettext("New timelapse %(movie_basename)s is done rendering."), payload)});
} else if (type == "MovieFailed") {
html = "<p>" + _.sprintf(gettext("Rendering of timelapse %(movie_basename)s failed with return code %(returncode)s"), payload) + "</p>";
html += pnotifyAdditionalInfo('<pre style="overflow: auto">' + payload.error + '</pre>');
new PNotify({
title: gettext("Rendering failed"),
text: html,
type: "error",
hide: false
});
} else if (type == "PostRollStart") {
if (payload.postroll_duration > 60) {
format = {duration: _.sprintf(gettext("%(minutes)d min"), {minutes: payload.postroll_duration / 60})};
} else {
format = {duration: _.sprintf(gettext("%(seconds)d sec"), {seconds: payload.postroll_duration})};
}
new PNotify({
title: gettext("Capturing timelapse postroll"),
text: _.sprintf(gettext("Now capturing timelapse post roll, this will take approximately %(duration)s..."), format)
});
} else if (type == "SlicingStarted") {
gcodeUploadProgress.addClass("progress-striped").addClass("active");
gcodeUploadProgressBar.css("width", "100%");
if (payload.progressAvailable) {
gcodeUploadProgressBar.text(_.sprintf(gettext("Slicing ... (%(percentage)d%%)"), {percentage: 0}));
} else {
gcodeUploadProgressBar.text(gettext("Slicing ..."));
}
} else if (type == "SlicingDone") {
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
gcodeUploadProgressBar.css("width", "0%");
gcodeUploadProgressBar.text("");
new PNotify({title: gettext("Slicing done"), text: _.sprintf(gettext("Sliced %(stl)s to %(gcode)s, took %(time).2f seconds"), payload), type: "success"});
} else if (type == "SlicingCancelled") {
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
gcodeUploadProgressBar.css("width", "0%");
gcodeUploadProgressBar.text("");
} else if (type == "SlicingFailed") {
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
gcodeUploadProgressBar.css("width", "0%");
gcodeUploadProgressBar.text("");
html = _.sprintf(gettext("Could not slice %(stl)s to %(gcode)s: %(reason)s"), payload);
new PNotify({title: gettext("Slicing failed"), text: html, type: "error", hide: false});
} else if (type == "TransferStarted") {
gcodeUploadProgress.addClass("progress-striped").addClass("active");
gcodeUploadProgressBar.css("width", "100%");
gcodeUploadProgressBar.text(gettext("Streaming ..."));
} else if (type == "TransferDone") {
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
gcodeUploadProgressBar.css("width", "0%");
gcodeUploadProgressBar.text("");
new PNotify({
title: gettext("Streaming done"),
text: _.sprintf(gettext("Streamed %(local)s to %(remote)s on SD, took %(time).2f seconds"), payload),
type: "success"
});
gcodeFilesViewModel.requestData(payload.remote, "sdcard");
}
var legacyEventHandlers = {
"UpdatedFiles": "onUpdatedFiles",
"MetadataStatisticsUpdated": "onMetadataStatisticsUpdated",
"MetadataAnalysisFinished": "onMetadataAnalysisFinished",
"SlicingDone": "onSlicingDone",
"SlicingCancelled": "onSlicingCancelled",
"SlicingFailed": "onSlicingFailed"
};
_.each(self.allViewModels, function(viewModel) {
if (viewModel.hasOwnProperty("onEvent" + type)) {
viewModel["onEvent" + type](payload);
} else if (legacyEventHandlers.hasOwnProperty(type) && viewModel.hasOwnProperty(legacyEventHandlers[type])) {
// there might still be code that uses the old callbacks, make sure those still get called
// but log a warning
log.warn("View model " + viewModel.name + " is using legacy event handler " + legacyEventHandlers[type] + ", new handler is called " + legacyEventHandlers[type]);
viewModel[legacyEventHandlers[type]](payload);
}
});
break;
}
case "timelapse": {
callViewModels(self.allViewModels, "fromTimelapseData", [data]);
break;
}
case "plugin": {
callViewModels(self.allViewModels, "onDataUpdaterPluginMessage", [data.plugin, data.data]);
break;
}
}
// if the version, the plugin hash or the config hash changed, we
// want the user to reload the UI since it might be stale now
var versionChanged = oldVersion != VERSION;
var pluginsChanged = oldPluginHash != undefined && oldPluginHash != self._pluginHash;
var configChanged = oldConfigHash != undefined && oldConfigHash != self._configHash;
if (versionChanged || pluginsChanged || configChanged) {
self.reloadOverlay.show();
}
};
self._onHistoryData = function(event) {
callViewModels(self.allViewModels, "fromHistoryData", [event.data]);
};
self._onCurrentData = function(event) {
callViewModels(self.allViewModels, "fromCurrentData", [event.data]);
};
self._onSlicingProgress = function(event) {
$("#gcode_upload_progress").find(".bar").text(_.sprintf(gettext("Slicing ... (%(percentage)d%%)"), {percentage: Math.round(event.data["progress"])}));
callViewModels(self.allViewModels, "onSlicingProgress", [
data["slicer"],
data["model_path"],
data["machinecode_path"],
data["progress"]
]);
};
self._onEvent = function(event) {
var gcodeUploadProgress = $("#gcode_upload_progress");
var gcodeUploadProgressBar = $(".bar", gcodeUploadProgress);
var type = event.data["type"];
var payload = event.data["payload"];
var html = "";
var format = {};
log.debug("Got event " + type + " with payload: " + JSON.stringify(payload));
if (type == "SettingsUpdated") {
if (payload && payload.hasOwnProperty("config_hash")) {
self._configHash = payload.config_hash;
}
} else if (type == "MovieRendering") {
new PNotify({title: gettext("Rendering timelapse"), text: _.sprintf(gettext("Now rendering timelapse %(movie_basename)s"), payload)});
} else if (type == "MovieDone") {
new PNotify({title: gettext("Timelapse ready"), text: _.sprintf(gettext("New timelapse %(movie_basename)s is done rendering."), payload)});
} else if (type == "MovieFailed") {
html = "<p>" + _.sprintf(gettext("Rendering of timelapse %(movie_basename)s failed with return code %(returncode)s"), payload) + "</p>";
html += pnotifyAdditionalInfo('<pre style="overflow: auto">' + payload.error + '</pre>');
new PNotify({
title: gettext("Rendering failed"),
text: html,
type: "error",
hide: false
});
} else if (type == "PostRollStart") {
var title = gettext("Capturing timelapse postroll");
var text;
if (!payload.postroll_duration) {
text = _.sprintf(gettext("Now capturing timelapse post roll, this will take only a moment..."), format);
} else {
if (payload.postroll_duration > 60) {
format = {duration: _.sprintf(gettext("%(minutes)d min"), {minutes: payload.postroll_duration / 60})};
} else {
format = {duration: _.sprintf(gettext("%(seconds)d sec"), {seconds: payload.postroll_duration})};
}
text = _.sprintf(gettext("Now capturing timelapse post roll, this will take approximately %(duration)s..."), format);
}
new PNotify({
title: title,
text: text
});
} else if (type == "SlicingStarted") {
gcodeUploadProgress.addClass("progress-striped").addClass("active");
gcodeUploadProgressBar.css("width", "100%");
if (payload.progressAvailable) {
gcodeUploadProgressBar.text(_.sprintf(gettext("Slicing ... (%(percentage)d%%)"), {percentage: 0}));
} else {
gcodeUploadProgressBar.text(gettext("Slicing ..."));
}
} else if (type == "SlicingDone") {
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
gcodeUploadProgressBar.css("width", "0%");
gcodeUploadProgressBar.text("");
new PNotify({title: gettext("Slicing done"), text: _.sprintf(gettext("Sliced %(stl)s to %(gcode)s, took %(time).2f seconds"), payload), type: "success"});
} else if (type == "SlicingCancelled") {
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
gcodeUploadProgressBar.css("width", "0%");
gcodeUploadProgressBar.text("");
} else if (type == "SlicingFailed") {
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
gcodeUploadProgressBar.css("width", "0%");
gcodeUploadProgressBar.text("");
html = _.sprintf(gettext("Could not slice %(stl)s to %(gcode)s: %(reason)s"), payload);
new PNotify({title: gettext("Slicing failed"), text: html, type: "error", hide: false});
} else if (type == "TransferStarted") {
gcodeUploadProgress.addClass("progress-striped").addClass("active");
gcodeUploadProgressBar.css("width", "100%");
gcodeUploadProgressBar.text(gettext("Streaming ..."));
} else if (type == "TransferDone") {
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
gcodeUploadProgressBar.css("width", "0%");
gcodeUploadProgressBar.text("");
new PNotify({
title: gettext("Streaming done"),
text: _.sprintf(gettext("Streamed %(local)s to %(remote)s on SD, took %(time).2f seconds"), payload),
type: "success"
});
gcodeFilesViewModel.requestData(payload.remote, "sdcard");
}
var legacyEventHandlers = {
"UpdatedFiles": "onUpdatedFiles",
"MetadataStatisticsUpdated": "onMetadataStatisticsUpdated",
"MetadataAnalysisFinished": "onMetadataAnalysisFinished",
"SlicingDone": "onSlicingDone",
"SlicingCancelled": "onSlicingCancelled",
"SlicingFailed": "onSlicingFailed"
};
_.each(self.allViewModels, function(viewModel) {
if (viewModel.hasOwnProperty("onEvent" + type)) {
viewModel["onEvent" + type](payload);
} else if (legacyEventHandlers.hasOwnProperty(type) && viewModel.hasOwnProperty(legacyEventHandlers[type])) {
// there might still be code that uses the old callbacks, make sure those still get called
// but log a warning
log.warn("View model " + viewModel.name + " is using legacy event handler " + legacyEventHandlers[type] + ", new handler is called " + legacyEventHandlers[type]);
viewModel[legacyEventHandlers[type]](payload);
}
});
};
self._onTimelapse = function(event) {
callViewModels(self.allViewModels, "fromTimelapseData", [event.data]);
};
self._onPluginMessage = function(event) {
callViewModels(self.allViewModels, "onDataUpdaterPluginMessage", [event.data.plugin, event.data.data]);
};
OctoPrint.socket.onReconnectAttempt = self._onReconnectAttempt;
OctoPrint.socket.onReconnectFailed = self._onReconnectFailed;
OctoPrint.socket
.onMessage("connected", self._onConnected)
.onMessage("history", self._onHistoryData)
.onMessage("current", self._onCurrentData)
.onMessage("slicingProgress", self._onSlicingProgress)
.onMessage("event", self._onEvent)
.onMessage("timelapse", self._onTimelapse)
.onMessage("plugin", self._onPluginMessage);
self.connect();
}

View file

@ -1,4 +1,6 @@
$(function() {
OctoPrint = window.OctoPrint;
//~~ Lodash setup
_.mixin({"sprintf": sprintf, "vsprintf": vsprintf});
@ -7,6 +9,23 @@ $(function() {
log.setLevel(CONFIG_DEBUG ? "debug" : "info");
//~~ OctoPrint client setup
OctoPrint.options.baseurl = BASEURL;
OctoPrint.options.apikey = UI_API_KEY;
OctoPrint.socket.onMessage("connected", function(data) {
var payload = data.data;
OctoPrint.options.apikey = payload.apikey;
// update the API key directly in jquery's ajax options too,
// to ensure the fileupload plugin and any plugins still using
// $.ajax directly still work fine too
UI_API_KEY = payload["apikey"];
$.ajaxSetup({
headers: {"X-Api-Key": UI_API_KEY}
});
});
//~~ AJAX setup
// work around a stupid iOS6 bug where ajax requests get cached and only work once, as described at
@ -62,6 +81,26 @@ $(function() {
PNotify.prototype.options.styling = "bootstrap2";
PNotify.prototype.options.mouse_reset = false;
PNotify.singleButtonNotify = function(options) {
if (!options.confirm || !options.confirm.buttons || !options.confirm.buttons.length) {
return new PNotify(options);
}
var autoDisplay = options.auto_display != false;
var params = $.extend(true, {}, options);
params.auto_display = false;
var notify = new PNotify(params);
notify.options.confirm.buttons = [notify.options.confirm.buttons[0]];
notify.modules.confirm.makeDialog(notify, notify.options.confirm);
if (autoDisplay) {
notify.open();
}
return notify;
};
//~~ Initialize view models
// the view model map is our basic look up table for dependencies that may be injected into other view models
@ -487,7 +526,8 @@ $(function() {
if (!_.has(viewModelMap, "settingsViewModel")) {
throw new Error("settingsViewModel is missing, can't run UI")
}
viewModelMap["settingsViewModel"].requestData(bindViewModels);
viewModelMap["settingsViewModel"].requestData()
.done(bindViewModels);
}
);

View file

@ -47,14 +47,8 @@ $(function() {
self.previousIsOperational = undefined;
self.requestData = function() {
$.ajax({
url: API_BASEURL + "connection",
method: "GET",
dataType: "json",
success: function(response) {
self.fromResponse(response);
}
})
OctoPrint.connection.getSettings()
.done(self.fromResponse);
};
self.fromResponse = function(response) {
@ -93,7 +87,7 @@ $(function() {
} else if (!self.isOperational() && !connectionTab.hasClass("in")) {
connectionTab.collapse("show");
}
}
};
self._processStateData = function(data) {
self.previousIsOperational = self.isOperational();
@ -116,7 +110,6 @@ $(function() {
self.connect = function() {
if (self.isErrorOrClosed()) {
var data = {
"command": "connect",
"port": self.selectedPort() || "AUTO",
"baudrate": self.selectedBaudrate() || 0,
"printerProfile": self.selectedPrinter(),
@ -126,26 +119,14 @@ $(function() {
if (self.saveSettings())
data["save"] = true;
$.ajax({
url: API_BASEURL + "connection",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(data),
success: function(response) {
OctoPrint.connection.connect(data)
.done(function() {
self.settings.requestData();
self.settings.printerProfiles.requestData();
}
});
});
} else {
self.requestData();
$.ajax({
url: API_BASEURL + "connection",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({"command": "disconnect"})
})
OctoPrint.connection.disconnect();
}
};

View file

@ -108,14 +108,10 @@ $(function() {
};
self.requestData = function () {
$.ajax({
url: API_BASEURL + "printer/command/custom",
method: "GET",
dataType: "json",
success: function (response) {
OctoPrint.control.getCustomControls()
.done(function(response) {
self._fromResponse(response);
}
});
});
};
self._fromResponse = function (response) {
@ -254,26 +250,17 @@ $(function() {
multiplier *= -1;
}
var data = {
"command": "jog"
};
var data = {};
data[axis] = distance * multiplier;
self.sendPrintHeadCommand(data);
OctoPrint.printer.jog(data);
};
self.sendHomeCommand = function (axis) {
self.sendPrintHeadCommand({
"command": "home",
"axes": axis
});
OctoPrint.printer.home(axis);
};
self.sendFeedRateCommand = function () {
self.sendPrintHeadCommand({
"command": "feedrate",
"factor": self.feedRate()
});
OctoPrint.printer.setFeedrate(self.feedRate());
};
self.sendExtrudeCommand = function () {
@ -285,90 +272,46 @@ $(function() {
};
self.sendFlowRateCommand = function () {
self.sendToolCommand({
"command": "flowrate",
"factor": self.flowRate()
});
OctoPrint.printer.setFlowrate(self.flowRate());
};
self._sendECommand = function (dir) {
var length = self.extrusionAmount();
if (!length) length = self.settings.printer_defaultExtrusionLength();
self.sendToolCommand({
command: "extrude",
amount: length * dir
});
var length = self.extrusionAmount() || self.settings.printer_defaultExtrusionLength();
OctoPrint.printer.extrude(length * dir);
};
self.sendSelectToolCommand = function (data) {
if (!data || !data.key()) return;
self.sendToolCommand({
command: "select",
tool: data.key()
});
};
self.sendPrintHeadCommand = function (data) {
$.ajax({
url: API_BASEURL + "printer/printhead",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(data)
});
};
self.sendToolCommand = function (data) {
$.ajax({
url: API_BASEURL + "printer/tool",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(data)
});
OctoPrint.printer.selectTool(data.key());
};
self.sendCustomCommand = function (command) {
if (!command)
return;
var data = undefined;
if (command.hasOwnProperty("command")) {
// single command
data = {"command": command.command};
} else if (command.hasOwnProperty("commands")) {
// multi command
data = {"commands": command.commands};
} else if (command.hasOwnProperty("script")) {
data = {"script": command.script};
if (command.hasOwnProperty("context")) {
data["context"] = command.context;
if (command.hasOwnProperty("command") || command.hasOwnProperty("commands")) {
var commands = command.commands || [command.command];
if (commands.hasOwnProperty("input")) {
var parameters = {};
_.each(command.input, function(input) {
if (!input.hasOwnProperty("parameter") || !input.hasOwnProperty("value")) {
return;
}
parameters[input.parameter] = input.value();
});
OctoPrint.control.sendGcodeWithParameters(commands, parameters);
} else {
OctoPrint.control.sendGcode(commands);
}
} else {
return;
} else if (command.hasOwnProperty("script")) {
var script = command.script;
var context = command.context || {};
OctoPrint.control.sendGcodeScript(script, context);
}
if (command.hasOwnProperty("input")) {
// parametric command(s)
data["parameters"] = {};
_.each(command.input, function(input) {
if (!input.hasOwnProperty("parameter") || !input.hasOwnProperty("value")) {
return;
}
data["parameters"][input.parameter] = input.value();
});
}
$.ajax({
url: API_BASEURL + "printer/command",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(data)
})
};
self.displayMode = function (customControl) {
@ -414,7 +357,7 @@ $(function() {
} else {
$("#webcam_rotator").css("height", "");
}
}
};
self.onSettingsBeforeSave = self.updateRotatorWidth;

View file

@ -165,19 +165,13 @@ $(function() {
if (self._otherRequestInProgress) return;
self._otherRequestInProgress = true;
$.ajax({
url: API_BASEURL + "files",
method: "GET",
dataType: "json",
data: {"recursive": true},
success: function(response) {
OctoPrint.files.list({ data: { recursive: true} })
.done(function(response) {
self.fromResponse(response, filenameToFocus, locationToFocus, switchToPath);
})
.always(function() {
self._otherRequestInProgress = false;
},
error: function() {
self._otherRequestInProgress = false;
}
});
});
};
self.fromResponse = function(response, filenameToFocus, locationToFocus, switchToPath) {
@ -244,7 +238,7 @@ $(function() {
self.currentPath("");
self.listHelper.updateItems(self.allItems());
}
}
};
self.pathByElement = function(element) {
if (!element || element.parent == undefined)
@ -277,20 +271,22 @@ $(function() {
};
self.loadFile = function(file, printAfterLoad) {
if (!file || !file.refs || !file.refs.hasOwnProperty("resource")) return;
$.ajax({
url: file.refs.resource,
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({command: "select", print: printAfterLoad})
});
if (!file) {
return;
}
OctoPrint.files.select(file.origin, file.name)
.done(function() {
if (printAfterLoad) {
OctoPrint.job.start();
}
});
};
self.removeFile = function(file) {
if (!file || !file.refs || !file.refs.hasOwnProperty("resource")) return;
if (!file) {
return;
}
var index = self.listHelper.paginatedItems().indexOf(file) + 1;
if (index >= self.listHelper.paginatedItems().length)
index = index - 2;
@ -301,42 +297,31 @@ $(function() {
var fileToFocus = self.listHelper.paginatedItems()[index];
if (fileToFocus)
filenameToFocus = fileToFocus.name;
$.ajax({
url: file.refs.resource,
type: "DELETE",
success: function() {
OctoPrint.files.delete(file.origin, file.name)
.done(function() {
self.requestData(undefined, filenameToFocus, self.pathByElement(file.parent));
}
});
})
};
self.sliceFile = function(file) {
if (!file) return;
if (!file) {
return;
}
self.slicing.show(file.origin, file.name, true);
};
self.initSdCard = function() {
self._sendSdCommand("init");
OctoPrint.printer.initSd();
};
self.releaseSdCard = function() {
self._sendSdCommand("release");
OctoPrint.printer.releaseSd();
};
self.refreshSdFiles = function() {
self._sendSdCommand("refresh");
};
self._sendSdCommand = function(command) {
$.ajax({
url: API_BASEURL + "printer/sd",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({command: command})
});
OctoPrint.printer.refreshSd();
};
self.downloadLink = function(data) {

View file

@ -29,6 +29,7 @@ $(function() {
self.ui_modelInfo = ko.observable("");
self.ui_layerInfo = ko.observable("");
self.tabActive = false;
self.enableReload = ko.observable(false);
self.waitForApproval = ko.observable(false);
@ -231,19 +232,20 @@ $(function() {
self._configureLayerSlider(layerSliderElement);
self._configureLayerCommandSlider(commandSliderElement);
self.settings.requestData(function() {
GCODE.ui.init({
container: "#gcode_canvas",
onProgress: self._onProgress,
onModelLoaded: self._onModelLoaded,
onLayerSelected: self._onLayerSelected,
bed: self._retrieveBedDimensions(),
toolOffsets: self._retrieveToolOffsets(),
invertAxes: self._retrieveAxesConfiguration()
self.settings.requestData()
.done(function() {
GCODE.ui.init({
container: "#gcode_canvas",
onProgress: self._onProgress,
onModelLoaded: self._onModelLoaded,
onLayerSelected: self._onLayerSelected,
bed: self._retrieveBedDimensions(),
toolOffsets: self._retrieveToolOffsets(),
invertAxes: self._retrieveAxesConfiguration()
});
self.synchronizeOptions();
self.enabled = true;
});
self.synchronizeOptions();
self.enabled = true;
});
};
self.reset = function() {
@ -289,11 +291,8 @@ $(function() {
self.enableReload(false);
if (self.status == "idle" && self.errorCount < 3) {
self.status = "request";
$.ajax({
url: BASEURL + "downloads/files/local/" + filename,
data: { "ctime": date },
type: "GET",
success: function(response, rstatus) {
OctoPrint.files.download("local", filename)
.done(function(response, rstatus) {
if(rstatus === 'success'){
self.showGCodeViewer(response, rstatus);
self.loadedFilename = filename;
@ -301,12 +300,11 @@ $(function() {
self.status = "idle";
self.enableReload(true);
}
},
error: function() {
})
.fail(function() {
self.status = "idle";
self.errorCount++;
}
});
});
}
};
@ -358,7 +356,7 @@ $(function() {
if(self.loadedFilename
&& self.loadedFilename == data.job.file.name
&& self.loadedFileDate == data.job.file.date) {
if (self.currentlyPrinting && self.renderer_syncProgress() && !self.waitForApproval()) {
if (self.tabActive && self.currentlyPrinting && self.renderer_syncProgress() && !self.waitForApproval()) {
var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.completion);
if(cmdIndex){
GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd);
@ -506,6 +504,10 @@ $(function() {
self.initialize();
}
self.onTabChange = function(current, previous) {
self.tabActive = current == "#gcode";
}
}
OCTOPRINT_VIEWMODELS.push([

View file

@ -36,12 +36,8 @@ $(function() {
);
self.requestData = function() {
$.ajax({
url: API_BASEURL + "logs",
type: "GET",
dataType: "json",
success: self.fromResponse
});
OctoPrint.logs.list()
.done(self.fromResponse);
};
self.fromResponse = function(response) {
@ -53,12 +49,8 @@ $(function() {
};
self.removeFile = function(filename) {
$.ajax({
url: API_BASEURL + "logs/" + filename,
type: "DELETE",
dataType: "json",
success: self.requestData
});
OctoPrint.logs.delete(filename)
.done(self.requestData);
};
self.onUserLoggedIn = function(user) {
@ -73,4 +65,4 @@ $(function() {
["loginStateViewModel"],
"#logs"
]);
});
});

View file

@ -32,20 +32,13 @@ $(function() {
return;
}
$.ajax({
url: API_BASEURL + "users/" + self.currentUser().name,
type: "GET",
success: self.fromResponse
})
OctoPrint.users.get(self.currentUser().name)
.done(self.fromResponse);
};
self.requestData = function() {
$.ajax({
url: API_BASEURL + "login",
type: "POST",
data: {"passive": true},
success: self.fromResponse
})
OctoPrint.browser.passiveLogin()
.done(self.fromResponse);
};
self.fromResponse = function(response) {
@ -70,7 +63,7 @@ $(function() {
}
};
self.login = function(u, p, r, callback) {
self.login = function(u, p, r) {
var username = u || self.loginUser();
var password = p || self.loginPass();
var remember = (r != undefined ? r : self.loginRemember());
@ -79,30 +72,22 @@ $(function() {
self.loginPass("");
self.loginRemember(false);
$.ajax({
url: API_BASEURL + "login",
type: "POST",
data: {"user": username, "pass": password, "remember": remember},
success: function(response) {
return OctoPrint.browser.login(username, password, remember)
.done(function(response) {
new PNotify({title: gettext("Login successful"), text: _.sprintf(gettext('You are now logged in as "%(username)s"'), {username: response.name}), type: "success"});
self.fromResponse(response);
if (callback) callback(response);
},
error: function(jqXHR, textStatus, errorThrown) {
})
.fail(function() {
new PNotify({title: gettext("Login failed"), text: gettext("User unknown or wrong password"), type: "error"});
}
})
});
};
self.logout = function() {
$.ajax({
url: API_BASEURL + "logout",
type: "POST",
success: function(response) {
OctoPrint.browser.logout()
.done(function(response) {
new PNotify({title: gettext("Logout successful"), text: gettext("You are now logged out"), type: "success"});
self.fromResponse(response);
}
})
});
};
self.onLoginUserKeyup = function(data, event) {

View file

@ -6,8 +6,7 @@ $(function() {
self.appearance = parameters[1];
self.settings = parameters[2];
self.usersettings = parameters[3];
self.systemActions = self.settings.system_actions;
self.system = parameters[4];
self.appearanceClasses = ko.computed(function() {
var classes = self.appearance.color();
@ -17,41 +16,11 @@ $(function() {
return classes;
});
self.triggerAction = function(action) {
var callback = function() {
$.ajax({
url: API_BASEURL + "system",
type: "POST",
dataType: "json",
data: "action=" + action.action,
success: function() {
new PNotify({title: "Success", text: _.sprintf(gettext("The command \"%(command)s\" executed successfully"), {command: action.name}), type: "success"});
},
error: function(jqXHR, textStatus, errorThrown) {
if (!action.hasOwnProperty("ignore") || !action.ignore) {
var error = "<p>" + _.sprintf(gettext("The command \"%(command)s\" could not be executed."), {command: action.name}) + "</p>";
error += pnotifyAdditionalInfo("<pre>" + jqXHR.responseText + "</pre>");
new PNotify({title: gettext("Error"), text: error, type: "error", hide: false});
}
}
})
};
if (action.confirm) {
showConfirmationDialog({
message: action.confirm,
onproceed: function(e) {
callback();
}
});
} else {
callback();
}
}
}
OCTOPRINT_VIEWMODELS.push([
NavigationViewModel,
["loginStateViewModel", "appearanceViewModel", "settingsViewModel", "userSettingsViewModel"],
["loginStateViewModel", "appearanceViewModel", "settingsViewModel", "userSettingsViewModel", "systemViewModel"],
"#navbar"
]);
});

View file

@ -194,12 +194,8 @@ $(function() {
};
self.requestData = function() {
$.ajax({
url: API_BASEURL + "printerprofiles",
type: "GET",
dataType: "json",
success: self.fromResponse
})
OctoPrint.printerprofiles.get()
.done(self.fromResponse);
};
self.fromResponse = function(data) {
@ -228,43 +224,35 @@ $(function() {
self.addProfile = function(callback) {
var profile = self._editorData();
self.requestInProgress(true);
$.ajax({
url: API_BASEURL + "printerprofiles",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({profile: profile}),
success: function() {
self.requestInProgress(false);
OctoPrint.printerprofiles.add(profile)
.done(function() {
if (callback !== undefined) {
callback();
}
self.requestData();
},
error: function() {
self.requestInProgress(false);
})
.fail(function() {
var text = gettext("There was unexpected error while saving the printer profile, please consult the logs.");
new PNotify({title: gettext("Saving failed"), text: text, type: "error", hide: false});
}
});
})
.always(function() {
self.requestInProgress(false);
});
};
self.removeProfile = function(data) {
self.requestInProgress(true);
$.ajax({
url: data.resource,
type: "DELETE",
dataType: "json",
success: function() {
self.requestInProgress(false);
OctoPrint.printerprofiles.delete(data.id, {url: data.resource})
.done(function() {
self.requestData();
},
error: function() {
self.requestInProgress(false);
})
.fail(function() {
var text = gettext("There was unexpected error while removing the printer profile, please consult the logs.");
new PNotify({title: gettext("Saving failed"), text: text, type: "error", hide: false});
}
})
})
.always(function() {
self.requestInProgress(false);
});
};
self.updateProfile = function(profile, callback) {
@ -273,26 +261,20 @@ $(function() {
}
self.requestInProgress(true);
$.ajax({
url: API_BASEURL + "printerprofiles/" + profile.id,
type: "PATCH",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({profile: profile}),
success: function() {
self.requestInProgress(false);
OctoPrint.printerprofiles.update(profile.key, profile)
.done(function() {
if (callback !== undefined) {
callback();
}
self.requestData();
},
error: function() {
self.requestInProgress(false);
})
.fail(function() {
var text = gettext("There was unexpected error while updating the printer profile, please consult the logs.");
new PNotify({title: gettext("Saving failed"), text: text, type: "error", hide: false});
}
});
})
.always(function() {
self.requestInProgress(false);
});
};
self.showEditProfileDialog = function(data) {

View file

@ -198,45 +198,25 @@ $(function() {
};
self.print = function() {
var restartCommand = function() {
self._jobCommand("restart");
};
if (self.isPaused()) {
showConfirmationDialog({
message: gettext("This will restart the print job from the beginning."),
onproceed: function(e) {
restartCommand();
onproceed: function() {
OctoPrint.job.restart();
}
});
} else {
self._jobCommand("start");
OctoPrint.job.start();
}
};
self.pause = function() {
self._jobCommand("pause");
OctoPrint.job.pause();
};
self.cancel = function() {
self._jobCommand("cancel");
OctoPrint.job.cancel();
};
self._jobCommand = function(command, callback) {
$.ajax({
url: API_BASEURL + "job",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({command: command}),
success: function(response) {
if (callback != undefined) {
callback();
}
}
});
}
}
OCTOPRINT_VIEWMODELS.push([

View file

@ -10,7 +10,7 @@ $(function() {
self.receiving = ko.observable(false);
self.sending = ko.observable(false);
self.callbacks = [];
self.outstanding = [];
self.settingsDialog = undefined;
self.settings_dialog_update_detected = undefined;
@ -225,18 +225,9 @@ $(function() {
var errorText = gettext("Could not retrieve snapshot URL, please double check the URL");
var errorTitle = gettext("Snapshot test failed");
$.ajax({
url: API_BASEURL + "util/test",
type: "POST",
dataType: "json",
data: JSON.stringify({
command: "url",
url: self.webcam_snapshotUrl(),
method: "GET",
response: true
}),
contentType: "application/json; charset=UTF-8",
success: function(response) {
OctoPrint.util.testUrl(self.webcam_snapshotUrl(), {method: "GET", response: true})
.done(function(response) {
$("i.icon-spinner", target).remove();
if (!response.result) {
@ -260,15 +251,14 @@ $(function() {
title: gettext("Snapshot test"),
message: $('<p>' + text + '</p><p><img src="data:' + mimeType + ';base64,' + content + '" /></p>')
});
},
error: function() {
})
.fail(function() {
$("i.icon-spinner", target).remove();
showMessageDialog({
title: errorTitle,
message: errorText
});
}
});
});
};
self.testWebcamFfmpegPath = function() {
@ -276,36 +266,22 @@ $(function() {
return;
}
var successCallback = function(response) {
if (!response.result) {
if (!response.exists) {
self.webcam_ffmpegPathText(gettext("The path doesn't exist"));
} else if (!response.typeok) {
self.webcam_ffmpegPathText(gettext("The path is not a file"));
} else if (!response.access) {
self.webcam_ffmpegPathText(gettext("The path is not an executable"));
OctoPrint.util.testExecutable(self.webcam_ffmpegPath())
.done(function(response) {
if (!response.result) {
if (!response.exists) {
self.webcam_ffmpegPathText(gettext("The path doesn't exist"));
} else if (!response.typeok) {
self.webcam_ffmpegPathText(gettext("The path is not a file"));
} else if (!response.access) {
self.webcam_ffmpegPathText(gettext("The path is not an executable"));
}
} else {
self.webcam_ffmpegPathText(gettext("The path is valid"));
}
} else {
self.webcam_ffmpegPathText(gettext("The path is valid"));
}
self.webcam_ffmpegPathOk(response.result);
self.webcam_ffmpegPathBroken(!response.result);
};
var path = self.webcam_ffmpegPath();
$.ajax({
url: API_BASEURL + "util/test",
type: "POST",
dataType: "json",
data: JSON.stringify({
command: "path",
path: path,
check_type: "file",
check_access: "x"
}),
contentType: "application/json; charset=UTF-8",
success: successCallback
})
self.webcam_ffmpegPathOk(response.result);
self.webcam_ffmpegPathBroken(!response.result);
});
};
self.onSettingsShown = function() {
@ -331,6 +307,7 @@ $(function() {
dataType: "json",
maxNumberOfFiles: 1,
autoUpload: false,
headers: OctoPrint.getRequestHeaders(),
add: function(e, data) {
if (data.files.length == 0) {
return false;
@ -409,57 +386,83 @@ $(function() {
return false;
};
self.requestData = function(callback, local) {
if (self.receiving()) {
if (callback) {
self.callbacks.push(callback);
self.requestData = function(local) {
// handle old parameter format
var callback = undefined;
if (arguments.length == 2 || _.isFunction(local)) {
var exc = new Error();
log.warn("The callback parameter of SettingsViewModel.requestData is deprecated, the method now returns a promise, please use that instead. Stacktrace:", (exc.stack || exc.stacktrace || "<n/a>"));
if (arguments.length == 2) {
callback = arguments[0];
local = arguments[1];
} else {
callback = local;
local = false;
}
return;
}
self.receiving(true);
$.ajax({
url: API_BASEURL + "settings",
type: "GET",
dataType: "json",
success: function(response) {
if (callback) {
self.callbacks.push(callback);
}
try {
self.fromResponse(response, local);
var cb;
while (self.callbacks.length) {
cb = self.callbacks.shift();
try {
cb();
} catch(exc) {
log.error("Error calling settings callback", cb, ":", (exc.stack || exc));
}
}
} finally {
self.receiving(false);
self.callbacks = [];
}
},
error: function(xhr) {
self.receiving(false);
// handler for any explicitely provided callbacks
var callbackHandler = function() {
if (!callback) return;
try {
callback();
} catch (exc) {
log.error("Error calling settings callback", callback, ":", (exc.stack || exc.stacktrace || exc));
}
});
};
// if a request is already active, create a new deferred and return
// its promise, it will be resolved in the response handler of the
// current request
if (self.receiving()) {
var deferred = $.Deferred();
self.outstanding.push(deferred);
if (callback) {
// if we have a callback, we need to make sure it will
// get called when the deferred is resolved
deferred.done(callbackHandler);
}
return deferred.promise();
}
// perform the request
self.receiving(true);
return OctoPrint.settings.get()
.done(function(response) {
self.fromResponse(response, local);
if (callback) {
var deferred = $.Deferred();
deferred.done(callbackHandler);
self.outstanding.push(deferred);
}
// resolve all promises
var args = arguments;
_.each(self.outstanding, function(deferred) {
deferred.resolve(args);
});
self.outstanding = [];
})
.fail(function() {
// reject all promises
var args = arguments;
_.each(self.outstanding, function(deferred) {
deferred.reject(args);
});
self.outstanding = [];
})
.always(function() {
self.receiving(false);
});
};
self.requestTranslationData = function(callback) {
$.ajax({
url: API_BASEURL + "languages",
type: "GET",
dataType: "json",
success: function(response) {
self.fromTranslationResponse(response);
if (callback) callback();
}
})
self.requestTranslationData = function() {
return OctoPrint.languages.list()
.done(self.fromTranslationResponse);
};
self.fromTranslationResponse = function(response) {
@ -509,14 +512,8 @@ $(function() {
});
self.deleteLanguagePack = function(locale, pack) {
$.ajax({
url: API_BASEURL + "languages/" + locale + "/" + pack,
type: "DELETE",
dataType: "json",
success: function(response) {
self.fromTranslationResponse(response);
}
})
OctoPrint.languages.delete(locale, pack)
.done(self.fromTranslationResponse);
};
/**
@ -652,7 +649,10 @@ $(function() {
longRunningCommands: function(value) { self.serial_longRunningCommands(value.join(", "))},
checksumRequiringCommands: function(value) { self.serial_checksumRequiringCommands(value.join(", "))}
},
terminalFilters: function(value) { self.terminalFilters.removeAll(); _.each(value, function(item) {self.terminalFilters.push(item)}); }
terminalFilters: function(value) { self.terminalFilters($.extend(true, [], value)) },
temperature: {
profiles: function(value) { self.temperature_profiles($.extend(true, [], value)); }
}
};
var mapToObservables = function(data, mapping, local, keyPrefix) {
@ -705,13 +705,8 @@ $(function() {
data = getOnlyChangedData(self.getLocalData(), self.lastReceivedSettings);
}
$.ajax({
url: API_BASEURL + "settings",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(data),
success: function(data, status, xhr) {
OctoPrint.settings.save(data)
.done(function(data, status, xhr) {
self.receiving(true);
self.sending(false);
try {
@ -720,15 +715,14 @@ $(function() {
} finally {
self.receiving(false);
}
},
error: function(xhr, status, error) {
})
.fail(function(xhr, status, error) {
self.sending(false);
if (options.error) options.error(xhr, status, error);
},
complete: function(xhr, status) {
})
.always(function(xhr, status) {
if (options.complete) options.complete(xhr, status);
}
});
});
};
self.onEventSettingsUpdated = function() {

View file

@ -64,18 +64,11 @@ $(function() {
&& self.profile() != undefined;
});
self.requestData = function(callback) {
$.ajax({
url: API_BASEURL + "slicing",
type: "GET",
dataType: "json",
success: function(data) {
self.requestData = function() {
return OctoPrint.slicing.listAllSlicersAndProfiles()
.done(function(data) {
self.fromResponse(data);
if (callback !== undefined) {
callback();
}
}
});
});
};
self.fromResponse = function(data) {
@ -150,7 +143,6 @@ $(function() {
}
var data = {
command: "slice",
slicer: self.slicer(),
profile: self.profile(),
printerProfile: self.printerProfile(),
@ -163,19 +155,14 @@ $(function() {
data["select"] = true;
}
$.ajax({
url: API_BASEURL + "files/" + self.target + "/" + self.file,
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(data)
});
OctoPrint.files.slice(self.target, self.file, data)
.done(function() {
$("#slicing_configuration_dialog").modal("hide");
$("#slicing_configuration_dialog").modal("hide");
self.gcodeFilename(undefined);
self.slicer(self.defaultSlicer);
self.profile(self.defaultProfile);
self.gcodeFilename(undefined);
self.slicer(self.defaultSlicer);
self.profile(self.defaultProfile);
});
};
self._sanitize = function(name) {
@ -196,4 +183,4 @@ $(function() {
["loginStateViewModel", "printerProfilesViewModel"],
"#slicing_configuration_dialog"
]);
});
});

View file

@ -0,0 +1,100 @@
$(function() {
function SystemViewModel(parameters) {
var self = this;
self.loginState = parameters[0];
self.lastCommandResponse = undefined;
self.systemActions = ko.observableArray([]);
self.requestData = function() {
self.requestCommandData();
};
self.requestCommandData = function() {
if (!self.loginState.isAdmin()) {
return $.Deferred().reject().promise();
}
return OctoPrint.system.getCommands()
.done(self.fromCommandResponse);
};
self.fromCommandResponse = function(response) {
var actions = [];
if (response.core && response.core.length) {
_.each(response.core, function(data) {
var action = _.extend({}, data);
action.actionSource = "core";
actions.push(action);
});
actions.push({action: "divider"});
}
_.each(response.custom, function(data) {
var action = _.extend({}, data);
action.actionSource = "custom";
actions.push(action);
});
self.lastCommandResponse = response;
self.systemActions(actions);
};
self.triggerCommand = function(commandSpec) {
var deferred = $.Deferred();
var callback = function() {
OctoPrint.system.executeCommand(commandSpec.actionSource, commandSpec.action)
.done(function() {
new PNotify({title: "Success", text: _.sprintf(gettext("The command \"%(command)s\" executed successfully"), {command: commandSpec.name}), type: "success"});
deferred.resolve(["success", arguments]);
})
.fail(function(jqXHR, textStatus, errorThrown) {
if (!commandSpec.hasOwnProperty("ignore") || !commandSpec.ignore) {
var error = "<p>" + _.sprintf(gettext("The command \"%(command)s\" could not be executed."), {command: commandSpec.name}) + "</p>";
error += pnotifyAdditionalInfo("<pre>" + jqXHR.responseText + "</pre>");
new PNotify({title: gettext("Error"), text: error, type: "error", hide: false});
deferred.reject(["error", arguments]);
} else {
deferred.resolve(["ignored", arguments]);
}
});
};
if (commandSpec.confirm) {
showConfirmationDialog({
message: commandSpec.confirm,
onproceed: function() {
callback();
},
oncancel: function() {
deferred.reject("cancelled", arguments);
}
});
} else {
callback();
}
return deferred.promise();
};
self.onUserLoggedIn = function(user) {
if (user.admin) {
self.requestData();
} else {
self.onUserLoggedOut();
}
};
self.onUserLoggedOut = function() {
self.lastCommandResponse = undefined;
self.systemActions([]);
}
}
// view model class, parameters for constructor, container to bind to
ADDITIONAL_VIEWMODELS.push([
SystemViewModel,
["loginStateViewModel"],
[]
]);
});

View file

@ -268,87 +268,84 @@ $(function() {
var value = item.newTarget();
if (!value) return;
self._sendToolCommand("target",
item.key(),
item.newTarget(),
function() {item.newTarget("");}
);
var onSuccess = function() {
item.newTarget("");
};
if (item.key() == "bed") {
self._setBedTemperature(value)
.done(onSuccess);
} else {
self._setToolTemperature(item.key(), value)
.done(onSuccess);
}
};
self.setTargetFromProfile = function(item, profile) {
if (!profile) return;
var value = undefined;
if (item.key() == "bed") {
value = profile.bed;
} else {
value = profile.extruder;
}
var onSuccess = function() {
item.newTarget("");
};
self._sendToolCommand("target",
item.key(),
value,
function() {item.newTarget("");}
);
if (item.key() == "bed") {
self._setBedTemperature(profile.bed)
.done(onSuccess);
} else {
self._setToolTemperature(item.key(), profile.extruder)
.done(onSuccess);
}
};
self.setTargetToZero = function(item) {
self._sendToolCommand("target",
item.key(),
0,
function() {item.newTarget("");}
);
var onSuccess = function() {
item.newTarget("");
};
if (item.key() == "bed") {
self._setBedTemperature(0)
.done(onSuccess);
} else {
self._setToolTemperature(item.key(), 0)
.done(onSuccess);
}
};
self.setOffset = function(item) {
self._sendToolCommand("offset",
item.key(),
item.newOffset(),
function() {item.newOffset("");}
);
};
var value = item.newOffset();
if (!value) return;
self._sendToolCommand = function(command, type, temp, successCb, errorCb) {
var data = {
command: command
var onSuccess = function() {
item.newOffset("");
};
var endpoint;
if (type == "bed") {
if ("target" == command) {
data["target"] = parseInt(temp);
} else if ("offset" == command) {
data["offset"] = parseInt(temp);
} else {
return;
}
endpoint = "bed";
if (item.key() == "bed") {
self._setBedOffset(value)
.done(onSuccess);
} else {
var group;
if ("target" == command) {
group = "targets";
} else if ("offset" == command) {
group = "offsets";
} else {
return;
}
data[group] = {};
data[group][type] = parseInt(temp);
endpoint = "tool";
self._setToolOffset(item.key(), value)
.done(onSuccess);
}
};
$.ajax({
url: API_BASEURL + "printer/" + endpoint,
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(data),
success: function() { if (successCb !== undefined) successCb(); },
error: function() { if (errorCb !== undefined) errorCb(); }
});
self._setToolTemperature = function(tool, temperature) {
var data = {};
data[tool] = parseInt(temperature);
return OctoPrint.printer.setToolTargetTemperatures(data);
};
self._setToolOffset = function(tool, offset) {
var data = {};
data[tool] = parseInt(offset);
return OctoPrint.printer.setToolTemperatureOffsets(data);
};
self._setBedTemperature = function(temperature) {
return OctoPrint.printer.setBedTargetTemperature(parseInt(temperature));
};
self._setBedOffset = function(offset) {
return OctoPrint.printer.setBedTemperatureOffset(parseInt(offset));
};
self.handleEnter = function(event, type, item) {
@ -375,4 +372,4 @@ $(function() {
["loginStateViewModel", "settingsViewModel"],
"#temp"
]);
});
});

View file

@ -158,29 +158,18 @@ $(function() {
}
if (command) {
$.ajax({
url: API_BASEURL + "printer/command",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({"command": command})
});
self.cmdHistory.push(command);
self.cmdHistory.slice(-300); // just to set a sane limit to how many manually entered commands will be saved...
self.cmdHistoryIdx = self.cmdHistory.length;
self.command("");
OctoPrint.control.sendGcode(command)
.done(function() {
self.cmdHistory.push(command);
self.cmdHistory.slice(-300); // just to set a sane limit to how many manually entered commands will be saved...
self.cmdHistoryIdx = self.cmdHistory.length;
self.command("");
});
}
};
self.fakeAck = function() {
$.ajax({
url: API_BASEURL + "connection",
type: "POST",
dataType: "json",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({"command": "fake_ack"})
});
OctoPrint.printer.fakeAck();
};
self.handleKeyDown = function(event) {
@ -232,4 +221,4 @@ $(function() {
["loginStateViewModel", "settingsViewModel"],
"#term"
]);
});
});

View file

@ -34,20 +34,20 @@ $(function() {
return self.isDirty() && self.isOperational() && !self.isPrinting() && self.loginState.isUser();
});
self.isOperational.subscribe(function(newValue) {
self.isOperational.subscribe(function() {
self.requestData();
});
self.timelapseType.subscribe(function(newValue) {
self.timelapseType.subscribe(function() {
self.isDirty(true);
});
self.timelapseTimedInterval.subscribe(function(newValue) {
self.timelapseTimedInterval.subscribe(function() {
self.isDirty(true);
});
self.timelapsePostRoll.subscribe(function(newValue) {
self.timelapsePostRoll.subscribe(function() {
self.isDirty(true);
});
self.timelapseFps.subscribe(function(newValue) {
self.timelapseFps.subscribe(function() {
self.isDirty(true);
});
@ -83,12 +83,8 @@ $(function() {
);
self.requestData = function() {
$.ajax({
url: API_BASEURL + "timelapse",
type: "GET",
dataType: "json",
success: self.fromResponse
});
OctoPrint.timelapse.get()
.done(self.fromResponse);
};
self.fromResponse = function(response) {
@ -141,15 +137,11 @@ $(function() {
};
self.removeFile = function(filename) {
$.ajax({
url: API_BASEURL + "timelapse/" + filename,
type: "DELETE",
dataType: "json",
success: self.requestData
});
OctoPrint.timelapse.delete(filename)
.done(self.requestData);
};
self.save = function(data, event) {
self.save = function() {
var payload = {
"type": self.timelapseType(),
"postRoll": self.timelapsePostRoll(),
@ -161,20 +153,15 @@ $(function() {
payload["interval"] = self.timelapseTimedInterval();
}
$.ajax({
url: API_BASEURL + "timelapse",
type: "POST",
dataType: "json",
data: payload,
success: self.fromResponse
});
OctoPrint.timelapse.saveConfig(payload)
.done(self.fromResponse);
};
self.onDataUpdaterReconnect = function() {
self.requestData();
};
self.onEventMovieDone = function(payload) {
self.onEventMovieDone = function() {
self.requestData();
};

View file

@ -60,12 +60,8 @@ $(function() {
self.requestData = function() {
if (!CONFIG_ACCESS_CONTROL) return;
$.ajax({
url: API_BASEURL + "users",
type: "GET",
dataType: "json",
success: self.fromResponse
});
OctoPrint.users.list()
.done(self.fromResponse);
};
self.fromResponse = function(response) {
@ -83,12 +79,19 @@ $(function() {
self.confirmAddUser = function() {
if (!CONFIG_ACCESS_CONTROL) return;
var user = {name: self.editorUsername(), password: self.editorPassword(), admin: self.editorAdmin(), active: self.editorActive()};
self.addUser(user, function() {
// close dialog
self.currentUser(undefined);
self.addUserDialog.modal("hide");
});
var user = {
name: self.editorUsername(),
password: self.editorPassword(),
admin: self.editorAdmin(),
active: self.editorActive()
};
self.addUser(user)
.done(function() {
// close dialog
self.currentUser(undefined);
self.addUserDialog.modal("hide");
});
};
self.showEditUserDialog = function(user) {
@ -105,12 +108,12 @@ $(function() {
user.active = self.editorActive();
user.admin = self.editorAdmin();
// make AJAX call
self.updateUser(user, function() {
// close dialog
self.currentUser(undefined);
self.editUserDialog.modal("hide");
});
self.updateUser(user)
.done(function() {
// close dialog
self.currentUser(undefined);
self.editUserDialog.modal("hide");
});
};
self.showChangePasswordDialog = function(user) {
@ -123,27 +126,30 @@ $(function() {
self.confirmChangePassword = function() {
if (!CONFIG_ACCESS_CONTROL) return;
self.updatePassword(self.currentUser().name, self.editorPassword(), function() {
// close dialog
self.currentUser(undefined);
self.changePasswordDialog.modal("hide");
});
self.updatePassword(self.currentUser().name, self.editorPassword())
.done(function() {
// close dialog
self.currentUser(undefined);
self.changePasswordDialog.modal("hide");
});
};
self.confirmGenerateApikey = function() {
if (!CONFIG_ACCESS_CONTROL) return;
self.generateApikey(self.currentUser().name, function(response) {
self._updateApikey(response.apikey);
})
self.generateApikey(self.currentUser().name)
.done(function(response) {
self._updateApikey(response.apikey);
});
};
self.confirmDeleteApikey = function() {
if (!CONFIG_ACCESS_CONTROL) return;
self.deleteApikey(self.currentUser().name, function() {
self._updateApikey(undefined);
})
self.deleteApikey(self.currentUser().name)
.done(function() {
self._updateApikey(undefined);
});
};
self._updateApikey = function(apikey) {
@ -159,108 +165,55 @@ $(function() {
self.changePasswordDialog = $("#settings-usersDialogChangePassword");
};
//~~ AJAX calls
//~~ API calls
self.addUser = function(user, callback) {
if (!CONFIG_ACCESS_CONTROL) return;
if (user === undefined) return;
self.addUser = function(user) {
if (!user) {
throw OctoPrint.InvalidArgumentError("user must be set");
}
$.ajax({
url: API_BASEURL + "users",
type: "POST",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(user),
success: function(response) {
self.fromResponse(response);
if (callback) {
callback(response);
}
}
});
return OctoPrint.users.add(user)
.done(self.fromResponse);
};
self.removeUser = function(user, callback) {
if (!CONFIG_ACCESS_CONTROL) return;
if (user === undefined) return;
self.removeUser = function(user) {
if (!user) {
throw OctoPrint.InvalidArgumentError("user must be set");
}
if (user.name == self.loginState.username()) {
// we do not allow to delete ourselves
new PNotify({title: "Not possible", text: "You may not delete your own account.", type: "error"});
return;
new PNotify({
title: gettext("Not possible"),
text: gettext("You may not delete your own account."),
type: "error"
});
return $.Deferred().reject("You may not delete your own account").promise();
}
$.ajax({
url: API_BASEURL + "users/" + user.name,
type: "DELETE",
success: function(response) {
self.fromResponse(response);
if (callback) {
callback(response);
}
}
});
return OctoPrint.users.delete(user.name)
.done(self.fromResponse);
};
self.updateUser = function(user, callback) {
if (!CONFIG_ACCESS_CONTROL) return;
if (user === undefined) return;
self.updateUser = function(user) {
if (!user) {
throw OctoPrint.InvalidArgumentError("user must be set");
}
$.ajax({
url: API_BASEURL + "users/" + user.name,
type: "PUT",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(user),
success: function(response) {
self.fromResponse(response);
if (callback) {
callback(response);
}
}
});
return OctoPrint.users.update(user.name, user.active, user.admin)
.done(self.fromResponse);
};
self.updatePassword = function(username, password, callback) {
if (!CONFIG_ACCESS_CONTROL) return;
$.ajax({
url: API_BASEURL + "users/" + username + "/password",
type: "PUT",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({password: password}),
success: function(response) {
if (callback) {
callback(response);
}
}
});
self.updatePassword = function(username, password) {
return OctoPrint.users.changePassword(username, password);
};
self.generateApikey = function(username, callback) {
if (!CONFIG_ACCESS_CONTROL) return;
$.ajax({
url: API_BASEURL + "users/" + username + "/apikey",
type: "POST",
success: function(response) {
if (callback) {
callback(response);
}
}
});
self.generateApikey = function(username) {
return OctoPrint.users.generateApiKey(username);
};
self.deleteApikey = function(username, callback) {
if (!CONFIG_ACCESS_CONTROL) return;
$.ajax({
url: API_BASEURL + "users/" + username + "/apikey",
type: "DELETE",
success: function(response) {
if (callback) {
callback(response);
}
}
});
self.deleteApikey = function(username) {
return OctoPrint.users.resetApiKey(username);
};
self.onUserLoggedIn = function(user) {
@ -275,4 +228,4 @@ $(function() {
["loginStateViewModel"],
[]
]);
});
});

View file

@ -60,24 +60,17 @@ $(function() {
"language": self.interface_language()
}
};
self.updateSettings(self.currentUser().name, settings, function() {
// close dialog
self.currentUser(undefined);
self.userSettingsDialog.modal("hide");
self.loginState.reloadUser();
});
self.updateSettings(self.currentUser().name, settings)
.done(function() {
// close dialog
self.currentUser(undefined);
self.userSettingsDialog.modal("hide");
self.loginState.reloadUser();
});
};
self.updateSettings = function(username, settings, callback) {
if (!CONFIG_ACCESS_CONTROL) return;
$.ajax({
url: API_BASEURL + "users/" + username + "/settings",
type: "PATCH",
contentType: "application/json; charset=UTF-8",
data: JSON.stringify(settings),
success: callback
});
self.updateSettings = function(username, settings) {
return OctoPrint.users.saveSettings(username, settings);
};
self.saveEnabled = function() {

View file

@ -19,18 +19,19 @@ $(function() {
self.showDialog = function() {
if (!CONFIG_WIZARD || !(CONFIG_FIRST_RUN || self.loginState.isAdmin())) return;
self.getWizardDetails(function(response) {
callViewModels(self.allViewModels, "onWizardDetails", [response]);
self.getWizardDetails()
.done(function(response) {
callViewModels(self.allViewModels, "onWizardDetails", [response]);
if (!self.isDialogActive()) {
self.wizardDialog.modal({
minHeight: function() { return Math.max($.fn.modal.defaults.maxHeight() - 80, 250); }
}).css({
width: 'auto',
'margin-left': function() { return -($(this).width() /2); }
});
}
});
if (!self.isDialogActive()) {
self.wizardDialog.modal({
minHeight: function() { return Math.max($.fn.modal.defaults.maxHeight() - 80, 250); }
}).css({
width: 'auto',
'margin-left': function() { return -($(this).width() /2); }
});
}
});
};
self.closeDialog = function() {
@ -121,53 +122,34 @@ $(function() {
// then
reload = (method() == "reload") || reload;
});
self.finishWizard(function() {
self.closeDialog();
if (reload) {
log.info("Wizard requested reloading");
location.reload(true);
}
});
self.finishWizard()
.done(function() {
self.closeDialog();
if (reload) {
log.info("Wizard requested reloading");
location.reload(true);
}
});
}
}
});
self.showDialog();
};
self.getWizardDetails = function(callback) {
if (!callback) return;
$.ajax({
url: API_BASEURL + "setup/wizard",
type: "GET",
dataType: "json",
success: function(response) {
self.getWizardDetails = function() {
return OctoPrint.wizard.get()
.done(function(response) {
self.wizards = _.filter(_.keys(response), function(key) { return response[key] && response[key]["required"] && !response[key]["ignored"]; });
if (callback) {
callback(response);
}
}
});
});
};
self.finishWizard = function(callback) {
self.finishWizard = function() {
self.finishing = true;
self.settingsViewModel.saveData();
$.ajax({
url: API_BASEURL + "setup/wizard",
type: "POST",
dataType: "json",
data: JSON.stringify({handled: self.wizards}),
contentType: "application/json; charset=UTF-8",
success: function() {
return OctoPrint.wizard.finish(self.wizards)
.always(function() {
self.finishing = false;
callback();
},
failure: function() {
self.finishing = false;
}
})
});
};
self.onSettingsPreventRefresh = function() {

View file

@ -2,6 +2,10 @@
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
{% assets "js_client" %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
{% assets "js_app" %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}

View file

@ -2,11 +2,11 @@
<i class="icon-off"></i> {{ _('System') }}
<b class="caret"></b>
</a>
<ul class="dropdown-menu" data-bind="foreach: systemActions">
<ul class="dropdown-menu" data-bind="foreach: system.systemActions">
<!-- ko if: action == "divider" -->
<li class="divider" />
<!-- /ko -->
<!-- ko if: action != "divider" -->
<li><a href="#" data-bind="click: $root.triggerAction, text: name"></a></li>
<li><a href="#" data-bind="click: $root.system.triggerCommand, text: name"></a></li>
<!-- /ko -->
</ul>

View file

@ -233,7 +233,7 @@ class Timelapse(object):
return f
if self._post_roll > 0:
eventManager().fire(Events.POSTROLL_START, dict(postroll_duration=self.post_roll * self.fps, postroll_length=self.post_roll, postroll_fps=self.fps))
eventManager().fire(Events.POSTROLL_START, dict(postroll_duration=self.calculate_post_roll(), postroll_length=self.post_roll, postroll_fps=self.fps))
self._post_roll_start = time.time()
if doCreateMovie:
self._on_post_roll_done = getWaitForCaptures(resetAndCreate)
@ -247,6 +247,9 @@ class Timelapse(object):
else:
resetImageNumber()
def calculate_post_roll(self):
return None
def process_post_roll(self):
self.post_roll_finished()
@ -470,6 +473,9 @@ class TimedTimelapse(Timelapse):
self._postroll_captures = self.post_roll * self.fps
Timelapse.on_print_done(self, event, payload)
def calculate_post_roll(self):
return self.post_roll * self.fps * self.interval
def process_post_roll(self):
pass