diff options
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 | 
