summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2013-03-25 19:12:39 +0100
committerGravatar RaNaN <Mast3rRaNaN@hotmail.de> 2013-03-25 19:12:39 +0100
commitb5f66789e70bd105e162470927942c40496cdc92 (patch)
tree3a762f29008dd7d7c92221eb363f90580f8acb6f
parentadded view type for input fields (diff)
downloadpyload-b5f66789e70bd105e162470927942c40496cdc92.tar.xz
simple rendering for settings page
-rw-r--r--module/api/ConfigApi.py35
-rw-r--r--module/config/ConfigManager.py12
-rw-r--r--module/config/ConfigParser.py4
-rw-r--r--module/remote/apitypes.py6
-rw-r--r--module/remote/apitypes_debug.py3
-rw-r--r--module/remote/pyload.thrift5
-rw-r--r--module/web/static/js/models/ConfigHolder.js44
-rw-r--r--module/web/static/js/models/ConfigItem.js22
-rw-r--r--module/web/static/js/views/headerView.js3
-rw-r--r--module/web/static/js/views/queryModal.js1
-rw-r--r--module/web/static/js/views/settingsView.js89
-rw-r--r--module/web/templates/default/settings.html148
-rw-r--r--tests/manager/test_configManager.py4
-rw-r--r--tests/other/test_configparser.py3
14 files changed, 233 insertions, 146 deletions
diff --git a/module/api/ConfigApi.py b/module/api/ConfigApi.py
index 9df9455a2..4fba0c34e 100644
--- a/module/api/ConfigApi.py
+++ b/module/api/ConfigApi.py
@@ -6,6 +6,14 @@ from module.utils import to_string
from ApiComponent import ApiComponent
+# helper function to create a ConfigHolder
+def toConfigHolder(section, config, values):
+ holder = ConfigHolder(section, config.name, config.description, config.long_desc)
+ holder.items = [ConfigItem(option, x.name, x.description, x.type, to_string(x.default),
+ to_string(values.get(option, x.default))) for option, x in
+ config.config.iteritems()]
+ return holder
+
class ConfigApi(ApiComponent):
""" Everything related to configuration """
@@ -39,11 +47,7 @@ class ConfigApi(ApiComponent):
"""
data = {}
for section, config, values in self.core.config.iterCoreSections():
- holder = ConfigHolder(section, config.name, config.description, config.long_desc)
- holder.items = [ConfigItem(option, x.name, x.description, x.type, to_string(x.default),
- to_string(values.get(option, x.default))) for option, x in config.config.iteritems()]
-
- data[section] = holder
+ data[section] = toConfigHolder(section, config, values)
return data
def getCoreConfig(self):
@@ -65,9 +69,9 @@ class ConfigApi(ApiComponent):
for name, config, values in self.core.config.iterSections(self.user):
if not values: continue
item = ConfigInfo(name, config.name, config.description,
- self.core.pluginManager.getCategory(name),
- self.core.pluginManager.isUserPlugin(name),
- values.get("activated", False))
+ self.core.pluginManager.getCategory(name),
+ self.core.pluginManager.isUserPlugin(name),
+ values.get("activated", False))
data.append(item)
return data
@@ -80,19 +84,21 @@ class ConfigApi(ApiComponent):
"""
# TODO: filter user_context / addons when not allowed
return [ConfigInfo(name, config.name, config.description,
- self.core.pluginManager.getCategory(name),
- self.core.pluginManager.isUserPlugin(name))
+ self.core.pluginManager.getCategory(name),
+ self.core.pluginManager.isUserPlugin(name))
for name, config, values in self.core.config.iterSections(self.user)]
@RequirePerm(Permission.Plugins)
- def configurePlugin(self, plugin):
+ def loadConfig(self, name):
"""Get complete config options for desired section
- :param plugin: Name of plugin or config section
+ :param name: Name of plugin or config section
:rtype: ConfigHolder
"""
+ # requires at least plugin permissions, but only admin can load core config
+ config, values = self.core.config.getSection(name)
+ return toConfigHolder(name, config, values)
- pass
@RequirePerm(Permission.Plugins)
def saveConfig(self, config):
@@ -110,9 +116,6 @@ class ConfigApi(ApiComponent):
"""
self.core.config.delete(plugin, self.user)
- @RequirePerm(Permission.Plugins)
- def setConfigHandler(self, plugin, iid, value):
- pass
if Api.extend(ConfigApi):
del ConfigApi \ No newline at end of file
diff --git a/module/config/ConfigManager.py b/module/config/ConfigManager.py
index 8d908abaf..ff638fd71 100644
--- a/module/config/ConfigManager.py
+++ b/module/config/ConfigManager.py
@@ -42,6 +42,7 @@ class ConfigManager(ConfigParser):
# Entries are saved as (user, section) keys
self.values = {}
# TODO: similar to a cache, could be deleted periodically
+ # TODO: user / primaryuid is a bit messy
def save(self):
self.parser.save()
@@ -78,6 +79,8 @@ class ConfigManager(ConfigParser):
self.values[user, section] = {}
self.core.print_exc()
+ return self.values[user, section]
+
@convertKeyError
def set(self, section, option, value, sync=True, user=None):
""" set config value """
@@ -107,7 +110,7 @@ class ConfigManager(ConfigParser):
def delete(self, section, user=False):
""" Deletes values saved in db and cached values for given user, NOT meta data
Does not trigger an error when nothing was deleted. """
- user = user.primary if user else None
+ user = primary_uid(user)
if (user, section) in self.values:
del self.values[user, section]
@@ -132,3 +135,10 @@ class ConfigManager(ConfigParser):
for name, config in self.config.iteritems():
yield name, config, values[name] if name in values else {}
+
+ def getSection(self, section, user=None):
+ if section in self.parser and primary_uid(user) is None:
+ return self.parser.getSection(section)
+
+ values = self.loadValues(section, user)
+ return self.config.get(section), values
diff --git a/module/config/ConfigParser.py b/module/config/ConfigParser.py
index 2f974b75e..bf9192270 100644
--- a/module/config/ConfigParser.py
+++ b/module/config/ConfigParser.py
@@ -168,6 +168,10 @@ class ConfigParser:
for name, config in self.config.iteritems():
yield name, config, self.values[name] if name in self.values else {}
+ def getSection(self, section):
+ """ Retrieves single config as tuple (section, values) """
+ return self.config[section], self.values[section] if section in self.values else {}
+
def addConfigSection(self, section, name, desc, long_desc, config):
"""Adds a section to the config. `config` is a list of config tuples as used in plugin api defined as:
The order of the config elements is preserved with OrderedDict
diff --git a/module/remote/apitypes.py b/module/remote/apitypes.py
index e81c960c8..41f9be50e 100644
--- a/module/remote/apitypes.py
+++ b/module/remote/apitypes.py
@@ -389,8 +389,6 @@ class Iface(object):
pass
def checkURLs(self, urls):
pass
- def configurePlugin(self, plugin):
- pass
def createPackage(self, name, folder, root, password, site, comment, paused):
pass
def deleteCollLink(self, url):
@@ -467,6 +465,8 @@ class Iface(object):
pass
def isInteractionWaiting(self, mode):
pass
+ def loadConfig(self, name):
+ pass
def login(self, username, password):
pass
def moveFiles(self, fids, pid):
@@ -505,8 +505,6 @@ class Iface(object):
pass
def searchSuggestions(self, pattern):
pass
- def setConfigHandler(self, plugin, iid, value):
- pass
def setConfigValue(self, section, option, value):
pass
def setInteractionResult(self, iid, result):
diff --git a/module/remote/apitypes_debug.py b/module/remote/apitypes_debug.py
index 7b1b5e7f3..96673cc99 100644
--- a/module/remote/apitypes_debug.py
+++ b/module/remote/apitypes_debug.py
@@ -60,7 +60,6 @@ methods = {
'checkOnlineStatus': OnlineCheck,
'checkOnlineStatusContainer': OnlineCheck,
'checkURLs': (dict, basestring, list),
- 'configurePlugin': ConfigHolder,
'createPackage': int,
'deleteCollLink': None,
'deleteCollPack': None,
@@ -99,6 +98,7 @@ methods = {
'getWSAddress': basestring,
'hasAddonHandler': bool,
'isInteractionWaiting': bool,
+ 'loadConfig': ConfigHolder,
'login': bool,
'moveFiles': bool,
'movePackage': bool,
@@ -118,7 +118,6 @@ methods = {
'restartPackage': None,
'saveConfig': None,
'searchSuggestions': (list, basestring),
- 'setConfigHandler': None,
'setConfigValue': None,
'setInteractionResult': None,
'setPackageFolder': bool,
diff --git a/module/remote/pyload.thrift b/module/remote/pyload.thrift
index 2aeb54091..adaede0ff 100644
--- a/module/remote/pyload.thrift
+++ b/module/remote/pyload.thrift
@@ -244,7 +244,7 @@ struct ConfigItem {
}
struct ConfigHolder {
- 1: string name,
+ 1: string name, // for plugin this is the PluginName
2: string label,
3: string description,
4: string long_description,
@@ -368,12 +368,11 @@ service Pyload {
list<ConfigInfo> getPluginConfig(),
list<ConfigInfo> getAvailablePlugins(),
- ConfigHolder configurePlugin(1: PluginName plugin),
+ ConfigHolder loadConfig(1: string name),
void setConfigValue(1: string section, 2: string option, 3: string value),
void saveConfig(1: ConfigHolder config),
void deleteConfig(1: PluginName plugin),
- void setConfigHandler(1: PluginName plugin, 2: InteractionID iid, 3: JSONString value),
///////////////////////
// Download Preparing
diff --git a/module/web/static/js/models/ConfigHolder.js b/module/web/static/js/models/ConfigHolder.js
new file mode 100644
index 000000000..8beb31fb8
--- /dev/null
+++ b/module/web/static/js/models/ConfigHolder.js
@@ -0,0 +1,44 @@
+define(['jquery', 'backbone', 'underscore', 'app', './ConfigItem'],
+ function($, Backbone, _, App, ConfigItem) {
+
+ return Backbone.Model.extend({
+
+ defaults: {
+ name: "",
+ label: "",
+ description: "",
+ long_description: null,
+ // simple list but no collection
+ items: null,
+ info: null
+ },
+
+ // Model Constructor
+ initialize: function() {
+
+ },
+
+ // Loads it from server by name
+ fetch: function(options) {
+ options = App.apiRequest('loadConfig/"' + this.get('name') + '"', null, options);
+ return Backbone.Model.prototype.fetch.call(this, options);
+ },
+
+ save: function(options) {
+ // TODO
+ },
+
+ parse: function(resp) {
+ // Create item models
+ resp.items = _.map(resp.items, function(item) {
+ return new ConfigItem(item);
+ });
+
+ return Backbone.Model.prototype.parse.call(this, resp);
+ },
+
+ isLoaded: function() {
+ return this.has('items') || this.has('long_description');
+ }
+ });
+ }); \ No newline at end of file
diff --git a/module/web/static/js/models/ConfigItem.js b/module/web/static/js/models/ConfigItem.js
new file mode 100644
index 000000000..f55bb2b9e
--- /dev/null
+++ b/module/web/static/js/models/ConfigItem.js
@@ -0,0 +1,22 @@
+define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'],
+ function($, Backbone, _, App, Api) {
+
+ return Backbone.Model.extend({
+
+ defaults: {
+ name: "",
+ label: "",
+ description: "",
+ input: null,
+ default_valie: null,
+ value: null,
+ // additional attributes
+ inputView: null
+ },
+
+ // Model Constructor
+ initialize: function() {
+
+ }
+ });
+ }); \ No newline at end of file
diff --git a/module/web/static/js/views/headerView.js b/module/web/static/js/views/headerView.js
index 25127a337..db704a3db 100644
--- a/module/web/static/js/views/headerView.js
+++ b/module/web/static/js/views/headerView.js
@@ -55,7 +55,8 @@ define(['jquery', 'underscore', 'backbone', 'app', 'models/ServerStatus', 'colle
// TODO compare with polling
ws.onmessage = _.bind(this.onData, this);
ws.onerror = function(error) {
- alert(error);
+ console.log(error);
+ alert("WebSocket error" + error);
};
this.ws = ws;
diff --git a/module/web/static/js/views/queryModal.js b/module/web/static/js/views/queryModal.js
index 86fd5b78b..5477334a0 100644
--- a/module/web/static/js/views/queryModal.js
+++ b/module/web/static/js/views/queryModal.js
@@ -2,6 +2,7 @@ define(['jquery', 'underscore', 'app', 'views/abstract/modalView', './input/inpu
function($, _, App, modalView, load_input, template) {
return modalView.extend({
+ // TODO: submit on enter reloads the page sometimes
events: {
'click .btn-success': 'submit',
'submit form': 'submit'
diff --git a/module/web/static/js/views/settingsView.js b/module/web/static/js/views/settingsView.js
index a322cdae7..00c4b3739 100644
--- a/module/web/static/js/views/settingsView.js
+++ b/module/web/static/js/views/settingsView.js
@@ -1,23 +1,31 @@
-define(['jquery', 'underscore', 'backbone'],
- function($, _, Backbone) {
+define(['jquery', 'underscore', 'backbone', 'app', './input/inputLoader', 'models/ConfigHolder'],
+ function($, _, Backbone, App, load_input, ConfigHolder) {
// Renders settings over view page
return Backbone.View.extend({
el: "#content",
- template_menu: _.compile($("#template-menu").html()),
+ templateMenu: _.compile($("#template-menu").html()),
+ templateConfig: _.compile($("#template-config").html()),
+ templateConfigItem: _.compile($("#template-config-item").html()),
events: {
'click .settings-menu li > a': 'change_section'
},
menu: null,
+ content: null,
core_config: null, // It seems models are not needed
plugin_config: null,
+ // currently open configHolder
+ config: null,
+ isLoading: false,
+
initialize: function() {
- this.menu = $('.settings-menu');
+ this.menu = this.$('.settings-menu');
+ this.content = this.$('#settings-form');
this.refresh();
console.log("Settings initialized");
@@ -25,27 +33,80 @@ define(['jquery', 'underscore', 'backbone'],
refresh: function() {
var self = this;
- $.ajax("/api/getCoreConfig", {success: function(data) {
+ $.ajax(App.apiRequest("getCoreConfig", null, {success: function(data) {
self.core_config = data;
- self.render()
- }});
- $.ajax("/api/getPluginConfig", {success: function(data) {
+ self.render();
+ }}));
+ $.ajax(App.apiRequest("getPluginConfig", null, {success: function(data) {
self.plugin_config = data;
self.render();
- }});
+ }}));
},
render: function() {
- this.menu.html(this.template_menu({
- core: this.core_config,
- plugin: this.plugin_config
- }));
+ this.menu.html(this.templateMenu({
+ core: this.core_config,
+ plugin: this.plugin_config
+ }));
+ },
+
+ openConfig: function(name) {
+ // Do nothing when this config is already open
+ if (this.config && this.config.get('name') === name)
+ return;
+
+ this.config = new ConfigHolder({name: name});
+ this.loading();
+
+ var self = this;
+ this.config.fetch({success: function() {
+ if (!self.isLoading)
+ self.show();
+
+ }, failure: _.bind(this.failure, this)});
+
+ },
+
+ loading: function() {
+ this.isLoading = true;
+ var self = this;
+ this.content.fadeOut({complete: function() {
+ if (self.config.isLoaded())
+ self.show();
+
+ self.isLoading = false;
+ }});
+
+ },
+
+ show: function() {
+ // TODO: better refactor in separate views
+ this.content.html(this.templateConfig(this.config.toJSON()));
+ var container = this.content.find('.control-content');
+ var items = this.config.get('items');
+ var self = this;
+ _.each(items, function(item) {
+ var el = $('<div>').html(self.templateConfigItem(item.toJSON()));
+ var inputView = load_input("todo");
+ el.find('.controls').append(
+ new inputView(item.get('input'), item.get('value'),
+ item.get('default_value'), item.get('description')).render().el);
+ container.append(el);
+ });
+
+ this.content.fadeIn();
+ },
+
+ failure: function() {
+
},
change_section: function(e) {
+ // TODO check for changes
+
var el = $(e.target).parent();
var name = el.data("name");
- console.log("Section changed to " + name);
+ this.openConfig(name);
this.menu.find("li.active").removeClass("active");
el.addClass("active");
diff --git a/module/web/templates/default/settings.html b/module/web/templates/default/settings.html
index 1f9be3db0..7f6bbeb8e 100644
--- a/module/web/templates/default/settings.html
+++ b/module/web/templates/default/settings.html
@@ -15,119 +15,59 @@
{% block head %}
<script type="text/template" id="template-menu">
<%=if core%>
- <li class="nav-header"><i class="icon-globe icon-white"></i> {{ _("General") }}</li>
- <%=each core%>
- <li data-name="<% this.name %>"><a href="#"><% this.label %></a></li>
- <%/each%>
+ <li class="nav-header"><i class="icon-globe icon-white"></i> {{ _("General") }}</li>
+ <%=each core%>
+ <li data-name="<% this.name %>"><a href="#"><% this.label %></a></li>
+ <%/each%>
<%/if%>
<li class="divider"></li>
<li class="nav-header"><i class="icon-th-large icon-white"></i> {{ _("Addons") }}</li>
<li class="divider"></li>
<li class="nav-header"><i class="icon-th-list icon-white"></i> {{ _("Other") }}</li>
</script>
-{% endblock %}
-
-{% block content %}
- <div class="span2">
- <ul class="nav nav-list well settings-menu">
- </ul>
+ <script type="text/template" id="template-config">
+ <legend>
+ <div class="page-header">
+ <h1><% label %>
+ <small><% description %></small>
+ <a class="btn btn-small " href="#"><i
+ class="icon-question-sign"></i></a>
+ </h1>
</div>
- <!-- Info Popup -->
- <div class="modal hide fade in" id="info">
- <div class="modal-header">
- <button type="button" class="close">×</button>
- <h3>Info Popup</h3>
- </div>
- <div class="modal-body">
- <h4>General</h4>
-
- <p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem.</p>
-
- <h4>And...</h4>
-
- <p>Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
- egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.</p>
-
- </div>
- <div class="modal-footer">
- <button class="btn">Close</button>
- </div>
- </div>
- <!-- End Info Popup -->
- <div class="span10">
- <div class="well setting-box">
- <form class="form-horizontal">
- <legend>
- <div class="page-header">
- <h1>Example Settings
- <small>Subtext for header</small>
- <a class="btn btn-small " href="#"><i
- class="icon-question-sign"></i></a>
- </h1>
- </div>
- </legend>
- <div class="control-group">
- <label class="control-label">Max Parallel Downloads</label>
-
- <div class="controls">
- <input id="in_mpd" type="text" placeholder="3">
- </div>
- </div>
- <div class="control-group">
- <label class="control-label">Limit Download Speed</label>
-
- <div class="controls">
- <div class="btn-group" data-toggle="buttons-radio">
- <button type="button" class="btn bnmaxspeed" id="onmaxspeed">On</button>
- <button type="button" class="btn bnmaxspeed active" id="offmaxspeed">Off</button>
- </div>
- <div id="downloadspeed" style="display:none">
- <label>Max Download Speed in kb/s</label>
- <input type="text" placeholder="Tipp etwas ...">
- </div>
- </div>
- </div>
- <div class="control-group">
- <label class="control-label">Allow IPv6</label>
-
- <div class="controls">
- <div class="btn-group" data-toggle="buttons-radio">
- <button type="button" class="btn bnip6" id="onip6">On</button>
- <button type="button" class="btn bnip6 active" id="offip6">Off</button>
- </div>
- </div>
- </div>
- <div class="form-actions">
- <button type="submit" class="btn btn-primary">Änderungen Speichern</button>
- <button type="button" class="btn">Abbrechen</button>
- </div>
- </form>
- </div>
+ </legend>
+ <div class="control-content">
+ </div>
+ <div class="form-actions">
+ <button type="submit" class="btn btn-primary disabled">Save changes</button>
+ <button type="button" class="btn">Cancel</button>
+ </div>
+ </script>
+ <script type="text/template" id="template-config-item">
+ <div class="control-group">
+ <label class="control-label"><% label %></label>
+ <div class="controls">
</div>
- <script src="static/js/libs/jquery-1.9.0.js"></script>
- {# <script src="static/js/libs/bootstrap-2.1.1.js"></script>#}
- <script type="text/javascript">
- $(".bnmaxspeed").click(function() {
- $(".bnmaxspeed").removeClass("active");
- $(this).toggleClass("active");
- if ($("#onmaxspeed").hasClass("active")) {
- $("#downloadspeed").show();
- }
- else {
- $("#downloadspeed").hide();
- }
+ </div>
+ </script>
- });
- $(".bnip6").click(function() {
- $(".bnip6").removeClass("active");
- $(this).toggleClass("active");
- });
- $('#info').modal('toggle');
+{% endblock %}
- $('#in_mpd').tooltip({
- placement: 'right',
- title: 'Gib an wie viele Downloads gleichzeitg laufen dürfen.'
- });
+{% block actionbar %}
+{# <ul class="actionbar nav nav-pills span9">#}
+{# <li>Add Plugin</li>#}
+{# </ul>#}
+{% endblock %}
- </script>
+{% block content %}
+ <div class="span2">
+ <ul class="nav nav-list well settings-menu">
+ </ul>
+ </div>
+ <div class="span10">
+ <div class="well setting-box">
+ <form class="form-horizontal" id="settings-form">
+ <h1>Please choose a config section</h1>
+ </form>
+ </div>
+ </div>
{% endblock %} \ No newline at end of file
diff --git a/tests/manager/test_configManager.py b/tests/manager/test_configManager.py
index 0fed702c1..6c10da4dd 100644
--- a/tests/manager/test_configManager.py
+++ b/tests/manager/test_configManager.py
@@ -89,7 +89,6 @@ class TestConfigManager(TestCase):
# should not trigger something
self.config.delete("foo")
-
def test_sections(self):
self.addConfig()
@@ -111,6 +110,9 @@ class TestConfigManager(TestCase):
i +=1
assert i == 1
+ def test_get_section(self):
+ self.addConfig()
+ assert self.config.getSection("plugin")[0].name == "Name"
@raises(InvalidConfigSection)
def test_restricted_access(self):
diff --git a/tests/other/test_configparser.py b/tests/other/test_configparser.py
index 09b686738..7f34e64d3 100644
--- a/tests/other/test_configparser.py
+++ b/tests/other/test_configparser.py
@@ -31,6 +31,9 @@ class TestConfigParser():
assert isinstance(config.config, dict)
assert isinstance(values, dict)
+ def test_get(self):
+ assert self.config.getSection("general")[0].config
+
@raises(KeyError)
def test_invalid_config(self):
print self.config["invalid"]["config"]