Merge remote-tracking branch 'upstream/devel' into dev/folderSupport
Conflicts: src/octoprint/static/js/app/viewmodels/files.js
This commit is contained in:
commit
38a0f383ae
11 changed files with 362 additions and 144 deletions
16
README.md
16
README.md
|
|
@ -38,11 +38,25 @@ For information about how to go about contributions of any kind, please see the
|
|||
Installation
|
||||
------------
|
||||
|
||||
Installation instructions for installing from source for different operating systems can be found [on the wiki](https://github.com/foosel/OctoPrint/wiki#assorted-guides).
|
||||
Installation instructions for installing from source for different operating
|
||||
systems can be found [on the wiki](https://github.com/foosel/OctoPrint/wiki#assorted-guides).
|
||||
|
||||
If you want to run OctoPrint on a Raspberry Pi you might want to take a look at [OctoPi](https://github.com/guysoft/OctoPi)
|
||||
which is a custom SD card image that includes OctoPrint plus dependencies.
|
||||
|
||||
The generic steps that should basically be done regardless of operating system
|
||||
and runtime environment are the following (as *regular
|
||||
user*, please keep your hands *off* of the `sudo` command here!) - this assumes
|
||||
you already have Python 2.7, pip and virtualenv set up:
|
||||
|
||||
1. Checkout OctoPrint: `git clone https://github.com/foosel/OctoPrint.git`
|
||||
2. Change into the OctoPrint folder: `cd OctoPrint`
|
||||
3. Create a user-owned virtual environment therein: `virtualenv --system-site-packages venv`
|
||||
4. Install OctoPrint *into that virtual environment*: `./venv/bin/python setup.py install`
|
||||
|
||||
You may then start the OctoPrint server via `/path/to/OctoPrint/venv/bin/octoprint`, see [Usage](#usage)
|
||||
for details.
|
||||
|
||||
After installation, please make sure you follow the first-run wizard and set up
|
||||
access control as necessary. If you want to not only be notified about new
|
||||
releases but also be able to automatically upgrade to them from within
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Log file management
|
|||
Retrieve a list of available log files
|
||||
======================================
|
||||
|
||||
.. http:post:: /api/logs
|
||||
.. http:get:: /api/logs
|
||||
|
||||
Retrieve information regarding all log files currently available and regarding the disk space still available
|
||||
in the system on the location the log files are being stored.
|
||||
|
|
|
|||
|
|
@ -83,14 +83,23 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
|
|||
repository="http://plugins.octoprint.org/plugins.json",
|
||||
repository_ttl=24*60,
|
||||
pip=None,
|
||||
pip_args=None,
|
||||
dependency_links=False,
|
||||
hidden=[]
|
||||
)
|
||||
|
||||
def on_settings_save(self, data):
|
||||
old_pip = self._settings.get(["pip"])
|
||||
octoprint.plugin.SettingsPlugin.on_settings_save(self, data)
|
||||
new_pip = self._settings.get(["pip"])
|
||||
|
||||
self._repository_cache_ttl = self._settings.get_int(["repository_ttl"]) * 60
|
||||
self._pip_caller.refresh = True
|
||||
if old_pip != new_pip:
|
||||
self._pip_caller.configured = new_pip
|
||||
try:
|
||||
self._pip_caller.trigger_refresh()
|
||||
except:
|
||||
self._pip_caller
|
||||
|
||||
##~~ AssetPlugin
|
||||
|
||||
|
|
@ -178,9 +187,19 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
|
|||
self._repository_available = self._refresh_repository()
|
||||
|
||||
return jsonify(plugins=self._get_plugins(),
|
||||
repository=dict(available=self._repository_available, plugins=self._repository_plugins),
|
||||
repository=dict(
|
||||
available=self._repository_available,
|
||||
plugins=self._repository_plugins
|
||||
),
|
||||
os=self._get_os(),
|
||||
octoprint=self._get_octoprint_version_string())
|
||||
octoprint=self._get_octoprint_version_string(),
|
||||
pip=dict(
|
||||
available=self._pip_caller.available,
|
||||
command=self._pip_caller.command,
|
||||
version=self._pip_caller.version_string,
|
||||
use_sudo=self._pip_caller.use_sudo,
|
||||
additional_args=self._settings.get(["pip_args"])
|
||||
))
|
||||
|
||||
def on_api_command(self, command, data):
|
||||
if not admin_permission.can():
|
||||
|
|
@ -437,6 +456,10 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
|
|||
if "--process-dependency-links" in args:
|
||||
self._log_message(u"Installation needs to process external dependencies, that might make it take a bit longer than usual depending on the pip version")
|
||||
|
||||
additional_args = self._settings.get(["pip_args"])
|
||||
if additional_args:
|
||||
args.append(additional_args)
|
||||
|
||||
return self._pip_caller.execute(*args)
|
||||
|
||||
def _log_message(self, *lines):
|
||||
|
|
@ -645,7 +668,8 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
|
|||
pending_enable=(not plugin.enabled and plugin.key in self._pending_enable),
|
||||
pending_disable=(plugin.enabled and plugin.key in self._pending_disable),
|
||||
pending_install=(plugin.key in self._pending_install),
|
||||
pending_uninstall=(plugin.key in self._pending_uninstall)
|
||||
pending_uninstall=(plugin.key in self._pending_uninstall),
|
||||
origin=plugin.origin.type
|
||||
)
|
||||
|
||||
__plugin_name__ = "Plugin Manager"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,13 @@ $(function() {
|
|||
self.settingsViewModel = parameters[1];
|
||||
self.printerState = parameters[2];
|
||||
|
||||
self.config_repositoryUrl = ko.observable();
|
||||
self.config_repositoryTtl = ko.observable();
|
||||
self.config_pipCommand = ko.observable();
|
||||
self.config_pipAdditionalArgs = ko.observable();
|
||||
|
||||
self.configurationDialog = $("#settings_plugin_pluginmanager_configurationdialog");
|
||||
|
||||
self.plugins = new ItemListHelper(
|
||||
"plugin.pluginmanager.installedplugins",
|
||||
{
|
||||
|
|
@ -72,6 +79,12 @@ $(function() {
|
|||
|
||||
self.followDependencyLinks = ko.observable(false);
|
||||
|
||||
self.pipAvailable = ko.observable(false);
|
||||
self.pipCommand = ko.observable();
|
||||
self.pipVersion = ko.observable();
|
||||
self.pipUseSudo = ko.observable();
|
||||
self.pipAdditionalArgs = ko.observable();
|
||||
|
||||
self.working = ko.observable(false);
|
||||
self.workingTitle = ko.observable();
|
||||
self.workingDialog = undefined;
|
||||
|
|
@ -86,11 +99,15 @@ $(function() {
|
|||
};
|
||||
|
||||
self.enableUninstall = function(data) {
|
||||
return self.enableManagement() && !data.bundled && data.key != 'pluginmanager' && !data.pending_uninstall;
|
||||
return self.enableManagement()
|
||||
&& (data.origin != "entry_point" || self.pipAvailable())
|
||||
&& !data.bundled
|
||||
&& data.key != 'pluginmanager'
|
||||
&& !data.pending_uninstall;
|
||||
};
|
||||
|
||||
self.enableRepoInstall = function(data) {
|
||||
return self.enableManagement() && self.isCompatible(data);
|
||||
return self.enableManagement() && self.pipAvailable() && self.isCompatible(data);
|
||||
};
|
||||
|
||||
self.invalidUrl = ko.computed(function() {
|
||||
|
|
@ -100,7 +117,7 @@ $(function() {
|
|||
|
||||
self.enableUrlInstall = ko.computed(function() {
|
||||
var url = self.installUrl();
|
||||
return self.enableManagement() && url !== undefined && url.trim() != "" && !self.invalidUrl();
|
||||
return self.enableManagement() && self.pipAvailable() && url !== undefined && url.trim() != "" && !self.invalidUrl();
|
||||
});
|
||||
|
||||
self.invalidArchive = ko.computed(function() {
|
||||
|
|
@ -110,7 +127,7 @@ $(function() {
|
|||
|
||||
self.enableArchiveInstall = ko.computed(function() {
|
||||
var name = self.uploadFilename();
|
||||
return self.enableManagement() && name !== undefined && name.trim() != "" && !self.invalidArchive();
|
||||
return self.enableManagement() && self.pipAvailable() && name !== undefined && name.trim() != "" && !self.invalidArchive();
|
||||
});
|
||||
|
||||
self.uploadElement.fileupload({
|
||||
|
|
@ -167,7 +184,8 @@ $(function() {
|
|||
|
||||
self.fromResponse = function(data) {
|
||||
self._fromPluginsResponse(data.plugins);
|
||||
self._fromRepositoryResponse(data.repository)
|
||||
self._fromRepositoryResponse(data.repository);
|
||||
self._fromPipResponse(data.pip);
|
||||
};
|
||||
|
||||
self._fromPluginsResponse = function(data) {
|
||||
|
|
@ -188,6 +206,21 @@ $(function() {
|
|||
}
|
||||
};
|
||||
|
||||
self._fromPipResponse = function(data) {
|
||||
self.pipAvailable(data.available);
|
||||
if (data.available) {
|
||||
self.pipCommand(data.command);
|
||||
self.pipVersion(data.version);
|
||||
self.pipUseSudo(data.use_sudo);
|
||||
self.pipAdditionalArgs(data.additional_args);
|
||||
} else {
|
||||
self.pipCommand(undefined);
|
||||
self.pipVersion(undefined);
|
||||
self.pipUseSudo(undefined);
|
||||
self.pipAdditionalArgs(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
self.requestData = function(includeRepo) {
|
||||
if (!self.loginState.isAdmin()) {
|
||||
return;
|
||||
|
|
@ -343,6 +376,58 @@ $(function() {
|
|||
self.requestData(true);
|
||||
};
|
||||
|
||||
self.showPluginSettings = function() {
|
||||
self._copyConfig();
|
||||
self.configurationDialog.modal();
|
||||
};
|
||||
|
||||
self.savePluginSettings = function() {
|
||||
var pipCommand = self.config_pipCommand();
|
||||
if (pipCommand != undefined && pipCommand.trim() == "") {
|
||||
pipCommand = null;
|
||||
}
|
||||
|
||||
var repository = self.config_repositoryUrl();
|
||||
if (repository != undefined && repository.trim() == "") {
|
||||
repository = null;
|
||||
}
|
||||
|
||||
var repositoryTtl;
|
||||
try {
|
||||
repositoryTtl = parseInt(self.config_repositoryTtl());
|
||||
} catch (ex) {
|
||||
repositoryTtl = null;
|
||||
}
|
||||
|
||||
var pipArgs = self.config_pipAdditionalArgs();
|
||||
if (pipArgs != undefined && pipArgs.trim() == "") {
|
||||
pipArgs = null;
|
||||
}
|
||||
|
||||
var data = {
|
||||
plugins: {
|
||||
pluginmanager: {
|
||||
repository: repository,
|
||||
repository_ttl: repositoryTtl,
|
||||
pip: pipCommand,
|
||||
pip_args: pipArgs
|
||||
}
|
||||
}
|
||||
};
|
||||
self.settingsViewModel.saveData(data, function() {
|
||||
self.configurationDialog.modal("hide");
|
||||
self._copyConfig();
|
||||
self.refreshRepository();
|
||||
});
|
||||
};
|
||||
|
||||
self._copyConfig = function() {
|
||||
self.config_repositoryUrl(self.settingsViewModel.settings.plugins.pluginmanager.repository());
|
||||
self.config_repositoryTtl(self.settingsViewModel.settings.plugins.pluginmanager.repository_ttl());
|
||||
self.config_pipCommand(self.settingsViewModel.settings.plugins.pluginmanager.pip());
|
||||
self.config_pipAdditionalArgs(self.settingsViewModel.settings.plugins.pluginmanager.pip_args());
|
||||
};
|
||||
|
||||
self.installed = function(data) {
|
||||
return _.includes(self.installedPlugins(), data.id);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,31 @@
|
|||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pluginmanager_nopip() %}
|
||||
<div class="alert" data-bind="visible: !pipAvailable()">{% trans %}
|
||||
The <code>pip</code> command could not be found.
|
||||
Please configure it manually. No installation and uninstallation of plugin
|
||||
packages is possible while <code>pip</code> is unavailable.
|
||||
{% endtrans %}</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pluginmanager_sudopip() %}
|
||||
<div class="alert alert-error" data-bind="visible: pipUseSudo()">{% trans %}
|
||||
The <code>pip</code> command is configured to use <code>sudo</code>. This
|
||||
is <strong>not</strong> recommended due to security reasons. It is <strong>strongly</strong>
|
||||
suggested you install OctoPrint under a
|
||||
<a href="https://github.com/foosel/OctoPrint/#installation">user-owned virtual environment</a>
|
||||
so that the use of <code>sudo</code> is not needed for plugin management.
|
||||
{% endtrans %}</div>
|
||||
{% endmacro %}
|
||||
|
||||
{{ pluginmanager_printing() }}
|
||||
{{ pluginmanager_nopip() }}
|
||||
{{ pluginmanager_sudopip() }}
|
||||
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-small" data-bind="click: function() { $root.showPluginSettings(); }" title="{{ _('Plugin Configuration') }}"><i class="icon-wrench"></i></button>
|
||||
</div>
|
||||
|
||||
<h3>{{ _('Installed Plugins') }}</h3>
|
||||
|
||||
|
|
@ -47,6 +71,10 @@
|
|||
|
||||
<button class="btn btn-block" data-bind="click: $root.showRepository">{{ _('Get More...') }}</button>
|
||||
|
||||
<p class="muted" data-bind="visible: pipAvailable()">
|
||||
<small>Using pip at "<span data-bind="text: pipCommand"></span>" (Version <span data-bind="text: pipVersion"></span><span data-bind="visible: pipAdditionalArgs">, additional arguments: <span data-bind="text: pipAdditionalArgs"></span></span>)</small>
|
||||
</p>
|
||||
|
||||
<div id="settings_plugin_pluginmanager_workingdialog" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
|
|
@ -67,6 +95,8 @@
|
|||
</div>
|
||||
<div class="modal-body">
|
||||
{{ pluginmanager_printing() }}
|
||||
{{ pluginmanager_nopip() }}
|
||||
{{ pluginmanager_sudopip() }}
|
||||
<h4 style="position: relative">
|
||||
{{ _('... from the <a href="%(url)s" target="_blank">Plugin Repository</a>', url='http://plugins.octoprint.org') }}
|
||||
<a class="dropdown-toggle pull-right" data-toggle="dropdown" href="#">
|
||||
|
|
@ -167,3 +197,47 @@
|
|||
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Close') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings_plugin_pluginmanager_configurationdialog" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
<h3>{{ _('Plugin Configuration') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group" title="{{ _('pip command to use for managing plugins. You might have to configure this if auto detection fails.') }}">
|
||||
<label class="control-label">{{ _('pip command') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: config_pipCommand" placeholder="{{ _('Autodetect') }}">
|
||||
<span class="help-inline">{{ _('<strong>Only</strong> set this if OctoPrint cannot autodetect the path to <code>pip</code> to use for managing plugins.') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" title="{{ _('Additional arguments for pip command. You should normally not have to change this.') }}">
|
||||
<label class="control-label">{{ _('Additional pip arguments') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: config_pipAdditionalArgs">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" title="{{ _('URL of the Plugin Repository to use. You should normally not have to change this.') }}">
|
||||
<label class="control-label">{{ _('Repository URL') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: config_repositoryUrl">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" title="{{ _('How long to cache repository data, in minutes. You should normally not have to change this.') }}">
|
||||
<label class="control-label">{{ _('Repository cache TTL') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="number" class="input-mini" data-bind="value: config_repositoryTtl">
|
||||
<span class="add-on">min</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Cancel') }}</button>
|
||||
<button class="btn btn-primary" data-bind="click: savePluginSettings" aria-hidden="true">{{ _('Save') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -530,7 +530,8 @@ def get_remote_address(request):
|
|||
|
||||
|
||||
def get_json_command_from_request(request, valid_commands):
|
||||
if not "application/json" in request.headers["Content-Type"]:
|
||||
content_type = request.headers.get("Content-Type", None)
|
||||
if content_type is None or not "application/json" in content_type:
|
||||
return None, None, make_response("Expected content-type JSON", 400)
|
||||
|
||||
data = request.json
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -21,6 +21,15 @@ $(function() {
|
|||
headers: {"X-Api-Key": UI_API_KEY}
|
||||
});
|
||||
|
||||
//~~ Initialize file upload plugin
|
||||
|
||||
$.widget("blueimp.fileupload", $.blueimp.fileupload, {
|
||||
options: {
|
||||
dropZone: null,
|
||||
pasteZone: null
|
||||
}
|
||||
});
|
||||
|
||||
//~~ Initialize i18n
|
||||
|
||||
var catalog = window["BABEL_TO_LOAD_" + LOCALE];
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ $(function() {
|
|||
});
|
||||
|
||||
self.uploadButton = undefined;
|
||||
self.uploadSdButton = undefined;
|
||||
|
||||
self.allItems = ko.observable(undefined);
|
||||
self.listStyle = ko.observable("folders_files");
|
||||
|
|
@ -452,10 +453,16 @@ $(function() {
|
|||
|
||||
self.onUserLoggedIn = function(user) {
|
||||
self.uploadButton.fileupload("enable");
|
||||
if (self.uploadSdButton) {
|
||||
self.uploadSdButton.fileupload("enable");
|
||||
}
|
||||
};
|
||||
|
||||
self.onUserLoggedOut = function() {
|
||||
self.uploadButton.fileupload("disable");
|
||||
if (self.uploadSdButton) {
|
||||
self.uploadSdButton.fileupload("disable");
|
||||
}
|
||||
};
|
||||
|
||||
self.onStartup = function() {
|
||||
|
|
@ -482,6 +489,31 @@ $(function() {
|
|||
//~~ Gcode upload
|
||||
|
||||
self.uploadButton = $("#gcode_upload");
|
||||
self.uploadSdButton = $("#gcode_upload_sd");
|
||||
if (!self.uploadSdButton.length) {
|
||||
self.uploadSdButton = undefined;
|
||||
}
|
||||
|
||||
var uploadProgress = $("#gcode_upload_progress");
|
||||
var uploadProgressBar = uploadProgress.find(".bar");
|
||||
|
||||
var localTarget = CONFIG_SD_SUPPORT ? $("#drop_locally") : $("#drop");
|
||||
var sdTarget = $("#drop_sd");
|
||||
|
||||
function setProgressBar(percentage, text, active) {
|
||||
uploadProgressBar
|
||||
.css("width", percentage + "%")
|
||||
.text(text);
|
||||
|
||||
if (active) {
|
||||
uploadProgress
|
||||
.addClass("progress-striped active");
|
||||
} else {
|
||||
uploadProgress
|
||||
.removeClass("progress-striped active");
|
||||
}
|
||||
}
|
||||
|
||||
function gcode_upload_done(e, data) {
|
||||
var filename = undefined;
|
||||
var location = undefined;
|
||||
|
|
@ -499,9 +531,7 @@ $(function() {
|
|||
}
|
||||
|
||||
if (data.result.done) {
|
||||
$("#gcode_upload_progress .bar").css("width", "0%");
|
||||
$("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
|
||||
$("#gcode_upload_progress .bar").text("");
|
||||
setProgressBar(0, "", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -514,113 +544,44 @@ $(function() {
|
|||
type: "error",
|
||||
hide: false
|
||||
});
|
||||
$("#gcode_upload_progress .bar").css("width", "0%");
|
||||
$("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
|
||||
$("#gcode_upload_progress .bar").text("");
|
||||
setProgressBar(0, "", false);
|
||||
}
|
||||
|
||||
function gcode_upload_progress(e, data) {
|
||||
var progress = parseInt(data.loaded / data.total * 100, 10);
|
||||
$("#gcode_upload_progress .bar").css("width", progress + "%");
|
||||
$("#gcode_upload_progress .bar").text(gettext("Uploading ..."));
|
||||
if (progress >= 100) {
|
||||
$("#gcode_upload_progress").addClass("progress-striped").addClass("active");
|
||||
$("#gcode_upload_progress .bar").text(gettext("Saving ..."));
|
||||
}
|
||||
var uploaded = progress >= 100;
|
||||
|
||||
setProgressBar(progress, uploaded ? gettext("Saving ...") : gettext("Uploading ..."), uploaded);
|
||||
}
|
||||
|
||||
function enable_local_dropzone() {
|
||||
$("#gcode_upload").fileupload({
|
||||
url: API_BASEURL + "files/local",
|
||||
dataType: "json",
|
||||
dropZone: localTarget,
|
||||
done: gcode_upload_done,
|
||||
fail: gcode_upload_fail,
|
||||
progressall: gcode_upload_progress
|
||||
}).bind('fileuploadsubmit', function(e, data) {
|
||||
data.formData = { path: self.currentPath() };
|
||||
});
|
||||
}
|
||||
function setDropzone(dropzone, enable) {
|
||||
var button = (dropzone == "local") ? self.uploadButton : self.uploadSdButton;
|
||||
var drop = (dropzone == "local") ? localTarget : sdTarget;
|
||||
var url = API_BASEURL + "files/" + dropzone;
|
||||
|
||||
function disable_local_dropzone() {
|
||||
$("#gcode_upload").fileupload({
|
||||
url: API_BASEURL + "files/local",
|
||||
button.fileupload({
|
||||
url: url,
|
||||
dataType: "json",
|
||||
dropZone: null,
|
||||
dropZone: enable ? drop : null,
|
||||
drop: function(e, data) {
|
||||
|
||||
},
|
||||
done: gcode_upload_done,
|
||||
fail: gcode_upload_fail,
|
||||
progressall: gcode_upload_progress
|
||||
});
|
||||
}
|
||||
|
||||
function enable_sd_dropzone() {
|
||||
$("#gcode_upload_sd").fileupload({
|
||||
url: API_BASEURL + "files/sdcard",
|
||||
dataType: "json",
|
||||
dropZone: $("#drop_sd"),
|
||||
done: gcode_upload_done,
|
||||
fail: gcode_upload_fail,
|
||||
progressall: gcode_upload_progress
|
||||
}).bind('fileuploadsubmit', function(e, data) {
|
||||
data.formData = { path: self.currentPath() };
|
||||
});
|
||||
}
|
||||
|
||||
function disable_sd_dropzone() {
|
||||
$("#gcode_upload_sd").fileupload({
|
||||
url: API_BASEURL + "files/sdcard",
|
||||
dataType: "json",
|
||||
dropZone: null,
|
||||
done: gcode_upload_done,
|
||||
fail: gcode_upload_fail,
|
||||
progressall: gcode_upload_progress
|
||||
});
|
||||
}
|
||||
|
||||
var localTarget;
|
||||
if (CONFIG_SD_SUPPORT) {
|
||||
localTarget = $("#drop_locally");
|
||||
} else {
|
||||
localTarget = $("#drop");
|
||||
}
|
||||
|
||||
self.loginState.isUser.subscribe(function(newValue) {
|
||||
if (newValue === true) {
|
||||
enable_local_dropzone();
|
||||
} else {
|
||||
disable_local_dropzone();
|
||||
}
|
||||
});
|
||||
|
||||
if (self.loginState.isUser()) {
|
||||
enable_local_dropzone();
|
||||
} else {
|
||||
disable_local_dropzone();
|
||||
}
|
||||
|
||||
if (CONFIG_SD_SUPPORT) {
|
||||
self.printerState.isSdReady.subscribe(function(newValue) {
|
||||
if (newValue === true && self.loginState.isUser()) {
|
||||
enable_sd_dropzone();
|
||||
} else {
|
||||
disable_sd_dropzone();
|
||||
}
|
||||
});
|
||||
|
||||
self.loginState.isUser.subscribe(function(newValue) {
|
||||
if (newValue === true && self.printerState.isSdReady()) {
|
||||
enable_sd_dropzone();
|
||||
} else {
|
||||
disable_sd_dropzone();
|
||||
}
|
||||
});
|
||||
|
||||
if (self.printerState.isSdReady() && self.loginState.isUser()) {
|
||||
enable_sd_dropzone();
|
||||
} else {
|
||||
disable_sd_dropzone();
|
||||
}
|
||||
function evaluateDropzones() {
|
||||
var enableLocal = self.loginState.isUser();
|
||||
var enableSd = enableLocal && CONFIG_SD_SUPPORT && self.printerState.isSdReady();
|
||||
|
||||
setDropzone("local", enableLocal);
|
||||
setDropzone("sdcard", enableSd);
|
||||
}
|
||||
self.loginState.isUser.subscribe(evaluateDropzones);
|
||||
self.printerState.isSdReady.subscribe(evaluateDropzones);
|
||||
evaluateDropzones();
|
||||
|
||||
$(document).bind("dragover", function (e) {
|
||||
var dropOverlay = $("#drop_overlay");
|
||||
|
|
@ -633,7 +594,7 @@ $(function() {
|
|||
var timeout = window.dropZoneTimeout;
|
||||
|
||||
if (!timeout) {
|
||||
dropOverlay.addClass('in');
|
||||
dropOverlay.addClass("in");
|
||||
} else {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -540,6 +540,29 @@ $(function() {
|
|||
additionalBaudrates: function() { return _.map(splitTextToArray(self.serial_additionalBaudrates(), ",", true, function(item) { return !isNaN(parseInt(item)); }), function(item) { return parseInt(item); }) },
|
||||
longRunningCommands: function() { return splitTextToArray(self.serial_longRunningCommands(), ",", true) },
|
||||
checksumRequiringCommands: function() { return splitTextToArray(self.serial_checksumRequiringCommands(), ",", true) }
|
||||
},
|
||||
scripts: {
|
||||
gcode: function() {
|
||||
// we have a special handler function for the gcode scripts since the
|
||||
// server will always send us those that have been set already, so we
|
||||
// can't depend on all keys that we support to be present in the
|
||||
// original request we iterate through in mapFromObservables to
|
||||
// generate our response - hence we use our observables instead
|
||||
//
|
||||
// Note: If we ever introduce sub categories in the gcode scripts
|
||||
// here (more _ after the prefix), we'll need to adjust this code
|
||||
// to be able to cope with that, right now it only strips the prefix
|
||||
// and uses the rest as key in the result, no recursive translation
|
||||
// is done!
|
||||
var result = {};
|
||||
var prefix = "scripts_gcode_";
|
||||
var observables = _.filter(_.keys(self), function(key) { return _.startsWith(key, prefix); });
|
||||
_.each(observables, function(observable) {
|
||||
var script = observable.substring(prefix.length);
|
||||
result[script] = self[observable]();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -554,7 +577,10 @@ $(function() {
|
|||
observable = keyPrefix + "_" + observable;
|
||||
}
|
||||
|
||||
if (_.isPlainObject(value)) {
|
||||
if (mapping && mapping[key] && _.isFunction(mapping[key])) {
|
||||
result[key] = mapping[key]();
|
||||
flag = true;
|
||||
} else if (_.isPlainObject(value)) {
|
||||
// value is another object, we'll dive deeper
|
||||
var subresult = mapFromObservables(value, (mapping && mapping[key]) ? mapping[key] : undefined, observable);
|
||||
if (subresult != undefined) {
|
||||
|
|
@ -562,15 +588,9 @@ $(function() {
|
|||
result[key] = subresult;
|
||||
flag = true;
|
||||
}
|
||||
} else {
|
||||
// if we have a custom read function for this, we'll use it
|
||||
if (mapping && mapping[key] && _.isFunction(mapping[key])) {
|
||||
result[key] = mapping[key]();
|
||||
flag = true;
|
||||
} else if (self.hasOwnProperty(observable)) {
|
||||
result[key] = self[observable]();
|
||||
flag = true;
|
||||
}
|
||||
} else if (self.hasOwnProperty(observable)) {
|
||||
result[key] = self[observable]();
|
||||
flag = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -647,22 +667,17 @@ $(function() {
|
|||
observable = keyPrefix + "_" + observable;
|
||||
}
|
||||
|
||||
if (_.isPlainObject(value)) {
|
||||
var haveLocalVersion = local && local.hasOwnProperty(key);
|
||||
|
||||
if (mapping && mapping[key] && _.isFunction(mapping[key]) && !haveLocalVersion) {
|
||||
// if we have a custom apply function for this, we'll use it
|
||||
mapping[key](value);
|
||||
} else if (_.isPlainObject(value)) {
|
||||
// value is another object, we'll dive deeper
|
||||
mapToObservables(value, (mapping && mapping[key]) ? mapping[key] : undefined, (local && local[key]) ? local[key] : undefined, observable);
|
||||
} else {
|
||||
// if we have a local version of this field, we'll not override it
|
||||
if (local && local.hasOwnProperty(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mapping && mapping[key] && _.isFunction(mapping[key])) {
|
||||
// if we have a custom apply function for this, we'll use it
|
||||
mapping[key](value);
|
||||
} else if (self.hasOwnProperty(observable)) {
|
||||
// else if we have a matching observable, we'll use that
|
||||
self[observable](value);
|
||||
}
|
||||
} else if (!haveLocalVersion && self.hasOwnProperty(observable)) {
|
||||
// if we have a matching observable, we'll use that
|
||||
self[observable](value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -27,14 +27,19 @@ class PipCaller(CommandlineCaller):
|
|||
CommandlineCaller.__init__(self)
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
self._configured = configured
|
||||
self.configured = configured
|
||||
self.refresh = False
|
||||
|
||||
self._command = None
|
||||
self._version = None
|
||||
self._version_string = None
|
||||
self._use_sudo = False
|
||||
|
||||
self._command, self._version = self._find_pip()
|
||||
self.trigger_refresh()
|
||||
|
||||
self.refresh = False
|
||||
self.on_log_call = lambda *args, **kwargs: None
|
||||
self.on_log_stdout = lambda *args, **kwargs: None
|
||||
self.on_log_stderr = lambda *args, **kwargs: None
|
||||
|
||||
def __le__(self, other):
|
||||
return self.version is not None and self.version <= other
|
||||
|
|
@ -56,14 +61,30 @@ class PipCaller(CommandlineCaller):
|
|||
def version(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def version_string(self):
|
||||
return self._version_string
|
||||
|
||||
@property
|
||||
def use_sudo(self):
|
||||
return self._use_sudo
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
return self._command is not None
|
||||
|
||||
def trigger_refresh(self):
|
||||
try:
|
||||
self._command, self._version, self._version_string, self._use_sudo = self._find_pip()
|
||||
except:
|
||||
self._logger.exception("Error while discovering pip command")
|
||||
self._command = None
|
||||
self._version = None
|
||||
self.refresh = False
|
||||
|
||||
def execute(self, *args):
|
||||
if self.refresh:
|
||||
self._command, self._version = self._find_pip()
|
||||
self.refresh = False
|
||||
self.trigger_refresh()
|
||||
|
||||
if self._command is None:
|
||||
raise UnknownPip()
|
||||
|
|
@ -77,12 +98,22 @@ class PipCaller(CommandlineCaller):
|
|||
self._logger.debug("Version {} needs --no-use-wheel to properly work.".format(self.version))
|
||||
arg_list.append("--no-use-wheel")
|
||||
|
||||
command = [self._command] + arg_list
|
||||
command = [self._command] + list(arg_list)
|
||||
if self._use_sudo:
|
||||
command = ["sudo"] + command
|
||||
return self.call(command)
|
||||
|
||||
def _find_pip(self):
|
||||
pip_command = self._configured
|
||||
pip_command = self.configured
|
||||
|
||||
if pip_command is not None and pip_command.startswith("sudo "):
|
||||
pip_command = pip_command[len("sudo "):]
|
||||
pip_sudo = True
|
||||
else:
|
||||
pip_sudo = False
|
||||
|
||||
pip_version = None
|
||||
version_segment = None
|
||||
|
||||
if pip_command is None:
|
||||
import os
|
||||
|
|
@ -114,7 +145,11 @@ class PipCaller(CommandlineCaller):
|
|||
|
||||
if pip_command is not None:
|
||||
self._logger.debug("Found pip at {}, going to figure out its version".format(pip_command))
|
||||
p = sarge.run([pip_command, "--version"], stdout=sarge.Capture(), stderr=sarge.Capture())
|
||||
|
||||
sarge_command = [pip_command, "--version"]
|
||||
if pip_sudo:
|
||||
sarge_command = ["sudo"] + sarge_command
|
||||
p = sarge.run(sarge_command, stdout=sarge.Capture(), stderr=sarge.Capture())
|
||||
|
||||
if p.returncode != 0:
|
||||
self._logger.warn("Error while trying to run pip --version: {}".format(p.stderr.text))
|
||||
|
|
@ -140,12 +175,12 @@ class PipCaller(CommandlineCaller):
|
|||
pip_version = pkg_resources.parse_version(version_segment)
|
||||
except:
|
||||
self._logger.exception("Error while trying to parse version string from pip command")
|
||||
return None, None
|
||||
return None, None, None
|
||||
else:
|
||||
self._logger.info("Found pip at {}, version is {}".format(pip_command, version_segment))
|
||||
|
||||
if pip_version in self.__class__.broken:
|
||||
self._logger.error("This version of pip is known to have errors that make it incompatible with how it needs to be used by OctoPrint. Please upgrade your pip version.")
|
||||
return None, None
|
||||
return None, None, None, False
|
||||
|
||||
return pip_command, pip_version
|
||||
return pip_command, pip_version, version_segment, pip_sudo
|
||||
|
|
|
|||
Loading…
Reference in a new issue