r4913 - in branches/clarity: cumin/python/cumin mint/python/mint
by croberts@fedoraproject.org
Author: croberts
Date: 2011-08-08 20:42:48 +0000 (Mon, 08 Aug 2011)
New Revision: 4913
Modified:
branches/clarity/cumin/python/cumin/session.py
branches/clarity/mint/python/mint/session.py
Log:
Addressing BZ 728960 by suppressing the "username/password" portion of the broker URI. It will still show the host and port in the log entry. Source pulled to clarity branch from trunk.
Modified: branches/clarity/cumin/python/cumin/session.py
===================================================================
--- branches/clarity/cumin/python/cumin/session.py 2011-08-08 19:44:34 UTC (rev 4912)
+++ branches/clarity/cumin/python/cumin/session.py 2011-08-08 20:42:48 UTC (rev 4913)
@@ -22,7 +22,8 @@
self.lock = Lock()
def add_broker(self, uri):
- log.info("Adding QMF broker at %s with mech_list %s", uri, str(self.app.sasl_mech_list))
+ uri_without_password = uri[uri.rfind("@") + 1:]
+ log.info("Adding QMF broker at %s with mech_list %s", uri_without_password, str(self.app.sasl_mech_list))
assert self.qmf_session
@@ -34,7 +35,8 @@
log.info("Checking %s", self)
def init(self):
- log.info("Initializing %s", self)
+ uris_without_password = [x[x.rfind("@")+1:] for x in self.broker_uris]
+ log.info("Initializing %s", uris_without_password)
def start(self):
log.info("Starting %s", self)
@@ -119,7 +121,8 @@
self.lock.release()
def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, self.broker_uris)
+ uris_without_password = [x[x.rfind("@")+1:] for x in self.broker_uris]
+ return "%s(%s)" % (self.__class__.__name__, uris_without_password)
class CuminConsole(Console):
def __init__(self, session):
Modified: branches/clarity/mint/python/mint/session.py
===================================================================
--- branches/clarity/mint/python/mint/session.py 2011-08-08 19:44:34 UTC (rev 4912)
+++ branches/clarity/mint/python/mint/session.py 2011-08-08 20:42:48 UTC (rev 4913)
@@ -14,7 +14,8 @@
self.qmf_brokers = list()
def add_broker(self, uri):
- log.info("Adding QMF broker at %s with mech_list %s", uri, str(self.app.sasl_mech_list))
+ uri_without_password = uri[uri.rfind("@") + 1:]
+ log.info("Adding QMF broker at %s with mech_list %s", uri_without_password, str(self.app.sasl_mech_list))
assert self.qmf_session
12 years, 9 months
r4912 - in trunk: cumin/python/cumin mint/python/mint
by croberts@fedoraproject.org
Author: croberts
Date: 2011-08-08 19:44:34 +0000 (Mon, 08 Aug 2011)
New Revision: 4912
Modified:
trunk/cumin/python/cumin/session.py
trunk/mint/python/mint/session.py
Log:
Addressing BZ 728960 by suppressing the "username/password" portion of the broker URI. It will still show the host and port in the log entry.
Modified: trunk/cumin/python/cumin/session.py
===================================================================
--- trunk/cumin/python/cumin/session.py 2011-08-05 21:06:27 UTC (rev 4911)
+++ trunk/cumin/python/cumin/session.py 2011-08-08 19:44:34 UTC (rev 4912)
@@ -22,7 +22,8 @@
self.lock = Lock()
def add_broker(self, uri):
- log.info("Adding QMF broker at %s with mech_list %s", uri, str(self.app.sasl_mech_list))
+ uri_without_password = uri[uri.rfind("@") + 1:]
+ log.info("Adding QMF broker at %s with mech_list %s", uri_without_password, str(self.app.sasl_mech_list))
assert self.qmf_session
@@ -34,7 +35,8 @@
log.info("Checking %s", self)
def init(self):
- log.info("Initializing %s", self)
+ uris_without_password = [x[x.rfind("@")+1:] for x in self.broker_uris]
+ log.info("Initializing %s", uris_without_password)
def start(self):
log.info("Starting %s", self)
@@ -119,7 +121,8 @@
self.lock.release()
def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, self.broker_uris)
+ uris_without_password = [x[x.rfind("@")+1:] for x in self.broker_uris]
+ return "%s(%s)" % (self.__class__.__name__, uris_without_password)
class CuminConsole(Console):
def __init__(self, session):
Modified: trunk/mint/python/mint/session.py
===================================================================
--- trunk/mint/python/mint/session.py 2011-08-05 21:06:27 UTC (rev 4911)
+++ trunk/mint/python/mint/session.py 2011-08-08 19:44:34 UTC (rev 4912)
@@ -14,7 +14,8 @@
self.qmf_brokers = list()
def add_broker(self, uri):
- log.info("Adding QMF broker at %s with mech_list %s", uri, str(self.app.sasl_mech_list))
+ uri_without_password = uri[uri.rfind("@") + 1:]
+ log.info("Adding QMF broker at %s with mech_list %s", uri_without_password, str(self.app.sasl_mech_list))
assert self.qmf_session
12 years, 9 months
r4911 - trunk/cumin/python/cumin/grid
by croberts@fedoraproject.org
Author: croberts
Date: 2011-08-05 21:06:27 +0000 (Fri, 05 Aug 2011)
New Revision: 4911
Modified:
trunk/cumin/python/cumin/grid/pool.py
trunk/cumin/python/cumin/grid/tags.py
Log:
Changing the order of the tabs under Grid to put Tags near the end, also adding some pydoc for the tags package.
Modified: trunk/cumin/python/cumin/grid/pool.py
===================================================================
--- trunk/cumin/python/cumin/grid/pool.py 2011-08-05 20:51:31 UTC (rev 4910)
+++ trunk/cumin/python/cumin/grid/pool.py 2011-08-05 21:06:27 UTC (rev 4911)
@@ -101,14 +101,14 @@
self.edit_dynamic_quota = NegotiatorEditDynamicQuota(app, self)
self.quotas = QuotaSelector(app, "quotas", self.negotiator_attribute, self)
self.view.add_tab(self.quotas)
+
+ self.limits = LimitSelector(app, "limits")
+ self.view.add_tab(self.limits)
self.edit_node_tags = TagsNodeEditTask(app, self)
config_editor = TagsEditor(app, "wallaby")
self.view.add_tab(config_editor)
- self.limits = LimitSelector(app, "limits")
- self.view.add_tab(self.limits)
-
self.top_tab = True
def render_title(self, session):
Modified: trunk/cumin/python/cumin/grid/tags.py
===================================================================
--- trunk/cumin/python/cumin/grid/tags.py 2011-08-05 20:51:31 UTC (rev 4910)
+++ trunk/cumin/python/cumin/grid/tags.py 2011-08-05 21:06:27 UTC (rev 4911)
@@ -125,12 +125,12 @@
return data
-class ObjectSelectorTableNoCheckboxes(ObjectSelector):
+class ObjectSelectorNoCheckboxes(ObjectSelector):
'''
All of the ObjectSelector goodness, but without the checkboxes that come from the default ObjectSelectorTable
'''
def __init__(self, app, name, cls):
- super(ObjectSelectorTableNoCheckboxes, self).__init__(app, name, cls)
+ super(ObjectSelectorNoCheckboxes, self).__init__(app, name, cls)
def create_table(self, app, name, cls):
''' override the default to give us a plain ObjectTable rather than and ObjectSelectorTable '''
@@ -143,7 +143,10 @@
self.set_tags = TagsNodeEditTask(app, self)
self.set_nodes = TagsTagEditTask(app, self)
-class TagInventory(ObjectSelectorTableNoCheckboxes):
+class TagInventory(ObjectSelectorNoCheckboxes):
+ '''
+ Table that will display the list of all tags across the system.
+ '''
def __init__(self, app, name):
cls = app.model.com_redhat_cumin_grid.Node
super(TagInventory, self).__init__(app, name, cls)
@@ -180,7 +183,11 @@
return fmt_link(href, tags)
-class NodeInventory(ObjectSelectorTableNoCheckboxes):
+class NodeInventory(ObjectSelectorNoCheckboxes):
+ '''
+ Table that will display the list of nodes, their (possibly abbreviated) list of tags and
+ the last checkin time for that node.
+ '''
def __init__(self, app, name):
cls = app.model.com_redhat_cumin_grid.Node
super(NodeInventory, self).__init__(app, name, cls)
@@ -443,6 +450,9 @@
class EditNodeTagsForm(ObjectFrameTaskFeedbackForm):
+ '''
+ This form is designed to allow the editing of tags for any single node
+ '''
def __init__(self, app, name, task):
super(EditNodeTagsForm, self).__init__(app, name, task)
@@ -565,6 +575,9 @@
pass
class EditTagNodesForm(ObjectFrameTaskFeedbackForm):
+ '''
+ This form will allow the editing of nodes for a single given tag
+ '''
def __init__(self, app, name, task):
super(EditTagNodesForm, self).__init__(app, name, task)
@@ -850,7 +863,7 @@
tag_list = list()
for i, tag in enumerate(wallaby_tags):
- tag_list.append(xml_escape(str(tag.name)))
+ tag_list.append(str(tag.name))
tag_list.sort()
return tag_list
@@ -863,7 +876,7 @@
node_list = list()
for i, node in enumerate(wallaby_nodes):
- node_list.append(xml_escape(node.name))
+ node_list.append(str(node.name))
node_list.sort()
return node_list
\ No newline at end of file
12 years, 9 months
r4910 - trunk/cumin/bin
by tmckay@fedoraproject.org
Author: tmckay
Date: 2011-08-05 20:51:31 +0000 (Fri, 05 Aug 2011)
New Revision: 4910
Modified:
trunk/cumin/bin/cumin
Log:
Allow configure file to have "webs:" or "datas:", master will not run
any instances if the list is empty.
Modified: trunk/cumin/bin/cumin
===================================================================
--- trunk/cumin/bin/cumin 2011-08-05 20:49:12 UTC (rev 4909)
+++ trunk/cumin/bin/cumin 2011-08-05 20:51:31 UTC (rev 4910)
@@ -115,13 +115,15 @@
# Get our list of cumin-web and data instances
# create list elements to hold the process object, section arg, and app
apps = []
- for instance in options.webs.split(','):
- args, prog_string = get_args("cumin-web", instance, options.init_only, console, options.web_options)
- apps.append([None, args, prog_string])
+ if len(options.webs) > 0:
+ for instance in options.webs.split(','):
+ args, prog_string = get_args("cumin-web", instance, options.init_only, console, options.web_options)
+ apps.append([None, args, prog_string])
- for instance in options.datas.split(','):
- args, prog_string = get_args("cumin-data", instance, options.init_only, console, options.data_options)
- apps.append([None, args, prog_string])
+ if len(options.datas) > 0:
+ for instance in options.datas.split(','):
+ args, prog_string = get_args("cumin-data", instance, options.init_only, console, options.data_options)
+ apps.append([None, args, prog_string])
# If we are just checking startup flags, invoke each instance
# with "--init-only" and return status to caller.
12 years, 9 months
r4909 - trunk/sage/python/sage
by tmckay@fedoraproject.org
Author: tmckay
Date: 2011-08-05 20:49:12 +0000 (Fri, 05 Aug 2011)
New Revision: 4909
Modified:
trunk/sage/python/sage/util.py
Log:
Change url parsing routines to use regular expressions and allow path, etc.
Modified: trunk/sage/python/sage/util.py
===================================================================
--- trunk/sage/python/sage/util.py 2011-08-05 18:59:48 UTC (rev 4908)
+++ trunk/sage/python/sage/util.py 2011-08-05 20:49:12 UTC (rev 4909)
@@ -1,5 +1,8 @@
from time import time, sleep
from threading import Thread, Lock
+import re
+import copy
+import string
class MethodResult(object):
'''
@@ -201,78 +204,81 @@
'''
raise Exception("Not implemented")
-def host_port(hostname):
- '''
- Returns a tuple containing 'host' and 'port' strings from hostname.
+class sage_URL(object):
+ def __init__(self, scheme, user, password, host, port, path):
+ self.scheme = scheme
+ self.user = user
+ self.password = password
+ self.host = host
+ self.port = port
+ self.path = path
- Strings are split at the first colon to produce host and port strings.
- A string containing only digits will result in a tuple with the host
- value set to None and the port value set to the entire string. A string
- containing non-digits but no colon will result in a tuple with the port
- value set to None and the host value set to the entire string.
- '''
- assert type(hostname) in (str, unicode)
- import string
+ def __repr__(self):
+ return "sage_URL(%r)" % str(self)
- host = None
- port = None
+ def __str__(self):
+ s = ""
+ if self.scheme:
+ s += "%s://" % self.scheme
+ if self.user:
+ s += self.user
+ if self.password:
+ s += "/%s" % self.password
+ s += "@"
+ s += self.host
+ if self.port:
+ s += ":%s" % self.port
+ if self.path:
+ s += "/%s" % self.path
+ return s
- info = string.split(hostname, ":", maxsplit=1)
- if len(info) == 1:
- # All digits, assume it was just a port number
- if info[0].isdigit():
- port = info[0]
- else:
- host = info[0]
- else:
- host = info[0]
- port = info[1]
- return host, port
+def parse_URL(hoststring):
-def host_port_list(netlocs, default_port=None):
- '''
- Parses a list of network locations and returns
- a dictionary keyed by host containing sets of ports for each host.
+ RE = re.compile(r"""
+ # [ <scheme>:// ] [ <user> [ / <password> ] @] <host> [ :<port> ] [ <path> ]
+ ^ (?: ([^:/@]+)://)? (?: ([^:/@]+) (?: / ([^:/@]+) )? @)? ([^@:/]+) (?: :([0-9]+))? (?: / (.*))?$
+ """, re.X)
- Uses sage.util.host_port() to parse each item in the list.
+ scheme = user = password = host = port = path = None
+ match = RE.match(hoststring)
+ if match is not None:
+ scheme, user, password, host, port, path = match.groups()
+ return sage_URL(scheme, user, password, host, port, path)
- netlocs -- comma-separated list of network locations. A network location
- may have one of the following forms: 'host', 'host:port', or 'port'.
- If the 'port' form is used, the 'host' value is assumed to be the last
- host encountered in the list or "localhost" if no host has been
- encountered. If the 'host' form is used, an entry for the host is made
- in the dictionary with an port list.
+def host_list(netlocs, default_scheme=None, default_port=None, default_path=None):
+ tokens = string.split(netlocs, ",")
- default_port -- port value for hosts in the dictionary which contain an
- empty port set after 'netlocs' is fully parsed. Ignored if equal to None.
- '''
- assert type(netlocs) in (str, unicode)
- import string
+ hosts = dict()
+ last_url = None
+ last_port_set = False
- # A dictionary of sets of ports keyed
- # by hostname
- hosts = dict()
- tokens = string.split(netlocs, ",")
- lasthost = "localhost"
- for name in tokens:
- host, port = host_port(string.strip(name))
- if host is None:
- host = lasthost
+ for loc in tokens:
+ url = None
+ loc = string.strip(loc)
+ if loc.isdigit():
+ # Allow just a port number to be specified if the previous
+ # url explicitly set a port. Shorthand for port list.
+ # Copy all information from the previous token except port.
+ if last_url is not None and last_port_set:
+ url = copy.copy(last_url)
+ url.port = loc
else:
- lasthost = host
- if host not in hosts:
- if port is None:
- hosts[host] = set()
+ url = parse_URL(loc)
+ if url.scheme is None:
+ url.scheme = default_scheme
+ if url.path is None:
+ url.path = default_path
+ if url.port is None:
+ url.port = str(default_port)
+ last_port_set = False
else:
- hosts[host] = set([port])
- elif port is not None:
- hosts[host].add(port)
+ last_port_set = True
+ last_url = url
+ if url is not None and url.host is not None and url.port is not None:
+ if url.host not in hosts:
+ hosts[url.host] = list()
+ hosts[url.host].append(url)
+
+ return hosts
- # Fill in default ports for hosts with no
- # ports assigned
- if default_port is not None:
- for host, ports in hosts.iteritems():
- if len(ports) == 0:
- ports.add(default_port)
- return hosts
12 years, 9 months
r4908 - trunk/cumin/resources
by croberts@fedoraproject.org
Author: croberts
Date: 2011-08-05 18:59:48 +0000 (Fri, 05 Aug 2011)
New Revision: 4908
Added:
trunk/cumin/resources/check-mark.png
Log:
check-mark graphic used in new style select box.
Added: trunk/cumin/resources/check-mark.png
===================================================================
(Binary files differ)
Property changes on: trunk/cumin/resources/check-mark.png
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
12 years, 9 months
r4907 - in trunk: cumin/model cumin/python/cumin/grid cumin/resources wooly/python/wooly wooly/resources
by croberts@fedoraproject.org
Author: croberts
Date: 2011-08-05 18:58:51 +0000 (Fri, 05 Aug 2011)
New Revision: 4907
Added:
trunk/cumin/python/cumin/grid/tags.py
trunk/cumin/python/cumin/grid/tags.strings
Modified:
trunk/cumin/model/cumin.xml
trunk/cumin/python/cumin/grid/pool.py
trunk/cumin/resources/app.css
trunk/wooly/python/wooly/forms.strings
trunk/wooly/resources/mootools.js
Log:
First cut of changes to bring wallaby tagging into the cumin UI. Highlights include: The tags.py package that contains all of the classes required, additions to mootools.js to provide a hybrid select box, changes to app.css to provide the required styling for the new elements.
Modified: trunk/cumin/model/cumin.xml
===================================================================
--- trunk/cumin/model/cumin.xml 2011-08-04 16:40:02 UTC (rev 4906)
+++ trunk/cumin/model/cumin.xml 2011-08-05 18:58:51 UTC (rev 4907)
@@ -53,5 +53,13 @@
<property name="Usage" type="sstr"/>
<property name="Allowance" type="sstr"/>
</class>
+
+ <class name="Node" storage="none">
+ <property name="Name" type="sstr"/>
+ <property name="Tags" type="sstr"/>
+ <property name="Checkin" type="sstr"/>
+ </class>
+
+
</package>
</model>
Modified: trunk/cumin/python/cumin/grid/pool.py
===================================================================
--- trunk/cumin/python/cumin/grid/pool.py 2011-08-04 16:40:02 UTC (rev 4906)
+++ trunk/cumin/python/cumin/grid/pool.py 2011-08-05 18:58:51 UTC (rev 4907)
@@ -7,6 +7,7 @@
from cumin.objectframe import ObjectFrame, ObjectView
from cumin.stat import StatFlashChart, StatSet
from cumin.grid.dashboard import PoolDashboard
+from cumin.grid.tags import TagsEditor, TagsNodeEditTask, TagsFrame
from submission import SubmissionFrame, PoolSubmissionJoinSelector
from slot import SlotFrame
@@ -74,6 +75,9 @@
self.limit = LimitFrame(app, "limit")
self.add_mode(self.limit)
+
+ self.tag = TagsFrame(app, "tag")
+ self.add_mode(self.tag)
dashboard = PoolDashboard(app, "dashboard", self.object)
self.view.add_tab(dashboard)
@@ -97,6 +101,10 @@
self.edit_dynamic_quota = NegotiatorEditDynamicQuota(app, self)
self.quotas = QuotaSelector(app, "quotas", self.negotiator_attribute, self)
self.view.add_tab(self.quotas)
+
+ self.edit_node_tags = TagsNodeEditTask(app, self)
+ config_editor = TagsEditor(app, "wallaby")
+ self.view.add_tab(config_editor)
self.limits = LimitSelector(app, "limits")
self.view.add_tab(self.limits)
Added: trunk/cumin/python/cumin/grid/tags.py
===================================================================
--- trunk/cumin/python/cumin/grid/tags.py (rev 0)
+++ trunk/cumin/python/cumin/grid/tags.py 2011-08-05 18:58:51 UTC (rev 4907)
@@ -0,0 +1,869 @@
+import logging
+
+from wooly import Widget, Attribute
+from wooly.util import StringCatalog, Writer
+from wooly.datatable import DataAdapterOptions, DataAdapterField, DataTableColumn, DataTable, DataAdapter
+from wooly.widgets import RadioModeSet, WidgetSet
+from wooly.template import WidgetTemplate
+from wooly.forms import *
+from wooly.table import Table, TableColumn
+from wooly.tables import *
+
+from cumin.formats import fmt_link, fmt_datetime
+from cumin.objectselector import *
+from cumin.objectframe import ObjectFrameTaskForm, ObjectFrameTaskFeedbackForm, ObjectFrameTask
+from cumin.parameters import RosemaryObjectParameter
+from cumin.task import TaskLink, Task, ObjectTaskForm
+from cumin.widgets import *
+
+from sage.wallaby.wallabyoperations import WallabyOperations, WBTypes
+from sage.util import *
+
+
+strings = StringCatalog(__file__)
+log = logging.getLogger("cumin.tags")
+
+class ResponseAdapter(ObjectQmfAdapter):
+ '''
+ This class is necessary to act as an adapter to take the response
+ that we get from wallaby and turn it into something that we can readily
+ display in a table format. Typically, a table will have an adapter assigned
+ to do the transformations necessary for display.
+ '''
+ def get_count(self, values):
+ ''' Returns the current count of rows for the table (after filtering which is done in get_data() '''
+ data = self.get_data(values, None)
+ return len(data)
+
+ def get_data(self, values, options):
+ '''
+ Calls do_get_data to fetch the remote data, then calls filter,
+ sort and limit to sift through the data.
+ '''
+ data = self.do_get_data(values)
+ rows = self.process_results(data)
+
+ rows = self.filter_rows(rows, values)
+ if options:
+ rows = self.sort_rows(rows, options)
+ rows = self.limit_rows(rows, options)
+
+ return rows
+
+ def process_results(self, results):
+ ''' take the dict response from the call and return a list of lists '''
+ records = list()
+
+ if results:
+ for i, result in enumerate(results):
+ row = self.process_record(i, results[i])
+ records.append(row)
+
+ return records
+
+ def filter_rows(self, rows, values):
+ '''
+ Looks for the field name and filter value in the 'values' parameter, does a "contains" filtering on
+ each rows entry for the given field.
+ '''
+ filtered = list()
+ filter_field = values.keys()[0]
+ filter_text = values[filter_field].replace("%","")
+
+ for row in rows:
+ if filter_text.lower() in row[self.fields_by_name[filter_field].index].lower():
+ filtered.append(row)
+ return filtered
+
+
+class TagsAdapter(ResponseAdapter):
+ '''
+ This class is meant to adapt the wallaby response for get_data(TAGS) into a format appropriate
+ for displaying in the tags table
+ '''
+ def do_get_data(self, values):
+ data = []
+ try:
+ wallaby_tags = self.app.wallaby.get_data(WBTypes.TAGS)
+
+ for i, tag in enumerate(wallaby_tags):
+ data.append({'Tag':str(tag.name), 'Nodes':self.app.wallaby.get_node_names(tag)})
+
+ except Exception, e:
+ log.exception(e)
+
+ return data
+
+ def process_results(self, results):
+ """ Take the get_data response and process it for viewing by tags """
+
+ records = list()
+ if results:
+ for result in results:
+ records.append([result['Tag'], result['Nodes']])
+
+ return records
+
+class NodesAdapter(ResponseAdapter):
+ '''
+ This class is meant to adapt the wallaby response for get_data(NODES) into a format appropriate
+ for displaying in the nodes table
+ '''
+ def process_record(self, key, record):
+ """ Take the get_data response and process it for viewing by nodes """
+ return [record["Name"],record["Tags"],record["Checkin"]]
+
+ def do_get_data(self, values):
+ '''
+ Make the wallaby call and return a list of dicts that will be
+ formatted for the table by the process_record method
+ '''
+ wallaby_nodes = self.app.wallaby.get_data(WBTypes.NODES)
+ data = []
+ for i, node in enumerate(wallaby_nodes):
+ data.append({'Name':node.name, 'Tags':self.app.wallaby.get_tag_names(node), 'Checkin':node.last_checkin})
+
+ return data
+
+class ObjectSelectorTableNoCheckboxes(ObjectSelector):
+ '''
+ All of the ObjectSelector goodness, but without the checkboxes that come from the default ObjectSelectorTable
+ '''
+ def __init__(self, app, name, cls):
+ super(ObjectSelectorTableNoCheckboxes, self).__init__(app, name, cls)
+
+ def create_table(self, app, name, cls):
+ ''' override the default to give us a plain ObjectTable rather than and ObjectSelectorTable '''
+ return ObjectTable(app, name, cls)
+
+class TagsFrame(ObjectFrame):
+ def __init__(self, app, name):
+ cls = app.model.com_redhat_cumin_grid.Node
+ super(TagsFrame, self).__init__(app, name, cls)
+ self.set_tags = TagsNodeEditTask(app, self)
+ self.set_nodes = TagsTagEditTask(app, self)
+
+class TagInventory(ObjectSelectorTableNoCheckboxes):
+ def __init__(self, app, name):
+ cls = app.model.com_redhat_cumin_grid.Node
+ super(TagInventory, self).__init__(app, name, cls)
+ self.table.adapter = TagsAdapter(app, cls)
+
+ tag_add_task = AddTags(app)
+ link = TaskLink(app, "tag_add", tag_add_task)
+ self.links.add_child(link)
+
+ remove_node_tags_task = RemoveNodeTags(app)
+ link = TaskLink(app, "tag_remove", remove_node_tags_task)
+ self.links.add_child(link)
+
+ col = self.TagColumn(app, "tagcol", cls.Tags)
+ self.add_column(col)
+ self.add_search_filter(col)
+
+ def render_title(self, session):
+ return "Tags"
+
+ class TagColumn(ObjectTableColumn):
+ def render_cell_content(self, session, data):
+ tags = super(TagInventory.TagColumn, self).render_cell_content(session, data)
+ node_list = data[1]
+ nodes = ""
+ if node_list:
+ for node in node_list:
+ nodes = nodes + "," + str(node)
+ nodes = nodes[1:]
+ #here we set some info that will be used in the display of the target form
+ self.frame.tag.set_nodes.form.node_name.set(session, nodes)
+ self.frame.tag.set_nodes.form.tags.set(session, tags)
+ href = self.frame.tag.set_nodes.get_href(session)
+ return fmt_link(href, tags)
+
+
+class NodeInventory(ObjectSelectorTableNoCheckboxes):
+ def __init__(self, app, name):
+ cls = app.model.com_redhat_cumin_grid.Node
+ super(NodeInventory, self).__init__(app, name, cls)
+ self.table.adapter = NodesAdapter(app, cls)
+
+# NOT SURE IF THIS FUNCTIONALITY IS NECESSARY, turned off for now
+# edit_node_tags_task = ChangeNodeTags(app)
+# link = TaskLink(app, "node_tag_edit", edit_node_tags_task)
+# self.links.add_child(link)
+
+ col = self.NodeColumn(app, "nodecol", cls.Name)
+ self.add_column(col)
+ self.add_search_filter(col)
+
+ col = self.TagsColumn(app, "tagcol", cls.Tags)
+ self.add_column(col)
+
+ col = self.CheckinColumn(app, "checkcol", cls.Checkin)
+ self.add_column(col)
+
+ self.update_enabled = False
+
+ def get_data_values(self, session):
+ values = super(NodeInventory, self).get_data_values(session)
+ values['session'] = session
+ return values
+
+ def render_title(self, session):
+ return "Nodes"
+
+ class CheckinColumn(ObjectTableColumn):
+ def render_cell_content(self, session, data):
+ checkin_timestamp = super(NodeInventory.CheckinColumn, self).render_cell_content(session, data)
+ if checkin_timestamp > 0:
+ checkin_value = datetime.fromtimestamp(float(checkin_timestamp/1000000)) # the timestamps we get are too large
+ retval = fmt_datetime(checkin_value, sec=True)
+ else:
+ retval = "Never"
+ return retval
+
+ def render_header_content(self, session):
+ return "Last checkin"
+
+ class TagsColumn(ObjectTableColumn):
+ def render_cell_content(self, session, data):
+ tags_list = super(NodeInventory.TagsColumn, self).render_cell_content(session, data)
+ tags = ""
+ if tags_list:
+ for tag in tags_list:
+ tags = tags + "," + str(tag)
+ tags = tags[1:]
+ return tags
+
+ class NodeColumn(ObjectTableColumn):
+ def render_cell_content(self, session, data):
+ node_name = super(NodeInventory.NodeColumn, self).render_cell_content(session, data)
+ tags_list = data[1]
+ tags = ""
+ if tags_list:
+ for tag in tags_list:
+ tags = tags + "," + str(tag)
+ tags = tags[1:]
+ self.frame.tag.set_tags.form.node_name.set(session, node_name)
+ self.frame.tag.set_tags.form.tags.set(session, tags)
+ href = self.frame.tag.set_tags.get_href(session)
+ return fmt_link(href, node_name)
+
+class CreateTags(ObjectTaskForm):
+ '''
+ This form is used to display a single input field that will handle a comma separated
+ list of tags to be created.
+ '''
+ def __init__(self, app, name, task, cls):
+ super(CreateTags, self).__init__(app, name, task, cls)
+
+ self.tag = self.TagNamesField(app, "tag")
+ self.add_field(self.tag)
+
+ def process_submit(self, session):
+ tag = self.tag.get(session)
+ self.tag.validate(session)
+ if not self.errors.get(session):
+ self.tag.set(session, tag)
+
+ self.task.invoke(session, None, tag)
+
+ url = self.return_url.get(session)
+ self.page.redirect.set(session, url)
+
+ def render_tags_id(self, session):
+ return self.tag.input.path
+
+ def render_title(self, session):
+ return "Create a tag"
+
+ class TagNamesField(StringField):
+ def render_title(self, session):
+ return("Tag name(s): ")
+
+class RemoveTags(ObjectTaskForm):
+ '''
+ This form is used to allow the user to pick a set of tags to be removed.
+ '''
+ def __init__(self, app, name, task, cls):
+ super(RemoveTags, self).__init__(app, name, task, cls)
+
+ self.task = task
+
+ self.tag = self.TagsList(app, "tag")
+ self.add_field(self.tag)
+
+ def process_submit(self, session):
+ tag_to_kill = self.tag.get(session)
+
+ self.tag.validate(session)
+ if not self.errors.get(session):
+ self.task.invoke(session, None, tag_to_kill)
+
+ url = self.return_url.get(session)
+ self.page.redirect.set(session, url)
+
+ def render_tags_id(self, session):
+ return self.tag.input.path
+
+ def render_title(self, session):
+ return "Remove tags"
+
+ class TagNamesField(StringField):
+ def render_title(self, session):
+ return("Tag name(s): ")
+
+ class TagsList(CheckboxItemSetField):
+ def __init__(self, app, name):
+ item_parameter = SymbolParameter(app, "tag")
+ super(RemoveTags.TagsList, self).__init__(app, name, item_parameter)
+ self._tmpl = WidgetTemplate(self, "filtered_select_html")
+
+ def do_render(self, session):
+ writer = Writer()
+ self._tmpl.render(writer, session)
+ return writer.to_string()
+
+ def render_tags(self, session):
+ items = self.do_get_items(session)
+ tags_string = ""
+
+ for i, tag in enumerate(items):
+ tags_string = tags_string + "<option id='" + str(i) + "' name='" + tag.title + "' value='" + tag.title + "'" + ">" + tag.title + "</option>"
+
+ return tags_string
+
+ def do_get_items(self, session):
+ tags = fetchTags(self, session)
+ items = list()
+
+ if tags:
+ for tag in tags:
+ item = CheckboxItem(tag)
+ item.title = tag
+ items.append(item)
+
+ return items
+
+ def render_title(self, session):
+ return "Select tags to delete"
+
+
+class TagToNodes(ObjectTaskForm):
+ '''
+ This form is designed to allow many nodes to be assigned to many tags at the same time.
+ '''
+ def __init__(self, app, name, task, cls):
+ super(TagToNodes, self).__init__(app, name, task, cls)
+
+ self.tagbox = StringParameter(app, "tagbox")
+ self.add_parameter(self.tagbox)
+
+ self.nodebox = StringParameter(app, "nodebox")
+ self.add_parameter(self.nodebox)
+
+ def process_submit(self, session):
+ nodes = self.nodebox.get(session)
+ tags = self.tagbox.get(session)
+
+ self.task.invoke(session, None, nodes, tags)
+
+ url = self.return_url.get(session)
+ self.page.redirect.set(session, url)
+
+ def render_tag_list(self, session):
+ items = fetchTags(self, session)
+ tags_string = ""
+ tags = list()
+
+ for i, tag in enumerate(items):
+ tags.append(str(tag))
+
+ return tags
+
+ def render_node_list(self, session):
+ items = fetchNodes(self, session)
+ nodes_string = ""
+ nodes = list()
+
+ for i, node in enumerate(items):
+ nodes.append(str(node))
+
+ return nodes
+
+ def render_title(self, session):
+ return "Apply tags to nodes"
+
+class TagSelectField(FormField):
+ '''
+ This specialized FormField can be used to give the user a text box
+ such that when the user enters text, they will be greeted with
+ a set of tags that match their text entry, or "No matches"
+
+ If we decide against this sort of interface, this class can be removed
+ '''
+ def __init__(self, app, name, form, param):
+ super(TagSelectField, self).__init__(app, name)
+
+ self.org_param = param
+ self.param = self.TagSearchInputSet(app, "tag_set", param)
+ self.add_child(self.param)
+
+ def get(self, session):
+ return self.org_param.get(session)
+
+ def render_title(self, session):
+ return "Tag"
+
+ def render_inputs(self, session):
+ return self.param.render(session)
+
+ def validate(self, session):
+ if self.required:
+ val = self.get(session)
+ if not val:
+ error = FormError("The %s is required" % self.render_title(session))
+ self.form.errors.add(session, error)
+
+ class TagSearchInputSet(IncrementalSearchInput):
+ def do_get_items(self, session):
+ items = fetchTags(self, session)
+ tags_string = ""
+ tags = list()
+
+ for i, tag in enumerate(items):
+ tags.append(str(tag))
+
+ return tags
+
+ def render_item_content(self, session, tag):
+ return tag or "<em>Default</em>"
+
+ def render_item_value(self, session, tag):
+ return tag
+
+
+class EditNodeTagsForm(ObjectFrameTaskFeedbackForm):
+ def __init__(self, app, name, task):
+ super(EditNodeTagsForm, self).__init__(app, name, task)
+
+ self.node_name = self.NodeName(app, "node_name")
+ self.add_field(self.node_name)
+
+ self.tags = self.Tags(app, "tags")
+ self.add_field(self.tags)
+
+ self.new_tags = self.TagsList(app, "ptag")
+ self.add_field(self.new_tags)
+
+ self.update_enabled = False
+
+
+ def process_submit(self, session):
+ orig_tags_value = self.tags.input.get(session)
+ new_tags_value = self.new_tags.get(session)
+
+ self.tags.validate(session)
+ if not self.errors.get(session):
+ self.tags.set(session, new_tags_value)
+
+ node_name = self.node_name.get(session)
+ negotiator = self.object.get(session)
+
+ self.task.invoke(session, negotiator, node_name, new_tags_value)
+ self.task.exit_with_redirect(session)
+
+ def render_form_class(self, session):
+ return " ".join((super(EditNodeTagsForm, self).render_form_class(session), "mform"))
+
+ class TagsList(CheckboxItemSetField):
+ '''
+ This class, note the rendering handled in tags.strings::double_filtered_select_html,
+ takes care of displaying the tags for the given node in a filterable select box with additions
+ being moved to a second box.
+ '''
+ def __init__(self, app, name):
+ item_parameter = SymbolParameter(app, "tag")
+ super(EditNodeTagsForm.TagsList, self).__init__(app, name, item_parameter)
+ self._tmpl = WidgetTemplate(self, "double_filtered_select_html")
+
+ self.update_enabled = False
+
+ def do_render(self, session):
+ writer = Writer()
+ self._tmpl.render(writer, session)
+ return writer.to_string()
+
+ def render_tags(self, session):
+ items = self.do_get_items(session)
+ tags_string = ""
+
+ for i, tag in enumerate(items):
+ tags_string = tags_string + "<option id='" + str(i) + "'>" + tag.title + "</option>\n"
+
+ return tags_string
+
+ def render_starting_tags(self, session):
+ tags_from_session= self.form.tags.get(session)
+ tags_string = ""
+ items = []
+ if tags_from_session != "":
+ items = [x.strip() for x in tags_from_session.split(',')]
+
+ for i, tag in enumerate(items):
+ tags_string = tags_string + "<option id='init" + str(i) + "'>" + tag + "</option>\n"
+
+ return tags_string
+
+ def do_get_items(self, session):
+ tags = fetchTags(self, session)
+
+ items = list()
+
+ if tags:
+ for tag in tags:
+ item = CheckboxItem(tag)
+ item.title = tag
+ items.append(item)
+
+ return items
+
+ def render_title(self, session):
+ return "Update tags:"
+
+ class NodeName(StringField):
+ def __init__(self, app, name):
+ super(EditNodeTagsForm.NodeName, self).__init__(app, name)
+
+ self.input = self.DisabledInput(app, "input")
+ self.replace_child(self.input)
+
+ def get(self,session):
+ value = self.input.get(session)
+ return value
+
+ def render_title(self, session):
+ return "Node name"
+
+ class DisabledInput(StringInput):
+ pass
+
+ class Tags(StringField):
+ def __init__(self, app, name):
+ super(EditNodeTagsForm.Tags, self).__init__(app, name)
+
+ self.input = self.DisabledInput(app, "input")
+ self.replace_child(self.input)
+
+ def render_title(self, session):
+ return "Current tags"
+
+ def get(self,session):
+ value = self.input.get(session)
+ return value
+
+ class DisabledInput(StringInput):
+ pass
+
+class EditTagNodesForm(ObjectFrameTaskFeedbackForm):
+ def __init__(self, app, name, task):
+ super(EditTagNodesForm, self).__init__(app, name, task)
+
+ self.tags = self.Tags(app, "tags")
+ self.add_field(self.tags)
+
+ self.node_name = self.NodeName(app, "node_name")
+ self.add_field(self.node_name)
+
+ self.possible_nodes = self.NodesList(app, "pnod")
+ self.add_field(self.possible_nodes)
+
+ self.update_enabled = False
+
+
+ def process_submit(self, session):
+ tag = self.tags.input.get(session)
+ nodes_value = self.possible_nodes.get(session)
+
+ self.tags.validate(session)
+ if not self.errors.get(session):
+ self.tags.set(session, tag)
+
+ self.task.invoke(session, None, tag, nodes_value)
+ self.task.exit_with_redirect(session)
+
+ def render_form_class(self, session):
+ return " ".join((super(EditTagNodesForm, self).render_form_class(session), "mform"))
+
+ class NodesList(CheckboxItemSetField):
+ def __init__(self, app, name):
+ item_parameter = SymbolParameter(app, "tag")
+ super(EditTagNodesForm.NodesList, self).__init__(app, name, item_parameter)
+ self._tmpl = WidgetTemplate(self, "filtered_select_html")
+
+ def do_render(self, session):
+ writer = Writer()
+ self._tmpl.render(writer, session)
+ return writer.to_string()
+
+ def render_nodes(self, session):
+ items = self.do_get_items(session)
+ nodes_string = ""
+
+ given_tag = self.form.tags.input.param.get(session)
+ selected_nodes = self.app.wallaby.get_node_names(given_tag)
+
+ for i, node in enumerate(items):
+ selected = ""
+ if(selected_nodes and node.title in selected_nodes):
+ selected = " selected='selected' "
+ nodes_string = nodes_string + "<option id='" + str(i) + "' name='" + node.title + "' value='" + node.title + "'" + selected + ">" + node.title + "</option>"
+
+ return nodes_string
+
+ def do_get_items(self, session):
+ nodes = fetchNodes(self, session)
+ items = list()
+
+ if nodes:
+ for node in nodes:
+ item = CheckboxItem(node)
+ item.title = node
+ items.append(item)
+
+ return items
+
+ def render_title(self, session):
+ return "Tags"
+
+ class NodeName(StringField):
+ def __init__(self, app, name):
+ super(EditTagNodesForm.NodeName, self).__init__(app, name)
+
+ self.input = self.DisabledInput(app, "input")
+ self.replace_child(self.input)
+
+ def get(self,session):
+ value = self.input.get(session)
+ if not value or value == "":
+ value = "No nodes currently selected"
+ return value
+
+ def render_title(self, session):
+ return "Current nodes:"
+
+ class DisabledInput(StringInput):
+ pass
+
+ class Tags(StringField):
+ def __init__(self, app, name):
+ super(EditTagNodesForm.Tags, self).__init__(app, name)
+
+ self.input = self.DisabledInput(app, "input")
+ self.replace_child(self.input)
+
+ def render_title(self, session):
+ return "Tag:"
+
+ def get(self,session):
+ value = self.input.get(session)
+ return value
+
+ class DisabledInput(StringInput):
+ pass
+
+class TagsNodeEditTask(ObjectFrameTask):
+ '''
+ On invocation, this task will take a node and reset the tags for that node to a
+ set of tags that are specified on the invocation
+ '''
+ def __init__(self, app, frame):
+ super(TagsNodeEditTask, self).__init__(app, frame)
+
+ self.form = EditNodeTagsForm(app, self.name, self)
+ self.visible = False
+
+ def get_title(self, session):
+ return "Change tags associated with this node"
+
+ def do_enter(self, session, osession):
+ self.form.tags.set(session, self.form.tags.get(osession))
+ self.form.node_name.set(session, self.form.node_name.get(osession))
+
+ def do_invoke(self, invoc, negotiator, node_name, tags):
+ try:
+ call_async(invoc.make_callback(), self.app.wallaby.edit_tags, node_name, *tags)
+ except Exception, e:
+ invoc.status = invoc.FAILED
+ log.exception(e)
+
+ invoc.end()
+
+class TagsTagEditTask(ObjectFrameTask):
+ '''
+ This is the task that will take a tag and a set of nodes and make the
+ necessary assignments or unassignments.
+ '''
+ def __init__(self, app, frame):
+ super(TagsTagEditTask, self).__init__(app, frame)
+
+ self.form = EditTagNodesForm(app, self.name, self)
+
+ def get_title(self, session):
+ return "Change nodes for this tag "
+
+ def do_enter(self, session, osession):
+ self.form.tags.set(session, self.form.tags.get(osession))
+ self.form.node_name.set(session, self.form.node_name.get(osession))
+
+ def do_invoke(self, invoc, negotiator, tag, chosen_nodes):
+ '''
+ if the node is on the current_nodes list and is on the chosen_nodes passed in, nothing to do
+ ** if the node is NOT on the current_nodes list and is on the chosen_nodes passed in, add this tag to that node
+ ** if the node is on the current_nodes list and is NOT on the chosen_nodes passed in, update that node sans this tag
+ if the node is NOT on the current_nodes list and is NOT on the chosen_nodes passed in, nothing to do
+ '''
+ current_nodes = self.app.wallaby.get_node_names(tag)
+ try:
+ for node in chosen_nodes:
+ if node not in current_nodes:
+ #we need to add the new tag to the existing tags for each node in the list
+ current_tags = self.app.wallaby.get_tag_names(node)
+ current_tags.append(tag)
+ call_async(invoc.make_callback(), self.app.wallaby.edit_tags, node, *current_tags)
+ for node in current_nodes:
+ if node not in chosen_nodes:
+ current_tags = self.app.wallaby.get_tag_names(node)
+ current_tags.remove(tag)
+ call_async(invoc.make_callback(), self.app.wallaby.edit_tags, node, *current_tags)
+ except Exception, e:
+ invoc.status = invoc.FAILED
+ log.exception(e)
+
+ invoc.end()
+
+class AddTags(Task):
+ '''
+ This task is used to create a tag in wallaby without assigning it to any nodes.
+ '''
+ def __init__(self, app):
+ super(AddTags, self).__init__(app)
+ cls = app.model.com_redhat_cumin_grid.Node
+ self.form = CreateTags(app, self.name, self, cls)
+
+ def get_title(self, session, object):
+ return "Create tag(s)"
+
+ def do_invoke(self, session, object, invoc, tag):
+ tags = [x.strip() for x in tag.split(',')]
+
+ try:
+ for new_tag in tags:
+ call_async(invoc.make_callback(), self.app.wallaby.create_tag, new_tag)
+ except Exception, e:
+ invoc.status = invoc.FAILED
+ log.exception(e)
+
+ invoc.end()
+
+class ChangeNodeTags(Task):
+ '''
+ This is the task that is invoked to change the tags for a given set of
+ nodes and tags. It will go through each node in the list and set the
+ tags list of that node to the supplied tags, supporting whatever "many to many"
+ UI elements are in front of this.
+ '''
+ def __init__(self, app):
+ super(ChangeNodeTags, self).__init__(app)
+ cls = app.model.com_redhat_cumin_grid.Node
+ self.form = TagToNodes(app, self.name, self, cls)
+
+ def get_title(self, session, object):
+ return "Edit node tags"
+
+ def do_invoke(self, session, object, invoc, nodes, tags):
+ tag_list = [x.strip() for x in tags.split(',')]
+ node_list = [x.strip() for x in nodes.split(',')]
+
+ try:
+ for node in node_list:
+ updated_tags = self.app.wallaby.get_tag_names(node)
+ for tag in tag_list:
+ if tag not in updated_tags:
+ updated_tags.append(tag)
+ call_async(invoc.make_callback(), self.app.wallaby.edit_tags, node, *updated_tags)
+
+ #self.app.wallaby.edit_tags(node, *updated_tags)
+
+ except Exception, e:
+ invoc.status = invoc.FAILED
+ log.exception(e)
+
+ invoc.end()
+
+class RemoveNodeTags(Task):
+ '''
+ This is the task that is invoked to trigger the deletion of a given tag.
+ It will call WallabyOperations.remove_tag, which in turn will remove the tag from
+ all nodes it is currently associated with and then will remove the tag from wallaby entirely.
+ '''
+ def __init__(self, app):
+ super(RemoveNodeTags, self).__init__(app)
+ cls = app.model.com_redhat_cumin_grid.Node
+ self.form = RemoveTags(app, self.name, self, cls)
+
+ def get_title(self, session, object):
+ return "Delete tag(s)"
+
+ def do_invoke(self, session, object, invoc, tags):
+ try:
+ for tag_to_kill in tags:
+ call_async(invoc.make_callback(), self.app.wallaby.remove_tag, tag_to_kill)
+ except Exception, e:
+ invoc.status = invoc.FAILED
+ log.exception(e)
+
+ invoc.end()
+
+
+class TagsEditor(RadioModeSet):
+ '''
+ This defines the set of radio buttons that appear under the Tags tab in the UI
+ '''
+ def __init__(self, app, name):
+ super(TagsEditor, self).__init__(app, name)
+
+ inventory_tab = NodeInventory(app, "nodi")
+ self.add_tab(inventory_tab)
+
+ tag_inventory_tab = TagInventory(app, "tagi")
+ self.add_tab(tag_inventory_tab)
+
+ def render_title(self, session):
+ return "Tags"
+
+
+def fetchTags(self, session):
+ '''
+ fetch the list of tags from wallaby
+ '''
+ wallaby_tags = self.app.wallaby.get_data(WBTypes.TAGS)
+ tag_list = list()
+
+ for i, tag in enumerate(wallaby_tags):
+ tag_list.append(xml_escape(str(tag.name)))
+ tag_list.sort()
+
+ return tag_list
+
+def fetchNodes(self, session):
+ '''
+ fetch the list of nodes from wallaby
+ '''
+ wallaby_nodes = self.app.wallaby.get_data(WBTypes.NODES)
+ node_list = list()
+
+ for i, node in enumerate(wallaby_nodes):
+ node_list.append(xml_escape(node.name))
+ node_list.sort()
+
+ return node_list
\ No newline at end of file
Added: trunk/cumin/python/cumin/grid/tags.strings
===================================================================
--- trunk/cumin/python/cumin/grid/tags.strings (rev 0)
+++ trunk/cumin/python/cumin/grid/tags.strings 2011-08-05 18:58:51 UTC (rev 4907)
@@ -0,0 +1,229 @@
+[DisabledInput.css]
+input.disabled {
+ border: 0px;
+ background-color: white;
+ color: black;
+ cursor:default;
+}
+
+[DisabledInput.html]
+{value}
+<input type="hidden" name="{name}" id="{name}" value="{value}" tabindex="{tab_index}" size="{size}"/>
+
+[TagToNodes.html]
+<form id="{id}" class="ButtonForm" method="post" action="?">
+ <div class="title">{title}</div>
+ <div class="content">
+ <div class='lfloat'>
+ <div>
+ Nodes
+ </div>
+ <div>
+ <textarea id='{id}.nodebox' name='{id}.nodebox' cols='30' rows='5' size='30' onfocus="javascript:if(this.value=='') {if(node_completer)node_completer.observer.onFired();}" />
+ </div>
+ </div>
+ <div class='rfloat'>
+ <div>
+ Tags
+ </div>
+ <div>
+ <textarea id='{id}.tagbox' name='{id}.tagbox' cols='30' rows='5' size='30' onfocus="javascript:if(this.value=='') {if(tag_completer)tag_completer.observer.onFired();}" />
+ </div>
+ </div>
+ <div style="clear:both;"> </div>
+ </div>
+ <div class="buttons">
+ {submit}
+ {cancel}
+ </div>
+ <div>{hidden_inputs}</div>
+</form>
+<script type="text/javascript">
+//<![CDATA[
+
+ updateResults = function() {
+ $('{id}.results').innerHTML = "";
+ for(var i=0; i < $('{id}.tagbox').options.length; i++) {
+ if ($('{id}.tagbox').options[i].selected == true) {
+ $('{id}.results').innerHTML = $('{id}.results').innerHTML + $('{id}.tagbox').options[i].text + ": ";
+ for(var j=0; j < $('{id}.nodebox').options.length; j++) {
+ if ($('{id}.nodebox').options[j].selected == true) {
+ $('{id}.results').innerHTML = $('{id}.results').innerHTML + $('{id}.nodebox').options[j].text + " ";
+ }
+ }
+ $('{id}.results').innerHTML = $('{id}.results').innerHTML + "<br/>";
+ }
+ }
+ }
+
+var node_completer = null;
+var tag_completer = null;
+document.addEvent('domready', function() {
+ var tokens = {node_list};
+ var tags = {tag_list};
+
+ node_completer = new Autocompleter.Local('{id}.nodebox', tokens, {
+ 'minLength': 1, // We need at least 1 character
+ 'selectMode': 'type-ahead', // Instant completion
+ 'multiple': true, // Tag support, by default comma separated
+ 'overflow': true,
+ 'filterSubset': true,
+ 'zindex': 50000
+ });
+
+ tag_completer = new Autocompleter.Local('{id}.tagbox', tags, {
+ 'minLength': 1, // We need at least 1 character
+ 'selectMode': 'type-ahead', // Instant completion
+ 'multiple': true, // Tag support, by default comma separated
+ 'overflow': true,
+ 'filterSubset': true,
+ 'zindex': 50000
+ });
+});
+
+
+//]]>
+</script>
+
+
+[NodesList.filtered_select_html]
+ <script type="text/javascript">
+//<![CDATA[
+window.addEvent('domready', function (){
+ $$('.multiselect').each(function(multiselect){
+ new MTMultiWidget({'datasrc': multiselect,
+ 'widgetcls':'mtmultiselect',
+ 'selectedcls':'selected',
+ 'paginator_on_bottom':'true',
+ 'items_per_page':15,
+ 'case_sensitive':false,
+ 'setview':'total'});
+ });
+});
+
+//]]>
+ </script>
+ <tr>
+ <th><div class="title">Nodes:</div></th>
+ <td>
+ <div style='width:250px;height:375px;'>
+ <select class='multiselect' name="{id}.inputs.param" multiple="multiple" id="{id}.inputs.param" size="10" style="width:400px">
+ {nodes}
+ </select>
+ </div>
+ </td>
+ </tr>
+
+
+[TagsList.filtered_select_html]
+ <script type="text/javascript">
+//<![CDATA[
+window.addEvent('domready', function (){
+ $$('.multiselect').each(function(multiselect){
+ new MTMultiWidget({'datasrc': multiselect,
+ 'widgetcls':'mtmultiselect',
+ 'selectedcls':'selected',
+ 'paginator_on_bottom':'true',
+ 'items_per_page':15,
+ 'case_sensitive':false,
+ 'setview':'total'});
+ });
+});
+
+//]]>
+ </script>
+ <tr>
+ <th><div class="title">{title}</div></th>
+ <td>
+ <div style='width:250px;height:375px;'>
+ <select class='multiselect' name="{id}.inputs.param" multiple="multiple" id="{id}.inputs.param" size="10" style="width:400px">
+ {tags}
+ </select>
+ </div>
+ </td>
+ </tr>
+
+
+
+
+
+
+[TagsList.double_filtered_select_html]
+ <script type="text/javascript">
+//<![CDATA[
+ MyUtil = new Object();
+ MyUtil.selectFilterData = new Object();
+ MyUtil.selectFilter = function(selectId, filter) {
+ var list = document.getElementById(selectId);
+ if(!MyUtil.selectFilterData[selectId]) { //if we don't have a list of all the options, cache them now'
+ MyUtil.selectFilterData[selectId] = new Object();
+ for(var i = 0; i < list.options.length; i++) MyUtil.selectFilterData[selectId][list.options[i].id] = list.options[i];
+ }
+
+ //go through all the options in the current list and set the selection status to the cached options
+ for(var i = 0; i < list.options.length; i++) MyUtil.selectFilterData[selectId][list.options[i].id] = list.options[i];
+
+ list.options.length = 0; //remove all elements from the list
+ for(var id in MyUtil.selectFilterData[selectId]) { //add elements from cache if they match filter
+ var o = MyUtil.selectFilterData[selectId][id];
+ if(o.text.toLowerCase().indexOf(filter.toLowerCase()) >= 0) list.add(o, null);
+ }
+ }
+
+ window.addEvent('domready', function() {
+ $('add').addEvent('click', function() {
+ $('{id}2.inputs.param').getSelected().each(function(el) {
+ el.inject($('{id}.inputs.param'));
+ });
+ });
+ $('remove').addEvent('click', function() {
+ $('{id}.inputs.param').getSelected().each(function(el) {
+ el.inject($('{id}2.inputs.param'));
+ });
+ });
+ });
+
+ // when the form is submitted, we want to grab everything in the "selected tags" box whether it is highlighted or not
+ $$('form').each(function(form) {
+ form.addEvent('submit',function(event){
+ sbox = $('{id}.inputs.param');
+ for(var i = 0; i < sbox.length; i++) {
+ sbox.options[i].selected = true;
+ }
+ });
+ });
+//]]>
+ </script>
+
+ <tr>
+ <th><div class="title">Filter available tags</div></th>
+ <td>
+ <input id='{id}.filter' type="text" onkeyup="MyUtil.selectFilter('{id}2.inputs.param', $('{id}.filter').value);"/>
+ </td>
+ </tr>
+ <tr>
+ <th><div class="title">Tags</div></th>
+ <td>
+ <div style="width:500px;">
+ <div class='lfloat'>
+ <div><label for='{id}2.inputs.param' class='title'>Available Tags</label></div>
+ <div>
+ <select name="{id}2.inputs.param" multiple="multiple" id="{id}2.inputs.param" size="10" style="width:200px">
+ {tags}
+ </select>
+ </div>
+ <div><input type="button" id="add" onclick="javascript:return false;" value="Add to selected tags"/></div>
+ </div>
+ <div class='rfloat'>
+ <div><label for='{id}.inputs.param' class='title'>Selected Tags</label></div>
+ <div>
+ <select name="{id}.inputs.param" multiple="multiple" id="{id}.inputs.param" size="10" style="width:200px">
+ {starting_tags}
+ </select>
+ </div>
+ <div><input type="button" id="remove" onclick="javascript:return false;" value="Remove"/></div>
+ </div>
+ </div>
+ </td>
+ </tr>
+
Modified: trunk/cumin/resources/app.css
===================================================================
--- trunk/cumin/resources/app.css 2011-08-04 16:40:02 UTC (rev 4906)
+++ trunk/cumin/resources/app.css 2011-08-05 18:58:51 UTC (rev 4907)
@@ -403,6 +403,10 @@
float: right;
}
+.lfloat {
+ float: left;
+}
+
.rclear {
font-size:0.01em;
width: 0.01px;
@@ -659,3 +663,109 @@
div.OverviewView {
clear: both;
}
+
+ .mtmultiselect{
+ width: 400px;
+ background: #0D171A;
+ font: 12px verdana;
+ }
+ .mtmultiselect .selected{
+ background-color: #E8F7E8;
+ }
+ .mtmultiselect ol .selected{
+ background-color: #E8F7E8;
+ background-image: url('resource?name=check-mark.png');
+ background-position: left;
+ background-repeat: no-repeat;
+ }
+ .mtmultiselect ul li{
+ display:inline;
+ }
+ .disabled{
+ color: #ccc;
+ }
+ .mtms_filterbox{
+ float: left;
+ padding: 5px;
+ }
+ .mtms_filterbox input{
+ background: #FFF;
+ border: 1px solid #141212;
+ }
+ .mtms_filtercontrols{
+ padding: 7px 0;
+ color: #B4A6A6;l
+ }
+ .mtms_filtercontrols a, .mtms_paginator a,
+ .mtms_filterlabel{
+ color: #8C8CD9;
+ padding: 0 4px 0 6px;
+ }
+ .mtms_paginator{
+ clear: both;
+ padding: 5px;
+ }
+ .mtmultiselect ol{
+ padding: 0;
+ }
+ .mtmultiselect ol li{
+ list-style-type: none;
+ padding: 1px 5px 1px 25px;
+ background-color: #FFF;
+ color: #000;
+ }
+ .mtmultiselect ol li:hover{
+ background-color: #84D1E0;
+ color: #111;
+ }
+
+ul.autocompleter-choices
+{
+ position: absolute;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ border: 1px solid #7c7c7c;
+ border-left-color: #c3c3c3;
+ border-right-color: #c3c3c3;
+ border-bottom-color: #ddd;
+ background-color: #fff;
+ text-align: left;
+ font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
+ z-index: 50;
+ background-color: #fff;
+}
+
+ul.autocompleter-choices li
+{
+ position: relative;
+ margin: -2px 0 0 0;
+ padding: 0.2em 1.5em 0.2em 1em;
+ display: block;
+ float: none !important;
+ cursor: pointer;
+ font-weight: normal;
+ white-space: nowrap;
+ font-size: 1em;
+ line-height: 1.5em;
+}
+
+ul.autocompleter-choices li.autocompleter-selected
+{
+ background-color: #444;
+ color: #fff;
+}
+
+ul.autocompleter-choices span.autocompleter-queried
+{
+ display: inline;
+ float: none;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+
+ul.autocompleter-choices li.autocompleter-selected span.autocompleter-queried
+{
+ color: #9FCFFF;
+}
\ No newline at end of file
Modified: trunk/wooly/python/wooly/forms.strings
===================================================================
--- trunk/wooly/python/wooly/forms.strings 2011-08-04 16:40:02 UTC (rev 4906)
+++ trunk/wooly/python/wooly/forms.strings 2011-08-05 18:58:51 UTC (rev 4907)
@@ -313,6 +313,9 @@
</form>
<script type="text/javascript">
//<![CDATA[
- $("{id}").elements[0].focus();
+ var focusElement = $("{id}").elements[0];
+ if( focusElement != "hidden" && focusElement.style.display != "none" && !focusElement.disabled && focusElement.type != "hidden" ) {
+ focusElement.focus();
+ }
//]]>
</script>
Modified: trunk/wooly/resources/mootools.js
===================================================================
--- trunk/wooly/resources/mootools.js 2011-08-04 16:40:02 UTC (rev 4906)
+++ trunk/wooly/resources/mootools.js 2011-08-05 18:58:51 UTC (rev 4907)
@@ -1482,4 +1482,535 @@
String.implement({findAllEmails:function(){return this.match(new RegExp("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?","gi"))||[];}});String.alias('parseQueryString','parseQuery');
var Waiter=new Class({Extends:Spinner,options:{baseHref:'http://www.cnet.com/html/rb/assets/global/waiter/',containerProps:{styles:{position:'absolute','text-align':'center'},'class':'waiterContainer'},containerPosition:{},msg:false,msgProps:{styles:{'text-align':'center',fontWeight:'bold'},'class':'waiterMsg'},img:{src:'waiter.gif',styles:{width:24,height:24},'class':'waiterImg'},layer:{styles:{width:0,height:0,position:'absolute',zIndex:999,display:'none',opacity:0.9,background:'#fff'},'class':'waitingDiv'},useIframeShim:true,fxOptions:{},injectWhere:null},render:function(){this.parent();this.waiterContainer=this.element.set(this.options.containerProps);if(this.msgContainer)this.msgContainer=this.content.set(this.options.msgProps);if(this.options.img)this.waiterImg=document.id(this.options.img.id)||new Element('img',$merge(this.options.img,{src:this.options.baseHref+this.options.img.src})).inject(this.img);this.element.set(this.options.layer);},place:function(){this.inject.apply(this,arguments);},reset:function(){return this.hide();},start:function(element){return this.show();},stop:function(callback){return this.hide();}});if(window.Request){Request=Class.refactor(Request,{options:{useWaiter:false,waiterOptions:{},waiterTarget:false},initialize:function(options){if(options){if(options.useWaiter)options.useSpinner=options.useWaiter;if(options.waiterOptions)options.spinnerOptions=options.waiterOptions;if(options.waiterTarget)options.spinnerTarget=options.waiterTarget;}
this.previous(options);}});}
-Element.Properties.waiter={set:function(options){return this.set('spinner',options);},get:function(options){return this.get('spinner',options);}};Element.implement({wait:function(options){return this.spin(options);},release:function(){return this.unspin(options);}});
\ No newline at end of file
+Element.Properties.waiter={set:function(options){return this.set('spinner',options);},get:function(options){return this.get('spinner',options);}};Element.implement({wait:function(options){return this.spin(options);},release:function(){return this.unspin(options);}});
+
+/*
+Copyright (c) 2009 Justin Donato (http://www.justindonato.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+MTMultiSelect: An widget to make more user friendly interfaces
+for a multiselect box, using Mootools
+
+Usage:
+
+Link to this file, then add
+
+window.addEvent('domready', function (){
+ $$('.multiselect').each(function(multiselect){
+ new MTMultiWidget({'datasrc': multiselect});
+ });
+
+});
+
+to your page, where '.multiselect' is a selector that picks out a class of
+<select MULTIPLE> elements. This element acts as the data source for the
+widget. Then create a css file and style the widget however you'd like.
+
+Options: add these to the object you pass to the MTMultiWidget constructor.
+Only datasrc is required.
+
+widgetcls:
+The css class applied to your final widget.
+Default is 'mtmultiselect'
+
+datasrc:
+A select multiple dom object
+
+selectedcls:
+The css class of the selected list item.
+Default is 'selected'
+
+paginator_on_bottom:
+Determines placement of paginator, true or false.
+Default is true
+
+items_per_page:
+Determines how many items to show on a single page.
+Default is 10.
+
+case_sensitive:
+Determines if the filter form is case_sensitive or not
+Default is False
+
+setview:
+Determines what view to show on initialization
+Possible values are 'selected', 'unselected' or 'total'
+default is 'total'
+*/
+
+var MTMultiWidget = new Class({
+
+ Implements: [Options],
+
+ options: {
+ 'widgetcls': 'mtmultiselect', // the class for your final widget
+ 'datasrc': null // a select multiple dom object
+ },
+
+ handleDisplayEvent: function(numselected){
+ this.filterform.update(numselected);
+ },
+
+ handleFilterEvent: function(list){
+ this.curlist = list;
+ this.paginator.setpagenum(1);
+ var page = this.paginator.getpage(list);
+ this.displaylist.build(page);
+ },
+
+ handlePaginatorEvent: function(){
+ var page = this.paginator.getpage(this.curlist);
+ this.displaylist.build(page);
+ },
+
+ initialize: function(options){
+ // Hide the original data source and create a new div for the widget
+ // and inject this new widget into the Dom.
+ options.datasrc.setStyles({'display': 'None'});
+ var view = new Element('div', {'class': this.options.widgetcls});
+ view.injectAfter(options.datasrc);
+
+ // Add the newly created widget to the options obj so other
+ // components have a reference to it.
+ options.view = view;
+ this.setOptions(options);
+
+ this.displaylist = new DisplayList(options);
+ options.numselected = this.displaylist.numselected();
+
+ this.filterform = new FilterForm(options);
+ this.paginator = new Paginator(options);
+
+ this.curlist = this.initialdata(options)
+
+ this.displaylist.addEvent('rebuild', this.handleDisplayEvent.bind(this));
+ this.filterform.addEvent('rebuild', this.handleFilterEvent.bind(this));
+ this.paginator.addEvent('rebuild', this.handlePaginatorEvent.bind(this));
+
+ var page = this.paginator.getpage(this.curlist);
+ this.filterform.build();
+ this.displaylist.build(page);
+ this.setview(this.filterform, this.displaylist, options)
+ },
+
+ setview: function(filterform, displaylist, options){
+ if(options.setview == 'selected'){
+ this.filterform.showselected();
+ }
+ else if(options.setview == 'unselected'){
+ this.filterform.showunselected();
+ }
+
+ },
+
+ initialdata: function(options){
+ // moved this out to a method in case you want to implement an
+ // ajax datasrc.
+ return this.options.datasrc.getChildren();
+ },
+
+ refresh: function(){
+ this.setview(this.filterform, this.displaylist, this.options);
+ this.filterform.update(this.displaylist.numselected());
+ return this;
+ }
+});
+
+/*
+ DisplayList
+*/
+
+var DisplayList = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ selectedcls: 'selected', // class of the item when its selected
+ datasrc: null, // a multiple select dom element
+ view: null, // A parent or wrapper dom element where this element lives
+ paginator_on_bottom: true, // determines location of paginator
+ represent: null
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ },
+
+ build: function(opts){
+
+ // If there's already an ol, remove it.
+ var old = this.options.view.getElement('ol');
+ if(old !== null) old.destroy();
+
+ // create the list to hold the visible elements
+ list = new Element('ol');
+ place = this.options.paginator_on_bottom ? 'before' : 'after';
+ list.inject(this.options.view.getLast(), place);
+ //this.options.view.grab(list);
+
+ opts.each(function(item){
+ var li = new Element('li',{
+ 'class': item.selected ? this.options.selectedcls : null
+ });
+
+ var repr;
+ if(this.options.represent){
+ repr = this.options.represent(item, li);
+ }
+ else{
+ repr = item.get('text');
+ }
+
+ li.set('text', repr);
+
+ li.store('select', item);
+ list.grab(li);
+
+ li.addEvent('click', function(evt){
+ evt.target.toggleClass('selected');
+ evt.target.retrieve('select').selected = evt.target.hasClass('selected');
+ this.fireEvent('rebuild', this.numselected());
+ }.bind(this));
+ }.bind(this));
+ },
+
+ total: function(){
+ return this.options.datasrc.getChildren().length;
+ },
+
+ numselected: function(){
+ var numselected = 0;
+ this.options.datasrc.getChildren().each(function(item){
+ if(item.selected) numselected++;
+ });
+ return numselected;
+ }
+
+});
+
+/*
+ Paginator
+*/
+
+var Paginator = new Class({
+ Implements: [Options, Events],
+
+ options: {
+ 'items_per_page': 10,
+ 'list': [], // the list the paginator will use
+ 'displaylist': null // the view element for the results
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ this.items_per_page = this.options.items_per_page;
+ this.page = 1;
+ // create the view element
+ this.controls = new Element('div', {'class':'mtms_paginator'});
+ this.options.view.grab(this.controls);
+ },
+
+ numpages: function(list){
+ return Math.ceil(list.length / this.items_per_page );
+ },
+
+ getpage: function(list){
+ var start = ((this.page - 1) * this.items_per_page);
+ var end = start + this.items_per_page;
+ this.updateControls(list);
+ return list.slice(start, end);
+ },
+
+ pageup: function(){
+ this.page++;
+ },
+
+ pagedown: function(){
+ this.page--;
+ },
+
+ pagefirst: function() {
+ this.page=1;
+ },
+
+ pagelast: function(lastpagenum) {
+ this.page=lastpagenum;
+ },
+
+ updateControls: function(list){
+
+ var numpages = this.numpages(list);
+ this.controls.empty();
+
+ var firstbtn = new Element('a', {'text':'first',
+ 'href':'javascript:void(0)'});
+ this.controls.grab(firstbtn, 'top');
+ firstbtn.addClass('disabled');
+
+ if(this.page > 1){
+ firstbtn.removeClass('disabled');
+ firstbtn.addEvent('click', function(evt){
+ this.pagefirst();
+ this.fireEvent('rebuild', list);
+ }.bind(this));
+ }
+
+ var prevbtn = new Element('a', {'text':'prev',
+ 'href':'javascript:void(0)'});
+ this.controls.grab(prevbtn, 'bottom');
+ prevbtn.addClass('disabled');
+
+ if(this.page > 1){
+ prevbtn.removeClass('disabled');
+ prevbtn.addEvent('click', function(evt){
+ this.pagedown();
+ this.fireEvent('rebuild', list);
+ }.bind(this));
+ }
+
+ for(var i = this.page; i <= this.page + 6 && i <= numpages; i++){
+ this.controls.grab(new Element('a', {'text': i,
+ 'href': '#cpcspswdnbd',
+ 'class': (i == this.page) ? 'selected' : '',
+ 'events': {
+ 'click': function(evt){
+ this.setpagenum(evt.target.innerHTML);
+ this.fireEvent('rebuild', [list]); // wrapped list
+ }.bind(this)}
+ }), 'bottom');
+ }
+
+ var nextbtn = new Element('a', {'text':'next',
+ 'href':'#cpcspswdnbd'});
+ this.controls.grab(nextbtn);
+ nextbtn.addClass('disabled');
+
+ if(this.page < numpages){
+ nextbtn.removeClass('disabled');
+ nextbtn.addEvent('click', function(evt){
+ this.pageup();
+ this.fireEvent('rebuild');
+ }.bind(this));
+ }
+
+ var lastbtn = new Element('a', {'text':'last',
+ 'href':'#cpcspswdnbd'});
+ this.controls.grab(lastbtn);
+ lastbtn.addClass('disabled');
+
+ if(this.page < numpages){
+ lastbtn.removeClass('disabled');
+ lastbtn.addEvent('click', function(evt){
+ this.pagelast(numpages);
+ this.fireEvent('rebuild');
+ }.bind(this));
+ }
+
+
+ },
+
+ setpagenum: function(pagenum){
+ this.page = Number(pagenum);
+ }
+});
+
+/*
+ FilterForm
+*/
+
+var FilterForm = new Class({
+
+ RESETINPUT: true,
+
+ Implements: [Options, Events],
+
+ options: {
+ view: null,
+ case_sensitive: false,
+ displaylist: null,
+ inputpos: 'top',
+ labels: {'total': 'total',
+ 'selected': 'selected',
+ 'unselected': 'unselected',
+ 'selectall': 'select all',
+ 'selectnone': 'select none',
+ 'filter': 'filter',
+ 'in': 'in',
+ 'out': 'out',
+ 'page': 'page'
+ },
+ classes: {
+ 'total': 'mttotal',
+ 'selected': 'mtselected'
+ }
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ this.numselected = options.numselected;
+ },
+
+ build: function(){
+ // infofilter bar is made out of a u list
+ var ul = new Element('ul', {'class': 'mtms_filtercontrols'});
+ this.options.view.grab(ul, this.options.inputpos);
+
+ this.totalbtn = this.makebtn(this.options.labels.total,
+ this.showtotal,
+ this.options.datasrc.getChildren().length);
+ ul.grab(this.totalbtn);
+
+ this.selectedbtn = this.makebtn(this.options.labels.selected,
+ this.showselected,
+ this.numselected);
+ ul.grab(this.selectedbtn);
+
+ this.unselectedbtn = this.makebtn(this.options.labels.unselected,
+ this.showunselected,
+ this.options.datasrc.getChildren().length - this.numselected);
+ ul.grab(this.unselectedbtn);
+
+ this.selectAllBtn = this.makebtn(this.options.labels.selectall,
+ this.selectAll,
+ '');
+ ul.grab(this.selectAllBtn);
+
+ this.selectNoneBtn = this.makebtn(this.options.labels.selectnone,
+ this.selectNone,
+ '');
+ ul.grab(this.selectNoneBtn);
+
+ // Make text field for filter
+ // On keyup, the displaylist.filter is called with a function
+ // that filters based on what's been entered in the textfield
+ filterbox_container = new Element('div', {'class': 'mtms_filterbox'});
+ this.filterbox = new Element('input', {
+ 'events': {
+ 'keyup': function(evt){
+ if(this.options.case_sensitive){
+ filter_by_text = function(item){ return item.text.contains(evt.target.value)};
+ } else {
+ filter_by_text = function(item){ return item.text.toLowerCase().contains(evt.target.value.toLowerCase()) };
+ }
+ this.filter(this.options.datasrc.getChildren(), filter_by_text);
+ }.bind(this)
+ }
+ });
+ this.filterbox_label = new Element('span', {'html':'Filter list: ', 'class':'mtms_filterlabel'});
+ filterbox_container.grab(this.filterbox_label);
+ filterbox_container.grab(this.filterbox);
+ this.options.view.grab(filterbox_container, this.options.inputpos);
+ },
+
+ /*
+ label: clickable link text
+ func: the function that is called when clicked
+ prefix: some bit of text that precedes the label. (Used to show
+ counts here.)
+
+ */
+ makebtn: function(label, func, prefix){
+ var li = new Element('li');
+ var btn = new Element('a', {'html': label,
+ 'href': '#cpcspswdnbd',
+ 'events': {
+ // You might have to bind this differently
+ 'click': func.bind(this)
+ }
+ });
+ li.grab(btn);
+ if(prefix !== undefined){
+ prefix = new Element('span', {'text': prefix});
+ li.grab(prefix);
+ }
+ return li;
+ },
+
+ showtotal: function(){
+ // return true for every item in the datasrc
+ this.filter(this.options.datasrc.getChildren(),
+ function(item){
+ return true;
+ },
+ this.RESETINPUT
+ );
+ },
+ showselected: function(){
+ this.filter(this.options.datasrc.getChildren(),
+ function(item){
+ return (item.selected === true);
+ },
+ this.RESETINPUT
+ );
+ },
+ showunselected:function(){
+ this.filter(this.options.datasrc.getChildren(),
+ function(item){
+ return (item.selected !== true);
+ },
+ this.RESETINPUT
+ );
+ },
+ selectAll:function(){
+ this.options.datasrc.getChildren().each(
+ function(item){
+ item.selected = true;
+ }
+ );
+ this.fireEvent('rebuild', [this.options.datasrc.getChildren()]);
+ this.update(this.options.datasrc.getChildren().length);
+ },
+ selectNone:function(){
+ this.options.datasrc.getChildren().each(
+ function(item){
+ item.selected = false;
+ }
+ );
+ this.fireEvent('rebuild', [this.options.datasrc.getChildren()]);
+ this.update(0);
+ },
+
+ // list is the list of option dom elements from the select elem
+ // test is a function that gets used in the filter
+ filter: function(list, test, reset){
+ results = list.filter(function(item, index){
+ return test(item);
+ });
+
+ if(reset){
+ this.filterbox.value = "";
+ }
+ this.fireEvent('rebuild', [results]);
+ },
+ update: function(numselected){
+ var total = this.options.datasrc.getChildren().length;
+ this.totalbtn.getElement('span').set('text', total);
+ this.selectedbtn.getElement('span').set('text', numselected);
+ this.unselectedbtn.getElement('span').set('text', total - numselected);
+ }
+});
+
+Element.Events.rebuild = {
+ 'base': 'change',
+ 'condition': function(evt){
+ return;
+ }
+};
+
12 years, 9 months
r4906 - trunk/sage/python/sage
by tmckay@fedoraproject.org
Author: tmckay
Date: 2011-08-04 16:40:02 +0000 (Thu, 04 Aug 2011)
New Revision: 4906
Modified:
trunk/sage/python/sage/util.py
Log:
Add utility routines to sage for hanlding listf of network locations.
Modified: trunk/sage/python/sage/util.py
===================================================================
--- trunk/sage/python/sage/util.py 2011-08-03 18:45:27 UTC (rev 4905)
+++ trunk/sage/python/sage/util.py 2011-08-04 16:40:02 UTC (rev 4906)
@@ -200,3 +200,79 @@
to create appropriate objects for addition to the pool.
'''
raise Exception("Not implemented")
+
+def host_port(hostname):
+ '''
+ Returns a tuple containing 'host' and 'port' strings from hostname.
+
+ Strings are split at the first colon to produce host and port strings.
+ A string containing only digits will result in a tuple with the host
+ value set to None and the port value set to the entire string. A string
+ containing non-digits but no colon will result in a tuple with the port
+ value set to None and the host value set to the entire string.
+ '''
+ assert type(hostname) in (str, unicode)
+ import string
+
+ host = None
+ port = None
+
+ info = string.split(hostname, ":", maxsplit=1)
+ if len(info) == 1:
+ # All digits, assume it was just a port number
+ if info[0].isdigit():
+ port = info[0]
+ else:
+ host = info[0]
+ else:
+ host = info[0]
+ port = info[1]
+ return host, port
+
+def host_port_list(netlocs, default_port=None):
+ '''
+ Parses a list of network locations and returns
+ a dictionary keyed by host containing sets of ports for each host.
+
+ Uses sage.util.host_port() to parse each item in the list.
+
+ netlocs -- comma-separated list of network locations. A network location
+ may have one of the following forms: 'host', 'host:port', or 'port'.
+ If the 'port' form is used, the 'host' value is assumed to be the last
+ host encountered in the list or "localhost" if no host has been
+ encountered. If the 'host' form is used, an entry for the host is made
+ in the dictionary with an port list.
+
+ default_port -- port value for hosts in the dictionary which contain an
+ empty port set after 'netlocs' is fully parsed. Ignored if equal to None.
+ '''
+ assert type(netlocs) in (str, unicode)
+ import string
+
+ # A dictionary of sets of ports keyed
+ # by hostname
+ hosts = dict()
+ tokens = string.split(netlocs, ",")
+ lasthost = "localhost"
+ for name in tokens:
+ host, port = host_port(string.strip(name))
+ if host is None:
+ host = lasthost
+ else:
+ lasthost = host
+ if host not in hosts:
+ if port is None:
+ hosts[host] = set()
+ else:
+ hosts[host] = set([port])
+ elif port is not None:
+ hosts[host].add(port)
+
+ # Fill in default ports for hosts with no
+ # ports assigned
+ if default_port is not None:
+ for host, ports in hosts.iteritems():
+ if len(ports) == 0:
+ ports.add(default_port)
+ return hosts
+
12 years, 9 months
r4905 - trunk/sage/python/sage
by tmckay@fedoraproject.org
Author: tmckay
Date: 2011-08-03 18:45:27 +0000 (Wed, 03 Aug 2011)
New Revision: 4905
Modified:
trunk/sage/python/sage/util.py
Log:
Typo in variable name
Modified: trunk/sage/python/sage/util.py
===================================================================
--- trunk/sage/python/sage/util.py 2011-08-03 18:17:08 UTC (rev 4904)
+++ trunk/sage/python/sage/util.py 2011-08-03 18:45:27 UTC (rev 4905)
@@ -61,7 +61,7 @@
data = status
status = 0
else:
- staus, data = args[0:2]
+ status, data = args[0:2]
self.status = status
if (status == 0) or (status == "OK"):
12 years, 9 months
r4904 - in trunk: cumin/python/cumin sage/python/sage sage/python/sage/wallaby
by tmckay@fedoraproject.org
Author: tmckay
Date: 2011-08-03 18:17:08 +0000 (Wed, 03 Aug 2011)
New Revision: 4904
Modified:
trunk/cumin/python/cumin/task.py
trunk/sage/python/sage/util.py
trunk/sage/python/sage/wallaby/wallabyoperations.py
Log:
Make callback functions in TaskInvocation and CallSync take variable
arguments to support both callback(status, result) and callback(result) formats.
Add call_async() general wrapper to sage.util.
Modified: trunk/cumin/python/cumin/task.py
===================================================================
--- trunk/cumin/python/cumin/task.py 2011-08-02 20:22:05 UTC (rev 4903)
+++ trunk/cumin/python/cumin/task.py 2011-08-03 18:17:08 UTC (rev 4904)
@@ -172,15 +172,39 @@
log.info("Ended %s", self.task)
def make_callback(self):
- def completion(status_code, output_args):
+ def completion(*args):
+ # Callback argument formats come in two basic flavors in Cumin
+ # depending on the semantics of the async op:
+ # callback(status, result)
+ # callback(result) where type(result) == Exception indicates
+ # a failure status.
+ # Allow this general mechanism to handle both types.
+
+ output_args = None
+ if len(args) == 0:
+ # We have to test this case now since we've made args
+ # variable length. Just call it OK
+ status_code = self.OK
+
+ elif len(args) == 1:
+ # Status is ok if status_code is not an exception and result
+ # goes in output_args
+ status_code = args[0]
+ if not isinstance(status_code, Exception):
+ output_args = status_code
+ status_code = self.OK
+
+ else:
+ status_code, output_args = args[0:2]
+
# Make results recorded directly through the callback
# match what is done when TaskInvocation values are
# set by an ObjectTask or Task on failure
# (see Task.invoke() and Task.exception() above)
# Previously the different mechanisms produced different results.
- if status_code not in (0, "OK"):
+ if isinstance(status_code, Exception):
self.status = self.FAILED
- self.exception = Exception(status_code)
+ self.exception = status_code
else:
self.status_code = status_code
self.output_args = output_args
Modified: trunk/sage/python/sage/util.py
===================================================================
--- trunk/sage/python/sage/util.py 2011-08-02 20:22:05 UTC (rev 4903)
+++ trunk/sage/python/sage/util.py 2011-08-03 18:17:08 UTC (rev 4904)
@@ -24,9 +24,9 @@
General callback object for asynchronous operations.
The 'get_completion' method will return a function that can be
used as a callback when an asynchronous operation completes.
- To change the signature of the callback function, derive from
+ To change the signature or function of the callback, derive from
this class and override 'get_completion', changing the definition
- of the 'completion' function.
+ of the 'completion'.
The 'done' method can be polled to determine if the operation has completed.
'''
def __init__(self, log=None, default=None):
@@ -40,8 +40,29 @@
# obj.get_completion is called and 'completion' is returned.
# This allows the code invoking the callback to call a simple
# function without an object reference (the reference is hidden
- # inside the bound function).
- def completion(status, data):
+ # inside the bound function). Formally known as a "closure"
+
+ def completion(*args):
+ # Callback argument formats come in two basic flavors in Cumin
+ # depending on the semantics of the async op:
+ # callback(status, result)
+ # callback(result) where type(result) == Exception indicates
+ # a failure status
+ # Allow this general mechanism to handle both types.
+
+ data = None
+ if len(args) == 0:
+ # Just call it successful with no data
+ status = 0
+
+ elif len(args) == 1:
+ status = args[0]
+ if not isinstance(status, Exception):
+ data = status
+ status = 0
+ else:
+ staus, data = args[0:2]
+
self.status = status
if (status == 0) or (status == "OK"):
self.data = data
@@ -127,6 +148,19 @@
if self.callback is not None:
self.callback(result)
+def call_async(callback, call, *args, **kwargs):
+ '''
+ Use CallThread to execute call(*args, **kwargs).
+
+ If callback is not None, the thread will call
+ callback(result) after call() completes.
+ '''
+ assert callable(call)
+ assert callback is None or callable(callback)
+
+ t = CallThread(call, callback, *args, **kwargs)
+ t.start()
+
class ObjectPool(object):
'''
Simple threadsafe pool class that holds up to max_size objects.
Modified: trunk/sage/python/sage/wallaby/wallabyoperations.py
===================================================================
--- trunk/sage/python/sage/wallaby/wallabyoperations.py 2011-08-02 20:22:05 UTC (rev 4903)
+++ trunk/sage/python/sage/wallaby/wallabyoperations.py 2011-08-03 18:17:08 UTC (rev 4904)
@@ -415,7 +415,7 @@
return False
return True
- def edit_tags(self, node, tags):
+ def edit_tags(self, node, *tags):
'''
Replace existing tags on a node with the specified tags.
12 years, 9 months