diff options
author | RaNaN <Mast3rRaNaN@hotmail.de> | 2013-03-25 19:12:39 +0100 |
---|---|---|
committer | RaNaN <Mast3rRaNaN@hotmail.de> | 2013-03-25 19:12:39 +0100 |
commit | b5f66789e70bd105e162470927942c40496cdc92 (patch) | |
tree | 3a762f29008dd7d7c92221eb363f90580f8acb6f /module | |
parent | added view type for input fields (diff) | |
download | pyload-b5f66789e70bd105e162470927942c40496cdc92.tar.xz |
simple rendering for settings page
Diffstat (limited to 'module')
-rw-r--r-- | module/api/ConfigApi.py | 35 | ||||
-rw-r--r-- | module/config/ConfigManager.py | 12 | ||||
-rw-r--r-- | module/config/ConfigParser.py | 4 | ||||
-rw-r--r-- | module/remote/apitypes.py | 6 | ||||
-rw-r--r-- | module/remote/apitypes_debug.py | 3 | ||||
-rw-r--r-- | module/remote/pyload.thrift | 5 | ||||
-rw-r--r-- | module/web/static/js/models/ConfigHolder.js | 44 | ||||
-rw-r--r-- | module/web/static/js/models/ConfigItem.js | 22 | ||||
-rw-r--r-- | module/web/static/js/views/headerView.js | 3 | ||||
-rw-r--r-- | module/web/static/js/views/queryModal.js | 1 | ||||
-rw-r--r-- | module/web/static/js/views/settingsView.js | 89 | ||||
-rw-r--r-- | module/web/templates/default/settings.html | 148 |
12 files changed, 227 insertions, 145 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 |