Merge branch 'fix/pmgrPipConfig' into devel
Conflicts: src/octoprint/plugins/pluginmanager/__init__.py src/octoprint/util/pip.py
This commit is contained in:
commit
2a4ee5072c
4 changed files with 169 additions and 15 deletions
|
|
@ -88,9 +88,17 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin,
|
|||
)
|
||||
|
||||
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 +186,17 @@ 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=str(self._pip_caller.version)
|
||||
))
|
||||
|
||||
def on_api_command(self, command, data):
|
||||
if not admin_permission.can():
|
||||
|
|
@ -645,7 +661,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,12 @@ $(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.configurationDialog = $("#settings_plugin_pluginmanager_configurationdialog");
|
||||
|
||||
self.plugins = new ItemListHelper(
|
||||
"plugin.pluginmanager.installedplugins",
|
||||
{
|
||||
|
|
@ -72,6 +78,10 @@ $(function() {
|
|||
|
||||
self.followDependencyLinks = ko.observable(false);
|
||||
|
||||
self.pipAvailable = ko.observable(false);
|
||||
self.pipCommand = ko.observable();
|
||||
self.pipVersion = ko.observable();
|
||||
|
||||
self.working = ko.observable(false);
|
||||
self.workingTitle = ko.observable();
|
||||
self.workingDialog = undefined;
|
||||
|
|
@ -86,11 +96,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 +114,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 +124,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 +181,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 +203,17 @@ $(function() {
|
|||
}
|
||||
};
|
||||
|
||||
self._fromPipResponse = function(data) {
|
||||
self.pipAvailable(data.available);
|
||||
if (data.available) {
|
||||
self.pipCommand(data.command);
|
||||
self.pipVersion(data.version);
|
||||
} else {
|
||||
self.pipCommand(undefined);
|
||||
self.pipVersion(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
self.requestData = function(includeRepo) {
|
||||
if (!self.loginState.isAdmin()) {
|
||||
return;
|
||||
|
|
@ -343,6 +369,51 @@ $(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 data = {
|
||||
plugins: {
|
||||
pluginmanager: {
|
||||
repository: repository,
|
||||
repository_ttl: repositoryTtl,
|
||||
pip: pipCommand
|
||||
}
|
||||
}
|
||||
};
|
||||
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.installed = function(data) {
|
||||
return _.includes(self.installedPlugins(), data.id);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,20 @@
|
|||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pluginmanager_nopip() %}
|
||||
<div class="alert" data-bind="visible: !pipAvailable()">{% trans %}
|
||||
The <code>pip</code> command could not be detected automatically,
|
||||
please configure it manually. No installation and uninstallation of plugin
|
||||
packages is possible while <code>pip</code> is unavailable.
|
||||
{% endtrans %}</div>
|
||||
{% endmacro %}
|
||||
|
||||
{{ pluginmanager_printing() }}
|
||||
{{ pluginmanager_nopip() }}
|
||||
|
||||
<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 +60,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>)</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 +84,7 @@
|
|||
</div>
|
||||
<div class="modal-body">
|
||||
{{ pluginmanager_printing() }}
|
||||
{{ pluginmanager_nopip() }}
|
||||
<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 +185,41 @@
|
|||
<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="{{ _('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>
|
||||
|
||||
|
|
|
|||
|
|
@ -27,14 +27,16 @@ class PipCaller(CommandlineCaller):
|
|||
CommandlineCaller.__init__(self)
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
self._configured = configured
|
||||
self.configured = configured
|
||||
|
||||
self._command = None
|
||||
self._version = None
|
||||
|
||||
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
|
||||
|
|
@ -60,10 +62,18 @@ class PipCaller(CommandlineCaller):
|
|||
def available(self):
|
||||
return self._command is not None
|
||||
|
||||
def trigger_refresh(self):
|
||||
try:
|
||||
self._command, self._version = 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()
|
||||
|
|
@ -81,7 +91,7 @@ class PipCaller(CommandlineCaller):
|
|||
return self.call(command)
|
||||
|
||||
def _find_pip(self):
|
||||
pip_command = self._configured
|
||||
pip_command = self.configured
|
||||
pip_version = None
|
||||
|
||||
if pip_command is None:
|
||||
|
|
|
|||
Loading…
Reference in a new issue